Reference: Framework Adapters¶
Complete API reference for Verity's framework integrations
Verity adapters are thin bridges between the core registry (which manages truth-state) and your UI framework (which manages view-state and rendering). They expose framework-idiomatic APIs while preserving Verity's philosophy.
The Boundary
Adapters sit at the truth-state ↔ view-state boundary. They provide reactive access to truth-state without leaking data-layer concerns into your components.
Philosophy: Two Lanes of State¶
Before diving into adapter APIs, remember the separation:
graph LR
Server[Server<br/>Truth-State Source] -->|directives| Verity[Verity Registry<br/>Truth-State Manager]
Verity -->|reactive data| Adapter[Framework Adapter<br/>Boundary Layer]
Adapter -->|props/hooks/magic| Component[UI Components<br/>View-State Manager]
Component -->|render| DOM[DOM]
style Server fill:#e1f5ff
style Verity fill:#fff4e1
style Adapter fill:#f0e1ff
style Component fill:#e1ffe1
Truth-State (Verity's domain): - Todos, users, orders, permissions - Fetched from server - Invalidated by directives - Cached and coalesced
View-State (Component's domain): - Which menu is open - Which tab is selected - Current filter UI state - Form input values (before submission)
Adapters expose truth-state. Components manage view-state.
Reactive Reads and Retries¶
Alpine getters and Vue computed or render functions may safely call it() and col(). When a fetch fails, Verity keeps the error state settled instead of starting another request on every reactive read.
Use { force: true } for explicit refreshes, such as a Retry button. Alpine and Vue consume a forced reactive read once so completion-driven rerenders do not create another request.
Alpine.js¶
Browser-native adapter for Alpine.js. Perfect for HTML-first applications.
Installation¶
<!-- 1. Core -->
<script src="https://cdn.jsdelivr.net/npm/verity-dl@latest/verity/shared/static/lib/core.min.js"></script>
<!-- 2. Alpine -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>
<!-- 3. Verity Alpine Adapter -->
<script src="https://cdn.jsdelivr.net/npm/verity-dl@latest/verity/shared/static/adapters/alpine.min.js"></script>
<script>
// Create registry
DL.init({
sse: { url: '/api/events', audience: 'global' }
})
// Register types
DL.createCollection('todos', {
fetch: async (params) => {
const url = new URL('/api/todos', window.location.origin)
if (params.status) url.searchParams.set('status', params.status)
const res = await fetch(url)
const data = await res.json()
return { items: data.todos }
}
})
// Install adapter
VerityAlpine.install(registry)
</script>
API¶
VerityAlpine.install(registry, options?)¶
Installs the Verity magic properties into Alpine.
VerityAlpine.install(registry, {
// Optional: custom registry name (default: '$verity')
magicName: '$verity'
})
Provides:
- $verity.item(name, params) - Get item handle
- $verity.collection(name, params) - Get collection handle
- $verity.applyDirectives(directives) - Apply directives
Usage¶
Basic Collection¶
<div x-data="{ todos: $verity.collection('todos', { status: 'active' }) }">
<!-- Loading state -->
<template x-if="todos.state.loading">
<div>Loading todos...</div>
</template>
<!-- Error state -->
<template x-if="todos.state.error">
<div x-text="'Error: ' + todos.state.error.message"></div>
</template>
<!-- Success state -->
<template x-if="!todos.state.loading && !todos.state.error">
<ul>
<template x-for="todo in todos.state.items" :key="todo.id">
<li>
<span x-text="todo.title"></span>
<button @click="toggleTodo(todo)">Toggle</button>
</li>
</template>
</ul>
</template>
</div>
With View-State¶
<div x-data="{
// Truth-state (from Verity)
todos: $verity.collection('todos', { status: 'active' }),
// View-state (local to component)
selectedFilter: 'active',
isMenuOpen: false,
// Actions
changeFilter(status) {
this.selectedFilter = status
this.todos.setParams({ status }) // Update Verity params
},
async toggleTodo(todo) {
// Mutation
const res = await fetch(`/api/todos/${todo.id}/toggle`, { method: 'PUT' })
const data = await res.json()
// Apply directives
if (data.directives) {
await $verity.applyDirectives(data.directives)
}
}
}">
<!-- Filter tabs (view-state) -->
<div>
<button @click="changeFilter('active')" :class="{ 'active': selectedFilter === 'active' }">
Active
</button>
<button @click="changeFilter('completed')" :class="{ 'active': selectedFilter === 'completed' }">
Completed
</button>
</div>
<!-- Todo list (truth-state) -->
<ul>
<template x-for="todo in todos.state.items" :key="todo.id">
<li>
<span x-text="todo.title"></span>
<button @click="toggleTodo(todo)">Toggle</button>
</li>
</template>
</ul>
</div>
Manual Refresh¶
<div x-data="{ todos: $verity.collection('todos', {}) }">
<button @click="todos.refresh()">Refresh</button>
<ul>
<template x-for="todo in todos.state.items" :key="todo.id">
<li x-text="todo.title"></li>
</template>
</ul>
</div>
React¶
Modern React adapter with hooks and context.
Installation¶
Setup¶
// App.jsx
import { init, createType, createCollection } from 'verity-dl/core'
import { VerityProvider } from 'verity-dl/adapters/react'
// Create registry
DL.init({
sse: { url: '/api/events', audience: 'global' }
})
// Register types
DL.createCollection('todos', {
fetch: async (params) => {
const url = new URL('/api/todos', window.location.origin)
if (params.status) url.searchParams.set('status', params.status)
const res = await fetch(url)
const data = await res.json()
return { items: data.todos }
}
})
function App() {
return (
<VerityProvider registry={registry}>
<TodoList />
</VerityProvider>
)
}
API¶
useVerityCollection(name, params?)¶
Hook that returns a reactive collection handle.
Returns:
interface CollectionHandle {
state: {
loading: boolean
error: Error | null
items: any[]
meta: any | null
}
refresh: () => Promise<void>
setParams: (params: any) => void
}
useVerityItem(name, params)¶
Hook that returns a reactive item handle.
Returns:
interface ItemHandle {
state: {
loading: boolean
error: Error | null
value: any | null
}
refresh: () => Promise<void>
}
useVerityRegistry()¶
Access the registry directly.
Usage¶
Basic Collection¶
import { useVerityCollection } from 'verity-dl/adapters/react'
function TodoList() {
const { state, refresh } = useVerityCollection('todos', { status: 'active' })
if (state.loading) return <div>Loading...</div>
if (state.error) return <div>Error: {state.error.message}</div>
return (
<div>
<button onClick={refresh}>Refresh</button>
<ul>
{state.items.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</div>
)
}
With View-State¶
import { useState } from 'react'
import { useVerityCollection, useVerityRegistry } from 'verity-dl/adapters/react'
function TodoList() {
// Truth-state (from Verity)
const { state, setParams } = useVerityCollection('todos', { status: 'active' })
const registry = useVerityRegistry()
// View-state (local to component)
const [selectedFilter, setSelectedFilter] = useState('active')
const [isMenuOpen, setIsMenuOpen] = useState(false)
// Actions
const changeFilter = (status) => {
setSelectedFilter(status) // Update view-state
setParams({ status }) // Update truth-state params
}
const toggleTodo = async (todo) => {
const res = await fetch(`/api/todos/${todo.id}/toggle`, { method: 'PUT' })
const data = await res.json()
if (data.directives) {
DL.applyDirectives(data.directives)
}
}
return (
<div>
{/* Filter tabs (view-state) */}
<div>
<button
onClick={() => changeFilter('active')}
className={selectedFilter === 'active' ? 'active' : ''}
>
Active
</button>
<button
onClick={() => changeFilter('completed')}
className={selectedFilter === 'completed' ? 'active' : ''}
>
Completed
</button>
</div>
{/* Todo list (truth-state) */}
{state.loading && <div>Loading...</div>}
{state.error && <div>Error: {state.error.message}</div>}
{!state.loading && !state.error && (
<ul>
{state.items.map(todo => (
<li key={todo.id}>
<span>{todo.title}</span>
<button onClick={() => toggleTodo(todo)}>Toggle</button>
</li>
))}
</ul>
)}
</div>
)
}
Item Hook¶
import { useVerityItem } from 'verity-dl/adapters/react'
function UserProfile({ userId }) {
const { state, refresh } = useVerityItem('user', { userId })
if (state.loading) return <div>Loading user...</div>
if (state.error) return <div>Error: {state.error.message}</div>
return (
<div>
<h1>{state.value.name}</h1>
<p>{state.value.email}</p>
<button onClick={refresh}>Refresh</button>
</div>
)
}
Vue 3¶
Composition API adapter for Vue 3.
Installation¶
Setup¶
// main.js
import { createApp } from 'vue'
import { init, createType, createCollection } from 'verity-dl/core'
import { createVerityVuePlugin } from 'verity-dl/adapters/vue'
import App from './App.vue'
// Create registry
DL.init({
sse: { url: '/api/events', audience: 'global' }
})
// Register types
DL.createCollection('todos', {
fetch: async (params) => {
const url = new URL('/api/todos', window.location.origin)
if (params.status) url.searchParams.set('status', params.status)
const res = await fetch(url)
const data = await res.json()
return { items: data.todos }
}
})
// Create and install plugin
const app = createApp(App)
app.use(createVerityVuePlugin(registry))
app.mount('#app')
API¶
useVerityCollection(name, params?)¶
Composition API hook for collections.
function useVerityCollection(
name: string,
params?: Ref<any> | any
): ComputedRef<CollectionHandle>
Note: params can be a reactive ref or plain object.
useVerityItem(name, params)¶
Composition API hook for items.
useVerityRegistry()¶
Access the registry.
Usage¶
Basic Collection¶
<script setup>
import { useVerityCollection } from 'verity-dl/adapters/vue'
const todos = useVerityCollection('todos', { status: 'active' })
</script>
<template>
<div>
<div v-if="todos.state.loading">Loading...</div>
<div v-else-if="todos.state.error">Error: {{ todos.state.error.message }}</div>
<ul v-else>
<li v-for="todo in todos.state.items" :key="todo.id">
{{ todo.title }}
</li>
</ul>
<button @click="todos.refresh()">Refresh</button>
</div>
</template>
With View-State¶
<script setup>
import { ref } from 'vue'
import { useVerityCollection, useVerityRegistry } from 'verity-dl/adapters/vue'
// Truth-state (from Verity)
const todos = useVerityCollection('todos', { status: 'active' })
const registry = useVerityRegistry()
// View-state (local to component)
const selectedFilter = ref('active')
const isMenuOpen = ref(false)
// Actions
const changeFilter = (status) => {
selectedFilter.value = status
todos.setParams({ status })
}
const toggleTodo = async (todo) => {
const res = await fetch(`/api/todos/${todo.id}/toggle`, { method: 'PUT' })
const data = await res.json()
if (data.directives) {
DL.applyDirectives(data.directives)
}
}
</script>
<template>
<div>
<!-- Filter tabs (view-state) -->
<div>
<button
@click="changeFilter('active')"
:class="{ active: selectedFilter === 'active' }"
>
Active
</button>
<button
@click="changeFilter('completed')"
:class="{ active: selectedFilter === 'completed' }"
>
Completed
</button>
</div>
<!-- Todo list (truth-state) -->
<div v-if="todos.state.loading">Loading...</div>
<div v-else-if="todos.state.error">Error: {{ todos.state.error.message }}</div>
<ul v-else>
<li v-for="todo in todos.state.items" :key="todo.id">
<span>{{ todo.title }}</span>
<button @click="toggleTodo(todo)">Toggle</button>
</li>
</ul>
</div>
</template>
Reactive Params¶
<script setup>
import { ref } from 'vue'
import { useVerityCollection } from 'verity-dl/adapters/vue'
// Reactive params
const statusFilter = ref('active')
// Collection automatically updates when params change
const todos = useVerityCollection('todos', { status: statusFilter })
</script>
<template>
<div>
<select v-model="statusFilter">
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
<ul>
<li v-for="todo in todos.state.items" :key="todo.id">
{{ todo.title }}
</li>
</ul>
</div>
</template>
Svelte¶
Store-based adapter for Svelte.
Installation¶
Setup¶
// stores.js
import { init, createType, createCollection } from 'verity-dl/core'
export DL.init({
sse: { url: '/api/events', audience: 'global' }
})
// Register types
DL.createCollection('todos', {
fetch: async (params) => {
const url = new URL('/api/todos', window.location.origin)
if (params.status) url.searchParams.set('status', params.status)
const res = await fetch(url)
const data = await res.json()
return { items: data.todos }
}
})
API¶
collectionStore(name, params?)¶
Creates a Svelte store for a collection.
itemStore(name, params)¶
Creates a Svelte store for an item.
Usage¶
Basic Collection¶
<script>
import { collectionStore } from 'verity-dl/adapters/svelte'
const todos = collectionStore('todos', { status: 'active' })
</script>
{#if $todos.state.loading}
<div>Loading...</div>
{:else if $todos.state.error}
<div>Error: {$todos.state.error.message}</div>
{:else}
<ul>
{#each $todos.state.items as todo (todo.id)}
<li>{todo.title}</li>
{/each}
</ul>
{/if}
<button on:click={() => $todos.refresh()}>Refresh</button>
With View-State¶
<script>
import { writable } from 'svelte/store'
import { collectionStore } from 'verity-dl/adapters/svelte'
import { registry } from './stores'
// Truth-state (from Verity)
const todos = collectionStore('todos', { status: 'active' })
// View-state (local to component)
const selectedFilter = writable('active')
const isMenuOpen = writable(false)
// Actions
function changeFilter(status) {
selectedFilter.set(status)
$todos.setParams({ status })
}
async function toggleTodo(todo) {
const res = await fetch(`/api/todos/${todo.id}/toggle`, { method: 'PUT' })
const data = await res.json()
if (data.directives) {
DL.applyDirectives(data.directives)
}
}
</script>
<!-- Filter tabs (view-state) -->
<div>
<button
on:click={() => changeFilter('active')}
class:active={$selectedFilter === 'active'}
>
Active
</button>
<button
on:click={() => changeFilter('completed')}
class:active={$selectedFilter === 'completed'}
>
Completed
</button>
</div>
<!-- Todo list (truth-state) -->
{#if $todos.state.loading}
<div>Loading...</div>
{:else if $todos.state.error}
<div>Error: {$todos.state.error.message}</div>
{:else}
<ul>
{#each $todos.state.items as todo (todo.id)}
<li>
<span>{todo.title}</span>
<button on:click={() => toggleTodo(todo)}>Toggle</button>
</li>
{/each}
</ul>
{/if}
Common Patterns¶
Pattern: Separating Truth-State and View-State¶
Good: Clear Separation
// React example
function TodoList() {
// Truth-state: managed by Verity
const { state } = useVerityCollection('todos', { status: 'active' })
// View-state: managed by component
const [selectedId, setSelectedId] = useState(null)
const [isDialogOpen, setIsDialogOpen] = useState(false)
return (
<div>
<ul>
{state.items.map(todo => (
<li
key={todo.id}
onClick={() => setSelectedId(todo.id)}
className={selectedId === todo.id ? 'selected' : ''}
>
{todo.title}
</li>
))}
</ul>
</div>
)
}
Bad: Mixed Concerns
// ❌ DON'T: Storing truth-state in local state
function TodoList() {
const { state } = useVerityCollection('todos', {})
const [todos, setTodos] = useState([]) // ❌ Duplicate truth-state
useEffect(() => {
setTodos(state.items) // ❌ Copying server data to local state
}, [state.items])
// Now you have two sources of truth!
}
Pattern: Mutations with Directive Application¶
// React
const registry = useVerityRegistry()
const updateTodo = async (id, data) => {
const res = await fetch(`/api/todos/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Verity-Client-ID': DL.clientId()
},
body: JSON.stringify(data)
})
const payload = await res.json()
if (payload.directives) {
DL.applyDirectives(payload.directives)
}
return payload
}
Pattern: Conditional Rendering Based on State¶
// React
function DataView() {
const { state } = useVerityCollection('items', {})
return (
<>
{state.loading && <Skeleton />}
{state.error && <ErrorMessage error={state.error} />}
{!state.loading && !state.error && <ItemList items={state.items} />}
</>
)
}
Pattern: Reactive Reads and Explicit Retry¶
Item and collection reads are safe inside Alpine getters, Vue computed values,
and framework render functions. Each adapter subscribes to the specific ref it
returns, so an update to one entity does not invalidate readers of another.
The global state() surface remains globally reactive by design.
When a fetch fails, the ref settles with meta.isLoading === false and the
error in meta.error. Re-reading the ref does not start another request. Retry
explicitly after a user action:
// Alpine
$store.lib.it('user', userId, null, { force: true })
// Vue
dl.it('user', userId, null, { force: true })
Server-authored refresh directives also bypass the settled error and retry the request. This prevents render loops while keeping retry decisions intentional.
Summary¶
Adapters Available:
| Framework | Hook/API | Install Method |
|---|---|---|
| Alpine.js | $verity magic |
VerityAlpine.install() |
| React | useVerityCollection() |
<VerityProvider> |
| Vue 3 | useVerityCollection() |
app.use(plugin) |
| Svelte | collectionStore() |
Direct import |
Common APIs:
| Method | Purpose |
|---|---|
collection(name, params) |
Get collection handle |
item(name, params) |
Get item handle |
state.loading |
Loading indicator |
state.error |
Error object |
state.items |
Collection data |
state.value |
Item data |
refresh() |
Manual refetch |
setParams() |
Update params |
Key Principles:
- Adapters manage truth-state access
- Components manage view-state
- Never duplicate truth-state in local state
- Apply directives after mutations
- Use framework-idiomatic patterns
Next Steps¶
- Core API: Registry reference
- Directives: Directive contract
- Devtools: Debugging with devtools
- Examples: Example applications