@vielzeug/codex 2.2.7 → 2.2.9

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.
Files changed (43) hide show
  1. package/data/catalog.json +1842 -0
  2. package/data/llms-full.txt +30872 -0
  3. package/data/llms.txt +44 -0
  4. package/data/manifest.json +8 -0
  5. package/data/packages/arsenal.json +210 -0
  6. package/data/packages/assay.json +39 -0
  7. package/data/packages/clockwork.json +67 -0
  8. package/data/packages/codex.json +43 -0
  9. package/data/packages/coins.json +102 -0
  10. package/data/packages/conduit.json +60 -0
  11. package/data/packages/courier.json +58 -0
  12. package/data/packages/dnd.json +77 -0
  13. package/data/packages/familiar.json +40 -0
  14. package/data/packages/flux.json +93 -0
  15. package/data/packages/focus.json +37 -0
  16. package/data/packages/forge.json +83 -0
  17. package/data/packages/gesture.json +25 -0
  18. package/data/packages/herald.json +108 -0
  19. package/data/packages/illusionist.json +132 -0
  20. package/data/packages/keymap.json +60 -0
  21. package/data/packages/ledger.json +57 -0
  22. package/data/packages/lingua.json +68 -0
  23. package/data/packages/necromancer.json +50 -0
  24. package/data/packages/orbit.json +99 -0
  25. package/data/packages/ore.json +68 -0
  26. package/data/packages/prism.json +66 -0
  27. package/data/packages/pulse.json +69 -0
  28. package/data/packages/refine.json +12 -0
  29. package/data/packages/ripple.json +83 -0
  30. package/data/packages/rune.json +79 -0
  31. package/data/packages/sandbox.json +40 -0
  32. package/data/packages/scout.json +60 -0
  33. package/data/packages/scroll.json +109 -0
  34. package/data/packages/sentinel.json +35 -0
  35. package/data/packages/sourcerer.json +73 -0
  36. package/data/packages/spell.json +133 -0
  37. package/data/packages/tempo.json +81 -0
  38. package/data/packages/vault.json +85 -0
  39. package/data/packages/ward.json +114 -0
  40. package/data/packages/wayfinder.json +110 -0
  41. package/data/refine.json +11887 -0
  42. package/data/search.json +1556 -0
  43. package/package.json +1 -1
