@emeryld/rrroutes-contract 2.7.5 → 2.7.7

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,62 @@ 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 = 'definition'
829
+ label.appendChild(link)
830
+ const location = document.createElement('span')
831
+ location.textContent =
832
+ ' (' +
833
+ source.definition.file +
834
+ ':' +
835
+ source.definition.line +
836
+ ':' +
837
+ source.definition.column +
838
+ ')'
839
+ label.appendChild(location)
840
+ sourceWrap.appendChild(label)
841
+ }
842
+
843
+ if (source.schemas && typeof source.schemas === 'object') {
844
+ Object.entries(source.schemas).forEach(([name, schema]) => {
845
+ if (!schema) return
846
+ const href = toHref(schema)
847
+ const row = document.createElement('div')
848
+ if (href) {
849
+ const link = document.createElement('a')
850
+ link.href = href
851
+ link.target = '_blank'
852
+ link.rel = 'noopener noreferrer'
853
+ link.textContent =
854
+ name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
855
+ row.appendChild(link)
856
+ const location = document.createElement('span')
857
+ location.textContent =
858
+ ' (' + schema.file + ':' + schema.line + ':' + schema.column + ')'
859
+ row.appendChild(location)
860
+ } else {
861
+ row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
862
+ }
863
+ sourceWrap.appendChild(row)
864
+ })
865
+ }
866
+
867
+ }
868
+
807
869
  details.appendChild(summary)
870
+ if (sourceWrap) details.appendChild(sourceWrap)
808
871
  details.appendChild(pre)
809
872
  resultsEl.appendChild(details)
810
873
  })
@@ -814,6 +877,551 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
814
877
  </html>
