experimental-a2 0.10.0 → 0.12.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 (121) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/actor-DJi3RsNu.d.ts +310 -0
  3. package/dist/actor-DJi3RsNu.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +13 -4
  5. package/dist/actor-client.d.ts.map +1 -1
  6. package/dist/actor-client.js +63 -7
  7. package/dist/actor-client.js.map +1 -1
  8. package/dist/actor-react.d.ts +5 -4
  9. package/dist/actor-react.d.ts.map +1 -1
  10. package/dist/actor-react.js +16 -2
  11. package/dist/actor-react.js.map +1 -1
  12. package/dist/actor.d.ts +2 -176
  13. package/dist/actor.js +13 -2
  14. package/dist/actor.js.map +1 -1
  15. package/dist/ai-server.d.ts +1 -1
  16. package/dist/ai-server.js +2 -2
  17. package/dist/ai.d.ts +1 -1
  18. package/dist/ai.js +1 -1
  19. package/dist/client-Bf6uSEAk.js +1342 -0
  20. package/dist/client-Bf6uSEAk.js.map +1 -0
  21. package/dist/client-P_NNNRM-.d.ts +243 -0
  22. package/dist/client-P_NNNRM-.d.ts.map +1 -0
  23. package/dist/client.d.ts +2 -208
  24. package/dist/client.js +2 -1206
  25. package/dist/errors-DCk6ch5n.js.map +1 -1
  26. package/dist/errors-DvhSXnxk.d.ts +28 -0
  27. package/dist/errors-DvhSXnxk.d.ts.map +1 -0
  28. package/dist/index.d.ts +3 -35
  29. package/dist/{internal-DRXJ56EI.js → internal-Dq2qYxou.js} +2 -2
  30. package/dist/{internal-DRXJ56EI.js.map → internal-Dq2qYxou.js.map} +1 -1
  31. package/dist/platform-B4TnJtWu.js +34 -0
  32. package/dist/platform-B4TnJtWu.js.map +1 -0
  33. package/dist/react.d.ts +3 -1
  34. package/dist/react.d.ts.map +1 -1
  35. package/dist/react.js +2 -1
  36. package/dist/react.js.map +1 -1
  37. package/dist/scheduler-qstash.d.ts +2 -2
  38. package/dist/scheduler-qstash.js +3 -2
  39. package/dist/scheduler-qstash.js.map +1 -1
  40. package/dist/scheduler-vercel.d.ts +2 -2
  41. package/dist/scheduler-vercel.js +2 -2
  42. package/dist/{server-DlLyvaSH.js → server-BeNADlCI.js} +171 -65
  43. package/dist/server-BeNADlCI.js.map +1 -0
  44. package/dist/{server-DgCrSuhB.d.ts → server-DjZZa1wr.d.ts} +9 -3
  45. package/dist/{server-DgCrSuhB.d.ts.map → server-DjZZa1wr.d.ts.map} +1 -1
  46. package/dist/server.d.ts +3 -3
  47. package/dist/server.js +2 -2
  48. package/dist/{store-DGHeBtIQ.d.ts → store-DtDOWLSn.d.ts} +4 -5
  49. package/dist/{store-DGHeBtIQ.d.ts.map → store-DtDOWLSn.d.ts.map} +1 -1
  50. package/dist/store-N8PXxDAS.js.map +1 -1
  51. package/dist/store-memory.d.ts +1 -1
  52. package/dist/store-memory.js +1 -1
  53. package/dist/{store-polling-6DW7F1DT.js → store-polling-CmxUbV93.js} +56 -7
  54. package/dist/store-polling-CmxUbV93.js.map +1 -0
  55. package/dist/store-postgres.d.ts +6 -4
  56. package/dist/store-postgres.d.ts.map +1 -1
  57. package/dist/store-postgres.js +701 -53
  58. package/dist/store-postgres.js.map +1 -1
  59. package/dist/store-presence-polling-C7-XZyW9.js +94 -0
  60. package/dist/store-presence-polling-C7-XZyW9.js.map +1 -0
  61. package/dist/store-redis-http.d.ts +2 -2
  62. package/dist/store-redis-http.d.ts.map +1 -1
  63. package/dist/store-redis-http.js +184 -38
  64. package/dist/store-redis-http.js.map +1 -1
  65. package/dist/{store-redis-core-z-ykbyMg.js → store-redis-notify-BUCyXOn0.js} +491 -27
  66. package/dist/store-redis-notify-BUCyXOn0.js.map +1 -0
  67. package/dist/store-redis.d.ts +5 -13
  68. package/dist/store-redis.d.ts.map +1 -1
  69. package/dist/store-redis.js +27 -271
  70. package/dist/store-redis.js.map +1 -1
  71. package/dist/store-sqlite.d.ts +1 -1
  72. package/dist/store-sqlite.js +1 -1
  73. package/dist/{wire--yji6mO3.js → wire-BO5wWCb1.js} +18 -16
  74. package/dist/wire-BO5wWCb1.js.map +1 -0
  75. package/docs/actors/04-routes.mdx +40 -8
  76. package/docs/guides/03-react.mdx +52 -10
  77. package/docs/guides/05-production.mdx +54 -5
  78. package/docs/guides/09-presence.mdx +12 -7
  79. package/docs/guides/10-transports.mdx +15 -2
  80. package/docs/reference/01-api.mdx +124 -17
  81. package/docs/reference/02-errors.mdx +45 -11
  82. package/examples/playground/package.json +1 -1
  83. package/package.json +1 -1
  84. package/src/actor-client.ts +114 -14
  85. package/src/actor-react.ts +18 -3
  86. package/src/actor.ts +16 -1
  87. package/src/client-errors.ts +51 -0
  88. package/src/client.ts +253 -96
  89. package/src/errors.ts +4 -9
  90. package/src/index.ts +1 -1
  91. package/src/internal.ts +1 -1
  92. package/src/postgres-notification-scope.ts +134 -0
  93. package/src/postgres-notifications.ts +244 -0
  94. package/src/postgres-pool.ts +65 -0
  95. package/src/postgres-resources.ts +185 -0
  96. package/src/presence-recovery.ts +120 -0
  97. package/src/react.ts +3 -0
  98. package/src/redis-http-subscriptions.ts +199 -0
  99. package/src/server.ts +160 -22
  100. package/src/session-socket.ts +25 -7
  101. package/src/sse.ts +17 -0
  102. package/src/store-polling.ts +32 -12
  103. package/src/store-postgres.ts +292 -75
  104. package/src/store-presence-polling.ts +125 -0
  105. package/src/store-redis-core.ts +37 -30
  106. package/src/store-redis-http.ts +51 -45
  107. package/src/store-redis-notify.ts +464 -0
  108. package/src/store-redis.ts +9 -364
  109. package/src/store.ts +3 -4
  110. package/src/stream-activity.ts +32 -0
  111. package/src/wire.ts +39 -15
  112. package/dist/actor-shared-USo5MyuF.d.ts +0 -136
  113. package/dist/actor-shared-USo5MyuF.d.ts.map +0 -1
  114. package/dist/actor.d.ts.map +0 -1
  115. package/dist/client.d.ts.map +0 -1
  116. package/dist/client.js.map +0 -1
  117. package/dist/index.d.ts.map +0 -1
  118. package/dist/server-DlLyvaSH.js.map +0 -1
  119. package/dist/store-polling-6DW7F1DT.js.map +0 -1
  120. package/dist/store-redis-core-z-ykbyMg.js.map +0 -1
  121. package/dist/wire--yji6mO3.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 523988e: Add `handler` to `experimental-a2/server` to normalize function and object handler entries into the object shape for composition while preserving event and presence types.