@@ -0,0 +1,69 @@
1
+ {
2
+ "apiSource": "export {\n PulseAbortError,\n PulseConnectionError,\n PulseDisposedError,\n PulseError,\n PulseProtocolError,\n PulseRoomTimeoutError,\n PulseTimeoutError,\n} from './errors';\nexport { createPulse } from './pulse';\nexport type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';\n",
3
+ "docs": {
4
+ "index": "---\ntitle: Pulse — Typed WebSocket sessions\ndescription: Explicitly connected, typed WebSocket sessions with scoped channels, ref-counted rooms with reactive presence, reconnect restoration, and heartbeat.\npackage: pulse\ncategory: websockets\nkeywords: [websocket, realtime, channels, presence, rooms, reconnect, heartbeat, typed-messaging, ripple]\nrelated: [herald, ripple, courier, clockwork]\nexports:\n [\n createPulse,\n Pulse,\n PulseChannel,\n RoomScope,\n RoomScopeBase,\n PresenceRoomScope,\n PulseOptions,\n PulseSchema,\n ChannelDefinition,\n ChannelDefinitions,\n RoomDefinition,\n RoomDefinitions,\n RoomOptions,\n OutgoingMessage,\n OutgoingTransform,\n PulseError,\n PulseConnectionError,\n PulseTimeoutError,\n PulseRoomTimeoutError,\n PulseAbortError,\n PulseDisposedError,\n PulseProtocolError,\n ]\nenvironments: [browser, node]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"pulse\" />\n\n## Why Pulse?\n\nNative WebSocket leaves connection ownership, event routing, reconnect restoration, and cleanup to each application. Pulse provides those boundaries while making readiness explicit: applications connect before sending, and disconnected messages never disappear silently.\n\n```ts\n// Before\nconst socket = new WebSocket('wss://api.example.com/ws');\nsocket.addEventListener('message', (event) => route(JSON.parse(event.data)));\nsocket.addEventListener('close', () => setTimeout(() => reconnect(), 1_000));\n\n// After\nconst pulse = createPulse<{ server: { 'chat:message': { text: string } }; client: { 'chat:send': { text: string } } }>(\n 'wss://api.example.com/ws',\n { reconnect: true },\n);\ntry {\n await pulse.connect();\n pulse.on('chat:message', (message) => console.log(message.text));\n pulse.send('chat:send', { text: 'Hello!' });\n} catch (error) {\n console.error('Pulse connection failed:', error);\n}\n```\n\n| Feature | Pulse | Native WebSocket | socket.io-client |\n| --- | --- | --- | --- |\n| Bundle size | <PackageInfo package=\"pulse\" type=\"size\" /> | 0 B | ~44 kB gzip |\n| Explicit readiness | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Manual | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Session restoration | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | Protocol-specific |\n| Typed scoped channels | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | Basic |\n| Typed rooms with presence | <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| Zero runtime dependencies | <ore-icon name=\"triangle-alert\" size=\"16\"></ore-icon> ripple | <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 Pulse when** you need a typed WebSocket session whose reconnect and cleanup behavior must be deterministic.\n\n**Consider native WebSocket when** a single untyped connection does not need retry, routing, or session restoration.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/pulse @vielzeug/ripple\n```\n\n```sh [npm]\nnpm install @vielzeug/pulse @vielzeug/ripple\n```\n\n```sh [yarn]\nyarn add @vielzeug/pulse @vielzeug/ripple\n```\n\n:::\n\n## Quick Start\n\nDefine the protocol schema at construction time, create scopes, then connect before sending.\n\n```ts\nimport { createPulse } from '@vielzeug/pulse';\n\ntype Schema = {\n server: { 'chat:message': { text: string } };\n client: { 'chat:send': { text: string } };\n channels: {\n chat: {\n client: { send: { text: string } };\n server: { message: { text: string } };\n };\n };\n rooms: {\n lobby: { presence: { name: string } };\n };\n};\n\nconst pulse = createPulse<Schema>('wss://api.example.com/ws', {\n reconnect: true,\n onError: (error) => console.error(error),\n});\nconst chat = pulse.channel('chat');\nconst lobby = pulse.room('lobby');\n\ntry {\n await pulse.connect();\n chat.send('send', { text: 'Hello!' });\n await lobby.joined;\n lobby.updatePresence({ name: 'Ada' });\n} catch (error) {\n console.error('Pulse connection failed:', error);\n}\n\npulse.dispose();\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- **`connect()`** — explicit readiness; application messages throw while disconnected.\n- **`channel()`** — named, schema-bound scopes with independent disposal and reference-counted server subscriptions.\n- **`room()`** — named, schema-bound ref-counted room scopes with optional reactive presence. The first scope sends `join`; the last disposal sends `leave`.\n- **`reconnect`** — ordered restoration of channel subscriptions, room memberships, and local presence state.\n- **`transform`** — one synchronous transform or filter for application messages.\n- **`onError`** — typed connection and protocol errors.\n- **`heartbeat`** — ping/pong liveness detection that uses the same reconnect controller.\n- **`status` and `rooms`** — ripple readables for transport and confirmed membership state.\n\n</div>\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- [Ripple](/ripple/) — provides the reactive values exposed by Pulse.\n- [Herald](/herald/) — receives routed Pulse events in an in-process application bus.\n- [Courier](/courier/) — handles request/response traffic alongside a Pulse session.\n- [Clockwork](/clockwork/) — models application-level authentication or session workflows.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
+ "api": "---\ntitle: API — Pulse\ndescription: Complete API reference for Pulse, including schema types, options, scopes, and error classes.\npackage: pulse\ncategory: websockets\n---\n\n<!-- markdownlint-disable MD025 -->\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `createPulse()` | Create a typed WebSocket session instance. | Sync (returns `Pulse`) | Does not open the connection — call `connect()`. |\n| `Pulse` | Main instance: channels, rooms, messaging, lifecycle. | Sync methods, async `connect()`/`wait()` | `send()` throws while disconnected. |\n| `PulseChannel` | Scoped channel namespace with independent disposal. | Sync methods, async `wait()` | Each call returns a new scope; ref-counted subscription. |\n| `RoomScope` | Ref-counted room membership with optional presence. | Sync methods, async `joined` | `joined` rejects on transport close or timeout. |\n| `PulseSchema` | Declares server/client events, channels, and rooms. | Type-only | Infer all named scope types from this schema. |\n| `PulseOptions` | Configuration: heartbeat, reconnect, transform, onError. | Type-only | `reconnect` and `heartbeat` default to `false`. |\n| `PulseError` | Base class for all Pulse errors. | Runtime | Check `instanceof` against subclasses. |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/pulse` | All public exports: `createPulse`, types, and error classes. |\n\n## `createPulse()`\n\n```ts\nfunction createPulse<S extends PulseSchema = PulseSchema>(url: string, options?: PulseOptions): Pulse<S>\n```\n\nCreates a Pulse instance. The WebSocket is not opened until `connect()` is called.\n\n### Type parameters\n\n| Parameter | Constraint | Description |\n| --- | --- | --- |\n| `S` | `PulseSchema` | Schema declaring server events, client events, channels, and rooms. |\n\n### Parameters\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `url` | `string` | WebSocket URL. |\n| `options` | `PulseOptions` | Optional configuration. |\n\n### Returns\n\n`Pulse<S>` — the Pulse instance.\n\n---\n\n## `PulseSchema`\n\n```ts\ntype PulseSchema = {\n server?: MessageMap;\n client?: MessageMap;\n channels?: ChannelDefinitions;\n rooms?: RoomDefinitions;\n};\n```\n\nDeclare all protocol surfaces once at construction. Named scopes infer their types from this schema.\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `server` | `MessageMap` | Root events the server sends. |\n| `client` | `MessageMap` | Root events the client sends. |\n| `channels` | `ChannelDefinitions` | Named channel schemas. |\n| `rooms` | `RoomDefinitions` | Named room schemas with optional presence. |\n\n---\n\n## `PulseOptions`\n\n```ts\ntype PulseOptions = {\n heartbeat?: boolean | HeartbeatOptions;\n onError?: (error: PulseError) => void;\n protocols?: string | string[];\n reconnect?: boolean | ReconnectOptions;\n transform?: OutgoingTransform;\n};\n```\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `heartbeat` | `boolean \\| HeartbeatOptions` | `false` | Ping/pong keep-alive. |\n| `onError` | `(error: PulseError) => void` | — | Receives typed transport and protocol errors. |\n| `protocols` | `string \\| string[]` | — | Sub-protocols passed to the WebSocket constructor. |\n| `reconnect` | `boolean \\| ReconnectOptions` | `false` | Auto-reconnect on unexpected close. |\n| `transform` | `OutgoingTransform` | — | Transform or filter outgoing application messages. |\n\n---\n\n## `HeartbeatOptions`\n\n```ts\ntype HeartbeatOptions = {\n interval?: number;\n timeout?: number;\n};\n```\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `interval` | `number` | `30_000` | Interval between pings in ms. |\n| `timeout` | `number` | `5_000` | How long to wait for a pong before treating the connection as dead. |\n\n---\n\n## `ReconnectOptions`\n\n```ts\ntype ReconnectOptions = {\n delay?: number | ((attempt: number) => number);\n maxAttempts?: number;\n};\n```\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `delay` | `number \\| ((attempt: number) => number)` | Full-jitter exponential backoff capped at 30 s | Delay between reconnect attempts in ms. `attempt` is zero-based. |\n| `maxAttempts` | `number` | `5` | Maximum number of reconnect attempts after initial failure. |\n\n---\n\n## `OutgoingMessage`\n\n```ts\ntype OutgoingMessage = { channel?: string; event: string; payload: unknown };\n```\n\nAn outgoing application message before it is serialized.\n\n---\n\n## `OutgoingTransform`\n\n```ts\ntype OutgoingTransform = (message: Readonly<OutgoingMessage>) => OutgoingMessage | null;\n```\n\nTransform or filter outgoing application messages. Internal protocol frames (subscribe, join, leave, presence, ping) bypass this hook. Return `null` to drop the message.\n\n---\n\n## `Pulse`\n\n```ts\ntype Pulse<S extends PulseSchema = PulseSchema> = {\n // Channels\n channel<K extends keyof ChannelMap<S> & string>(\n name: K,\n ): PulseChannel<ChannelMap<S>[K]['server'], ChannelMap<S>[K]['client']>;\n\n // Connection\n connect(): Promise<void>;\n disconnect(code?: number, reason?: string): void;\n\n // Lifecycle\n readonly disposalSignal: AbortSignal;\n dispose(): void;\n readonly disposed: boolean;\n\n // Messaging\n on<K extends EventKey<ServerEvents<S>>>(event: K, handler: (payload: ServerEvents<S>[K]) => void): Unsubscribe;\n once<K extends EventKey<ServerEvents<S>>>(event: K, handler: (payload: ServerEvents<S>[K]) => void): Unsubscribe;\n send<K extends EventKey<ClientEvents<S>>>(event: K, payload: ClientEvents<S>[K]): void;\n wait<K extends EventKey<ServerEvents<S>>>(event: K, opts?: { signal?: AbortSignal; timeout?: number }): Promise<ServerEvents<S>[K]>;\n\n // Rooms\n room<K extends keyof RoomMap<S> & string>(name: K, opts?: RoomOptions): RoomScope<RoomMap<S>[K]>;\n readonly rooms: Readable<ReadonlySet<string>>;\n\n // Status\n readonly status: Readable<PulseStatus>;\n\n [Symbol.dispose](): void;\n};\n```\n\n### `channel(name)`\n\nCreates an isolated message namespace over the shared connection. Each call returns an independently disposable scope. The server subscription is reference-counted.\n\n### `connect()`\n\nExplicitly opens the connection. Resolves after session restoration completes. Rejects if the connection closes before opening.\n\n### `disconnect(code?, reason?)`\n\nCloses the connection without triggering reconnection. Default code is `1000`.\n\n### `dispose()`\n\nPermanently closes the connection and releases all resources. Idempotent.\n\n### `on(event, handler)`\n\nSubscribes to a typed server event. Returns an unsubscribe function.\n\n### `once(event, handler)`\n\nSubscribes once — auto-removes after first invocation.\n\n### `send(event, payload)`\n\nSends a typed event to the server. Throws `PulseConnectionError` unless the connection is open.\n\n### `wait(event, opts?)`\n\nResolves on the next emission of the given server event. Rejects when `opts.signal` aborts, the timeout elapses, or the instance is disposed.\n\n### `room(name, opts?)`\n\nCreates a ref-counted room scope. The first scope sends `join`; the last disposal sends `leave`. When the room definition includes `presence`, the scope exposes reactive presence state.\n\n### `rooms`\n\nReactive set of rooms the client is currently a confirmed member of.\n\n### `status`\n\nReactive connection status: `'connecting' | 'open' | 'reconnecting' | 'closed'`.\n\n---\n\n## `PulseChannel`\n\n```ts\ntype PulseChannel<TServer extends MessageMap = MessageMap, TClient extends MessageMap = MessageMap> = {\n readonly disposalSignal: AbortSignal;\n readonly disposed: boolean;\n readonly name: string;\n dispose(): void;\n on<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe;\n once<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe;\n send<K extends EventKey<TClient>>(event: K, payload: TClient[K]): void;\n wait<K extends EventKey<TServer>>(event: K, opts?: { signal?: AbortSignal; timeout?: number }): Promise<TServer[K]>;\n [Symbol.dispose](): void;\n};\n```\n\n---\n\n## `RoomScope`\n\n```ts\ntype RoomScope<R extends RoomDefinition = RoomDefinition> = R extends { presence: infer P }\n ? P extends undefined\n ? RoomScopeBase\n : PresenceRoomScope<P>\n : RoomScopeBase;\n```\n\nA room scope. When the room definition includes `presence`, the scope is a `PresenceRoomScope`; otherwise it is a `RoomScopeBase`.\n\n### `RoomScopeBase`\n\n```ts\ntype RoomScopeBase = {\n readonly disposalSignal: AbortSignal;\n readonly disposed: boolean;\n readonly name: string;\n readonly joined: Promise<void>;\n dispose(): void;\n [Symbol.dispose](): void;\n};\n```\n\n### `PresenceRoomScope`\n\n```ts\ntype PresenceRoomScope<T = unknown> = RoomScopeBase & {\n readonly presence: Readable<ReadonlyMap<string, T>>;\n updatePresence(state: T): void;\n onJoin(handler: (memberId: string, state: T) => void): Unsubscribe;\n onLeave(handler: (memberId: string) => void): Unsubscribe;\n};\n```\n\n| Member | Type | Description |\n| --- | --- | --- |\n| `presence` | `Readable<ReadonlyMap<string, T>>` | Reactive map of `memberId → state`. |\n| `updatePresence(state)` | `(state: T) => void` | Broadcast this client's presence state. Throws `PulseConnectionError` unless open. |\n| `onJoin(handler)` | `(handler) => Unsubscribe` | Called whenever a new member joins with their initial state. |\n| `onLeave(handler)` | `(handler) => Unsubscribe` | Called whenever a member leaves. |\n\n### `RoomOptions`\n\n```ts\ntype RoomOptions = {\n signal?: AbortSignal;\n timeout?: number;\n};\n```\n\n| Option | Type | Description |\n| --- | --- | --- |\n| `signal` | `AbortSignal` | Aborts the join, rejecting `joined` with `PulseAbortError`. |\n| `timeout` | `number` | Join timeout in ms. Rejects `joined` with `PulseRoomTimeoutError`. |\n\n---\n\n## Errors\n\nAll errors extend `PulseError`.\n\n### `PulseError`\n\nBase class for all Pulse errors.\n\n### `PulseConnectionError`\n\nTransport failure, send while disconnected, or room join rejected on close.\n\n### `PulseProtocolError`\n\nMalformed frame or server error frame.\n\n### `PulseTimeoutError`\n\n`wait()` timed out before the server event arrived.\n\n### `PulseRoomTimeoutError`\n\nRoom scope `joined` timed out before the server confirmed membership.\n\n### `PulseAbortError`\n\n`wait()` or room `joined` aborted via AbortSignal.\n\n### `PulseDisposedError`\n\nOperation attempted after disposal.\n\n---\n\n## Channel and room definitions\n\n### `ChannelDefinition`\n\n```ts\ntype ChannelDefinition = { client: MessageMap; server: MessageMap };\n```\n\n### `ChannelDefinitions`\n\n```ts\ntype ChannelDefinitions = Record<string, ChannelDefinition>;\n```\n\n### `RoomDefinition`\n\n```ts\ntype RoomDefinition = { presence?: unknown };\n```\n\n### `RoomDefinitions`\n\n```ts\ntype RoomDefinitions = Record<string, RoomDefinition>;\n```\n\n---\n\n## Utility types\n\n### `MessageMap`\n\n```ts\ntype MessageMap = Record<string, unknown>;\n```\n\n### `EventKey`\n\n```ts\ntype EventKey<T extends MessageMap> = keyof T & string;\n```\n\n### `ServerEvents`\n\n```ts\ntype ServerEvents<S extends PulseSchema> = S extends { server: infer M extends MessageMap } ? M : MessageMap;\n```\n\nExtract server events from a schema, defaulting to an empty map.\n\n### `ClientEvents`\n\n```ts\ntype ClientEvents<S extends PulseSchema> = S extends { client: infer M extends MessageMap } ? M : MessageMap;\n```\n\nExtract client events from a schema, defaulting to an empty map.\n\n### `RoomMap`\n\n```ts\ntype RoomMap<S extends PulseSchema> = S extends { rooms: infer R extends RoomDefinitions } ? R : RoomDefinitions;\n```\n\nExtract room definitions from a schema, defaulting to an empty map.\n\n### `Unsubscribe`\n\n```ts\ntype Unsubscribe = () => void;\n```\n\n### `PulseStatus`\n\n```ts\ntype PulseStatus = 'connecting' | 'open' | 'reconnecting' | 'closed';\n```\n",
6
+ "usage": "---\ntitle: Usage — Pulse\ndescription: Practical guide for connecting, sending, subscribing, joining rooms, and managing lifecycle with Pulse.\npackage: pulse\ncategory: websockets\n---\n\n<!-- markdownlint-disable MD025 -->\n\n[[toc]]\n\n## Basic Usage\n\nDeclare server events, client events, channel schemas, and room schemas once at construction. Named scopes infer their types from this schema.\n\n```ts\nimport { createPulse } from '@vielzeug/pulse';\n\ntype Schema = {\n // Root events the server sends\n server: { 'chat:message': { text: string }; notice: string };\n // Root events the client sends\n client: { 'chat:send': { text: string } };\n // Named channel scopes\n channels: {\n chat: {\n client: { send: { text: string } };\n server: { message: { text: string } };\n };\n alerts: {\n client: { subscribe: { topic: string } };\n server: { alert: { topic: string; severity: 'info' | 'warn' | 'error' } };\n };\n };\n // Named room scopes with optional presence state\n rooms: {\n lobby: { presence: { name: string; color: string } };\n announcements: {};\n };\n};\n```\n\n## Create and connect\n\n```ts\nconst pulse = createPulse<Schema>('wss://api.example.com/ws', {\n reconnect: { delay: 1_000, maxAttempts: 5 },\n heartbeat: { interval: 30_000, timeout: 5_000 },\n onError: (error) => console.error(error),\n});\n\ntry {\n await pulse.connect();\n} catch (error) {\n console.error('Connection failed:', error);\n}\n```\n\n`connect()` opens the WebSocket and resolves after session restoration completes. `send()` throws `PulseConnectionError` while disconnected — Pulse never silently drops or buffers application messages.\n\n## Send and receive root events\n\n```ts\npulse.on('chat:message', (message) => console.log(message.text));\npulse.send('chat:send', { text: 'Hello!' });\n```\n\n## Channels\n\nEach `channel()` call returns an independently disposable scope. The server subscription is reference-counted: the first scope sends `subscribe`, the last disposal sends `unsubscribe`.\n\n```ts\nconst chat = pulse.channel('chat');\n\nchat.on('message', (message) => console.log(message.text));\nchat.send('send', { text: 'Hello!' });\n\n// Later\nchat.dispose();\n```\n\nUse `using` for automatic cleanup:\n\n```ts\n{\n using chat = pulse.channel('chat');\n chat.on('message', (message) => console.log(message.text));\n} // chat.dispose() called automatically\n```\n\n## Rooms and presence\n\nEach `room()` call returns a ref-counted room scope. The first scope sends `join`; the last disposal sends `leave`. When the room definition includes `presence`, the scope exposes reactive presence state.\n\n```ts\nconst lobby = pulse.room('lobby');\n\n// joined resolves when the server confirms membership\nawait lobby.joined;\n\n// Reactive presence map: memberId → state\nlobby.onJoin((memberId, state) => console.log(`${memberId} joined: ${state.name}`));\nlobby.onLeave((memberId) => console.log(`${memberId} left`));\n\n// Broadcast your presence\nlobby.updatePresence({ name: 'Ada', color: 'blue' });\n\n// Read current presence\nfor (const [memberId, state] of lobby.presence.value) {\n console.log(`${memberId}: ${state.name}`);\n}\n\n// Leave\nlobby.dispose();\n```\n\nPlain rooms (without presence) work the same way but don't expose presence members:\n\n```ts\nconst announcements = pulse.room('announcements');\nawait announcements.joined;\nannouncements.dispose();\n```\n\n### Room scope options\n\n```ts\n// Timeout if the server doesn't confirm in time\nconst lobby = pulse.room('lobby', { timeout: 5_000 });\ntry {\n await lobby.joined;\n} catch (error) {\n console.error('Join failed:', error);\n}\n\n// Abort via AbortSignal\nconst ctrl = new AbortController();\nconst lobby = pulse.room('lobby', { signal: ctrl.signal });\nctrl.abort(); // joined rejects with PulseAbortError, scope auto-disposes\n```\n\n### Reactive rooms set\n\n`pulse.rooms` is a ripple readable that tracks confirmed room memberships:\n\n```ts\nimport { effect } from '@vielzeug/ripple';\n\neffect(() => {\n console.log('Joined rooms:', [...pulse.rooms.value]);\n});\n```\n\n## Reconnect\n\nWhen the connection drops unexpectedly, Pulse reconnects using the configured strategy. On reconnect, it restores:\n\n1. Channel subscriptions (sends `subscribe` for each active channel).\n2. Room memberships (sends `join` for each active room scope).\n3. Local presence state (sends `presence` with the last successfully published state).\n\n```ts\nconst pulse = createPulse<Schema>('wss://api.example.com/ws', {\n reconnect: {\n delay: (attempt) => Math.min(1_000 * 2 ** attempt, 30_000),\n maxAttempts: 5,\n },\n});\n```\n\n`joined` rejects on transport close. For post-reconnect membership, read `pulse.rooms` instead.\n\n## Heartbeat\n\n```ts\nconst pulse = createPulse<Schema>('wss://api.example.com/ws', {\n heartbeat: { interval: 30_000, timeout: 5_000 },\n});\n```\n\nPulse sends periodic pings. If a pong doesn't arrive before the timeout, it forces a reconnect using the same reconnect controller.\n\n## Transform outgoing messages\n\n```ts\nconst pulse = createPulse<Schema>('wss://api.example.com/ws', {\n transform: (message) => {\n // Add a timestamp to all messages\n return { ...message, payload: { ...message.payload, ts: Date.now() } };\n },\n});\n```\n\nReturn `null` to drop a message:\n\n```ts\nconst pulse = createPulse<Schema>('wss://api.example.com/ws', {\n transform: (message) => (message.event === 'debug' ? null : message),\n});\n```\n\n## Wait for a specific event\n\n```ts\nconst notice = await pulse.wait('notice', { timeout: 10_000 });\nconsole.log(notice);\n```\n\n## Dispose\n\n```ts\npulse.dispose();\n```\n\nDisposal is idempotent. It closes the connection, rejects pending room joins, clears all listeners, and aborts all scope disposal signals.\n\n## Error handling\n\n```ts\nconst pulse = createPulse<Schema>('wss://api.example.com/ws', {\n onError: (error) => {\n if (error instanceof PulseConnectionError) {\n console.error('Connection error:', error);\n } else if (error instanceof PulseProtocolError) {\n console.error('Protocol error:', error);\n }\n },\n});\n```\n\n| Error | When |\n| --- | --- |\n| `PulseConnectionError` | Transport failure, send while disconnected, room join rejected on close. |\n| `PulseProtocolError` | Malformed frame or server error frame. |\n| `PulseTimeoutError` | `wait()` times out. |\n| `PulseRoomTimeoutError` | Room scope `joined` times out. |\n| `PulseAbortError` | `wait()` or room `joined` aborted via AbortSignal. |\n| `PulseDisposedError` | Operation attempted after disposal. |\n\n## Best Practices\n\n- Await `connect()` before sending; never assume construction opens the transport.\n- Define the full schema at `createPulse()` so named scopes are type-safe without per-call generics.\n- Use `using` declarations for channel and room scopes so disposal is automatic at block exit.\n- Always call `dispose()` when done — it closes the connection, rejects pending joins, and clears listeners.\n- Provide an `onError` handler; Pulse reports transport and protocol errors there rather than throwing asynchronously.\n- Read `pulse.rooms` for post-reconnect membership; `joined` rejects on transport close.\n- Set a `timeout` on room scopes when the server may never confirm membership.\n- Keep `transform` synchronous; resolve async policy decisions before calling `send()`.\n",
7
+ "examples": "---\ntitle: Examples — Pulse\ndescription: Practical examples for common Pulse usage patterns.\npackage: pulse\ncategory: websockets\n---\n\n<!-- markdownlint-disable MD025 -->\n\n- [Basic Connection](./examples/basic-connection.md)\n- [Channel Multiplexing](./examples/channels.md)\n- [Outgoing Transform](./examples/middleware.md)\n- [Reconnect and Heartbeat](./examples/reconnect-and-heartbeat.md)\n- [Rooms and Presence](./examples/rooms-and-presence.md)\n"
8
+ },
9
+ "examples": [
10
+ {
11
+ "id": "channels",
12
+ "code": "import { createPulse } from '@vielzeug/pulse'\n\n// Isolated channel namespace — listeners and sends are scoped to 'chat'\nconst pulse = createPulse('wss://api.example.com/ws')\nconst chat = pulse.channel('chat')\n\n// Listeners scoped to the channel\nchat.on('message', ({ from, text }) => {\n console.log('[chat] ' + from + ': ' + text)\n})\n\ntry {\n await pulse.connect()\n // Send scoped to the channel\n chat.send('send', { text: 'hey!' })\n} catch (err) {\n console.log('connect failed:', err.message)\n}\n\n// Wait with a per-event timeout\ntry {\n const msg = await chat.wait('message', { timeout: 3_000 })\n console.log('got:', msg.text)\n} catch (err) {\n console.log('channel wait timed out:', err.message)\n}\n\n// Disposing the channel removes all its listeners\n// but the underlying pulse connection stays open\nchat.dispose()\nconsole.log('channel disposed, pulse still open:', pulse.status.value)\n\npulse.dispose()",
13
+ "name": "Typed Channels"
14
+ },
15
+ {
16
+ "id": "connect-and-send",
17
+ "code": "import { createPulse } from '@vielzeug/pulse'\n\n// Typed WebSocket client: on(), once(), send(), wait()\nconst pulse = createPulse('wss://api.example.com/ws', {\n reconnect: { maxAttempts: 5 },\n onError: (error) => console.log('transport error:', error.message),\n})\n\n// Subscribe before connecting — listeners are synchronous\nconst unsub = pulse.on('chat:message', ({ from, text }) => {\n console.log('[' + from + '] ' + text)\n})\n\n// One-shot listener: fires once and auto-removes\npulse.once('chat:message', (msg) => {\n console.log('first message:', msg.text)\n})\n\n// Connect; send when open\ntry {\n await pulse.connect()\n pulse.send('chat:send', { text: 'Hello, world!' })\n} catch (err) {\n console.log('connect failed:', err.message)\n}\n\n// Await next server event with a 5 s deadline\ntry {\n const msg = await pulse.wait('chat:message', { timeout: 500 })\n console.log('received:', msg.text)\n} catch (err) {\n console.log('wait ended:', err.message)\n}\n\nunsub()\npulse.dispose()",
18
+ "name": "Connect & Send"
19
+ },
20
+ {
21
+ "id": "lifecycle",
22
+ "code": "import { createPulse, PulseDisposedError } from '@vielzeug/pulse'\n\n// Status signal, disposalSignal, and error handling on dispose\nconst pulse = createPulse('wss://api.example.com/ws', {\n reconnect: { delay: 1_000, maxAttempts: 3 },\n heartbeat: { interval: 30_000, timeout: 5_000 },\n onError: (error) => console.log('Pulse error:', error.message),\n})\n\n// Construction is closed. connect() makes the transport available.\nconsole.log('initial status:', pulse.status.value)\n\n// disposalSignal aborts when dispose() is called\npulse.disposalSignal.addEventListener('abort', () => {\n console.log('disposal signal fired')\n})\n\ntry {\n await pulse.connect()\n console.log('connected:', pulse.status.value)\n} catch (err) {\n console.log('connect failed:', err.message)\n}\n\n// dispose() is idempotent — safe to call multiple times\npulse.dispose()\npulse.dispose()\nconsole.log('disposed:', pulse.disposed)\n\n// Methods reject with PulseDisposedError after dispose\ntry {\n await pulse.connect()\n} catch (err) {\n if (err instanceof PulseDisposedError) {\n console.log('connect() rejected with PulseDisposedError — correct')\n }\n}",
23
+ "name": "Lifecycle & Disposal"
24
+ },
25
+ {
26
+ "id": "reconnect",
27
+ "code": "import { createPulse, PulseConnectionError } from '@vielzeug/pulse'\n\n// Channels, rooms, and local presence state are restored on reconnect.\nconst pulse = createPulse('wss://api.example.com/ws', {\n reconnect: { delay: 500, maxAttempts: 3 },\n onError: (error) => console.log('transport error:', error.message),\n})\n\n// Channel is tracked: re-subscribed automatically after every reconnect\nconst chat = pulse.channel('chat')\nchat.on('message', ({ from, text }) => console.log(from + ': ' + text))\n\n// Connect explicitly to observe the status\ntry {\n await pulse.connect()\n console.log('connected, status:', pulse.status.value)\n} catch (err) {\n if (err instanceof PulseConnectionError) {\n console.log('connection failed:', err.message)\n }\n}\n\nconsole.log('channel name:', chat.name)\nconsole.log('channel disposed?', chat.disposed)\n\n// Disposing a channel removes it from re-subscription tracking\nchat.dispose()\nconsole.log('channel disposed, pulse still running:', !pulse.disposed)\n\npulse.dispose()",
28
+ "name": "Reconnect & Restoration"
29
+ },
30
+ {
31
+ "id": "rooms-presence",
32
+ "code": "import { createPulse } from '@vielzeug/pulse'\n\n// Room scopes: ref-counted membership with reactive presence\nconst pulse = createPulse('wss://api.example.com/ws')\nconst lobby = pulse.room('lobby')\n\ntry {\n await pulse.connect()\n\n // Wait for server confirmation\n await lobby.joined\n console.log('joined lobby, rooms:', [...pulse.rooms.value])\n\n // Broadcast our own presence\n lobby.updatePresence({ avatar: '/me.png', name: 'Alice', status: 'online' })\n\n // Reactive presence map: memberId → state\n const printMembers = () => {\n for (const [id, state] of lobby.presence.value) {\n console.log(' ' + id + ': ' + state.name + ' (' + state.status + ')')\n }\n }\n\n // React to individual joins and leaves\n lobby.onJoin((id, state) => console.log(state.name + ' joined'))\n lobby.onLeave((id) => console.log(id + ' left'))\n} catch (err) {\n console.log('connection or room operation failed:', err.message)\n}\n\n// Dispose the room scope — sends leave when last scope is released\nlobby.dispose()\nconsole.log('rooms after leave:', [...pulse.rooms.value])\n\npulse.dispose()",
33
+ "name": "Rooms & Presence"
34
+ }
35
+ ],
36
+ "typeSignatures": {
37
+ "PulseAbortError": "export {\n PulseAbortError,\n PulseConnectionError,\n PulseDisposedError,\n PulseError,\n PulseProtocolError,\n PulseRoomTimeoutError,\n PulseTimeoutError,\n} from './errors';",
38
+ "PulseConnectionError": "export {\n PulseAbortError,\n PulseConnectionError,\n PulseDisposedError,\n PulseError,\n PulseProtocolError,\n PulseRoomTimeoutError,\n PulseTimeoutError,\n} from './errors';",
39
+ "PulseDisposedError": "export {\n PulseAbortError,\n PulseConnectionError,\n PulseDisposedError,\n PulseError,\n PulseProtocolError,\n PulseRoomTimeoutError,\n PulseTimeoutError,\n} from './errors';",
40
+ "PulseError": "export {\n PulseAbortError,\n PulseConnectionError,\n PulseDisposedError,\n PulseError,\n PulseProtocolError,\n PulseRoomTimeoutError,\n PulseTimeoutError,\n} from './errors';",
41
+ "PulseProtocolError": "export {\n PulseAbortError,\n PulseConnectionError,\n PulseDisposedError,\n PulseError,\n PulseProtocolError,\n PulseRoomTimeoutError,\n PulseTimeoutError,\n} from './errors';",
42
+ "PulseRoomTimeoutError": "export {\n PulseAbortError,\n PulseConnectionError,\n PulseDisposedError,\n PulseError,\n PulseProtocolError,\n PulseRoomTimeoutError,\n PulseTimeoutError,\n} from './errors';",
43
+ "PulseTimeoutError": "export {\n PulseAbortError,\n PulseConnectionError,\n PulseDisposedError,\n PulseError,\n PulseProtocolError,\n PulseRoomTimeoutError,\n PulseTimeoutError,\n} from './errors';",
44
+ "createPulse": "export { createPulse } from './pulse';",
45
+ "ChannelDefinition": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
46
+ "ChannelDefinitions": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
47
+ "ClientEvents": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
48
+ "EventKey": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
49
+ "HeartbeatOptions": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
50
+ "MessageMap": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
51
+ "OutgoingMessage": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
52
+ "OutgoingTransform": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
53
+ "PresenceRoomScope": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
54
+ "Pulse": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
55
+ "PulseChannel": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
56
+ "PulseOptions": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
57
+ "PulseSchema": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
58
+ "PulseStatus": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
59
+ "ReconnectOptions": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
60
+ "RoomDefinition": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
61
+ "RoomDefinitions": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
62
+ "RoomMap": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
63
+ "RoomOptions": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
64
+ "RoomScope": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
65
+ "RoomScopeBase": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
66
+ "ServerEvents": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';",
67
+ "Unsubscribe": "export type {\n ChannelDefinition,\n ChannelDefinitions,\n ClientEvents,\n EventKey,\n HeartbeatOptions,\n MessageMap,\n OutgoingMessage,\n OutgoingTransform,\n PresenceRoomScope,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseSchema,\n PulseStatus,\n ReconnectOptions,\n RoomDefinition,\n RoomDefinitions,\n RoomMap,\n RoomOptions,\n RoomScope,\n RoomScopeBase,\n ServerEvents,\n Unsubscribe,\n} from './types';"
68
+ }
69
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "apiSource": "/**\n * Refine components register through their explicit component entry points.\n *\n * Keeping the package root free of registration side effects makes dependency\n * ownership and bundle contents obvious to application code.\n */\nexport { RefineError } from './errors';\n",
3
+ "docs": {
4
+ "index": "---\ntitle: Refine — Web component library\ndescription: Accessible, themeable web components built with Ore for framework and vanilla DOM apps.\npackage: refine\ncategory: ui-components\nkeywords: [web-components, accessible, themeable, ui, components, design-system]\nrelated: [ore, orbit, forge, keymap]\nexports:\n [\n ore-accordion,\n ore-accordion-item,\n ore-alert,\n ore-async,\n ore-avatar,\n ore-avatar-group,\n ore-badge,\n ore-box,\n ore-breadcrumb,\n ore-breadcrumb-item,\n ore-button,\n ore-button-group,\n ore-calendar,\n ore-card,\n ore-carousel,\n ore-chat-message,\n ore-checkbox,\n ore-checkbox-group,\n ore-chip,\n ore-combobox,\n ore-command-palette,\n ore-command-palette-item,\n ore-datagrid,\n ore-date-picker,\n ore-dialog,\n ore-drawer,\n ore-file-input,\n ore-grid,\n ore-grid-item,\n ore-icon,\n ore-input,\n ore-list,\n ore-list-item,\n ore-menu,\n ore-menu-item,\n ore-menu-separator,\n ore-message-composer,\n ore-navbar,\n ore-navbar-item,\n ore-number-input,\n ore-otp-input,\n ore-pagination,\n ore-password-strength,\n ore-popover,\n ore-progress,\n ore-radio,\n ore-radio-group,\n ore-rating,\n ore-select,\n ore-separator,\n ore-sidebar,\n ore-sidebar-group,\n ore-sidebar-item,\n ore-skeleton,\n ore-slider,\n ore-step,\n ore-stepper,\n ore-switch,\n ore-tab-item,\n ore-tab-panel,\n ore-table,\n ore-tabs,\n ore-text,\n ore-textarea,\n ore-time-picker,\n ore-toast,\n ore-tooltip,\n ore-typing-indicator,\n ]\nenvironments: [browser]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"refine\" />\n\n## Why Refine?\n\nEvery project needs UI primitives. Refine provides accessible web components that work natively anywhere HTML is rendered—no framework required.\n\n```html\n<!-- Before — roll your own button with ARIA -->\n<button class=\"btn btn-primary\" role=\"button\" aria-pressed=\"false\" tabindex=\"0\">\n <span class=\"btn-spinner\" aria-hidden=\"true\"></span>\n Save\n</button>\n\n<!-- After — Refine -->\n<ore-button variant=\"primary\" loading>Save</ore-button>\n```\n\n| Feature | Refine | Shoelace | Material Web |\n| ------------------ | ------------------------------------------- | ------------------------------------------ | ------------------------------------------ |\n| Bundle size | <PackageInfo package=\"refine\" type=\"size\" /> | ~145 kB | ~200 kB |\n| Built with | Ore | Lit | Lit |\n| Accessible | WCAG AA | WCAG AA | WCAG AA |\n| Framework agnostic | <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\n<div class=\"decision-callout\">\n\n**Use Refine when** you want accessible web components that match the Vielzeug design system without a heavy framework dependency.\n\n**Consider Shoelace or Material Web** if your team is already standardized on those ecosystems and you need their established component catalogs.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/refine\n```\n\n```sh [npm]\nnpm install @vielzeug/refine\n```\n\n```sh [yarn]\nyarn add @vielzeug/refine\n```\n\n:::\n\n## Quick Start\n\n```ts\n// 1. Import global styles once\nimport '@vielzeug/refine/fouc.css'; // Hide unupgraded custom elements until first paint\nimport '@vielzeug/refine/tokens.css'; // Tokens, animations, cascade layers\n\n// 2. Register only the elements you need\nimport '@vielzeug/refine/button';\nimport '@vielzeug/refine/input';\nimport '@vielzeug/refine/card';\n```\n\n```html\n<ore-button variant=\"solid\" color=\"primary\">Save</ore-button>\n<ore-input label=\"Email\" type=\"email\" required></ore-input>\n<ore-card padding=\"lg\">\n <span slot=\"header\">Account</span>\n <p>Card content goes here.</p>\n</ore-card>\n```\n\n```ts\n```\n\n### CDN / Vanilla HTML\n\nUse the self-contained IIFE bundle to load Refine directly from a CDN in any HTML page — no build step required:\n\n```html\n<!-- 1. Styles -->\n<link rel=\"stylesheet\" href=\"https://unpkg.com/@vielzeug/refine/dist/styles/fouc.css\" />\n<link rel=\"stylesheet\" href=\"https://unpkg.com/@vielzeug/refine/dist/styles/tokens.css\" />\n\n<!-- 2. All components (IIFE — registers global Refine namespace) -->\n<script src=\"https://unpkg.com/@vielzeug/refine/dist/refine.iife.js\"></script>\n```\n\nFor bundler-based projects that still want a CDN URL, use the ESM bundle via an import map:\n\n```html\n<script type=\"importmap\">\n {\n \"imports\": {\n \"@vielzeug/refine\": \"https://esm.sh/@vielzeug/refine\",\n \"@vielzeug/refine/button\": \"https://esm.sh/@vielzeug/refine/button\",\n \"@vielzeug/refine/input\": \"https://esm.sh/@vielzeug/refine/input\"\n }\n }\n</script>\n\n<script type=\"module\">\n import '@vielzeug/refine/button';\n import '@vielzeug/refine/input';\n</script>\n```\n\n### Package Entry Points\n\n| Import | Purpose |\n| ------------------------ | ----------------------------------------- |\n| `@vielzeug/refine/fouc.css` | FOUC suppression for unupgraded custom elements |\n| `@vielzeug/refine/tokens.css` | Global design tokens and cascade layers |\n| `@vielzeug/refine/styles/preflight.css` | Optional browser-default reset (includes FOUC suppression) |\n\nComponent registration happens through side-effect imports such as `@vielzeug/refine/button` and `@vielzeug/refine/dialog`.\n\n### Components\n\n**Content:** `ore-avatar`, `ore-avatar-group`, `ore-breadcrumb`, `ore-card`, `ore-carousel`, `ore-carousel-slide`, `ore-chat-message`, `ore-icon`, `ore-list`, `ore-list-item`, `ore-marquee`, `ore-pagination`, `ore-separator`, `ore-step`, `ore-stepper`, `ore-table`, `ore-text`\n\n**Disclosure:** `ore-accordion`, `ore-accordion-item`, `ore-tabs`, `ore-tab-item`, `ore-tab-panel`\n\n**Feedback:** `ore-alert`, `ore-async`, `ore-badge`, `ore-chip`, `ore-password-strength`, `ore-progress`, `ore-skeleton`, `ore-toast`, `ore-typing-indicator`\n\n**Inputs:** `ore-button`, `ore-button-group`, `ore-calendar`, `ore-checkbox`, `ore-checkbox-group`, `ore-column`, `ore-combobox`, `ore-datagrid`, `ore-date-picker`, `ore-file-input`, `ore-input`, `ore-message-composer`, `ore-number-input`, `ore-otp-input`, `ore-radio`, `ore-radio-group`, `ore-rating`, `ore-select`, `ore-slider`, `ore-switch`, `ore-textarea`, `ore-time-picker`\n\n**Layout:** `ore-box`, `ore-grid`, `ore-grid-item`, `ore-navbar`, `ore-sidebar`\n\n**Overlay:** `ore-command-palette`, `ore-command-palette-item`, `ore-dialog`, `ore-drawer`, `ore-menu`, `ore-popover`, `ore-tooltip`\n\n## Features\n\n<div class=\"features-grid\">\n\n- **Accessible** — keyboard navigation, ARIA wiring, and focus management across interactive components\n- **Themeable** — global tokens plus component-level CSS custom properties\n- **Framework agnostic** — works anywhere HTML can be rendered\n- **Tree-shakeable** — import only the component entry points you register\n- **Comprehensive surface** — inputs, content, disclosure, feedback, layout, and overlay primitives\n- **Zero runtime deps** — <PackageInfo package=\"refine\" type=\"size\" /> gzipped\n\n</div>\n\n### Prerequisites\n\n- Browser runtime with Custom Elements support.\n- Import `@vielzeug/refine/fouc.css` and `@vielzeug/refine/tokens.css` before rendering components.\n- For SSR, render placeholders server-side and hydrate components only on the client.\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Ore](/ore/) — Web component runtime that powers Refine\n- [Orbit](/orbit/) — Floating UI positioning used in Refine's overlays\n- [Forge](/forge/) — Form state management for use with Refine inputs\n- [Keymap](/keymap/) — Keyboard shortcut manager that powers the command palette's global trigger\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
+ "api": "---\ntitle: Refine — API Reference\ndescription: Published component registration and stylesheet entry points for @vielzeug/refine.\n---\n\n# API Reference\n\n[[toc]]\n\nRefine deliberately publishes components, not a second headless framework. Register each element through its component\nsubpath and import its types from the same path.\n\n## Styles\n\n```ts\nimport '@vielzeug/refine/fouc.css'; // Hide unupgraded custom elements until first paint\nimport '@vielzeug/refine/tokens.css'; // Required: tokens, animations, cascade layers\nimport '@vielzeug/refine/styles/preflight.css'; // Optional: normalizes browser defaults.\n```\n\n`fouc.css` suppresses flash-of-unstyled-content by hiding custom elements (`:not(:defined)`)\nuntil their shadow DOM attaches. Import it in your CSS bundle — not via JS injection — so the\nrule is available at first paint. `tokens.css` defines Refine's design tokens, animations, and\ncascade-layer order without modifying global element defaults. `preflight.css` is a separate\nopt-in reset that also imports `fouc.css`.\n\nDirect CSS entry points are also available when needed:\n\n| Import path | Purpose |\n| --- | --- |\n| `@vielzeug/refine/fouc.css` | FOUC suppression for unupgraded custom elements |\n| `@vielzeug/refine/tokens.css` | Tokens, animation helpers, and cascade layers |\n| `@vielzeug/refine/styles/theme.css` | Theme token declarations |\n| `@vielzeug/refine/styles/animation.css` | Animation helpers |\n| `@vielzeug/refine/styles/layers.css` | Cascade layer declarations |\n| `@vielzeug/refine/styles/preflight.css` | Optional browser-default reset (includes FOUC suppression) |\n\n## Components\n\nEach component has a single registration and type entry point:\n\n```ts\nimport '@vielzeug/refine/button';\nimport type { OreButtonEvents, OreButtonProps } from '@vielzeug/refine/button';\n```\n\nThe package root only exports `RefineError`; it does not register elements. This keeps component ownership and bundle\ncontents explicit.\n\n| Area | Components |\n| --- | --- |\n| Content | `accordion`, `accordion-item`, `avatar`, `avatar-group`, `badge`, `breadcrumb`, `card`, `carousel`, `chat-message`, `code-window`, `copy-command`, `icon`, `list`, `list-item`, `marquee`, `pagination`, `separator`, `step`, `stepper`, `table`, `text` |\n| Feedback | `alert`, `async`, `chip`, `password-strength`, `progress`, `skeleton`, `toast`, `typing-indicator` |\n| Inputs | `button`, `button-group`, `calendar`, `checkbox`, `checkbox-group`, `combobox`, `datagrid`, `date-picker`, `file-input`, `input`, `message-composer`, `number-input`, `otp-input`, `radio`, `radio-group`, `rating`, `select`, `slider`, `switch`, `textarea`, `time-picker` |\n| Layout | `box`, `grid`, `grid-item`, `navbar`, `sidebar` |\n| Overlays | `command-palette`, `dialog`, `drawer`, `menu`, `popover`, `tooltip` |\n\nEach component's documentation page describes its attributes, properties, events, slots, parts, and custom properties.\n\n## Events and Form Controls\n\nForm controls expose their current `.value` or `.checked` property and dispatch standard `input` and `change` events.\nRead the property from `event.currentTarget`; do not rely on framework-specific custom-event casts.\n\nStateful overlays expose `open` and `default-open` properties/attributes and dispatch `open-change` with\n`{ open, reason }` detail. The per-component pages describe valid reasons and focus behavior.\n",
6
+ "usage": "---\ntitle: Refine — Usage Guide\ndescription: Installation, attributes, events, slots, and ecosystem integration for Refine components.\n---\n\n# Usage Guide\n\n[[toc]]\n\nRefine components are native Web Components. Once imported, they behave like regular HTML elements — set attributes, listen to DOM events, use slots for content projection.\n\n## Installation\n\nImport the global styles first, then register only the components you need:\n\n```ts\nimport '@vielzeug/refine/tokens.css';\nimport '@vielzeug/refine/button';\nimport '@vielzeug/refine/input';\nimport '@vielzeug/refine/dialog';\n```\n\nThe token stylesheet supplies Refine's design tokens and cascade layers without changing browser defaults. Add the reset only when your application explicitly wants it:\n\n```ts\nimport '@vielzeug/refine/styles/preflight.css';\n```\n\n## Attributes and Events\n\nSet attributes directly on the element. Attributes map to component props:\n\n```html\n<ore-button variant=\"outline\" color=\"secondary\" size=\"lg\" disabled>\n Large Outline Button\n</ore-button>\n```\n\nComponents emit standard DOM events. Common event names: `click`, `input`, `change`, and `open-change`. Custom events carry a `detail` object:\n\n```javascript\nconst input = document.querySelector('ore-input');\n\ninput.addEventListener('input', () => {\n console.log(input.value);\n});\n```\n\nNative browser events (`click`, `focus`, `blur`) work as normal. Custom events with `event.detail` require `addEventListener` in React 18 and earlier — see the [Framework Integration](./frameworks.md) guide.\n\n## Slots\n\nSlots let you pass HTML into named regions of a component without JavaScript.\n\nContent placed directly inside the element fills the default slot:\n\n```html\n<ore-button>Save Changes</ore-button>\n<ore-card>Any HTML content here</ore-card>\n```\n\nComponents with distinct regions expose named slots:\n\n```html\n<ore-card>\n <span slot=\"header\">Card Heading</span>\n <p>Main body content fills the default slot.</p>\n <div slot=\"footer\">\n <ore-button size=\"sm\" variant=\"outline\">Cancel</ore-button>\n <ore-button size=\"sm\">Confirm</ore-button>\n </div>\n</ore-card>\n```\n\nMany input components expose `prefix` and `suffix` slots for icons or actions:\n\n```html\n<ore-button>\n <ore-icon slot=\"prefix\" name=\"arrow-left\" size=\"18\"></ore-icon>\n Back\n</ore-button>\n\n<ore-input label=\"Search\">\n <ore-icon slot=\"suffix\" name=\"search\" size=\"18\" aria-hidden=\"true\"></ore-icon>\n</ore-input>\n```\n\nEach component's available slots are listed in its API Reference table.\n\n## Composing with Ore and Ripple\n\nRefine components are plain HTML elements — they compose naturally with [Ore](/ore/) custom elements and [Ripple](/ripple/) signals.\n\n**Build a custom component that wraps Refine elements:**\n\n```ts\nimport '@vielzeug/refine/button';\nimport '@vielzeug/refine/input';\nimport { define, html } from '@vielzeug/ore';\nimport { signal } from '@vielzeug/ripple';\n\ndefine('my-search-bar', () => {\n const query = signal('');\n return html`\n <ore-input\n .value=${query}\n @input=${(e) => (query.value = e.currentTarget.value)}\n label=\"Search\"\n />\n <ore-button @click=${() => search(query.value)} variant=\"solid\" color=\"primary\">\n Search\n </ore-button>\n `;\n});\n```\n\n**Drive component state from reactive signals:**\n\n```ts\nimport { signal, effect } from '@vielzeug/ripple';\n\nconst isLoading = signal(false);\nconst btn = document.querySelector('ore-button');\n\neffect(() => {\n btn.loading = isLoading.value;\n});\n```\n\n## Framework Integration\n\nFor React, Vue, Svelte, and Angular wiring — including event handling, TypeScript declarations, Vite setup, and SSR guards — see the [Framework Integration](./frameworks.md) guide.\n\n## Accessibility\n\nAll Refine components target WCAG 2.1 AA. ARIA roles and states are managed automatically. For the full compliance contract, per-component coverage, and testing strategy, see the [Accessibility](./accessibility.md) page.\n\nThe two things you always control:\n\n- **Icon-only buttons** require a `label` attribute — it becomes `aria-label`.\n- **Decorative icons** should have `aria-hidden=\"true\"` so screen readers skip them.\n"
7
+ },
8
+ "examples": [],
9
+ "typeSignatures": {
10
+ "RefineError": "export { RefineError } from './errors';"
11
+ }
12
+ }
@@ -0,0 +1,83 @@
1
+ {
2
+ "apiSource": "export type { AsyncState, Resource, ResourceOptions } from './_async';\nexport { createRipple, type Ripple } from './_default';\nexport type { WatchOptions } from './_watch';\nexport {\n RippleComputedCycleError,\n RippleDisposedRuntimeError,\n RippleDisposedScopeError,\n RippleError,\n RippleInfiniteLoopError,\n} from './errors';\nexport { isReactive } from './runtime';\nexport type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';\n\nimport { defaultRipple } from './_default';\n\nexport const signal = defaultRipple.signal;\nexport const computed = defaultRipple.computed;\nexport const effect = defaultRipple.effect;\nexport const batch = defaultRipple.batch;\nexport const createScope = defaultRipple.createScope;\nexport const untrack = defaultRipple.untrack;\nexport const watch = defaultRipple.watch;\nexport const resource = defaultRipple.resource;\n",
3
+ "docs": {
4
+ "index": "---\ntitle: Ripple — Reactive graphs\ndescription: Framework-agnostic signals, derived values, effects, scopes, watchers, and async resources.\npackage: ripple\ncategory: state\nkeywords: [reactive, signals, computed, effects, graph, scope, batch, watch, resource, async]\nrelated: [ore, clockwork, ledger]\nexports: [createRipple, signal, computed, effect, batch, createScope, untrack, watch, resource, isReactive]\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"ripple\" />\n\n## Why Ripple?\n\nHand-rolled reactive state spreads subscription, cleanup, and derived-value rules across application code. Ripple gives you one graph boundary with explicit disposal and fine-grained dependencies while keeping rendering and routing outside the runtime.\n\n```ts\n// Before\nlet count = 0;\nconst listeners = new Set<() => void>();\n\nfunction setCount(next: number) {\n count = next;\n for (const listener of listeners) listener();\n}\n\n// After\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\nconst doubled = ripple.computed(() => count.value * 2);\nconst stop = ripple.effect(() => console.log(doubled.value));\n\ncount.value = 1;\nstop.dispose();\nripple.dispose();\n```\n\n| Feature | Ripple | Zustand | Jotai |\n| --- | --- | --- | --- |\n| Bundle size | <PackageInfo package=\"ripple\" type=\"size\" /> | ~3.5 kB | ~7 kB |\n| Zero dependencies | <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| Framework-agnostic | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | React-first |\n| Explicit graph lifetime | <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| Fine-grained derived values | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Selectors | Atoms |\n\n<div class=\"decision-callout\">\n\n**Use Ripple when** you need framework-independent state with explicit graph lifetime and small composable primitives.\n\n**Consider a framework store when** component bindings, server cache, or framework-specific tooling matter more than portable reactive state.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/ripple\n```\n\n```sh [npm]\nnpm install @vielzeug/ripple\n```\n\n```sh [yarn]\nyarn add @vielzeug/ripple\n```\n\n:::\n\n## Quick Start\n\nCreate one graph, derive a value, observe it, then dispose resources when the graph lifetime ends.\n\n```ts\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\nconst doubled = ripple.computed(() => count.value * 2);\nconst stop = ripple.effect(() => console.log(doubled.value));\n\nripple.batch(() => {\n count.value = 1;\n count.value = 2;\n});\n\nstop.dispose();\nripple.dispose();\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `createRipple()` creates an isolated graph and lifetime boundary.\n- `signal()` stores writable values with configurable equality.\n- `computed()` derives lazy read-only values.\n- `effect()` reacts to dependency changes with cleanup support.\n- `batch()` coalesces synchronous writes and notifications.\n- `createScope()` groups owned reactive work.\n- `watch()` observes one selected source transition.\n- `resource()` loads async values with stale-work cancellation.\n\n</div>\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- [Ore](/ore/) — uses Ripple signals and effects for web-component reactivity.\n- [Clockwork](/clockwork/) — exposes machine state through reactive Ripple values.\n- [Ledger](/ledger/) — adds command-based undo and redo beside Ripple state.\n\n</div>\n\n<!-- markdownlint-enable -->\n",
5
+ "api": "---\ntitle: Ripple — API Reference\ndescription: Complete reference for reactive graphs, signals, effects, scopes, watchers, and resources.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `createRipple()` | Create isolated graph | Sync | Disposal is terminal; create a new graph instead of reusing it |\n| `signal()` | Create writable value | Sync | Default graph is process-wide |\n| `computed()` | Create lazy derived value | Sync | Keep derivation pure |\n| `effect()` | React to dependency reads | Sync | Dispose handle or return cleanup |\n| `batch()` | Coalesce synchronous writes | Sync | Does not roll back writes |\n| `createScope()` | Group owned reactive work | Sync | Call `run()` to activate it |\n| `untrack()` | Read without tracking | Sync | Read still happens immediately |\n| `watch()` | Observe selected output | Sync | Use `effect()` for broad reads |\n| `resource()` | Load async source | Async | Read dependencies in source callback |\n| `isReactive()` | Test `Readable` identity | Sync | Does not test arbitrary objects |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/ripple` | All primitives, types, and errors — signals, computed, effects, scopes, watch, resource, and the isolated graph factory |\n\n## Graph Creation\n\n### `createRipple(options?)`\n\n```ts\nfunction createRipple(options?: RippleOptions): Ripple;\n```\n\nCreates one isolated reactive graph. Factories on the returned object share scheduling, ownership, observer, and error boundaries. `dispose()` is terminal: `ripple.disposed` becomes `true`, existing owned work is disposed, and creating more graph work throws `RippleDisposedRuntimeError`. Create a new graph for a new lifetime.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `options.onError` | `(error, context) => void` | Receives effect, cleanup, listener, or observer failures. |\n| `options.observer` | `ReactiveObserver` | Receives graph events. |\n\n**Returns:** `Ripple`.\n\n**Example:**\n\n```ts\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\nconst stop = ripple.effect(() => console.log(count.value));\n\nstop.dispose();\nripple.dispose();\n```\n\n---\n\n### `isReactive(value)`\n\n```ts\nfunction isReactive<T>(value: T | Readable<T>): value is Readable<T>;\n```\n\nTests whether a value is a Ripple-created readable node, including `Resource`. Recognition works across duplicated Ripple module graphs.\n\n**Returns:** `true` for a Ripple `Signal`, computed value, or `Resource`; otherwise `false`.\n\n**Example:**\n\n```ts\nimport { isReactive, signal } from '@vielzeug/ripple';\n\nconsole.log(isReactive(signal(0)));\n```\n\n## Default Graph Functions\n\n### `signal(initial, options?)`\n\n```ts\nfunction signal<T>(initial: T, options?: SignalOptions<T>): Signal<T>;\n```\n\nCreates writable state on the default graph. Use `update()` for immutable replacement patterns.\n\n**Returns:** `Signal<T>`.\n\n**Example:**\n\n```ts\nimport { signal } from '@vielzeug/ripple';\n\nconst count = signal(0);\ncount.value += 1;\n\nconst cart = signal({ items: 0 });\ncart.update((state) => ({ ...state, items: state.items + 1 }));\n```\n\n---\n\n### `computed(derive, options?)`\n\n```ts\nfunction computed<T>(derive: () => T, options?: ComputedOptions<T>): Readable<T>;\n```\n\nCreates a lazy read-only value from reactive reads in `derive`.\n\n**Returns:** `Readable<T>`.\n\n**Example:**\n\n```ts\nimport { computed, signal } from '@vielzeug/ripple';\n\nconst count = signal(2);\nconst doubled = computed(() => count.value * 2);\nconsole.log(doubled.value);\n```\n\n---\n\n### `effect(callback, options?)`\n\n```ts\nfunction effect(callback: () => Cleanup | undefined, options?: EffectOptions): EffectHandle;\n```\n\nRuns immediately and reruns when its tracked reads change. A returned cleanup runs before the next callback or disposal.\n\n**Returns:** `EffectHandle`.\n\n**Example:**\n\n```ts\nimport { effect, signal } from '@vielzeug/ripple';\n\nconst connected = signal(false);\nconst stop = effect(() => {\n if (!connected.value) return;\n\n return () => console.log('disconnect');\n});\n\nstop.dispose();\n```\n\n---\n\n### `batch(fn)` and `untrack(fn)`\n\n```ts\nfunction batch<T>(fn: () => T): T;\nfunction untrack<T>(fn: () => T): T;\n```\n\n`batch()` defers effects and listeners until its callback returns. `untrack()` reads current state without adding dependencies to an enclosing effect.\n\n**Returns:** the callback result.\n\n**Example:**\n\n```ts\nimport { batch, signal, untrack } from '@vielzeug/ripple';\n\nconst first = signal('Ada');\nconst last = signal('Lovelace');\nconst locale = signal('en-US');\n\nbatch(() => {\n first.value = 'Grace';\n last.value = 'Hopper';\n});\n\nconsole.log(untrack(() => locale.value));\n```\n\n---\n\n### `createScope(name?)`\n\n```ts\nfunction createScope(name?: string): Scope;\n```\n\nCreates a disposable ownership boundary. Work created inside `scope.run()` belongs to that scope.\n\n**Returns:** `Scope`.\n\n**Example:**\n\n```ts\nimport { createScope, effect, signal } from '@vielzeug/ripple';\n\nconst scope = createScope('panel');\nconst count = signal(0);\n\nscope.run(() => effect(() => console.log(count.value)));\nscope.dispose();\n```\n\n## Watch and Resources\n\n### `watch(source, callback, options?)`\n\n```ts\nfunction watch<T>(\n source: Readable<T> | (() => T),\n callback: (value: T, previous: T | undefined) => void,\n options?: WatchOptions<T>,\n): EffectHandle;\n```\n\nObserves selected output changes using the default graph or a `Ripple.watch()` method.\n\n**Returns:** `EffectHandle`.\n\n**Example:**\n\n```ts\nimport { signal, watch } from '@vielzeug/ripple';\n\nconst count = signal(0);\nconst stop = watch(count, (value, previous) => console.log(previous, value), { immediate: true });\nstop.dispose();\n```\n\n---\n\n### `resource(source, loader, options?)`\n\n```ts\nfunction resource<Source, Value>(\n source: () => Source,\n loader: (source: Source, context: { readonly signal: AbortSignal }) => Promise<Value>,\n options?: ResourceOptions,\n): Resource<Value>;\n```\n\nTracks `source`, aborts stale loader work, and exposes `AsyncState<Value>`. Source and loader failures become `status: 'error'` state; handle them from `resource.value` rather than `RippleOptions.onError`, which is reserved for runtime callback, cleanup, listener, and observer failures.\n\n**Returns:** `Resource<Value>`.\n\n**Example:**\n\n```ts\nimport { resource, signal } from '@vielzeug/ripple';\n\nconst userId = signal('42');\nconst user = resource(() => userId.value, async (id) => ({ id }));\n\nif (user.value.status === 'error') console.error(user.value.error);\nuser.dispose();\n```\n\n## Types\n\n```ts\ntype Cleanup = () => void;\ntype Equality<T> = (previous: T, next: T) => boolean;\ntype Unsubscribe = () => void;\n\ntype SignalOptions<T> = { equals?: Equality<T>; name?: string };\ntype ComputedOptions<T> = { equals?: Equality<T>; name?: string };\ntype EffectOptions = { name?: string; scheduler?: 'microtask' | 'sync' };\ntype WatchOptions<T> = { equals?: Equality<T>; immediate?: boolean; name?: string; once?: boolean };\ntype ResourceOptions = { name?: string };\n\ntype ReactiveEvent =\n | { readonly kind: 'compute'; readonly name?: string }\n | { readonly kind: 'effect'; readonly name?: string }\n | { readonly kind: 'write'; readonly name?: string; readonly next: unknown; readonly previous: unknown }\n | { readonly kind: 'dispose'; readonly name?: string; readonly node: 'effect' | 'scope' };\n\ntype ReactiveObserver = (event: ReactiveEvent) => void;\ntype ReactiveErrorContext = { readonly kind: 'cleanup' | 'effect' | 'listener' | 'observer'; readonly name?: string };\ntype RippleOptions = { observer?: ReactiveObserver; onError?: (error: unknown, context: ReactiveErrorContext) => void };\n\ntype AsyncState<T> =\n | { readonly previous?: T; readonly status: 'pending' }\n | { readonly status: 'success'; readonly value: T }\n | { readonly error: unknown; readonly previous?: T; readonly status: 'error' };\n\ninterface Readable<T> {\n readonly name?: string;\n peek(): T;\n subscribe(listener: () => void): Unsubscribe;\n readonly value: T;\n}\n\ninterface Signal<T> extends Readable<T> { update(updater: (prev: T) => T): void; value: T }\ninterface Disposable { dispose(): void; readonly disposed: boolean; readonly disposalSignal: AbortSignal; [Symbol.dispose](): void }\ntype EffectHandle = Disposable;\ninterface Scope extends Disposable { run<T>(fn: () => T): T }\n\ninterface Resource<T> extends Readable<AsyncState<T>>, Disposable { reload(): void }\n\ninterface Ripple {\n batch<T>(fn: () => T): T;\n computed<T>(derive: () => T, options?: ComputedOptions<T>): Readable<T>;\n createScope(name?: string): Scope;\n dispose(): void;\n readonly disposed: boolean;\n effect(callback: () => Cleanup | undefined, options?: EffectOptions): EffectHandle;\n resource<Source, Value>(source: () => Source, loader: (source: Source, context: { readonly signal: AbortSignal }) => Promise<Value>, options?: ResourceOptions): Resource<Value>;\n signal<T>(initial: T, options?: SignalOptions<T>): Signal<T>;\n untrack<T>(fn: () => T): T;\n watch<T>(source: Readable<T> | (() => T), callback: (value: T, previous: T | undefined) => void, options?: WatchOptions<T>): EffectHandle;\n}\n```\n\n## Errors\n\n| Error | Trigger | Notable properties |\n| --- | --- | --- |\n| `RippleError` | Base Ripple error | Use `instanceof RippleError` to narrow unknown values. |\n| `RippleComputedCycleError` | Computed dependency reads itself through a cycle | Extends `RippleError`. |\n| `RippleDisposedRuntimeError` | Factory or execution API used after `ripple.dispose()` | Extends `RippleError`. |\n| `RippleDisposedScopeError` | `scope.run()` after scope disposal | Extends `RippleError`. |\n| `RippleInfiniteLoopError` | Effect flush exceeds graph iteration limit | Extends `RippleError`. |\n",
6
+ "usage": "---\ntitle: Ripple — Usage Guide\ndescription: Build reactive state with one explicit graph boundary.\n---\n\n[[toc]]\n\n## Basic Usage\n\nUse top-level functions when one application-lifetime graph is sufficient. Read a signal inside an effect to make that read reactive.\n\n```ts\nimport { computed, effect, signal } from '@vielzeug/ripple';\n\nconst count = signal(0);\nconst label = computed(() => `Count: ${count.value}`);\nconst stop = effect(() => console.log(label.value));\n\ncount.value = 1;\nstop.dispose();\n```\n\n## Isolated Graphs\n\nUse `createRipple()` for tests, SSR requests, embedded applications, or independently disposable features. Never mix reactive values from separate graphs.\n\n```ts\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple({\n onError(error, context) {\n console.log(context.kind, error);\n },\n});\n\nconst count = ripple.signal(0);\nconst stop = ripple.effect(() => console.log(count.value));\n\nstop.dispose();\nripple.dispose();\n```\n\n## Derived Values and Batches\n\nUse `computed()` for pure derivation. Use `untrack()` when a current read must not become an effect dependency. Use `batch()` for related synchronous writes.\n\n```ts\nconst first = ripple.signal('Ada');\nconst last = ripple.signal('Lovelace');\nconst locale = ripple.signal('en-US');\nconst name = ripple.computed(() => `${first.value} ${last.value}`);\n\nripple.effect(() => {\n console.log({ locale: ripple.untrack(() => locale.value), name: name.value });\n});\n\nripple.batch(() => {\n first.value = 'Grace';\n last.value = 'Hopper';\n});\n```\n\n## Scheduling and Subscriptions\n\nRipple propagates every synchronous write before flushing effects. Each flush pass runs effects queued at its\nstart before direct `subscribe()` listeners queued at its start. Work queued by either runs in a later pass.\nEffects using `scheduler: 'microtask'` join a later microtask and coalesce writes made before that task runs.\n\n```ts\nconst count = ripple.signal(0);\nconst log: string[] = [];\n\nripple.effect(() => log.push(`effect: ${count.value}`));\ncount.subscribe(() => log.push(`listener: ${count.value}`));\nripple.effect(() => log.push(`deferred: ${count.value}`), { scheduler: 'microtask' });\n\nlog.length = 0; // Ignore synchronous creation runs.\ncount.value = 1;\nconsole.log(log); // ['effect: 1', 'listener: 1']\n\nawait Promise.resolve();\nconsole.log(log); // ['effect: 1', 'listener: 1', 'deferred: 1']\n```\n\n## Ownership with Scopes\n\nCreate a scope when a group of effects or derived values shares one lifetime. Dispose the scope when its feature ends.\n\n```ts\nconst scope = ripple.createScope('panel');\nconst count = ripple.signal(0);\n\nscope.run(() => {\n ripple.effect(() => console.log(`Panel count: ${count.value}`));\n});\n\ncount.value = 1;\nscope.dispose();\n```\n\n## Watch Selected Values\n\nUse `watch()` for one selected output. Use `effect()` when every reactive read in the callback should be a dependency.\n\n```ts\nconst stopWatch = ripple.watch(\n () => `${first.value} ${last.value}`,\n (value, previous) => console.log({ previous, value }),\n { immediate: true },\n);\n\nstopWatch.dispose();\n```\n\n## Async Data\n\n`resource()` captures source dependencies synchronously and passes a cancellation signal to the loader.\n\n```ts\nconst userId = ripple.signal('42');\nconst user = ripple.resource(\n () => userId.value,\n async (id, { signal }) => {\n const response = await fetch(`/users/${id}`, { signal });\n if (!response.ok) throw new Error(`Request failed: ${response.status}`);\n\n return response.json() as Promise<{ id: string; name: string }>;\n },\n);\n\nif (user.value.status === 'success') console.log(user.value.value.name);\nif (user.value.status === 'error') console.error(user.value.error);\nuser.dispose();\n```\n\n## Object State\n\n`signal()` with `update()` holds one value and supports immutable replacement patterns. Return replacement objects from `update()` when object consumers depend on immutable updates.\n\n```ts\nconst cart = ripple.signal({ items: 0, label: 'empty' });\nconst items = ripple.computed(() => cart.value.items);\n\ncart.update((state) => ({ ...state, items: state.items + 1 }));\ncart.value = { items: 3, label: 'ready' };\n\nconsole.log(items.value);\n```\n\n## Testing\n\nCreate an isolated graph per test. Disposal prevents effects and resource work from leaking into later tests.\n\n```ts\nimport { expect, test } from 'vitest';\nimport { createRipple } from '@vielzeug/ripple';\n\ntest('derives a doubled count', () => {\n const ripple = createRipple();\n const count = ripple.signal(2);\n const doubled = ripple.computed(() => count.value * 2);\n\n expect(doubled.value).toBe(4);\n ripple.dispose();\n});\n```\n\n## Framework Integration\n\nUse signals and effects with any renderer. Dispose component-owned effects when the component unmounts.\n\n::: code-group\n\n```ts [React]\nimport { useEffect, useState } from 'react';\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\n\nexport function Counter() {\n const [, rerender] = useState(0);\n\n useEffect(() => {\n const stop = ripple.effect(() => {\n void count.value;\n rerender((revision) => revision + 1);\n });\n\n return () => stop.dispose();\n }, []);\n\n return <button onClick={() => (count.value += 1)}>{count.value}</button>;\n}\n```\n\n```ts [Vue 3]\nimport { onUnmounted, ref } from 'vue';\nimport { createRipple } from '@vielzeug/ripple';\n\nconst ripple = createRipple();\nconst count = ripple.signal(0);\nconst revision = ref(0);\nconst stop = ripple.effect(() => {\n void count.value;\n revision.value++;\n});\n\nonUnmounted(() => stop.dispose());\n```\n\n```ts [Svelte]\n<script lang=\"ts\">\n import { onDestroy } from 'svelte';\n import { createRipple } from '@vielzeug/ripple';\n\n const ripple = createRipple();\n const count = ripple.signal(0);\n let revision = 0;\n const stop = ripple.effect(() => {\n void count.value;\n revision++;\n });\n\n onDestroy(() => stop.dispose());\n</script>\n\n<button on:click={() => (count.value += 1)}>{count.value}</button>\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\nOre uses Ripple for component reactivity. Clockwork actors expose framework-neutral snapshots; bridge actor subscriptions into a Ripple signal. Ledger adds undo/redo commands around state changes without replacing graph.\n\n```ts\nimport { createRipple } from '@vielzeug/ripple';\nimport { defineMachine } from '@vielzeug/clockwork';\n\nconst ripple = createRipple();\nconst actor = defineMachine<Record<string, never>, { type: 'START' }>()({\n initial: 'idle',\n states: { active: {}, idle: { on: { START: { target: 'active' } } } },\n}).createActor();\n\nconst snapshot = ripple.signal(actor.snapshot);\nconst stop = actor.subscribe((next) => (snapshot.value = next));\nconst status = ripple.computed(() => snapshot.value.state);\nconsole.log(status.value);\n\nstop();\nactor.dispose();\nripple.dispose();\n```\n\n## Gotchas\n\n### `subscribe()` forces computed evaluation\n\n`Readable.subscribe()` calls `peek()` before registering the listener. For signals this is a no-op, but for computeds it forces `refresh()` — the derivation runs immediately even if no one reads `.value`. This ensures `equals` comparison works on the first dependency change. Avoid subscribing to expensive computeds unless you need their value.\n\n### Computed first-run failure is recoverable\n\nIf a computed's `derive` throws on its first run (e.g., a source is `null`), the computed commits the partial dependencies it tracked before the throw. When a dependency changes and the derivation can succeed, the computed refreshes and notifies its dependents. Effects that read a failing computed report the error through `onError` and re-run when the computed recovers.\n\n## Best Practices\n\n- Create one graph per ownership boundary.\n- Keep computed callbacks pure.\n- Return cleanup from effects.\n- Dispose request, test, and feature graphs.\n- Batch related synchronous writes.\n- Use `watch()` only for selected source transitions.\n- Read dependencies in a resource source, not its loader.\n- Use `onError` for runtime callback, cleanup, listener, and observer failures; handle resource source and loader failures through `resource.value.status === 'error'`.\n",
7
+ "examples": "---\ntitle: Ripple — Examples\ndescription: Practical Ripple recipes.\n---\n\n## Examples\n\n- [Reactive Counter](./examples/reactive-counter.md)\n- [Batch and Untrack](./examples/batch-and-untrack.md)\n- [Scope Ownership](./examples/scope-ownership.md)\n- [Watch Selected Value](./examples/watch-selected-value.md)\n- [Immutable State](./examples/immutable-store.md)\n- [Isolated Graph](./examples/isolated-runtime.md)\n- [Async Resource](./examples/async-resource.md)\n"
8
+ },
9
+ "examples": [
10
+ {
11
+ "id": "async-resource",
12
+ "code": "import { createRipple } from '@vielzeug/ripple'\n\n// Resource reloads from tracked input and ignores stale loader work.\nconst ripple = createRipple()\nconst userId = ripple.signal('u1')\nconst user = ripple.resource(\n () => userId.value,\n async (id, { signal }) => {\n await new Promise((resolve) => setTimeout(resolve, 30))\n if (signal.aborted) throw new Error('request aborted')\n return { id, name: 'User ' + id }\n },\n)\n\nripple.effect(() => console.log(user.value))\n\nuserId.value = 'u2'\nsetTimeout(() => user.reload(), 50)\nsetTimeout(() => {\n user.dispose()\n ripple.dispose()\n}, 100)",
13
+ "name": "Async Resource"
14
+ },
15
+ {
16
+ "id": "basic-signal",
17
+ "code": "import { createRipple } from '@vielzeug/ripple'\n\n// One graph owns state, derived values, effects, and disposal.\nconst ripple = createRipple()\nconst count = ripple.signal(0)\nconst doubled = ripple.computed(() => count.value * 2)\n\nconst stop = ripple.effect(() => {\n console.log({ count: count.value, doubled: doubled.value })\n})\n\ncount.value = 1\ncount.value = 2\n\nstop.dispose()\nripple.dispose()",
18
+ "name": "Create Graph, Signal, Computed & Effect"
19
+ },
20
+ {
21
+ "id": "batch-untrack",
22
+ "code": "import { createRipple } from '@vielzeug/ripple'\n\n// Batch coalesces updates; untrack reads current state without subscribing.\nconst ripple = createRipple()\nconst first = ripple.signal('Ada')\nconst last = ripple.signal('Lovelace')\nconst locale = ripple.signal('en-US')\n\nconst stop = ripple.effect(() => {\n const name = first.value + ' ' + last.value\n const currentLocale = ripple.untrack(() => locale.value)\n console.log({ name, currentLocale })\n})\n\nripple.batch(() => {\n first.value = 'Grace'\n last.value = 'Hopper'\n})\nlocale.value = 'de-DE'\n\nstop.dispose()\nripple.dispose()",
23
+ "name": "Batch & Untrack"
24
+ },
25
+ {
26
+ "id": "effect-options",
27
+ "code": "import { createRipple } from '@vielzeug/ripple'\n\n// Microtask effects coalesce writes until current task ends.\nconst ripple = createRipple()\nconst count = ripple.signal(0)\nconst stop = ripple.effect(\n () => console.log('count:', count.value),\n { name: 'count logger', scheduler: 'microtask' },\n)\n\ncount.value = 1\ncount.value = 2\ncount.value = 3\nconsole.log('writes complete')\n\nqueueMicrotask(() => {\n stop.dispose()\n ripple.dispose()\n})",
28
+ "name": "Microtask Effect"
29
+ },
30
+ {
31
+ "id": "scope-ownership",
32
+ "code": "import { createRipple } from '@vielzeug/ripple'\n\n// Nested work automatically belongs to parent effect run.\nconst ripple = createRipple()\nconst enabled = ripple.signal(true)\nconst count = ripple.signal(0)\n\nconst stop = ripple.effect(() => {\n if (!enabled.value) return\n\n ripple.effect(() => console.log('nested count:', count.value))\n})\n\ncount.value = 1\nenabled.value = false\ncount.value = 2\n\nstop.dispose()\nripple.dispose()",
33
+ "name": "Nested Effect Ownership"
34
+ },
35
+ {
36
+ "id": "store-basics",
37
+ "code": "import { createRipple } from '@vielzeug/ripple'\n\n// signal.update keeps immutable object updates explicit.\nconst ripple = createRipple()\nconst user = ripple.signal({ name: 'Ada', visits: 0 })\nconst greeting = ripple.computed(() => user.value.name + ': ' + user.value.visits)\n\nconst stop = ripple.effect(() => console.log(greeting.value))\n\nuser.update((state) => ({ ...state, visits: state.visits + 1 }))\nuser.value = { name: 'Grace', visits: 5 }\n\nstop.dispose()\nripple.dispose()",
38
+ "name": "Immutable State"
39
+ },
40
+ {
41
+ "id": "watch-selected-value",
42
+ "code": "import { createRipple } from '@vielzeug/ripple'\n\n// Watch receives selected value transitions, not every graph update.\nconst ripple = createRipple()\nconst first = ripple.signal('Ada')\nconst last = ripple.signal('Lovelace')\nconst fullName = ripple.computed(() => first.value + ' ' + last.value)\n\nconst stop = ripple.watch(fullName, (value, previous) => {\n console.log({ previous, value })\n}, { immediate: true })\n\nfirst.value = 'Grace'\nlast.value = 'Hopper'\n\nstop.dispose()\nripple.dispose()",
43
+ "name": "Watch Selected Value"
44
+ }
45
+ ],
46
+ "typeSignatures": {
47
+ "AsyncState": "export type { AsyncState, Resource, ResourceOptions } from './_async';",
48
+ "Resource": "export type { AsyncState, Resource, ResourceOptions } from './_async';",
49
+ "ResourceOptions": "export type { AsyncState, Resource, ResourceOptions } from './_async';",
50
+ "createRipple": "export { createRipple, type Ripple } from './_default';",
51
+ "Ripple": "export { createRipple, type Ripple } from './_default';",
52
+ "WatchOptions": "export type { WatchOptions } from './_watch';",
53
+ "RippleComputedCycleError": "export {\n RippleComputedCycleError,\n RippleDisposedRuntimeError,\n RippleDisposedScopeError,\n RippleError,\n RippleInfiniteLoopError,\n} from './errors';",
54
+ "RippleDisposedRuntimeError": "export {\n RippleComputedCycleError,\n RippleDisposedRuntimeError,\n RippleDisposedScopeError,\n RippleError,\n RippleInfiniteLoopError,\n} from './errors';",
55
+ "RippleDisposedScopeError": "export {\n RippleComputedCycleError,\n RippleDisposedRuntimeError,\n RippleDisposedScopeError,\n RippleError,\n RippleInfiniteLoopError,\n} from './errors';",
56
+ "RippleError": "export {\n RippleComputedCycleError,\n RippleDisposedRuntimeError,\n RippleDisposedScopeError,\n RippleError,\n RippleInfiniteLoopError,\n} from './errors';",
57
+ "RippleInfiniteLoopError": "export {\n RippleComputedCycleError,\n RippleDisposedRuntimeError,\n RippleDisposedScopeError,\n RippleError,\n RippleInfiniteLoopError,\n} from './errors';",
58
+ "isReactive": "export { isReactive } from './runtime';",
59
+ "Cleanup": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
60
+ "ComputedOptions": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
61
+ "Disposable": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
62
+ "EffectHandle": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
63
+ "EffectOptions": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
64
+ "Equality": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
65
+ "ReactiveErrorContext": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
66
+ "ReactiveEvent": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
67
+ "ReactiveObserver": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
68
+ "Readable": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
69
+ "RippleOptions": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
70
+ "Scope": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
71
+ "Signal": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
72
+ "SignalOptions": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
73
+ "Unsubscribe": "export type {\n Cleanup,\n ComputedOptions,\n Disposable,\n EffectHandle,\n EffectOptions,\n Equality,\n ReactiveErrorContext,\n ReactiveEvent,\n ReactiveObserver,\n Readable,\n RippleOptions,\n Scope,\n Signal,\n SignalOptions,\n Unsubscribe,\n} from './types';",
74
+ "signal": "export const signal = defaultRipple.signal;",
75
+ "computed": "export const computed = defaultRipple.computed;",
76
+ "effect": "export const effect = defaultRipple.effect;",
77
+ "batch": "export const batch = defaultRipple.batch;",
78
+ "createScope": "export const createScope = defaultRipple.createScope;",
79
+ "untrack": "export const untrack = defaultRipple.untrack;",
80
+ "watch": "export const watch = defaultRipple.watch;",
81
+ "resource": "export const resource = defaultRipple.resource;"
82
+ }
83
+ }