815
878
  `;
816
879
 
880
+ // src/export/extractLeafSourceByAst.ts
881
+ var import_node_path = __toESM(require("path"), 1);
882
+ var import_typescript = __toESM(require("typescript"), 1);
883
+ var SCHEMA_KEYS = [
884
+ "bodySchema",
885
+ "querySchema",
886
+ "paramsSchema",
887
+ "outputSchema",
888
+ "outputMetaSchema",
889
+ "queryExtensionSchema"
890
+ ];
891
+ var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
892
+ var MAX_RECURSION_DEPTH = 120;
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 markFile(ctx, sourceFile) {
905
+ if (!sourceFile) return;
906
+ ctx.visitedFilePaths.add(import_node_path.default.resolve(sourceFile.fileName));
907
+ }
908
+ function trimPreview(text, max = 80) {
909
+ const normalized = text.replace(/\s+/g, " ").trim();
910
+ return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
911
+ }
912
+ function unwrapExpression(expression) {
913
+ let current = expression;
914
+ while (true) {
915
+ 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)) {
916
+ current = current.expression;
917
+ continue;
918
+ }
919
+ break;
920
+ }
921
+ return current;
922
+ }
923
+ function getTextName(name) {
924
+ if (import_typescript.default.isIdentifier(name) || import_typescript.default.isStringLiteral(name) || import_typescript.default.isNumericLiteral(name)) {
925
+ return name.text;
926
+ }
927
+ return void 0;
928
+ }
929
+ function joinPaths2(parent, child) {
930
+ if (!parent) return child;
931
+ if (!child) return parent;
932
+ const trimmedParent = parent.endsWith("/") ? parent.replace(/\/+$/, "") : parent;
933
+ const trimmedChild = child.startsWith("/") ? child.replace(/^\/+/, "") : child;
934
+ if (!trimmedChild) return trimmedParent;
935
+ return `${trimmedParent}/${trimmedChild}`;
936
+ }
937
+ function buildLeafKey(method, leafPath) {
938
+ return `${method.toUpperCase()} ${leafPath}`;
939
+ }
940
+ function normalizeResourceBase(raw) {
941
+ if (raw === "") return "";
942
+ if (raw.startsWith("/") || raw.startsWith(":") || raw.startsWith("*")) {
943
+ return raw;
944
+ }
945
+ return `/${raw}`;
946
+ }
947
+ function getModuleSymbol(checker, sourceFile) {
948
+ return checker.getSymbolAtLocation(sourceFile);
949
+ }
950
+ function getAliasedSymbolIfNeeded(checker, symbol) {
951
+ if ((symbol.flags & import_typescript.default.SymbolFlags.Alias) !== 0) {
952
+ try {
953
+ return checker.getAliasedSymbol(symbol);
954
+ } catch {
955
+ return symbol;
956
+ }
957
+ }
958
+ return symbol;
959
+ }
960
+ function findSourceFile(program, filePath) {
961
+ const wanted = import_node_path.default.resolve(filePath);
962
+ const normalizedWanted = import_node_path.default.normalize(wanted);
963
+ return program.getSourceFiles().find((file) => import_node_path.default.normalize(import_node_path.default.resolve(file.fileName)) === normalizedWanted);
964
+ }
965
+ function declarationToExpression(declaration) {
966
+ if (!declaration) return void 0;
967
+ if (import_typescript.default.isVariableDeclaration(declaration)) {
968
+ return declaration.initializer;
969
+ }
970
+ if (import_typescript.default.isExportAssignment(declaration)) {
971
+ return declaration.expression;
972
+ }
973
+ if (import_typescript.default.isPropertyAssignment(declaration)) {
974
+ return declaration.initializer;
975
+ }
976
+ if (import_typescript.default.isShorthandPropertyAssignment(declaration)) {
977
+ return declaration.name;
978
+ }
979
+ if (import_typescript.default.isBindingElement(declaration)) {
980
+ return declaration.initializer;
981
+ }
982
+ if (import_typescript.default.isEnumMember(declaration)) {
983
+ return declaration.initializer;
984
+ }
985
+ return void 0;
986
+ }
987
+ function symbolKey(symbol) {
988
+ const decl = symbol.declarations?.[0];
989
+ if (!decl) return `${symbol.getName()}#${symbol.flags}`;
990
+ const file = import_node_path.default.resolve(decl.getSourceFile().fileName);
991
+ return `${file}:${decl.getStart()}:${symbol.getName()}`;
992
+ }
993
+ function resolveSymbolFromNode(node, ctx) {
994
+ const symbol = ctx.checker.getSymbolAtLocation(node);
995
+ if (!symbol) {
996
+ ctx.unresolvedReferences += 1;
997
+ return void 0;
998
+ }
999
+ const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
1000
+ const key = symbolKey(target);
1001
+ ctx.visitedSymbolKeys.add(key);
1002
+ return target;
1003
+ }
1004
+ function getExpressionFromSymbol(symbol, ctx, depth) {
1005
+ const key = symbolKey(symbol);
1006
+ if (ctx.activeSymbols.has(key)) return void 0;
1007
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
1008
+ ctx.activeSymbols.add(key);
1009
+ try {
1010
+ const declaration = symbol.declarations?.[0];
1011
+ markFile(ctx, declaration?.getSourceFile());
1012
+ const direct = declarationToExpression(declaration);
1013
+ if (direct) return direct;
1014
+ if (declaration && import_typescript.default.isImportSpecifier(declaration)) {
1015
+ const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
1016
+ if (target !== symbol) {
1017
+ return getExpressionFromSymbol(target, ctx, depth + 1);
1018
+ }
1019
+ }
1020
+ return void 0;
1021
+ } finally {
1022
+ ctx.activeSymbols.delete(key);
1023
+ }
1024
+ }
1025
+ function resolveIdentifierExpression(identifier, ctx, depth) {
1026
+ const symbol = resolveSymbolFromNode(identifier, ctx);
1027
+ if (!symbol) return void 0;
1028
+ return getExpressionFromSymbol(symbol, ctx, depth);
1029
+ }
1030
+ function resolvePropertyExpression(expression, ctx, depth) {
1031
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
1032
+ if (import_typescript.default.isPropertyAccessExpression(expression)) {
1033
+ const symbol = resolveSymbolFromNode(expression.name, ctx);
1034
+ if (symbol) {
1035
+ const fromSymbol = getExpressionFromSymbol(symbol, ctx, depth + 1);
1036
+ if (fromSymbol) return fromSymbol;
1037
+ }
1038
+ } else if (expression.argumentExpression) {
1039
+ const symbol = resolveSymbolFromNode(expression.argumentExpression, ctx);
1040
+ if (symbol) {
1041
+ const fromSymbol = getExpressionFromSymbol(symbol, ctx, depth + 1);
1042
+ if (fromSymbol) return fromSymbol;
1043
+ }
1044
+ }
1045
+ const ownerExpr = evaluateExpressionReference(expression.expression, ctx, depth + 1);
1046
+ const owner = ownerExpr ? maybeObjectLiteral(ownerExpr, ctx, depth + 1) : void 0;
1047
+ if (!owner) return void 0;
1048
+ const propName = import_typescript.default.isPropertyAccessExpression(expression) ? expression.name.text : expression.argumentExpression && import_typescript.default.isStringLiteralLike(expression.argumentExpression) ? expression.argumentExpression.text : void 0;
1049
+ if (!propName) return void 0;
1050
+ for (const property of owner.properties) {
1051
+ if (import_typescript.default.isPropertyAssignment(property) && getTextName(property.name) === propName) {
1052
+ return property.initializer;
1053
+ }
1054
+ if (import_typescript.default.isShorthandPropertyAssignment(property) && property.name.text === propName) {
1055
+ return property.name;
1056
+ }
1057
+ }
1058
+ return void 0;
1059
+ }
1060
+ function evaluateExpressionReference(expression, ctx, depth) {
1061
+ const resolved = unwrapExpression(expression);
1062
+ markFile(ctx, resolved.getSourceFile());
1063
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
1064
+ if (import_typescript.default.isIdentifier(resolved)) {
1065
+ return resolveIdentifierExpression(resolved, ctx, depth + 1);
1066
+ }
1067
+ if (import_typescript.default.isPropertyAccessExpression(resolved) || import_typescript.default.isElementAccessExpression(resolved)) {
1068
+ return resolvePropertyExpression(resolved, ctx, depth + 1);
1069
+ }
1070
+ return void 0;
1071
+ }
1072
+ function resolveExportExpression(sourceFile, exportName, checker) {
1073
+ const moduleSymbol = getModuleSymbol(checker, sourceFile);
1074
+ if (!moduleSymbol) return void 0;
1075
+ const exports2 = checker.getExportsOfModule(moduleSymbol);
1076
+ const explicit = exports2.find((entry) => entry.getName() === exportName);
1077
+ if (explicit) {
1078
+ const declaration = getAliasedSymbolIfNeeded(checker, explicit).declarations?.[0];
1079
+ return declarationToExpression(declaration);
1080
+ }
1081
+ const defaultExport = exports2.find((entry) => entry.getName() === "default");
1082
+ if (!defaultExport) return void 0;
1083
+ const defaultDecl = getAliasedSymbolIfNeeded(checker, defaultExport).declarations?.[0];
1084
+ const defaultExpr = declarationToExpression(defaultDecl);
1085
+ if (!defaultExpr) return void 0;
1086
+ const resolved = unwrapExpression(defaultExpr);
1087
+ if (!import_typescript.default.isObjectLiteralExpression(resolved)) return void 0;
1088
+ for (const property of resolved.properties) {
1089
+ if (!import_typescript.default.isPropertyAssignment(property)) continue;
1090
+ const propertyName = getTextName(property.name);
1091
+ if (propertyName !== exportName) continue;
1092
+ return property.initializer;
1093
+ }
1094
+ return void 0;
1095
+ }
1096
+ function maybeObjectLiteral(expression, ctx, depth = 0) {
1097
+ if (!expression || depth > MAX_RECURSION_DEPTH) return void 0;
1098
+ const resolved = unwrapExpression(expression);
1099
+ markFile(ctx, resolved.getSourceFile());
1100
+ if (import_typescript.default.isObjectLiteralExpression(resolved)) return resolved;
1101
+ const referenced = evaluateExpressionReference(resolved, ctx, depth + 1);
1102
+ if (!referenced) return void 0;
1103
+ return maybeObjectLiteral(referenced, ctx, depth + 1);
1104
+ }
1105
+ function collectSchemaExpressionsFromObject(objectLiteral, ctx, depth) {
1106
+ const schemas = {};
1107
+ for (const property of objectLiteral.properties) {
1108
+ if (import_typescript.default.isSpreadAssignment(property)) {
1109
+ const spreadObject = maybeObjectLiteral(property.expression, ctx, depth + 1);
1110
+ if (!spreadObject) continue;
1111
+ Object.assign(
1112
+ schemas,
1113
+ collectSchemaExpressionsFromObject(spreadObject, ctx, depth + 1)
1114
+ );
1115
+ continue;
1116
+ }
1117
+ if (import_typescript.default.isPropertyAssignment(property)) {
1118
+ const key = getTextName(property.name);
1119
+ if (!key || !SCHEMA_KEYS.includes(key)) continue;
1120
+ schemas[key] = property.initializer;
1121
+ continue;
1122
+ }
1123
+ if (import_typescript.default.isShorthandPropertyAssignment(property)) {
1124
+ const key = property.name.text;
1125
+ if (!SCHEMA_KEYS.includes(key)) continue;
1126
+ schemas[key] = property.name;
1127
+ }
1128
+ }
1129
+ return schemas;
1130
+ }
1131
+ function extractSchemaExpressions(cfgExpression, ctx, depth) {
1132
+ const objectLiteral = maybeObjectLiteral(cfgExpression, ctx, depth);
1133
+ if (!objectLiteral) return {};
1134
+ return collectSchemaExpressionsFromObject(objectLiteral, ctx, depth);
1135
+ }
1136
+ function getNearestVariableName(node) {
1137
+ let current = node;
1138
+ while (current) {
1139
+ if (import_typescript.default.isVariableDeclaration(current) && import_typescript.default.isIdentifier(current.name)) {
1140
+ return current.name.text;
1141
+ }
1142
+ current = current.parent;
1143
+ }
1144
+ return void 0;
1145
+ }
1146
+ function isAllAccess(expression) {
1147
+ if (import_typescript.default.isPropertyAccessExpression(expression)) {
1148
+ return expression.name.text === "all";
1149
+ }
1150
+ return Boolean(
1151
+ expression.argumentExpression && import_typescript.default.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === "all"
1152
+ );
1153
+ }
1154
+ function evaluateBranchExpression(expression, ctx, depth) {
1155
+ const resolved = unwrapExpression(expression);
1156
+ markFile(ctx, resolved.getSourceFile());
1157
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
1158
+ if (import_typescript.default.isIdentifier(resolved)) {
1159
+ const valueExpr = resolveIdentifierExpression(resolved, ctx, depth + 1);
1160
+ if (!valueExpr) return void 0;
1161
+ return evaluateBranchExpression(valueExpr, ctx, depth + 1);
1162
+ }
1163
+ if (!import_typescript.default.isCallExpression(resolved)) return void 0;
1164
+ const call = resolved;
1165
+ if (import_typescript.default.isIdentifier(call.expression) && call.expression.text === "resource") {
1166
+ const firstArg = call.arguments[0];
1167
+ if (!firstArg || !import_typescript.default.isStringLiteralLike(firstArg)) {
1168
+ ctx.unsupportedShapeSeen = true;
1169
+ return void 0;
1170
+ }
1171
+ return { base: normalizeResourceBase(firstArg.text), leaves: [] };
1172
+ }
1173
+ if (!import_typescript.default.isPropertyAccessExpression(call.expression)) {
1174
+ ctx.unsupportedShapeSeen = true;
1175
+ return void 0;
1176
+ }
1177
+ const owner = call.expression.expression;
1178
+ const method = call.expression.name.text;
1179
+ const branch = evaluateBranchExpression(owner, ctx, depth + 1);
1180
+ if (!branch) return void 0;
1181
+ if (method === "with") return branch;
1182
+ if (HTTP_METHODS.has(method)) {
1183
+ const cfgExpression = call.arguments[0];
1184
+ const schemas = extractSchemaExpressions(cfgExpression, ctx, depth + 1);
1185
+ const nextLeaf = {
1186
+ method,
1187
+ path: branch.base,
1188
+ definitionNode: call.expression.name,
1189
+ schemas
1190
+ };
1191
+ return {
1192
+ ...branch,
1193
+ leaves: [...branch.leaves, nextLeaf]
1194
+ };
1195
+ }
1196
+ if (method === "sub") {
1197
+ const mountedLeaves = call.arguments.flatMap(
1198
+ (arg) => evaluateLeavesFromExpression(arg, ctx, depth + 1)
1199
+ );
1200
+ const prefixed = mountedLeaves.map((leaf) => ({
1201
+ ...leaf,
1202
+ path: joinPaths2(branch.base, leaf.path)
1203
+ }));
1204
+ return {
1205
+ ...branch,
1206
+ leaves: [...branch.leaves, ...prefixed]
1207
+ };
1208
+ }
1209
+ ctx.unsupportedShapeSeen = true;
1210
+ return void 0;
1211
+ }
1212
+ function expressionKey(expression) {
1213
+ const source = expression.getSourceFile();
1214
+ return `${import_node_path.default.resolve(source.fileName)}:${expression.getStart(source)}:${expression.getEnd()}:${expression.kind}`;
1215
+ }
1216
+ function evaluateLeavesFromExpression(expression, ctx, depth = 0) {
1217
+ const resolved = unwrapExpression(expression);
1218
+ markFile(ctx, resolved.getSourceFile());
1219
+ const key = expressionKey(resolved);
1220
+ if (ctx.activeExpressionKeys.has(key)) return [];
1221
+ if (depth > MAX_RECURSION_DEPTH) return [];
1222
+ ctx.activeExpressionKeys.add(key);
1223
+ try {
1224
+ if (import_typescript.default.isIdentifier(resolved)) {
1225
+ const valueExpr = resolveIdentifierExpression(resolved, ctx, depth + 1);
1226
+ if (!valueExpr) return [];
1227
+ return evaluateLeavesFromExpression(valueExpr, ctx, depth + 1);
1228
+ }
1229
+ if (import_typescript.default.isPropertyAccessExpression(resolved) || import_typescript.default.isElementAccessExpression(resolved)) {
1230
+ if (isAllAccess(resolved)) {
1231
+ return evaluateLeavesFromExpression(resolved.expression, ctx, depth + 1);
1232
+ }
1233
+ const refExpr = resolvePropertyExpression(resolved, ctx, depth + 1);
1234
+ if (refExpr) {
1235
+ return evaluateLeavesFromExpression(refExpr, ctx, depth + 1);
1236
+ }
1237
+ return [];
1238
+ }
1239
+ if (import_typescript.default.isArrayLiteralExpression(resolved)) {
1240
+ const leaves = [];
1241
+ for (const element of resolved.elements) {
1242
+ if (import_typescript.default.isSpreadElement(element)) {
1243
+ leaves.push(...evaluateLeavesFromExpression(element.expression, ctx, depth + 1));
1244
+ continue;
1245
+ }
1246
+ leaves.push(...evaluateLeavesFromExpression(element, ctx, depth + 1));
1247
+ }
1248
+ return leaves;
1249
+ }
1250
+ if (import_typescript.default.isCallExpression(resolved)) {
1251
+ if (import_typescript.default.isIdentifier(resolved.expression)) {
1252
+ const callName = resolved.expression.text;
1253
+ if (callName === "finalize") {
1254
+ const arg = resolved.arguments[0];
1255
+ if (!arg) return [];
1256
+ return evaluateLeavesFromExpression(arg, ctx, depth + 1);
1257
+ }
1258
+ if (callName === "mergeArrays") {
1259
+ return resolved.arguments.flatMap(
1260
+ (arg) => evaluateLeavesFromExpression(arg, ctx, depth + 1)
1261
+ );
1262
+ }
1263
+ }
1264
+ if (import_typescript.default.isPropertyAccessExpression(resolved.expression)) {
1265
+ const prop = resolved.expression.name.text;
1266
+ if (prop === "done") {
1267
+ const branch2 = evaluateBranchExpression(
1268
+ resolved.expression.expression,
1269
+ ctx,
1270
+ depth + 1
1271
+ );
1272
+ return branch2?.leaves ?? [];
1273
+ }
1274
+ }
1275
+ const refExpr = evaluateExpressionReference(resolved, ctx, depth + 1);
1276
+ if (refExpr) return evaluateLeavesFromExpression(refExpr, ctx, depth + 1);
1277
+ ctx.unsupportedShapeSeen = true;
1278
+ return [];
1279
+ }
1280
+ const branch = evaluateBranchExpression(resolved, ctx, depth + 1);
1281
+ if (branch) return branch.leaves;
1282
+ ctx.unsupportedShapeSeen = true;
1283
+ return [];
1284
+ } finally {
1285
+ ctx.activeExpressionKeys.delete(key);
1286
+ }
1287
+ }
1288
+ function resolveSchemaMetadata(expression, ctx) {
1289
+ const resolved = unwrapExpression(expression);
1290
+ if (import_typescript.default.isIdentifier(resolved)) {
1291
+ const symbol = resolveSymbolFromNode(resolved, ctx);
1292
+ const declaration = symbol?.declarations?.[0];
1293
+ const locationNode = declaration ? import_typescript.default.isVariableDeclaration(declaration) ? declaration.name : declaration : resolved;
1294
+ const sourceName = declaration && import_typescript.default.isVariableDeclaration(declaration) && import_typescript.default.isIdentifier(declaration.name) ? declaration.name.text : resolved.text;
1295
+ return {
1296
+ ...toLocation(locationNode),
1297
+ sourceName,
1298
+ tag: declaration ? void 0 : "<anonymous>"
1299
+ };
1300
+ }
1301
+ if (import_typescript.default.isPropertyAccessExpression(resolved) || import_typescript.default.isElementAccessExpression(resolved)) {
1302
+ return {
1303
+ ...toLocation(resolved),
1304
+ sourceName: trimPreview(resolved.getText()),
1305
+ tag: "<expression>"
1306
+ };
1307
+ }
1308
+ return {
1309
+ ...toLocation(resolved),
1310
+ sourceName: trimPreview(resolved.getText()),
1311
+ tag: "<inline>"
1312
+ };
1313
+ }
1314
+ function parseTsConfig(cwd, tsconfigPath) {
1315
+ const resolvedTsconfig = tsconfigPath ? import_node_path.default.resolve(cwd, tsconfigPath) : import_typescript.default.findConfigFile(cwd, import_typescript.default.sys.fileExists, "tsconfig.json");
1316
+ if (!resolvedTsconfig) {
1317
+ return void 0;
1318
+ }
1319
+ const read = import_typescript.default.readConfigFile(resolvedTsconfig, import_typescript.default.sys.readFile);
1320
+ if (read.error) {
1321
+ throw new Error(import_typescript.default.flattenDiagnosticMessageText(read.error.messageText, "\n"));
1322
+ }
1323
+ const parsed = import_typescript.default.parseJsonConfigFileContent(
1324
+ read.config,
1325
+ import_typescript.default.sys,
1326
+ import_node_path.default.dirname(resolvedTsconfig),
1327
+ void 0,
1328
+ resolvedTsconfig
1329
+ );
1330
+ const nonEmptyInputErrors = parsed.errors.filter((entry) => entry.code !== 18003);
1331
+ if (nonEmptyInputErrors.length > 0) {
1332
+ throw new Error(
1333
+ nonEmptyInputErrors.map((entry) => import_typescript.default.flattenDiagnosticMessageText(entry.messageText, "\n")).join("\n")
1334
+ );
1335
+ }
1336
+ return { resolvedTsconfig, parsed };
1337
+ }
1338
+ function createProgramWithFallback(parsed, moduleFileAbs) {
1339
+ const base = import_typescript.default.createProgram({
1340
+ rootNames: parsed.fileNames,
1341
+ options: parsed.options
1342
+ });
1343
+ if (findSourceFile(base, moduleFileAbs)) {
1344
+ return base;
1345
+ }
1346
+ const rootNames = Array.from(/* @__PURE__ */ new Set([...parsed.fileNames, moduleFileAbs]));
1347
+ return import_typescript.default.createProgram({ rootNames, options: parsed.options });
1348
+ }
1349
+ function extractLeafSourceByAst({
1350
+ modulePath,
1351
+ exportName,
1352
+ tsconfigPath,
1353
+ cwd = process.cwd()
1354
+ }) {
1355
+ const parsedConfig = parseTsConfig(cwd, tsconfigPath);
1356
+ if (!parsedConfig) {
1357
+ return {
1358
+ sourceByLeaf: {},
1359
+ reason: "module_not_in_program",
1360
+ stats: { visitedSymbols: 0, visitedFiles: 0, unresolvedReferences: 0 }
1361
+ };
1362
+ }
1363
+ const moduleAbs = import_node_path.default.resolve(cwd, modulePath);
1364
+ const program = createProgramWithFallback(parsedConfig.parsed, moduleAbs);
1365
+ const checker = program.getTypeChecker();
1366
+ const moduleFile = findSourceFile(program, moduleAbs);
1367
+ if (!moduleFile) {
1368
+ return {
1369
+ sourceByLeaf: {},
1370
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1371
+ reason: "module_not_in_program",
1372
+ stats: { visitedSymbols: 0, visitedFiles: 0, unresolvedReferences: 0 }
1373
+ };
1374
+ }
1375
+ const exportedExpression = resolveExportExpression(moduleFile, exportName, checker);
1376
+ if (!exportedExpression) {
1377
+ return {
1378
+ sourceByLeaf: {},
1379
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1380
+ reason: "export_not_found",
1381
+ stats: { visitedSymbols: 0, visitedFiles: 1, unresolvedReferences: 0 }
1382
+ };
1383
+ }
1384
+ const ctx = {
1385
+ checker,
1386
+ activeExpressionKeys: /* @__PURE__ */ new Set(),
1387
+ activeSymbols: /* @__PURE__ */ new Set(),
1388
+ visitedSymbolKeys: /* @__PURE__ */ new Set(),
1389
+ visitedFilePaths: /* @__PURE__ */ new Set([import_node_path.default.resolve(moduleFile.fileName)]),
1390
+ unresolvedReferences: 0,
1391
+ unsupportedShapeSeen: false
1392
+ };
1393
+ const evaluatedLeaves = evaluateLeavesFromExpression(exportedExpression, ctx, 0);
1394
+ const sourceByLeaf = {};
1395
+ for (const leaf of evaluatedLeaves) {
1396
+ const key = buildLeafKey(leaf.method, leaf.path);
1397
+ const definition = {
1398
+ ...toLocation(leaf.definitionNode),
1399
+ symbolName: getNearestVariableName(leaf.definitionNode)
1400
+ };
1401
+ ctx.visitedFilePaths.add(definition.file);
1402
+ const schemas = {};
1403
+ for (const schemaKey of SCHEMA_KEYS) {
1404
+ const schemaExpression = leaf.schemas[schemaKey];
1405
+ if (!schemaExpression) continue;
1406
+ const schemaMeta = resolveSchemaMetadata(schemaExpression, ctx);
1407
+ ctx.visitedFilePaths.add(schemaMeta.file);
1408
+ schemas[schemaKey] = schemaMeta;
1409
+ }
1410
+ sourceByLeaf[key] = { definition, schemas };
1411
+ }
1412
+ const reason = Object.keys(sourceByLeaf).length > 0 ? void 0 : ctx.unsupportedShapeSeen ? "unsupported_expression_shape" : "resolved_zero_leaves";
1413
+ return {
1414
+ sourceByLeaf,
1415
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1416
+ reason,
1417
+ stats: {
1418
+ visitedSymbols: ctx.visitedSymbolKeys.size,
1419
+ visitedFiles: ctx.visitedFilePaths.size,
1420
+ unresolvedReferences: ctx.unresolvedReferences
1421
+ }
1422
+ };
1423
+ }
1424
+
817
1425
  // src/export/exportFinalizedLeaves.ts
