experimental-a2 0.3.0 → 0.5.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 (205) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/dist/ai-CFNeCrRl.d.ts +334 -0
  3. package/dist/ai-CFNeCrRl.d.ts.map +1 -0
  4. package/dist/ai-server.browser.js +2 -0
  5. package/dist/ai-server.browser.js.map +1 -0
  6. package/dist/ai-server.d.ts +49 -9
  7. package/dist/ai-server.d.ts.map +1 -0
  8. package/dist/ai-server.js +123 -49
  9. package/dist/ai-server.js.map +1 -0
  10. package/dist/ai.d.ts +2 -303
  11. package/dist/ai.js +233 -86
  12. package/dist/ai.js.map +1 -0
  13. package/dist/cli-B3VuxoDe.js +599 -0
  14. package/dist/cli-B3VuxoDe.js.map +1 -0
  15. package/dist/cli-bin.d.ts +1 -0
  16. package/dist/cli-bin.js +7 -0
  17. package/dist/cli-bin.js.map +1 -0
  18. package/dist/cli.d.ts +20 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +2 -0
  21. package/dist/client-BKlyLiOU.js +1008 -0
  22. package/dist/client-BKlyLiOU.js.map +1 -0
  23. package/dist/client-D7mvIXrF.d.ts +191 -0
  24. package/dist/client-D7mvIXrF.d.ts.map +1 -0
  25. package/dist/client.d.ts +2 -90
  26. package/dist/client.js +1 -409
  27. package/dist/{contract-CG_adnu_.js → contract-48bUMgcL.js} +12 -2
  28. package/dist/contract-48bUMgcL.js.map +1 -0
  29. package/dist/{contract-C_3dIIEU.d.ts → contract-jIfaR085.d.ts} +63 -8
  30. package/dist/contract-jIfaR085.d.ts.map +1 -0
  31. package/dist/devtools-J_jZ2vQf.d.ts +152 -0
  32. package/dist/devtools-J_jZ2vQf.d.ts.map +1 -0
  33. package/dist/devtools-kJJaORn-.js +340 -0
  34. package/dist/devtools-kJJaORn-.js.map +1 -0
  35. package/dist/devtools-server.browser.js +3 -1
  36. package/dist/devtools-server.browser.js.map +1 -0
  37. package/dist/devtools-server.d.ts +4 -3
  38. package/dist/devtools-server.d.ts.map +1 -0
  39. package/dist/devtools-server.js +226 -43
  40. package/dist/devtools-server.js.map +1 -0
  41. package/dist/devtools.d.ts +2 -0
  42. package/dist/devtools.js +2 -0
  43. package/dist/{errors-BJRMd-h6.js → errors-BQuJpe82.js} +6 -4
  44. package/dist/errors-BQuJpe82.js.map +1 -0
  45. package/dist/{errors-xL_JTXsY.d.ts → errors-W6nwJ-fm.d.ts} +3 -2
  46. package/dist/errors-W6nwJ-fm.d.ts.map +1 -0
  47. package/dist/http.d.ts +135 -28
  48. package/dist/http.d.ts.map +1 -0
  49. package/dist/http.js +647 -61
  50. package/dist/http.js.map +1 -0
  51. package/dist/{idempotent-replay-BMyHrP0L.js → idempotent-replay-DuqEkYA7.js} +4 -2
  52. package/dist/idempotent-replay-DuqEkYA7.js.map +1 -0
  53. package/dist/index.d.ts +5 -5
  54. package/dist/index.js +2 -2
  55. package/dist/{inspection-E7qbD0Xj.js → inspection-DaxB5jM2.js} +4 -1
  56. package/dist/inspection-DaxB5jM2.js.map +1 -0
  57. package/dist/internal-DstsI6Re.js +211 -0
  58. package/dist/internal-DstsI6Re.js.map +1 -0
  59. package/dist/otel.d.ts +3 -2
  60. package/dist/otel.d.ts.map +1 -0
  61. package/dist/otel.js +2 -0
  62. package/dist/otel.js.map +1 -0
  63. package/dist/platform-B4TnJtWu.js +34 -0
  64. package/dist/platform-B4TnJtWu.js.map +1 -0
  65. package/dist/react.d.ts +47 -15
  66. package/dist/react.d.ts.map +1 -0
  67. package/dist/react.js +30 -15
  68. package/dist/react.js.map +1 -0
  69. package/dist/retryable-lazy-DZWmHpii.js +2 -0
  70. package/dist/retryable-lazy-DZWmHpii.js.map +1 -0
  71. package/dist/scheduler-qstash.d.ts +79 -0
  72. package/dist/scheduler-qstash.d.ts.map +1 -0
  73. package/dist/scheduler-qstash.js +501 -0
  74. package/dist/scheduler-qstash.js.map +1 -0
  75. package/dist/scheduler-task-BpzhPnRS.js +56 -0
  76. package/dist/scheduler-task-BpzhPnRS.js.map +1 -0
  77. package/dist/{recovery-vercel.d.ts → scheduler-vercel.d.ts} +18 -24
  78. package/dist/scheduler-vercel.d.ts.map +1 -0
  79. package/dist/scheduler-vercel.js +228 -0
  80. package/dist/scheduler-vercel.js.map +1 -0
  81. package/dist/server-C72KOw51.js +1457 -0
  82. package/dist/server-C72KOw51.js.map +1 -0
  83. package/dist/server-DUF9pjsx.d.ts +261 -0
  84. package/dist/server-DUF9pjsx.d.ts.map +1 -0
  85. package/dist/server.browser.js +2 -0
  86. package/dist/server.browser.js.map +1 -0
  87. package/dist/server.d.ts +4 -155
  88. package/dist/server.js +2 -2
  89. package/dist/{log-ldf5g8Cx.d.ts → store-DysUkTH3.d.ts} +120 -35
  90. package/dist/store-DysUkTH3.d.ts.map +1 -0
  91. package/dist/{log-yJbXUf72.js → store-N8PXxDAS.js} +3 -1
  92. package/dist/store-N8PXxDAS.js.map +1 -0
  93. package/dist/store-codec-DTG0Ftek.js +10 -0
  94. package/dist/store-codec-DTG0Ftek.js.map +1 -0
  95. package/dist/store-memory.d.ts +12 -0
  96. package/dist/store-memory.d.ts.map +1 -0
  97. package/dist/{log-memory.js → store-memory.js} +145 -34
  98. package/dist/store-memory.js.map +1 -0
  99. package/dist/{log-polling-DZ1MiKLg.js → store-polling-dSeLxzfb.js} +7 -5
  100. package/dist/store-polling-dSeLxzfb.js.map +1 -0
  101. package/dist/{log-postgres.d.ts → store-postgres.d.ts} +7 -6
  102. package/dist/store-postgres.d.ts.map +1 -0
  103. package/dist/{log-postgres.js → store-postgres.js} +214 -24
  104. package/dist/store-postgres.js.map +1 -0
  105. package/dist/{log-redis-core-CyJ5L8yR.js → store-redis-core-BFLwz0Wj.js} +271 -66
  106. package/dist/store-redis-core-BFLwz0Wj.js.map +1 -0
  107. package/dist/{log-redis-http.d.ts → store-redis-http.d.ts} +8 -7
  108. package/dist/store-redis-http.d.ts.map +1 -0
  109. package/dist/{log-redis-http.js → store-redis-http.js} +22 -12
  110. package/dist/store-redis-http.js.map +1 -0
  111. package/dist/{log-redis.d.ts → store-redis.d.ts} +7 -6
  112. package/dist/store-redis.d.ts.map +1 -0
  113. package/dist/{log-redis.js → store-redis.js} +101 -39
  114. package/dist/store-redis.js.map +1 -0
  115. package/dist/{log-sqlite.d.ts → store-sqlite.d.ts} +7 -6
  116. package/dist/store-sqlite.d.ts.map +1 -0
  117. package/dist/{log-sqlite.js → store-sqlite.js} +118 -22
  118. package/dist/store-sqlite.js.map +1 -0
  119. package/dist/{telemetry-Cso0qyHQ.d.ts → telemetry-BjYHTfh2.d.ts} +3 -2
  120. package/dist/telemetry-BjYHTfh2.d.ts.map +1 -0
  121. package/dist/testing.browser.d.ts +1 -0
  122. package/dist/testing.browser.js +6 -0
  123. package/dist/testing.browser.js.map +1 -0
  124. package/dist/testing.d.ts +32 -0
  125. package/dist/testing.d.ts.map +1 -0
  126. package/dist/testing.js +103 -0
  127. package/dist/testing.js.map +1 -0
  128. package/dist/validate-XKT4FSNn.js +2 -0
  129. package/dist/validate-XKT4FSNn.js.map +1 -0
  130. package/dist/wire-BFQmSJ-9.js +284 -0
  131. package/dist/wire-BFQmSJ-9.js.map +1 -0
  132. package/docs/01-quickstart.mdx +3 -3
  133. package/docs/concepts/01-contracts.mdx +18 -13
  134. package/docs/concepts/02-handlers.mdx +7 -7
  135. package/docs/concepts/03-durability.mdx +21 -21
  136. package/docs/concepts/04-state.mdx +18 -21
  137. package/docs/guides/01-timers.mdx +154 -49
  138. package/docs/guides/02-cancellation.mdx +30 -4
  139. package/docs/guides/03-react.mdx +76 -57
  140. package/docs/guides/04-local-first.mdx +1 -1
  141. package/docs/guides/05-production.mdx +310 -61
  142. package/docs/guides/06-ai-agents.mdx +252 -74
  143. package/docs/guides/07-devtools.mdx +137 -12
  144. package/docs/guides/08-application-data.mdx +1 -1
  145. package/docs/guides/09-presence.mdx +263 -0
  146. package/docs/guides/10-transports.mdx +140 -0
  147. package/docs/index.mdx +18 -14
  148. package/docs/reference/01-api.mdx +839 -110
  149. package/docs/reference/02-errors.mdx +13 -6
  150. package/package.json +24 -7
  151. package/src/ai-coordinator.ts +358 -0
  152. package/src/ai-projector.ts +524 -0
  153. package/src/ai-sdk-step.ts +261 -0
  154. package/src/ai-server.browser.ts +5 -0
  155. package/src/ai-server.ts +1719 -0
  156. package/src/ai.ts +2155 -0
  157. package/src/cache-indexeddb.ts +10 -0
  158. package/src/cli-bin.ts +5 -0
  159. package/src/cli.ts +1046 -0
  160. package/src/client.ts +1826 -0
  161. package/src/contract.ts +206 -0
  162. package/src/deterministic-id.ts +72 -0
  163. package/src/devtools-app.ts +989 -0
  164. package/src/devtools-server.browser.ts +5 -0
  165. package/src/devtools-server.ts +604 -0
  166. package/src/devtools.ts +716 -0
  167. package/src/errors.ts +50 -0
  168. package/src/http.ts +394 -0
  169. package/src/idempotent-replay.ts +53 -0
  170. package/src/index.ts +37 -0
  171. package/src/inspection.ts +39 -0
  172. package/src/internal.ts +426 -0
  173. package/src/otel.ts +59 -0
  174. package/src/platform.ts +60 -0
  175. package/src/push-envelope.ts +137 -0
  176. package/src/react.ts +284 -0
  177. package/src/reducer.ts +108 -0
  178. package/src/retryable-lazy.ts +27 -0
  179. package/src/scheduler-qstash.ts +915 -0
  180. package/src/scheduler-task.ts +106 -0
  181. package/src/scheduler-vercel.ts +437 -0
  182. package/src/server.browser.ts +12 -0
  183. package/src/server.ts +2700 -0
  184. package/src/session-socket.ts +548 -0
  185. package/src/sse.ts +141 -0
  186. package/src/standard-schema.ts +77 -0
  187. package/src/store-codec.ts +10 -0
  188. package/src/store-memory.ts +788 -0
  189. package/src/store-polling.ts +102 -0
  190. package/src/store-postgres.ts +1212 -0
  191. package/src/store-redis-core.ts +1494 -0
  192. package/src/store-redis-http.ts +116 -0
  193. package/src/store-redis.ts +458 -0
  194. package/src/store-sqlite.ts +1108 -0
  195. package/src/store.ts +385 -0
  196. package/src/telemetry.ts +47 -0
  197. package/src/testing.browser.ts +5 -0
  198. package/src/testing.ts +185 -0
  199. package/src/validate.ts +39 -0
  200. package/src/wire.ts +454 -0
  201. package/dist/internal-gCd5qMry.js +0 -44
  202. package/dist/log-memory.d.ts +0 -11
  203. package/dist/recovery-vercel.js +0 -119
  204. package/dist/server-BcLa4RFL.js +0 -877
  205. package/dist/wire-BVsgR8o9.js +0 -62
