@emeryld/rrroutes-export 1.1.0 → 1.1.2
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/README.md +115 -1
- package/bin/rrroutes-checkpoint.mjs +9 -0
- package/bin/rrroutes-export-changes.mjs +9 -0
- package/bin/rrroutes-export-snapshot.mjs +9 -0
- package/bin/rrroutes-inspector-serve.mjs +9 -0
- package/dist/adapters/express.d.ts +6 -0
- package/dist/checkpoint/lock.d.ts +4 -0
- package/dist/checkpoint/retention.d.ts +15 -0
- package/dist/checkpoint/service.d.ts +11 -0
- package/dist/checkpoint/types.d.ts +65 -0
- package/dist/cli/changes.d.ts +4 -0
- package/dist/cli/checkpoint.d.ts +1 -0
- package/dist/cli/runtime.d.ts +7 -0
- package/dist/cli/serve.d.ts +7 -0
- package/dist/cli/snapshot.d.ts +4 -0
- package/dist/diff/diff-snapshots.d.ts +4 -0
- package/dist/diff/types.d.ts +58 -0
- package/dist/exportFinalizedLeaves.changelog.d.ts +4 -0
- package/dist/files/baked-payload.d.ts +21 -0
- package/dist/files/write.d.ts +10 -0
- package/dist/http/handler.d.ts +2 -0
- package/dist/http/types.d.ts +24 -0
- package/dist/index.cjs +3100 -121
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +25 -0
- package/dist/index.mjs +3070 -118
- package/dist/index.mjs.map +1 -1
- package/dist/inspection/diagnostics.d.ts +8 -0
- package/dist/inspection/inspect-clients.d.ts +9 -0
- package/dist/inspection/inspect-contract.d.ts +8 -0
- package/dist/inspection/inspect-server.d.ts +8 -0
- package/dist/inspection/inspect-sockets.d.ts +4 -0
- package/dist/inspection/join-routes.d.ts +10 -0
- package/dist/inspection/native.d.ts +4 -0
- package/dist/inspection/normalize-clients.d.ts +8 -0
- package/dist/inspection/normalize-routes.d.ts +3 -0
- package/dist/inspection/normalize-sockets.d.ts +7 -0
- package/dist/inspection/serializable.d.ts +3 -0
- package/dist/runtime.d.ts +16 -0
- package/dist/serializeLeafContract.d.ts +1 -0
- package/dist/snapshot/build.d.ts +2 -0
- package/dist/snapshot/canonicalize.d.ts +6 -0
- package/dist/snapshot/hash.d.ts +5 -0
- package/dist/snapshot/types.d.ts +287 -0
- package/dist/storage/filesystem.d.ts +5 -0
- package/dist/storage/memory.d.ts +2 -0
- package/dist/storage/sqlite.d.ts +15 -0
- package/dist/storage/types.d.ts +27 -0
- package/package.json +70 -8
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __typeError = (msg) => {
|
|
9
|
+
throw TypeError(msg);
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -26,33 +29,66 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
29
|
mod
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
33
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
34
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
29
35
|
|
|
30
36
|
// src/index.ts
|
|
31
37
|
var index_exports = {};
|
|
32
38
|
__export(index_exports, {
|
|
33
39
|
DEFAULT_VIEWER_TEMPLATE: () => DEFAULT_VIEWER_TEMPLATE,
|
|
40
|
+
RRROUTES_BAKED_PAYLOAD_MARKER: () => RRROUTES_BAKED_PAYLOAD_MARKER,
|
|
34
41
|
__private: () => __private,
|
|
42
|
+
applyChangeSetRetention: () => applyChangeSetRetention,
|
|
43
|
+
applyCheckpointRetention: () => applyCheckpointRetention,
|
|
35
44
|
buildFinalizedLeavesViewerBundle: () => buildFinalizedLeavesViewerBundle,
|
|
45
|
+
buildRRRoutesSnapshot: () => buildRRRoutesSnapshot,
|
|
46
|
+
canonicalSnapshotStructure: () => canonicalSnapshotStructure,
|
|
47
|
+
canonicalizeJson: () => canonicalizeJson,
|
|
36
48
|
clearSchemaIntrospectionHandlers: () => clearSchemaIntrospectionHandlers,
|
|
49
|
+
createBakedInspectorPayload: () => createBakedInspectorPayload,
|
|
50
|
+
createCheckpointService: () => createCheckpointService,
|
|
51
|
+
createExpressRouter: () => createExpressRouter,
|
|
52
|
+
createFileSystemInspectionStorage: () => createFileSystemInspectionStorage,
|
|
53
|
+
createInspectorHttpHandler: () => createInspectorHttpHandler,
|
|
54
|
+
createMemoryInspectionStorage: () => createMemoryInspectionStorage,
|
|
55
|
+
createRRRoutesExportRuntime: () => createRRRoutesExportRuntime,
|
|
56
|
+
createSQLiteInspectionStorage: () => createSQLiteInspectionStorage,
|
|
37
57
|
createSchemaIntrospector: () => createSchemaIntrospector,
|
|
58
|
+
createSnapshotChangeSet: () => createSnapshotChangeSet,
|
|
59
|
+
diffRRRoutesSnapshots: () => diffRRRoutesSnapshots,
|
|
60
|
+
escapeBakedPayload: () => escapeBakedPayload,
|
|
38
61
|
exportFinalizedLeaves: () => exportFinalizedLeaves,
|
|
39
62
|
exportFinalizedLeavesChangelog: () => exportFinalizedLeavesChangelog,
|
|
40
63
|
extractLeafSourceByAst: () => extractLeafSourceByAst,
|
|
41
64
|
flattenLeafSchemas: () => flattenLeafSchemas,
|
|
42
65
|
flattenSerializableSchema: () => flattenSerializableSchema,
|
|
66
|
+
hashCanonicalValue: () => hashCanonicalValue,
|
|
67
|
+
hashRRRoutesSnapshot: () => hashRRRoutesSnapshot,
|
|
68
|
+
injectBakedInspectorPayload: () => injectBakedInspectorPayload,
|
|
43
69
|
introspectSchema: () => introspectSchema,
|
|
70
|
+
loadExportRuntime: () => loadExportRuntime,
|
|
44
71
|
loadFinalizedLeavesInput: () => loadFinalizedLeavesInput,
|
|
72
|
+
parseCliFlags: () => parseCliFlags,
|
|
45
73
|
parseFinalizedLeavesChangelogCliArgs: () => parseFinalizedLeavesChangelogCliArgs,
|
|
46
74
|
parseFinalizedLeavesCliArgs: () => parseFinalizedLeavesCliArgs,
|
|
47
75
|
registerSchemaIntrospectionHandler: () => registerSchemaIntrospectionHandler,
|
|
76
|
+
requiredFlag: () => requiredFlag,
|
|
77
|
+
runChangesCli: () => runChangesCli,
|
|
78
|
+
runCheckpointCli: () => runCheckpointCli,
|
|
48
79
|
runExportFinalizedLeavesChangelogCli: () => runExportFinalizedLeavesChangelogCli,
|
|
49
80
|
runExportFinalizedLeavesCli: () => runExportFinalizedLeavesCli,
|
|
81
|
+
runInspectorServeCli: () => runInspectorServeCli,
|
|
82
|
+
runSnapshotCli: () => runSnapshotCli,
|
|
50
83
|
serializableSchemaKinds: () => serializableSchemaKinds,
|
|
51
84
|
serializeLeafContract: () => serializeLeafContract,
|
|
52
85
|
serializeLeavesContract: () => serializeLeavesContract,
|
|
86
|
+
stableStringify: () => stableStringify,
|
|
53
87
|
writeFinalizedLeavesChangelogExport: () => writeFinalizedLeavesChangelogExport,
|
|
54
88
|
writeFinalizedLeavesExport: () => writeFinalizedLeavesExport,
|
|
55
|
-
writeFinalizedLeavesViewerBundle: () => writeFinalizedLeavesViewerBundle
|
|
89
|
+
writeFinalizedLeavesViewerBundle: () => writeFinalizedLeavesViewerBundle,
|
|
90
|
+
writeRRRoutesChangeSet: () => writeRRRoutesChangeSet,
|
|
91
|
+
writeRRRoutesSnapshot: () => writeRRRoutesSnapshot
|
|
56
92
|
});
|
|
57
93
|
module.exports = __toCommonJS(index_exports);
|
|
58
94
|
|
|
@@ -271,7 +307,7 @@ function introspectSchema(schema, options = {}) {
|
|
|
271
307
|
// src/serializeLeafContract.ts
|
|
272
308
|
var import_rrroutes_contract = require("@emeryld/rrroutes-contract");
|
|
273
309
|
function serializeContractSchema(schema, options) {
|
|
274
|
-
return schema ? introspectSchema((0, import_rrroutes_contract.
|
|
310
|
+
return schema ? introspectSchema((0, import_rrroutes_contract.asZodSchema)(schema), options) : void 0;
|
|
275
311
|
}
|
|
276
312
|
function serializeBodyFiles(cfg) {
|
|
277
313
|
if (!Array.isArray(cfg.bodyFiles) || cfg.bodyFiles.length === 0) {
|
|
@@ -283,6 +319,7 @@ function serializeLeafContract(leaf, options = {}) {
|
|
|
283
319
|
const cfg = leaf.cfg;
|
|
284
320
|
return {
|
|
285
321
|
key: `${leaf.method.toUpperCase()} ${leaf.path}`,
|
|
322
|
+
id: cfg.id,
|
|
286
323
|
method: leaf.method,
|
|
287
324
|
path: leaf.path,
|
|
288
325
|
cfg: {
|
|
@@ -302,7 +339,10 @@ function serializeLeafContract(leaf, options = {}) {
|
|
|
302
339
|
params: serializeContractSchema(cfg.paramsSchema, options),
|
|
303
340
|
output: serializeContractSchema(cfg.outputSchema, options),
|
|
304
341
|
outputMeta: serializeContractSchema(cfg.outputMetaSchema, options),
|
|
305
|
-
queryExtension: serializeContractSchema(
|
|
342
|
+
queryExtension: serializeContractSchema(
|
|
343
|
+
cfg.queryExtensionSchema,
|
|
344
|
+
options
|
|
345
|
+
)
|
|
306
346
|
}
|
|
307
347
|
}
|
|
308
348
|
};
|
|
@@ -352,46 +392,46 @@ function normalizeType(schema) {
|
|
|
352
392
|
return "unknown";
|
|
353
393
|
}
|
|
354
394
|
}
|
|
355
|
-
function isNonEmptyPath(
|
|
356
|
-
return typeof
|
|
395
|
+
function isNonEmptyPath(path12) {
|
|
396
|
+
return typeof path12 === "string" && path12.length > 0;
|
|
357
397
|
}
|
|
358
|
-
function setNode(out,
|
|
359
|
-
if (!isNonEmptyPath(
|
|
360
|
-
out[
|
|
398
|
+
function setNode(out, path12, schema, inherited) {
|
|
399
|
+
if (!isNonEmptyPath(path12)) return;
|
|
400
|
+
out[path12] = {
|
|
361
401
|
type: normalizeType(schema),
|
|
362
402
|
nullable: inherited.nullable || Boolean(schema.nullable),
|
|
363
403
|
optional: inherited.optional || Boolean(schema.optional),
|
|
364
404
|
literal: schema.kind === "literal" ? schema.literal : void 0
|
|
365
405
|
};
|
|
366
406
|
}
|
|
367
|
-
function flattenInto(out, schema,
|
|
368
|
-
if (!schema || !isNonEmptyPath(
|
|
407
|
+
function flattenInto(out, schema, path12, inherited) {
|
|
408
|
+
if (!schema || !isNonEmptyPath(path12)) return;
|
|
369
409
|
const nextInherited = {
|
|
370
410
|
optional: inherited.optional || Boolean(schema.optional),
|
|
371
411
|
nullable: inherited.nullable || Boolean(schema.nullable)
|
|
372
412
|
};
|
|
373
413
|
if (schema.kind === "union" && Array.isArray(schema.union) && schema.union.length > 0) {
|
|
374
414
|
schema.union.forEach((option, index) => {
|
|
375
|
-
const optionPath = `${
|
|
415
|
+
const optionPath = `${path12}-${index + 1}`;
|
|
376
416
|
flattenInto(out, option, optionPath, inherited);
|
|
377
417
|
});
|
|
378
418
|
return;
|
|
379
419
|
}
|
|
380
|
-
setNode(out,
|
|
420
|
+
setNode(out, path12, schema, inherited);
|
|
381
421
|
if (schema.kind === "object" && schema.properties) {
|
|
382
422
|
for (const [key, child] of Object.entries(schema.properties)) {
|
|
383
|
-
const childPath = `${
|
|
423
|
+
const childPath = `${path12}.${key}`;
|
|
384
424
|
flattenInto(out, child, childPath, nextInherited);
|
|
385
425
|
}
|
|
386
426
|
return;
|
|
387
427
|
}
|
|
388
428
|
if (schema.kind === "array" && schema.element) {
|
|
389
|
-
flattenInto(out, schema.element, `${
|
|
429
|
+
flattenInto(out, schema.element, `${path12}[]`, nextInherited);
|
|
390
430
|
}
|
|
391
431
|
}
|
|
392
|
-
function flattenSerializableSchema(schema,
|
|
432
|
+
function flattenSerializableSchema(schema, path12) {
|
|
393
433
|
const out = {};
|
|
394
|
-
flattenInto(out, schema,
|
|
434
|
+
flattenInto(out, schema, path12, { optional: false, nullable: false });
|
|
395
435
|
return out;
|
|
396
436
|
}
|
|
397
437
|
function isSerializedLeaf(value) {
|
|
@@ -1115,6 +1155,33 @@ function extractLeafSourceByAst({
|
|
|
1115
1155
|
};
|
|
1116
1156
|
}
|
|
1117
1157
|
|
|
1158
|
+
// src/files/baked-payload.ts
|
|
1159
|
+
var RRROUTES_BAKED_PAYLOAD_MARKER = "<!--__RRROUTES_INSPECTOR_BAKED_PAYLOAD__-->";
|
|
1160
|
+
function createBakedInspectorPayload(args) {
|
|
1161
|
+
return {
|
|
1162
|
+
kind: "rrroutes-inspector-baked",
|
|
1163
|
+
snapshot: args.snapshot,
|
|
1164
|
+
checkpoints: args.checkpoints ?? [],
|
|
1165
|
+
changeSets: args.changeSets ?? []
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
function escapeBakedPayload(payload) {
|
|
1169
|
+
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
1170
|
+
}
|
|
1171
|
+
function injectBakedInspectorPayload(html, payload, options = {}) {
|
|
1172
|
+
const marker = options.marker ?? RRROUTES_BAKED_PAYLOAD_MARKER;
|
|
1173
|
+
const globalName = options.globalName ?? "__RRROUTES_INSPECTOR_PAYLOAD";
|
|
1174
|
+
const scriptId = options.scriptId ?? "rrroutes-inspector-baked-payload";
|
|
1175
|
+
const script = `${marker}
|
|
1176
|
+
<script id="${scriptId}">window.${globalName} = ${escapeBakedPayload(payload)};</script>`;
|
|
1177
|
+
if (html.includes(marker)) return html.replace(marker, script);
|
|
1178
|
+
if (html.includes("</body>"))
|
|
1179
|
+
return html.replace("</body>", `${script}
|
|
1180
|
+
</body>`);
|
|
1181
|
+
return `${script}
|
|
1182
|
+
${html}`;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1118
1185
|
// src/exportFinalizedLeaves.ts
|
|
1119
1186
|
function isRegistry(value) {
|
|
1120
1187
|
return typeof value === "object" && value !== null && "all" in value && "byKey" in value;
|
|
@@ -1128,7 +1195,7 @@ function buildMeta(sourceExtraction) {
|
|
|
1128
1195
|
description: "Finalized RRRoutes leaves export with serialized schemas and flattened schema paths for downstream processing.",
|
|
1129
1196
|
sourceExtraction,
|
|
1130
1197
|
fieldCatalog: {
|
|
1131
|
-
leaf: ["key", "method", "path", "cfg"],
|
|
1198
|
+
leaf: ["key", "id?", "method", "path", "cfg"],
|
|
1132
1199
|
cfg: [
|
|
1133
1200
|
"description?",
|
|
1134
1201
|
"summary?",
|
|
@@ -1162,25 +1229,6 @@ function buildMeta(sourceExtraction) {
|
|
|
1162
1229
|
}
|
|
1163
1230
|
};
|
|
1164
1231
|
}
|
|
1165
|
-
var BAKED_PAYLOAD_MARKER = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
|
|
1166
|
-
function escapePayloadForInlineScript(payload) {
|
|
1167
|
-
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
|
|
1168
|
-
}
|
|
1169
|
-
function injectPayloadIntoViewerHtml(htmlTemplate, payload) {
|
|
1170
|
-
const payloadScript = `${BAKED_PAYLOAD_MARKER}
|
|
1171
|
-
<script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript(
|
|
1172
|
-
payload
|
|
1173
|
-
)};</script>`;
|
|
1174
|
-
if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER)) {
|
|
1175
|
-
return htmlTemplate.replace(BAKED_PAYLOAD_MARKER, payloadScript);
|
|
1176
|
-
}
|
|
1177
|
-
if (htmlTemplate.includes("</body>")) {
|
|
1178
|
-
return htmlTemplate.replace("</body>", `${payloadScript}
|
|
1179
|
-
</body>`);
|
|
1180
|
-
}
|
|
1181
|
-
return `${payloadScript}
|
|
1182
|
-
${htmlTemplate}`;
|
|
1183
|
-
}
|
|
1184
1232
|
async function resolveViewerTemplatePath(viewerTemplateFile) {
|
|
1185
1233
|
if (viewerTemplateFile) {
|
|
1186
1234
|
const resolved = import_node_path2.default.resolve(viewerTemplateFile);
|
|
@@ -1192,10 +1240,7 @@ async function resolveViewerTemplatePath(viewerTemplateFile) {
|
|
|
1192
1240
|
process.cwd(),
|
|
1193
1241
|
"node_modules/@emeryld/rrroutes-export/tools/finalized-leaves-viewer.html"
|
|
1194
1242
|
),
|
|
1195
|
-
import_node_path2.default.resolve(
|
|
1196
|
-
process.cwd(),
|
|
1197
|
-
"tools/finalized-leaves-viewer.html"
|
|
1198
|
-
),
|
|
1243
|
+
import_node_path2.default.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
|
|
1199
1244
|
import_node_path2.default.resolve(
|
|
1200
1245
|
process.cwd(),
|
|
1201
1246
|
"packages/export/tools/finalized-leaves-viewer.html"
|
|
@@ -1220,7 +1265,11 @@ async function writeJsonExport(payload, outFile) {
|
|
|
1220
1265
|
async function writeBakedHtmlExport(payload, htmlFile, viewerTemplateFile) {
|
|
1221
1266
|
const templatePath = await resolveViewerTemplatePath(viewerTemplateFile);
|
|
1222
1267
|
const template = templatePath ? await import_promises.default.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
|
|
1223
|
-
const baked =
|
|
1268
|
+
const baked = injectBakedInspectorPayload(template, payload, {
|
|
1269
|
+
marker: "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->",
|
|
1270
|
+
globalName: "__FINALIZED_LEAVES_PAYLOAD",
|
|
1271
|
+
scriptId: "finalized-leaves-baked-payload"
|
|
1272
|
+
});
|
|
1224
1273
|
const resolved = import_node_path2.default.resolve(htmlFile);
|
|
1225
1274
|
await import_promises.default.mkdir(import_node_path2.default.dirname(resolved), { recursive: true });
|
|
1226
1275
|
await import_promises.default.writeFile(resolved, baked, "utf8");
|
|
@@ -1396,11 +1445,874 @@ async function runExportFinalizedLeavesCli(argv) {
|
|
|
1396
1445
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
1397
1446
|
var import_node_os = __toESM(require("os"), 1);
|
|
1398
1447
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
1399
|
-
var
|
|
1448
|
+
var import_node_crypto2 = __toESM(require("crypto"), 1);
|
|
1400
1449
|
var import_node_child_process2 = require("child_process");
|
|
1401
1450
|
var import_node_util = require("util");
|
|
1402
1451
|
var import_node_url2 = require("url");
|
|
1403
1452
|
var import_typescript2 = __toESM(require("typescript"), 1);
|
|
1453
|
+
|
|
1454
|
+
// src/snapshot/canonicalize.ts
|
|
1455
|
+
function compareJson(left, right) {
|
|
1456
|
+
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
1457
|
+
}
|
|
1458
|
+
function canonicalizeValue(value, key) {
|
|
1459
|
+
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
1460
|
+
return void 0;
|
|
1461
|
+
}
|
|
1462
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
1463
|
+
return value;
|
|
1464
|
+
}
|
|
1465
|
+
if (typeof value === "number") {
|
|
1466
|
+
return Number.isFinite(value) ? value : null;
|
|
1467
|
+
}
|
|
1468
|
+
if (typeof value === "bigint") return String(value);
|
|
1469
|
+
if (value instanceof Date) return value.toISOString();
|
|
1470
|
+
if (Array.isArray(value)) {
|
|
1471
|
+
const array = value.map((item) => canonicalizeValue(item)).filter((item) => item !== void 0);
|
|
1472
|
+
if (key === "tags" || key === "events" || key === "eventNames") {
|
|
1473
|
+
return array.slice().sort(compareJson);
|
|
1474
|
+
}
|
|
1475
|
+
return array;
|
|
1476
|
+
}
|
|
1477
|
+
if (typeof value !== "object") return void 0;
|
|
1478
|
+
const out = {};
|
|
1479
|
+
for (const property of Object.keys(value).sort()) {
|
|
1480
|
+
const child = canonicalizeValue(
|
|
1481
|
+
value[property],
|
|
1482
|
+
property
|
|
1483
|
+
);
|
|
1484
|
+
if (child !== void 0) out[property] = child;
|
|
1485
|
+
}
|
|
1486
|
+
return out;
|
|
1487
|
+
}
|
|
1488
|
+
function canonicalizeJson(value) {
|
|
1489
|
+
return canonicalizeValue(value) ?? null;
|
|
1490
|
+
}
|
|
1491
|
+
function canonicalSnapshotStructure(snapshot, options = {}) {
|
|
1492
|
+
const sockets = Object.fromEntries(
|
|
1493
|
+
Object.entries(snapshot.sockets).map(([identity, socket]) => [
|
|
1494
|
+
identity,
|
|
1495
|
+
options.includeLive ? socket : { ...socket, live: void 0 }
|
|
1496
|
+
])
|
|
1497
|
+
);
|
|
1498
|
+
return canonicalizeJson({
|
|
1499
|
+
...snapshot,
|
|
1500
|
+
meta: {
|
|
1501
|
+
...snapshot.meta,
|
|
1502
|
+
capturedAt: void 0,
|
|
1503
|
+
snapshotHash: void 0
|
|
1504
|
+
},
|
|
1505
|
+
capabilities: {
|
|
1506
|
+
...snapshot.capabilities,
|
|
1507
|
+
live: false
|
|
1508
|
+
},
|
|
1509
|
+
sockets
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
function stableStringify(value) {
|
|
1513
|
+
return JSON.stringify(canonicalizeJson(value));
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// src/snapshot/hash.ts
|
|
1517
|
+
var import_node_crypto = require("crypto");
|
|
1518
|
+
function hashCanonicalValue(value) {
|
|
1519
|
+
return (0, import_node_crypto.createHash)("sha256").update(stableStringify(value)).digest("hex");
|
|
1520
|
+
}
|
|
1521
|
+
function hashRRRoutesSnapshot(snapshot, options = {}) {
|
|
1522
|
+
return hashCanonicalValue(canonicalSnapshotStructure(snapshot, options));
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// src/diff/diff-snapshots.ts
|
|
1526
|
+
function same(left, right) {
|
|
1527
|
+
return stableStringify(left) === stableStringify(right);
|
|
1528
|
+
}
|
|
1529
|
+
function severityFor(type) {
|
|
1530
|
+
switch (type) {
|
|
1531
|
+
case "route_removed":
|
|
1532
|
+
case "client_binding_removed":
|
|
1533
|
+
case "client_resource_removed":
|
|
1534
|
+
case "server_controller_unregistered":
|
|
1535
|
+
case "server_endpoint_disabled":
|
|
1536
|
+
case "socket_runtime_removed":
|
|
1537
|
+
case "socket_contract_event_removed":
|
|
1538
|
+
return "breaking";
|
|
1539
|
+
case "schema_changed":
|
|
1540
|
+
case "cfg_changed":
|
|
1541
|
+
case "client_binding_target_changed":
|
|
1542
|
+
case "client_build_options_changed":
|
|
1543
|
+
case "client_augment_changed":
|
|
1544
|
+
case "client_augment_reordered":
|
|
1545
|
+
case "client_socket_connection_changed":
|
|
1546
|
+
case "server_controller_changed":
|
|
1547
|
+
case "server_middleware_changed":
|
|
1548
|
+
case "server_middleware_reordered":
|
|
1549
|
+
case "server_output_validation_changed":
|
|
1550
|
+
case "socket_payload_schema_changed":
|
|
1551
|
+
case "socket_binding_changed":
|
|
1552
|
+
case "socket_room_strategy_changed":
|
|
1553
|
+
case "socket_cache_reducer_changed":
|
|
1554
|
+
case "socket_heartbeat_configuration_changed":
|
|
1555
|
+
return "behavioral";
|
|
1556
|
+
case "route_added":
|
|
1557
|
+
case "client_resource_added":
|
|
1558
|
+
case "client_binding_added":
|
|
1559
|
+
case "client_binding_enabled":
|
|
1560
|
+
case "client_augment_added":
|
|
1561
|
+
case "client_socket_connection_added":
|
|
1562
|
+
case "server_controller_registered":
|
|
1563
|
+
case "server_endpoint_enabled":
|
|
1564
|
+
case "server_middleware_added":
|
|
1565
|
+
case "socket_runtime_added":
|
|
1566
|
+
case "socket_contract_event_added":
|
|
1567
|
+
case "socket_handler_added":
|
|
1568
|
+
return "non-breaking";
|
|
1569
|
+
case "route_id_changed":
|
|
1570
|
+
case "description_changed":
|
|
1571
|
+
case "files_changed":
|
|
1572
|
+
case "source_changed":
|
|
1573
|
+
case "client_binding_disabled":
|
|
1574
|
+
case "client_augment_removed":
|
|
1575
|
+
case "client_socket_connection_removed":
|
|
1576
|
+
case "server_middleware_removed":
|
|
1577
|
+
case "socket_handler_removed":
|
|
1578
|
+
return "informational";
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
function makeChange(input) {
|
|
1582
|
+
const before = input.before === void 0 ? void 0 : canonicalizeJson(input.before);
|
|
1583
|
+
const after = input.after === void 0 ? void 0 : canonicalizeJson(input.after);
|
|
1584
|
+
const base = {
|
|
1585
|
+
...input,
|
|
1586
|
+
before,
|
|
1587
|
+
after,
|
|
1588
|
+
severity: input.severity ?? severityFor(input.type)
|
|
1589
|
+
};
|
|
1590
|
+
return {
|
|
1591
|
+
...base,
|
|
1592
|
+
id: hashCanonicalValue(base)
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
function fieldChanges(before, after, fields) {
|
|
1596
|
+
return fields.flatMap(
|
|
1597
|
+
(field) => same(before[field], after[field]) ? [] : [
|
|
1598
|
+
{
|
|
1599
|
+
field,
|
|
1600
|
+
before: canonicalizeJson(before[field]),
|
|
1601
|
+
after: canonicalizeJson(after[field])
|
|
1602
|
+
}
|
|
1603
|
+
]
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
function diffContract(before, after) {
|
|
1607
|
+
const changes = [];
|
|
1608
|
+
const keys = /* @__PURE__ */ new Set([
|
|
1609
|
+
...Object.keys(before.routes),
|
|
1610
|
+
...Object.keys(after.routes)
|
|
1611
|
+
]);
|
|
1612
|
+
for (const routeKey of Array.from(keys).sort()) {
|
|
1613
|
+
const previous = before.routes[routeKey];
|
|
1614
|
+
const next = after.routes[routeKey];
|
|
1615
|
+
if (!previous && next) {
|
|
1616
|
+
changes.push(
|
|
1617
|
+
makeChange({
|
|
1618
|
+
layer: "contract",
|
|
1619
|
+
type: "route_added",
|
|
1620
|
+
routeKey,
|
|
1621
|
+
entityId: routeKey,
|
|
1622
|
+
after: next.contract.serializedLeaf
|
|
1623
|
+
})
|
|
1624
|
+
);
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
if (previous && !next) {
|
|
1628
|
+
changes.push(
|
|
1629
|
+
makeChange({
|
|
1630
|
+
layer: "contract",
|
|
1631
|
+
type: "route_removed",
|
|
1632
|
+
routeKey,
|
|
1633
|
+
entityId: routeKey,
|
|
1634
|
+
before: previous.contract.serializedLeaf
|
|
1635
|
+
})
|
|
1636
|
+
);
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
if (!previous || !next) continue;
|
|
1640
|
+
if (previous.id !== next.id) {
|
|
1641
|
+
changes.push(
|
|
1642
|
+
makeChange({
|
|
1643
|
+
layer: "contract",
|
|
1644
|
+
type: "route_id_changed",
|
|
1645
|
+
routeKey,
|
|
1646
|
+
entityId: routeKey,
|
|
1647
|
+
before: previous.id,
|
|
1648
|
+
after: next.id
|
|
1649
|
+
})
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
if (!same(previous.contract.schemaFlat, next.contract.schemaFlat)) {
|
|
1653
|
+
changes.push(
|
|
1654
|
+
makeChange({
|
|
1655
|
+
layer: "contract",
|
|
1656
|
+
type: "schema_changed",
|
|
1657
|
+
routeKey,
|
|
1658
|
+
entityId: routeKey,
|
|
1659
|
+
before: previous.contract.schemaFlat,
|
|
1660
|
+
after: next.contract.schemaFlat
|
|
1661
|
+
})
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
const previousCfg = previous.contract.serializedLeaf.cfg;
|
|
1665
|
+
const nextCfg = next.contract.serializedLeaf.cfg;
|
|
1666
|
+
if (previousCfg.description !== nextCfg.description) {
|
|
1667
|
+
changes.push(
|
|
1668
|
+
makeChange({
|
|
1669
|
+
layer: "contract",
|
|
1670
|
+
type: "description_changed",
|
|
1671
|
+
routeKey,
|
|
1672
|
+
entityId: routeKey,
|
|
1673
|
+
before: previousCfg.description,
|
|
1674
|
+
after: nextCfg.description
|
|
1675
|
+
})
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
if (!same(previousCfg.bodyFiles, nextCfg.bodyFiles)) {
|
|
1679
|
+
changes.push(
|
|
1680
|
+
makeChange({
|
|
1681
|
+
layer: "contract",
|
|
1682
|
+
type: "files_changed",
|
|
1683
|
+
routeKey,
|
|
1684
|
+
entityId: routeKey,
|
|
1685
|
+
before: previousCfg.bodyFiles,
|
|
1686
|
+
after: nextCfg.bodyFiles
|
|
1687
|
+
})
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
const cfgFields = [
|
|
1691
|
+
"summary",
|
|
1692
|
+
"docsGroup",
|
|
1693
|
+
"tags",
|
|
1694
|
+
"deprecated",
|
|
1695
|
+
"stability",
|
|
1696
|
+
"docsHidden",
|
|
1697
|
+
"docsMeta",
|
|
1698
|
+
"feed"
|
|
1699
|
+
];
|
|
1700
|
+
const fields = fieldChanges(
|
|
1701
|
+
previousCfg,
|
|
1702
|
+
nextCfg,
|
|
1703
|
+
cfgFields
|
|
1704
|
+
);
|
|
1705
|
+
if (fields.length > 0) {
|
|
1706
|
+
changes.push(
|
|
1707
|
+
makeChange({
|
|
1708
|
+
layer: "contract",
|
|
1709
|
+
type: "cfg_changed",
|
|
1710
|
+
routeKey,
|
|
1711
|
+
entityId: routeKey,
|
|
1712
|
+
before: previousCfg,
|
|
1713
|
+
after: nextCfg,
|
|
1714
|
+
changedFields: fields
|
|
1715
|
+
})
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
if (!same(previous.contract.source, next.contract.source)) {
|
|
1719
|
+
changes.push(
|
|
1720
|
+
makeChange({
|
|
1721
|
+
layer: "contract",
|
|
1722
|
+
type: "source_changed",
|
|
1723
|
+
routeKey,
|
|
1724
|
+
entityId: routeKey,
|
|
1725
|
+
before: previous.contract.source,
|
|
1726
|
+
after: next.contract.source
|
|
1727
|
+
})
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
return changes;
|
|
1732
|
+
}
|
|
1733
|
+
function diffOrderedEntities(args) {
|
|
1734
|
+
const changes = [];
|
|
1735
|
+
const previous = new Map(args.before.map((item) => [item.identity, item]));
|
|
1736
|
+
const next = new Map(args.after.map((item) => [item.identity, item]));
|
|
1737
|
+
const identities = /* @__PURE__ */ new Set([...previous.keys(), ...next.keys()]);
|
|
1738
|
+
for (const identity of Array.from(identities).sort()) {
|
|
1739
|
+
const left = previous.get(identity);
|
|
1740
|
+
const right = next.get(identity);
|
|
1741
|
+
if (!left && right) {
|
|
1742
|
+
changes.push(
|
|
1743
|
+
makeChange({
|
|
1744
|
+
layer: args.layer,
|
|
1745
|
+
type: args.added,
|
|
1746
|
+
routeKey: args.routeKey,
|
|
1747
|
+
entityId: identity,
|
|
1748
|
+
after: right
|
|
1749
|
+
})
|
|
1750
|
+
);
|
|
1751
|
+
} else if (left && !right) {
|
|
1752
|
+
changes.push(
|
|
1753
|
+
makeChange({
|
|
1754
|
+
layer: args.layer,
|
|
1755
|
+
type: args.removed,
|
|
1756
|
+
routeKey: args.routeKey,
|
|
1757
|
+
entityId: identity,
|
|
1758
|
+
before: left
|
|
1759
|
+
})
|
|
1760
|
+
);
|
|
1761
|
+
} else if (left && right) {
|
|
1762
|
+
const leftWithoutOrder = { ...left, order: void 0 };
|
|
1763
|
+
const rightWithoutOrder = { ...right, order: void 0 };
|
|
1764
|
+
if (!same(leftWithoutOrder, rightWithoutOrder)) {
|
|
1765
|
+
changes.push(
|
|
1766
|
+
makeChange({
|
|
1767
|
+
layer: args.layer,
|
|
1768
|
+
type: args.changed,
|
|
1769
|
+
routeKey: args.routeKey,
|
|
1770
|
+
entityId: identity,
|
|
1771
|
+
before: left,
|
|
1772
|
+
after: right
|
|
1773
|
+
})
|
|
1774
|
+
);
|
|
1775
|
+
} else if (left.order !== right.order) {
|
|
1776
|
+
changes.push(
|
|
1777
|
+
makeChange({
|
|
1778
|
+
layer: args.layer,
|
|
1779
|
+
type: args.reordered,
|
|
1780
|
+
routeKey: args.routeKey,
|
|
1781
|
+
entityId: identity,
|
|
1782
|
+
before: left.order,
|
|
1783
|
+
after: right.order
|
|
1784
|
+
})
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
return changes;
|
|
1790
|
+
}
|
|
1791
|
+
function allBindings(snapshot) {
|
|
1792
|
+
const map = /* @__PURE__ */ new Map();
|
|
1793
|
+
for (const client of Object.values(snapshot.clients)) {
|
|
1794
|
+
for (const binding of client.bindings)
|
|
1795
|
+
map.set(binding.bindingIdentity, binding);
|
|
1796
|
+
}
|
|
1797
|
+
return map;
|
|
1798
|
+
}
|
|
1799
|
+
function diffClientSocketConnections(args) {
|
|
1800
|
+
const changes = [];
|
|
1801
|
+
const previous = new Map(args.before.map((item) => [item.identity, item]));
|
|
1802
|
+
const next = new Map(args.after.map((item) => [item.identity, item]));
|
|
1803
|
+
const identities = /* @__PURE__ */ new Set([...previous.keys(), ...next.keys()]);
|
|
1804
|
+
for (const identity of Array.from(identities).sort()) {
|
|
1805
|
+
const left = previous.get(identity);
|
|
1806
|
+
const right = next.get(identity);
|
|
1807
|
+
if (!left && right) {
|
|
1808
|
+
changes.push(
|
|
1809
|
+
makeChange({
|
|
1810
|
+
layer: "client",
|
|
1811
|
+
type: "client_socket_connection_added",
|
|
1812
|
+
routeKey: args.routeKey,
|
|
1813
|
+
entityId: identity,
|
|
1814
|
+
after: right
|
|
1815
|
+
})
|
|
1816
|
+
);
|
|
1817
|
+
continue;
|
|
1818
|
+
}
|
|
1819
|
+
if (left && !right) {
|
|
1820
|
+
changes.push(
|
|
1821
|
+
makeChange({
|
|
1822
|
+
layer: "client",
|
|
1823
|
+
type: "client_socket_connection_removed",
|
|
1824
|
+
routeKey: args.routeKey,
|
|
1825
|
+
entityId: identity,
|
|
1826
|
+
before: left
|
|
1827
|
+
})
|
|
1828
|
+
);
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
if (!left || !right) continue;
|
|
1832
|
+
const leftConnection = {
|
|
1833
|
+
id: left.id,
|
|
1834
|
+
name: left.name,
|
|
1835
|
+
events: left.events,
|
|
1836
|
+
order: left.order,
|
|
1837
|
+
identityStability: left.identityStability
|
|
1838
|
+
};
|
|
1839
|
+
const rightConnection = {
|
|
1840
|
+
id: right.id,
|
|
1841
|
+
name: right.name,
|
|
1842
|
+
events: right.events,
|
|
1843
|
+
order: right.order,
|
|
1844
|
+
identityStability: right.identityStability
|
|
1845
|
+
};
|
|
1846
|
+
if (!same(leftConnection, rightConnection)) {
|
|
1847
|
+
changes.push(
|
|
1848
|
+
makeChange({
|
|
1849
|
+
layer: "client",
|
|
1850
|
+
type: "client_socket_connection_changed",
|
|
1851
|
+
routeKey: args.routeKey,
|
|
1852
|
+
entityId: identity,
|
|
1853
|
+
before: leftConnection,
|
|
1854
|
+
after: rightConnection
|
|
1855
|
+
})
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
if (!same(left.roomStrategy, right.roomStrategy)) {
|
|
1859
|
+
changes.push(
|
|
1860
|
+
makeChange({
|
|
1861
|
+
layer: "socket",
|
|
1862
|
+
type: "socket_room_strategy_changed",
|
|
1863
|
+
routeKey: args.routeKey,
|
|
1864
|
+
entityId: identity,
|
|
1865
|
+
before: left.roomStrategy,
|
|
1866
|
+
after: right.roomStrategy
|
|
1867
|
+
})
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
if (!same(left.cacheReducers, right.cacheReducers)) {
|
|
1871
|
+
changes.push(
|
|
1872
|
+
makeChange({
|
|
1873
|
+
layer: "socket",
|
|
1874
|
+
type: "socket_cache_reducer_changed",
|
|
1875
|
+
routeKey: args.routeKey,
|
|
1876
|
+
entityId: identity,
|
|
1877
|
+
before: left.cacheReducers,
|
|
1878
|
+
after: right.cacheReducers
|
|
1879
|
+
})
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
return changes;
|
|
1884
|
+
}
|
|
1885
|
+
function diffClients(before, after) {
|
|
1886
|
+
const changes = [];
|
|
1887
|
+
const resourceIds = /* @__PURE__ */ new Set([
|
|
1888
|
+
...Object.keys(before.clients),
|
|
1889
|
+
...Object.keys(after.clients)
|
|
1890
|
+
]);
|
|
1891
|
+
for (const identity of Array.from(resourceIds).sort()) {
|
|
1892
|
+
if (!before.clients[identity] && after.clients[identity]) {
|
|
1893
|
+
changes.push(
|
|
1894
|
+
makeChange({
|
|
1895
|
+
layer: "client",
|
|
1896
|
+
type: "client_resource_added",
|
|
1897
|
+
entityId: identity,
|
|
1898
|
+
after: after.clients[identity]
|
|
1899
|
+
})
|
|
1900
|
+
);
|
|
1901
|
+
} else if (before.clients[identity] && !after.clients[identity]) {
|
|
1902
|
+
changes.push(
|
|
1903
|
+
makeChange({
|
|
1904
|
+
layer: "client",
|
|
1905
|
+
type: "client_resource_removed",
|
|
1906
|
+
entityId: identity,
|
|
1907
|
+
before: before.clients[identity]
|
|
1908
|
+
})
|
|
1909
|
+
);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
const previous = allBindings(before);
|
|
1913
|
+
const next = allBindings(after);
|
|
1914
|
+
const identities = /* @__PURE__ */ new Set([...previous.keys(), ...next.keys()]);
|
|
1915
|
+
for (const identity of Array.from(identities).sort()) {
|
|
1916
|
+
const left = previous.get(identity);
|
|
1917
|
+
const right = next.get(identity);
|
|
1918
|
+
if (!left && right) {
|
|
1919
|
+
changes.push(
|
|
1920
|
+
makeChange({
|
|
1921
|
+
layer: "client",
|
|
1922
|
+
type: "client_binding_added",
|
|
1923
|
+
routeKey: right.routeKey,
|
|
1924
|
+
entityId: identity,
|
|
1925
|
+
after: right
|
|
1926
|
+
})
|
|
1927
|
+
);
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1930
|
+
if (left && !right) {
|
|
1931
|
+
changes.push(
|
|
1932
|
+
makeChange({
|
|
1933
|
+
layer: "client",
|
|
1934
|
+
type: "client_binding_removed",
|
|
1935
|
+
routeKey: left.routeKey,
|
|
1936
|
+
entityId: identity,
|
|
1937
|
+
before: left
|
|
1938
|
+
})
|
|
1939
|
+
);
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
if (!left || !right) continue;
|
|
1943
|
+
if (left.routeKey !== right.routeKey) {
|
|
1944
|
+
changes.push(
|
|
1945
|
+
makeChange({
|
|
1946
|
+
layer: "client",
|
|
1947
|
+
type: "client_binding_target_changed",
|
|
1948
|
+
routeKey: right.routeKey,
|
|
1949
|
+
entityId: identity,
|
|
1950
|
+
before: left.routeKey,
|
|
1951
|
+
after: right.routeKey
|
|
1952
|
+
})
|
|
1953
|
+
);
|
|
1954
|
+
}
|
|
1955
|
+
if (left.enabled !== right.enabled) {
|
|
1956
|
+
changes.push(
|
|
1957
|
+
makeChange({
|
|
1958
|
+
layer: "client",
|
|
1959
|
+
type: right.enabled ? "client_binding_enabled" : "client_binding_disabled",
|
|
1960
|
+
routeKey: right.routeKey,
|
|
1961
|
+
entityId: identity,
|
|
1962
|
+
before: left.enabled,
|
|
1963
|
+
after: right.enabled
|
|
1964
|
+
})
|
|
1965
|
+
);
|
|
1966
|
+
}
|
|
1967
|
+
if (!same(left.build, right.build)) {
|
|
1968
|
+
changes.push(
|
|
1969
|
+
makeChange({
|
|
1970
|
+
layer: "client",
|
|
1971
|
+
type: "client_build_options_changed",
|
|
1972
|
+
routeKey: right.routeKey,
|
|
1973
|
+
entityId: identity,
|
|
1974
|
+
before: left.build,
|
|
1975
|
+
after: right.build
|
|
1976
|
+
})
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
changes.push(
|
|
1980
|
+
...diffOrderedEntities({
|
|
1981
|
+
before: left.augments,
|
|
1982
|
+
after: right.augments,
|
|
1983
|
+
layer: "client",
|
|
1984
|
+
routeKey: right.routeKey,
|
|
1985
|
+
added: "client_augment_added",
|
|
1986
|
+
removed: "client_augment_removed",
|
|
1987
|
+
changed: "client_augment_changed",
|
|
1988
|
+
reordered: "client_augment_reordered"
|
|
1989
|
+
})
|
|
1990
|
+
);
|
|
1991
|
+
changes.push(
|
|
1992
|
+
...diffClientSocketConnections({
|
|
1993
|
+
before: left.socketConnections,
|
|
1994
|
+
after: right.socketConnections,
|
|
1995
|
+
routeKey: right.routeKey
|
|
1996
|
+
})
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
return changes;
|
|
2000
|
+
}
|
|
2001
|
+
function routeServerMap(snapshot) {
|
|
2002
|
+
const map = /* @__PURE__ */ new Map();
|
|
2003
|
+
for (const [routeKey, route] of Object.entries(snapshot.routes)) {
|
|
2004
|
+
if (route.server) map.set(routeKey, route.server);
|
|
2005
|
+
}
|
|
2006
|
+
return map;
|
|
2007
|
+
}
|
|
2008
|
+
function diffServers(before, after) {
|
|
2009
|
+
const changes = [];
|
|
2010
|
+
const previous = routeServerMap(before);
|
|
2011
|
+
const next = routeServerMap(after);
|
|
2012
|
+
const keys = /* @__PURE__ */ new Set([...previous.keys(), ...next.keys()]);
|
|
2013
|
+
for (const routeKey of Array.from(keys).sort()) {
|
|
2014
|
+
const left = previous.get(routeKey);
|
|
2015
|
+
const right = next.get(routeKey);
|
|
2016
|
+
if ((!left || !left.registered) && right?.registered) {
|
|
2017
|
+
changes.push(
|
|
2018
|
+
makeChange({
|
|
2019
|
+
layer: "server",
|
|
2020
|
+
type: "server_controller_registered",
|
|
2021
|
+
routeKey,
|
|
2022
|
+
entityId: right.controllerId ?? routeKey,
|
|
2023
|
+
after: right
|
|
2024
|
+
})
|
|
2025
|
+
);
|
|
2026
|
+
continue;
|
|
2027
|
+
}
|
|
2028
|
+
if (left?.registered && (!right || !right.registered)) {
|
|
2029
|
+
changes.push(
|
|
2030
|
+
makeChange({
|
|
2031
|
+
layer: "server",
|
|
2032
|
+
type: "server_controller_unregistered",
|
|
2033
|
+
routeKey,
|
|
2034
|
+
entityId: left.controllerId ?? routeKey,
|
|
2035
|
+
before: left
|
|
2036
|
+
})
|
|
2037
|
+
);
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
if (!left || !right || !left.registered || !right.registered) continue;
|
|
2041
|
+
if (left.enabled !== right.enabled) {
|
|
2042
|
+
changes.push(
|
|
2043
|
+
makeChange({
|
|
2044
|
+
layer: "server",
|
|
2045
|
+
type: right.enabled ? "server_endpoint_enabled" : "server_endpoint_disabled",
|
|
2046
|
+
routeKey,
|
|
2047
|
+
entityId: right.controllerId ?? routeKey,
|
|
2048
|
+
before: left.enabled,
|
|
2049
|
+
after: right.enabled
|
|
2050
|
+
})
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
2053
|
+
if (left.controllerId !== right.controllerId || left.controllerName !== right.controllerName) {
|
|
2054
|
+
changes.push(
|
|
2055
|
+
makeChange({
|
|
2056
|
+
layer: "server",
|
|
2057
|
+
type: "server_controller_changed",
|
|
2058
|
+
routeKey,
|
|
2059
|
+
entityId: right.controllerId ?? routeKey,
|
|
2060
|
+
before: {
|
|
2061
|
+
id: left.controllerId ?? null,
|
|
2062
|
+
name: left.controllerName ?? null
|
|
2063
|
+
},
|
|
2064
|
+
after: {
|
|
2065
|
+
id: right.controllerId ?? null,
|
|
2066
|
+
name: right.controllerName ?? null
|
|
2067
|
+
}
|
|
2068
|
+
})
|
|
2069
|
+
);
|
|
2070
|
+
}
|
|
2071
|
+
changes.push(
|
|
2072
|
+
...diffOrderedEntities({
|
|
2073
|
+
before: left.before,
|
|
2074
|
+
after: right.before,
|
|
2075
|
+
layer: "server",
|
|
2076
|
+
routeKey,
|
|
2077
|
+
added: "server_middleware_added",
|
|
2078
|
+
removed: "server_middleware_removed",
|
|
2079
|
+
changed: "server_middleware_changed",
|
|
2080
|
+
reordered: "server_middleware_reordered"
|
|
2081
|
+
})
|
|
2082
|
+
);
|
|
2083
|
+
if (left.validateOutput !== right.validateOutput) {
|
|
2084
|
+
changes.push(
|
|
2085
|
+
makeChange({
|
|
2086
|
+
layer: "server",
|
|
2087
|
+
type: "server_output_validation_changed",
|
|
2088
|
+
routeKey,
|
|
2089
|
+
entityId: right.controllerId ?? routeKey,
|
|
2090
|
+
before: left.validateOutput,
|
|
2091
|
+
after: right.validateOutput
|
|
2092
|
+
})
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
return changes;
|
|
2097
|
+
}
|
|
2098
|
+
function diffSockets(before, after) {
|
|
2099
|
+
const changes = [];
|
|
2100
|
+
const identities = /* @__PURE__ */ new Set([
|
|
2101
|
+
...Object.keys(before.sockets),
|
|
2102
|
+
...Object.keys(after.sockets)
|
|
2103
|
+
]);
|
|
2104
|
+
for (const identity of Array.from(identities).sort()) {
|
|
2105
|
+
const left = before.sockets[identity];
|
|
2106
|
+
const right = after.sockets[identity];
|
|
2107
|
+
if (!left && right) {
|
|
2108
|
+
changes.push(
|
|
2109
|
+
makeChange({
|
|
2110
|
+
layer: "socket",
|
|
2111
|
+
type: "socket_runtime_added",
|
|
2112
|
+
entityId: identity,
|
|
2113
|
+
after: right
|
|
2114
|
+
})
|
|
2115
|
+
);
|
|
2116
|
+
continue;
|
|
2117
|
+
}
|
|
2118
|
+
if (left && !right) {
|
|
2119
|
+
changes.push(
|
|
2120
|
+
makeChange({
|
|
2121
|
+
layer: "socket",
|
|
2122
|
+
type: "socket_runtime_removed",
|
|
2123
|
+
entityId: identity,
|
|
2124
|
+
before: left
|
|
2125
|
+
})
|
|
2126
|
+
);
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
if (!left || !right) continue;
|
|
2130
|
+
const eventNames = /* @__PURE__ */ new Set([
|
|
2131
|
+
...Object.keys(left.contract.events),
|
|
2132
|
+
...Object.keys(right.contract.events)
|
|
2133
|
+
]);
|
|
2134
|
+
for (const eventName of Array.from(eventNames).sort()) {
|
|
2135
|
+
const previousEvent = left.contract.events[eventName];
|
|
2136
|
+
const nextEvent = right.contract.events[eventName];
|
|
2137
|
+
const eventIdentity = `${identity}:event:${eventName}`;
|
|
2138
|
+
if (!previousEvent && nextEvent) {
|
|
2139
|
+
changes.push(
|
|
2140
|
+
makeChange({
|
|
2141
|
+
layer: "socket",
|
|
2142
|
+
type: "socket_contract_event_added",
|
|
2143
|
+
entityId: eventIdentity,
|
|
2144
|
+
after: nextEvent
|
|
2145
|
+
})
|
|
2146
|
+
);
|
|
2147
|
+
} else if (previousEvent && !nextEvent) {
|
|
2148
|
+
changes.push(
|
|
2149
|
+
makeChange({
|
|
2150
|
+
layer: "socket",
|
|
2151
|
+
type: "socket_contract_event_removed",
|
|
2152
|
+
entityId: eventIdentity,
|
|
2153
|
+
before: previousEvent
|
|
2154
|
+
})
|
|
2155
|
+
);
|
|
2156
|
+
} else if (previousEvent && nextEvent && !same(previousEvent.schema, nextEvent.schema)) {
|
|
2157
|
+
changes.push(
|
|
2158
|
+
makeChange({
|
|
2159
|
+
layer: "socket",
|
|
2160
|
+
type: "socket_payload_schema_changed",
|
|
2161
|
+
entityId: eventIdentity,
|
|
2162
|
+
before: previousEvent.schema,
|
|
2163
|
+
after: nextEvent.schema
|
|
2164
|
+
})
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
if (!same(left.configuration.heartbeat, right.configuration.heartbeat)) {
|
|
2169
|
+
changes.push(
|
|
2170
|
+
makeChange({
|
|
2171
|
+
layer: "socket",
|
|
2172
|
+
type: "socket_heartbeat_configuration_changed",
|
|
2173
|
+
entityId: identity,
|
|
2174
|
+
before: left.configuration.heartbeat,
|
|
2175
|
+
after: right.configuration.heartbeat
|
|
2176
|
+
})
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
const previousHandlers = new Map(
|
|
2180
|
+
left.configuration.handlers.map((handler) => [handler.identity, handler])
|
|
2181
|
+
);
|
|
2182
|
+
const nextHandlers = new Map(
|
|
2183
|
+
right.configuration.handlers.map((handler) => [handler.identity, handler])
|
|
2184
|
+
);
|
|
2185
|
+
for (const handlerIdentity of Array.from(
|
|
2186
|
+
/* @__PURE__ */ new Set([...previousHandlers.keys(), ...nextHandlers.keys()])
|
|
2187
|
+
).sort()) {
|
|
2188
|
+
const previousHandler = previousHandlers.get(handlerIdentity);
|
|
2189
|
+
const nextHandler = nextHandlers.get(handlerIdentity);
|
|
2190
|
+
if (!previousHandler && nextHandler) {
|
|
2191
|
+
changes.push(
|
|
2192
|
+
makeChange({
|
|
2193
|
+
layer: "socket",
|
|
2194
|
+
type: "socket_handler_added",
|
|
2195
|
+
entityId: handlerIdentity,
|
|
2196
|
+
after: nextHandler
|
|
2197
|
+
})
|
|
2198
|
+
);
|
|
2199
|
+
} else if (previousHandler && !nextHandler) {
|
|
2200
|
+
changes.push(
|
|
2201
|
+
makeChange({
|
|
2202
|
+
layer: "socket",
|
|
2203
|
+
type: "socket_handler_removed",
|
|
2204
|
+
entityId: handlerIdentity,
|
|
2205
|
+
before: previousHandler
|
|
2206
|
+
})
|
|
2207
|
+
);
|
|
2208
|
+
} else if (previousHandler && nextHandler && !same(previousHandler, nextHandler)) {
|
|
2209
|
+
changes.push(
|
|
2210
|
+
makeChange({
|
|
2211
|
+
layer: "socket",
|
|
2212
|
+
type: "socket_handler_removed",
|
|
2213
|
+
entityId: handlerIdentity,
|
|
2214
|
+
before: previousHandler
|
|
2215
|
+
}),
|
|
2216
|
+
makeChange({
|
|
2217
|
+
layer: "socket",
|
|
2218
|
+
type: "socket_handler_added",
|
|
2219
|
+
entityId: handlerIdentity,
|
|
2220
|
+
after: nextHandler
|
|
2221
|
+
})
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
const previousBindings = new Map(
|
|
2226
|
+
left.bindings.map((binding) => [binding.identity, binding])
|
|
2227
|
+
);
|
|
2228
|
+
const nextBindings = new Map(
|
|
2229
|
+
right.bindings.map((binding) => [binding.identity, binding])
|
|
2230
|
+
);
|
|
2231
|
+
for (const bindingIdentity of Array.from(
|
|
2232
|
+
/* @__PURE__ */ new Set([...previousBindings.keys(), ...nextBindings.keys()])
|
|
2233
|
+
).sort()) {
|
|
2234
|
+
const previousBinding = previousBindings.get(bindingIdentity);
|
|
2235
|
+
const nextBinding = nextBindings.get(bindingIdentity);
|
|
2236
|
+
if (!same(previousBinding, nextBinding)) {
|
|
2237
|
+
changes.push(
|
|
2238
|
+
makeChange({
|
|
2239
|
+
layer: "socket",
|
|
2240
|
+
type: "socket_binding_changed",
|
|
2241
|
+
routeKey: nextBinding?.routeKey ?? previousBinding?.routeKey,
|
|
2242
|
+
entityId: bindingIdentity,
|
|
2243
|
+
before: previousBinding,
|
|
2244
|
+
after: nextBinding
|
|
2245
|
+
})
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
return changes;
|
|
2251
|
+
}
|
|
2252
|
+
function summarize(changes) {
|
|
2253
|
+
const byLayer = {
|
|
2254
|
+
contract: 0,
|
|
2255
|
+
client: 0,
|
|
2256
|
+
server: 0,
|
|
2257
|
+
socket: 0
|
|
2258
|
+
};
|
|
2259
|
+
const bySeverity = {
|
|
2260
|
+
breaking: 0,
|
|
2261
|
+
behavioral: 0,
|
|
2262
|
+
"non-breaking": 0,
|
|
2263
|
+
informational: 0
|
|
2264
|
+
};
|
|
2265
|
+
const byType = {};
|
|
2266
|
+
for (const change of changes) {
|
|
2267
|
+
byLayer[change.layer] += 1;
|
|
2268
|
+
bySeverity[change.severity] += 1;
|
|
2269
|
+
byType[change.type] = (byType[change.type] ?? 0) + 1;
|
|
2270
|
+
}
|
|
2271
|
+
return { total: changes.length, byLayer, bySeverity, byType };
|
|
2272
|
+
}
|
|
2273
|
+
function diffRRRoutesSnapshots(before, after, options = {}) {
|
|
2274
|
+
const changes = [
|
|
2275
|
+
...diffContract(before, after),
|
|
2276
|
+
...diffClients(before, after),
|
|
2277
|
+
...diffServers(before, after),
|
|
2278
|
+
...diffSockets(before, after)
|
|
2279
|
+
].sort((left, right) => {
|
|
2280
|
+
const route = (left.routeKey ?? "").localeCompare(right.routeKey ?? "");
|
|
2281
|
+
if (route !== 0) return route;
|
|
2282
|
+
const layer = left.layer.localeCompare(right.layer);
|
|
2283
|
+
if (layer !== 0) return layer;
|
|
2284
|
+
const type = left.type.localeCompare(right.type);
|
|
2285
|
+
return type !== 0 ? type : left.entityId.localeCompare(right.entityId);
|
|
2286
|
+
});
|
|
2287
|
+
const scope = options.scope ?? "default";
|
|
2288
|
+
const trigger = options.trigger ?? { type: "manual" };
|
|
2289
|
+
const transition = {
|
|
2290
|
+
scope,
|
|
2291
|
+
from: before.meta.snapshotHash,
|
|
2292
|
+
to: after.meta.snapshotHash
|
|
2293
|
+
};
|
|
2294
|
+
return {
|
|
2295
|
+
id: hashCanonicalValue(transition),
|
|
2296
|
+
scope,
|
|
2297
|
+
createdAt: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2298
|
+
from: {
|
|
2299
|
+
snapshotHash: before.meta.snapshotHash,
|
|
2300
|
+
capturedAt: before.meta.capturedAt,
|
|
2301
|
+
checkpointId: options.fromCheckpointId
|
|
2302
|
+
},
|
|
2303
|
+
to: {
|
|
2304
|
+
snapshotHash: after.meta.snapshotHash,
|
|
2305
|
+
capturedAt: after.meta.capturedAt,
|
|
2306
|
+
checkpointId: options.toCheckpointId
|
|
2307
|
+
},
|
|
2308
|
+
trigger,
|
|
2309
|
+
summary: summarize(changes),
|
|
2310
|
+
changes
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
var createSnapshotChangeSet = diffRRRoutesSnapshots;
|
|
2314
|
+
|
|
2315
|
+
// src/exportFinalizedLeaves.changelog.ts
|
|
1404
2316
|
var execFile = (0, import_node_util.promisify)(import_node_child_process2.execFile);
|
|
1405
2317
|
var CHANGESET_CFG_FIELDS = [
|
|
1406
2318
|
"summary",
|
|
@@ -1413,7 +2325,15 @@ var CHANGESET_CFG_FIELDS = [
|
|
|
1413
2325
|
"feed"
|
|
1414
2326
|
];
|
|
1415
2327
|
var SCHEMA_SECTIONS = ["params", "query", "body", "output"];
|
|
1416
|
-
var MODULE_EXTENSIONS = [
|
|
2328
|
+
var MODULE_EXTENSIONS = [
|
|
2329
|
+
".ts",
|
|
2330
|
+
".tsx",
|
|
2331
|
+
".mts",
|
|
2332
|
+
".cts",
|
|
2333
|
+
".js",
|
|
2334
|
+
".mjs",
|
|
2335
|
+
".cjs"
|
|
2336
|
+
];
|
|
1417
2337
|
var COMPILE_CACHE_VERSION = "v2";
|
|
1418
2338
|
var SOURCE_KEY_BY_SECTION = {
|
|
1419
2339
|
params: "paramsSchema",
|
|
@@ -1424,7 +2344,7 @@ var SOURCE_KEY_BY_SECTION = {
|
|
|
1424
2344
|
function normalizePath(p) {
|
|
1425
2345
|
return p.replace(/\\/g, "/");
|
|
1426
2346
|
}
|
|
1427
|
-
function
|
|
2347
|
+
function stableStringify2(value) {
|
|
1428
2348
|
return JSON.stringify(value, (_key, v) => {
|
|
1429
2349
|
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
1430
2350
|
const sorted = Object.entries(v).sort(([a], [b]) => a.localeCompare(b)).reduce((acc, [k, inner]) => {
|
|
@@ -1453,7 +2373,7 @@ function toCfgSubset(cfg) {
|
|
|
1453
2373
|
return out;
|
|
1454
2374
|
}
|
|
1455
2375
|
function entrySignature(entry) {
|
|
1456
|
-
return
|
|
2376
|
+
return stableStringify2({
|
|
1457
2377
|
type: entry.type,
|
|
1458
2378
|
nullable: Boolean(entry.nullable),
|
|
1459
2379
|
optional: Boolean(entry.optional),
|
|
@@ -1461,7 +2381,9 @@ function entrySignature(entry) {
|
|
|
1461
2381
|
});
|
|
1462
2382
|
}
|
|
1463
2383
|
function semverToTuple(tag) {
|
|
1464
|
-
const match = tag.match(
|
|
2384
|
+
const match = tag.match(
|
|
2385
|
+
/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
|
|
2386
|
+
);
|
|
1465
2387
|
if (!match) return null;
|
|
1466
2388
|
return {
|
|
1467
2389
|
major: Number(match[1]),
|
|
@@ -1507,7 +2429,9 @@ async function findDefaultFromCommit(cwd, toCommit) {
|
|
|
1507
2429
|
const root = await runGit(cwd, ["rev-list", "--max-parents=0", toCommit]);
|
|
1508
2430
|
const rootCommit = root.split("\n").map((item) => item.trim()).filter(Boolean)[0];
|
|
1509
2431
|
if (!rootCommit) {
|
|
1510
|
-
throw new Error(
|
|
2432
|
+
throw new Error(
|
|
2433
|
+
"Failed to resolve a baseline commit for changelog generation."
|
|
2434
|
+
);
|
|
1511
2435
|
}
|
|
1512
2436
|
return rootCommit;
|
|
1513
2437
|
}
|
|
@@ -1626,11 +2550,15 @@ async function commitTouchedRelevantPaths(cwd, commit, moduleCandidates, tsconfi
|
|
|
1626
2550
|
]);
|
|
1627
2551
|
const changedFiles = changed.split("\n").map((item) => normalizePath(item.trim()).replace(/^\/+/, "")).filter(Boolean);
|
|
1628
2552
|
const moduleFiles = Array.from(
|
|
1629
|
-
new Set(
|
|
2553
|
+
new Set(
|
|
2554
|
+
moduleCandidates.map((item) => normalizePath(item).replace(/^\/+/, ""))
|
|
2555
|
+
)
|
|
1630
2556
|
);
|
|
1631
2557
|
const moduleDirs = Array.from(
|
|
1632
2558
|
new Set(
|
|
1633
|
-
moduleFiles.map(
|
|
2559
|
+
moduleFiles.map(
|
|
2560
|
+
(item) => normalizePath(import_node_path4.default.posix.dirname(item)).replace(/^\/+/, "")
|
|
2561
|
+
).filter((item) => item && item !== ".")
|
|
1634
2562
|
)
|
|
1635
2563
|
);
|
|
1636
2564
|
const tsconfigFile = tsconfigRel ? normalizePath(tsconfigRel).replace(/^\/+/, "") : void 0;
|
|
@@ -1653,7 +2581,12 @@ async function filterCommits(cwd, commits, moduleCandidates, tsconfigRel) {
|
|
|
1653
2581
|
const keep = [commits[0]];
|
|
1654
2582
|
for (let i = 1; i < commits.length; i += 1) {
|
|
1655
2583
|
const commit = commits[i];
|
|
1656
|
-
if (await commitTouchedRelevantPaths(
|
|
2584
|
+
if (await commitTouchedRelevantPaths(
|
|
2585
|
+
cwd,
|
|
2586
|
+
commit,
|
|
2587
|
+
moduleCandidates,
|
|
2588
|
+
tsconfigRel
|
|
2589
|
+
)) {
|
|
1657
2590
|
keep.push(commit);
|
|
1658
2591
|
}
|
|
1659
2592
|
}
|
|
@@ -1664,7 +2597,12 @@ async function filterCommits(cwd, commits, moduleCandidates, tsconfigRel) {
|
|
|
1664
2597
|
return keep;
|
|
1665
2598
|
}
|
|
1666
2599
|
async function getCommitMetadata(cwd, commit) {
|
|
1667
|
-
const out = await runGit(cwd, [
|
|
2600
|
+
const out = await runGit(cwd, [
|
|
2601
|
+
"show",
|
|
2602
|
+
"-s",
|
|
2603
|
+
"--format=%H%x1f%aI%x1f%an%x1f%s",
|
|
2604
|
+
commit
|
|
2605
|
+
]);
|
|
1668
2606
|
const [sha, authorDate, authorName, subject] = out.split("");
|
|
1669
2607
|
if (!sha || !authorDate || !authorName) {
|
|
1670
2608
|
throw new Error(`Unable to read commit metadata for ${commit}`);
|
|
@@ -1807,7 +2745,7 @@ function diffRouteCfg(before, after) {
|
|
|
1807
2745
|
for (const field of CHANGESET_CFG_FIELDS) {
|
|
1808
2746
|
const prev = before[field];
|
|
1809
2747
|
const next = after[field];
|
|
1810
|
-
if (
|
|
2748
|
+
if (stableStringify2(prev) !== stableStringify2(next)) {
|
|
1811
2749
|
out.push({
|
|
1812
2750
|
field,
|
|
1813
2751
|
before: prev,
|
|
@@ -1869,7 +2807,7 @@ function diffSourceSchemas(before, after) {
|
|
|
1869
2807
|
continue;
|
|
1870
2808
|
}
|
|
1871
2809
|
if (!prevSignatures || !nextSignatures) continue;
|
|
1872
|
-
if (
|
|
2810
|
+
if (stableStringify2(prevSignatures) !== stableStringify2(nextSignatures)) {
|
|
1873
2811
|
out.push({
|
|
1874
2812
|
section,
|
|
1875
2813
|
path: schemaPath,
|
|
@@ -2010,7 +2948,7 @@ function toEmittedModulePath(moduleRel) {
|
|
|
2010
2948
|
return normalized;
|
|
2011
2949
|
}
|
|
2012
2950
|
function buildCompileCacheKey(commitSha, moduleRel, tsconfigKeyPath) {
|
|
2013
|
-
return
|
|
2951
|
+
return import_node_crypto2.default.createHash("sha1").update(COMPILE_CACHE_VERSION).update("\n").update(commitSha).update("\n").update(normalizePath(moduleRel)).update("\n").update(normalizePath(tsconfigKeyPath)).digest("hex");
|
|
2014
2952
|
}
|
|
2015
2953
|
async function collectFilesRecursive(rootDir) {
|
|
2016
2954
|
const files = [];
|
|
@@ -2083,13 +3021,16 @@ async function compileSnapshotEntry(options) {
|
|
|
2083
3021
|
log(
|
|
2084
3022
|
`[changelog] compile cache ${options.noCache ? "disabled" : "miss"}: ${options.commitSha.slice(0, 12)}`
|
|
2085
3023
|
);
|
|
2086
|
-
await import_promises2.default.rm(import_node_path4.default.join(options.cacheRoot, cacheKey), {
|
|
2087
|
-
|
|
2088
|
-
|
|
3024
|
+
await import_promises2.default.rm(import_node_path4.default.join(options.cacheRoot, cacheKey), {
|
|
3025
|
+
recursive: true,
|
|
3026
|
+
force: true
|
|
3027
|
+
}).catch(() => void 0);
|
|
2089
3028
|
await import_promises2.default.mkdir(emitRoot, { recursive: true });
|
|
2090
3029
|
const readResult = import_typescript2.default.readConfigFile(options.tsconfigPath, import_typescript2.default.sys.readFile);
|
|
2091
3030
|
if (readResult.error) {
|
|
2092
|
-
throw new Error(
|
|
3031
|
+
throw new Error(
|
|
3032
|
+
import_typescript2.default.flattenDiagnosticMessageText(readResult.error.messageText, "\n")
|
|
3033
|
+
);
|
|
2093
3034
|
}
|
|
2094
3035
|
const parsed = import_typescript2.default.parseJsonConfigFileContent(
|
|
2095
3036
|
readResult.config,
|
|
@@ -2118,7 +3059,10 @@ async function compileSnapshotEntry(options) {
|
|
|
2118
3059
|
getCurrentDirectory: () => options.worktreeDir,
|
|
2119
3060
|
getNewLine: () => "\n"
|
|
2120
3061
|
};
|
|
2121
|
-
const message = import_typescript2.default.formatDiagnosticsWithColorAndContext(
|
|
3062
|
+
const message = import_typescript2.default.formatDiagnosticsWithColorAndContext(
|
|
3063
|
+
diagnostics.slice(0, 20),
|
|
3064
|
+
host
|
|
3065
|
+
);
|
|
2122
3066
|
throw new Error(
|
|
2123
3067
|
`TypeScript snapshot compile failed for ${options.moduleRel} at ${options.commitSha.slice(0, 12)}:
|
|
2124
3068
|
${message}`
|
|
@@ -2138,7 +3082,13 @@ async function loadSnapshotExport(options) {
|
|
|
2138
3082
|
}
|
|
2139
3083
|
async function createSnapshot(repoRoot, moduleCandidates, exportName, commit, tempRoot, tsconfigRel, exportOptions, log) {
|
|
2140
3084
|
const worktreeDir = import_node_path4.default.join(tempRoot, `wt-${commit.sha.slice(0, 12)}`);
|
|
2141
|
-
await runGit(repoRoot, [
|
|
3085
|
+
await runGit(repoRoot, [
|
|
3086
|
+
"worktree",
|
|
3087
|
+
"add",
|
|
3088
|
+
"--detach",
|
|
3089
|
+
worktreeDir,
|
|
3090
|
+
commit.sha
|
|
3091
|
+
]);
|
|
2142
3092
|
try {
|
|
2143
3093
|
try {
|
|
2144
3094
|
await ensureWorktreeDependencies(repoRoot, worktreeDir);
|
|
@@ -2223,13 +3173,94 @@ async function createSnapshot(repoRoot, moduleCandidates, exportName, commit, te
|
|
|
2223
3173
|
};
|
|
2224
3174
|
}
|
|
2225
3175
|
} finally {
|
|
2226
|
-
await runGit(repoRoot, [
|
|
3176
|
+
await runGit(repoRoot, [
|
|
3177
|
+
"worktree",
|
|
3178
|
+
"remove",
|
|
3179
|
+
"--force",
|
|
3180
|
+
worktreeDir
|
|
3181
|
+
]).catch(() => void 0);
|
|
2227
3182
|
await import_promises2.default.rm(worktreeDir, { recursive: true, force: true }).catch(() => void 0);
|
|
2228
3183
|
}
|
|
2229
3184
|
}
|
|
3185
|
+
function toCanonicalGitSnapshot(snapshot) {
|
|
3186
|
+
const routes = Object.fromEntries(
|
|
3187
|
+
Object.entries(snapshot.routesByKey).map(([routeKey, route]) => [
|
|
3188
|
+
routeKey,
|
|
3189
|
+
{
|
|
3190
|
+
routeKey,
|
|
3191
|
+
method: route.method,
|
|
3192
|
+
path: route.path,
|
|
3193
|
+
contract: {
|
|
3194
|
+
serializedLeaf: {
|
|
3195
|
+
key: routeKey,
|
|
3196
|
+
method: route.method.toLowerCase(),
|
|
3197
|
+
path: route.path,
|
|
3198
|
+
cfg: { ...route.cfg, schemas: {} }
|
|
3199
|
+
},
|
|
3200
|
+
schemaFlat: route.flatSchema,
|
|
3201
|
+
source: route.sourceBySection,
|
|
3202
|
+
fingerprint: hashCanonicalValue({
|
|
3203
|
+
cfg: route.cfg,
|
|
3204
|
+
schema: route.flatSchema
|
|
3205
|
+
})
|
|
3206
|
+
},
|
|
3207
|
+
clients: [],
|
|
3208
|
+
sockets: [],
|
|
3209
|
+
status: {
|
|
3210
|
+
client: "unbound",
|
|
3211
|
+
server: "unavailable",
|
|
3212
|
+
socket: "none"
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
])
|
|
3216
|
+
);
|
|
3217
|
+
const snapshotHash = hashCanonicalValue(routes);
|
|
3218
|
+
return {
|
|
3219
|
+
schemaVersion: 1,
|
|
3220
|
+
meta: {
|
|
3221
|
+
capturedAt: snapshot.commit.authorDate,
|
|
3222
|
+
snapshotHash,
|
|
3223
|
+
commit: snapshot.commit.sha,
|
|
3224
|
+
packageVersions: {}
|
|
3225
|
+
},
|
|
3226
|
+
capabilities: {
|
|
3227
|
+
contract: true,
|
|
3228
|
+
client: false,
|
|
3229
|
+
server: false,
|
|
3230
|
+
socket: false,
|
|
3231
|
+
live: false,
|
|
3232
|
+
source: true
|
|
3233
|
+
},
|
|
3234
|
+
routes,
|
|
3235
|
+
clients: {},
|
|
3236
|
+
servers: {},
|
|
3237
|
+
sockets: {},
|
|
3238
|
+
diagnostics: []
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
2230
3241
|
function diffSnapshots(previous, current) {
|
|
2231
3242
|
const routeEvents = [];
|
|
2232
3243
|
const sourceEvents = [];
|
|
3244
|
+
const changeSet = diffRRRoutesSnapshots(
|
|
3245
|
+
toCanonicalGitSnapshot(previous),
|
|
3246
|
+
toCanonicalGitSnapshot(current),
|
|
3247
|
+
{
|
|
3248
|
+
scope: "git",
|
|
3249
|
+
trigger: {
|
|
3250
|
+
type: "git",
|
|
3251
|
+
externalId: current.commit.sha,
|
|
3252
|
+
label: current.commit.subject
|
|
3253
|
+
},
|
|
3254
|
+
createdAt: current.commit.authorDate
|
|
3255
|
+
}
|
|
3256
|
+
);
|
|
3257
|
+
const canonicalRouteTypes = /* @__PURE__ */ new Map();
|
|
3258
|
+
for (const change of changeSet.changes) {
|
|
3259
|
+
if (change.layer !== "contract" || !change.routeKey) continue;
|
|
3260
|
+
const types = canonicalRouteTypes.get(change.routeKey) ?? /* @__PURE__ */ new Set();
|
|
3261
|
+
types.add(change.type);
|
|
3262
|
+
canonicalRouteTypes.set(change.routeKey, types);
|
|
3263
|
+
}
|
|
2233
3264
|
const routeKeys = /* @__PURE__ */ new Set([
|
|
2234
3265
|
...Object.keys(previous.routesByKey),
|
|
2235
3266
|
...Object.keys(current.routesByKey)
|
|
@@ -2237,7 +3268,8 @@ function diffSnapshots(previous, current) {
|
|
|
2237
3268
|
for (const routeKey of Array.from(routeKeys).sort()) {
|
|
2238
3269
|
const prevRoute = previous.routesByKey[routeKey];
|
|
2239
3270
|
const nextRoute = current.routesByKey[routeKey];
|
|
2240
|
-
|
|
3271
|
+
const canonicalTypes = canonicalRouteTypes.get(routeKey) ?? /* @__PURE__ */ new Set();
|
|
3272
|
+
if (!prevRoute && nextRoute && canonicalTypes.has("route_added")) {
|
|
2241
3273
|
const schemaDiff2 = diffRouteSchemas({}, nextRoute.flatSchema);
|
|
2242
3274
|
routeEvents.push({
|
|
2243
3275
|
type: "route_added",
|
|
@@ -2249,7 +3281,7 @@ function diffSnapshots(previous, current) {
|
|
|
2249
3281
|
});
|
|
2250
3282
|
continue;
|
|
2251
3283
|
}
|
|
2252
|
-
if (prevRoute && !nextRoute) {
|
|
3284
|
+
if (prevRoute && !nextRoute && canonicalTypes.has("route_removed")) {
|
|
2253
3285
|
routeEvents.push({
|
|
2254
3286
|
type: "route_removed",
|
|
2255
3287
|
commitSha: current.commit.sha,
|
|
@@ -2260,8 +3292,11 @@ function diffSnapshots(previous, current) {
|
|
|
2260
3292
|
continue;
|
|
2261
3293
|
}
|
|
2262
3294
|
if (!prevRoute || !nextRoute) continue;
|
|
2263
|
-
const schemaDiff = diffRouteSchemas(
|
|
2264
|
-
|
|
3295
|
+
const schemaDiff = diffRouteSchemas(
|
|
3296
|
+
prevRoute.flatSchema,
|
|
3297
|
+
nextRoute.flatSchema
|
|
3298
|
+
);
|
|
3299
|
+
if (schemaDiff.length > 0 && canonicalTypes.has("schema_changed")) {
|
|
2265
3300
|
routeEvents.push({
|
|
2266
3301
|
type: "schema_changed",
|
|
2267
3302
|
commitSha: current.commit.sha,
|
|
@@ -2272,7 +3307,9 @@ function diffSnapshots(previous, current) {
|
|
|
2272
3307
|
});
|
|
2273
3308
|
}
|
|
2274
3309
|
const cfgDiff = diffRouteCfg(prevRoute.cfg, nextRoute.cfg);
|
|
2275
|
-
if (cfgDiff.length > 0
|
|
3310
|
+
if (cfgDiff.length > 0 && ["cfg_changed", "description_changed", "files_changed"].some(
|
|
3311
|
+
(type) => canonicalTypes.has(type)
|
|
3312
|
+
)) {
|
|
2276
3313
|
routeEvents.push({
|
|
2277
3314
|
type: "cfg_changed",
|
|
2278
3315
|
commitSha: current.commit.sha,
|
|
@@ -2319,8 +3356,12 @@ function diffSnapshots(previous, current) {
|
|
|
2319
3356
|
const schemaDiff = diffSourceSchemas(prevSource, nextSource);
|
|
2320
3357
|
const prevRoutes = new Set(prevSource.routeKeys);
|
|
2321
3358
|
const nextRoutes = new Set(nextSource.routeKeys);
|
|
2322
|
-
const routeAdded = Array.from(nextRoutes).filter(
|
|
2323
|
-
|
|
3359
|
+
const routeAdded = Array.from(nextRoutes).filter(
|
|
3360
|
+
(routeKey) => !prevRoutes.has(routeKey)
|
|
3361
|
+
);
|
|
3362
|
+
const routeRemoved = Array.from(prevRoutes).filter(
|
|
3363
|
+
(routeKey) => !nextRoutes.has(routeKey)
|
|
3364
|
+
);
|
|
2324
3365
|
if (schemaDiff.length > 0 || routeAdded.length > 0 || routeRemoved.length > 0) {
|
|
2325
3366
|
sourceEvents.push({
|
|
2326
3367
|
type: "source_schema_changed",
|
|
@@ -2336,28 +3377,10 @@ function diffSnapshots(previous, current) {
|
|
|
2336
3377
|
}
|
|
2337
3378
|
return {
|
|
2338
3379
|
routeEvents,
|
|
2339
|
-
sourceEvents
|
|
3380
|
+
sourceEvents,
|
|
3381
|
+
changeSet
|
|
2340
3382
|
};
|
|
2341
3383
|
}
|
|
2342
|
-
var BAKED_PAYLOAD_MARKER2 = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
|
|
2343
|
-
function escapePayloadForInlineScript2(payload) {
|
|
2344
|
-
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
|
|
2345
|
-
}
|
|
2346
|
-
function injectPayloadIntoViewerHtml2(htmlTemplate, payload) {
|
|
2347
|
-
const payloadScript = `${BAKED_PAYLOAD_MARKER2}
|
|
2348
|
-
<script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript2(
|
|
2349
|
-
payload
|
|
2350
|
-
)};</script>`;
|
|
2351
|
-
if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER2)) {
|
|
2352
|
-
return htmlTemplate.replace(BAKED_PAYLOAD_MARKER2, payloadScript);
|
|
2353
|
-
}
|
|
2354
|
-
if (htmlTemplate.includes("</body>")) {
|
|
2355
|
-
return htmlTemplate.replace("</body>", `${payloadScript}
|
|
2356
|
-
</body>`);
|
|
2357
|
-
}
|
|
2358
|
-
return `${payloadScript}
|
|
2359
|
-
${htmlTemplate}`;
|
|
2360
|
-
}
|
|
2361
3384
|
async function resolveViewerTemplatePath2(viewerTemplateFile) {
|
|
2362
3385
|
if (viewerTemplateFile) {
|
|
2363
3386
|
const resolved = import_node_path4.default.resolve(viewerTemplateFile);
|
|
@@ -2370,7 +3393,10 @@ async function resolveViewerTemplatePath2(viewerTemplateFile) {
|
|
|
2370
3393
|
"node_modules/@emeryld/rrroutes-export/tools/finalized-leaves-viewer.html"
|
|
2371
3394
|
),
|
|
2372
3395
|
import_node_path4.default.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
|
|
2373
|
-
import_node_path4.default.resolve(
|
|
3396
|
+
import_node_path4.default.resolve(
|
|
3397
|
+
process.cwd(),
|
|
3398
|
+
"packages/export/tools/finalized-leaves-viewer.html"
|
|
3399
|
+
)
|
|
2374
3400
|
];
|
|
2375
3401
|
for (const candidate of candidates) {
|
|
2376
3402
|
try {
|
|
@@ -2391,7 +3417,11 @@ async function writeJsonExport2(payload, outFile) {
|
|
|
2391
3417
|
async function writeBakedHtmlExport2(payload, htmlFile, viewerTemplateFile) {
|
|
2392
3418
|
const templatePath = await resolveViewerTemplatePath2(viewerTemplateFile);
|
|
2393
3419
|
const template = templatePath ? await import_promises2.default.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
|
|
2394
|
-
const baked =
|
|
3420
|
+
const baked = injectBakedInspectorPayload(template, payload, {
|
|
3421
|
+
marker: "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->",
|
|
3422
|
+
globalName: "__FINALIZED_LEAVES_PAYLOAD",
|
|
3423
|
+
scriptId: "finalized-leaves-baked-payload"
|
|
3424
|
+
});
|
|
2395
3425
|
const resolved = import_node_path4.default.resolve(htmlFile);
|
|
2396
3426
|
await import_promises2.default.mkdir(import_node_path4.default.dirname(resolved), { recursive: true });
|
|
2397
3427
|
await import_promises2.default.writeFile(resolved, baked, "utf8");
|
|
@@ -2442,7 +3472,9 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2442
3472
|
log(`[changelog] resolving repository root from ${cwd}`);
|
|
2443
3473
|
const repoRoot = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
2444
3474
|
const modulePathAbsolute = import_node_path4.default.resolve(cwd, options.modulePath);
|
|
2445
|
-
const modulePathRelative = normalizePath(
|
|
3475
|
+
const modulePathRelative = normalizePath(
|
|
3476
|
+
import_node_path4.default.relative(repoRoot, modulePathAbsolute)
|
|
3477
|
+
);
|
|
2446
3478
|
if (modulePathRelative.startsWith("..")) {
|
|
2447
3479
|
throw new Error("modulePath must resolve inside the git repository root.");
|
|
2448
3480
|
}
|
|
@@ -2454,8 +3486,10 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2454
3486
|
const exportName = options.exportName ?? "leaves";
|
|
2455
3487
|
log("[changelog] resolving commit window");
|
|
2456
3488
|
const window = await resolveCommitWindow(repoRoot, options.from, options.to);
|
|
2457
|
-
log(
|
|
2458
|
-
|
|
3489
|
+
log(
|
|
3490
|
+
`[changelog] window: ${window.from.slice(0, 12)}..${window.to.slice(0, 12)}`
|
|
3491
|
+
);
|
|
3492
|
+
const moduleCandidates = await buildModulePathCandidates(
|
|
2459
3493
|
repoRoot,
|
|
2460
3494
|
window.to,
|
|
2461
3495
|
modulePathRelative
|
|
@@ -2479,7 +3513,9 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2479
3513
|
for (const commit of selectedCommitShas) {
|
|
2480
3514
|
commits.push(await getCommitMetadata(repoRoot, commit));
|
|
2481
3515
|
}
|
|
2482
|
-
const tempRoot = await import_promises2.default.mkdtemp(
|
|
3516
|
+
const tempRoot = await import_promises2.default.mkdtemp(
|
|
3517
|
+
import_node_path4.default.join(import_node_os.default.tmpdir(), "rrroutes-export-changelog-")
|
|
3518
|
+
);
|
|
2483
3519
|
log(`[changelog] temp workspace: ${tempRoot}`);
|
|
2484
3520
|
try {
|
|
2485
3521
|
const snapshots = [];
|
|
@@ -2516,6 +3552,7 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2516
3552
|
}
|
|
2517
3553
|
const routeEvents = [];
|
|
2518
3554
|
const sourceEvents = [];
|
|
3555
|
+
const changeSets = [];
|
|
2519
3556
|
log("[changelog] diffing successful snapshots");
|
|
2520
3557
|
let previousSnapshot;
|
|
2521
3558
|
for (const snapshot of snapshots) {
|
|
@@ -2526,12 +3563,14 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2526
3563
|
previousSnapshot = snapshot;
|
|
2527
3564
|
continue;
|
|
2528
3565
|
}
|
|
2529
|
-
const {
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
3566
|
+
const {
|
|
3567
|
+
routeEvents: nextRouteEvents,
|
|
3568
|
+
sourceEvents: nextSourceEvents,
|
|
3569
|
+
changeSet
|
|
3570
|
+
} = diffSnapshots(previousSnapshot, snapshot);
|
|
2533
3571
|
routeEvents.push(...nextRouteEvents);
|
|
2534
3572
|
sourceEvents.push(...nextSourceEvents);
|
|
3573
|
+
changeSets.push(changeSet);
|
|
2535
3574
|
previousSnapshot = snapshot;
|
|
2536
3575
|
}
|
|
2537
3576
|
log(
|
|
@@ -2553,7 +3592,8 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2553
3592
|
commits,
|
|
2554
3593
|
byRoute: routeRecordByKey(routeEvents),
|
|
2555
3594
|
bySourceObject: sourceRecordById(sourceEvents),
|
|
2556
|
-
rollups: buildRollups(routeEvents, sourceEvents, commits)
|
|
3595
|
+
rollups: buildRollups(routeEvents, sourceEvents, commits),
|
|
3596
|
+
changeSets
|
|
2557
3597
|
};
|
|
2558
3598
|
if (options.outFile || options.htmlFile) {
|
|
2559
3599
|
log("[changelog] writing output artifacts");
|
|
@@ -2660,25 +3700,6 @@ async function runExportFinalizedLeavesChangelogCli(argv) {
|
|
|
2660
3700
|
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
2661
3701
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
2662
3702
|
var import_node_child_process3 = require("child_process");
|
|
2663
|
-
var BAKED_PAYLOAD_MARKER3 = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
|
|
2664
|
-
function escapePayloadForInlineScript3(payload) {
|
|
2665
|
-
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
|
|
2666
|
-
}
|
|
2667
|
-
function injectPayloadIntoViewerHtml3(htmlTemplate, payload) {
|
|
2668
|
-
const payloadScript = `${BAKED_PAYLOAD_MARKER3}
|
|
2669
|
-
<script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript3(
|
|
2670
|
-
payload
|
|
2671
|
-
)};</script>`;
|
|
2672
|
-
if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER3)) {
|
|
2673
|
-
return htmlTemplate.replace(BAKED_PAYLOAD_MARKER3, payloadScript);
|
|
2674
|
-
}
|
|
2675
|
-
if (htmlTemplate.includes("</body>")) {
|
|
2676
|
-
return htmlTemplate.replace("</body>", `${payloadScript}
|
|
2677
|
-
</body>`);
|
|
2678
|
-
}
|
|
2679
|
-
return `${payloadScript}
|
|
2680
|
-
${htmlTemplate}`;
|
|
2681
|
-
}
|
|
2682
3703
|
async function resolveViewerTemplatePath3(viewerTemplateFile) {
|
|
2683
3704
|
if (viewerTemplateFile) {
|
|
2684
3705
|
const resolved = import_node_path6.default.resolve(viewerTemplateFile);
|
|
@@ -2691,7 +3712,10 @@ async function resolveViewerTemplatePath3(viewerTemplateFile) {
|
|
|
2691
3712
|
"node_modules/@emeryld/rrroutes-export/tools/finalized-leaves-viewer.html"
|
|
2692
3713
|
),
|
|
2693
3714
|
import_node_path6.default.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
|
|
2694
|
-
import_node_path6.default.resolve(
|
|
3715
|
+
import_node_path6.default.resolve(
|
|
3716
|
+
process.cwd(),
|
|
3717
|
+
"packages/export/tools/finalized-leaves-viewer.html"
|
|
3718
|
+
)
|
|
2695
3719
|
];
|
|
2696
3720
|
for (const candidate of candidates) {
|
|
2697
3721
|
try {
|
|
@@ -2712,7 +3736,11 @@ async function writeJsonExport3(payload, outFile) {
|
|
|
2712
3736
|
async function writeBakedHtmlExport3(payload, htmlFile, viewerTemplateFile) {
|
|
2713
3737
|
const templatePath = await resolveViewerTemplatePath3(viewerTemplateFile);
|
|
2714
3738
|
const template = templatePath ? await import_promises3.default.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
|
|
2715
|
-
const baked =
|
|
3739
|
+
const baked = injectBakedInspectorPayload(template, payload, {
|
|
3740
|
+
marker: "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->",
|
|
3741
|
+
globalName: "__FINALIZED_LEAVES_PAYLOAD",
|
|
3742
|
+
scriptId: "finalized-leaves-baked-payload"
|
|
3743
|
+
});
|
|
2716
3744
|
const resolved = import_node_path6.default.resolve(htmlFile);
|
|
2717
3745
|
await import_promises3.default.mkdir(import_node_path6.default.dirname(resolved), { recursive: true });
|
|
2718
3746
|
await import_promises3.default.writeFile(resolved, baked, "utf8");
|
|
@@ -2768,30 +3796,1981 @@ async function writeFinalizedLeavesViewerBundle(payload, outFileOrOptions) {
|
|
|
2768
3796
|
}
|
|
2769
3797
|
return written;
|
|
2770
3798
|
}
|
|
3799
|
+
|
|
3800
|
+
// src/snapshot/build.ts
|
|
3801
|
+
var import_rrroutes_contract5 = require("@emeryld/rrroutes-contract");
|
|
3802
|
+
|
|
3803
|
+
// src/inspection/diagnostics.ts
|
|
3804
|
+
var _items;
|
|
3805
|
+
var InspectionDiagnostics = class {
|
|
3806
|
+
constructor() {
|
|
3807
|
+
__privateAdd(this, _items, []);
|
|
3808
|
+
}
|
|
3809
|
+
add(diagnostic) {
|
|
3810
|
+
__privateGet(this, _items).push(diagnostic);
|
|
3811
|
+
}
|
|
3812
|
+
list(options = {}) {
|
|
3813
|
+
const items = options.includeInfo === false ? __privateGet(this, _items).filter((item) => item.severity !== "info") : __privateGet(this, _items);
|
|
3814
|
+
return items.slice().sort((left, right) => {
|
|
3815
|
+
const byCode = left.code.localeCompare(right.code);
|
|
3816
|
+
if (byCode !== 0) return byCode;
|
|
3817
|
+
return JSON.stringify(left.entity ?? {}).localeCompare(
|
|
3818
|
+
JSON.stringify(right.entity ?? {})
|
|
3819
|
+
);
|
|
3820
|
+
});
|
|
3821
|
+
}
|
|
3822
|
+
};
|
|
3823
|
+
_items = new WeakMap();
|
|
3824
|
+
|
|
3825
|
+
// src/inspection/inspect-clients.ts
|
|
3826
|
+
var import_rrroutes_contract3 = require("@emeryld/rrroutes-contract");
|
|
3827
|
+
|
|
3828
|
+
// src/inspection/native.ts
|
|
3829
|
+
var import_rrroutes_contract2 = require("@emeryld/rrroutes-contract");
|
|
3830
|
+
function asRecord2(value) {
|
|
3831
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
3832
|
+
}
|
|
3833
|
+
function readNativeInspection(value) {
|
|
3834
|
+
const record = asRecord2(value);
|
|
3835
|
+
if (!record) return void 0;
|
|
3836
|
+
const inspect = record[import_rrroutes_contract2.RRROUTES_INSPECTION];
|
|
3837
|
+
if (typeof inspect !== "function") return void 0;
|
|
3838
|
+
try {
|
|
3839
|
+
return asRecord2(inspect.call(value));
|
|
3840
|
+
} catch {
|
|
3841
|
+
return void 0;
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
function optionalString(value) {
|
|
3845
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3846
|
+
}
|
|
3847
|
+
function optionalBoolean(value) {
|
|
3848
|
+
return typeof value === "boolean" ? value : void 0;
|
|
3849
|
+
}
|
|
3850
|
+
|
|
3851
|
+
// src/inspection/normalize-clients.ts
|
|
3852
|
+
function normalizeClientsInput(endpointClient, allClients) {
|
|
3853
|
+
const out = [];
|
|
3854
|
+
if (endpointClient) {
|
|
3855
|
+
out.push({
|
|
3856
|
+
value: endpointClient,
|
|
3857
|
+
registeredName: "endpointClient",
|
|
3858
|
+
index: 0
|
|
3859
|
+
});
|
|
3860
|
+
}
|
|
3861
|
+
if (!allClients) return out;
|
|
3862
|
+
if (Array.isArray(allClients)) {
|
|
3863
|
+
const offset2 = out.length;
|
|
3864
|
+
allClients.forEach(
|
|
3865
|
+
(value, index) => out.push({ value, index: offset2 + index })
|
|
3866
|
+
);
|
|
3867
|
+
return out;
|
|
3868
|
+
}
|
|
3869
|
+
const offset = out.length;
|
|
3870
|
+
Object.entries(allClients).forEach(([registeredName, value], index) => {
|
|
3871
|
+
out.push({ value, registeredName, index: offset + index });
|
|
3872
|
+
});
|
|
3873
|
+
return out;
|
|
3874
|
+
}
|
|
3875
|
+
function looksLikeClientEndpoint(value) {
|
|
3876
|
+
const native = readNativeInspection(value);
|
|
3877
|
+
if (native?.kind === "client-endpoint") return true;
|
|
3878
|
+
return Boolean(
|
|
3879
|
+
value && typeof value === "object" && "leaf" in value && value.leaf
|
|
3880
|
+
);
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3883
|
+
// src/inspection/serializable.ts
|
|
3884
|
+
var MAX_METADATA_DEPTH = 8;
|
|
3885
|
+
function serializeSelectedMetadata(value, depth = 0) {
|
|
3886
|
+
if (depth > MAX_METADATA_DEPTH || value === void 0) return void 0;
|
|
3887
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
3888
|
+
return value;
|
|
3889
|
+
}
|
|
3890
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
3891
|
+
if (typeof value === "bigint") return String(value);
|
|
3892
|
+
if (value instanceof Date) return value.toISOString();
|
|
3893
|
+
if (Array.isArray(value)) {
|
|
3894
|
+
return value.map((item) => serializeSelectedMetadata(item, depth + 1)).filter((item) => item !== void 0);
|
|
3895
|
+
}
|
|
3896
|
+
if (typeof value !== "object") return void 0;
|
|
3897
|
+
const out = {};
|
|
3898
|
+
for (const key of Object.keys(value).sort()) {
|
|
3899
|
+
const child = serializeSelectedMetadata(
|
|
3900
|
+
value[key],
|
|
3901
|
+
depth + 1
|
|
3902
|
+
);
|
|
3903
|
+
if (child !== void 0) out[key] = child;
|
|
3904
|
+
}
|
|
3905
|
+
return out;
|
|
3906
|
+
}
|
|
3907
|
+
var CLIENT_BUILD_OPTION_FIELDS = [
|
|
3908
|
+
"enabled",
|
|
3909
|
+
"staleTime",
|
|
3910
|
+
"gcTime",
|
|
3911
|
+
"retry",
|
|
3912
|
+
"retryDelay",
|
|
3913
|
+
"refetchInterval",
|
|
3914
|
+
"refetchOnMount",
|
|
3915
|
+
"refetchOnWindowFocus",
|
|
3916
|
+
"refetchOnReconnect",
|
|
3917
|
+
"singleton",
|
|
3918
|
+
"splitPageSize",
|
|
3919
|
+
"splitPageSizeParam",
|
|
3920
|
+
"pagination",
|
|
3921
|
+
"feed",
|
|
3922
|
+
"many"
|
|
3923
|
+
];
|
|
3924
|
+
function serializeClientBuildOptions(value) {
|
|
3925
|
+
if (!value || typeof value !== "object") return {};
|
|
3926
|
+
const source = value;
|
|
3927
|
+
const out = {};
|
|
3928
|
+
for (const field of CLIENT_BUILD_OPTION_FIELDS) {
|
|
3929
|
+
const selected = serializeSelectedMetadata(source[field]);
|
|
3930
|
+
if (selected !== void 0) out[field] = selected;
|
|
3931
|
+
}
|
|
3932
|
+
return out;
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
// src/inspection/inspect-clients.ts
|
|
3936
|
+
function isLeaf(value) {
|
|
3937
|
+
const record = asRecord2(value);
|
|
3938
|
+
return Boolean(
|
|
3939
|
+
record && typeof record.method === "string" && typeof record.path === "string" && record.cfg && typeof record.cfg === "object"
|
|
3940
|
+
);
|
|
3941
|
+
}
|
|
3942
|
+
function endpointFrom(value) {
|
|
3943
|
+
const descriptor = readNativeInspection(value);
|
|
3944
|
+
if (descriptor?.kind === "client-endpoint" && isLeaf(descriptor.leaf)) {
|
|
3945
|
+
return {
|
|
3946
|
+
leaf: descriptor.leaf,
|
|
3947
|
+
id: optionalString(descriptor.id),
|
|
3948
|
+
name: optionalString(descriptor.name),
|
|
3949
|
+
enabled: optionalBoolean(descriptor.enabled) ?? true,
|
|
3950
|
+
built: optionalBoolean(descriptor.built) ?? true,
|
|
3951
|
+
buildOptions: descriptor.buildOptions,
|
|
3952
|
+
augments: Array.isArray(descriptor.augments) ? descriptor.augments.map(asRecord2).filter(
|
|
3953
|
+
(item) => Boolean(item)
|
|
3954
|
+
) : [],
|
|
3955
|
+
socketConnections: Array.isArray(descriptor.socketConnections) ? descriptor.socketConnections.map(asRecord2).filter(
|
|
3956
|
+
(item) => Boolean(item)
|
|
3957
|
+
) : [],
|
|
3958
|
+
complete: true
|
|
3959
|
+
};
|
|
3960
|
+
}
|
|
3961
|
+
const record = asRecord2(value);
|
|
3962
|
+
if (!record || !isLeaf(record.leaf)) return void 0;
|
|
3963
|
+
return {
|
|
3964
|
+
leaf: record.leaf,
|
|
3965
|
+
enabled: true,
|
|
3966
|
+
built: true,
|
|
3967
|
+
augments: [],
|
|
3968
|
+
socketConnections: [],
|
|
3969
|
+
complete: false
|
|
3970
|
+
};
|
|
3971
|
+
}
|
|
3972
|
+
function bindingsFromDescriptor(descriptor) {
|
|
3973
|
+
if (descriptor.kind === "client-resource") {
|
|
3974
|
+
const bindings = Array.isArray(descriptor.bindings) ? descriptor.bindings.flatMap((value, index) => {
|
|
3975
|
+
const binding = asRecord2(value);
|
|
3976
|
+
const endpoint = endpointFromInspectionRecord(binding?.endpoint) ?? endpointFrom(binding?.endpoint);
|
|
3977
|
+
if (!binding || !endpoint) return [];
|
|
3978
|
+
return [
|
|
3979
|
+
{
|
|
3980
|
+
id: optionalString(binding.id),
|
|
3981
|
+
publicName: optionalString(binding.publicName) ?? endpoint.name ?? `endpoint-${index}`,
|
|
3982
|
+
enabled: optionalBoolean(binding.enabled),
|
|
3983
|
+
endpoint
|
|
3984
|
+
}
|
|
3985
|
+
];
|
|
3986
|
+
}) : [];
|
|
3987
|
+
return {
|
|
3988
|
+
id: optionalString(descriptor.id),
|
|
3989
|
+
name: optionalString(descriptor.name),
|
|
3990
|
+
bindings
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
if (descriptor.kind === "endpoint-client") {
|
|
3994
|
+
const endpoints = Array.isArray(descriptor.endpoints) ? descriptor.endpoints : [];
|
|
3995
|
+
return {
|
|
3996
|
+
name: "Endpoint client",
|
|
3997
|
+
bindings: endpoints.flatMap((value, index) => {
|
|
3998
|
+
const endpoint = endpointFromInspectionRecord(value);
|
|
3999
|
+
if (!endpoint) return [];
|
|
4000
|
+
return [
|
|
4001
|
+
{
|
|
4002
|
+
id: endpoint.id,
|
|
4003
|
+
publicName: endpoint.name ?? endpoint.id ?? (0, import_rrroutes_contract3.routeKeyOf)(endpoint.leaf.method, endpoint.leaf.path),
|
|
4004
|
+
enabled: endpoint.enabled,
|
|
4005
|
+
endpoint
|
|
4006
|
+
}
|
|
4007
|
+
];
|
|
4008
|
+
})
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
return void 0;
|
|
4012
|
+
}
|
|
4013
|
+
function endpointFromInspectionRecord(value) {
|
|
4014
|
+
const record = asRecord2(value);
|
|
4015
|
+
if (!record || record.kind !== "client-endpoint" || !isLeaf(record.leaf)) {
|
|
4016
|
+
return void 0;
|
|
4017
|
+
}
|
|
4018
|
+
return {
|
|
4019
|
+
leaf: record.leaf,
|
|
4020
|
+
id: optionalString(record.id),
|
|
4021
|
+
name: optionalString(record.name),
|
|
4022
|
+
enabled: optionalBoolean(record.enabled) ?? true,
|
|
4023
|
+
built: optionalBoolean(record.built) ?? true,
|
|
4024
|
+
buildOptions: record.buildOptions,
|
|
4025
|
+
augments: Array.isArray(record.augments) ? record.augments.map(asRecord2).filter((item) => Boolean(item)) : [],
|
|
4026
|
+
socketConnections: Array.isArray(record.socketConnections) ? record.socketConnections.map(asRecord2).filter((item) => Boolean(item)) : [],
|
|
4027
|
+
complete: true
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
4030
|
+
function resourceFrom(input) {
|
|
4031
|
+
const descriptor = readNativeInspection(input.value);
|
|
4032
|
+
const native = descriptor ? bindingsFromDescriptor(descriptor) : void 0;
|
|
4033
|
+
if (native) return native;
|
|
4034
|
+
if (looksLikeClientEndpoint(input.value)) {
|
|
4035
|
+
const endpoint = endpointFrom(input.value);
|
|
4036
|
+
return {
|
|
4037
|
+
bindings: endpoint ? [
|
|
4038
|
+
{
|
|
4039
|
+
id: endpoint.id,
|
|
4040
|
+
publicName: endpoint.name ?? endpoint.id ?? "endpoint",
|
|
4041
|
+
enabled: endpoint.enabled,
|
|
4042
|
+
endpoint
|
|
4043
|
+
}
|
|
4044
|
+
] : []
|
|
4045
|
+
};
|
|
4046
|
+
}
|
|
4047
|
+
return {
|
|
4048
|
+
bindings: Object.entries(input.value).flatMap(
|
|
4049
|
+
([publicName, value]) => {
|
|
4050
|
+
const endpoint = endpointFrom(value);
|
|
4051
|
+
return endpoint ? [{ publicName, endpoint }] : [];
|
|
4052
|
+
}
|
|
4053
|
+
)
|
|
4054
|
+
};
|
|
4055
|
+
}
|
|
4056
|
+
function inspectAugments(descriptors, bindingIdentity) {
|
|
4057
|
+
return descriptors.map((descriptor, index) => {
|
|
4058
|
+
const id = optionalString(descriptor.id);
|
|
4059
|
+
return {
|
|
4060
|
+
identity: id ?? `${bindingIdentity}:augment:${index}`,
|
|
4061
|
+
id,
|
|
4062
|
+
name: optionalString(descriptor.name),
|
|
4063
|
+
kind: optionalString(descriptor.kind),
|
|
4064
|
+
options: serializeSelectedMetadata(descriptor.options),
|
|
4065
|
+
order: index,
|
|
4066
|
+
identityStability: id ? "stable" : "positional"
|
|
4067
|
+
};
|
|
4068
|
+
});
|
|
4069
|
+
}
|
|
4070
|
+
function inspectSocketConnections(descriptors, bindingIdentity) {
|
|
4071
|
+
return descriptors.map((descriptor, index) => {
|
|
4072
|
+
const id = optionalString(descriptor.id);
|
|
4073
|
+
return {
|
|
4074
|
+
identity: id ?? `${bindingIdentity}:socket:${index}`,
|
|
4075
|
+
id,
|
|
4076
|
+
name: optionalString(descriptor.name),
|
|
4077
|
+
events: Array.isArray(descriptor.events) ? descriptor.events.filter((item) => typeof item === "string").sort() : [],
|
|
4078
|
+
roomStrategy: serializeSelectedMetadata(descriptor.roomStrategy),
|
|
4079
|
+
cacheReducers: Array.isArray(descriptor.cacheReducers) ? descriptor.cacheReducers.map((item) => serializeSelectedMetadata(item)).filter(
|
|
4080
|
+
(item) => item !== void 0
|
|
4081
|
+
) : [],
|
|
4082
|
+
order: index,
|
|
4083
|
+
identityStability: id ? "stable" : "positional"
|
|
4084
|
+
};
|
|
4085
|
+
});
|
|
4086
|
+
}
|
|
4087
|
+
function inspectClients(inputs, diagnostics) {
|
|
4088
|
+
const clients = {};
|
|
4089
|
+
const byRoute = {};
|
|
4090
|
+
for (const input of inputs) {
|
|
4091
|
+
const resource = resourceFrom(input);
|
|
4092
|
+
const identity = resource.id ?? input.registeredName ?? `client-${input.index}`;
|
|
4093
|
+
const resourceStability = resource.id || input.registeredName ? "stable" : "positional";
|
|
4094
|
+
const seenPublicNames = /* @__PURE__ */ new Map();
|
|
4095
|
+
const bindings = resource.bindings.map((binding, index) => {
|
|
4096
|
+
const routeKey = (0, import_rrroutes_contract3.routeKeyOf)(
|
|
4097
|
+
binding.endpoint.leaf.method,
|
|
4098
|
+
binding.endpoint.leaf.path
|
|
4099
|
+
);
|
|
4100
|
+
const seen = seenPublicNames.get(binding.publicName) ?? 0;
|
|
4101
|
+
seenPublicNames.set(binding.publicName, seen + 1);
|
|
4102
|
+
const publicName = seen === 0 ? binding.publicName : `${binding.publicName}-${index}`;
|
|
4103
|
+
const bindingIdentity = binding.id ?? `${identity}:${publicName}`;
|
|
4104
|
+
const buildOptions = serializeClientBuildOptions(
|
|
4105
|
+
binding.endpoint.buildOptions
|
|
4106
|
+
);
|
|
4107
|
+
const pagination = buildOptions.pagination;
|
|
4108
|
+
const inspected = {
|
|
4109
|
+
clientId: identity,
|
|
4110
|
+
clientName: resource.name ?? input.registeredName,
|
|
4111
|
+
bindingId: binding.id,
|
|
4112
|
+
bindingIdentity,
|
|
4113
|
+
publicName,
|
|
4114
|
+
routeKey,
|
|
4115
|
+
enabled: binding.enabled ?? binding.endpoint.enabled,
|
|
4116
|
+
built: binding.endpoint.built,
|
|
4117
|
+
build: {
|
|
4118
|
+
feed: Boolean(binding.endpoint.leaf.cfg.feed),
|
|
4119
|
+
singleton: buildOptions.singleton === true,
|
|
4120
|
+
many: Boolean(buildOptions.many),
|
|
4121
|
+
pagination,
|
|
4122
|
+
options: buildOptions
|
|
4123
|
+
},
|
|
4124
|
+
augments: inspectAugments(binding.endpoint.augments, bindingIdentity),
|
|
4125
|
+
socketConnections: inspectSocketConnections(
|
|
4126
|
+
binding.endpoint.socketConnections,
|
|
4127
|
+
bindingIdentity
|
|
4128
|
+
),
|
|
4129
|
+
identityStability: binding.id || seen === 0 ? "stable" : "positional"
|
|
4130
|
+
};
|
|
4131
|
+
if (!binding.endpoint.complete) {
|
|
4132
|
+
diagnostics.add({
|
|
4133
|
+
code: "CLIENT_AUGMENT_DETAILS_UNAVAILABLE",
|
|
4134
|
+
severity: "info",
|
|
4135
|
+
entity: { routeKey, client: identity },
|
|
4136
|
+
message: "The endpoint was detected, but augment descriptors were not exposed."
|
|
4137
|
+
});
|
|
4138
|
+
}
|
|
4139
|
+
byRoute[routeKey] ?? (byRoute[routeKey] = []);
|
|
4140
|
+
byRoute[routeKey].push(inspected);
|
|
4141
|
+
return inspected;
|
|
4142
|
+
});
|
|
4143
|
+
clients[identity] = {
|
|
4144
|
+
identity,
|
|
4145
|
+
id: resource.id,
|
|
4146
|
+
name: resource.name ?? input.registeredName,
|
|
4147
|
+
identityStability: resourceStability,
|
|
4148
|
+
bindings
|
|
4149
|
+
};
|
|
4150
|
+
if (resourceStability === "positional") {
|
|
4151
|
+
diagnostics.add({
|
|
4152
|
+
code: "CLIENT_IDENTITY_POSITIONAL",
|
|
4153
|
+
severity: "info",
|
|
4154
|
+
entity: { client: identity },
|
|
4155
|
+
message: "Client identity uses its array position and may change after reordering."
|
|
4156
|
+
});
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
for (const bindings of Object.values(byRoute)) {
|
|
4160
|
+
bindings.sort(
|
|
4161
|
+
(left, right) => left.bindingIdentity.localeCompare(right.bindingIdentity)
|
|
4162
|
+
);
|
|
4163
|
+
}
|
|
4164
|
+
return { clients, byRoute };
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
// src/inspection/inspect-contract.ts
|
|
4168
|
+
var import_rrroutes_contract4 = require("@emeryld/rrroutes-contract");
|
|
4169
|
+
async function inspectContractRoutes(leaves, source, includeSource) {
|
|
4170
|
+
const exported = await exportFinalizedLeaves(leaves, {
|
|
4171
|
+
includeSource: includeSource && (source?.include ?? true),
|
|
4172
|
+
sourceModulePath: source?.modulePath,
|
|
4173
|
+
sourceExportName: source?.exportName,
|
|
4174
|
+
tsconfigPath: source?.tsconfigPath
|
|
4175
|
+
});
|
|
4176
|
+
const routes = {};
|
|
4177
|
+
for (const serializedLeaf of exported.leaves) {
|
|
4178
|
+
const routeKey = (0, import_rrroutes_contract4.routeKeyOf)(serializedLeaf.method, serializedLeaf.path);
|
|
4179
|
+
const schemaFlat = exported.schemaFlatByLeaf[routeKey] ?? {};
|
|
4180
|
+
routes[routeKey] = {
|
|
4181
|
+
serializedLeaf,
|
|
4182
|
+
schemaFlat,
|
|
4183
|
+
source: exported.sourceByLeaf?.[routeKey],
|
|
4184
|
+
fingerprint: hashCanonicalValue({ serializedLeaf, schemaFlat })
|
|
4185
|
+
};
|
|
4186
|
+
}
|
|
4187
|
+
return {
|
|
4188
|
+
routes,
|
|
4189
|
+
sourceExtraction: exported._meta.sourceExtraction
|
|
4190
|
+
};
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
// src/inspection/inspect-server.ts
|
|
4194
|
+
function isLeaf2(value) {
|
|
4195
|
+
const record = asRecord2(value);
|
|
4196
|
+
return Boolean(
|
|
4197
|
+
record && typeof record.method === "string" && typeof record.path === "string" && record.cfg && typeof record.cfg === "object"
|
|
4198
|
+
);
|
|
4199
|
+
}
|
|
4200
|
+
function inspectServer(input, diagnostics) {
|
|
4201
|
+
if (!input)
|
|
4202
|
+
return {
|
|
4203
|
+
servers: {},
|
|
4204
|
+
byRoute: {}
|
|
4205
|
+
};
|
|
4206
|
+
const descriptor = readNativeInspection(input);
|
|
4207
|
+
if (!descriptor || descriptor.kind !== "server-runtime") {
|
|
4208
|
+
diagnostics.add({
|
|
4209
|
+
code: "SERVER_REGISTRATION_DETAILS_UNAVAILABLE",
|
|
4210
|
+
severity: "warning",
|
|
4211
|
+
entity: { server: "server" },
|
|
4212
|
+
message: "A server runtime was supplied, but it did not expose the RRRoutes inspection protocol."
|
|
4213
|
+
});
|
|
4214
|
+
return {
|
|
4215
|
+
servers: {
|
|
4216
|
+
server: {
|
|
4217
|
+
identity: "server",
|
|
4218
|
+
identityStability: "stable",
|
|
4219
|
+
controllers: {},
|
|
4220
|
+
middleware: { sanitizerCount: 0, preCtxCount: 0, postCtxCount: 0 },
|
|
4221
|
+
validateOutput: false
|
|
4222
|
+
}
|
|
4223
|
+
},
|
|
4224
|
+
byRoute: {}
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
const id = optionalString(descriptor.id);
|
|
4228
|
+
const name = optionalString(descriptor.name);
|
|
4229
|
+
const identity = id ?? name ?? "server";
|
|
4230
|
+
const nativeMiddleware = asRecord2(descriptor.middleware);
|
|
4231
|
+
const middleware = {
|
|
4232
|
+
sanitizerCount: typeof nativeMiddleware?.sanitizerCount === "number" ? nativeMiddleware.sanitizerCount : 0,
|
|
4233
|
+
preCtxCount: typeof nativeMiddleware?.preCtxCount === "number" ? nativeMiddleware.preCtxCount : 0,
|
|
4234
|
+
postCtxCount: typeof nativeMiddleware?.postCtxCount === "number" ? nativeMiddleware.postCtxCount : 0
|
|
4235
|
+
};
|
|
4236
|
+
const validateOutput = optionalBoolean(descriptor.validateOutput) ?? false;
|
|
4237
|
+
const controllers = {};
|
|
4238
|
+
for (const nativeValue of Array.isArray(descriptor.controllers) ? descriptor.controllers : []) {
|
|
4239
|
+
const native = asRecord2(nativeValue);
|
|
4240
|
+
const routeKey = optionalString(native?.routeKey);
|
|
4241
|
+
if (!native || !routeKey || !isLeaf2(native.leaf)) continue;
|
|
4242
|
+
const controllerId = optionalString(native.id);
|
|
4243
|
+
const before = (Array.isArray(native.before) ? native.before : []).flatMap((middlewareValue, index) => {
|
|
4244
|
+
const item = asRecord2(middlewareValue);
|
|
4245
|
+
if (!item) return [];
|
|
4246
|
+
const middlewareId = optionalString(item.id);
|
|
4247
|
+
return [
|
|
4248
|
+
{
|
|
4249
|
+
identity: middlewareId ?? `${routeKey}:before:${index}`,
|
|
4250
|
+
id: middlewareId,
|
|
4251
|
+
name: optionalString(item.name),
|
|
4252
|
+
order: index,
|
|
4253
|
+
identityStability: middlewareId ? "stable" : "positional"
|
|
4254
|
+
}
|
|
4255
|
+
];
|
|
4256
|
+
});
|
|
4257
|
+
controllers[routeKey] = {
|
|
4258
|
+
routeKey,
|
|
4259
|
+
registered: true,
|
|
4260
|
+
enabled: optionalBoolean(native.enabled) ?? true,
|
|
4261
|
+
controllerId,
|
|
4262
|
+
controllerName: optionalString(native.name),
|
|
4263
|
+
before,
|
|
4264
|
+
middleware: {
|
|
4265
|
+
...middleware,
|
|
4266
|
+
beforeCount: before.length
|
|
4267
|
+
},
|
|
4268
|
+
validateOutput,
|
|
4269
|
+
multipart: Boolean(native.leaf.cfg.bodyFiles?.length),
|
|
4270
|
+
identityStability: "stable"
|
|
4271
|
+
};
|
|
4272
|
+
for (const item of before) {
|
|
4273
|
+
if (item.identityStability === "positional") {
|
|
4274
|
+
diagnostics.add({
|
|
4275
|
+
code: "SERVER_MIDDLEWARE_IDENTITY_POSITIONAL",
|
|
4276
|
+
severity: "info",
|
|
4277
|
+
entity: { routeKey, server: identity },
|
|
4278
|
+
message: "Middleware identity uses its route-local position and may change after reordering."
|
|
4279
|
+
});
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
return {
|
|
4284
|
+
servers: {
|
|
4285
|
+
[identity]: {
|
|
4286
|
+
identity,
|
|
4287
|
+
id,
|
|
4288
|
+
name,
|
|
4289
|
+
identityStability: "stable",
|
|
4290
|
+
controllers,
|
|
4291
|
+
middleware,
|
|
4292
|
+
validateOutput
|
|
4293
|
+
}
|
|
4294
|
+
},
|
|
4295
|
+
byRoute: controllers
|
|
4296
|
+
};
|
|
4297
|
+
}
|
|
4298
|
+
|
|
4299
|
+
// src/inspection/inspect-sockets.ts
|
|
4300
|
+
function toLiveInspection(value) {
|
|
4301
|
+
const live = asRecord2(value);
|
|
4302
|
+
if (!live) return void 0;
|
|
4303
|
+
const routeSubscriptions = Array.isArray(live.leaves) ? live.leaves.flatMap(
|
|
4304
|
+
(leafValue) => {
|
|
4305
|
+
const leaf = asRecord2(leafValue);
|
|
4306
|
+
const routeKey = optionalString(leaf?.leaf);
|
|
4307
|
+
if (!leaf || !routeKey) return [];
|
|
4308
|
+
return [
|
|
4309
|
+
{
|
|
4310
|
+
routeKey,
|
|
4311
|
+
rooms: Array.isArray(leaf.rooms) ? leaf.rooms.flatMap((roomValue) => {
|
|
4312
|
+
const room = asRecord2(roomValue);
|
|
4313
|
+
return room && typeof room.room === "string" && typeof room.count === "number" ? [{ room: room.room, count: room.count }] : [];
|
|
4314
|
+
}) : [],
|
|
4315
|
+
handlers: Array.isArray(leaf.handlers) ? leaf.handlers.flatMap((handlerValue) => {
|
|
4316
|
+
const handler = asRecord2(handlerValue);
|
|
4317
|
+
return handler && typeof handler.event === "string" && typeof handler.handlers === "number" ? [{ event: handler.event, handlers: handler.handlers }] : [];
|
|
4318
|
+
}) : []
|
|
4319
|
+
}
|
|
4320
|
+
];
|
|
4321
|
+
}
|
|
4322
|
+
) : void 0;
|
|
4323
|
+
return {
|
|
4324
|
+
connected: optionalBoolean(live.connected) ?? false,
|
|
4325
|
+
socketId: optionalString(live.socketId),
|
|
4326
|
+
namespace: optionalString(live.nsp),
|
|
4327
|
+
roomsCount: typeof live.roomsCount === "number" ? live.roomsCount : void 0,
|
|
4328
|
+
totalHandlers: typeof live.totalHandlers === "number" ? live.totalHandlers : void 0,
|
|
4329
|
+
rooms: Array.isArray(live.rooms) ? live.rooms.flatMap((roomValue) => {
|
|
4330
|
+
const room = asRecord2(roomValue);
|
|
4331
|
+
return room && typeof room.room === "string" && typeof room.count === "number" ? [{ room: room.room, count: room.count }] : [];
|
|
4332
|
+
}) : void 0,
|
|
4333
|
+
handlers: Array.isArray(live.handlers) ? live.handlers.flatMap((handlerValue) => {
|
|
4334
|
+
const handler = asRecord2(handlerValue);
|
|
4335
|
+
return handler && typeof handler.event === "string" && typeof handler.handlers === "number" ? [{ event: handler.event, handlers: handler.handlers }] : [];
|
|
4336
|
+
}) : void 0,
|
|
4337
|
+
routeSubscriptions
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
function inspectSockets(inputs, includeLive, diagnostics) {
|
|
4341
|
+
const sockets = {};
|
|
4342
|
+
for (const input of inputs) {
|
|
4343
|
+
const descriptor = readNativeInspection(input.value);
|
|
4344
|
+
const id = optionalString(descriptor?.id);
|
|
4345
|
+
const name = optionalString(descriptor?.name);
|
|
4346
|
+
const identity = id ?? input.registeredName ?? name ?? `socket-${input.index}`;
|
|
4347
|
+
const stability = id || input.registeredName || name ? "stable" : "positional";
|
|
4348
|
+
const events = asRecord2(descriptor?.events);
|
|
4349
|
+
const contractEvents = {};
|
|
4350
|
+
const handlers = (Array.isArray(descriptor?.handlers) ? descriptor.handlers : []).flatMap((handlerValue, index) => {
|
|
4351
|
+
const handler = asRecord2(handlerValue);
|
|
4352
|
+
const event = optionalString(handler?.event);
|
|
4353
|
+
if (!handler || !event) return [];
|
|
4354
|
+
return [
|
|
4355
|
+
{
|
|
4356
|
+
identity: `${identity}:handler:${event}:${index}`,
|
|
4357
|
+
event,
|
|
4358
|
+
name: optionalString(handler.name),
|
|
4359
|
+
order: typeof handler.order === "number" ? handler.order : index,
|
|
4360
|
+
identityStability: "positional"
|
|
4361
|
+
}
|
|
4362
|
+
];
|
|
4363
|
+
});
|
|
4364
|
+
const bindings = (Array.isArray(descriptor?.bindings) ? descriptor.bindings : []).flatMap((bindingValue, index) => {
|
|
4365
|
+
const binding = asRecord2(bindingValue);
|
|
4366
|
+
const routeKey = optionalString(binding?.routeKey);
|
|
4367
|
+
if (!binding || !routeKey) return [];
|
|
4368
|
+
return [
|
|
4369
|
+
{
|
|
4370
|
+
identity: `${identity}:binding:${routeKey}`,
|
|
4371
|
+
routeKey,
|
|
4372
|
+
eventNames: Array.isArray(binding.events) ? binding.events.filter((event) => typeof event === "string").sort() : [],
|
|
4373
|
+
identityStability: "stable"
|
|
4374
|
+
}
|
|
4375
|
+
];
|
|
4376
|
+
});
|
|
4377
|
+
for (const eventName of Object.keys(events ?? {}).sort()) {
|
|
4378
|
+
const event = asRecord2(events?.[eventName]);
|
|
4379
|
+
const message = event?.message;
|
|
4380
|
+
contractEvents[eventName] = {
|
|
4381
|
+
identity: `${identity}:event:${eventName}`,
|
|
4382
|
+
name: eventName,
|
|
4383
|
+
schema: message && typeof message === "object" ? introspectSchema(message) : void 0
|
|
4384
|
+
};
|
|
4385
|
+
}
|
|
4386
|
+
if (!descriptor || descriptor.kind !== "socket-runtime") {
|
|
4387
|
+
diagnostics.add({
|
|
4388
|
+
code: "SOCKET_DETAILS_UNAVAILABLE",
|
|
4389
|
+
severity: "info",
|
|
4390
|
+
entity: { socket: identity },
|
|
4391
|
+
message: "A socket runtime was supplied, but it did not expose the RRRoutes inspection protocol."
|
|
4392
|
+
});
|
|
4393
|
+
}
|
|
4394
|
+
if (stability === "positional") {
|
|
4395
|
+
diagnostics.add({
|
|
4396
|
+
code: "SOCKET_IDENTITY_POSITIONAL",
|
|
4397
|
+
severity: "info",
|
|
4398
|
+
entity: { socket: identity },
|
|
4399
|
+
message: "Socket identity uses its array position and may change after reordering."
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
sockets[identity] = {
|
|
4403
|
+
identity,
|
|
4404
|
+
id,
|
|
4405
|
+
name: name ?? input.registeredName,
|
|
4406
|
+
identityStability: stability,
|
|
4407
|
+
contract: { events: contractEvents },
|
|
4408
|
+
configuration: {
|
|
4409
|
+
heartbeat: serializeSelectedMetadata(descriptor?.heartbeat),
|
|
4410
|
+
reconnection: serializeSelectedMetadata(descriptor?.reconnection),
|
|
4411
|
+
system: serializeSelectedMetadata(descriptor?.config),
|
|
4412
|
+
handlers
|
|
4413
|
+
},
|
|
4414
|
+
bindings,
|
|
4415
|
+
live: includeLive ? toLiveInspection(
|
|
4416
|
+
descriptor?.live ?? asRecord2(input.value)?.statsSnapshot
|
|
4417
|
+
) : void 0
|
|
4418
|
+
};
|
|
4419
|
+
}
|
|
4420
|
+
return sockets;
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
// src/inspection/join-routes.ts
|
|
4424
|
+
function joinRouteInspections(args) {
|
|
4425
|
+
const routes = {};
|
|
4426
|
+
const serverRuntime = Object.values(args.servers)[0];
|
|
4427
|
+
for (const routeKey of Object.keys(args.contract).sort()) {
|
|
4428
|
+
const contract = args.contract[routeKey];
|
|
4429
|
+
const clients = (args.clients[routeKey] ?? []).slice();
|
|
4430
|
+
const registeredServer = args.server[routeKey];
|
|
4431
|
+
const server = registeredServer ?? (args.serverSupplied ? {
|
|
4432
|
+
routeKey,
|
|
4433
|
+
registered: false,
|
|
4434
|
+
enabled: false,
|
|
4435
|
+
before: [],
|
|
4436
|
+
middleware: {
|
|
4437
|
+
sanitizerCount: serverRuntime?.middleware.sanitizerCount ?? 0,
|
|
4438
|
+
preCtxCount: serverRuntime?.middleware.preCtxCount ?? 0,
|
|
4439
|
+
postCtxCount: serverRuntime?.middleware.postCtxCount ?? 0,
|
|
4440
|
+
beforeCount: 0
|
|
4441
|
+
},
|
|
4442
|
+
validateOutput: serverRuntime?.validateOutput ?? false,
|
|
4443
|
+
multipart: Boolean(contract.serializedLeaf.cfg.bodyFiles?.length),
|
|
4444
|
+
identityStability: "stable"
|
|
4445
|
+
} : void 0);
|
|
4446
|
+
const sockets = clients.flatMap(
|
|
4447
|
+
(client) => client.socketConnections.map((connection) => ({
|
|
4448
|
+
identity: connection.identity,
|
|
4449
|
+
clientBindingIdentity: client.bindingIdentity,
|
|
4450
|
+
events: connection.events,
|
|
4451
|
+
roomStrategy: connection.roomStrategy,
|
|
4452
|
+
cacheReducers: connection.cacheReducers,
|
|
4453
|
+
identityStability: connection.identityStability
|
|
4454
|
+
}))
|
|
4455
|
+
);
|
|
4456
|
+
routes[routeKey] = {
|
|
4457
|
+
routeKey,
|
|
4458
|
+
id: contract.serializedLeaf.id,
|
|
4459
|
+
method: contract.serializedLeaf.method.toUpperCase(),
|
|
4460
|
+
path: contract.serializedLeaf.path,
|
|
4461
|
+
contract,
|
|
4462
|
+
clients,
|
|
4463
|
+
server,
|
|
4464
|
+
sockets,
|
|
4465
|
+
status: {
|
|
4466
|
+
client: clients.length === 0 ? "unbound" : clients.some((client) => client.enabled) ? "bound" : "disabled",
|
|
4467
|
+
server: !args.serverSupplied ? "unavailable" : !server?.registered ? "missing" : server.enabled ? "enabled" : "disabled",
|
|
4468
|
+
socket: sockets.length > 0 ? "configured" : "none"
|
|
4469
|
+
}
|
|
4470
|
+
};
|
|
4471
|
+
}
|
|
4472
|
+
for (const routeKey of Object.keys(args.clients)) {
|
|
4473
|
+
if (args.contract[routeKey]) continue;
|
|
4474
|
+
args.diagnostics.push({
|
|
4475
|
+
code: "CLIENT_ROUTE_NOT_IN_CONTRACT_INPUT",
|
|
4476
|
+
severity: "warning",
|
|
4477
|
+
entity: { routeKey },
|
|
4478
|
+
message: "A client binding targets a route that was not present in allRoutes."
|
|
4479
|
+
});
|
|
4480
|
+
}
|
|
4481
|
+
for (const routeKey of Object.keys(args.server)) {
|
|
4482
|
+
if (args.contract[routeKey]) continue;
|
|
4483
|
+
args.diagnostics.push({
|
|
4484
|
+
code: "SERVER_ROUTE_NOT_IN_CONTRACT_INPUT",
|
|
4485
|
+
severity: "warning",
|
|
4486
|
+
entity: { routeKey },
|
|
4487
|
+
message: "A server controller targets a route that was not present in allRoutes."
|
|
4488
|
+
});
|
|
4489
|
+
}
|
|
4490
|
+
return routes;
|
|
4491
|
+
}
|
|
4492
|
+
|
|
4493
|
+
// src/inspection/normalize-routes.ts
|
|
4494
|
+
function hasAll(value) {
|
|
4495
|
+
return Boolean(
|
|
4496
|
+
value && typeof value === "object" && "all" in value && Array.isArray(value.all)
|
|
4497
|
+
);
|
|
4498
|
+
}
|
|
4499
|
+
function normalizeRoutesInput(input) {
|
|
4500
|
+
if (Array.isArray(input)) return input;
|
|
4501
|
+
if (hasAll(input)) return input.all;
|
|
4502
|
+
throw new TypeError(
|
|
4503
|
+
"allRoutes must be a finalized registry, RouteResource, or readonly leaf array."
|
|
4504
|
+
);
|
|
4505
|
+
}
|
|
4506
|
+
|
|
4507
|
+
// src/inspection/normalize-sockets.ts
|
|
4508
|
+
function normalizeSocketsInput(input) {
|
|
4509
|
+
if (!input) return [];
|
|
4510
|
+
if (Array.isArray(input)) {
|
|
4511
|
+
return input.map((value, index) => ({ value, index }));
|
|
4512
|
+
}
|
|
4513
|
+
if (readNativeInspection(input)?.kind === "socket-runtime") {
|
|
4514
|
+
return [{ value: input, index: 0 }];
|
|
4515
|
+
}
|
|
4516
|
+
if ("statsSnapshot" in input || "on" in input && typeof input.on === "function") {
|
|
4517
|
+
return [{ value: input, index: 0 }];
|
|
4518
|
+
}
|
|
4519
|
+
return Object.entries(input).map(([registeredName, value], index) => ({
|
|
4520
|
+
value,
|
|
4521
|
+
registeredName,
|
|
4522
|
+
index
|
|
4523
|
+
}));
|
|
4524
|
+
}
|
|
4525
|
+
|
|
4526
|
+
// src/snapshot/build.ts
|
|
4527
|
+
async function buildRRRoutesSnapshot(options, inspectOptions = {}) {
|
|
4528
|
+
const diagnostics = new InspectionDiagnostics();
|
|
4529
|
+
const leaves = normalizeRoutesInput(options.allRoutes);
|
|
4530
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4531
|
+
for (const leaf of leaves) {
|
|
4532
|
+
const routeKey = (0, import_rrroutes_contract5.routeKeyOf)(leaf.method, leaf.path);
|
|
4533
|
+
if (seen.has(routeKey)) {
|
|
4534
|
+
diagnostics.add({
|
|
4535
|
+
code: "DUPLICATE_ROUTE_KEY",
|
|
4536
|
+
severity: "error",
|
|
4537
|
+
entity: { routeKey },
|
|
4538
|
+
message: "Multiple contract leaves resolve to the same canonical method + path identity."
|
|
4539
|
+
});
|
|
4540
|
+
}
|
|
4541
|
+
seen.add(routeKey);
|
|
4542
|
+
}
|
|
4543
|
+
const includeSource = inspectOptions.includeSource ?? options.source?.include ?? false;
|
|
4544
|
+
const includeLive = inspectOptions.includeLive ?? false;
|
|
4545
|
+
const [contract, clients, server, sockets] = await Promise.all([
|
|
4546
|
+
inspectContractRoutes(leaves, options.source, includeSource),
|
|
4547
|
+
Promise.resolve(
|
|
4548
|
+
inspectClients(
|
|
4549
|
+
normalizeClientsInput(options.endpointClient, options.allClients),
|
|
4550
|
+
diagnostics
|
|
4551
|
+
)
|
|
4552
|
+
),
|
|
4553
|
+
Promise.resolve(inspectServer(options.server, diagnostics)),
|
|
4554
|
+
Promise.resolve(
|
|
4555
|
+
inspectSockets(
|
|
4556
|
+
normalizeSocketsInput(options.sockets),
|
|
4557
|
+
includeLive,
|
|
4558
|
+
diagnostics
|
|
4559
|
+
)
|
|
4560
|
+
)
|
|
4561
|
+
]);
|
|
4562
|
+
const diagnosticItems = diagnostics.list({
|
|
4563
|
+
includeInfo: options.diagnostics?.includeInfo
|
|
4564
|
+
});
|
|
4565
|
+
const routes = joinRouteInspections({
|
|
4566
|
+
contract: contract.routes,
|
|
4567
|
+
clients: clients.byRoute,
|
|
4568
|
+
server: server.byRoute,
|
|
4569
|
+
servers: server.servers,
|
|
4570
|
+
serverSupplied: Boolean(options.server),
|
|
4571
|
+
diagnostics: diagnosticItems
|
|
4572
|
+
});
|
|
4573
|
+
diagnosticItems.sort((left, right) => left.code.localeCompare(right.code));
|
|
4574
|
+
const snapshot = {
|
|
4575
|
+
schemaVersion: 1,
|
|
4576
|
+
meta: {
|
|
4577
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4578
|
+
snapshotHash: "",
|
|
4579
|
+
...options.meta,
|
|
4580
|
+
packageVersions: {},
|
|
4581
|
+
sourceExtraction: contract.sourceExtraction
|
|
4582
|
+
},
|
|
4583
|
+
capabilities: {
|
|
4584
|
+
contract: true,
|
|
4585
|
+
client: Boolean(options.endpointClient || options.allClients),
|
|
4586
|
+
server: Boolean(options.server),
|
|
4587
|
+
socket: Boolean(options.sockets),
|
|
4588
|
+
live: includeLive,
|
|
4589
|
+
source: includeSource
|
|
4590
|
+
},
|
|
4591
|
+
routes,
|
|
4592
|
+
clients: clients.clients,
|
|
4593
|
+
servers: server.servers,
|
|
4594
|
+
sockets,
|
|
4595
|
+
diagnostics: diagnosticItems
|
|
4596
|
+
};
|
|
4597
|
+
snapshot.meta.snapshotHash = hashRRRoutesSnapshot(snapshot);
|
|
4598
|
+
return snapshot;
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
// src/storage/memory.ts
|
|
4602
|
+
function clone(value) {
|
|
4603
|
+
return structuredClone(value);
|
|
4604
|
+
}
|
|
4605
|
+
function createMemoryInspectionStorage() {
|
|
4606
|
+
const checkpoints = /* @__PURE__ */ new Map();
|
|
4607
|
+
const activeByScope = /* @__PURE__ */ new Map();
|
|
4608
|
+
const changes = /* @__PURE__ */ new Map();
|
|
4609
|
+
return {
|
|
4610
|
+
checkpoints: {
|
|
4611
|
+
async get(id) {
|
|
4612
|
+
const value = checkpoints.get(id);
|
|
4613
|
+
return value ? clone(value) : void 0;
|
|
4614
|
+
},
|
|
4615
|
+
async getActive(scope) {
|
|
4616
|
+
const id = activeByScope.get(scope);
|
|
4617
|
+
const value = id ? checkpoints.get(id) : void 0;
|
|
4618
|
+
return value ? clone(value) : void 0;
|
|
4619
|
+
},
|
|
4620
|
+
async list(scope) {
|
|
4621
|
+
return Array.from(checkpoints.values()).filter((checkpoint) => !scope || checkpoint.scope === scope).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).map(
|
|
4622
|
+
(checkpoint) => ({
|
|
4623
|
+
id: checkpoint.id,
|
|
4624
|
+
scope: checkpoint.scope,
|
|
4625
|
+
createdAt: checkpoint.createdAt,
|
|
4626
|
+
updatedAt: checkpoint.updatedAt,
|
|
4627
|
+
snapshotHash: checkpoint.snapshotHash,
|
|
4628
|
+
trigger: clone(checkpoint.trigger),
|
|
4629
|
+
expiresAt: checkpoint.expiresAt,
|
|
4630
|
+
active: activeByScope.get(checkpoint.scope) === checkpoint.id
|
|
4631
|
+
})
|
|
4632
|
+
);
|
|
4633
|
+
},
|
|
4634
|
+
async save(checkpoint) {
|
|
4635
|
+
checkpoints.set(checkpoint.id, clone(checkpoint));
|
|
4636
|
+
},
|
|
4637
|
+
async setActive(scope, checkpointId) {
|
|
4638
|
+
const checkpoint = checkpoints.get(checkpointId);
|
|
4639
|
+
if (!checkpoint || checkpoint.scope !== scope) {
|
|
4640
|
+
throw new Error(
|
|
4641
|
+
`Checkpoint ${checkpointId} does not belong to scope ${scope}.`
|
|
4642
|
+
);
|
|
4643
|
+
}
|
|
4644
|
+
activeByScope.set(scope, checkpointId);
|
|
4645
|
+
},
|
|
4646
|
+
async delete(id) {
|
|
4647
|
+
checkpoints.delete(id);
|
|
4648
|
+
for (const [scope, activeId] of activeByScope) {
|
|
4649
|
+
if (activeId === id) activeByScope.delete(scope);
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
},
|
|
4653
|
+
changes: {
|
|
4654
|
+
async get(id) {
|
|
4655
|
+
const value = changes.get(id);
|
|
4656
|
+
return value ? clone(value) : void 0;
|
|
4657
|
+
},
|
|
4658
|
+
async findByTransition(scope, fromHash, toHash) {
|
|
4659
|
+
const value = Array.from(changes.values()).find(
|
|
4660
|
+
(changeSet) => changeSet.scope === scope && changeSet.from.snapshotHash === fromHash && changeSet.to.snapshotHash === toHash
|
|
4661
|
+
);
|
|
4662
|
+
return value ? clone(value) : void 0;
|
|
4663
|
+
},
|
|
4664
|
+
async list(query = {}) {
|
|
4665
|
+
const values = Array.from(changes.values()).filter(
|
|
4666
|
+
(changeSet) => !query.scope || changeSet.scope === query.scope
|
|
4667
|
+
).sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
4668
|
+
const selected = query.limit ? values.slice(0, query.limit) : values;
|
|
4669
|
+
return selected.map((changeSet) => {
|
|
4670
|
+
const { changes: _changes, ...summary } = changeSet;
|
|
4671
|
+
return clone(summary);
|
|
4672
|
+
});
|
|
4673
|
+
},
|
|
4674
|
+
async save(changeSet) {
|
|
4675
|
+
changes.set(changeSet.id, clone(changeSet));
|
|
4676
|
+
},
|
|
4677
|
+
async delete(id) {
|
|
4678
|
+
changes.delete(id);
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
};
|
|
4682
|
+
}
|
|
4683
|
+
|
|
4684
|
+
// src/checkpoint/lock.ts
|
|
4685
|
+
var _tails;
|
|
4686
|
+
var ScopeLock = class {
|
|
4687
|
+
constructor() {
|
|
4688
|
+
__privateAdd(this, _tails, /* @__PURE__ */ new Map());
|
|
4689
|
+
}
|
|
4690
|
+
async run(scope, action) {
|
|
4691
|
+
const previous = __privateGet(this, _tails).get(scope) ?? Promise.resolve();
|
|
4692
|
+
let release;
|
|
4693
|
+
const current = new Promise((resolve) => {
|
|
4694
|
+
release = resolve;
|
|
4695
|
+
});
|
|
4696
|
+
__privateGet(this, _tails).set(scope, current);
|
|
4697
|
+
await previous;
|
|
4698
|
+
try {
|
|
4699
|
+
return await action();
|
|
4700
|
+
} finally {
|
|
4701
|
+
release();
|
|
4702
|
+
if (__privateGet(this, _tails).get(scope) === current) __privateGet(this, _tails).delete(scope);
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
};
|
|
4706
|
+
_tails = new WeakMap();
|
|
4707
|
+
|
|
4708
|
+
// src/checkpoint/retention.ts
|
|
4709
|
+
async function applyCheckpointRetention(args) {
|
|
4710
|
+
if (!args.retention) return;
|
|
4711
|
+
const summaries = await args.storage.checkpoints.list(args.scope);
|
|
4712
|
+
const now = args.now ?? Date.now();
|
|
4713
|
+
const deletions = /* @__PURE__ */ new Set();
|
|
4714
|
+
if (args.retention.type === "duration" || args.retention.type === "combined") {
|
|
4715
|
+
for (const checkpoint of summaries) {
|
|
4716
|
+
if (checkpoint.id !== args.activeId && now - Date.parse(checkpoint.updatedAt) > args.retention.maximumAgeMs) {
|
|
4717
|
+
deletions.add(checkpoint.id);
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
if (args.retention.type === "count" || args.retention.type === "combined") {
|
|
4722
|
+
const retained = summaries.filter((item) => !deletions.has(item.id));
|
|
4723
|
+
for (const checkpoint of retained.slice(args.retention.maximum)) {
|
|
4724
|
+
if (checkpoint.id !== args.activeId) deletions.add(checkpoint.id);
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
await Promise.all(
|
|
4728
|
+
Array.from(deletions).map((id) => args.storage.checkpoints.delete(id))
|
|
4729
|
+
);
|
|
4730
|
+
}
|
|
4731
|
+
async function applyChangeSetRetention(args) {
|
|
4732
|
+
if (args.retention.type === "forever" || !args.storage.changes.delete) return;
|
|
4733
|
+
const summaries = await args.storage.changes.list({ scope: args.scope });
|
|
4734
|
+
const now = args.now ?? Date.now();
|
|
4735
|
+
const deletions = args.retention.type === "count" ? summaries.slice(args.retention.maximum) : (() => {
|
|
4736
|
+
const maximumAgeMs = args.retention.maximumAgeMs;
|
|
4737
|
+
return summaries.filter(
|
|
4738
|
+
(item) => now - Date.parse(item.createdAt) > maximumAgeMs
|
|
4739
|
+
);
|
|
4740
|
+
})();
|
|
4741
|
+
await Promise.all(
|
|
4742
|
+
deletions.map((item) => args.storage.changes.delete(item.id))
|
|
4743
|
+
);
|
|
4744
|
+
}
|
|
4745
|
+
|
|
4746
|
+
// src/checkpoint/service.ts
|
|
4747
|
+
function createCheckpointService(options) {
|
|
4748
|
+
const storage = options.storage ?? createMemoryInspectionStorage();
|
|
4749
|
+
const lock = new ScopeLock();
|
|
4750
|
+
const defaultScope = options.scope ?? "default";
|
|
4751
|
+
const transaction = (operation) => storage.transaction ? storage.transaction(operation) : operation();
|
|
4752
|
+
const createRecord = (scope, snapshot, trigger) => {
|
|
4753
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4754
|
+
return {
|
|
4755
|
+
id: hashCanonicalValue({
|
|
4756
|
+
scope,
|
|
4757
|
+
snapshotHash: snapshot.meta.snapshotHash,
|
|
4758
|
+
now
|
|
4759
|
+
}),
|
|
4760
|
+
scope,
|
|
4761
|
+
createdAt: now,
|
|
4762
|
+
updatedAt: now,
|
|
4763
|
+
snapshotHash: snapshot.meta.snapshotHash,
|
|
4764
|
+
snapshot,
|
|
4765
|
+
trigger
|
|
4766
|
+
};
|
|
4767
|
+
};
|
|
4768
|
+
const service = {
|
|
4769
|
+
async advance(input = {}) {
|
|
4770
|
+
const scope = input.scope ?? defaultScope;
|
|
4771
|
+
const trigger = input.trigger ?? { type: "manual" };
|
|
4772
|
+
return lock.run(scope, () => transaction(async () => {
|
|
4773
|
+
if (trigger.externalId) {
|
|
4774
|
+
const priorSummary = (await storage.checkpoints.list(scope)).find(
|
|
4775
|
+
(checkpoint2) => checkpoint2.trigger.externalId === trigger.externalId
|
|
4776
|
+
);
|
|
4777
|
+
if (priorSummary) {
|
|
4778
|
+
const checkpoint2 = await storage.checkpoints.get(priorSummary.id);
|
|
4779
|
+
if (checkpoint2) {
|
|
4780
|
+
const changeSummary = (await storage.changes.list({ scope })).find(
|
|
4781
|
+
(changeSet3) => changeSet3.trigger.externalId === trigger.externalId
|
|
4782
|
+
);
|
|
4783
|
+
const changeSet2 = changeSummary ? await storage.changes.get(changeSummary.id) : void 0;
|
|
4784
|
+
return {
|
|
4785
|
+
status: "idempotent_replay",
|
|
4786
|
+
checkpoint: checkpoint2,
|
|
4787
|
+
changeSet: changeSet2
|
|
4788
|
+
};
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
const current = await options.inspect();
|
|
4793
|
+
const active = await storage.checkpoints.getActive(scope);
|
|
4794
|
+
if (!active) {
|
|
4795
|
+
const checkpoint2 = createRecord(scope, current, trigger);
|
|
4796
|
+
await storage.checkpoints.save(checkpoint2);
|
|
4797
|
+
await storage.checkpoints.setActive(scope, checkpoint2.id);
|
|
4798
|
+
return { status: "baseline_created", checkpoint: checkpoint2 };
|
|
4799
|
+
}
|
|
4800
|
+
if (active.snapshotHash === current.meta.snapshotHash) {
|
|
4801
|
+
return { status: "no_changes", checkpoint: active };
|
|
4802
|
+
}
|
|
4803
|
+
const duplicate = await storage.changes.findByTransition(
|
|
4804
|
+
scope,
|
|
4805
|
+
active.snapshotHash,
|
|
4806
|
+
current.meta.snapshotHash
|
|
4807
|
+
);
|
|
4808
|
+
const checkpoint = createRecord(scope, current, trigger);
|
|
4809
|
+
const changeSet = duplicate ?? diffRRRoutesSnapshots(active.snapshot, current, {
|
|
4810
|
+
scope,
|
|
4811
|
+
trigger,
|
|
4812
|
+
fromCheckpointId: active.id,
|
|
4813
|
+
toCheckpointId: checkpoint.id
|
|
4814
|
+
});
|
|
4815
|
+
if (!duplicate) await storage.changes.save(changeSet);
|
|
4816
|
+
await storage.checkpoints.save(checkpoint);
|
|
4817
|
+
await storage.checkpoints.setActive(scope, checkpoint.id);
|
|
4818
|
+
await applyCheckpointRetention({
|
|
4819
|
+
storage,
|
|
4820
|
+
scope,
|
|
4821
|
+
activeId: checkpoint.id,
|
|
4822
|
+
retention: options.retention
|
|
4823
|
+
});
|
|
4824
|
+
await applyChangeSetRetention({
|
|
4825
|
+
storage,
|
|
4826
|
+
scope,
|
|
4827
|
+
retention: options.changeSetRetention ?? { type: "forever" }
|
|
4828
|
+
});
|
|
4829
|
+
return { status: "advanced", checkpoint, changeSet };
|
|
4830
|
+
}));
|
|
4831
|
+
},
|
|
4832
|
+
async create(input = {}) {
|
|
4833
|
+
const scope = input.scope ?? defaultScope;
|
|
4834
|
+
const trigger = input.trigger ?? { type: "manual" };
|
|
4835
|
+
return lock.run(scope, () => transaction(async () => {
|
|
4836
|
+
const checkpoint = createRecord(scope, await options.inspect(), trigger);
|
|
4837
|
+
await storage.checkpoints.save(checkpoint);
|
|
4838
|
+
await storage.checkpoints.setActive(scope, checkpoint.id);
|
|
4839
|
+
await applyCheckpointRetention({
|
|
4840
|
+
storage,
|
|
4841
|
+
scope,
|
|
4842
|
+
activeId: checkpoint.id,
|
|
4843
|
+
retention: options.retention
|
|
4844
|
+
});
|
|
4845
|
+
return checkpoint;
|
|
4846
|
+
}));
|
|
4847
|
+
},
|
|
4848
|
+
get: (id) => storage.checkpoints.get(id),
|
|
4849
|
+
getActive: (scope = defaultScope) => storage.checkpoints.getActive(scope),
|
|
4850
|
+
list: (scope) => storage.checkpoints.list(scope),
|
|
4851
|
+
async delete(id) {
|
|
4852
|
+
const checkpoint = await storage.checkpoints.get(id);
|
|
4853
|
+
if (!checkpoint) return;
|
|
4854
|
+
const active = await storage.checkpoints.getActive(checkpoint.scope);
|
|
4855
|
+
if (active?.id === id) {
|
|
4856
|
+
throw new Error("The active checkpoint cannot be deleted.");
|
|
4857
|
+
}
|
|
4858
|
+
await storage.checkpoints.delete(id);
|
|
4859
|
+
},
|
|
4860
|
+
listChangeSets: (scope) => storage.changes.list({ scope }),
|
|
4861
|
+
getChangeSet: (id) => storage.changes.get(id)
|
|
4862
|
+
};
|
|
4863
|
+
return service;
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4866
|
+
// src/storage/filesystem.ts
|
|
4867
|
+
var import_node_crypto3 = require("crypto");
|
|
4868
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
4869
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
4870
|
+
function encodeSegment(value) {
|
|
4871
|
+
return Buffer.from(value, "utf8").toString("base64url");
|
|
4872
|
+
}
|
|
4873
|
+
async function exists(file) {
|
|
4874
|
+
try {
|
|
4875
|
+
await import_promises4.default.access(file);
|
|
4876
|
+
return true;
|
|
4877
|
+
} catch {
|
|
4878
|
+
return false;
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4881
|
+
async function atomicWriteJson(file, value) {
|
|
4882
|
+
const directory = import_node_path7.default.dirname(file);
|
|
4883
|
+
await import_promises4.default.mkdir(directory, { recursive: true });
|
|
4884
|
+
const temporary = import_node_path7.default.join(
|
|
4885
|
+
directory,
|
|
4886
|
+
`.${import_node_path7.default.basename(file)}.${(0, import_node_crypto3.randomUUID)()}.tmp`
|
|
4887
|
+
);
|
|
4888
|
+
const handle = await import_promises4.default.open(temporary, "wx");
|
|
4889
|
+
try {
|
|
4890
|
+
await handle.writeFile(`${JSON.stringify(value, null, 2)}
|
|
4891
|
+
`, "utf8");
|
|
4892
|
+
await handle.sync();
|
|
4893
|
+
} finally {
|
|
4894
|
+
await handle.close();
|
|
4895
|
+
}
|
|
4896
|
+
await import_promises4.default.rename(temporary, file);
|
|
4897
|
+
try {
|
|
4898
|
+
const directoryHandle = await import_promises4.default.open(directory, "r");
|
|
4899
|
+
try {
|
|
4900
|
+
await directoryHandle.sync();
|
|
4901
|
+
} finally {
|
|
4902
|
+
await directoryHandle.close();
|
|
4903
|
+
}
|
|
4904
|
+
} catch {
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
async function readJson(file) {
|
|
4908
|
+
try {
|
|
4909
|
+
return JSON.parse(await import_promises4.default.readFile(file, "utf8"));
|
|
4910
|
+
} catch (error) {
|
|
4911
|
+
if (error.code === "ENOENT") return void 0;
|
|
4912
|
+
throw error;
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
async function listJsonFiles(directory) {
|
|
4916
|
+
if (!await exists(directory)) return [];
|
|
4917
|
+
const out = [];
|
|
4918
|
+
const visit = async (current) => {
|
|
4919
|
+
for (const entry of await import_promises4.default.readdir(current, { withFileTypes: true })) {
|
|
4920
|
+
const resolved = import_node_path7.default.join(current, entry.name);
|
|
4921
|
+
if (entry.isDirectory()) await visit(resolved);
|
|
4922
|
+
else if (entry.isFile() && entry.name.endsWith(".json"))
|
|
4923
|
+
out.push(resolved);
|
|
4924
|
+
}
|
|
4925
|
+
};
|
|
4926
|
+
await visit(directory);
|
|
4927
|
+
return out.sort();
|
|
4928
|
+
}
|
|
4929
|
+
function createFileSystemInspectionStorage(options) {
|
|
4930
|
+
const root = import_node_path7.default.resolve(options.directory);
|
|
4931
|
+
const checkpointRoot = import_node_path7.default.join(root, "checkpoints");
|
|
4932
|
+
const changeRoot = import_node_path7.default.join(root, "changes");
|
|
4933
|
+
const checkpointFile = (scope, id) => import_node_path7.default.join(
|
|
4934
|
+
checkpointRoot,
|
|
4935
|
+
encodeSegment(scope),
|
|
4936
|
+
`checkpoint-${encodeSegment(id)}.json`
|
|
4937
|
+
);
|
|
4938
|
+
const activeFile = (scope) => import_node_path7.default.join(checkpointRoot, encodeSegment(scope), "active.json");
|
|
4939
|
+
const changeFile = (changeSet) => {
|
|
4940
|
+
const date = new Date(changeSet.createdAt);
|
|
4941
|
+
const year = String(date.getUTCFullYear()).padStart(4, "0");
|
|
4942
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
4943
|
+
return import_node_path7.default.join(
|
|
4944
|
+
changeRoot,
|
|
4945
|
+
encodeSegment(changeSet.scope),
|
|
4946
|
+
year,
|
|
4947
|
+
month,
|
|
4948
|
+
`${encodeSegment(changeSet.id)}.json`
|
|
4949
|
+
);
|
|
4950
|
+
};
|
|
4951
|
+
const findCheckpoint = async (id) => {
|
|
4952
|
+
const files = (await listJsonFiles(checkpointRoot)).filter(
|
|
4953
|
+
(file) => import_node_path7.default.basename(file) !== "active.json"
|
|
4954
|
+
);
|
|
4955
|
+
for (const file of files) {
|
|
4956
|
+
const checkpoint = await readJson(file);
|
|
4957
|
+
if (checkpoint?.id === id) return checkpoint;
|
|
4958
|
+
}
|
|
4959
|
+
return void 0;
|
|
4960
|
+
};
|
|
4961
|
+
const findChangeSet = async (id) => {
|
|
4962
|
+
for (const file of await listJsonFiles(changeRoot)) {
|
|
4963
|
+
const changeSet = await readJson(file);
|
|
4964
|
+
if (changeSet?.id === id) return changeSet;
|
|
4965
|
+
}
|
|
4966
|
+
return void 0;
|
|
4967
|
+
};
|
|
4968
|
+
return {
|
|
4969
|
+
checkpoints: {
|
|
4970
|
+
get: findCheckpoint,
|
|
4971
|
+
async getActive(scope) {
|
|
4972
|
+
const pointer = await readJson(
|
|
4973
|
+
activeFile(scope)
|
|
4974
|
+
);
|
|
4975
|
+
return pointer ? findCheckpoint(pointer.checkpointId) : void 0;
|
|
4976
|
+
},
|
|
4977
|
+
async list(scope) {
|
|
4978
|
+
const files = (await listJsonFiles(checkpointRoot)).filter(
|
|
4979
|
+
(file) => import_node_path7.default.basename(file) !== "active.json"
|
|
4980
|
+
);
|
|
4981
|
+
const activeIds = /* @__PURE__ */ new Map();
|
|
4982
|
+
const values = [];
|
|
4983
|
+
for (const file of files) {
|
|
4984
|
+
const checkpoint = await readJson(file);
|
|
4985
|
+
if (!checkpoint || scope && checkpoint.scope !== scope) continue;
|
|
4986
|
+
values.push(checkpoint);
|
|
4987
|
+
if (!activeIds.has(checkpoint.scope)) {
|
|
4988
|
+
const pointer = await readJson(
|
|
4989
|
+
activeFile(checkpoint.scope)
|
|
4990
|
+
);
|
|
4991
|
+
if (pointer) activeIds.set(checkpoint.scope, pointer.checkpointId);
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
return values.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).map((checkpoint) => {
|
|
4995
|
+
const { snapshot: _snapshot, ...summary } = checkpoint;
|
|
4996
|
+
return {
|
|
4997
|
+
...summary,
|
|
4998
|
+
active: activeIds.get(checkpoint.scope) === checkpoint.id
|
|
4999
|
+
};
|
|
5000
|
+
});
|
|
5001
|
+
},
|
|
5002
|
+
async save(checkpoint) {
|
|
5003
|
+
await atomicWriteJson(
|
|
5004
|
+
checkpointFile(checkpoint.scope, checkpoint.id),
|
|
5005
|
+
checkpoint
|
|
5006
|
+
);
|
|
5007
|
+
},
|
|
5008
|
+
async setActive(scope, checkpointId) {
|
|
5009
|
+
const checkpoint = await findCheckpoint(checkpointId);
|
|
5010
|
+
if (!checkpoint || checkpoint.scope !== scope) {
|
|
5011
|
+
throw new Error(
|
|
5012
|
+
`Checkpoint ${checkpointId} does not belong to scope ${scope}.`
|
|
5013
|
+
);
|
|
5014
|
+
}
|
|
5015
|
+
await atomicWriteJson(activeFile(scope), { checkpointId });
|
|
5016
|
+
},
|
|
5017
|
+
async delete(id) {
|
|
5018
|
+
const checkpoint = await findCheckpoint(id);
|
|
5019
|
+
if (!checkpoint) return;
|
|
5020
|
+
const file = checkpointFile(checkpoint.scope, checkpoint.id);
|
|
5021
|
+
await import_promises4.default.unlink(file).catch((error) => {
|
|
5022
|
+
if (error.code !== "ENOENT") throw error;
|
|
5023
|
+
});
|
|
5024
|
+
}
|
|
5025
|
+
},
|
|
5026
|
+
changes: {
|
|
5027
|
+
get: findChangeSet,
|
|
5028
|
+
async findByTransition(scope, fromHash, toHash) {
|
|
5029
|
+
for (const file of await listJsonFiles(
|
|
5030
|
+
import_node_path7.default.join(changeRoot, encodeSegment(scope))
|
|
5031
|
+
)) {
|
|
5032
|
+
const changeSet = await readJson(file);
|
|
5033
|
+
if (changeSet?.scope === scope && changeSet.from.snapshotHash === fromHash && changeSet.to.snapshotHash === toHash) {
|
|
5034
|
+
return changeSet;
|
|
5035
|
+
}
|
|
5036
|
+
}
|
|
5037
|
+
return void 0;
|
|
5038
|
+
},
|
|
5039
|
+
async list(query = {}) {
|
|
5040
|
+
const directory = query.scope ? import_node_path7.default.join(changeRoot, encodeSegment(query.scope)) : changeRoot;
|
|
5041
|
+
const values = [];
|
|
5042
|
+
for (const file of await listJsonFiles(directory)) {
|
|
5043
|
+
const changeSet = await readJson(file);
|
|
5044
|
+
if (changeSet && (!query.scope || changeSet.scope === query.scope)) {
|
|
5045
|
+
values.push(changeSet);
|
|
5046
|
+
}
|
|
5047
|
+
}
|
|
5048
|
+
const selected = values.sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, query.limit ?? values.length);
|
|
5049
|
+
return selected.map((changeSet) => {
|
|
5050
|
+
const { changes: _changes, ...summary } = changeSet;
|
|
5051
|
+
return summary;
|
|
5052
|
+
});
|
|
5053
|
+
},
|
|
5054
|
+
async save(changeSet) {
|
|
5055
|
+
const existing = await this.findByTransition(
|
|
5056
|
+
changeSet.scope,
|
|
5057
|
+
changeSet.from.snapshotHash,
|
|
5058
|
+
changeSet.to.snapshotHash
|
|
5059
|
+
);
|
|
5060
|
+
if (existing) return;
|
|
5061
|
+
await atomicWriteJson(changeFile(changeSet), changeSet);
|
|
5062
|
+
},
|
|
5063
|
+
async delete(id) {
|
|
5064
|
+
const changeSet = await findChangeSet(id);
|
|
5065
|
+
if (!changeSet) return;
|
|
5066
|
+
await import_promises4.default.unlink(changeFile(changeSet)).catch((error) => {
|
|
5067
|
+
if (error.code !== "ENOENT") throw error;
|
|
5068
|
+
});
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
};
|
|
5072
|
+
}
|
|
5073
|
+
|
|
5074
|
+
// src/storage/sqlite.ts
|
|
5075
|
+
function readRow(row) {
|
|
5076
|
+
if (!row || typeof row !== "object" || typeof row.json !== "string") {
|
|
5077
|
+
return void 0;
|
|
5078
|
+
}
|
|
5079
|
+
return JSON.parse(row.json);
|
|
5080
|
+
}
|
|
5081
|
+
function readRows(rows) {
|
|
5082
|
+
return Array.isArray(rows) ? rows.flatMap((row) => {
|
|
5083
|
+
const value = readRow(row);
|
|
5084
|
+
return value ? [value] : [];
|
|
5085
|
+
}) : [];
|
|
5086
|
+
}
|
|
5087
|
+
function createSQLiteInspectionStorage(options) {
|
|
5088
|
+
const database = options.database;
|
|
5089
|
+
database.exec(`
|
|
5090
|
+
PRAGMA foreign_keys = ON;
|
|
5091
|
+
CREATE TABLE IF NOT EXISTS rrroutes_checkpoints (
|
|
5092
|
+
id TEXT PRIMARY KEY,
|
|
5093
|
+
scope TEXT NOT NULL,
|
|
5094
|
+
updated_at TEXT NOT NULL,
|
|
5095
|
+
json TEXT NOT NULL
|
|
5096
|
+
);
|
|
5097
|
+
CREATE INDEX IF NOT EXISTS rrroutes_checkpoints_scope_updated
|
|
5098
|
+
ON rrroutes_checkpoints(scope, updated_at DESC);
|
|
5099
|
+
CREATE TABLE IF NOT EXISTS rrroutes_active_checkpoints (
|
|
5100
|
+
scope TEXT PRIMARY KEY,
|
|
5101
|
+
checkpoint_id TEXT NOT NULL REFERENCES rrroutes_checkpoints(id)
|
|
5102
|
+
);
|
|
5103
|
+
CREATE TABLE IF NOT EXISTS rrroutes_change_sets (
|
|
5104
|
+
id TEXT PRIMARY KEY,
|
|
5105
|
+
scope TEXT NOT NULL,
|
|
5106
|
+
from_hash TEXT NOT NULL,
|
|
5107
|
+
to_hash TEXT NOT NULL,
|
|
5108
|
+
created_at TEXT NOT NULL,
|
|
5109
|
+
json TEXT NOT NULL,
|
|
5110
|
+
UNIQUE(scope, from_hash, to_hash)
|
|
5111
|
+
);
|
|
5112
|
+
CREATE INDEX IF NOT EXISTS rrroutes_changes_scope_created
|
|
5113
|
+
ON rrroutes_change_sets(scope, created_at DESC);
|
|
5114
|
+
`);
|
|
5115
|
+
const getCheckpoint = database.prepare(
|
|
5116
|
+
"SELECT json FROM rrroutes_checkpoints WHERE id = ?"
|
|
5117
|
+
);
|
|
5118
|
+
const storage = {
|
|
5119
|
+
async transaction(operation) {
|
|
5120
|
+
database.exec("BEGIN IMMEDIATE");
|
|
5121
|
+
try {
|
|
5122
|
+
const result = await operation();
|
|
5123
|
+
database.exec("COMMIT");
|
|
5124
|
+
return result;
|
|
5125
|
+
} catch (error) {
|
|
5126
|
+
database.exec("ROLLBACK");
|
|
5127
|
+
throw error;
|
|
5128
|
+
}
|
|
5129
|
+
},
|
|
5130
|
+
checkpoints: {
|
|
5131
|
+
async get(id) {
|
|
5132
|
+
return readRow(getCheckpoint.get(id));
|
|
5133
|
+
},
|
|
5134
|
+
async getActive(scope) {
|
|
5135
|
+
const statement = database.prepare(`
|
|
5136
|
+
SELECT checkpoint.json AS json
|
|
5137
|
+
FROM rrroutes_active_checkpoints active
|
|
5138
|
+
JOIN rrroutes_checkpoints checkpoint ON checkpoint.id = active.checkpoint_id
|
|
5139
|
+
WHERE active.scope = ?
|
|
5140
|
+
`);
|
|
5141
|
+
return readRow(statement.get(scope));
|
|
5142
|
+
},
|
|
5143
|
+
async list(scope) {
|
|
5144
|
+
const rows = scope ? database.prepare(
|
|
5145
|
+
"SELECT json FROM rrroutes_checkpoints WHERE scope = ? ORDER BY updated_at DESC"
|
|
5146
|
+
).all(scope) : database.prepare(
|
|
5147
|
+
"SELECT json FROM rrroutes_checkpoints ORDER BY updated_at DESC"
|
|
5148
|
+
).all();
|
|
5149
|
+
const activeRows = database.prepare(
|
|
5150
|
+
"SELECT scope, checkpoint_id FROM rrroutes_active_checkpoints"
|
|
5151
|
+
).all();
|
|
5152
|
+
const active = new Map(
|
|
5153
|
+
Array.isArray(activeRows) ? activeRows.map((row) => [row.scope, row.checkpoint_id]) : []
|
|
5154
|
+
);
|
|
5155
|
+
return readRows(rows).map(
|
|
5156
|
+
(checkpoint) => {
|
|
5157
|
+
const { snapshot: _snapshot, ...summary } = checkpoint;
|
|
5158
|
+
return {
|
|
5159
|
+
...summary,
|
|
5160
|
+
active: active.get(checkpoint.scope) === checkpoint.id
|
|
5161
|
+
};
|
|
5162
|
+
}
|
|
5163
|
+
);
|
|
5164
|
+
},
|
|
5165
|
+
async save(checkpoint) {
|
|
5166
|
+
database.prepare(
|
|
5167
|
+
`
|
|
5168
|
+
INSERT INTO rrroutes_checkpoints(id, scope, updated_at, json)
|
|
5169
|
+
VALUES (?, ?, ?, ?)
|
|
5170
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
5171
|
+
scope = excluded.scope,
|
|
5172
|
+
updated_at = excluded.updated_at,
|
|
5173
|
+
json = excluded.json
|
|
5174
|
+
`
|
|
5175
|
+
).run(
|
|
5176
|
+
checkpoint.id,
|
|
5177
|
+
checkpoint.scope,
|
|
5178
|
+
checkpoint.updatedAt,
|
|
5179
|
+
JSON.stringify(checkpoint)
|
|
5180
|
+
);
|
|
5181
|
+
},
|
|
5182
|
+
async setActive(scope, checkpointId) {
|
|
5183
|
+
const checkpoint = readRow(
|
|
5184
|
+
getCheckpoint.get(checkpointId)
|
|
5185
|
+
);
|
|
5186
|
+
if (!checkpoint || checkpoint.scope !== scope) {
|
|
5187
|
+
throw new Error(
|
|
5188
|
+
`Checkpoint ${checkpointId} does not belong to scope ${scope}.`
|
|
5189
|
+
);
|
|
5190
|
+
}
|
|
5191
|
+
database.prepare(
|
|
5192
|
+
`
|
|
5193
|
+
INSERT INTO rrroutes_active_checkpoints(scope, checkpoint_id)
|
|
5194
|
+
VALUES (?, ?)
|
|
5195
|
+
ON CONFLICT(scope) DO UPDATE SET checkpoint_id = excluded.checkpoint_id
|
|
5196
|
+
`
|
|
5197
|
+
).run(scope, checkpointId);
|
|
5198
|
+
},
|
|
5199
|
+
async delete(id) {
|
|
5200
|
+
database.prepare("DELETE FROM rrroutes_checkpoints WHERE id = ?").run(id);
|
|
5201
|
+
}
|
|
5202
|
+
},
|
|
5203
|
+
changes: {
|
|
5204
|
+
async get(id) {
|
|
5205
|
+
return readRow(
|
|
5206
|
+
database.prepare("SELECT json FROM rrroutes_change_sets WHERE id = ?").get(id)
|
|
5207
|
+
);
|
|
5208
|
+
},
|
|
5209
|
+
async findByTransition(scope, fromHash, toHash) {
|
|
5210
|
+
return readRow(
|
|
5211
|
+
database.prepare(
|
|
5212
|
+
`
|
|
5213
|
+
SELECT json FROM rrroutes_change_sets
|
|
5214
|
+
WHERE scope = ? AND from_hash = ? AND to_hash = ?
|
|
5215
|
+
`
|
|
5216
|
+
).get(scope, fromHash, toHash)
|
|
5217
|
+
);
|
|
5218
|
+
},
|
|
5219
|
+
async list(query = {}) {
|
|
5220
|
+
const rows = query.scope ? database.prepare(
|
|
5221
|
+
`
|
|
5222
|
+
SELECT json FROM rrroutes_change_sets
|
|
5223
|
+
WHERE scope = ? ORDER BY created_at DESC LIMIT ?
|
|
5224
|
+
`
|
|
5225
|
+
).all(query.scope, query.limit ?? -1) : database.prepare(
|
|
5226
|
+
`
|
|
5227
|
+
SELECT json FROM rrroutes_change_sets
|
|
5228
|
+
ORDER BY created_at DESC LIMIT ?
|
|
5229
|
+
`
|
|
5230
|
+
).all(query.limit ?? -1);
|
|
5231
|
+
return readRows(rows).map(
|
|
5232
|
+
(changeSet) => {
|
|
5233
|
+
const { changes: _changes, ...summary } = changeSet;
|
|
5234
|
+
return summary;
|
|
5235
|
+
}
|
|
5236
|
+
);
|
|
5237
|
+
},
|
|
5238
|
+
async save(changeSet) {
|
|
5239
|
+
database.prepare(
|
|
5240
|
+
`
|
|
5241
|
+
INSERT OR IGNORE INTO rrroutes_change_sets(
|
|
5242
|
+
id, scope, from_hash, to_hash, created_at, json
|
|
5243
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
5244
|
+
`
|
|
5245
|
+
).run(
|
|
5246
|
+
changeSet.id,
|
|
5247
|
+
changeSet.scope,
|
|
5248
|
+
changeSet.from.snapshotHash,
|
|
5249
|
+
changeSet.to.snapshotHash,
|
|
5250
|
+
changeSet.createdAt,
|
|
5251
|
+
JSON.stringify(changeSet)
|
|
5252
|
+
);
|
|
5253
|
+
},
|
|
5254
|
+
async delete(id) {
|
|
5255
|
+
database.prepare("DELETE FROM rrroutes_change_sets WHERE id = ?").run(id);
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5258
|
+
};
|
|
5259
|
+
return storage;
|
|
5260
|
+
}
|
|
5261
|
+
|
|
5262
|
+
// src/http/handler.ts
|
|
5263
|
+
function json(value, status = 200) {
|
|
5264
|
+
return Response.json(value, {
|
|
5265
|
+
status,
|
|
5266
|
+
headers: {
|
|
5267
|
+
"cache-control": "no-store, max-age=0",
|
|
5268
|
+
"content-type": "application/json; charset=utf-8",
|
|
5269
|
+
"x-content-type-options": "nosniff"
|
|
5270
|
+
}
|
|
5271
|
+
});
|
|
5272
|
+
}
|
|
5273
|
+
async function overview(snapshot, runtime) {
|
|
5274
|
+
const routes = Object.values(snapshot.routes);
|
|
5275
|
+
const clientBindings = Object.values(snapshot.clients).flatMap(
|
|
5276
|
+
(client) => client.bindings
|
|
5277
|
+
);
|
|
5278
|
+
const [activeCheckpoint, latestChangeSet] = await Promise.all([
|
|
5279
|
+
runtime.checkpoints.getActive(),
|
|
5280
|
+
runtime.checkpoints.listChangeSets().then((items) => items[0])
|
|
5281
|
+
]);
|
|
5282
|
+
return {
|
|
5283
|
+
totalRoutes: routes.length,
|
|
5284
|
+
contractOnlyRoutes: routes.filter(
|
|
5285
|
+
(route) => route.clients.length === 0 && route.status.server !== "enabled"
|
|
5286
|
+
).length,
|
|
5287
|
+
clientBoundRoutes: routes.filter((route) => route.clients.length > 0).length,
|
|
5288
|
+
serverRegisteredRoutes: routes.filter((route) => route.server?.registered).length,
|
|
5289
|
+
disabledServerRoutes: routes.filter(
|
|
5290
|
+
(route) => route.server?.registered && !route.server.enabled
|
|
5291
|
+
).length,
|
|
5292
|
+
routesMissingServerController: routes.filter(
|
|
5293
|
+
(route) => route.status.server === "missing"
|
|
5294
|
+
).length,
|
|
5295
|
+
clientBindings: clientBindings.length,
|
|
5296
|
+
augments: clientBindings.reduce(
|
|
5297
|
+
(total, binding) => total + binding.augments.length,
|
|
5298
|
+
0
|
|
5299
|
+
),
|
|
5300
|
+
routeSocketConnections: clientBindings.reduce(
|
|
5301
|
+
(total, binding) => total + binding.socketConnections.length,
|
|
5302
|
+
0
|
|
5303
|
+
),
|
|
5304
|
+
socketContractEvents: Object.values(snapshot.sockets).reduce(
|
|
5305
|
+
(total, socket) => total + Object.keys(socket.contract.events).length,
|
|
5306
|
+
0
|
|
5307
|
+
),
|
|
5308
|
+
serverBeforeMiddleware: routes.reduce(
|
|
5309
|
+
(total, route) => total + (route.server?.before.length ?? 0),
|
|
5310
|
+
0
|
|
5311
|
+
),
|
|
5312
|
+
activeCheckpoint: activeCheckpoint ? {
|
|
5313
|
+
id: activeCheckpoint.id,
|
|
5314
|
+
scope: activeCheckpoint.scope,
|
|
5315
|
+
snapshotHash: activeCheckpoint.snapshotHash,
|
|
5316
|
+
updatedAt: activeCheckpoint.updatedAt
|
|
5317
|
+
} : void 0,
|
|
5318
|
+
latestChangeSet,
|
|
5319
|
+
diagnostics: snapshot.diagnostics.length
|
|
5320
|
+
};
|
|
5321
|
+
}
|
|
5322
|
+
async function readMutationBody(request, maximum) {
|
|
5323
|
+
const contentLength = Number(request.headers.get("content-length") ?? "0");
|
|
5324
|
+
if (contentLength > maximum) throw new Error("REQUEST_BODY_TOO_LARGE");
|
|
5325
|
+
const text = await request.text();
|
|
5326
|
+
if (new TextEncoder().encode(text).byteLength > maximum) {
|
|
5327
|
+
throw new Error("REQUEST_BODY_TOO_LARGE");
|
|
5328
|
+
}
|
|
5329
|
+
if (!text) return {};
|
|
5330
|
+
const value = JSON.parse(text);
|
|
5331
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5332
|
+
throw new Error("REQUEST_BODY_INVALID");
|
|
5333
|
+
}
|
|
5334
|
+
return value;
|
|
5335
|
+
}
|
|
5336
|
+
function createInspectorHttpHandler(runtime, options = {}) {
|
|
5337
|
+
const mutationOptions = options.mutations;
|
|
5338
|
+
const authorizeRead = async (request) => !options.authorize || await options.authorize({ request, action: "read" });
|
|
5339
|
+
const authorizeMutation = async (request, action) => mutationOptions !== false && mutationOptions?.enabled === true && (!mutationOptions.authorize || await mutationOptions.authorize({ request, action }));
|
|
5340
|
+
return async (request) => {
|
|
5341
|
+
try {
|
|
5342
|
+
if (!await authorizeRead(request)) {
|
|
5343
|
+
return json(
|
|
5344
|
+
{ error: { code: "FORBIDDEN", message: "Access denied." } },
|
|
5345
|
+
403
|
|
5346
|
+
);
|
|
5347
|
+
}
|
|
5348
|
+
const url = new URL(request.url);
|
|
5349
|
+
const pathname = url.pathname.replace(/\/$/, "") || "/";
|
|
5350
|
+
const method = request.method.toUpperCase();
|
|
5351
|
+
const snapshot = async () => runtime.inspect({ includeLive: options.includeLive ?? false });
|
|
5352
|
+
if (method === "GET" && pathname === "/api/meta") {
|
|
5353
|
+
const value = await snapshot();
|
|
5354
|
+
return json({ meta: value.meta, capabilities: value.capabilities });
|
|
5355
|
+
}
|
|
5356
|
+
if (method === "GET" && pathname === "/api/snapshot")
|
|
5357
|
+
return json(await snapshot());
|
|
5358
|
+
if (method === "GET" && pathname === "/api/overview")
|
|
5359
|
+
return json(await overview(await snapshot(), runtime));
|
|
5360
|
+
if (method === "GET" && pathname === "/api/routes") {
|
|
5361
|
+
return json(Object.values((await snapshot()).routes));
|
|
5362
|
+
}
|
|
5363
|
+
if (method === "GET" && pathname.startsWith("/api/routes/")) {
|
|
5364
|
+
const routeKey = decodeURIComponent(
|
|
5365
|
+
pathname.slice("/api/routes/".length)
|
|
5366
|
+
);
|
|
5367
|
+
const route = (await snapshot()).routes[routeKey];
|
|
5368
|
+
return route ? json(route) : json(
|
|
5369
|
+
{ error: { code: "NOT_FOUND", message: "Route not found." } },
|
|
5370
|
+
404
|
|
5371
|
+
);
|
|
5372
|
+
}
|
|
5373
|
+
if (method === "GET" && pathname === "/api/clients") {
|
|
5374
|
+
return json((await snapshot()).clients);
|
|
5375
|
+
}
|
|
5376
|
+
if (method === "GET" && pathname.startsWith("/api/clients/")) {
|
|
5377
|
+
const id = decodeURIComponent(pathname.slice("/api/clients/".length));
|
|
5378
|
+
const client = (await snapshot()).clients[id];
|
|
5379
|
+
return client ? json(client) : json(
|
|
5380
|
+
{ error: { code: "NOT_FOUND", message: "Client not found." } },
|
|
5381
|
+
404
|
|
5382
|
+
);
|
|
5383
|
+
}
|
|
5384
|
+
if (method === "GET" && pathname === "/api/servers")
|
|
5385
|
+
return json((await snapshot()).servers);
|
|
5386
|
+
if (method === "GET" && pathname === "/api/sockets")
|
|
5387
|
+
return json((await snapshot()).sockets);
|
|
5388
|
+
if (method === "GET" && pathname === "/api/checkpoints")
|
|
5389
|
+
return json(await runtime.checkpoints.list());
|
|
5390
|
+
if (method === "GET" && pathname === "/api/changes")
|
|
5391
|
+
return json(await runtime.checkpoints.listChangeSets());
|
|
5392
|
+
if (method === "GET" && pathname.startsWith("/api/changes/")) {
|
|
5393
|
+
const id = decodeURIComponent(pathname.slice("/api/changes/".length));
|
|
5394
|
+
const changeSet = await runtime.checkpoints.getChangeSet(id);
|
|
5395
|
+
return changeSet ? json(changeSet) : json(
|
|
5396
|
+
{
|
|
5397
|
+
error: { code: "NOT_FOUND", message: "Change set not found." }
|
|
5398
|
+
},
|
|
5399
|
+
404
|
|
5400
|
+
);
|
|
5401
|
+
}
|
|
5402
|
+
if (method === "POST" && pathname === "/api/checkpoints") {
|
|
5403
|
+
if (!await authorizeMutation(request, "checkpoint.create")) {
|
|
5404
|
+
return json(
|
|
5405
|
+
{
|
|
5406
|
+
error: {
|
|
5407
|
+
code: "MUTATIONS_DISABLED",
|
|
5408
|
+
message: "Inspector mutations are disabled."
|
|
5409
|
+
}
|
|
5410
|
+
},
|
|
5411
|
+
403
|
|
5412
|
+
);
|
|
5413
|
+
}
|
|
5414
|
+
const body = await readMutationBody(
|
|
5415
|
+
request,
|
|
5416
|
+
options.maxMutationBodyBytes ?? 16384
|
|
5417
|
+
);
|
|
5418
|
+
return json(
|
|
5419
|
+
await runtime.checkpoints.create({
|
|
5420
|
+
scope: typeof body.scope === "string" ? body.scope : void 0,
|
|
5421
|
+
trigger: body.trigger
|
|
5422
|
+
}),
|
|
5423
|
+
201
|
|
5424
|
+
);
|
|
5425
|
+
}
|
|
5426
|
+
if (method === "POST" && pathname === "/api/checkpoints/advance") {
|
|
5427
|
+
if (!await authorizeMutation(request, "checkpoint.advance")) {
|
|
5428
|
+
return json(
|
|
5429
|
+
{
|
|
5430
|
+
error: {
|
|
5431
|
+
code: "MUTATIONS_DISABLED",
|
|
5432
|
+
message: "Inspector mutations are disabled."
|
|
5433
|
+
}
|
|
5434
|
+
},
|
|
5435
|
+
403
|
|
5436
|
+
);
|
|
5437
|
+
}
|
|
5438
|
+
const body = await readMutationBody(
|
|
5439
|
+
request,
|
|
5440
|
+
options.maxMutationBodyBytes ?? 16384
|
|
5441
|
+
);
|
|
5442
|
+
const idempotencyKey = request.headers.get("idempotency-key") ?? void 0;
|
|
5443
|
+
const trigger = body.trigger && typeof body.trigger === "object" ? {
|
|
5444
|
+
...body.trigger,
|
|
5445
|
+
externalId: body.trigger.externalId ?? idempotencyKey
|
|
5446
|
+
} : { type: "api", externalId: idempotencyKey };
|
|
5447
|
+
return json(
|
|
5448
|
+
await runtime.checkpoints.advance({
|
|
5449
|
+
scope: typeof body.scope === "string" ? body.scope : void 0,
|
|
5450
|
+
trigger
|
|
5451
|
+
})
|
|
5452
|
+
);
|
|
5453
|
+
}
|
|
5454
|
+
if (method === "DELETE" && pathname.startsWith("/api/checkpoints/")) {
|
|
5455
|
+
if (!await authorizeMutation(request, "checkpoint.delete")) {
|
|
5456
|
+
return json(
|
|
5457
|
+
{
|
|
5458
|
+
error: {
|
|
5459
|
+
code: "MUTATIONS_DISABLED",
|
|
5460
|
+
message: "Inspector mutations are disabled."
|
|
5461
|
+
}
|
|
5462
|
+
},
|
|
5463
|
+
403
|
|
5464
|
+
);
|
|
5465
|
+
}
|
|
5466
|
+
const id = decodeURIComponent(
|
|
5467
|
+
pathname.slice("/api/checkpoints/".length)
|
|
5468
|
+
);
|
|
5469
|
+
await runtime.checkpoints.delete(id);
|
|
5470
|
+
return new Response(null, {
|
|
5471
|
+
status: 204,
|
|
5472
|
+
headers: { "cache-control": "no-store" }
|
|
5473
|
+
});
|
|
5474
|
+
}
|
|
5475
|
+
return json(
|
|
5476
|
+
{
|
|
5477
|
+
error: {
|
|
5478
|
+
code: "NOT_FOUND",
|
|
5479
|
+
message: "Inspector endpoint not found."
|
|
5480
|
+
}
|
|
5481
|
+
},
|
|
5482
|
+
404
|
|
5483
|
+
);
|
|
5484
|
+
} catch (error) {
|
|
5485
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5486
|
+
if (message === "REQUEST_BODY_TOO_LARGE") {
|
|
5487
|
+
return json(
|
|
5488
|
+
{
|
|
5489
|
+
error: {
|
|
5490
|
+
code: message,
|
|
5491
|
+
message: "Mutation body exceeds the configured limit."
|
|
5492
|
+
}
|
|
5493
|
+
},
|
|
5494
|
+
413
|
|
5495
|
+
);
|
|
5496
|
+
}
|
|
5497
|
+
if (message === "REQUEST_BODY_INVALID" || error instanceof SyntaxError) {
|
|
5498
|
+
return json(
|
|
5499
|
+
{
|
|
5500
|
+
error: {
|
|
5501
|
+
code: "REQUEST_BODY_INVALID",
|
|
5502
|
+
message: "Mutation body must be a JSON object."
|
|
5503
|
+
}
|
|
5504
|
+
},
|
|
5505
|
+
400
|
|
5506
|
+
);
|
|
5507
|
+
}
|
|
5508
|
+
return json({ error: { code: "INSPECTOR_ERROR", message } }, 500);
|
|
5509
|
+
}
|
|
5510
|
+
};
|
|
5511
|
+
}
|
|
5512
|
+
|
|
5513
|
+
// src/adapters/express.ts
|
|
5514
|
+
var import_node_module = require("module");
|
|
5515
|
+
function loadExpress() {
|
|
5516
|
+
const require2 = (0, import_node_module.createRequire)(`${process.cwd()}/package.json`);
|
|
5517
|
+
const loaded = require2("express");
|
|
5518
|
+
return "default" in loaded ? loaded.default : loaded;
|
|
5519
|
+
}
|
|
5520
|
+
function createExpressRouter(options) {
|
|
5521
|
+
const express = loadExpress();
|
|
5522
|
+
const router = express.Router();
|
|
5523
|
+
const limit = options.maxMutationBodyBytes ?? 16384;
|
|
5524
|
+
router.use(express.json({ limit }));
|
|
5525
|
+
const handler = createInspectorHttpHandler(options.runtime, options);
|
|
5526
|
+
const bridge = async (req, res, next) => {
|
|
5527
|
+
try {
|
|
5528
|
+
const headers = new Headers();
|
|
5529
|
+
for (const [name, raw] of Object.entries(req.headers)) {
|
|
5530
|
+
if (Array.isArray(raw))
|
|
5531
|
+
raw.forEach((value) => headers.append(name, value));
|
|
5532
|
+
else if (raw !== void 0) headers.set(name, raw);
|
|
5533
|
+
}
|
|
5534
|
+
const method = req.method.toUpperCase();
|
|
5535
|
+
const body = method === "GET" || method === "HEAD" || req.body === void 0 ? void 0 : JSON.stringify(req.body);
|
|
5536
|
+
const request = new Request(`http://rrroutes.local${req.url}`, {
|
|
5537
|
+
method,
|
|
5538
|
+
headers,
|
|
5539
|
+
body
|
|
5540
|
+
});
|
|
5541
|
+
const response = await handler(request);
|
|
5542
|
+
response.headers.forEach((value, name) => res.setHeader(name, value));
|
|
5543
|
+
res.status(response.status);
|
|
5544
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
5545
|
+
res.send(Buffer.from(bytes));
|
|
5546
|
+
} catch (error) {
|
|
5547
|
+
next(error);
|
|
5548
|
+
}
|
|
5549
|
+
};
|
|
5550
|
+
router.use(bridge);
|
|
5551
|
+
return router;
|
|
5552
|
+
}
|
|
5553
|
+
|
|
5554
|
+
// src/files/write.ts
|
|
5555
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
5556
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
5557
|
+
async function writeJson(file, value) {
|
|
5558
|
+
const resolved = import_node_path8.default.resolve(file);
|
|
5559
|
+
await import_promises5.default.mkdir(import_node_path8.default.dirname(resolved), { recursive: true });
|
|
5560
|
+
await import_promises5.default.writeFile(resolved, `${JSON.stringify(value, null, 2)}
|
|
5561
|
+
`, "utf8");
|
|
5562
|
+
}
|
|
5563
|
+
async function writeRRRoutesSnapshot(snapshot, options) {
|
|
5564
|
+
await writeJson(options.outFile, snapshot);
|
|
5565
|
+
}
|
|
5566
|
+
async function writeRRRoutesChangeSet(changeSet, options) {
|
|
5567
|
+
await writeJson(options.outFile, changeSet);
|
|
5568
|
+
}
|
|
5569
|
+
|
|
5570
|
+
// src/runtime.ts
|
|
5571
|
+
function createRRRoutesExportRuntime(options) {
|
|
5572
|
+
const inspect = (inspectOptions) => buildRRRoutesSnapshot(options, inspectOptions);
|
|
5573
|
+
const checkpoints = createCheckpointService({
|
|
5574
|
+
inspect: () => inspect({ includeLive: false }),
|
|
5575
|
+
storage: options.storage,
|
|
5576
|
+
scope: options.checkpoints?.scope,
|
|
5577
|
+
retention: options.checkpoints?.retention,
|
|
5578
|
+
changeSetRetention: options.checkpoints?.changeSetRetention
|
|
5579
|
+
});
|
|
5580
|
+
const runtime = {
|
|
5581
|
+
inspect,
|
|
5582
|
+
async diff(before, after, diffOptions) {
|
|
5583
|
+
return diffRRRoutesSnapshots(
|
|
5584
|
+
before,
|
|
5585
|
+
after ?? await inspect(),
|
|
5586
|
+
diffOptions
|
|
5587
|
+
);
|
|
5588
|
+
},
|
|
5589
|
+
checkpoints,
|
|
5590
|
+
createHttpHandler(httpOptions) {
|
|
5591
|
+
return createInspectorHttpHandler(runtime, httpOptions);
|
|
5592
|
+
},
|
|
5593
|
+
createExpressRouter(expressOptions = {}) {
|
|
5594
|
+
return createExpressRouter({ runtime, ...expressOptions });
|
|
5595
|
+
},
|
|
5596
|
+
async writeSnapshot(writeOptions) {
|
|
5597
|
+
const snapshot = await inspect();
|
|
5598
|
+
await writeRRRoutesSnapshot(snapshot, writeOptions);
|
|
5599
|
+
return snapshot;
|
|
5600
|
+
},
|
|
5601
|
+
writeChangeSet
|
|
5602
|
+
};
|
|
5603
|
+
return runtime;
|
|
5604
|
+
}
|
|
5605
|
+
async function writeChangeSet(changeSet, writeOptions) {
|
|
5606
|
+
await writeRRRoutesChangeSet(changeSet, writeOptions);
|
|
5607
|
+
}
|
|
5608
|
+
|
|
5609
|
+
// src/cli/runtime.ts
|
|
5610
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
5611
|
+
var import_node_url3 = require("url");
|
|
5612
|
+
function parseCliFlags(argv) {
|
|
5613
|
+
const flags = /* @__PURE__ */ new Map();
|
|
5614
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
5615
|
+
const flag = argv[index];
|
|
5616
|
+
if (!flag?.startsWith("--")) continue;
|
|
5617
|
+
const value = argv[index + 1];
|
|
5618
|
+
if (!value || value.startsWith("--")) {
|
|
5619
|
+
flags.set(flag, "true");
|
|
5620
|
+
continue;
|
|
5621
|
+
}
|
|
5622
|
+
flags.set(flag, value);
|
|
5623
|
+
index += 1;
|
|
5624
|
+
}
|
|
5625
|
+
return flags;
|
|
5626
|
+
}
|
|
5627
|
+
async function loadExportRuntime(args) {
|
|
5628
|
+
const resolved = import_node_path9.default.resolve(args.modulePath);
|
|
5629
|
+
const module2 = await import((0, import_node_url3.pathToFileURL)(resolved).href);
|
|
5630
|
+
const exportName = args.exportName ?? "rrroutesExport";
|
|
5631
|
+
const value = module2[exportName] ?? module2.default?.[exportName];
|
|
5632
|
+
if (!value || typeof value.inspect !== "function" || !value.checkpoints) {
|
|
5633
|
+
throw new Error(
|
|
5634
|
+
`Export "${exportName}" in ${resolved} is not an RRRoutes export runtime.`
|
|
5635
|
+
);
|
|
5636
|
+
}
|
|
5637
|
+
return value;
|
|
5638
|
+
}
|
|
5639
|
+
function requiredFlag(flags, name) {
|
|
5640
|
+
const value = flags.get(name);
|
|
5641
|
+
if (!value || value === "true")
|
|
5642
|
+
throw new Error(`Missing required ${name} argument.`);
|
|
5643
|
+
return value;
|
|
5644
|
+
}
|
|
5645
|
+
|
|
5646
|
+
// src/cli/snapshot.ts
|
|
5647
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
5648
|
+
async function runSnapshotCli(argv) {
|
|
5649
|
+
const flags = parseCliFlags(argv);
|
|
5650
|
+
const runtime = await loadExportRuntime({
|
|
5651
|
+
modulePath: requiredFlag(flags, "--module"),
|
|
5652
|
+
exportName: flags.get("--export")
|
|
5653
|
+
});
|
|
5654
|
+
const outFile = flags.get("--out") ?? "rrroutes.snapshot.json";
|
|
5655
|
+
const snapshot = await runtime.writeSnapshot({ outFile });
|
|
5656
|
+
return { snapshot, outFile: import_node_path10.default.resolve(outFile) };
|
|
5657
|
+
}
|
|
5658
|
+
|
|
5659
|
+
// src/cli/changes.ts
|
|
5660
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
5661
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
5662
|
+
async function runChangesCli(argv) {
|
|
5663
|
+
const flags = parseCliFlags(argv);
|
|
5664
|
+
const runtime = await loadExportRuntime({
|
|
5665
|
+
modulePath: requiredFlag(flags, "--module"),
|
|
5666
|
+
exportName: flags.get("--export")
|
|
5667
|
+
});
|
|
5668
|
+
const beforeFile = requiredFlag(flags, "--before");
|
|
5669
|
+
const before = JSON.parse(
|
|
5670
|
+
await import_promises6.default.readFile(import_node_path11.default.resolve(beforeFile), "utf8")
|
|
5671
|
+
);
|
|
5672
|
+
const changeSet = await runtime.diff(before, void 0, {
|
|
5673
|
+
scope: flags.get("--scope"),
|
|
5674
|
+
trigger: { type: "cli" }
|
|
5675
|
+
});
|
|
5676
|
+
const outFile = flags.get("--out") ?? "rrroutes.changes.json";
|
|
5677
|
+
await runtime.writeChangeSet(changeSet, { outFile });
|
|
5678
|
+
return { changeSet, outFile: import_node_path11.default.resolve(outFile) };
|
|
5679
|
+
}
|
|
5680
|
+
|
|
5681
|
+
// src/cli/checkpoint.ts
|
|
5682
|
+
async function runCheckpointCli(argv) {
|
|
5683
|
+
const [action = "advance", ...rest] = argv;
|
|
5684
|
+
const flags = parseCliFlags(rest);
|
|
5685
|
+
const runtime = await loadExportRuntime({
|
|
5686
|
+
modulePath: requiredFlag(flags, "--module"),
|
|
5687
|
+
exportName: flags.get("--export")
|
|
5688
|
+
});
|
|
5689
|
+
const scope = flags.get("--scope");
|
|
5690
|
+
if (action === "advance") {
|
|
5691
|
+
return runtime.checkpoints.advance({ scope, trigger: { type: "cli" } });
|
|
5692
|
+
}
|
|
5693
|
+
if (action === "create") {
|
|
5694
|
+
return runtime.checkpoints.create({ scope, trigger: { type: "cli" } });
|
|
5695
|
+
}
|
|
5696
|
+
throw new Error(`Unsupported checkpoint action: ${action}`);
|
|
5697
|
+
}
|
|
5698
|
+
|
|
5699
|
+
// src/cli/serve.ts
|
|
5700
|
+
var import_node_module2 = require("module");
|
|
5701
|
+
async function runInspectorServeCli(argv) {
|
|
5702
|
+
const loaded = (0, import_node_module2.createRequire)(`${process.cwd()}/package.json`)("express");
|
|
5703
|
+
const express = "default" in loaded ? loaded.default : loaded;
|
|
5704
|
+
const flags = parseCliFlags(argv);
|
|
5705
|
+
const runtime = await loadExportRuntime({
|
|
5706
|
+
modulePath: requiredFlag(flags, "--module"),
|
|
5707
|
+
exportName: flags.get("--export")
|
|
5708
|
+
});
|
|
5709
|
+
const port = Number(flags.get("--port") ?? "4310");
|
|
5710
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
5711
|
+
throw new Error("--port must be an integer between 1 and 65535.");
|
|
5712
|
+
}
|
|
5713
|
+
const app = express();
|
|
5714
|
+
app.use("/", runtime.createExpressRouter());
|
|
5715
|
+
const server = await new Promise((resolve) => {
|
|
5716
|
+
const listening = app.listen(port, () => resolve(listening));
|
|
5717
|
+
});
|
|
5718
|
+
return { app, server, port };
|
|
5719
|
+
}
|
|
2771
5720
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2772
5721
|
0 && (module.exports = {
|
|
2773
5722
|
DEFAULT_VIEWER_TEMPLATE,
|
|
5723
|
+
RRROUTES_BAKED_PAYLOAD_MARKER,
|
|
2774
5724
|
__private,
|
|
5725
|
+
applyChangeSetRetention,
|
|
5726
|
+
applyCheckpointRetention,
|
|
2775
5727
|
buildFinalizedLeavesViewerBundle,
|
|
5728
|
+
buildRRRoutesSnapshot,
|
|
5729
|
+
canonicalSnapshotStructure,
|
|
5730
|
+
canonicalizeJson,
|
|
2776
5731
|
clearSchemaIntrospectionHandlers,
|
|
5732
|
+
createBakedInspectorPayload,
|
|
5733
|
+
createCheckpointService,
|
|
5734
|
+
createExpressRouter,
|
|
5735
|
+
createFileSystemInspectionStorage,
|
|
5736
|
+
createInspectorHttpHandler,
|
|
5737
|
+
createMemoryInspectionStorage,
|
|
5738
|
+
createRRRoutesExportRuntime,
|
|
5739
|
+
createSQLiteInspectionStorage,
|
|
2777
5740
|
createSchemaIntrospector,
|
|
5741
|
+
createSnapshotChangeSet,
|
|
5742
|
+
diffRRRoutesSnapshots,
|
|
5743
|
+
escapeBakedPayload,
|
|
2778
5744
|
exportFinalizedLeaves,
|
|
2779
5745
|
exportFinalizedLeavesChangelog,
|
|
2780
5746
|
extractLeafSourceByAst,
|
|
2781
5747
|
flattenLeafSchemas,
|
|
2782
5748
|
flattenSerializableSchema,
|
|
5749
|
+
hashCanonicalValue,
|
|
5750
|
+
hashRRRoutesSnapshot,
|
|
5751
|
+
injectBakedInspectorPayload,
|
|
2783
5752
|
introspectSchema,
|
|
5753
|
+
loadExportRuntime,
|
|
2784
5754
|
loadFinalizedLeavesInput,
|
|
5755
|
+
parseCliFlags,
|
|
2785
5756
|
parseFinalizedLeavesChangelogCliArgs,
|
|
2786
5757
|
parseFinalizedLeavesCliArgs,
|
|
2787
5758
|
registerSchemaIntrospectionHandler,
|
|
5759
|
+
requiredFlag,
|
|
5760
|
+
runChangesCli,
|
|
5761
|
+
runCheckpointCli,
|
|
2788
5762
|
runExportFinalizedLeavesChangelogCli,
|
|
2789
5763
|
runExportFinalizedLeavesCli,
|
|
5764
|
+
runInspectorServeCli,
|
|
5765
|
+
runSnapshotCli,
|
|
2790
5766
|
serializableSchemaKinds,
|
|
2791
5767
|
serializeLeafContract,
|
|
2792
5768
|
serializeLeavesContract,
|
|
5769
|
+
stableStringify,
|
|
2793
5770
|
writeFinalizedLeavesChangelogExport,
|
|
2794
5771
|
writeFinalizedLeavesExport,
|
|
2795
|
-
writeFinalizedLeavesViewerBundle
|
|
5772
|
+
writeFinalizedLeavesViewerBundle,
|
|
5773
|
+
writeRRRoutesChangeSet,
|
|
5774
|
+
writeRRRoutesSnapshot
|
|
2796
5775
|
});
|
|
2797
5776
|
//# sourceMappingURL=index.cjs.map
|