npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@myexamsai/conjure

v3.0.1

Published

Conjure — Type-safe server-side document builder for @myexamsai/revelio. Define components with Zod schemas, compose them into SduiLayoutDocuments with a fluent API, and push real-time patches — all fully typed.

Readme

@myexamsai/conjure

Type-safe server-side document builder for @myexamsai/revelio.

Conjure is the server half of the Revelio Server-Driven UI system. You use it to describe UI screens as structured data — called Blueprints — that are serialised to JSON and consumed by the Revelio client renderer. The key idea is that your server decides what the screen looks like; the client renders whatever Blueprint it receives without containing any screen-specific logic.


Table of Contents

  1. Installation
  2. Core Concepts
  3. Quick Start
  4. API Reference
  5. TypeScript Types
  6. Error Classes
  7. Patterns & Recipes

Installation

npm install @myexamsai/conjure zod

Zod is a peer dependency. Conjure requires Zod v4.


Core Concepts

Blueprint

A Blueprint is a complete description of a screen — a JSON-serialisable tree of nodes with optional metadata, variables, and a design-token palette. Blueprints are produced by draft() and sent from your API to the Revelio client.

Blueprint
├── metadata    (kind, sequenceNumber, id, name, …)
├── palette     (colors, spacing, typography, borderRadius)
├── variables   (global key-value pairs)
└── root        (BlueprintNode — the top of the UI tree)
      └── children[]
            └── …

BlueprintNode

Every element in the UI tree is a BlueprintNode. A node has:

  • id — unique within the Blueprint, used by Revelio to track identity across updates
  • type — maps to a registered component in Revelio's componentMap
  • state — the component's props; validated against its Zod schema
  • attributes — style hints and analytics declarations, passed through opaquely
  • children — nested nodes (for container components)
  • reference — IDs of other nodes whose state this node subscribes to

Structural vs Update Blueprints

draft() produces a structural Blueprint (metadata.kind = 'structural'). This is a full layout description that Revelio uses to replace or initialise a screen.

revision() produces an update Blueprint (metadata.kind = 'update'). It contains only the nodes that have changed. Revelio merges these surgically into the existing store without touching anything else.

Both functions auto-stamp a monotonically increasing sequenceNumber. Revelio uses this to discard out-of-order updates.

Shared Schemas

The Zod schema passed to define() is the single source of truth for a component's state. You share the same descriptor object between server and client — the server uses .node() to build typed nodes, and the client passes .schema to useSduiNodeSubscription for end-to-end type safety.


Quick Start

// shared/components.ts  ← imported by BOTH server and client
import { define } from '@myexamsai/conjure'
import { z } from 'zod'

export const Card = define('Card', z.object({
  title: z.string(),
  subtitle: z.string().optional(),
}))

export const Button = define('Button', z.object({
  label: z.string(),
  variant: z.enum(['primary', 'secondary', 'ghost', 'danger']),
  action: z.record(z.unknown()).optional(),
}))

export const Stat = define('Stat', z.object({
  label: z.string(),
  value: z.string(),
  delta: z.string().optional(),
}))
// server/routes/dashboard.ts
import { draft, revision, actions, blocks, beacon, asset, createPalette } from '@myexamsai/conjure'
import { Card, Button, Stat } from '../shared/components'

// Build a full screen
app.get('/api/sdui/dashboard', (req, res) => {
  res.json(
    draft({
      metadata: { id: 'dashboard', name: 'Dashboard' },
      palette: createPalette({ colors: { primary: '#6200EE' } }),
      variables: { userId: req.user.id },
      root: Card.node('root', { title: `Welcome, ${req.user.name}` }, {
        children: [
          blocks.row('stats', ['stat-revenue', 'stat-orders'], { gap: 12 }),
          Stat.node('stat-revenue', { label: 'Revenue', value: '$48k', delta: '+12%' }),
          Stat.node('stat-orders', { label: 'Orders', value: '142' }),
          Button.node('cta', {
            label: 'View all orders',
            variant: 'primary',
            action: actions.navigate('orders'),
          }, {
            attributes: {
              analytics: [beacon.onClick('cta_tapped', { screen: 'dashboard' })],
            },
          }),
          blocks.image('hero', asset.url('https://cdn.example.com/banner.png'), {
            width: '100%',
          }),
        ],
      }),
    })
  )
})