818
1426
  function isRegistry(value) {
819
1427
  return typeof value === "object" && value !== null && "all" in value && "byKey" in value;
@@ -821,10 +1429,11 @@ function isRegistry(value) {
821
1429
  function getLeaves(input) {
822
1430
  return isRegistry(input) ? input.all : input;
823
1431
  }
824
- function buildMeta() {
1432
+ function buildMeta(sourceExtraction) {
825
1433
  return {
826
1434
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
827
1435
  description: "Finalized RRRoutes leaves export with serialized schemas and flattened schema paths for downstream processing.",
1436
+ sourceExtraction,
828
1437
  fieldCatalog: {
829
1438
  leaf: ["key", "method", "path", "cfg"],
830
1439
  cfg: [
@@ -881,20 +1490,20 @@ ${htmlTemplate}`;
881
1490
  }
882
1491
  async function resolveViewerTemplatePath(viewerTemplateFile) {
883
1492
  if (viewerTemplateFile) {
884
- const resolved = import_node_path.default.resolve(viewerTemplateFile);
1493
+ const resolved = import_node_path2.default.resolve(viewerTemplateFile);
885
1494
  await import_promises.default.access(resolved);
886
1495
  return resolved;
887
1496
  }
888
1497
  const candidates = [
889
- import_node_path.default.resolve(
1498
+ import_node_path2.default.resolve(
890
1499
  process.cwd(),
891
1500
  "node_modules/@emeryld/rrroutes-contract/tools/finalized-leaves-viewer.html"
892
1501
  ),
893
- import_node_path.default.resolve(
1502
+ import_node_path2.default.resolve(
894
1503
  process.cwd(),
895
1504
  "tools/finalized-leaves-viewer.html"
896
1505
  ),
897
- import_node_path.default.resolve(
1506
+ import_node_path2.default.resolve(
898
1507
  process.cwd(),
899
1508
  "packages/contract/tools/finalized-leaves-viewer.html"
900
1509
  )
@@ -909,8 +1518,8 @@ async function resolveViewerTemplatePath(viewerTemplateFile) {
909
1518
  return void 0;
910
1519
  }
911
1520
  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 });
1521
+ const resolved = import_node_path2.default.resolve(outFile);
1522
+ await import_promises.default.mkdir(import_node_path2.default.dirname(resolved), { recursive: true });
914
1523
  await import_promises.default.writeFile(resolved, `${JSON.stringify(payload, null, 2)}
915
1524
  `, "utf8");
916
1525
  return resolved;
@@ -919,13 +1528,13 @@ async function writeBakedHtmlExport(payload, htmlFile, viewerTemplateFile) {
919
1528
  const templatePath = await resolveViewerTemplatePath(viewerTemplateFile);
920
1529
  const template = templatePath ? await import_promises.default.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
921
1530
  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 });
1531
+ const resolved = import_node_path2.default.resolve(htmlFile);
1532
+ await import_promises.default.mkdir(import_node_path2.default.dirname(resolved), { recursive: true });
924
1533
  await import_promises.default.writeFile(resolved, baked, "utf8");
925
1534
  return resolved;
926
1535
  }
927
1536
  async function openHtmlInBrowser(filePath) {
928
- const resolved = import_node_path.default.resolve(filePath);
1537
+ const resolved = import_node_path2.default.resolve(filePath);
929
1538
  const platform = process.platform;
930
1539
  if (platform === "darwin") {
931
1540
  (0, import_node_child_process.spawn)("open", [resolved], { detached: true, stdio: "ignore" }).unref();
@@ -969,10 +1578,54 @@ async function exportFinalizedLeaves(input, options = {}) {
969
1578
  const schemaFlatByLeaf = Object.fromEntries(
970
1579
  serializedLeaves.map((leaf) => [leaf.key, flattenLeafSchemas(leaf)])
971
1580
  );
1581
+ const sourceByLeaf = {};
1582
+ let sourceExtraction;
1583
+ if (options.includeSource) {
1584
+ const modulePath = options.sourceModulePath;
1585
+ const exportName = options.sourceExportName ?? "leaves";
1586
+ if (modulePath) {
1587
+ const extracted = extractLeafSourceByAst({
1588
+ modulePath,
1589
+ exportName,
1590
+ tsconfigPath: options.tsconfigPath
1591
+ });
1592
+ const allowedLeafKeys = new Set(serializedLeaves.map((leaf) => leaf.key));
1593
+ for (const [key, source] of Object.entries(extracted.sourceByLeaf)) {
1594
+ if (allowedLeafKeys.has(key)) {
1595
+ sourceByLeaf[key] = source;
1596
+ }
1597
+ }
1598
+ sourceExtraction = {
1599
+ mode: "ast",
1600
+ enabled: true,
1601
+ modulePath: import_node_path2.default.resolve(modulePath),
1602
+ exportName,
1603
+ tsconfigPath: extracted.tsconfigPath,
1604
+ resolvedLeafCount: Object.keys(sourceByLeaf).length,
1605
+ reason: extracted.reason,
1606
+ stats: extracted.stats
1607
+ };
1608
+ } else {
1609
+ sourceExtraction = {
1610
+ mode: "ast",
1611
+ enabled: false,
1612
+ exportName,
1613
+ tsconfigPath: options.tsconfigPath ? import_node_path2.default.resolve(options.tsconfigPath) : void 0,
1614
+ resolvedLeafCount: 0,
1615
+ reason: "resolved_zero_leaves",
1616
+ stats: {
1617
+ visitedSymbols: 0,
1618
+ visitedFiles: 0,
1619
+ unresolvedReferences: 0
1620
+ }
1621
+ };
1622
+ }
1623
+ }
972
1624
  const payload = {
973
- _meta: buildMeta(),
1625
+ _meta: buildMeta(sourceExtraction),
974
1626
  leaves: serializedLeaves,
975
- schemaFlatByLeaf
1627
+ schemaFlatByLeaf,
1628
+ sourceByLeaf: options.includeSource && Object.keys(sourceByLeaf).length > 0 ? sourceByLeaf : void 0
976
1629
  };
977
1630
  if (options.outFile || options.htmlFile) {
978
1631
  await writeFinalizedLeavesExport(payload, {
@@ -986,14 +1639,19 @@ async function exportFinalizedLeaves(input, options = {}) {
986
1639
  }
987
1640
 
988
1641
  // src/export/exportFinalizedLeaves.cli.ts
989
- var import_node_path2 = __toESM(require("path"), 1);
1642
+ var import_node_path3 = __toESM(require("path"), 1);
990
1643
  var import_node_url = require("url");
991
1644
  function parseFinalizedLeavesCliArgs(argv) {
992
1645
  const args = /* @__PURE__ */ new Map();
1646
+ let withSource = false;
993
1647
  for (let i = 0; i < argv.length; i += 1) {
994
1648
  const key = argv[i];
995
1649
  if (key === "--") continue;
996
1650
  if (!key.startsWith("--")) continue;
1651
+ if (key === "--with-source") {
1652
+ withSource = true;
1653
+ continue;
1654
+ }
997
1655
  const value = argv[i + 1];
998
1656
  if (!value || value.startsWith("--")) {
999
1657
  throw new Error(`Missing value for ${key}`);
@@ -1008,14 +1666,16 @@ function parseFinalizedLeavesCliArgs(argv) {
1008
1666
  return {
1009
1667
  modulePath,
1010
1668
  exportName: args.get("--export") ?? "leaves",
1011
- outFile: args.get("--out") ?? "finalized-leaves.export.json"
1669
+ outFile: args.get("--out") ?? "finalized-leaves.export.json",
1670
+ withSource,
1671
+ tsconfigPath: args.get("--tsconfig") ?? void 0
1012
1672
  };
1013
1673
  }
1014
1674
  async function loadFinalizedLeavesInput({
1015
1675
  modulePath,
1016
1676
  exportName
1017
1677
  }) {
1018
- const resolvedModule = import_node_path2.default.resolve(process.cwd(), modulePath);
1678
+ const resolvedModule = import_node_path3.default.resolve(process.cwd(), modulePath);
1019
1679
  const mod = await import((0, import_node_url.pathToFileURL)(resolvedModule).href);
1020
1680
  const value = mod[exportName] ?? (mod.default && mod.default[exportName]);
1021
1681
  if (!value) {
@@ -1026,10 +1686,16 @@ async function loadFinalizedLeavesInput({
1026
1686
  async function runExportFinalizedLeavesCli(argv) {
1027
1687
  const args = parseFinalizedLeavesCliArgs(argv);
1028
1688
  const input = await loadFinalizedLeavesInput(args);
1029
- const payload = await exportFinalizedLeaves(input, { outFile: args.outFile });
1689
+ const payload = await exportFinalizedLeaves(input, {
1690
+ outFile: args.outFile,
1691
+ includeSource: args.withSource,
1692
+ tsconfigPath: args.tsconfigPath,
1693
+ sourceModulePath: args.modulePath,
1694
+ sourceExportName: args.exportName
1695
+ });
1030
1696
  return {
1031
1697
  payload,
1032
- outFile: import_node_path2.default.resolve(args.outFile)
1698
+ outFile: import_node_path3.default.resolve(args.outFile)
1033
1699
  };
1034
1700
  }
1035
1701
  // Annotate the CommonJS export names for ESM import in node:
@@ -1043,6 +1709,7 @@ async function runExportFinalizedLeavesCli(argv) {
1043
1709
  createSchemaIntrospector,
1044
1710
  defineSocketEvents,
1045
1711
  exportFinalizedLeaves,
1712
+ extractLeafSourceByAst,
1046
1713
  finalize,
1047
1714
  flattenLeafSchemas,
1048
1715
  flattenSerializableSchema,