package/dist/server.d.ts CHANGED
@@ -1,155 +1,4 @@
1
- import { a as EventDefs, c as Reducer, n as Contract, r as ContractEvent, t as AppendInput } from "./contract-C_3dIIEU.js";
2
- import { a as Event, c as IdSource, d as LogStateRead, f as StoredEvent, h as StoredSnapshot, i as Clock, l as LogAppendResult, m as StoredSessionSummary, n as A2LogInspection, o as EventCause, p as StoredSessionPage, r as AppendEvent, s as FailAttemptResult, t as A2Log, u as LogClaimAvailableResult } from "./log-ldf5g8Cx.js";
3
- import { i as A2Telemetry } from "./telemetry-Cso0qyHQ.js";
4
- //#region src/server.d.ts
5
- /**
6
- * Events that arrived over the wire through `parsePushBody` — already
7
- * envelope-validated, headed for schema validation inside `append`.
8
- * The brand lets the documented push route hand them straight to
9
- * `session.append` without weakening typed appends for app code: a
10
- * hand-written `{ type: string }` literal still fails to compile.
11
- */
12
- type PushedEvent = {
13
- type: string;
14
- payload: unknown;
15
- id?: string;
16
- readonly "~a2.pushed": true;
17
- };
18
- type PushValidationContext = {
19
- sessionId: string;
20
- events: readonly PushedEvent[];
21
- };
22
- /** What every handler receives. */
23
- type HandlerContext<D extends EventDefs, K extends keyof D & string = keyof D & string> = {
24
- /** The triggering event. */
25
- event: ContractEvent<D, K>;
26
- /** Durable, 1-based dispatch ordinal for this event. */
27
- attempt: number;
28
- /** This session, with handler-scoped idempotent append. */
29
- session: Session<D, HandlerAppend<D>>;
30
- /** Fires on `abortOn` events (cancellation slice); dormant otherwise. */
31
- signal: AbortSignal;
32
- };
33
- type Handler<D extends EventDefs, K extends keyof D & string = keyof D & string> = (ctx: HandlerContext<D, K>) => Promise<void | AppendInput<D> | readonly AppendInput<D>[]>;
34
- type LaneContext<D extends EventDefs, K extends keyof D & string = keyof D & string> = {
35
- sessionId: string;
36
- event: Pick<ContractEvent<D, K>, "type" | "payload"> & {
37
- id?: string;
38
- };
39
- };
40
- type Lane<D extends EventDefs, K extends keyof D & string = keyof D & string> = string | ((context: LaneContext<D, K>) => string);
41
- type SessionDispatch<D extends EventDefs> = {
42
- (...events: AppendInput<D>[]): Promise<ContractEvent<D>[]>;
43
- /** The push-route path: events from `parsePushBody`. */
44
- (...events: PushedEvent[]): Promise<ContractEvent<D>[]>;
45
- };
46
- type SessionAppend<D extends EventDefs> = SessionDispatch<D> & {
47
- /** Commit, then hand pending work directly to configured recovery. */
48
- dispatch: SessionDispatch<D>;
49
- };
50
- type HandlerAppend<D extends EventDefs> = (name: string, ...events: AppendInput<D>[]) => Promise<ContractEvent<D>[]>;
51
- /** A handle on one instance of the machine. Creating it does no I/O. */
52
- type Session<D extends EventDefs, Append = SessionAppend<D>> = {
53
- readonly id: string;
54
- append: Append;
55
- history(): Promise<ContractEvent<D>[]>;
56
- state<S>(reducer: Reducer<D, S>): Promise<{
57
- state: S;
58
- index: number;
59
- }>;
60
- /**
61
- * A live feed of this session's events, starting after `startAt`
62
- * (exclusive). Server-side only — expose it over SSE with
63
- * `sseResponse` from experimental-a2/http.
64
- */
65
- stream(opts?: {
66
- startAt?: number;
67
- }): AsyncIterable<ContractEvent<D>>;
68
- };
69
- /**
70
- * What recovery needs from a server — structural, so servers of any
71
- * contract mix in one `handler(...)` call. Deliberately tiny: recovery
72
- * consults the log through `drain` and nothing else.
73
- */
74
- type RecoverableServer = {
75
- readonly contract: {
76
- readonly name: string;
77
- };
78
- drain(sessionId: string): Promise<{
79
- settled: boolean;
80
- }>;
81
- };
82
- /**
83
- * The recovery seam (a2-implementation.md §7, §9). `arm` puts a
84
- * delayed, coalesced "drain this session" message on durable
85
- * infrastructure while inline work starts; `handler` returns the route
86
- * the transport delivers to. Claim holders move the watchdog alongside
87
- * their renewable execution window. Implementations ship as
88
- * entry points (`experimental-a2/recovery-vercel`); core never imports a transport.
89
- */
90
- type A2Recovery = {
91
- arm(opts: {
92
- contract: string;
93
- sessionId: string;
94
- /** Earliest useful delivery as epoch milliseconds. */
95
- dueAt?: number;
96
- }): Promise<void>;
97
- handler(...servers: RecoverableServer[]): (req: Request) => Promise<Response>;
98
- };
99
- type A2Server<D extends EventDefs> = {
100
- /** The contract this server implements. */
101
- readonly contract: Contract<D>;
102
- session(id: string): Session<D>;
103
- /**
104
- * Process every currently eligible event. `settled` means nothing
105
- * actionable remains, including work blocked behind a dead letter.
106
- */
107
- drain(sessionId: string): Promise<{
108
- settled: boolean;
109
- }>;
110
- };
111
- /**
112
- * Which events fire `ctx.signal` while a handler runs — the preemption
113
- * channel for user cancellation. The array form matches by type; the
114
- * object form takes per-type predicates for targeted cancellation
115
- * (`(event, trigger) => event.payload.of === trigger.id`). Handlers
116
- * without `abortOn` pay nothing. An aborted handler should catch and
117
- * return normally; throwing means "retry me".
118
- */
119
- type AbortSpec<D extends EventDefs, K extends keyof D & string> = Array<keyof D & string> | { [T in keyof D & string]?: true | ((event: ContractEvent<D, T>, trigger: ContractEvent<D, K>, context: {
120
- attempt: number;
121
- }) => boolean); };
122
- type HandlerEntry<D extends EventDefs, K extends keyof D & string = keyof D & string> = Handler<D, K> | {
123
- abortOn?: AbortSpec<D, K>;
124
- /** Session-scoped FIFO key, resolved and persisted when the event lands. */
125
- lane?: Lane<D, K>;
126
- handler: Handler<D, K>;
127
- };
128
- type ServerOptions<D extends EventDefs> = {
129
- /** The contract this server implements (see `a2.contract`). */
130
- contract: Contract<D>;
131
- /** Where events live. Defaults: sqlite in dev, memory in tests, required in prod. */
132
- log?: A2Log;
133
- /**
134
- * Queue-backed recovery — e.g. `vercelQueues()` from
135
- * `experimental-a2/recovery-vercel`. Absent means append-driven healing only: a
136
- * working configuration, but a clockless one. Recommended in
137
- * production.
138
- */
139
- recovery?: A2Recovery;
140
- /** Optional instrumentation — e.g. `otel()` from `experimental-a2/otel`. */
141
- telemetry?: A2Telemetry;
142
- /** Validate events that came through `parsePushBody` before writing them. */
143
- validatePush?: (context: PushValidationContext) => void | PromiseLike<void>;
144
- /**
145
- * The reactions, keyed by event type — all present at construction,
146
- * so a handler can never be silently missing because its module
147
- * wasn't imported. Compose across files by spreading objects (note:
148
- * a duplicate key under spread silently last-wins).
149
- */
150
- handlers?: { [K in keyof D & string]?: HandlerEntry<D, K>; };
151
- };
152
- /** Implement a contract: bind its vocabulary to storage and reactions. */
153
- declare function createServer<D extends EventDefs>(options: ServerOptions<D>): A2Server<D>;
154
- //#endregion
155
- export { type A2Log, type A2LogInspection, A2Recovery, A2Server, AbortSpec, type AppendEvent, type AppendInput, type Clock, type Contract, type ContractEvent, type Event, type EventCause, type EventDefs, type FailAttemptResult, Handler, HandlerAppend, HandlerContext, HandlerEntry, type IdSource, Lane, LaneContext, type LogAppendResult, type LogClaimAvailableResult, type LogStateRead, PushValidationContext, PushedEvent, RecoverableServer, ServerOptions, Session, SessionAppend, SessionDispatch, type StoredEvent, type StoredSessionPage, type StoredSessionSummary, type StoredSnapshot, createServer };
1
+ import { a as EventDefs, c as PresencePatch, l as PresenceSnapshot, n as Contract, o as PresenceDefs, r as ContractEvent, s as PresenceMap, t as AppendInput } from "./contract-jIfaR085.js";
2
+ import { a as Event, c as IdSource, d as StoreClaimAvailableResult, f as StoreStateRead, g as StoredSnapshot, h as StoredSessionSummary, i as Clock, l as PresenceRow, m as StoredSessionPage, n as A2StoreInspection, o as EventCause, p as StoredEvent, r as AppendEvent, s as FailAttemptResult, t as A2Store, u as StoreAppendResult } from "./store-DysUkTH3.js";
3
+ import { C as deliverSchedulerAppend, D as SchedulerTask, E as SchedulerDrainTask, S as createServer, T as SchedulerAppendTask, _ as Session, a as Handler, b as SessionPresence, c as HandlerEntry, d as PushValidationContext, f as PushedEvent, g as ServerOptions, h as ScheduleTiming, i as DrainableServer, l as Lane, m as ScheduleDelay, n as A2Server, o as HandlerAppend, p as PushedPresence, r as AbortSpec, s as HandlerContext, t as A2Scheduler, u as LaneContext, v as SessionAppend, w as ScheduledEvent, x as SessionSchedule, y as SessionDispatch } from "./server-DUF9pjsx.js";
4
+ export { A2Scheduler, A2Server, type A2Store, type A2StoreInspection, AbortSpec, type AppendEvent, type AppendInput, type Clock, type Contract, type ContractEvent, DrainableServer, type Event, type EventCause, type EventDefs, type FailAttemptResult, Handler, HandlerAppend, HandlerContext, HandlerEntry, type IdSource, Lane, LaneContext, type PresenceDefs, type PresenceMap, type PresencePatch, type PresenceRow, type PresenceSnapshot, PushValidationContext, PushedEvent, PushedPresence, ScheduleDelay, ScheduleTiming, type ScheduledEvent, type SchedulerAppendTask, type SchedulerDrainTask, type SchedulerTask, ServerOptions, Session, SessionAppend, SessionDispatch, SessionPresence, SessionSchedule, type StoreAppendResult, type StoreClaimAvailableResult, type StoreStateRead, type StoredEvent, type StoredSessionPage, type StoredSessionSummary, type StoredSnapshot, createServer, deliverSchedulerAppend };
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as createServer } from "./server-BcLa4RFL.js";
2
- export { createServer };
1
+ import { n as deliverSchedulerAppend, t as createServer } from "./server-C72KOw51.js";
2
+ export { createServer, deliverSchedulerAppend };
@@ -1,19 +1,11 @@
1
- //#region src/log.d.ts
2
- /**
3
- * The A2Log interface — the storage contract every log backend
4
- * implements. See specs/a2-implementation.md §2–3.
5
- *
6
- * This is the whole storage contract: append, read, dispatch claims,
7
- * failure markers, snapshots, and the live stream. Recovery
8
- * needs nothing extra — the armed queue message is its own state, and
9
- * the log is the only thing it consults.
10
- */
1
+ import { c as PresencePatch } from "./contract-jIfaR085.js";
2
+ //#region src/store.d.ts
11
3
  /** A stored event, as the public API exposes it. */