// Push a real-time update over SSE
function pushRevenueUpdate(res: Response, newValue: string) {
  const payload = JSON.stringify(
    revision({
      nodes: [
        Stat.patch('stat-revenue', { value: newValue }),
      ],
    })
  )
  res.write(`data: ${payload}\n\n`)
}

API Reference


define()

function define<TShape, TSchema>(
  type: string,
  schema: ZodObject<TShape>
): ComponentDescriptor<TShape, TSchema>

Defines a component by pairing a type name with a Zod schema. Returns a ComponentDescriptor that exposes .node() and .patch() factory methods.

Calling define() also registers the descriptor in an internal type registry. This registry is used by revision() to validate node state automatically — you do not need to pass a component map to revision().

Parameters

| Parameter | Type | Description | |-----------|------|-------------| | type | string | Component type name. Must match the key used in Revelio's componentMap. Case-sensitive. | | schema | ZodObject | Zod object schema describing the component's state shape. |

ReturnsComponentDescriptor with the following members:

| Member | Description | |--------|-------------| | .type | The type name string (read-only). | | .schema | The Zod schema (read-only). Share this with the client for useSduiNodeSubscription. | | .node(id, state, opts?) | Creates a fully typed BlueprintNode. TypeScript enforces that state matches the schema. | | .patch(id, partialState) | Creates a minimal node carrying only the changed fields, for use with revision(). |

ComponentDescriptor.node(id, state, opts?)

Card.node('hero', { title: 'Hello' })
Card.node('hero', { title: 'Hello' }, {
  attributes: { style: { padding: 16 } },
  children: [Button.node('cta', { label: 'Go', variant: 'primary' })],
  reference: 'some-other-node-id',
})

| Option | Type | Description | |--------|------|-------------| | attributes | Record<string, unknown> | Style hints, analytics declarations, or any opaque data passed to the component. | | children | BlueprintNode[] | Child nodes nested under this node. | | reference | string \| string[] | Node IDs this node subscribes to for state sharing. |

ComponentDescriptor.patch(id, partialState)

Stat.patch('revenue', { value: '$52k' })
// → { id: 'revenue', type: 'Stat', state: { value: '$52k' } }

Produces a node carrying only the fields you pass. Use this as input to revision(). Only the provided state fields are merged on the client; unprovided fields are left unchanged.

Example

import { define } from '@myexamsai/conjure'
import { z } from 'zod'

export const ProductCard = define('ProductCard', z.object({
  name: z.string(),
  price: z.number(),
  imageId: z.string().optional(),
  badge: z.object({
    label: z.string(),
    variant: z.enum(['sale', 'new', 'sold-out']),
  }).optional(),
}))

// TypeScript enforces state shape — price: 'free' would be a compile error
ProductCard.node('product-1', { name: 'Widget', price: 9.99 })

draft()

function draft(options: BuildDocumentOptions): Blueprint

Assembles a complete structural Blueprint ready to send to the Revelio client. Automatically stamps metadata.kind = 'structural' and an auto-incrementing metadata.sequenceNumber on every call.

BuildDocumentOptions

| Field | Type | Required | Description | |-------|------|----------|-------------| | root | BlueprintNode | ✓ | The root node of the UI tree. | | metadata | DraftMetadataInput | — | Document identity and display metadata. kind and sequenceNumber are excluded — they are stamped automatically. | | variables | Record<string, unknown> | — | Global key-value pairs accessible to all nodes via the Revelio store. | | version | string | — | Schema version string. Defaults to '1.0.0'. Increment on breaking layout changes so Revelio can detect genuine document replacements. | | palette | ConjurePalette | — | Design-token palette. See createPalette(). |

DraftMetadataInput — all fields optional:

| Field | Type | Description | |-------|------|-------------| | id | string | Stable document identity. Revelio uses this to detect when a genuinely different screen has been loaded. | | name | string | Human-readable screen name. | | description | string | Optional description. | | createdAt | string | ISO-8601 timestamp. | | updatedAt | string | ISO-8601 timestamp. | | author | string | Originating service or author. | | generatedAt | string | ISO-8601 generation timestamp. | | pageKey | string | Page / route key this Blueprint was generated for. |

Example

import { draft, createPalette } from '@myexamsai/conjure'
import { Screen, Card } from './components'

app.get('/api/sdui/profile/:userId', async (req, res) => {
  const user = await db.users.find(req.params.userId)

  res.json(
    draft({
      version: '2.0.0',
      metadata: {
        id: `profile:${user.id}`,
        name: 'User Profile',
        pageKey: 'profile',
      },
      variables: {
        userId: user.id,
        locale: req.headers['accept-language'] ?? 'en',
      },
      palette: createPalette({
        colors: { primary: user.brandColor },
      }),
      root: Screen.node('root', { title: user.displayName }, {
        children: [
          Card.node('bio', { content: user.bio }),
        ],
      }),
    })
  )
})

revision()

function revision(input: RevisionInput): Blueprint

Builds a minimal update Blueprint containing only the nodes that have changed. Revelio merges these nodes into the live store without re-rendering the full screen.

Unlike draft(), a revision does not carry a full node tree, palette, or variables. It carries only the changed nodes, wrapped in a synthetic container (__revision__).

revision() validates each node's state against the Zod schema registered by define(). It throws RevisionValidationError on the first failure.

RevisionInput

| Field | Type | Required | Description | |-------|------|----------|-------------| | nodes | BlueprintNode[] | ✓ | Nodes with updated state. Produce them via ComponentDescriptor.patch(). | | sequenceNumber | number | — | Explicit sequence number. Omit to use the auto-incrementing shared counter. |

ThrowsRevisionValidationError with .nodeId, .path, and .message if any node's state is invalid.

Example

import { revision } from '@myexamsai/conjure'
import { Stat, Card } from './components'

// Single node — write to an active SSE response
res.write(`data: ${JSON.stringify(
  revision({
    nodes: [Stat.patch('revenue', { value: '$52k', delta: '+8%' })],
  })
)}\n\n`)

// Multiple nodes at once
res.write(`data: ${JSON.stringify(
  revision({
    nodes: [
      Stat.patch('revenue', { value: '$52k' }),
      Stat.patch('orders', { value: '148' }),
      Card.patch('header', { subtitle: `Updated ${new Date().toLocaleTimeString()}` }),
    ],
  })
)}\n\n`)

// With explicit sequence number (e.g. from an event stream)
revision({ nodes: [...], sequenceNumber: event.seq })

Validation behaviour

revision() only validates nodes whose type was registered via define(). Nodes with unrecognised types (e.g. block:* nodes) are passed through without validation. This matches the full-document behaviour of assertDocument().


blocks

import { blocks } from '@myexamsai/conjure'

Built-in layout and content primitives. Nodes in the blocks namespace use a block: type prefix and are rendered natively by Revelio — no componentMap registration is required.

All five builders accept an optional BlockStyle object as their last argument.


blocks.stack(id, childrenIds, style?)

A generic stacking container. Children are stacked in the platform's default direction.

blocks.stack(id: string, childrenIds: string[], style?: BlockStyle): BlueprintNode
// → { id, type: 'block:stack', state: { childrenIds }, attributes: { style } }
blocks.stack('feed', ['post-1', 'post-2', 'post-3'], { gap: 8 })

blocks.row(id, childrenIds, style?)

A horizontal container. Children are laid out left-to-right.

blocks.row(id: string, childrenIds: string[], style?: BlockStyle): BlueprintNode
// → { id, type: 'block:row', state: { childrenIds }, attributes: { style } }
blocks.row('toolbar', ['back-btn', 'title', 'menu-btn'], {
  justify: 'between',
  align: 'center',
  padding: 12,
})

blocks.column(id, childrenIds, style?)

A vertical container. Children are laid out top-to-bottom.

blocks.column(id: stri