experimental-a2 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/AGENTS.md +11 -0
  2. package/CHANGELOG.md +36 -0
  3. package/README.md +29 -0
  4. package/dist/actor-client.d.ts +46 -0
  5. package/dist/actor-client.d.ts.map +1 -0
  6. package/dist/actor-client.js +54 -0
  7. package/dist/actor-client.js.map +1 -0
  8. package/dist/actor-react.d.ts +54 -0
  9. package/dist/actor-react.d.ts.map +1 -0
  10. package/dist/actor-react.js +79 -0
  11. package/dist/actor-react.js.map +1 -0
  12. package/dist/actor-shared-BACubf4x.d.ts +136 -0
  13. package/dist/actor-shared-BACubf4x.d.ts.map +1 -0
  14. package/dist/actor-shared-DI7J5upy.js +127 -0
  15. package/dist/actor-shared-DI7J5upy.js.map +1 -0
  16. package/dist/actor.browser.d.ts +1 -0
  17. package/dist/actor.browser.js +13 -0
  18. package/dist/actor.browser.js.map +1 -0
  19. package/dist/actor.d.ts +176 -0
  20. package/dist/actor.d.ts.map +1 -0
  21. package/dist/actor.js +437 -0
  22. package/dist/actor.js.map +1 -0
  23. package/dist/ai-server.d.ts +2 -2
  24. package/dist/ai-server.js +2 -2
  25. package/dist/ai.d.ts +2 -2
  26. package/dist/client.d.ts +1 -1
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +4 -4
  29. package/dist/client.js.map +1 -1
  30. package/dist/{errors-BQuJpe82.js → errors-DCk6ch5n.js} +16 -2
  31. package/dist/{errors-BQuJpe82.js.map → errors-DCk6ch5n.js.map} +1 -1
  32. package/dist/{idempotent-replay-DuqEkYA7.js → idempotent-replay-DVOlyYbx.js} +2 -2
  33. package/dist/{idempotent-replay-DuqEkYA7.js.map → idempotent-replay-DVOlyYbx.js.map} +1 -1
  34. package/dist/index.d.ts +16 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +2 -2
  37. package/dist/react.d.ts +1 -1
  38. package/dist/{contract-jIfaR085.d.ts → reducer-DJKWm3cp.d.ts} +39 -39
  39. package/dist/reducer-DJKWm3cp.d.ts.map +1 -0
  40. package/dist/scheduler-qstash.d.ts +2 -2
  41. package/dist/scheduler-qstash.js +2 -2
  42. package/dist/scheduler-vercel.d.ts +2 -2
  43. package/dist/scheduler-vercel.js +1 -1
  44. package/dist/{server-B2XNevQA.js → server-CBET-jSz.js} +6 -6
  45. package/dist/server-CBET-jSz.js.map +1 -0
  46. package/dist/{server-DjPhHnbI.d.ts → server-CKY3_lbw.d.ts} +3 -3
  47. package/dist/{server-DjPhHnbI.d.ts.map → server-CKY3_lbw.d.ts.map} +1 -1
  48. package/dist/server.d.ts +3 -3
  49. package/dist/server.js +1 -1
  50. package/dist/{store-RJO35BMj.d.ts → store-DGHeBtIQ.d.ts} +2 -2
  51. package/dist/{store-RJO35BMj.d.ts.map → store-DGHeBtIQ.d.ts.map} +1 -1
  52. package/dist/store-memory.d.ts +1 -1
  53. package/dist/store-memory.js +2 -2
  54. package/dist/store-postgres.d.ts +1 -1
  55. package/dist/store-postgres.js +2 -2
  56. package/dist/{store-redis-core-DT01r4GZ.js → store-redis-core-z-ykbyMg.js} +3 -3
  57. package/dist/{store-redis-core-DT01r4GZ.js.map → store-redis-core-z-ykbyMg.js.map} +1 -1
  58. package/dist/store-redis-http.d.ts +1 -1
  59. package/dist/store-redis-http.js +2 -2
  60. package/dist/store-redis.d.ts +1 -1
  61. package/dist/store-redis.js +2 -2
  62. package/dist/store-sqlite.d.ts +1 -1
  63. package/dist/store-sqlite.js +2 -2
  64. package/dist/{wire-B6te_wns.js → wire--yji6mO3.js} +2 -2
  65. package/dist/{wire-B6te_wns.js.map → wire--yji6mO3.js.map} +1 -1
  66. package/docs/actors/01-introduction.mdx +189 -0
  67. package/docs/actors/02-concurrency.mdx +154 -0
  68. package/docs/actors/03-timers.mdx +120 -0
  69. package/docs/actors/04-routes.mdx +352 -0
  70. package/docs/actors/meta.ts +1 -0
  71. package/docs/concepts/meta.ts +1 -0
  72. package/docs/guides/07-examples.mdx +56 -0
  73. package/docs/guides/meta.ts +1 -0
  74. package/docs/index.mdx +16 -0
  75. package/docs/reference/02-errors.mdx +33 -0
  76. package/docs/reference/meta.ts +1 -0
  77. package/examples/README.md +15 -0
  78. package/examples/playground/AGENTS.md +11 -0
  79. package/examples/playground/DEPLOY.md +106 -0
  80. package/examples/playground/README.md +19 -0
  81. package/examples/playground/activity-feed.test.ts +10 -0
  82. package/examples/playground/app/agent/[agentId]/agent-client.tsx +376 -0
  83. package/examples/playground/app/agent/[agentId]/page.tsx +29 -0
  84. package/examples/playground/app/agent/events/route.ts +4 -0
  85. package/examples/playground/app/agent/model.ts +3 -0
  86. package/examples/playground/app/agent/new-agent-session.tsx +98 -0
  87. package/examples/playground/app/agent/page.tsx +25 -0
  88. package/examples/playground/app/agent/scheduler/route.ts +5 -0
  89. package/examples/playground/app/agent/server.ts +153 -0
  90. package/examples/playground/app/agent/session.ts +13 -0
  91. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +682 -0
  92. package/examples/playground/app/canvas/[canvasId]/canvas-replay.test.ts +68 -0
  93. package/examples/playground/app/canvas/[canvasId]/canvas-replay.ts +19 -0
  94. package/examples/playground/app/canvas/[canvasId]/page.tsx +22 -0
  95. package/examples/playground/app/canvas/[canvasId]/session.ts +19 -0
  96. package/examples/playground/app/canvas/events/route.ts +13 -0
  97. package/examples/playground/app/canvas/model.ts +94 -0
  98. package/examples/playground/app/canvas/open-canvas.tsx +40 -0
  99. package/examples/playground/app/canvas/page.tsx +20 -0
  100. package/examples/playground/app/canvas/server.ts +9 -0
  101. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.test.tsx +118 -0
  102. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +316 -0
  103. package/examples/playground/app/chat/[chatId]/chat-client.tsx +922 -0
  104. package/examples/playground/app/chat/[chatId]/chat-view.test.ts +152 -0
  105. package/examples/playground/app/chat/[chatId]/chat-view.ts +101 -0
  106. package/examples/playground/app/chat/[chatId]/composer.test.ts +44 -0
  107. package/examples/playground/app/chat/[chatId]/composer.ts +30 -0
  108. package/examples/playground/app/chat/[chatId]/page.tsx +30 -0
  109. package/examples/playground/app/chat/[chatId]/session.ts +7 -0
  110. package/examples/playground/app/chat/events/route.ts +7 -0
  111. package/examples/playground/app/chat/model.test.ts +155 -0
  112. package/examples/playground/app/chat/model.ts +310 -0
  113. package/examples/playground/app/chat/new-conversation.tsx +16 -0
  114. package/examples/playground/app/chat/page.tsx +25 -0
  115. package/examples/playground/app/chat/scheduler/route.ts +5 -0
  116. package/examples/playground/app/chat/server.ts +184 -0
  117. package/examples/playground/app/components/activity-feed.tsx +54 -0
  118. package/examples/playground/app/components/connection-pill.tsx +29 -0
  119. package/examples/playground/app/counter/counter-client.tsx +72 -0
  120. package/examples/playground/app/counter/events/route.ts +4 -0
  121. package/examples/playground/app/counter/model.test.ts +36 -0
  122. package/examples/playground/app/counter/model.ts +31 -0
  123. package/examples/playground/app/counter/page.tsx +24 -0
  124. package/examples/playground/app/counter/server.ts +9 -0
  125. package/examples/playground/app/counter/session.ts +13 -0
  126. package/examples/playground/app/documents/[documentId]/code-editor.tsx +80 -0
  127. package/examples/playground/app/documents/[documentId]/document-client.tsx +525 -0
  128. package/examples/playground/app/documents/[documentId]/page.tsx +23 -0
  129. package/examples/playground/app/documents/[documentId]/session.ts +7 -0
  130. package/examples/playground/app/documents/events/route.ts +4 -0
  131. package/examples/playground/app/documents/model.ts +55 -0
  132. package/examples/playground/app/documents/open-document.tsx +40 -0
  133. package/examples/playground/app/documents/page.tsx +22 -0
  134. package/examples/playground/app/documents/server.ts +9 -0
  135. package/examples/playground/app/globals.css +2078 -0
  136. package/examples/playground/app/layout.tsx +44 -0
  137. package/examples/playground/app/orders/[orderId]/order-client.tsx +140 -0
  138. package/examples/playground/app/orders/[orderId]/page.tsx +29 -0
  139. package/examples/playground/app/orders/[orderId]/session.ts +11 -0
  140. package/examples/playground/app/orders/create/route.ts +30 -0
  141. package/examples/playground/app/orders/events/route.ts +4 -0
  142. package/examples/playground/app/orders/model.ts +79 -0
  143. package/examples/playground/app/orders/new-order-form.tsx +98 -0
  144. package/examples/playground/app/orders/page.tsx +22 -0
  145. package/examples/playground/app/orders/scheduler/route.ts +5 -0
  146. package/examples/playground/app/orders/server.ts +50 -0
  147. package/examples/playground/app/page.tsx +111 -0
  148. package/examples/playground/app/recovery/[recoveryId]/page.tsx +31 -0
  149. package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +144 -0
  150. package/examples/playground/app/recovery/[recoveryId]/session.ts +7 -0
  151. package/examples/playground/app/recovery/events/route.ts +3 -0
  152. package/examples/playground/app/recovery/model.ts +55 -0
  153. package/examples/playground/app/recovery/new-recovery-session.tsx +20 -0
  154. package/examples/playground/app/recovery/page.tsx +22 -0
  155. package/examples/playground/app/recovery/scheduler/route.ts +7 -0
  156. package/examples/playground/app/recovery/server.ts +53 -0
  157. package/examples/playground/app/recovery/start/route.ts +41 -0
  158. package/examples/playground/app/vault/[vaultId]/route.ts +19 -0
  159. package/examples/playground/app/vault/page.tsx +12 -0
  160. package/examples/playground/app/vault/server.ts +9 -0
  161. package/examples/playground/app/vault/vault-client.tsx +124 -0
  162. package/examples/playground/app/vault/vault.test.ts +147 -0
  163. package/examples/playground/app/vault/vault.ts +119 -0
  164. package/examples/playground/css.d.ts +4 -0
  165. package/examples/playground/lib/store.ts +15 -0
  166. package/examples/playground/next-env.d.ts +5 -0
  167. package/examples/playground/next.config.ts +10 -0
  168. package/examples/playground/package.json +46 -0
  169. package/examples/playground/tsconfig.json +37 -0
  170. package/examples/playground/vercel.json +40 -0
  171. package/package.json +11 -2
  172. package/src/actor-client.ts +132 -0
  173. package/src/actor-react.ts +143 -0
  174. package/src/actor-shared.ts +356 -0
  175. package/src/actor.browser.ts +12 -0
  176. package/src/actor.ts +914 -0
  177. package/src/client.ts +9 -1
  178. package/src/errors.ts +15 -0
  179. package/src/index.ts +1 -1
  180. package/src/server.ts +13 -3
  181. package/dist/contract-jIfaR085.d.ts.map +0 -1
  182. package/dist/server-B2XNevQA.js.map +0 -1
