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.
- package/CHANGELOG.md +207 -0
- package/dist/ai-CFNeCrRl.d.ts +334 -0
- package/dist/ai-CFNeCrRl.d.ts.map +1 -0
- package/dist/ai-server.browser.js +2 -0
- package/dist/ai-server.browser.js.map +1 -0
- package/dist/ai-server.d.ts +49 -9
- package/dist/ai-server.d.ts.map +1 -0
- package/dist/ai-server.js +123 -49
- package/dist/ai-server.js.map +1 -0
- package/dist/ai.d.ts +2 -303
- package/dist/ai.js +233 -86
- package/dist/ai.js.map +1 -0
- package/dist/cli-B3VuxoDe.js +599 -0
- package/dist/cli-B3VuxoDe.js.map +1 -0
- package/dist/cli-bin.d.ts +1 -0
- package/dist/cli-bin.js +7 -0
- package/dist/cli-bin.js.map +1 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/client-BKlyLiOU.js +1008 -0
- package/dist/client-BKlyLiOU.js.map +1 -0
- package/dist/client-D7mvIXrF.d.ts +191 -0
- package/dist/client-D7mvIXrF.d.ts.map +1 -0
- package/dist/client.d.ts +2 -90
- package/dist/client.js +1 -409
- package/dist/{contract-CG_adnu_.js → contract-48bUMgcL.js} +12 -2
- package/dist/contract-48bUMgcL.js.map +1 -0
- package/dist/{contract-C_3dIIEU.d.ts → contract-jIfaR085.d.ts} +63 -8
- package/dist/contract-jIfaR085.d.ts.map +1 -0
- package/dist/devtools-J_jZ2vQf.d.ts +152 -0
- package/dist/devtools-J_jZ2vQf.d.ts.map +1 -0
- package/dist/devtools-kJJaORn-.js +340 -0
- package/dist/devtools-kJJaORn-.js.map +1 -0
- package/dist/devtools-server.browser.js +3 -1
- package/dist/devtools-server.browser.js.map +1 -0
- package/dist/devtools-server.d.ts +4 -3
- package/dist/devtools-server.d.ts.map +1 -0
- package/dist/devtools-server.js +226 -43
- package/dist/devtools-server.js.map +1 -0
- package/dist/devtools.d.ts +2 -0
- package/dist/devtools.js +2 -0
- package/dist/{errors-BJRMd-h6.js → errors-BQuJpe82.js} +6 -4
- package/dist/errors-BQuJpe82.js.map +1 -0
- package/dist/{errors-xL_JTXsY.d.ts → errors-W6nwJ-fm.d.ts} +3 -2
- package/dist/errors-W6nwJ-fm.d.ts.map +1 -0
- package/dist/http.d.ts +135 -28
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +647 -61
- package/dist/http.js.map +1 -0
- package/dist/{idempotent-replay-BMyHrP0L.js → idempotent-replay-DuqEkYA7.js} +4 -2
- package/dist/idempotent-replay-DuqEkYA7.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/{inspection-E7qbD0Xj.js → inspection-DaxB5jM2.js} +4 -1
- package/dist/inspection-DaxB5jM2.js.map +1 -0
- package/dist/internal-DstsI6Re.js +211 -0
- package/dist/internal-DstsI6Re.js.map +1 -0
- package/dist/otel.d.ts +3 -2
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +2 -0
- package/dist/otel.js.map +1 -0
- package/dist/platform-B4TnJtWu.js +34 -0
- package/dist/platform-B4TnJtWu.js.map +1 -0
- package/dist/react.d.ts +47 -15
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +30 -15
- package/dist/react.js.map +1 -0
- package/dist/retryable-lazy-DZWmHpii.js +2 -0
- package/dist/retryable-lazy-DZWmHpii.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +79 -0
- package/dist/scheduler-qstash.d.ts.map +1 -0
- package/dist/scheduler-qstash.js +501 -0
- package/dist/scheduler-qstash.js.map +1 -0
- package/dist/scheduler-task-BpzhPnRS.js +56 -0
- package/dist/scheduler-task-BpzhPnRS.js.map +1 -0
- package/dist/{recovery-vercel.d.ts → scheduler-vercel.d.ts} +18 -24
- package/dist/scheduler-vercel.d.ts.map +1 -0
- package/dist/scheduler-vercel.js +228 -0
- package/dist/scheduler-vercel.js.map +1 -0
- package/dist/server-C72KOw51.js +1457 -0
- package/dist/server-C72KOw51.js.map +1 -0
- package/dist/server-DUF9pjsx.d.ts +261 -0
- package/dist/server-DUF9pjsx.d.ts.map +1 -0
- package/dist/server.browser.js +2 -0
- package/dist/server.browser.js.map +1 -0
- package/dist/server.d.ts +4 -155
- package/dist/server.js +2 -2
- package/dist/{log-ldf5g8Cx.d.ts → store-DysUkTH3.d.ts} +120 -35
- package/dist/store-DysUkTH3.d.ts.map +1 -0
- package/dist/{log-yJbXUf72.js → store-N8PXxDAS.js} +3 -1
- package/dist/store-N8PXxDAS.js.map +1 -0
- package/dist/store-codec-DTG0Ftek.js +10 -0
- package/dist/store-codec-DTG0Ftek.js.map +1 -0
- package/dist/store-memory.d.ts +12 -0
- package/dist/store-memory.d.ts.map +1 -0
- package/dist/{log-memory.js → store-memory.js} +145 -34
- package/dist/store-memory.js.map +1 -0
- package/dist/{log-polling-DZ1MiKLg.js → store-polling-dSeLxzfb.js} +7 -5
- package/dist/store-polling-dSeLxzfb.js.map +1 -0
- package/dist/{log-postgres.d.ts → store-postgres.d.ts} +7 -6
- package/dist/store-postgres.d.ts.map +1 -0
- package/dist/{log-postgres.js → store-postgres.js} +214 -24
- package/dist/store-postgres.js.map +1 -0
- package/dist/{log-redis-core-CyJ5L8yR.js → store-redis-core-BFLwz0Wj.js} +271 -66
- package/dist/store-redis-core-BFLwz0Wj.js.map +1 -0
- package/dist/{log-redis-http.d.ts → store-redis-http.d.ts} +8 -7
- package/dist/store-redis-http.d.ts.map +1 -0
- package/dist/{log-redis-http.js → store-redis-http.js} +22 -12
- package/dist/store-redis-http.js.map +1 -0
- package/dist/{log-redis.d.ts → store-redis.d.ts} +7 -6
- package/dist/store-redis.d.ts.map +1 -0
- package/dist/{log-redis.js → store-redis.js} +101 -39
- package/dist/store-redis.js.map +1 -0
- package/dist/{log-sqlite.d.ts → store-sqlite.d.ts} +7 -6
- package/dist/store-sqlite.d.ts.map +1 -0
- package/dist/{log-sqlite.js → store-sqlite.js} +118 -22
- package/dist/store-sqlite.js.map +1 -0
- package/dist/{telemetry-Cso0qyHQ.d.ts → telemetry-BjYHTfh2.d.ts} +3 -2
- package/dist/telemetry-BjYHTfh2.d.ts.map +1 -0
- package/dist/testing.browser.d.ts +1 -0
- package/dist/testing.browser.js +6 -0
- package/dist/testing.browser.js.map +1 -0
- package/dist/testing.d.ts +32 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +103 -0
- package/dist/testing.js.map +1 -0
- package/dist/validate-XKT4FSNn.js +2 -0
- package/dist/validate-XKT4FSNn.js.map +1 -0
- package/dist/wire-BFQmSJ-9.js +284 -0
- package/dist/wire-BFQmSJ-9.js.map +1 -0
- package/docs/01-quickstart.mdx +3 -3
- package/docs/concepts/01-contracts.mdx +18 -13
- package/docs/concepts/02-handlers.mdx +7 -7
- package/docs/concepts/03-durability.mdx +21 -21
- package/docs/concepts/04-state.mdx +18 -21
- package/docs/guides/01-timers.mdx +154 -49
- package/docs/guides/02-cancellation.mdx +30 -4
- package/docs/guides/03-react.mdx +76 -57
- package/docs/guides/04-local-first.mdx +1 -1
- package/docs/guides/05-production.mdx +310 -61
- package/docs/guides/06-ai-agents.mdx +252 -74
- package/docs/guides/07-devtools.mdx +137 -12
- package/docs/guides/08-application-data.mdx +1 -1
- package/docs/guides/09-presence.mdx +263 -0
- package/docs/guides/10-transports.mdx +140 -0
- package/docs/index.mdx +18 -14
- package/docs/reference/01-api.mdx +839 -110
- package/docs/reference/02-errors.mdx +13 -6
- package/package.json +24 -7
- package/src/ai-coordinator.ts +358 -0
- package/src/ai-projector.ts +524 -0
- package/src/ai-sdk-step.ts +261 -0
- package/src/ai-server.browser.ts +5 -0
- package/src/ai-server.ts +1719 -0
- package/src/ai.ts +2155 -0
- package/src/cache-indexeddb.ts +10 -0
- package/src/cli-bin.ts +5 -0
- package/src/cli.ts +1046 -0
- package/src/client.ts +1826 -0
- package/src/contract.ts +206 -0
- package/src/deterministic-id.ts +72 -0
- package/src/devtools-app.ts +989 -0
- package/src/devtools-server.browser.ts +5 -0
- package/src/devtools-server.ts +604 -0
- package/src/devtools.ts +716 -0
- package/src/errors.ts +50 -0
- package/src/http.ts +394 -0
- package/src/idempotent-replay.ts +53 -0
- package/src/index.ts +37 -0
- package/src/inspection.ts +39 -0
- package/src/internal.ts +426 -0
- package/src/otel.ts +59 -0
- package/src/platform.ts +60 -0
- package/src/push-envelope.ts +137 -0
- package/src/react.ts +284 -0
- package/src/reducer.ts +108 -0
- package/src/retryable-lazy.ts +27 -0
- package/src/scheduler-qstash.ts +915 -0
- package/src/scheduler-task.ts +106 -0
- package/src/scheduler-vercel.ts +437 -0
- package/src/server.browser.ts +12 -0
- package/src/server.ts +2700 -0
- package/src/session-socket.ts +548 -0
- package/src/sse.ts +141 -0
- package/src/standard-schema.ts +77 -0
- package/src/store-codec.ts +10 -0
- package/src/store-memory.ts +788 -0
- package/src/store-polling.ts +102 -0
- package/src/store-postgres.ts +1212 -0
- package/src/store-redis-core.ts +1494 -0
- package/src/store-redis-http.ts +116 -0
- package/src/store-redis.ts +458 -0
- package/src/store-sqlite.ts +1108 -0
- package/src/store.ts +385 -0
- package/src/telemetry.ts +47 -0
- package/src/testing.browser.ts +5 -0
- package/src/testing.ts +185 -0
- package/src/validate.ts +39 -0
- package/src/wire.ts +454 -0
- package/dist/internal-gCd5qMry.js +0 -44
- package/dist/log-memory.d.ts +0 -11
- package/dist/recovery-vercel.js +0 -119
- package/dist/server-BcLa4RFL.js +0 -877
- package/dist/wire-BVsgR8o9.js +0 -62
package/dist/devtools-server.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { n as serverInspection, t as DEVTOOLS_TIMINGS } from "./inspection-
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
4
6
|
const escapeAttribute = (value) => escapeText(value).replaceAll("\"", """);
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
868
|
-
events: detail.events.map(
|
|
869
|
-
|
|
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
|
|
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
|
|
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
|
|
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({
|
|
1055
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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('&', '&').replaceAll('<', '<').replaceAll('>', '>')\n\nconst escapeAttribute = (value: string): string =>\n escapeText(value).replaceAll('\"', '"')\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 };
|
package/dist/devtools.js
ADDED
|
@@ -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
|
|
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
|
|
18
|
+
function asStoreUnavailable(err) {
|
|
19
19
|
if (err instanceof A2Error) return err;
|
|
20
|
-
return new A2Error("
|
|
20
|
+
return new A2Error("STORE_UNAVAILABLE", "the store backend failed", { cause: err });
|
|
21
21
|
}
|
|
22
22
|
//#endregion
|
|
23
|
-
export {
|
|
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" | "
|
|
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"}
|