12
4
  type Event = {
13
5
  id: string;
14
6
  type: string;
15
7
  payload: unknown;
16
- /** Position in the session's log, from 1. */
8
+ /** Position in the session's event log, from 1. */
17
9
  index: number;
18
10
  sessionId: string;
19
11
  createdAt: Date;
@@ -26,7 +18,7 @@ type EventCause = {
26
18
  batchSize?: number;
27
19
  };
28
20
  /**
29
- * What the log stores: immutable event history, including its causal edge,
21
+ * What the store persists: immutable event history, including its causal edge,
30
22
  * plus derived dispatch and failure bookkeeping. The bookkeeping is disposable;
31
23
  * the event and its cause are not.
32
24
  */
@@ -75,6 +67,21 @@ type StoredSessionSummary = {
75
67
  firstEventAt: Date;
76
68
  updatedAt: Date;
77
69
  };
70
+ /**
71
+ * One live presence value, as `A2Store.presence.read` returns it. The
72
+ * row is the LWW unit — one participant's one field. `at` is the
73
+ * sender's stamp, the LWW comparator. `expiresAt` is the storage's
74
+ * own clock at the applied set plus its `ttlMs` — never derived from
75
+ * the sender stamp; a row is live strictly before it.
76
+ */
77
+ type PresenceRow = {
78
+ participant: string;
79
+ field: string;
80
+ value: unknown;
81
+ seen: number;
82
+ at: Date;
83
+ expiresAt: Date;
84
+ };
78
85
  /** Metadata for the latest cached fold of one reducer. */
79
86
  type StoredSnapshot = {
80
87
  reducerName: string;
@@ -82,7 +89,7 @@ type StoredSnapshot = {
82
89
  updatedAt: Date;
83
90
  };
84
91
  /** One consistent cache-plus-tail read for a reducer fold. */
85
- type LogStateRead = {
92
+ type StoreStateRead = {
86
93
  /** The latest cached fold for this reducer, if one exists. */
87
94
  snapshot: {
88
95
  index: number;
@@ -96,22 +103,37 @@ type StoredSessionPage = {
96
103
  /** Opaque backend cursor; null means there is no next page. */
97
104
  cursor: string | null;
98
105
  };
106
+ type StoredEventPage = {
107
+ events: StoredEvent[];
108
+ /** Fixed inclusive log frontier shared by every page in one inspection. */
109
+ throughIndex: number;
110
+ };
99
111
  /**
100
- * Optional read-only administration implemented by A2's shipped logs.
101
- * It is separate from the correctness contract so custom logs do not
112
+ * Optional read-only administration implemented by A2's shipped stores.
113
+ * It is separate from the correctness contract so custom stores do not
102
114
  * need to expose storage-wide discovery merely to process events.
103
115
  */
104
- type A2LogInspection = {
116
+ type A2StoreInspection = {
105
117
  listSessions(options: {
106
118
  /** Storage namespace prefix, including the contract separator. */
107
119
  prefix: string;
108
120
  cursor?: string;
109
121
  limit: number;
110
122
  }): Promise<StoredSessionPage>;
123
+ /**
124
+ * A bounded event page for read-only administration. The first page captures
125
+ * `throughIndex`; callers pass it back so concurrent appends cannot extend a
126
+ * walk indefinitely. Optional for custom stores; A2 falls back to `read()`.
127
+ */
128
+ readEvents?(sessionId: string, options: {
129
+ afterIndex: number;
130
+ throughIndex?: number;
131
+ limit: number;
132
+ }): Promise<StoredEventPage>;
111
133
  listSnapshots(sessionId: string): Promise<StoredSnapshot[]>;
112
134
  };
113
135
  /** The result of atomically claiming every currently eligible event. */
114
- type LogClaimAvailableResult = {
136
+ type StoreClaimAvailableResult = {
115
137
  outcome: "claimed";
116
138
  events: StoredEvent[];
117
139
  } | {
@@ -127,12 +149,19 @@ type CompleteAttemptResult = {
127
149
  } | {
128
150
  outcome: "superseded";
129
151
  };
152
+ /** The result of one claim-renewal heartbeat, both lists in append order. */
153
+ type RenewClaimsResult = {
154
+ /** Listed claims that remain owned by the holder. */
155
+ renewed: number[];
156
+ /** Listed claims whose event a newer attempt has durably taken. */
157
+ superseded: number[];
158
+ };
130
159
  /** The result of atomically recording a caught handler failure. */
131
160
  type FailAttemptResult = {
132
161
  outcome: "failed" | "dead_lettered" | "superseded";
133
162
  failureCount: number;
134
163
  };
135
- /** Input to `A2Log.append` — already validated by the machine. */
164
+ /** Input to `A2Store.append` — already validated by the machine. */
136
165
  type AppendEvent = {
137
166
  type: string;
138
167
  payload: unknown;
@@ -145,12 +174,12 @@ type AppendEvent = {
145
174
  /** Internal hint: settle this event in the append transaction; no handler is registered. */
146
175
  settled?: true;
147
176
  };
148
- /** A handler-returned event with the deterministic id recovery requires. */
177
+ /** A handler-returned event with the deterministic id retries require. */
149
178
  type ReturnedEvent = AppendEvent & {
150
179
  id: string;
151
180
  };
152
181
  /** The rows written by an append and the session's pending state. */
153
- type LogAppendResult = {
182
+ type StoreAppendResult = {
154
183
  events: StoredEvent[];
155
184
  /** Whether the session contains an event without a completion marker. */
156
185
  hasPending: boolean;
@@ -165,7 +194,7 @@ type Clock = {
165
194
  };
166
195
  /** An injectable id source for generated event ids. */
167
196
  type IdSource = () => string;
168
- interface A2Log {
197
+ interface A2Store {
169
198
  /**
170
199
  * Accepts a batch; the batch is atomic — one transaction, consecutive
171
200
  * `index`es, all-or-nothing. The idempotency key covers the whole
@@ -178,17 +207,27 @@ interface A2Log {
178
207
  * operation, with no dispatch claim or `processedByAttempt`. The result's
179
208
  * `hasPending` reflects the whole session in the same atomic operation,
180
209
  * including older events and idempotent retries.
210
+ *
211
+ * An event carrying `cause` is a handler append, fenced by attempt
212
+ * currency: accept it only while `cause.attempt` is still the parent
213
+ * event's latest attempt and the parent is not dead-lettered; otherwise
214
+ * throw `A2Error('SUPERSEDED_ATTEMPT')` and write nothing. The check is
215
+ * part of this atomic operation and must serialize against a concurrent
216
+ * `claimAvailable` — an unlocked read of the parent admits write skew.
217
+ * The idempotent-replay path runs first, so a batch whose ids all exist
218
+ * replays regardless of the current attempt.
181
219
  */
182
- append(sessionId: string, events: AppendEvent[]): Promise<LogAppendResult>;
183
- /** Events for one session, oldest first. */
220
+ append(sessionId: string, events: AppendEvent[]): Promise<StoreAppendResult>;
221
+ /** Events for one session, oldest first. Bounds form `(afterIndex, throughIndex]`. */
184
222
  read(sessionId: string, opts?: {
185
223
  afterIndex?: number;
224
+ throughIndex?: number;
186
225
  }): Promise<StoredEvent[]>;
187
226
  /**
188
227
  * Atomically claims every eligible pending event. Unlaned events are all
189
228
  * independently eligible. Within a lane, only the lowest-index unfinished
190
229
  * event is eligible. A live claim produces `busy` only when it is the sole
191
- * remaining obstacle to actionable work. Claimed events are returned in log
230
+ * remaining obstacle to actionable work. Claimed events are returned in event-log
192
231
  * order. Excluded rows remain lane barriers.
193
232
  */
194
233
  claimAvailable(options: {
@@ -197,19 +236,26 @@ interface A2Log {
197
236
  ttlMs: number;
198
237
  expiresAtMs?: number;
199
238
  excludeIndexes?: readonly number[];
200
- }): Promise<LogClaimAvailableResult>;
239
+ }): Promise<StoreClaimAvailableResult>;
201
240
  /**
202
- * Renews the listed live claims still owned by `holder`. Missing, expired,
203
- * completed, failed, or superseded claims are omitted from the result. The
204
- * renewed indexes are returned in log order.
241
+ * Renews the listed live claims still owned by `holder`. Each listed claim
242
+ * carries the attempt ordinal the holder owns. `renewed` lists the claims
243
+ * that remain owned after the operation; `superseded` lists the claims
244
+ * whose event's `attemptCount` has durably passed the listed attempt. An
245
+ * expired claim no successor has taken appears in neither list — its
246
+ * attempt may still complete (`completeAttempt` is the fence), so it is
247
+ * not reported as lost. Renewal never revives an expired claim.
205
248
  */
206
249
  renewClaims(options: {
207
250
  sessionId: string;
208
251
  holder: string;
209
- indexes: number[];
252
+ claims: readonly {
253
+ index: number;
254
+ attempt: number;
255
+ }[];
210
256
  ttlMs: number;
211
257
  expiresAtMs?: number;
212
- }): Promise<number[]>;
258
+ }): Promise<RenewClaimsResult>;
213
259
  /**
214
260
  * Atomically completes one current attempt and appends its returned events.
215
261
  * Retrying a committed completion with the same attempt and deterministic
@@ -239,25 +285,64 @@ interface A2Log {
239
285
  * log. The snapshot is untrusted; core may reject it and issue a full
240
286
  * `read()` when its state schema no longer accepts the cached value.
241
287
  */
242
- readState(sessionId: string, reducerName: string): Promise<LogStateRead>;
288
+ readState(sessionId: string, reducerName: string): Promise<StoreStateRead>;
289
+ /**
290
+ * Optional batched `readState` — one consistent snapshot-plus-tail read
291
+ * per session id, aligned positionally with the input (duplicates
292
+ * allowed). Each element has its own frontier; the batch makes no
293
+ * cross-session consistency claim. Core falls back to parallel
294
+ * `readState` calls when absent.
295
+ */
296
+ readStates?(sessionIds: string[], reducerName: string): Promise<StoreStateRead[]>;
243
297
  /**
244
298
  * Writes a disposable reducer cache. Guard this operation so a slower
245
299
  * concurrent writer can never clobber a further-along snapshot
246
300
  * (`where up_to_index < excluded.up_to_index`).
247
301
  */
248
302
  putSnapshot(sessionId: string, reducerName: string, index: number, state: unknown): Promise<void>;
303
+ /**
304
+ * Optional ephemeral-plane capability (specs/a2-implementation.md
305
+ * §15.1) — optional like snapshots are. Values arrive already
306
+ * validated by core; adapters store them opaquely. Presence never
307
+ * touches the event log: no index, no history row, no recovery arm.
308
+ */
309
+ presence?: {
310
+ /**
311
+ * Field-wise last-writer-wins merge of one participant's values:
312
+ * a field whose existing row has a strictly newer `at` (the
313
+ * sender's stamp) is left untouched; `null` deletes the row. Every
314
+ * applied write refreshes that field's expiry to the storage's own
315
+ * clock plus `ttlMs` — the sender stamp orders writes but never
316
+ * anchors their lifetime.
317
+ */
318
+ set(ns: string, participant: string, values: Record<string, unknown | null>, meta: {
319
+ seen: number;
320
+ at: Date;
321
+ ttlMs: number;
322
+ }): Promise<void>;
323
+ /** The current map, pruned of rows at or past their `expiresAt`. */
324
+ read(ns: string): Promise<PresenceRow[]>;
325
+ /**
326
+ * Push-tier patch delivery; present only on backends with a real
327
+ * broadcast primitive. Without it the backend is the degraded
328
+ * tier: live feeds surface presence by re-reading on their
329
+ * existing poll cadence.
330
+ */
331
+ subscribe?(ns: string, onPatch: (patch: PresencePatch) => void): () => void;
332
+ };
249
333
  /** Read-only operational data used by `experimental-a2/devtools/server`. */
250
- inspect?: A2LogInspection;
334
+ inspect?: A2StoreInspection;
251
335
  /**
252
- * A live feed of one session's events, starting after `startAt`
336
+ * A live feed of one session's events, starting after `startAfter`
253
337
  * (exclusive). Transport is the backend's choice — in-process pub/sub,
254
338
  * polling, LISTEN/NOTIFY — callers never branch on which. The iterable
255
339
  * ends when the consumer calls `return()` (e.g. a disconnecting SSE
256
340
  * client) and must deliver events appended after subscription.
257
341
  */
258
342
  stream(sessionId: string, opts?: {
259
- startAt?: number;
343
+ startAfter?: number;
260
344
  }): AsyncIterable<Event>;
261
345
  }
262
346
  //#endregion
263
- export { Event as a, IdSource as c, LogStateRead as d, StoredEvent as f, StoredSnapshot as h, Clock as i, LogAppendResult as l, StoredSessionSummary as m, A2LogInspection as n, EventCause as o, StoredSessionPage as p, AppendEvent as r, FailAttemptResult as s, A2Log as t, LogClaimAvailableResult as u };
347
+ export { Event as a, IdSource as c, StoreClaimAvailableResult as d, StoreStateRead as f, StoredSnapshot as g, StoredSessionSummary as h, Clock as i, PresenceRow as l, StoredSessionPage as m, A2StoreInspection as n, EventCause as o, StoredEvent as p, AppendEvent as r, FailAttemptResult as s, A2Store as t, StoreAppendResult as u };
348
+ //# sourceMappingURL=store-DysUkTH3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-DysUkTH3.d.ts","names":[],"sources":["../src/store.ts"],"mappings":";;;KAaY;EACV;EACA;EACA;;EAEA;EACA;EACA,WAAW;;;KAID;EACV;EACA;;EAEA;;;;;;;KAQU,cAAc;;EAExB,OAAO;;EAEP;;EAEA,aAAa;;EAEb;;EAEA;;EAEA,gBAAgB;;EAEhB,eAAe;;EAEf;;EAEA;;EAEA,gBAAgB;;EAEhB;;EAEA,cAAc;;EAEd;;EAEA;;EAEA,UAAU;;;KAIA;EACV;EACA;EACA;EACA;;EAEA;;EAEA;EACA,cAAc;EACd,WAAW;;;;;;;;;KAUD;EACV;EACA;EACA;EACA;EACA,IAAI;EACJ,WAAW;;;KAID;EACV;EACA;EACA,WAAW;;;KAID;;EAEV;IAAY;IAAe;;;EAE3B,QAAQ;;KAGE;EACV,UAAU;;EAEV;;KAGU;EACV,QAAQ;;EAER;;;;;;;KAQU;EACV,aAAa;;IAEX;IACA;IACA;MACE,QAAQ;;;;;;EAMZ,YACE,mBACA;IAAW;IAAoB;IAAuB;MACrD,QAAQ;EACX,cAAc,oBAAoB,QAAQ;;;KAIhC;EACN;EAAoB,QAAQ;;EAC5B;EAAiB,SAAS;;EAC1B;;;KAGM;EACR;EAAsB,QAAQ;;EAAoB;;;KAG1C;;EAEV;;EAEA;;;KAIU;EACV;EACA;;;KAIU;EACV;EACA;;EAEA;;EAEA,QAAQ;;EAER;;EAEA;;;KAIU,gBAAgB;EAAgB;;;KAGhC;EACV,QAAQ;;EAER;;;;;;;KAQU;EACV,OAAO;;;KAIG;UAQK;;;;;;;;;;;;;;;;;;;;;;;EAuBf,OAAO,mBAAmB,QAAQ,gBAAgB,QAAQ;;EAG1D,KACE,mBACA;IAAS;IAAqB;MAC7B,QAAQ;;;;;;;;EASX,eAAe;IACb;IACA;IACA;IACA;IACA;MACE,QAAQ;;;;;;;;;;EAWZ,YAAY;IACV;IACA;IACA;MAAmB;MAAe;;IAClC;IACA;MACE,QAAQ;;;;;;EAOZ,gBAAgB;IACd;IACA;IACA;IACA,QAAQ;MACN,QAAQ;;;;;;EAOZ,YAAY;IACV;IACA;IACA;IACA;IACA;MACE,QAAQ;;;;;;;EAQZ,UAAU,mBAAmB,sBAAsB,QAAQ;;;;;;;;EAS3D,YACE,sBACA,sBACC,QAAQ;;;;;;EAOX,YACE,mBACA,qBACA,eACA,iBACC;;;;;;;EAQH;;;;;;;;;IASE,IACE,YACA,qBACA,QAAQ,gCACR;MAAQ;MAAc,IAAI;MAAM;QAC/B;;IAGH,KAAK,aAAa,QAAQ;;;;;;;IAQ1B,WAAW,YAAY,UAAU,OAAO;;;EAI1C,UAAU;;;;;;;;EASV,OACE,mBACA;IAAS;MACR,cAAc"}
@@ -1,5 +1,7 @@
1
- //#region src/log.ts
1
+ //#region src/store.ts
2
2
  const SYSTEM_CLOCK = { now: () => /* @__PURE__ */ new Date() };
3
3
  const RANDOM_IDS = () => crypto.randomUUID();
4
4
  //#endregion
5
5
  export { SYSTEM_CLOCK as n, RANDOM_IDS as t };
6
+
7
+ //# sourceMappingURL=store-N8PXxDAS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-N8PXxDAS.js","names":[],"sources":["../src/store.ts"],"sourcesContent":["/**\n * The A2Store interface — the storage contract every store backend\n * implements. See specs/a2-implementation.md §2–3.\n *\n * This is the whole storage contract: append, read, dispatch claims,\n * failure markers, snapshots, and the live stream. The scheduler\n * needs nothing extra — the armed queue message is its own state, and\n * the store is the only thing it consults.\n */\n\nimport type { PresencePatch } from './contract.ts'\n\n/** A stored event, as the public API exposes it. */\nexport type Event = {\n id: string\n type: string\n payload: unknown\n /** Position in the session's event log, from 1. */\n index: number\n sessionId: string\n createdAt: Date\n}\n\n/** The handler dispatch whose append first persisted a child event. */\nexport type EventCause = {\n index: number\n attempt: number\n /** Size of one named handler append, used to reject truncated retries. */\n batchSize?: number\n}\n\n/**\n * What the store persists: immutable event history, including its causal edge,\n * plus derived dispatch and failure bookkeeping. The bookkeeping is disposable;\n * the event and its cause are not.\n */\nexport type StoredEvent = Event & {\n /** Same-session handler dispatch that appended this event; null means root. */\n cause: EventCause | null\n /** Session-scoped serial execution key resolved when the event is appended. */\n lane: string | null\n /** Adapter clock time recorded by append settlement, completion, or manual skip; null while pending. */\n processedAt: Date | null\n /** Dispatch attempt that completed this event; null without dispatch or while pending. */\n processedByAttempt: number | null\n /** Exact ordered child ids atomically returned by the completing attempt. */\n returnedEventIds: string[] | null\n /** Adapter clock time recorded by the first durable dispatch claim. */\n firstClaimedAt: Date | null\n /** Adapter clock time recorded by the most recent durable dispatch claim. */\n lastClaimedAt: Date | null\n /** Durable dispatch claims, including claims abandoned by hard kills. */\n attemptCount: number\n /** Current dispatch holder; null when the event is not claimed. */\n claimHolder: string | null\n /** Adapter clock expiry for the current dispatch claim. */\n claimExpiresAt: Date | null\n /** Caught handler failures. This alone drives dead-lettering. */\n failureCount: number\n /** Adapter clock time recorded by the most recent caught handler failure. */\n lastFailedAt: Date | null\n /** Dispatch attempt that produced the most recent caught handler failure. */\n lastFailedAttempt: number | null\n /** The last handler failure, stringified. */\n lastError: string | null\n /** Adapter clock time recorded when dead-lettered; null otherwise. */\n failedAt: Date | null\n}\n\n/** Durable, read-only session metadata for administrative inspection. */\nexport type StoredSessionSummary = {\n sessionId: string\n eventCount: number\n pendingCount: number\n failedCount: number\n /** Durable dispatch claims across every event in the session. */\n attemptCount: number\n /** Caught handler failures across every event in the session. */\n failureCount: number\n firstEventAt: Date\n updatedAt: Date\n}\n\n/**\n * One live presence value, as `A2Store.presence.read` returns it. The\n * row is the LWW unit — one participant's one field. `at` is the\n * sender's stamp, the LWW comparator. `expiresAt` is the storage's\n * own clock at the applied set plus its `ttlMs` — never derived from\n * the sender stamp; a row is live strictly before it.\n */\nexport type PresenceRow = {\n participant: string\n field: string\n value: unknown\n seen: number\n at: Date\n expiresAt: Date\n}\n\n/** Metadata for the latest cached fold of one reducer. */\nexport type StoredSnapshot = {\n reducerName: string\n index: number\n updatedAt: Date\n}\n\n/** One consistent cache-plus-tail read for a reducer fold. */\nexport type StoreStateRead = {\n /** The latest cached fold for this reducer, if one exists. */\n snapshot: { index: number; state: unknown } | null\n /** Immutable events strictly after `snapshot.index`, or the full log on a miss. */\n events: Event[]\n}\n\nexport type StoredSessionPage = {\n sessions: StoredSessionSummary[]\n /** Opaque backend cursor; null means there is no next page. */\n cursor: string | null\n}\n\nexport type StoredEventPage = {\n events: StoredEvent[]\n /** Fixed inclusive log frontier shared by every page in one inspection. */\n throughIndex: number\n}\n\n/**\n * Optional read-only administration implemented by A2's shipped stores.\n * It is separate from the correctness contract so custom stores do not\n * need to expose storage-wide discovery merely to process events.\n */\nexport type A2StoreInspection = {\n listSessions(options: {\n /** Storage namespace prefix, including the contract separator. */\n prefix: string\n cursor?: string\n limit: number\n }): Promise<StoredSessionPage>\n /**\n * A bounded event page for read-only administration. The first page captures\n * `throughIndex`; callers pass it back so concurrent appends cannot extend a\n * walk indefinitely. Optional for custom stores; A2 falls back to `read()`.\n */\n readEvents?(\n sessionId: string,\n options: { afterIndex: number; throughIndex?: number; limit: number },\n ): Promise<StoredEventPage>\n listSnapshots(sessionId: string): Promise<StoredSnapshot[]>\n}\n\n/** The result of atomically claiming every currently eligible event. */\nexport type StoreClaimAvailableResult =\n | { outcome: 'claimed'; events: StoredEvent[] }\n | { outcome: 'busy'; retryAt: Date }\n | { outcome: 'settled' }\n\n/** A completion may lose to a newer claim or an earlier completion. */\nexport type CompleteAttemptResult =\n { outcome: 'completed'; events: StoredEvent[] } | { outcome: 'superseded' }\n\n/** The result of one claim-renewal heartbeat, both lists in append order. */\nexport type RenewClaimsResult = {\n /** Listed claims that remain owned by the holder. */\n renewed: number[]\n /** Listed claims whose event a newer attempt has durably taken. */\n superseded: number[]\n}\n\n/** The result of atomically recording a caught handler failure. */\nexport type FailAttemptResult = {\n outcome: 'failed' | 'dead_lettered' | 'superseded'\n failureCount: number\n}\n\n/** Input to `A2Store.append` — already validated by the machine. */\nexport type AppendEvent = {\n type: string\n payload: unknown\n /** Caller-supplied idempotency key; generated when absent. */\n id?: string\n /** Internal causal edge supplied atomically by handler `session.append`. */\n cause?: EventCause\n /** Session-scoped serial execution key resolved before persistence. */\n lane?: string\n /** Internal hint: settle this event in the append transaction; no handler is registered. */\n settled?: true\n}\n\n/** A handler-returned event with the deterministic id retries require. */\nexport type ReturnedEvent = AppendEvent & { id: string }\n\n/** The rows written by an append and the session's pending state. */\nexport type StoreAppendResult = {\n events: StoredEvent[]\n /** Whether the session contains an event without a completion marker. */\n hasPending: boolean\n}\n\n/**\n * An injectable clock. Adapters take one so tests can drive claim\n * expiry, failure timestamps, and (later) stuck-session detection\n * deterministically — against real storage, no mocking.\n */\nexport type Clock = {\n now(): Date\n}\n\n/** An injectable id source for generated event ids. */\nexport type IdSource = () => string\n\nexport const SYSTEM_CLOCK: Clock = {\n now: () => new Date(),\n}\n\nexport const RANDOM_IDS: IdSource = () => crypto.randomUUID()\n\nexport interface A2Store {\n /**\n * Accepts a batch; the batch is atomic — one transaction, consecutive\n * `index`es, all-or-nothing. The idempotency key covers the whole\n * operation, not each item: if *every* event's `id` already exists in\n * this session, this is a retry of a committed batch whose ack was\n * lost — return the existing rows as success. If only *some* ids\n * exist, the caller mixed an already-sent batch with fresh events —\n * always a caller bug — so throw `A2Error('PARTIAL_DUPLICATE_BATCH')`.\n * Events carrying `settled: true` get `processedAt` in this same atomic\n * operation, with no dispatch claim or `processedByAttempt`. The result's\n * `hasPending` reflects the whole session in the same atomic operation,\n * including older events and idempotent retries.\n *\n * An event carrying `cause` is a handler append, fenced by attempt\n * currency: accept it only while `cause.attempt` is still the parent\n * event's latest attempt and the parent is not dead-lettered; otherwise\n * throw `A2Error('SUPERSEDED_ATTEMPT')` and write nothing. The check is\n * part of this atomic operation and must serialize against a concurrent\n * `claimAvailable` — an unlocked read of the parent admits write skew.\n * The idempotent-replay path runs first, so a batch whose ids all exist\n * replays regardless of the current attempt.\n */\n append(sessionId: string, events: AppendEvent[]): Promise<StoreAppendResult>\n\n /** Events for one session, oldest first. Bounds form `(afterIndex, throughIndex]`. */\n read(\n sessionId: string,\n opts?: { afterIndex?: number; throughIndex?: number },\n ): Promise<StoredEvent[]>\n\n /**\n * Atomically claims every eligible pending event. Unlaned events are all\n * independently eligible. Within a lane, only the lowest-index unfinished\n * event is eligible. A live claim produces `busy` only when it is the sole\n * remaining obstacle to actionable work. Claimed events are returned in event-log\n * order. Excluded rows remain lane barriers.\n */\n claimAvailable(options: {\n sessionId: string\n holder: string\n ttlMs: number\n expiresAtMs?: number\n excludeIndexes?: readonly number[]\n }): Promise<StoreClaimAvailableResult>\n\n /**\n * Renews the listed live claims still owned by `holder`. Each listed claim\n * carries the attempt ordinal the holder owns. `renewed` lists the claims\n * that remain owned after the operation; `superseded` lists the claims\n * whose event's `attemptCount` has durably passed the listed attempt. An\n * expired claim no successor has taken appears in neither list — its\n * attempt may still complete (`completeAttempt` is the fence), so it is\n * not reported as lost. Renewal never revives an expired claim.\n */\n renewClaims(options: {\n sessionId: string\n holder: string\n claims: readonly { index: number; attempt: number }[]\n ttlMs: number\n expiresAtMs?: number\n }): Promise<RenewClaimsResult>\n\n /**\n * Atomically completes one current attempt and appends its returned events.\n * Retrying a committed completion with the same attempt and deterministic\n * child ids returns the existing children. A stale attempt never appends.\n */\n completeAttempt(options: {\n sessionId: string\n index: number\n attempt: number\n events: ReturnedEvent[]\n }): Promise<CompleteAttemptResult>\n\n /**\n * Atomically records a caught failure for one claimed attempt. A stale\n * attempt cannot poison a processed event or a newer dispatch. Accepted\n * failures record the operation's clock time.\n */\n failAttempt(options: {\n sessionId: string\n index: number\n attempt: number\n error: string\n maxFailures: number\n }): Promise<FailAttemptResult>\n\n /**\n * Reads a reducer snapshot and its event tail as one consistent adapter\n * operation. On a cache miss, `snapshot` is null and `events` is the full\n * log. The snapshot is untrusted; core may reject it and issue a full\n * `read()` when its state schema no longer accepts the cached value.\n */\n readState(sessionId: string, reducerName: string): Promise<StoreStateRead>\n\n /**\n * Optional batched `readState` — one consistent snapshot-plus-tail read\n * per session id, aligned positionally with the input (duplicates\n * allowed). Each element has its own frontier; the batch makes no\n * cross-session consistency claim. Core falls back to parallel\n * `readState` calls when absent.\n */\n readStates?(\n sessionIds: string[],\n reducerName: string,\n ): Promise<StoreStateRead[]>\n\n /**\n * Writes a disposable reducer cache. Guard this operation so a slower\n * concurrent writer can never clobber a further-along snapshot\n * (`where up_to_index < excluded.up_to_index`).\n */\n putSnapshot(\n sessionId: string,\n reducerName: string,\n index: number,\n state: unknown,\n ): Promise<void>\n\n /**\n * Optional ephemeral-plane capability (specs/a2-implementation.md\n * §15.1) — optional like snapshots are. Values arrive already\n * validated by core; adapters store them opaquely. Presence never\n * touches the event log: no index, no history row, no recovery arm.\n */\n presence?: {\n /**\n * Field-wise last-writer-wins merge of one participant's values:\n * a field whose existing row has a strictly newer `at` (the\n * sender's stamp) is left untouched; `null` deletes the row. Every\n * applied write refreshes that field's expiry to the storage's own\n * clock plus `ttlMs` — the sender stamp orders writes but never\n * anchors their lifetime.\n */\n set(\n ns: string,\n participant: string,\n values: Record<string, unknown | null>,\n meta: { seen: number; at: Date; ttlMs: number },\n ): Promise<void>\n\n /** The current map, pruned of rows at or past their `expiresAt`. */\n read(ns: string): Promise<PresenceRow[]>\n\n /**\n * Push-tier patch delivery; present only on backends with a real\n * broadcast primitive. Without it the backend is the degraded\n * tier: live feeds surface presence by re-reading on their\n * existing poll cadence.\n */\n subscribe?(ns: string, onPatch: (patch: PresencePatch) => void): () => void\n }\n\n /** Read-only operational data used by `experimental-a2/devtools/server`. */\n inspect?: A2StoreInspection\n\n /**\n * A live feed of one session's events, starting after `startAfter`\n * (exclusive). Transport is the backend's choice — in-process pub/sub,\n * polling, LISTEN/NOTIFY — callers never branch on which. The iterable\n * ends when the consumer calls `return()` (e.g. a disconnecting SSE\n * client) and must deliver events appended after subscription.\n */\n stream(\n sessionId: string,\n opts?: { startAfter?: number },\n ): AsyncIterable<Event>\n}\n"],"mappings":";AAkNA,MAAa,eAAsB,EACjC,2BAAW,IAAI,KAAK,EACtB;AAEA,MAAa,mBAA6B,OAAO,WAAW"}
@@ -0,0 +1,10 @@
1
+ //#region src/store-codec.ts
2
+ const decodeReturnedEventIds = (value) => {
3
+ const decoded = typeof value === "string" ? JSON.parse(value) : value;
4
+ if (!Array.isArray(decoded) || decoded.some((eventId) => typeof eventId !== "string")) throw new TypeError("stored event has invalid returned event ids");
5
+ return decoded;
6
+ };
7
+ //#endregion
8
+ export { decodeReturnedEventIds as t };
9
+
10
+ //# sourceMappingURL=store-codec-DTG0Ftek.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-codec-DTG0Ftek.js","names":[],"sources":["../src/store-codec.ts"],"sourcesContent":["export const decodeReturnedEventIds = (value: unknown): string[] => {\n const decoded: unknown = typeof value === 'string' ? JSON.parse(value) : value\n if (\n !Array.isArray(decoded) ||\n decoded.some((eventId) => typeof eventId !== 'string')\n ) {\n throw new TypeError('stored event has invalid returned event ids')\n }\n return decoded\n}\n"],"mappings":";AAAA,MAAa,0BAA0B,UAA6B;CAClE,MAAM,UAAmB,OAAO,UAAU,WAAW,KAAK,MAAM,KAAK,IAAI;CACzE,IACE,CAAC,MAAM,QAAQ,OAAO,KACtB,QAAQ,MAAM,YAAY,OAAO,YAAY,QAAQ,GAErD,MAAM,IAAI,UAAU,6CAA6C;CAEnE,OAAO;AACT"}
@@ -0,0 +1,12 @@
1
+ import { c as IdSource, i as Clock, t as A2Store } from "./store-DysUkTH3.js";
2
+ //#region src/store-memory.d.ts
3
+ type MemoryStoreOptions = {
4
+ /** Injectable clock — every stored timestamp comes from here. */
5
+ clock?: Clock;
6
+ /** Injectable id source for generated event ids. */
7
+ ids?: IdSource;
8
+ };
9
+ declare function memory(options?: MemoryStoreOptions): A2Store;
10
+ //#endregion
11
+ export { MemoryStoreOptions, memory };
12
+ //# sourceMappingURL=store-memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-memory.d.ts","names":[],"sources":["../src/store-memory.ts"],"mappings":";;KA2BY;;EAEV,QAAQ;;EAER,MAAM;;iBAyEQ,OAAO,UAAS,qBAA0B"}