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
@@ -1,5 +1,7 @@
1
- import { n as serverInspection, t as DEVTOOLS_TIMINGS } from "./inspection-E7qbD0Xj.js";
1
+ import { n as InspectionUnsupportedError, r as serverInspection, t as DEVTOOLS_TIMINGS } from "./inspection-DaxB5jM2.js";
2
+ import { c as encodeDevtoolsCapture, n as DEVTOOLS_CAPTURE_MEDIA_TYPE, o as DEVTOOLS_SESSION_PAGE_MAX_LIMIT, s as devtoolsSessionRevision, t as DEVTOOLS_CAPABILITIES } from "./devtools-kJJaORn-.js";
2
3
  //#region src/devtools-app.ts
4
+ /** Private browser application served by `experimental-a2/devtools/server`. */
3
5
  const escapeText = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
4
6
  const escapeAttribute = (value) => escapeText(value).replaceAll("\"", "&quot;");
5
7
  const formatDuration = (milliseconds) => {
@@ -322,6 +324,91 @@ const request = async (resource, params = {}) => {
322
324
  return response.json()
323
325
  }
324
326
 
327
+ const detailRevision = async (detail) => {
328
+ const timestamp = (value) => value === null ? null : new Date(value).getTime()
329
+ const bytes = new TextEncoder().encode(JSON.stringify({
330
+ events: detail.events.map((event) => [
331
+ event.id,
332
+ event.index,
333
+ event.cause?.index ?? null,
334
+ event.cause?.attempt ?? null,
335
+ timestamp(event.processedAt),
336
+ event.processedByAttempt,
337
+ timestamp(event.firstClaimedAt),
338
+ timestamp(event.lastClaimedAt),
339
+ event.attemptCount,
340
+ event.failureCount,
341
+ timestamp(event.lastFailedAt),
342
+ event.lastFailedAttempt,
343
+ event.lastError,
344
+ timestamp(event.failedAt),
345
+ ]),
346
+ snapshots: detail.snapshots.map((snapshot) => [
347
+ snapshot.reducerName,
348
+ snapshot.index,
349
+ snapshot.updatedAt === null ? 0 : new Date(snapshot.updatedAt).getTime(),
350
+ ]),
351
+ }))
352
+ const digest = await crypto.subtle.digest('SHA-256', bytes)
353
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('')
354
+ }
355
+
356
+ const requestDetail = async (contract, session) => {
357
+ const events = []
358
+ let snapshots = []
359
+ let cursor = null
360
+ let throughIndex = null
361
+ const seenCursors = new Set()
362
+ do {
363
+ const page = await request('session', {
364
+ contract,
365
+ session,
366
+ limit: ${250},
367
+ cursor,
368
+ })
369
+ if ('revision' in page) return page
370
+ if (page.contract !== contract || page.sessionId !== session) {
371
+ throw new Error('Session page identity changed during inspection.')
372
+ }
373
+ if (throughIndex === null) {
374
+ throughIndex = page.throughIndex
375
+ snapshots = page.snapshots
376
+ } else if (page.throughIndex !== throughIndex || page.snapshots.length > 0) {
377
+ throw new Error('Session page frontier changed during inspection.')
378
+ }
379
+ for (const event of page.events) {
380
+ if (event.index !== events.length + 1 || event.index > throughIndex) {
381
+ throw new Error('Session event pages are not contiguous.')
382
+ }
383
+ events.push(event)
384
+ }
385
+ if (page.cursor !== null) {
386
+ if (
387
+ page.events.length === 0 ||
388
+ events.length === throughIndex ||
389
+ seenCursors.has(page.cursor)
390
+ ) {
391
+ throw new Error('Session cursor did not advance.')
392
+ }
393
+ seenCursors.add(page.cursor)
394
+ }
395
+ cursor = page.cursor
396
+ } while (cursor !== null)
397
+ if (throughIndex !== events.length) {
398
+ throw new Error('Session event pages ended before their frozen frontier.')
399
+ }
400
+ const detail = {
401
+ protocolVersion: ${1},
402
+ contract,
403
+ sessionId: session,
404
+ revision: '',
405
+ events,
406
+ snapshots,
407
+ }
408
+ detail.revision = await detailRevision(detail)
409
+ return detail
410
+ }
411
+
325
412
  const watchUrl = () => {
326
413
  const url = new URL('_a2/watch', document.baseURI)
327
414
  url.searchParams.set('contract', state.contract)
@@ -411,10 +498,7 @@ const loadSessions = async (reset) => {
411
498
 
412
499
  const loadDetail = async () => {
413
500
  if (!state.contract || !state.session) return
414
- state.detail = await request('session', {
415
- contract: state.contract,
416
- session: state.session,
417
- })
501
+ state.detail = await requestDetail(state.contract, state.session)
418
502
  state.error = null
419
503
  render()
420
504
  }
@@ -715,7 +799,7 @@ const renderMain = () => {
715
799
  return main
716
800
  }
717
801
  if (!state.detail) {
718
- main.append(node('div', 'empty', 'Reading the durable log…'))
802
+ main.append(node('div', 'empty', 'Reading the durable store…'))
719
803
  return main
720
804
  }
721
805
 
@@ -825,7 +909,7 @@ if (bootstrap) {
825
909
  */
826
910
  const MARKER = "/_a2/";
827
911
  const encoder = new TextEncoder();
828
- const isProduction = () => typeof process !== "undefined" && process.env?.["NODE_ENV"] === "production";
912
+ const isDevelopment = () => typeof process !== "undefined" && process.env?.["NODE_ENV"] === "development";
829
913
  const headers = (contentType) => ({
830
914
  "content-type": contentType,
831
915
  "cache-control": "no-store",
@@ -846,59 +930,91 @@ const notFound = () => new Response("Not found", {
846
930
  const badRequest = (message) => json({ error: message }, { status: 400 });
847
931
  const unavailable = (message) => json({ error: message }, { status: 501 });
848
932
  const wireSummary = (session) => ({
849
- ...session,
933
+ sessionId: session.sessionId,
934
+ eventCount: session.eventCount,
935
+ pendingCount: session.pendingCount,
936
+ failedCount: session.failedCount,
937
+ attemptCount: session.attemptCount,
938
+ failureCount: session.failureCount,
850
939
  firstEventAt: session.firstEventAt.toISOString(),
851
940
  updatedAt: session.updatedAt.toISOString()
852
941
  });
853
942
  const wireEvent = (event) => ({
854
- ...event,
943
+ id: event.id,
944
+ type: event.type,
945
+ payload: event.payload,
946
+ index: event.index,
947
+ sessionId: event.sessionId,
855
948
  createdAt: event.createdAt.toISOString(),
949
+ cause: event.cause === null ? null : {
950
+ index: event.cause.index,
951
+ attempt: event.cause.attempt,
952
+ ...event.cause.batchSize === void 0 ? {} : { batchSize: event.cause.batchSize }
953
+ },
954
+ lane: event.lane,
856
955
  processedAt: event.processedAt?.toISOString() ?? null,
956
+ processedByAttempt: event.processedByAttempt,
957
+ returnedEventIds: event.returnedEventIds,
857
958
  firstClaimedAt: event.firstClaimedAt?.toISOString() ?? null,
858
959
  lastClaimedAt: event.lastClaimedAt?.toISOString() ?? null,
960
+ attemptCount: event.attemptCount,
961
+ claimHolder: event.claimHolder,
962
+ claimExpiresAt: event.claimExpiresAt?.toISOString() ?? null,
963
+ failureCount: event.failureCount,
859
964
  lastFailedAt: event.lastFailedAt?.toISOString() ?? null,
965
+ lastFailedAttempt: event.lastFailedAttempt,
966
+ lastError: event.lastError,
860
967
  failedAt: event.failedAt?.toISOString() ?? null
861
968
  });
862
969
  const wireSnapshot = (snapshot) => ({
863
- ...snapshot,
970
+ reducerName: snapshot.reducerName,
971
+ index: snapshot.index,
864
972
  updatedAt: snapshot.updatedAt.getTime() === 0 ? null : snapshot.updatedAt.toISOString()
865
973
  });
866
974
  const sessionRevision = async (detail) => {
867
- const serialized = JSON.stringify({
868
- events: detail.events.map((event) => [
869
- event.id,
870
- event.index,
871
- event.cause?.index ?? null,
872
- event.cause?.attempt ?? null,
873
- event.processedAt?.getTime() ?? null,
874
- event.processedByAttempt,
875
- event.firstClaimedAt?.getTime() ?? null,
876
- event.lastClaimedAt?.getTime() ?? null,
877
- event.attemptCount,
878
- event.failureCount,
879
- event.lastFailedAt?.getTime() ?? null,
880
- event.lastFailedAttempt,
881
- event.lastError,
882
- event.failedAt?.getTime() ?? null
883
- ]),
884
- snapshots: detail.snapshots.map((snapshot) => [
885
- snapshot.reducerName,
886
- snapshot.index,
887
- snapshot.updatedAt.getTime()
888
- ])
975
+ return devtoolsSessionRevision({
976
+ events: detail.events.map(wireEvent),
977
+ snapshots: detail.snapshots.map(wireSnapshot)
889
978
  });
890
- const digest = await crypto.subtle.digest("SHA-256", encoder.encode(serialized));
891
- return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
892
979
  };
893
980
  const wireDetail = async (contract, sessionId, detail) => ({
981
+ protocolVersion: 1,
894
982
  contract,
895
983
  sessionId,
896
984
  revision: await sessionRevision(detail),
897
985
  events: detail.events.map(wireEvent),
898
986
  snapshots: detail.snapshots.map(wireSnapshot)
899
987
  });
988
+ const readCompleteSession = async (inspection, sessionId) => {
989
+ const events = [];
990
+ let snapshots = [];
991
+ let afterIndex = 0;
992
+ let throughIndex;
993
+ for (;;) {
994
+ const page = await inspection.readSessionPage(sessionId, {
995
+ afterIndex,
996
+ limit: 250,
997
+ ...throughIndex === void 0 ? {} : { throughIndex }
998
+ });
999
+ if (throughIndex === void 0) {
1000
+ throughIndex = page.throughIndex;
1001
+ snapshots = page.snapshots;
1002
+ } else if (page.throughIndex !== throughIndex) throw new Error("inspection session frontier changed between pages");
1003
+ if (events.length + page.events.length > page.throughIndex) throw new Error("inspection session page crossed its frozen frontier");
1004
+ events.push(...page.events);
1005
+ if (page.nextIndex === null) break;
1006
+ if (events.length === page.throughIndex) throw new Error("inspection session cursor continued past its frontier");
1007
+ if (page.nextIndex <= afterIndex) throw new Error("inspection session cursor did not advance");
1008
+ afterIndex = page.nextIndex;
1009
+ }
1010
+ if (throughIndex !== events.length) throw new Error("inspection session ended before its frozen frontier");
1011
+ return {
1012
+ events,
1013
+ snapshots
1014
+ };
1015
+ };
900
1016
  const readRevision = async (inspection, sessionId) => {
901
- const detail = await inspection.readSession(sessionId);
1017
+ const detail = await readCompleteSession(inspection, sessionId);
902
1018
  return {
903
1019
  detail,
904
1020
  revision: await sessionRevision(detail)
@@ -930,7 +1046,7 @@ const bootstrapState = async (url, servers) => {
930
1046
  bootstrap.cursor = page.cursor;
931
1047
  bootstrap.session = (requestedContract === contract ? url.searchParams.get("session") : null) ?? page.sessions[0]?.sessionId ?? null;
932
1048
  if (!bootstrap.session) return bootstrap;
933
- const detail = await inspection.readSession(bootstrap.session);
1049
+ const detail = await readCompleteSession(inspection, bootstrap.session);
934
1050
  if (detail.events.length === 0) {
935
1051
  bootstrap.error = `session '${bootstrap.session}' was not found`;
936
1052
  return bootstrap;
@@ -951,6 +1067,30 @@ const inspectionOf = (servers, contractName) => {
951
1067
  if (!server) return notFound();
952
1068
  return serverInspection.get(server) ?? unavailable(`contract '${contractName}' is not an inspectable A2 server`);
953
1069
  };
1070
+ const encodeSessionCursor = (contract, sessionId, cursor) => JSON.stringify([
1071
+ 1,
1072
+ contract,
1073
+ sessionId,
1074
+ cursor.throughIndex,
1075
+ cursor.afterIndex
1076
+ ]);
1077
+ const parseSessionCursor = (value, contract, sessionId) => {
1078
+ let decoded;
1079
+ try {
1080
+ decoded = JSON.parse(value);
1081
+ } catch {
1082
+ return badRequest("cursor is invalid");
1083
+ }
1084
+ if (!Array.isArray(decoded) || decoded.length !== 5 || decoded[0] !== 1 || !Number.isSafeInteger(decoded[3]) || !Number.isSafeInteger(decoded[4]) || decoded[3] < 1 || decoded[4] < 1 || decoded[4] >= decoded[3]) return badRequest("cursor is invalid");
1085
+ if (decoded[1] !== contract || decoded[2] !== sessionId) return badRequest("cursor does not belong to this contract and session");
1086
+ return {
1087
+ throughIndex: decoded[3],
1088
+ afterIndex: decoded[4]
1089
+ };
1090
+ };
1091
+ const captureFilename = (contract, sessionId) => {
1092
+ return `${`${contract}-${sessionId}`.replaceAll(/[^a-zA-Z0-9._-]/g, "_").slice(0, 160) || "a2-session"}.a2log`;
1093
+ };
954
1094
  const watchResponse = (request, inspection, sessionId, initial) => {
955
1095
  let stopped = false;
956
1096
  let timeout = null;
@@ -1014,10 +1154,10 @@ const watchResponse = (request, inspection, sessionId, initial) => {
1014
1154
  } });
1015
1155
  };
1016
1156
  const authorizeRequest = async (request, authorize) => {
1017
- if (!authorize) return isProduction() ? notFound() : null;
1157
+ if (!authorize) return isDevelopment() ? null : notFound();
1018
1158
  const result = await authorize(request);
1019
1159
  if (result instanceof Response) return result;
1020
- return result ? null : notFound();
1160
+ return result === true ? null : notFound();
1021
1161
  };
1022
1162
  function createDevtools(options) {
1023
1163
  if (!options || !Array.isArray(options.servers) || options.servers.length === 0) throw new TypeError("createDevtools expects at least one A2 server");
@@ -1051,8 +1191,13 @@ function createDevtools(options) {
1051
1191
  }
1052
1192
  if (resource === "app.js") return new Response(DEVTOOLS_APP_JS, { headers: headers("text/javascript; charset=utf-8") });
1053
1193
  if (resource === "app.css") return new Response(DEVTOOLS_APP_CSS, { headers: headers("text/css; charset=utf-8") });
1054
- if (resource === "contracts") return json({ contracts: [...servers.keys()].map((name) => ({ name })) });
1055
- const inspection = inspectionOf(servers, url.searchParams.get("contract"));
1194
+ if (resource === "contracts") return json({
1195
+ protocolVersion: 1,
1196
+ contracts: [...servers.keys()].map((name) => ({ name }))
1197
+ });
1198
+ if (resource === "capabilities") return json(DEVTOOLS_CAPABILITIES);
1199
+ const contractName = url.searchParams.get("contract");
1200
+ const inspection = inspectionOf(servers, contractName);
1056
1201
  if (inspection instanceof Response) return inspection;
1057
1202
  try {
1058
1203
  if (resource === "sessions") {
@@ -1065,18 +1210,54 @@ function createDevtools(options) {
1065
1210
  ...cursor !== null ? { cursor } : {}
1066
1211
  });
1067
1212
  return json({
1213
+ protocolVersion: 1,
1068
1214
  cursor: page.cursor,
1069
1215
  sessions: page.sessions.map(wireSummary)
1070
1216
  });
1071
1217
  }
1072
1218
  const sessionId = url.searchParams.get("session");
1073
1219
  if (!sessionId) return badRequest("session is required");
1074
- const detail = await inspection.readSession(sessionId);
1220
+ if (resource === "session" && (url.searchParams.has("limit") || url.searchParams.has("cursor"))) {
1221
+ const requestedLimit = Number(url.searchParams.get("limit") ?? 250);
1222
+ if (!Number.isSafeInteger(requestedLimit) || requestedLimit < 1) return badRequest("limit must be a positive integer");
1223
+ const limit = Math.min(requestedLimit, DEVTOOLS_SESSION_PAGE_MAX_LIMIT);
1224
+ const cursorValue = url.searchParams.get("cursor");
1225
+ const cursor = cursorValue === null ? {
1226
+ afterIndex: 0,
1227
+ throughIndex: void 0
1228
+ } : parseSessionCursor(cursorValue, contractName, sessionId);
1229
+ if (cursor instanceof Response) return cursor;
1230
+ const page = await inspection.readSessionPage(sessionId, {
1231
+ afterIndex: cursor.afterIndex,
1232
+ limit,
1233
+ ...cursor.throughIndex === void 0 ? {} : { throughIndex: cursor.throughIndex }
1234
+ });
1235
+ if (page.throughIndex === 0) return notFound();
1236
+ const response = {
1237
+ protocolVersion: 1,
1238
+ contract: contractName,
1239
+ sessionId,
1240
+ throughIndex: page.throughIndex,
1241
+ cursor: page.nextIndex === null ? null : encodeSessionCursor(contractName, sessionId, {
1242
+ throughIndex: page.throughIndex,
1243
+ afterIndex: page.nextIndex
1244
+ }),
1245
+ events: page.events.map(wireEvent),
1246
+ snapshots: page.snapshots.map(wireSnapshot)
1247
+ };
1248
+ return json(response);
1249
+ }
1250
+ const detail = await readCompleteSession(inspection, sessionId);
1075
1251
  if (detail.events.length === 0) return notFound();
1076
- if (resource === "session") return json(await wireDetail(url.searchParams.get("contract"), sessionId, detail));
1252
+ const wired = await wireDetail(contractName, sessionId, detail);
1253
+ if (resource === "session") return json(wired);
1254
+ if (resource === "export") return new Response(await encodeDevtoolsCapture(wired), { headers: {
1255
+ ...headers(`${DEVTOOLS_CAPTURE_MEDIA_TYPE}; charset=utf-8`),
1256
+ "content-disposition": `attachment; filename="${captureFilename(wired.contract, sessionId)}"`
1257
+ } });
1077
1258
  if (resource === "watch") return watchResponse(request, inspection, sessionId, detail);
1078
1259
  } catch (error) {
1079
- if (error instanceof TypeError && error.message.includes("does not support inspection")) return unavailable(error.message);
1260
+ if (error instanceof InspectionUnsupportedError) return unavailable(error.message);
1080
1261
  throw error;
1081
1262
  }
1082
1263
  return notFound();
@@ -1085,3 +1266,5 @@ function createDevtools(options) {
1085
1266
  }
1086
1267
  //#endregion
1087
1268
  export { createDevtools };
1269
+
1270
+ //# sourceMappingURL=devtools-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools-server.js","names":[],"sources":["../src/devtools-app.ts","../src/devtools-server.ts"],"sourcesContent":["/** Private browser application served by `experimental-a2/devtools/server`. */\n\nimport {\n DEVTOOLS_PROTOCOL_VERSION,\n DEVTOOLS_SESSION_PAGE_LIMIT,\n} from './devtools.ts'\n\nimport type {\n DevtoolsSessionDetail,\n DevtoolsWireSessionSummary,\n} from './devtools.ts'\n\nexport type DevtoolsBootstrap = {\n contracts: Array<{ name: string }>\n contract: string | null\n sessions: DevtoolsWireSessionSummary[]\n cursor: string | null\n session: string | null\n detail: DevtoolsSessionDetail | null\n error: string | null\n}\n\nconst escapeText = (value: string): string =>\n value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')\n\nconst escapeAttribute = (value: string): string =>\n escapeText(value).replaceAll('\"', '&quot;')\n\nconst formatDuration = (milliseconds: number): string => {\n if (milliseconds < 1_000) {\n return `${Math.max(0, Math.round(milliseconds))} ms`\n }\n if (milliseconds < 60_000) {\n return `${(milliseconds / 1_000).toFixed(milliseconds < 10_000 ? 2 : 1)} s`\n }\n return `${(milliseconds / 60_000).toFixed(1)} min`\n}\n\nconst formatRelative = (value: string): string => {\n const delta = Date.now() - new Date(value).getTime()\n if (delta < 1_000) return 'now'\n if (delta < 60_000) return `${Math.floor(delta / 1_000)}s ago`\n if (delta < 3_600_000) return `${Math.floor(delta / 60_000)}m ago`\n if (delta < 86_400_000) return `${Math.floor(delta / 3_600_000)}h ago`\n return `${Math.floor(delta / 86_400_000)}d ago`\n}\n\nconst sessionStatus = (\n session: DevtoolsBootstrap['sessions'][number],\n): 'failed' | 'pending' | 'settled' =>\n session.failedCount > 0\n ? 'failed'\n : session.pendingCount > 0\n ? 'pending'\n : 'settled'\n\nconst metricHtml = (label: string, value: string): string =>\n `<div class=\"card\"><span>${escapeText(label)}</span><strong>${escapeText(value)}</strong></div>`\n\nconst shellHtml = (bootstrap: DevtoolsBootstrap): string => {\n const options = bootstrap.contracts\n .map(\n ({ name }) =>\n `<option value=\"${escapeAttribute(name)}\"${name === bootstrap.contract ? ' selected' : ''}>${escapeText(name)}</option>`,\n )\n .join('')\n const sessions = bootstrap.sessions.length\n ? bootstrap.sessions\n .map((session) => {\n const active =\n session.sessionId === bootstrap.session ? ' active' : ''\n const events = `${session.eventCount} ${session.eventCount === 1 ? 'event' : 'events'}`\n return `<button class=\"session-button${active}\" type=\"button\"><span class=\"session-name\">${escapeText(session.sessionId)}</span><span class=\"dot ${sessionStatus(session)}\"></span><span class=\"session-meta\">${events}</span><span class=\"session-meta\">${formatRelative(session.updatedAt)}</span></button>`\n })\n .join('')\n : '<div class=\"session-meta\">No durable sessions yet.</div>'\n const more = bootstrap.cursor\n ? '<button class=\"load-more\" type=\"button\">Load more</button>'\n : ''\n\n let main: string\n if (!bootstrap.session) {\n main =\n '<main class=\"main\"><div class=\"empty\"><div><strong>No session selected</strong><span>Choose a durable session to inspect its lifecycle.</span></div></div></main>'\n } else if (!bootstrap.detail) {\n const error = bootstrap.error\n ? `<div class=\"error-box\">${escapeText(bootstrap.error)}</div>`\n : ''\n main = `<main class=\"main\">${error}<div class=\"empty\"><div><strong>Session unavailable</strong><span>The selected durable session could not be read.</span></div></div></main>`\n } else {\n const { events } = bootstrap.detail\n const pending = events.filter(\n (event) => event.processedAt === null && event.failedAt === null,\n ).length\n const attempts = events.reduce(\n (total, event) => total + event.attemptCount,\n 0,\n )\n const failures = events.reduce(\n (total, event) => total + event.failureCount,\n 0,\n )\n const first = events[0]\n const last = events.at(-1)\n const lifetime =\n first && last\n ? formatDuration(\n new Date(\n last.failedAt ?? last.processedAt ?? Date.now(),\n ).getTime() - new Date(first.createdAt).getTime(),\n )\n : '0 ms'\n const error = bootstrap.error\n ? `<div class=\"error-box\">${escapeText(bootstrap.error)}</div>`\n : ''\n main = `<main class=\"main\">${error}<header class=\"session-header\"><div class=\"session-title\"><p class=\"eyebrow\">${escapeText(bootstrap.contract ?? '')} / session</p><h1>${escapeText(bootstrap.session)}</h1><p>Read-only · durable data · refreshed by SSE</p></div><span class=\"live\"><i></i>live</span></header><div class=\"cards\">${metricHtml('Events', String(events.length))}${metricHtml('Pending', String(pending))}${metricHtml('Dispatches', String(attempts))}${metricHtml('Caught failures', String(failures))}${metricHtml('Lifetime', lifetime)}</div></main>`\n }\n\n return `<div class=\"shell\"><aside class=\"sidebar\"><div class=\"brand\"><span class=\"mark\">A2</span><div class=\"brand-copy\"><strong>A2 Devtools</strong><span>Durable execution data</span></div></div><div class=\"side-controls\"><p class=\"eyebrow\">Contract</p><select class=\"contract-select\" aria-label=\"Contract\">${options}</select></div><div class=\"sessions\"><p class=\"eyebrow\">Sessions</p>${sessions}${more}</div></aside>${main}</div>`\n}\n\nconst bootstrapJson = (bootstrap: DevtoolsBootstrap): string =>\n JSON.stringify(bootstrap)\n .replaceAll('<', '\\\\u003c')\n .replaceAll('\\u2028', '\\\\u2028')\n .replaceAll('\\u2029', '\\\\u2029')\n\nexport const devtoolsHtml = (\n basePath: string,\n bootstrap: DevtoolsBootstrap,\n): string => `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"color-scheme\" content=\"light\">\n <base href=\"${escapeAttribute(basePath)}\">\n <title>A2 Devtools</title>\n <link rel=\"stylesheet\" href=\"_a2/app.css\">\n </head>\n <body>\n <div id=\"app\">${shellHtml(bootstrap)}</div>\n <script id=\"a2-bootstrap\" type=\"application/json\">${bootstrapJson(bootstrap)}</script>\n <script type=\"module\" src=\"_a2/app.js\"></script>\n </body>\n</html>`\n\nexport const DEVTOOLS_APP_CSS: string = String.raw`\n:root {\n color: #1a1a1a;\n background: #fff;\n font-family: Geist, \"Geist Sans\", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n font-synthesis: none;\n --surface: #fafafa;\n --line: #eaeaea;\n --line-strong: #d4d4d4;\n --muted: #666;\n --faint: #8f8f8f;\n --success: #15803d;\n --warning: #b45309;\n --danger: #dc2626;\n}\n\n* { box-sizing: border-box; }\nhtml, body, #app { min-height: 100%; margin: 0; }\nbody { min-width: 320px; background: #fff; }\nbutton, select { font: inherit; }\nbutton { color: inherit; }\nbutton:focus-visible, select:focus-visible, summary:focus-visible { outline: 2px solid #000; outline-offset: 2px; }\n\n.mark {\n display: inline-grid;\n place-items: center;\n width: 30px;\n height: 30px;\n border-radius: 6px;\n background: #000;\n color: #fff;\n font-size: 12px;\n font-weight: 700;\n letter-spacing: -.04em;\n}\n\n.shell {\n min-height: 100vh;\n display: grid;\n grid-template-columns: 248px minmax(0, 1fr);\n}\n\n.sidebar {\n position: sticky;\n top: 0;\n height: 100vh;\n display: flex;\n flex-direction: column;\n border-right: 1px solid var(--line);\n background: var(--surface);\n}\n\n.brand {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 18px;\n border-bottom: 1px solid var(--line);\n}\n\n.brand-copy strong { display: block; font-size: 13px; font-weight: 600; letter-spacing: -.01em; }\n.brand-copy span { color: var(--muted); font-size: 10px; }\n.side-controls { padding: 14px; border-bottom: 1px solid var(--line); }\n.eyebrow { margin: 0 0 8px; color: var(--muted); font-size: 10px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; }\n\n.contract-select {\n width: 100%;\n padding: 8px 9px;\n color: #1a1a1a;\n border: 1px solid var(--line-strong);\n border-radius: 6px;\n outline: none;\n background: #fff;\n}\n.contract-select:focus { border-color: #000; }\n\n.sessions { min-height: 0; flex: 1; overflow: auto; padding: 12px 8px; }\n.session-button {\n width: 100%;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 5px 9px;\n padding: 9px 10px;\n margin-bottom: 2px;\n text-align: left;\n border: 1px solid transparent;\n border-radius: 6px;\n background: transparent;\n cursor: pointer;\n}\n.session-button:hover { background: #f0f0f0; }\n.session-button.active { border-color: var(--line-strong); background: #fff; }\n.session-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }\n.session-meta { color: var(--muted); font-size: 10px; }\n.dot { align-self: center; width: 6px; height: 6px; border-radius: 99px; background: #999; }\n.dot.pending { background: var(--warning); }\n.dot.failed { background: var(--danger); }\n.dot.settled { background: var(--success); }\n.load-more { width: 100%; margin: 6px 0 10px; padding: 8px; border: 1px solid var(--line-strong); border-radius: 6px; background: #fff; cursor: pointer; color: var(--muted); }\n.load-more:hover { color: #000; border-color: #999; }\n\n.main { min-width: 0; width: 100%; max-width: 1440px; padding: 38px clamp(24px, 5vw, 72px) 72px; }\n.empty { min-height: 70vh; display: grid; place-items: center; color: var(--muted); text-align: center; }\n.empty strong { display: block; margin-bottom: 7px; color: #1a1a1a; font-size: 16px; font-weight: 500; }\n\n.session-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; margin-bottom: 28px; }\n.session-title { min-width: 0; }\n.session-title h1 { margin: 4px 0 8px; overflow-wrap: anywhere; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: clamp(22px, 3vw, 32px); font-weight: 500; letter-spacing: -.04em; }\n.session-title p { margin: 0; color: var(--muted); font-size: 12px; }\n.live { display: inline-flex; align-items: center; gap: 7px; padding: 6px 9px; color: var(--muted); border: 1px solid var(--line); border-radius: 99px; font-size: 11px; }\n.live i { width: 6px; height: 6px; border-radius: 99px; background: var(--success); }\n\n.cards { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); margin-bottom: 32px; overflow: hidden; border: 1px solid var(--line); border-radius: 8px; }\n.card { padding: 15px 16px; border-right: 1px solid var(--line); background: #fff; }\n.card:last-child { border-right: 0; }\n.card span { display: block; margin-bottom: 8px; color: var(--muted); font-size: 10px; font-weight: 500; }\n.card strong { font-size: 20px; font-weight: 500; letter-spacing: -.03em; }\n\n.section { margin-top: 32px; }\n.section-head { display: flex; align-items: end; justify-content: space-between; gap: 20px; margin-bottom: 11px; }\n.section-head h2 { margin: 0; font-size: 14px; font-weight: 500; letter-spacing: -.01em; }\n.section-head p { margin: 0; color: var(--muted); font-size: 11px; }\n\n.forest { overflow: hidden; border: 1px solid var(--line); border-radius: 8px; background: #fff; }\n.forest-row { --depth: 0; position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 16px; min-height: 42px; padding: 9px 13px 9px calc(13px + var(--depth) * 22px); border-bottom: 1px solid var(--line); }\n.forest-row:last-child { border-bottom: 0; }\n.forest-row::before { content: \"\"; position: absolute; top: 0; bottom: 0; left: calc(20px + (var(--depth) - 1) * 22px); width: 1px; background: var(--depth-line, transparent); }\n.forest-row.nested { --depth-line: var(--line-strong); }\n.forest-main { min-width: 0; display: flex; align-items: center; gap: 8px; }\n.forest-main .dot { flex: none; }\n.forest-type { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 11px ui-monospace, SFMono-Regular, Menlo, monospace; }\n.forest-edge { color: var(--muted); font-size: 10px; white-space: nowrap; }\n.forest-edge.unknown { color: var(--warning); }\n\n.timeline { overflow: hidden; border: 1px solid var(--line); border-radius: 8px; background: #fff; }\n.timeline-axis, .event-row { display: grid; grid-template-columns: 310px minmax(320px, 1fr); }\n.timeline-axis { min-height: 37px; border-bottom: 1px solid var(--line); color: var(--muted); font-size: 9px; }\n.axis-label { padding: 12px 14px; border-right: 1px solid var(--line); font-weight: 500; }\n.axis-track { position: relative; display: flex; justify-content: space-between; padding: 12px 16px; }\n.event-row { min-height: 84px; border-bottom: 1px solid var(--line); }\n.event-row:last-child { border-bottom: 0; }\n.event-info { min-width: 0; padding: 11px 14px; border-right: 1px solid var(--line); }\n.event-line { display: flex; align-items: center; gap: 8px; min-width: 0; }\n.event-index { color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; }\n.event-type { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }\n.event-sub { display: flex; flex-wrap: wrap; gap: 5px 9px; margin-top: 7px; color: var(--muted); font-size: 9px; }\n.event-sub .danger { color: var(--danger); }\n.event-sub .warning { color: var(--warning); }\n.event-facts { display: flex; flex-wrap: wrap; gap: 4px 9px; margin-top: 7px; color: var(--muted); font-size: 9px; }\n.event-facts .warning { color: var(--warning); }\n.event-details { margin-top: 8px; }\n.event-details summary { color: var(--muted); cursor: pointer; font-size: 9px; }\n.event-details pre { max-height: 220px; overflow: auto; margin: 8px 0 0; padding: 10px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: #333; font: 10px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }\n.event-track { position: relative; min-height: 84px; }\n.event-track::before { content: \"\"; position: absolute; top: 42px; right: 0; left: 0; height: 1px; background: var(--line); }\n.event-bar { position: absolute; top: 39px; height: 7px; min-width: 4px; border-radius: 99px; background: #111; }\n.event-bar.processed { background: #111; }\n.event-bar.pending { top: 38px; height: 9px; border: 1px solid #777; background: #fff; }\n.event-bar.failed { background: var(--danger); }\n.lifecycle-marker { position: absolute; z-index: 2; width: 11px; height: 11px; margin-left: -5px; border: 2px solid #fff; border-radius: 99px; background: #777; box-shadow: 0 0 0 1px #777; }\n.lifecycle-marker.created { top: 27px; border-radius: 2px; background: #fff; box-shadow: 0 0 0 1px #777; }\n.lifecycle-marker.claim { top: 37px; background: #111; box-shadow: 0 0 0 1px #111; }\n.lifecycle-marker.failure { top: 47px; background: var(--danger); box-shadow: 0 0 0 1px var(--danger); }\n.lifecycle-marker.complete { top: 47px; background: var(--success); box-shadow: 0 0 0 1px var(--success); }\n.lifecycle-marker.dead-letter { top: 57px; background: #fff; box-shadow: 0 0 0 1px var(--danger); }\n.timeline-legend { display: flex; flex-wrap: wrap; gap: 7px 14px; margin: 9px 2px 0; color: var(--muted); font-size: 9px; }\n.legend-item { display: inline-flex; align-items: center; gap: 6px; }\n.legend-key { width: 7px; height: 7px; border-radius: 99px; background: #777; }\n.legend-key.created { border: 1px solid #777; border-radius: 1px; background: #fff; }\n.legend-key.claim { background: #111; }\n.legend-key.failure { background: var(--danger); }\n.legend-key.dead-letter { border: 1px solid var(--danger); background: #fff; }\n.legend-key.complete { background: var(--success); }\n\n.snapshots { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px; }\n.snapshot { padding: 14px; border: 1px solid var(--line); border-radius: 8px; background: #fff; }\n.snapshot strong { display: block; margin-bottom: 8px; font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; }\n.snapshot div { display: flex; justify-content: space-between; color: var(--muted); font-size: 10px; }\n.snapshot b { color: #111; font-weight: 500; }\n.error-box { margin-bottom: 20px; padding: 12px 14px; border: 1px solid #fecaca; border-radius: 7px; background: #fffafa; color: var(--danger); font: 11px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; }\n\n@media (max-width: 850px) {\n .shell { grid-template-columns: 1fr; }\n .sidebar { position: static; height: auto; max-height: 310px; border-right: 0; border-bottom: 1px solid var(--line); }\n .sessions { max-height: 180px; }\n .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n .card { border-bottom: 1px solid var(--line); }\n .card:nth-child(2n) { border-right: 0; }\n .card:last-child { border-bottom: 0; }\n .timeline-axis, .event-row { grid-template-columns: 260px minmax(300px, 1fr); }\n .timeline { overflow-x: auto; }\n .timeline-axis, .event-row { min-width: 610px; }\n .main { padding: 28px 20px 60px; }\n}\n`\n\nexport const DEVTOOLS_APP_JS: string = String.raw`\nconst root = document.querySelector('#app')\nconst readBootstrap = () => {\n const element = document.querySelector('#a2-bootstrap')\n if (!element) return null\n try {\n const bootstrap = JSON.parse(element.textContent || 'null')\n element.remove()\n return bootstrap\n } catch {\n return null\n }\n}\nconst navigationFromUrl = () => {\n const params = new URL(window.location.href).searchParams\n return {\n contract: params.get('contract'),\n session: params.get('session'),\n }\n}\nconst initialNavigation = navigationFromUrl()\nconst bootstrap = readBootstrap()\nconst state = {\n contracts: bootstrap?.contracts || [],\n contract: bootstrap ? bootstrap.contract : initialNavigation.contract,\n sessions: bootstrap?.sessions || [],\n cursor: bootstrap?.cursor || null,\n session: bootstrap ? bootstrap.session : initialNavigation.session,\n detail: bootstrap?.detail || null,\n watch: null,\n error: bootstrap?.error || null,\n}\n\nconst syncUrl = (replace) => {\n const url = new URL(window.location.href)\n if (state.contract) url.searchParams.set('contract', state.contract)\n else url.searchParams.delete('contract')\n if (state.session) url.searchParams.set('session', state.session)\n else url.searchParams.delete('session')\n const historyState = { contract: state.contract, session: state.session }\n if (replace) window.history.replaceState(historyState, '', url)\n else window.history.pushState(historyState, '', url)\n}\n\nconst node = (tag, className, text) => {\n const element = document.createElement(tag)\n if (className) element.className = className\n if (text !== undefined) element.textContent = text\n return element\n}\n\nconst append = (parent, ...children) => {\n for (const child of children) if (child) parent.append(child)\n return parent\n}\n\nconst request = async (resource, params = {}) => {\n const url = new URL('_a2/' + resource, document.baseURI)\n for (const [key, value] of Object.entries(params)) {\n if (value !== null && value !== undefined) url.searchParams.set(key, String(value))\n }\n const response = await fetch(url, { headers: { accept: 'application/json' } })\n if (!response.ok) {\n let message = response.status + ' ' + response.statusText\n try { message = (await response.json()).error || message } catch {}\n throw new Error(message)\n }\n return response.json()\n}\n\nconst detailRevision = async (detail) => {\n const timestamp = (value) => value === null ? null : new Date(value).getTime()\n const bytes = new TextEncoder().encode(JSON.stringify({\n events: detail.events.map((event) => [\n event.id,\n event.index,\n event.cause?.index ?? null,\n event.cause?.attempt ?? null,\n timestamp(event.processedAt),\n event.processedByAttempt,\n timestamp(event.firstClaimedAt),\n timestamp(event.lastClaimedAt),\n event.attemptCount,\n event.failureCount,\n timestamp(event.lastFailedAt),\n event.lastFailedAttempt,\n event.lastError,\n timestamp(event.failedAt),\n ]),\n snapshots: detail.snapshots.map((snapshot) => [\n snapshot.reducerName,\n snapshot.index,\n snapshot.updatedAt === null ? 0 : new Date(snapshot.updatedAt).getTime(),\n ]),\n }))\n const digest = await crypto.subtle.digest('SHA-256', bytes)\n return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\nconst requestDetail = async (contract, session) => {\n const events = []\n let snapshots = []\n let cursor = null\n let throughIndex = null\n const seenCursors = new Set()\n do {\n const page = await request('session', {\n contract,\n session,\n limit: ${DEVTOOLS_SESSION_PAGE_LIMIT},\n cursor,\n })\n if ('revision' in page) return page\n if (page.contract !== contract || page.sessionId !== session) {\n throw new Error('Session page identity changed during inspection.')\n }\n if (throughIndex === null) {\n throughIndex = page.throughIndex\n snapshots = page.snapshots\n } else if (page.throughIndex !== throughIndex || page.snapshots.length > 0) {\n throw new Error('Session page frontier changed during inspection.')\n }\n for (const event of page.events) {\n if (event.index !== events.length + 1 || event.index > throughIndex) {\n throw new Error('Session event pages are not contiguous.')\n }\n events.push(event)\n }\n if (page.cursor !== null) {\n if (\n page.events.length === 0 ||\n events.length === throughIndex ||\n seenCursors.has(page.cursor)\n ) {\n throw new Error('Session cursor did not advance.')\n }\n seenCursors.add(page.cursor)\n }\n cursor = page.cursor\n } while (cursor !== null)\n if (throughIndex !== events.length) {\n throw new Error('Session event pages ended before their frozen frontier.')\n }\n const detail = {\n protocolVersion: ${DEVTOOLS_PROTOCOL_VERSION},\n contract,\n sessionId: session,\n revision: '',\n events,\n snapshots,\n }\n detail.revision = await detailRevision(detail)\n return detail\n}\n\nconst watchUrl = () => {\n const url = new URL('_a2/watch', document.baseURI)\n url.searchParams.set('contract', state.contract)\n url.searchParams.set('session', state.session)\n if (state.detail?.revision) url.searchParams.set('revision', state.detail.revision)\n return url\n}\n\nconst formatTime = (value) => new Intl.DateTimeFormat(undefined, {\n hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3,\n}).format(new Date(value))\n\nconst formatRelative = (value) => {\n const delta = Date.now() - new Date(value).getTime()\n if (delta < 1000) return 'now'\n if (delta < 60000) return Math.floor(delta / 1000) + 's ago'\n if (delta < 3600000) return Math.floor(delta / 60000) + 'm ago'\n if (delta < 86400000) return Math.floor(delta / 3600000) + 'h ago'\n return Math.floor(delta / 86400000) + 'd ago'\n}\n\nconst formatDuration = (milliseconds) => {\n if (milliseconds < 1000) return Math.max(0, Math.round(milliseconds)) + ' ms'\n if (milliseconds < 60000) return (milliseconds / 1000).toFixed(milliseconds < 10000 ? 2 : 1) + ' s'\n return (milliseconds / 60000).toFixed(1) + ' min'\n}\n\nconst sessionStatus = (session) => session.failedCount > 0 ? 'failed' : session.pendingCount > 0 ? 'pending' : 'settled'\nconst eventStatus = (event) => event.failedAt ? 'failed' : event.processedAt ? 'processed' : 'pending'\nconst eventStatusLabel = (event) => event.failedAt ? 'dead-lettered' : event.processedAt ? 'completed' : 'pending'\n\nconst durableOutcomeGap = (event) => Math.max(\n 0,\n event.attemptCount - event.failureCount - (event.processedByAttempt === null ? 0 : 1),\n)\n\nconst causalEntries = (events) => {\n const byIndex = new Map(events.map((event) => [event.index, event]))\n const children = new Map()\n for (const event of events) {\n if (!event.cause || !byIndex.has(event.cause.index)) continue\n const siblings = children.get(event.cause.index) || []\n siblings.push(event)\n children.set(event.cause.index, siblings)\n }\n\n const roots = events.filter((event) => !event.cause || !byIndex.has(event.cause.index))\n const entries = []\n const visited = new Set()\n const visit = (event, depth) => {\n if (visited.has(event.index)) return\n visited.add(event.index)\n entries.push({ event, depth })\n for (const child of children.get(event.index) || []) visit(child, depth + 1)\n }\n for (const root of roots) visit(root, 0)\n for (const event of events) visit(event, 0)\n return entries\n}\n\nconst loadContracts = async () => {\n const data = await request('contracts')\n state.contracts = data.contracts\n if (!data.contracts.some((contract) => contract.name === state.contract)) {\n state.contract = data.contracts[0]?.name || null\n state.session = null\n }\n if (state.contract) await loadSessions(true)\n syncUrl(true)\n render()\n}\n\nconst loadSessions = async (reset) => {\n if (!state.contract) return\n const data = await request('sessions', {\n contract: state.contract,\n cursor: reset ? null : state.cursor,\n limit: 50,\n })\n state.sessions = reset ? data.sessions : state.sessions.concat(data.sessions)\n state.cursor = data.cursor\n if (reset && !state.session) {\n state.session = state.sessions[0]?.sessionId || null\n }\n if (state.session) await loadDetail()\n}\n\nconst loadDetail = async () => {\n if (!state.contract || !state.session) return\n state.detail = await requestDetail(state.contract, state.session)\n state.error = null\n render()\n}\n\nconst beginWatch = () => {\n state.watch?.close()\n if (!state.session) return\n const watchedContract = state.contract\n const watchedSession = state.session\n const source = new EventSource(watchUrl())\n source.addEventListener('invalidate', async () => {\n if (state.contract !== watchedContract || state.session !== watchedSession) return\n try {\n await Promise.all([loadDetail(), refreshSelectedSummary()])\n } catch (error) {\n state.error = error instanceof Error ? error.message : String(error)\n render()\n }\n })\n source.onerror = () => {\n // EventSource reconnects. Durable state is fetched on the next\n // invalidation, so a dropped frame cannot make the view incorrect.\n }\n state.watch = source\n}\n\nconst refreshSelectedSummary = async () => {\n const data = await request('sessions', { contract: state.contract, limit: 100 })\n const fresh = data.sessions.find((session) => session.sessionId === state.session)\n const index = state.sessions.findIndex((session) => session.sessionId === state.session)\n if (fresh && index !== -1) state.sessions[index] = fresh\n render()\n}\n\nconst selectContract = async (name) => {\n state.watch?.close()\n state.contract = name\n state.session = null\n state.detail = null\n state.sessions = []\n state.cursor = null\n state.error = null\n syncUrl(false)\n render()\n try {\n await loadSessions(true)\n syncUrl(true)\n beginWatch()\n } catch (error) {\n state.error = error instanceof Error ? error.message : String(error)\n render()\n }\n}\n\nconst selectSession = async (id) => {\n if (id === state.session) return\n\n state.watch?.close()\n state.session = id\n state.detail = null\n state.error = null\n syncUrl(false)\n render()\n try {\n await loadDetail()\n beginWatch()\n } catch (error) {\n state.error = error instanceof Error ? error.message : String(error)\n render()\n }\n}\n\nconst renderSidebar = () => {\n const sidebar = node('aside', 'sidebar')\n const brand = node('div', 'brand')\n const copy = node('div', 'brand-copy')\n append(copy, node('strong', '', 'A2 Devtools'), node('span', '', 'Durable execution data'))\n append(brand, node('span', 'mark', 'A2'), copy)\n sidebar.append(brand)\n\n const controls = node('div', 'side-controls')\n controls.append(node('p', 'eyebrow', 'Contract'))\n const select = node('select', 'contract-select')\n select.setAttribute('aria-label', 'Contract')\n for (const contract of state.contracts) {\n const option = node('option', '', contract.name)\n option.value = contract.name\n option.selected = contract.name === state.contract\n select.append(option)\n }\n select.addEventListener('change', () => void selectContract(select.value))\n controls.append(select)\n sidebar.append(controls)\n\n const sessions = node('div', 'sessions')\n sessions.append(node('p', 'eyebrow', 'Sessions'))\n if (state.sessions.length === 0) sessions.append(node('div', 'session-meta', 'No durable sessions yet.'))\n for (const session of state.sessions) {\n const status = sessionStatus(session)\n const button = node('button', 'session-button' + (session.sessionId === state.session ? ' active' : ''))\n button.type = 'button'\n button.addEventListener('click', () => void selectSession(session.sessionId))\n append(\n button,\n node('span', 'session-name', session.sessionId),\n node('span', 'dot ' + status),\n node('span', 'session-meta', session.eventCount + (session.eventCount === 1 ? ' event' : ' events')),\n node('span', 'session-meta', formatRelative(session.updatedAt)),\n )\n sessions.append(button)\n }\n if (state.cursor) {\n const more = node('button', 'load-more', 'Load more')\n more.type = 'button'\n more.addEventListener('click', async () => {\n more.disabled = true\n try { await loadSessions(false); render() } catch (error) {\n state.error = error instanceof Error ? error.message : String(error)\n render()\n }\n })\n sessions.append(more)\n }\n sidebar.append(sessions)\n return sidebar\n}\n\nconst metric = (label, value) => {\n const card = node('div', 'card')\n append(card, node('span', '', label), node('strong', '', value))\n return card\n}\n\nconst renderCausalForest = (events) => {\n const forest = node('div', 'forest')\n const indexes = new Set(events.map((event) => event.index))\n for (const { event, depth } of causalEntries(events)) {\n const row = node('div', 'forest-row' + (depth > 0 ? ' nested' : ''))\n row.style.setProperty('--depth', String(depth))\n const main = node('div', 'forest-main')\n append(\n main,\n node('span', 'dot ' + eventStatus(event)),\n node('span', 'event-index', '#' + event.index),\n node('span', 'forest-type', event.type),\n )\n row.append(main)\n\n let edge\n if (!event.cause) {\n edge = node('span', 'forest-edge unknown', 'origin unknown')\n edge.title = 'This may be a root append or an event written before causal metadata existed.'\n } else if (!indexes.has(event.cause.index)) {\n edge = node('span', 'forest-edge unknown', 'parent #' + event.cause.index + ' unavailable · attempt ' + event.cause.attempt)\n } else {\n edge = node('span', 'forest-edge', 'from #' + event.cause.index + ' · attempt ' + event.cause.attempt)\n }\n row.append(edge)\n forest.append(row)\n }\n return forest\n}\n\nconst renderTimeline = (events) => {\n const timeline = node('div', 'timeline')\n const now = Date.now()\n const times = events.flatMap((event) => [\n event.createdAt,\n event.firstClaimedAt,\n event.lastClaimedAt,\n event.lastFailedAt,\n event.processedAt,\n event.failedAt,\n eventStatus(event) === 'pending' ? now : null,\n ].filter((value) => value !== null).map((value) => new Date(value).getTime()))\n const minimum = Math.min(...events.map((event) => new Date(event.createdAt).getTime()))\n const maximum = Math.max(...times, minimum + 1)\n const range = maximum - minimum\n const position = (value) => Math.max(0, Math.min(100, (new Date(value).getTime() - minimum) / range * 100))\n const markerPosition = (value) => Math.max(.75, Math.min(99.25, position(value)))\n const lifecycleMarker = (kind, value, title) => {\n const marker = node('span', 'lifecycle-marker ' + kind)\n marker.style.left = markerPosition(value) + '%'\n marker.title = title + ' · ' + formatTime(value)\n return marker\n }\n\n const axis = node('div', 'timeline-axis')\n axis.append(node('div', 'axis-label', 'Event'))\n const track = node('div', 'axis-track')\n for (const percent of [0, 25, 50, 75, 100]) {\n track.append(node('span', '', formatDuration(range * percent / 100)))\n }\n axis.append(track)\n timeline.append(axis)\n\n for (const event of events) {\n const status = eventStatus(event)\n const start = new Date(event.createdAt).getTime()\n const end = new Date(event.failedAt || event.processedAt || now).getTime()\n const left = position(event.createdAt)\n const width = Math.max((end - start) / range * 100, .45)\n const row = node('div', 'event-row')\n const info = node('div', 'event-info')\n const line = node('div', 'event-line')\n append(line, node('span', 'event-index', '#' + event.index), node('span', 'event-type', event.type))\n info.append(line)\n const sub = node('div', 'event-sub')\n sub.append(node('span', status === 'failed' ? 'danger' : '', eventStatusLabel(event)))\n sub.append(node('span', '', event.attemptCount + (event.attemptCount === 1 ? ' dispatch' : ' dispatches')))\n sub.append(node('span', event.failureCount > 0 ? 'danger' : '', event.failureCount + (event.failureCount === 1 ? ' caught failure' : ' caught failures')))\n sub.append(node('span', '', 'created ' + formatTime(event.createdAt)))\n info.append(sub)\n\n const facts = node('div', 'event-facts')\n if (event.attemptCount === 0) {\n facts.append(node('span', '', 'not dispatched'))\n } else if (!event.firstClaimedAt) {\n facts.append(node('span', 'warning', 'first dispatch time unknown'))\n }\n if (event.attemptCount > 1) {\n facts.append(node('span', '', (event.attemptCount - 1) + (event.attemptCount === 2 ? ' redispatch' : ' redispatches')))\n }\n const outcomeGap = durableOutcomeGap(event)\n if (outcomeGap > 0) {\n const hint = node('span', 'warning', outcomeGap + (outcomeGap === 1 ? ' dispatch without outcome' : ' dispatches without outcomes'))\n hint.title = 'May indicate active work, a hard crash, or a superseded attempt.'\n facts.append(hint)\n }\n if (event.lastFailedAttempt !== null) {\n facts.append(node('span', 'danger', 'latest failure · attempt ' + event.lastFailedAttempt))\n }\n if (event.processedAt) {\n facts.append(node('span', '', event.processedByAttempt === null ? 'completing attempt not recorded' : 'completed by attempt ' + event.processedByAttempt))\n }\n if (facts.childNodes.length > 0) info.append(facts)\n\n const details = node('details', 'event-details')\n details.append(node('summary', '', 'payload'))\n details.append(node('pre', '', JSON.stringify(event.payload, null, 2)))\n info.append(details)\n if (event.lastError) {\n const failure = node('details', 'event-details')\n failure.append(node('summary', 'danger', 'last error'))\n failure.append(node('pre', '', event.lastError))\n info.append(failure)\n }\n row.append(info)\n\n const eventTrack = node('div', 'event-track')\n const bar = node('span', 'event-bar ' + status)\n bar.style.left = left + '%'\n bar.style.width = Math.min(width, 100 - left) + '%'\n bar.title = eventStatusLabel(event) + ' · ' + formatDuration(end - start)\n append(eventTrack, bar)\n eventTrack.append(lifecycleMarker('created', event.createdAt, 'Created'))\n if (event.firstClaimedAt && event.lastClaimedAt === event.firstClaimedAt) {\n eventTrack.append(lifecycleMarker('claim', event.firstClaimedAt, 'First and latest dispatch'))\n } else {\n if (event.firstClaimedAt) eventTrack.append(lifecycleMarker('claim', event.firstClaimedAt, 'First dispatch'))\n if (event.lastClaimedAt) eventTrack.append(lifecycleMarker('claim', event.lastClaimedAt, 'Latest dispatch'))\n }\n if (event.lastFailedAt) {\n eventTrack.append(lifecycleMarker('failure', event.lastFailedAt, 'Latest caught failure' + (event.lastFailedAttempt === null ? '' : ' · attempt ' + event.lastFailedAttempt)))\n }\n if (event.processedAt) {\n eventTrack.append(lifecycleMarker('complete', event.processedAt, 'Completed' + (event.processedByAttempt === null ? '' : ' · attempt ' + event.processedByAttempt)))\n }\n if (event.failedAt) eventTrack.append(lifecycleMarker('dead-letter', event.failedAt, 'Dead-lettered'))\n row.append(eventTrack)\n timeline.append(row)\n }\n\n const legend = node('div', 'timeline-legend')\n for (const [kind, label] of [\n ['created', 'Created'],\n ['claim', 'First / latest dispatch'],\n ['failure', 'Latest caught failure'],\n ['complete', 'Completion'],\n ['dead-letter', 'Dead-letter'],\n ]) {\n const item = node('span', 'legend-item')\n append(item, node('i', 'legend-key ' + kind), document.createTextNode(label))\n legend.append(item)\n }\n return append(node('div'), timeline, legend)\n}\n\nconst renderMain = () => {\n const main = node('main', 'main')\n if (state.error) main.append(node('div', 'error-box', state.error))\n if (!state.session) {\n const empty = node('div', 'empty')\n const copy = node('div')\n append(copy, node('strong', '', 'No session selected'), node('span', '', 'Choose a durable session to inspect its lifecycle.'))\n empty.append(copy)\n main.append(empty)\n return main\n }\n if (!state.detail) {\n main.append(node('div', 'empty', 'Reading the durable store…'))\n return main\n }\n\n const events = state.detail.events\n const snapshots = state.detail.snapshots\n const pending = events.filter((event) => eventStatus(event) === 'pending').length\n const failed = events.filter((event) => eventStatus(event) === 'failed').length\n const attempts = events.reduce((total, event) => total + event.attemptCount, 0)\n const failures = events.reduce((total, event) => total + event.failureCount, 0)\n const first = new Date(events[0].createdAt).getTime()\n const lastEvent = events[events.length - 1]\n const last = new Date(lastEvent.failedAt || lastEvent.processedAt || Date.now()).getTime()\n\n const header = node('header', 'session-header')\n const title = node('div', 'session-title')\n append(title, node('p', 'eyebrow', state.contract + ' / session'), node('h1', '', state.session), node('p', '', 'Read-only · durable data · refreshed by SSE'))\n const live = node('span', 'live')\n append(live, node('i'), document.createTextNode('live'))\n append(header, title, live)\n main.append(header)\n\n const cards = node('div', 'cards')\n append(cards, metric('Events', String(events.length)), metric('Pending', String(pending)), metric('Dispatches', String(attempts)), metric('Caught failures', String(failures)), metric('Lifetime', formatDuration(last - first)))\n main.append(cards)\n\n const forestSection = node('section', 'section')\n const forestHead = node('div', 'section-head')\n append(forestHead, node('h2', '', 'Causal forest'), node('p', '', 'Unknown origins may be roots or legacy events'))\n append(forestSection, forestHead, renderCausalForest(events))\n main.append(forestSection)\n\n const section = node('section', 'section')\n const head = node('div', 'section-head')\n append(head, node('h2', '', 'Event lifecycle'), node('p', '', 'Durable operation boundaries · missing history stays unknown'))\n append(section, head, renderTimeline(events))\n main.append(section)\n\n const snapshotSection = node('section', 'section')\n const snapshotHead = node('div', 'section-head')\n append(snapshotHead, node('h2', '', 'Snapshots'), node('p', '', failed ? 'Session is stalled' : snapshots.length ? 'Latest durable reducer frontiers' : 'No cached folds'))\n snapshotSection.append(snapshotHead)\n if (snapshots.length) {\n const grid = node('div', 'snapshots')\n for (const snapshot of snapshots) {\n const card = node('div', 'snapshot')\n card.append(node('strong', '', snapshot.reducerName))\n const detail = node('div')\n append(detail, node('span', '', snapshot.updatedAt ? formatRelative(snapshot.updatedAt) : 'time unknown'), node('b', '', '#' + snapshot.index))\n card.append(detail)\n grid.append(card)\n }\n snapshotSection.append(grid)\n }\n main.append(snapshotSection)\n return main\n}\n\nconst render = () => {\n root.replaceChildren(append(node('div', 'shell'), renderSidebar(), renderMain()))\n}\n\nconst restoreFromUrl = async () => {\n state.watch?.close()\n const navigation = navigationFromUrl()\n state.contract = state.contracts.some((contract) => contract.name === navigation.contract)\n ? navigation.contract\n : state.contracts[0]?.name || null\n state.session = navigation.session\n state.sessions = []\n state.cursor = null\n state.detail = null\n state.error = null\n render()\n try {\n if (state.contract) await loadSessions(true)\n syncUrl(true)\n beginWatch()\n } catch (error) {\n state.error = error instanceof Error ? error.message : String(error)\n render()\n }\n}\n\nwindow.addEventListener('popstate', () => void restoreFromUrl())\n\nsetInterval(() => {\n if (state.detail?.events.some((event) => eventStatus(event) === 'pending')) render()\n}, 1000)\n\nif (bootstrap) {\n syncUrl(true)\n render()\n beginWatch()\n} else {\n loadContracts().then(() => beginWatch()).catch((error) => {\n state.error = error instanceof Error ? error.message : String(error)\n render()\n })\n}\n`\n","/**\n * experimental-a2/devtools/server — a read-only dashboard over A2's durable data.\n * One Request handler serves the application, its private browser bundle,\n * JSON inspection endpoints, and live SSE invalidations.\n */\n\nimport {\n DEVTOOLS_APP_CSS,\n DEVTOOLS_APP_JS,\n devtoolsHtml,\n type DevtoolsBootstrap,\n} from './devtools-app.ts'\nimport {\n DEVTOOLS_CAPABILITIES,\n DEVTOOLS_CAPTURE_MEDIA_TYPE,\n DEVTOOLS_PROTOCOL_VERSION,\n DEVTOOLS_SESSION_PAGE_LIMIT,\n DEVTOOLS_SESSION_PAGE_MAX_LIMIT,\n devtoolsSessionRevision,\n encodeDevtoolsCapture,\n type DevtoolsSessionPage,\n type DevtoolsSessionDetail,\n type DevtoolsWireEvent,\n type DevtoolsWireSessionSummary,\n type DevtoolsWireSnapshot,\n} from './devtools.ts'\nimport {\n DEVTOOLS_TIMINGS,\n InspectionUnsupportedError,\n serverInspection,\n type ServerInspection,\n} from './inspection.ts'\nimport type {\n StoredEvent,\n StoredSessionSummary,\n StoredSnapshot,\n} from './store.ts'\n\nexport type DevtoolsServer = {\n readonly contract: { readonly name: string }\n}\n\nexport type DevtoolsAuthorization = (\n request: Request,\n) => boolean | Response | Promise<boolean | Response>\n\nexport type DevtoolsOptions = {\n servers: readonly DevtoolsServer[]\n /**\n * Application-owned access control. Returning a Response short-circuits\n * the request. Without this option, devtools is available only when\n * NODE_ENV is exactly \"development\" and otherwise returns 404.\n */\n authorize?: DevtoolsAuthorization\n}\n\nexport type Devtools = {\n handler(): (request: Request) => Promise<Response>\n}\n\nconst MARKER = '/_a2/'\nconst encoder = new TextEncoder()\n\ntype InspectionDetail = {\n events: StoredEvent[]\n snapshots: StoredSnapshot[]\n}\n\nconst isDevelopment = (): boolean =>\n typeof process !== 'undefined' && process.env?.['NODE_ENV'] === 'development'\n\nconst headers = (contentType: string): HeadersInit => ({\n 'content-type': contentType,\n 'cache-control': 'no-store',\n 'x-content-type-options': 'nosniff',\n 'referrer-policy': 'no-referrer',\n})\n\nconst json = (value: unknown, init?: ResponseInit): Response =>\n Response.json(value, {\n ...init,\n headers: {\n ...headers('application/json; charset=utf-8'),\n ...init?.headers,\n },\n })\n\nconst notFound = (): Response =>\n new Response('Not found', { status: 404, headers: headers('text/plain') })\n\nconst badRequest = (message: string): Response =>\n json({ error: message }, { status: 400 })\n\nconst unavailable = (message: string): Response =>\n json({ error: message }, { status: 501 })\n\nconst wireSummary = (\n session: StoredSessionSummary,\n): DevtoolsWireSessionSummary => ({\n sessionId: session.sessionId,\n eventCount: session.eventCount,\n pendingCount: session.pendingCount,\n failedCount: session.failedCount,\n attemptCount: session.attemptCount,\n failureCount: session.failureCount,\n firstEventAt: session.firstEventAt.toISOString(),\n updatedAt: session.updatedAt.toISOString(),\n})\n\nconst wireEvent = (event: StoredEvent): DevtoolsWireEvent => ({\n id: event.id,\n type: event.type,\n payload: event.payload,\n index: event.index,\n sessionId: event.sessionId,\n createdAt: event.createdAt.toISOString(),\n cause:\n event.cause === null\n ? null\n : {\n index: event.cause.index,\n attempt: event.cause.attempt,\n ...(event.cause.batchSize === undefined\n ? {}\n : { batchSize: event.cause.batchSize }),\n },\n lane: event.lane,\n processedAt: event.processedAt?.toISOString() ?? null,\n processedByAttempt: event.processedByAttempt,\n returnedEventIds: event.returnedEventIds,\n firstClaimedAt: event.firstClaimedAt?.toISOString() ?? null,\n lastClaimedAt: event.lastClaimedAt?.toISOString() ?? null,\n attemptCount: event.attemptCount,\n claimHolder: event.claimHolder,\n claimExpiresAt: event.claimExpiresAt?.toISOString() ?? null,\n failureCount: event.failureCount,\n lastFailedAt: event.lastFailedAt?.toISOString() ?? null,\n lastFailedAttempt: event.lastFailedAttempt,\n lastError: event.lastError,\n failedAt: event.failedAt?.toISOString() ?? null,\n})\n\nconst wireSnapshot = (snapshot: StoredSnapshot): DevtoolsWireSnapshot => ({\n reducerName: snapshot.reducerName,\n index: snapshot.index,\n // Redis snapshots written before inspection metadata have no durable\n // timestamp. Epoch zero is the adapter's explicit \"unknown\" marker.\n updatedAt:\n snapshot.updatedAt.getTime() === 0\n ? null\n : snapshot.updatedAt.toISOString(),\n})\n\nconst sessionRevision = async (detail: InspectionDetail): Promise<string> => {\n return devtoolsSessionRevision({\n events: detail.events.map(wireEvent),\n snapshots: detail.snapshots.map(wireSnapshot),\n })\n}\n\nconst wireDetail = async (\n contract: string,\n sessionId: string,\n detail: InspectionDetail,\n): Promise<DevtoolsSessionDetail> => ({\n protocolVersion: DEVTOOLS_PROTOCOL_VERSION,\n contract,\n sessionId,\n revision: await sessionRevision(detail),\n events: detail.events.map(wireEvent),\n snapshots: detail.snapshots.map(wireSnapshot),\n})\n\nconst readCompleteSession = async (\n inspection: ServerInspection,\n sessionId: string,\n): Promise<InspectionDetail> => {\n const events: StoredEvent[] = []\n let snapshots: StoredSnapshot[] = []\n let afterIndex = 0\n let throughIndex: number | undefined\n for (;;) {\n // oxlint-disable-next-line no-await-in-loop -- each request advances one fixed-frontier page\n const page = await inspection.readSessionPage(sessionId, {\n afterIndex,\n limit: DEVTOOLS_SESSION_PAGE_LIMIT,\n ...(throughIndex === undefined ? {} : { throughIndex }),\n })\n if (throughIndex === undefined) {\n throughIndex = page.throughIndex\n snapshots = page.snapshots\n } else if (page.throughIndex !== throughIndex) {\n throw new Error('inspection session frontier changed between pages')\n }\n if (events.length + page.events.length > page.throughIndex) {\n throw new Error('inspection session page crossed its frozen frontier')\n }\n events.push(...page.events)\n if (page.nextIndex === null) break\n if (events.length === page.throughIndex) {\n throw new Error('inspection session cursor continued past its frontier')\n }\n if (page.nextIndex <= afterIndex) {\n throw new Error('inspection session cursor did not advance')\n }\n afterIndex = page.nextIndex\n }\n if (throughIndex !== events.length) {\n throw new Error('inspection session ended before its frozen frontier')\n }\n return { events, snapshots }\n}\n\nconst readRevision = async (\n inspection: ServerInspection,\n sessionId: string,\n): Promise<{\n detail: InspectionDetail\n revision: string\n}> => {\n const detail = await readCompleteSession(inspection, sessionId)\n return { detail, revision: await sessionRevision(detail) }\n}\n\nconst bootstrapState = async (\n url: URL,\n servers: ReadonlyMap<string, DevtoolsServer>,\n): Promise<DevtoolsBootstrap> => {\n const contracts = [...servers.keys()].map((name) => ({ name }))\n const requestedContract = url.searchParams.get('contract')\n const contract =\n requestedContract && servers.has(requestedContract)\n ? requestedContract\n : (contracts[0]?.name ?? null)\n const bootstrap: DevtoolsBootstrap = {\n contracts,\n contract,\n sessions: [],\n cursor: null,\n session: null,\n detail: null,\n error: null,\n }\n if (!contract) return bootstrap\n\n const server = servers.get(contract)\n const inspection = server ? serverInspection.get(server) : undefined\n if (!inspection) {\n bootstrap.error = `contract '${contract}' is not an inspectable A2 server`\n return bootstrap\n }\n\n try {\n const page = await inspection.listSessions({ limit: 50 })\n bootstrap.sessions = page.sessions.map(wireSummary)\n bootstrap.cursor = page.cursor\n const requestedSession =\n requestedContract === contract ? url.searchParams.get('session') : null\n bootstrap.session = requestedSession ?? page.sessions[0]?.sessionId ?? null\n if (!bootstrap.session) return bootstrap\n\n const detail = await readCompleteSession(inspection, bootstrap.session)\n if (detail.events.length === 0) {\n bootstrap.error = `session '${bootstrap.session}' was not found`\n return bootstrap\n }\n bootstrap.detail = await wireDetail(contract, bootstrap.session, detail)\n } catch (error) {\n bootstrap.error = error instanceof Error ? error.message : String(error)\n }\n return bootstrap\n}\n\nconst resourceOf = (pathname: string): string | null => {\n const marker = pathname.lastIndexOf(MARKER)\n return marker === -1 ? null : pathname.slice(marker + MARKER.length)\n}\n\nconst inspectionOf = (\n servers: ReadonlyMap<string, DevtoolsServer>,\n contractName: string | null,\n): ServerInspection | Response => {\n if (!contractName) return badRequest('contract is required')\n const server = servers.get(contractName)\n if (!server) return notFound()\n return (\n serverInspection.get(server) ??\n unavailable(`contract '${contractName}' is not an inspectable A2 server`)\n )\n}\n\ntype SessionCursor = { afterIndex: number; throughIndex: number }\n\nconst encodeSessionCursor = (\n contract: string,\n sessionId: string,\n cursor: SessionCursor,\n): string =>\n JSON.stringify([\n 1,\n contract,\n sessionId,\n cursor.throughIndex,\n cursor.afterIndex,\n ])\n\nconst parseSessionCursor = (\n value: string,\n contract: string,\n sessionId: string,\n): SessionCursor | Response => {\n let decoded: unknown\n try {\n decoded = JSON.parse(value) as unknown\n } catch {\n return badRequest('cursor is invalid')\n }\n if (\n !Array.isArray(decoded) ||\n decoded.length !== 5 ||\n decoded[0] !== 1 ||\n !Number.isSafeInteger(decoded[3]) ||\n !Number.isSafeInteger(decoded[4]) ||\n (decoded[3] as number) < 1 ||\n (decoded[4] as number) < 1 ||\n (decoded[4] as number) >= (decoded[3] as number)\n ) {\n return badRequest('cursor is invalid')\n }\n if (decoded[1] !== contract || decoded[2] !== sessionId) {\n return badRequest('cursor does not belong to this contract and session')\n }\n return {\n throughIndex: decoded[3] as number,\n afterIndex: decoded[4] as number,\n }\n}\n\nconst captureFilename = (contract: string, sessionId: string): string => {\n const safe = `${contract}-${sessionId}`\n .replaceAll(/[^a-zA-Z0-9._-]/g, '_')\n .slice(0, 160)\n return `${safe || 'a2-session'}.a2log`\n}\n\nconst watchResponse = (\n request: Request,\n inspection: ServerInspection,\n sessionId: string,\n initial: InspectionDetail,\n): Response => {\n let stopped = false\n let timeout: ReturnType<typeof setTimeout> | null = null\n let wake: (() => void) | null = null\n const stop = (): void => {\n stopped = true\n if (timeout !== null) clearTimeout(timeout)\n timeout = null\n wake?.()\n wake = null\n }\n const wait = (milliseconds: number): Promise<void> =>\n new Promise((resolve) => {\n wake = resolve\n timeout = setTimeout(() => {\n timeout = null\n wake = null\n resolve()\n }, milliseconds)\n })\n\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n request.signal.addEventListener('abort', stop, { once: true })\n controller.enqueue(encoder.encode('retry: 1000\\n: connected\\n\\n'))\n void (async () => {\n let detail = initial\n let previous = new URL(request.url).searchParams.get('revision') ?? ''\n let lastHeartbeat = Date.now()\n try {\n let revision = await sessionRevision(detail)\n for (;;) {\n if (stopped) break\n if (revision !== previous) {\n previous = revision\n controller.enqueue(\n encoder.encode(\n `event: invalidate\\ndata: ${JSON.stringify({ revision })}\\n\\n`,\n ),\n )\n } else if (\n Date.now() - lastHeartbeat >=\n DEVTOOLS_TIMINGS.heartbeatMs\n ) {\n controller.enqueue(encoder.encode(': ping\\n\\n'))\n lastHeartbeat = Date.now()\n }\n const active = detail.events.some(\n (event) => event.processedAt === null && event.failedAt === null,\n )\n // oxlint-disable-next-line no-await-in-loop -- each revision read follows its poll interval\n await wait(\n active ? DEVTOOLS_TIMINGS.activeMs : DEVTOOLS_TIMINGS.idleMs,\n )\n if (!stopped) {\n // oxlint-disable-next-line no-await-in-loop -- poll state advances from the prior read\n const next = await readRevision(inspection, sessionId)\n detail = next.detail\n revision = next.revision\n }\n }\n controller.close()\n } catch (error) {\n if (!stopped) controller.error(error)\n }\n })()\n },\n cancel() {\n stop()\n },\n })\n\n return new Response(stream, {\n headers: {\n ...headers('text/event-stream; charset=utf-8'),\n connection: 'keep-alive',\n 'x-accel-buffering': 'no',\n },\n })\n}\n\nconst authorizeRequest = async (\n request: Request,\n authorize: DevtoolsAuthorization | undefined,\n): Promise<Response | null> => {\n if (!authorize) return isDevelopment() ? null : notFound()\n const result = await authorize(request)\n if (result instanceof Response) return result\n return result === true ? null : notFound()\n}\n\nexport function createDevtools(options: DevtoolsOptions): Devtools {\n if (\n !options ||\n !Array.isArray(options.servers) ||\n options.servers.length === 0\n ) {\n throw new TypeError('createDevtools expects at least one A2 server')\n }\n const servers = new Map<string, DevtoolsServer>()\n for (const server of options.servers) {\n const name = server?.contract?.name\n if (typeof name !== 'string' || name.length === 0) {\n throw new TypeError('createDevtools received a server without a contract')\n }\n if (servers.has(name)) {\n throw new TypeError(`createDevtools received contract '${name}' twice`)\n }\n servers.set(name, server)\n }\n\n return {\n handler() {\n return async (request) => {\n const denied = await authorizeRequest(request, options.authorize)\n if (denied) return denied\n if (request.method !== 'GET') {\n return new Response('Method not allowed', {\n status: 405,\n headers: { ...headers('text/plain'), allow: 'GET' },\n })\n }\n\n const url = new URL(request.url)\n const resource = resourceOf(url.pathname)\n if (resource === null) {\n const basePath = `${url.pathname.replace(/\\/$/, '')}/`\n const bootstrap = await bootstrapState(url, servers)\n return new Response(devtoolsHtml(basePath, bootstrap), {\n headers: {\n ...headers('text/html; charset=utf-8'),\n 'content-security-policy':\n \"default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'self' data:; base-uri 'self'; frame-ancestors 'none'\",\n },\n })\n }\n if (resource === 'app.js') {\n return new Response(DEVTOOLS_APP_JS, {\n headers: headers('text/javascript; charset=utf-8'),\n })\n }\n if (resource === 'app.css') {\n return new Response(DEVTOOLS_APP_CSS, {\n headers: headers('text/css; charset=utf-8'),\n })\n }\n if (resource === 'contracts') {\n return json({\n protocolVersion: DEVTOOLS_PROTOCOL_VERSION,\n contracts: [...servers.keys()].map((name) => ({ name })),\n })\n }\n if (resource === 'capabilities') {\n return json(DEVTOOLS_CAPABILITIES)\n }\n\n const contractName = url.searchParams.get('contract')\n const inspection = inspectionOf(servers, contractName)\n if (inspection instanceof Response) return inspection\n try {\n if (resource === 'sessions') {\n const requestedLimit = Number(url.searchParams.get('limit') ?? 50)\n if (!Number.isInteger(requestedLimit) || requestedLimit < 1) {\n return badRequest('limit must be a positive integer')\n }\n const limit = Math.min(requestedLimit, 100)\n const cursor = url.searchParams.get('cursor')\n const page = await inspection.listSessions({\n limit,\n ...(cursor !== null ? { cursor } : {}),\n })\n return json({\n protocolVersion: DEVTOOLS_PROTOCOL_VERSION,\n cursor: page.cursor,\n sessions: page.sessions.map(wireSummary),\n })\n }\n\n const sessionId = url.searchParams.get('session')\n if (!sessionId) return badRequest('session is required')\n if (\n resource === 'session' &&\n (url.searchParams.has('limit') || url.searchParams.has('cursor'))\n ) {\n const requestedLimit = Number(\n url.searchParams.get('limit') ?? DEVTOOLS_SESSION_PAGE_LIMIT,\n )\n if (!Number.isSafeInteger(requestedLimit) || requestedLimit < 1) {\n return badRequest('limit must be a positive integer')\n }\n const limit = Math.min(\n requestedLimit,\n DEVTOOLS_SESSION_PAGE_MAX_LIMIT,\n )\n const cursorValue = url.searchParams.get('cursor')\n const cursor =\n cursorValue === null\n ? { afterIndex: 0, throughIndex: undefined }\n : parseSessionCursor(cursorValue, contractName!, sessionId)\n if (cursor instanceof Response) return cursor\n const page = await inspection.readSessionPage(sessionId, {\n afterIndex: cursor.afterIndex,\n limit,\n ...(cursor.throughIndex === undefined\n ? {}\n : { throughIndex: cursor.throughIndex }),\n })\n if (page.throughIndex === 0) return notFound()\n const response: DevtoolsSessionPage = {\n protocolVersion: DEVTOOLS_PROTOCOL_VERSION,\n contract: contractName!,\n sessionId,\n throughIndex: page.throughIndex,\n cursor:\n page.nextIndex === null\n ? null\n : encodeSessionCursor(contractName!, sessionId, {\n throughIndex: page.throughIndex,\n afterIndex: page.nextIndex,\n }),\n events: page.events.map(wireEvent),\n snapshots: page.snapshots.map(wireSnapshot),\n }\n return json(response)\n }\n\n const detail = await readCompleteSession(inspection, sessionId)\n if (detail.events.length === 0) return notFound()\n const wired = await wireDetail(contractName!, sessionId, detail)\n if (resource === 'session') {\n return json(wired)\n }\n if (resource === 'export') {\n return new Response(await encodeDevtoolsCapture(wired), {\n headers: {\n ...headers(`${DEVTOOLS_CAPTURE_MEDIA_TYPE}; charset=utf-8`),\n 'content-disposition': `attachment; filename=\"${captureFilename(wired.contract, sessionId)}\"`,\n },\n })\n }\n if (resource === 'watch') {\n return watchResponse(request, inspection, sessionId, detail)\n }\n } catch (error) {\n if (error instanceof InspectionUnsupportedError) {\n return unavailable(error.message)\n }\n throw error\n }\n return notFound()\n }\n },\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { C as parseDevtoolsCapture, S as encodeDevtoolsCapture, _ as DevtoolsSessionsResponse, a as DEVTOOLS_SESSION_PAGE_LIMIT, b as DevtoolsWireSnapshot, c as DevtoolsCapture, d as DevtoolsCaptureManifest, f as DevtoolsCaptureRecord, g as DevtoolsSessionPage, h as DevtoolsSessionDetail, i as DEVTOOLS_PROTOCOL_VERSION, l as DevtoolsCaptureEvent, m as DevtoolsContractsResponse, n as DEVTOOLS_CAPTURE_MEDIA_TYPE, o as DEVTOOLS_SESSION_PAGE_MAX_LIMIT, p as DevtoolsCaptureSnapshot, r as DEVTOOLS_CAPTURE_VERSION, s as DevtoolsCapabilities, t as DEVTOOLS_CAPABILITIES, u as DevtoolsCaptureFooter, v as DevtoolsWireEvent, w as verifyDevtoolsCapture, x as devtoolsSessionRevision, y as DevtoolsWireSessionSummary } from "./devtools-J_jZ2vQf.js";
2
+ export { DEVTOOLS_CAPABILITIES, DEVTOOLS_CAPTURE_MEDIA_TYPE, DEVTOOLS_CAPTURE_VERSION, DEVTOOLS_PROTOCOL_VERSION, DEVTOOLS_SESSION_PAGE_LIMIT, DEVTOOLS_SESSION_PAGE_MAX_LIMIT, DevtoolsCapabilities, DevtoolsCapture, DevtoolsCaptureEvent, DevtoolsCaptureFooter, DevtoolsCaptureManifest, DevtoolsCaptureRecord, DevtoolsCaptureSnapshot, DevtoolsContractsResponse, DevtoolsSessionDetail, DevtoolsSessionPage, DevtoolsSessionsResponse, DevtoolsWireEvent, DevtoolsWireSessionSummary, DevtoolsWireSnapshot, devtoolsSessionRevision, encodeDevtoolsCapture, parseDevtoolsCapture, verifyDevtoolsCapture };
@@ -0,0 +1,2 @@
1
+ import { a as DEVTOOLS_SESSION_PAGE_LIMIT, c as encodeDevtoolsCapture, i as DEVTOOLS_PROTOCOL_VERSION, l as parseDevtoolsCapture, n as DEVTOOLS_CAPTURE_MEDIA_TYPE, o as DEVTOOLS_SESSION_PAGE_MAX_LIMIT, r as DEVTOOLS_CAPTURE_VERSION, s as devtoolsSessionRevision, t as DEVTOOLS_CAPABILITIES, u as verifyDevtoolsCapture } from "./devtools-kJJaORn-.js";
2
+ export { DEVTOOLS_CAPABILITIES, DEVTOOLS_CAPTURE_MEDIA_TYPE, DEVTOOLS_CAPTURE_VERSION, DEVTOOLS_PROTOCOL_VERSION, DEVTOOLS_SESSION_PAGE_LIMIT, DEVTOOLS_SESSION_PAGE_MAX_LIMIT, devtoolsSessionRevision, encodeDevtoolsCapture, parseDevtoolsCapture, verifyDevtoolsCapture };
@@ -11,13 +11,15 @@ var A2Error = class extends Error {
11
11
  }
12
12
  };
13
13
  /**
14
- * Wrap an arbitrary thrown value as LOG_UNAVAILABLE, preserving the
14
+ * Wrap an arbitrary thrown value as STORE_UNAVAILABLE, preserving the
15
15
  * original as `cause`. A2Errors pass through untouched — they already
16
16
  * carry their own meaning.
17
17
  */
18
- function asLogUnavailable(err) {
18
+ function asStoreUnavailable(err) {
19
19
  if (err instanceof A2Error) return err;
20
- return new A2Error("LOG_UNAVAILABLE", "the log backend failed", { cause: err });
20
+ return new A2Error("STORE_UNAVAILABLE", "the store backend failed", { cause: err });
21
21
  }
22
22
  //#endregion
23
- export { asLogUnavailable as n, A2Error as t };
23
+ export { asStoreUnavailable as n, A2Error as t };
24
+
25
+ //# sourceMappingURL=errors-BQuJpe82.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-BQuJpe82.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * a2 errors — one class, discriminated by `code`.\n *\n * See specs/a2-implementation.md §10 and docs/reference/02-errors.mdx.\n * A single class serializes cleanly across the push-route boundary and\n * keeps `instanceof` checks working after bundling.\n */\n\nexport type A2ErrorCode =\n | 'INVALID_PAYLOAD' // schema validation failed — thrown before anything is written\n | 'UNKNOWN_EVENT_TYPE' // event type not in the machine's `events` map\n | 'PARTIAL_DUPLICATE_BATCH' // batch mixed already-appended and fresh events\n | 'SUPERSEDED_ATTEMPT' // handler append from an attempt a recovery claim replaced\n | 'CLAIM_EXPIRED' // ctx.signal abort reason: the attempt's lease lapsed without renewal\n | 'STORE_UNAVAILABLE' // store backend failure — retryable, nothing was written\n | 'STORE_NOT_CONFIGURED' // production boot with no `store` passed\n | 'UNKNOWN_PRESENCE_FIELD' // presence field not in the contract's `presence` map\n | 'PRESENCE_NOT_SUPPORTED' // contract declares presence but the store lacks the capability\n\nexport class A2Error extends Error {\n readonly code: A2ErrorCode\n /** e.g. the Standard Schema issues for INVALID_PAYLOAD */\n readonly details: unknown\n\n constructor(\n code: A2ErrorCode,\n message: string,\n options?: { details?: unknown; cause?: unknown },\n ) {\n super(\n message,\n options && 'cause' in options ? { cause: options.cause } : undefined,\n )\n this.name = 'A2Error'\n this.code = code\n this.details = options?.details\n }\n}\n\n/**\n * Wrap an arbitrary thrown value as STORE_UNAVAILABLE, preserving the\n * original as `cause`. A2Errors pass through untouched — they already\n * carry their own meaning.\n */\nexport function asStoreUnavailable(err: unknown): A2Error {\n if (err instanceof A2Error) return err\n return new A2Error('STORE_UNAVAILABLE', 'the store backend failed', {\n cause: err,\n })\n}\n"],"mappings":";AAmBA,IAAa,UAAb,cAA6B,MAAM;CACjC;;CAEA;CAEA,YACE,MACA,SACA,SACA;EACA,MACE,SACA,WAAW,WAAW,UAAU,EAAE,OAAO,QAAQ,MAAM,IAAI,KAAA,CAC7D;EACA,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,UAAU,SAAS;CAC1B;AACF;;;;;;AAOA,SAAgB,mBAAmB,KAAuB;CACxD,IAAI,eAAe,SAAS,OAAO;CACnC,OAAO,IAAI,QAAQ,qBAAqB,4BAA4B,EAClE,OAAO,IACT,CAAC;AACH"}
@@ -6,7 +6,7 @@
6
6
  * A single class serializes cleanly across the push-route boundary and
7
7
  * keeps `instanceof` checks working after bundling.
8
8
  */
9
- type A2ErrorCode = "INVALID_PAYLOAD" | "UNKNOWN_EVENT_TYPE" | "PARTIAL_DUPLICATE_BATCH" | "LOG_UNAVAILABLE" | "LOG_NOT_CONFIGURED";
9
+ type A2ErrorCode = "INVALID_PAYLOAD" | "UNKNOWN_EVENT_TYPE" | "PARTIAL_DUPLICATE_BATCH" | "SUPERSEDED_ATTEMPT" | "CLAIM_EXPIRED" | "STORE_UNAVAILABLE" | "STORE_NOT_CONFIGURED" | "UNKNOWN_PRESENCE_FIELD" | "PRESENCE_NOT_SUPPORTED";
10
10
  declare class A2Error extends Error {
11
11
  readonly code: A2ErrorCode;
12
12
  /** e.g. the Standard Schema issues for INVALID_PAYLOAD */
@@ -17,4 +17,5 @@ declare class A2Error extends Error {
17
17
  });
18
18
  }
19
19
  //#endregion
20
- export { A2ErrorCode as n, A2Error as t };
20
+ export { A2ErrorCode as n, A2Error as t };
21
+ //# sourceMappingURL=errors-W6nwJ-fm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-W6nwJ-fm.d.ts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;KAQY;cAWC,gBAAgB;WAClB,MAAM;;WAEN;EAET,YACE,MAAM,aACN,iBACA;IAAY;IAAmB"}