@emeryld/rrroutes-contract 2.7.6 → 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/README.md CHANGED
@@ -390,6 +390,7 @@ const payload = await exportFinalizedLeaves(registry, {
390
390
  `payload` contains:
391
391
 
392
392
  - `_meta`: export/documentation metadata
393
+ - when source extraction is enabled, `_meta.sourceExtraction` also includes diagnostics (`reason`, `stats`)
393
394
  - `leaves`: contract-native serialized leaves
394
395
  - `schemaFlatByLeaf`: flattened schema map per leaf
395
396
  - `sourceByLeaf` (when `includeSource` is true): AST-derived definition + schema source metadata keyed by `METHOD path`
@@ -1 +1 @@
1
- export declare const DEFAULT_VIEWER_TEMPLATE = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Finalized Leaves Viewer</title>\n <style>\n :root {\n --bg: #f5f7fb;\n --surface: #ffffff;\n --border: #d6ddea;\n --text: #172033;\n --muted: #5b6680;\n --accent: #1858c6;\n }\n body {\n margin: 0;\n font-family: 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;\n color: var(--text);\n background: linear-gradient(180deg, var(--bg), #eef2fa);\n }\n .wrap { max-width: 1100px; margin: 0 auto; padding: 20px; }\n .card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px; }\n .meta { color: var(--muted); font-size: 12px; }\n #results { margin-top: 12px; display: grid; gap: 8px; }\n details { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 8px 10px; }\n summary { cursor: pointer; font-weight: 700; color: var(--accent); }\n pre { margin: 10px 0 0; overflow: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px; background: #fafcff; }\n </style>\n </head>\n <body>\n <div class=\"wrap\">\n <h1>Finalized Leaves Viewer (Baked)</h1>\n <div class=\"card\">\n <div id=\"status\" class=\"meta\">Waiting for baked payload...</div>\n </div>\n <div id=\"results\"></div>\n </div>\n\n <!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->\n\n <script>\n const statusEl = document.getElementById('status')\n const resultsEl = document.getElementById('results')\n const payload = window.__FINALIZED_LEAVES_PAYLOAD\n\n if (!payload || !Array.isArray(payload.leaves)) {\n statusEl.textContent = 'No baked payload found in this HTML file.'\n } else {\n statusEl.textContent = 'Loaded baked payload with ' + payload.leaves.length + ' routes.'\n\n const toHref = (source) => {\n if (!source || !source.file) return null\n const normalizedPath = String(source.file).replace(/\\\\/g, '/')\n const prefix = normalizedPath.startsWith('/') ? 'file://' : 'file:///'\n return prefix + encodeURI(normalizedPath)\n }\n\n payload.leaves.forEach((leaf) => {\n const details = document.createElement('details')\n const summary = document.createElement('summary')\n summary.textContent = String(leaf.method || '').toUpperCase() + ' ' + (leaf.path || '')\n\n const pre = document.createElement('pre')\n pre.textContent = JSON.stringify(leaf, null, 2)\n\n const source = payload.sourceByLeaf && payload.sourceByLeaf[leaf.key]\n let sourceWrap = null\n if (source) {\n sourceWrap = document.createElement('div')\n sourceWrap.className = 'meta'\n\n const definitionHref = toHref(source.definition)\n if (definitionHref) {\n const label = document.createElement('div')\n const link = document.createElement('a')\n link.href = definitionHref\n link.target = '_blank'\n link.rel = 'noopener noreferrer'\n link.textContent =\n 'definition: ' +\n source.definition.file +\n ':' +\n source.definition.line +\n ':' +\n source.definition.column\n label.appendChild(link)\n sourceWrap.appendChild(label)\n }\n\n if (source.schemas && typeof source.schemas === 'object') {\n Object.entries(source.schemas).forEach(([name, schema]) => {\n if (!schema) return\n const href = toHref(schema)\n const row = document.createElement('div')\n if (href) {\n const link = document.createElement('a')\n link.href = href\n link.target = '_blank'\n link.rel = 'noopener noreferrer'\n link.textContent =\n name +\n ': ' +\n (schema.sourceName || schema.tag || '<anonymous>') +\n ' (' +\n schema.file +\n ':' +\n schema.line +\n ':' +\n schema.column +\n ')'\n row.appendChild(link)\n } else {\n row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')\n }\n sourceWrap.appendChild(row)\n })\n }\n\n }\n\n details.appendChild(summary)\n if (sourceWrap) details.appendChild(sourceWrap)\n details.appendChild(pre)\n resultsEl.appendChild(details)\n })\n }\n </script>\n </body>\n</html>\n";
1
+ export declare const DEFAULT_VIEWER_TEMPLATE = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Finalized Leaves Viewer</title>\n <style>\n :root {\n --bg: #f5f7fb;\n --surface: #ffffff;\n --border: #d6ddea;\n --text: #172033;\n --muted: #5b6680;\n --accent: #1858c6;\n }\n body {\n margin: 0;\n font-family: 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;\n color: var(--text);\n background: linear-gradient(180deg, var(--bg), #eef2fa);\n }\n .wrap { max-width: 1100px; margin: 0 auto; padding: 20px; }\n .card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px; }\n .meta { color: var(--muted); font-size: 12px; }\n #results { margin-top: 12px; display: grid; gap: 8px; }\n details { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 8px 10px; }\n summary { cursor: pointer; font-weight: 700; color: var(--accent); }\n pre { margin: 10px 0 0; overflow: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px; background: #fafcff; }\n </style>\n </head>\n <body>\n <div class=\"wrap\">\n <h1>Finalized Leaves Viewer (Baked)</h1>\n <div class=\"card\">\n <div id=\"status\" class=\"meta\">Waiting for baked payload...</div>\n </div>\n <div id=\"results\"></div>\n </div>\n\n <!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->\n\n <script>\n const statusEl = document.getElementById('status')\n const resultsEl = document.getElementById('results')\n const payload = window.__FINALIZED_LEAVES_PAYLOAD\n\n if (!payload || !Array.isArray(payload.leaves)) {\n statusEl.textContent = 'No baked payload found in this HTML file.'\n } else {\n statusEl.textContent = 'Loaded baked payload with ' + payload.leaves.length + ' routes.'\n\n const toHref = (source) => {\n if (!source || !source.file) return null\n const normalizedPath = String(source.file).replace(/\\\\/g, '/')\n const prefix = normalizedPath.startsWith('/') ? 'file://' : 'file:///'\n return prefix + encodeURI(normalizedPath)\n }\n\n payload.leaves.forEach((leaf) => {\n const details = document.createElement('details')\n const summary = document.createElement('summary')\n summary.textContent = String(leaf.method || '').toUpperCase() + ' ' + (leaf.path || '')\n\n const pre = document.createElement('pre')\n pre.textContent = JSON.stringify(leaf, null, 2)\n\n const source = payload.sourceByLeaf && payload.sourceByLeaf[leaf.key]\n let sourceWrap = null\n if (source) {\n sourceWrap = document.createElement('div')\n sourceWrap.className = 'meta'\n\n const definitionHref = toHref(source.definition)\n if (definitionHref) {\n const label = document.createElement('div')\n const link = document.createElement('a')\n link.href = definitionHref\n link.target = '_blank'\n link.rel = 'noopener noreferrer'\n link.textContent = 'definition'\n label.appendChild(link)\n const location = document.createElement('span')\n location.textContent =\n ' (' +\n source.definition.file +\n ':' +\n source.definition.line +\n ':' +\n source.definition.column +\n ')'\n label.appendChild(location)\n sourceWrap.appendChild(label)\n }\n\n if (source.schemas && typeof source.schemas === 'object') {\n Object.entries(source.schemas).forEach(([name, schema]) => {\n if (!schema) return\n const href = toHref(schema)\n const row = document.createElement('div')\n if (href) {\n const link = document.createElement('a')\n link.href = href\n link.target = '_blank'\n link.rel = 'noopener noreferrer'\n link.textContent =\n name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')\n row.appendChild(link)\n const location = document.createElement('span')\n location.textContent =\n ' (' + schema.file + ':' + schema.line + ':' + schema.column + ')'\n row.appendChild(location)\n } else {\n row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')\n }\n sourceWrap.appendChild(row)\n })\n }\n\n }\n\n details.appendChild(summary)\n if (sourceWrap) details.appendChild(sourceWrap)\n details.appendChild(pre)\n resultsEl.appendChild(details)\n })\n }\n </script>\n </body>\n</html>\n";
@@ -2,7 +2,7 @@ import type { AnyLeafLowProfile } from '../core/routesV3.core';
2
2
  import type { FinalizedRegistry } from '../core/routesV3.finalize';
3
3
  import { type FlatSchemaMap } from './flattenSchema';
4
4
  import { type SerializeLeafContractOptions, type SerializedLeafContract } from './serializeLeafContract';
5
- import { type LeafSourceByKey } from './extractLeafSourceByAst';
5
+ import { type LeafSourceByKey, type SourceExtractionReason } from './extractLeafSourceByAst';
6
6
  export type ExportFinalizedLeavesInput = readonly AnyLeafLowProfile[] | FinalizedRegistry<readonly AnyLeafLowProfile[]>;
7
7
  export type ExportFinalizedLeavesMeta = {
8
8
  generatedAt: string;
@@ -14,6 +14,12 @@ export type ExportFinalizedLeavesMeta = {
14
14
  exportName?: string;
15
15
  tsconfigPath?: string;
16
16
  resolvedLeafCount: number;
17
+ reason?: SourceExtractionReason;
18
+ stats?: {
19
+ visitedSymbols: number;
20
+ visitedFiles: number;
21
+ unresolvedReferences: number;
22
+ };
17
23
  };
18
24
  fieldCatalog: {
19
25
  leaf: string[];
@@ -1,4 +1,5 @@
1
1
  declare const SCHEMA_KEYS: readonly ["bodySchema", "querySchema", "paramsSchema", "outputSchema", "outputMetaSchema", "queryExtensionSchema"];
2
+ export type SourceExtractionReason = 'module_not_in_program' | 'export_not_found' | 'unsupported_expression_shape' | 'resolved_zero_leaves';
2
3
  type SchemaKey = (typeof SCHEMA_KEYS)[number];
3
4
  export type LeafSourceLocation = {
4
5
  file: string;
@@ -22,9 +23,16 @@ export type ExtractLeafSourceByAstOptions = {
22
23
  tsconfigPath?: string;
23
24
  cwd?: string;
24
25
  };
26
+ export type ExtractLeafSourceByAstStats = {
27
+ visitedSymbols: number;
28
+ visitedFiles: number;
29
+ unresolvedReferences: number;
30
+ };
25
31
  export type ExtractLeafSourceByAstResult = {
26
32
  sourceByLeaf: LeafSourceByKey;
27
33
  tsconfigPath?: string;
34
+ reason?: SourceExtractionReason;
35
+ stats: ExtractLeafSourceByAstStats;
28
36
  };
29
37
  export declare function extractLeafSourceByAst({ modulePath, exportName, tsconfigPath, cwd, }: ExtractLeafSourceByAstOptions): ExtractLeafSourceByAstResult;
30
38
  export {};
package/dist/index.cjs CHANGED
@@ -825,14 +825,18 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
825
825
  link.href = definitionHref
826
826
  link.target = '_blank'
827
827
  link.rel = 'noopener noreferrer'
828
- link.textContent =
829
- 'definition: ' +
828
+ link.textContent = 'definition'
829
+ label.appendChild(link)
830
+ const location = document.createElement('span')
831
+ location.textContent =
832
+ ' (' +
830
833
  source.definition.file +
831
834
  ':' +
832
835
  source.definition.line +
833
836
  ':' +
834
- source.definition.column
835
- label.appendChild(link)
837
+ source.definition.column +
838
+ ')'
839
+ label.appendChild(location)
836
840
  sourceWrap.appendChild(label)
837
841
  }
838
842
 
@@ -847,17 +851,12 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
847
851
  link.target = '_blank'
848
852
  link.rel = 'noopener noreferrer'
849
853
  link.textContent =
850
- name +
851
- ': ' +
852
- (schema.sourceName || schema.tag || '<anonymous>') +
853
- ' (' +
854
- schema.file +
855
- ':' +
856
- schema.line +
857
- ':' +
858
- schema.column +
859
- ')'
854
+ name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
860
855
  row.appendChild(link)
856
+ const location = document.createElement('span')
857
+ location.textContent =
858
+ ' (' + schema.file + ':' + schema.line + ':' + schema.column + ')'
859
+ row.appendChild(location)
861
860
  } else {
862
861
  row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
863
862
  }
@@ -890,6 +889,7 @@ var SCHEMA_KEYS = [
890
889
  "queryExtensionSchema"
891
890
  ];
892
891
  var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
892
+ var MAX_RECURSION_DEPTH = 120;
893
893
  function toLocation(node) {
894
894
  const sourceFile = node.getSourceFile();
895
895
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(
@@ -901,6 +901,10 @@ function toLocation(node) {
901
901
  column: character + 1
902
902
  };
903
903
  }
904
+ function markFile(ctx, sourceFile) {
905
+ if (!sourceFile) return;
906
+ ctx.visitedFilePaths.add(import_node_path.default.resolve(sourceFile.fileName));
907
+ }
904
908
  function trimPreview(text, max = 80) {
905
909
  const normalized = text.replace(/\s+/g, " ").trim();
906
910
  return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
@@ -958,7 +962,7 @@ function findSourceFile(program, filePath) {
958
962
  const normalizedWanted = import_node_path.default.normalize(wanted);
959
963
  return program.getSourceFiles().find((file) => import_node_path.default.normalize(import_node_path.default.resolve(file.fileName)) === normalizedWanted);
960
964
  }
961
- function expressionFromDeclaration(declaration) {
965
+ function declarationToExpression(declaration) {
962
966
  if (!declaration) return void 0;
963
967
  if (import_typescript.default.isVariableDeclaration(declaration)) {
964
968
  return declaration.initializer;
@@ -975,14 +979,95 @@ function expressionFromDeclaration(declaration) {
975
979
  if (import_typescript.default.isBindingElement(declaration)) {
976
980
  return declaration.initializer;
977
981
  }
982
+ if (import_typescript.default.isEnumMember(declaration)) {
983
+ return declaration.initializer;
984
+ }
978
985
  return void 0;
979
986
  }
980
- function resolveIdentifierExpression(identifier, ctx) {
981
- const symbol = ctx.checker.getSymbolAtLocation(identifier);
982
- if (!symbol) return void 0;
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
+ }
983
999
  const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
984
- const declaration = target.declarations?.[0];
985
- return expressionFromDeclaration(declaration);
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;
986
1071
  }
987
1072
  function resolveExportExpression(sourceFile, exportName, checker) {
988
1073
  const moduleSymbol = getModuleSymbol(checker, sourceFile);
@@ -991,12 +1076,12 @@ function resolveExportExpression(sourceFile, exportName, checker) {
991
1076
  const explicit = exports2.find((entry) => entry.getName() === exportName);
992
1077
  if (explicit) {
993
1078
  const declaration = getAliasedSymbolIfNeeded(checker, explicit).declarations?.[0];
994
- return expressionFromDeclaration(declaration);
1079
+ return declarationToExpression(declaration);
995
1080
  }
996
1081
  const defaultExport = exports2.find((entry) => entry.getName() === "default");
997
1082
  if (!defaultExport) return void 0;
998
1083
  const defaultDecl = getAliasedSymbolIfNeeded(checker, defaultExport).declarations?.[0];
999
- const defaultExpr = expressionFromDeclaration(defaultDecl);
1084
+ const defaultExpr = declarationToExpression(defaultDecl);
1000
1085
  if (!defaultExpr) return void 0;
1001
1086
  const resolved = unwrapExpression(defaultExpr);
1002
1087
  if (!import_typescript.default.isObjectLiteralExpression(resolved)) return void 0;
@@ -1008,24 +1093,25 @@ function resolveExportExpression(sourceFile, exportName, checker) {
1008
1093
  }
1009
1094
  return void 0;
1010
1095
  }
1011
- function maybeObjectLiteral(expression, ctx) {
1012
- if (!expression) return void 0;
1096
+ function maybeObjectLiteral(expression, ctx, depth = 0) {
1097
+ if (!expression || depth > MAX_RECURSION_DEPTH) return void 0;
1013
1098
  const resolved = unwrapExpression(expression);
1099
+ markFile(ctx, resolved.getSourceFile());
1014
1100
  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;
1101
+ const referenced = evaluateExpressionReference(resolved, ctx, depth + 1);
1102
+ if (!referenced) return void 0;
1103
+ return maybeObjectLiteral(referenced, ctx, depth + 1);
1021
1104
  }
1022
- function collectSchemaExpressionsFromObject(objectLiteral, ctx) {
1105
+ function collectSchemaExpressionsFromObject(objectLiteral, ctx, depth) {
1023
1106
  const schemas = {};
1024
1107
  for (const property of objectLiteral.properties) {
1025
1108
  if (import_typescript.default.isSpreadAssignment(property)) {
1026
- const spreadObject = maybeObjectLiteral(property.expression, ctx);
1109
+ const spreadObject = maybeObjectLiteral(property.expression, ctx, depth + 1);
1027
1110
  if (!spreadObject) continue;
1028
- Object.assign(schemas, collectSchemaExpressionsFromObject(spreadObject, ctx));
1111
+ Object.assign(
1112
+ schemas,
1113
+ collectSchemaExpressionsFromObject(spreadObject, ctx, depth + 1)
1114
+ );
1029
1115
  continue;
1030
1116
  }
1031
1117
  if (import_typescript.default.isPropertyAssignment(property)) {
@@ -1042,10 +1128,10 @@ function collectSchemaExpressionsFromObject(objectLiteral, ctx) {
1042
1128
  }
1043
1129
  return schemas;
1044
1130
  }
1045
- function extractSchemaExpressions(cfgExpression, ctx) {
1046
- const objectLiteral = maybeObjectLiteral(cfgExpression, ctx);
1131
+ function extractSchemaExpressions(cfgExpression, ctx, depth) {
1132
+ const objectLiteral = maybeObjectLiteral(cfgExpression, ctx, depth);
1047
1133
  if (!objectLiteral) return {};
1048
- return collectSchemaExpressionsFromObject(objectLiteral, ctx);
1134
+ return collectSchemaExpressionsFromObject(objectLiteral, ctx, depth);
1049
1135
  }
1050
1136
  function getNearestVariableName(node) {
1051
1137
  let current = node;
@@ -1057,31 +1143,45 @@ function getNearestVariableName(node) {
1057
1143
  }
1058
1144
  return void 0;
1059
1145
  }
1060
- function evaluateBranchExpression(expression, ctx) {
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) {
1061
1155
  const resolved = unwrapExpression(expression);
1156
+ markFile(ctx, resolved.getSourceFile());
1157
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
1062
1158
  if (import_typescript.default.isIdentifier(resolved)) {
1063
- const valueExpr = resolveIdentifierExpression(resolved, ctx);
1159
+ const valueExpr = resolveIdentifierExpression(resolved, ctx, depth + 1);
1064
1160
  if (!valueExpr) return void 0;
1065
- return evaluateBranchExpression(valueExpr, ctx);
1161
+ return evaluateBranchExpression(valueExpr, ctx, depth + 1);
1066
1162
  }
1067
1163
  if (!import_typescript.default.isCallExpression(resolved)) return void 0;
1068
1164
  const call = resolved;
1069
1165
  if (import_typescript.default.isIdentifier(call.expression) && call.expression.text === "resource") {
1070
1166
  const firstArg = call.arguments[0];
1071
- if (!firstArg || !import_typescript.default.isStringLiteralLike(firstArg)) return void 0;
1167
+ if (!firstArg || !import_typescript.default.isStringLiteralLike(firstArg)) {
1168
+ ctx.unsupportedShapeSeen = true;
1169
+ return void 0;
1170
+ }
1072
1171
  return { base: normalizeResourceBase(firstArg.text), leaves: [] };
1073
1172
  }
1074
- if (!import_typescript.default.isPropertyAccessExpression(call.expression)) return void 0;
1173
+ if (!import_typescript.default.isPropertyAccessExpression(call.expression)) {
1174
+ ctx.unsupportedShapeSeen = true;
1175
+ return void 0;
1176
+ }
1075
1177
  const owner = call.expression.expression;
1076
1178
  const method = call.expression.name.text;
1077
- const branch = evaluateBranchExpression(owner, ctx);
1179
+ const branch = evaluateBranchExpression(owner, ctx, depth + 1);
1078
1180
  if (!branch) return void 0;
1079
- if (method === "with") {
1080
- return branch;
1081
- }
1181
+ if (method === "with") return branch;
1082
1182
  if (HTTP_METHODS.has(method)) {
1083
1183
  const cfgExpression = call.arguments[0];
1084
- const schemas = extractSchemaExpressions(cfgExpression, ctx);
1184
+ const schemas = extractSchemaExpressions(cfgExpression, ctx, depth + 1);
1085
1185
  const nextLeaf = {
1086
1186
  method,
1087
1187
  path: branch.base,
@@ -1095,7 +1195,7 @@ function evaluateBranchExpression(expression, ctx) {
1095
1195
  }
1096
1196
  if (method === "sub") {
1097
1197
  const mountedLeaves = call.arguments.flatMap(
1098
- (arg) => evaluateLeavesFromExpression(arg, ctx)
1198
+ (arg) => evaluateLeavesFromExpression(arg, ctx, depth + 1)
1099
1199
  );
1100
1200
  const prefixed = mountedLeaves.map((leaf) => ({
1101
1201
  ...leaf,
@@ -1106,29 +1206,44 @@ function evaluateBranchExpression(expression, ctx) {
1106
1206
  leaves: [...branch.leaves, ...prefixed]
1107
1207
  };
1108
1208
  }
1209
+ ctx.unsupportedShapeSeen = true;
1109
1210
  return void 0;
1110
1211
  }
1111
- function evaluateLeavesFromExpression(expression, ctx) {
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) {
1112
1217
  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);
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);
1118
1223
  try {
1119
1224
  if (import_typescript.default.isIdentifier(resolved)) {
1120
- const valueExpr = resolveIdentifierExpression(resolved, ctx);
1225
+ const valueExpr = resolveIdentifierExpression(resolved, ctx, depth + 1);
1121
1226
  if (!valueExpr) return [];
1122
- return evaluateLeavesFromExpression(valueExpr, ctx);
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 [];
1123
1238
  }
1124
1239
  if (import_typescript.default.isArrayLiteralExpression(resolved)) {
1125
1240
  const leaves = [];
1126
1241
  for (const element of resolved.elements) {
1127
1242
  if (import_typescript.default.isSpreadElement(element)) {
1128
- leaves.push(...evaluateLeavesFromExpression(element.expression, ctx));
1243
+ leaves.push(...evaluateLeavesFromExpression(element.expression, ctx, depth + 1));
1129
1244
  continue;
1130
1245
  }
1131
- leaves.push(...evaluateLeavesFromExpression(element, ctx));
1246
+ leaves.push(...evaluateLeavesFromExpression(element, ctx, depth + 1));
1132
1247
  }
1133
1248
  return leaves;
1134
1249
  }
@@ -1138,34 +1253,43 @@ function evaluateLeavesFromExpression(expression, ctx) {
1138
1253
  if (callName === "finalize") {
1139
1254
  const arg = resolved.arguments[0];
1140
1255
  if (!arg) return [];
1141
- return evaluateLeavesFromExpression(arg, ctx);
1256
+ return evaluateLeavesFromExpression(arg, ctx, depth + 1);
1142
1257
  }
1143
1258
  if (callName === "mergeArrays") {
1144
1259
  return resolved.arguments.flatMap(
1145
- (arg) => evaluateLeavesFromExpression(arg, ctx)
1260
+ (arg) => evaluateLeavesFromExpression(arg, ctx, depth + 1)
1146
1261
  );
1147
1262
  }
1148
1263
  }
1149
1264
  if (import_typescript.default.isPropertyAccessExpression(resolved.expression)) {
1150
1265
  const prop = resolved.expression.name.text;
1151
1266
  if (prop === "done") {
1152
- const branch2 = evaluateBranchExpression(resolved.expression.expression, ctx);
1267
+ const branch2 = evaluateBranchExpression(
1268
+ resolved.expression.expression,
1269
+ ctx,
1270
+ depth + 1
1271
+ );
1153
1272
  return branch2?.leaves ?? [];
1154
1273
  }
1155
1274
  }
1275
+ const refExpr = evaluateExpressionReference(resolved, ctx, depth + 1);
1276
+ if (refExpr) return evaluateLeavesFromExpression(refExpr, ctx, depth + 1);
1277
+ ctx.unsupportedShapeSeen = true;
1278
+ return [];
1156
1279
  }
1157
- const branch = evaluateBranchExpression(resolved, ctx);
1158
- return branch?.leaves ?? [];
1280
+ const branch = evaluateBranchExpression(resolved, ctx, depth + 1);
1281
+ if (branch) return branch.leaves;
1282
+ ctx.unsupportedShapeSeen = true;
1283
+ return [];
1159
1284
  } finally {
1160
- ctx.visitedExpressionStarts.delete(key);
1285
+ ctx.activeExpressionKeys.delete(key);
1161
1286
  }
1162
1287
  }
1163
1288
  function resolveSchemaMetadata(expression, ctx) {
1164
1289
  const resolved = unwrapExpression(expression);
1165
1290
  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];
1291
+ const symbol = resolveSymbolFromNode(resolved, ctx);
1292
+ const declaration = symbol?.declarations?.[0];
1169
1293
  const locationNode = declaration ? import_typescript.default.isVariableDeclaration(declaration) ? declaration.name : declaration : resolved;
1170
1294
  const sourceName = declaration && import_typescript.default.isVariableDeclaration(declaration) && import_typescript.default.isIdentifier(declaration.name) ? declaration.name.text : resolved.text;
1171
1295
  return {
@@ -1203,13 +1327,25 @@ function parseTsConfig(cwd, tsconfigPath) {
1203
1327
  void 0,
1204
1328
  resolvedTsconfig
1205
1329
  );
1206
- if (parsed.errors.length > 0) {
1330
+ const nonEmptyInputErrors = parsed.errors.filter((entry) => entry.code !== 18003);
1331
+ if (nonEmptyInputErrors.length > 0) {
1207
1332
  throw new Error(
1208
- parsed.errors.map((entry) => import_typescript.default.flattenDiagnosticMessageText(entry.messageText, "\n")).join("\n")
1333
+ nonEmptyInputErrors.map((entry) => import_typescript.default.flattenDiagnosticMessageText(entry.messageText, "\n")).join("\n")
1209
1334
  );
1210
1335
  }
1211
1336
  return { resolvedTsconfig, parsed };
1212
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
+ }
1213
1349
  function extractLeafSourceByAst({
1214
1350
  modulePath,
1215
1351
  exportName,
@@ -1218,32 +1354,43 @@ function extractLeafSourceByAst({
1218
1354
  }) {
1219
1355
  const parsedConfig = parseTsConfig(cwd, tsconfigPath);
1220
1356
  if (!parsedConfig) {
1221
- return { sourceByLeaf: {} };
1357
+ return {
1358
+ sourceByLeaf: {},
1359
+ reason: "module_not_in_program",
1360
+ stats: { visitedSymbols: 0, visitedFiles: 0, unresolvedReferences: 0 }
1361
+ };
1222
1362
  }
1223
- const program = import_typescript.default.createProgram({
1224
- rootNames: parsedConfig.parsed.fileNames,
1225
- options: parsedConfig.parsed.options
1226
- });
1363
+ const moduleAbs = import_node_path.default.resolve(cwd, modulePath);
1364
+ const program = createProgramWithFallback(parsedConfig.parsed, moduleAbs);
1227
1365
  const checker = program.getTypeChecker();
1228
- const moduleFile = findSourceFile(program, import_node_path.default.resolve(cwd, modulePath));
1366
+ const moduleFile = findSourceFile(program, moduleAbs);
1229
1367
  if (!moduleFile) {
1230
1368
  return {
1231
1369
  sourceByLeaf: {},
1232
- tsconfigPath: parsedConfig.resolvedTsconfig
1370
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1371
+ reason: "module_not_in_program",
1372
+ stats: { visitedSymbols: 0, visitedFiles: 0, unresolvedReferences: 0 }
1233
1373
  };
1234
1374
  }
1235
1375
  const exportedExpression = resolveExportExpression(moduleFile, exportName, checker);
1236
1376
  if (!exportedExpression) {
1237
1377
  return {
1238
1378
  sourceByLeaf: {},
1239
- tsconfigPath: parsedConfig.resolvedTsconfig
1379
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1380
+ reason: "export_not_found",
1381
+ stats: { visitedSymbols: 0, visitedFiles: 1, unresolvedReferences: 0 }
1240
1382
  };
1241
1383
  }
1242
1384
  const ctx = {
1243
1385
  checker,
1244
- visitedExpressionStarts: /* @__PURE__ */ new Set()
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
1245
1392
  };
1246
- const evaluatedLeaves = evaluateLeavesFromExpression(exportedExpression, ctx);
1393
+ const evaluatedLeaves = evaluateLeavesFromExpression(exportedExpression, ctx, 0);
1247
1394
  const sourceByLeaf = {};
1248
1395
  for (const leaf of evaluatedLeaves) {
1249
1396
  const key = buildLeafKey(leaf.method, leaf.path);
@@ -1251,17 +1398,27 @@ function extractLeafSourceByAst({
1251
1398
  ...toLocation(leaf.definitionNode),
1252
1399
  symbolName: getNearestVariableName(leaf.definitionNode)
1253
1400
  };
1401
+ ctx.visitedFilePaths.add(definition.file);
1254
1402
  const schemas = {};
1255
1403
  for (const schemaKey of SCHEMA_KEYS) {
1256
1404
  const schemaExpression = leaf.schemas[schemaKey];
1257
1405
  if (!schemaExpression) continue;
1258
- schemas[schemaKey] = resolveSchemaMetadata(schemaExpression, ctx);
1406
+ const schemaMeta = resolveSchemaMetadata(schemaExpression, ctx);
1407
+ ctx.visitedFilePaths.add(schemaMeta.file);
1408
+ schemas[schemaKey] = schemaMeta;
1259
1409
  }
1260
1410
  sourceByLeaf[key] = { definition, schemas };
1261
1411
  }
1412
+ const reason = Object.keys(sourceByLeaf).length > 0 ? void 0 : ctx.unsupportedShapeSeen ? "unsupported_expression_shape" : "resolved_zero_leaves";
1262
1413
  return {
1263
1414
  sourceByLeaf,
1264
- tsconfigPath: parsedConfig.resolvedTsconfig
1415
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1416
+ reason,
1417
+ stats: {
1418
+ visitedSymbols: ctx.visitedSymbolKeys.size,
1419
+ visitedFiles: ctx.visitedFilePaths.size,
1420
+ unresolvedReferences: ctx.unresolvedReferences
1421
+ }
1265
1422
  };
1266
1423
  }
1267
1424
 
@@ -1444,7 +1601,9 @@ async function exportFinalizedLeaves(input, options = {}) {
1444
1601
  modulePath: import_node_path2.default.resolve(modulePath),
1445
1602
  exportName,
1446
1603
  tsconfigPath: extracted.tsconfigPath,
1447
- resolvedLeafCount: Object.keys(sourceByLeaf).length
1604
+ resolvedLeafCount: Object.keys(sourceByLeaf).length,
1605
+ reason: extracted.reason,
1606
+ stats: extracted.stats
1448
1607
  };
1449
1608
  } else {
1450
1609
  sourceExtraction = {
@@ -1452,7 +1611,13 @@ async function exportFinalizedLeaves(input, options = {}) {
1452
1611
  enabled: false,
1453
1612
  exportName,
1454
1613
  tsconfigPath: options.tsconfigPath ? import_node_path2.default.resolve(options.tsconfigPath) : void 0,
1455
- resolvedLeafCount: 0
1614
+ resolvedLeafCount: 0,
1615
+ reason: "resolved_zero_leaves",
1616
+ stats: {
1617
+ visitedSymbols: 0,
1618
+ visitedFiles: 0,
1619
+ unresolvedReferences: 0
1620
+ }
1456
1621
  };
1457
1622
  }
1458
1623
  }