8
+
9
+ ## 0.11.0
10
+
11
+ ### Minor Changes
12
+
13
+ - bcd0782: Adopt actor call answers into React's live state before resolving, without
14
+ waiting for the stream. Add authorized, view-projected snapshot reads,
15
+ `client.state()`, `onSnapshot`, and `useActor().refresh()`. Older snapshots
16
+ cannot overwrite newer state. Track stream progress separately so delayed
17
+ commits still enter the event feed and reconnects resume without skipping them.
18
+ Expose HTTP status through `ActorRequestError`
19
+ so clients can distinguish request failures from handler refusals.
20
+ - 7bfab77: Add a caller-configurable reconnection policy and a paused connection state. Paused sessions retain their state, optimistic writes, and connection leases; `session.reconnect()` resumes them explicitly.
21
+
22
+ Expose `A2ClientError extends A2Error` from the client entry point for server and client failures, preserving available HTTP, WebSocket, and timeout details. Multiplexed subscription failures carry structured errors.
23
+
24
+ - 853fa79: Back idle event and presence polling off to two seconds. Local pushes reactivate idle streams: SSE refreshes from the current cursor without reconnect backoff, while WebSockets wake the attached session iterator in place. Pending writes keep their stream active until their acknowledged events arrive.
25
+
26
+ Add `session.reconnect()` and `useSession().reconnect()` to restart a leased live subscription after custom REST mutations. Reconnection preserves state, pending writes, and leases. Multiplexed WebSockets re-subscribe the selected session without replacing the shared socket.
27
+
28
+ Stop the previous session subscription before authorizing its replacement, so denied or failed reconnects cannot retain access through the old lane.
29
+
30
+ ### Patch Changes
31
+
32
+ - f92e00e: Reduce live-stream read overhead: Postgres selects only the six public event columns, and Redis HTTP reads public events directly without fetching dispatch metadata. Redis live reads use bounded pages while preserving cursor-based replay and delivery.
33
+ - 6080ac2: Add an optional listenConnectionString to the Postgres store for a direct LISTEN connection. Stores share query pools and listeners across module copies in the same runtime, without sharing application state. Transactional triggers notify event and presence subscribers across writers. Reconnects catch up, and the last subscriber releases the listener.
34
+
35
+ Pools created by A2 use ambient waitUntil on Vercel to let idle connections close before suspension while preserving reuse between requests.
36
+
37
+ Keep LISTEN startup errors visible while allowing transient presence seed-read failures to recover.
38
+
39
+ Keep requested presence snapshots independent of listener unsubscribe, returning the real database state while cancelled streams discard late results.
40
+
41
+ - f13c2cb: Stop idle event and presence subscriptions when clients disconnect or streams reach their invocation deadline. Cancellation now reaches the underlying store iterator while a session is waiting for its next event, preventing abandoned streams from continuing to poll the database.
42
+ - e65dc9a: Use Upstash HTTP subscriptions for live events and presence without extra configuration. Share subscriptions across local viewers, close them on disconnect, and catch up after reconnect. Retain ten-second event safety reads for missed notifications. Publish wake-ups inside Redis write scripts, removing the separate request and application crash gap. Fall back to adaptive event and presence polling when an HTTP endpoint explicitly lacks subscription support.
43
+
44
+ Reconcile presence deletions after subscription reconnects while preserving newer patches and discarding superseded initial snapshots.
45
+
46
+ Recover from initial presence seed-read failures without suppressing foreground snapshot errors.
47
+
3
48
  ## 0.10.0
4
49
 
5
50
  ### Minor Changes
