@emeryld/rrroutes-contract 2.7.6 → 2.7.8

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: #212121;\n --surface: #2a2a2a;\n --border: #4a4a4a;\n --text: #fffafa;\n --muted: #c8c2c2;\n --accent: #a764d3;\n }\n body {\n margin: 0;\n font-family: 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;\n color: var(--text);\n background: var(--bg);\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: #303030; color: var(--text); }\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 const sourceDisplay = (source) => {\n return ''\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 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 } 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
@@ -754,18 +754,18 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
754
754
  <title>Finalized Leaves Viewer</title>
755
755
  <style>
756
756
  :root {
757
- --bg: #f5f7fb;
758
- --surface: #ffffff;
759
- --border: #d6ddea;
760
- --text: #172033;
761
- --muted: #5b6680;
762
- --accent: #1858c6;
757
+ --bg: #212121;
758
+ --surface: #2a2a2a;
759
+ --border: #4a4a4a;
760
+ --text: #fffafa;
761
+ --muted: #c8c2c2;
762
+ --accent: #a764d3;
763
763
  }
764
764
  body {
765
765
  margin: 0;
766
766
  font-family: 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;
767
767
  color: var(--text);
768
- background: linear-gradient(180deg, var(--bg), #eef2fa);
768
+ background: var(--bg);
769
769
  }
770
770
  .wrap { max-width: 1100px; margin: 0 auto; padding: 20px; }
771
771
  .card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px; }
@@ -773,7 +773,7 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
773
773
  #results { margin-top: 12px; display: grid; gap: 8px; }
774
774
  details { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 8px 10px; }
775
775
  summary { cursor: pointer; font-weight: 700; color: var(--accent); }
