experimental-a2 0.2.0 → 0.4.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 +169 -0
- package/dist/ai-B4YhEnfw.d.ts +333 -0
- package/dist/ai-server.d.ts +48 -9
- package/dist/ai-server.js +121 -49
- package/dist/ai.d.ts +2 -303
- package/dist/ai.js +231 -86
- package/dist/cli-B3VuxoDe.js +597 -0
- package/dist/cli-bin.d.ts +1 -0
- package/dist/cli-bin.js +5 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +2 -0
- package/dist/client-BrfDXQ8A.d.ts +155 -0
- package/dist/client-Bt4tAKi9.js +798 -0
- package/dist/client.d.ts +2 -90
- package/dist/client.js +1 -409
- package/dist/{contract-CG_adnu_.js → contract-48bUMgcL.js} +10 -2
- package/dist/{contract-C_3dIIEU.d.ts → contract-jIfaR085.d.ts} +62 -8
- package/dist/devtools-J_jZ2vQf.d.ts +151 -0
- package/dist/devtools-kJJaORn-.js +338 -0
- package/dist/devtools-server.browser.js +1 -1
- package/dist/devtools-server.d.ts +2 -2
- package/dist/devtools-server.js +224 -43
- package/dist/devtools.d.ts +2 -0
- package/dist/devtools.js +2 -0
- package/dist/{errors-BJRMd-h6.js → errors-BQuJpe82.js} +4 -4
- package/dist/{errors-xL_JTXsY.d.ts → errors-W6nwJ-fm.d.ts} +1 -1
- package/dist/http.d.ts +71 -13
- package/dist/http.js +302 -41
- package/dist/{idempotent-replay-BMyHrP0L.js → idempotent-replay-DuqEkYA7.js} +2 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/{inspection-E7qbD0Xj.js → inspection-DaxB5jM2.js} +2 -1
- package/dist/internal-aEotMzu_.js +209 -0
- package/dist/otel.d.ts +1 -1
- package/dist/platform-B4TnJtWu.js +32 -0
- package/dist/react.d.ts +37 -14
- package/dist/react.js +26 -15
- package/dist/scheduler-qstash.d.ts +78 -0
- package/dist/scheduler-qstash.js +499 -0
- package/dist/scheduler-task-BpzhPnRS.js +54 -0
- package/dist/{recovery-vercel.d.ts → scheduler-vercel.d.ts} +17 -24
- package/dist/scheduler-vercel.js +226 -0
- package/dist/server-CcNnFnoW.js +1405 -0
- package/dist/server-YtPq7hjw.d.ts +260 -0
- package/dist/server.d.ts +4 -155
- package/dist/server.js +2 -2
- package/dist/{log-ldf5g8Cx.d.ts → store-C3sNAaBT.d.ts} +111 -35
- package/dist/{log-yJbXUf72.js → store-N8PXxDAS.js} +1 -1
- package/dist/store-codec-DTG0Ftek.js +8 -0
- package/dist/store-memory.d.ts +11 -0
- package/dist/{log-memory.js → store-memory.js} +127 -24
- package/dist/{log-polling-6COoN60V.js → store-polling-DgrrAE3d.js} +7 -6
- package/dist/{log-postgres.d.ts → store-postgres.d.ts} +6 -6
- package/dist/{log-postgres.js → store-postgres.js} +158 -24
- package/dist/{log-redis.js → store-redis-core-DWqx3F47.js} +294 -156
- package/dist/store-redis-http.d.ts +21 -0
- package/dist/store-redis-http.js +70 -0
- package/dist/store-redis.d.ts +37 -0
- package/dist/store-redis.js +298 -0
- package/dist/{log-sqlite.d.ts → store-sqlite.d.ts} +6 -6
- package/dist/{log-sqlite.js → store-sqlite.js} +116 -22
- package/dist/{telemetry-Cso0qyHQ.d.ts → telemetry-BjYHTfh2.d.ts} +1 -1
- package/dist/testing.browser.d.ts +1 -0
- package/dist/testing.browser.js +4 -0
- package/dist/testing.d.ts +31 -0
- package/dist/testing.js +101 -0
- package/dist/wire-DCUZBUlT.js +222 -0
- package/docs/01-quickstart.mdx +4 -5
- package/docs/concepts/01-contracts.mdx +21 -17
- package/docs/concepts/02-handlers.mdx +7 -7
- package/docs/concepts/03-durability.mdx +26 -29
- package/docs/concepts/04-state.mdx +18 -21
- package/docs/guides/01-timers.mdx +154 -54
- package/docs/guides/02-cancellation.mdx +30 -4
- package/docs/guides/03-react.mdx +20 -21
- package/docs/guides/04-local-first.mdx +1 -1
- package/docs/guides/05-production.mdx +321 -60
- package/docs/guides/06-ai-agents.mdx +249 -49
- package/docs/guides/07-devtools.mdx +137 -12
- package/docs/guides/08-application-data.mdx +1 -1
- package/docs/guides/09-presence.mdx +284 -0
- package/docs/guides/10-transports.mdx +131 -0
- package/docs/index.mdx +22 -46
- package/docs/reference/01-api.mdx +751 -102
- package/docs/reference/02-errors.mdx +10 -5
- package/package.json +23 -6
- package/dist/internal-D6wNxTck.js +0 -36
- package/dist/log-memory.d.ts +0 -11
- package/dist/log-redis.d.ts +0 -31
- package/dist/recovery-vercel.js +0 -119
- package/dist/server-DJgD2YWP.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();
|
|
@@ -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,13 @@ 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 };
|
|
@@ -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 */
|
package/dist/http.d.ts
CHANGED
|
@@ -1,36 +1,55 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { a as EventDefs, c as PresencePatch, l as PresenceSnapshot } from "./contract-jIfaR085.js";
|
|
2
|
+
import { a as Event } from "./store-C3sNAaBT.js";
|
|
3
|
+
import { _ as Session, f as PushedEvent, i as DrainableServer, p as PushedPresence } from "./server-YtPq7hjw.js";
|
|
4
|
+
import { t as A2Error } from "./errors-W6nwJ-fm.js";
|
|
4
5
|
//#region src/http.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Create one scheduler callback route for servers sharing the same configured
|
|
8
|
+
* scheduler. Configuration mistakes throw immediately while the route module
|
|
9
|
+
* is evaluated, before a delivery can be acknowledged by the wrong adapter.
|
|
10
|
+
*/
|
|
11
|
+
declare function schedulerHandler(firstServer: DrainableServer, ...servers: DrainableServer[]): (req: Request) => Promise<Response>;
|
|
5
12
|
/** The push envelope: what a client POSTs to append events. */
|
|
6
13
|
type PushBody = {
|
|
7
14
|
sessionId: string;
|
|
8
15
|
/** Branded: `session.append` accepts these directly (the push route
|
|
9
|
-
* path); schema validation still happens inside `append`.
|
|
16
|
+
* path); schema validation still happens inside `append`. Empty only
|
|
17
|
+
* for a presence-only push. */
|
|
10
18
|
events: PushedEvent[];
|
|
19
|
+
/** Branded like `events`: `session.setPresence` accepts it whole;
|
|
20
|
+
* field validation still happens inside `setPresence`. */
|
|
21
|
+
presence?: PushedPresence;
|
|
11
22
|
};
|
|
12
23
|
/**
|
|
13
|
-
* Validate the push envelope — `{ sessionId, events }` —
|
|
14
|
-
* `INVALID_PAYLOAD` on a malformed body. Payload validation
|
|
15
|
-
* machine's schemas happens in `append
|
|
24
|
+
* Validate the push envelope — `{ sessionId, events, presence? }` —
|
|
25
|
+
* throwing `INVALID_PAYLOAD` on a malformed body. Payload validation
|
|
26
|
+
* against the machine's schemas happens in `append` and `setPresence`,
|
|
27
|
+
* not here. Either plane may appear alone: a presence-only push has no
|
|
28
|
+
* (or empty) `events`.
|
|
16
29
|
*/
|
|
17
30
|
declare function parsePushBody(req: Request): Promise<PushBody>;
|
|
18
31
|
/**
|
|
19
32
|
* Pipe a live event iterable into an SSE `Response`. Each event is one
|
|
20
|
-
* frame — `id:` carries the log index, `data:` the JSON event. A
|
|
33
|
+
* frame — `id:` carries the event-log index, `data:` the JSON event. A
|
|
34
|
+
* `stream({ presence: true })` iterable also yields presence items,
|
|
35
|
+
* which ride as named frames old clients skip: `event:
|
|
36
|
+
* presence-snapshot` first (the map, ISO dates), then `event: presence`
|
|
37
|
+
* per patch — no `id:`, presence never advances the resume frontier. A
|
|
21
38
|
* disconnecting client cancels the stream, which closes the underlying
|
|
22
39
|
* subscription. Two kinds of comment frames ride along: a `: connected`
|
|
23
40
|
* prelude that flushes headers immediately, and a `: ping` heartbeat
|
|
24
41
|
* every 15 seconds so clients (and proxies) can tell a quiet stream
|
|
25
42
|
* from a dead connection — the session client's stall watchdog counts
|
|
26
|
-
* on it.
|
|
43
|
+
* on it. When the platform exposes an invocation deadline, the response
|
|
44
|
+
* closes cleanly one second before it so the client reconnects without
|
|
45
|
+
* a platform-timeout failure.
|
|
27
46
|
*/
|
|
28
|
-
declare function sseResponse(iterable: AsyncIterable<Event>): Response;
|
|
47
|
+
declare function sseResponse(iterable: AsyncIterable<Event | PresencePatch | PresenceSnapshot>): Response;
|
|
29
48
|
/**
|
|
30
49
|
* Serialize an error into the documented wire shape,
|
|
31
50
|
* `{ error: { code, message, details } }`, with the mapped status
|
|
32
|
-
* (400 for caller bugs, 503 for
|
|
33
|
-
* 503
|
|
51
|
+
* (400 for caller bugs, 503 for STORE_UNAVAILABLE). Non-A2Errors become a
|
|
52
|
+
* 503 STORE_UNAVAILABLE — from the client's perspective an unknown server
|
|
34
53
|
* failure is retryable-once, not a protocol contract.
|
|
35
54
|
*/
|
|
36
55
|
declare function errorResponse(error: unknown): Response;
|
|
@@ -40,5 +59,44 @@ declare function errorResponse(error: unknown): Response;
|
|
|
40
59
|
* sides branch on identical codes.
|
|
41
60
|
*/
|
|
42
61
|
declare function deserializeError(body: unknown): A2Error | null;
|
|
62
|
+
/**
|
|
63
|
+
* The structural socket `sessionSocket` speaks against — satisfied by
|
|
64
|
+
* `ws` and by `@vercel/functions`' upgraded socket alike, so the
|
|
65
|
+
* platform upgrade API stays out of the library. `message` data
|
|
66
|
+
* arrives as whatever the platform delivers (string, Buffer,
|
|
67
|
+
* ArrayBuffer, or fragments); `sessionSocket` normalizes defensively.
|
|
68
|
+
*/
|
|
69
|
+
type A2Socket = {
|
|
70
|
+
send(data: string): void;
|
|
71
|
+
on(event: "message", listener: (data: unknown) => void): void;
|
|
72
|
+
on(event: "close", listener: () => void): void;
|
|
73
|
+
on(event: "error", listener: (error: unknown) => void): void;
|
|
74
|
+
close(code?: number, reason?: string): void;
|
|
75
|
+
/** Bytes queued but not yet transmitted — when exposed, the down
|
|
76
|
+
* pump uses it for backpressure (`SOCKET_TIMINGS`). */
|
|
77
|
+
bufferedAmount?: number;
|
|
78
|
+
};
|
|
79
|
+
type SessionSocketOptions = {
|
|
80
|
+
/** The resume frontier, from the upgrade URL's query — the same
|
|
81
|
+
* parameter the SSE GET reads. */
|
|
82
|
+
startAfter?: number;
|
|
83
|
+
/** The `stream()` presence opt-in — the server decides, same rule as
|
|
84
|
+
* the SSE route. */
|
|
85
|
+
presence?: boolean;
|
|
86
|
+
/** Epoch ms: close cleanly (code 1000) at this time, ahead of a known
|
|
87
|
+
* platform deadline, so clients reconnect on our schedule. */
|
|
88
|
+
deadline?: number;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Speak the A2 socket protocol (specs/a2-api.md §13) against one
|
|
92
|
+
* session: the live stream pumps down as `kind`-framed JSON with a
|
|
93
|
+
* `ping` heartbeat on the SSE cadence; `push` frames come up through
|
|
94
|
+
* the same validation seams as `parsePushBody` (same provenance brand,
|
|
95
|
+
* same `validatePush` calls, once per plane) and are answered with an
|
|
96
|
+
* ack; `presence` frames are fire-and-forget. Socket close or error
|
|
97
|
+
* tears the pump down and releases the store subscription; the stream
|
|
98
|
+
* ending closes the socket.
|
|
99
|
+
*/
|
|
100
|
+
declare function sessionSocket<D extends EventDefs>(session: Session<D>, socket: A2Socket, options?: SessionSocketOptions): void;
|
|
43
101
|
//#endregion
|
|
44
|
-
export { PushBody, deserializeError, errorResponse, parsePushBody, sseResponse };
|
|
102
|
+
export { A2Socket, PushBody, SessionSocketOptions, deserializeError, errorResponse, parsePushBody, schedulerHandler, sessionSocket, sseResponse };
|