@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.mjs
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __typeError = (msg) => {
|
|
2
|
+
throw TypeError(msg);
|
|
3
|
+
};
|
|
4
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
6
|
+
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);
|
|
7
|
+
|
|
1
8
|
// src/schemaIntrospection.ts
|
|
2
9
|
import * as z from "zod";
|
|
3
10
|
var serializableSchemaKinds = [
|
|
@@ -212,10 +219,10 @@ function introspectSchema(schema, options = {}) {
|
|
|
212
219
|
|
|
213
220
|
// src/serializeLeafContract.ts
|
|
214
221
|
import {
|
|
215
|
-
|
|
222
|
+
asZodSchema
|
|
216
223
|
} from "@emeryld/rrroutes-contract";
|
|
217
224
|
function serializeContractSchema(schema, options) {
|
|
218
|
-
return schema ? introspectSchema(
|
|
225
|
+
return schema ? introspectSchema(asZodSchema(schema), options) : void 0;
|
|
219
226
|
}
|
|
220
227
|
function serializeBodyFiles(cfg) {
|
|
221
228
|
if (!Array.isArray(cfg.bodyFiles) || cfg.bodyFiles.length === 0) {
|
|
@@ -227,6 +234,7 @@ function serializeLeafContract(leaf, options = {}) {
|
|
|
227
234
|
const cfg = leaf.cfg;
|
|
228
235
|
return {
|
|
229
236
|
key: `${leaf.method.toUpperCase()} ${leaf.path}`,
|
|
237
|
+
id: cfg.id,
|
|
230
238
|
method: leaf.method,
|
|
231
239
|
path: leaf.path,
|
|
232
240
|
cfg: {
|
|
@@ -246,7 +254,10 @@ function serializeLeafContract(leaf, options = {}) {
|
|
|
246
254
|
params: serializeContractSchema(cfg.paramsSchema, options),
|
|
247
255
|
output: serializeContractSchema(cfg.outputSchema, options),
|
|
248
256
|
outputMeta: serializeContractSchema(cfg.outputMetaSchema, options),
|
|
249
|
-
queryExtension: serializeContractSchema(
|
|
257
|
+
queryExtension: serializeContractSchema(
|
|
258
|
+
cfg.queryExtensionSchema,
|
|
259
|
+
options
|
|
260
|
+
)
|
|
250
261
|
}
|
|
251
262
|
}
|
|
252
263
|
};
|
|
@@ -296,46 +307,46 @@ function normalizeType(schema) {
|
|
|
296
307
|
return "unknown";
|
|
297
308
|
}
|
|
298
309
|
}
|
|
299
|
-
function isNonEmptyPath(
|
|
300
|
-
return typeof
|
|
310
|
+
function isNonEmptyPath(path12) {
|
|
311
|
+
return typeof path12 === "string" && path12.length > 0;
|
|
301
312
|
}
|
|
302
|
-
function setNode(out,
|
|
303
|
-
if (!isNonEmptyPath(
|
|
304
|
-
out[
|
|
313
|
+
function setNode(out, path12, schema, inherited) {
|
|
314
|
+
if (!isNonEmptyPath(path12)) return;
|
|
315
|
+
out[path12] = {
|
|
305
316
|
type: normalizeType(schema),
|
|
306
317
|
nullable: inherited.nullable || Boolean(schema.nullable),
|
|
307
318
|
optional: inherited.optional || Boolean(schema.optional),
|
|
308
319
|
literal: schema.kind === "literal" ? schema.literal : void 0
|
|
309
320
|
};
|
|
310
321
|
}
|
|
311
|
-
function flattenInto(out, schema,
|
|
312
|
-
if (!schema || !isNonEmptyPath(
|
|
322
|
+
function flattenInto(out, schema, path12, inherited) {
|
|
323
|
+
if (!schema || !isNonEmptyPath(path12)) return;
|
|
313
324
|
const nextInherited = {
|
|
314
325
|
optional: inherited.optional || Boolean(schema.optional),
|
|
315
326
|
nullable: inherited.nullable || Boolean(schema.nullable)
|
|
316
327
|
};
|
|
317
328
|
if (schema.kind === "union" && Array.isArray(schema.union) && schema.union.length > 0) {
|
|
318
329
|
schema.union.forEach((option, index) => {
|
|
319
|
-
const optionPath = `${
|
|
330
|
+
const optionPath = `${path12}-${index + 1}`;
|
|
320
331
|
flattenInto(out, option, optionPath, inherited);
|
|
321
332
|
});
|
|
322
333
|
return;
|
|
323
334
|
}
|
|
324
|
-
setNode(out,
|
|
335
|
+
setNode(out, path12, schema, inherited);
|
|
325
336
|
if (schema.kind === "object" && schema.properties) {
|
|
326
337
|
for (const [key, child] of Object.entries(schema.properties)) {
|
|
327
|
-
const childPath = `${
|
|
338
|
+
const childPath = `${path12}.${key}`;
|
|
328
339
|
flattenInto(out, child, childPath, nextInherited);
|
|
329
340
|
}
|
|
330
341
|
return;
|
|
331
342
|
}
|
|
332
343
|
if (schema.kind === "array" && schema.element) {
|
|
333
|
-
flattenInto(out, schema.element, `${
|
|
344
|
+
flattenInto(out, schema.element, `${path12}[]`, nextInherited);
|
|
334
345
|
}
|
|
335
346
|
}
|
|
336
|
-
function flattenSerializableSchema(schema,
|
|
347
|
+
function flattenSerializableSchema(schema, path12) {
|
|
337
348
|
const out = {};
|
|
338
|
-
flattenInto(out, schema,
|
|
349
|
+
flattenInto(out, schema, path12, { optional: false, nullable: false });
|
|
339
350
|
return out;
|
|
340
351
|
}
|
|
341
352
|
function isSerializedLeaf(value) {
|
|
@@ -1059,6 +1070,33 @@ function extractLeafSourceByAst({
|
|
|
1059
1070
|
};
|
|
1060
1071
|
}
|
|
1061
1072
|
|
|
1073
|
+
// src/files/baked-payload.ts
|
|
1074
|
+
var RRROUTES_BAKED_PAYLOAD_MARKER = "<!--__RRROUTES_INSPECTOR_BAKED_PAYLOAD__-->";
|
|
1075
|
+
function createBakedInspectorPayload(args) {
|
|
1076
|
+
return {
|
|
1077
|
+
kind: "rrroutes-inspector-baked",
|
|
1078
|
+
snapshot: args.snapshot,
|
|
1079
|
+
checkpoints: args.checkpoints ?? [],
|
|
1080
|
+
changeSets: args.changeSets ?? []
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
function escapeBakedPayload(payload) {
|
|
1084
|
+
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
1085
|
+
}
|
|
1086
|
+
function injectBakedInspectorPayload(html, payload, options = {}) {
|
|
1087
|
+
const marker = options.marker ?? RRROUTES_BAKED_PAYLOAD_MARKER;
|
|
1088
|
+
const globalName = options.globalName ?? "__RRROUTES_INSPECTOR_PAYLOAD";
|
|
1089
|
+
const scriptId = options.scriptId ?? "rrroutes-inspector-baked-payload";
|
|
1090
|
+
const script = `${marker}
|
|
1091
|
+
<script id="${scriptId}">window.${globalName} = ${escapeBakedPayload(payload)};</script>`;
|
|
1092
|
+
if (html.includes(marker)) return html.replace(marker, script);
|
|
1093
|
+
if (html.includes("</body>"))
|
|
1094
|
+
return html.replace("</body>", `${script}
|
|
1095
|
+
</body>`);
|
|
1096
|
+
return `${script}
|
|
1097
|
+
${html}`;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1062
1100
|
// src/exportFinalizedLeaves.ts
|
|
1063
1101
|
function isRegistry(value) {
|
|
1064
1102
|
return typeof value === "object" && value !== null && "all" in value && "byKey" in value;
|
|
@@ -1072,7 +1110,7 @@ function buildMeta(sourceExtraction) {
|
|
|
1072
1110
|
description: "Finalized RRRoutes leaves export with serialized schemas and flattened schema paths for downstream processing.",
|
|
1073
1111
|
sourceExtraction,
|
|
1074
1112
|
fieldCatalog: {
|
|
1075
|
-
leaf: ["key", "method", "path", "cfg"],
|
|
1113
|
+
leaf: ["key", "id?", "method", "path", "cfg"],
|
|
1076
1114
|
cfg: [
|
|
1077
1115
|
"description?",
|
|
1078
1116
|
"summary?",
|
|
@@ -1106,25 +1144,6 @@ function buildMeta(sourceExtraction) {
|
|
|
1106
1144
|
}
|
|
1107
1145
|
};
|
|
1108
1146
|
}
|
|
1109
|
-
var BAKED_PAYLOAD_MARKER = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
|
|
1110
|
-
function escapePayloadForInlineScript(payload) {
|
|
1111
|
-
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
|
|
1112
|
-
}
|
|
1113
|
-
function injectPayloadIntoViewerHtml(htmlTemplate, payload) {
|
|
1114
|
-
const payloadScript = `${BAKED_PAYLOAD_MARKER}
|
|
1115
|
-
<script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript(
|
|
1116
|
-
payload
|
|
1117
|
-
)};</script>`;
|
|
1118
|
-
if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER)) {
|
|
1119
|
-
return htmlTemplate.replace(BAKED_PAYLOAD_MARKER, payloadScript);
|
|
1120
|
-
}
|
|
1121
|
-
if (htmlTemplate.includes("</body>")) {
|
|
1122
|
-
return htmlTemplate.replace("</body>", `${payloadScript}
|
|
1123
|
-
</body>`);
|
|
1124
|
-
}
|
|
1125
|
-
return `${payloadScript}
|
|
1126
|
-
${htmlTemplate}`;
|
|
1127
|
-
}
|
|
1128
1147
|
async function resolveViewerTemplatePath(viewerTemplateFile) {
|
|
1129
1148
|
if (viewerTemplateFile) {
|
|
1130
1149
|
const resolved = path2.resolve(viewerTemplateFile);
|
|
@@ -1136,10 +1155,7 @@ async function resolveViewerTemplatePath(viewerTemplateFile) {
|
|
|
1136
1155
|
process.cwd(),
|
|
1137
1156
|
"node_modules/@emeryld/rrroutes-export/tools/finalized-leaves-viewer.html"
|
|
1138
1157
|
),
|
|
1139
|
-
path2.resolve(
|
|
1140
|
-
process.cwd(),
|
|
1141
|
-
"tools/finalized-leaves-viewer.html"
|
|
1142
|
-
),
|
|
1158
|
+
path2.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
|
|
1143
1159
|
path2.resolve(
|
|
1144
1160
|
process.cwd(),
|
|
1145
1161
|
"packages/export/tools/finalized-leaves-viewer.html"
|
|
@@ -1164,7 +1180,11 @@ async function writeJsonExport(payload, outFile) {
|
|
|
1164
1180
|
async function writeBakedHtmlExport(payload, htmlFile, viewerTemplateFile) {
|
|
1165
1181
|
const templatePath = await resolveViewerTemplatePath(viewerTemplateFile);
|
|
1166
1182
|
const template = templatePath ? await fs.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
|
|
1167
|
-
const baked =
|
|
1183
|
+
const baked = injectBakedInspectorPayload(template, payload, {
|
|
1184
|
+
marker: "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->",
|
|
1185
|
+
globalName: "__FINALIZED_LEAVES_PAYLOAD",
|
|
1186
|
+
scriptId: "finalized-leaves-baked-payload"
|
|
1187
|
+
});
|
|
1168
1188
|
const resolved = path2.resolve(htmlFile);
|
|
1169
1189
|
await fs.mkdir(path2.dirname(resolved), { recursive: true });
|
|
1170
1190
|
await fs.writeFile(resolved, baked, "utf8");
|
|
@@ -1345,6 +1365,869 @@ import { execFile as execFileCallback, spawn as spawn2 } from "child_process";
|
|
|
1345
1365
|
import { promisify } from "util";
|
|
1346
1366
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1347
1367
|
import ts2 from "typescript";
|
|
1368
|
+
|
|
1369
|
+
// src/snapshot/canonicalize.ts
|
|
1370
|
+
function compareJson(left, right) {
|
|
1371
|
+
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
1372
|
+
}
|
|
1373
|
+
function canonicalizeValue(value, key) {
|
|
1374
|
+
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
1375
|
+
return void 0;
|
|
1376
|
+
}
|
|
1377
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
1378
|
+
return value;
|
|
1379
|
+
}
|
|
1380
|
+
if (typeof value === "number") {
|
|
1381
|
+
return Number.isFinite(value) ? value : null;
|
|
1382
|
+
}
|
|
1383
|
+
if (typeof value === "bigint") return String(value);
|
|
1384
|
+
if (value instanceof Date) return value.toISOString();
|
|
1385
|
+
if (Array.isArray(value)) {
|
|
1386
|
+
const array = value.map((item) => canonicalizeValue(item)).filter((item) => item !== void 0);
|
|
1387
|
+
if (key === "tags" || key === "events" || key === "eventNames") {
|
|
1388
|
+
return array.slice().sort(compareJson);
|
|
1389
|
+
}
|
|
1390
|
+
return array;
|
|
1391
|
+
}
|
|
1392
|
+
if (typeof value !== "object") return void 0;
|
|
1393
|
+
const out = {};
|
|
1394
|
+
for (const property of Object.keys(value).sort()) {
|
|
1395
|
+
const child = canonicalizeValue(
|
|
1396
|
+
value[property],
|
|
1397
|
+
property
|
|
1398
|
+
);
|
|
1399
|
+
if (child !== void 0) out[property] = child;
|
|
1400
|
+
}
|
|
1401
|
+
return out;
|
|
1402
|
+
}
|
|
1403
|
+
function canonicalizeJson(value) {
|
|
1404
|
+
return canonicalizeValue(value) ?? null;
|
|
1405
|
+
}
|
|
1406
|
+
function canonicalSnapshotStructure(snapshot, options = {}) {
|
|
1407
|
+
const sockets = Object.fromEntries(
|
|
1408
|
+
Object.entries(snapshot.sockets).map(([identity, socket]) => [
|
|
1409
|
+
identity,
|
|
1410
|
+
options.includeLive ? socket : { ...socket, live: void 0 }
|
|
1411
|
+
])
|
|
1412
|
+
);
|
|
1413
|
+
return canonicalizeJson({
|
|
1414
|
+
...snapshot,
|
|
1415
|
+
meta: {
|
|
1416
|
+
...snapshot.meta,
|
|
1417
|
+
capturedAt: void 0,
|
|
1418
|
+
snapshotHash: void 0
|
|
1419
|
+
},
|
|
1420
|
+
capabilities: {
|
|
1421
|
+
...snapshot.capabilities,
|
|
1422
|
+
live: false
|
|
1423
|
+
},
|
|
1424
|
+
sockets
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
function stableStringify(value) {
|
|
1428
|
+
return JSON.stringify(canonicalizeJson(value));
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// src/snapshot/hash.ts
|
|
1432
|
+
import { createHash } from "crypto";
|
|
1433
|
+
function hashCanonicalValue(value) {
|
|
1434
|
+
return createHash("sha256").update(stableStringify(value)).digest("hex");
|
|
1435
|
+
}
|
|
1436
|
+
function hashRRRoutesSnapshot(snapshot, options = {}) {
|
|
1437
|
+
return hashCanonicalValue(canonicalSnapshotStructure(snapshot, options));
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// src/diff/diff-snapshots.ts
|
|
1441
|
+
function same(left, right) {
|
|
1442
|
+
return stableStringify(left) === stableStringify(right);
|
|
1443
|
+
}
|
|
1444
|
+
function severityFor(type) {
|
|
1445
|
+
switch (type) {
|
|
1446
|
+
case "route_removed":
|
|
1447
|
+
case "client_binding_removed":
|
|
1448
|
+
case "client_resource_removed":
|
|
1449
|
+
case "server_controller_unregistered":
|
|
1450
|
+
case "server_endpoint_disabled":
|
|
1451
|
+
case "socket_runtime_removed":
|
|
1452
|
+
case "socket_contract_event_removed":
|
|
1453
|
+
return "breaking";
|
|
1454
|
+
case "schema_changed":
|
|
1455
|
+
case "cfg_changed":
|
|
1456
|
+
case "client_binding_target_changed":
|
|
1457
|
+
case "client_build_options_changed":
|
|
1458
|
+
case "client_augment_changed":
|
|
1459
|
+
case "client_augment_reordered":
|
|
1460
|
+
case "client_socket_connection_changed":
|
|
1461
|
+
case "server_controller_changed":
|
|
1462
|
+
case "server_middleware_changed":
|
|
1463
|
+
case "server_middleware_reordered":
|
|
1464
|
+
case "server_output_validation_changed":
|
|
1465
|
+
case "socket_payload_schema_changed":
|
|
1466
|
+
case "socket_binding_changed":
|
|
1467
|
+
case "socket_room_strategy_changed":
|
|
1468
|
+
case "socket_cache_reducer_changed":
|
|
1469
|
+
case "socket_heartbeat_configuration_changed":
|
|
1470
|
+
return "behavioral";
|
|
1471
|
+
case "route_added":
|
|
1472
|
+
case "client_resource_added":
|
|
1473
|
+
case "client_binding_added":
|
|
1474
|
+
case "client_binding_enabled":
|
|
1475
|
+
case "client_augment_added":
|
|
1476
|
+
case "client_socket_connection_added":
|
|
1477
|
+
case "server_controller_registered":
|
|
1478
|
+
case "server_endpoint_enabled":
|
|
1479
|
+
case "server_middleware_added":
|
|
1480
|
+
case "socket_runtime_added":
|
|
1481
|
+
case "socket_contract_event_added":
|
|
1482
|
+
case "socket_handler_added":
|
|
1483
|
+
return "non-breaking";
|
|
1484
|
+
case "route_id_changed":
|
|
1485
|
+
case "description_changed":
|
|
1486
|
+
case "files_changed":
|
|
1487
|
+
case "source_changed":
|
|
1488
|
+
case "client_binding_disabled":
|
|
1489
|
+
case "client_augment_removed":
|
|
1490
|
+
case "client_socket_connection_removed":
|
|
1491
|
+
case "server_middleware_removed":
|
|
1492
|
+
case "socket_handler_removed":
|
|
1493
|
+
return "informational";
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
function makeChange(input) {
|
|
1497
|
+
const before = input.before === void 0 ? void 0 : canonicalizeJson(input.before);
|
|
1498
|
+
const after = input.after === void 0 ? void 0 : canonicalizeJson(input.after);
|
|
1499
|
+
const base = {
|
|
1500
|
+
...input,
|
|
1501
|
+
before,
|
|
1502
|
+
after,
|
|
1503
|
+
severity: input.severity ?? severityFor(input.type)
|
|
1504
|
+
};
|
|
1505
|
+
return {
|
|
1506
|
+
...base,
|
|
1507
|
+
id: hashCanonicalValue(base)
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
function fieldChanges(before, after, fields) {
|
|
1511
|
+
return fields.flatMap(
|
|
1512
|
+
(field) => same(before[field], after[field]) ? [] : [
|
|
1513
|
+
{
|
|
1514
|
+
field,
|
|
1515
|
+
before: canonicalizeJson(before[field]),
|
|
1516
|
+
after: canonicalizeJson(after[field])
|
|
1517
|
+
}
|
|
1518
|
+
]
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
function diffContract(before, after) {
|
|
1522
|
+
const changes = [];
|
|
1523
|
+
const keys = /* @__PURE__ */ new Set([
|
|
1524
|
+
...Object.keys(before.routes),
|
|
1525
|
+
...Object.keys(after.routes)
|
|
1526
|
+
]);
|
|
1527
|
+
for (const routeKey of Array.from(keys).sort()) {
|
|
1528
|
+
const previous = before.routes[routeKey];
|
|
1529
|
+
const next = after.routes[routeKey];
|
|
1530
|
+
if (!previous && next) {
|
|
1531
|
+
changes.push(
|
|
1532
|
+
makeChange({
|
|
1533
|
+
layer: "contract",
|
|
1534
|
+
type: "route_added",
|
|
1535
|
+
routeKey,
|
|
1536
|
+
entityId: routeKey,
|
|
1537
|
+
after: next.contract.serializedLeaf
|
|
1538
|
+
})
|
|
1539
|
+
);
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
if (previous && !next) {
|
|
1543
|
+
changes.push(
|
|
1544
|
+
makeChange({
|
|
1545
|
+
layer: "contract",
|
|
1546
|
+
type: "route_removed",
|
|
1547
|
+
routeKey,
|
|
1548
|
+
entityId: routeKey,
|
|
1549
|
+
before: previous.contract.serializedLeaf
|
|
1550
|
+
})
|
|
1551
|
+
);
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
if (!previous || !next) continue;
|
|
1555
|
+
if (previous.id !== next.id) {
|
|
1556
|
+
changes.push(
|
|
1557
|
+
makeChange({
|
|
1558
|
+
layer: "contract",
|
|
1559
|
+
type: "route_id_changed",
|
|
1560
|
+
routeKey,
|
|
1561
|
+
entityId: routeKey,
|
|
1562
|
+
before: previous.id,
|
|
1563
|
+
after: next.id
|
|
1564
|
+
})
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
if (!same(previous.contract.schemaFlat, next.contract.schemaFlat)) {
|
|
1568
|
+
changes.push(
|
|
1569
|
+
makeChange({
|
|
1570
|
+
layer: "contract",
|
|
1571
|
+
type: "schema_changed",
|
|
1572
|
+
routeKey,
|
|
1573
|
+
entityId: routeKey,
|
|
1574
|
+
before: previous.contract.schemaFlat,
|
|
1575
|
+
after: next.contract.schemaFlat
|
|
1576
|
+
})
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
const previousCfg = previous.contract.serializedLeaf.cfg;
|
|
1580
|
+
const nextCfg = next.contract.serializedLeaf.cfg;
|
|
1581
|
+
if (previousCfg.description !== nextCfg.description) {
|
|
1582
|
+
changes.push(
|
|
1583
|
+
makeChange({
|
|
1584
|
+
layer: "contract",
|
|
1585
|
+
type: "description_changed",
|
|
1586
|
+
routeKey,
|
|
1587
|
+
entityId: routeKey,
|
|
1588
|
+
before: previousCfg.description,
|
|
1589
|
+
after: nextCfg.description
|
|
1590
|
+
})
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
if (!same(previousCfg.bodyFiles, nextCfg.bodyFiles)) {
|
|
1594
|
+
changes.push(
|
|
1595
|
+
makeChange({
|
|
1596
|
+
layer: "contract",
|
|
1597
|
+
type: "files_changed",
|
|
1598
|
+
routeKey,
|
|
1599
|
+
entityId: routeKey,
|
|
1600
|
+
before: previousCfg.bodyFiles,
|
|
1601
|
+
after: nextCfg.bodyFiles
|
|
1602
|
+
})
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
const cfgFields = [
|
|
1606
|
+
"summary",
|
|
1607
|
+
"docsGroup",
|
|
1608
|
+
"tags",
|
|
1609
|
+
"deprecated",
|
|
1610
|
+
"stability",
|
|
1611
|
+
"docsHidden",
|
|
1612
|
+
"docsMeta",
|
|
1613
|
+
"feed"
|
|
1614
|
+
];
|
|
1615
|
+
const fields = fieldChanges(
|
|
1616
|
+
previousCfg,
|
|
1617
|
+
nextCfg,
|
|
1618
|
+
cfgFields
|
|
1619
|
+
);
|
|
1620
|
+
if (fields.length > 0) {
|
|
1621
|
+
changes.push(
|
|
1622
|
+
makeChange({
|
|
1623
|
+
layer: "contract",
|
|
1624
|
+
type: "cfg_changed",
|
|
1625
|
+
routeKey,
|
|
1626
|
+
entityId: routeKey,
|
|
1627
|
+
before: previousCfg,
|
|
1628
|
+
after: nextCfg,
|
|
1629
|
+
changedFields: fields
|
|
1630
|
+
})
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
if (!same(previous.contract.source, next.contract.source)) {
|
|
1634
|
+
changes.push(
|
|
1635
|
+
makeChange({
|
|
1636
|
+
layer: "contract",
|
|
1637
|
+
type: "source_changed",
|
|
1638
|
+
routeKey,
|
|
1639
|
+
entityId: routeKey,
|
|
1640
|
+
before: previous.contract.source,
|
|
1641
|
+
after: next.contract.source
|
|
1642
|
+
})
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
return changes;
|
|
1647
|
+
}
|
|
1648
|
+
function diffOrderedEntities(args) {
|
|
1649
|
+
const changes = [];
|
|
1650
|
+
const previous = new Map(args.before.map((item) => [item.identity, item]));
|
|
1651
|
+
const next = new Map(args.after.map((item) => [item.identity, item]));
|
|
1652
|
+
const identities = /* @__PURE__ */ new Set([...previous.keys(), ...next.keys()]);
|
|
1653
|
+
for (const identity of Array.from(identities).sort()) {
|
|
1654
|
+
const left = previous.get(identity);
|
|
1655
|
+
const right = next.get(identity);
|
|
1656
|
+
if (!left && right) {
|
|
1657
|
+
changes.push(
|
|
1658
|
+
makeChange({
|
|
1659
|
+
layer: args.layer,
|
|
1660
|
+
type: args.added,
|
|
1661
|
+
routeKey: args.routeKey,
|
|
1662
|
+
entityId: identity,
|
|
1663
|
+
after: right
|
|
1664
|
+
})
|
|
1665
|
+
);
|
|
1666
|
+
} else if (left && !right) {
|
|
1667
|
+
changes.push(
|
|
1668
|
+
makeChange({
|
|
1669
|
+
layer: args.layer,
|
|
1670
|
+
type: args.removed,
|
|
1671
|
+
routeKey: args.routeKey,
|
|
1672
|
+
entityId: identity,
|
|
1673
|
+
before: left
|
|
1674
|
+
})
|
|
1675
|
+
);
|
|
1676
|
+
} else if (left && right) {
|
|
1677
|
+
const leftWithoutOrder = { ...left, order: void 0 };
|
|
1678
|
+
const rightWithoutOrder = { ...right, order: void 0 };
|
|
1679
|
+
if (!same(leftWithoutOrder, rightWithoutOrder)) {
|
|
1680
|
+
changes.push(
|
|
1681
|
+
makeChange({
|
|
1682
|
+
layer: args.layer,
|
|
1683
|
+
type: args.changed,
|
|
1684
|
+
routeKey: args.routeKey,
|
|
1685
|
+
entityId: identity,
|
|
1686
|
+
before: left,
|
|
1687
|
+
after: right
|
|
1688
|
+
})
|
|
1689
|
+
);
|
|
1690
|
+
} else if (left.order !== right.order) {
|
|
1691
|
+
changes.push(
|
|
1692
|
+
makeChange({
|
|
1693
|
+
layer: args.layer,
|
|
1694
|
+
type: args.reordered,
|
|
1695
|
+
routeKey: args.routeKey,
|
|
1696
|
+
entityId: identity,
|
|
1697
|
+
before: left.order,
|
|
1698
|
+
after: right.order
|
|
1699
|
+
})
|
|
1700
|
+
);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
return changes;
|
|
1705
|
+
}
|
|
1706
|
+
function allBindings(snapshot) {
|
|
1707
|
+
const map = /* @__PURE__ */ new Map();
|
|
1708
|
+
for (const client of Object.values(snapshot.clients)) {
|
|
1709
|
+
for (const binding of client.bindings)
|
|
1710
|
+
map.set(binding.bindingIdentity, binding);
|
|
1711
|
+
}
|
|
1712
|
+
return map;
|
|
1713
|
+
}
|
|
1714
|
+
function diffClientSocketConnections(args) {
|
|
1715
|
+
const changes = [];
|
|
1716
|
+
const previous = new Map(args.before.map((item) => [item.identity, item]));
|
|
1717
|
+
const next = new Map(args.after.map((item) => [item.identity, item]));
|
|
1718
|
+
const identities = /* @__PURE__ */ new Set([...previous.keys(), ...next.keys()]);
|
|
1719
|
+
for (const identity of Array.from(identities).sort()) {
|
|
1720
|
+
const left = previous.get(identity);
|
|
1721
|
+
const right = next.get(identity);
|
|
1722
|
+
if (!left && right) {
|
|
1723
|
+
changes.push(
|
|
1724
|
+
makeChange({
|
|
1725
|
+
layer: "client",
|
|
1726
|
+
type: "client_socket_connection_added",
|
|
1727
|
+
routeKey: args.routeKey,
|
|
1728
|
+
entityId: identity,
|
|
1729
|
+
after: right
|
|
1730
|
+
})
|
|
1731
|
+
);
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
if (left && !right) {
|
|
1735
|
+
changes.push(
|
|
1736
|
+
makeChange({
|
|
1737
|
+
layer: "client",
|
|
1738
|
+
type: "client_socket_connection_removed",
|
|
1739
|
+
routeKey: args.routeKey,
|
|
1740
|
+
entityId: identity,
|
|
1741
|
+
before: left
|
|
1742
|
+
})
|
|
1743
|
+
);
|
|
1744
|
+
continue;
|
|
1745
|
+
}
|
|
1746
|
+
if (!left || !right) continue;
|
|
1747
|
+
const leftConnection = {
|
|
1748
|
+
id: left.id,
|
|
1749
|
+
name: left.name,
|
|
1750
|
+
events: left.events,
|
|
1751
|
+
order: left.order,
|
|
1752
|
+
identityStability: left.identityStability
|
|
1753
|
+
};
|
|
1754
|
+
const rightConnection = {
|
|
1755
|
+
id: right.id,
|
|
1756
|
+
name: right.name,
|
|
1757
|
+
events: right.events,
|
|
1758
|
+
order: right.order,
|
|
1759
|
+
identityStability: right.identityStability
|
|
1760
|
+
};
|
|
1761
|
+
if (!same(leftConnection, rightConnection)) {
|
|
1762
|
+
changes.push(
|
|
1763
|
+
makeChange({
|
|
1764
|
+
layer: "client",
|
|
1765
|
+
type: "client_socket_connection_changed",
|
|
1766
|
+
routeKey: args.routeKey,
|
|
1767
|
+
entityId: identity,
|
|
1768
|
+
before: leftConnection,
|
|
1769
|
+
after: rightConnection
|
|
1770
|
+
})
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
if (!same(left.roomStrategy, right.roomStrategy)) {
|
|
1774
|
+
changes.push(
|
|
1775
|
+
makeChange({
|
|
1776
|
+
layer: "socket",
|
|
1777
|
+
type: "socket_room_strategy_changed",
|
|
1778
|
+
routeKey: args.routeKey,
|
|
1779
|
+
entityId: identity,
|
|
1780
|
+
before: left.roomStrategy,
|
|
1781
|
+
after: right.roomStrategy
|
|
1782
|
+
})
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
if (!same(left.cacheReducers, right.cacheReducers)) {
|
|
1786
|
+
changes.push(
|
|
1787
|
+
makeChange({
|
|
1788
|
+
layer: "socket",
|
|
1789
|
+
type: "socket_cache_reducer_changed",
|
|
1790
|
+
routeKey: args.routeKey,
|
|
1791
|
+
entityId: identity,
|
|
1792
|
+
before: left.cacheReducers,
|
|
1793
|
+
after: right.cacheReducers
|
|
1794
|
+
})
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return changes;
|
|
1799
|
+
}
|
|
1800
|
+
function diffClients(before, after) {
|
|
1801
|
+
const changes = [];
|
|
1802
|
+
const resourceIds = /* @__PURE__ */ new Set([
|
|
1803
|
+
...Object.keys(before.clients),
|
|
1804
|
+
...Object.keys(after.clients)
|
|
1805
|
+
]);
|
|
1806
|
+
for (const identity of Array.from(resourceIds).sort()) {
|
|
1807
|
+
if (!before.clients[identity] && after.clients[identity]) {
|
|
1808
|
+
changes.push(
|
|
1809
|
+
makeChange({
|
|
1810
|
+
layer: "client",
|
|
1811
|
+
type: "client_resource_added",
|
|
1812
|
+
entityId: identity,
|
|
1813
|
+
after: after.clients[identity]
|
|
1814
|
+
})
|
|
1815
|
+
);
|
|
1816
|
+
} else if (before.clients[identity] && !after.clients[identity]) {
|
|
1817
|
+
changes.push(
|
|
1818
|
+
makeChange({
|
|
1819
|
+
layer: "client",
|
|
1820
|
+
type: "client_resource_removed",
|
|
1821
|
+
entityId: identity,
|
|
1822
|
+
before: before.clients[identity]
|
|
1823
|
+
})
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
const previous = allBindings(before);
|
|
1828
|
+
const next = allBindings(after);
|
|
1829
|
+
const identities = /* @__PURE__ */ new Set([...previous.keys(), ...next.keys()]);
|
|
1830
|
+
for (const identity of Array.from(identities).sort()) {
|
|
1831
|
+
const left = previous.get(identity);
|
|
1832
|
+
const right = next.get(identity);
|
|
1833
|
+
if (!left && right) {
|
|
1834
|
+
changes.push(
|
|
1835
|
+
makeChange({
|
|
1836
|
+
layer: "client",
|
|
1837
|
+
type: "client_binding_added",
|
|
1838
|
+
routeKey: right.routeKey,
|
|
1839
|
+
entityId: identity,
|
|
1840
|
+
after: right
|
|
1841
|
+
})
|
|
1842
|
+
);
|
|
1843
|
+
continue;
|
|
1844
|
+
}
|
|
1845
|
+
if (left && !right) {
|
|
1846
|
+
changes.push(
|
|
1847
|
+
makeChange({
|
|
1848
|
+
layer: "client",
|
|
1849
|
+
type: "client_binding_removed",
|
|
1850
|
+
routeKey: left.routeKey,
|
|
1851
|
+
entityId: identity,
|
|
1852
|
+
before: left
|
|
1853
|
+
})
|
|
1854
|
+
);
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
if (!left || !right) continue;
|
|
1858
|
+
if (left.routeKey !== right.routeKey) {
|
|
1859
|
+
changes.push(
|
|
1860
|
+
makeChange({
|
|
1861
|
+
layer: "client",
|
|
1862
|
+
type: "client_binding_target_changed",
|
|
1863
|
+
routeKey: right.routeKey,
|
|
1864
|
+
entityId: identity,
|
|
1865
|
+
before: left.routeKey,
|
|
1866
|
+
after: right.routeKey
|
|
1867
|
+
})
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
if (left.enabled !== right.enabled) {
|
|
1871
|
+
changes.push(
|
|
1872
|
+
makeChange({
|
|
1873
|
+
layer: "client",
|
|
1874
|
+
type: right.enabled ? "client_binding_enabled" : "client_binding_disabled",
|
|
1875
|
+
routeKey: right.routeKey,
|
|
1876
|
+
entityId: identity,
|
|
1877
|
+
before: left.enabled,
|
|
1878
|
+
after: right.enabled
|
|
1879
|
+
})
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
if (!same(left.build, right.build)) {
|
|
1883
|
+
changes.push(
|
|
1884
|
+
makeChange({
|
|
1885
|
+
layer: "client",
|
|
1886
|
+
type: "client_build_options_changed",
|
|
1887
|
+
routeKey: right.routeKey,
|
|
1888
|
+
entityId: identity,
|
|
1889
|
+
before: left.build,
|
|
1890
|
+
after: right.build
|
|
1891
|
+
})
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
changes.push(
|
|
1895
|
+
...diffOrderedEntities({
|
|
1896
|
+
before: left.augments,
|
|
1897
|
+
after: right.augments,
|
|
1898
|
+
layer: "client",
|
|
1899
|
+
routeKey: right.routeKey,
|
|
1900
|
+
added: "client_augment_added",
|
|
1901
|
+
removed: "client_augment_removed",
|
|
1902
|
+
changed: "client_augment_changed",
|
|
1903
|
+
reordered: "client_augment_reordered"
|
|
1904
|
+
})
|
|
1905
|
+
);
|
|
1906
|
+
changes.push(
|
|
1907
|
+
...diffClientSocketConnections({
|
|
1908
|
+
before: left.socketConnections,
|
|
1909
|
+
after: right.socketConnections,
|
|
1910
|
+
routeKey: right.routeKey
|
|
1911
|
+
})
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
return changes;
|
|
1915
|
+
}
|
|
1916
|
+
function routeServerMap(snapshot) {
|
|
1917
|
+
const map = /* @__PURE__ */ new Map();
|
|
1918
|
+
for (const [routeKey, route] of Object.entries(snapshot.routes)) {
|
|
1919
|
+
if (route.server) map.set(routeKey, route.server);
|
|
1920
|
+
}
|
|
1921
|
+
return map;
|
|
1922
|
+
}
|
|
1923
|
+
function diffServers(before, after) {
|
|
1924
|
+
const changes = [];
|
|
1925
|
+
const previous = routeServerMap(before);
|
|
1926
|
+
const next = routeServerMap(after);
|
|
1927
|
+
const keys = /* @__PURE__ */ new Set([...previous.keys(), ...next.keys()]);
|
|
1928
|
+
for (const routeKey of Array.from(keys).sort()) {
|
|
1929
|
+
const left = previous.get(routeKey);
|
|
1930
|
+
const right = next.get(routeKey);
|
|
1931
|
+
if ((!left || !left.registered) && right?.registered) {
|
|
1932
|
+
changes.push(
|
|
1933
|
+
makeChange({
|
|
1934
|
+
layer: "server",
|
|
1935
|
+
type: "server_controller_registered",
|
|
1936
|
+
routeKey,
|
|
1937
|
+
entityId: right.controllerId ?? routeKey,
|
|
1938
|
+
after: right
|
|
1939
|
+
})
|
|
1940
|
+
);
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
if (left?.registered && (!right || !right.registered)) {
|
|
1944
|
+
changes.push(
|
|
1945
|
+
makeChange({
|
|
1946
|
+
layer: "server",
|
|
1947
|
+
type: "server_controller_unregistered",
|
|
1948
|
+
routeKey,
|
|
1949
|
+
entityId: left.controllerId ?? routeKey,
|
|
1950
|
+
before: left
|
|
1951
|
+
})
|
|
1952
|
+
);
|
|
1953
|
+
continue;
|
|
1954
|
+
}
|
|
1955
|
+
if (!left || !right || !left.registered || !right.registered) continue;
|
|
1956
|
+
if (left.enabled !== right.enabled) {
|
|
1957
|
+
changes.push(
|
|
1958
|
+
makeChange({
|
|
1959
|
+
layer: "server",
|
|
1960
|
+
type: right.enabled ? "server_endpoint_enabled" : "server_endpoint_disabled",
|
|
1961
|
+
routeKey,
|
|
1962
|
+
entityId: right.controllerId ?? routeKey,
|
|
1963
|
+
before: left.enabled,
|
|
1964
|
+
after: right.enabled
|
|
1965
|
+
})
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
if (left.controllerId !== right.controllerId || left.controllerName !== right.controllerName) {
|
|
1969
|
+
changes.push(
|
|
1970
|
+
makeChange({
|
|
1971
|
+
layer: "server",
|
|
1972
|
+
type: "server_controller_changed",
|
|
1973
|
+
routeKey,
|
|
1974
|
+
entityId: right.controllerId ?? routeKey,
|
|
1975
|
+
before: {
|
|
1976
|
+
id: left.controllerId ?? null,
|
|
1977
|
+
name: left.controllerName ?? null
|
|
1978
|
+
},
|
|
1979
|
+
after: {
|
|
1980
|
+
id: right.controllerId ?? null,
|
|
1981
|
+
name: right.controllerName ?? null
|
|
1982
|
+
}
|
|
1983
|
+
})
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
changes.push(
|
|
1987
|
+
...diffOrderedEntities({
|
|
1988
|
+
before: left.before,
|
|
1989
|
+
after: right.before,
|
|
1990
|
+
layer: "server",
|
|
1991
|
+
routeKey,
|
|
1992
|
+
added: "server_middleware_added",
|
|
1993
|
+
removed: "server_middleware_removed",
|
|
1994
|
+
changed: "server_middleware_changed",
|
|
1995
|
+
reordered: "server_middleware_reordered"
|
|
1996
|
+
})
|
|
1997
|
+
);
|
|
1998
|
+
if (left.validateOutput !== right.validateOutput) {
|
|
1999
|
+
changes.push(
|
|
2000
|
+
makeChange({
|
|
2001
|
+
layer: "server",
|
|
2002
|
+
type: "server_output_validation_changed",
|
|
2003
|
+
routeKey,
|
|
2004
|
+
entityId: right.controllerId ?? routeKey,
|
|
2005
|
+
before: left.validateOutput,
|
|
2006
|
+
after: right.validateOutput
|
|
2007
|
+
})
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
return changes;
|
|
2012
|
+
}
|
|
2013
|
+
function diffSockets(before, after) {
|
|
2014
|
+
const changes = [];
|
|
2015
|
+
const identities = /* @__PURE__ */ new Set([
|
|
2016
|
+
...Object.keys(before.sockets),
|
|
2017
|
+
...Object.keys(after.sockets)
|
|
2018
|
+
]);
|
|
2019
|
+
for (const identity of Array.from(identities).sort()) {
|
|
2020
|
+
const left = before.sockets[identity];
|
|
2021
|
+
const right = after.sockets[identity];
|
|
2022
|
+
if (!left && right) {
|
|
2023
|
+
changes.push(
|
|
2024
|
+
makeChange({
|
|
2025
|
+
layer: "socket",
|
|
2026
|
+
type: "socket_runtime_added",
|
|
2027
|
+
entityId: identity,
|
|
2028
|
+
after: right
|
|
2029
|
+
})
|
|
2030
|
+
);
|
|
2031
|
+
continue;
|
|
2032
|
+
}
|
|
2033
|
+
if (left && !right) {
|
|
2034
|
+
changes.push(
|
|
2035
|
+
makeChange({
|
|
2036
|
+
layer: "socket",
|
|
2037
|
+
type: "socket_runtime_removed",
|
|
2038
|
+
entityId: identity,
|
|
2039
|
+
before: left
|
|
2040
|
+
})
|
|
2041
|
+
);
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
if (!left || !right) continue;
|
|
2045
|
+
const eventNames = /* @__PURE__ */ new Set([
|
|
2046
|
+
...Object.keys(left.contract.events),
|
|
2047
|
+
...Object.keys(right.contract.events)
|
|
2048
|
+
]);
|
|
2049
|
+
for (const eventName of Array.from(eventNames).sort()) {
|
|
2050
|
+
const previousEvent = left.contract.events[eventName];
|
|
2051
|
+
const nextEvent = right.contract.events[eventName];
|
|
2052
|
+
const eventIdentity = `${identity}:event:${eventName}`;
|
|
2053
|
+
if (!previousEvent && nextEvent) {
|
|
2054
|
+
changes.push(
|
|
2055
|
+
makeChange({
|
|
2056
|
+
layer: "socket",
|
|
2057
|
+
type: "socket_contract_event_added",
|
|
2058
|
+
entityId: eventIdentity,
|
|
2059
|
+
after: nextEvent
|
|
2060
|
+
})
|
|
2061
|
+
);
|
|
2062
|
+
} else if (previousEvent && !nextEvent) {
|
|
2063
|
+
changes.push(
|
|
2064
|
+
makeChange({
|
|
2065
|
+
layer: "socket",
|
|
2066
|
+
type: "socket_contract_event_removed",
|
|
2067
|
+
entityId: eventIdentity,
|
|
2068
|
+
before: previousEvent
|
|
2069
|
+
})
|
|
2070
|
+
);
|
|
2071
|
+
} else if (previousEvent && nextEvent && !same(previousEvent.schema, nextEvent.schema)) {
|
|
2072
|
+
changes.push(
|
|
2073
|
+
makeChange({
|
|
2074
|
+
layer: "socket",
|
|
2075
|
+
type: "socket_payload_schema_changed",
|
|
2076
|
+
entityId: eventIdentity,
|
|
2077
|
+
before: previousEvent.schema,
|
|
2078
|
+
after: nextEvent.schema
|
|
2079
|
+
})
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
if (!same(left.configuration.heartbeat, right.configuration.heartbeat)) {
|
|
2084
|
+
changes.push(
|
|
2085
|
+
makeChange({
|
|
2086
|
+
layer: "socket",
|
|
2087
|
+
type: "socket_heartbeat_configuration_changed",
|
|
2088
|
+
entityId: identity,
|
|
2089
|
+
before: left.configuration.heartbeat,
|
|
2090
|
+
after: right.configuration.heartbeat
|
|
2091
|
+
})
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
const previousHandlers = new Map(
|
|
2095
|
+
left.configuration.handlers.map((handler) => [handler.identity, handler])
|
|
2096
|
+
);
|
|
2097
|
+
const nextHandlers = new Map(
|
|
2098
|
+
right.configuration.handlers.map((handler) => [handler.identity, handler])
|
|
2099
|
+
);
|
|
2100
|
+
for (const handlerIdentity of Array.from(
|
|
2101
|
+
/* @__PURE__ */ new Set([...previousHandlers.keys(), ...nextHandlers.keys()])
|
|
2102
|
+
).sort()) {
|
|
2103
|
+
const previousHandler = previousHandlers.get(handlerIdentity);
|
|
2104
|
+
const nextHandler = nextHandlers.get(handlerIdentity);
|
|
2105
|
+
if (!previousHandler && nextHandler) {
|
|
2106
|
+
changes.push(
|
|
2107
|
+
makeChange({
|
|
2108
|
+
layer: "socket",
|
|
2109
|
+
type: "socket_handler_added",
|
|
2110
|
+
entityId: handlerIdentity,
|
|
2111
|
+
after: nextHandler
|
|
2112
|
+
})
|
|
2113
|
+
);
|
|
2114
|
+
} else if (previousHandler && !nextHandler) {
|
|
2115
|
+
changes.push(
|
|
2116
|
+
makeChange({
|
|
2117
|
+
layer: "socket",
|
|
2118
|
+
type: "socket_handler_removed",
|
|
2119
|
+
entityId: handlerIdentity,
|
|
2120
|
+
before: previousHandler
|
|
2121
|
+
})
|
|
2122
|
+
);
|
|
2123
|
+
} else if (previousHandler && nextHandler && !same(previousHandler, nextHandler)) {
|
|
2124
|
+
changes.push(
|
|
2125
|
+
makeChange({
|
|
2126
|
+
layer: "socket",
|
|
2127
|
+
type: "socket_handler_removed",
|
|
2128
|
+
entityId: handlerIdentity,
|
|
2129
|
+
before: previousHandler
|
|
2130
|
+
}),
|
|
2131
|
+
makeChange({
|
|
2132
|
+
layer: "socket",
|
|
2133
|
+
type: "socket_handler_added",
|
|
2134
|
+
entityId: handlerIdentity,
|
|
2135
|
+
after: nextHandler
|
|
2136
|
+
})
|
|
2137
|
+
);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
const previousBindings = new Map(
|
|
2141
|
+
left.bindings.map((binding) => [binding.identity, binding])
|
|
2142
|
+
);
|
|
2143
|
+
const nextBindings = new Map(
|
|
2144
|
+
right.bindings.map((binding) => [binding.identity, binding])
|
|
2145
|
+
);
|
|
2146
|
+
for (const bindingIdentity of Array.from(
|
|
2147
|
+
/* @__PURE__ */ new Set([...previousBindings.keys(), ...nextBindings.keys()])
|
|
2148
|
+
).sort()) {
|
|
2149
|
+
const previousBinding = previousBindings.get(bindingIdentity);
|
|
2150
|
+
const nextBinding = nextBindings.get(bindingIdentity);
|
|
2151
|
+
if (!same(previousBinding, nextBinding)) {
|
|
2152
|
+
changes.push(
|
|
2153
|
+
makeChange({
|
|
2154
|
+
layer: "socket",
|
|
2155
|
+
type: "socket_binding_changed",
|
|
2156
|
+
routeKey: nextBinding?.routeKey ?? previousBinding?.routeKey,
|
|
2157
|
+
entityId: bindingIdentity,
|
|
2158
|
+
before: previousBinding,
|
|
2159
|
+
after: nextBinding
|
|
2160
|
+
})
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
return changes;
|
|
2166
|
+
}
|
|
2167
|
+
function summarize(changes) {
|
|
2168
|
+
const byLayer = {
|
|
2169
|
+
contract: 0,
|
|
2170
|
+
client: 0,
|
|
2171
|
+
server: 0,
|
|
2172
|
+
socket: 0
|
|
2173
|
+
};
|
|
2174
|
+
const bySeverity = {
|
|
2175
|
+
breaking: 0,
|
|
2176
|
+
behavioral: 0,
|
|
2177
|
+
"non-breaking": 0,
|
|
2178
|
+
informational: 0
|
|
2179
|
+
};
|
|
2180
|
+
const byType = {};
|
|
2181
|
+
for (const change of changes) {
|
|
2182
|
+
byLayer[change.layer] += 1;
|
|
2183
|
+
bySeverity[change.severity] += 1;
|
|
2184
|
+
byType[change.type] = (byType[change.type] ?? 0) + 1;
|
|
2185
|
+
}
|
|
2186
|
+
return { total: changes.length, byLayer, bySeverity, byType };
|
|
2187
|
+
}
|
|
2188
|
+
function diffRRRoutesSnapshots(before, after, options = {}) {
|
|
2189
|
+
const changes = [
|
|
2190
|
+
...diffContract(before, after),
|
|
2191
|
+
...diffClients(before, after),
|
|
2192
|
+
...diffServers(before, after),
|
|
2193
|
+
...diffSockets(before, after)
|
|
2194
|
+
].sort((left, right) => {
|
|
2195
|
+
const route = (left.routeKey ?? "").localeCompare(right.routeKey ?? "");
|
|
2196
|
+
if (route !== 0) return route;
|
|
2197
|
+
const layer = left.layer.localeCompare(right.layer);
|
|
2198
|
+
if (layer !== 0) return layer;
|
|
2199
|
+
const type = left.type.localeCompare(right.type);
|
|
2200
|
+
return type !== 0 ? type : left.entityId.localeCompare(right.entityId);
|
|
2201
|
+
});
|
|
2202
|
+
const scope = options.scope ?? "default";
|
|
2203
|
+
const trigger = options.trigger ?? { type: "manual" };
|
|
2204
|
+
const transition = {
|
|
2205
|
+
scope,
|
|
2206
|
+
from: before.meta.snapshotHash,
|
|
2207
|
+
to: after.meta.snapshotHash
|
|
2208
|
+
};
|
|
2209
|
+
return {
|
|
2210
|
+
id: hashCanonicalValue(transition),
|
|
2211
|
+
scope,
|
|
2212
|
+
createdAt: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2213
|
+
from: {
|
|
2214
|
+
snapshotHash: before.meta.snapshotHash,
|
|
2215
|
+
capturedAt: before.meta.capturedAt,
|
|
2216
|
+
checkpointId: options.fromCheckpointId
|
|
2217
|
+
},
|
|
2218
|
+
to: {
|
|
2219
|
+
snapshotHash: after.meta.snapshotHash,
|
|
2220
|
+
capturedAt: after.meta.capturedAt,
|
|
2221
|
+
checkpointId: options.toCheckpointId
|
|
2222
|
+
},
|
|
2223
|
+
trigger,
|
|
2224
|
+
summary: summarize(changes),
|
|
2225
|
+
changes
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
var createSnapshotChangeSet = diffRRRoutesSnapshots;
|
|
2229
|
+
|
|
2230
|
+
// src/exportFinalizedLeaves.changelog.ts
|
|
1348
2231
|
var execFile = promisify(execFileCallback);
|
|
1349
2232
|
var CHANGESET_CFG_FIELDS = [
|
|
1350
2233
|
"summary",
|
|
@@ -1357,7 +2240,15 @@ var CHANGESET_CFG_FIELDS = [
|
|
|
1357
2240
|
"feed"
|
|
1358
2241
|
];
|
|
1359
2242
|
var SCHEMA_SECTIONS = ["params", "query", "body", "output"];
|
|
1360
|
-
var MODULE_EXTENSIONS = [
|
|
2243
|
+
var MODULE_EXTENSIONS = [
|
|
2244
|
+
".ts",
|
|
2245
|
+
".tsx",
|
|
2246
|
+
".mts",
|
|
2247
|
+
".cts",
|
|
2248
|
+
".js",
|
|
2249
|
+
".mjs",
|
|
2250
|
+
".cjs"
|
|
2251
|
+
];
|
|
1361
2252
|
var COMPILE_CACHE_VERSION = "v2";
|
|
1362
2253
|
var SOURCE_KEY_BY_SECTION = {
|
|
1363
2254
|
params: "paramsSchema",
|
|
@@ -1368,7 +2259,7 @@ var SOURCE_KEY_BY_SECTION = {
|
|
|
1368
2259
|
function normalizePath(p) {
|
|
1369
2260
|
return p.replace(/\\/g, "/");
|
|
1370
2261
|
}
|
|
1371
|
-
function
|
|
2262
|
+
function stableStringify2(value) {
|
|
1372
2263
|
return JSON.stringify(value, (_key, v) => {
|
|
1373
2264
|
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
1374
2265
|
const sorted = Object.entries(v).sort(([a], [b]) => a.localeCompare(b)).reduce((acc, [k, inner]) => {
|
|
@@ -1397,7 +2288,7 @@ function toCfgSubset(cfg) {
|
|
|
1397
2288
|
return out;
|
|
1398
2289
|
}
|
|
1399
2290
|
function entrySignature(entry) {
|
|
1400
|
-
return
|
|
2291
|
+
return stableStringify2({
|
|
1401
2292
|
type: entry.type,
|
|
1402
2293
|
nullable: Boolean(entry.nullable),
|
|
1403
2294
|
optional: Boolean(entry.optional),
|
|
@@ -1405,7 +2296,9 @@ function entrySignature(entry) {
|
|
|
1405
2296
|
});
|
|
1406
2297
|
}
|
|
1407
2298
|
function semverToTuple(tag) {
|
|
1408
|
-
const match = tag.match(
|
|
2299
|
+
const match = tag.match(
|
|
2300
|
+
/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
|
|
2301
|
+
);
|
|
1409
2302
|
if (!match) return null;
|
|
1410
2303
|
return {
|
|
1411
2304
|
major: Number(match[1]),
|
|
@@ -1451,7 +2344,9 @@ async function findDefaultFromCommit(cwd, toCommit) {
|
|
|
1451
2344
|
const root = await runGit(cwd, ["rev-list", "--max-parents=0", toCommit]);
|
|
1452
2345
|
const rootCommit = root.split("\n").map((item) => item.trim()).filter(Boolean)[0];
|
|
1453
2346
|
if (!rootCommit) {
|
|
1454
|
-
throw new Error(
|
|
2347
|
+
throw new Error(
|
|
2348
|
+
"Failed to resolve a baseline commit for changelog generation."
|
|
2349
|
+
);
|
|
1455
2350
|
}
|
|
1456
2351
|
return rootCommit;
|
|
1457
2352
|
}
|
|
@@ -1570,11 +2465,15 @@ async function commitTouchedRelevantPaths(cwd, commit, moduleCandidates, tsconfi
|
|
|
1570
2465
|
]);
|
|
1571
2466
|
const changedFiles = changed.split("\n").map((item) => normalizePath(item.trim()).replace(/^\/+/, "")).filter(Boolean);
|
|
1572
2467
|
const moduleFiles = Array.from(
|
|
1573
|
-
new Set(
|
|
2468
|
+
new Set(
|
|
2469
|
+
moduleCandidates.map((item) => normalizePath(item).replace(/^\/+/, ""))
|
|
2470
|
+
)
|
|
1574
2471
|
);
|
|
1575
2472
|
const moduleDirs = Array.from(
|
|
1576
2473
|
new Set(
|
|
1577
|
-
moduleFiles.map(
|
|
2474
|
+
moduleFiles.map(
|
|
2475
|
+
(item) => normalizePath(path4.posix.dirname(item)).replace(/^\/+/, "")
|
|
2476
|
+
).filter((item) => item && item !== ".")
|
|
1578
2477
|
)
|
|
1579
2478
|
);
|
|
1580
2479
|
const tsconfigFile = tsconfigRel ? normalizePath(tsconfigRel).replace(/^\/+/, "") : void 0;
|
|
@@ -1597,7 +2496,12 @@ async function filterCommits(cwd, commits, moduleCandidates, tsconfigRel) {
|
|
|
1597
2496
|
const keep = [commits[0]];
|
|
1598
2497
|
for (let i = 1; i < commits.length; i += 1) {
|
|
1599
2498
|
const commit = commits[i];
|
|
1600
|
-
if (await commitTouchedRelevantPaths(
|
|
2499
|
+
if (await commitTouchedRelevantPaths(
|
|
2500
|
+
cwd,
|
|
2501
|
+
commit,
|
|
2502
|
+
moduleCandidates,
|
|
2503
|
+
tsconfigRel
|
|
2504
|
+
)) {
|
|
1601
2505
|
keep.push(commit);
|
|
1602
2506
|
}
|
|
1603
2507
|
}
|
|
@@ -1608,7 +2512,12 @@ async function filterCommits(cwd, commits, moduleCandidates, tsconfigRel) {
|
|
|
1608
2512
|
return keep;
|
|
1609
2513
|
}
|
|
1610
2514
|
async function getCommitMetadata(cwd, commit) {
|
|
1611
|
-
const out = await runGit(cwd, [
|
|
2515
|
+
const out = await runGit(cwd, [
|
|
2516
|
+
"show",
|
|
2517
|
+
"-s",
|
|
2518
|
+
"--format=%H%x1f%aI%x1f%an%x1f%s",
|
|
2519
|
+
commit
|
|
2520
|
+
]);
|
|
1612
2521
|
const [sha, authorDate, authorName, subject] = out.split("");
|
|
1613
2522
|
if (!sha || !authorDate || !authorName) {
|
|
1614
2523
|
throw new Error(`Unable to read commit metadata for ${commit}`);
|
|
@@ -1751,7 +2660,7 @@ function diffRouteCfg(before, after) {
|
|
|
1751
2660
|
for (const field of CHANGESET_CFG_FIELDS) {
|
|
1752
2661
|
const prev = before[field];
|
|
1753
2662
|
const next = after[field];
|
|
1754
|
-
if (
|
|
2663
|
+
if (stableStringify2(prev) !== stableStringify2(next)) {
|
|
1755
2664
|
out.push({
|
|
1756
2665
|
field,
|
|
1757
2666
|
before: prev,
|
|
@@ -1813,7 +2722,7 @@ function diffSourceSchemas(before, after) {
|
|
|
1813
2722
|
continue;
|
|
1814
2723
|
}
|
|
1815
2724
|
if (!prevSignatures || !nextSignatures) continue;
|
|
1816
|
-
if (
|
|
2725
|
+
if (stableStringify2(prevSignatures) !== stableStringify2(nextSignatures)) {
|
|
1817
2726
|
out.push({
|
|
1818
2727
|
section,
|
|
1819
2728
|
path: schemaPath,
|
|
@@ -2027,13 +2936,16 @@ async function compileSnapshotEntry(options) {
|
|
|
2027
2936
|
log(
|
|
2028
2937
|
`[changelog] compile cache ${options.noCache ? "disabled" : "miss"}: ${options.commitSha.slice(0, 12)}`
|
|
2029
2938
|
);
|
|
2030
|
-
await fs2.rm(path4.join(options.cacheRoot, cacheKey), {
|
|
2031
|
-
|
|
2032
|
-
|
|
2939
|
+
await fs2.rm(path4.join(options.cacheRoot, cacheKey), {
|
|
2940
|
+
recursive: true,
|
|
2941
|
+
force: true
|
|
2942
|
+
}).catch(() => void 0);
|
|
2033
2943
|
await fs2.mkdir(emitRoot, { recursive: true });
|
|
2034
2944
|
const readResult = ts2.readConfigFile(options.tsconfigPath, ts2.sys.readFile);
|
|
2035
2945
|
if (readResult.error) {
|
|
2036
|
-
throw new Error(
|
|
2946
|
+
throw new Error(
|
|
2947
|
+
ts2.flattenDiagnosticMessageText(readResult.error.messageText, "\n")
|
|
2948
|
+
);
|
|
2037
2949
|
}
|
|
2038
2950
|
const parsed = ts2.parseJsonConfigFileContent(
|
|
2039
2951
|
readResult.config,
|
|
@@ -2062,7 +2974,10 @@ async function compileSnapshotEntry(options) {
|
|
|
2062
2974
|
getCurrentDirectory: () => options.worktreeDir,
|
|
2063
2975
|
getNewLine: () => "\n"
|
|
2064
2976
|
};
|
|
2065
|
-
const message = ts2.formatDiagnosticsWithColorAndContext(
|
|
2977
|
+
const message = ts2.formatDiagnosticsWithColorAndContext(
|
|
2978
|
+
diagnostics.slice(0, 20),
|
|
2979
|
+
host
|
|
2980
|
+
);
|
|
2066
2981
|
throw new Error(
|
|
2067
2982
|
`TypeScript snapshot compile failed for ${options.moduleRel} at ${options.commitSha.slice(0, 12)}:
|
|
2068
2983
|
${message}`
|
|
@@ -2082,7 +2997,13 @@ async function loadSnapshotExport(options) {
|
|
|
2082
2997
|
}
|
|
2083
2998
|
async function createSnapshot(repoRoot, moduleCandidates, exportName, commit, tempRoot, tsconfigRel, exportOptions, log) {
|
|
2084
2999
|
const worktreeDir = path4.join(tempRoot, `wt-${commit.sha.slice(0, 12)}`);
|
|
2085
|
-
await runGit(repoRoot, [
|
|
3000
|
+
await runGit(repoRoot, [
|
|
3001
|
+
"worktree",
|
|
3002
|
+
"add",
|
|
3003
|
+
"--detach",
|
|
3004
|
+
worktreeDir,
|
|
3005
|
+
commit.sha
|
|
3006
|
+
]);
|
|
2086
3007
|
try {
|
|
2087
3008
|
try {
|
|
2088
3009
|
await ensureWorktreeDependencies(repoRoot, worktreeDir);
|
|
@@ -2167,13 +3088,94 @@ async function createSnapshot(repoRoot, moduleCandidates, exportName, commit, te
|
|
|
2167
3088
|
};
|
|
2168
3089
|
}
|
|
2169
3090
|
} finally {
|
|
2170
|
-
await runGit(repoRoot, [
|
|
3091
|
+
await runGit(repoRoot, [
|
|
3092
|
+
"worktree",
|
|
3093
|
+
"remove",
|
|
3094
|
+
"--force",
|
|
3095
|
+
worktreeDir
|
|
3096
|
+
]).catch(() => void 0);
|
|
2171
3097
|
await fs2.rm(worktreeDir, { recursive: true, force: true }).catch(() => void 0);
|
|
2172
3098
|
}
|
|
2173
3099
|
}
|
|
3100
|
+
function toCanonicalGitSnapshot(snapshot) {
|
|
3101
|
+
const routes = Object.fromEntries(
|
|
3102
|
+
Object.entries(snapshot.routesByKey).map(([routeKey, route]) => [
|
|
3103
|
+
routeKey,
|
|
3104
|
+
{
|
|
3105
|
+
routeKey,
|
|
3106
|
+
method: route.method,
|
|
3107
|
+
path: route.path,
|
|
3108
|
+
contract: {
|
|
3109
|
+
serializedLeaf: {
|
|
3110
|
+
key: routeKey,
|
|
3111
|
+
method: route.method.toLowerCase(),
|
|
3112
|
+
path: route.path,
|
|
3113
|
+
cfg: { ...route.cfg, schemas: {} }
|
|
3114
|
+
},
|
|
3115
|
+
schemaFlat: route.flatSchema,
|
|
3116
|
+
source: route.sourceBySection,
|
|
3117
|
+
fingerprint: hashCanonicalValue({
|
|
3118
|
+
cfg: route.cfg,
|
|
3119
|
+
schema: route.flatSchema
|
|
3120
|
+
})
|
|
3121
|
+
},
|
|
3122
|
+
clients: [],
|
|
3123
|
+
sockets: [],
|
|
3124
|
+
status: {
|
|
3125
|
+
client: "unbound",
|
|
3126
|
+
server: "unavailable",
|
|
3127
|
+
socket: "none"
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
])
|
|
3131
|
+
);
|
|
3132
|
+
const snapshotHash = hashCanonicalValue(routes);
|
|
3133
|
+
return {
|
|
3134
|
+
schemaVersion: 1,
|
|
3135
|
+
meta: {
|
|
3136
|
+
capturedAt: snapshot.commit.authorDate,
|
|
3137
|
+
snapshotHash,
|
|
3138
|
+
commit: snapshot.commit.sha,
|
|
3139
|
+
packageVersions: {}
|
|
3140
|
+
},
|
|
3141
|
+
capabilities: {
|
|
3142
|
+
contract: true,
|
|
3143
|
+
client: false,
|
|
3144
|
+
server: false,
|
|
3145
|
+
socket: false,
|
|
3146
|
+
live: false,
|
|
3147
|
+
source: true
|
|
3148
|
+
},
|
|
3149
|
+
routes,
|
|
3150
|
+
clients: {},
|
|
3151
|
+
servers: {},
|
|
3152
|
+
sockets: {},
|
|
3153
|
+
diagnostics: []
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
2174
3156
|
function diffSnapshots(previous, current) {
|
|
2175
3157
|
const routeEvents = [];
|
|
2176
3158
|
const sourceEvents = [];
|
|
3159
|
+
const changeSet = diffRRRoutesSnapshots(
|
|
3160
|
+
toCanonicalGitSnapshot(previous),
|
|
3161
|
+
toCanonicalGitSnapshot(current),
|
|
3162
|
+
{
|
|
3163
|
+
scope: "git",
|
|
3164
|
+
trigger: {
|
|
3165
|
+
type: "git",
|
|
3166
|
+
externalId: current.commit.sha,
|
|
3167
|
+
label: current.commit.subject
|
|
3168
|
+
},
|
|
3169
|
+
createdAt: current.commit.authorDate
|
|
3170
|
+
}
|
|
3171
|
+
);
|
|
3172
|
+
const canonicalRouteTypes = /* @__PURE__ */ new Map();
|
|
3173
|
+
for (const change of changeSet.changes) {
|
|
3174
|
+
if (change.layer !== "contract" || !change.routeKey) continue;
|
|
3175
|
+
const types = canonicalRouteTypes.get(change.routeKey) ?? /* @__PURE__ */ new Set();
|
|
3176
|
+
types.add(change.type);
|
|
3177
|
+
canonicalRouteTypes.set(change.routeKey, types);
|
|
3178
|
+
}
|
|
2177
3179
|
const routeKeys = /* @__PURE__ */ new Set([
|
|
2178
3180
|
...Object.keys(previous.routesByKey),
|
|
2179
3181
|
...Object.keys(current.routesByKey)
|
|
@@ -2181,7 +3183,8 @@ function diffSnapshots(previous, current) {
|
|
|
2181
3183
|
for (const routeKey of Array.from(routeKeys).sort()) {
|
|
2182
3184
|
const prevRoute = previous.routesByKey[routeKey];
|
|
2183
3185
|
const nextRoute = current.routesByKey[routeKey];
|
|
2184
|
-
|
|
3186
|
+
const canonicalTypes = canonicalRouteTypes.get(routeKey) ?? /* @__PURE__ */ new Set();
|
|
3187
|
+
if (!prevRoute && nextRoute && canonicalTypes.has("route_added")) {
|
|
2185
3188
|
const schemaDiff2 = diffRouteSchemas({}, nextRoute.flatSchema);
|
|
2186
3189
|
routeEvents.push({
|
|
2187
3190
|
type: "route_added",
|
|
@@ -2193,7 +3196,7 @@ function diffSnapshots(previous, current) {
|
|
|
2193
3196
|
});
|
|
2194
3197
|
continue;
|
|
2195
3198
|
}
|
|
2196
|
-
if (prevRoute && !nextRoute) {
|
|
3199
|
+
if (prevRoute && !nextRoute && canonicalTypes.has("route_removed")) {
|
|
2197
3200
|
routeEvents.push({
|
|
2198
3201
|
type: "route_removed",
|
|
2199
3202
|
commitSha: current.commit.sha,
|
|
@@ -2204,8 +3207,11 @@ function diffSnapshots(previous, current) {
|
|
|
2204
3207
|
continue;
|
|
2205
3208
|
}
|
|
2206
3209
|
if (!prevRoute || !nextRoute) continue;
|
|
2207
|
-
const schemaDiff = diffRouteSchemas(
|
|
2208
|
-
|
|
3210
|
+
const schemaDiff = diffRouteSchemas(
|
|
3211
|
+
prevRoute.flatSchema,
|
|
3212
|
+
nextRoute.flatSchema
|
|
3213
|
+
);
|
|
3214
|
+
if (schemaDiff.length > 0 && canonicalTypes.has("schema_changed")) {
|
|
2209
3215
|
routeEvents.push({
|
|
2210
3216
|
type: "schema_changed",
|
|
2211
3217
|
commitSha: current.commit.sha,
|
|
@@ -2216,7 +3222,9 @@ function diffSnapshots(previous, current) {
|
|
|
2216
3222
|
});
|
|
2217
3223
|
}
|
|
2218
3224
|
const cfgDiff = diffRouteCfg(prevRoute.cfg, nextRoute.cfg);
|
|
2219
|
-
if (cfgDiff.length > 0
|
|
3225
|
+
if (cfgDiff.length > 0 && ["cfg_changed", "description_changed", "files_changed"].some(
|
|
3226
|
+
(type) => canonicalTypes.has(type)
|
|
3227
|
+
)) {
|
|
2220
3228
|
routeEvents.push({
|
|
2221
3229
|
type: "cfg_changed",
|
|
2222
3230
|
commitSha: current.commit.sha,
|
|
@@ -2263,8 +3271,12 @@ function diffSnapshots(previous, current) {
|
|
|
2263
3271
|
const schemaDiff = diffSourceSchemas(prevSource, nextSource);
|
|
2264
3272
|
const prevRoutes = new Set(prevSource.routeKeys);
|
|
2265
3273
|
const nextRoutes = new Set(nextSource.routeKeys);
|
|
2266
|
-
const routeAdded = Array.from(nextRoutes).filter(
|
|
2267
|
-
|
|
3274
|
+
const routeAdded = Array.from(nextRoutes).filter(
|
|
3275
|
+
(routeKey) => !prevRoutes.has(routeKey)
|
|
3276
|
+
);
|
|
3277
|
+
const routeRemoved = Array.from(prevRoutes).filter(
|
|
3278
|
+
(routeKey) => !nextRoutes.has(routeKey)
|
|
3279
|
+
);
|
|
2268
3280
|
if (schemaDiff.length > 0 || routeAdded.length > 0 || routeRemoved.length > 0) {
|
|
2269
3281
|
sourceEvents.push({
|
|
2270
3282
|
type: "source_schema_changed",
|
|
@@ -2280,28 +3292,10 @@ function diffSnapshots(previous, current) {
|
|
|
2280
3292
|
}
|
|
2281
3293
|
return {
|
|
2282
3294
|
routeEvents,
|
|
2283
|
-
sourceEvents
|
|
3295
|
+
sourceEvents,
|
|
3296
|
+
changeSet
|
|
2284
3297
|
};
|
|
2285
3298
|
}
|
|
2286
|
-
var BAKED_PAYLOAD_MARKER2 = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
|
|
2287
|
-
function escapePayloadForInlineScript2(payload) {
|
|
2288
|
-
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
|
|
2289
|
-
}
|
|
2290
|
-
function injectPayloadIntoViewerHtml2(htmlTemplate, payload) {
|
|
2291
|
-
const payloadScript = `${BAKED_PAYLOAD_MARKER2}
|
|
2292
|
-
<script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript2(
|
|
2293
|
-
payload
|
|
2294
|
-
)};</script>`;
|
|
2295
|
-
if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER2)) {
|
|
2296
|
-
return htmlTemplate.replace(BAKED_PAYLOAD_MARKER2, payloadScript);
|
|
2297
|
-
}
|
|
2298
|
-
if (htmlTemplate.includes("</body>")) {
|
|
2299
|
-
return htmlTemplate.replace("</body>", `${payloadScript}
|
|
2300
|
-
</body>`);
|
|
2301
|
-
}
|
|
2302
|
-
return `${payloadScript}
|
|
2303
|
-
${htmlTemplate}`;
|
|
2304
|
-
}
|
|
2305
3299
|
async function resolveViewerTemplatePath2(viewerTemplateFile) {
|
|
2306
3300
|
if (viewerTemplateFile) {
|
|
2307
3301
|
const resolved = path4.resolve(viewerTemplateFile);
|
|
@@ -2314,7 +3308,10 @@ async function resolveViewerTemplatePath2(viewerTemplateFile) {
|
|
|
2314
3308
|
"node_modules/@emeryld/rrroutes-export/tools/finalized-leaves-viewer.html"
|
|
2315
3309
|
),
|
|
2316
3310
|
path4.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
|
|
2317
|
-
path4.resolve(
|
|
3311
|
+
path4.resolve(
|
|
3312
|
+
process.cwd(),
|
|
3313
|
+
"packages/export/tools/finalized-leaves-viewer.html"
|
|
3314
|
+
)
|
|
2318
3315
|
];
|
|
2319
3316
|
for (const candidate of candidates) {
|
|
2320
3317
|
try {
|
|
@@ -2335,7 +3332,11 @@ async function writeJsonExport2(payload, outFile) {
|
|
|
2335
3332
|
async function writeBakedHtmlExport2(payload, htmlFile, viewerTemplateFile) {
|
|
2336
3333
|
const templatePath = await resolveViewerTemplatePath2(viewerTemplateFile);
|
|
2337
3334
|
const template = templatePath ? await fs2.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
|
|
2338
|
-
const baked =
|
|
3335
|
+
const baked = injectBakedInspectorPayload(template, payload, {
|
|
3336
|
+
marker: "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->",
|
|
3337
|
+
globalName: "__FINALIZED_LEAVES_PAYLOAD",
|
|
3338
|
+
scriptId: "finalized-leaves-baked-payload"
|
|
3339
|
+
});
|
|
2339
3340
|
const resolved = path4.resolve(htmlFile);
|
|
2340
3341
|
await fs2.mkdir(path4.dirname(resolved), { recursive: true });
|
|
2341
3342
|
await fs2.writeFile(resolved, baked, "utf8");
|
|
@@ -2386,7 +3387,9 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2386
3387
|
log(`[changelog] resolving repository root from ${cwd}`);
|
|
2387
3388
|
const repoRoot = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
2388
3389
|
const modulePathAbsolute = path4.resolve(cwd, options.modulePath);
|
|
2389
|
-
const modulePathRelative = normalizePath(
|
|
3390
|
+
const modulePathRelative = normalizePath(
|
|
3391
|
+
path4.relative(repoRoot, modulePathAbsolute)
|
|
3392
|
+
);
|
|
2390
3393
|
if (modulePathRelative.startsWith("..")) {
|
|
2391
3394
|
throw new Error("modulePath must resolve inside the git repository root.");
|
|
2392
3395
|
}
|
|
@@ -2398,7 +3401,9 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2398
3401
|
const exportName = options.exportName ?? "leaves";
|
|
2399
3402
|
log("[changelog] resolving commit window");
|
|
2400
3403
|
const window = await resolveCommitWindow(repoRoot, options.from, options.to);
|
|
2401
|
-
log(
|
|
3404
|
+
log(
|
|
3405
|
+
`[changelog] window: ${window.from.slice(0, 12)}..${window.to.slice(0, 12)}`
|
|
3406
|
+
);
|
|
2402
3407
|
const moduleCandidates = await buildModulePathCandidates(
|
|
2403
3408
|
repoRoot,
|
|
2404
3409
|
window.to,
|
|
@@ -2423,7 +3428,9 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2423
3428
|
for (const commit of selectedCommitShas) {
|
|
2424
3429
|
commits.push(await getCommitMetadata(repoRoot, commit));
|
|
2425
3430
|
}
|
|
2426
|
-
const tempRoot = await fs2.mkdtemp(
|
|
3431
|
+
const tempRoot = await fs2.mkdtemp(
|
|
3432
|
+
path4.join(os.tmpdir(), "rrroutes-export-changelog-")
|
|
3433
|
+
);
|
|
2427
3434
|
log(`[changelog] temp workspace: ${tempRoot}`);
|
|
2428
3435
|
try {
|
|
2429
3436
|
const snapshots = [];
|
|
@@ -2460,6 +3467,7 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2460
3467
|
}
|
|
2461
3468
|
const routeEvents = [];
|
|
2462
3469
|
const sourceEvents = [];
|
|
3470
|
+
const changeSets = [];
|
|
2463
3471
|
log("[changelog] diffing successful snapshots");
|
|
2464
3472
|
let previousSnapshot;
|
|
2465
3473
|
for (const snapshot of snapshots) {
|
|
@@ -2470,12 +3478,14 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2470
3478
|
previousSnapshot = snapshot;
|
|
2471
3479
|
continue;
|
|
2472
3480
|
}
|
|
2473
|
-
const {
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
3481
|
+
const {
|
|
3482
|
+
routeEvents: nextRouteEvents,
|
|
3483
|
+
sourceEvents: nextSourceEvents,
|
|
3484
|
+
changeSet
|
|
3485
|
+
} = diffSnapshots(previousSnapshot, snapshot);
|
|
2477
3486
|
routeEvents.push(...nextRouteEvents);
|
|
2478
3487
|
sourceEvents.push(...nextSourceEvents);
|
|
3488
|
+
changeSets.push(changeSet);
|
|
2479
3489
|
previousSnapshot = snapshot;
|
|
2480
3490
|
}
|
|
2481
3491
|
log(
|
|
@@ -2497,7 +3507,8 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2497
3507
|
commits,
|
|
2498
3508
|
byRoute: routeRecordByKey(routeEvents),
|
|
2499
3509
|
bySourceObject: sourceRecordById(sourceEvents),
|
|
2500
|
-
rollups: buildRollups(routeEvents, sourceEvents, commits)
|
|
3510
|
+
rollups: buildRollups(routeEvents, sourceEvents, commits),
|
|
3511
|
+
changeSets
|
|
2501
3512
|
};
|
|
2502
3513
|
if (options.outFile || options.htmlFile) {
|
|
2503
3514
|
log("[changelog] writing output artifacts");
|
|
@@ -2604,25 +3615,6 @@ async function runExportFinalizedLeavesChangelogCli(argv) {
|
|
|
2604
3615
|
import fs3 from "fs/promises";
|
|
2605
3616
|
import path6 from "path";
|
|
2606
3617
|
import { spawn as spawn3 } from "child_process";
|
|
2607
|
-
var BAKED_PAYLOAD_MARKER3 = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
|
|
2608
|
-
function escapePayloadForInlineScript3(payload) {
|
|
2609
|
-
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
|
|
2610
|
-
}
|
|
2611
|
-
function injectPayloadIntoViewerHtml3(htmlTemplate, payload) {
|
|
2612
|
-
const payloadScript = `${BAKED_PAYLOAD_MARKER3}
|
|
2613
|
-
<script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript3(
|
|
2614
|
-
payload
|
|
2615
|
-
)};</script>`;
|
|
2616
|
-
if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER3)) {
|
|
2617
|
-
return htmlTemplate.replace(BAKED_PAYLOAD_MARKER3, payloadScript);
|
|
2618
|
-
}
|
|
2619
|
-
if (htmlTemplate.includes("</body>")) {
|
|
2620
|
-
return htmlTemplate.replace("</body>", `${payloadScript}
|
|
2621
|
-
</body>`);
|
|
2622
|
-
}
|
|
2623
|
-
return `${payloadScript}
|
|
2624
|
-
${htmlTemplate}`;
|
|
2625
|
-
}
|
|
2626
3618
|
async function resolveViewerTemplatePath3(viewerTemplateFile) {
|
|
2627
3619
|
if (viewerTemplateFile) {
|
|
2628
3620
|
const resolved = path6.resolve(viewerTemplateFile);
|
|
@@ -2635,7 +3627,10 @@ async function resolveViewerTemplatePath3(viewerTemplateFile) {
|
|
|
2635
3627
|
"node_modules/@emeryld/rrroutes-export/tools/finalized-leaves-viewer.html"
|
|
2636
3628
|
),
|
|
2637
3629
|
path6.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
|
|
2638
|
-
path6.resolve(
|
|
3630
|
+
path6.resolve(
|
|
3631
|
+
process.cwd(),
|
|
3632
|
+
"packages/export/tools/finalized-leaves-viewer.html"
|
|
3633
|
+
)
|
|
2639
3634
|
];
|
|
2640
3635
|
for (const candidate of candidates) {
|
|
2641
3636
|
try {
|
|
@@ -2656,7 +3651,11 @@ async function writeJsonExport3(payload, outFile) {
|
|
|
2656
3651
|
async function writeBakedHtmlExport3(payload, htmlFile, viewerTemplateFile) {
|
|
2657
3652
|
const templatePath = await resolveViewerTemplatePath3(viewerTemplateFile);
|
|
2658
3653
|
const template = templatePath ? await fs3.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
|
|
2659
|
-
const baked =
|
|
3654
|
+
const baked = injectBakedInspectorPayload(template, payload, {
|
|
3655
|
+
marker: "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->",
|
|
3656
|
+
globalName: "__FINALIZED_LEAVES_PAYLOAD",
|
|
3657
|
+
scriptId: "finalized-leaves-baked-payload"
|
|
3658
|
+
});
|
|
2660
3659
|
const resolved = path6.resolve(htmlFile);
|
|
2661
3660
|
await fs3.mkdir(path6.dirname(resolved), { recursive: true });
|
|
2662
3661
|
await fs3.writeFile(resolved, baked, "utf8");
|
|
@@ -2712,29 +3711,1982 @@ async function writeFinalizedLeavesViewerBundle(payload, outFileOrOptions) {
|
|
|
2712
3711
|
}
|
|
2713
3712
|
return written;
|
|
2714
3713
|
}
|
|
3714
|
+
|
|
3715
|
+
// src/snapshot/build.ts
|
|
3716
|
+
import { routeKeyOf as routeKeyOf3 } from "@emeryld/rrroutes-contract";
|
|
3717
|
+
|
|
3718
|
+
// src/inspection/diagnostics.ts
|
|
3719
|
+
var _items;
|
|
3720
|
+
var InspectionDiagnostics = class {
|
|
3721
|
+
constructor() {
|
|
3722
|
+
__privateAdd(this, _items, []);
|
|
3723
|
+
}
|
|
3724
|
+
add(diagnostic) {
|
|
3725
|
+
__privateGet(this, _items).push(diagnostic);
|
|
3726
|
+
}
|
|
3727
|
+
list(options = {}) {
|
|
3728
|
+
const items = options.includeInfo === false ? __privateGet(this, _items).filter((item) => item.severity !== "info") : __privateGet(this, _items);
|
|
3729
|
+
return items.slice().sort((left, right) => {
|
|
3730
|
+
const byCode = left.code.localeCompare(right.code);
|
|
3731
|
+
if (byCode !== 0) return byCode;
|
|
3732
|
+
return JSON.stringify(left.entity ?? {}).localeCompare(
|
|
3733
|
+
JSON.stringify(right.entity ?? {})
|
|
3734
|
+
);
|
|
3735
|
+
});
|
|
3736
|
+
}
|
|
3737
|
+
};
|
|
3738
|
+
_items = new WeakMap();
|
|
3739
|
+
|
|
3740
|
+
// src/inspection/inspect-clients.ts
|
|
3741
|
+
import {
|
|
3742
|
+
routeKeyOf
|
|
3743
|
+
} from "@emeryld/rrroutes-contract";
|
|
3744
|
+
|
|
3745
|
+
// src/inspection/native.ts
|
|
3746
|
+
import { RRROUTES_INSPECTION } from "@emeryld/rrroutes-contract";
|
|
3747
|
+
function asRecord2(value) {
|
|
3748
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
3749
|
+
}
|
|
3750
|
+
function readNativeInspection(value) {
|
|
3751
|
+
const record = asRecord2(value);
|
|
3752
|
+
if (!record) return void 0;
|
|
3753
|
+
const inspect = record[RRROUTES_INSPECTION];
|
|
3754
|
+
if (typeof inspect !== "function") return void 0;
|
|
3755
|
+
try {
|
|
3756
|
+
return asRecord2(inspect.call(value));
|
|
3757
|
+
} catch {
|
|
3758
|
+
return void 0;
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
function optionalString(value) {
|
|
3762
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3763
|
+
}
|
|
3764
|
+
function optionalBoolean(value) {
|
|
3765
|
+
return typeof value === "boolean" ? value : void 0;
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
// src/inspection/normalize-clients.ts
|
|
3769
|
+
function normalizeClientsInput(endpointClient, allClients) {
|
|
3770
|
+
const out = [];
|
|
3771
|
+
if (endpointClient) {
|
|
3772
|
+
out.push({
|
|
3773
|
+
value: endpointClient,
|
|
3774
|
+
registeredName: "endpointClient",
|
|
3775
|
+
index: 0
|
|
3776
|
+
});
|
|
3777
|
+
}
|
|
3778
|
+
if (!allClients) return out;
|
|
3779
|
+
if (Array.isArray(allClients)) {
|
|
3780
|
+
const offset2 = out.length;
|
|
3781
|
+
allClients.forEach(
|
|
3782
|
+
(value, index) => out.push({ value, index: offset2 + index })
|
|
3783
|
+
);
|
|
3784
|
+
return out;
|
|
3785
|
+
}
|
|
3786
|
+
const offset = out.length;
|
|
3787
|
+
Object.entries(allClients).forEach(([registeredName, value], index) => {
|
|
3788
|
+
out.push({ value, registeredName, index: offset + index });
|
|
3789
|
+
});
|
|
3790
|
+
return out;
|
|
3791
|
+
}
|
|
3792
|
+
function looksLikeClientEndpoint(value) {
|
|
3793
|
+
const native = readNativeInspection(value);
|
|
3794
|
+
if (native?.kind === "client-endpoint") return true;
|
|
3795
|
+
return Boolean(
|
|
3796
|
+
value && typeof value === "object" && "leaf" in value && value.leaf
|
|
3797
|
+
);
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
// src/inspection/serializable.ts
|
|
3801
|
+
var MAX_METADATA_DEPTH = 8;
|
|
3802
|
+
function serializeSelectedMetadata(value, depth = 0) {
|
|
3803
|
+
if (depth > MAX_METADATA_DEPTH || value === void 0) return void 0;
|
|
3804
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
3805
|
+
return value;
|
|
3806
|
+
}
|
|
3807
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
3808
|
+
if (typeof value === "bigint") return String(value);
|
|
3809
|
+
if (value instanceof Date) return value.toISOString();
|
|
3810
|
+
if (Array.isArray(value)) {
|
|
3811
|
+
return value.map((item) => serializeSelectedMetadata(item, depth + 1)).filter((item) => item !== void 0);
|
|
3812
|
+
}
|
|
3813
|
+
if (typeof value !== "object") return void 0;
|
|
3814
|
+
const out = {};
|
|
3815
|
+
for (const key of Object.keys(value).sort()) {
|
|
3816
|
+
const child = serializeSelectedMetadata(
|
|
3817
|
+
value[key],
|
|
3818
|
+
depth + 1
|
|
3819
|
+
);
|
|
3820
|
+
if (child !== void 0) out[key] = child;
|
|
3821
|
+
}
|
|
3822
|
+
return out;
|
|
3823
|
+
}
|
|
3824
|
+
var CLIENT_BUILD_OPTION_FIELDS = [
|
|
3825
|
+
"enabled",
|
|
3826
|
+
"staleTime",
|
|
3827
|
+
"gcTime",
|
|
3828
|
+
"retry",
|
|
3829
|
+
"retryDelay",
|
|
3830
|
+
"refetchInterval",
|
|
3831
|
+
"refetchOnMount",
|
|
3832
|
+
"refetchOnWindowFocus",
|
|
3833
|
+
"refetchOnReconnect",
|
|
3834
|
+
"singleton",
|
|
3835
|
+
"splitPageSize",
|
|
3836
|
+
"splitPageSizeParam",
|
|
3837
|
+
"pagination",
|
|
3838
|
+
"feed",
|
|
3839
|
+
"many"
|
|
3840
|
+
];
|
|
3841
|
+
function serializeClientBuildOptions(value) {
|
|
3842
|
+
if (!value || typeof value !== "object") return {};
|
|
3843
|
+
const source = value;
|
|
3844
|
+
const out = {};
|
|
3845
|
+
for (const field of CLIENT_BUILD_OPTION_FIELDS) {
|
|
3846
|
+
const selected = serializeSelectedMetadata(source[field]);
|
|
3847
|
+
if (selected !== void 0) out[field] = selected;
|
|
3848
|
+
}
|
|
3849
|
+
return out;
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
// src/inspection/inspect-clients.ts
|
|
3853
|
+
function isLeaf(value) {
|
|
3854
|
+
const record = asRecord2(value);
|
|
3855
|
+
return Boolean(
|
|
3856
|
+
record && typeof record.method === "string" && typeof record.path === "string" && record.cfg && typeof record.cfg === "object"
|
|
3857
|
+
);
|
|
3858
|
+
}
|
|
3859
|
+
function endpointFrom(value) {
|
|
3860
|
+
const descriptor = readNativeInspection(value);
|
|
3861
|
+
if (descriptor?.kind === "client-endpoint" && isLeaf(descriptor.leaf)) {
|
|
3862
|
+
return {
|
|
3863
|
+
leaf: descriptor.leaf,
|
|
3864
|
+
id: optionalString(descriptor.id),
|
|
3865
|
+
name: optionalString(descriptor.name),
|
|
3866
|
+
enabled: optionalBoolean(descriptor.enabled) ?? true,
|
|
3867
|
+
built: optionalBoolean(descriptor.built) ?? true,
|
|
3868
|
+
buildOptions: descriptor.buildOptions,
|
|
3869
|
+
augments: Array.isArray(descriptor.augments) ? descriptor.augments.map(asRecord2).filter(
|
|
3870
|
+
(item) => Boolean(item)
|
|
3871
|
+
) : [],
|
|
3872
|
+
socketConnections: Array.isArray(descriptor.socketConnections) ? descriptor.socketConnections.map(asRecord2).filter(
|
|
3873
|
+
(item) => Boolean(item)
|
|
3874
|
+
) : [],
|
|
3875
|
+
complete: true
|
|
3876
|
+
};
|
|
3877
|
+
}
|
|
3878
|
+
const record = asRecord2(value);
|
|
3879
|
+
if (!record || !isLeaf(record.leaf)) return void 0;
|
|
3880
|
+
return {
|
|
3881
|
+
leaf: record.leaf,
|
|
3882
|
+
enabled: true,
|
|
3883
|
+
built: true,
|
|
3884
|
+
augments: [],
|
|
3885
|
+
socketConnections: [],
|
|
3886
|
+
complete: false
|
|
3887
|
+
};
|
|
3888
|
+
}
|
|
3889
|
+
function bindingsFromDescriptor(descriptor) {
|
|
3890
|
+
if (descriptor.kind === "client-resource") {
|
|
3891
|
+
const bindings = Array.isArray(descriptor.bindings) ? descriptor.bindings.flatMap((value, index) => {
|
|
3892
|
+
const binding = asRecord2(value);
|
|
3893
|
+
const endpoint = endpointFromInspectionRecord(binding?.endpoint) ?? endpointFrom(binding?.endpoint);
|
|
3894
|
+
if (!binding || !endpoint) return [];
|
|
3895
|
+
return [
|
|
3896
|
+
{
|
|
3897
|
+
id: optionalString(binding.id),
|
|
3898
|
+
publicName: optionalString(binding.publicName) ?? endpoint.name ?? `endpoint-${index}`,
|
|
3899
|
+
enabled: optionalBoolean(binding.enabled),
|
|
3900
|
+
endpoint
|
|
3901
|
+
}
|
|
3902
|
+
];
|
|
3903
|
+
}) : [];
|
|
3904
|
+
return {
|
|
3905
|
+
id: optionalString(descriptor.id),
|
|
3906
|
+
name: optionalString(descriptor.name),
|
|
3907
|
+
bindings
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
if (descriptor.kind === "endpoint-client") {
|
|
3911
|
+
const endpoints = Array.isArray(descriptor.endpoints) ? descriptor.endpoints : [];
|
|
3912
|
+
return {
|
|
3913
|
+
name: "Endpoint client",
|
|
3914
|
+
bindings: endpoints.flatMap((value, index) => {
|
|
3915
|
+
const endpoint = endpointFromInspectionRecord(value);
|
|
3916
|
+
if (!endpoint) return [];
|
|
3917
|
+
return [
|
|
3918
|
+
{
|
|
3919
|
+
id: endpoint.id,
|
|
3920
|
+
publicName: endpoint.name ?? endpoint.id ?? routeKeyOf(endpoint.leaf.method, endpoint.leaf.path),
|
|
3921
|
+
enabled: endpoint.enabled,
|
|
3922
|
+
endpoint
|
|
3923
|
+
}
|
|
3924
|
+
];
|
|
3925
|
+
})
|
|
3926
|
+
};
|
|
3927
|
+
}
|
|
3928
|
+
return void 0;
|
|
3929
|
+
}
|
|
3930
|
+
function endpointFromInspectionRecord(value) {
|
|
3931
|
+
const record = asRecord2(value);
|
|
3932
|
+
if (!record || record.kind !== "client-endpoint" || !isLeaf(record.leaf)) {
|
|
3933
|
+
return void 0;
|
|
3934
|
+
}
|
|
3935
|
+
return {
|
|
3936
|
+
leaf: record.leaf,
|
|
3937
|
+
id: optionalString(record.id),
|
|
3938
|
+
name: optionalString(record.name),
|
|
3939
|
+
enabled: optionalBoolean(record.enabled) ?? true,
|
|
3940
|
+
built: optionalBoolean(record.built) ?? true,
|
|
3941
|
+
buildOptions: record.buildOptions,
|
|
3942
|
+
augments: Array.isArray(record.augments) ? record.augments.map(asRecord2).filter((item) => Boolean(item)) : [],
|
|
3943
|
+
socketConnections: Array.isArray(record.socketConnections) ? record.socketConnections.map(asRecord2).filter((item) => Boolean(item)) : [],
|
|
3944
|
+
complete: true
|
|
3945
|
+
};
|
|
3946
|
+
}
|
|
3947
|
+
function resourceFrom(input) {
|
|
3948
|
+
const descriptor = readNativeInspection(input.value);
|
|
3949
|
+
const native = descriptor ? bindingsFromDescriptor(descriptor) : void 0;
|
|
3950
|
+
if (native) return native;
|
|
3951
|
+
if (looksLikeClientEndpoint(input.value)) {
|
|
3952
|
+
const endpoint = endpointFrom(input.value);
|
|
3953
|
+
return {
|
|
3954
|
+
bindings: endpoint ? [
|
|
3955
|
+
{
|
|
3956
|
+
id: endpoint.id,
|
|
3957
|
+
publicName: endpoint.name ?? endpoint.id ?? "endpoint",
|
|
3958
|
+
enabled: endpoint.enabled,
|
|
3959
|
+
endpoint
|
|
3960
|
+
}
|
|
3961
|
+
] : []
|
|
3962
|
+
};
|
|
3963
|
+
}
|
|
3964
|
+
return {
|
|
3965
|
+
bindings: Object.entries(input.value).flatMap(
|
|
3966
|
+
([publicName, value]) => {
|
|
3967
|
+
const endpoint = endpointFrom(value);
|
|
3968
|
+
return endpoint ? [{ publicName, endpoint }] : [];
|
|
3969
|
+
}
|
|
3970
|
+
)
|
|
3971
|
+
};
|
|
3972
|
+
}
|
|
3973
|
+
function inspectAugments(descriptors, bindingIdentity) {
|
|
3974
|
+
return descriptors.map((descriptor, index) => {
|
|
3975
|
+
const id = optionalString(descriptor.id);
|
|
3976
|
+
return {
|
|
3977
|
+
identity: id ?? `${bindingIdentity}:augment:${index}`,
|
|
3978
|
+
id,
|
|
3979
|
+
name: optionalString(descriptor.name),
|
|
3980
|
+
kind: optionalString(descriptor.kind),
|
|
3981
|
+
options: serializeSelectedMetadata(descriptor.options),
|
|
3982
|
+
order: index,
|
|
3983
|
+
identityStability: id ? "stable" : "positional"
|
|
3984
|
+
};
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3987
|
+
function inspectSocketConnections(descriptors, bindingIdentity) {
|
|
3988
|
+
return descriptors.map((descriptor, index) => {
|
|
3989
|
+
const id = optionalString(descriptor.id);
|
|
3990
|
+
return {
|
|
3991
|
+
identity: id ?? `${bindingIdentity}:socket:${index}`,
|
|
3992
|
+
id,
|
|
3993
|
+
name: optionalString(descriptor.name),
|
|
3994
|
+
events: Array.isArray(descriptor.events) ? descriptor.events.filter((item) => typeof item === "string").sort() : [],
|
|
3995
|
+
roomStrategy: serializeSelectedMetadata(descriptor.roomStrategy),
|
|
3996
|
+
cacheReducers: Array.isArray(descriptor.cacheReducers) ? descriptor.cacheReducers.map((item) => serializeSelectedMetadata(item)).filter(
|
|
3997
|
+
(item) => item !== void 0
|
|
3998
|
+
) : [],
|
|
3999
|
+
order: index,
|
|
4000
|
+
identityStability: id ? "stable" : "positional"
|
|
4001
|
+
};
|
|
4002
|
+
});
|
|
4003
|
+
}
|
|
4004
|
+
function inspectClients(inputs, diagnostics) {
|
|
4005
|
+
const clients = {};
|
|
4006
|
+
const byRoute = {};
|
|
4007
|
+
for (const input of inputs) {
|
|
4008
|
+
const resource = resourceFrom(input);
|
|
4009
|
+
const identity = resource.id ?? input.registeredName ?? `client-${input.index}`;
|
|
4010
|
+
const resourceStability = resource.id || input.registeredName ? "stable" : "positional";
|
|
4011
|
+
const seenPublicNames = /* @__PURE__ */ new Map();
|
|
4012
|
+
const bindings = resource.bindings.map((binding, index) => {
|
|
4013
|
+
const routeKey = routeKeyOf(
|
|
4014
|
+
binding.endpoint.leaf.method,
|
|
4015
|
+
binding.endpoint.leaf.path
|
|
4016
|
+
);
|
|
4017
|
+
const seen = seenPublicNames.get(binding.publicName) ?? 0;
|
|
4018
|
+
seenPublicNames.set(binding.publicName, seen + 1);
|
|
4019
|
+
const publicName = seen === 0 ? binding.publicName : `${binding.publicName}-${index}`;
|
|
4020
|
+
const bindingIdentity = binding.id ?? `${identity}:${publicName}`;
|
|
4021
|
+
const buildOptions = serializeClientBuildOptions(
|
|
4022
|
+
binding.endpoint.buildOptions
|
|
4023
|
+
);
|
|
4024
|
+
const pagination = buildOptions.pagination;
|
|
4025
|
+
const inspected = {
|
|
4026
|
+
clientId: identity,
|
|
4027
|
+
clientName: resource.name ?? input.registeredName,
|
|
4028
|
+
bindingId: binding.id,
|
|
4029
|
+
bindingIdentity,
|
|
4030
|
+
publicName,
|
|
4031
|
+
routeKey,
|
|
4032
|
+
enabled: binding.enabled ?? binding.endpoint.enabled,
|
|
4033
|
+
built: binding.endpoint.built,
|
|
4034
|
+
build: {
|
|
4035
|
+
feed: Boolean(binding.endpoint.leaf.cfg.feed),
|
|
4036
|
+
singleton: buildOptions.singleton === true,
|
|
4037
|
+
many: Boolean(buildOptions.many),
|
|
4038
|
+
pagination,
|
|
4039
|
+
options: buildOptions
|
|
4040
|
+
},
|
|
4041
|
+
augments: inspectAugments(binding.endpoint.augments, bindingIdentity),
|
|
4042
|
+
socketConnections: inspectSocketConnections(
|
|
4043
|
+
binding.endpoint.socketConnections,
|
|
4044
|
+
bindingIdentity
|
|
4045
|
+
),
|
|
4046
|
+
identityStability: binding.id || seen === 0 ? "stable" : "positional"
|
|
4047
|
+
};
|
|
4048
|
+
if (!binding.endpoint.complete) {
|
|
4049
|
+
diagnostics.add({
|
|
4050
|
+
code: "CLIENT_AUGMENT_DETAILS_UNAVAILABLE",
|
|
4051
|
+
severity: "info",
|
|
4052
|
+
entity: { routeKey, client: identity },
|
|
4053
|
+
message: "The endpoint was detected, but augment descriptors were not exposed."
|
|
4054
|
+
});
|
|
4055
|
+
}
|
|
4056
|
+
byRoute[routeKey] ?? (byRoute[routeKey] = []);
|
|
4057
|
+
byRoute[routeKey].push(inspected);
|
|
4058
|
+
return inspected;
|
|
4059
|
+
});
|
|
4060
|
+
clients[identity] = {
|
|
4061
|
+
identity,
|
|
4062
|
+
id: resource.id,
|
|
4063
|
+
name: resource.name ?? input.registeredName,
|
|
4064
|
+
identityStability: resourceStability,
|
|
4065
|
+
bindings
|
|
4066
|
+
};
|
|
4067
|
+
if (resourceStability === "positional") {
|
|
4068
|
+
diagnostics.add({
|
|
4069
|
+
code: "CLIENT_IDENTITY_POSITIONAL",
|
|
4070
|
+
severity: "info",
|
|
4071
|
+
entity: { client: identity },
|
|
4072
|
+
message: "Client identity uses its array position and may change after reordering."
|
|
4073
|
+
});
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
for (const bindings of Object.values(byRoute)) {
|
|
4077
|
+
bindings.sort(
|
|
4078
|
+
(left, right) => left.bindingIdentity.localeCompare(right.bindingIdentity)
|
|
4079
|
+
);
|
|
4080
|
+
}
|
|
4081
|
+
return { clients, byRoute };
|
|
4082
|
+
}
|
|
4083
|
+
|
|
4084
|
+
// src/inspection/inspect-contract.ts
|
|
4085
|
+
import { routeKeyOf as routeKeyOf2 } from "@emeryld/rrroutes-contract";
|
|
4086
|
+
async function inspectContractRoutes(leaves, source, includeSource) {
|
|
4087
|
+
const exported = await exportFinalizedLeaves(leaves, {
|
|
4088
|
+
includeSource: includeSource && (source?.include ?? true),
|
|
4089
|
+
sourceModulePath: source?.modulePath,
|
|
4090
|
+
sourceExportName: source?.exportName,
|
|
4091
|
+
tsconfigPath: source?.tsconfigPath
|
|
4092
|
+
});
|
|
4093
|
+
const routes = {};
|
|
4094
|
+
for (const serializedLeaf of exported.leaves) {
|
|
4095
|
+
const routeKey = routeKeyOf2(serializedLeaf.method, serializedLeaf.path);
|
|
4096
|
+
const schemaFlat = exported.schemaFlatByLeaf[routeKey] ?? {};
|
|
4097
|
+
routes[routeKey] = {
|
|
4098
|
+
serializedLeaf,
|
|
4099
|
+
schemaFlat,
|
|
4100
|
+
source: exported.sourceByLeaf?.[routeKey],
|
|
4101
|
+
fingerprint: hashCanonicalValue({ serializedLeaf, schemaFlat })
|
|
4102
|
+
};
|
|
4103
|
+
}
|
|
4104
|
+
return {
|
|
4105
|
+
routes,
|
|
4106
|
+
sourceExtraction: exported._meta.sourceExtraction
|
|
4107
|
+
};
|
|
4108
|
+
}
|
|
4109
|
+
|
|
4110
|
+
// src/inspection/inspect-server.ts
|
|
4111
|
+
function isLeaf2(value) {
|
|
4112
|
+
const record = asRecord2(value);
|
|
4113
|
+
return Boolean(
|
|
4114
|
+
record && typeof record.method === "string" && typeof record.path === "string" && record.cfg && typeof record.cfg === "object"
|
|
4115
|
+
);
|
|
4116
|
+
}
|
|
4117
|
+
function inspectServer(input, diagnostics) {
|
|
4118
|
+
if (!input)
|
|
4119
|
+
return {
|
|
4120
|
+
servers: {},
|
|
4121
|
+
byRoute: {}
|
|
4122
|
+
};
|
|
4123
|
+
const descriptor = readNativeInspection(input);
|
|
4124
|
+
if (!descriptor || descriptor.kind !== "server-runtime") {
|
|
4125
|
+
diagnostics.add({
|
|
4126
|
+
code: "SERVER_REGISTRATION_DETAILS_UNAVAILABLE",
|
|
4127
|
+
severity: "warning",
|
|
4128
|
+
entity: { server: "server" },
|
|
4129
|
+
message: "A server runtime was supplied, but it did not expose the RRRoutes inspection protocol."
|
|
4130
|
+
});
|
|
4131
|
+
return {
|
|
4132
|
+
servers: {
|
|
4133
|
+
server: {
|
|
4134
|
+
identity: "server",
|
|
4135
|
+
identityStability: "stable",
|
|
4136
|
+
controllers: {},
|
|
4137
|
+
middleware: { sanitizerCount: 0, preCtxCount: 0, postCtxCount: 0 },
|
|
4138
|
+
validateOutput: false
|
|
4139
|
+
}
|
|
4140
|
+
},
|
|
4141
|
+
byRoute: {}
|
|
4142
|
+
};
|
|
4143
|
+
}
|
|
4144
|
+
const id = optionalString(descriptor.id);
|
|
4145
|
+
const name = optionalString(descriptor.name);
|
|
4146
|
+
const identity = id ?? name ?? "server";
|
|
4147
|
+
const nativeMiddleware = asRecord2(descriptor.middleware);
|
|
4148
|
+
const middleware = {
|
|
4149
|
+
sanitizerCount: typeof nativeMiddleware?.sanitizerCount === "number" ? nativeMiddleware.sanitizerCount : 0,
|
|
4150
|
+
preCtxCount: typeof nativeMiddleware?.preCtxCount === "number" ? nativeMiddleware.preCtxCount : 0,
|
|
4151
|
+
postCtxCount: typeof nativeMiddleware?.postCtxCount === "number" ? nativeMiddleware.postCtxCount : 0
|
|
4152
|
+
};
|
|
4153
|
+
const validateOutput = optionalBoolean(descriptor.validateOutput) ?? false;
|
|
4154
|
+
const controllers = {};
|
|
4155
|
+
for (const nativeValue of Array.isArray(descriptor.controllers) ? descriptor.controllers : []) {
|
|
4156
|
+
const native = asRecord2(nativeValue);
|
|
4157
|
+
const routeKey = optionalString(native?.routeKey);
|
|
4158
|
+
if (!native || !routeKey || !isLeaf2(native.leaf)) continue;
|
|
4159
|
+
const controllerId = optionalString(native.id);
|
|
4160
|
+
const before = (Array.isArray(native.before) ? native.before : []).flatMap((middlewareValue, index) => {
|
|
4161
|
+
const item = asRecord2(middlewareValue);
|
|
4162
|
+
if (!item) return [];
|
|
4163
|
+
const middlewareId = optionalString(item.id);
|
|
4164
|
+
return [
|
|
4165
|
+
{
|
|
4166
|
+
identity: middlewareId ?? `${routeKey}:before:${index}`,
|
|
4167
|
+
id: middlewareId,
|
|
4168
|
+
name: optionalString(item.name),
|
|
4169
|
+
order: index,
|
|
4170
|
+
identityStability: middlewareId ? "stable" : "positional"
|
|
4171
|
+
}
|
|
4172
|
+
];
|
|
4173
|
+
});
|
|
4174
|
+
controllers[routeKey] = {
|
|
4175
|
+
routeKey,
|
|
4176
|
+
registered: true,
|
|
4177
|
+
enabled: optionalBoolean(native.enabled) ?? true,
|
|
4178
|
+
controllerId,
|
|
4179
|
+
controllerName: optionalString(native.name),
|
|
4180
|
+
before,
|
|
4181
|
+
middleware: {
|
|
4182
|
+
...middleware,
|
|
4183
|
+
beforeCount: before.length
|
|
4184
|
+
},
|
|
4185
|
+
validateOutput,
|
|
4186
|
+
multipart: Boolean(native.leaf.cfg.bodyFiles?.length),
|
|
4187
|
+
identityStability: "stable"
|
|
4188
|
+
};
|
|
4189
|
+
for (const item of before) {
|
|
4190
|
+
if (item.identityStability === "positional") {
|
|
4191
|
+
diagnostics.add({
|
|
4192
|
+
code: "SERVER_MIDDLEWARE_IDENTITY_POSITIONAL",
|
|
4193
|
+
severity: "info",
|
|
4194
|
+
entity: { routeKey, server: identity },
|
|
4195
|
+
message: "Middleware identity uses its route-local position and may change after reordering."
|
|
4196
|
+
});
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
return {
|
|
4201
|
+
servers: {
|
|
4202
|
+
[identity]: {
|
|
4203
|
+
identity,
|
|
4204
|
+
id,
|
|
4205
|
+
name,
|
|
4206
|
+
identityStability: "stable",
|
|
4207
|
+
controllers,
|
|
4208
|
+
middleware,
|
|
4209
|
+
validateOutput
|
|
4210
|
+
}
|
|
4211
|
+
},
|
|
4212
|
+
byRoute: controllers
|
|
4213
|
+
};
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4216
|
+
// src/inspection/inspect-sockets.ts
|
|
4217
|
+
function toLiveInspection(value) {
|
|
4218
|
+
const live = asRecord2(value);
|
|
4219
|
+
if (!live) return void 0;
|
|
4220
|
+
const routeSubscriptions = Array.isArray(live.leaves) ? live.leaves.flatMap(
|
|
4221
|
+
(leafValue) => {
|
|
4222
|
+
const leaf = asRecord2(leafValue);
|
|
4223
|
+
const routeKey = optionalString(leaf?.leaf);
|
|
4224
|
+
if (!leaf || !routeKey) return [];
|
|
4225
|
+
return [
|
|
4226
|
+
{
|
|
4227
|
+
routeKey,
|
|
4228
|
+
rooms: Array.isArray(leaf.rooms) ? leaf.rooms.flatMap((roomValue) => {
|
|
4229
|
+
const room = asRecord2(roomValue);
|
|
4230
|
+
return room && typeof room.room === "string" && typeof room.count === "number" ? [{ room: room.room, count: room.count }] : [];
|
|
4231
|
+
}) : [],
|
|
4232
|
+
handlers: Array.isArray(leaf.handlers) ? leaf.handlers.flatMap((handlerValue) => {
|
|
4233
|
+
const handler = asRecord2(handlerValue);
|
|
4234
|
+
return handler && typeof handler.event === "string" && typeof handler.handlers === "number" ? [{ event: handler.event, handlers: handler.handlers }] : [];
|
|
4235
|
+
}) : []
|
|
4236
|
+
}
|
|
4237
|
+
];
|
|
4238
|
+
}
|
|
4239
|
+
) : void 0;
|
|
4240
|
+
return {
|
|
4241
|
+
connected: optionalBoolean(live.connected) ?? false,
|
|
4242
|
+
socketId: optionalString(live.socketId),
|
|
4243
|
+
namespace: optionalString(live.nsp),
|
|
4244
|
+
roomsCount: typeof live.roomsCount === "number" ? live.roomsCount : void 0,
|
|
4245
|
+
totalHandlers: typeof live.totalHandlers === "number" ? live.totalHandlers : void 0,
|
|
4246
|
+
rooms: Array.isArray(live.rooms) ? live.rooms.flatMap((roomValue) => {
|
|
4247
|
+
const room = asRecord2(roomValue);
|
|
4248
|
+
return room && typeof room.room === "string" && typeof room.count === "number" ? [{ room: room.room, count: room.count }] : [];
|
|
4249
|
+
}) : void 0,
|
|
4250
|
+
handlers: Array.isArray(live.handlers) ? live.handlers.flatMap((handlerValue) => {
|
|
4251
|
+
const handler = asRecord2(handlerValue);
|
|
4252
|
+
return handler && typeof handler.event === "string" && typeof handler.handlers === "number" ? [{ event: handler.event, handlers: handler.handlers }] : [];
|
|
4253
|
+
}) : void 0,
|
|
4254
|
+
routeSubscriptions
|
|
4255
|
+
};
|
|
4256
|
+
}
|
|
4257
|
+
function inspectSockets(inputs, includeLive, diagnostics) {
|
|
4258
|
+
const sockets = {};
|
|
4259
|
+
for (const input of inputs) {
|
|
4260
|
+
const descriptor = readNativeInspection(input.value);
|
|
4261
|
+
const id = optionalString(descriptor?.id);
|
|
4262
|
+
const name = optionalString(descriptor?.name);
|
|
4263
|
+
const identity = id ?? input.registeredName ?? name ?? `socket-${input.index}`;
|
|
4264
|
+
const stability = id || input.registeredName || name ? "stable" : "positional";
|
|
4265
|
+
const events = asRecord2(descriptor?.events);
|
|
4266
|
+
const contractEvents = {};
|
|
4267
|
+
const handlers = (Array.isArray(descriptor?.handlers) ? descriptor.handlers : []).flatMap((handlerValue, index) => {
|
|
4268
|
+
const handler = asRecord2(handlerValue);
|
|
4269
|
+
const event = optionalString(handler?.event);
|
|
4270
|
+
if (!handler || !event) return [];
|
|
4271
|
+
return [
|
|
4272
|
+
{
|
|
4273
|
+
identity: `${identity}:handler:${event}:${index}`,
|
|
4274
|
+
event,
|
|
4275
|
+
name: optionalString(handler.name),
|
|
4276
|
+
order: typeof handler.order === "number" ? handler.order : index,
|
|
4277
|
+
identityStability: "positional"
|
|
4278
|
+
}
|
|
4279
|
+
];
|
|
4280
|
+
});
|
|
4281
|
+
const bindings = (Array.isArray(descriptor?.bindings) ? descriptor.bindings : []).flatMap((bindingValue, index) => {
|
|
4282
|
+
const binding = asRecord2(bindingValue);
|
|
4283
|
+
const routeKey = optionalString(binding?.routeKey);
|
|
4284
|
+
if (!binding || !routeKey) return [];
|
|
4285
|
+
return [
|
|
4286
|
+
{
|
|
4287
|
+
identity: `${identity}:binding:${routeKey}`,
|
|
4288
|
+
routeKey,
|
|
4289
|
+
eventNames: Array.isArray(binding.events) ? binding.events.filter((event) => typeof event === "string").sort() : [],
|
|
4290
|
+
identityStability: "stable"
|
|
4291
|
+
}
|
|
4292
|
+
];
|
|
4293
|
+
});
|
|
4294
|
+
for (const eventName of Object.keys(events ?? {}).sort()) {
|
|
4295
|
+
const event = asRecord2(events?.[eventName]);
|
|
4296
|
+
const message = event?.message;
|
|
4297
|
+
contractEvents[eventName] = {
|
|
4298
|
+
identity: `${identity}:event:${eventName}`,
|
|
4299
|
+
name: eventName,
|
|
4300
|
+
schema: message && typeof message === "object" ? introspectSchema(message) : void 0
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
if (!descriptor || descriptor.kind !== "socket-runtime") {
|
|
4304
|
+
diagnostics.add({
|
|
4305
|
+
code: "SOCKET_DETAILS_UNAVAILABLE",
|
|
4306
|
+
severity: "info",
|
|
4307
|
+
entity: { socket: identity },
|
|
4308
|
+
message: "A socket runtime was supplied, but it did not expose the RRRoutes inspection protocol."
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
if (stability === "positional") {
|
|
4312
|
+
diagnostics.add({
|
|
4313
|
+
code: "SOCKET_IDENTITY_POSITIONAL",
|
|
4314
|
+
severity: "info",
|
|
4315
|
+
entity: { socket: identity },
|
|
4316
|
+
message: "Socket identity uses its array position and may change after reordering."
|
|
4317
|
+
});
|
|
4318
|
+
}
|
|
4319
|
+
sockets[identity] = {
|
|
4320
|
+
identity,
|
|
4321
|
+
id,
|
|
4322
|
+
name: name ?? input.registeredName,
|
|
4323
|
+
identityStability: stability,
|
|
4324
|
+
contract: { events: contractEvents },
|
|
4325
|
+
configuration: {
|
|
4326
|
+
heartbeat: serializeSelectedMetadata(descriptor?.heartbeat),
|
|
4327
|
+
reconnection: serializeSelectedMetadata(descriptor?.reconnection),
|
|
4328
|
+
system: serializeSelectedMetadata(descriptor?.config),
|
|
4329
|
+
handlers
|
|
4330
|
+
},
|
|
4331
|
+
bindings,
|
|
4332
|
+
live: includeLive ? toLiveInspection(
|
|
4333
|
+
descriptor?.live ?? asRecord2(input.value)?.statsSnapshot
|
|
4334
|
+
) : void 0
|
|
4335
|
+
};
|
|
4336
|
+
}
|
|
4337
|
+
return sockets;
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
// src/inspection/join-routes.ts
|
|
4341
|
+
function joinRouteInspections(args) {
|
|
4342
|
+
const routes = {};
|
|
4343
|
+
const serverRuntime = Object.values(args.servers)[0];
|
|
4344
|
+
for (const routeKey of Object.keys(args.contract).sort()) {
|
|
4345
|
+
const contract = args.contract[routeKey];
|
|
4346
|
+
const clients = (args.clients[routeKey] ?? []).slice();
|
|
4347
|
+
const registeredServer = args.server[routeKey];
|
|
4348
|
+
const server = registeredServer ?? (args.serverSupplied ? {
|
|
4349
|
+
routeKey,
|
|
4350
|
+
registered: false,
|
|
4351
|
+
enabled: false,
|
|
4352
|
+
before: [],
|
|
4353
|
+
middleware: {
|
|
4354
|
+
sanitizerCount: serverRuntime?.middleware.sanitizerCount ?? 0,
|
|
4355
|
+
preCtxCount: serverRuntime?.middleware.preCtxCount ?? 0,
|
|
4356
|
+
postCtxCount: serverRuntime?.middleware.postCtxCount ?? 0,
|
|
4357
|
+
beforeCount: 0
|
|
4358
|
+
},
|
|
4359
|
+
validateOutput: serverRuntime?.validateOutput ?? false,
|
|
4360
|
+
multipart: Boolean(contract.serializedLeaf.cfg.bodyFiles?.length),
|
|
4361
|
+
identityStability: "stable"
|
|
4362
|
+
} : void 0);
|
|
4363
|
+
const sockets = clients.flatMap(
|
|
4364
|
+
(client) => client.socketConnections.map((connection) => ({
|
|
4365
|
+
identity: connection.identity,
|
|
4366
|
+
clientBindingIdentity: client.bindingIdentity,
|
|
4367
|
+
events: connection.events,
|
|
4368
|
+
roomStrategy: connection.roomStrategy,
|
|
4369
|
+
cacheReducers: connection.cacheReducers,
|
|
4370
|
+
identityStability: connection.identityStability
|
|
4371
|
+
}))
|
|
4372
|
+
);
|
|
4373
|
+
routes[routeKey] = {
|
|
4374
|
+
routeKey,
|
|
4375
|
+
id: contract.serializedLeaf.id,
|
|
4376
|
+
method: contract.serializedLeaf.method.toUpperCase(),
|
|
4377
|
+
path: contract.serializedLeaf.path,
|
|
4378
|
+
contract,
|
|
4379
|
+
clients,
|
|
4380
|
+
server,
|
|
4381
|
+
sockets,
|
|
4382
|
+
status: {
|
|
4383
|
+
client: clients.length === 0 ? "unbound" : clients.some((client) => client.enabled) ? "bound" : "disabled",
|
|
4384
|
+
server: !args.serverSupplied ? "unavailable" : !server?.registered ? "missing" : server.enabled ? "enabled" : "disabled",
|
|
4385
|
+
socket: sockets.length > 0 ? "configured" : "none"
|
|
4386
|
+
}
|
|
4387
|
+
};
|
|
4388
|
+
}
|
|
4389
|
+
for (const routeKey of Object.keys(args.clients)) {
|
|
4390
|
+
if (args.contract[routeKey]) continue;
|
|
4391
|
+
args.diagnostics.push({
|
|
4392
|
+
code: "CLIENT_ROUTE_NOT_IN_CONTRACT_INPUT",
|
|
4393
|
+
severity: "warning",
|
|
4394
|
+
entity: { routeKey },
|
|
4395
|
+
message: "A client binding targets a route that was not present in allRoutes."
|
|
4396
|
+
});
|
|
4397
|
+
}
|
|
4398
|
+
for (const routeKey of Object.keys(args.server)) {
|
|
4399
|
+
if (args.contract[routeKey]) continue;
|
|
4400
|
+
args.diagnostics.push({
|
|
4401
|
+
code: "SERVER_ROUTE_NOT_IN_CONTRACT_INPUT",
|
|
4402
|
+
severity: "warning",
|
|
4403
|
+
entity: { routeKey },
|
|
4404
|
+
message: "A server controller targets a route that was not present in allRoutes."
|
|
4405
|
+
});
|
|
4406
|
+
}
|
|
4407
|
+
return routes;
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4410
|
+
// src/inspection/normalize-routes.ts
|
|
4411
|
+
function hasAll(value) {
|
|
4412
|
+
return Boolean(
|
|
4413
|
+
value && typeof value === "object" && "all" in value && Array.isArray(value.all)
|
|
4414
|
+
);
|
|
4415
|
+
}
|
|
4416
|
+
function normalizeRoutesInput(input) {
|
|
4417
|
+
if (Array.isArray(input)) return input;
|
|
4418
|
+
if (hasAll(input)) return input.all;
|
|
4419
|
+
throw new TypeError(
|
|
4420
|
+
"allRoutes must be a finalized registry, RouteResource, or readonly leaf array."
|
|
4421
|
+
);
|
|
4422
|
+
}
|
|
4423
|
+
|
|
4424
|
+
// src/inspection/normalize-sockets.ts
|
|
4425
|
+
function normalizeSocketsInput(input) {
|
|
4426
|
+
if (!input) return [];
|
|
4427
|
+
if (Array.isArray(input)) {
|
|
4428
|
+
return input.map((value, index) => ({ value, index }));
|
|
4429
|
+
}
|
|
4430
|
+
if (readNativeInspection(input)?.kind === "socket-runtime") {
|
|
4431
|
+
return [{ value: input, index: 0 }];
|
|
4432
|
+
}
|
|
4433
|
+
if ("statsSnapshot" in input || "on" in input && typeof input.on === "function") {
|
|
4434
|
+
return [{ value: input, index: 0 }];
|
|
4435
|
+
}
|
|
4436
|
+
return Object.entries(input).map(([registeredName, value], index) => ({
|
|
4437
|
+
value,
|
|
4438
|
+
registeredName,
|
|
4439
|
+
index
|
|
4440
|
+
}));
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
// src/snapshot/build.ts
|
|
4444
|
+
async function buildRRRoutesSnapshot(options, inspectOptions = {}) {
|
|
4445
|
+
const diagnostics = new InspectionDiagnostics();
|
|
4446
|
+
const leaves = normalizeRoutesInput(options.allRoutes);
|
|
4447
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4448
|
+
for (const leaf of leaves) {
|
|
4449
|
+
const routeKey = routeKeyOf3(leaf.method, leaf.path);
|
|
4450
|
+
if (seen.has(routeKey)) {
|
|
4451
|
+
diagnostics.add({
|
|
4452
|
+
code: "DUPLICATE_ROUTE_KEY",
|
|
4453
|
+
severity: "error",
|
|
4454
|
+
entity: { routeKey },
|
|
4455
|
+
message: "Multiple contract leaves resolve to the same canonical method + path identity."
|
|
4456
|
+
});
|
|
4457
|
+
}
|
|
4458
|
+
seen.add(routeKey);
|
|
4459
|
+
}
|
|
4460
|
+
const includeSource = inspectOptions.includeSource ?? options.source?.include ?? false;
|
|
4461
|
+
const includeLive = inspectOptions.includeLive ?? false;
|
|
4462
|
+
const [contract, clients, server, sockets] = await Promise.all([
|
|
4463
|
+
inspectContractRoutes(leaves, options.source, includeSource),
|
|
4464
|
+
Promise.resolve(
|
|
4465
|
+
inspectClients(
|
|
4466
|
+
normalizeClientsInput(options.endpointClient, options.allClients),
|
|
4467
|
+
diagnostics
|
|
4468
|
+
)
|
|
4469
|
+
),
|
|
4470
|
+
Promise.resolve(inspectServer(options.server, diagnostics)),
|
|
4471
|
+
Promise.resolve(
|
|
4472
|
+
inspectSockets(
|
|
4473
|
+
normalizeSocketsInput(options.sockets),
|
|
4474
|
+
includeLive,
|
|
4475
|
+
diagnostics
|
|
4476
|
+
)
|
|
4477
|
+
)
|
|
4478
|
+
]);
|
|
4479
|
+
const diagnosticItems = diagnostics.list({
|
|
4480
|
+
includeInfo: options.diagnostics?.includeInfo
|
|
4481
|
+
});
|
|
4482
|
+
const routes = joinRouteInspections({
|
|
4483
|
+
contract: contract.routes,
|
|
4484
|
+
clients: clients.byRoute,
|
|
4485
|
+
server: server.byRoute,
|
|
4486
|
+
servers: server.servers,
|
|
4487
|
+
serverSupplied: Boolean(options.server),
|
|
4488
|
+
diagnostics: diagnosticItems
|
|
4489
|
+
});
|
|
4490
|
+
diagnosticItems.sort((left, right) => left.code.localeCompare(right.code));
|
|
4491
|
+
const snapshot = {
|
|
4492
|
+
schemaVersion: 1,
|
|
4493
|
+
meta: {
|
|
4494
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4495
|
+
snapshotHash: "",
|
|
4496
|
+
...options.meta,
|
|
4497
|
+
packageVersions: {},
|
|
4498
|
+
sourceExtraction: contract.sourceExtraction
|
|
4499
|
+
},
|
|
4500
|
+
capabilities: {
|
|
4501
|
+
contract: true,
|
|
4502
|
+
client: Boolean(options.endpointClient || options.allClients),
|
|
4503
|
+
server: Boolean(options.server),
|
|
4504
|
+
socket: Boolean(options.sockets),
|
|
4505
|
+
live: includeLive,
|
|
4506
|
+
source: includeSource
|
|
4507
|
+
},
|
|
4508
|
+
routes,
|
|
4509
|
+
clients: clients.clients,
|
|
4510
|
+
servers: server.servers,
|
|
4511
|
+
sockets,
|
|
4512
|
+
diagnostics: diagnosticItems
|
|
4513
|
+
};
|
|
4514
|
+
snapshot.meta.snapshotHash = hashRRRoutesSnapshot(snapshot);
|
|
4515
|
+
return snapshot;
|
|
4516
|
+
}
|
|
4517
|
+
|
|
4518
|
+
// src/storage/memory.ts
|
|
4519
|
+
function clone(value) {
|
|
4520
|
+
return structuredClone(value);
|
|
4521
|
+
}
|
|
4522
|
+
function createMemoryInspectionStorage() {
|
|
4523
|
+
const checkpoints = /* @__PURE__ */ new Map();
|
|
4524
|
+
const activeByScope = /* @__PURE__ */ new Map();
|
|
4525
|
+
const changes = /* @__PURE__ */ new Map();
|
|
4526
|
+
return {
|
|
4527
|
+
checkpoints: {
|
|
4528
|
+
async get(id) {
|
|
4529
|
+
const value = checkpoints.get(id);
|
|
4530
|
+
return value ? clone(value) : void 0;
|
|
4531
|
+
},
|
|
4532
|
+
async getActive(scope) {
|
|
4533
|
+
const id = activeByScope.get(scope);
|
|
4534
|
+
const value = id ? checkpoints.get(id) : void 0;
|
|
4535
|
+
return value ? clone(value) : void 0;
|
|
4536
|
+
},
|
|
4537
|
+
async list(scope) {
|
|
4538
|
+
return Array.from(checkpoints.values()).filter((checkpoint) => !scope || checkpoint.scope === scope).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).map(
|
|
4539
|
+
(checkpoint) => ({
|
|
4540
|
+
id: checkpoint.id,
|
|
4541
|
+
scope: checkpoint.scope,
|
|
4542
|
+
createdAt: checkpoint.createdAt,
|
|
4543
|
+
updatedAt: checkpoint.updatedAt,
|
|
4544
|
+
snapshotHash: checkpoint.snapshotHash,
|
|
4545
|
+
trigger: clone(checkpoint.trigger),
|
|
4546
|
+
expiresAt: checkpoint.expiresAt,
|
|
4547
|
+
active: activeByScope.get(checkpoint.scope) === checkpoint.id
|
|
4548
|
+
})
|
|
4549
|
+
);
|
|
4550
|
+
},
|
|
4551
|
+
async save(checkpoint) {
|
|
4552
|
+
checkpoints.set(checkpoint.id, clone(checkpoint));
|
|
4553
|
+
},
|
|
4554
|
+
async setActive(scope, checkpointId) {
|
|
4555
|
+
const checkpoint = checkpoints.get(checkpointId);
|
|
4556
|
+
if (!checkpoint || checkpoint.scope !== scope) {
|
|
4557
|
+
throw new Error(
|
|
4558
|
+
`Checkpoint ${checkpointId} does not belong to scope ${scope}.`
|
|
4559
|
+
);
|
|
4560
|
+
}
|
|
4561
|
+
activeByScope.set(scope, checkpointId);
|
|
4562
|
+
},
|
|
4563
|
+
async delete(id) {
|
|
4564
|
+
checkpoints.delete(id);
|
|
4565
|
+
for (const [scope, activeId] of activeByScope) {
|
|
4566
|
+
if (activeId === id) activeByScope.delete(scope);
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
},
|
|
4570
|
+
changes: {
|
|
4571
|
+
async get(id) {
|
|
4572
|
+
const value = changes.get(id);
|
|
4573
|
+
return value ? clone(value) : void 0;
|
|
4574
|
+
},
|
|
4575
|
+
async findByTransition(scope, fromHash, toHash) {
|
|
4576
|
+
const value = Array.from(changes.values()).find(
|
|
4577
|
+
(changeSet) => changeSet.scope === scope && changeSet.from.snapshotHash === fromHash && changeSet.to.snapshotHash === toHash
|
|
4578
|
+
);
|
|
4579
|
+
return value ? clone(value) : void 0;
|
|
4580
|
+
},
|
|
4581
|
+
async list(query = {}) {
|
|
4582
|
+
const values = Array.from(changes.values()).filter(
|
|
4583
|
+
(changeSet) => !query.scope || changeSet.scope === query.scope
|
|
4584
|
+
).sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
4585
|
+
const selected = query.limit ? values.slice(0, query.limit) : values;
|
|
4586
|
+
return selected.map((changeSet) => {
|
|
4587
|
+
const { changes: _changes, ...summary } = changeSet;
|
|
4588
|
+
return clone(summary);
|
|
4589
|
+
});
|
|
4590
|
+
},
|
|
4591
|
+
async save(changeSet) {
|
|
4592
|
+
changes.set(changeSet.id, clone(changeSet));
|
|
4593
|
+
},
|
|
4594
|
+
async delete(id) {
|
|
4595
|
+
changes.delete(id);
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
};
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
// src/checkpoint/lock.ts
|
|
4602
|
+
var _tails;
|
|
4603
|
+
var ScopeLock = class {
|
|
4604
|
+
constructor() {
|
|
4605
|
+
__privateAdd(this, _tails, /* @__PURE__ */ new Map());
|
|
4606
|
+
}
|
|
4607
|
+
async run(scope, action) {
|
|
4608
|
+
const previous = __privateGet(this, _tails).get(scope) ?? Promise.resolve();
|
|
4609
|
+
let release;
|
|
4610
|
+
const current = new Promise((resolve) => {
|
|
4611
|
+
release = resolve;
|
|
4612
|
+
});
|
|
4613
|
+
__privateGet(this, _tails).set(scope, current);
|
|
4614
|
+
await previous;
|
|
4615
|
+
try {
|
|
4616
|
+
return await action();
|
|
4617
|
+
} finally {
|
|
4618
|
+
release();
|
|
4619
|
+
if (__privateGet(this, _tails).get(scope) === current) __privateGet(this, _tails).delete(scope);
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
};
|
|
4623
|
+
_tails = new WeakMap();
|
|
4624
|
+
|
|
4625
|
+
// src/checkpoint/retention.ts
|
|
4626
|
+
async function applyCheckpointRetention(args) {
|
|
4627
|
+
if (!args.retention) return;
|
|
4628
|
+
const summaries = await args.storage.checkpoints.list(args.scope);
|
|
4629
|
+
const now = args.now ?? Date.now();
|
|
4630
|
+
const deletions = /* @__PURE__ */ new Set();
|
|
4631
|
+
if (args.retention.type === "duration" || args.retention.type === "combined") {
|
|
4632
|
+
for (const checkpoint of summaries) {
|
|
4633
|
+
if (checkpoint.id !== args.activeId && now - Date.parse(checkpoint.updatedAt) > args.retention.maximumAgeMs) {
|
|
4634
|
+
deletions.add(checkpoint.id);
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
if (args.retention.type === "count" || args.retention.type === "combined") {
|
|
4639
|
+
const retained = summaries.filter((item) => !deletions.has(item.id));
|
|
4640
|
+
for (const checkpoint of retained.slice(args.retention.maximum)) {
|
|
4641
|
+
if (checkpoint.id !== args.activeId) deletions.add(checkpoint.id);
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
await Promise.all(
|
|
4645
|
+
Array.from(deletions).map((id) => args.storage.checkpoints.delete(id))
|
|
4646
|
+
);
|
|
4647
|
+
}
|
|
4648
|
+
async function applyChangeSetRetention(args) {
|
|
4649
|
+
if (args.retention.type === "forever" || !args.storage.changes.delete) return;
|
|
4650
|
+
const summaries = await args.storage.changes.list({ scope: args.scope });
|
|
4651
|
+
const now = args.now ?? Date.now();
|
|
4652
|
+
const deletions = args.retention.type === "count" ? summaries.slice(args.retention.maximum) : (() => {
|
|
4653
|
+
const maximumAgeMs = args.retention.maximumAgeMs;
|
|
4654
|
+
return summaries.filter(
|
|
4655
|
+
(item) => now - Date.parse(item.createdAt) > maximumAgeMs
|
|
4656
|
+
);
|
|
4657
|
+
})();
|
|
4658
|
+
await Promise.all(
|
|
4659
|
+
deletions.map((item) => args.storage.changes.delete(item.id))
|
|
4660
|
+
);
|
|
4661
|
+
}
|
|
4662
|
+
|
|
4663
|
+
// src/checkpoint/service.ts
|
|
4664
|
+
function createCheckpointService(options) {
|
|
4665
|
+
const storage = options.storage ?? createMemoryInspectionStorage();
|
|
4666
|
+
const lock = new ScopeLock();
|
|
4667
|
+
const defaultScope = options.scope ?? "default";
|
|
4668
|
+
const transaction = (operation) => storage.transaction ? storage.transaction(operation) : operation();
|
|
4669
|
+
const createRecord = (scope, snapshot, trigger) => {
|
|
4670
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4671
|
+
return {
|
|
4672
|
+
id: hashCanonicalValue({
|
|
4673
|
+
scope,
|
|
4674
|
+
snapshotHash: snapshot.meta.snapshotHash,
|
|
4675
|
+
now
|
|
4676
|
+
}),
|
|
4677
|
+
scope,
|
|
4678
|
+
createdAt: now,
|
|
4679
|
+
updatedAt: now,
|
|
4680
|
+
snapshotHash: snapshot.meta.snapshotHash,
|
|
4681
|
+
snapshot,
|
|
4682
|
+
trigger
|
|
4683
|
+
};
|
|
4684
|
+
};
|
|
4685
|
+
const service = {
|
|
4686
|
+
async advance(input = {}) {
|
|
4687
|
+
const scope = input.scope ?? defaultScope;
|
|
4688
|
+
const trigger = input.trigger ?? { type: "manual" };
|
|
4689
|
+
return lock.run(scope, () => transaction(async () => {
|
|
4690
|
+
if (trigger.externalId) {
|
|
4691
|
+
const priorSummary = (await storage.checkpoints.list(scope)).find(
|
|
4692
|
+
(checkpoint2) => checkpoint2.trigger.externalId === trigger.externalId
|
|
4693
|
+
);
|
|
4694
|
+
if (priorSummary) {
|
|
4695
|
+
const checkpoint2 = await storage.checkpoints.get(priorSummary.id);
|
|
4696
|
+
if (checkpoint2) {
|
|
4697
|
+
const changeSummary = (await storage.changes.list({ scope })).find(
|
|
4698
|
+
(changeSet3) => changeSet3.trigger.externalId === trigger.externalId
|
|
4699
|
+
);
|
|
4700
|
+
const changeSet2 = changeSummary ? await storage.changes.get(changeSummary.id) : void 0;
|
|
4701
|
+
return {
|
|
4702
|
+
status: "idempotent_replay",
|
|
4703
|
+
checkpoint: checkpoint2,
|
|
4704
|
+
changeSet: changeSet2
|
|
4705
|
+
};
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
}
|
|
4709
|
+
const current = await options.inspect();
|
|
4710
|
+
const active = await storage.checkpoints.getActive(scope);
|
|
4711
|
+
if (!active) {
|
|
4712
|
+
const checkpoint2 = createRecord(scope, current, trigger);
|
|
4713
|
+
await storage.checkpoints.save(checkpoint2);
|
|
4714
|
+
await storage.checkpoints.setActive(scope, checkpoint2.id);
|
|
4715
|
+
return { status: "baseline_created", checkpoint: checkpoint2 };
|
|
4716
|
+
}
|
|
4717
|
+
if (active.snapshotHash === current.meta.snapshotHash) {
|
|
4718
|
+
return { status: "no_changes", checkpoint: active };
|
|
4719
|
+
}
|
|
4720
|
+
const duplicate = await storage.changes.findByTransition(
|
|
4721
|
+
scope,
|
|
4722
|
+
active.snapshotHash,
|
|
4723
|
+
current.meta.snapshotHash
|
|
4724
|
+
);
|
|
4725
|
+
const checkpoint = createRecord(scope, current, trigger);
|
|
4726
|
+
const changeSet = duplicate ?? diffRRRoutesSnapshots(active.snapshot, current, {
|
|
4727
|
+
scope,
|
|
4728
|
+
trigger,
|
|
4729
|
+
fromCheckpointId: active.id,
|
|
4730
|
+
toCheckpointId: checkpoint.id
|
|
4731
|
+
});
|
|
4732
|
+
if (!duplicate) await storage.changes.save(changeSet);
|
|
4733
|
+
await storage.checkpoints.save(checkpoint);
|
|
4734
|
+
await storage.checkpoints.setActive(scope, checkpoint.id);
|
|
4735
|
+
await applyCheckpointRetention({
|
|
4736
|
+
storage,
|
|
4737
|
+
scope,
|
|
4738
|
+
activeId: checkpoint.id,
|
|
4739
|
+
retention: options.retention
|
|
4740
|
+
});
|
|
4741
|
+
await applyChangeSetRetention({
|
|
4742
|
+
storage,
|
|
4743
|
+
scope,
|
|
4744
|
+
retention: options.changeSetRetention ?? { type: "forever" }
|
|
4745
|
+
});
|
|
4746
|
+
return { status: "advanced", checkpoint, changeSet };
|
|
4747
|
+
}));
|
|
4748
|
+
},
|
|
4749
|
+
async create(input = {}) {
|
|
4750
|
+
const scope = input.scope ?? defaultScope;
|
|
4751
|
+
const trigger = input.trigger ?? { type: "manual" };
|
|
4752
|
+
return lock.run(scope, () => transaction(async () => {
|
|
4753
|
+
const checkpoint = createRecord(scope, await options.inspect(), trigger);
|
|
4754
|
+
await storage.checkpoints.save(checkpoint);
|
|
4755
|
+
await storage.checkpoints.setActive(scope, checkpoint.id);
|
|
4756
|
+
await applyCheckpointRetention({
|
|
4757
|
+
storage,
|
|
4758
|
+
scope,
|
|
4759
|
+
activeId: checkpoint.id,
|
|
4760
|
+
retention: options.retention
|
|
4761
|
+
});
|
|
4762
|
+
return checkpoint;
|
|
4763
|
+
}));
|
|
4764
|
+
},
|
|
4765
|
+
get: (id) => storage.checkpoints.get(id),
|
|
4766
|
+
getActive: (scope = defaultScope) => storage.checkpoints.getActive(scope),
|
|
4767
|
+
list: (scope) => storage.checkpoints.list(scope),
|
|
4768
|
+
async delete(id) {
|
|
4769
|
+
const checkpoint = await storage.checkpoints.get(id);
|
|
4770
|
+
if (!checkpoint) return;
|
|
4771
|
+
const active = await storage.checkpoints.getActive(checkpoint.scope);
|
|
4772
|
+
if (active?.id === id) {
|
|
4773
|
+
throw new Error("The active checkpoint cannot be deleted.");
|
|
4774
|
+
}
|
|
4775
|
+
await storage.checkpoints.delete(id);
|
|
4776
|
+
},
|
|
4777
|
+
listChangeSets: (scope) => storage.changes.list({ scope }),
|
|
4778
|
+
getChangeSet: (id) => storage.changes.get(id)
|
|
4779
|
+
};
|
|
4780
|
+
return service;
|
|
4781
|
+
}
|
|
4782
|
+
|
|
4783
|
+
// src/storage/filesystem.ts
|
|
4784
|
+
import { randomUUID } from "crypto";
|
|
4785
|
+
import fs4 from "fs/promises";
|
|
4786
|
+
import path7 from "path";
|
|
4787
|
+
function encodeSegment(value) {
|
|
4788
|
+
return Buffer.from(value, "utf8").toString("base64url");
|
|
4789
|
+
}
|
|
4790
|
+
async function exists(file) {
|
|
4791
|
+
try {
|
|
4792
|
+
await fs4.access(file);
|
|
4793
|
+
return true;
|
|
4794
|
+
} catch {
|
|
4795
|
+
return false;
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
async function atomicWriteJson(file, value) {
|
|
4799
|
+
const directory = path7.dirname(file);
|
|
4800
|
+
await fs4.mkdir(directory, { recursive: true });
|
|
4801
|
+
const temporary = path7.join(
|
|
4802
|
+
directory,
|
|
4803
|
+
`.${path7.basename(file)}.${randomUUID()}.tmp`
|
|
4804
|
+
);
|
|
4805
|
+
const handle = await fs4.open(temporary, "wx");
|
|
4806
|
+
try {
|
|
4807
|
+
await handle.writeFile(`${JSON.stringify(value, null, 2)}
|
|
4808
|
+
`, "utf8");
|
|
4809
|
+
await handle.sync();
|
|
4810
|
+
} finally {
|
|
4811
|
+
await handle.close();
|
|
4812
|
+
}
|
|
4813
|
+
await fs4.rename(temporary, file);
|
|
4814
|
+
try {
|
|
4815
|
+
const directoryHandle = await fs4.open(directory, "r");
|
|
4816
|
+
try {
|
|
4817
|
+
await directoryHandle.sync();
|
|
4818
|
+
} finally {
|
|
4819
|
+
await directoryHandle.close();
|
|
4820
|
+
}
|
|
4821
|
+
} catch {
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
async function readJson(file) {
|
|
4825
|
+
try {
|
|
4826
|
+
return JSON.parse(await fs4.readFile(file, "utf8"));
|
|
4827
|
+
} catch (error) {
|
|
4828
|
+
if (error.code === "ENOENT") return void 0;
|
|
4829
|
+
throw error;
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
async function listJsonFiles(directory) {
|
|
4833
|
+
if (!await exists(directory)) return [];
|
|
4834
|
+
const out = [];
|
|
4835
|
+
const visit = async (current) => {
|
|
4836
|
+
for (const entry of await fs4.readdir(current, { withFileTypes: true })) {
|
|
4837
|
+
const resolved = path7.join(current, entry.name);
|
|
4838
|
+
if (entry.isDirectory()) await visit(resolved);
|
|
4839
|
+
else if (entry.isFile() && entry.name.endsWith(".json"))
|
|
4840
|
+
out.push(resolved);
|
|
4841
|
+
}
|
|
4842
|
+
};
|
|
4843
|
+
await visit(directory);
|
|
4844
|
+
return out.sort();
|
|
4845
|
+
}
|
|
4846
|
+
function createFileSystemInspectionStorage(options) {
|
|
4847
|
+
const root = path7.resolve(options.directory);
|
|
4848
|
+
const checkpointRoot = path7.join(root, "checkpoints");
|
|
4849
|
+
const changeRoot = path7.join(root, "changes");
|
|
4850
|
+
const checkpointFile = (scope, id) => path7.join(
|
|
4851
|
+
checkpointRoot,
|
|
4852
|
+
encodeSegment(scope),
|
|
4853
|
+
`checkpoint-${encodeSegment(id)}.json`
|
|
4854
|
+
);
|
|
4855
|
+
const activeFile = (scope) => path7.join(checkpointRoot, encodeSegment(scope), "active.json");
|
|
4856
|
+
const changeFile = (changeSet) => {
|
|
4857
|
+
const date = new Date(changeSet.createdAt);
|
|
4858
|
+
const year = String(date.getUTCFullYear()).padStart(4, "0");
|
|
4859
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
4860
|
+
return path7.join(
|
|
4861
|
+
changeRoot,
|
|
4862
|
+
encodeSegment(changeSet.scope),
|
|
4863
|
+
year,
|
|
4864
|
+
month,
|
|
4865
|
+
`${encodeSegment(changeSet.id)}.json`
|
|
4866
|
+
);
|
|
4867
|
+
};
|
|
4868
|
+
const findCheckpoint = async (id) => {
|
|
4869
|
+
const files = (await listJsonFiles(checkpointRoot)).filter(
|
|
4870
|
+
(file) => path7.basename(file) !== "active.json"
|
|
4871
|
+
);
|
|
4872
|
+
for (const file of files) {
|
|
4873
|
+
const checkpoint = await readJson(file);
|
|
4874
|
+
if (checkpoint?.id === id) return checkpoint;
|
|
4875
|
+
}
|
|
4876
|
+
return void 0;
|
|
4877
|
+
};
|
|
4878
|
+
const findChangeSet = async (id) => {
|
|
4879
|
+
for (const file of await listJsonFiles(changeRoot)) {
|
|
4880
|
+
const changeSet = await readJson(file);
|
|
4881
|
+
if (changeSet?.id === id) return changeSet;
|
|
4882
|
+
}
|
|
4883
|
+
return void 0;
|
|
4884
|
+
};
|
|
4885
|
+
return {
|
|
4886
|
+
checkpoints: {
|
|
4887
|
+
get: findCheckpoint,
|
|
4888
|
+
async getActive(scope) {
|
|
4889
|
+
const pointer = await readJson(
|
|
4890
|
+
activeFile(scope)
|
|
4891
|
+
);
|
|
4892
|
+
return pointer ? findCheckpoint(pointer.checkpointId) : void 0;
|
|
4893
|
+
},
|
|
4894
|
+
async list(scope) {
|
|
4895
|
+
const files = (await listJsonFiles(checkpointRoot)).filter(
|
|
4896
|
+
(file) => path7.basename(file) !== "active.json"
|
|
4897
|
+
);
|
|
4898
|
+
const activeIds = /* @__PURE__ */ new Map();
|
|
4899
|
+
const values = [];
|
|
4900
|
+
for (const file of files) {
|
|
4901
|
+
const checkpoint = await readJson(file);
|
|
4902
|
+
if (!checkpoint || scope && checkpoint.scope !== scope) continue;
|
|
4903
|
+
values.push(checkpoint);
|
|
4904
|
+
if (!activeIds.has(checkpoint.scope)) {
|
|
4905
|
+
const pointer = await readJson(
|
|
4906
|
+
activeFile(checkpoint.scope)
|
|
4907
|
+
);
|
|
4908
|
+
if (pointer) activeIds.set(checkpoint.scope, pointer.checkpointId);
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
return values.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).map((checkpoint) => {
|
|
4912
|
+
const { snapshot: _snapshot, ...summary } = checkpoint;
|
|
4913
|
+
return {
|
|
4914
|
+
...summary,
|
|
4915
|
+
active: activeIds.get(checkpoint.scope) === checkpoint.id
|
|
4916
|
+
};
|
|
4917
|
+
});
|
|
4918
|
+
},
|
|
4919
|
+
async save(checkpoint) {
|
|
4920
|
+
await atomicWriteJson(
|
|
4921
|
+
checkpointFile(checkpoint.scope, checkpoint.id),
|
|
4922
|
+
checkpoint
|
|
4923
|
+
);
|
|
4924
|
+
},
|
|
4925
|
+
async setActive(scope, checkpointId) {
|
|
4926
|
+
const checkpoint = await findCheckpoint(checkpointId);
|
|
4927
|
+
if (!checkpoint || checkpoint.scope !== scope) {
|
|
4928
|
+
throw new Error(
|
|
4929
|
+
`Checkpoint ${checkpointId} does not belong to scope ${scope}.`
|
|
4930
|
+
);
|
|
4931
|
+
}
|
|
4932
|
+
await atomicWriteJson(activeFile(scope), { checkpointId });
|
|
4933
|
+
},
|
|
4934
|
+
async delete(id) {
|
|
4935
|
+
const checkpoint = await findCheckpoint(id);
|
|
4936
|
+
if (!checkpoint) return;
|
|
4937
|
+
const file = checkpointFile(checkpoint.scope, checkpoint.id);
|
|
4938
|
+
await fs4.unlink(file).catch((error) => {
|
|
4939
|
+
if (error.code !== "ENOENT") throw error;
|
|
4940
|
+
});
|
|
4941
|
+
}
|
|
4942
|
+
},
|
|
4943
|
+
changes: {
|
|
4944
|
+
get: findChangeSet,
|
|
4945
|
+
async findByTransition(scope, fromHash, toHash) {
|
|
4946
|
+
for (const file of await listJsonFiles(
|
|
4947
|
+
path7.join(changeRoot, encodeSegment(scope))
|
|
4948
|
+
)) {
|
|
4949
|
+
const changeSet = await readJson(file);
|
|
4950
|
+
if (changeSet?.scope === scope && changeSet.from.snapshotHash === fromHash && changeSet.to.snapshotHash === toHash) {
|
|
4951
|
+
return changeSet;
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
return void 0;
|
|
4955
|
+
},
|
|
4956
|
+
async list(query = {}) {
|
|
4957
|
+
const directory = query.scope ? path7.join(changeRoot, encodeSegment(query.scope)) : changeRoot;
|
|
4958
|
+
const values = [];
|
|
4959
|
+
for (const file of await listJsonFiles(directory)) {
|
|
4960
|
+
const changeSet = await readJson(file);
|
|
4961
|
+
if (changeSet && (!query.scope || changeSet.scope === query.scope)) {
|
|
4962
|
+
values.push(changeSet);
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
const selected = values.sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, query.limit ?? values.length);
|
|
4966
|
+
return selected.map((changeSet) => {
|
|
4967
|
+
const { changes: _changes, ...summary } = changeSet;
|
|
4968
|
+
return summary;
|
|
4969
|
+
});
|
|
4970
|
+
},
|
|
4971
|
+
async save(changeSet) {
|
|
4972
|
+
const existing = await this.findByTransition(
|
|
4973
|
+
changeSet.scope,
|
|
4974
|
+
changeSet.from.snapshotHash,
|
|
4975
|
+
changeSet.to.snapshotHash
|
|
4976
|
+
);
|
|
4977
|
+
if (existing) return;
|
|
4978
|
+
await atomicWriteJson(changeFile(changeSet), changeSet);
|
|
4979
|
+
},
|
|
4980
|
+
async delete(id) {
|
|
4981
|
+
const changeSet = await findChangeSet(id);
|
|
4982
|
+
if (!changeSet) return;
|
|
4983
|
+
await fs4.unlink(changeFile(changeSet)).catch((error) => {
|
|
4984
|
+
if (error.code !== "ENOENT") throw error;
|
|
4985
|
+
});
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
};
|
|
4989
|
+
}
|
|
4990
|
+
|
|
4991
|
+
// src/storage/sqlite.ts
|
|
4992
|
+
function readRow(row) {
|
|
4993
|
+
if (!row || typeof row !== "object" || typeof row.json !== "string") {
|
|
4994
|
+
return void 0;
|
|
4995
|
+
}
|
|
4996
|
+
return JSON.parse(row.json);
|
|
4997
|
+
}
|
|
4998
|
+
function readRows(rows) {
|
|
4999
|
+
return Array.isArray(rows) ? rows.flatMap((row) => {
|
|
5000
|
+
const value = readRow(row);
|
|
5001
|
+
return value ? [value] : [];
|
|
5002
|
+
}) : [];
|
|
5003
|
+
}
|
|
5004
|
+
function createSQLiteInspectionStorage(options) {
|
|
5005
|
+
const database = options.database;
|
|
5006
|
+
database.exec(`
|
|
5007
|
+
PRAGMA foreign_keys = ON;
|
|
5008
|
+
CREATE TABLE IF NOT EXISTS rrroutes_checkpoints (
|
|
5009
|
+
id TEXT PRIMARY KEY,
|
|
5010
|
+
scope TEXT NOT NULL,
|
|
5011
|
+
updated_at TEXT NOT NULL,
|
|
5012
|
+
json TEXT NOT NULL
|
|
5013
|
+
);
|
|
5014
|
+
CREATE INDEX IF NOT EXISTS rrroutes_checkpoints_scope_updated
|
|
5015
|
+
ON rrroutes_checkpoints(scope, updated_at DESC);
|
|
5016
|
+
CREATE TABLE IF NOT EXISTS rrroutes_active_checkpoints (
|
|
5017
|
+
scope TEXT PRIMARY KEY,
|
|
5018
|
+
checkpoint_id TEXT NOT NULL REFERENCES rrroutes_checkpoints(id)
|
|
5019
|
+
);
|
|
5020
|
+
CREATE TABLE IF NOT EXISTS rrroutes_change_sets (
|
|
5021
|
+
id TEXT PRIMARY KEY,
|
|
5022
|
+
scope TEXT NOT NULL,
|
|
5023
|
+
from_hash TEXT NOT NULL,
|
|
5024
|
+
to_hash TEXT NOT NULL,
|
|
5025
|
+
created_at TEXT NOT NULL,
|
|
5026
|
+
json TEXT NOT NULL,
|
|
5027
|
+
UNIQUE(scope, from_hash, to_hash)
|
|
5028
|
+
);
|
|
5029
|
+
CREATE INDEX IF NOT EXISTS rrroutes_changes_scope_created
|
|
5030
|
+
ON rrroutes_change_sets(scope, created_at DESC);
|
|
5031
|
+
`);
|
|
5032
|
+
const getCheckpoint = database.prepare(
|
|
5033
|
+
"SELECT json FROM rrroutes_checkpoints WHERE id = ?"
|
|
5034
|
+
);
|
|
5035
|
+
const storage = {
|
|
5036
|
+
async transaction(operation) {
|
|
5037
|
+
database.exec("BEGIN IMMEDIATE");
|
|
5038
|
+
try {
|
|
5039
|
+
const result = await operation();
|
|
5040
|
+
database.exec("COMMIT");
|
|
5041
|
+
return result;
|
|
5042
|
+
} catch (error) {
|
|
5043
|
+
database.exec("ROLLBACK");
|
|
5044
|
+
throw error;
|
|
5045
|
+
}
|
|
5046
|
+
},
|
|
5047
|
+
checkpoints: {
|
|
5048
|
+
async get(id) {
|
|
5049
|
+
return readRow(getCheckpoint.get(id));
|
|
5050
|
+
},
|
|
5051
|
+
async getActive(scope) {
|
|
5052
|
+
const statement = database.prepare(`
|
|
5053
|
+
SELECT checkpoint.json AS json
|
|
5054
|
+
FROM rrroutes_active_checkpoints active
|
|
5055
|
+
JOIN rrroutes_checkpoints checkpoint ON checkpoint.id = active.checkpoint_id
|
|
5056
|
+
WHERE active.scope = ?
|
|
5057
|
+
`);
|
|
5058
|
+
return readRow(statement.get(scope));
|
|
5059
|
+
},
|
|
5060
|
+
async list(scope) {
|
|
5061
|
+
const rows = scope ? database.prepare(
|
|
5062
|
+
"SELECT json FROM rrroutes_checkpoints WHERE scope = ? ORDER BY updated_at DESC"
|
|
5063
|
+
).all(scope) : database.prepare(
|
|
5064
|
+
"SELECT json FROM rrroutes_checkpoints ORDER BY updated_at DESC"
|
|
5065
|
+
).all();
|
|
5066
|
+
const activeRows = database.prepare(
|
|
5067
|
+
"SELECT scope, checkpoint_id FROM rrroutes_active_checkpoints"
|
|
5068
|
+
).all();
|
|
5069
|
+
const active = new Map(
|
|
5070
|
+
Array.isArray(activeRows) ? activeRows.map((row) => [row.scope, row.checkpoint_id]) : []
|
|
5071
|
+
);
|
|
5072
|
+
return readRows(rows).map(
|
|
5073
|
+
(checkpoint) => {
|
|
5074
|
+
const { snapshot: _snapshot, ...summary } = checkpoint;
|
|
5075
|
+
return {
|
|
5076
|
+
...summary,
|
|
5077
|
+
active: active.get(checkpoint.scope) === checkpoint.id
|
|
5078
|
+
};
|
|
5079
|
+
}
|
|
5080
|
+
);
|
|
5081
|
+
},
|
|
5082
|
+
async save(checkpoint) {
|
|
5083
|
+
database.prepare(
|
|
5084
|
+
`
|
|
5085
|
+
INSERT INTO rrroutes_checkpoints(id, scope, updated_at, json)
|
|
5086
|
+
VALUES (?, ?, ?, ?)
|
|
5087
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
5088
|
+
scope = excluded.scope,
|
|
5089
|
+
updated_at = excluded.updated_at,
|
|
5090
|
+
json = excluded.json
|
|
5091
|
+
`
|
|
5092
|
+
).run(
|
|
5093
|
+
checkpoint.id,
|
|
5094
|
+
checkpoint.scope,
|
|
5095
|
+
checkpoint.updatedAt,
|
|
5096
|
+
JSON.stringify(checkpoint)
|
|
5097
|
+
);
|
|
5098
|
+
},
|
|
5099
|
+
async setActive(scope, checkpointId) {
|
|
5100
|
+
const checkpoint = readRow(
|
|
5101
|
+
getCheckpoint.get(checkpointId)
|
|
5102
|
+
);
|
|
5103
|
+
if (!checkpoint || checkpoint.scope !== scope) {
|
|
5104
|
+
throw new Error(
|
|
5105
|
+
`Checkpoint ${checkpointId} does not belong to scope ${scope}.`
|
|
5106
|
+
);
|
|
5107
|
+
}
|
|
5108
|
+
database.prepare(
|
|
5109
|
+
`
|
|
5110
|
+
INSERT INTO rrroutes_active_checkpoints(scope, checkpoint_id)
|
|
5111
|
+
VALUES (?, ?)
|
|
5112
|
+
ON CONFLICT(scope) DO UPDATE SET checkpoint_id = excluded.checkpoint_id
|
|
5113
|
+
`
|
|
5114
|
+
).run(scope, checkpointId);
|
|
5115
|
+
},
|
|
5116
|
+
async delete(id) {
|
|
5117
|
+
database.prepare("DELETE FROM rrroutes_checkpoints WHERE id = ?").run(id);
|
|
5118
|
+
}
|
|
5119
|
+
},
|
|
5120
|
+
changes: {
|
|
5121
|
+
async get(id) {
|
|
5122
|
+
return readRow(
|
|
5123
|
+
database.prepare("SELECT json FROM rrroutes_change_sets WHERE id = ?").get(id)
|
|
5124
|
+
);
|
|
5125
|
+
},
|
|
5126
|
+
async findByTransition(scope, fromHash, toHash) {
|
|
5127
|
+
return readRow(
|
|
5128
|
+
database.prepare(
|
|
5129
|
+
`
|
|
5130
|
+
SELECT json FROM rrroutes_change_sets
|
|
5131
|
+
WHERE scope = ? AND from_hash = ? AND to_hash = ?
|
|
5132
|
+
`
|
|
5133
|
+
).get(scope, fromHash, toHash)
|
|
5134
|
+
);
|
|
5135
|
+
},
|
|
5136
|
+
async list(query = {}) {
|
|
5137
|
+
const rows = query.scope ? database.prepare(
|
|
5138
|
+
`
|
|
5139
|
+
SELECT json FROM rrroutes_change_sets
|
|
5140
|
+
WHERE scope = ? ORDER BY created_at DESC LIMIT ?
|
|
5141
|
+
`
|
|
5142
|
+
).all(query.scope, query.limit ?? -1) : database.prepare(
|
|
5143
|
+
`
|
|
5144
|
+
SELECT json FROM rrroutes_change_sets
|
|
5145
|
+
ORDER BY created_at DESC LIMIT ?
|
|
5146
|
+
`
|
|
5147
|
+
).all(query.limit ?? -1);
|
|
5148
|
+
return readRows(rows).map(
|
|
5149
|
+
(changeSet) => {
|
|
5150
|
+
const { changes: _changes, ...summary } = changeSet;
|
|
5151
|
+
return summary;
|
|
5152
|
+
}
|
|
5153
|
+
);
|
|
5154
|
+
},
|
|
5155
|
+
async save(changeSet) {
|
|
5156
|
+
database.prepare(
|
|
5157
|
+
`
|
|
5158
|
+
INSERT OR IGNORE INTO rrroutes_change_sets(
|
|
5159
|
+
id, scope, from_hash, to_hash, created_at, json
|
|
5160
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
5161
|
+
`
|
|
5162
|
+
).run(
|
|
5163
|
+
changeSet.id,
|
|
5164
|
+
changeSet.scope,
|
|
5165
|
+
changeSet.from.snapshotHash,
|
|
5166
|
+
changeSet.to.snapshotHash,
|
|
5167
|
+
changeSet.createdAt,
|
|
5168
|
+
JSON.stringify(changeSet)
|
|
5169
|
+
);
|
|
5170
|
+
},
|
|
5171
|
+
async delete(id) {
|
|
5172
|
+
database.prepare("DELETE FROM rrroutes_change_sets WHERE id = ?").run(id);
|
|
5173
|
+
}
|
|
5174
|
+
}
|
|
5175
|
+
};
|
|
5176
|
+
return storage;
|
|
5177
|
+
}
|
|
5178
|
+
|
|
5179
|
+
// src/http/handler.ts
|
|
5180
|
+
function json(value, status = 200) {
|
|
5181
|
+
return Response.json(value, {
|
|
5182
|
+
status,
|
|
5183
|
+
headers: {
|
|
5184
|
+
"cache-control": "no-store, max-age=0",
|
|
5185
|
+
"content-type": "application/json; charset=utf-8",
|
|
5186
|
+
"x-content-type-options": "nosniff"
|
|
5187
|
+
}
|
|
5188
|
+
});
|
|
5189
|
+
}
|
|
5190
|
+
async function overview(snapshot, runtime) {
|
|
5191
|
+
const routes = Object.values(snapshot.routes);
|
|
5192
|
+
const clientBindings = Object.values(snapshot.clients).flatMap(
|
|
5193
|
+
(client) => client.bindings
|
|
5194
|
+
);
|
|
5195
|
+
const [activeCheckpoint, latestChangeSet] = await Promise.all([
|
|
5196
|
+
runtime.checkpoints.getActive(),
|
|
5197
|
+
runtime.checkpoints.listChangeSets().then((items) => items[0])
|
|
5198
|
+
]);
|
|
5199
|
+
return {
|
|
5200
|
+
totalRoutes: routes.length,
|
|
5201
|
+
contractOnlyRoutes: routes.filter(
|
|
5202
|
+
(route) => route.clients.length === 0 && route.status.server !== "enabled"
|
|
5203
|
+
).length,
|
|
5204
|
+
clientBoundRoutes: routes.filter((route) => route.clients.length > 0).length,
|
|
5205
|
+
serverRegisteredRoutes: routes.filter((route) => route.server?.registered).length,
|
|
5206
|
+
disabledServerRoutes: routes.filter(
|
|
5207
|
+
(route) => route.server?.registered && !route.server.enabled
|
|
5208
|
+
).length,
|
|
5209
|
+
routesMissingServerController: routes.filter(
|
|
5210
|
+
(route) => route.status.server === "missing"
|
|
5211
|
+
).length,
|
|
5212
|
+
clientBindings: clientBindings.length,
|
|
5213
|
+
augments: clientBindings.reduce(
|
|
5214
|
+
(total, binding) => total + binding.augments.length,
|
|
5215
|
+
0
|
|
5216
|
+
),
|
|
5217
|
+
routeSocketConnections: clientBindings.reduce(
|
|
5218
|
+
(total, binding) => total + binding.socketConnections.length,
|
|
5219
|
+
0
|
|
5220
|
+
),
|
|
5221
|
+
socketContractEvents: Object.values(snapshot.sockets).reduce(
|
|
5222
|
+
(total, socket) => total + Object.keys(socket.contract.events).length,
|
|
5223
|
+
0
|
|
5224
|
+
),
|
|
5225
|
+
serverBeforeMiddleware: routes.reduce(
|
|
5226
|
+
(total, route) => total + (route.server?.before.length ?? 0),
|
|
5227
|
+
0
|
|
5228
|
+
),
|
|
5229
|
+
activeCheckpoint: activeCheckpoint ? {
|
|
5230
|
+
id: activeCheckpoint.id,
|
|
5231
|
+
scope: activeCheckpoint.scope,
|
|
5232
|
+
snapshotHash: activeCheckpoint.snapshotHash,
|
|
5233
|
+
updatedAt: activeCheckpoint.updatedAt
|
|
5234
|
+
} : void 0,
|
|
5235
|
+
latestChangeSet,
|
|
5236
|
+
diagnostics: snapshot.diagnostics.length
|
|
5237
|
+
};
|
|
5238
|
+
}
|
|
5239
|
+
async function readMutationBody(request, maximum) {
|
|
5240
|
+
const contentLength = Number(request.headers.get("content-length") ?? "0");
|
|
5241
|
+
if (contentLength > maximum) throw new Error("REQUEST_BODY_TOO_LARGE");
|
|
5242
|
+
const text = await request.text();
|
|
5243
|
+
if (new TextEncoder().encode(text).byteLength > maximum) {
|
|
5244
|
+
throw new Error("REQUEST_BODY_TOO_LARGE");
|
|
5245
|
+
}
|
|
5246
|
+
if (!text) return {};
|
|
5247
|
+
const value = JSON.parse(text);
|
|
5248
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5249
|
+
throw new Error("REQUEST_BODY_INVALID");
|
|
5250
|
+
}
|
|
5251
|
+
return value;
|
|
5252
|
+
}
|
|
5253
|
+
function createInspectorHttpHandler(runtime, options = {}) {
|
|
5254
|
+
const mutationOptions = options.mutations;
|
|
5255
|
+
const authorizeRead = async (request) => !options.authorize || await options.authorize({ request, action: "read" });
|
|
5256
|
+
const authorizeMutation = async (request, action) => mutationOptions !== false && mutationOptions?.enabled === true && (!mutationOptions.authorize || await mutationOptions.authorize({ request, action }));
|
|
5257
|
+
return async (request) => {
|
|
5258
|
+
try {
|
|
5259
|
+
if (!await authorizeRead(request)) {
|
|
5260
|
+
return json(
|
|
5261
|
+
{ error: { code: "FORBIDDEN", message: "Access denied." } },
|
|
5262
|
+
403
|
|
5263
|
+
);
|
|
5264
|
+
}
|
|
5265
|
+
const url = new URL(request.url);
|
|
5266
|
+
const pathname = url.pathname.replace(/\/$/, "") || "/";
|
|
5267
|
+
const method = request.method.toUpperCase();
|
|
5268
|
+
const snapshot = async () => runtime.inspect({ includeLive: options.includeLive ?? false });
|
|
5269
|
+
if (method === "GET" && pathname === "/api/meta") {
|
|
5270
|
+
const value = await snapshot();
|
|
5271
|
+
return json({ meta: value.meta, capabilities: value.capabilities });
|
|
5272
|
+
}
|
|
5273
|
+
if (method === "GET" && pathname === "/api/snapshot")
|
|
5274
|
+
return json(await snapshot());
|
|
5275
|
+
if (method === "GET" && pathname === "/api/overview")
|
|
5276
|
+
return json(await overview(await snapshot(), runtime));
|
|
5277
|
+
if (method === "GET" && pathname === "/api/routes") {
|
|
5278
|
+
return json(Object.values((await snapshot()).routes));
|
|
5279
|
+
}
|
|
5280
|
+
if (method === "GET" && pathname.startsWith("/api/routes/")) {
|
|
5281
|
+
const routeKey = decodeURIComponent(
|
|
5282
|
+
pathname.slice("/api/routes/".length)
|
|
5283
|
+
);
|
|
5284
|
+
const route = (await snapshot()).routes[routeKey];
|
|
5285
|
+
return route ? json(route) : json(
|
|
5286
|
+
{ error: { code: "NOT_FOUND", message: "Route not found." } },
|
|
5287
|
+
404
|
|
5288
|
+
);
|
|
5289
|
+
}
|
|
5290
|
+
if (method === "GET" && pathname === "/api/clients") {
|
|
5291
|
+
return json((await snapshot()).clients);
|
|
5292
|
+
}
|
|
5293
|
+
if (method === "GET" && pathname.startsWith("/api/clients/")) {
|
|
5294
|
+
const id = decodeURIComponent(pathname.slice("/api/clients/".length));
|
|
5295
|
+
const client = (await snapshot()).clients[id];
|
|
5296
|
+
return client ? json(client) : json(
|
|
5297
|
+
{ error: { code: "NOT_FOUND", message: "Client not found." } },
|
|
5298
|
+
404
|
|
5299
|
+
);
|
|
5300
|
+
}
|
|
5301
|
+
if (method === "GET" && pathname === "/api/servers")
|
|
5302
|
+
return json((await snapshot()).servers);
|
|
5303
|
+
if (method === "GET" && pathname === "/api/sockets")
|
|
5304
|
+
return json((await snapshot()).sockets);
|
|
5305
|
+
if (method === "GET" && pathname === "/api/checkpoints")
|
|
5306
|
+
return json(await runtime.checkpoints.list());
|
|
5307
|
+
if (method === "GET" && pathname === "/api/changes")
|
|
5308
|
+
return json(await runtime.checkpoints.listChangeSets());
|
|
5309
|
+
if (method === "GET" && pathname.startsWith("/api/changes/")) {
|
|
5310
|
+
const id = decodeURIComponent(pathname.slice("/api/changes/".length));
|
|
5311
|
+
const changeSet = await runtime.checkpoints.getChangeSet(id);
|
|
5312
|
+
return changeSet ? json(changeSet) : json(
|
|
5313
|
+
{
|
|
5314
|
+
error: { code: "NOT_FOUND", message: "Change set not found." }
|
|
5315
|
+
},
|
|
5316
|
+
404
|
|
5317
|
+
);
|
|
5318
|
+
}
|
|
5319
|
+
if (method === "POST" && pathname === "/api/checkpoints") {
|
|
5320
|
+
if (!await authorizeMutation(request, "checkpoint.create")) {
|
|
5321
|
+
return json(
|
|
5322
|
+
{
|
|
5323
|
+
error: {
|
|
5324
|
+
code: "MUTATIONS_DISABLED",
|
|
5325
|
+
message: "Inspector mutations are disabled."
|
|
5326
|
+
}
|
|
5327
|
+
},
|
|
5328
|
+
403
|
|
5329
|
+
);
|
|
5330
|
+
}
|
|
5331
|
+
const body = await readMutationBody(
|
|
5332
|
+
request,
|
|
5333
|
+
options.maxMutationBodyBytes ?? 16384
|
|
5334
|
+
);
|
|
5335
|
+
return json(
|
|
5336
|
+
await runtime.checkpoints.create({
|
|
5337
|
+
scope: typeof body.scope === "string" ? body.scope : void 0,
|
|
5338
|
+
trigger: body.trigger
|
|
5339
|
+
}),
|
|
5340
|
+
201
|
|
5341
|
+
);
|
|
5342
|
+
}
|
|
5343
|
+
if (method === "POST" && pathname === "/api/checkpoints/advance") {
|
|
5344
|
+
if (!await authorizeMutation(request, "checkpoint.advance")) {
|
|
5345
|
+
return json(
|
|
5346
|
+
{
|
|
5347
|
+
error: {
|
|
5348
|
+
code: "MUTATIONS_DISABLED",
|
|
5349
|
+
message: "Inspector mutations are disabled."
|
|
5350
|
+
}
|
|
5351
|
+
},
|
|
5352
|
+
403
|
|
5353
|
+
);
|
|
5354
|
+
}
|
|
5355
|
+
const body = await readMutationBody(
|
|
5356
|
+
request,
|
|
5357
|
+
options.maxMutationBodyBytes ?? 16384
|
|
5358
|
+
);
|
|
5359
|
+
const idempotencyKey = request.headers.get("idempotency-key") ?? void 0;
|
|
5360
|
+
const trigger = body.trigger && typeof body.trigger === "object" ? {
|
|
5361
|
+
...body.trigger,
|
|
5362
|
+
externalId: body.trigger.externalId ?? idempotencyKey
|
|
5363
|
+
} : { type: "api", externalId: idempotencyKey };
|
|
5364
|
+
return json(
|
|
5365
|
+
await runtime.checkpoints.advance({
|
|
5366
|
+
scope: typeof body.scope === "string" ? body.scope : void 0,
|
|
5367
|
+
trigger
|
|
5368
|
+
})
|
|
5369
|
+
);
|
|
5370
|
+
}
|
|
5371
|
+
if (method === "DELETE" && pathname.startsWith("/api/checkpoints/")) {
|
|
5372
|
+
if (!await authorizeMutation(request, "checkpoint.delete")) {
|
|
5373
|
+
return json(
|
|
5374
|
+
{
|
|
5375
|
+
error: {
|
|
5376
|
+
code: "MUTATIONS_DISABLED",
|
|
5377
|
+
message: "Inspector mutations are disabled."
|
|
5378
|
+
}
|
|
5379
|
+
},
|
|
5380
|
+
403
|
|
5381
|
+
);
|
|
5382
|
+
}
|
|
5383
|
+
const id = decodeURIComponent(
|
|
5384
|
+
pathname.slice("/api/checkpoints/".length)
|
|
5385
|
+
);
|
|
5386
|
+
await runtime.checkpoints.delete(id);
|
|
5387
|
+
return new Response(null, {
|
|
5388
|
+
status: 204,
|
|
5389
|
+
headers: { "cache-control": "no-store" }
|
|
5390
|
+
});
|
|
5391
|
+
}
|
|
5392
|
+
return json(
|
|
5393
|
+
{
|
|
5394
|
+
error: {
|
|
5395
|
+
code: "NOT_FOUND",
|
|
5396
|
+
message: "Inspector endpoint not found."
|
|
5397
|
+
}
|
|
5398
|
+
},
|
|
5399
|
+
404
|
|
5400
|
+
);
|
|
5401
|
+
} catch (error) {
|
|
5402
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5403
|
+
if (message === "REQUEST_BODY_TOO_LARGE") {
|
|
5404
|
+
return json(
|
|
5405
|
+
{
|
|
5406
|
+
error: {
|
|
5407
|
+
code: message,
|
|
5408
|
+
message: "Mutation body exceeds the configured limit."
|
|
5409
|
+
}
|
|
5410
|
+
},
|
|
5411
|
+
413
|
|
5412
|
+
);
|
|
5413
|
+
}
|
|
5414
|
+
if (message === "REQUEST_BODY_INVALID" || error instanceof SyntaxError) {
|
|
5415
|
+
return json(
|
|
5416
|
+
{
|
|
5417
|
+
error: {
|
|
5418
|
+
code: "REQUEST_BODY_INVALID",
|
|
5419
|
+
message: "Mutation body must be a JSON object."
|
|
5420
|
+
}
|
|
5421
|
+
},
|
|
5422
|
+
400
|
|
5423
|
+
);
|
|
5424
|
+
}
|
|
5425
|
+
return json({ error: { code: "INSPECTOR_ERROR", message } }, 500);
|
|
5426
|
+
}
|
|
5427
|
+
};
|
|
5428
|
+
}
|
|
5429
|
+
|
|
5430
|
+
// src/adapters/express.ts
|
|
5431
|
+
import { createRequire } from "module";
|
|
5432
|
+
function loadExpress() {
|
|
5433
|
+
const require2 = createRequire(`${process.cwd()}/package.json`);
|
|
5434
|
+
const loaded = require2("express");
|
|
5435
|
+
return "default" in loaded ? loaded.default : loaded;
|
|
5436
|
+
}
|
|
5437
|
+
function createExpressRouter(options) {
|
|
5438
|
+
const express = loadExpress();
|
|
5439
|
+
const router = express.Router();
|
|
5440
|
+
const limit = options.maxMutationBodyBytes ?? 16384;
|
|
5441
|
+
router.use(express.json({ limit }));
|
|
5442
|
+
const handler = createInspectorHttpHandler(options.runtime, options);
|
|
5443
|
+
const bridge = async (req, res, next) => {
|
|
5444
|
+
try {
|
|
5445
|
+
const headers = new Headers();
|
|
5446
|
+
for (const [name, raw] of Object.entries(req.headers)) {
|
|
5447
|
+
if (Array.isArray(raw))
|
|
5448
|
+
raw.forEach((value) => headers.append(name, value));
|
|
5449
|
+
else if (raw !== void 0) headers.set(name, raw);
|
|
5450
|
+
}
|
|
5451
|
+
const method = req.method.toUpperCase();
|
|
5452
|
+
const body = method === "GET" || method === "HEAD" || req.body === void 0 ? void 0 : JSON.stringify(req.body);
|
|
5453
|
+
const request = new Request(`http://rrroutes.local${req.url}`, {
|
|
5454
|
+
method,
|
|
5455
|
+
headers,
|
|
5456
|
+
body
|
|
5457
|
+
});
|
|
5458
|
+
const response = await handler(request);
|
|
5459
|
+
response.headers.forEach((value, name) => res.setHeader(name, value));
|
|
5460
|
+
res.status(response.status);
|
|
5461
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
5462
|
+
res.send(Buffer.from(bytes));
|
|
5463
|
+
} catch (error) {
|
|
5464
|
+
next(error);
|
|
5465
|
+
}
|
|
5466
|
+
};
|
|
5467
|
+
router.use(bridge);
|
|
5468
|
+
return router;
|
|
5469
|
+
}
|
|
5470
|
+
|
|
5471
|
+
// src/files/write.ts
|
|
5472
|
+
import fs5 from "fs/promises";
|
|
5473
|
+
import path8 from "path";
|
|
5474
|
+
async function writeJson(file, value) {
|
|
5475
|
+
const resolved = path8.resolve(file);
|
|
5476
|
+
await fs5.mkdir(path8.dirname(resolved), { recursive: true });
|
|
5477
|
+
await fs5.writeFile(resolved, `${JSON.stringify(value, null, 2)}
|
|
5478
|
+
`, "utf8");
|
|
5479
|
+
}
|
|
5480
|
+
async function writeRRRoutesSnapshot(snapshot, options) {
|
|
5481
|
+
await writeJson(options.outFile, snapshot);
|
|
5482
|
+
}
|
|
5483
|
+
async function writeRRRoutesChangeSet(changeSet, options) {
|
|
5484
|
+
await writeJson(options.outFile, changeSet);
|
|
5485
|
+
}
|
|
5486
|
+
|
|
5487
|
+
// src/runtime.ts
|
|
5488
|
+
function createRRRoutesExportRuntime(options) {
|
|
5489
|
+
const inspect = (inspectOptions) => buildRRRoutesSnapshot(options, inspectOptions);
|
|
5490
|
+
const checkpoints = createCheckpointService({
|
|
5491
|
+
inspect: () => inspect({ includeLive: false }),
|
|
5492
|
+
storage: options.storage,
|
|
5493
|
+
scope: options.checkpoints?.scope,
|
|
5494
|
+
retention: options.checkpoints?.retention,
|
|
5495
|
+
changeSetRetention: options.checkpoints?.changeSetRetention
|
|
5496
|
+
});
|
|
5497
|
+
const runtime = {
|
|
5498
|
+
inspect,
|
|
5499
|
+
async diff(before, after, diffOptions) {
|
|
5500
|
+
return diffRRRoutesSnapshots(
|
|
5501
|
+
before,
|
|
5502
|
+
after ?? await inspect(),
|
|
5503
|
+
diffOptions
|
|
5504
|
+
);
|
|
5505
|
+
},
|
|
5506
|
+
checkpoints,
|
|
5507
|
+
createHttpHandler(httpOptions) {
|
|
5508
|
+
return createInspectorHttpHandler(runtime, httpOptions);
|
|
5509
|
+
},
|
|
5510
|
+
createExpressRouter(expressOptions = {}) {
|
|
5511
|
+
return createExpressRouter({ runtime, ...expressOptions });
|
|
5512
|
+
},
|
|
5513
|
+
async writeSnapshot(writeOptions) {
|
|
5514
|
+
const snapshot = await inspect();
|
|
5515
|
+
await writeRRRoutesSnapshot(snapshot, writeOptions);
|
|
5516
|
+
return snapshot;
|
|
5517
|
+
},
|
|
5518
|
+
writeChangeSet
|
|
5519
|
+
};
|
|
5520
|
+
return runtime;
|
|
5521
|
+
}
|
|
5522
|
+
async function writeChangeSet(changeSet, writeOptions) {
|
|
5523
|
+
await writeRRRoutesChangeSet(changeSet, writeOptions);
|
|
5524
|
+
}
|
|
5525
|
+
|
|
5526
|
+
// src/cli/runtime.ts
|
|
5527
|
+
import path9 from "path";
|
|
5528
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
5529
|
+
function parseCliFlags(argv) {
|
|
5530
|
+
const flags = /* @__PURE__ */ new Map();
|
|
5531
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
5532
|
+
const flag = argv[index];
|
|
5533
|
+
if (!flag?.startsWith("--")) continue;
|
|
5534
|
+
const value = argv[index + 1];
|
|
5535
|
+
if (!value || value.startsWith("--")) {
|
|
5536
|
+
flags.set(flag, "true");
|
|
5537
|
+
continue;
|
|
5538
|
+
}
|
|
5539
|
+
flags.set(flag, value);
|
|
5540
|
+
index += 1;
|
|
5541
|
+
}
|
|
5542
|
+
return flags;
|
|
5543
|
+
}
|
|
5544
|
+
async function loadExportRuntime(args) {
|
|
5545
|
+
const resolved = path9.resolve(args.modulePath);
|
|
5546
|
+
const module = await import(pathToFileURL3(resolved).href);
|
|
5547
|
+
const exportName = args.exportName ?? "rrroutesExport";
|
|
5548
|
+
const value = module[exportName] ?? module.default?.[exportName];
|
|
5549
|
+
if (!value || typeof value.inspect !== "function" || !value.checkpoints) {
|
|
5550
|
+
throw new Error(
|
|
5551
|
+
`Export "${exportName}" in ${resolved} is not an RRRoutes export runtime.`
|
|
5552
|
+
);
|
|
5553
|
+
}
|
|
5554
|
+
return value;
|
|
5555
|
+
}
|
|
5556
|
+
function requiredFlag(flags, name) {
|
|
5557
|
+
const value = flags.get(name);
|
|
5558
|
+
if (!value || value === "true")
|
|
5559
|
+
throw new Error(`Missing required ${name} argument.`);
|
|
5560
|
+
return value;
|
|
5561
|
+
}
|
|
5562
|
+
|
|
5563
|
+
// src/cli/snapshot.ts
|
|
5564
|
+
import path10 from "path";
|
|
5565
|
+
async function runSnapshotCli(argv) {
|
|
5566
|
+
const flags = parseCliFlags(argv);
|
|
5567
|
+
const runtime = await loadExportRuntime({
|
|
5568
|
+
modulePath: requiredFlag(flags, "--module"),
|
|
5569
|
+
exportName: flags.get("--export")
|
|
5570
|
+
});
|
|
5571
|
+
const outFile = flags.get("--out") ?? "rrroutes.snapshot.json";
|
|
5572
|
+
const snapshot = await runtime.writeSnapshot({ outFile });
|
|
5573
|
+
return { snapshot, outFile: path10.resolve(outFile) };
|
|
5574
|
+
}
|
|
5575
|
+
|
|
5576
|
+
// src/cli/changes.ts
|
|
5577
|
+
import fs6 from "fs/promises";
|
|
5578
|
+
import path11 from "path";
|
|
5579
|
+
async function runChangesCli(argv) {
|
|
5580
|
+
const flags = parseCliFlags(argv);
|
|
5581
|
+
const runtime = await loadExportRuntime({
|
|
5582
|
+
modulePath: requiredFlag(flags, "--module"),
|
|
5583
|
+
exportName: flags.get("--export")
|
|
5584
|
+
});
|
|
5585
|
+
const beforeFile = requiredFlag(flags, "--before");
|
|
5586
|
+
const before = JSON.parse(
|
|
5587
|
+
await fs6.readFile(path11.resolve(beforeFile), "utf8")
|
|
5588
|
+
);
|
|
5589
|
+
const changeSet = await runtime.diff(before, void 0, {
|
|
5590
|
+
scope: flags.get("--scope"),
|
|
5591
|
+
trigger: { type: "cli" }
|
|
5592
|
+
});
|
|
5593
|
+
const outFile = flags.get("--out") ?? "rrroutes.changes.json";
|
|
5594
|
+
await runtime.writeChangeSet(changeSet, { outFile });
|
|
5595
|
+
return { changeSet, outFile: path11.resolve(outFile) };
|
|
5596
|
+
}
|
|
5597
|
+
|
|
5598
|
+
// src/cli/checkpoint.ts
|
|
5599
|
+
async function runCheckpointCli(argv) {
|
|
5600
|
+
const [action = "advance", ...rest] = argv;
|
|
5601
|
+
const flags = parseCliFlags(rest);
|
|
5602
|
+
const runtime = await loadExportRuntime({
|
|
5603
|
+
modulePath: requiredFlag(flags, "--module"),
|
|
5604
|
+
exportName: flags.get("--export")
|
|
5605
|
+
});
|
|
5606
|
+
const scope = flags.get("--scope");
|
|
5607
|
+
if (action === "advance") {
|
|
5608
|
+
return runtime.checkpoints.advance({ scope, trigger: { type: "cli" } });
|
|
5609
|
+
}
|
|
5610
|
+
if (action === "create") {
|
|
5611
|
+
return runtime.checkpoints.create({ scope, trigger: { type: "cli" } });
|
|
5612
|
+
}
|
|
5613
|
+
throw new Error(`Unsupported checkpoint action: ${action}`);
|
|
5614
|
+
}
|
|
5615
|
+
|
|
5616
|
+
// src/cli/serve.ts
|
|
5617
|
+
import { createRequire as createRequire2 } from "module";
|
|
5618
|
+
async function runInspectorServeCli(argv) {
|
|
5619
|
+
const loaded = createRequire2(`${process.cwd()}/package.json`)("express");
|
|
5620
|
+
const express = "default" in loaded ? loaded.default : loaded;
|
|
5621
|
+
const flags = parseCliFlags(argv);
|
|
5622
|
+
const runtime = await loadExportRuntime({
|
|
5623
|
+
modulePath: requiredFlag(flags, "--module"),
|
|
5624
|
+
exportName: flags.get("--export")
|
|
5625
|
+
});
|
|
5626
|
+
const port = Number(flags.get("--port") ?? "4310");
|
|
5627
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
5628
|
+
throw new Error("--port must be an integer between 1 and 65535.");
|
|
5629
|
+
}
|
|
5630
|
+
const app = express();
|
|
5631
|
+
app.use("/", runtime.createExpressRouter());
|
|
5632
|
+
const server = await new Promise((resolve) => {
|
|
5633
|
+
const listening = app.listen(port, () => resolve(listening));
|
|
5634
|
+
});
|
|
5635
|
+
return { app, server, port };
|
|
5636
|
+
}
|
|
2715
5637
|
export {
|
|
2716
5638
|
DEFAULT_VIEWER_TEMPLATE,
|
|
5639
|
+
RRROUTES_BAKED_PAYLOAD_MARKER,
|
|
2717
5640
|
__private,
|
|
5641
|
+
applyChangeSetRetention,
|
|
5642
|
+
applyCheckpointRetention,
|
|
2718
5643
|
buildFinalizedLeavesViewerBundle,
|
|
5644
|
+
buildRRRoutesSnapshot,
|
|
5645
|
+
canonicalSnapshotStructure,
|
|
5646
|
+
canonicalizeJson,
|
|
2719
5647
|
clearSchemaIntrospectionHandlers,
|
|
5648
|
+
createBakedInspectorPayload,
|
|
5649
|
+
createCheckpointService,
|
|
5650
|
+
createExpressRouter,
|
|
5651
|
+
createFileSystemInspectionStorage,
|
|
5652
|
+
createInspectorHttpHandler,
|
|
5653
|
+
createMemoryInspectionStorage,
|
|
5654
|
+
createRRRoutesExportRuntime,
|
|
5655
|
+
createSQLiteInspectionStorage,
|
|
2720
5656
|
createSchemaIntrospector,
|
|
5657
|
+
createSnapshotChangeSet,
|
|
5658
|
+
diffRRRoutesSnapshots,
|
|
5659
|
+
escapeBakedPayload,
|
|
2721
5660
|
exportFinalizedLeaves,
|
|
2722
5661
|
exportFinalizedLeavesChangelog,
|
|
2723
5662
|
extractLeafSourceByAst,
|
|
2724
5663
|
flattenLeafSchemas,
|
|
2725
5664
|
flattenSerializableSchema,
|
|
5665
|
+
hashCanonicalValue,
|
|
5666
|
+
hashRRRoutesSnapshot,
|
|
5667
|
+
injectBakedInspectorPayload,
|
|
2726
5668
|
introspectSchema,
|
|
5669
|
+
loadExportRuntime,
|
|
2727
5670
|
loadFinalizedLeavesInput,
|
|
5671
|
+
parseCliFlags,
|
|
2728
5672
|
parseFinalizedLeavesChangelogCliArgs,
|
|
2729
5673
|
parseFinalizedLeavesCliArgs,
|
|
2730
5674
|
registerSchemaIntrospectionHandler,
|
|
5675
|
+
requiredFlag,
|
|
5676
|
+
runChangesCli,
|
|
5677
|
+
runCheckpointCli,
|
|
2731
5678
|
runExportFinalizedLeavesChangelogCli,
|
|
2732
5679
|
runExportFinalizedLeavesCli,
|
|
5680
|
+
runInspectorServeCli,
|
|
5681
|
+
runSnapshotCli,
|
|
2733
5682
|
serializableSchemaKinds,
|
|
2734
5683
|
serializeLeafContract,
|
|
2735
5684
|
serializeLeavesContract,
|
|
5685
|
+
stableStringify,
|
|
2736
5686
|
writeFinalizedLeavesChangelogExport,
|
|
2737
5687
|
writeFinalizedLeavesExport,
|
|
2738
|
-
writeFinalizedLeavesViewerBundle
|
|
5688
|
+
writeFinalizedLeavesViewerBundle,
|
|
5689
|
+
writeRRRoutesChangeSet,
|
|
5690
|
+
writeRRRoutesSnapshot
|
|
2739
5691
|
};
|
|
2740
5692
|
//# sourceMappingURL=index.mjs.map
|