experimental-a2 0.6.0 → 0.7.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 +22 -0
- package/dist/ai-server.d.ts +4 -5
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +8 -7
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +334 -2
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +1 -1
- package/dist/client.d.ts +202 -2
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1025 -1
- package/dist/client.js.map +1 -0
- package/dist/errors-BQuJpe82.js.map +1 -1
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/{internal-DstsI6Re.js → internal-DRXJ56EI.js} +5 -28
- package/dist/internal-DRXJ56EI.js.map +1 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/scheduler-qstash.d.ts +3 -3
- package/dist/scheduler-qstash.js +4 -5
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +4 -4
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-Duw6MVlB.js → server-286j79Mt.js} +708 -79
- package/dist/server-286j79Mt.js.map +1 -0
- package/dist/{server-DpvjhdoE.d.ts → server-DgXmORIq.d.ts} +67 -49
- package/dist/server-DgXmORIq.d.ts.map +1 -0
- package/dist/server.d.ts +3 -3
- package/dist/server.js +1 -1
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/{store-DysUkTH3.d.ts → store-flRz1OWh.d.ts} +2 -57
- package/dist/store-flRz1OWh.d.ts.map +1 -0
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +1 -59
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-dSeLxzfb.js → store-polling-6DW7F1DT.js} +2 -2
- package/dist/{store-polling-dSeLxzfb.js.map → store-polling-6DW7F1DT.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.js +1 -81
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-redis-core-BFLwz0Wj.js → store-redis-core-DEYO8Ryv.js} +48 -134
- package/dist/store-redis-core-DEYO8Ryv.js.map +1 -0
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +2 -3
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +3 -4
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +1 -72
- package/dist/store-sqlite.js.map +1 -1
- package/dist/{wire-BFQmSJ-9.js → wire-B6te_wns.js} +4 -3
- package/dist/wire-B6te_wns.js.map +1 -0
- package/docs/guides/03-react.mdx +20 -28
- package/docs/guides/05-production.mdx +9 -11
- package/docs/guides/06-ai-agents.mdx +8 -14
- package/docs/guides/09-presence.mdx +14 -19
- package/docs/guides/10-transports.mdx +104 -86
- package/docs/reference/01-api.mdx +143 -277
- package/docs/reference/02-errors.mdx +5 -7
- package/package.json +1 -14
- package/src/ai-server.ts +9 -5
- package/src/client.ts +2 -2
- package/src/errors.ts +1 -0
- package/src/internal.ts +3 -62
- package/src/push-envelope.ts +24 -21
- package/src/scheduler-qstash.ts +3 -3
- package/src/scheduler-vercel.ts +2 -2
- package/src/server-fetch.ts +344 -0
- package/src/server.ts +73 -225
- package/src/session-socket.ts +36 -20
- package/src/sse.ts +2 -2
- package/src/store-memory.ts +0 -81
- package/src/store-postgres.ts +0 -100
- package/src/store-redis-core.ts +47 -211
- package/src/store-redis-http.ts +0 -1
- package/src/store-redis.ts +0 -1
- package/src/store-sqlite.ts +0 -119
- package/src/store.ts +0 -60
- package/src/wire.ts +2 -1
- package/dist/ai-D_PGS-JR.d.ts +0 -334
- package/dist/ai-D_PGS-JR.d.ts.map +0 -1
- package/dist/cli-B3VuxoDe.js +0 -599
- package/dist/cli-B3VuxoDe.js.map +0 -1
- package/dist/cli-bin.d.ts +0 -1
- package/dist/cli-bin.js +0 -7
- package/dist/cli-bin.js.map +0 -1
- package/dist/cli.d.ts +0 -20
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -2
- package/dist/client-CdMqi7mC.d.ts +0 -202
- package/dist/client-CdMqi7mC.d.ts.map +0 -1
- package/dist/client-Dj5d3SP_.js +0 -1026
- package/dist/client-Dj5d3SP_.js.map +0 -1
- package/dist/devtools-J_jZ2vQf.d.ts +0 -152
- package/dist/devtools-J_jZ2vQf.d.ts.map +0 -1
- package/dist/devtools-kJJaORn-.js +0 -340
- package/dist/devtools-kJJaORn-.js.map +0 -1
- package/dist/devtools-server.browser.d.ts +0 -1
- package/dist/devtools-server.browser.js +0 -6
- package/dist/devtools-server.browser.js.map +0 -1
- package/dist/devtools-server.d.ts +0 -23
- package/dist/devtools-server.d.ts.map +0 -1
- package/dist/devtools-server.js +0 -1270
- package/dist/devtools-server.js.map +0 -1
- package/dist/devtools.d.ts +0 -2
- package/dist/devtools.js +0 -2
- package/dist/errors-W6nwJ-fm.d.ts +0 -21
- package/dist/errors-W6nwJ-fm.d.ts.map +0 -1
- package/dist/http.d.ts +0 -151
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js +0 -706
- package/dist/http.js.map +0 -1
- package/dist/inspection-DaxB5jM2.js +0 -13
- package/dist/inspection-DaxB5jM2.js.map +0 -1
- package/dist/internal-DstsI6Re.js.map +0 -1
- package/dist/platform-B4TnJtWu.js +0 -34
- package/dist/platform-B4TnJtWu.js.map +0 -1
- package/dist/server-DpvjhdoE.d.ts.map +0 -1
- package/dist/server-Duw6MVlB.js.map +0 -1
- package/dist/store-DysUkTH3.d.ts.map +0 -1
- package/dist/store-redis-core-BFLwz0Wj.js.map +0 -1
- package/dist/testing.browser.d.ts +0 -1
- package/dist/testing.browser.js +0 -6
- package/dist/testing.browser.js.map +0 -1
- package/dist/testing.d.ts +0 -32
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -103
- package/dist/testing.js.map +0 -1
- package/dist/wire-BFQmSJ-9.js.map +0 -1
- package/docs/guides/07-devtools.mdx +0 -229
- package/src/cli-bin.ts +0 -5
- package/src/cli.ts +0 -1046
- package/src/devtools-app.ts +0 -989
- package/src/devtools-server.browser.ts +0 -5
- package/src/devtools-server.ts +0 -604
- package/src/devtools.ts +0 -716
- package/src/http.ts +0 -394
- package/src/inspection.ts +0 -39
- package/src/testing.browser.ts +0 -5
- package/src/testing.ts +0 -185
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
//#region src/devtools.ts
|
|
2
|
-
/**
|
|
3
|
-
* Shared wire protocol for A2's read-only devtools surfaces.
|
|
4
|
-
*
|
|
5
|
-
* The HTTP dashboard and non-interactive consumers use the same response
|
|
6
|
-
* shapes. Captures are newline-delimited JSON so they can be streamed,
|
|
7
|
-
* inspected with ordinary command-line tools, and verified without access to
|
|
8
|
-
* an A2 server.
|
|
9
|
-
*/
|
|
10
|
-
const DEVTOOLS_PROTOCOL_VERSION = 1;
|
|
11
|
-
const DEVTOOLS_CAPTURE_VERSION = 1;
|
|
12
|
-
const DEVTOOLS_CAPTURE_MEDIA_TYPE = "application/x-ndjson";
|
|
13
|
-
const DEVTOOLS_SESSION_PAGE_LIMIT = 250;
|
|
14
|
-
const DEVTOOLS_SESSION_PAGE_MAX_LIMIT = 1e3;
|
|
15
|
-
const DEVTOOLS_CAPABILITIES = {
|
|
16
|
-
protocolVersion: 1,
|
|
17
|
-
resources: [
|
|
18
|
-
"contracts",
|
|
19
|
-
"sessions",
|
|
20
|
-
"session",
|
|
21
|
-
"watch",
|
|
22
|
-
"export"
|
|
23
|
-
],
|
|
24
|
-
sessionPagination: {
|
|
25
|
-
defaultLimit: 250,
|
|
26
|
-
maxLimit: DEVTOOLS_SESSION_PAGE_MAX_LIMIT
|
|
27
|
-
},
|
|
28
|
-
capture: {
|
|
29
|
-
version: 1,
|
|
30
|
-
mediaType: DEVTOOLS_CAPTURE_MEDIA_TYPE
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
const encoder = new TextEncoder();
|
|
34
|
-
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
35
|
-
const objectValue = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
36
|
-
var DevtoolsCaptureError = class extends TypeError {};
|
|
37
|
-
const captureError = (message) => new DevtoolsCaptureError(`invalid A2 devtools capture: ${message}`);
|
|
38
|
-
const sha256 = async (value) => {
|
|
39
|
-
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(value));
|
|
40
|
-
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
41
|
-
};
|
|
42
|
-
const timestampMs = (value) => value === null ? null : new Date(value).getTime();
|
|
43
|
-
/** Compute the server-compatible lifecycle invalidation token. */
|
|
44
|
-
const devtoolsSessionRevision = (detail) => sha256(JSON.stringify({
|
|
45
|
-
events: detail.events.map((event) => [
|
|
46
|
-
event.id,
|
|
47
|
-
event.index,
|
|
48
|
-
event.cause?.index ?? null,
|
|
49
|
-
event.cause?.attempt ?? null,
|
|
50
|
-
timestampMs(event.processedAt),
|
|
51
|
-
event.processedByAttempt,
|
|
52
|
-
timestampMs(event.firstClaimedAt),
|
|
53
|
-
timestampMs(event.lastClaimedAt),
|
|
54
|
-
event.attemptCount,
|
|
55
|
-
event.failureCount,
|
|
56
|
-
timestampMs(event.lastFailedAt),
|
|
57
|
-
event.lastFailedAttempt,
|
|
58
|
-
event.lastError,
|
|
59
|
-
timestampMs(event.failedAt)
|
|
60
|
-
]),
|
|
61
|
-
snapshots: detail.snapshots.map((snapshot) => [
|
|
62
|
-
snapshot.reducerName,
|
|
63
|
-
snapshot.index,
|
|
64
|
-
snapshot.updatedAt === null ? 0 : new Date(snapshot.updatedAt).getTime()
|
|
65
|
-
])
|
|
66
|
-
}));
|
|
67
|
-
const encodeRecords = (records) => records.map((record) => JSON.stringify(record)).join("\n") + "\n";
|
|
68
|
-
const asCapturedAt = (value) => {
|
|
69
|
-
if (value instanceof Date) {
|
|
70
|
-
if (Number.isNaN(value.getTime())) throw new TypeError("capturedAt must be a canonical ISO date");
|
|
71
|
-
return value.toISOString();
|
|
72
|
-
}
|
|
73
|
-
if (value === void 0) return (/* @__PURE__ */ new Date()).toISOString();
|
|
74
|
-
const parsed = new Date(value);
|
|
75
|
-
if (Number.isNaN(parsed.getTime()) || parsed.toISOString() !== value) throw new TypeError("capturedAt must be a canonical ISO date string or Date");
|
|
76
|
-
return value;
|
|
77
|
-
};
|
|
78
|
-
async function encodeDevtoolsCapture(detail, options = {}) {
|
|
79
|
-
const manifest = {
|
|
80
|
-
kind: "a2.capture",
|
|
81
|
-
captureVersion: 1,
|
|
82
|
-
protocolVersion: 1,
|
|
83
|
-
contract: detail.contract,
|
|
84
|
-
sessionId: detail.sessionId,
|
|
85
|
-
revision: detail.revision,
|
|
86
|
-
capturedAt: asCapturedAt(options.capturedAt),
|
|
87
|
-
fidelity: "exact"
|
|
88
|
-
};
|
|
89
|
-
const events = detail.events.map((event) => ({
|
|
90
|
-
...event,
|
|
91
|
-
kind: "event"
|
|
92
|
-
}));
|
|
93
|
-
const snapshots = detail.snapshots.map((snapshot) => ({
|
|
94
|
-
...snapshot,
|
|
95
|
-
kind: "snapshot"
|
|
96
|
-
}));
|
|
97
|
-
const signedRecords = [
|
|
98
|
-
manifest,
|
|
99
|
-
...events,
|
|
100
|
-
...snapshots
|
|
101
|
-
];
|
|
102
|
-
validateCanonicalJsonValue(signedRecords, "capture");
|
|
103
|
-
const signed = encodeRecords(signedRecords);
|
|
104
|
-
const footer = {
|
|
105
|
-
kind: "end",
|
|
106
|
-
captureVersion: 1,
|
|
107
|
-
eventCount: events.length,
|
|
108
|
-
snapshotCount: snapshots.length,
|
|
109
|
-
maxIndex: events.at(-1)?.index ?? null,
|
|
110
|
-
sha256: `sha256:${await sha256(signed)}`
|
|
111
|
-
};
|
|
112
|
-
return `${signed}${JSON.stringify(footer)}\n`;
|
|
113
|
-
}
|
|
114
|
-
const recordsFromText = (text) => {
|
|
115
|
-
if (text.length === 0) throw captureError("capture is empty");
|
|
116
|
-
const lines = text.split("\n");
|
|
117
|
-
if (lines.pop() !== "") throw captureError("capture must end with a newline");
|
|
118
|
-
const records = lines.map((line, index) => {
|
|
119
|
-
if (line.length === 0) throw captureError(`record ${index + 1} is empty`);
|
|
120
|
-
try {
|
|
121
|
-
return JSON.parse(line);
|
|
122
|
-
} catch {
|
|
123
|
-
throw captureError(`record ${index + 1} is not valid JSON`);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
if (encodeRecords(records) !== text) throw captureError("capture must use canonical UTF-8 NDJSON encoding");
|
|
127
|
-
return records;
|
|
128
|
-
};
|
|
129
|
-
const recordsFromValues = (records) => {
|
|
130
|
-
try {
|
|
131
|
-
validateCanonicalJsonValue(records, "capture.records");
|
|
132
|
-
return recordsFromText(encodeRecords(records));
|
|
133
|
-
} catch (error) {
|
|
134
|
-
if (error instanceof DevtoolsCaptureError) throw error;
|
|
135
|
-
throw captureError("capture records are not JSON-serializable");
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
const captureRecordsFrom = (value) => {
|
|
139
|
-
if (typeof value === "string") return recordsFromText(value);
|
|
140
|
-
if (value instanceof Uint8Array) try {
|
|
141
|
-
return recordsFromText(decoder.decode(value));
|
|
142
|
-
} catch (error) {
|
|
143
|
-
if (error instanceof DevtoolsCaptureError) throw error;
|
|
144
|
-
throw captureError("capture is not valid UTF-8");
|
|
145
|
-
}
|
|
146
|
-
if (Array.isArray(value)) return recordsFromValues(value);
|
|
147
|
-
const capture = objectValue(value);
|
|
148
|
-
if (capture) {
|
|
149
|
-
if (Array.isArray(capture["records"])) return recordsFromValues(capture["records"]);
|
|
150
|
-
const manifest = capture["manifest"];
|
|
151
|
-
const events = capture["events"];
|
|
152
|
-
const snapshots = capture["snapshots"];
|
|
153
|
-
const footer = capture["footer"];
|
|
154
|
-
if (Array.isArray(events) && Array.isArray(snapshots)) return recordsFromValues([
|
|
155
|
-
manifest,
|
|
156
|
-
...events,
|
|
157
|
-
...snapshots,
|
|
158
|
-
footer
|
|
159
|
-
]);
|
|
160
|
-
}
|
|
161
|
-
throw captureError("expected NDJSON text, bytes, records, or a capture");
|
|
162
|
-
};
|
|
163
|
-
const stringField = (value, field, record) => {
|
|
164
|
-
const candidate = value[field];
|
|
165
|
-
if (typeof candidate !== "string" || candidate.length === 0) throw captureError(`${record}.${field} must be a non-empty string`);
|
|
166
|
-
return candidate;
|
|
167
|
-
};
|
|
168
|
-
const integerField = (value, field, record) => {
|
|
169
|
-
const candidate = value[field];
|
|
170
|
-
if (!Number.isSafeInteger(candidate) || candidate < 0) throw captureError(`${record}.${field} must be a non-negative integer`);
|
|
171
|
-
return candidate;
|
|
172
|
-
};
|
|
173
|
-
const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
|
|
174
|
-
const nonNegativeInteger = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
175
|
-
const canonicalDate = (value) => {
|
|
176
|
-
if (typeof value !== "string") return false;
|
|
177
|
-
const parsed = new Date(value);
|
|
178
|
-
return !Number.isNaN(parsed.getTime()) && parsed.toISOString() === value;
|
|
179
|
-
};
|
|
180
|
-
const optionalDate = (value) => value === null || canonicalDate(value);
|
|
181
|
-
const nullableString = (value) => value === null || typeof value === "string";
|
|
182
|
-
const nullablePositiveInteger = (value) => value === null || positiveInteger(value);
|
|
183
|
-
const validateDate = (value, field, record, nullable) => {
|
|
184
|
-
if (nullable ? optionalDate(value) : canonicalDate(value)) return;
|
|
185
|
-
throw captureError(`${record}.${field} has an invalid timestamp; expected a canonical ISO date${nullable ? " or null" : ""}`);
|
|
186
|
-
};
|
|
187
|
-
const validateCause = (value, record) => {
|
|
188
|
-
if (value === null) return;
|
|
189
|
-
const cause = objectValue(value);
|
|
190
|
-
if (cause === null || !positiveInteger(cause["index"]) || !positiveInteger(cause["attempt"]) || cause["batchSize"] !== void 0 && !positiveInteger(cause["batchSize"])) throw captureError(`${record}.cause is invalid`);
|
|
191
|
-
};
|
|
192
|
-
const validateEventRecord = (record, label) => {
|
|
193
|
-
stringField(record, "id", label);
|
|
194
|
-
stringField(record, "type", label);
|
|
195
|
-
stringField(record, "sessionId", label);
|
|
196
|
-
if (!Object.hasOwn(record, "payload")) throw captureError(`${label}.payload is required`);
|
|
197
|
-
if (!positiveInteger(record["index"])) throw captureError(`${label}.index must be a positive integer`);
|
|
198
|
-
validateDate(record["createdAt"], "createdAt", label, false);
|
|
199
|
-
validateCause(record["cause"], label);
|
|
200
|
-
if (!nullableString(record["lane"])) throw captureError(`${label}.lane must be a string or null`);
|
|
201
|
-
validateDate(record["processedAt"], "processedAt", label, true);
|
|
202
|
-
if (!nullablePositiveInteger(record["processedByAttempt"])) throw captureError(`${label}.processedByAttempt must be a positive integer or null`);
|
|
203
|
-
validateDate(record["firstClaimedAt"], "firstClaimedAt", label, true);
|
|
204
|
-
validateDate(record["lastClaimedAt"], "lastClaimedAt", label, true);
|
|
205
|
-
if (!nonNegativeInteger(record["attemptCount"])) throw captureError(`${label}.attemptCount must be a non-negative integer`);
|
|
206
|
-
if (!nullableString(record["claimHolder"])) throw captureError(`${label}.claimHolder must be a string or null`);
|
|
207
|
-
validateDate(record["claimExpiresAt"], "claimExpiresAt", label, true);
|
|
208
|
-
if (!nonNegativeInteger(record["failureCount"])) throw captureError(`${label}.failureCount must be a non-negative integer`);
|
|
209
|
-
validateDate(record["lastFailedAt"], "lastFailedAt", label, true);
|
|
210
|
-
if (!nullablePositiveInteger(record["lastFailedAttempt"])) throw captureError(`${label}.lastFailedAttempt must be a positive integer or null`);
|
|
211
|
-
if (!nullableString(record["lastError"])) throw captureError(`${label}.lastError must be a string or null`);
|
|
212
|
-
validateDate(record["failedAt"], "failedAt", label, true);
|
|
213
|
-
const returned = record["returnedEventIds"];
|
|
214
|
-
if (returned !== null && (!Array.isArray(returned) || returned.some((id) => typeof id !== "string" || id.length === 0))) throw captureError(`${label}.returnedEventIds is invalid`);
|
|
215
|
-
};
|
|
216
|
-
const validateSnapshotRecord = (record, label) => {
|
|
217
|
-
stringField(record, "reducerName", label);
|
|
218
|
-
if (!nonNegativeInteger(record["index"])) throw captureError(`${label}.index must be a non-negative integer`);
|
|
219
|
-
validateDate(record["updatedAt"], "updatedAt", label, true);
|
|
220
|
-
};
|
|
221
|
-
function validateCanonicalJsonValue(value, path, ancestors = /* @__PURE__ */ new WeakSet()) {
|
|
222
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
223
|
-
if (typeof value === "number") {
|
|
224
|
-
if (!Number.isFinite(value) || Object.is(value, -0)) throw captureError(`${path} is not a canonical JSON number`);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
if (Array.isArray(value)) {
|
|
228
|
-
if (ancestors.has(value)) throw captureError(`${path} is not a canonical JSON tree`);
|
|
229
|
-
ancestors.add(value);
|
|
230
|
-
try {
|
|
231
|
-
const keys = Object.keys(value);
|
|
232
|
-
const ownKeys = Reflect.ownKeys(value);
|
|
233
|
-
if (keys.length !== value.length || ownKeys.length !== keys.length + 1 || !ownKeys.includes("length") || keys.some((key, index) => key !== String(index))) throw captureError(`${path} is not a canonical JSON array`);
|
|
234
|
-
value.forEach((entry, index) => validateCanonicalJsonValue(entry, `${path}[${index}]`, ancestors));
|
|
235
|
-
} finally {
|
|
236
|
-
ancestors.delete(value);
|
|
237
|
-
}
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
const object = objectValue(value);
|
|
241
|
-
if (object === null || Object.getPrototypeOf(object) !== Object.prototype && Object.getPrototypeOf(object) !== null) throw captureError(`${path} is not a canonical JSON value`);
|
|
242
|
-
if (ancestors.has(object)) throw captureError(`${path} is not a canonical JSON tree`);
|
|
243
|
-
ancestors.add(object);
|
|
244
|
-
try {
|
|
245
|
-
const keys = Object.keys(object);
|
|
246
|
-
if (Reflect.ownKeys(object).length !== keys.length || keys.some((key) => !Object.prototype.propertyIsEnumerable.call(object, key))) throw captureError(`${path} is not a canonical JSON object`);
|
|
247
|
-
for (const key of keys) validateCanonicalJsonValue(object[key], `${path}.${key}`, ancestors);
|
|
248
|
-
} finally {
|
|
249
|
-
ancestors.delete(object);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
function parseDevtoolsCapture(value) {
|
|
253
|
-
const records = captureRecordsFrom(value);
|
|
254
|
-
if (records.length < 2) throw captureError("capture needs a manifest and footer");
|
|
255
|
-
const manifestValue = objectValue(records[0]);
|
|
256
|
-
if (manifestValue?.["kind"] !== "a2.capture") throw captureError("first record must have kind 'a2.capture'");
|
|
257
|
-
if (manifestValue["captureVersion"] !== 1) throw captureError(`unsupported capture version '${String(manifestValue["captureVersion"])}'`);
|
|
258
|
-
if (manifestValue["protocolVersion"] !== 1) throw captureError(`unsupported protocol version '${String(manifestValue["protocolVersion"])}'`);
|
|
259
|
-
if (manifestValue["fidelity"] !== "exact") throw captureError("manifest.fidelity must be 'exact'");
|
|
260
|
-
const manifest = manifestValue;
|
|
261
|
-
const contract = stringField(manifestValue, "contract", "manifest");
|
|
262
|
-
const sessionId = stringField(manifestValue, "sessionId", "manifest");
|
|
263
|
-
const revision = stringField(manifestValue, "revision", "manifest");
|
|
264
|
-
const capturedAt = stringField(manifestValue, "capturedAt", "manifest");
|
|
265
|
-
if (!canonicalDate(capturedAt)) throw captureError("manifest.capturedAt must be a canonical ISO date");
|
|
266
|
-
const footerValue = objectValue(records.at(-1));
|
|
267
|
-
if (footerValue?.["kind"] !== "end") throw captureError("last record must have kind 'end'");
|
|
268
|
-
if (footerValue["captureVersion"] !== 1) throw captureError("footer capture version does not match");
|
|
269
|
-
const footer = footerValue;
|
|
270
|
-
integerField(footerValue, "eventCount", "footer");
|
|
271
|
-
integerField(footerValue, "snapshotCount", "footer");
|
|
272
|
-
stringField(footerValue, "sha256", "footer");
|
|
273
|
-
if (footerValue["maxIndex"] !== null && (!Number.isSafeInteger(footerValue["maxIndex"]) || footerValue["maxIndex"] < 1)) throw captureError("footer.maxIndex must be null or a positive integer");
|
|
274
|
-
const events = [];
|
|
275
|
-
const snapshots = [];
|
|
276
|
-
let sawSnapshot = false;
|
|
277
|
-
for (let index = 1; index < records.length - 1; index += 1) {
|
|
278
|
-
const record = objectValue(records[index]);
|
|
279
|
-
if (record?.["kind"] === "event") {
|
|
280
|
-
if (sawSnapshot) throw captureError("event records must precede snapshot records");
|
|
281
|
-
validateEventRecord(record, `record ${index + 1}`);
|
|
282
|
-
events.push(record);
|
|
283
|
-
} else if (record?.["kind"] === "snapshot") {
|
|
284
|
-
sawSnapshot = true;
|
|
285
|
-
validateSnapshotRecord(record, `record ${index + 1}`);
|
|
286
|
-
snapshots.push(record);
|
|
287
|
-
} else throw captureError(`record ${index + 1} has an unknown kind`);
|
|
288
|
-
}
|
|
289
|
-
return {
|
|
290
|
-
contract,
|
|
291
|
-
sessionId,
|
|
292
|
-
revision,
|
|
293
|
-
capturedAt,
|
|
294
|
-
manifest,
|
|
295
|
-
events,
|
|
296
|
-
snapshots,
|
|
297
|
-
footer,
|
|
298
|
-
records
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
async function verifyDevtoolsCapture(capture) {
|
|
302
|
-
validateCanonicalJsonValue(capture.records, "capture.records");
|
|
303
|
-
parseDevtoolsCapture(capture.records);
|
|
304
|
-
const expectedRecords = [
|
|
305
|
-
capture.manifest,
|
|
306
|
-
...capture.events,
|
|
307
|
-
...capture.snapshots,
|
|
308
|
-
capture.footer
|
|
309
|
-
];
|
|
310
|
-
if (capture.records.length !== expectedRecords.length || capture.records.some((record, index) => record !== expectedRecords[index])) throw captureError("record sequence does not match the capture");
|
|
311
|
-
if (capture.contract !== capture.manifest.contract || capture.sessionId !== capture.manifest.sessionId || capture.revision !== capture.manifest.revision || capture.capturedAt !== capture.manifest.capturedAt) throw captureError("capture identity does not match its manifest");
|
|
312
|
-
if (!canonicalDate(capture.capturedAt)) throw captureError("manifest.capturedAt must be a canonical ISO date");
|
|
313
|
-
if (capture.footer.eventCount !== capture.events.length) throw captureError("footer event count does not match");
|
|
314
|
-
if (capture.footer.snapshotCount !== capture.snapshots.length) throw captureError("footer snapshot count does not match");
|
|
315
|
-
let previousIndex = 0;
|
|
316
|
-
const ids = /* @__PURE__ */ new Set();
|
|
317
|
-
for (const event of capture.events) {
|
|
318
|
-
if (event.kind !== "event") throw captureError("invalid event record kind");
|
|
319
|
-
validateEventRecord(event, `event #${event.index}`);
|
|
320
|
-
if (!Number.isSafeInteger(event.index) || event.index !== previousIndex + 1) throw captureError("event indexes must be contiguous from 1");
|
|
321
|
-
previousIndex = event.index;
|
|
322
|
-
if (event.sessionId !== capture.sessionId) throw captureError(`event #${event.index} belongs to another session`);
|
|
323
|
-
if (ids.has(event.id)) throw captureError(`event #${event.index} repeats id '${event.id}'`);
|
|
324
|
-
ids.add(event.id);
|
|
325
|
-
}
|
|
326
|
-
for (const snapshot of capture.snapshots) {
|
|
327
|
-
if (snapshot.kind !== "snapshot") throw captureError("invalid snapshot record kind");
|
|
328
|
-
validateSnapshotRecord(snapshot, `snapshot '${snapshot.reducerName}'`);
|
|
329
|
-
if (!Number.isSafeInteger(snapshot.index) || snapshot.index > previousIndex) throw captureError(`snapshot '${snapshot.reducerName}' is invalid`);
|
|
330
|
-
}
|
|
331
|
-
const maxIndex = capture.events.at(-1)?.index ?? null;
|
|
332
|
-
if (capture.footer.maxIndex !== maxIndex) throw captureError("footer max index does not match");
|
|
333
|
-
if (!/^sha256:[\da-f]{64}$/.test(capture.footer.sha256)) throw captureError("footer.sha256 is malformed");
|
|
334
|
-
const expected = `sha256:${await sha256(encodeRecords(capture.records.slice(0, -1)))}`;
|
|
335
|
-
if (capture.footer.sha256 !== expected) throw captureError("capture digest does not match");
|
|
336
|
-
}
|
|
337
|
-
//#endregion
|
|
338
|
-
export { DEVTOOLS_SESSION_PAGE_LIMIT as a, encodeDevtoolsCapture as c, DEVTOOLS_PROTOCOL_VERSION as i, parseDevtoolsCapture as l, DEVTOOLS_CAPTURE_MEDIA_TYPE as n, DEVTOOLS_SESSION_PAGE_MAX_LIMIT as o, DEVTOOLS_CAPTURE_VERSION as r, devtoolsSessionRevision as s, DEVTOOLS_CAPABILITIES as t, verifyDevtoolsCapture as u };
|
|
339
|
-
|
|
340
|
-
//# sourceMappingURL=devtools-kJJaORn-.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"devtools-kJJaORn-.js","names":[],"sources":["../src/devtools.ts"],"sourcesContent":["/**\n * Shared wire protocol for A2's read-only devtools surfaces.\n *\n * The HTTP dashboard and non-interactive consumers use the same response\n * shapes. Captures are newline-delimited JSON so they can be streamed,\n * inspected with ordinary command-line tools, and verified without access to\n * an A2 server.\n */\n\nexport const DEVTOOLS_PROTOCOL_VERSION = 1 as const\nexport const DEVTOOLS_CAPTURE_VERSION = 1 as const\nexport const DEVTOOLS_CAPTURE_MEDIA_TYPE = 'application/x-ndjson' as const\nexport const DEVTOOLS_SESSION_PAGE_LIMIT = 250 as const\nexport const DEVTOOLS_SESSION_PAGE_MAX_LIMIT = 1_000 as const\n\nexport type DevtoolsWireSessionSummary = {\n sessionId: string\n eventCount: number\n pendingCount: number\n failedCount: number\n attemptCount: number\n failureCount: number\n firstEventAt: string\n updatedAt: string\n}\n\nexport type DevtoolsWireEvent = {\n id: string\n type: string\n payload: unknown\n index: number\n sessionId: string\n createdAt: string\n cause: { index: number; attempt: number; batchSize?: number } | null\n lane: string | null\n processedAt: string | null\n processedByAttempt: number | null\n returnedEventIds: string[] | null\n firstClaimedAt: string | null\n lastClaimedAt: string | null\n attemptCount: number\n claimHolder: string | null\n claimExpiresAt: string | null\n failureCount: number\n lastFailedAt: string | null\n lastFailedAttempt: number | null\n lastError: string | null\n failedAt: string | null\n}\n\nexport type DevtoolsWireSnapshot = {\n reducerName: string\n index: number\n updatedAt: string | null\n}\n\nexport type DevtoolsContractsResponse = {\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n contracts: Array<{ name: string }>\n}\n\nexport type DevtoolsSessionsResponse = {\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n cursor: string | null\n sessions: DevtoolsWireSessionSummary[]\n}\n\nexport type DevtoolsSessionDetail = {\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n contract: string\n sessionId: string\n /** An invalidation token, not a content-integrity digest. */\n revision: string\n events: DevtoolsWireEvent[]\n snapshots: DevtoolsWireSnapshot[]\n}\n\nexport type DevtoolsSessionPage = {\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n contract: string\n sessionId: string\n /** Inclusive log frontier frozen by the first page. */\n throughIndex: number\n /** Opaque continuation token; null means this frozen read is complete. */\n cursor: string | null\n events: DevtoolsWireEvent[]\n /** Snapshot metadata is present only on the first page. */\n snapshots: DevtoolsWireSnapshot[]\n}\n\nexport type DevtoolsCapabilities = {\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n resources: Array<'contracts' | 'sessions' | 'session' | 'watch' | 'export'>\n sessionPagination: {\n defaultLimit: typeof DEVTOOLS_SESSION_PAGE_LIMIT\n maxLimit: typeof DEVTOOLS_SESSION_PAGE_MAX_LIMIT\n }\n capture: {\n version: typeof DEVTOOLS_CAPTURE_VERSION\n mediaType: typeof DEVTOOLS_CAPTURE_MEDIA_TYPE\n }\n}\n\nexport const DEVTOOLS_CAPABILITIES: DevtoolsCapabilities = {\n protocolVersion: DEVTOOLS_PROTOCOL_VERSION,\n resources: ['contracts', 'sessions', 'session', 'watch', 'export'],\n sessionPagination: {\n defaultLimit: DEVTOOLS_SESSION_PAGE_LIMIT,\n maxLimit: DEVTOOLS_SESSION_PAGE_MAX_LIMIT,\n },\n capture: {\n version: DEVTOOLS_CAPTURE_VERSION,\n mediaType: DEVTOOLS_CAPTURE_MEDIA_TYPE,\n },\n}\n\nexport type DevtoolsCaptureManifest = {\n kind: 'a2.capture'\n captureVersion: typeof DEVTOOLS_CAPTURE_VERSION\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n contract: string\n sessionId: string\n revision: string\n capturedAt: string\n fidelity: 'exact'\n}\n\n/** One exact durable event, with the record discriminator added. */\nexport type DevtoolsCaptureEvent = DevtoolsWireEvent & { kind: 'event' }\n\n/** Snapshot metadata only. Cached reducer state is never exported. */\nexport type DevtoolsCaptureSnapshot = DevtoolsWireSnapshot & {\n kind: 'snapshot'\n}\n\nexport type DevtoolsCaptureFooter = {\n kind: 'end'\n captureVersion: typeof DEVTOOLS_CAPTURE_VERSION\n eventCount: number\n snapshotCount: number\n maxIndex: number | null\n sha256: string\n}\n\nexport type DevtoolsCaptureRecord =\n | DevtoolsCaptureManifest\n | DevtoolsCaptureEvent\n | DevtoolsCaptureSnapshot\n | DevtoolsCaptureFooter\n\nexport type DevtoolsCapture = {\n contract: string\n sessionId: string\n revision: string\n capturedAt: string\n manifest: DevtoolsCaptureManifest\n events: DevtoolsCaptureEvent[]\n snapshots: DevtoolsCaptureSnapshot[]\n footer: DevtoolsCaptureFooter\n records: DevtoolsCaptureRecord[]\n}\n\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder('utf-8', { fatal: true })\n\nconst objectValue = (value: unknown): Record<string, unknown> | null =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null\n\nclass DevtoolsCaptureError extends TypeError {}\n\nconst captureError = (message: string): DevtoolsCaptureError =>\n new DevtoolsCaptureError(`invalid A2 devtools capture: ${message}`)\n\nconst sha256 = async (value: string): Promise<string> => {\n const digest = await crypto.subtle.digest('SHA-256', encoder.encode(value))\n return [...new Uint8Array(digest)]\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('')\n}\n\nconst timestampMs = (value: string | null): number | null =>\n value === null ? null : new Date(value).getTime()\n\n/** Compute the server-compatible lifecycle invalidation token. */\nexport const devtoolsSessionRevision = (detail: {\n events: readonly DevtoolsWireEvent[]\n snapshots: readonly DevtoolsWireSnapshot[]\n}): Promise<string> =>\n sha256(\n 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 timestampMs(event.processedAt),\n event.processedByAttempt,\n timestampMs(event.firstClaimedAt),\n timestampMs(event.lastClaimedAt),\n event.attemptCount,\n event.failureCount,\n timestampMs(event.lastFailedAt),\n event.lastFailedAttempt,\n event.lastError,\n timestampMs(event.failedAt),\n ]),\n snapshots: detail.snapshots.map((snapshot) => [\n snapshot.reducerName,\n snapshot.index,\n snapshot.updatedAt === null\n ? 0\n : new Date(snapshot.updatedAt).getTime(),\n ]),\n }),\n )\n\nconst encodeRecords = (records: readonly unknown[]): string =>\n records.map((record) => JSON.stringify(record)).join('\\n') + '\\n'\n\nconst asCapturedAt = (value: string | Date | undefined): string => {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new TypeError('capturedAt must be a canonical ISO date')\n }\n return value.toISOString()\n }\n if (value === undefined) return new Date().toISOString()\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime()) || parsed.toISOString() !== value) {\n throw new TypeError(\n 'capturedAt must be a canonical ISO date string or Date',\n )\n }\n return value\n}\n\nexport async function encodeDevtoolsCapture(\n detail: DevtoolsSessionDetail,\n options: { capturedAt?: string | Date } = {},\n): Promise<string> {\n const manifest: DevtoolsCaptureManifest = {\n kind: 'a2.capture',\n captureVersion: DEVTOOLS_CAPTURE_VERSION,\n protocolVersion: DEVTOOLS_PROTOCOL_VERSION,\n contract: detail.contract,\n sessionId: detail.sessionId,\n revision: detail.revision,\n capturedAt: asCapturedAt(options.capturedAt),\n fidelity: 'exact',\n }\n const events = detail.events.map((event): DevtoolsCaptureEvent => ({\n ...event,\n kind: 'event',\n }))\n const snapshots = detail.snapshots.map(\n (snapshot): DevtoolsCaptureSnapshot => ({\n ...snapshot,\n kind: 'snapshot',\n }),\n )\n const signedRecords = [manifest, ...events, ...snapshots]\n validateCanonicalJsonValue(signedRecords, 'capture')\n const signed = encodeRecords(signedRecords)\n const footer: DevtoolsCaptureFooter = {\n kind: 'end',\n captureVersion: DEVTOOLS_CAPTURE_VERSION,\n eventCount: events.length,\n snapshotCount: snapshots.length,\n maxIndex: events.at(-1)?.index ?? null,\n sha256: `sha256:${await sha256(signed)}`,\n }\n return `${signed}${JSON.stringify(footer)}\\n`\n}\n\nconst recordsFromText = (text: string): unknown[] => {\n if (text.length === 0) throw captureError('capture is empty')\n const lines = text.split('\\n')\n if (lines.pop() !== '') {\n throw captureError('capture must end with a newline')\n }\n const records = lines.map((line, index) => {\n if (line.length === 0) {\n throw captureError(`record ${index + 1} is empty`)\n }\n try {\n return JSON.parse(line) as unknown\n } catch {\n throw captureError(`record ${index + 1} is not valid JSON`)\n }\n })\n if (encodeRecords(records) !== text) {\n throw captureError('capture must use canonical UTF-8 NDJSON encoding')\n }\n return records\n}\n\nconst recordsFromValues = (records: readonly unknown[]): unknown[] => {\n try {\n validateCanonicalJsonValue(records, 'capture.records')\n return recordsFromText(encodeRecords(records))\n } catch (error) {\n if (error instanceof DevtoolsCaptureError) throw error\n throw captureError('capture records are not JSON-serializable')\n }\n}\n\nconst captureRecordsFrom = (value: unknown): unknown[] => {\n if (typeof value === 'string') return recordsFromText(value)\n if (value instanceof Uint8Array) {\n try {\n return recordsFromText(decoder.decode(value))\n } catch (error) {\n if (error instanceof DevtoolsCaptureError) throw error\n throw captureError('capture is not valid UTF-8')\n }\n }\n if (Array.isArray(value)) return recordsFromValues(value)\n const capture = objectValue(value)\n if (capture) {\n if (Array.isArray(capture['records'])) {\n return recordsFromValues(capture['records'])\n }\n const manifest = capture['manifest']\n const events = capture['events']\n const snapshots = capture['snapshots']\n const footer = capture['footer']\n if (Array.isArray(events) && Array.isArray(snapshots)) {\n return recordsFromValues([manifest, ...events, ...snapshots, footer])\n }\n }\n throw captureError('expected NDJSON text, bytes, records, or a capture')\n}\n\nconst stringField = (\n value: Record<string, unknown>,\n field: string,\n record: string,\n): string => {\n const candidate = value[field]\n if (typeof candidate !== 'string' || candidate.length === 0) {\n throw captureError(`${record}.${field} must be a non-empty string`)\n }\n return candidate\n}\n\nconst integerField = (\n value: Record<string, unknown>,\n field: string,\n record: string,\n): number => {\n const candidate = value[field]\n if (!Number.isSafeInteger(candidate) || (candidate as number) < 0) {\n throw captureError(`${record}.${field} must be a non-negative integer`)\n }\n return candidate as number\n}\n\nconst positiveInteger = (value: unknown): value is number =>\n Number.isSafeInteger(value) && (value as number) > 0\n\nconst nonNegativeInteger = (value: unknown): value is number =>\n Number.isSafeInteger(value) && (value as number) >= 0\n\nconst canonicalDate = (value: unknown): value is string => {\n if (typeof value !== 'string') return false\n const parsed = new Date(value)\n return !Number.isNaN(parsed.getTime()) && parsed.toISOString() === value\n}\n\nconst optionalDate = (value: unknown): value is string | null =>\n value === null || canonicalDate(value)\n\nconst nullableString = (value: unknown): value is string | null =>\n value === null || typeof value === 'string'\n\nconst nullablePositiveInteger = (value: unknown): value is number | null =>\n value === null || positiveInteger(value)\n\nconst validateDate = (\n value: unknown,\n field: string,\n record: string,\n nullable: boolean,\n): void => {\n if (nullable ? optionalDate(value) : canonicalDate(value)) return\n throw captureError(\n `${record}.${field} has an invalid timestamp; expected a canonical ISO date${nullable ? ' or null' : ''}`,\n )\n}\n\nconst validateCause = (value: unknown, record: string): void => {\n if (value === null) return\n const cause = objectValue(value)\n if (\n cause === null ||\n !positiveInteger(cause['index']) ||\n !positiveInteger(cause['attempt']) ||\n (cause['batchSize'] !== undefined && !positiveInteger(cause['batchSize']))\n ) {\n throw captureError(`${record}.cause is invalid`)\n }\n}\n\nconst validateEventRecord = (\n record: Record<string, unknown>,\n label: string,\n): void => {\n stringField(record, 'id', label)\n stringField(record, 'type', label)\n stringField(record, 'sessionId', label)\n if (!Object.hasOwn(record, 'payload')) {\n throw captureError(`${label}.payload is required`)\n }\n if (!positiveInteger(record['index'])) {\n throw captureError(`${label}.index must be a positive integer`)\n }\n validateDate(record['createdAt'], 'createdAt', label, false)\n validateCause(record['cause'], label)\n if (!nullableString(record['lane'])) {\n throw captureError(`${label}.lane must be a string or null`)\n }\n validateDate(record['processedAt'], 'processedAt', label, true)\n if (!nullablePositiveInteger(record['processedByAttempt'])) {\n throw captureError(\n `${label}.processedByAttempt must be a positive integer or null`,\n )\n }\n validateDate(record['firstClaimedAt'], 'firstClaimedAt', label, true)\n validateDate(record['lastClaimedAt'], 'lastClaimedAt', label, true)\n if (!nonNegativeInteger(record['attemptCount'])) {\n throw captureError(`${label}.attemptCount must be a non-negative integer`)\n }\n if (!nullableString(record['claimHolder'])) {\n throw captureError(`${label}.claimHolder must be a string or null`)\n }\n validateDate(record['claimExpiresAt'], 'claimExpiresAt', label, true)\n if (!nonNegativeInteger(record['failureCount'])) {\n throw captureError(`${label}.failureCount must be a non-negative integer`)\n }\n validateDate(record['lastFailedAt'], 'lastFailedAt', label, true)\n if (!nullablePositiveInteger(record['lastFailedAttempt'])) {\n throw captureError(\n `${label}.lastFailedAttempt must be a positive integer or null`,\n )\n }\n if (!nullableString(record['lastError'])) {\n throw captureError(`${label}.lastError must be a string or null`)\n }\n validateDate(record['failedAt'], 'failedAt', label, true)\n const returned = record['returnedEventIds']\n if (\n returned !== null &&\n (!Array.isArray(returned) ||\n returned.some((id) => typeof id !== 'string' || id.length === 0))\n ) {\n throw captureError(`${label}.returnedEventIds is invalid`)\n }\n}\n\nconst validateSnapshotRecord = (\n record: Record<string, unknown>,\n label: string,\n): void => {\n stringField(record, 'reducerName', label)\n if (!nonNegativeInteger(record['index'])) {\n throw captureError(`${label}.index must be a non-negative integer`)\n }\n validateDate(record['updatedAt'], 'updatedAt', label, true)\n}\n\nfunction validateCanonicalJsonValue(\n value: unknown,\n path: string,\n ancestors = new WeakSet<object>(),\n): void {\n if (\n value === null ||\n typeof value === 'string' ||\n typeof value === 'boolean'\n ) {\n return\n }\n if (typeof value === 'number') {\n if (!Number.isFinite(value) || Object.is(value, -0)) {\n throw captureError(`${path} is not a canonical JSON number`)\n }\n return\n }\n if (Array.isArray(value)) {\n if (ancestors.has(value)) {\n throw captureError(`${path} is not a canonical JSON tree`)\n }\n ancestors.add(value)\n try {\n const keys = Object.keys(value)\n const ownKeys = Reflect.ownKeys(value)\n if (\n keys.length !== value.length ||\n ownKeys.length !== keys.length + 1 ||\n !ownKeys.includes('length') ||\n keys.some((key, index) => key !== String(index))\n ) {\n throw captureError(`${path} is not a canonical JSON array`)\n }\n value.forEach((entry, index) =>\n validateCanonicalJsonValue(entry, `${path}[${index}]`, ancestors),\n )\n } finally {\n ancestors.delete(value)\n }\n return\n }\n const object = objectValue(value)\n if (\n object === null ||\n (Object.getPrototypeOf(object) !== Object.prototype &&\n Object.getPrototypeOf(object) !== null)\n ) {\n throw captureError(`${path} is not a canonical JSON value`)\n }\n if (ancestors.has(object)) {\n throw captureError(`${path} is not a canonical JSON tree`)\n }\n ancestors.add(object)\n try {\n const keys = Object.keys(object)\n if (\n Reflect.ownKeys(object).length !== keys.length ||\n keys.some(\n (key) => !Object.prototype.propertyIsEnumerable.call(object, key),\n )\n ) {\n throw captureError(`${path} is not a canonical JSON object`)\n }\n for (const key of keys) {\n validateCanonicalJsonValue(object[key], `${path}.${key}`, ancestors)\n }\n } finally {\n ancestors.delete(object)\n }\n}\n\nexport function parseDevtoolsCapture(value: unknown): DevtoolsCapture {\n const records = captureRecordsFrom(value)\n if (records.length < 2) {\n throw captureError('capture needs a manifest and footer')\n }\n const manifestValue = objectValue(records[0])\n if (manifestValue?.['kind'] !== 'a2.capture') {\n throw captureError(\"first record must have kind 'a2.capture'\")\n }\n if (manifestValue['captureVersion'] !== DEVTOOLS_CAPTURE_VERSION) {\n throw captureError(\n `unsupported capture version '${String(manifestValue['captureVersion'])}'`,\n )\n }\n if (manifestValue['protocolVersion'] !== DEVTOOLS_PROTOCOL_VERSION) {\n throw captureError(\n `unsupported protocol version '${String(manifestValue['protocolVersion'])}'`,\n )\n }\n if (manifestValue['fidelity'] !== 'exact') {\n throw captureError(\"manifest.fidelity must be 'exact'\")\n }\n const manifest = manifestValue as DevtoolsCaptureManifest\n const contract = stringField(manifestValue, 'contract', 'manifest')\n const sessionId = stringField(manifestValue, 'sessionId', 'manifest')\n const revision = stringField(manifestValue, 'revision', 'manifest')\n const capturedAt = stringField(manifestValue, 'capturedAt', 'manifest')\n if (!canonicalDate(capturedAt)) {\n throw captureError('manifest.capturedAt must be a canonical ISO date')\n }\n\n const footerValue = objectValue(records.at(-1))\n if (footerValue?.['kind'] !== 'end') {\n throw captureError(\"last record must have kind 'end'\")\n }\n if (footerValue['captureVersion'] !== DEVTOOLS_CAPTURE_VERSION) {\n throw captureError('footer capture version does not match')\n }\n const footer = footerValue as DevtoolsCaptureFooter\n integerField(footerValue, 'eventCount', 'footer')\n integerField(footerValue, 'snapshotCount', 'footer')\n stringField(footerValue, 'sha256', 'footer')\n if (\n footerValue['maxIndex'] !== null &&\n (!Number.isSafeInteger(footerValue['maxIndex']) ||\n (footerValue['maxIndex'] as number) < 1)\n ) {\n throw captureError('footer.maxIndex must be null or a positive integer')\n }\n\n const events: DevtoolsCaptureEvent[] = []\n const snapshots: DevtoolsCaptureSnapshot[] = []\n let sawSnapshot = false\n for (let index = 1; index < records.length - 1; index += 1) {\n const record = objectValue(records[index])\n if (record?.['kind'] === 'event') {\n if (sawSnapshot) {\n throw captureError('event records must precede snapshot records')\n }\n validateEventRecord(record, `record ${index + 1}`)\n events.push(record as DevtoolsCaptureEvent)\n } else if (record?.['kind'] === 'snapshot') {\n sawSnapshot = true\n validateSnapshotRecord(record, `record ${index + 1}`)\n snapshots.push(record as DevtoolsCaptureSnapshot)\n } else {\n throw captureError(`record ${index + 1} has an unknown kind`)\n }\n }\n\n return {\n contract,\n sessionId,\n revision,\n capturedAt,\n manifest,\n events,\n snapshots,\n footer,\n records: records as DevtoolsCaptureRecord[],\n }\n}\n\nexport async function verifyDevtoolsCapture(\n capture: DevtoolsCapture,\n): Promise<void> {\n validateCanonicalJsonValue(capture.records, 'capture.records')\n parseDevtoolsCapture(capture.records)\n const expectedRecords: DevtoolsCaptureRecord[] = [\n capture.manifest,\n ...capture.events,\n ...capture.snapshots,\n capture.footer,\n ]\n if (\n capture.records.length !== expectedRecords.length ||\n capture.records.some((record, index) => record !== expectedRecords[index])\n ) {\n throw captureError('record sequence does not match the capture')\n }\n if (\n capture.contract !== capture.manifest.contract ||\n capture.sessionId !== capture.manifest.sessionId ||\n capture.revision !== capture.manifest.revision ||\n capture.capturedAt !== capture.manifest.capturedAt\n ) {\n throw captureError('capture identity does not match its manifest')\n }\n if (!canonicalDate(capture.capturedAt)) {\n throw captureError('manifest.capturedAt must be a canonical ISO date')\n }\n if (capture.footer.eventCount !== capture.events.length) {\n throw captureError('footer event count does not match')\n }\n if (capture.footer.snapshotCount !== capture.snapshots.length) {\n throw captureError('footer snapshot count does not match')\n }\n\n let previousIndex = 0\n const ids = new Set<string>()\n for (const event of capture.events) {\n if (event.kind !== 'event') throw captureError('invalid event record kind')\n validateEventRecord(\n event as unknown as Record<string, unknown>,\n `event #${event.index}`,\n )\n if (\n !Number.isSafeInteger(event.index) ||\n event.index !== previousIndex + 1\n ) {\n throw captureError('event indexes must be contiguous from 1')\n }\n previousIndex = event.index\n if (event.sessionId !== capture.sessionId) {\n throw captureError(`event #${event.index} belongs to another session`)\n }\n if (ids.has(event.id)) {\n throw captureError(`event #${event.index} repeats id '${event.id}'`)\n }\n ids.add(event.id)\n }\n\n for (const snapshot of capture.snapshots) {\n if (snapshot.kind !== 'snapshot') {\n throw captureError('invalid snapshot record kind')\n }\n validateSnapshotRecord(\n snapshot as unknown as Record<string, unknown>,\n `snapshot '${snapshot.reducerName}'`,\n )\n if (\n !Number.isSafeInteger(snapshot.index) ||\n snapshot.index > previousIndex\n ) {\n throw captureError(`snapshot '${snapshot.reducerName}' is invalid`)\n }\n }\n\n const maxIndex = capture.events.at(-1)?.index ?? null\n if (capture.footer.maxIndex !== maxIndex) {\n throw captureError('footer max index does not match')\n }\n if (!/^sha256:[\\da-f]{64}$/.test(capture.footer.sha256)) {\n throw captureError('footer.sha256 is malformed')\n }\n const expected = `sha256:${await sha256(\n encodeRecords(capture.records.slice(0, -1)),\n )}`\n if (capture.footer.sha256 !== expected) {\n throw captureError('capture digest does not match')\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
//#region src/devtools-server.browser.ts
|
|
2
|
-
/** experimental-a2/devtools/server owns store access and is server-only. */
|
|
3
|
-
throw new Error("experimental-a2/devtools/server is server-only — mount its handler in a server route and open that URL in the browser.");
|
|
4
|
-
//#endregion
|
|
5
|
-
|
|
6
|
-
//# sourceMappingURL=devtools-server.browser.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"devtools-server.browser.js","names":[],"sources":["../src/devtools-server.browser.ts"],"sourcesContent":["/** experimental-a2/devtools/server owns store access and is server-only. */\nthrow new Error(\n 'experimental-a2/devtools/server is server-only — mount its handler in a server route and open that URL in the browser.',\n)\nexport {}\n"],"mappings":";;AACA,MAAM,IAAI,MACR,wHACF"}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
//#region src/devtools-server.d.ts
|
|
2
|
-
type DevtoolsServer = {
|
|
3
|
-
readonly contract: {
|
|
4
|
-
readonly name: string;
|
|
5
|
-
};
|
|
6
|
-
};
|
|
7
|
-
type DevtoolsAuthorization = (request: Request) => boolean | Response | Promise<boolean | Response>;
|
|
8
|
-
type DevtoolsOptions = {
|
|
9
|
-
servers: readonly DevtoolsServer[];
|
|
10
|
-
/**
|
|
11
|
-
* Application-owned access control. Returning a Response short-circuits
|
|
12
|
-
* the request. Without this option, devtools is available only when
|
|
13
|
-
* NODE_ENV is exactly "development" and otherwise returns 404.
|
|
14
|
-
*/
|
|
15
|
-
authorize?: DevtoolsAuthorization;
|
|
16
|
-
};
|
|
17
|
-
type Devtools = {
|
|
18
|
-
handler(): (request: Request) => Promise<Response>;
|
|
19
|
-
};
|
|
20
|
-
declare function createDevtools(options: DevtoolsOptions): Devtools;
|
|
21
|
-
//#endregion
|
|
22
|
-
export { Devtools, DevtoolsAuthorization, DevtoolsOptions, DevtoolsServer, createDevtools };
|
|
23
|
-
//# sourceMappingURL=devtools-server.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"devtools-server.d.ts","names":[],"sources":["../src/devtools-server.ts"],"mappings":";KAsCY;WACD;aAAqB;;;KAGpB,yBACV,SAAS,sBACI,WAAW,kBAAkB;KAEhC;EACV,kBAAkB;;;;;;EAMlB,YAAY;;KAGF;EACV,YAAY,SAAS,YAAY,QAAQ;;iBAgY3B,eAAe,SAAS,kBAAkB"}
|