🔒 VhyxSeal
GitHub

Vue Adapter

@vhyxseal/vue provides composables, headless components, and a Vue plugin for integrating VhyxSeal into Vue 3 applications. The adapter mirrors the React adapter API — developers familiar with the React adapter will find the Vue API immediately familiar.

What the Adapter Provides

  • VhyxSealPlugin — Vue plugin (app.use) that installs the contract context
  • useSeal() — access manifest and contracts map from any component
  • useContract(id) — read a registered contract by component id
  • useCapability() — read the full capability map with counts
  • useAgentAction() — track agent action lifecycle (standalone, no plugin needed)
  • Button, Input, Form, Nav, Display, Confirmation — headless components with contract registration

Installation

npm install @vhyxseal/vue @vhyxseal/core

Vue ≥ 3.3.0 required.

Full Setup

main.ts
// main.ts — install the plugin
import { createApp } from 'vue'
import { VhyxSealPlugin } from '@vhyxseal/vue'
import App from './App.vue'

const app = createApp(App)

app.use(VhyxSealPlugin, {
  config: {
    domain: "example.com",
    domainVerified: false,
    verificationToken: "",
    agentPolicy: {
      allowedAgents: ["*"],
      requiresConfirmation: ["place-order", "delete-account"],
    },
    cacheDurationSeconds: 3600,
  },
  dev: process.env.NODE_ENV !== "production",
})

app.mount('#app')

VhyxSealPlugin Configuration

OptionTypeDescription
configManifestConfigRequired. Same shape as React SealProvider config
devbooleanOptional. Enable verbose logging and DevTools

Composables Usage

<script setup lang="ts">
import { useSeal, useContract, useCapability, useAgentAction } from '@vhyxseal/vue'

// useSeal — access the plugin context (must be inside a component setup)
const { manifest, contracts } = useSeal()

// useContract — read a specific contract by id
const checkoutContract = useContract('checkout-submit-btn')

// useCapability — read the full capability map
const capability = useCapability()
console.log(capability.counts) // { total, full, inferred }

// useAgentAction — track agent action lifecycle (standalone, no plugin needed)
const { record, initiate, complete, cancel } = useAgentAction()
</script>

useSeal() outside plugin context

Important: useSeal() throws a VhyxSealError (not a plain Error) when called outside a component setup() or when called without the plugin installed. This is the correct behavior — it means the component is missing its contract infrastructure. Catch VhyxSealError specifically in error boundaries.

// useSeal() throws VhyxSealError when called outside the plugin
// This is the correct behavior — document this in your app's error handling

import { useSeal } from '@vhyxseal/vue'
import { VhyxSealError } from '@vhyxseal/core'

// Inside a component setup() — correct
const { manifest } = useSeal() // works

// Outside a component setup() — throws
try {
  const result = useSeal() // throws VhyxSealError
} catch (e) {
  if (e instanceof VhyxSealError) {
    // code: VHYX_CONTRACT_VALIDATION_FAILED
    // message: "useSeal() must be called inside a Vue component setup()"
  }
}

Composables Reference

// All composables are re-exported from @vhyxseal/vue
import {
  useSeal,        // Access plugin context — manifest, contracts map
  useContract,    // useContract(id: string) — get contract by component id
  useCapability,  // useCapability() — full capability map with counts
  useAgentAction, // useAgentAction() — track action lifecycle (standalone)
} from '@vhyxseal/vue'

Component Usage

<script setup lang="ts">
import { Button, Input, Form, Nav, Display, Confirmation } from '@vhyxseal/vue'
import { defineContract } from '@vhyxseal/core'

const searchContract = defineContract({
  id: "search-input",
  type: "input",
  intent: "search",
  description: "Search site content",
  requires: [], requiredPermissions: [],
  consequence: "Filters content to match query",
  affects: ["search-results"],
  reversible: true, safetyLevel: "low",
  requiresConfirmation: false, destructive: false,
  contractVersion: "1.0.0",
})
</script>