package/AGENTS.md ADDED
@@ -0,0 +1,11 @@
1
+ # Using A2 from this package
2
+
3
+ When answering questions or editing applications that use A2:
4
+
5
+ 1. Read `docs/` for the supported API and guarantees.
6
+ 2. Read `examples/playground/` for complete application structure.
7
+ 3. Read `src/` only when implementation details are relevant.
8
+
9
+ Do not infer application patterns from `dist/`. The playground uses public
10
+ package entry points and shows where contracts, reducers, servers, route
11
+ handlers, clients, authorization, WebSocket capabilities, and schedulers live.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a29bad2: New experimental actor module: durable-objects-shaped actors built on
8
+ sessions. Three entry points: `experimental-a2/actor` (server),
9
+ `experimental-a2/actor/client` (framework-agnostic typed calls), and
10
+ `experimental-a2/actor/react` (the `useActor` hook).
11
+
12
+ Define an actor over a protocol of types (`state`, `events`, optional
13
+ `presence`): handlers are serial per instance (one message at a time,
14
+ one atomic commit at return), `concurrent(...)` opts a handler off the
15
+ lane for slow work, `ctx.send` buffers typed self-sends into the
16
+ commit, and `ctx.schedule` arms protocol-typed durable timers. Handles
17
+ expose `call` (send and await the answer), `send`, `state`, and a
18
+ managed `fetch` door: GET streams state commits over SSE, POST serves
19
+ calls and presence envelopes, with per-operation `authorize` and
20
+ per-mount `view` projections. Presence is protocol-typed audience
21
+ state: `participant`, `presence`, and `setPresence` on `useActor`.
22
+
23
+ Core additions: `NonRetriableError` (a deterministic failure that
24
+ dead-letters on its first throw instead of burning the retry budget)
25
+ and a `'*'` catch-all entry in presence vocabularies (unknown fields
26
+ validate against it; object-plumbing names never do).
27
+
28
+ Docs: a new Actors section (introduction, concurrency, deadlines and
29
+ timers, routes and clients).
30
+
31
+ ## 0.8.1
32
+
33
+ ### Patch Changes
34
+
35
+ - 3bff761: Include a standalone snapshot of the complete Next.js playground in the npm
36
+ package, with package and documentation indexes that direct coding agents to
37
+ the supported docs and example structure.
38
+
3
39
  ## 0.8.0
