@emeryld/rrroutes-contract 2.7.5 → 2.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  createSchemaIntrospector: () => createSchemaIntrospector,
40
40
  defineSocketEvents: () => defineSocketEvents,
41
41
  exportFinalizedLeaves: () => exportFinalizedLeaves,
42
+ extractLeafSourceByAst: () => extractLeafSourceByAst,
42
43
  finalize: () => finalize,
43
44
  flattenLeafSchemas: () => flattenLeafSchemas,
44
45
  flattenSerializableSchema: () => flattenSerializableSchema,
@@ -107,9 +108,9 @@ function collectNestedFieldSuggestions(shape, prefix = []) {
107
108
  }
108
109
  return suggestions;
109
110
  }
110
- function compilePath(path3, params) {
111
- if (!params) return path3;
112
- const withParams = path3.replace(/:([A-Za-z0-9_]+)/g, (_, k) => {
111
+ function compilePath(path4, params) {
112
+ if (!params) return path4;
113
+ const withParams = path4.replace(/:([A-Za-z0-9_]+)/g, (_, k) => {
113
114
  const v = params[k];
114
115
  if (v === void 0 || v === null) throw new Error(`Missing param :${k}`);
115
116
  return String(v);
@@ -184,8 +185,8 @@ function buildLowProfileLeaf(leaf) {
184
185
  }
185
186
  };
186
187
  }
187
- var keyOf = (method, path3, encodeSafe) => {
188
- const key = `${method.toUpperCase()} ${path3}`;
188
+ var keyOf = (method, path4, encodeSafe) => {
189
+ const key = `${method.toUpperCase()} ${path4}`;
189
190
  return encodeSafe ? encodeURIComponent(key) : key;
190
191
  };
191
192
 
@@ -361,8 +362,8 @@ function joinPaths(parent, child) {
361
362
  if (!trimmedChild) return trimmedParent;
362
363
  return `${trimmedParent}/${trimmedChild}`;
363
364
  }
364
- function assertDynamicLayerUniqueness(path3, dynamicLayerMap) {
365
- const segments = path3.split("/").filter(Boolean);
365
+ function assertDynamicLayerUniqueness(path4, dynamicLayerMap) {
366
+ const segments = path4.split("/").filter(Boolean);
366
367
  if (segments.length === 0) return;
367
368
  for (let i = 0; i < segments.length; i++) {
368
369
  const segment = segments[i];
@@ -682,45 +683,45 @@ function normalizeType(schema) {
682
683
  return "unknown";
683
684
  }
684
685
  }
685
- function isNonEmptyPath(path3) {
686
- return typeof path3 === "string" && path3.length > 0;
686
+ function isNonEmptyPath(path4) {
687
+ return typeof path4 === "string" && path4.length > 0;
687
688
  }
688
- function setNode(out, path3, schema, inherited) {
689
- if (!isNonEmptyPath(path3)) return;
690
- out[path3] = {
689
+ function setNode(out, path4, schema, inherited) {
690
+ if (!isNonEmptyPath(path4)) return;
691
+ out[path4] = {
691
692
  type: normalizeType(schema),
692
693
  nullable: inherited.nullable || Boolean(schema.nullable),
693
694
  optional: inherited.optional || Boolean(schema.optional)
694
695
  };
695
696
  }
696
- function flattenInto(out, schema, path3, inherited) {
697
- if (!schema || !isNonEmptyPath(path3)) return;
697
+ function flattenInto(out, schema, path4, inherited) {
698
+ if (!schema || !isNonEmptyPath(path4)) return;
698
699
  const nextInherited = {
699
700
  optional: inherited.optional || Boolean(schema.optional),
700
701
  nullable: inherited.nullable || Boolean(schema.nullable)
701
702
  };
702
703
  if (schema.kind === "union" && Array.isArray(schema.union) && schema.union.length > 0) {
703
704
  schema.union.forEach((option, index) => {
704
- const optionPath = `${path3}-${index + 1}`;
705
+ const optionPath = `${path4}-${index + 1}`;
705
706
  flattenInto(out, option, optionPath, inherited);
706
707
  });
707
708
  return;
708
709
  }
709
- setNode(out, path3, schema, inherited);
710
+ setNode(out, path4, schema, inherited);
710
711
  if (schema.kind === "object" && schema.properties) {
711
712
  for (const [key, child] of Object.entries(schema.properties)) {
712
- const childPath = `${path3}.${key}`;
713
+ const childPath = `${path4}.${key}`;
713
714
  flattenInto(out, child, childPath, nextInherited);
714
715
  }
715
716
  return;
716
717
  }
717
718
  if (schema.kind === "array" && schema.element) {
718
- flattenInto(out, schema.element, `${path3}[]`, nextInherited);
719
+ flattenInto(out, schema.element, `${path4}[]`, nextInherited);
719
720
  }
720
721
  }
721
- function flattenSerializableSchema(schema, path3) {
722
+ function flattenSerializableSchema(schema, path4) {
722
723
  const out = {};
723
- flattenInto(out, schema, path3, { optional: false, nullable: false });
724
+ flattenInto(out, schema, path4, { optional: false, nullable: false });
724
725
  return out;
725
726
  }
726
727
  function isSerializedLeaf(value) {
@@ -741,7 +742,7 @@ function flattenLeafSchemas(leaf) {
741
742
 
742
743
  // src/export/exportFinalizedLeaves.ts
743
744
  var import_promises = __toESM(require("fs/promises"), 1);
744
- var import_node_path = __toESM(require("path"), 1);
745
+ var import_node_path2 = __toESM(require("path"), 1);
745
746
  var import_node_child_process = require("child_process");
746
747
 
747
748
  // src/export/defaultViewerTemplate.ts
@@ -796,6 +797,13 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
796
797
  } else {
797
798
  statusEl.textContent = 'Loaded baked payload with ' + payload.leaves.length + ' routes.'
798
799
 
800
+ const toHref = (source) => {
801
+ if (!source || !source.file) return null
802
+ const normalizedPath = String(source.file).replace(/\\\\/g, '/')
803
+ const prefix = normalizedPath.startsWith('/') ? 'file://' : 'file:///'
804
+ return prefix + encodeURI(normalizedPath)
805
+ }
806
+
799
807
  payload.leaves.forEach((leaf) => {
800
808
  const details = document.createElement('details')
801
809
  const summary = document.createElement('summary')
@@ -804,7 +812,63 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
804
812
  const pre = document.createElement('pre')
805
813
  pre.textContent = JSON.stringify(leaf, null, 2)
806
814
 
815
+ const source = payload.sourceByLeaf && payload.sourceByLeaf[leaf.key]
816
+ let sourceWrap = null
817
+ if (source) {
818
+ sourceWrap = document.createElement('div')
819
+ sourceWrap.className = 'meta'
820
+
821
+ const definitionHref = toHref(source.definition)
822
+ if (definitionHref) {
823
+ const label = document.createElement('div')
824
+ const link = document.createElement('a')
825
+ link.href = definitionHref
826
+ link.target = '_blank'
827
+ link.rel = 'noopener noreferrer'
828
+ link.textContent =
829
+ 'definition: ' +
830
+ source.definition.file +
831
+ ':' +
832
+ source.definition.line +
833
+ ':' +
834
+ source.definition.column
835
+ label.appendChild(link)
836
+ sourceWrap.appendChild(label)
837
+ }
838
+
839
+ if (source.schemas && typeof source.schemas === 'object') {
840
+ Object.entries(source.schemas).forEach(([name, schema]) => {
841
+ if (!schema) return
842
+ const href = toHref(schema)
843
+ const row = document.createElement('div')
844
+ if (href) {
845
+ const link = document.createElement('a')
846
+ link.href = href
847
+ link.target = '_blank'
848
+ link.rel = 'noopener noreferrer'
849
+ link.textContent =
850
+ name +
851
+ ': ' +
852
+ (schema.sourceName || schema.tag || '<anonymous>') +
853
+ ' (' +
854
+ schema.file +
855
+ ':' +
856
+ schema.line +
857
+ ':' +
858
+ schema.column +
859
+ ')'
860
+ row.appendChild(link)
861
+ } else {
862
+ row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
863
+ }
864
+ sourceWrap.appendChild(row)
865
+ })
866
+ }
867
+
868
+ }
869
+
807
870
  details.appendChild(summary)
871
+ if (sourceWrap) details.appendChild(sourceWrap)
808
872
  details.appendChild(pre)
809
873
  resultsEl.appendChild(details)
810
874
  })
@@ -814,6 +878,393 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
814
878
  </html>
815
879
  `;
816
880
 
881
+ // src/export/extractLeafSourceByAst.ts
882
+ var import_node_path = __toESM(require("path"), 1);
883
+ var import_typescript = __toESM(require("typescript"), 1);
884
+ var SCHEMA_KEYS = [
885
+ "bodySchema",
886
+ "querySchema",
887
+ "paramsSchema",
888
+ "outputSchema",
889
+ "outputMetaSchema",
890
+ "queryExtensionSchema"
891
+ ];
892
+ var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
893
+ function toLocation(node) {
894
+ const sourceFile = node.getSourceFile();
895
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(
896
+ node.getStart(sourceFile)
897
+ );
898
+ return {
899
+ file: import_node_path.default.resolve(sourceFile.fileName),
900
+ line: line + 1,
901
+ column: character + 1
902
+ };
903
+ }
904
+ function trimPreview(text, max = 80) {
905
+ const normalized = text.replace(/\s+/g, " ").trim();
906
+ return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
907
+ }
908
+ function unwrapExpression(expression) {
909
+ let current = expression;
910
+ while (true) {
911
+ if (import_typescript.default.isParenthesizedExpression(current) || import_typescript.default.isAsExpression(current) || import_typescript.default.isTypeAssertionExpression(current) || import_typescript.default.isNonNullExpression(current) || import_typescript.default.isSatisfiesExpression(current)) {
912
+ current = current.expression;
913
+ continue;
914
+ }
915
+ break;
916
+ }
917
+ return current;
918
+ }
919
+ function getTextName(name) {
920
+ if (import_typescript.default.isIdentifier(name) || import_typescript.default.isStringLiteral(name) || import_typescript.default.isNumericLiteral(name)) {
921
+ return name.text;
922
+ }
923
+ return void 0;
924
+ }
925
+ function joinPaths2(parent, child) {
926
+ if (!parent) return child;
927
+ if (!child) return parent;
928
+ const trimmedParent = parent.endsWith("/") ? parent.replace(/\/+$/, "") : parent;
929
+ const trimmedChild = child.startsWith("/") ? child.replace(/^\/+/, "") : child;
930
+ if (!trimmedChild) return trimmedParent;
931
+ return `${trimmedParent}/${trimmedChild}`;
932
+ }
933
+ function buildLeafKey(method, leafPath) {
934
+ return `${method.toUpperCase()} ${leafPath}`;
935
+ }
936
+ function normalizeResourceBase(raw) {
937
+ if (raw === "") return "";
938
+ if (raw.startsWith("/") || raw.startsWith(":") || raw.startsWith("*")) {
939
+ return raw;
940
+ }
941
+ return `/${raw}`;
942
+ }
943
+ function getModuleSymbol(checker, sourceFile) {
944
+ return checker.getSymbolAtLocation(sourceFile);
945
+ }
946
+ function getAliasedSymbolIfNeeded(checker, symbol) {
947
+ if ((symbol.flags & import_typescript.default.SymbolFlags.Alias) !== 0) {
948
+ try {
949
+ return checker.getAliasedSymbol(symbol);
950
+ } catch {
951
+ return symbol;
952
+ }
953
+ }
954
+ return symbol;
955
+ }
956
+ function findSourceFile(program, filePath) {
957
+ const wanted = import_node_path.default.resolve(filePath);
958
+ const normalizedWanted = import_node_path.default.normalize(wanted);
959
+ return program.getSourceFiles().find((file) => import_node_path.default.normalize(import_node_path.default.resolve(file.fileName)) === normalizedWanted);
960
+ }
961
+ function expressionFromDeclaration(declaration) {
962
+ if (!declaration) return void 0;
963
+ if (import_typescript.default.isVariableDeclaration(declaration)) {
964
+ return declaration.initializer;
965
+ }
966
+ if (import_typescript.default.isExportAssignment(declaration)) {
967
+ return declaration.expression;
968
+ }
969
+ if (import_typescript.default.isPropertyAssignment(declaration)) {
970
+ return declaration.initializer;
971
+ }
972
+ if (import_typescript.default.isShorthandPropertyAssignment(declaration)) {
973
+ return declaration.name;
974
+ }
975
+ if (import_typescript.default.isBindingElement(declaration)) {
976
+ return declaration.initializer;
977
+ }
978
+ return void 0;
979
+ }
980
+ function resolveIdentifierExpression(identifier, ctx) {
981
+ const symbol = ctx.checker.getSymbolAtLocation(identifier);
982
+ if (!symbol) return void 0;
983
+ const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
984
+ const declaration = target.declarations?.[0];
985
+ return expressionFromDeclaration(declaration);
986
+ }
987
+ function resolveExportExpression(sourceFile, exportName, checker) {
988
+ const moduleSymbol = getModuleSymbol(checker, sourceFile);
989
+ if (!moduleSymbol) return void 0;
990
+ const exports2 = checker.getExportsOfModule(moduleSymbol);
991
+ const explicit = exports2.find((entry) => entry.getName() === exportName);
992
+ if (explicit) {
993
+ const declaration = getAliasedSymbolIfNeeded(checker, explicit).declarations?.[0];
994
+ return expressionFromDeclaration(declaration);
995
+ }
996
+ const defaultExport = exports2.find((entry) => entry.getName() === "default");
997
+ if (!defaultExport) return void 0;
998
+ const defaultDecl = getAliasedSymbolIfNeeded(checker, defaultExport).declarations?.[0];
999
+ const defaultExpr = expressionFromDeclaration(defaultDecl);
1000
+ if (!defaultExpr) return void 0;
1001
+ const resolved = unwrapExpression(defaultExpr);
1002
+ if (!import_typescript.default.isObjectLiteralExpression(resolved)) return void 0;
1003
+ for (const property of resolved.properties) {
1004
+ if (!import_typescript.default.isPropertyAssignment(property)) continue;
1005
+ const propertyName = getTextName(property.name);
1006
+ if (propertyName !== exportName) continue;
1007
+ return property.initializer;
1008
+ }
1009
+ return void 0;
1010
+ }
1011
+ function maybeObjectLiteral(expression, ctx) {
1012
+ if (!expression) return void 0;
1013
+ const resolved = unwrapExpression(expression);
1014
+ if (import_typescript.default.isObjectLiteralExpression(resolved)) return resolved;
1015
+ if (import_typescript.default.isIdentifier(resolved)) {
1016
+ const target = resolveIdentifierExpression(resolved, ctx);
1017
+ if (!target) return void 0;
1018
+ return maybeObjectLiteral(target, ctx);
1019
+ }
1020
+ return void 0;
1021
+ }
1022
+ function collectSchemaExpressionsFromObject(objectLiteral, ctx) {
1023
+ const schemas = {};
1024
+ for (const property of objectLiteral.properties) {
1025
+ if (import_typescript.default.isSpreadAssignment(property)) {
1026
+ const spreadObject = maybeObjectLiteral(property.expression, ctx);
1027
+ if (!spreadObject) continue;
1028
+ Object.assign(schemas, collectSchemaExpressionsFromObject(spreadObject, ctx));
1029
+ continue;
1030
+ }
1031
+ if (import_typescript.default.isPropertyAssignment(property)) {
1032
+ const key = getTextName(property.name);
1033
+ if (!key || !SCHEMA_KEYS.includes(key)) continue;
1034
+ schemas[key] = property.initializer;
1035
+ continue;
1036
+ }
1037
+ if (import_typescript.default.isShorthandPropertyAssignment(property)) {
1038
+ const key = property.name.text;
1039
+ if (!SCHEMA_KEYS.includes(key)) continue;
1040
+ schemas[key] = property.name;
1041
+ }
1042
+ }
1043
+ return schemas;
1044
+ }
1045
+ function extractSchemaExpressions(cfgExpression, ctx) {
1046
+ const objectLiteral = maybeObjectLiteral(cfgExpression, ctx);
1047
+ if (!objectLiteral) return {};
1048
+ return collectSchemaExpressionsFromObject(objectLiteral, ctx);
1049
+ }
1050
+ function getNearestVariableName(node) {
1051
+ let current = node;
1052
+ while (current) {
1053
+ if (import_typescript.default.isVariableDeclaration(current) && import_typescript.default.isIdentifier(current.name)) {
1054
+ return current.name.text;
1055
+ }
1056
+ current = current.parent;
1057
+ }
1058
+ return void 0;
1059
+ }
1060
+ function evaluateBranchExpression(expression, ctx) {
1061
+ const resolved = unwrapExpression(expression);
1062
+ if (import_typescript.default.isIdentifier(resolved)) {
1063
+ const valueExpr = resolveIdentifierExpression(resolved, ctx);
1064
+ if (!valueExpr) return void 0;
1065
+ return evaluateBranchExpression(valueExpr, ctx);
1066
+ }
1067
+ if (!import_typescript.default.isCallExpression(resolved)) return void 0;
1068
+ const call = resolved;
1069
+ if (import_typescript.default.isIdentifier(call.expression) && call.expression.text === "resource") {
1070
+ const firstArg = call.arguments[0];
1071
+ if (!firstArg || !import_typescript.default.isStringLiteralLike(firstArg)) return void 0;
1072
+ return { base: normalizeResourceBase(firstArg.text), leaves: [] };
1073
+ }
1074
+ if (!import_typescript.default.isPropertyAccessExpression(call.expression)) return void 0;
1075
+ const owner = call.expression.expression;
1076
+ const method = call.expression.name.text;
1077
+ const branch = evaluateBranchExpression(owner, ctx);
1078
+ if (!branch) return void 0;
1079
+ if (method === "with") {
1080
+ return branch;
1081
+ }
1082
+ if (HTTP_METHODS.has(method)) {
1083
+ const cfgExpression = call.arguments[0];
1084
+ const schemas = extractSchemaExpressions(cfgExpression, ctx);
1085
+ const nextLeaf = {
1086
+ method,
1087
+ path: branch.base,
1088
+ definitionNode: call.expression.name,
1089
+ schemas
1090
+ };
1091
+ return {
1092
+ ...branch,
1093
+ leaves: [...branch.leaves, nextLeaf]
1094
+ };
1095
+ }
1096
+ if (method === "sub") {
1097
+ const mountedLeaves = call.arguments.flatMap(
1098
+ (arg) => evaluateLeavesFromExpression(arg, ctx)
1099
+ );
1100
+ const prefixed = mountedLeaves.map((leaf) => ({
1101
+ ...leaf,
1102
+ path: joinPaths2(branch.base, leaf.path)
1103
+ }));
1104
+ return {
1105
+ ...branch,
1106
+ leaves: [...branch.leaves, ...prefixed]
1107
+ };
1108
+ }
1109
+ return void 0;
1110
+ }
1111
+ function evaluateLeavesFromExpression(expression, ctx) {
1112
+ const resolved = unwrapExpression(expression);
1113
+ const key = resolved.getStart(resolved.getSourceFile());
1114
+ if (ctx.visitedExpressionStarts.has(key)) {
1115
+ return [];
1116
+ }
1117
+ ctx.visitedExpressionStarts.add(key);
1118
+ try {
1119
+ if (import_typescript.default.isIdentifier(resolved)) {
1120
+ const valueExpr = resolveIdentifierExpression(resolved, ctx);
1121
+ if (!valueExpr) return [];
1122
+ return evaluateLeavesFromExpression(valueExpr, ctx);
1123
+ }
1124
+ if (import_typescript.default.isArrayLiteralExpression(resolved)) {
1125
+ const leaves = [];
1126
+ for (const element of resolved.elements) {
1127
+ if (import_typescript.default.isSpreadElement(element)) {
1128
+ leaves.push(...evaluateLeavesFromExpression(element.expression, ctx));
1129
+ continue;
1130
+ }
1131
+ leaves.push(...evaluateLeavesFromExpression(element, ctx));
1132
+ }
1133
+ return leaves;
1134
+ }
1135
+ if (import_typescript.default.isCallExpression(resolved)) {
1136
+ if (import_typescript.default.isIdentifier(resolved.expression)) {
1137
+ const callName = resolved.expression.text;
1138
+ if (callName === "finalize") {
1139
+ const arg = resolved.arguments[0];
1140
+ if (!arg) return [];
1141
+ return evaluateLeavesFromExpression(arg, ctx);
1142
+ }
1143
+ if (callName === "mergeArrays") {
1144
+ return resolved.arguments.flatMap(
1145
+ (arg) => evaluateLeavesFromExpression(arg, ctx)
1146
+ );
1147
+ }
1148
+ }
1149
+ if (import_typescript.default.isPropertyAccessExpression(resolved.expression)) {
1150
+ const prop = resolved.expression.name.text;
1151
+ if (prop === "done") {
1152
+ const branch2 = evaluateBranchExpression(resolved.expression.expression, ctx);
1153
+ return branch2?.leaves ?? [];
1154
+ }
1155
+ }
1156
+ }
1157
+ const branch = evaluateBranchExpression(resolved, ctx);
1158
+ return branch?.leaves ?? [];
1159
+ } finally {
1160
+ ctx.visitedExpressionStarts.delete(key);
1161
+ }
1162
+ }
1163
+ function resolveSchemaMetadata(expression, ctx) {
1164
+ const resolved = unwrapExpression(expression);
1165
+ if (import_typescript.default.isIdentifier(resolved)) {
1166
+ const symbol = ctx.checker.getSymbolAtLocation(resolved);
1167
+ const target = symbol ? getAliasedSymbolIfNeeded(ctx.checker, symbol) : void 0;
1168
+ const declaration = target?.declarations?.[0];
1169
+ const locationNode = declaration ? import_typescript.default.isVariableDeclaration(declaration) ? declaration.name : declaration : resolved;
1170
+ const sourceName = declaration && import_typescript.default.isVariableDeclaration(declaration) && import_typescript.default.isIdentifier(declaration.name) ? declaration.name.text : resolved.text;
1171
+ return {
1172
+ ...toLocation(locationNode),
1173
+ sourceName,
1174
+ tag: declaration ? void 0 : "<anonymous>"
1175
+ };
1176
+ }
1177
+ if (import_typescript.default.isPropertyAccessExpression(resolved) || import_typescript.default.isElementAccessExpression(resolved)) {
1178
+ return {
1179
+ ...toLocation(resolved),
1180
+ sourceName: trimPreview(resolved.getText()),
1181
+ tag: "<expression>"
1182
+ };
1183
+ }
1184
+ return {
1185
+ ...toLocation(resolved),
1186
+ sourceName: trimPreview(resolved.getText()),
1187
+ tag: "<inline>"
1188
+ };
1189
+ }
1190
+ function parseTsConfig(cwd, tsconfigPath) {
1191
+ const resolvedTsconfig = tsconfigPath ? import_node_path.default.resolve(cwd, tsconfigPath) : import_typescript.default.findConfigFile(cwd, import_typescript.default.sys.fileExists, "tsconfig.json");
1192
+ if (!resolvedTsconfig) {
1193
+ return void 0;
1194
+ }
1195
+ const read = import_typescript.default.readConfigFile(resolvedTsconfig, import_typescript.default.sys.readFile);
1196
+ if (read.error) {
1197
+ throw new Error(import_typescript.default.flattenDiagnosticMessageText(read.error.messageText, "\n"));
1198
+ }
1199
+ const parsed = import_typescript.default.parseJsonConfigFileContent(
1200
+ read.config,
1201
+ import_typescript.default.sys,
1202
+ import_node_path.default.dirname(resolvedTsconfig),
1203
+ void 0,
1204
+ resolvedTsconfig
1205
+ );
1206
+ if (parsed.errors.length > 0) {
1207
+ throw new Error(
1208
+ parsed.errors.map((entry) => import_typescript.default.flattenDiagnosticMessageText(entry.messageText, "\n")).join("\n")
1209
+ );
1210
+ }
1211
+ return { resolvedTsconfig, parsed };
1212
+ }
1213
+ function extractLeafSourceByAst({
1214
+ modulePath,
1215
+ exportName,
1216
+ tsconfigPath,
1217
+ cwd = process.cwd()
1218
+ }) {
1219
+ const parsedConfig = parseTsConfig(cwd, tsconfigPath);
1220
+ if (!parsedConfig) {
1221
+ return { sourceByLeaf: {} };
1222
+ }
1223
+ const program = import_typescript.default.createProgram({
1224
+ rootNames: parsedConfig.parsed.fileNames,
1225
+ options: parsedConfig.parsed.options
1226
+ });
1227
+ const checker = program.getTypeChecker();
1228
+ const moduleFile = findSourceFile(program, import_node_path.default.resolve(cwd, modulePath));
1229
+ if (!moduleFile) {
1230
+ return {
1231
+ sourceByLeaf: {},
1232
+ tsconfigPath: parsedConfig.resolvedTsconfig
1233
+ };
1234
+ }
1235
+ const exportedExpression = resolveExportExpression(moduleFile, exportName, checker);
1236
+ if (!exportedExpression) {
1237
+ return {
1238
+ sourceByLeaf: {},
1239
+ tsconfigPath: parsedConfig.resolvedTsconfig
1240
+ };
1241
+ }
1242
+ const ctx = {
1243
+ checker,
1244
+ visitedExpressionStarts: /* @__PURE__ */ new Set()
1245
+ };
1246
+ const evaluatedLeaves = evaluateLeavesFromExpression(exportedExpression, ctx);
1247
+ const sourceByLeaf = {};
1248
+ for (const leaf of evaluatedLeaves) {
1249
+ const key = buildLeafKey(leaf.method, leaf.path);
1250
+ const definition = {
1251
+ ...toLocation(leaf.definitionNode),
1252
+ symbolName: getNearestVariableName(leaf.definitionNode)
1253
+ };
1254
+ const schemas = {};
1255
+ for (const schemaKey of SCHEMA_KEYS) {
1256
+ const schemaExpression = leaf.schemas[schemaKey];
1257
+ if (!schemaExpression) continue;
1258
+ schemas[schemaKey] = resolveSchemaMetadata(schemaExpression, ctx);
1259
+ }
1260
+ sourceByLeaf[key] = { definition, schemas };
1261
+ }
1262
+ return {
1263
+ sourceByLeaf,
1264
+ tsconfigPath: parsedConfig.resolvedTsconfig
1265
+ };
1266
+ }
1267
+
817
1268
  // src/export/exportFinalizedLeaves.ts
818
1269
  function isRegistry(value) {
819
1270
  return typeof value === "object" && value !== null && "all" in value && "byKey" in value;
@@ -821,10 +1272,11 @@ function isRegistry(value) {
821
1272
  function getLeaves(input) {
822
1273
  return isRegistry(input) ? input.all : input;
823
1274
  }
824
- function buildMeta() {
1275
+ function buildMeta(sourceExtraction) {
825
1276
  return {
826
1277
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
827
1278
  description: "Finalized RRRoutes leaves export with serialized schemas and flattened schema paths for downstream processing.",
1279
+ sourceExtraction,
828
1280
  fieldCatalog: {
829
1281
  leaf: ["key", "method", "path", "cfg"],
830
1282
  cfg: [
@@ -881,20 +1333,20 @@ ${htmlTemplate}`;
881
1333
  }
882
1334
  async function resolveViewerTemplatePath(viewerTemplateFile) {
883
1335
  if (viewerTemplateFile) {
884
- const resolved = import_node_path.default.resolve(viewerTemplateFile);
1336
+ const resolved = import_node_path2.default.resolve(viewerTemplateFile);
885
1337
  await import_promises.default.access(resolved);
886
1338
  return resolved;
887
1339
  }
888
1340
  const candidates = [
889
- import_node_path.default.resolve(
1341
+ import_node_path2.default.resolve(
890
1342
  process.cwd(),
891
1343
  "node_modules/@emeryld/rrroutes-contract/tools/finalized-leaves-viewer.html"
892
1344
  ),
893
- import_node_path.default.resolve(
1345
+ import_node_path2.default.resolve(
894
1346
  process.cwd(),
895
1347
  "tools/finalized-leaves-viewer.html"
896
1348
  ),
897
- import_node_path.default.resolve(
1349
+ import_node_path2.default.resolve(
898
1350
  process.cwd(),
899
1351
  "packages/contract/tools/finalized-leaves-viewer.html"
900
1352
  )
@@ -909,8 +1361,8 @@ async function resolveViewerTemplatePath(viewerTemplateFile) {
909
1361
  return void 0;
910
1362
  }
911
1363
  async function writeJsonExport(payload, outFile) {
912
- const resolved = import_node_path.default.resolve(outFile);
913
- await import_promises.default.mkdir(import_node_path.default.dirname(resolved), { recursive: true });
1364
+ const resolved = import_node_path2.default.resolve(outFile);
1365
+ await import_promises.default.mkdir(import_node_path2.default.dirname(resolved), { recursive: true });
914
1366
  await import_promises.default.writeFile(resolved, `${JSON.stringify(payload, null, 2)}
915
1367
  `, "utf8");
916
1368
  return resolved;
@@ -919,13 +1371,13 @@ async function writeBakedHtmlExport(payload, htmlFile, viewerTemplateFile) {
919
1371
  const templatePath = await resolveViewerTemplatePath(viewerTemplateFile);
920
1372
  const template = templatePath ? await import_promises.default.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
921
1373
  const baked = injectPayloadIntoViewerHtml(template, payload);
922
- const resolved = import_node_path.default.resolve(htmlFile);
923
- await import_promises.default.mkdir(import_node_path.default.dirname(resolved), { recursive: true });
1374
+ const resolved = import_node_path2.default.resolve(htmlFile);
1375
+ await import_promises.default.mkdir(import_node_path2.default.dirname(resolved), { recursive: true });
924
1376
  await import_promises.default.writeFile(resolved, baked, "utf8");
925
1377
  return resolved;
926
1378
  }
927
1379
  async function openHtmlInBrowser(filePath) {
928
- const resolved = import_node_path.default.resolve(filePath);
1380
+ const resolved = import_node_path2.default.resolve(filePath);
929
1381
  const platform = process.platform;
930
1382
  if (platform === "darwin") {
931
1383
  (0, import_node_child_process.spawn)("open", [resolved], { detached: true, stdio: "ignore" }).unref();
@@ -969,10 +1421,46 @@ async function exportFinalizedLeaves(input, options = {}) {
969
1421
  const schemaFlatByLeaf = Object.fromEntries(
970
1422
  serializedLeaves.map((leaf) => [leaf.key, flattenLeafSchemas(leaf)])
971
1423
  );
1424
+ const sourceByLeaf = {};
1425
+ let sourceExtraction;
1426
+ if (options.includeSource) {
1427
+ const modulePath = options.sourceModulePath;
1428
+ const exportName = options.sourceExportName ?? "leaves";
1429
+ if (modulePath) {
1430
+ const extracted = extractLeafSourceByAst({
1431
+ modulePath,
1432
+ exportName,
1433
+ tsconfigPath: options.tsconfigPath
1434
+ });
1435
+ const allowedLeafKeys = new Set(serializedLeaves.map((leaf) => leaf.key));
1436
+ for (const [key, source] of Object.entries(extracted.sourceByLeaf)) {
1437
+ if (allowedLeafKeys.has(key)) {
1438
+ sourceByLeaf[key] = source;
1439
+ }
1440
+ }
1441
+ sourceExtraction = {
1442
+ mode: "ast",
1443
+ enabled: true,
1444
+ modulePath: import_node_path2.default.resolve(modulePath),
1445
+ exportName,
1446
+ tsconfigPath: extracted.tsconfigPath,
1447
+ resolvedLeafCount: Object.keys(sourceByLeaf).length
1448
+ };
1449
+ } else {
1450
+ sourceExtraction = {
1451
+ mode: "ast",
1452
+ enabled: false,
1453
+ exportName,
1454
+ tsconfigPath: options.tsconfigPath ? import_node_path2.default.resolve(options.tsconfigPath) : void 0,
1455
+ resolvedLeafCount: 0
1456
+ };
1457
+ }
1458
+ }
972
1459
  const payload = {
973
- _meta: buildMeta(),
1460
+ _meta: buildMeta(sourceExtraction),
974
1461
  leaves: serializedLeaves,
975
- schemaFlatByLeaf
1462
+ schemaFlatByLeaf,
1463
+ sourceByLeaf: options.includeSource && Object.keys(sourceByLeaf).length > 0 ? sourceByLeaf : void 0
976
1464
  };
977
1465
  if (options.outFile || options.htmlFile) {
978
1466
  await writeFinalizedLeavesExport(payload, {
@@ -986,14 +1474,19 @@ async function exportFinalizedLeaves(input, options = {}) {
986
1474
  }
987
1475
 
988
1476
  // src/export/exportFinalizedLeaves.cli.ts
989
- var import_node_path2 = __toESM(require("path"), 1);
1477
+ var import_node_path3 = __toESM(require("path"), 1);
990
1478
  var import_node_url = require("url");
991
1479
  function parseFinalizedLeavesCliArgs(argv) {
992
1480
  const args = /* @__PURE__ */ new Map();
1481
+ let withSource = false;
993
1482
  for (let i = 0; i < argv.length; i += 1) {
994
1483
  const key = argv[i];
995
1484
  if (key === "--") continue;
996
1485
  if (!key.startsWith("--")) continue;
1486
+ if (key === "--with-source") {
1487
+ withSource = true;
1488
+ continue;
1489
+ }
997
1490
  const value = argv[i + 1];
998
1491
  if (!value || value.startsWith("--")) {
999
1492
  throw new Error(`Missing value for ${key}`);
@@ -1008,14 +1501,16 @@ function parseFinalizedLeavesCliArgs(argv) {
1008
1501
  return {
1009
1502
  modulePath,
1010
1503
  exportName: args.get("--export") ?? "leaves",
1011
- outFile: args.get("--out") ?? "finalized-leaves.export.json"
1504
+ outFile: args.get("--out") ?? "finalized-leaves.export.json",
1505
+ withSource,
1506
+ tsconfigPath: args.get("--tsconfig") ?? void 0
1012
1507
  };
1013
1508
  }
1014
1509
  async function loadFinalizedLeavesInput({
1015
1510
  modulePath,
1016
1511
  exportName
1017
1512
  }) {
1018
- const resolvedModule = import_node_path2.default.resolve(process.cwd(), modulePath);
1513
+ const resolvedModule = import_node_path3.default.resolve(process.cwd(), modulePath);
1019
1514
  const mod = await import((0, import_node_url.pathToFileURL)(resolvedModule).href);
1020
1515
  const value = mod[exportName] ?? (mod.default && mod.default[exportName]);
1021
1516
  if (!value) {
@@ -1026,10 +1521,16 @@ async function loadFinalizedLeavesInput({
1026
1521
  async function runExportFinalizedLeavesCli(argv) {
1027
1522
  const args = parseFinalizedLeavesCliArgs(argv);
1028
1523
  const input = await loadFinalizedLeavesInput(args);
1029
- const payload = await exportFinalizedLeaves(input, { outFile: args.outFile });
1524
+ const payload = await exportFinalizedLeaves(input, {
1525
+ outFile: args.outFile,
1526
+ includeSource: args.withSource,
1527
+ tsconfigPath: args.tsconfigPath,
1528
+ sourceModulePath: args.modulePath,
1529
+ sourceExportName: args.exportName
1530
+ });
1030
1531
  return {
1031
1532
  payload,
1032
- outFile: import_node_path2.default.resolve(args.outFile)
1533
+ outFile: import_node_path3.default.resolve(args.outFile)
1033
1534
  };
1034
1535
  }
1035
1536
  // Annotate the CommonJS export names for ESM import in node:
@@ -1043,6 +1544,7 @@ async function runExportFinalizedLeavesCli(argv) {
1043
1544
  createSchemaIntrospector,
1044
1545
  defineSocketEvents,
1045
1546
  exportFinalizedLeaves,
1547
+ extractLeafSourceByAst,
1046
1548
  finalize,
1047
1549
  flattenLeafSchemas,
1048
1550
  flattenSerializableSchema,