@@ -0,0 +1,310 @@
1
+ import { h as StandardSchemaV1, i as Contract, s as EventDefs, t as Reducer } from "./reducer-DJKWm3cp.js";
2
+ import { t as A2Store } from "./store-DtDOWLSn.js";
3
+ import { f as ScheduleTiming, m as Session, n as A2Server, t as A2Scheduler } from "./server-DjZZa1wr.js";
4
+ import { i as A2Telemetry } from "./telemetry-CpeclqB2.js";
5
+ //#region src/actor-shared.d.ts
6
+ /**
7
+ * A handler answered by throwing `NonRetriableError`: the event
8
+ * settled, the state did not change. Isomorphic — the server raises it
9
+ * from calls and `fetch()` serializes it as a 409; the client's `call`
10
+ * proxy revives it from that response.
11
+ */
12
+ declare class ActorRefusedError extends Error {
13
+ /** The refused event's name. */
14
+ readonly event: string;
15
+ /** The refused event's message id. */
16
+ readonly messageId: string;
17
+ constructor(event: string, messageId: string, message: string);
18
+ }
19
+ /**
20
+ * The declaration an actor is defined over: its state shape and its
21
+ * event vocabulary, as types. Core A2 contracts are schemas because
22
+ * the wire is untrusted; actor protocols are types because your
23
+ * server is trusted — same contract-first design language, dialed to
24
+ * the trust level.
25
+ */
26
+ type ActorProtocol = {
27
+ state: object;
28
+ events: object;
29
+ /**
30
+ * Optional third vocabulary: presence field → value type. Ephemeral
31
+ * audience state (who is here, cursors), replicated to subscribers
32
+ * and never stored in the log. Declaring it requires `presence:
33
+ * true` in the actor options — the wire bit types cannot carry.
34
+ */
35
+ presence?: object;
36
+ };
37
+ /** The protocol's presence vocabulary, `never` when undeclared. */
38
+ type ActorPresenceOf<D extends ActorProtocol> = D extends {
39
+ presence: infer P extends object;
40
+ } ? P : never;
41
+ /** A `setPresence` patch: changed fields, `null` clears one. */
42
+ type ActorPresenceValues<P> = { [F in keyof P & string]?: P[F] | null; };
43
+ /**
44
+ * The replicated presence map: participant → field → latest value,
45
+ * last write wins per field. Values are peer-authored — render them
46
+ * like user input.
47
+ */
48
+ type ActorPresenceMap<P> = {
49
+ [participant: string]: { [F in keyof P & string]?: {
50
+ value: P[F];
51
+ seen: number;
52
+ at: Date;
53
+ }; };
54
+ };
55
+ /**
56
+ * The typed self-send surface: one method per declared event —
57
+ * `ctx.send.transfer({ ref, amount })`. Buffered, not immediate: every
58
+ * send requested during a handler commits atomically with that
59
+ * handler's completion (a serial handler's state commit, a concurrent
60
+ * handler's settlement), riding core's returned-events semantics — so
61
+ * a reserve-and-trigger can never half-happen, and re-runs converge on
62
+ * the same deterministic message ids.
63
+ */
64
+ type ActorSend<E> = { readonly [K in keyof E]: {} extends E[K] ? (input?: E[K]) => void : (input: E[K]) => void; };
65
+ /**
66
+ * Timing for a scheduled event: exactly `{ delay: '5d' }` or
67
+ * `{ at: Date }` (core §6 semantics — relative delays anchor to the
68
+ * triggering message's durable `createdAt`, so re-runs resolve the
69
+ * same due time). `name` overrides the timer's identity — it defaults
70
+ * to the target event name, scoped to the triggering message, so one
71
+ * handler run gets one timer per target event unless named apart.
72
+ */
73
+ type ActorScheduleOptions = ScheduleTiming & {
74
+ name?: string;
75
+ };
76
+ /**
77
+ * The typed durable-timer surface: one method per declared event —
78
+ * `ctx.schedule.refund({ ref: ctx.id }, { delay: '1h' })`. Unlike
79
+ * `send`, scheduling is immediate, not buffered: it awaits provider
80
+ * acceptance at call time (a timer is provider-side, not a log row),
81
+ * so a handler that schedules and then refuses has still armed the
82
+ * timer. That is safe by the guarded-delivery idiom — the stale timer
83
+ * fires into an idempotent no-op — but it is the one exception to
84
+ * "refusals are total". Requires a configured `scheduler`.
85
+ */
86
+ 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>; };
87
+ /** What a serial handler receives alongside its typed input. */
88
+ type ActorContext<D extends ActorProtocol> = {
89
+ /** Mutable draft, committed atomically with the handler's completion. */
90
+ state: D["state"];
91
+ /** The message id — stable across re-runs; the idempotency key for external I/O. */
92
+ id: string;
93
+ /** Durable 1-based dispatch ordinal of this message. */
94
+ attempt: number;
95
+ /** The ordinary A2 handler signal — fires on claim expiry or supersession. */
96
+ signal: AbortSignal;
97
+ /** Typed buffered self-send — committed atomically with the state commit. */
98
+ send: ActorSend<D["events"]>;
99
+ /** Typed durable timers — immediate provider handoff, see ActorScheduleSend. */
100
+ schedule: ActorScheduleSend<D["events"]>;
101
+ };
102
+ /**
103
+ * What a `concurrent: true` handler receives. No draft — concurrent
104
+ * handlers run off the lane, in parallel, so state is a snapshot read
105
+ * (honest about staleness) and mutations happen by sending events
106
+ * whose serial handlers decide against fresh state.
107
+ */
108
+ type ActorConcurrentContext<D extends ActorProtocol> = {
109
+ /** The message id — stable across re-runs; the idempotency key for external I/O. */
110
+ id: string;
111
+ /** Durable 1-based dispatch ordinal of this message. */
112
+ attempt: number;
113
+ /** The ordinary A2 handler signal — fires on claim expiry or supersession. */
114
+ signal: AbortSignal;
115
+ /** Snapshot read — observational; the world moves while this runs. */
116
+ state(): Promise<{
117
+ state: D["state"];
118
+ index: number;
119
+ }>;
120
+ /** Typed buffered self-send — committed atomically with settlement. */
121
+ send: ActorSend<D["events"]>;
122
+ /** Typed durable timers — immediate provider handoff, see ActorScheduleSend. */
123
+ schedule: ActorScheduleSend<D["events"]>;
124
+ };
125
+ type ActorStatePayload<S> = {
126
+ state: S;
127
+ /** The event whose handler committed this state. */
128
+ event: string;
129
+ /** The message (invocation event) id this state answers. */
130
+ message: string;
131
+ };
132
+ /** The event vocabulary a client needs to follow an actor's state. */
133
+ type ActorClientEventDefs<S> = {
134
+ "a2.actor.state": StandardSchemaV1<ActorStatePayload<S>>;
135
+ };
136
+ //#endregion
137
+ //#region src/actor.d.ts
138
+ /**
139
+ * One event's handler: ordinary server code. Serial by default — read
140
+ * `ctx.state`, perform I/O, mutate `ctx.state`, one atomic commit at
141
+ * return. `{ concurrent: true, handle }` opts the handler out of the
142
+ * lane for slow I/O: state becomes a snapshot read and mutations
143
+ * happen by sending events. The input is typed by the protocol; the
144
+ * wire hands handlers parsed JSON, so a wire-exposed event guards its
145
+ * input in its first line (`schema.parse(input)` if you like schemas —
146
+ * userland either way).
147
+ */
148
+ type ActorHandler<D extends ActorProtocol, K extends keyof D["events"]> = ((ctx: ActorContext<D>, input: D["events"][K]) => void | Promise<void>) | {
149
+ concurrent: true;
150
+ handle: (ctx: ActorConcurrentContext<D>, input: D["events"][K]) => void | Promise<void>;
151
+ };
152
+ /**
153
+ * Mark a handler concurrent — off the lane, in parallel with the lane
154
+ * and with other concurrent handlers, `ctx.state()` a snapshot read.
155
+ * Sugar for the structural `{ concurrent: true, handle }` form:
156
+ *
157
+ * ```ts
158
+ * handlers: {
159
+ * transfer: concurrent(async (ctx, input) => {
160
+ * await bank.transfer(input, { idempotencyKey: input.ref })
161
+ * ctx.send.settle({ ref: input.ref })
162
+ * }),
163
+ * }
164
+ * ```
165
+ */
166
+ declare function concurrent<Ctx, I>(handle: (ctx: Ctx, input: I) => void | Promise<void>): {
167
+ concurrent: true;
168
+ handle: (ctx: Ctx, input: I) => void | Promise<void>;
169
+ };
170
+ type ActorOptions<D extends ActorProtocol> = {
171
+ /** Identity — prefixes storage keys; one contract per actor definition. */
172
+ name: string;
173
+ /** The initial state — checked against the protocol, a plain JSON tree. */
174
+ state: D["state"];
175
+ /**
176
+ * One handler per declared event — completeness and payload shapes
177
+ * are compile-checked against the protocol.
178
+ */
179
+ handlers: { [K in keyof D["events"]]: ActorHandler<D, K>; };
180
+ store?: A2Store;
181
+ scheduler?: A2Scheduler;
182
+ telemetry?: A2Telemetry;
183
+ } & ([ActorPresenceOf<D>] extends [never] ? {
184
+ presence?: never;
185
+ } : {
186
+ /**
187
+ * The wire bit for the protocol's `presence` vocabulary — types
188
+ * erase, so the runtime needs one value to arm the presence
189
+ * lanes (frames on GET, envelopes on POST). Required exactly
190
+ * when the protocol declares `presence`; the vocabulary itself
191
+ * is typed there. Values are validated to the JSON floor and
192
+ * pass through `authorize` as `{ type: 'presence' }` operations;
193
+ * meaning stays with your renderers — treat values as user
194
+ * input.
195
+ */
196
+ presence: true;
197
+ });
198
+ /**
199
+ * One HTTP operation `handle.fetch` is about to serve — the actor
200
+ * mirror of core's `A2Operation`. `authorize` sees it before any
201
+ * read, write, or subscription; returning `false` answers 403.
202
+ */
203
+ type ActorOperation = {
204
+ readonly type: "state";
205
+ readonly id: string;
206
+ } | {
207
+ readonly type: "stream";
208
+ readonly id: string;
209
+ readonly startAfter: number;
210
+ } | {
211
+ readonly type: "call";
212
+ readonly id: string;
213
+ readonly event: string;
214
+ readonly input: unknown;
215
+ readonly messageId?: string;
216
+ } | {
217
+ readonly type: "presence";
218
+ readonly id: string;
219
+ readonly participant: string;
220
+ readonly values: Readonly<Record<string, unknown>>;
221
+ };
222
+ type ActorFetchOptions<D extends ActorProtocol> = {
223
+ /** Per-operation policy — authentication happened in your route. */
224
+ authorize?: (operation: ActorOperation) => boolean | Promise<boolean>;
225
+ /**
226
+ * Request-time projection: what this mount's audience sees. Applied
227
+ * to every state frame on the stream AND to call answers (the two
228
+ * untrusted lanes must agree, or the call lane leaks what the
229
+ * stream hides). The definition stays audience-agnostic — different
230
+ * routes project the same actor differently, and per-viewer views
231
+ * are just closures over the route's auth. Output is held to the
232
+ * JSON-tree floor like everything else. Transport-agnostic: applied
233
+ * at frame emission, so any wire the door speaks emits projected
234
+ * frames. Absent, the full state ships (the trusted default).
235
+ */
236
+ view?: (state: D["state"]) => unknown;
237
+ };
238
+ type ActorSendOptions = {
239
+ /** Explicit message id — makes retries of this send idempotent. */
240
+ id?: string;
241
+ };
242
+ type ActorCallOptions = ActorSendOptions & {
243
+ /** How long to await the answer before rejecting the wait (default 30s). */
244
+ timeoutMs?: number;
245
+ };
246
+ type ActorCallResult<S> = {
247
+ /** The actor's state after this message was processed. */
248
+ state: S;
249
+ /** Log index of the state commit that answered this message. */
250
+ index: number;
251
+ };
252
+ type CallMethod<D extends ActorProtocol, K extends keyof D["events"]> = {} extends D["events"][K] ? (input?: D["events"][K], options?: ActorCallOptions) => Promise<ActorCallResult<D["state"]>> : (input: D["events"][K], options?: ActorCallOptions) => Promise<ActorCallResult<D["state"]>>;
253
+ type SendMethod<D extends ActorProtocol, K extends keyof D["events"]> = {} extends D["events"][K] ? (input?: D["events"][K], options?: ActorSendOptions) => Promise<{
254
+ id: string;
255
+ index: number;
256
+ }> : (input: D["events"][K], options?: ActorSendOptions) => Promise<{
257
+ id: string;
258
+ index: number;
259
+ }>;
260
+ /** One instance: typed calls and sends, reads, the managed door, the escape hatch. */
261
+ type ActorHandle<D extends ActorProtocol> = {
262
+ readonly id: string;
263
+ /**
264
+ * Send the event and await its answer: the state after the handler
265
+ * ran, or a rejection with `ActorRefusedError`. Serial events only —
266
+ * concurrent handlers produce no answer to await; `send` them.
267
+ */
268
+ readonly call: { readonly [K in keyof D["events"]]: CallMethod<D, K>; };
269
+ /** Cast: validate, append the event durably, return without waiting. */
270
+ readonly send: { readonly [K in keyof D["events"]]: SendMethod<D, K>; };
271
+ /** Snapshot read — never queues behind pending messages. */
272
+ readonly state: () => Promise<ActorCallResult<D["state"]>>;
273
+ /**
274
+ * The managed door — the actor mirror of `server.fetch`, bound to
275
+ * this instance. GET streams the state plane (`a2.actor.state`
276
+ * commits only, `?index` resume, heartbeat, deadline rotation);
277
+ * POST is the call lane `createActorClient` speaks (`{ event,
278
+ * input, messageId? }` → the answer, 409 for a refusal). Your route
279
+ * authenticates and picks the instance; `authorize` sees every
280
+ * operation. Custom endpoints are route code around this call.
281
+ */
282
+ readonly fetch: (request: Request, options?: ActorFetchOptions<D>) => Promise<Response>;
283
+ /** Escape hatch: the raw A2 session (history, stream, schedule). */
284
+ readonly session: Session<EventDefs>;
285
+ };
286
+ type ActorDefinition<D extends ActorProtocol> = {
287
+ readonly name: string;
288
+ /**
289
+ * Type-only carrier of the protocol, so clients deriving from
290
+ * `typeof def` can see the vocabularies (`experimental-a2/actor/react`
291
+ * types `presence` from it). Never a runtime value.
292
+ */
293
+ readonly protocol?: D;
294
+ /** The assembled contract — declared events plus the reserved a2.actor.* pair. */
295
+ readonly contract: Contract<EventDefs>;
296
+ /** The underlying A2 server — `server.fetch` mounts the full session wire. */
297
+ readonly server: A2Server<EventDefs>;
298
+ /** The state fold — same identity as the client's follower fold. */
299
+ readonly reducer: Reducer<ActorClientEventDefs<D["state"]>, D["state"]>;
300
+ actor(id: string): ActorHandle<D>;
301
+ };
302
+ /**
303
+ * Define and serve an actor over its protocol: a named, durable
304
+ * instance-per-id with serialized state handlers, concurrent I/O
305
+ * handlers, and typed self-sends.
306
+ */
307
+ declare function actor<D extends ActorProtocol>(options: ActorOptions<D>): ActorDefinition<D>;
308
+ //#endregion
309
+ export { ActorProtocol as _, ActorHandle as a, ActorScheduleSend as b, ActorOptions as c, concurrent as d, ActorConcurrentContext as f, ActorPresenceValues as g, ActorPresenceOf as h, ActorFetchOptions as i, ActorSendOptions as l, ActorPresenceMap as m, ActorCallResult as n, ActorHandler as o, ActorContext as p, ActorDefinition as r, ActorOperation as s, ActorCallOptions as t, actor as u, ActorRefusedError as v, ActorSend as x, ActorScheduleOptions as y };
310
+ //# sourceMappingURL=actor-DJi3RsNu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actor-DJi3RsNu.d.ts","names":[],"sources":["../src/actor-shared.ts","../src/actor.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;;;;;;;;;;;;;;KCzE3C,aAAa,UAAU,eAAe,gBAAgB,iBAC5D,KAAK,aAAa,IAAI,OAAO,YAAY,cAAc;EAEvD;EACA,SACE,KAAK,uBAAuB,IAC5B,OAAO,YAAY,cACT;;;;;;;;;;;;;;;;iBAiBF,WAAW,KAAK,GAC9B,SAAS,KAAK,KAAK,OAAO,aAAa;EACpC;EAAkB,SAAS,KAAK,KAAK,OAAO,aAAa;;KAIlD,aAAa,UAAU;;EAEjC;;EAEA,OAAO;;;;;EAKP,aAAa,WAAW,cAAc,aAAa,GAAG;EACtD,QAAQ;EACR,YAAY;EACZ,YAAY;MACR,gBAAgB;EAChB;;;;;;;;;;;;EAYA;;;;;;;KAQM;WACG;WAAwB;;WAExB;WACA;WACA;;WAGA;WACA;WACA;WACA;WACA;;WAGA;WACA;WACA;WACA,QAAQ,SAAS;;KAGpB,kBAAkB,UAAU;;EAEtC,aAAa,WAAW,6BAA6B;;;;;;;;;;;;EAYrD,QAAQ,OAAO;;KAGL;;EAEV;;KAGU,mBAAmB;;EAE7B;;KAGU,gBAAgB;;EAE1B,OAAO;;EAEP;;KAGG,WACH,UAAU,eACV,gBAAgB,0BACH,YAAY,MAErB,QAAQ,YAAY,IACpB,UAAU,qBACP,QAAQ,gBAAgB,gBAE3B,OAAO,YAAY,IACnB,UAAU,qBACP,QAAQ,gBAAgB;KAE5B,WACH,UAAU,eACV,gBAAgB,0BACH,YAAY,MAErB,QAAQ,YAAY,IACpB,UAAU,qBACP;EAAU;EAAY;MAEzB,OAAO,YAAY,IACnB,UAAU,qBACP;EAAU;EAAY;;;KAGnB,YAAY,UAAU;WACvB;;;;;;WAMA,kBAAkB,WAAW,cAAc,WAAW,GAAG;;WAEzD,kBAAkB,WAAW,cAAc,WAAW,GAAG;;WAEzD,aAAa,QAAQ,gBAAgB;;;;;;;;;;WAUrC,QACP,SAAS,SACT,UAAU,kBAAkB,OACzB,QAAQ;;WAEJ,SAAS,QAAQ;;KAGhB,gBAAgB,UAAU;WAC3B;;;;;;WAMA,WAAW;;WAEX,UAAU,SAAS;;WAEnB,QAAQ,SAAS;;WAEjB,SAAS,QAAQ,qBAAqB,aAAa;EAC5D,MAAM,aAAa,YAAY;;;;;;;iBAsDjB,MAAM,UAAU,eAC9B,SAAS,aAAa,KACrB,gBAAgB"}
@@ -1,5 +1,12 @@
1
- import { c as ActorRefusedError } from "./actor-shared-USo5MyuF.js";
1
+ import { n as ActorCallResult, v as ActorRefusedError } from "./actor-DJi3RsNu.js";
2
2
  //#region src/actor-client.d.ts