<template>
  <!-- Components accept a :contract prop -->
  <Input :contract="searchContract" placeholder="Search..." />
  <Button :contract="submitContract" type="submit">Search</Button>

  <!-- Confirmation uses a scoped slot -->
  <Confirmation :contract="deleteContract">
    <template #default="{ confirmed, isPending, confirm, cancel }">
      <dialog v-if="isPending" open>
        <p>Delete this item permanently?</p>
        <button @click="confirm">Delete</button>
        <button @click="cancel">Cancel</button>
      </dialog>
      <Button :contract="deleteContract" @click="isPending ? undefined : requestDelete()">
        Delete
      </Button>
    </template>
  </Confirmation>
</template>

TypeScript Types

// TypeScript types from @vhyxseal/vue
import type {
  VhyxSealPluginOptions,  // Options for app.use(VhyxSealPlugin, options)
  SealContextValue,       // Value injected by the plugin (useSeal() return type)
} from '@vhyxseal/vue'

// Core types are re-exported from @vhyxseal/core directly
import type {
  ComponentContract,
  ManifestConfig,
  VhyxSealManifest,
} from '@vhyxseal/core'

Common Patterns

Form with validation and confirmation

<!-- Example 1 — Form with validation and confirmation -->
<script setup lang="ts">
import { Form, Input, Button, Confirmation } from '@vhyxseal/vue'
import { defineContract, defineRelationship } from '@vhyxseal/core'

const emailContract = defineContract({
  id: "newsletter-email-input", type: "input", intent: "collect-email",
  description: "Email address for newsletter subscription",
  requires: [], requiredPermissions: [],
  consequence: "Stores email for newsletter",
  affects: ["newsletter-subscribers"],
  reversible: true, safetyLevel: "low",
  requiresConfirmation: false, destructive: false,
  contractVersion: "1.0.0",
})

const subscribeContract = defineContract({
  id: "newsletter-subscribe-btn", type: "action", intent: "submit-form",
  description: "Subscribe to the newsletter",
  requires: [{ field: "email.isValid", operator: "===", value: true, description: "Email must be valid" }],
  requiredPermissions: [],
  consequence: "Adds email to newsletter list",
  affects: ["newsletter-subscribers"],
  reversible: false, safetyLevel: "low",
  requiresConfirmation: false, destructive: false,
  contractVersion: "1.0.0",
})
</script>

<template>
  <Form :contract="newsletterFormContract">
    <Input :contract="emailContract" type="email" v-model="email" />
    <Button :contract="subscribeContract" type="submit" :disabled="!emailValid">
      Subscribe
    </Button>
  </Form>
</template>

Navigation with active state

<!-- Example 2 — Navigation with active state -->
<script setup lang="ts">
import { Nav } from '@vhyxseal/vue'
import { defineContract } from '@vhyxseal/core'
import { useRoute } from 'vue-router'

const route = useRoute()

const navContract = defineContract({
  id: "main-navigation",
  type: "navigation",
  intent: "navigate",
  description: "Main site navigation",
  requires: [], requiredPermissions: [],
  consequence: "Navigates to selected section",
  affects: ["current-page"],
  reversible: true, safetyLevel: "low",
  requiresConfirmation: false, destructive: false,
  contractVersion: "1.0.0",
})
</script>

<template>
  <Nav :contract="navContract" aria-label="Main navigation">
    <a v-for="link in navLinks" :key="link.href"
       :href="link.href"
       :aria-current="route.path === link.href ? 'page' : undefined">
      {{ link.label }}
    </a>
  </Nav>
</template>

Known Limitations

  • useSeal() throws VhyxSealError (not raw Error) when called outside plugin — catch specifically
  • Vue components are implemented as TypeScript files using defineComponent and h() render functions — not .vue SFC files. The API is identical from the consumer perspective
  • Vue devtools integration (separate from @vhyxseal/devtools) is planned for a future release
  • useAgentAction() is standalone — it does not require VhyxSealPlugin to be installed