776
- pre { margin: 10px 0 0; overflow: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px; background: #fafcff; }
776
+ pre { margin: 10px 0 0; overflow: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px; background: #303030; color: var(--text); }
777
777
  </style>
778
778
  </head>
779
779
  <body>
@@ -804,6 +804,10 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
804
804
  return prefix + encodeURI(normalizedPath)
805
805
  }
806
806
 
807
+ const sourceDisplay = (source) => {
808
+ return ''
809
+ }
810
+
807
811
  payload.leaves.forEach((leaf) => {
808
812
  const details = document.createElement('details')
809
813
  const summary = document.createElement('summary')
@@ -825,13 +829,7 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
825
829
  link.href = definitionHref
826
830
  link.target = '_blank'
827
831
  link.rel = 'noopener noreferrer'
828
- link.textContent =
829
- 'definition: ' +
830
- source.definition.file +
831
- ':' +
832
- source.definition.line +
833
- ':' +
834
- source.definition.column
832
+ link.textContent = 'definition'
835
833
  label.appendChild(link)
836
834
  sourceWrap.appendChild(label)
837
835
  }
@@ -847,16 +845,7 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
847
845
  link.target = '_blank'
848
846
  link.rel = 'noopener noreferrer'
849
847
  link.textContent =
850
- name +
851
- ': ' +
852
- (schema.sourceName || schema.tag || '<anonymous>') +
853
- ' (' +
854
- schema.file +
855
- ':' +
856
- schema.line +
857
- ':' +
858
- schema.column +
859
- ')'
848
+ name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
860
849
  row.appendChild(link)
861
850
  } else {
862
851
  row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
@@ -890,6 +879,7 @@ var SCHEMA_KEYS = [
890
879
  "queryExtensionSchema"
891
880
  ];
892
881
  var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
882
+ var MAX_RECURSION_DEPTH = 120;
893
883
  function toLocation(node) {
894
884
  const sourceFile = node.getSourceFile();
895
885
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(
@@ -901,6 +891,10 @@ function toLocation(node) {
901
891
  column: character + 1
902
892
  };
903
893
  }
894
+ function markFile(ctx, sourceFile) {
895
+ if (!sourceFile) return;
896
+ ctx.visitedFilePaths.add(import_node_path.default.resolve(sourceFile.fileName));
897
+ }
904
898
  function trimPreview(text, max = 80) {
905
899
  const normalized = text.replace(/\s+/g, " ").trim();
906
900
  return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
@@ -958,7 +952,7 @@ function findSourceFile(program, filePath) {
958
952
  const normalizedWanted = import_node_path.default.normalize(wanted);
959
953
  return program.getSourceFiles().find((file) => import_node_path.default.normalize(import_node_path.default.resolve(file.fileName)) === normalizedWanted);
960
954
  }
961
- function expressionFromDeclaration(declaration) {
955
+ function declarationToExpression(declaration) {
962
956
  if (!declaration) return void 0;
963
957
  if (import_typescript.default.isVariableDeclaration(declaration)) {
964
958
  return declaration.initializer;
@@ -975,14 +969,95 @@ function expressionFromDeclaration(declaration) {
975
969
  if (import_typescript.default.isBindingElement(declaration)) {
976
970
  return declaration.initializer;
977
971
  }
972
+ if (import_typescript.default.isEnumMember(declaration)) {
973
+ return declaration.initializer;
974
+ }
978
975
  return void 0;
979
976
  }
980
- function resolveIdentifierExpression(identifier, ctx) {
981
- const symbol = ctx.checker.getSymbolAtLocation(identifier);
982
- if (!symbol) return void 0;
977
+ function symbolKey(symbol) {
978
+ const decl = symbol.declarations?.[0];
979
+ if (!decl) return `${symbol.getName()}#${symbol.flags}`;
980
+ const file = import_node_path.default.resolve(decl.getSourceFile().fileName);
981
+ return `${file}:${decl.getStart()}:${symbol.getName()}`;
982
+ }
983
+ function resolveSymbolFromNode(node, ctx) {
984
+ const symbol = ctx.checker.getSymbolAtLocation(node);
985
+ if (!symbol) {
986
+ ctx.unresolvedReferences += 1;
987
+ return void 0;
988
+ }
983
989
  const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
984
- const declaration = target.declarations?.[0];
985
- return expressionFromDeclaration(declaration);
990
+ const key = symbolKey(target);
991
+ ctx.visitedSymbolKeys.add(key);
992
+ return target;
993
+ }
994
+ function getExpressionFromSymbol(symbol, ctx, depth) {
995
+ const key = symbolKey(symbol);
996
+ if (ctx.activeSymbols.has(key)) return void 0;
997
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
998
+ ctx.activeSymbols.add(key);
999
+ try {
1000
+ const declaration = symbol.declarations?.[0];
1001
+ markFile(ctx, declaration?.getSourceFile());
1002
+ const direct = declarationToExpression(declaration);
1003
+ if (direct) return direct;
1004
+ if (declaration && import_typescript.default.isImportSpecifier(declaration)) {
1005
+ const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
1006
+ if (target !== symbol) {
1007
+ return getExpressionFromSymbol(target, ctx, depth + 1);
1008
+ }
1009
+ }
1010
+ return void 0;
1011
+ } finally {
1012
+ ctx.activeSymbols.delete(key);
1013
+ }
1014
+ }
1015
+ function resolveIdentifierExpression(identifier, ctx, depth) {
1016
+ const symbol = resolveSymbolFromNode(identifier, ctx);
1017
+ if (!symbol) return void 0;
1018
+ return getExpressionFromSymbol(symbol, ctx, depth);
1019
+ }
1020
+ function resolvePropertyExpression(expression, ctx, depth) {
1021
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
1022
+ if (import_typescript.default.isPropertyAccessExpression(expression)) {
1023
+ const symbol = resolveSymbolFromNode(expression.name, ctx);
1024
+ if (symbol) {
1025
+ const fromSymbol = getExpressionFromSymbol(symbol, ctx, depth + 1);
1026
+ if (fromSymbol) return fromSymbol;
1027
+ }
1028
+ } else if (expression.argumentExpression) {
1029
+ const symbol = resolveSymbolFromNode(expression.argumentExpression, ctx);
1030
+ if (symbol) {
1031
+ const fromSymbol = getExpressionFromSymbol(symbol, ctx, depth + 1);
1032
+ if (fromSymbol) return fromSymbol;
1033
+ }
1034
+ }
1035
+ const ownerExpr = evaluateExpressionReference(expression.expression, ctx, depth + 1);
1036
+ const owner = ownerExpr ? maybeObjectLiteral(ownerExpr, ctx, depth + 1) : void 0;
1037
+ if (!owner) return void 0;
1038
+ const propName = import_typescript.default.isPropertyAccessExpression(expression) ? expression.name.text : expression.argumentExpression && import_typescript.default.isStringLiteralLike(expression.argumentExpression) ? expression.argumentExpression.text : void 0;
1039
+ if (!propName) return void 0;
1040
+ for (const property of owner.properties) {
1041
+ if (import_typescript.default.isPropertyAssignment(property) && getTextName(property.name) === propName) {
1042
+ return property.initializer;
1043
+ }
1044
+ if (import_typescript.default.isShorthandPropertyAssignment(property) && property.name.text === propName) {
1045
+ return property.name;
1046
+ }
1047
+ }
1048
+ return void 0;
1049
+ }
1050
+ function evaluateExpressionReference(expression, ctx, depth) {
1051
+ const resolved = unwrapExpression(expression);
1052
+ markFile(ctx, resolved.getSourceFile());
1053
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
1054
+ if (import_typescript.default.isIdentifier(resolved)) {
1055
+ return resolveIdentifierExpression(resolved, ctx, depth + 1);
1056
+ }
1057
+ if (import_typescript.default.isPropertyAccessExpression(resolved) || import_typescript.default.isElementAccessExpression(resolved)) {
1058
+ return resolvePropertyExpression(resolved, ctx, depth + 1);
1059
+ }
1060
+ return void 0;
986
1061
  }
987
1062
  function resolveExportExpression(sourceFile, exportName, checker) {
988
1063
  const moduleSymbol = getModuleSymbol(checker, sourceFile);
@@ -991,12 +1066,12 @@ function resolveExportExpression(sourceFile, exportName, checker) {
991
1066
  const explicit = exports2.find((entry) => entry.getName() === exportName);
992
1067
  if (explicit) {
993
1068
  const declaration = getAliasedSymbolIfNeeded(checker, explicit).declarations?.[0];
994
- return expressionFromDeclaration(declaration);
1069
+ return declarationToExpression(declaration);
995
1070
  }
996
1071
  const defaultExport = exports2.find((entry) => entry.getName() === "default");
997
1072
  if (!defaultExport) return void 0;
998
1073
  const defaultDecl = getAliasedSymbolIfNeeded(checker, defaultExport).declarations?.[0];
999
- const defaultExpr = expressionFromDeclaration(defaultDecl);
1074
+ const defaultExpr = declarationToExpression(defaultDecl);
1000
1075
  if (!defaultExpr) return void 0;
1001
1076
  const resolved = unwrapExpression(defaultExpr);
1002
1077
  if (!import_typescript.default.isObjectLiteralExpression(resolved)) return void 0;
@@ -1008,24 +1083,25 @@ function resolveExportExpression(sourceFile, exportName, checker) {
1008
1083
  }
1009
1084
  return void 0;
1010
1085
  }
1011
- function maybeObjectLiteral(expression, ctx) {
1012
- if (!expression) return void 0;
1086
+ function maybeObjectLiteral(expression, ctx, depth = 0) {
1087
+ if (!expression || depth > MAX_RECURSION_DEPTH) return void 0;
1013
1088
  const resolved = unwrapExpression(expression);
1089
+ markFile(ctx, resolved.getSourceFile());
1014
1090
  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;
1091
+ const referenced = evaluateExpressionReference(resolved, ctx, depth + 1);
1092
+ if (!referenced) return void 0;
1093
+ return maybeObjectLiteral(referenced, ctx, depth + 1);
1021
1094
  }
1022
- function collectSchemaExpressionsFromObject(objectLiteral, ctx) {
1095
+ function collectSchemaExpressionsFromObject(objectLiteral, ctx, depth) {
1023
1096
  const schemas = {};
1024
1097
  for (const property of objectLiteral.properties) {
1025
1098
  if (import_typescript.default.isSpreadAssignment(property)) {
1026
- const spreadObject = maybeObjectLiteral(property.expression, ctx);
1099
+ const spreadObject = maybeObjectLiteral(property.expression, ctx, depth + 1);
1027
1100
  if (!spreadObject) continue;
1028
- Object.assign(schemas, collectSchemaExpressionsFromObject(spreadObject, ctx));
1101
+ Object.assign(
1102
+ schemas,
1103
+ collectSchemaExpressionsFromObject(spreadObject, ctx, depth + 1)
1104
+ );
1029
1105
  continue;
1030
1106
  }
1031
1107
  if (import_typescript.default.isPropertyAssignment(property)) {
@@ -1042,10 +1118,10 @@ function collectSchemaExpressionsFromObject(objectLiteral, ctx) {
1042
1118
  }
1043
1119
  return schemas;
1044
1120
  }
1045
- function extractSchemaExpressions(cfgExpression, ctx) {
1046
- const objectLiteral = maybeObjectLiteral(cfgExpression, ctx);
1121
+ function extractSchemaExpressions(cfgExpression, ctx, depth) {
1122
+ const objectLiteral = maybeObjectLiteral(cfgExpression, ctx, depth);
1047
1123
  if (!objectLiteral) return {};
1048
- return collectSchemaExpressionsFromObject(objectLiteral, ctx);
1124
+ return collectSchemaExpressionsFromObject(objectLiteral, ctx, depth);
1049
1125
  }
1050
1126
  function getNearestVariableName(node) {
1051
1127
  let current = node;
@@ -1057,31 +1133,45 @@ function getNearestVariableName(node) {
1057
1133
  }
1058
1134
  return void 0;
1059
1135
  }
1060
- function evaluateBranchExpression(expression, ctx) {
1136
+ function isAllAccess(expression) {
1137
+ if (import_typescript.default.isPropertyAccessExpression(expression)) {
1138
+ return expression.name.text === "all";
1139
+ }
1140
+ return Boolean(
1141
+ expression.argumentExpression && import_typescript.default.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === "all"
1142
+ );
1143
+ }
1144
+ function evaluateBranchExpression(expression, ctx, depth) {
1061
1145
  const resolved = unwrapExpression(expression);
1146
+ markFile(ctx, resolved.getSourceFile());
1147
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
1062
1148
  if (import_typescript.default.isIdentifier(resolved)) {
1063
- const valueExpr = resolveIdentifierExpression(resolved, ctx);
1149
+ const valueExpr = resolveIdentifierExpression(resolved, ctx, depth + 1);
1064
1150
  if (!valueExpr) return void 0;
1065
- return evaluateBranchExpression(valueExpr, ctx);
1151
+ return evaluateBranchExpression(valueExpr, ctx, depth + 1);
1066
1152
  }
1067
1153
  if (!import_typescript.default.isCallExpression(resolved)) return void 0;
1068
1154
  const call = resolved;
1069
1155
  if (import_typescript.default.isIdentifier(call.expression) && call.expression.text === "resource") {
1070
1156
  const firstArg = call.arguments[0];
1071
- if (!firstArg || !import_typescript.default.isStringLiteralLike(firstArg)) return void 0;
1157
+ if (!firstArg || !import_typescript.default.isStringLiteralLike(firstArg)) {
1158
+ ctx.unsupportedShapeSeen = true;
1159
+ return void 0;
1160
+ }
1072
1161
  return { base: normalizeResourceBase(firstArg.text), leaves: [] };
1073
1162
  }
1074
- if (!import_typescript.default.isPropertyAccessExpression(call.expression)) return void 0;
1163
+ if (!import_typescript.default.isPropertyAccessExpression(call.expression)) {
1164
+ ctx.unsupportedShapeSeen = true;
1165
+ return void 0;
1166
+ }
1075
1167
  const owner = call.expression.expression;
1076
1168
  const method = call.expression.name.text;
1077
- const branch = evaluateBranchExpression(owner, ctx);
1169
+ const branch = evaluateBranchExpression(owner, ctx, depth + 1);
1078
1170
  if (!branch) return void 0;
1079
- if (method === "with") {
1080
- return branch;
1081
- }
1171
+ if (method === "with") return branch;
1082
1172
  if (HTTP_METHODS.has(method)) {
1083
1173
  const cfgExpression = call.arguments[0];
1084
- const schemas = extractSchemaExpressions(cfgExpression, ctx);
1174
+ const schemas = extractSchemaExpressions(cfgExpression, ctx, depth + 1);
1085
1175
  const nextLeaf = {
1086
1176
  method,
1087
1177
  path: branch.base,
@@ -1095,7 +1185,7 @@ function evaluateBranchExpression(expression, ctx) {
1095
1185
  }
1096
1186
  if (method === "sub") {
1097
1187
  const mountedLeaves = call.arguments.flatMap(
1098
- (arg) => evaluateLeavesFromExpression(arg, ctx)
1188
+ (arg) => evaluateLeavesFromExpression(arg, ctx, depth + 1)
1099
1189
  );
1100
1190
  const prefixed = mountedLeaves.map((leaf) => ({
1101
1191
  ...leaf,
@@ -1106,29 +1196,44 @@ function evaluateBranchExpression(expression, ctx) {
1106
1196
  leaves: [...branch.leaves, ...prefixed]
1107
1197
  };
1108
1198
  }
1199
+ ctx.unsupportedShapeSeen = true;
1109
1200
  return void 0;
1110
1201
  }
1111
- function evaluateLeavesFromExpression(expression, ctx) {
1202
+ function expressionKey(expression) {
1203
+ const source = expression.getSourceFile();
1204
+ return `${import_node_path.default.resolve(source.fileName)}:${expression.getStart(source)}:${expression.getEnd()}:${expression.kind}`;
1205
+ }
1206
+ function evaluateLeavesFromExpression(expression, ctx, depth = 0) {
1112
1207
  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);
1208
+ markFile(ctx, resolved.getSourceFile());
1209
+ const key = expressionKey(resolved);
1210
+ if (ctx.activeExpressionKeys.has(key)) return [];
1211
+ if (depth > MAX_RECURSION_DEPTH) return [];
1212
+ ctx.activeExpressionKeys.add(key);
1118
1213
  try {
1119
1214
  if (import_typescript.default.isIdentifier(resolved)) {
1120
- const valueExpr = resolveIdentifierExpression(resolved, ctx);
1215
+ const valueExpr = resolveIdentifierExpression(resolved, ctx, depth + 1);
1121
1216
  if (!valueExpr) return [];
1122
- return evaluateLeavesFromExpression(valueExpr, ctx);
1217
+ return evaluateLeavesFromExpression(valueExpr, ctx, depth + 1);
1218
+ }
1219
+ if (import_typescript.default.isPropertyAccessExpression(resolved) || import_typescript.default.isElementAccessExpression(resolved)) {
1220
+ if (isAllAccess(resolved)) {
1221
+ return evaluateLeavesFromExpression(resolved.expression, ctx, depth + 1);
1222
+ }
1223
+ const refExpr = resolvePropertyExpression(resolved, ctx, depth + 1);
1224
+ if (refExpr) {
1225
+ return evaluateLeavesFromExpression(refExpr, ctx, depth + 1);
1226
+ }
1227
+ return [];
1123
1228
  }
1124
1229
  if (import_typescript.default.isArrayLiteralExpression(resolved)) {
1125
1230
  const leaves = [];
1126
1231
  for (const element of resolved.elements) {
1127
1232
  if (import_typescript.default.isSpreadElement(element)) {
1128
- leaves.push(...evaluateLeavesFromExpression(element.expression, ctx));
1233
+ leaves.push(...evaluateLeavesFromExpression(element.expression, ctx, depth + 1));
1129
1234
  continue;
1130
1235
  }
1131
- leaves.push(...evaluateLeavesFromExpression(element, ctx));
1236
+ leaves.push(...evaluateLeavesFromExpression(element, ctx, depth + 1));
1132
1237
  }
1133
1238
  return leaves;
1134
1239
  }
@@ -1138,34 +1243,43 @@ function evaluateLeavesFromExpression(expression, ctx) {
1138
1243
  if (callName === "finalize") {
1139
1244
  const arg = resolved.arguments[0];
1140
1245
  if (!arg) return [];
1141
- return evaluateLeavesFromExpression(arg, ctx);
1246
+ return evaluateLeavesFromExpression(arg, ctx, depth + 1);
1142
1247
  }
1143
1248
  if (callName === "mergeArrays") {
1144
1249
  return resolved.arguments.flatMap(
1145
- (arg) => evaluateLeavesFromExpression(arg, ctx)
1250
+ (arg) => evaluateLeavesFromExpression(arg, ctx, depth + 1)
1146
1251
  );
1147
1252
  }
1148
1253
  }
1149
1254
  if (import_typescript.default.isPropertyAccessExpression(resolved.expression)) {
1150
1255
  const prop = resolved.expression.name.text;
1151
1256
  if (prop === "done") {
1152
- const branch2 = evaluateBranchExpression(resolved.expression.expression, ctx);
1257
+ const branch2 = evaluateBranchExpression(
1258
+ resolved.expression.expression,
1259
+ ctx,
1260
+ depth + 1
1261
+ );
1153
1262
  return branch2?.leaves ?? [];
1154
1263
  }
1155
1264
  }
1265
+ const refExpr = evaluateExpressionReference(resolved, ctx, depth + 1);
1266
+ if (refExpr) return evaluateLeavesFromExpression(refExpr, ctx, depth + 1);
1267
+ ctx.unsupportedShapeSeen = true;
1268
+ return [];
1156
1269
  }
1157
- const branch = evaluateBranchExpression(resolved, ctx);
1158
- return branch?.leaves ?? [];
1270
+ const branch = evaluateBranchExpression(resolved, ctx, depth + 1);
1271
+ if (branch) return branch.leaves;
1272
+ ctx.unsupportedShapeSeen = true;
1273
+ return [];
1159
1274
  } finally {
1160
- ctx.visitedExpressionStarts.delete(key);
1275
+ ctx.activeExpressionKeys.delete(key);
1161
1276
  }
1162
1277
  }
1163
1278
  function resolveSchemaMetadata(expression, ctx) {
1164
1279
  const resolved = unwrapExpression(expression);
1165
1280
  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];
1281
+ const symbol = resolveSymbolFromNode(resolved, ctx);
1282
+ const declaration = symbol?.declarations?.[0];
1169
1283
  const locationNode = declaration ? import_typescript.default.isVariableDeclaration(declaration) ? declaration.name : declaration : resolved;
1170
1284
  const sourceName = declaration && import_typescript.default.isVariableDeclaration(declaration) && import_typescript.default.isIdentifier(declaration.name) ? declaration.name.text : resolved.text;
1171
1285
  return {
@@ -1203,13 +1317,25 @@ function parseTsConfig(cwd, tsconfigPath) {
1203
1317
  void 0,
1204
1318
  resolvedTsconfig
1205
1319
  );
1206
- if (parsed.errors.length > 0) {
1320
+ const nonEmptyInputErrors = parsed.errors.filter((entry) => entry.code !== 18003);
1321
+ if (nonEmptyInputErrors.length > 0) {
1207
1322
  throw new Error(
1208
- parsed.errors.map((entry) => import_typescript.default.flattenDiagnosticMessageText(entry.messageText, "\n")).join("\n")
1323
+ nonEmptyInputErrors.map((entry) => import_typescript.default.flattenDiagnosticMessageText(entry.messageText, "\n")).join("\n")
1209
1324
  );
1210
1325
  }
1211
1326
  return { resolvedTsconfig, parsed };
1212
1327
  }
1328
+ function createProgramWithFallback(parsed, moduleFileAbs) {
1329
+ const base = import_typescript.default.createProgram({
1330
+ rootNames: parsed.fileNames,
1331
+ options: parsed.options
1332
+ });
1333
+ if (findSourceFile(base, moduleFileAbs)) {
1334
+ return base;
1335
+ }
1336
+ const rootNames = Array.from(/* @__PURE__ */ new Set([...parsed.fileNames, moduleFileAbs]));
1337
+ return import_typescript.default.createProgram({ rootNames, options: parsed.options });
1338
+ }
1213
1339
  function extractLeafSourceByAst({
1214
1340
  modulePath,
1215
1341
  exportName,
@@ -1218,32 +1344,43 @@ function extractLeafSourceByAst({
1218
1344
  }) {
1219
1345
  const parsedConfig = parseTsConfig(cwd, tsconfigPath);
1220
1346
  if (!parsedConfig) {
1221
- return { sourceByLeaf: {} };
1347
+ return {
1348
+ sourceByLeaf: {},
1349
+ reason: "module_not_in_program",
1350
+ stats: { visitedSymbols: 0, visitedFiles: 0, unresolvedReferences: 0 }
1351
+ };
1222
1352
  }
1223
- const program = import_typescript.default.createProgram({
1224
- rootNames: parsedConfig.parsed.fileNames,
1225
- options: parsedConfig.parsed.options
1226
- });
1353
+ const moduleAbs = import_node_path.default.resolve(cwd, modulePath);
1354
+ const program = createProgramWithFallback(parsedConfig.parsed, moduleAbs);
1227
1355
  const checker = program.getTypeChecker();
1228
- const moduleFile = findSourceFile(program, import_node_path.default.resolve(cwd, modulePath));
1356
+ const moduleFile = findSourceFile(program, moduleAbs);
1229
1357
  if (!moduleFile) {
1230
1358
  return {
1231
1359
  sourceByLeaf: {},
1232
- tsconfigPath: parsedConfig.resolvedTsconfig
1360
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1361
+ reason: "module_not_in_program",
1362
+ stats: { visitedSymbols: 0, visitedFiles: 0, unresolvedReferences: 0 }
1233
1363
  };
1234
1364
  }
1235
1365
  const exportedExpression = resolveExportExpression(moduleFile, exportName, checker);
1236
1366
  if (!exportedExpression) {
1237
1367
  return {
1238
1368
  sourceByLeaf: {},
1239
- tsconfigPath: parsedConfig.resolvedTsconfig
1369
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1370
+ reason: "export_not_found",
1371
+ stats: { visitedSymbols: 0, visitedFiles: 1, unresolvedReferences: 0 }
1240
1372
  };
1241
1373
  }
1242
1374
  const ctx = {
1243
1375
  checker,
1244
- visitedExpressionStarts: /* @__PURE__ */ new Set()
1376
+ activeExpressionKeys: /* @__PURE__ */ new Set(),
1377
+ activeSymbols: /* @__PURE__ */ new Set(),
1378
+ visitedSymbolKeys: /* @__PURE__ */ new Set(),
1379
+ visitedFilePaths: /* @__PURE__ */ new Set([import_node_path.default.resolve(moduleFile.fileName)]),
1380
+ unresolvedReferences: 0,
1381
+ unsupportedShapeSeen: false
1245
1382
  };
1246
- const evaluatedLeaves = evaluateLeavesFromExpression(exportedExpression, ctx);
1383
+ const evaluatedLeaves = evaluateLeavesFromExpression(exportedExpression, ctx, 0);
1247
1384
  const sourceByLeaf = {};
1248
1385
  for (const leaf of evaluatedLeaves) {
1249
1386
  const key = buildLeafKey(leaf.method, leaf.path);
@@ -1251,17 +1388,27 @@ function extractLeafSourceByAst({
1251
1388
  ...toLocation(leaf.definitionNode),
1252
1389
  symbolName: getNearestVariableName(leaf.definitionNode)
1253
1390
  };
1391
+ ctx.visitedFilePaths.add(definition.file);
1254
1392
  const schemas = {};
1255
1393
  for (const schemaKey of SCHEMA_KEYS) {
1256
1394
  const schemaExpression = leaf.schemas[schemaKey];
1257
1395
  if (!schemaExpression) continue;
1258
- schemas[schemaKey] = resolveSchemaMetadata(schemaExpression, ctx);
1396
+ const schemaMeta = resolveSchemaMetadata(schemaExpression, ctx);
1397
+ ctx.visitedFilePaths.add(schemaMeta.file);
1398
+ schemas[schemaKey] = schemaMeta;
1259
1399
  }
1260
1400
  sourceByLeaf[key] = { definition, schemas };
1261
1401
  }
1402
+ const reason = Object.keys(sourceByLeaf).length > 0 ? void 0 : ctx.unsupportedShapeSeen ? "unsupported_expression_shape" : "resolved_zero_leaves";
1262
1403
  return {
1263
1404
  sourceByLeaf,
1264
- tsconfigPath: parsedConfig.resolvedTsconfig
1405
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1406
+ reason,
1407
+ stats: {
1408
+ visitedSymbols: ctx.visitedSymbolKeys.size,
1409
+ visitedFiles: ctx.visitedFilePaths.size,
1410
+ unresolvedReferences: ctx.unresolvedReferences
1411
+ }
1265
1412
  };
1266
1413
  }
1267
1414
 
@@ -1444,7 +1591,9 @@ async function exportFinalizedLeaves(input, options = {}) {
1444
1591
  modulePath: import_node_path2.default.resolve(modulePath),
1445
1592
  exportName,
1446
1593
  tsconfigPath: extracted.tsconfigPath,
1447
- resolvedLeafCount: Object.keys(sourceByLeaf).length
1594
+ resolvedLeafCount: Object.keys(sourceByLeaf).length,
1595
+ reason: extracted.reason,
1596
+ stats: extracted.stats
1448
1597
  };
1449
1598
  } else {
1450
1599
  sourceExtraction = {
@@ -1452,7 +1601,13 @@ async function exportFinalizedLeaves(input, options = {}) {
1452
1601
  enabled: false,
1453
1602
  exportName,
1454
1603
  tsconfigPath: options.tsconfigPath ? import_node_path2.default.resolve(options.tsconfigPath) : void 0,
1455
- resolvedLeafCount: 0
1604
+ resolvedLeafCount: 0,
1605
+ reason: "resolved_zero_leaves",
1606
+ stats: {
1607
+ visitedSymbols: 0,
1608
+ visitedFiles: 0,
1609
+ unresolvedReferences: 0
1610
+ }
1456
1611
  };
1457
1612
  }
1458
1613
  }