3
+ declare class ActorRequestError extends Error {
4
+ readonly status: number | undefined;
5
+ constructor(message: string, options?: {
6
+ status?: number;
7
+ cause?: unknown;
8
+ });
9
+ }
3
10
  /** The loose shape of an `actor()` definition — carried type-only. */
4
11
  type AnyActorDefinition = {
5
12
  actor(id: string): {
@@ -27,12 +34,14 @@ type ActorClientCall<D extends AnyActorDefinition, View = ActorStateOf<D>> = { r
27
34
  type ActorClient<D extends AnyActorDefinition, View = ActorStateOf<D>> = {
28
35
  /** Typed calls over POST — the response is the answer. */
29
36
  readonly call: ActorClientCall<D, View>;
37
+ readonly state: () => Promise<ActorCallResult<View>>;
30
38
  };
31
- type CreateActorClientOptions = {
39
+ type CreateActorClientOptions<View = unknown> = {
32
40
  /** The route whose handler delegates to `handle.fetch` for this instance. */
33
41
  api: string;
34
42
  /** Injectable transport (tests, custom auth headers). */
35
43
  fetch?: typeof globalThis.fetch;
44
+ onSnapshot?: (snapshot: ActorCallResult<View>) => void;
36
45
  };
37
46
  /**
38
47
  * A typed call surface for one actor instance's route. When the route
@@ -40,7 +49,7 @@ type CreateActorClientOptions = {
40
49
  * answers carry the projected state:
41
50
  * `createActorClient<typeof vault, PublicVault>({ api })`.
42
51
  */
43
- declare function createActorClient<D extends AnyActorDefinition, View = ActorStateOf<D>>(options: CreateActorClientOptions): ActorClient<D, View>;
52
+ declare function createActorClient<D extends AnyActorDefinition, View = ActorStateOf<D>>(options: CreateActorClientOptions<View>): ActorClient<D, View>;
44
53
  //#endregion
45
- export { ActorClient, ActorClientCall, ActorRefusedError, ActorStateOf, AnyActorDefinition, CreateActorClientOptions, createActorClient };
54
+ export { ActorClient, ActorClientCall, ActorRefusedError, ActorRequestError, ActorStateOf, AnyActorDefinition, CreateActorClientOptions, createActorClient };
46
55
  //# sourceMappingURL=actor-client.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"actor-client.d.ts","names":[],"sources":["../src/actor-client.ts"],"mappings":";;cAwBa,0BAA0B;WAC5B;EAET,YAAY,iBAAiB;IAAY;IAAiB;;;;KAQhD;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;WACzB,aAAa,QAAQ,gBAAgB;;KAGpC,yBAAyB;;EAEnC;;EAEA,eAAe,WAAW;EAC1B,cAAc,UAAU,gBAAgB;;;;;;;;iBA4F1B,kBACd,UAAU,oBACV,OAAO,aAAa,IACpB,SAAS,yBAAyB,QAAQ,YAAY,GAAG"}
@@ -18,9 +18,17 @@ import { i as ActorRefusedError } from "./actor-shared-DI7J5upy.js";
18
18
  * await client.call.withdraw({ amount: 60 }) // ActorRefusedError on refusal
19
19
  * ```
20
20
  */
21
+ var ActorRequestError = class extends Error {
22
+ status;
23
+ constructor(message, options) {
24
+ super(message, { cause: options?.cause });
25
+ this.name = "ActorRequestError";
26
+ this.status = options?.status;
27
+ }
28
+ };
21
29
  const CALL_TIMEOUT_MS = 3e4;
22
30
  const callOverWire = async (fetchImpl, api, event, input, options) => {
23
- const response = await fetchImpl(api, {
31
+ const response = await requestOverWire(fetchImpl, api, {
24
32
  method: "POST",
25
33
  headers: { "content-type": "application/json" },
26
34
  body: JSON.stringify({
@@ -30,12 +38,32 @@ const callOverWire = async (fetchImpl, api, event, input, options) => {
30
38
  }),
31
39
  signal: AbortSignal.timeout(options?.timeoutMs ?? CALL_TIMEOUT_MS)
32
40
  });
33
- const body = await response.json().catch(() => ({}));
41
+ const body = await responseBody(response);
34
42
  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);
43
+ if (!response.ok) throw new ActorRequestError(typeof body["error"] === "string" ? body["error"] : `actor call failed (${response.status})`, { status: response.status });
44
+ return snapshotBody(body, response.status);
45
+ };
46
+ const requestOverWire = async (fetchImpl, api, init) => {
47
+ try {
48
+ return await fetchImpl(api, init);
49
+ } catch (cause) {
50
+ throw new ActorRequestError("actor request did not receive a response", { cause });
38
51
  }
52
+ };
53
+ const responseBody = async (response) => {
54
+ try {
55
+ const body = await response.json();
56
+ if (!body || typeof body !== "object" || Array.isArray(body)) throw new Error("invalid response");
57
+ return body;
58
+ } catch (cause) {
59
+ throw new ActorRequestError("actor request returned an invalid response", {
60
+ status: response.status,
61
+ cause
62
+ });
63
+ }
64
+ };
65
+ const snapshotBody = (body, status) => {
66
+ if (!("state" in body) || !Number.isSafeInteger(body["index"]) || body["index"] < 0) throw new ActorRequestError("actor request returned an invalid snapshot", { status });
39
67
  return body;
40
68
  };
41
69
  /**
@@ -46,9 +74,37 @@ const callOverWire = async (fetchImpl, api, event, input, options) => {
46
74
  */
47
75
  function createActorClient(options) {
48
76
  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 }) };
77
+ const adopt = (snapshot) => {
78
+ const result = snapshot;
79
+ options.onSnapshot?.(result);
80
+ return result;
81
+ };
82
+ const state = async () => {
83
+ const api = options.api.split("#")[0];
84
+ const separator = api.includes("?") ? "&" : "?";
85
+ const response = await requestOverWire(fetchImpl, `${api}${separator}snapshot=1`, {
86
+ cache: "no-store",
87
+ signal: AbortSignal.timeout(CALL_TIMEOUT_MS)
88
+ });
89
+ const body = await responseBody(response);
90
+ if (!response.ok) throw new ActorRequestError(typeof body["error"] === "string" ? body["error"] : `actor state read failed (${response.status})`, { status: response.status });
91
+ return adopt(snapshotBody(body, response.status));
92
+ };
93
+ const methods = /* @__PURE__ */ new Map();
94
+ return {
95
+ call: new Proxy({}, { get: (_target, event) => {
96
+ if (typeof event !== "string") return void 0;
97
+ let method = methods.get(event);
98
+ if (!method) {
99
+ method = (input, callOptions) => callOverWire(fetchImpl, options.api, event, input, callOptions).then(adopt);
100
+ methods.set(event, method);
101
+ }
102
+ return method;
103
+ } }),
104
+ state
105
+ };
50
106
  }
51
107
  //#endregion
52
- export { ActorRefusedError, createActorClient };
108
+ export { ActorRefusedError, ActorRequestError, createActorClient };
53
109
 
54
110
  //# sourceMappingURL=actor-client.js.map
@@ -1 +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"}
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\nexport class ActorRequestError extends Error {\n readonly status: number | undefined\n\n constructor(message: string, options?: { status?: number; cause?: unknown }) {\n super(message, { cause: options?.cause })\n this.name = 'ActorRequestError'\n this.status = options?.status\n }\n}\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 readonly state: () => Promise<ActorCallResult<View>>\n}\n\nexport type CreateActorClientOptions<View = unknown> = {\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 onSnapshot?: (snapshot: ActorCallResult<View>) => void\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 requestOverWire(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 responseBody(response)\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 ActorRequestError(message, { status: response.status })\n }\n return snapshotBody(body, response.status)\n}\n\nconst requestOverWire = async (\n fetchImpl: typeof globalThis.fetch,\n api: string,\n init: RequestInit,\n): Promise<Response> => {\n try {\n return await fetchImpl(api, init)\n } catch (cause) {\n throw new ActorRequestError('actor request did not receive a response', {\n cause,\n })\n }\n}\n\nconst responseBody = async (\n response: Response,\n): Promise<Record<string, unknown>> => {\n try {\n const body: unknown = await response.json()\n if (!body || typeof body !== 'object' || Array.isArray(body))\n throw new Error('invalid response')\n return body as Record<string, unknown>\n } catch (cause) {\n throw new ActorRequestError('actor request returned an invalid response', {\n status: response.status,\n cause,\n })\n }\n}\n\nconst snapshotBody = (\n body: Record<string, unknown>,\n status: number,\n): ActorCallResult<unknown> => {\n if (\n !('state' in body) ||\n !Number.isSafeInteger(body['index']) ||\n (body['index'] as number) < 0\n ) {\n throw new ActorRequestError('actor request returned an invalid snapshot', {\n status,\n })\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<View>): ActorClient<D, View> {\n const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis)\n const adopt = (snapshot: ActorCallResult<unknown>): ActorCallResult<View> => {\n const result = snapshot as ActorCallResult<View>\n options.onSnapshot?.(result)\n return result\n }\n const state = async (): Promise<ActorCallResult<View>> => {\n const api = options.api.split('#')[0]!\n const separator = api.includes('?') ? '&' : '?'\n const response = await requestOverWire(\n fetchImpl,\n `${api}${separator}snapshot=1`,\n {\n cache: 'no-store',\n signal: AbortSignal.timeout(CALL_TIMEOUT_MS),\n },\n )\n const body = await responseBody(response)\n if (!response.ok) {\n throw new ActorRequestError(\n typeof body['error'] === 'string'\n ? body['error']\n : `actor state read failed (${response.status})`,\n { status: response.status },\n )\n }\n return adopt(snapshotBody(body, response.status))\n }\n const methods = new Map<\n string,\n (\n input?: unknown,\n callOptions?: ActorCallOptions,\n ) => Promise<ActorCallResult<View>>\n >()\n const call = new Proxy(\n {},\n {\n get: (_target, event) => {\n if (typeof event !== 'string') return undefined\n let method = methods.get(event)\n if (!method) {\n method = (input, callOptions) =>\n callOverWire(\n fetchImpl,\n options.api,\n event,\n input,\n callOptions,\n ).then(adopt)\n methods.set(event, method)\n }\n return method\n },\n },\n ) as ActorClientCall<D, View>\n return { call, state }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { a as ContractEvent, s as EventDefs } from "./reducer-DJKWm3cp.js";
2
- import { i as ActorPresenceMap, o as ActorPresenceValues } from "./actor-shared-USo5MyuF.js";
2
+ import { g as ActorPresenceValues, m as ActorPresenceMap } from "./actor-DJi3RsNu.js";
3
3
  import { ActorClientCall, ActorStateOf, AnyActorDefinition } from "./actor-client.js";
4
- import { Connection } from "./client.js";
4
+ import { a as Connection } from "./client-P_NNNRM-.js";
5
5
  //#region src/actor-react.d.ts
6
6
  /** The server-to-client handoff: `await def.actor(id).state()`. */
7
7
  type ActorSnapshot<S> = {
@@ -25,13 +25,14 @@ type PresenceOf<D extends AnyActorDefinition> = D extends {
25
25
  presence: infer Values extends object;
26
26
  } ? Values : never : never;
27
27
  type UseActorResult<D extends AnyActorDefinition, View> = {
28
- /** The actor's state, live: every commit streams in and replaces it. */
28
+ /** The live view, advanced by call answers, refreshes, and stream commits. */
29
29
  state: View;
30
- /** The stream frontier. */
30
+ /** The latest adopted state index. */
31
31
  index: number;
32
32
  connection: Connection;
33
33
  /** Typed calls over POST — the response is the answer. */
34
34
  call: ActorClientCall<D, View>;
35
+ refresh: () => Promise<ActorSnapshot<View>>;
35
36
  /** The state-commit feed this browser has observed. */
36
37
  events: ContractEvent<EventDefs>[];
37
38
  /**
@@ -1 +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"}
1
+ {"version":3,"file":"actor-react.d.ts","names":[],"sources":["../src/actor-react.ts"],"mappings":";;;;;;KA8CY,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;EACzB,eAAe,QAAQ,cAAc;;EAErC,QAAQ,cAAc;;;;;;;EAOtB,UAAU,iBAAiB,WAAW;;;;;EAKtC,cAAc,QAAQ,oBAAoB,WAAW;;;iBA2BvC,SAAS,UAAU,oBAAoB,OAAO,aAAa,IACzE,SAAS,gBAAgB,QACxB,eAAe,GAAG"}
@@ -1,8 +1,9 @@
1
1
  "use client";
2
- import { createClient } from "./client.js";
2
+ import { t as createClient } from "./client-Bf6uSEAk.js";
3
3
  import { useSession } from "./react.js";
4
4
  import { l as openPresenceDefs } from "./actor-shared-DI7J5upy.js";
5
5
  import { createActorClient } from "./actor-client.js";
6
+ import { useMemo } from "react";
6
7
  //#region src/actor-react.ts
7
8
  /**
8
9
  * experimental-a2/actor/react — use an actor from React: live state
@@ -62,12 +63,25 @@ function useActor(options) {
62
63
  hydrate: options.initial,
63
64
  ...options.participant === void 0 ? {} : { participant: options.participant }
64
65
  });
65
- const { call } = createActorClient({ api });
66
+ const { call, state: refresh } = useMemo(() => createActorClient({
67
+ api,
68
+ onSnapshot: ({ state, index }) => {
69
+ client.session(options.id, {
70
+ initialState: state,
71
+ initialIndex: index
72
+ });
73
+ }
74
+ }), [
75
+ api,
76
+ client,
77
+ options.id
78
+ ]);
66
79
  return {
67
80
  state: session.state,
68
81
  index: session.index,
69
82
  connection: session.connection,
70
83
  call,
84
+ refresh,
71
85
  events: session.events,
72
86
  presence: session.presence,
73
87
  setPresence: session.setPresence