@emeryld/rrroutes-export 1.1.0 → 1.1.2

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