4
40
 
5
41
  ### Minor Changes
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # experimental-a2
2
+
3
+ Durable sync and reactions for things with a lifecycle. Each session has an
4
+ event log, derived state, handlers, timers, and a live client.
5
+
6
+ ## Read the package
7
+
8
+ The npm package includes more than compiled output:
9
+
10
+ - `docs/` defines the supported API.
11
+ - `examples/playground/` contains complete, standalone Next.js examples.
12
+ - `src/` contains the TypeScript implementation.
13
+
14
+ Coding agents should read `docs/` and `examples/playground/` before inferring
15
+ usage from `dist/` or internal implementation details. Every playground feature
16
+ owns its contract, reducer, server, route handlers, and scheduler when needed.
17
+ The package and copied playground both include `AGENTS.md` discovery files.
18
+
19
+ ## Copy the playground
20
+
21
+ ```sh
22
+ cp -R node_modules/experimental-a2/examples/playground ./a2-playground
23
+ cd a2-playground
24
+ pnpm install
25
+ pnpm dev
26
+ ```
27
+
28
+ The copied application has a standalone `package.json`. Its A2 dependency is
29
+ pinned to the package version that shipped the snapshot.
@@ -0,0 +1,46 @@
1
+ import { c as ActorRefusedError } from "./actor-shared-BACubf4x.js";
2
+ //#region src/actor-client.d.ts
3
+ /** The loose shape of an `actor()` definition — carried type-only. */
4
+ type AnyActorDefinition = {
5
+ actor(id: string): {
6
+ state(): Promise<{
7
+ state: unknown;
8
+ index: number;
9
+ }>;
10
+ call: object;
11
+ };
12
+ };
13
+ type HandleOf<D extends AnyActorDefinition> = ReturnType<D["actor"]>;
14
+ type ActorStateOf<D extends AnyActorDefinition> = Awaited<ReturnType<HandleOf<D>["state"]>>["state"];
15
+ /**
16
+ * The server handle's `call` record mirrored to the client, with
17
+ * answers carrying `View` — the projected shape when the route mounts
18
+ * `fetch(req, { view })`, the full state otherwise (the default).
19
+ */
20
+ type ActorClientCall<D extends AnyActorDefinition, View = ActorStateOf<D>> = { readonly [K in keyof HandleOf<D>["call"]]: HandleOf<D>["call"][K] extends ((...args: infer P) => Promise<{
21
+ state: unknown;
22
+ index: number;
23
+ }>) ? (...args: P) => Promise<{
24
+ state: View;
25
+ index: number;
26
+ }> : never; };
27
+ type ActorClient<D extends AnyActorDefinition, View = ActorStateOf<D>> = {
28
+ /** Typed calls over POST — the response is the answer. */
29
+ readonly call: ActorClientCall<D, View>;
30
+ };
31
+ type CreateActorClientOptions = {
32
+ /** The route whose handler delegates to `handle.fetch` for this instance. */
33
+ api: string;
34
+ /** Injectable transport (tests, custom auth headers). */
35
+ fetch?: typeof globalThis.fetch;
36
+ };
37
+ /**
38
+ * A typed call surface for one actor instance's route. When the route
39
+ * mounts a `view`, pass its shape as the second type argument so call
40
+ * answers carry the projected state:
41
+ * `createActorClient<typeof vault, PublicVault>({ api })`.
42
+ */
43
+ declare function createActorClient<D extends AnyActorDefinition, View = ActorStateOf<D>>(options: CreateActorClientOptions): ActorClient<D, View>;
44
+ //#endregion
45
+ export { ActorClient, ActorClientCall, ActorRefusedError, ActorStateOf, AnyActorDefinition, CreateActorClientOptions, createActorClient };
46
+ //# sourceMappingURL=actor-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actor-client.d.ts","names":[],"sources":["../src/actor-client.ts"],"mappings":";;;KAyBY;EACV,MAAM;IACJ,SAAS;MAAU;MAAgB;;IACnC;;;KAIC,SAAS,UAAU,sBAAsB,WAAW;KAE7C,aAAa,UAAU,sBAAsB,QACvD,WAAW,SAAS;;;;;;KAQV,gBACV,UAAU,oBACV,OAAO,aAAa,kBAEV,WAAW,SAAS,aAAa,SAAS,WAAW,gBAC1D,YAAY,MACZ;EAAU;EAAgB;UACvB,MAAM,MAAM;EAAU,OAAO;EAAM;;KAIjC,YACV,UAAU,oBACV,OAAO,aAAa;;WAGX,MAAM,gBAAgB,GAAG;;KAGxB;;EAEV;;EAEA,eAAe,WAAW;;;;;;;;iBAiDZ,kBACd,UAAU,oBACV,OAAO,aAAa,IACpB,SAAS,2BAA2B,YAAY,GAAG"}
@@ -0,0 +1,54 @@
1
+ import { i as ActorRefusedError } from "./actor-shared-DI7J5upy.js";
2
+ //#region src/actor-client.ts
3
+ /**
4
+ * experimental-a2/actor/client — typed calls to an actor over HTTP.
5
+ * Framework-agnostic: no React, no framework machinery; works in any
6
+ * browser code and server-to-server.
7
+ *
8
+ * `client.call` mirrors the server handle's `call` record over the
9
+ * call lane `handle.fetch` serves — POST `{ event, input, messageId? }`
10
+ * → the answer (post-state), a 409 refusal (revived as
11
+ * `ActorRefusedError`), or a 400. Both ends of that wire are library
12
+ * code: the types here are backed by `handle.fetch`, not by a
13
+ * hand-written convention.
14
+ *
15
+ * ```ts
16
+ * import type { vault } from '@/app/vault/server' // type-only, erased
17
+ * const client = createActorClient<typeof vault>({ api: `/vault/${id}` })
18
+ * await client.call.withdraw({ amount: 60 }) // ActorRefusedError on refusal
19
+ * ```
20
+ */
21
+ const CALL_TIMEOUT_MS = 3e4;
22
+ const callOverWire = async (fetchImpl, api, event, input, options) => {
23
+ const response = await fetchImpl(api, {
24
+ method: "POST",
25
+ headers: { "content-type": "application/json" },
26
+ body: JSON.stringify({
27
+ event,
28
+ ...input === void 0 ? {} : { input },
29
+ ...options?.id === void 0 ? {} : { messageId: options.id }
30
+ }),
31
+ signal: AbortSignal.timeout(options?.timeoutMs ?? CALL_TIMEOUT_MS)
32
+ });
33
+ const body = await response.json().catch(() => ({}));
34
+ if (response.status === 409 && typeof body["error"] === "string") throw new ActorRefusedError(typeof body["event"] === "string" ? body["event"] : event, typeof body["messageId"] === "string" ? body["messageId"] : "", body["error"]);
35
+ if (!response.ok) {
36
+ const message = typeof body["error"] === "string" ? body["error"] : `actor call failed (${response.status})`;
37
+ throw new Error(message);
38
+ }
39
+ return body;
40
+ };
41
+ /**
42
+ * A typed call surface for one actor instance's route. When the route
43
+ * mounts a `view`, pass its shape as the second type argument so call
44
+ * answers carry the projected state:
45
+ * `createActorClient<typeof vault, PublicVault>({ api })`.
46
+ */
47
+ function createActorClient(options) {
48
+ const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
49
+ return { call: new Proxy({}, { get: (_target, event) => typeof event === "string" ? (input, callOptions) => callOverWire(fetchImpl, options.api, event, input, callOptions) : void 0 }) };
50
+ }
51
+ //#endregion
52
+ export { ActorRefusedError, createActorClient };
53
+
54
+ //# sourceMappingURL=actor-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actor-client.js","names":[],"sources":["../src/actor-client.ts"],"sourcesContent":["/**\n * experimental-a2/actor/client — typed calls to an actor over HTTP.\n * Framework-agnostic: no React, no framework machinery; works in any\n * browser code and server-to-server.\n *\n * `client.call` mirrors the server handle's `call` record over the\n * call lane `handle.fetch` serves — POST `{ event, input, messageId? }`\n * → the answer (post-state), a 409 refusal (revived as\n * `ActorRefusedError`), or a 400. Both ends of that wire are library\n * code: the types here are backed by `handle.fetch`, not by a\n * hand-written convention.\n *\n * ```ts\n * import type { vault } from '@/app/vault/server' // type-only, erased\n * const client = createActorClient<typeof vault>({ api: `/vault/${id}` })\n * await client.call.withdraw({ amount: 60 }) // ActorRefusedError on refusal\n * ```\n */\n\nimport { ActorRefusedError } from './actor-shared.ts'\nimport type { ActorCallOptions, ActorCallResult } from './actor.ts'\n\nexport { ActorRefusedError }\n\n/** The loose shape of an `actor()` definition — carried type-only. */\nexport type AnyActorDefinition = {\n actor(id: string): {\n state(): Promise<{ state: unknown; index: number }>\n call: object\n }\n}\n\ntype HandleOf<D extends AnyActorDefinition> = ReturnType<D['actor']>\n\nexport type ActorStateOf<D extends AnyActorDefinition> = Awaited<\n ReturnType<HandleOf<D>['state']>\n>['state']\n\n/**\n * The server handle's `call` record mirrored to the client, with\n * answers carrying `View` — the projected shape when the route mounts\n * `fetch(req, { view })`, the full state otherwise (the default).\n */\nexport type ActorClientCall<\n D extends AnyActorDefinition,\n View = ActorStateOf<D>,\n> = {\n readonly [K in keyof HandleOf<D>['call']]: HandleOf<D>['call'][K] extends (\n ...args: infer P\n ) => Promise<{ state: unknown; index: number }>\n ? (...args: P) => Promise<{ state: View; index: number }>\n : never\n}\n\nexport type ActorClient<\n D extends AnyActorDefinition,\n View = ActorStateOf<D>,\n> = {\n /** Typed calls over POST — the response is the answer. */\n readonly call: ActorClientCall<D, View>\n}\n\nexport type CreateActorClientOptions = {\n /** The route whose handler delegates to `handle.fetch` for this instance. */\n api: string\n /** Injectable transport (tests, custom auth headers). */\n fetch?: typeof globalThis.fetch\n}\n\nconst CALL_TIMEOUT_MS = 30_000\n\nconst callOverWire = async (\n fetchImpl: typeof globalThis.fetch,\n api: string,\n event: string,\n input: unknown,\n options?: ActorCallOptions,\n): Promise<ActorCallResult<unknown>> => {\n const response = await fetchImpl(api, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n event,\n ...(input === undefined ? {} : { input }),\n ...(options?.id === undefined ? {} : { messageId: options.id }),\n }),\n signal: AbortSignal.timeout(options?.timeoutMs ?? CALL_TIMEOUT_MS),\n })\n const body = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >\n if (response.status === 409 && typeof body['error'] === 'string') {\n throw new ActorRefusedError(\n typeof body['event'] === 'string' ? body['event'] : event,\n typeof body['messageId'] === 'string' ? body['messageId'] : '',\n body['error'],\n )\n }\n if (!response.ok) {\n const message =\n typeof body['error'] === 'string'\n ? body['error']\n : `actor call failed (${response.status})`\n throw new Error(message)\n }\n return body as ActorCallResult<unknown>\n}\n\n/**\n * A typed call surface for one actor instance's route. When the route\n * mounts a `view`, pass its shape as the second type argument so call\n * answers carry the projected state:\n * `createActorClient<typeof vault, PublicVault>({ api })`.\n */\nexport function createActorClient<\n D extends AnyActorDefinition,\n View = ActorStateOf<D>,\n>(options: CreateActorClientOptions): ActorClient<D, View> {\n const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis)\n const call = new Proxy(\n {},\n {\n get: (_target, event) =>\n typeof event === 'string'\n ? (input?: unknown, callOptions?: ActorCallOptions) =>\n callOverWire(fetchImpl, options.api, event, input, callOptions)\n : undefined,\n },\n ) as ActorClientCall<D, View>\n return { call }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqEA,MAAM,kBAAkB;AAExB,MAAM,eAAe,OACnB,WACA,KACA,OACA,OACA,YACsC;CACtC,MAAM,WAAW,MAAM,UAAU,KAAK;EACpC,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,KAAK,UAAU;GACnB;GACA,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM;GACvC,GAAI,SAAS,OAAO,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,QAAQ,GAAG;EAC/D,CAAC;EACD,QAAQ,YAAY,QAAQ,SAAS,aAAa,eAAe;CACnE,CAAC;CACD,MAAM,OAAQ,MAAM,SAAS,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;CAIpD,IAAI,SAAS,WAAW,OAAO,OAAO,KAAK,aAAa,UACtD,MAAM,IAAI,kBACR,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW,OACpD,OAAO,KAAK,iBAAiB,WAAW,KAAK,eAAe,IAC5D,KAAK,QACP;CAEF,IAAI,CAAC,SAAS,IAAI;EAChB,MAAM,UACJ,OAAO,KAAK,aAAa,WACrB,KAAK,WACL,sBAAsB,SAAS,OAAO;EAC5C,MAAM,IAAI,MAAM,OAAO;CACzB;CACA,OAAO;AACT;;;;;;;AAQA,SAAgB,kBAGd,SAAyD;CACzD,MAAM,YAAY,QAAQ,SAAS,WAAW,MAAM,KAAK,UAAU;CAWnE,OAAO,EAAE,MAAA,IAVQ,MACf,CAAC,GACD,EACE,MAAM,SAAS,UACb,OAAO,UAAU,YACZ,OAAiB,gBAChB,aAAa,WAAW,QAAQ,KAAK,OAAO,OAAO,WAAW,IAChE,KAAA,EACR,CAEU,EAAE;AAChB"}
@@ -0,0 +1,54 @@
1
+ import { a as ContractEvent, s as EventDefs } from "./reducer-DJKWm3cp.js";
2
+ import { i as ActorPresenceMap, o as ActorPresenceValues } from "./actor-shared-BACubf4x.js";
3
+ import { ActorClientCall, ActorStateOf, AnyActorDefinition } from "./actor-client.js";
4
+ import { Connection } from "./client.js";
5
+ //#region src/actor-react.d.ts
6
+ /** The server-to-client handoff: `await def.actor(id).state()`. */
7
+ type ActorSnapshot<S> = {
8
+ state: S;
9
+ index: number;
10
+ };
11
+ type UseActorOptions<View> = {
12
+ /** The route whose handler delegates to `handle.fetch` for this instance. */
13
+ api: string;
14
+ /** The instance id. */
15
+ id: string;
16
+ /** The server-rendered fold — the stream resumes after `index`. */
17
+ initial: ActorSnapshot<View>;
18
+ /** This client's presence identity — required to call `setPresence`. */
19
+ participant?: string;
20
+ };
21
+ /** The definition's presence vocabulary, `never` when undeclared. */
22
+ type PresenceOf<D extends AnyActorDefinition> = D extends {
23
+ protocol?: infer P;
24
+ } ? NonNullable<P> extends {
25
+ presence: infer Values extends object;
26
+ } ? Values : never : never;
27
+ type UseActorResult<D extends AnyActorDefinition, View> = {
28
+ /** The actor's state, live: every commit streams in and replaces it. */
29
+ state: View;
30
+ /** The stream frontier. */
31
+ index: number;
32
+ connection: Connection;
33
+ /** Typed calls over POST — the response is the answer. */
34
+ call: ActorClientCall<D, View>;
35
+ /** The state-commit feed this browser has observed. */
36
+ events: ContractEvent<EventDefs>[];
37
+ /**
38
+ * The replicated presence map: participant → field → latest value.
39
+ * Peer-authored — render values like user input. Empty (and
40
+ * `setPresence` untyped-callable-with-nothing) when the protocol
41
+ * declares no presence.
42
+ */
43
+ presence: ActorPresenceMap<PresenceOf<D>>;
44
+ /**
45
+ * Announce this client's presence fields (throttled, resent on
46
+ * reconnect; `null` clears a field). Requires `participant`.
47
+ */
48
+ setPresence: (values: ActorPresenceValues<PresenceOf<D>>) => void;
49
+ };
50
+ /** Use one actor instance: live state plus typed calls. No provider. */
51
+ declare function useActor<D extends AnyActorDefinition, View = ActorStateOf<D>>(options: UseActorOptions<View>): UseActorResult<D, View>;
52
+ //#endregion
53
+ export { ActorSnapshot, UseActorOptions, UseActorResult, useActor };
54
+ //# sourceMappingURL=actor-react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actor-react.d.ts","names":[],"sources":["../src/actor-react.ts"],"mappings":";;;;;;KA6CY,cAAc;EAAO,OAAO;EAAG;;KAE/B,gBAAgB;;EAE1B;;EAEA;;EAEA,SAAS,cAAc;;EAEvB;;;KAIG,WAAW,UAAU,sBAAsB;EAC9C,iBAAiB;IAEf,YAAY;EAAa,gBAAgB;IACvC;KAIM,eAAe,UAAU,oBAAoB;;EAEvD,OAAO;;EAEP;EACA,YAAY;;EAEZ,MAAM,gBAAgB,GAAG;;EAEzB,QAAQ,cAAc;;;;;;;EAOtB,UAAU,iBAAiB,WAAW;;;;;EAKtC,cAAc,QAAQ,oBAAoB,WAAW;;;iBA2BvC,SAAS,UAAU,oBAAoB,OAAO,aAAa,IACzE,SAAS,gBAAgB,QACxB,eAAe,GAAG"}
@@ -0,0 +1,79 @@
1
+ "use client";
2
+ import { createClient } from "./client.js";
3
+ import { useSession } from "./react.js";
4
+ import { l as openPresenceDefs } from "./actor-shared-DI7J5upy.js";
5
+ import { createActorClient } from "./actor-client.js";
6
+ //#region src/actor-react.ts
7
+ /**
8
+ * experimental-a2/actor/react — use an actor from React: live state
9
+ * plus typed calls, one hook. A thin wrapper: the state pump follows
10
+ * the SSE that `handle.fetch` serves (frontier resume, reconnection,
11
+ * heartbeat watchdog — both ends library code), and `call` is
12
+ * `createActorClient`'s typed surface (the framework-agnostic
13
+ * primitive for everything that isn't React).
14
+ *
15
+ * ```tsx
16
+ * // server component
17
+ * const initial = await vault.actor(id).state()
18
+ * // client component — typed by the server module, erased at build
19
+ * const { state, call, connection } = useActor<typeof vault>({
20
+ * api: `/vault/${id}`, // your route: auth, then handle.fetch
21
+ * id,
22
+ * initial,
23
+ * })
24
+ * await call.withdraw({ amount: 60 }) // ActorRefusedError on refusal
25
+ * ```
26
+ *
27
+ * The fold is library code (last write wins over state commits), so no
28
+ * reducers, schemas, or contracts appear in browser code. Live, not
29
+ * optimistic: handlers are server code deciding against serialized
30
+ * fresh state, so a local guess would be wrong exactly when the actor
31
+ * matters. On a view-projected mount, pass the projected shape as the
32
+ * second type argument — `useActor<typeof vault, PublicVault>` — and
33
+ * `initial`, `state`, and call answers all carry it.
34
+ */
35
+ /**
36
+ * The follower fold, untyped on purpose: the state type comes from the
37
+ * caller's `initial` handoff, and the fold never inspects state.
38
+ * `initialState` is never rendered — `initial` is required, so every
39
+ * session hydrates before it connects.
40
+ */
41
+ const followerReducer = {
42
+ name: "a2.actor.follower.v1",
43
+ events: {},
44
+ presence: openPresenceDefs,
45
+ fold: (state, event) => event.type === "a2.actor.state" ? event.payload.state : state,
46
+ initialState: void 0,
47
+ stateSchema: void 0
48
+ };
49
+ const clients = /* @__PURE__ */ new Map();
50
+ /** Use one actor instance: live state plus typed calls. No provider. */
51
+ function useActor(options) {
52
+ const { api } = options;
53
+ let client = clients.get(api);
54
+ if (!client) {
55
+ client = createClient({
56
+ reducer: followerReducer,
57
+ api
58
+ });
59
+ clients.set(api, client);
60
+ }
61
+ const session = useSession(client, options.id, {
62
+ hydrate: options.initial,
63
+ ...options.participant === void 0 ? {} : { participant: options.participant }
64
+ });
65
+ const { call } = createActorClient({ api });
66
+ return {
67
+ state: session.state,
68
+ index: session.index,
69
+ connection: session.connection,
70
+ call,
71
+ events: session.events,
72
+ presence: session.presence,
73
+ setPresence: session.setPresence
74
+ };
75
+ }
76
+ //#endregion
77
+ export { useActor };
78
+
79
+ //# sourceMappingURL=actor-react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actor-react.js","names":[],"sources":["../src/actor-react.ts"],"sourcesContent":["'use client'\n/**\n * experimental-a2/actor/react — use an actor from React: live state\n * plus typed calls, one hook. A thin wrapper: the state pump follows\n * the SSE that `handle.fetch` serves (frontier resume, reconnection,\n * heartbeat watchdog — both ends library code), and `call` is\n * `createActorClient`'s typed surface (the framework-agnostic\n * primitive for everything that isn't React).\n *\n * ```tsx\n * // server component\n * const initial = await vault.actor(id).state()\n * // client component — typed by the server module, erased at build\n * const { state, call, connection } = useActor<typeof vault>({\n * api: `/vault/${id}`, // your route: auth, then handle.fetch\n * id,\n * initial,\n * })\n * await call.withdraw({ amount: 60 }) // ActorRefusedError on refusal\n * ```\n *\n * The fold is library code (last write wins over state commits), so no\n * reducers, schemas, or contracts appear in browser code. Live, not\n * optimistic: handlers are server code deciding against serialized\n * fresh state, so a local guess would be wrong exactly when the actor\n * matters. On a view-projected mount, pass the projected shape as the\n * second type argument — `useActor<typeof vault, PublicVault>` — and\n * `initial`, `state`, and call answers all carry it.\n */\n\nimport { ACTOR_STATE_EVENT, openPresenceDefs } from './actor-shared.ts'\nimport type { ActorPresenceMap, ActorPresenceValues } from './actor-shared.ts'\nimport {\n createActorClient,\n type ActorClientCall,\n type ActorStateOf,\n type AnyActorDefinition,\n} from './actor-client.ts'\nimport { createClient } from './client.ts'\nimport type { A2Client, Connection } from './client.ts'\nimport type { ContractEvent, EventDefs, PresenceDefs } from './contract.ts'\nimport type { Reducer } from './reducer.ts'\nimport { useSession } from './react.ts'\n\n/** The server-to-client handoff: `await def.actor(id).state()`. */\nexport type ActorSnapshot<S> = { state: S; index: number }\n\nexport type UseActorOptions<View> = {\n /** The route whose handler delegates to `handle.fetch` for this instance. */\n api: string\n /** The instance id. */\n id: string\n /** The server-rendered fold — the stream resumes after `index`. */\n initial: ActorSnapshot<View>\n /** This client's presence identity — required to call `setPresence`. */\n participant?: string\n}\n\n/** The definition's presence vocabulary, `never` when undeclared. */\ntype PresenceOf<D extends AnyActorDefinition> = D extends {\n protocol?: infer P\n}\n ? NonNullable<P> extends { presence: infer Values extends object }\n ? Values\n : never\n : never\n\nexport type UseActorResult<D extends AnyActorDefinition, View> = {\n /** The actor's state, live: every commit streams in and replaces it. */\n state: View\n /** The stream frontier. */\n index: number\n connection: Connection\n /** Typed calls over POST — the response is the answer. */\n call: ActorClientCall<D, View>\n /** The state-commit feed this browser has observed. */\n events: ContractEvent<EventDefs>[]\n /**\n * The replicated presence map: participant → field → latest value.\n * Peer-authored — render values like user input. Empty (and\n * `setPresence` untyped-callable-with-nothing) when the protocol\n * declares no presence.\n */\n presence: ActorPresenceMap<PresenceOf<D>>\n /**\n * Announce this client's presence fields (throttled, resent on\n * reconnect; `null` clears a field). Requires `participant`.\n */\n setPresence: (values: ActorPresenceValues<PresenceOf<D>>) => void\n}\n\n/**\n * The follower fold, untyped on purpose: the state type comes from the\n * caller's `initial` handoff, and the fold never inspects state.\n * `initialState` is never rendered — `initial` is required, so every\n * session hydrates before it connects.\n */\nconst followerReducer: Reducer<EventDefs, unknown, PresenceDefs> = {\n name: 'a2.actor.follower.v1',\n events: {},\n // The open vocabulary ('*' catch-all): the browser cannot know a\n // server-defined field list, so the server stays the authority and\n // the protocol types honest callers at compile time.\n presence: openPresenceDefs,\n fold: (state, event) =>\n event.type === ACTOR_STATE_EVENT\n ? (event.payload as { state: unknown }).state\n : state,\n initialState: undefined,\n stateSchema: undefined,\n}\n\nconst clients = new Map<string, A2Client<EventDefs, unknown, PresenceDefs>>()\n\n/** Use one actor instance: live state plus typed calls. No provider. */\nexport function useActor<D extends AnyActorDefinition, View = ActorStateOf<D>>(\n options: UseActorOptions<View>,\n): UseActorResult<D, View> {\n const { api } = options\n let client = clients.get(api)\n if (!client) {\n client = createClient({ reducer: followerReducer, api })\n clients.set(api, client)\n }\n const session = useSession(client, options.id, {\n hydrate: options.initial,\n ...(options.participant === undefined\n ? {}\n : { participant: options.participant }),\n })\n const { call } = createActorClient<D, View>({ api })\n return {\n state: session.state as View,\n index: session.index,\n connection: session.connection,\n call,\n events: session.events,\n presence: session.presence as ActorPresenceMap<PresenceOf<D>>,\n setPresence: session.setPresence as (\n values: ActorPresenceValues<PresenceOf<D>>,\n ) => void,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiGA,MAAM,kBAA6D;CACjE,MAAM;CACN,QAAQ,CAAC;CAIT,UAAU;CACV,OAAO,OAAO,UACZ,MAAM,SAAA,mBACD,MAAM,QAA+B,QACtC;CACN,cAAc,KAAA;CACd,aAAa,KAAA;AACf;AAEA,MAAM,0BAAU,IAAI,IAAwD;;AAG5E,SAAgB,SACd,SACyB;CACzB,MAAM,EAAE,QAAQ;CAChB,IAAI,SAAS,QAAQ,IAAI,GAAG;CAC5B,IAAI,CAAC,QAAQ;EACX,SAAS,aAAa;GAAE,SAAS;GAAiB;EAAI,CAAC;EACvD,QAAQ,IAAI,KAAK,MAAM;CACzB;CACA,MAAM,UAAU,WAAW,QAAQ,QAAQ,IAAI;EAC7C,SAAS,QAAQ;EACjB,GAAI,QAAQ,gBAAgB,KAAA,IACxB,CAAC,IACD,EAAE,aAAa,QAAQ,YAAY;CACzC,CAAC;CACD,MAAM,EAAE,SAAS,kBAA2B,EAAE,IAAI,CAAC;CACnD,OAAO;EACL,OAAO,QAAQ;EACf,OAAO,QAAQ;EACf,YAAY,QAAQ;EACpB;EACA,QAAQ,QAAQ;EAChB,UAAU,QAAQ;EAClB,aAAa,QAAQ;CAGvB;AACF"}
@@ -0,0 +1,136 @@
1
+ import { h as StandardSchemaV1 } from "./reducer-DJKWm3cp.js";
2
+ import { f as ScheduleTiming } from "./server-CKY3_lbw.js";
3
+ //#region src/actor-shared.d.ts
4
+ /**
5
+ * A handler answered by throwing `NonRetriableError`: the event
6
+ * settled, the state did not change. Isomorphic — the server raises it
7
+ * from calls and `fetch()` serializes it as a 409; the client's `call`
8
+ * proxy revives it from that response.
9
+ */
10
+ declare class ActorRefusedError extends Error {
11
+ /** The refused event's name. */
12
+ readonly event: string;
13
+ /** The refused event's message id. */
14
+ readonly messageId: string;
15
+ constructor(event: string, messageId: string, message: string);
16
+ }
17
+ /**
18
+ * The declaration an actor is defined over: its state shape and its
19
+ * event vocabulary, as types. Core A2 contracts are schemas because
20
+ * the wire is untrusted; actor protocols are types because your
21
+ * server is trusted — same contract-first design language, dialed to
22
+ * the trust level.
23
+ */
24
+ type ActorProtocol = {
25
+ state: object;
26
+ events: object;
27
+ /**
28
+ * Optional third vocabulary: presence field → value type. Ephemeral
29
+ * audience state (who is here, cursors), replicated to subscribers
30
+ * and never stored in the log. Declaring it requires `presence:
31
+ * true` in the actor options — the wire bit types cannot carry.
32
+ */
33
+ presence?: object;
34
+ };
35
+ /** The protocol's presence vocabulary, `never` when undeclared. */
36
+ type ActorPresenceOf<D extends ActorProtocol> = D extends {
37
+ presence: infer P extends object;
38
+ } ? P : never;
39
+ /** A `setPresence` patch: changed fields, `null` clears one. */
40
+ type ActorPresenceValues<P> = { [F in keyof P & string]?: P[F] | null; };
41
+ /**
42
+ * The replicated presence map: participant → field → latest value,
43
+ * last write wins per field. Values are peer-authored — render them
44
+ * like user input.
45
+ */
46
+ type ActorPresenceMap<P> = {
47
+ [participant: string]: { [F in keyof P & string]?: {
48
+ value: P[F];
49
+ seen: number;
50
+ at: Date;
51
+ }; };
52
+ };
53
+ /**
54
+ * The typed self-send surface: one method per declared event —
55
+ * `ctx.send.transfer({ ref, amount })`. Buffered, not immediate: every
56
+ * send requested during a handler commits atomically with that
57
+ * handler's completion (a serial handler's state commit, a concurrent
58
+ * handler's settlement), riding core's returned-events semantics — so
59
+ * a reserve-and-trigger can never half-happen, and re-runs converge on
60
+ * the same deterministic message ids.
61
+ */
62
+ type ActorSend<E> = { readonly [K in keyof E]: {} extends E[K] ? (input?: E[K]) => void : (input: E[K]) => void; };
63
+ /**
64
+ * Timing for a scheduled event: exactly `{ delay: '5d' }` or
65
+ * `{ at: Date }` (core §6 semantics — relative delays anchor to the
66
+ * triggering message's durable `createdAt`, so re-runs resolve the
67
+ * same due time). `name` overrides the timer's identity — it defaults
68
+ * to the target event name, scoped to the triggering message, so one
69
+ * handler run gets one timer per target event unless named apart.
70
+ */
71
+ type ActorScheduleOptions = ScheduleTiming & {
72
+ name?: string;
73
+ };
74
+ /**
75
+ * The typed durable-timer surface: one method per declared event —
76
+ * `ctx.schedule.refund({ ref: ctx.id }, { delay: '1h' })`. Unlike
77
+ * `send`, scheduling is immediate, not buffered: it awaits provider
78
+ * acceptance at call time (a timer is provider-side, not a log row),
79
+ * so a handler that schedules and then refuses has still armed the
80
+ * timer. That is safe by the guarded-delivery idiom — the stale timer
81
+ * fires into an idempotent no-op — but it is the one exception to
82
+ * "refusals are total". Requires a configured `scheduler`.
83
+ */
84
+ type ActorScheduleSend<E> = { readonly [K in keyof E]: {} extends E[K] ? (input: E[K] | undefined, options: ActorScheduleOptions) => Promise<void> : (input: E[K], options: ActorScheduleOptions) => Promise<void>; };
85
+ /** What a serial handler receives alongside its typed input. */
86
+ type ActorContext<D extends ActorProtocol> = {
87
+ /** Mutable draft, committed atomically with the handler's completion. */
88
+ state: D["state"];
89
+ /** The message id — stable across re-runs; the idempotency key for external I/O. */
90
+ id: string;
91
+ /** Durable 1-based dispatch ordinal of this message. */
92
+ attempt: number;
93
+ /** The ordinary A2 handler signal — fires on claim expiry or supersession. */
94
+ signal: AbortSignal;
95
+ /** Typed buffered self-send — committed atomically with the state commit. */
96
+ send: ActorSend<D["events"]>;
97
+ /** Typed durable timers — immediate provider handoff, see ActorScheduleSend. */
98
+ schedule: ActorScheduleSend<D["events"]>;
99
+ };
100
+ /**
101
+ * What a `concurrent: true` handler receives. No draft — concurrent
102
+ * handlers run off the lane, in parallel, so state is a snapshot read
103
+ * (honest about staleness) and mutations happen by sending events
104
+ * whose serial handlers decide against fresh state.
105
+ */
106
+ type ActorConcurrentContext<D extends ActorProtocol> = {
107
+ /** The message id — stable across re-runs; the idempotency key for external I/O. */
108
+ id: string;
109
+ /** Durable 1-based dispatch ordinal of this message. */
110
+ attempt: number;
111
+ /** The ordinary A2 handler signal — fires on claim expiry or supersession. */
112
+ signal: AbortSignal;
113
+ /** Snapshot read — observational; the world moves while this runs. */
114
+ state(): Promise<{
115
+ state: D["state"];
116
+ index: number;
117
+ }>;
118
+ /** Typed buffered self-send — committed atomically with settlement. */
119
+ send: ActorSend<D["events"]>;
120
+ /** Typed durable timers — immediate provider handoff, see ActorScheduleSend. */
121
+ schedule: ActorScheduleSend<D["events"]>;
122
+ };
123
+ type ActorStatePayload<S> = {
124
+ state: S;
125
+ /** The event whose handler committed this state. */
126
+ event: string;
127
+ /** The message (invocation event) id this state answers. */
128
+ message: string;
129
+ };
130
+ /** The event vocabulary a client needs to follow an actor's state. */
131
+ type ActorClientEventDefs<S> = {
132
+ "a2.actor.state": StandardSchemaV1<ActorStatePayload<S>>;
133
+ };
134
+ //#endregion
135
+ export { ActorPresenceOf as a, ActorRefusedError as c, ActorSend as d, ActorPresenceMap as i, ActorScheduleOptions as l, ActorConcurrentContext as n, ActorPresenceValues as o, ActorContext as r, ActorProtocol as s, ActorClientEventDefs as t, ActorScheduleSend as u };
136
+ //# sourceMappingURL=actor-shared-BACubf4x.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actor-shared-BACubf4x.d.ts","names":[],"sources":["../src/actor-shared.ts"],"mappings":";;;;;;;;;cAgCa,0BAA0B;;WAE5B;;WAEA;EACT,YAAY,eAAe,mBAAmB;;;;;;;;;KAepC;EACV;EACA;;;;;;;EAOA;;;KAIU,gBAAgB,UAAU,iBAAiB;EACrD,gBAAgB;IAEd;;KAIQ,oBAAoB,QAC7B,WAAW,cAAc,EAAE;;;;;;KAQlB,iBAAiB;GAExB,yBAAA,WAAW;IAAgB,OAAO,EAAE;IAAI;IAAc,IAAI;;;;;;;;;;;;KAanD,UAAU,iBACV,WAAW,eAAe,EAAE,MACjC,QAAQ,EAAE,eACV,OAAO,EAAE;;;;;;;;;KAWJ,uBAAuB;EAAmB;;;;;;;;;;;;KAY1C,kBAAkB,iBAClB,WAAW,eAAe,EAAE,MACjC,OAAO,EAAE,gBAAgB,SAAS,yBAAyB,iBAC3D,OAAO,EAAE,IAAI,SAAS,yBAAyB;;KAI1C,aAAa,UAAU;;EAEjC,OAAO;;EAEP;;EAEA;;EAEA,QAAQ;;EAER,MAAM,UAAU;;EAEhB,UAAU,kBAAkB;;;;;;;;KASlB,uBAAuB,UAAU;;EAE3C;;EAEA;;EAEA,QAAQ;;EAER,SAAS;IAAU,OAAO;IAAY;;;EAEtC,MAAM,UAAU;;EAEhB,UAAU,kBAAkB;;KAGlB,kBAAkB;EAC5B,OAAO;;EAEP;;EAEA;;;KAWU,qBAAqB;EAC/B,kBAAkB,iBAAiB,kBAAkB"}
@@ -0,0 +1,127 @@
1
+ import { t as contract } from "./contract-48bUMgcL.js";
2
+ //#region src/actor-shared.ts
3
+ /**
4
+ * Internal shared vocabulary of the actor module — the reserved event
5
+ * envelopes, the protocol and context types, the state fold. Not an
6
+ * entry point: the public surfaces are experimental-a2/actor (server),
7
+ * /actor/client, and /actor/react.
8
+ *
9
+ * An actor is a durable mailbox with memory: one session per instance,
10
+ * events processed one at a time (a lane) unless a handler opts out
11
+ * with `concurrent: true`, state materialized as `a2.actor.state`
12
+ * events committed atomically with each handler's completion.
13
+ */
14
+ /** Event type of a committed state change — one per completed serial handler. */
15
+ const ACTOR_STATE_EVENT = "a2.actor.state";
16
+ /** Event type of a refusal — a handler that answered by throwing NonRetriableError. */
17
+ const ACTOR_FAILED_EVENT = "a2.actor.failed";
18
+ /** The per-instance lane every serial handler shares — the state's write lock. */
19
+ const ACTOR_LANE = "a2.actor";
20
+ /** Reducer identity — bump when the fold's meaning changes. */
21
+ const ACTOR_REDUCER_NAME = "a2.actor.v1";
22
+ /**
23
+ * A handler answered by throwing `NonRetriableError`: the event
24
+ * settled, the state did not change. Isomorphic — the server raises it
25
+ * from calls and `fetch()` serializes it as a 409; the client's `call`
26
+ * proxy revives it from that response.
27
+ */
28
+ var ActorRefusedError = class extends Error {
29
+ /** The refused event's name. */
30
+ event;
31
+ /** The refused event's message id. */
32
+ messageId;
33
+ constructor(event, messageId, message) {
34
+ super(message);
35
+ this.name = "ActorRefusedError";
36
+ this.event = event;
37
+ this.messageId = messageId;
38
+ }
39
+ };
40
+ const issue = (message) => ({ issues: [{ message }] });
41
+ const schema = (label, parse) => ({ "~standard": {
42
+ version: 1,
43
+ vendor: "a2",
44
+ validate(value) {
45
+ try {
46
+ return parse(value);
47
+ } catch (error) {
48
+ return issue(`${label}: ${error instanceof Error ? error.message : String(error)}`);
49
+ }
50
+ }
51
+ } });
52
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
53
+ /** True for plain JSON trees — what the log can store without loss. */
54
+ function isJsonTree(value, seen = /* @__PURE__ */ new Set()) {
55
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
56
+ if (typeof value === "number") return Number.isFinite(value);
57
+ if (typeof value !== "object") return false;
58
+ if (seen.has(value)) return false;
59
+ seen.add(value);
60
+ const valid = Array.isArray(value) ? value.every((item) => isJsonTree(item, seen)) : Object.getPrototypeOf(value) === Object.prototype && Object.values(value).every((item) => item === void 0 || isJsonTree(item, seen));
61
+ seen.delete(value);
62
+ return valid;
63
+ }
64
+ /** Per-field ceiling for presence values — presence is a cursor, not a document. */
65
+ const PRESENCE_VALUE_MAX_BYTES = 8192;
66
+ /**
67
+ * The open presence vocabulary: the '*' catch-all validates any field
68
+ * name against the JSON floor. The protocol types the vocabulary at
69
+ * compile time; the route's `authorize` sees every set before
70
+ * acceptance; renderers treat values as user input. Meaning has three
71
+ * guards — this schema only owns the bytes.
72
+ */
73
+ const openPresenceDefs = Object.freeze({ "*": { "~standard": {
74
+ version: 1,
75
+ vendor: "a2",
76
+ validate: (value) => {
77
+ if (!isJsonTree(value)) return { issues: [{ message: "presence values must be plain JSON trees" }] };
78
+ if (JSON.stringify(value).length > 8192) return { issues: [{ message: `presence values are capped at ${PRESENCE_VALUE_MAX_BYTES} bytes` }] };
79
+ return { value };
80
+ }
81
+ } } });
82
+ /**
83
+ * The `a2.actor.state` envelope: provenance plus the state, held to a
84
+ * plain JSON tree (a `Date` or `Map` in state fails loudly at commit,
85
+ * never silently coerces in the log).
86
+ */
87
+ function actorStateSchema() {
88
+ return schema(ACTOR_STATE_EVENT, (value) => {
89
+ if (!isRecord(value) || typeof value["event"] !== "string" || typeof value["message"] !== "string") return issue(`invalid ${ACTOR_STATE_EVENT} payload`);
90
+ const state = value["state"];
91
+ if (!isJsonTree(state)) return issue("the actor state must be a plain JSON tree");
92
+ return { value: {
93
+ state,
94
+ event: value["event"],
95
+ message: value["message"]
96
+ } };
97
+ });
98
+ }
99
+ const actorFailedSchema = schema(ACTOR_FAILED_EVENT, (value) => {
100
+ if (!isRecord(value) || typeof value["message"] !== "string" || typeof value["event"] !== "string" || typeof value["error"] !== "string") return issue(`invalid ${ACTOR_FAILED_EVENT} payload`);
101
+ return { value: {
102
+ message: value["message"],
103
+ event: value["event"],
104
+ error: value["error"]
105
+ } };
106
+ });
107
+ /**
108
+ * The library fold over an actor's state commits — last write wins.
109
+ * Pure library code: a browser bundle folds an actor's live state with
110
+ * no user code, so the actor definition itself stays server-only
111
+ * (import the server module's type for typing, this reducer for data).
112
+ */
113
+ function actorReducer(options) {
114
+ if (!isJsonTree(options.state)) throw new TypeError(`actor '${options.name}': the initial state must be a plain JSON tree`);
115
+ const events = { [ACTOR_STATE_EVENT]: actorStateSchema() };
116
+ return contract({
117
+ name: options.name,
118
+ events
119
+ }).reducer({
120
+ name: ACTOR_REDUCER_NAME,
121
+ initialState: options.state
122
+ }).fold((state, event) => event.type === "a2.actor.state" ? event.payload.state : state);
123
+ }
124
+ //#endregion
125
+ export { actorFailedSchema as a, isJsonTree as c, ActorRefusedError as i, openPresenceDefs as l, ACTOR_LANE as n, actorReducer as o, ACTOR_STATE_EVENT as r, actorStateSchema as s, ACTOR_FAILED_EVENT as t };
126
+
127
+ //# sourceMappingURL=actor-shared-DI7J5upy.js.map