@vielzeug/codex 2.2.8 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  {
2
- "apiSource": "export type { ComponentDefinition } from './component-types';\nexport { createContext, type InjectionKey, inject, injectStrict, provide } from './context';\nexport { define, prop } from './define';\n// Near-universal template directives — used in most non-trivial components (lists,\n// conditionals, and class/style maps. Kept in the main entry alongside\n// `html`/`define` rather than a separate sub-path: tree-shaking already means an unused export\n// costs nothing in a bundled consumer, so splitting these off only adds an extra import line\n// for functionality most components need on day one. `unsafeHtml()` and `live()` remain here\n// too: their explicit names make their specialized behavior clear without a second import path.\nexport { classMap } from './directives/classMap';\nexport { each } from './directives/each';\nexport { type LiveBinding, live } from './directives/live';\nexport { styleMap } from './directives/styleMap';\nexport { unsafeHtml } from './directives/unsafe-html';\nexport { when } from './directives/when';\nexport { OreApiError, OreError, type OreErrorPhase, OreInternalError, OreLifecycleError } from './errors';\nexport { type FormFieldHandle, type FormFieldOptions, useField } from './forms/field';\nexport {\n type BindOptions,\n bind,\n type HostBindConfig,\n type HostBindFn,\n type HostBindingValue,\n type ReflectConfig,\n} from './host-bind';\nexport { intersectionObserver } from './observers/intersection-observe';\nexport { mediaObserver } from './observers/media-observe';\nexport { type MutationObserverValue, mutationObserver } from './observers/mutation-observe';\nexport { resizeObserver } from './observers/resize-observe';\nexport type { InferProps, PropDef, PropInputDefs, PropsDef } from './props';\n// Lifecycle hooks — plain functions, called during setup() or a composable it invokes.\nexport {\n getHost,\n type OnFormResetCallback,\n type OnMountedCallback,\n onCleanup,\n onElement,\n onEvent,\n onFormReset,\n onMounted,\n watchEffect,\n} from './runtime';\nexport { type ComponentSlots, useSlots } from './slots';\nexport { html } from './template/instantiator';\nexport { type HTMLResult, type Ref, type RefCallback, ref } from './template/result';\nexport { type CSSResult, css } from './utils/css';\nexport { type EmitFn, useEmit } from './utils/emit';\n\nexport { createId, createStableId, resetStableIdCounter } from './utils/id';\n",
2
+ "apiSource": "export type { ComponentDefinition } from './component-types';\nexport { createContext, type InjectionKey, inject, injectStrict, provide } from './context';\nexport { define, prop } from './define';\n// Near-universal template directives — used in most non-trivial components (lists,\n// conditionals, and class/style maps. Kept in the main entry alongside\n// `html`/`define` rather than a separate sub-path: tree-shaking already means an unused export\n// costs nothing in a bundled consumer, so splitting these off only adds an extra import line\n// for functionality most components need on day one. `unsafeHtml()` and `live()` remain here\n// too: their explicit names make their specialized behavior clear without a second import path.\nexport { classMap } from './directives/classMap';\nexport { each } from './directives/each';\nexport { type LiveBinding, live } from './directives/live';\nexport { styleMap } from './directives/styleMap';\nexport { unsafeHtml } from './directives/unsafe-html';\nexport { when } from './directives/when';\nexport { OreApiError, OreError, type OreErrorPhase, OreInternalError, OreLifecycleError } from './errors';\nexport { type FormFieldHandle, type FormFieldOptions, useField } from './forms/field';\nexport {\n type BindOptions,\n bind,\n type HostBindConfig,\n type HostBindFn,\n type HostBindingValue,\n type ReflectConfig,\n} from './host-bind';\nexport type { InferProps, PropDef, PropInputDefs, PropsDef } from './props';\n// Lifecycle hooks — plain functions, called during setup() or a composable it invokes.\nexport {\n getHost,\n type OnFormResetCallback,\n type OnMountedCallback,\n onCleanup,\n onElement,\n onEvent,\n onFormReset,\n onMounted,\n watchEffect,\n} from './runtime';\nexport { type ComponentSlots, useSlots } from './slots';\nexport { html } from './template/instantiator';\nexport { type HTMLResult, type Ref, type RefCallback, ref } from './template/result';\nexport { type CSSResult, css } from './utils/css';\nexport { type EmitFn, useEmit } from './utils/emit';\n\nexport { createId, createStableId, resetStableIdCounter } from './utils/id';\n",
3
3
  "docs": {
4
- "index": "---\ntitle: Ore — Web component authoring with signals\ndescription: Functional custom-element authoring with typed props, reactive templates, lifecycle helpers, observers, and testing utilities.\npackage: ore\ncategory: ui-primitives\nkeywords: [web-components, custom-elements, reactive, templates, signals, lifecycle]\nrelated: [ripple, refine, orbit]\nexports: [define, prop, html, css, ref, createContext, inject, injectStrict, provide, onMounted, onCleanup, onEvent, onElement, onFormReset, watchEffect, useEmit, useSlots, getHost, bind, each, when, classMap, styleMap, live, unsafeHtml, useField, intersectionObserver, mediaObserver, mutationObserver, resizeObserver, createId, createStableId, resetStableIdCounter, OreError, OreApiError, OreInternalError, OreLifecycleError, BindOptions]\nenvironments: [browser]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"ore\" />\n\n## Why Ore?\n\nOre keeps custom elements functional and signal-driven while giving you direct control over templates, lifecycle hooks, host bindings, and form-associated behavior.\n\n```ts\n// Before — vanilla custom element boilerplate\nclass MyCounter extends HTMLElement {\n #count = 0;\n connectedCallback() {\n this.attachShadow({ mode: 'open' });\n this.#render();\n }\n #render() {\n this.shadowRoot!.innerHTML = `<button>${this.#count}</button>`;\n this.shadowRoot!.querySelector('button')!.onclick = () => {\n this.#count++;\n this.#render();\n };\n }\n}\ncustomElements.define('my-counter', MyCounter);\n\n// After — Ore\nimport { signal } from '@vielzeug/ripple';\nimport { define, html } from '@vielzeug/ore';\n\ndefine('my-counter', {\n setup() {\n const count = signal(0);\n return html`<button @click=${() => count.value++}>${count}</button>`;\n },\n});\n```\n\n| Feature | Ore | Lit | Stencil |\n| -------------------------- | ------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------ |\n| Bundle size | <PackageInfo package=\"ore\" type=\"size\" /> | ~12 kB | ~60 kB+ toolchain |\n| Signal-first runtime | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> (separate signals package) | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Functional component setup | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Typed prop helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Host binding helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | Partial |\n| Form-associated helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Manual | Partial |\n| Zero dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Ore when** you want typed, signal-driven custom elements with minimal runtime overhead and no framework lock-in.\n\n**Consider Lit when** you need a mature ecosystem with wide community adoption and don't need signal-based reactivity.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/ore @vielzeug/ripple\n```\n\n```sh [npm]\nnpm install @vielzeug/ore @vielzeug/ripple\n```\n\n```sh [yarn]\nyarn add @vielzeug/ore @vielzeug/ripple\n```\n\n:::\n\n## Quick Start\n\n```ts\nimport { computed, signal } from '@vielzeug/ripple';\nimport { bind, css, define, html, onMounted, prop } from '@vielzeug/ore';\n\ndefine('my-counter', {\n props: {\n label: prop.string('Count'),\n step: prop.number(1),\n },\n styles: [\n css`\n :host {\n display: inline-grid;\n gap: 0.5rem;\n }\n `,\n ],\n setup(props) {\n const count = signal(0);\n const doubled = computed(() => count.value * 2);\n\n bind({ class: { 'is-positive': () => count.value > 0 } });\n\n onMounted(() => console.log('mounted'));\n\n return html`\n <button @click=${() => (count.value += props.step.value)}>${props.label}: ${count}</button>\n <p>Doubled: ${doubled}</p>\n `;\n },\n});\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- Signal-first runtime with `signal`, `computed`, `watch`, `batch` from `@vielzeug/ripple` — import them directly\n- Functional component authoring via `define(tag, { props, setup, styles, formAssociated })`\n- Props via `prop.*` helpers (`prop.string`, `prop.number`, `prop.bool`, `prop.oneOf`, `prop.json`, `prop.data`) or raw `PropDef` objects\n- `setup(props)` takes only props and returns an `HTMLResult` directly: `return html\\`...\\``\n- Lifecycle hooks — `onMounted`, `onCleanup`, `onEvent`, `onElement`, `watchEffect` — plain functions imported from `@vielzeug/ore`, called directly from `setup()` or any composable it calls\n- Directives: `each` (keyed reactive list rendering), `classMap`, `styleMap`, `when`, `live`, `unsafeHtml`\n- Host bindings via `bind({ attr, class, style, on })` — pass `{ target: el }` to bind any off-host element\n- Reactive ARIA sync via `bind({ aria }, { target })` — applies `aria-*` attributes reactively to any element, auto-cleanup on disconnect\n- Context via `provide(key, value)` / `inject(key)`; typed emit/slots via `useEmit<Emits>()` / `useSlots<SlotNames>()`\n- Form-associated `useField()` and observer helpers are root exports\n- Testing utilities (`@vielzeug/ore/testing`) — `mount`, `renderHook`, `flush`, `cleanup`\n- Generic testing utilities (scoped queries, named event dispatchers, and async waits) are exported by `@vielzeug/assay`\n- Debug utilities (`@vielzeug/ore/testing`) — `debugFlush()` for diagnosing update timing\n\n</div>\n\n## Package Entry Points\n\n| Import | Purpose |\n| --------------------------- | ----------------------------------------------------------------------------- |\n| `@vielzeug/ore` | All browser runtime APIs: components, directives, `useField`, and observers |\n| `@vielzeug/ore/testing` | Ore-specific mounting, lifecycle flushing, hooks, cleanup, and form internals |\n| `@vielzeug/assay` | Generic DOM events, scoped queries, and async waiting |\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Refine](../refine/index.md) for prebuilt accessible components powered by Ore.\n- [Ripple](../ripple/index.md) for reactive state used inside Ore components.\n- [Forge](../forge/index.md) for typed form state that integrates with Ore.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
- "api": "---\ntitle: Ore — API Reference\ndescription: Complete API reference for @vielzeug/ore and @vielzeug/ore/testing.\n---\n\n[[toc]]\n\n## API Overview\n\nAll browser-runtime symbols below are imported from `@vielzeug/ore`. Lifecycle/context/binding functions (`onMounted`, `onCleanup`, `onEvent`, `onElement`, `watchEffect`, `bind`, `provide`, `useEmit`, `useSlots`, `getHost`) resolve the active component through an implicit \"current component\" context — they work when called synchronously during `setup()`, or from any composable function `setup()` calls (transitively), but throw if called outside that window.\n\n> `watchEffect` is not named `watch` — `@vielzeug/ripple` already exports a `watch(source, callback)` with different semantics (explicit source + old/new value pair), and the two are frequently imported in the same file.\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| ---------------------- | ----------------------------------------------------- | -------------- | -------------------------------------------------------------------------- |\n| `define()` | Register a custom element with reactive setup | Sync | Tag must contain a hyphen; call before first use |\n| `html` | Tagged template literal returning HTMLResult | Sync | Expressions must be signals, functions, or primitives |\n| `prop.*` | Typed prop helpers (string, bool, number, …) | Sync | Prop values are signals — read `.value` |\n| `provide()`/`inject()` | Context API for parent-to-descendant sharing | Setup only | Must be called synchronously during `setup()` |\n| `ref()` | Reactive reference to a DOM element | Sync | Value is null until after first mount |\n| `createContext()` | Create a typed injection key | Sync | Context is scoped to the component tree |\n| `each()` | Keyed list rendering with DOM diffing | Sync | Duplicate keys report `ore:error`; plain `T[]` is a one-time static render |\n| `when()` | Conditional branch rendering | Sync | Getter-fn computed disposed on cleanup; static bool skips subscription |\n| `live(signal)` | One-way binding that skips stale writes during input | Sync | Use for controlled inputs alongside a manual `@input` handler |\n| `onMounted(fn)` | DOM-ready callback | Setup only | Must be called synchronously during `setup()` |\n| `onCleanup(fn)` | Register teardown | Setup only | Called on component disconnect |\n| `onEvent(target, …)` | Scoped event listener with auto-cleanup | Setup only | No-ops on null target; removed on disconnect |\n| `useField(options)` | Wire signal to form `ElementInternals` | Setup only | Requires `formAssociated: true` on the component definition |\n| `onFormReset(fn)` | Run work when the ancestor `<form>` resets | Setup only | Fires every reset (not one-shot); only for `formAssociated: true` components |\n| `useEmit<Emits>()` | Typed `emit()` bound to the current host | Setup only | Call once per component; returns `dispatchEvent`'s boolean (`false` if a listener called `preventDefault()`) |\n| `useSlots<SlotNames>()`| Reactive slot presence/element signals | Setup only | Safe to call more than once — the underlying registry is created once |\n| `getHost()` | The current component's host element | Setup only | Prefer a higher-level helper (`bind`, …) when one exists |\n\n## Package Entry Points\n\n| Import | Purpose |\n| ------------------------- | ------------------------------------------------------------------ |\n| `@vielzeug/ore` | All browser runtime APIs, including directives, fields, and observers |\n| `@vielzeug/ore/testing` | Ore-specific mounting, lifecycle, hook, cleanup, and form test support |\n| `@vielzeug/assay` | Generic DOM events, scoped queries, and async waiting |\n\n## Core Component API\n\n### `define(tag, definition)`\n\n```ts\ndefine<Props>(tag: string, definition: ComponentDefinition<Props>): void;\n```\n\nThe `setup()` function receives only typed prop signals:\n\n```ts\nsetup(props) {\n return html`<div>${props.label}</div>`;\n}\n```\n\nEverything else — lifecycle hooks, host bindings, context, slots, emit — is a plain function imported from `@vielzeug/ore`, called directly from `setup()` (or a composable it calls):\n\n```ts\nimport { define, html, onMounted, useEmit, useSlots } from '@vielzeug/ore';\n\ndefine('my-card', {\n setup(_props) {\n const emit = useEmit<{ close: undefined }>();\n const slots = useSlots<'header' | 'footer'>();\n\n onMounted(() => console.log('mounted'));\n\n // emit() returns dispatchEvent's boolean — false if a listener called preventDefault()\n const notCancelled = emit('close');\n\n return html`${when(slots.has('header'), () => html`<slot name=\"header\"></slot>`)}`;\n },\n});\n```\n\n`useEmit<Emits>()` and `useSlots<SlotNames>()` are factory hooks — call them once per setup run to get a typed\n`emit`/`slots` bound to the current host. `useSlots()` is safe to call more than once within that setup run.\n\n### ComponentDefinition\n\n```ts\ntype ComponentDefinition<Props> = {\n formAssociated?: boolean;\n props?: PropsDef<Props>;\n setup: (props: InferProps<PropsDef<Props>>) => HTMLResult | null;\n shadow?: Partial<ShadowRootInit> | false; // false = light DOM (no shadow root)\n styles?: (string | CSSStyleSheet | CSSResult)[];\n};\n```\n\n## Runtime Helpers\n\n`onMounted`, `onCleanup`, `onEvent`, `onElement`, and `watchEffect` are plain functions imported from `@vielzeug/ore`. Call them directly during `setup()`.\n\n```ts\nimport { html, onCleanup, onEvent, onMounted } from '@vielzeug/ore';\n\nsetup(props) {\n onMounted(() => {\n // DOM is ready; return a function for mount-scoped cleanup\n return () => { /* cleanup on unmount */ };\n });\n\n onCleanup(() => { /* called on disconnect */ });\n\n onEvent(window, 'keydown', (e) => { /* auto-removed on disconnect */ });\n\n return html`...`;\n}\n```\n\nBecause these resolve the active component through an implicit context (rather than a value threaded through parameters), composable helper functions can call them directly too — no need to pass hooks in as options:\n\n```ts\nimport { onCleanup } from '@vielzeug/ore';\n\nfunction useMyHelper() {\n onCleanup(() => { /* teardown */ });\n}\n\n// In setup:\nsetup(_props) {\n useMyHelper();\n return html`...`;\n}\n```\n\n## Props API\n\n| Helper | Signature | Notes |\n| ----------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------ |\n| `prop.string(defaultValue?)` | `PropDef<string>` | Reflects by default |\n| `prop.bool(defaultValue?)` | `PropDef<boolean>` | Any non-null attribute value other than `\"false\"` parses as `true`; `\"false\"` or absent attribute is `false` |\n| `prop.number(defaultValue?)` | `PropDef<number>` | Returns default (not NaN) and warns in dev when attribute is not a valid number |\n| `prop.oneOf(allowed, defaultValue)` | `PropDef<T>` | Restricts to provided string union |\n| `prop.json(defaultValue)` | `PropDef<T>` | JSON.parse from attribute; `reflect: false` |\n| `prop.data<T>(defaultValue?)` | `PropDef<T>` | JS-only — never reads/writes an attribute; use for objects, arrays, callbacks, or any non-serialisable value |\n\n> **Choosing the right prop helper:**\n>\n> - **`prop.json`** — value can be declared in HTML (`<my-el config='{\"x\":1}'>`); attribute string is `JSON.parse`d.\n> - **`prop.data`** — value is always set from JavaScript (objects, arrays, callbacks, class instances); the attribute is never read. Use this for both data and function props.\n\nWhen you need custom parsing or `reflect: false`, use a raw `PropDef` object:\n\n```ts\nprops: {\n items: { default: [], parse: () => [], reflect: false },\n}\n```\n\nUse `prop.data` for props that hold JS-only values (including callbacks) that cannot be serialised through an HTML attribute:\n\n```ts\ndefine('data-grid', {\n props: {\n getRowKey: prop.data<(row: unknown) => string>(),\n columns: prop.data<DataGridColumn[]>([]),\n onSort: prop.data<(key: string) => void>(),\n },\n setup(props) {\n // Set from JS: grid.getRowKey = (row) => row.id\n return html`...`;\n },\n});\n```\n\n## Template and Directives\n\n### `html`\n\nTagged template literal that returns an `HTMLResult`. Supports text interpolation, ordinary attributes (`attr=`),\nboolean attributes (`?attr=`), events (`@event=`), refs (`ref=`), and nested templates.\n\n### `css`\n\nTagged template literal that returns a `CSSResult` for use in `styles`.\n\n### Directives\n\n| Directive | Purpose |\n| -------------------------------------- | ----------------------------------------------------------------------------------------------------- |\n| `each(source, key, render, fallback?)` | Keyed reactive list; render receives `Readable<T>` and `Readable<number>`; plain `T[]` is a one-time static snapshot |\n| `when(condition, truthy, falsy?)` | Conditional rendering |\n| `classMap(record)` | Reactive class string from object map |\n| `styleMap(record)` | Reactive inline style string from object map |\n| `live(signal)` | One-way binding that skips stale writes during active user input; use with `@input` handler |\n| `unsafeHtml(value)` | HTML rendering sink; sanitize untrusted values before calling |\n\n### `unsafeHtml`\n\n`unsafeHtml()` is an explicit HTML injection sink. It has no global sanitizer: sanitize untrusted\ncontent before passing it to the directive, so the trust boundary remains at the call site.\n\n```ts\nimport { unsafeHtml } from '@vielzeug/ore';\n\nconst safeArticle = sanitize(userSuppliedArticle);\n\nreturn html`<article>${unsafeHtml(safeArticle)}</article>`;\n```\n\n## Host Bindings\n\n`bind(config, options?)` is a plain function imported from `@vielzeug/ore`:\n\n```ts\nbind({\n attr: { role: 'button', 'aria-expanded': () => String(open.value) },\n class: { 'is-open': open },\n style: { '--height': () => height.value + 'px' },\n on: { click: handleClick },\n});\n```\n\n`bind()` auto-registers cleanup with the component scope — no manual `onCleanup` needed. Returns a cleanup function for early teardown.\n\n### Off-host bindings\n\nPass `{ target: el }` as a second argument to bind to any element other than the host:\n\n```ts\nbind(\n { attr: { 'aria-expanded': () => String(isOpen.value) } },\n { target: triggerEl },\n);\n```\n\nEvent listener options (`once`, `capture`, `passive`) are also accepted in the second argument. Cleanup is auto-registered with the component scope when called during setup.\n\n### Reactive ARIA attributes\n\nFor reactive ARIA attribute syncing, use `bind({ aria: config }, { target })`. Shorthand keys are normalised to `aria-*` automatically (`expanded` → `aria-expanded`; `role` is passed verbatim):\n\n```ts\n// Inside setup — cleanup auto-registered\nbind(\n {\n aria: {\n expanded: () => isOpen.value,\n controls: panelId,\n haspopup: 'listbox',\n },\n },\n { target: triggerEl },\n);\n\n// Manage cleanup manually — bind() always returns a cleanup fn\nconst stopAria = bind({ aria: { expanded: () => isOpen.value } }, { target: triggerEl });\n// Call stopAria() when the trigger is swapped out\n```\n\nStatic values (strings, numbers, booleans) are applied once. Getter functions and signals create reactive effects. Setting a value to `null`, `undefined`, or `false` removes the attribute.\n\n## Slots\n\n- `slots.has(name?)` — `Readable<boolean>` — whether the named (or default) slot has assigned content\n- `slots.elements(name?)` — `Readable<Element[]>` — the assigned elements for the slot\n\nSlot signals update reactively when assigned content changes, including when slots are inserted dynamically (via `when()` or `each()`) after mount.\n\n## Context API\n\n- `createContext<T>(description?)` — Create a typed injection key\n- `provide(key, value)` — Provide a value to descendants\n- `inject(key)` — Resolve from nearest ancestor; returns `undefined` if not found\n- `inject(key, fallback)` — Resolve with a fallback value\n- `injectStrict(key)` — Resolve or throw if absent\n\n`provide()` and `inject()` must be called synchronously during `setup()`. Calling them outside a setup context throws\n`'Lifecycle hooks must be called during component setup'`. Context resolution walks the ancestor chain including shadow\nDOM boundaries. `inject()` resolves and caches its result once per consumer — provide a `Readable` (signal/computed)\nrather than a raw value if descendants need to observe later changes; re-calling `provide()` with a new raw value\nafterward is not seen by consumers that already resolved it (a dev-mode warning fires when a key is provided twice on\nthe same element).\n\n## Utilities\n\n- `ref<T>()` — Create a `Signal<T | null>` element reference. Set to the element via `ref=` in templates.\n- `createId(prefix = 'id')` — Generate a unique incremental string ID (e.g. `'id-1'`, `'id-2'`). Each call returns a new ID — it does not deduplicate by prefix.\n- `createStableId(prefix = 'id')` — Generate a unique ID that also embeds a short random tag shared across all IDs generated in the session (e.g. `'field-a3k21'`), reducing collision risk when multiple app instances run on the same page. Like `createId()`, every call returns a new ID.\n- `resetStableIdCounter()` — Reset the `createStableId()` counter to 0. Call in test `beforeEach` for deterministic IDs. Scoped to `createStableId()` only — `createId()` has no public reset (it's for uniqueness, not cross-test determinism).\n\n## Form-Associated API\n\nImport from `@vielzeug/ore`.\n\n### `useField(options)`\n\nWire a form-associated element to `ElementInternals`. Requires `formAssociated: true` on the component definition. The `disabled` state tracking via `internals.states` (CustomStateSet) is skipped with a dev warning if the API is unavailable in the current environment.\n\n```ts\ntype FormFieldOptions<T> = {\n disabled?: Readable<boolean>;\n /** Defaults to the host element active during setup. */\n el?: HTMLElement;\n /**\n * When true, a null/undefined value is submitted as '' instead of null,\n * keeping the field's key present in FormData even when the value is absent.\n * Only applies to the default toFormValue; ignored if toFormValue is provided.\n * @default false\n */\n emptyStringForNull?: boolean;\n /** Called when the ancestor <form> resets (see onFormReset) — restore local field state here. */\n onReset?: () => void;\n toFormValue?: (value: T) => File | FormData | string | null;\n /** Recomputed reactively and passed straight to internals.setValidity(). null = always valid. */\n validationMessage?: Readable<string>;\n validity?: Readable<ValidityStateFlags | null>;\n value: Signal<T> | Readable<T>;\n};\n\ntype FormFieldHandle = {\n checkValidity(): boolean;\n readonly internals: ElementInternals;\n reportValidity(): boolean;\n /** Set (non-empty message) or clear (empty string) a custom validity error. */\n setCustomValidity(message: string): void;\n};\n```\n\nPass `validity`/`validationMessage` to make `required`-style constraints participate in native constraint validation\nthrough `checkValidity()` and `reportValidity()`:\n\n```ts\nconst isBlank = (v: string) => v.trim() === '';\n\nuseField({\n validationMessage: computed(() => (required.value && isBlank(value.value) ? 'This field is required.' : '')),\n validity: computed(() => (required.value && isBlank(value.value) ? { valueMissing: true } : null)),\n value,\n});\n```\n\n## Observer APIs\n\nImport from `@vielzeug/ore`.\n\n- `resizeObserver(element)` — Returns `Readable<{ height: number; width: number }>`, initialised to `{ height: 0, width: 0 }`\n- `intersectionObserver(element, options?)` — Returns `Readable<IntersectionObserverEntry | null>`, initialised to `null`\n- `mutationObserver(element, options?)` — Returns `Readable<{ entries: MutationRecord[]; latest: MutationRecord | null }>`, initialised to `{ entries: [], latest: null }`\n- `mediaObserver(query)` — Returns `Readable<boolean>`, initialised to the query's current `matches` state\n\n## Testing APIs\n\nImport from `@vielzeug/ore/testing`.\n\n| API | Purpose |\n| ------------------------ | ------------------------------------------------------------------------------------------ |\n| `mount(setup, options?)` | Mount a component and return a test fixture |\n| `cleanup()` | Remove all mounted elements and reset test state |\n| `install(afterEach, options?)` | Register auto-cleanup; pass `{ formInternals: true }` to also install the `ElementInternals`/`FormData`/`<form>.reset()` jsdom polyfill (see below) |\n| `installFormInternalsPolyfill()` | Installs the form-internals polyfill directly (returns an `uninstall()` that restores every patched global). Usually called via `install(afterEach, { formInternals: true })` |\n| `walkFlatTree(root, visit)` | Walks the flat tree (expanding `<slot>` via `assignedElements()`) — for finding slotted content across a shadow boundary that `querySelectorAll()` can't cross |\n| `flush(options?)` | Drain reactive updates and animation frames |\n| `debugFlush()` | Run `flush()` with `console.debug` diagnostics |\n| `mock(tag, template?)` | Register a no-op stub custom element |\n| `renderHook(setup)` | Run lifecycle hooks in isolation; overload accepts `propDefs` as first arg for typed props |\n| `resetOreForTests()` | Reset styles and ID counters when mounting is managed manually |\n| `OreTimeoutError` | Error thrown when `flush()` cannot settle tracked Ore work |\n\n> **Test isolation:** `cleanup()` removes mounted elements and resets all cross-test Ore state (the stylesheet cache and ID counters) via `resetOreForTests()`. Call it in `afterEach` (or use `install()`) to prevent state leaking between tests.\n\nImport `within`, named dispatchers such as `fireClick`, and waits such as `waitUntil` or `waitForEvent` from\n`@vielzeug/assay`.\n\n> **Form-associated component testing:** jsdom implements none of the `ElementInternals` form-association API — `install(afterEach, { formInternals: true })` polyfills `setFormValue`/`setValidity`/`checkValidity`/`reportValidity`/`validationMessage`/`validity`/`states`, mixes `checkValidity`/`reportValidity`/`validity`/`validationMessage` onto the host element itself (real browsers do this for any `formAssociated: true` element), makes `FormData` collect a form-associated element's set value, and makes `<form>.reset()` invoke `formResetCallback()`. Every patch is a guarded no-op when its target already exists, and `installFormInternalsPolyfill()` returns an `uninstall()` that restores every patched global. The polyfill is opt-in (`{ formInternals: true }`) because the patches are global — suites without form-associated components shouldn't carry them. A downstream package (e.g. a component library built on `ore`) should rely on this instead of hand-rolling its own copy.\n\n#### `Fixture` interface\n\n```ts\ninterface Fixture<T extends HTMLElement = HTMLElement> {\n [Symbol.dispose](): void; // Delegates to dispose() — enables `using` declarations\n element: T;\n readonly disposed: boolean; // true after dispose() has been called\n readonly shadow: ShadowRoot | null;\n get<E extends Element>(selector: string): E;\n query<E extends Element>(selector: string): E | null;\n queryAll<E extends Element>(selector: string): E[];\n getByText<E extends Element>(text: string, selector?: string): E;\n queryByText<E extends Element>(text: string, selector?: string): E | null;\n queryAllByText<E extends Element>(text: string, selector?: string): E[];\n getByTestId<E extends Element>(testId: string): E;\n queryByTestId<E extends Element>(testId: string): E | null;\n queryAllByTestId<E extends Element>(testId: string): E[];\n attr(name: string, value: string | number | boolean): Promise<void>;\n attrs(record: Record<string, string | number | boolean>): Promise<void>;\n flush(options?: FlushOptions): Promise<void>;\n act(fn: () => unknown): Promise<void>;\n dispose(): void; // Removes the component from the DOM — idempotent\n}\n```\n\n#### `renderHook`\n\nUseful for testing composable lifecycle hooks (`onMounted`, `watchEffect`, `inject`, etc.) without a template. `onMounted`/`onCleanup`/`watchEffect`/... work exactly as inside a real `setup()`, since they resolve the same implicit current-component context:\n\n```ts\n// Without props\nconst { result, flush, dispose } = await renderHook(() => {\n const count = signal(0);\n onMounted(() => {\n count.value = 1;\n });\n return count;\n});\nexpect(result.value).toBe(1);\n\n// With typed props (prop-defs overload)\nconst { result } = await renderHook({ label: prop.string('hello'), count: prop.number(0) }, (props) => props.label);\nexpect(result.value).toBe('hello');\n```\n\n## Ripple Primitives\n\nOre does **not** re-export reactive primitives. Import them directly from `@vielzeug/ripple`:\n\n```ts\nimport { batch, computed, signal, watch } from '@vielzeug/ripple';\n```\n\nSee the [Ripple documentation](/ripple/) for the full API.\n\n## Lifecycle Events\n\n| Event | When |\n| ------------------ | ------------------------------------------------------------- |\n| `ore:connect` | After every `connectedCallback` (including reconnects) |\n| `ore:disconnect` | After `disconnectedCallback`, before component state is reset |\n| `ore:error` | When a lifecycle callback fails — bubbles, composed; detail is `OreLifecycleError` |\n\n## Types\n\n```ts\ntype PropDef<T> = {\n readonly default: T;\n readonly parse: (value: string | null) => T;\n reflect?: boolean;\n};\n\ntype PropsDef<T extends Record<string, unknown>> = {\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\ntype PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Infer reactive props type from a PropInputDefs map.\n * Each entry becomes Readable<T> keyed by prop name.\n */\ntype InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\n// Runtime hooks — all plain functions imported from '@vielzeug/ore', not fields on an object.\ntype OnMountedCallback = () => Cleanup | undefined;\ntype OnFormResetCallback = () => void;\n\ndeclare function onMounted(fn: OnMountedCallback): void; // DOM-ready callback; runs after each connection's render\ndeclare function onCleanup(fn: Cleanup): void; // Register teardown; called on disconnect\ndeclare function onElement<T extends HTMLElement>(\n ref: Readable<T | null>,\n callback: (el: T) => Cleanup | undefined,\n): () => void;\ndeclare function onEvent<K extends keyof HTMLElementEventMap>(\n target: EventTarget | null | undefined,\n event: K,\n listener: (e: HTMLElementEventMap[K]) => void,\n options?: AddEventListenerOptions,\n): void;\ndeclare function onEvent(\n target: EventTarget | null | undefined,\n event: string,\n listener: EventListener,\n options?: AddEventListenerOptions,\n): void;\ndeclare function onFormReset(fn: OnFormResetCallback): void; // Runs on every ancestor <form> reset; formAssociated only\ndeclare function watchEffect(fn: () => Cleanup | undefined): () => void; // Scoped reactive effect; auto-cleaned on disconnect\ndeclare function bind(config: HostBindConfig, options?: BindOptions): () => void; // Bindings for host or any target element\ndeclare function provide<T>(key: InjectionKey<T>, value: T): void; // Register a context value on the host element\ndeclare function inject<T>(key: InjectionKey<T>): T | undefined;\ndeclare function inject<T>(key: InjectionKey<T>, fallback: T): T;\ndeclare function getHost(): HTMLElement; // The current component's host element\ndeclare function useEmit<Emits extends Record<string, unknown> = Record<string, never>>(): EmitFn<Emits>;\ndeclare function useSlots<SlotNames extends string = string>(): ComponentSlots<SlotNames>;\n\ntype ComponentDefinition<Props extends Record<string, unknown> = Record<never, never>> = {\n formAssociated?: boolean;\n props?: PropsDef<Props>;\n setup: (props: InferProps<PropsDef<Props>>) => HTMLResult | null;\n shadow?: Partial<ShadowRootInit> | false; // false = light DOM\n styles?: (string | CSSStyleSheet | CSSResult)[];\n};\n\ntype HostBindingValue =\n | (() => string | number | boolean | null | undefined)\n | Readable<string | number | boolean | null | undefined>\n | string\n | number\n | boolean\n | null\n | undefined;\n\ntype ReflectConfig = Record<string, HostBindingValue>;\n\ntype HostBindConfig = {\n aria?: ReflectConfig;\n attr?: ReflectConfig;\n class?: (() => Record<string, boolean>) | Record<string, Readable<boolean> | (() => boolean) | boolean>;\n on?: Record<string, ((event: Event) => void) | undefined>;\n style?: Record<string, HostBindingValue>;\n};\n\ntype BindOptions = AddEventListenerOptions & {\n target?: Element;\n};\n\ntype HostBindFn = (config: HostBindConfig, options?: BindOptions) => () => void;\n\ntype ComponentSlots<S extends string = string> = {\n elements(name?: S): Readable<Element[]>;\n has(name?: S): Readable<boolean>;\n};\n\ntype Ref<T extends Element> = Signal<T | null>;\n\ntype RefCallback<T extends Element> = (el: T | null) => void;\n\ntype InjectionKey<T> = symbol & { readonly __ore_injection_key?: T };\n\ninterface HTMLResult {\n mount(\n parent: ParentNode,\n anchor: Node | null,\n registerCleanup: (fn: () => void) => void,\n ): Node[];\n}\n\ntype CSSResult = {\n content: string;\n toString(): string;\n};\n\ntype LiveBinding<T> = { readonly source: Readable<T> };\n\ntype EmitFn<T extends Record<string, unknown>> = {\n <K extends KeysWithoutDetail<T>>(event: K): boolean;\n <K extends Exclude<keyof T, KeysWithoutDetail<T>>>(event: K, detail: T[K]): boolean;\n};\n// KeysWithoutDetail is an internal helper type, not exported.\n\ntype FormFieldOptions<T = unknown> = {\n disabled?: Readable<boolean>;\n el?: HTMLElement;\n emptyStringForNull?: boolean;\n onReset?: () => void;\n toFormValue?: (value: T) => File | FormData | string | null;\n validationMessage?: Readable<string>;\n validity?: Readable<ValidityStateFlags | null>;\n value: Signal<T> | Readable<T>;\n};\n\ntype FormFieldHandle = {\n checkValidity: () => boolean;\n readonly internals: ElementInternals;\n reportValidity: () => boolean;\n setCustomValidity: (message: string) => void;\n};\n\ntype MutationObserverValue = {\n entries: MutationRecord[];\n latest: MutationRecord | null;\n};\n\n/** Phase in which a OreError occurred. */\ntype OreErrorPhase = 'each-reconcile' | 'form-reset' | 'mounted' | 'setup';\n```\n\n## Errors\n\n`OreError` is the base class for every Ore error class — `err instanceof OreError` catches all of them.\n`OreError.is(err)` is the equivalent static type-guard.\n\n- **`OreApiError`** — thrown when the `ore` API itself is misused: calling `define()` with a duplicate tag, calling a lifecycle hook (`inject`, `onMounted`, `onCleanup`, `onEvent`, …) outside of `setup()`, or passing an invalid prop definition to `define()`.\n- **`OreInternalError`** — thrown when an Ore invariant fails, indicating a package bug rather than invalid application code.\n- **`OreLifecycleError`** — reported in the `ore:error` event when component `setup()`, a mounted callback, a form-reset callback, or `each()` reconciliation fails. Extends `OreError` with:\n - `component: string` — the element's local name\n - `phase: OreErrorPhase` — `'setup'` | `'mounted'` | `'form-reset'` | `'each-reconcile'`\n - `cause: Error` — the original error thrown by `setup()`\n- **`OreTimeoutError`** — thrown by `flush()` (from `@vielzeug/ore/testing`) when pending Ore work does not settle before its timeout.\n\nLifecycle failures dispatch a bubbling, composed `ore:error` event whose `detail` is the `OreLifecycleError`. Setup\nfailures still rethrow their original error; mounted and form-reset callback failures are reported through the same\nevent so their remaining callbacks can continue.\n",
6
- "usage": "---\ntitle: Ore — Usage Guide\ndescription: Practical Ore usage patterns for components, props, templates, slots, context, forms, observers, and tests.\n---\n\n[[toc]]\n\n## Basic Usage\n\n`define(tag, definition)` registers a custom element.\n\nYour `setup()` function receives typed prop signals and returns an `HTMLResult` directly. Its state belongs to the\ncurrent connection: disconnect disposes it, and reconnecting the same element runs setup again.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html } from '@vielzeug/ore';\n\ndefine('status-chip', {\n setup() {\n const online = signal(true);\n\n return html`\n <button @click=${() => (online.value = !online.value)}>${() => (online.value ? 'Online' : 'Offline')}</button>\n `;\n },\n});\n```\n\nEverything besides `props` — lifecycle hooks, host bindings, context, slots, emit — is a plain function imported from `@vielzeug/ore`, called directly from `setup()` (or a composable it calls):\n\n```ts\nimport { define, getHost, html, bind, useEmit, useSlots } from '@vielzeug/ore';\n\ndefine('my-widget', {\n setup(_props) {\n const el = getHost(); // the host HTMLElement\n const emit = useEmit<{ close: undefined }>(); // typed event emitter\n const slots = useSlots<'header'>(); // reactive slot observation\n\n bind({ attr: { role: 'group' } }); // host binding helper (attr, class, style, on)\n\n return html`<slot></slot>`;\n },\n});\n```\n\n## signals and effects\n\nOre does not re-export ripple primitives — import them directly from `@vielzeug/ripple`.\n\n```ts\nimport { batch, computed, effect, signal, watch } from '@vielzeug/ripple';\n\nconst count = signal(0);\nconst doubled = computed(() => count.value * 2);\n\neffect(() => {\n console.log('doubled =', doubled.value);\n});\n\nwatch(count, (next, prev) => {\n console.log('count changed', prev, '->', next);\n});\n\nbatch(() => {\n count.value = 1;\n count.value = 2;\n});\n```\n\n## onMounted and lifecycle\n\nUse `onMounted()` for DOM-dependent initialization that must run after the template is mounted. Use `onElement(ref, cb)` for work tied to a specific DOM node. `onEvent()` attaches a listener that is automatically removed on disconnect.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html, onElement, onEvent, onMounted, ref, useSlots } from '@vielzeug/ore';\n\ndefine('deferred-init', {\n setup(_props) {\n const tabIndex = signal(0);\n const inputRef = ref<HTMLInputElement>();\n const slots = useSlots<'items'>();\n\n onMounted(() => {\n const items = slots.elements('items').value;\n console.log('Found', items.length, 'items');\n });\n\n onElement(inputRef, (input) => {\n input.focus();\n });\n\n onEvent(window, 'keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape') tabIndex.value = 0;\n });\n\n return html`<div><slot name=\"items\"></slot><input ref=${inputRef} /></div>`;\n },\n});\n```\n\n## prop definitions\n\nUse `prop.*` helpers for common cases, or raw `PropDef` objects for custom parsing or `reflect: false`.\n\n```ts\nimport { define, html, prop } from '@vielzeug/ore';\n\ndefine('x-button', {\n props: {\n label: prop.string('Button'),\n disabled: prop.bool(false),\n variant: prop.oneOf(['primary', 'secondary'] as const, 'primary'),\n count: prop.number(0),\n },\n setup(props) {\n return html`\n <button ?disabled=${props.disabled} data-variant=${props.variant}>${props.label} (${props.count})</button>\n `;\n },\n});\n```\n\n## template bindings\n\n`html` supports text, attributes, booleans, properties, events, refs, and nested templates.\n\n```ts\nimport { computed, signal } from '@vielzeug/ripple';\nimport { define, html, ref } from '@vielzeug/ore';\n\ndefine('profile-name', {\n setup() {\n const name = signal('Alice');\n const inputRef = ref<HTMLInputElement>();\n\n return html`\n <label title=${computed(() => 'Current: ' + name.value)}>Name</label>\n <input\n ref=${inputRef}\n value=${name}\n aria-label=${() => 'Current name ' + name.value}\n @input=${(event: Event) => {\n name.value = (event.target as HTMLInputElement).value;\n }} />\n <p>Hello ${name}</p>\n `;\n },\n});\n```\n\n## directives\n\nOre exports `each`, `classMap`, `styleMap`, `when`, `live`, and `unsafeHtml` from `@vielzeug/ore`. Use ordinary\nattribute bindings plus native event handlers for two-way input state; no special model directive is required.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { classMap, define, each, html, styleMap, when } from '@vielzeug/ore';\n\ndefine('task-list', {\n setup() {\n const tasks = signal([{ id: 1, text: 'Write tests' }]);\n const active = signal(true);\n\n return html`\n <ul\n class=\"${classMap({ ready: () => tasks.value.length > 0 })}\"\n style=${styleMap({ opacity: () => (active.value ? 1 : 0.5) })}>\n ${when(\n () => active.value,\n () => html`<li>Active</li>`,\n () => html`<li>Paused</li>`,\n )}\n ${each(\n tasks,\n (task) => task.id,\n (task) => html`<li>${() => task.value.text}</li>`,\n )}\n </ul>\n `;\n },\n});\n```\n\n### each() API\n\n`each(source, key, render, fallback?)` takes positional arguments:\n\n- **source** — signal, getter, or plain array\n- **key** — function returning a unique key per item\n- **render** — receives reactive `item` and `index` signals\n- **fallback** — optional, rendered when the list is empty\n\n```ts\neach(\n items,\n (item) => item.id,\n (item, index) => html`<li>#${index}: ${() => item.value.label}</li>`,\n () => html`<li>No items</li>`,\n);\n```\n\n## live form bindings\n\nUse `live(signal)` for inputs that should preserve in-progress user edits instead of overwriting the DOM on stale writes.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html, live } from '@vielzeug/ore';\n\ndefine('live-search', {\n setup() {\n const query = signal('');\n\n return html`\n <input value=${live(query)} @input=${(e: Event) => (query.value = (e.target as HTMLInputElement).value)} />\n `;\n },\n});\n```\n\n## host bindings\n\n`bind()` wires reactive attrs, classes, styles, and events to the host element.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { bind, define, html } from '@vielzeug/ore';\n\ndefine('x-toggle', {\n setup(_props) {\n const open = signal(false);\n\n bind({\n attr: { 'aria-expanded': () => String(open.value), role: 'button', tabindex: 0 },\n class: { 'is-open': open },\n on: { click: () => (open.value = !open.value) },\n });\n\n return html`<slot></slot>`;\n },\n});\n```\n\nThe `bind` config supports `attr`, `class`, `style`, and `on` sections.\n\n## ARIA bindings\n\nUse `bind({ aria: config }, { target })` to reactively sync ARIA attributes to any element. Shorthand keys are normalised to `aria-*` automatically — `expanded` becomes `aria-expanded`, `role` is set verbatim.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { bind, define, html, onMounted } from '@vielzeug/ore';\n\ndefine('x-disclosure', {\n setup(_props) {\n const open = signal(false);\n const panelId = 'disclosure-panel';\n\n bind({\n attr: { role: 'button', tabindex: 0 },\n on: { click: () => (open.value = !open.value) },\n });\n\n onMounted(() => {\n const trigger = document.querySelector('#trigger') as HTMLElement;\n if (trigger) {\n // bind() registers cleanup automatically when called inside setup\n bind(\n {\n aria: {\n controls: panelId,\n expanded: () => String(open.value),\n haspopup: 'region',\n },\n },\n { target: trigger },\n );\n }\n });\n\n return html`<slot></slot>`;\n },\n});\n```\n\nStatic values are applied once. Getter functions create reactive effects. Setting a value to `null`, `undefined`, or `false` removes the attribute.\n\n`bind()` always returns a cleanup function. Use it to stop syncing early when a trigger element can be swapped out:\n\n```ts\nonMounted(() => {\n const trigger = document.querySelector('#trigger') as HTMLElement;\n const stopAria = bind({ aria: { expanded: () => String(open.value) } }, { target: trigger });\n\n // Stop syncing when the trigger is replaced\n onCleanup(stopAria);\n});\n```\n\n### Binding a non-host element with `bind()`\n\nPass `{ target: el }` as a second argument to bind attributes, classes, styles, or events to any element:\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { bind, define, html, onMounted, ref } from '@vielzeug/ore';\n\ndefine('button-wrapper', {\n setup(_props) {\n const visible = signal(false);\n const btnRef = ref<HTMLButtonElement>();\n\n onMounted(() => {\n const btn = btnRef.value;\n if (!btn) return;\n\n bind(\n {\n attr: { 'aria-pressed': () => String(visible.value) },\n on: { click: () => (visible.value = !visible.value) },\n },\n { target: btn },\n );\n });\n\n return html`<button ref=${btnRef}>Toggle</button>`;\n },\n});\n```\n\n## slots and emits\n\n```ts\nimport { define, html, useEmit, useSlots, when } from '@vielzeug/ore';\n\ndefine('card-with-footer', {\n setup(_props) {\n const slots = useSlots<'header' | 'footer'>();\n const emit = useEmit<{ action: undefined }>();\n\n return html`\n <div class=\"card\">\n <slot name=\"header\"></slot>\n <slot></slot>\n ${when(slots.has('footer'), () => html`<footer><slot name=\"footer\"></slot></footer>`)}\n </div>\n <button @click=${() => emit('action')}>Go</button>\n `;\n },\n});\n```\n\nPass a `SlotNames` type parameter to `useSlots<SlotNames>()` to get typed `slots.has()` and `slots.elements()` calls.\n\n## context provide/inject\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { createContext, define, html, injectStrict, provide } from '@vielzeug/ore';\n\nconst COUNT_CTX = createContext<ReturnType<typeof signal<number>>>('count');\n\ndefine('count-provider', {\n setup(_props) {\n const count = signal(0);\n provide(COUNT_CTX, count);\n\n return html`<button @click=${() => count.value++}><slot></slot></button>`;\n },\n});\n\ndefine('count-consumer', {\n setup() {\n const count = injectStrict(COUNT_CTX);\n\n return html`<p>Count: ${count}</p>`;\n },\n});\n```\n\n## form-associated elements\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html, prop } from '@vielzeug/ore';\nimport { useField } from '@vielzeug/ore';\n\ndefine('rating-input', {\n formAssociated: true,\n setup() {\n const value = signal(0);\n const field = useField({ value });\n\n return html`\n <button @click=${() => (value.value = 1)}>1</button>\n <button @click=${() => (value.value = 2)}>2</button>\n <button @click=${() => (value.value = 3)}>3</button>\n <button @click=${() => field.reportValidity()}>Validate</button>\n <p>Current: ${value}</p>\n `;\n },\n});\n```\n\n## platform observers\n\nObserver helpers from `@vielzeug/ore` require real DOM nodes, so call them inside `onMounted()`.\n\n```ts\nimport { effect } from '@vielzeug/ripple';\nimport { define, html, intersectionObserver, mediaObserver, onMounted, ref, resizeObserver } from '@vielzeug/ore';\n\ndefine('x-observed', {\n setup(_props) {\n const boxRef = ref<HTMLDivElement>();\n\n onMounted(() => {\n const element = boxRef.value;\n if (!element) return;\n\n const size = resizeObserver(element);\n const visible = intersectionObserver(element, { threshold: 0.5 });\n const dark = mediaObserver('(prefers-color-scheme: dark)');\n\n // effect() auto-tracks every signal read inside — re-runs when any of the three change.\n effect(() => {\n console.log(size.value.width, visible.value?.isIntersecting, dark.value);\n });\n });\n\n return html`<div ref=${boxRef}>Observe me</div>`;\n },\n});\n```\n\n## testing utilities\n\nImport from `@vielzeug/ore/testing`.\n\n```ts\nimport { afterEach, describe, expect, it } from 'vitest';\nimport { signal } from '@vielzeug/ripple';\nimport { fireClick } from '@vielzeug/assay';\nimport { html } from '@vielzeug/ore';\nimport { cleanup, mount } from '@vielzeug/ore/testing';\n\ndescribe('my-counter', () => {\n afterEach(cleanup);\n\n it('increments on click', async () => {\n let count!: ReturnType<typeof signal<number>>;\n const { query, act } = await mount(() => {\n count = signal(0);\n return html`<button @click=${() => count.value++}>${count}</button>`;\n });\n\n expect(query('button')?.textContent).toBe('0');\n\n await act(() => fireClick(query('button')!));\n\n expect(query('button')?.textContent).toBe('1');\n });\n});\n```\n\n## Framework Integration\n\nOre components are standard custom elements and work natively in any framework.\n\n::: code-group\n\n```tsx [React]\n// React 19+ supports custom elements natively.\nimport './x-toggle'; // wherever define('x-toggle', { ... }) is called\n\nfunction App() {\n return <x-toggle aria-label=\"Open menu\" />;\n}\n```\n\n```ts [Vue 3]\n<script setup lang=\"ts\">\nimport './x-toggle'; // wherever define('x-toggle', { ... }) is called\nimport { ref } from 'vue';\n\nconst open = ref(false);\n</script>\n\n<template>\n <x-toggle :aria-label=\"'Open menu'\" @click=\"open = !open\" />\n</template>\n```\n\n```svelte [Svelte]\n<script>\n import './x-toggle'; // wherever define('x-toggle', { ... }) is called\n\n function handleClick() {\n console.log('toggled');\n }\n</script>\n\n<x-toggle aria-label=\"Open menu\" on:click={handleClick} />\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\n### With Ripple\n\nImport ripple primitives directly from `@vielzeug/ripple` for standalone reactive state outside components.\n\n```ts\nimport { signal, computed } from '@vielzeug/ripple';\nimport { define, html } from '@vielzeug/ore';\n\n// Shared state created outside any component\nconst theme = signal<'light' | 'dark'>('light');\nconst isDark = computed(() => theme.value === 'dark');\n\ndefine('theme-toggle', {\n setup() {\n return html`\n <button @click=${() => (theme.value = isDark.value ? 'light' : 'dark')}>\n ${() =>\n isDark.value ? '<ore-icon name=\"sun\" size=\"16\"></ore-icon>' : '<ore-icon name=\"moon\" size=\"16\"></ore-icon>'}\n </button>\n `;\n },\n});\n```\n\n### With Forge\n\nUse `@vielzeug/forge` for typed form state. `useField()` remains intentionally narrow: it connects a form-associated\ncustom element to native `ElementInternals` without imposing submission, validation, or dirty-state policy.\n\n```ts\nimport { createForm } from '@vielzeug/forge';\nimport { define, html } from '@vielzeug/ore';\n\ndefine('signup-form', {\n setup(_props) {\n const form = createForm({ initialValues: { email: '' } });\n\n return html`\n <form\n @submit=${(event: SubmitEvent) => {\n event.preventDefault();\n void form.submit(async (values) => {\n console.log(values);\n });\n }}>\n <slot></slot>\n </form>\n `;\n },\n});\n```\n\n## Best Practices\n\n- Setup returns `html\\`...\\`` directly — not a function wrapping the template.\n- Use `watchEffect()` for reactive subscriptions tied to component lifetime — it auto-registers cleanup on disconnect.\n- Use `onElement(ref, cb)` instead of `onMounted` when the work is tied to a single DOM node.\n- Bind host attributes and classes via `bind()` rather than mutating the element directly.\n- Provide context at the nearest ancestor — avoid global context singletons.\n- Call `onCleanup()` for every resource allocated in `setup()` (WebSockets, intervals, external subscriptions).\n- Use `live(signal)` for form inputs to prevent clobbering user-in-progress edits.\n- Extract composable helper functions freely — `onMounted`/`onCleanup`/`bind`/... resolve the active component through implicit context, so they work from any function called (transitively) during `setup()`, with no need to pass them in as parameters.\n- Test component mounting and lifecycle with `@vielzeug/ore/testing`; import generic DOM events, queries, and waits\n from `@vielzeug/assay`.\n",
4
+ "index": "---\ntitle: Ore — Web component authoring with signals\ndescription: Functional custom-element authoring with typed props, reactive templates, lifecycle helpers, and testing utilities.\npackage: ore\ncategory: ui-primitives\nkeywords: [web-components, custom-elements, reactive, templates, signals, lifecycle]\nrelated: [ripple, refine, orbit]\nexports: [define, prop, html, css, ref, createContext, inject, injectStrict, provide, onMounted, onCleanup, onEvent, onElement, onFormReset, watchEffect, useEmit, useSlots, getHost, bind, each, when, classMap, styleMap, live, unsafeHtml, useField, createId, createStableId, resetStableIdCounter, OreError, OreApiError, OreInternalError, OreLifecycleError, BindOptions]\nenvironments: [browser]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"ore\" />\n\n## Why Ore?\n\nOre keeps custom elements functional and signal-driven while giving you direct control over templates, lifecycle hooks, host bindings, and form-associated behavior.\n\n```ts\n// Before — vanilla custom element boilerplate\nclass MyCounter extends HTMLElement {\n #count = 0;\n connectedCallback() {\n this.attachShadow({ mode: 'open' });\n this.#render();\n }\n #render() {\n this.shadowRoot!.innerHTML = `<button>${this.#count}</button>`;\n this.shadowRoot!.querySelector('button')!.onclick = () => {\n this.#count++;\n this.#render();\n };\n }\n}\ncustomElements.define('my-counter', MyCounter);\n\n// After — Ore\nimport { signal } from '@vielzeug/ripple';\nimport { define, html } from '@vielzeug/ore';\n\ndefine('my-counter', {\n setup() {\n const count = signal(0);\n return html`<button @click=${() => count.value++}>${count}</button>`;\n },\n});\n```\n\n| Feature | Ore | Lit | Stencil |\n| -------------------------- | ------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------ |\n| Bundle size | <PackageInfo package=\"ore\" type=\"size\" /> | ~12 kB | ~60 kB+ toolchain |\n| Signal-first runtime | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> (separate signals package) | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Functional component setup | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Typed prop helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Host binding helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial | Partial |\n| Form-associated helpers | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Manual | Partial |\n| Zero dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Ore when** you want typed, signal-driven custom elements with minimal runtime overhead and no framework lock-in.\n\n**Consider Lit when** you need a mature ecosystem with wide community adoption and don't need signal-based reactivity.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/ore @vielzeug/ripple\n```\n\n```sh [npm]\nnpm install @vielzeug/ore @vielzeug/ripple\n```\n\n```sh [yarn]\nyarn add @vielzeug/ore @vielzeug/ripple\n```\n\n:::\n\n## Quick Start\n\n```ts\nimport { computed, signal } from '@vielzeug/ripple';\nimport { bind, css, define, html, onMounted, prop } from '@vielzeug/ore';\n\ndefine('my-counter', {\n props: {\n label: prop.string('Count'),\n step: prop.number(1),\n },\n styles: [\n css`\n :host {\n display: inline-grid;\n gap: 0.5rem;\n }\n `,\n ],\n setup(props) {\n const count = signal(0);\n const doubled = computed(() => count.value * 2);\n\n bind({ class: { 'is-positive': () => count.value > 0 } });\n\n onMounted(() => console.log('mounted'));\n\n return html`\n <button @click=${() => (count.value += props.step.value)}>${props.label}: ${count}</button>\n <p>Doubled: ${doubled}</p>\n `;\n },\n});\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- Signal-first runtime with `signal`, `computed`, `watch`, `batch` from `@vielzeug/ripple` — import them directly\n- Functional component authoring via `define(tag, { props, setup, styles, formAssociated })`\n- Props via `prop.*` helpers (`prop.string`, `prop.number`, `prop.bool`, `prop.oneOf`, `prop.json`, `prop.data`) or raw `PropDef` objects\n- `setup(props)` takes only props and returns an `HTMLResult` directly: `return html\\`...\\``\n- Lifecycle hooks — `onMounted`, `onCleanup`, `onEvent`, `onElement`, `watchEffect` — plain functions imported from `@vielzeug/ore`, called directly from `setup()` or any composable it calls\n- Directives: `each` (keyed reactive list rendering), `classMap`, `styleMap`, `when`, `live`, `unsafeHtml`\n- Host bindings via `bind({ attr, class, style, on })` — pass `{ target: el }` to bind any off-host element\n- Reactive ARIA sync via `bind({ aria }, { target })` — applies `aria-*` attributes reactively to any element, auto-cleanup on disconnect\n- Context via `provide(key, value)` / `inject(key)`; typed emit/slots via `useEmit<Emits>()` / `useSlots<SlotNames>()`\n- Form-associated `useField()` and observer helpers are root exports\n- Testing utilities (`@vielzeug/ore/testing`) — `mount`, `renderHook`, `flush`, `cleanup`\n- Generic testing utilities (scoped queries, named event dispatchers, and async waits) are exported by `@vielzeug/assay`\n- Debug utilities (`@vielzeug/ore/testing`) — `debugFlush()` for diagnosing update timing\n\n</div>\n\n## Package Entry Points\n\n| Import | Purpose |\n| --------------------------- | ----------------------------------------------------------------------------- |\n| `@vielzeug/ore` | All browser runtime APIs: components, directives, `useField`, and observers |\n| `@vielzeug/ore/testing` | Ore-specific mounting, lifecycle flushing, hooks, cleanup, and form internals |\n| `@vielzeug/assay` | Generic DOM events, scoped queries, and async waiting |\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Refine](../refine/index.md) for prebuilt accessible components powered by Ore.\n- [Ripple](../ripple/index.md) for reactive state used inside Ore components.\n- [Forge](../forge/index.md) for typed form state that integrates with Ore.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
+ "api": "---\ntitle: Ore — API Reference\ndescription: Complete API reference for @vielzeug/ore and @vielzeug/ore/testing.\n---\n\n[[toc]]\n\n## API Overview\n\nAll browser-runtime symbols below are imported from `@vielzeug/ore`. Lifecycle/context/binding functions (`onMounted`, `onCleanup`, `onEvent`, `onElement`, `watchEffect`, `bind`, `provide`, `useEmit`, `useSlots`, `getHost`) resolve the active component through an implicit \"current component\" context — they work when called synchronously during `setup()`, or from any composable function `setup()` calls (transitively), but throw if called outside that window.\n\n> `watchEffect` is not named `watch` — `@vielzeug/ripple` already exports a `watch(source, callback)` with different semantics (explicit source + old/new value pair), and the two are frequently imported in the same file.\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| ---------------------- | ----------------------------------------------------- | -------------- | -------------------------------------------------------------------------- |\n| `define()` | Register a custom element with reactive setup | Sync | Tag must contain a hyphen; call before first use |\n| `html` | Tagged template literal returning HTMLResult | Sync | Expressions must be signals, functions, or primitives |\n| `prop.*` | Typed prop helpers (string, bool, number, …) | Sync | Prop values are signals — read `.value` |\n| `provide()`/`inject()` | Context API for parent-to-descendant sharing | Setup only | Must be called synchronously during `setup()` |\n| `ref()` | Reactive reference to a DOM element | Sync | Value is null until after first mount |\n| `createContext()` | Create a typed injection key | Sync | Context is scoped to the component tree |\n| `each()` | Keyed list rendering with DOM diffing | Sync | Duplicate keys report `ore:error`; plain `T[]` is a one-time static render |\n| `when()` | Conditional branch rendering | Sync | Getter-fn computed disposed on cleanup; static bool skips subscription |\n| `live(signal)` | One-way binding that skips stale writes during input | Sync | Use for controlled inputs alongside a manual `@input` handler |\n| `onMounted(fn)` | DOM-ready callback | Setup only | Must be called synchronously during `setup()` |\n| `onCleanup(fn)` | Register teardown | Setup only | Called on component disconnect |\n| `onEvent(target, …)` | Scoped event listener with auto-cleanup | Setup only | No-ops on null target; removed on disconnect |\n| `useField(options)` | Wire signal to form `ElementInternals` | Setup only | Requires `formAssociated: true` on the component definition |\n| `onFormReset(fn)` | Run work when the ancestor `<form>` resets | Setup only | Fires every reset (not one-shot); only for `formAssociated: true` components |\n| `useEmit<Emits>()` | Typed `emit()` bound to the current host | Setup only | Call once per component; returns `dispatchEvent`'s boolean (`false` if a listener called `preventDefault()`) |\n| `useSlots<SlotNames>()`| Reactive slot presence/element signals | Setup only | Safe to call more than once — the underlying registry is created once |\n| `getHost()` | The current component's host element | Setup only | Prefer a higher-level helper (`bind`, …) when one exists |\n\n## Package Entry Points\n\n| Import | Purpose |\n| ------------------------- | ------------------------------------------------------------------ |\n| `@vielzeug/ore` | All browser runtime APIs, including directives, fields, and lifecycle helpers |\n| `@vielzeug/ore/testing` | Ore-specific mounting, lifecycle, hook, cleanup, and form test support |\n| `@vielzeug/assay` | Generic DOM events, scoped queries, and async waiting |\n\n## Core Component API\n\n### `define(tag, definition)`\n\n```ts\ndefine<Props>(tag: string, definition: ComponentDefinition<Props>): void;\n```\n\nThe `setup()` function receives only typed prop signals:\n\n```ts\nsetup(props) {\n return html`<div>${props.label}</div>`;\n}\n```\n\nEverything else — lifecycle hooks, host bindings, context, slots, emit — is a plain function imported from `@vielzeug/ore`, called directly from `setup()` (or a composable it calls):\n\n```ts\nimport { define, html, onMounted, useEmit, useSlots } from '@vielzeug/ore';\n\ndefine('my-card', {\n setup(_props) {\n const emit = useEmit<{ close: undefined }>();\n const slots = useSlots<'header' | 'footer'>();\n\n onMounted(() => console.log('mounted'));\n\n // emit() returns dispatchEvent's boolean — false if a listener called preventDefault()\n const notCancelled = emit('close');\n\n return html`${when(slots.has('header'), () => html`<slot name=\"header\"></slot>`)}`;\n },\n});\n```\n\n`useEmit<Emits>()` and `useSlots<SlotNames>()` are factory hooks — call them once per setup run to get a typed\n`emit`/`slots` bound to the current host. `useSlots()` is safe to call more than once within that setup run.\n\n### ComponentDefinition\n\n```ts\ntype ComponentDefinition<Props> = {\n formAssociated?: boolean;\n props?: PropsDef<Props>;\n setup: (props: InferProps<PropsDef<Props>>) => HTMLResult | null;\n shadow?: Partial<ShadowRootInit> | false; // false = light DOM (no shadow root)\n styles?: (string | CSSStyleSheet | CSSResult)[];\n};\n```\n\n## Runtime Helpers\n\n`onMounted`, `onCleanup`, `onEvent`, `onElement`, and `watchEffect` are plain functions imported from `@vielzeug/ore`. Call them directly during `setup()`.\n\n```ts\nimport { html, onCleanup, onEvent, onMounted } from '@vielzeug/ore';\n\nsetup(props) {\n onMounted(() => {\n // DOM is ready; return a function for mount-scoped cleanup\n return () => { /* cleanup on unmount */ };\n });\n\n onCleanup(() => { /* called on disconnect */ });\n\n onEvent(window, 'keydown', (e) => { /* auto-removed on disconnect */ });\n\n return html`...`;\n}\n```\n\nBecause these resolve the active component through an implicit context (rather than a value threaded through parameters), composable helper functions can call them directly too — no need to pass hooks in as options:\n\n```ts\nimport { onCleanup } from '@vielzeug/ore';\n\nfunction useMyHelper() {\n onCleanup(() => { /* teardown */ });\n}\n\n// In setup:\nsetup(_props) {\n useMyHelper();\n return html`...`;\n}\n```\n\n## Props API\n\n| Helper | Signature | Notes |\n| ----------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------ |\n| `prop.string(defaultValue?)` | `PropDef<string>` | Reflects by default |\n| `prop.bool(defaultValue?)` | `PropDef<boolean>` | Any non-null attribute value other than `\"false\"` parses as `true`; `\"false\"` or absent attribute is `false` |\n| `prop.number(defaultValue?)` | `PropDef<number>` | Returns default (not NaN) and warns in dev when attribute is not a valid number |\n| `prop.oneOf(allowed, defaultValue)` | `PropDef<T>` | Restricts to provided string union |\n| `prop.json(defaultValue)` | `PropDef<T>` | JSON.parse from attribute; `reflect: false` |\n| `prop.data<T>(defaultValue?)` | `PropDef<T>` | JS-only — never reads/writes an attribute; use for objects, arrays, callbacks, or any non-serialisable value |\n\n> **Choosing the right prop helper:**\n>\n> - **`prop.json`** — value can be declared in HTML (`<my-el config='{\"x\":1}'>`); attribute string is `JSON.parse`d.\n> - **`prop.data`** — value is always set from JavaScript (objects, arrays, callbacks, class instances); the attribute is never read. Use this for both data and function props.\n\nWhen you need custom parsing or `reflect: false`, use a raw `PropDef` object:\n\n```ts\nprops: {\n items: { default: [], parse: () => [], reflect: false },\n}\n```\n\nUse `prop.data` for props that hold JS-only values (including callbacks) that cannot be serialised through an HTML attribute:\n\n```ts\ndefine('data-grid', {\n props: {\n getRowKey: prop.data<(row: unknown) => string>(),\n columns: prop.data<DataGridColumn[]>([]),\n onSort: prop.data<(key: string) => void>(),\n },\n setup(props) {\n // Set from JS: grid.getRowKey = (row) => row.id\n return html`...`;\n },\n});\n```\n\n## Template and Directives\n\n### `html`\n\nTagged template literal that returns an `HTMLResult`. Supports text interpolation, ordinary attributes (`attr=`),\nboolean attributes (`?attr=`), events (`@event=`), refs (`ref=`), and nested templates.\n\n### `css`\n\nTagged template literal that returns a `CSSResult` for use in `styles`.\n\n### Directives\n\n| Directive | Purpose |\n| -------------------------------------- | ----------------------------------------------------------------------------------------------------- |\n| `each(source, key, render, fallback?)` | Keyed reactive list; render receives `Readable<T>` and `Readable<number>`; plain `T[]` is a one-time static snapshot |\n| `when(condition, truthy, falsy?)` | Conditional rendering |\n| `classMap(record)` | Reactive class string from object map |\n| `styleMap(record)` | Reactive inline style string from object map |\n| `live(signal)` | One-way binding that skips stale writes during active user input; use with `@input` handler |\n| `unsafeHtml(value)` | HTML rendering sink; sanitize untrusted values before calling |\n\n### `unsafeHtml`\n\n`unsafeHtml()` is an explicit HTML injection sink. It has no global sanitizer: sanitize untrusted\ncontent before passing it to the directive, so the trust boundary remains at the call site.\n\n```ts\nimport { unsafeHtml } from '@vielzeug/ore';\n\nconst safeArticle = sanitize(userSuppliedArticle);\n\nreturn html`<article>${unsafeHtml(safeArticle)}</article>`;\n```\n\n## Host Bindings\n\n`bind(config, options?)` is a plain function imported from `@vielzeug/ore`:\n\n```ts\nbind({\n attr: { role: 'button', 'aria-expanded': () => String(open.value) },\n class: { 'is-open': open },\n style: { '--height': () => height.value + 'px' },\n on: { click: handleClick },\n});\n```\n\n`bind()` auto-registers cleanup with the component scope — no manual `onCleanup` needed. Returns a cleanup function for early teardown.\n\n### Off-host bindings\n\nPass `{ target: el }` as a second argument to bind to any element other than the host:\n\n```ts\nbind(\n { attr: { 'aria-expanded': () => String(isOpen.value) } },\n { target: triggerEl },\n);\n```\n\nEvent listener options (`once`, `capture`, `passive`) are also accepted in the second argument. Cleanup is auto-registered with the component scope when called during setup.\n\n### Reactive ARIA attributes\n\nFor reactive ARIA attribute syncing, use `bind({ aria: config }, { target })`. Shorthand keys are normalised to `aria-*` automatically (`expanded` → `aria-expanded`; `role` is passed verbatim):\n\n```ts\n// Inside setup — cleanup auto-registered\nbind(\n {\n aria: {\n expanded: () => isOpen.value,\n controls: panelId,\n haspopup: 'listbox',\n },\n },\n { target: triggerEl },\n);\n\n// Manage cleanup manually — bind() always returns a cleanup fn\nconst stopAria = bind({ aria: { expanded: () => isOpen.value } }, { target: triggerEl });\n// Call stopAria() when the trigger is swapped out\n```\n\nStatic values (strings, numbers, booleans) are applied once. Getter functions and signals create reactive effects. Setting a value to `null`, `undefined`, or `false` removes the attribute.\n\n## Slots\n\n- `slots.has(name?)` — `Readable<boolean>` — whether the named (or default) slot has assigned content\n- `slots.elements(name?)` — `Readable<Element[]>` — the assigned elements for the slot\n\nSlot signals update reactively when assigned content changes, including when slots are inserted dynamically (via `when()` or `each()`) after mount.\n\n## Context API\n\n- `createContext<T>(description?)` — Create a typed injection key\n- `provide(key, value)` — Provide a value to descendants\n- `inject(key)` — Resolve from nearest ancestor; returns `undefined` if not found\n- `inject(key, fallback)` — Resolve with a fallback value\n- `injectStrict(key)` — Resolve or throw if absent\n\n`provide()` and `inject()` must be called synchronously during `setup()`. Calling them outside a setup context throws\n`'Lifecycle hooks must be called during component setup'`. Context resolution walks the ancestor chain including shadow\nDOM boundaries. `inject()` resolves and caches its result once per consumer — provide a `Readable` (signal/computed)\nrather than a raw value if descendants need to observe later changes; re-calling `provide()` with a new raw value\nafterward is not seen by consumers that already resolved it (a dev-mode warning fires when a key is provided twice on\nthe same element). `provide()` registers cleanup automatically — context keys are removed from the registry when the\nproviding component disconnects, so reconnecting the same element runs `setup()` fresh without spurious \"overwriting\"\nwarnings or stale keys leaking to descendants.\n\n## Utilities\n\n- `ref<T>()` — Create a `Signal<T | null>` element reference. Set to the element via `ref=` in templates.\n- `createId(prefix = 'id')` — Generate a unique incremental string ID (e.g. `'id-1'`, `'id-2'`). Each call returns a new ID — it does not deduplicate by prefix.\n- `createStableId(prefix = 'id')` — Generate a unique ID that also embeds a short random tag shared across all IDs generated in the session (e.g. `'field-a3k21'`), reducing collision risk when multiple app instances run on the same page. Like `createId()`, every call returns a new ID.\n- `resetStableIdCounter()` — Reset the `createStableId()` counter to 0. Call in test `beforeEach` for deterministic IDs. Scoped to `createStableId()` only — `createId()` has no public reset (it's for uniqueness, not cross-test determinism).\n\n## Form-Associated API\n\nImport from `@vielzeug/ore`.\n\n### `useField(options)`\n\nWire a form-associated element to `ElementInternals`. Requires `formAssociated: true` on the component definition. The `disabled` state tracking via `internals.states` (CustomStateSet) is skipped with a dev warning if the API is unavailable in the current environment.\n\n```ts\ntype FormFieldOptions<T> = {\n disabled?: Readable<boolean>;\n /** Defaults to the host element active during setup. */\n el?: HTMLElement;\n /**\n * When true, a null/undefined value is submitted as '' instead of null,\n * keeping the field's key present in FormData even when the value is absent.\n * Only applies to the default toFormValue; ignored if toFormValue is provided.\n * @default false\n */\n emptyStringForNull?: boolean;\n /** Called when the ancestor <form> resets (see onFormReset) — restore local field state here. */\n onReset?: () => void;\n toFormValue?: (value: T) => File | FormData | string | null;\n /** Recomputed reactively and passed straight to internals.setValidity(). null = always valid. */\n validationMessage?: Readable<string>;\n validity?: Readable<ValidityStateFlags | null>;\n value: Signal<T> | Readable<T>;\n};\n\ntype FormFieldHandle = {\n checkValidity(): boolean;\n readonly internals: ElementInternals;\n reportValidity(): boolean;\n /** Set (non-empty message) or clear (empty string) a custom validity error. */\n setCustomValidity(message: string): void;\n};\n```\n\nPass `validity`/`validationMessage` to make `required`-style constraints participate in native constraint validation\nthrough `checkValidity()` and `reportValidity()`:\n\n```ts\nconst isBlank = (v: string) => v.trim() === '';\n\nuseField({\n validationMessage: computed(() => (required.value && isBlank(value.value) ? 'This field is required.' : '')),\n validity: computed(() => (required.value && isBlank(value.value) ? { valueMissing: true } : null)),\n value,\n});\n```\n\n## Testing APIs\n\nImport from `@vielzeug/ore/testing`.\n\n| API | Purpose |\n| ------------------------ | ------------------------------------------------------------------------------------------ |\n| `mount(setup, options?)` | Mount a component and return a test fixture |\n| `cleanup()` | Remove all mounted elements and reset test state |\n| `install(afterEach, options?)` | Register auto-cleanup; pass `{ formInternals: true }` to also install the `ElementInternals`/`FormData`/`<form>.reset()` jsdom polyfill (see below) |\n| `installFormInternalsPolyfill()` | Installs the form-internals polyfill directly (returns an `uninstall()` that restores every patched global). Usually called via `install(afterEach, { formInternals: true })` |\n| `walkFlatTree(root, visit)` | Walks the flat tree (expanding `<slot>` via `assignedElements()`) — for finding slotted content across a shadow boundary that `querySelectorAll()` can't cross |\n| `flush(options?)` | Drain reactive updates and animation frames |\n| `debugFlush()` | Run `flush()` with `console.debug` diagnostics |\n| `mock(tag, template?)` | Register a no-op stub custom element |\n| `renderHook(setup)` | Run lifecycle hooks in isolation; overload accepts `propDefs` as first arg for typed props |\n| `resetOreForTests()` | Reset styles and ID counters when mounting is managed manually |\n| `OreTimeoutError` | Error thrown when `flush()` cannot settle tracked Ore work |\n\n> **Test isolation:** `cleanup()` removes mounted elements and resets all cross-test Ore state (the stylesheet cache and ID counters) via `resetOreForTests()`. Call it in `afterEach` (or use `install()`) to prevent state leaking between tests.\n\nImport `within`, named dispatchers such as `fireClick`, and waits such as `waitUntil` or `waitForEvent` from\n`@vielzeug/assay`.\n\n> **Form-associated component testing:** jsdom implements none of the `ElementInternals` form-association API — `install(afterEach, { formInternals: true })` polyfills `setFormValue`/`setValidity`/`checkValidity`/`reportValidity`/`validationMessage`/`validity`/`states`, mixes `checkValidity`/`reportValidity`/`validity`/`validationMessage` onto the host element itself (real browsers do this for any `formAssociated: true` element), makes `FormData` collect a form-associated element's set value, and makes `<form>.reset()` invoke `formResetCallback()`. Every patch is a guarded no-op when its target already exists, and `installFormInternalsPolyfill()` returns an `uninstall()` that restores every patched global. The polyfill is opt-in (`{ formInternals: true }`) because the patches are global — suites without form-associated components shouldn't carry them. A downstream package (e.g. a component library built on `ore`) should rely on this instead of hand-rolling its own copy.\n\n#### `Fixture` interface\n\n```ts\ninterface Fixture<T extends HTMLElement = HTMLElement> {\n [Symbol.dispose](): void; // Delegates to dispose() — enables `using` declarations\n element: T;\n readonly disposed: boolean; // true after dispose() has been called\n readonly shadow: ShadowRoot | null;\n get<E extends Element>(selector: string): E;\n query<E extends Element>(selector: string): E | null;\n queryAll<E extends Element>(selector: string): E[];\n getByText<E extends Element>(text: string, selector?: string): E;\n queryByText<E extends Element>(text: string, selector?: string): E | null;\n queryAllByText<E extends Element>(text: string, selector?: string): E[];\n getByTestId<E extends Element>(testId: string): E;\n queryByTestId<E extends Element>(testId: string): E | null;\n queryAllByTestId<E extends Element>(testId: string): E[];\n attr(name: string, value: string | number | boolean): Promise<void>;\n attrs(record: Record<string, string | number | boolean>): Promise<void>;\n flush(options?: FlushOptions): Promise<void>;\n act(fn: () => unknown): Promise<void>;\n dispose(): void; // Removes the component from the DOM — idempotent\n}\n```\n\n#### `renderHook`\n\nUseful for testing composable lifecycle hooks (`onMounted`, `watchEffect`, `inject`, etc.) without a template. `onMounted`/`onCleanup`/`watchEffect`/... work exactly as inside a real `setup()`, since they resolve the same implicit current-component context:\n\n```ts\n// Without props\nconst { result, flush, dispose } = await renderHook(() => {\n const count = signal(0);\n onMounted(() => {\n count.value = 1;\n });\n return count;\n});\nexpect(result.value).toBe(1);\n\n// With typed props (prop-defs overload)\nconst { result } = await renderHook({ label: prop.string('hello'), count: prop.number(0) }, (props) => props.label);\nexpect(result.value).toBe('hello');\n```\n\n## Ripple Primitives\n\nOre does **not** re-export reactive primitives. Import them directly from `@vielzeug/ripple`:\n\n```ts\nimport { batch, computed, signal, watch } from '@vielzeug/ripple';\n```\n\nSee the [Ripple documentation](/ripple/) for the full API.\n\n## Lifecycle Events\n\n| Event | When |\n| ------------------ | ------------------------------------------------------------- |\n| `ore:connect` | After every `connectedCallback` (including reconnects) |\n| `ore:disconnect` | After `disconnectedCallback`, before component state is reset |\n| `ore:error` | When a lifecycle callback fails — bubbles, composed; detail is `OreLifecycleError` |\n\n## Types\n\n```ts\ntype PropDef<T> = {\n readonly default: T;\n readonly parse: (value: string | null) => T;\n reflect?: boolean;\n};\n\ntype PropsDef<T extends Record<string, unknown>> = {\n [K in keyof Required<T>]: PropDef<T[K & keyof T]>;\n};\n\ntype PropInputDefs = Record<string, PropDef<unknown>>;\n\n/**\n * Infer reactive props type from a PropInputDefs map.\n * Each entry becomes Readable<T> keyed by prop name.\n */\ntype InferProps<D extends PropInputDefs> = {\n readonly [K in keyof D]-?: Readable<InferPropValue<D[K]>>;\n};\n\n// Runtime hooks — all plain functions imported from '@vielzeug/ore', not fields on an object.\ntype OnMountedCallback = () => Cleanup | undefined;\ntype OnFormResetCallback = () => void;\n\ndeclare function onMounted(fn: OnMountedCallback): void; // DOM-ready callback; runs after each connection's render\ndeclare function onCleanup(fn: Cleanup): void; // Register teardown; called on disconnect\ndeclare function onElement<T extends HTMLElement>(\n ref: Readable<T | null>,\n callback: (el: T) => Cleanup | undefined,\n): () => void;\ndeclare function onEvent<K extends keyof HTMLElementEventMap>(\n target: EventTarget | null | undefined,\n event: K,\n listener: (e: HTMLElementEventMap[K]) => void,\n options?: AddEventListenerOptions,\n): void;\ndeclare function onEvent(\n target: EventTarget | null | undefined,\n event: string,\n listener: EventListener,\n options?: AddEventListenerOptions,\n): void;\ndeclare function onFormReset(fn: OnFormResetCallback): void; // Runs on every ancestor <form> reset; formAssociated only\ndeclare function watchEffect(fn: () => Cleanup | undefined): () => void; // Scoped reactive effect; auto-cleaned on disconnect\ndeclare function bind(config: HostBindConfig, options?: BindOptions): () => void; // Bindings for host or any target element\ndeclare function provide<T>(key: InjectionKey<T>, value: T): void; // Register a context value on the host element\ndeclare function inject<T>(key: InjectionKey<T>): T | undefined;\ndeclare function inject<T>(key: InjectionKey<T>, fallback: T): T;\ndeclare function getHost(): HTMLElement; // The current component's host element\ndeclare function useEmit<Emits extends Record<string, unknown> = Record<string, never>>(): EmitFn<Emits>;\ndeclare function useSlots<SlotNames extends string = string>(): ComponentSlots<SlotNames>;\n\ntype ComponentDefinition<Props extends Record<string, unknown> = Record<never, never>> = {\n formAssociated?: boolean;\n props?: PropsDef<Props>;\n setup: (props: InferProps<PropsDef<Props>>) => HTMLResult | null;\n shadow?: Partial<ShadowRootInit> | false; // false = light DOM\n styles?: (string | CSSStyleSheet | CSSResult)[];\n};\n\ntype HostBindingValue =\n | (() => string | number | boolean | null | undefined)\n | Readable<string | number | boolean | null | undefined>\n | string\n | number\n | boolean\n | null\n | undefined;\n\ntype ReflectConfig = Record<string, HostBindingValue>;\n\ntype HostBindConfig = {\n aria?: ReflectConfig;\n attr?: ReflectConfig;\n class?: (() => Record<string, boolean>) | Record<string, Readable<boolean> | (() => boolean) | boolean>;\n on?: Record<string, ((event: Event) => void) | undefined>;\n style?: Record<string, HostBindingValue>;\n};\n\ntype BindOptions = AddEventListenerOptions & {\n target?: Element;\n};\n\ntype HostBindFn = (config: HostBindConfig, options?: BindOptions) => () => void;\n\ntype ComponentSlots<S extends string = string> = {\n elements(name?: S): Readable<Element[]>;\n has(name?: S): Readable<boolean>;\n};\n\ntype Ref<T extends Element> = Signal<T | null>;\n\ntype RefCallback<T extends Element> = (el: T | null) => void;\n\ntype InjectionKey<T> = symbol & { readonly __ore_injection_key?: T };\n\ninterface HTMLResult {\n mount(\n parent: ParentNode,\n anchor: Node | null,\n registerCleanup: (fn: () => void) => void,\n ): Node[];\n}\n\ntype CSSResult = {\n content: string;\n toString(): string;\n};\n\ntype LiveBinding<T> = { readonly source: Readable<T> };\n\ntype EmitFn<T extends Record<string, unknown>> = {\n <K extends KeysWithoutDetail<T>>(event: K): boolean;\n <K extends Exclude<keyof T, KeysWithoutDetail<T>>>(event: K, detail: T[K]): boolean;\n};\n// KeysWithoutDetail is an internal helper type, not exported.\n\ntype FormFieldOptions<T = unknown> = {\n disabled?: Readable<boolean>;\n el?: HTMLElement;\n emptyStringForNull?: boolean;\n onReset?: () => void;\n toFormValue?: (value: T) => File | FormData | string | null;\n validationMessage?: Readable<string>;\n validity?: Readable<ValidityStateFlags | null>;\n value: Signal<T> | Readable<T>;\n};\n\ntype FormFieldHandle = {\n checkValidity: () => boolean;\n readonly internals: ElementInternals;\n reportValidity: () => boolean;\n setCustomValidity: (message: string) => void;\n};\n\ntype MutationObserverValue = {\n entries: MutationRecord[];\n latest: MutationRecord | null;\n};\n\n/** Phase in which a OreError occurred. */\ntype OreErrorPhase = 'each-reconcile' | 'form-reset' | 'mounted' | 'setup';\n```\n\n## Errors\n\n`OreError` is the base class for every Ore error class — `err instanceof OreError` catches all of them.\n\n- **`OreApiError`** — thrown when the `ore` API itself is misused: calling `define()` with a duplicate tag, calling a lifecycle hook (`inject`, `onMounted`, `onCleanup`, `onEvent`, …) outside of `setup()`, or passing an invalid prop definition to `define()`.\n- **`OreInternalError`** — thrown when an Ore invariant fails, indicating a package bug rather than invalid application code.\n- **`OreLifecycleError`** — reported in the `ore:error` event when component `setup()`, a mounted callback, a form-reset callback, or `each()` reconciliation fails. Extends `OreError` with:\n - `component: string` — the element's local name\n - `phase: OreErrorPhase` — `'setup'` | `'mounted'` | `'form-reset'` | `'each-reconcile'`\n - `cause: Error` — the original error thrown by `setup()`\n- **`OreTimeoutError`** — thrown by `flush()` (from `@vielzeug/ore/testing`) when pending Ore work does not settle before its timeout.\n\nLifecycle failures dispatch a bubbling, composed `ore:error` event whose `detail` is the `OreLifecycleError`. Setup\nfailures still rethrow their original error; mounted and form-reset callback failures are reported through the same\nevent so their remaining callbacks can continue.\n",
6
+ "usage": "---\ntitle: Ore — Usage Guide\ndescription: Practical Ore usage patterns for components, props, templates, slots, context, forms, Sentinel integration, and tests.\n---\n\n[[toc]]\n\n## Basic Usage\n\n`define(tag, definition)` registers a custom element.\n\nYour `setup()` function receives typed prop signals and returns an `HTMLResult` directly. Its state belongs to the\ncurrent connection: disconnect disposes it, and reconnecting the same element runs setup again.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html } from '@vielzeug/ore';\n\ndefine('status-chip', {\n setup() {\n const online = signal(true);\n\n return html`\n <button @click=${() => (online.value = !online.value)}>${() => (online.value ? 'Online' : 'Offline')}</button>\n `;\n },\n});\n```\n\nEverything besides `props` — lifecycle hooks, host bindings, context, slots, emit — is a plain function imported from `@vielzeug/ore`, called directly from `setup()` (or a composable it calls):\n\n```ts\nimport { define, getHost, html, bind, useEmit, useSlots } from '@vielzeug/ore';\n\ndefine('my-widget', {\n setup(_props) {\n const el = getHost(); // the host HTMLElement\n const emit = useEmit<{ close: undefined }>(); // typed event emitter\n const slots = useSlots<'header'>(); // reactive slot observation\n\n bind({ attr: { role: 'group' } }); // host binding helper (attr, class, style, on)\n\n return html`<slot></slot>`;\n },\n});\n```\n\n## signals and effects\n\nOre does not re-export ripple primitives — import them directly from `@vielzeug/ripple`.\n\n```ts\nimport { batch, computed, effect, signal, watch } from '@vielzeug/ripple';\n\nconst count = signal(0);\nconst doubled = computed(() => count.value * 2);\n\neffect(() => {\n console.log('doubled =', doubled.value);\n});\n\nwatch(count, (next, prev) => {\n console.log('count changed', prev, '->', next);\n});\n\nbatch(() => {\n count.value = 1;\n count.value = 2;\n});\n```\n\n## onMounted and lifecycle\n\nUse `onMounted()` for DOM-dependent initialization that must run after the template is mounted. Use `onElement(ref, cb)` for work tied to a specific DOM node. `onEvent()` attaches a listener that is automatically removed on disconnect.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html, onElement, onEvent, onMounted, ref, useSlots } from '@vielzeug/ore';\n\ndefine('deferred-init', {\n setup(_props) {\n const tabIndex = signal(0);\n const inputRef = ref<HTMLInputElement>();\n const slots = useSlots<'items'>();\n\n onMounted(() => {\n const items = slots.elements('items').value;\n console.log('Found', items.length, 'items');\n });\n\n onElement(inputRef, (input) => {\n input.focus();\n });\n\n onEvent(window, 'keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape') tabIndex.value = 0;\n });\n\n return html`<div><slot name=\"items\"></slot><input ref=${inputRef} /></div>`;\n },\n});\n```\n\n## prop definitions\n\nUse `prop.*` helpers for common cases, or raw `PropDef` objects for custom parsing or `reflect: false`.\n\n```ts\nimport { define, html, prop } from '@vielzeug/ore';\n\ndefine('x-button', {\n props: {\n label: prop.string('Button'),\n disabled: prop.bool(false),\n variant: prop.oneOf(['primary', 'secondary'] as const, 'primary'),\n count: prop.number(0),\n },\n setup(props) {\n return html`\n <button ?disabled=${props.disabled} data-variant=${props.variant}>${props.label} (${props.count})</button>\n `;\n },\n});\n```\n\n## template bindings\n\n`html` supports text, attributes, booleans, properties, events, refs, and nested templates.\n\n```ts\nimport { computed, signal } from '@vielzeug/ripple';\nimport { define, html, ref } from '@vielzeug/ore';\n\ndefine('profile-name', {\n setup() {\n const name = signal('Alice');\n const inputRef = ref<HTMLInputElement>();\n\n return html`\n <label title=${computed(() => 'Current: ' + name.value)}>Name</label>\n <input\n ref=${inputRef}\n value=${name}\n aria-label=${() => 'Current name ' + name.value}\n @input=${(event: Event) => {\n name.value = (event.target as HTMLInputElement).value;\n }} />\n <p>Hello ${name}</p>\n `;\n },\n});\n```\n\n## directives\n\nOre exports `each`, `classMap`, `styleMap`, `when`, `live`, and `unsafeHtml` from `@vielzeug/ore`. Use ordinary\nattribute bindings plus native event handlers for two-way input state; no special model directive is required.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { classMap, define, each, html, styleMap, when } from '@vielzeug/ore';\n\ndefine('task-list', {\n setup() {\n const tasks = signal([{ id: 1, text: 'Write tests' }]);\n const active = signal(true);\n\n return html`\n <ul\n class=\"${classMap({ ready: () => tasks.value.length > 0 })}\"\n style=${styleMap({ opacity: () => (active.value ? 1 : 0.5) })}>\n ${when(\n () => active.value,\n () => html`<li>Active</li>`,\n () => html`<li>Paused</li>`,\n )}\n ${each(\n tasks,\n (task) => task.id,\n (task) => html`<li>${() => task.value.text}</li>`,\n )}\n </ul>\n `;\n },\n});\n```\n\n### each() API\n\n`each(source, key, render, fallback?)` takes positional arguments:\n\n- **source** — signal, getter, or plain array\n- **key** — function returning a unique key per item\n- **render** — receives reactive `item` and `index` signals\n- **fallback** — optional, rendered when the list is empty\n\n```ts\neach(\n items,\n (item) => item.id,\n (item, index) => html`<li>#${index}: ${() => item.value.label}</li>`,\n () => html`<li>No items</li>`,\n);\n```\n\n## live form bindings\n\nUse `live(signal)` for inputs that should preserve in-progress user edits instead of overwriting the DOM on stale writes.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html, live } from '@vielzeug/ore';\n\ndefine('live-search', {\n setup() {\n const query = signal('');\n\n return html`\n <input value=${live(query)} @input=${(e: Event) => (query.value = (e.target as HTMLInputElement).value)} />\n `;\n },\n});\n```\n\n## host bindings\n\n`bind()` wires reactive attrs, classes, styles, and events to the host element.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { bind, define, html } from '@vielzeug/ore';\n\ndefine('x-toggle', {\n setup(_props) {\n const open = signal(false);\n\n bind({\n attr: { 'aria-expanded': () => String(open.value), role: 'button', tabindex: 0 },\n class: { 'is-open': open },\n on: { click: () => (open.value = !open.value) },\n });\n\n return html`<slot></slot>`;\n },\n});\n```\n\nThe `bind` config supports `attr`, `class`, `style`, and `on` sections.\n\n## ARIA bindings\n\nUse `bind({ aria: config }, { target })` to reactively sync ARIA attributes to any element. Shorthand keys are normalised to `aria-*` automatically — `expanded` becomes `aria-expanded`, `role` is set verbatim.\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { bind, define, html, onMounted } from '@vielzeug/ore';\n\ndefine('x-disclosure', {\n setup(_props) {\n const open = signal(false);\n const panelId = 'disclosure-panel';\n\n bind({\n attr: { role: 'button', tabindex: 0 },\n on: { click: () => (open.value = !open.value) },\n });\n\n onMounted(() => {\n const trigger = document.querySelector('#trigger') as HTMLElement;\n if (trigger) {\n // bind() registers cleanup automatically when called inside setup\n bind(\n {\n aria: {\n controls: panelId,\n expanded: () => String(open.value),\n haspopup: 'region',\n },\n },\n { target: trigger },\n );\n }\n });\n\n return html`<slot></slot>`;\n },\n});\n```\n\nStatic values are applied once. Getter functions create reactive effects. Setting a value to `null`, `undefined`, or `false` removes the attribute.\n\n`bind()` always returns a cleanup function. Use it to stop syncing early when a trigger element can be swapped out:\n\n```ts\nonMounted(() => {\n const trigger = document.querySelector('#trigger') as HTMLElement;\n const stopAria = bind({ aria: { expanded: () => String(open.value) } }, { target: trigger });\n\n // Stop syncing when the trigger is replaced\n onCleanup(stopAria);\n});\n```\n\n### Binding a non-host element with `bind()`\n\nPass `{ target: el }` as a second argument to bind attributes, classes, styles, or events to any element:\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { bind, define, html, onMounted, ref } from '@vielzeug/ore';\n\ndefine('button-wrapper', {\n setup(_props) {\n const visible = signal(false);\n const btnRef = ref<HTMLButtonElement>();\n\n onMounted(() => {\n const btn = btnRef.value;\n if (!btn) return;\n\n bind(\n {\n attr: { 'aria-pressed': () => String(visible.value) },\n on: { click: () => (visible.value = !visible.value) },\n },\n { target: btn },\n );\n });\n\n return html`<button ref=${btnRef}>Toggle</button>`;\n },\n});\n```\n\n## slots and emits\n\n```ts\nimport { define, html, useEmit, useSlots, when } from '@vielzeug/ore';\n\ndefine('card-with-footer', {\n setup(_props) {\n const slots = useSlots<'header' | 'footer'>();\n const emit = useEmit<{ action: undefined }>();\n\n return html`\n <div class=\"card\">\n <slot name=\"header\"></slot>\n <slot></slot>\n ${when(slots.has('footer'), () => html`<footer><slot name=\"footer\"></slot></footer>`)}\n </div>\n <button @click=${() => emit('action')}>Go</button>\n `;\n },\n});\n```\n\nPass a `SlotNames` type parameter to `useSlots<SlotNames>()` to get typed `slots.has()` and `slots.elements()` calls.\n\n## context provide/inject\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { createContext, define, html, injectStrict, provide } from '@vielzeug/ore';\n\nconst COUNT_CTX = createContext<ReturnType<typeof signal<number>>>('count');\n\ndefine('count-provider', {\n setup(_props) {\n const count = signal(0);\n provide(COUNT_CTX, count);\n\n return html`<button @click=${() => count.value++}><slot></slot></button>`;\n },\n});\n\ndefine('count-consumer', {\n setup() {\n const count = injectStrict(COUNT_CTX);\n\n return html`<p>Count: ${count}</p>`;\n },\n});\n```\n\n`provide()` registers cleanup automatically — context keys are removed from the registry when the providing component disconnects. On reconnect, `setup()` runs fresh and `provide()` re-registers without spurious \"overwriting\" warnings. Provide a `Readable` (signal/computed) rather than a raw value if descendants need to observe later changes — `inject()` resolves and caches the value once per consumer connection.\n\n## form-associated elements\n\n```ts\nimport { signal } from '@vielzeug/ripple';\nimport { define, html, prop } from '@vielzeug/ore';\nimport { useField } from '@vielzeug/ore';\n\ndefine('rating-input', {\n formAssociated: true,\n setup() {\n const value = signal(0);\n const field = useField({ value });\n\n return html`\n <button @click=${() => (value.value = 1)}>1</button>\n <button @click=${() => (value.value = 2)}>2</button>\n <button @click=${() => (value.value = 3)}>3</button>\n <button @click=${() => field.reportValidity()}>Validate</button>\n <p>Current: ${value}</p>\n `;\n },\n});\n```\n\n## Sentinel Observers\n\nUse `@vielzeug/sentinel` for reactive browser and DOM observations. Create element-dependent Sentinels inside `onMounted()` and dispose them with the component.\n\n```ts\nimport { define, html, onCleanup, onMounted, ref, watchEffect } from '@vielzeug/ore';\nimport { createElementSize, SentinelUnavailableError } from '@vielzeug/sentinel';\n\ndefine('x-observed', {\n setup(_props) {\n const boxRef = ref<HTMLDivElement>();\n\n onMounted(() => {\n const element = boxRef.value;\n if (!element) return;\n\n try {\n const size = createElementSize(element);\n\n watchEffect(() => {\n console.log(size.value?.width);\n });\n\n onCleanup(() => size.dispose());\n } catch (error) {\n if (!(error instanceof SentinelUnavailableError)) throw error;\n }\n });\n\n return html`<div ref=${boxRef}>Observe me</div>`;\n },\n});\n```\n\n## testing utilities\n\nImport from `@vielzeug/ore/testing`.\n\n```ts\nimport { afterEach, describe, expect, it } from 'vitest';\nimport { signal } from '@vielzeug/ripple';\nimport { fireClick } from '@vielzeug/assay';\nimport { html } from '@vielzeug/ore';\nimport { cleanup, mount } from '@vielzeug/ore/testing';\n\ndescribe('my-counter', () => {\n afterEach(cleanup);\n\n it('increments on click', async () => {\n let count!: ReturnType<typeof signal<number>>;\n const { query, act } = await mount(() => {\n count = signal(0);\n return html`<button @click=${() => count.value++}>${count}</button>`;\n });\n\n expect(query('button')?.textContent).toBe('0');\n\n await act(() => fireClick(query('button')!));\n\n expect(query('button')?.textContent).toBe('1');\n });\n});\n```\n\n## Framework Integration\n\nOre components are standard custom elements and work natively in any framework.\n\n::: code-group\n\n```tsx [React]\n// React 19+ supports custom elements natively.\nimport './x-toggle'; // wherever define('x-toggle', { ... }) is called\n\nfunction App() {\n return <x-toggle aria-label=\"Open menu\" />;\n}\n```\n\n```ts [Vue 3]\n<script setup lang=\"ts\">\nimport './x-toggle'; // wherever define('x-toggle', { ... }) is called\nimport { ref } from 'vue';\n\nconst open = ref(false);\n</script>\n\n<template>\n <x-toggle :aria-label=\"'Open menu'\" @click=\"open = !open\" />\n</template>\n```\n\n```svelte [Svelte]\n<script>\n import './x-toggle'; // wherever define('x-toggle', { ... }) is called\n\n function handleClick() {\n console.log('toggled');\n }\n</script>\n\n<x-toggle aria-label=\"Open menu\" on:click={handleClick} />\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\n### With Ripple\n\nImport ripple primitives directly from `@vielzeug/ripple` for standalone reactive state outside components.\n\n```ts\nimport { signal, computed } from '@vielzeug/ripple';\nimport { define, html } from '@vielzeug/ore';\n\n// Shared state created outside any component\nconst theme = signal<'light' | 'dark'>('light');\nconst isDark = computed(() => theme.value === 'dark');\n\ndefine('theme-toggle', {\n setup() {\n return html`\n <button @click=${() => (theme.value = isDark.value ? 'light' : 'dark')}>\n ${() =>\n isDark.value ? '<ore-icon name=\"sun\" size=\"16\"></ore-icon>' : '<ore-icon name=\"moon\" size=\"16\"></ore-icon>'}\n </button>\n `;\n },\n});\n```\n\n### With Forge\n\nUse `@vielzeug/forge` for typed form state. `useField()` remains intentionally narrow: it connects a form-associated\ncustom element to native `ElementInternals` without imposing submission, validation, or dirty-state policy.\n\n```ts\nimport { createForm } from '@vielzeug/forge';\nimport { define, html } from '@vielzeug/ore';\n\ndefine('signup-form', {\n setup(_props) {\n const form = createForm({ initialValues: { email: '' } });\n\n return html`\n <form\n @submit=${(event: SubmitEvent) => {\n event.preventDefault();\n void form.submit(async (values) => {\n console.log(values);\n });\n }}>\n <slot></slot>\n </form>\n `;\n },\n});\n```\n\n## Best Practices\n\n- Setup returns `html\\`...\\`` directly — not a function wrapping the template.\n- Use `watchEffect()` for reactive subscriptions tied to component lifetime — it auto-registers cleanup on disconnect.\n- Use `onElement(ref, cb)` instead of `onMounted` when the work is tied to a single DOM node.\n- Bind host attributes and classes via `bind()` rather than mutating the element directly.\n- Provide context at the nearest ancestor — avoid global context singletons.\n- Call `onCleanup()` for every resource allocated in `setup()` (WebSockets, intervals, external subscriptions).\n- Use `live(signal)` for form inputs to prevent clobbering user-in-progress edits.\n- Extract composable helper functions freely — `onMounted`/`onCleanup`/`bind`/... resolve the active component through implicit context, so they work from any function called (transitively) during `setup()`, with no need to pass them in as parameters.\n- Test component mounting and lifecycle with `@vielzeug/ore/testing`; import generic DOM events, queries, and waits\n from `@vielzeug/assay`.\n",
7
7
  "examples": "---\ntitle: Ore — Examples\ndescription: Practical examples and recipes for ore.\n---\n\n## Examples\n\n- [Counter Component](./examples/counter-component.md)\n- [Typed Props And Emits](./examples/typed-props-and-emits.md)\n- [Observers In onMounted()](./examples/observers-in-onmount.md)\n- [Search List With Directives](./examples/search-list-with-directives.md)\n- [Context Provider And Consumer](./examples/context-provider-and-consumer.md)\n- [Prop Helpers And Raw PropDef](./examples/propsof-builder-api.md)\n- [Form Associated Rating Input](./examples/form-associated-rating-input.md)\n- [Test Example With @vielzeug/ore/testing](./examples/test-example-at-vielzeug-ore-testing.md)\n"
8
8
  },
9
9
  "examples": [],
@@ -37,11 +37,6 @@
37
37
  "HostBindFn": "export {\n type BindOptions,\n bind,\n type HostBindConfig,\n type HostBindFn,\n type HostBindingValue,\n type ReflectConfig,\n} from './host-bind';",
38
38
  "HostBindingValue": "export {\n type BindOptions,\n bind,\n type HostBindConfig,\n type HostBindFn,\n type HostBindingValue,\n type ReflectConfig,\n} from './host-bind';",
39
39
  "ReflectConfig": "export {\n type BindOptions,\n bind,\n type HostBindConfig,\n type HostBindFn,\n type HostBindingValue,\n type ReflectConfig,\n} from './host-bind';",
40
- "intersectionObserver": "export { intersectionObserver } from './observers/intersection-observe';",
41
- "mediaObserver": "export { mediaObserver } from './observers/media-observe';",
42
- "MutationObserverValue": "export { type MutationObserverValue, mutationObserver } from './observers/mutation-observe';",
43
- "mutationObserver": "export { type MutationObserverValue, mutationObserver } from './observers/mutation-observe';",
44
- "resizeObserver": "export { resizeObserver } from './observers/resize-observe';",
45
40
  "InferProps": "export type { InferProps, PropDef, PropInputDefs, PropsDef } from './props';",
46
41
  "PropDef": "export type { InferProps, PropDef, PropInputDefs, PropsDef } from './props';",
47
42
  "PropInputDefs": "export type { InferProps, PropDef, PropInputDefs, PropsDef } from './props';",
@@ -0,0 +1,45 @@
1
+ {
2
+ "apiSource": "export { defineJobs } from './definitions.ts';\nexport { PostmasterDisposedError, PostmasterError, PostmasterJobError } from './errors.ts';\nexport { createPostmaster } from './postmaster.ts';\nexport type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';\n",
3
+ "docs": {
4
+ "index": "---\ntitle: Postmaster — Durable job outbox\ndescription: Typed durable job outbox with leased processing, retries, and dead-letter recovery for browser applications.\npackage: postmaster\ncategory: Async\nkeywords: [durable, outbox, jobs, retry, dead-letter, idempotency, indexeddb, lease]\nrelated: [courier, vault, sentinel, familiar, ripple]\nexports: [createPostmaster, defineJobs, createIndexedDbPostmasterStore, createMemoryPostmasterStore, PostmasterError, PostmasterDisposedError, PostmasterJobError]\nenvironments: [browser, node]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"postmaster\" />\n\n## Why Postmaster?\n\nApplication jobs that touch a remote service — posting a form, syncing state, sending analytics — must survive page reloads, resume later, retry according to an explicit policy, and retain terminal failures for recovery. Postmaster coordinates that delivery with typed job definitions, leased processing, and a dead-letter queue, all backed by IndexedDB.\n\n```ts\n// Before\nasync function createTodo(payload: { id: string; title: string }) {\n // Lost on reload. No retry. No recovery. Silent failure.\n await fetch('/api/todos', { method: 'POST', body: JSON.stringify(payload) });\n}\n\n// After\nimport { createPostmaster, defineJobs } from '@vielzeug/postmaster';\nimport { createIndexedDbPostmasterStore } from '@vielzeug/postmaster/indexeddb';\nimport { s } from '@vielzeug/spell';\n\nconst jobs = defineJobs({\n createTodo: {\n version: 1,\n validate: s.object({ id: s.string(), title: s.string() }),\n key: (p) => p.id,\n execute: async (payload, { key, signal }) => {\n await fetch('/api/todos', {\n method: 'POST',\n body: JSON.stringify(payload),\n headers: { 'Idempotency-Key': key },\n signal,\n });\n },\n },\n});\n\nconst store = createIndexedDbPostmasterStore({ name: 'my-app-outbox' });\nconst postmaster = createPostmaster({ jobs, store });\n\nawait postmaster.enqueue('createTodo', { id: crypto.randomUUID(), title: 'Buy milk' });\nawait postmaster.start();\n```\n\n| Feature | Postmaster | Ad hoc outbox | Familiar |\n| --- | --- | --- | --- |\n| Bundle size | <PackageInfo package=\"postmaster\" type=\"size\" /> | Application-defined | <PackageInfo package=\"familiar\" type=\"size\" /> |\n| Zero dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Survives page reload | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Leased cross-tab processing | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Dead-letter recovery | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Typed job payloads | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Postmaster when** application jobs must survive reloads, retry explicitly, and remain recoverable after terminal failure.\n\n**Consider Familiar when** jobs are CPU-bound, in-memory only, and never need to survive a page reload.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/postmaster\n```\n\n```sh [npm]\nnpm install @vielzeug/postmaster\n```\n\n```sh [yarn]\nyarn add @vielzeug/postmaster\n```\n\n:::\n\nFor browser persistence, also install `@vielzeug/vault` (a workspace peer of the IndexedDB adapter):\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/postmaster @vielzeug/vault\n```\n\n```sh [npm]\nnpm install @vielzeug/postmaster @vielzeug/vault\n```\n\n```sh [yarn]\nyarn add @vielzeug/postmaster @vielzeug/vault\n```\n\n:::\n\n## Quick Start\n\nDefine typed jobs, create a durable store, enqueue work, and start the processor. Dispose both the processor and the store when the page lifetime ends.\n\n```ts\nimport { createPostmaster, defineJobs } from '@vielzeug/postmaster';\nimport { createIndexedDbPostmasterStore } from '@vielzeug/postmaster/indexeddb';\n\nconst jobs = defineJobs({\n createTodo: {\n version: 1,\n validate: (v: unknown) => v as { id: string; title: string },\n key: (p) => p.id,\n execute: async (payload, { key, signal }) => {\n await fetch('/api/todos', {\n method: 'POST',\n body: JSON.stringify(payload),\n headers: { 'Idempotency-Key': key },\n signal,\n });\n },\n retry: { maxAttempts: 5, shouldRetry: () => true },\n },\n});\n\nconst store = createIndexedDbPostmasterStore({ name: 'my-app-outbox' });\nconst postmaster = createPostmaster({ jobs, store });\n\nawait postmaster.enqueue('createTodo', { id: crypto.randomUUID(), title: 'Buy milk' });\nawait postmaster.start();\n\n// On page unload:\nawait postmaster.dispose();\nawait store.dispose();\n```\n\n<div class=\"features-grid\">\n\n## Features\n\n- `defineJobs()` — Typed job registry with payload inference and validation.\n- `createPostmaster()` — Processor with leased claims, heartbeat renewal, and crash recovery.\n- `enqueue()` — Persist a job and wake the processor.\n- `flush()` — Process every available job until the queue is empty.\n- `retry()` / `remove()` — Recover or discard dead-letter jobs.\n- `tap()` — Typed runtime events for enqueued, started, completed, retry-scheduled, dead-lettered, removed, lease-lost, and processor-error.\n- `createIndexedDbPostmasterStore()` — Durable browser store backed by Vault IndexedDB.\n- `createMemoryPostmasterStore()` — Deterministic in-memory store for tests.\n\n</div>\n\n<div class=\"doc-links\">\n\n## Documentation\n\n- [**Usage Guide**](./usage.md)\n- [**API Reference**](./api.md)\n- [**Examples**](./examples.md)\n\n</div>\n\n<div class=\"see-also\">\n\n## See Also\n\n- [@vielzeug/courier](../courier/) — Perform the HTTP requests Postmaster jobs coordinate.\n- [@vielzeug/vault](../vault/) — IndexedDB storage primitive backing the durable store.\n- [@vielzeug/sentinel](../sentinel/) — Flush the outbox when the network returns.\n- [@vielzeug/familiar](../familiar/) — In-memory Web Worker pool for CPU-bound tasks.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
+ "api": "---\ntitle: Postmaster — API Reference\ndescription: Job definitions, processor, store contracts, events, errors, and entry points for Postmaster.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `defineJobs()` | Typed job registry with validation | Sync | Throws on invalid version, missing fields, or bad retry config |\n| `createPostmaster()` | Processor with leased claims and retry | Sync | Store is borrowed, not disposed with the processor |\n| `createIndexedDbPostmasterStore()` | Durable browser store | Sync | Requires `@vielzeug/vault` as a workspace peer |\n| `createMemoryPostmasterStore()` | Deterministic in-memory store | Sync | Use for tests only |\n| `PostmasterError` | Base class for package errors | Sync | Catch a subtype when recovery is specific |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/postmaster` | Job definitions, processor, store contract, events, errors |\n| `@vielzeug/postmaster/indexeddb` | Durable browser store backed by Vault IndexedDB |\n| `@vielzeug/postmaster/testing` | Deterministic in-memory store and test helpers |\n\n## Factories\n\n### `defineJobs()`\n\n```ts\nfunction defineJobs<const J extends JobDefinitions>(jobs: J): J;\n```\n\nReturns the job registry after validating each definition. Rejects invalid versions, missing `execute`/`key`, and retry configurations with non-positive `maxAttempts`.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `jobs` | `J extends JobDefinitions` | Map of job name to definition |\n\n**Returns:** `J` — the same registry, typed for payload inference.\n\n**Example**\n\n```ts\nimport { defineJobs } from '@vielzeug/postmaster';\n\nconst jobs = defineJobs({\n createTodo: {\n version: 1,\n validate: (v) => v as { id: string; title: string },\n key: (p) => p.id,\n execute: async (payload, { key, signal }) => {\n await fetch('/api/todos', {\n method: 'POST',\n body: JSON.stringify(payload),\n headers: { 'Idempotency-Key': key },\n signal,\n });\n },\n },\n});\n```\n\n---\n\n### `createPostmaster()`\n\n```ts\nfunction createPostmaster<J extends JobDefinitions>(options: CreatePostmasterOptions<J>): Postmaster<J>;\n```\n\nReturns a Postmaster processor that claims, executes, retries, and dead-letters jobs from the borrowed store.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `options.jobs` | `J` | Job registry from `defineJobs()` |\n| `options.store` | `PostmasterStore` | Borrowed store; not disposed with the processor |\n| `options.leaseDuration` | `number` | Lease duration in ms (default 30000, minimum 1000) |\n| `options.clock` | `() => number` | Deterministic clock for tests (default `Date.now`) |\n| `options.signal` | `AbortSignal` | External signal that disposes the processor |\n\n**Returns:** `Postmaster<J>`.\n\n**Example**\n\n```ts\nimport { createPostmaster } from '@vielzeug/postmaster';\nimport { createIndexedDbPostmasterStore } from '@vielzeug/postmaster/indexeddb';\n\nconst store = createIndexedDbPostmasterStore({ name: 'outbox' });\nconst postmaster = createPostmaster({ jobs, store });\n\nawait postmaster.start();\nawait postmaster.dispose();\nawait store.dispose();\n```\n\n---\n\n### `createIndexedDbPostmasterStore()`\n\n```ts\nfunction createIndexedDbPostmasterStore(options: { name: string }): PostmasterStore;\n```\n\nReturns a durable Postmaster store backed by Vault IndexedDB. Uses one internal table indexed by `status`, `availableAt`, and `leaseExpiresAt`. All operations run inside Vault transactions.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `options.name` | `string` | IndexedDB database name |\n\n**Returns:** `PostmasterStore`.\n\n**Example**\n\n```ts\nimport { createIndexedDbPostmasterStore } from '@vielzeug/postmaster/indexeddb';\n\nconst store = createIndexedDbPostmasterStore({ name: 'my-app-outbox' });\nawait store.dispose();\n```\n\n---\n\n### `createMemoryPostmasterStore()`\n\n```ts\nfunction createMemoryPostmasterStore(entries?: readonly StoredJob[]): PostmasterStore;\n```\n\nReturns a deterministic in-memory store for tests. Serializes all operations through a promise chain.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `entries` | `readonly StoredJob[]` | Initial records (default empty) |\n\n**Returns:** `PostmasterStore`.\n\n**Example**\n\n```ts\nimport { createMemoryPostmasterStore } from '@vielzeug/postmaster/testing';\n\nconst store = createMemoryPostmasterStore();\nawait store.dispose();\n```\n\n## Postmaster Methods\n\n### `enqueue()`\n\n```ts\nenqueue<K extends keyof J & string>(name: K, payload: InferJobPayload<J[K]>): Promise<PostmasterEntry>;\n```\n\nValidates the payload (if `validate` is defined), derives the key, persists the job, and wakes the processor. Throws `PostmasterError` for an empty key or non-JSON-serializable payload.\n\n---\n\n### `start()`\n\n```ts\nstart(): Promise<void>;\n```\n\nBegins background processing. Idempotent.\n\n---\n\n### `flush()`\n\n```ts\nflush(options?: { signal?: AbortSignal }): Promise<FlushResult>;\n```\n\nProcesses every available job until the queue is empty or the signal aborts. Concurrent `flush()` calls join the same drain. Returns counts of processed, completed, dead-lettered, and retry-scheduled jobs.\n\n---\n\n### `list()`\n\n```ts\nlist(filter?: EntryFilter): Promise<PostmasterEntry[]>;\n```\n\nReturns entries ordered by `createdAt`. Filter by `status` optionally.\n\n---\n\n### `stats()`\n\n```ts\nstats(): Promise<PostmasterStats>;\n```\n\nReturns counts of queued, running, and dead-letter jobs.\n\n---\n\n### `retry()`\n\n```ts\nretry(id: string): Promise<RetryResult>;\n```\n\nMoves a dead-letter job back to queued. Returns a discriminated result: `retried`, `not-found`, `not-dead-letter`, or `running`.\n\n---\n\n### `remove()`\n\n```ts\nremove(id: string): Promise<RemoveResult>;\n```\n\nDeletes a queued or dead-letter job. Returns a discriminated result: `removed`, `not-found`, or `running`.\n\n---\n\n### `tap()`\n\n```ts\ntap(handler: (event: PostmasterEvent) => void, options?: { signal?: AbortSignal }): () => void;\n```\n\nObserve runtime events (enqueued, started, completed, retry-scheduled, dead-lettered, removed, lease-lost, processor-error, dispose). Handler errors are swallowed — observability never affects processing. Returns an unsubscribe function. Pass `{ signal }` to auto-detach on abort.\n\n---\n\n### `dispose()`\n\n```ts\ndispose(): Promise<void>;\n[Symbol.asyncDispose](): Promise<void>;\n```\n\nAborts owned work, releases all active leases, and tears down subscriptions. Idempotent. Does not dispose the borrowed store.\n\n## Types\n\n### `JobDefinition<T>`\n\n```ts\ninterface JobDefinition<T> {\n readonly version: number;\n readonly validate?: Validate<T>;\n readonly key: (payload: T) => string;\n readonly execute: (payload: T, context: JobContext) => Promise<void>;\n readonly retry?: RetryPolicy;\n readonly migrate?: (payload: unknown, fromVersion: number) => unknown;\n}\n```\n\n`validate` is optional. Accepts a function `(value: unknown) => T` or any structural parser with `parse(value: unknown): T` (Spell schemas, Zod schemas, etc). Called once at enqueue. If omitted, payload trusted as-is.\n\n---\n\n### `Validate<T>`\n\n```ts\ntype Validate<T> = ((value: unknown) => T) | { parse(value: unknown): T };\n```\n\nAccepts either a plain validation function or any object with a `parse(value: unknown): T` method. Spell's `Schema` and `s.object(...)` satisfy this contract directly — no adapter needed.\n\n---\n\n### `JobContext`\n\n```ts\ninterface JobContext {\n readonly attempt: number;\n readonly entryId: string;\n readonly key: string;\n readonly signal: AbortSignal;\n}\n```\n\n---\n\n### `RetryPolicy`\n\n```ts\ninterface RetryPolicy {\n readonly maxAttempts: number;\n readonly shouldRetry: (error: unknown, attempt: number) => boolean;\n readonly delay?: (attempt: number) => number;\n}\n```\n\n`maxAttempts` is total executions including the first. `shouldRetry` is required when retries are enabled. Default delay uses Arsenal's `backoff(attempt)`.\n\n---\n\n### `StoredJob`\n\n```ts\ninterface StoredJob {\n readonly id: string;\n readonly name: string;\n readonly version: number;\n readonly payload: JsonValue;\n readonly key: string;\n readonly status: 'queued' | 'running' | 'dead-letter';\n readonly attempts: number;\n readonly createdAt: number;\n readonly updatedAt: number;\n readonly availableAt: number;\n readonly ownerId?: string;\n readonly leaseExpiresAt?: number;\n readonly failure?: StoredFailure;\n}\n```\n\n---\n\n### `StoredFailure`\n\n```ts\ninterface StoredFailure {\n readonly name: string;\n readonly message: string;\n readonly occurredAt: number;\n}\n```\n\nOnly a bounded error name/message/timestamp is persisted. Never persist arbitrary error objects, response bodies, headers, or stacks.\n\n---\n\n### `PostmasterEntry`\n\n```ts\ntype PostmasterEntry = Pick<StoredJob,\n 'attempts' | 'availableAt' | 'createdAt' | 'failure' | 'id' |\n 'key' | 'name' | 'status' | 'updatedAt' | 'version'\n>;\n```\n\nThe public entry view excludes `payload`, `ownerId`, and `leaseExpiresAt`.\n\n---\n\n### `PostmasterStore`\n\n```ts\ninterface PostmasterStore {\n transact<T>(fn: (tx: StoreTx) => Promise<T>): Promise<T>;\n list(filter?: EntryFilter): Promise<StoredJob[]>;\n subscribe(listener: () => void): () => void;\n dispose(): Promise<void>;\n readonly disposed: boolean;\n readonly disposalSignal: AbortSignal;\n [Symbol.asyncDispose](): Promise<void>;\n}\n\ninterface StoreTx {\n get(id: string): Promise<StoredJob | undefined>;\n put(entry: StoredJob): Promise<void>;\n delete(id: string): Promise<void>;\n findClaimable(now: number): Promise<StoredJob | undefined>;\n findNextWake(now: number): Promise<number | undefined>;\n countByStatus(): Promise<PostmasterStats>;\n}\n```\n\nThe store exposes transactional primitives. The processor owns all ownership and transition logic — stores implement storage, not the job state machine. `transact` wraps all operations in an atomic transaction. `findClaimable` returns the earliest eligible job (queued with `availableAt <= now`, or running with expired lease). `findNextWake` returns the earliest future wake time across queued and running jobs.\n\n---\n\n### `PostmasterEvent`\n\n```ts\ntype PostmasterEvent =\n | { readonly type: 'enqueued' | 'started' | 'completed' | 'retry-scheduled' | 'dead-lettered'; readonly entry: PostmasterEntry }\n | { readonly type: 'removed' | 'lease-lost'; readonly id: string }\n | { readonly type: 'processor-error'; readonly error: Error }\n | { readonly type: 'dispose' };\n```\n\n---\n\n### `FlushResult`\n\n```ts\ninterface FlushResult {\n readonly processed: number;\n readonly completed: number;\n readonly deadLettered: number;\n readonly retryScheduled: number;\n}\n```\n\n---\n\n### `RetryResult` / `RemoveResult`\n\n```ts\ntype RetryResult =\n | { readonly status: 'not-found' | 'not-dead-letter' | 'running' }\n | { readonly status: 'retried'; readonly entry: PostmasterEntry };\n\ntype RemoveResult =\n | { readonly status: 'not-found' | 'running' }\n | { readonly status: 'removed'; readonly id: string };\n```\n\n## Errors\n\n### `PostmasterError`\n\n```ts\nclass PostmasterError extends Error {\n constructor(message: string, options?: ErrorOptions);\n}\n```\n\nBase class for package-defined errors. Use `instanceof PostmasterError` to narrow to the hierarchy. Covers configuration errors, serialization errors, and store failures.\n\n---\n\n### `PostmasterDisposedError`\n\n```ts\nclass PostmasterDisposedError extends PostmasterError {}\n```\n\nThrown when a public method is called after disposal.\n\n---\n\n### `PostmasterJobError`\n\n```ts\nclass PostmasterJobError extends PostmasterError {}\n```\n\nThrown when a job definition is missing, a version is incompatible, or a migration fails. These errors move the job to dead-letter rather than rejecting the public call.\n",
6
+ "usage": "---\ntitle: Postmaster — Usage Guide\ndescription: Define durable jobs, process them with leases, retry failures, and recover dead-letter work.\n---\n\n[[toc]]\n\n## Basic Usage\n\nDefine typed jobs, create a durable store, enqueue work, and start the processor. Dispose both handles when the owner ends.\n\n```ts\nimport { createPostmaster, defineJobs } from '@vielzeug/postmaster';\nimport { createIndexedDbPostmasterStore } from '@vielzeug/postmaster/indexeddb';\n\nconst jobs = defineJobs({\n createTodo: {\n version: 1,\n validate: (v: unknown) => v as { id: string; title: string },\n key: (p) => p.id,\n execute: async (payload, { key, signal }) => {\n await fetch('/api/todos', {\n method: 'POST',\n body: JSON.stringify(payload),\n headers: { 'Idempotency-Key': key },\n signal,\n });\n },\n },\n});\n\nconst store = createIndexedDbPostmasterStore({ name: 'my-app-outbox' });\nconst postmaster = createPostmaster({ jobs, store });\n\nawait postmaster.enqueue('createTodo', { id: crypto.randomUUID(), title: 'Buy milk' });\nawait postmaster.start();\n\n// On page unload:\nawait postmaster.dispose();\nawait store.dispose();\n```\n\nThe store is borrowed by `createPostmaster()` and is not disposed with the processor. Dispose both explicitly.\n\n## At-least-once delivery and idempotency\n\nPostmaster provides **at-least-once delivery**. A crash after the remote write but before local completion can repeat the job. Every job must derive a stable idempotency key, and handlers must send or otherwise enforce that key.\n\n```ts\nconst jobs = defineJobs({\n createTodo: {\n version: 1,\n validate: (v: unknown) => v as { id: string; title: string },\n key: (p) => p.id,\n execute: async (payload, { key, signal }) => {\n await fetch('/api/todos', {\n method: 'POST',\n body: JSON.stringify(payload),\n headers: { 'Idempotency-Key': key },\n signal,\n });\n },\n },\n});\n```\n\nNever assume exactly-once execution. Design handlers so a repeated delivery is safe.\n\n## Postmaster jobs vs Courier mutations\n\nCourier performs immediate HTTP requests and cache reconciliation. Postmaster coordinates durable delivery. Use Courier inside a Postmaster job when the write must survive reloads.\n\n```ts\nimport { createCourier, CourierNetworkError } from '@vielzeug/courier';\nimport { createPostmaster, defineJobs } from '@vielzeug/postmaster';\n\nconst courier = createCourier({ baseUrl: 'https://api.example.com' });\n\nconst jobs = defineJobs({\n createTodo: {\n version: 1,\n validate: (v: unknown) => v as { id: string; title: string },\n key: (p) => p.id,\n execute: async (payload, { key, signal }) => {\n await courier.mutate({\n request: () =>\n courier.post('/todos', {\n body: payload,\n headers: { 'Idempotency-Key': key },\n signal,\n }),\n invalidateKeys: [['todos']],\n });\n },\n retry: { maxAttempts: 5, shouldRetry: (error) => error instanceof CourierNetworkError },\n },\n});\n```\n\nPostmaster does not import Courier. The integration happens in your job definitions.\n\n## Payload and version migration\n\nEach job declares a `version` and an optional `validate` function. When a stored job's version is older than the registered version, Postmaster calls `migrate()` before validating. `validate` is called once at enqueue; omit it to accept the payload as-is. `validate` accepts a plain function `(value: unknown) => T` or any structural parser with `parse(value: unknown): T` — Spell schemas work directly:\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst jobs = defineJobs({\n createTodo: {\n version: 2,\n validate: s.object({ id: s.string(), title: s.string(), priority: s.number().optional() }),\n key: (p) => p.id,\n migrate: (payload, fromVersion) => {\n if (fromVersion === 1) return { ...(payload as { id: string; title: string }), priority: 0 };\n return payload;\n },\n execute: async (payload, { key, signal }) => {\n await fetch('/api/todos', {\n method: 'POST',\n body: JSON.stringify(payload),\n headers: { 'Idempotency-Key': key },\n signal,\n });\n },\n },\n});\n```\n\nUnknown job names, incompatible versions, failed migrations, and invalid persisted payloads move to dead-letter rather than being executed.\n\n## Retry semantics\n\nRetries are opt-in and explicitly classified. No `retry` block means one attempt followed by dead-letter.\n\n```ts\nconst jobs = defineJobs({\n syncTodo: {\n version: 1,\n validate: (v: unknown) => v as { id: string },\n key: (p) => p.id,\n execute: async (payload, { signal }) => {\n await fetch(`/api/todos/${payload.id}/sync`, { signal });\n },\n retry: {\n maxAttempts: 5,\n shouldRetry: (error) => error instanceof TypeError, // network errors only\n },\n },\n});\n```\n\n- `maxAttempts` means total executions, including the first.\n- `shouldRetry` is required when retries are enabled. Postmaster never guesses whether a write is safe to repeat.\n- Default delay uses Arsenal's deterministic `backoff(attempt)` helper. Override with `delay`.\n- Delay must be finite and non-negative.\n- Lifecycle aborts caused by disposal are not classified as job failures.\n\n## Dead-letter recovery\n\nJobs that exhaust retries or hit a terminal failure move to dead-letter. Inspect, retry, or remove them.\n\n```ts\nconst deadLettered = await postmaster.list({ status: 'dead-letter' });\n\nfor (const entry of deadLettered) {\n console.log(entry.id, entry.name, entry.failure);\n}\n\n// Retry a dead-letter job back into the queue.\nawait postmaster.retry(entry.id);\n\n// Or remove it permanently.\nawait postmaster.remove(entry.id);\n```\n\n`retry()` and `remove()` return discriminated results so callers can distinguish `not-found`, `not-dead-letter`, `running`, and successful outcomes without exceptions.\n\n## Lifecycle and disposal\n\n`start()` begins background processing. `dispose()` stops claiming new work, aborts owned work, and is idempotent. `flush()` processes every available job synchronously.\n\n```ts\nawait postmaster.start();\n// ...on unload\nawait postmaster.dispose();\nawait store.dispose();\n```\n\nDisposal aborts owned work, releases the active lease, and is idempotent. A controlled disposal abort does not consume the attempt — the job returns to queued.\n\n## Events\n\nTap runtime events for observability. Handler errors are swallowed — observability never affects processing.\n\n```ts\nconst unsubscribe = postmaster.tap((event) => {\n switch (event.type) {\n case 'enqueued':\n console.log('enqueued', event.entry.id);\n break;\n case 'completed':\n console.log('completed', event.entry.id);\n break;\n case 'dead-lettered':\n console.error('dead-lettered', event.entry.id, event.entry.failure);\n break;\n case 'processor-error':\n console.error('processor error', event.error);\n break;\n }\n});\n```\n\nPass an `AbortSignal` to auto-detach:\n\n```ts\nconst controller = new AbortController();\npostmaster.tap(handler, { signal: controller.signal });\ncontroller.abort(); // stops tapping\n```\n\n## Testing\n\nUse the in-memory store for deterministic tests.\n\n```ts\nimport { createPostmaster, defineJobs } from '@vielzeug/postmaster';\nimport { createMemoryPostmasterStore } from '@vielzeug/postmaster/testing';\n\nconst store = createMemoryPostmasterStore();\nconst postmaster = createPostmaster({\n jobs: defineJobs({\n send: {\n version: 1,\n validate: (v: unknown) => String(v),\n key: (p) => p,\n execute: async () => {},\n },\n }),\n store,\n});\n\nawait postmaster.enqueue('send', 'hello');\nawait postmaster.flush();\nawait postmaster.dispose();\n```\n\nInject a deterministic clock to control retry scheduling.\n\n```ts\nlet now = 0;\nconst postmaster = createPostmaster({ clock: () => now, jobs, store });\n```\n\n## Framework Integration\n\nCreate the Postmaster after the component mounts, start processing, and dispose on unmount.\n\n::: code-group\n\n```tsx [React]\nimport { createIndexedDbPostmasterStore } from '@vielzeug/postmaster/indexeddb';\nimport { createPostmaster, defineJobs, type Postmaster } from '@vielzeug/postmaster';\nimport { useEffect } from 'react';\n\nconst jobs = defineJobs({\n sync: {\n version: 1,\n validate: (v: unknown) => v as { id: string },\n key: (p) => p.id,\n execute: async (payload, { signal }) => {\n await fetch(`/api/sync/${payload.id}`, { signal });\n },\n },\n});\n\nexport function OutboxProvider() {\n useEffect(() => {\n const store = createIndexedDbPostmasterStore({ name: 'outbox' });\n const postmaster = createPostmaster({ jobs, store });\n void postmaster.start();\n\n return () => {\n void postmaster.dispose();\n void store.dispose();\n };\n }, []);\n\n return null;\n}\n```\n\n```vue [Vue 3]\n<script setup lang=\"ts\">\nimport { createIndexedDbPostmasterStore } from '@vielzeug/postmaster/indexeddb';\nimport { createPostmaster, defineJobs } from '@vielzeug/postmaster';\nimport { onMounted, onUnmounted } from 'vue';\n\nconst jobs = defineJobs({\n sync: {\n version: 1,\n validate: (v: unknown) => v as { id: string },\n key: (p) => p.id,\n execute: async (payload, { signal }) => {\n await fetch(`/api/sync/${payload.id}`, { signal });\n },\n },\n});\n\nlet postmaster: ReturnType<typeof createPostmaster> | undefined;\nlet store: ReturnType<typeof createIndexedDbPostmasterStore> | undefined;\n\nonMounted(() => {\n store = createIndexedDbPostmasterStore({ name: 'outbox' });\n postmaster = createPostmaster({ jobs, store });\n void postmaster.start();\n});\n\nonUnmounted(() => {\n void postmaster?.dispose();\n void store?.dispose();\n});\n</script>\n\n<template>\n <slot />\n</template>\n```\n\n```svelte [Svelte]\n<script lang=\"ts\">\n import { createIndexedDbPostmasterStore } from '@vielzeug/postmaster/indexeddb';\n import { createPostmaster, defineJobs } from '@vielzeug/postmaster';\n import { onMount } from 'svelte';\n\n const jobs = defineJobs({\n sync: {\n version: 1,\n validate: (v: unknown) => v as { id: string },\n key: (p) => p.id,\n execute: async (payload, { signal }) => {\n await fetch(`/api/sync/${payload.id}`, { signal });\n },\n },\n });\n\n onMount(() => {\n const store = createIndexedDbPostmasterStore({ name: 'outbox' });\n const postmaster = createPostmaster({ jobs, store });\n void postmaster.start();\n\n return () => {\n void postmaster.dispose();\n void store.dispose();\n };\n });\n</script>\n\n<slot />\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\n### Postmaster + Courier\n\nUse Courier inside job handlers for HTTP transport and cache invalidation. Postmaster coordinates delivery; Courier performs the request.\n\n```ts\nimport { createCourier, CourierNetworkError } from '@vielzeug/courier';\nimport { createPostmaster, defineJobs } from '@vielzeug/postmaster';\n\nconst courier = createCourier({ baseUrl: 'https://api.example.com' });\n\nconst jobs = defineJobs({\n createTodo: {\n version: 1,\n validate: (v: unknown) => v as { id: string; title: string },\n key: (p) => p.id,\n execute: async (payload, { key, signal }) => {\n await courier.mutate({\n request: () =>\n courier.post('/todos', {\n body: payload,\n headers: { 'Idempotency-Key': key },\n signal,\n }),\n invalidateKeys: [['todos']],\n });\n },\n retry: { maxAttempts: 5, shouldRetry: (e) => e instanceof CourierNetworkError },\n },\n});\n```\n\n### Postmaster + Sentinel\n\nFlush the outbox when the network returns. Sentinel reports online state; Postmaster does the rest.\n\n```ts\nimport { createNetwork } from '@vielzeug/sentinel';\nimport { createPostmaster } from '@vielzeug/postmaster';\n\nconst network = createNetwork();\nconst postmaster = createPostmaster({ jobs, store });\n\nconst unsubscribe = network.subscribe(() => {\n if (network.value.online) void postmaster.flush();\n});\n\n// On teardown:\nunsubscribe();\nnetwork.dispose();\nawait postmaster.dispose();\n```\n\n### Postmaster + Vault\n\nThe IndexedDB adapter is built on Vault. Use Vault directly for unrelated storage; the Postmaster store owns its own database name.\n\n## Best Practices\n\n- **Derive** a stable idempotency key from every job payload and send it with the remote write.\n- **Dispose** both the processor and the store explicitly; the processor does not own the store.\n- **Classify** retryable errors explicitly with `shouldRetry`; never let Postmaster guess.\n- **Migrate** persisted payloads when job versions change; test migrations against stored fixtures.\n- **Inspect** the dead-letter queue regularly and retry or remove terminal failures.\n- **Avoid** persisting sensitive data in payloads or failure messages; IndexedDB is per-origin but not encrypted.\n- **Flush** the outbox when Sentinel reports the network returns.\n- **Test** with the in-memory store and a deterministic clock for reproducible retry timing.\n",
7
+ "examples": "---\ntitle: Postmaster — Examples\ndescription: Durable outbox recipes for offline mutations, network recovery, and dead-letter handling.\n---\n\n## Examples\n\n- [Queue Offline Courier Mutations](./examples/queue-offline-courier-mutations.md)\n- [Resume When Network Returns](./examples/resume-when-network-returns.md)\n- [Recover Dead-Letter Jobs](./examples/recover-dead-letter-jobs.md)\n- [Service Worker Background Sync](./examples/service-worker-background-sync.md)\n"
8
+ },
9
+ "examples": [
10
+ {
11
+ "id": "define-jobs",
12
+ "code": "import { createPostmaster, defineJobs } from '@vielzeug/postmaster'\nimport { createMemoryPostmasterStore } from '@vielzeug/postmaster/testing'\n\nconst jobs = defineJobs({\n send: {\n version: 1,\n validate: (v) => String(v),\n key: (p) => `send:${p}`,\n execute: async (payload, { key, attempt }) => {\n console.log(`delivering \"${payload}\" (attempt ${attempt}, key ${key})`)\n },\n },\n})\n\nconst store = createMemoryPostmasterStore()\nconst postmaster = createPostmaster({ jobs, store })\n\nawait postmaster.enqueue('send', 'hello')\nconst result = await postmaster.flush()\nconsole.log('flush result:', result)\nawait postmaster.dispose()",
13
+ "name": "defineJobs - Basic Outbox"
14
+ }
15
+ ],
16
+ "typeSignatures": {
17
+ "defineJobs": "export { defineJobs } from './definitions.ts';",
18
+ "PostmasterDisposedError": "export { PostmasterDisposedError, PostmasterError, PostmasterJobError } from './errors.ts';",
19
+ "PostmasterError": "export { PostmasterDisposedError, PostmasterError, PostmasterJobError } from './errors.ts';",
20
+ "PostmasterJobError": "export { PostmasterDisposedError, PostmasterError, PostmasterJobError } from './errors.ts';",
21
+ "createPostmaster": "export { createPostmaster } from './postmaster.ts';",
22
+ "CreatePostmasterOptions": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
23
+ "EntryFilter": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
24
+ "EntryStatus": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
25
+ "FlushResult": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
26
+ "InferJobPayload": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
27
+ "JobContext": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
28
+ "JobDefinition": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
29
+ "JobDefinitions": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
30
+ "JsonPrimitive": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
31
+ "JsonValue": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
32
+ "Postmaster": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
33
+ "PostmasterEntry": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
34
+ "PostmasterEvent": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
35
+ "PostmasterStats": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
36
+ "PostmasterStore": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
37
+ "RemoveResult": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
38
+ "RetryPolicy": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
39
+ "RetryResult": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
40
+ "StoredFailure": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
41
+ "StoredJob": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
42
+ "StoreTx": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';",
43
+ "Validate": "export type {\n CreatePostmasterOptions,\n EntryFilter,\n EntryStatus,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n JsonPrimitive,\n JsonValue,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n PostmasterStore,\n RemoveResult,\n RetryPolicy,\n RetryResult,\n StoredFailure,\n StoredJob,\n StoreTx,\n Validate,\n} from './types.ts';"
44
+ }
45
+ }