Skip to content

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.


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
  const registry = Verity.createRegistry({
    sse: { url: '/api/events', audience: 'global' }
  })

  // Register types
  registry.registerCollection('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

npm install verity-dl

Setup

// App.jsx
import { createRegistry } from 'verity-dl/core'
import { VerityProvider } from 'verity-dl/adapters/react'

// Create registry
const registry = createRegistry({
  sse: { url: '/api/events', audience: 'global' }
})

// Register types
registry.registerCollection('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.

function useVerityCollection(
  name: string,
  params?: any
): CollectionHandle

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.

function useVerityItem(
  name: string,
  params: any
): ItemHandle

Returns:

interface ItemHandle {
  state: {
    loading: boolean
    error: Error | null
    value: any | null
  }
  refresh: () => Promise<void>
}

useVerityRegistry()

Access the registry directly.

function useVerityRegistry(): Registry

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) {
      await registry.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

npm install verity-dl

Setup

// main.js
import { createApp } from 'vue'
import { createRegistry } from 'verity-dl/core'
import { createVerityVuePlugin } from 'verity-dl/adapters/vue'
import App from './App.vue'

// Create registry
const registry = createRegistry({
  sse: { url: '/api/events', audience: 'global' }
})

// Register types
registry.registerCollection('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.

function useVerityItem(
  name: string,
  params: Ref<any> | any
): ComputedRef<ItemHandle>

useVerityRegistry()

Access the registry.

function useVerityRegistry(): 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) {
    await registry.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

npm install verity-dl

Setup

// stores.js
import { createRegistry } from 'verity-dl/core'

export const registry = createRegistry({
  sse: { url: '/api/events', audience: 'global' }
})

// Register types
registry.registerCollection('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.

function collectionStore(
  name: string,
  params?: any
): Readable<CollectionHandle>

itemStore(name, params)

Creates a Svelte store for an item.

function itemStore(
  name: string,
  params: any
): Readable<ItemHandle>

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) {
    await registry.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': registry.clientId
    },
    body: JSON.stringify(data)
  })

  const payload = await res.json()

  if (payload.directives) {
    await registry.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} />}
    </>
  )
}

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:

  1. Adapters manage truth-state access
  2. Components manage view-state
  3. Never duplicate truth-state in local state
  4. Apply directives after mutations
  5. Use framework-idiomatic patterns

Next Steps