@emeryld/rrroutes-export 1.1.1 → 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 +3094 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +25 -0
- package/dist/index.mjs +3065 -116
- 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 +66 -7
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 = [
|
|
@@ -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: {
|
|
@@ -299,46 +307,46 @@ function normalizeType(schema) {
|
|
|
299
307
|
return "unknown";
|
|
300
308
|
}
|
|
301
309
|
}
|
|
302
|
-
function isNonEmptyPath(
|
|
303
|
-
return typeof
|
|
310
|
+
function isNonEmptyPath(path12) {
|
|
311
|
+
return typeof path12 === "string" && path12.length > 0;
|
|
304
312
|
}
|
|
305
|
-
function setNode(out,
|
|
306
|
-
if (!isNonEmptyPath(
|
|
307
|
-
out[
|
|
313
|
+
function setNode(out, path12, schema, inherited) {
|
|
314
|
+
if (!isNonEmptyPath(path12)) return;
|
|
315
|
+
out[path12] = {
|
|
308
316
|
type: normalizeType(schema),
|
|
309
317
|
nullable: inherited.nullable || Boolean(schema.nullable),
|
|
310
318
|
optional: inherited.optional || Boolean(schema.optional),
|
|
311
319
|
literal: schema.kind === "literal" ? schema.literal : void 0
|
|
312
320
|
};
|
|
313
321
|
}
|
|
314
|
-
function flattenInto(out, schema,
|
|
315
|
-
if (!schema || !isNonEmptyPath(
|
|
322
|
+
function flattenInto(out, schema, path12, inherited) {
|
|
323
|
+
if (!schema || !isNonEmptyPath(path12)) return;
|
|
316
324
|
const nextInherited = {
|
|
317
325
|
optional: inherited.optional || Boolean(schema.optional),
|
|
318
326
|
nullable: inherited.nullable || Boolean(schema.nullable)
|
|
319
327
|
};
|
|
320
328
|
if (schema.kind === "union" && Array.isArray(schema.union) && schema.union.length > 0) {
|
|
321
329
|
schema.union.forEach((option, index) => {
|
|
322
|
-
const optionPath = `${
|
|
330
|
+
const optionPath = `${path12}-${index + 1}`;
|
|
323
331
|
flattenInto(out, option, optionPath, inherited);
|
|
324
332
|
});
|
|
325
333
|
return;
|
|
326
334
|
}
|
|
327
|
-
setNode(out,
|
|
335
|
+
setNode(out, path12, schema, inherited);
|
|
328
336
|
if (schema.kind === "object" && schema.properties) {
|
|
329
337
|
for (const [key, child] of Object.entries(schema.properties)) {
|
|
330
|
-
const childPath = `${
|
|
338
|
+
const childPath = `${path12}.${key}`;
|
|
331
339
|
flattenInto(out, child, childPath, nextInherited);
|
|
332
340
|
}
|
|
333
341
|
return;
|
|
334
342
|
}
|
|
335
343
|
if (schema.kind === "array" && schema.element) {
|
|
336
|
-
flattenInto(out, schema.element, `${
|
|
344
|
+
flattenInto(out, schema.element, `${path12}[]`, nextInherited);
|
|
337
345
|
}
|
|
338
346
|
}
|
|
339
|
-
function flattenSerializableSchema(schema,
|
|
347
|
+
function flattenSerializableSchema(schema, path12) {
|
|
340
348
|
const out = {};
|
|
341
|
-
flattenInto(out, schema,
|
|
349
|
+
flattenInto(out, schema, path12, { optional: false, nullable: false });
|
|
342
350
|
return out;
|
|
343
351
|
}
|
|
344
352
|
function isSerializedLeaf(value) {
|
|
@@ -1062,6 +1070,33 @@ function extractLeafSourceByAst({
|
|
|
1062
1070
|
};
|
|
1063
1071
|
}
|
|
1064
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
|
+
|
|
1065
1100
|
// src/exportFinalizedLeaves.ts
|
|
1066
1101
|
function isRegistry(value) {
|
|
1067
1102
|
return typeof value === "object" && value !== null && "all" in value && "byKey" in value;
|
|
@@ -1075,7 +1110,7 @@ function buildMeta(sourceExtraction) {
|
|
|
1075
1110
|
description: "Finalized RRRoutes leaves export with serialized schemas and flattened schema paths for downstream processing.",
|
|
1076
1111
|
sourceExtraction,
|
|
1077
1112
|
fieldCatalog: {
|
|
1078
|
-
leaf: ["key", "method", "path", "cfg"],
|
|
1113
|
+
leaf: ["key", "id?", "method", "path", "cfg"],
|
|
1079
1114
|
cfg: [
|
|
1080
1115
|
"description?",
|
|
1081
1116
|
"summary?",
|
|
@@ -1109,25 +1144,6 @@ function buildMeta(sourceExtraction) {
|
|
|
1109
1144
|
}
|
|
1110
1145
|
};
|
|
1111
1146
|
}
|
|
1112
|
-
var BAKED_PAYLOAD_MARKER = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
|
|
1113
|
-
function escapePayloadForInlineScript(payload) {
|
|
1114
|
-
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
|
|
1115
|
-
}
|
|
1116
|
-
function injectPayloadIntoViewerHtml(htmlTemplate, payload) {
|
|
1117
|
-
const payloadScript = `${BAKED_PAYLOAD_MARKER}
|
|
1118
|
-
<script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript(
|
|
1119
|
-
payload
|
|
1120
|
-
)};</script>`;
|
|
1121
|
-
if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER)) {
|
|
1122
|
-
return htmlTemplate.replace(BAKED_PAYLOAD_MARKER, payloadScript);
|
|
1123
|
-
}
|
|
1124
|
-
if (htmlTemplate.includes("</body>")) {
|
|
1125
|
-
return htmlTemplate.replace("</body>", `${payloadScript}
|
|
1126
|
-
</body>`);
|
|
1127
|
-
}
|
|
1128
|
-
return `${payloadScript}
|
|
1129
|
-
${htmlTemplate}`;
|
|
1130
|
-
}
|
|
1131
1147
|
async function resolveViewerTemplatePath(viewerTemplateFile) {
|
|
1132
1148
|
if (viewerTemplateFile) {
|
|
1133
1149
|
const resolved = path2.resolve(viewerTemplateFile);
|
|
@@ -1139,10 +1155,7 @@ async function resolveViewerTemplatePath(viewerTemplateFile) {
|
|
|
1139
1155
|
process.cwd(),
|
|
1140
1156
|
"node_modules/@emeryld/rrroutes-export/tools/finalized-leaves-viewer.html"
|
|
1141
1157
|
),
|
|
1142
|
-
path2.resolve(
|
|
1143
|
-
process.cwd(),
|
|
1144
|
-
"tools/finalized-leaves-viewer.html"
|
|
1145
|
-
),
|
|
1158
|
+
path2.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
|
|
1146
1159
|
path2.resolve(
|
|
1147
1160
|
process.cwd(),
|
|
1148
1161
|
"packages/export/tools/finalized-leaves-viewer.html"
|
|
@@ -1167,7 +1180,11 @@ async function writeJsonExport(payload, outFile) {
|
|
|
1167
1180
|
async function writeBakedHtmlExport(payload, htmlFile, viewerTemplateFile) {
|
|
1168
1181
|
const templatePath = await resolveViewerTemplatePath(viewerTemplateFile);
|
|
1169
1182
|
const template = templatePath ? await fs.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
|
|
1170
|
-
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
|
+
});
|
|
1171
1188
|
const resolved = path2.resolve(htmlFile);
|
|
1172
1189
|
await fs.mkdir(path2.dirname(resolved), { recursive: true });
|
|
1173
1190
|
await fs.writeFile(resolved, baked, "utf8");
|
|
@@ -1348,6 +1365,869 @@ import { execFile as execFileCallback, spawn as spawn2 } from "child_process";
|
|
|
1348
1365
|
import { promisify } from "util";
|
|
1349
1366
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1350
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
|
|
1351
2231
|
var execFile = promisify(execFileCallback);
|
|
1352
2232
|
var CHANGESET_CFG_FIELDS = [
|
|
1353
2233
|
"summary",
|
|
@@ -1360,7 +2240,15 @@ var CHANGESET_CFG_FIELDS = [
|
|
|
1360
2240
|
"feed"
|
|
1361
2241
|
];
|
|
1362
2242
|
var SCHEMA_SECTIONS = ["params", "query", "body", "output"];
|
|
1363
|
-
var MODULE_EXTENSIONS = [
|
|
2243
|
+
var MODULE_EXTENSIONS = [
|
|
2244
|
+
".ts",
|
|
2245
|
+
".tsx",
|
|
2246
|
+
".mts",
|
|
2247
|
+
".cts",
|
|
2248
|
+
".js",
|
|
2249
|
+
".mjs",
|
|
2250
|
+
".cjs"
|
|
2251
|
+
];
|
|
1364
2252
|
var COMPILE_CACHE_VERSION = "v2";
|
|
1365
2253
|
var SOURCE_KEY_BY_SECTION = {
|
|
1366
2254
|
params: "paramsSchema",
|
|
@@ -1371,7 +2259,7 @@ var SOURCE_KEY_BY_SECTION = {
|
|
|
1371
2259
|
function normalizePath(p) {
|
|
1372
2260
|
return p.replace(/\\/g, "/");
|
|
1373
2261
|
}
|
|
1374
|
-
function
|
|
2262
|
+
function stableStringify2(value) {
|
|
1375
2263
|
return JSON.stringify(value, (_key, v) => {
|
|
1376
2264
|
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
1377
2265
|
const sorted = Object.entries(v).sort(([a], [b]) => a.localeCompare(b)).reduce((acc, [k, inner]) => {
|
|
@@ -1400,7 +2288,7 @@ function toCfgSubset(cfg) {
|
|
|
1400
2288
|
return out;
|
|
1401
2289
|
}
|
|
1402
2290
|
function entrySignature(entry) {
|
|
1403
|
-
return
|
|
2291
|
+
return stableStringify2({
|
|
1404
2292
|
type: entry.type,
|
|
1405
2293
|
nullable: Boolean(entry.nullable),
|
|
1406
2294
|
optional: Boolean(entry.optional),
|
|
@@ -1408,7 +2296,9 @@ function entrySignature(entry) {
|
|
|
1408
2296
|
});
|
|
1409
2297
|
}
|
|
1410
2298
|
function semverToTuple(tag) {
|
|
1411
|
-
const match = tag.match(
|
|
2299
|
+
const match = tag.match(
|
|
2300
|
+
/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
|
|
2301
|
+
);
|
|
1412
2302
|
if (!match) return null;
|
|
1413
2303
|
return {
|
|
1414
2304
|
major: Number(match[1]),
|
|
@@ -1454,7 +2344,9 @@ async function findDefaultFromCommit(cwd, toCommit) {
|
|
|
1454
2344
|
const root = await runGit(cwd, ["rev-list", "--max-parents=0", toCommit]);
|
|
1455
2345
|
const rootCommit = root.split("\n").map((item) => item.trim()).filter(Boolean)[0];
|
|
1456
2346
|
if (!rootCommit) {
|
|
1457
|
-
throw new Error(
|
|
2347
|
+
throw new Error(
|
|
2348
|
+
"Failed to resolve a baseline commit for changelog generation."
|
|
2349
|
+
);
|
|
1458
2350
|
}
|
|
1459
2351
|
return rootCommit;
|
|
1460
2352
|
}
|
|
@@ -1573,11 +2465,15 @@ async function commitTouchedRelevantPaths(cwd, commit, moduleCandidates, tsconfi
|
|
|
1573
2465
|
]);
|
|
1574
2466
|
const changedFiles = changed.split("\n").map((item) => normalizePath(item.trim()).replace(/^\/+/, "")).filter(Boolean);
|
|
1575
2467
|
const moduleFiles = Array.from(
|
|
1576
|
-
new Set(
|
|
2468
|
+
new Set(
|
|
2469
|
+
moduleCandidates.map((item) => normalizePath(item).replace(/^\/+/, ""))
|
|
2470
|
+
)
|
|
1577
2471
|
);
|
|
1578
2472
|
const moduleDirs = Array.from(
|
|
1579
2473
|
new Set(
|
|
1580
|
-
moduleFiles.map(
|
|
2474
|
+
moduleFiles.map(
|
|
2475
|
+
(item) => normalizePath(path4.posix.dirname(item)).replace(/^\/+/, "")
|
|
2476
|
+
).filter((item) => item && item !== ".")
|
|
1581
2477
|
)
|
|
1582
2478
|
);
|
|
1583
2479
|
const tsconfigFile = tsconfigRel ? normalizePath(tsconfigRel).replace(/^\/+/, "") : void 0;
|
|
@@ -1600,7 +2496,12 @@ async function filterCommits(cwd, commits, moduleCandidates, tsconfigRel) {
|
|
|
1600
2496
|
const keep = [commits[0]];
|
|
1601
2497
|
for (let i = 1; i < commits.length; i += 1) {
|
|
1602
2498
|
const commit = commits[i];
|
|
1603
|
-
if (await commitTouchedRelevantPaths(
|
|
2499
|
+
if (await commitTouchedRelevantPaths(
|
|
2500
|
+
cwd,
|
|
2501
|
+
commit,
|
|
2502
|
+
moduleCandidates,
|
|
2503
|
+
tsconfigRel
|
|
2504
|
+
)) {
|
|
1604
2505
|
keep.push(commit);
|
|
1605
2506
|
}
|
|
1606
2507
|
}
|
|
@@ -1611,7 +2512,12 @@ async function filterCommits(cwd, commits, moduleCandidates, tsconfigRel) {
|
|
|
1611
2512
|
return keep;
|
|
1612
2513
|
}
|
|
1613
2514
|
async function getCommitMetadata(cwd, commit) {
|
|
1614
|
-
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
|
+
]);
|
|
1615
2521
|
const [sha, authorDate, authorName, subject] = out.split("");
|
|
1616
2522
|
if (!sha || !authorDate || !authorName) {
|
|
1617
2523
|
throw new Error(`Unable to read commit metadata for ${commit}`);
|
|
@@ -1754,7 +2660,7 @@ function diffRouteCfg(before, after) {
|
|
|
1754
2660
|
for (const field of CHANGESET_CFG_FIELDS) {
|
|
1755
2661
|
const prev = before[field];
|
|
1756
2662
|
const next = after[field];
|
|
1757
|
-
if (
|
|
2663
|
+
if (stableStringify2(prev) !== stableStringify2(next)) {
|
|
1758
2664
|
out.push({
|
|
1759
2665
|
field,
|
|
1760
2666
|
before: prev,
|
|
@@ -1816,7 +2722,7 @@ function diffSourceSchemas(before, after) {
|
|
|
1816
2722
|
continue;
|
|
1817
2723
|
}
|
|
1818
2724
|
if (!prevSignatures || !nextSignatures) continue;
|
|
1819
|
-
if (
|
|
2725
|
+
if (stableStringify2(prevSignatures) !== stableStringify2(nextSignatures)) {
|
|
1820
2726
|
out.push({
|
|
1821
2727
|
section,
|
|
1822
2728
|
path: schemaPath,
|
|
@@ -2030,13 +2936,16 @@ async function compileSnapshotEntry(options) {
|
|
|
2030
2936
|
log(
|
|
2031
2937
|
`[changelog] compile cache ${options.noCache ? "disabled" : "miss"}: ${options.commitSha.slice(0, 12)}`
|
|
2032
2938
|
);
|
|
2033
|
-
await fs2.rm(path4.join(options.cacheRoot, cacheKey), {
|
|
2034
|
-
|
|
2035
|
-
|
|
2939
|
+
await fs2.rm(path4.join(options.cacheRoot, cacheKey), {
|
|
2940
|
+
recursive: true,
|
|
2941
|
+
force: true
|
|
2942
|
+
}).catch(() => void 0);
|
|
2036
2943
|
await fs2.mkdir(emitRoot, { recursive: true });
|
|
2037
2944
|
const readResult = ts2.readConfigFile(options.tsconfigPath, ts2.sys.readFile);
|
|
2038
2945
|
if (readResult.error) {
|
|
2039
|
-
throw new Error(
|
|
2946
|
+
throw new Error(
|
|
2947
|
+
ts2.flattenDiagnosticMessageText(readResult.error.messageText, "\n")
|
|
2948
|
+
);
|
|
2040
2949
|
}
|
|
2041
2950
|
const parsed = ts2.parseJsonConfigFileContent(
|
|
2042
2951
|
readResult.config,
|
|
@@ -2065,7 +2974,10 @@ async function compileSnapshotEntry(options) {
|
|
|
2065
2974
|
getCurrentDirectory: () => options.worktreeDir,
|
|
2066
2975
|
getNewLine: () => "\n"
|
|
2067
2976
|
};
|
|
2068
|
-
const message = ts2.formatDiagnosticsWithColorAndContext(
|
|
2977
|
+
const message = ts2.formatDiagnosticsWithColorAndContext(
|
|
2978
|
+
diagnostics.slice(0, 20),
|
|
2979
|
+
host
|
|
2980
|
+
);
|
|
2069
2981
|
throw new Error(
|
|
2070
2982
|
`TypeScript snapshot compile failed for ${options.moduleRel} at ${options.commitSha.slice(0, 12)}:
|
|
2071
2983
|
${message}`
|
|
@@ -2085,7 +2997,13 @@ async function loadSnapshotExport(options) {
|
|
|
2085
2997
|
}
|
|
2086
2998
|
async function createSnapshot(repoRoot, moduleCandidates, exportName, commit, tempRoot, tsconfigRel, exportOptions, log) {
|
|
2087
2999
|
const worktreeDir = path4.join(tempRoot, `wt-${commit.sha.slice(0, 12)}`);
|
|
2088
|
-
await runGit(repoRoot, [
|
|
3000
|
+
await runGit(repoRoot, [
|
|
3001
|
+
"worktree",
|
|
3002
|
+
"add",
|
|
3003
|
+
"--detach",
|
|
3004
|
+
worktreeDir,
|
|
3005
|
+
commit.sha
|
|
3006
|
+
]);
|
|
2089
3007
|
try {
|
|
2090
3008
|
try {
|
|
2091
3009
|
await ensureWorktreeDependencies(repoRoot, worktreeDir);
|
|
@@ -2170,13 +3088,94 @@ async function createSnapshot(repoRoot, moduleCandidates, exportName, commit, te
|
|
|
2170
3088
|
};
|
|
2171
3089
|
}
|
|
2172
3090
|
} finally {
|
|
2173
|
-
await runGit(repoRoot, [
|
|
3091
|
+
await runGit(repoRoot, [
|
|
3092
|
+
"worktree",
|
|
3093
|
+
"remove",
|
|
3094
|
+
"--force",
|
|
3095
|
+
worktreeDir
|
|
3096
|
+
]).catch(() => void 0);
|
|
2174
3097
|
await fs2.rm(worktreeDir, { recursive: true, force: true }).catch(() => void 0);
|
|
2175
3098
|
}
|
|
2176
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
|
+
}
|
|
2177
3156
|
function diffSnapshots(previous, current) {
|
|
2178
3157
|
const routeEvents = [];
|
|
2179
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
|
+
}
|
|
2180
3179
|
const routeKeys = /* @__PURE__ */ new Set([
|
|
2181
3180
|
...Object.keys(previous.routesByKey),
|
|
2182
3181
|
...Object.keys(current.routesByKey)
|
|
@@ -2184,7 +3183,8 @@ function diffSnapshots(previous, current) {
|
|
|
2184
3183
|
for (const routeKey of Array.from(routeKeys).sort()) {
|
|
2185
3184
|
const prevRoute = previous.routesByKey[routeKey];
|
|
2186
3185
|
const nextRoute = current.routesByKey[routeKey];
|
|
2187
|
-
|
|
3186
|
+
const canonicalTypes = canonicalRouteTypes.get(routeKey) ?? /* @__PURE__ */ new Set();
|
|
3187
|
+
if (!prevRoute && nextRoute && canonicalTypes.has("route_added")) {
|
|
2188
3188
|
const schemaDiff2 = diffRouteSchemas({}, nextRoute.flatSchema);
|
|
2189
3189
|
routeEvents.push({
|
|
2190
3190
|
type: "route_added",
|
|
@@ -2196,7 +3196,7 @@ function diffSnapshots(previous, current) {
|
|
|
2196
3196
|
});
|
|
2197
3197
|
continue;
|
|
2198
3198
|
}
|
|
2199
|
-
if (prevRoute && !nextRoute) {
|
|
3199
|
+
if (prevRoute && !nextRoute && canonicalTypes.has("route_removed")) {
|
|
2200
3200
|
routeEvents.push({
|
|
2201
3201
|
type: "route_removed",
|
|
2202
3202
|
commitSha: current.commit.sha,
|
|
@@ -2207,8 +3207,11 @@ function diffSnapshots(previous, current) {
|
|
|
2207
3207
|
continue;
|
|
2208
3208
|
}
|
|
2209
3209
|
if (!prevRoute || !nextRoute) continue;
|
|
2210
|
-
const schemaDiff = diffRouteSchemas(
|
|
2211
|
-
|
|
3210
|
+
const schemaDiff = diffRouteSchemas(
|
|
3211
|
+
prevRoute.flatSchema,
|
|
3212
|
+
nextRoute.flatSchema
|
|
3213
|
+
);
|
|
3214
|
+
if (schemaDiff.length > 0 && canonicalTypes.has("schema_changed")) {
|
|
2212
3215
|
routeEvents.push({
|
|
2213
3216
|
type: "schema_changed",
|
|
2214
3217
|
commitSha: current.commit.sha,
|
|
@@ -2219,7 +3222,9 @@ function diffSnapshots(previous, current) {
|
|
|
2219
3222
|
});
|
|
2220
3223
|
}
|
|
2221
3224
|
const cfgDiff = diffRouteCfg(prevRoute.cfg, nextRoute.cfg);
|
|
2222
|
-
if (cfgDiff.length > 0
|
|
3225
|
+
if (cfgDiff.length > 0 && ["cfg_changed", "description_changed", "files_changed"].some(
|
|
3226
|
+
(type) => canonicalTypes.has(type)
|
|
3227
|
+
)) {
|
|
2223
3228
|
routeEvents.push({
|
|
2224
3229
|
type: "cfg_changed",
|
|
2225
3230
|
commitSha: current.commit.sha,
|
|
@@ -2266,8 +3271,12 @@ function diffSnapshots(previous, current) {
|
|
|
2266
3271
|
const schemaDiff = diffSourceSchemas(prevSource, nextSource);
|
|
2267
3272
|
const prevRoutes = new Set(prevSource.routeKeys);
|
|
2268
3273
|
const nextRoutes = new Set(nextSource.routeKeys);
|
|
2269
|
-
const routeAdded = Array.from(nextRoutes).filter(
|
|
2270
|
-
|
|
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
|
+
);
|
|
2271
3280
|
if (schemaDiff.length > 0 || routeAdded.length > 0 || routeRemoved.length > 0) {
|
|
2272
3281
|
sourceEvents.push({
|
|
2273
3282
|
type: "source_schema_changed",
|
|
@@ -2283,28 +3292,10 @@ function diffSnapshots(previous, current) {
|
|
|
2283
3292
|
}
|
|
2284
3293
|
return {
|
|
2285
3294
|
routeEvents,
|
|
2286
|
-
sourceEvents
|
|
3295
|
+
sourceEvents,
|
|
3296
|
+
changeSet
|
|
2287
3297
|
};
|
|
2288
3298
|
}
|
|
2289
|
-
var BAKED_PAYLOAD_MARKER2 = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
|
|
2290
|
-
function escapePayloadForInlineScript2(payload) {
|
|
2291
|
-
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
|
|
2292
|
-
}
|
|
2293
|
-
function injectPayloadIntoViewerHtml2(htmlTemplate, payload) {
|
|
2294
|
-
const payloadScript = `${BAKED_PAYLOAD_MARKER2}
|
|
2295
|
-
<script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript2(
|
|
2296
|
-
payload
|
|
2297
|
-
)};</script>`;
|
|
2298
|
-
if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER2)) {
|
|
2299
|
-
return htmlTemplate.replace(BAKED_PAYLOAD_MARKER2, payloadScript);
|
|
2300
|
-
}
|
|
2301
|
-
if (htmlTemplate.includes("</body>")) {
|
|
2302
|
-
return htmlTemplate.replace("</body>", `${payloadScript}
|
|
2303
|
-
</body>`);
|
|
2304
|
-
}
|
|
2305
|
-
return `${payloadScript}
|
|
2306
|
-
${htmlTemplate}`;
|
|
2307
|
-
}
|
|
2308
3299
|
async function resolveViewerTemplatePath2(viewerTemplateFile) {
|
|
2309
3300
|
if (viewerTemplateFile) {
|
|
2310
3301
|
const resolved = path4.resolve(viewerTemplateFile);
|
|
@@ -2317,7 +3308,10 @@ async function resolveViewerTemplatePath2(viewerTemplateFile) {
|
|
|
2317
3308
|
"node_modules/@emeryld/rrroutes-export/tools/finalized-leaves-viewer.html"
|
|
2318
3309
|
),
|
|
2319
3310
|
path4.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
|
|
2320
|
-
path4.resolve(
|
|
3311
|
+
path4.resolve(
|
|
3312
|
+
process.cwd(),
|
|
3313
|
+
"packages/export/tools/finalized-leaves-viewer.html"
|
|
3314
|
+
)
|
|
2321
3315
|
];
|
|
2322
3316
|
for (const candidate of candidates) {
|
|
2323
3317
|
try {
|
|
@@ -2338,7 +3332,11 @@ async function writeJsonExport2(payload, outFile) {
|
|
|
2338
3332
|
async function writeBakedHtmlExport2(payload, htmlFile, viewerTemplateFile) {
|
|
2339
3333
|
const templatePath = await resolveViewerTemplatePath2(viewerTemplateFile);
|
|
2340
3334
|
const template = templatePath ? await fs2.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
|
|
2341
|
-
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
|
+
});
|
|
2342
3340
|
const resolved = path4.resolve(htmlFile);
|
|
2343
3341
|
await fs2.mkdir(path4.dirname(resolved), { recursive: true });
|
|
2344
3342
|
await fs2.writeFile(resolved, baked, "utf8");
|
|
@@ -2389,7 +3387,9 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2389
3387
|
log(`[changelog] resolving repository root from ${cwd}`);
|
|
2390
3388
|
const repoRoot = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
2391
3389
|
const modulePathAbsolute = path4.resolve(cwd, options.modulePath);
|
|
2392
|
-
const modulePathRelative = normalizePath(
|
|
3390
|
+
const modulePathRelative = normalizePath(
|
|
3391
|
+
path4.relative(repoRoot, modulePathAbsolute)
|
|
3392
|
+
);
|
|
2393
3393
|
if (modulePathRelative.startsWith("..")) {
|
|
2394
3394
|
throw new Error("modulePath must resolve inside the git repository root.");
|
|
2395
3395
|
}
|
|
@@ -2401,8 +3401,10 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2401
3401
|
const exportName = options.exportName ?? "leaves";
|
|
2402
3402
|
log("[changelog] resolving commit window");
|
|
2403
3403
|
const window = await resolveCommitWindow(repoRoot, options.from, options.to);
|
|
2404
|
-
log(
|
|
2405
|
-
|
|
3404
|
+
log(
|
|
3405
|
+
`[changelog] window: ${window.from.slice(0, 12)}..${window.to.slice(0, 12)}`
|
|
3406
|
+
);
|
|
3407
|
+
const moduleCandidates = await buildModulePathCandidates(
|
|
2406
3408
|
repoRoot,
|
|
2407
3409
|
window.to,
|
|
2408
3410
|
modulePathRelative
|
|
@@ -2426,7 +3428,9 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2426
3428
|
for (const commit of selectedCommitShas) {
|
|
2427
3429
|
commits.push(await getCommitMetadata(repoRoot, commit));
|
|
2428
3430
|
}
|
|
2429
|
-
const tempRoot = await fs2.mkdtemp(
|
|
3431
|
+
const tempRoot = await fs2.mkdtemp(
|
|
3432
|
+
path4.join(os.tmpdir(), "rrroutes-export-changelog-")
|
|
3433
|
+
);
|
|
2430
3434
|
log(`[changelog] temp workspace: ${tempRoot}`);
|
|
2431
3435
|
try {
|
|
2432
3436
|
const snapshots = [];
|
|
@@ -2463,6 +3467,7 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2463
3467
|
}
|
|
2464
3468
|
const routeEvents = [];
|
|
2465
3469
|
const sourceEvents = [];
|
|
3470
|
+
const changeSets = [];
|
|
2466
3471
|
log("[changelog] diffing successful snapshots");
|
|
2467
3472
|
let previousSnapshot;
|
|
2468
3473
|
for (const snapshot of snapshots) {
|
|
@@ -2473,12 +3478,14 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2473
3478
|
previousSnapshot = snapshot;
|
|
2474
3479
|
continue;
|
|
2475
3480
|
}
|
|
2476
|
-
const {
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
3481
|
+
const {
|
|
3482
|
+
routeEvents: nextRouteEvents,
|
|
3483
|
+
sourceEvents: nextSourceEvents,
|
|
3484
|
+
changeSet
|
|
3485
|
+
} = diffSnapshots(previousSnapshot, snapshot);
|
|
2480
3486
|
routeEvents.push(...nextRouteEvents);
|
|
2481
3487
|
sourceEvents.push(...nextSourceEvents);
|
|
3488
|
+
changeSets.push(changeSet);
|
|
2482
3489
|
previousSnapshot = snapshot;
|
|
2483
3490
|
}
|
|
2484
3491
|
log(
|
|
@@ -2500,7 +3507,8 @@ async function exportFinalizedLeavesChangelog(options) {
|
|
|
2500
3507
|
commits,
|
|
2501
3508
|
byRoute: routeRecordByKey(routeEvents),
|
|
2502
3509
|
bySourceObject: sourceRecordById(sourceEvents),
|
|
2503
|
-
rollups: buildRollups(routeEvents, sourceEvents, commits)
|
|
3510
|
+
rollups: buildRollups(routeEvents, sourceEvents, commits),
|
|
3511
|
+
changeSets
|
|
2504
3512
|
};
|
|
2505
3513
|
if (options.outFile || options.htmlFile) {
|
|
2506
3514
|
log("[changelog] writing output artifacts");
|
|
@@ -2607,25 +3615,6 @@ async function runExportFinalizedLeavesChangelogCli(argv) {
|
|
|
2607
3615
|
import fs3 from "fs/promises";
|
|
2608
3616
|
import path6 from "path";
|
|
2609
3617
|
import { spawn as spawn3 } from "child_process";
|
|
2610
|
-
var BAKED_PAYLOAD_MARKER3 = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
|
|
2611
|
-
function escapePayloadForInlineScript3(payload) {
|
|
2612
|
-
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
|
|
2613
|
-
}
|
|
2614
|
-
function injectPayloadIntoViewerHtml3(htmlTemplate, payload) {
|
|
2615
|
-
const payloadScript = `${BAKED_PAYLOAD_MARKER3}
|
|
2616
|
-
<script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript3(
|
|
2617
|
-
payload
|
|
2618
|
-
)};</script>`;
|
|
2619
|
-
if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER3)) {
|
|
2620
|
-
return htmlTemplate.replace(BAKED_PAYLOAD_MARKER3, payloadScript);
|
|
2621
|
-
}
|
|
2622
|
-
if (htmlTemplate.includes("</body>")) {
|
|
2623
|
-
return htmlTemplate.replace("</body>", `${payloadScript}
|
|
2624
|
-
</body>`);
|
|
2625
|
-
}
|
|
2626
|
-
return `${payloadScript}
|
|
2627
|
-
${htmlTemplate}`;
|
|
2628
|
-
}
|
|
2629
3618
|
async function resolveViewerTemplatePath3(viewerTemplateFile) {
|
|
2630
3619
|
if (viewerTemplateFile) {
|
|
2631
3620
|
const resolved = path6.resolve(viewerTemplateFile);
|
|
@@ -2638,7 +3627,10 @@ async function resolveViewerTemplatePath3(viewerTemplateFile) {
|
|
|
2638
3627
|
"node_modules/@emeryld/rrroutes-export/tools/finalized-leaves-viewer.html"
|
|
2639
3628
|
),
|
|
2640
3629
|
path6.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
|
|
2641
|
-
path6.resolve(
|
|
3630
|
+
path6.resolve(
|
|
3631
|
+
process.cwd(),
|
|
3632
|
+
"packages/export/tools/finalized-leaves-viewer.html"
|
|
3633
|
+
)
|
|
2642
3634
|
];
|
|
2643
3635
|
for (const candidate of candidates) {
|
|
2644
3636
|
try {
|
|
@@ -2659,7 +3651,11 @@ async function writeJsonExport3(payload, outFile) {
|
|
|
2659
3651
|
async function writeBakedHtmlExport3(payload, htmlFile, viewerTemplateFile) {
|
|
2660
3652
|
const templatePath = await resolveViewerTemplatePath3(viewerTemplateFile);
|
|
2661
3653
|
const template = templatePath ? await fs3.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
|
|
2662
|
-
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
|
+
});
|
|
2663
3659
|
const resolved = path6.resolve(htmlFile);
|
|
2664
3660
|
await fs3.mkdir(path6.dirname(resolved), { recursive: true });
|
|
2665
3661
|
await fs3.writeFile(resolved, baked, "utf8");
|
|
@@ -2715,29 +3711,1982 @@ async function writeFinalizedLeavesViewerBundle(payload, outFileOrOptions) {
|
|
|
2715
3711
|
}
|
|
2716
3712
|
return written;
|
|
2717
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
|
+
}
|
|
2718
5637
|
export {
|
|
2719
5638
|
DEFAULT_VIEWER_TEMPLATE,
|
|
5639
|
+
RRROUTES_BAKED_PAYLOAD_MARKER,
|
|
2720
5640
|
__private,
|
|
5641
|
+
applyChangeSetRetention,
|
|
5642
|
+
applyCheckpointRetention,
|
|
2721
5643
|
buildFinalizedLeavesViewerBundle,
|
|
5644
|
+
buildRRRoutesSnapshot,
|
|
5645
|
+
canonicalSnapshotStructure,
|
|
5646
|
+
canonicalizeJson,
|
|
2722
5647
|
clearSchemaIntrospectionHandlers,
|
|
5648
|
+
createBakedInspectorPayload,
|
|
5649
|
+
createCheckpointService,
|
|
5650
|
+
createExpressRouter,
|
|
5651
|
+
createFileSystemInspectionStorage,
|
|
5652
|
+
createInspectorHttpHandler,
|
|
5653
|
+
createMemoryInspectionStorage,
|
|
5654
|
+
createRRRoutesExportRuntime,
|
|
5655
|
+
createSQLiteInspectionStorage,
|
|
2723
5656
|
createSchemaIntrospector,
|
|
5657
|
+
createSnapshotChangeSet,
|
|
5658
|
+
diffRRRoutesSnapshots,
|
|
5659
|
+
escapeBakedPayload,
|
|
2724
5660
|
exportFinalizedLeaves,
|
|
2725
5661
|
exportFinalizedLeavesChangelog,
|
|
2726
5662
|
extractLeafSourceByAst,
|
|
2727
5663
|
flattenLeafSchemas,
|
|
2728
5664
|
flattenSerializableSchema,
|
|
5665
|
+
hashCanonicalValue,
|
|
5666
|
+
hashRRRoutesSnapshot,
|
|
5667
|
+
injectBakedInspectorPayload,
|
|
2729
5668
|
introspectSchema,
|
|
5669
|
+
loadExportRuntime,
|
|
2730
5670
|
loadFinalizedLeavesInput,
|
|
5671
|
+
parseCliFlags,
|
|
2731
5672
|
parseFinalizedLeavesChangelogCliArgs,
|
|
2732
5673
|
parseFinalizedLeavesCliArgs,
|
|
2733
5674
|
registerSchemaIntrospectionHandler,
|
|
5675
|
+
requiredFlag,
|
|
5676
|
+
runChangesCli,
|
|
5677
|
+
runCheckpointCli,
|
|
2734
5678
|
runExportFinalizedLeavesChangelogCli,
|
|
2735
5679
|
runExportFinalizedLeavesCli,
|
|
5680
|
+
runInspectorServeCli,
|
|
5681
|
+
runSnapshotCli,
|
|
2736
5682
|
serializableSchemaKinds,
|
|
2737
5683
|
serializeLeafContract,
|
|
2738
5684
|
serializeLeavesContract,
|
|
5685
|
+
stableStringify,
|
|
2739
5686
|
writeFinalizedLeavesChangelogExport,
|
|
2740
5687
|
writeFinalizedLeavesExport,
|
|
2741
|
-
writeFinalizedLeavesViewerBundle
|
|
5688
|
+
writeFinalizedLeavesViewerBundle,
|
|
5689
|
+
writeRRRoutesChangeSet,
|
|
5690
|
+
writeRRRoutesSnapshot
|
|
2742
5691
|
};
|
|
2743
5692
|
//# sourceMappingURL=index.mjs.map
|