@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.mjs CHANGED
@@ -42,9 +42,9 @@ function collectNestedFieldSuggestions(shape, prefix = []) {
42
42
  }
43
43
  return suggestions;
44
44
  }
45
- function compilePath(path3, params) {
46
- if (!params) return path3;
47
- const withParams = path3.replace(/:([A-Za-z0-9_]+)/g, (_, k) => {
45
+ function compilePath(path4, params) {
46
+ if (!params) return path4;
47
+ const withParams = path4.replace(/:([A-Za-z0-9_]+)/g, (_, k) => {
48
48
  const v = params[k];
49
49
  if (v === void 0 || v === null) throw new Error(`Missing param :${k}`);
50
50
  return String(v);
@@ -119,8 +119,8 @@ function buildLowProfileLeaf(leaf) {
119
119
  }
120
120
  };
121
121
  }
122
- var keyOf = (method, path3, encodeSafe) => {
123
- const key = `${method.toUpperCase()} ${path3}`;
122
+ var keyOf = (method, path4, encodeSafe) => {
123
+ const key = `${method.toUpperCase()} ${path4}`;
124
124
  return encodeSafe ? encodeURIComponent(key) : key;
125
125
  };
126
126
 
@@ -296,8 +296,8 @@ function joinPaths(parent, child) {
296
296
  if (!trimmedChild) return trimmedParent;
297
297
  return `${trimmedParent}/${trimmedChild}`;
298
298
  }
299
- function assertDynamicLayerUniqueness(path3, dynamicLayerMap) {
300
- const segments = path3.split("/").filter(Boolean);
299
+ function assertDynamicLayerUniqueness(path4, dynamicLayerMap) {
300
+ const segments = path4.split("/").filter(Boolean);
301
301
  if (segments.length === 0) return;
302
302
  for (let i = 0; i < segments.length; i++) {
303
303
  const segment = segments[i];
@@ -617,45 +617,45 @@ function normalizeType(schema) {
617
617
  return "unknown";
618
618
  }
619
619
  }
620
- function isNonEmptyPath(path3) {
621
- return typeof path3 === "string" && path3.length > 0;
620
+ function isNonEmptyPath(path4) {
621
+ return typeof path4 === "string" && path4.length > 0;
622
622
  }
623
- function setNode(out, path3, schema, inherited) {
624
- if (!isNonEmptyPath(path3)) return;
625
- out[path3] = {
623
+ function setNode(out, path4, schema, inherited) {
624
+ if (!isNonEmptyPath(path4)) return;
625
+ out[path4] = {
626
626
  type: normalizeType(schema),
627
627
  nullable: inherited.nullable || Boolean(schema.nullable),
628
628
  optional: inherited.optional || Boolean(schema.optional)
629
629
  };
630
630
  }
631
- function flattenInto(out, schema, path3, inherited) {
632
- if (!schema || !isNonEmptyPath(path3)) return;
631
+ function flattenInto(out, schema, path4, inherited) {
632
+ if (!schema || !isNonEmptyPath(path4)) return;
633
633
  const nextInherited = {
634
634
  optional: inherited.optional || Boolean(schema.optional),
635
635
  nullable: inherited.nullable || Boolean(schema.nullable)
636
636
  };
637
637
  if (schema.kind === "union" && Array.isArray(schema.union) && schema.union.length > 0) {
638
638
  schema.union.forEach((option, index) => {
639
- const optionPath = `${path3}-${index + 1}`;
639
+ const optionPath = `${path4}-${index + 1}`;
640
640
  flattenInto(out, option, optionPath, inherited);
641
641
  });
642
642
  return;
643
643
  }
644
- setNode(out, path3, schema, inherited);
644
+ setNode(out, path4, schema, inherited);
645
645
  if (schema.kind === "object" && schema.properties) {
646
646
  for (const [key, child] of Object.entries(schema.properties)) {
647
- const childPath = `${path3}.${key}`;
647
+ const childPath = `${path4}.${key}`;
648
648
  flattenInto(out, child, childPath, nextInherited);
649
649
  }
650
650
  return;
651
651
  }
652
652
  if (schema.kind === "array" && schema.element) {
653
- flattenInto(out, schema.element, `${path3}[]`, nextInherited);
653
+ flattenInto(out, schema.element, `${path4}[]`, nextInherited);
654
654
  }
655
655
  }
656
- function flattenSerializableSchema(schema, path3) {
656
+ function flattenSerializableSchema(schema, path4) {
657
657
  const out = {};
658
- flattenInto(out, schema, path3, { optional: false, nullable: false });
658
+ flattenInto(out, schema, path4, { optional: false, nullable: false });
659
659
  return out;
660
660
  }
661
661
  function isSerializedLeaf(value) {
@@ -676,7 +676,7 @@ function flattenLeafSchemas(leaf) {
676
676
 
677
677
  // src/export/exportFinalizedLeaves.ts
678
678
  import fs from "fs/promises";
679
- import path from "path";
679
+ import path2 from "path";
680
680
  import { spawn } from "child_process";
681
681
 
682
682
  // src/export/defaultViewerTemplate.ts
@@ -731,6 +731,13 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
731
731
  } else {
732
732
  statusEl.textContent = 'Loaded baked payload with ' + payload.leaves.length + ' routes.'
733
733
 
734
+ const toHref = (source) => {
735
+ if (!source || !source.file) return null
736
+ const normalizedPath = String(source.file).replace(/\\\\/g, '/')
737
+ const prefix = normalizedPath.startsWith('/') ? 'file://' : 'file:///'
738
+ return prefix + encodeURI(normalizedPath)
739
+ }
740
+
734
741
  payload.leaves.forEach((leaf) => {
735
742
  const details = document.createElement('details')
736
743
  const summary = document.createElement('summary')
@@ -739,7 +746,62 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
739
746
  const pre = document.createElement('pre')
740
747
  pre.textContent = JSON.stringify(leaf, null, 2)
741
748
 
749
+ const source = payload.sourceByLeaf && payload.sourceByLeaf[leaf.key]
750
+ let sourceWrap = null
751
+ if (source) {
752
+ sourceWrap = document.createElement('div')
753
+ sourceWrap.className = 'meta'
754
+
755
+ const definitionHref = toHref(source.definition)
756
+ if (definitionHref) {
757
+ const label = document.createElement('div')
758
+ const link = document.createElement('a')
759
+ link.href = definitionHref
760
+ link.target = '_blank'
761
+ link.rel = 'noopener noreferrer'
762
+ link.textContent = 'definition'
763
+ label.appendChild(link)
764
+ const location = document.createElement('span')
765
+ location.textContent =
766
+ ' (' +
767
+ source.definition.file +
768
+ ':' +
769
+ source.definition.line +
770
+ ':' +
771
+ source.definition.column +
772
+ ')'
773
+ label.appendChild(location)
774
+ sourceWrap.appendChild(label)
775
+ }
776
+
777
+ if (source.schemas && typeof source.schemas === 'object') {
778
+ Object.entries(source.schemas).forEach(([name, schema]) => {
779
+ if (!schema) return
780
+ const href = toHref(schema)
781
+ const row = document.createElement('div')
782
+ if (href) {
783
+ const link = document.createElement('a')
784
+ link.href = href
785
+ link.target = '_blank'
786
+ link.rel = 'noopener noreferrer'
787
+ link.textContent =
788
+ name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
789
+ row.appendChild(link)
790
+ const location = document.createElement('span')
791
+ location.textContent =
792
+ ' (' + schema.file + ':' + schema.line + ':' + schema.column + ')'
793
+ row.appendChild(location)
794
+ } else {
795
+ row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
796
+ }
797
+ sourceWrap.appendChild(row)
798
+ })
799
+ }
800
+
801
+ }
802
+
742
803
  details.appendChild(summary)
804
+ if (sourceWrap) details.appendChild(sourceWrap)
743
805
  details.appendChild(pre)
744
806
  resultsEl.appendChild(details)
745
807
  })
@@ -749,6 +811,551 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
749
811
  </html>
750
812
  `;
751
813
 
814
+ // src/export/extractLeafSourceByAst.ts
815
+ import path from "path";
816
+ import ts from "typescript";
817
+ var SCHEMA_KEYS = [
818
+ "bodySchema",
819
+ "querySchema",
820
+ "paramsSchema",
821
+ "outputSchema",
822
+ "outputMetaSchema",
823
+ "queryExtensionSchema"
824
+ ];
825
+ var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
826
+ var MAX_RECURSION_DEPTH = 120;
827
+ function toLocation(node) {
828
+ const sourceFile = node.getSourceFile();
829
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(
830
+ node.getStart(sourceFile)
831
+ );
832
+ return {
833
+ file: path.resolve(sourceFile.fileName),
834
+ line: line + 1,
835
+ column: character + 1
836
+ };
837
+ }
838
+ function markFile(ctx, sourceFile) {
839
+ if (!sourceFile) return;
840
+ ctx.visitedFilePaths.add(path.resolve(sourceFile.fileName));
841
+ }
842
+ function trimPreview(text, max = 80) {
843
+ const normalized = text.replace(/\s+/g, " ").trim();
844
+ return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
845
+ }
846
+ function unwrapExpression(expression) {
847
+ let current = expression;
848
+ while (true) {
849
+ if (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isTypeAssertionExpression(current) || ts.isNonNullExpression(current) || ts.isSatisfiesExpression(current)) {
850
+ current = current.expression;
851
+ continue;
852
+ }
853
+ break;
854
+ }
855
+ return current;
856
+ }
857
+ function getTextName(name) {
858
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
859
+ return name.text;
860
+ }
861
+ return void 0;
862
+ }
863
+ function joinPaths2(parent, child) {
864
+ if (!parent) return child;
865
+ if (!child) return parent;
866
+ const trimmedParent = parent.endsWith("/") ? parent.replace(/\/+$/, "") : parent;
867
+ const trimmedChild = child.startsWith("/") ? child.replace(/^\/+/, "") : child;
868
+ if (!trimmedChild) return trimmedParent;
869
+ return `${trimmedParent}/${trimmedChild}`;
870
+ }
871
+ function buildLeafKey(method, leafPath) {
872
+ return `${method.toUpperCase()} ${leafPath}`;
873
+ }
874
+ function normalizeResourceBase(raw) {
875
+ if (raw === "") return "";
876
+ if (raw.startsWith("/") || raw.startsWith(":") || raw.startsWith("*")) {
877
+ return raw;
878
+ }
879
+ return `/${raw}`;
880
+ }
881
+ function getModuleSymbol(checker, sourceFile) {
882
+ return checker.getSymbolAtLocation(sourceFile);
883
+ }
884
+ function getAliasedSymbolIfNeeded(checker, symbol) {
885
+ if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) {
886
+ try {
887
+ return checker.getAliasedSymbol(symbol);
888
+ } catch {
889
+ return symbol;
890
+ }
891
+ }
892
+ return symbol;
893
+ }
894
+ function findSourceFile(program, filePath) {
895
+ const wanted = path.resolve(filePath);
896
+ const normalizedWanted = path.normalize(wanted);
897
+ return program.getSourceFiles().find((file) => path.normalize(path.resolve(file.fileName)) === normalizedWanted);
898
+ }
899
+ function declarationToExpression(declaration) {
900
+ if (!declaration) return void 0;
901
+ if (ts.isVariableDeclaration(declaration)) {
902
+ return declaration.initializer;
903
+ }
904
+ if (ts.isExportAssignment(declaration)) {
905
+ return declaration.expression;
906
+ }
907
+ if (ts.isPropertyAssignment(declaration)) {
908
+ return declaration.initializer;
909
+ }
910
+ if (ts.isShorthandPropertyAssignment(declaration)) {
911
+ return declaration.name;
912
+ }
913
+ if (ts.isBindingElement(declaration)) {
914
+ return declaration.initializer;
915
+ }
916
+ if (ts.isEnumMember(declaration)) {
917
+ return declaration.initializer;
918
+ }
919
+ return void 0;
920
+ }
921
+ function symbolKey(symbol) {
922
+ const decl = symbol.declarations?.[0];
923
+ if (!decl) return `${symbol.getName()}#${symbol.flags}`;
924
+ const file = path.resolve(decl.getSourceFile().fileName);
925
+ return `${file}:${decl.getStart()}:${symbol.getName()}`;
926
+ }
927
+ function resolveSymbolFromNode(node, ctx) {
928
+ const symbol = ctx.checker.getSymbolAtLocation(node);
929
+ if (!symbol) {
930
+ ctx.unresolvedReferences += 1;
931
+ return void 0;
932
+ }
933
+ const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
934
+ const key = symbolKey(target);
935
+ ctx.visitedSymbolKeys.add(key);
936
+ return target;
937
+ }
938
+ function getExpressionFromSymbol(symbol, ctx, depth) {
939
+ const key = symbolKey(symbol);
940
+ if (ctx.activeSymbols.has(key)) return void 0;
941
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
942
+ ctx.activeSymbols.add(key);
943
+ try {
944
+ const declaration = symbol.declarations?.[0];
945
+ markFile(ctx, declaration?.getSourceFile());
946
+ const direct = declarationToExpression(declaration);
947
+ if (direct) return direct;
948
+ if (declaration && ts.isImportSpecifier(declaration)) {
949
+ const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
950
+ if (target !== symbol) {
951
+ return getExpressionFromSymbol(target, ctx, depth + 1);
952
+ }
953
+ }
954
+ return void 0;
955
+ } finally {
956
+ ctx.activeSymbols.delete(key);
957
+ }
958
+ }
959
+ function resolveIdentifierExpression(identifier, ctx, depth) {
960
+ const symbol = resolveSymbolFromNode(identifier, ctx);
961
+ if (!symbol) return void 0;
962
+ return getExpressionFromSymbol(symbol, ctx, depth);
963
+ }
964
+ function resolvePropertyExpression(expression, ctx, depth) {
965
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
966
+ if (ts.isPropertyAccessExpression(expression)) {
967
+ const symbol = resolveSymbolFromNode(expression.name, ctx);
968
+ if (symbol) {
969
+ const fromSymbol = getExpressionFromSymbol(symbol, ctx, depth + 1);
970
+ if (fromSymbol) return fromSymbol;
971
+ }
972
+ } else if (expression.argumentExpression) {
973
+ const symbol = resolveSymbolFromNode(expression.argumentExpression, ctx);
974
+ if (symbol) {
975
+ const fromSymbol = getExpressionFromSymbol(symbol, ctx, depth + 1);
976
+ if (fromSymbol) return fromSymbol;
977
+ }
978
+ }
979
+ const ownerExpr = evaluateExpressionReference(expression.expression, ctx, depth + 1);
980
+ const owner = ownerExpr ? maybeObjectLiteral(ownerExpr, ctx, depth + 1) : void 0;
981
+ if (!owner) return void 0;
982
+ const propName = ts.isPropertyAccessExpression(expression) ? expression.name.text : expression.argumentExpression && ts.isStringLiteralLike(expression.argumentExpression) ? expression.argumentExpression.text : void 0;
983
+ if (!propName) return void 0;
984
+ for (const property of owner.properties) {
985
+ if (ts.isPropertyAssignment(property) && getTextName(property.name) === propName) {
986
+ return property.initializer;
987
+ }
988
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === propName) {
989
+ return property.name;
990
+ }
991
+ }
992
+ return void 0;
993
+ }
994
+ function evaluateExpressionReference(expression, ctx, depth) {
995
+ const resolved = unwrapExpression(expression);
996
+ markFile(ctx, resolved.getSourceFile());
997
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
998
+ if (ts.isIdentifier(resolved)) {
999
+ return resolveIdentifierExpression(resolved, ctx, depth + 1);
1000
+ }
1001
+ if (ts.isPropertyAccessExpression(resolved) || ts.isElementAccessExpression(resolved)) {
1002
+ return resolvePropertyExpression(resolved, ctx, depth + 1);
1003
+ }
1004
+ return void 0;
1005
+ }
1006
+ function resolveExportExpression(sourceFile, exportName, checker) {
1007
+ const moduleSymbol = getModuleSymbol(checker, sourceFile);
1008
+ if (!moduleSymbol) return void 0;
1009
+ const exports = checker.getExportsOfModule(moduleSymbol);
1010
+ const explicit = exports.find((entry) => entry.getName() === exportName);
1011
+ if (explicit) {
1012
+ const declaration = getAliasedSymbolIfNeeded(checker, explicit).declarations?.[0];
1013
+ return declarationToExpression(declaration);
1014
+ }
1015
+ const defaultExport = exports.find((entry) => entry.getName() === "default");
1016
+ if (!defaultExport) return void 0;
1017
+ const defaultDecl = getAliasedSymbolIfNeeded(checker, defaultExport).declarations?.[0];
1018
+ const defaultExpr = declarationToExpression(defaultDecl);
1019
+ if (!defaultExpr) return void 0;
1020
+ const resolved = unwrapExpression(defaultExpr);
1021
+ if (!ts.isObjectLiteralExpression(resolved)) return void 0;
1022
+ for (const property of resolved.properties) {
1023
+ if (!ts.isPropertyAssignment(property)) continue;
1024
+ const propertyName = getTextName(property.name);
1025
+ if (propertyName !== exportName) continue;
1026
+ return property.initializer;
1027
+ }
1028
+ return void 0;
1029
+ }
1030
+ function maybeObjectLiteral(expression, ctx, depth = 0) {
1031
+ if (!expression || depth > MAX_RECURSION_DEPTH) return void 0;
1032
+ const resolved = unwrapExpression(expression);
1033
+ markFile(ctx, resolved.getSourceFile());
1034
+ if (ts.isObjectLiteralExpression(resolved)) return resolved;
1035
+ const referenced = evaluateExpressionReference(resolved, ctx, depth + 1);
1036
+ if (!referenced) return void 0;
1037
+ return maybeObjectLiteral(referenced, ctx, depth + 1);
1038
+ }
1039
+ function collectSchemaExpressionsFromObject(objectLiteral, ctx, depth) {
1040
+ const schemas = {};
1041
+ for (const property of objectLiteral.properties) {
1042
+ if (ts.isSpreadAssignment(property)) {
1043
+ const spreadObject = maybeObjectLiteral(property.expression, ctx, depth + 1);
1044
+ if (!spreadObject) continue;
1045
+ Object.assign(
1046
+ schemas,
1047
+ collectSchemaExpressionsFromObject(spreadObject, ctx, depth + 1)
1048
+ );
1049
+ continue;
1050
+ }
1051
+ if (ts.isPropertyAssignment(property)) {
1052
+ const key = getTextName(property.name);
1053
+ if (!key || !SCHEMA_KEYS.includes(key)) continue;
1054
+ schemas[key] = property.initializer;
1055
+ continue;
1056
+ }
1057
+ if (ts.isShorthandPropertyAssignment(property)) {
1058
+ const key = property.name.text;
1059
+ if (!SCHEMA_KEYS.includes(key)) continue;
1060
+ schemas[key] = property.name;
1061
+ }
1062
+ }
1063
+ return schemas;
1064
+ }
1065
+ function extractSchemaExpressions(cfgExpression, ctx, depth) {
1066
+ const objectLiteral = maybeObjectLiteral(cfgExpression, ctx, depth);
1067
+ if (!objectLiteral) return {};
1068
+ return collectSchemaExpressionsFromObject(objectLiteral, ctx, depth);
1069
+ }
1070
+ function getNearestVariableName(node) {
1071
+ let current = node;
1072
+ while (current) {
1073
+ if (ts.isVariableDeclaration(current) && ts.isIdentifier(current.name)) {
1074
+ return current.name.text;
1075
+ }
1076
+ current = current.parent;
1077
+ }
1078
+ return void 0;
1079
+ }
1080
+ function isAllAccess(expression) {
1081
+ if (ts.isPropertyAccessExpression(expression)) {
1082
+ return expression.name.text === "all";
1083
+ }
1084
+ return Boolean(
1085
+ expression.argumentExpression && ts.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === "all"
1086
+ );
1087
+ }
1088
+ function evaluateBranchExpression(expression, ctx, depth) {
1089
+ const resolved = unwrapExpression(expression);
1090
+ markFile(ctx, resolved.getSourceFile());
1091
+ if (depth > MAX_RECURSION_DEPTH) return void 0;
1092
+ if (ts.isIdentifier(resolved)) {
1093
+ const valueExpr = resolveIdentifierExpression(resolved, ctx, depth + 1);
1094
+ if (!valueExpr) return void 0;
1095
+ return evaluateBranchExpression(valueExpr, ctx, depth + 1);
1096
+ }
1097
+ if (!ts.isCallExpression(resolved)) return void 0;
1098
+ const call = resolved;
1099
+ if (ts.isIdentifier(call.expression) && call.expression.text === "resource") {
1100
+ const firstArg = call.arguments[0];
1101
+ if (!firstArg || !ts.isStringLiteralLike(firstArg)) {
1102
+ ctx.unsupportedShapeSeen = true;
1103
+ return void 0;
1104
+ }
1105
+ return { base: normalizeResourceBase(firstArg.text), leaves: [] };
1106
+ }
1107
+ if (!ts.isPropertyAccessExpression(call.expression)) {
1108
+ ctx.unsupportedShapeSeen = true;
1109
+ return void 0;
1110
+ }
1111
+ const owner = call.expression.expression;
1112
+ const method = call.expression.name.text;
1113
+ const branch = evaluateBranchExpression(owner, ctx, depth + 1);
1114
+ if (!branch) return void 0;
1115
+ if (method === "with") return branch;
1116
+ if (HTTP_METHODS.has(method)) {
1117
+ const cfgExpression = call.arguments[0];
1118
+ const schemas = extractSchemaExpressions(cfgExpression, ctx, depth + 1);
1119
+ const nextLeaf = {
1120
+ method,
1121
+ path: branch.base,
1122
+ definitionNode: call.expression.name,
1123
+ schemas
1124
+ };
1125
+ return {
1126
+ ...branch,
1127
+ leaves: [...branch.leaves, nextLeaf]
1128
+ };
1129
+ }
1130
+ if (method === "sub") {
1131
+ const mountedLeaves = call.arguments.flatMap(
1132
+ (arg) => evaluateLeavesFromExpression(arg, ctx, depth + 1)
1133
+ );
1134
+ const prefixed = mountedLeaves.map((leaf) => ({
1135
+ ...leaf,
1136
+ path: joinPaths2(branch.base, leaf.path)
1137
+ }));
1138
+ return {
1139
+ ...branch,
1140
+ leaves: [...branch.leaves, ...prefixed]
1141
+ };
1142
+ }
1143
+ ctx.unsupportedShapeSeen = true;
1144
+ return void 0;
1145
+ }
1146
+ function expressionKey(expression) {
1147
+ const source = expression.getSourceFile();
1148
+ return `${path.resolve(source.fileName)}:${expression.getStart(source)}:${expression.getEnd()}:${expression.kind}`;
1149
+ }
1150
+ function evaluateLeavesFromExpression(expression, ctx, depth = 0) {
1151
+ const resolved = unwrapExpression(expression);
1152
+ markFile(ctx, resolved.getSourceFile());
1153
+ const key = expressionKey(resolved);
1154
+ if (ctx.activeExpressionKeys.has(key)) return [];
1155
+ if (depth > MAX_RECURSION_DEPTH) return [];
1156
+ ctx.activeExpressionKeys.add(key);
1157
+ try {
1158
+ if (ts.isIdentifier(resolved)) {
1159
+ const valueExpr = resolveIdentifierExpression(resolved, ctx, depth + 1);
1160
+ if (!valueExpr) return [];
1161
+ return evaluateLeavesFromExpression(valueExpr, ctx, depth + 1);
1162
+ }
1163
+ if (ts.isPropertyAccessExpression(resolved) || ts.isElementAccessExpression(resolved)) {
1164
+ if (isAllAccess(resolved)) {
1165
+ return evaluateLeavesFromExpression(resolved.expression, ctx, depth + 1);
1166
+ }
1167
+ const refExpr = resolvePropertyExpression(resolved, ctx, depth + 1);
1168
+ if (refExpr) {
1169
+ return evaluateLeavesFromExpression(refExpr, ctx, depth + 1);
1170
+ }
1171
+ return [];
1172
+ }
1173
+ if (ts.isArrayLiteralExpression(resolved)) {
1174
+ const leaves = [];
1175
+ for (const element of resolved.elements) {
1176
+ if (ts.isSpreadElement(element)) {
1177
+ leaves.push(...evaluateLeavesFromExpression(element.expression, ctx, depth + 1));
1178
+ continue;
1179
+ }
1180
+ leaves.push(...evaluateLeavesFromExpression(element, ctx, depth + 1));
1181
+ }
1182
+ return leaves;
1183
+ }
1184
+ if (ts.isCallExpression(resolved)) {
1185
+ if (ts.isIdentifier(resolved.expression)) {
1186
+ const callName = resolved.expression.text;
1187
+ if (callName === "finalize") {
1188
+ const arg = resolved.arguments[0];
1189
+ if (!arg) return [];
1190
+ return evaluateLeavesFromExpression(arg, ctx, depth + 1);
1191
+ }
1192
+ if (callName === "mergeArrays") {
1193
+ return resolved.arguments.flatMap(
1194
+ (arg) => evaluateLeavesFromExpression(arg, ctx, depth + 1)
1195
+ );
1196
+ }
1197
+ }
1198
+ if (ts.isPropertyAccessExpression(resolved.expression)) {
1199
+ const prop = resolved.expression.name.text;
1200
+ if (prop === "done") {
1201
+ const branch2 = evaluateBranchExpression(
1202
+ resolved.expression.expression,
1203
+ ctx,
1204
+ depth + 1
1205
+ );
1206
+ return branch2?.leaves ?? [];
1207
+ }
1208
+ }
1209
+ const refExpr = evaluateExpressionReference(resolved, ctx, depth + 1);
1210
+ if (refExpr) return evaluateLeavesFromExpression(refExpr, ctx, depth + 1);
1211
+ ctx.unsupportedShapeSeen = true;
1212
+ return [];
1213
+ }
1214
+ const branch = evaluateBranchExpression(resolved, ctx, depth + 1);
1215
+ if (branch) return branch.leaves;
1216
+ ctx.unsupportedShapeSeen = true;
1217
+ return [];
1218
+ } finally {
1219
+ ctx.activeExpressionKeys.delete(key);
1220
+ }
1221
+ }
1222
+ function resolveSchemaMetadata(expression, ctx) {
1223
+ const resolved = unwrapExpression(expression);
1224
+ if (ts.isIdentifier(resolved)) {
1225
+ const symbol = resolveSymbolFromNode(resolved, ctx);
1226
+ const declaration = symbol?.declarations?.[0];
1227
+ const locationNode = declaration ? ts.isVariableDeclaration(declaration) ? declaration.name : declaration : resolved;
1228
+ const sourceName = declaration && ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name) ? declaration.name.text : resolved.text;
1229
+ return {
1230
+ ...toLocation(locationNode),
1231
+ sourceName,
1232
+ tag: declaration ? void 0 : "<anonymous>"
1233
+ };
1234
+ }
1235
+ if (ts.isPropertyAccessExpression(resolved) || ts.isElementAccessExpression(resolved)) {
1236
+ return {
1237
+ ...toLocation(resolved),
1238
+ sourceName: trimPreview(resolved.getText()),
1239
+ tag: "<expression>"
1240
+ };
1241
+ }
1242
+ return {
1243
+ ...toLocation(resolved),
1244
+ sourceName: trimPreview(resolved.getText()),
1245
+ tag: "<inline>"
1246
+ };
1247
+ }
1248
+ function parseTsConfig(cwd, tsconfigPath) {
1249
+ const resolvedTsconfig = tsconfigPath ? path.resolve(cwd, tsconfigPath) : ts.findConfigFile(cwd, ts.sys.fileExists, "tsconfig.json");
1250
+ if (!resolvedTsconfig) {
1251
+ return void 0;
1252
+ }
1253
+ const read = ts.readConfigFile(resolvedTsconfig, ts.sys.readFile);
1254
+ if (read.error) {
1255
+ throw new Error(ts.flattenDiagnosticMessageText(read.error.messageText, "\n"));
1256
+ }
1257
+ const parsed = ts.parseJsonConfigFileContent(
1258
+ read.config,
1259
+ ts.sys,
1260
+ path.dirname(resolvedTsconfig),
1261
+ void 0,
1262
+ resolvedTsconfig
1263
+ );
1264
+ const nonEmptyInputErrors = parsed.errors.filter((entry) => entry.code !== 18003);
1265
+ if (nonEmptyInputErrors.length > 0) {
1266
+ throw new Error(
1267
+ nonEmptyInputErrors.map((entry) => ts.flattenDiagnosticMessageText(entry.messageText, "\n")).join("\n")
1268
+ );
1269
+ }
1270
+ return { resolvedTsconfig, parsed };
1271
+ }
1272
+ function createProgramWithFallback(parsed, moduleFileAbs) {
1273
+ const base = ts.createProgram({
1274
+ rootNames: parsed.fileNames,
1275
+ options: parsed.options
1276
+ });
1277
+ if (findSourceFile(base, moduleFileAbs)) {
1278
+ return base;
1279
+ }
1280
+ const rootNames = Array.from(/* @__PURE__ */ new Set([...parsed.fileNames, moduleFileAbs]));
1281
+ return ts.createProgram({ rootNames, options: parsed.options });
1282
+ }
1283
+ function extractLeafSourceByAst({
1284
+ modulePath,
1285
+ exportName,
1286
+ tsconfigPath,
1287
+ cwd = process.cwd()
1288
+ }) {
1289
+ const parsedConfig = parseTsConfig(cwd, tsconfigPath);
1290
+ if (!parsedConfig) {
1291
+ return {
1292
+ sourceByLeaf: {},
1293
+ reason: "module_not_in_program",
1294
+ stats: { visitedSymbols: 0, visitedFiles: 0, unresolvedReferences: 0 }
1295
+ };
1296
+ }
1297
+ const moduleAbs = path.resolve(cwd, modulePath);
1298
+ const program = createProgramWithFallback(parsedConfig.parsed, moduleAbs);
1299
+ const checker = program.getTypeChecker();
1300
+ const moduleFile = findSourceFile(program, moduleAbs);
1301
+ if (!moduleFile) {
1302
+ return {
1303
+ sourceByLeaf: {},
1304
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1305
+ reason: "module_not_in_program",
1306
+ stats: { visitedSymbols: 0, visitedFiles: 0, unresolvedReferences: 0 }
1307
+ };
1308
+ }
1309
+ const exportedExpression = resolveExportExpression(moduleFile, exportName, checker);
1310
+ if (!exportedExpression) {
1311
+ return {
1312
+ sourceByLeaf: {},
1313
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1314
+ reason: "export_not_found",
1315
+ stats: { visitedSymbols: 0, visitedFiles: 1, unresolvedReferences: 0 }
1316
+ };
1317
+ }
1318
+ const ctx = {
1319
+ checker,
1320
+ activeExpressionKeys: /* @__PURE__ */ new Set(),
1321
+ activeSymbols: /* @__PURE__ */ new Set(),
1322
+ visitedSymbolKeys: /* @__PURE__ */ new Set(),
1323
+ visitedFilePaths: /* @__PURE__ */ new Set([path.resolve(moduleFile.fileName)]),
1324
+ unresolvedReferences: 0,
1325
+ unsupportedShapeSeen: false
1326
+ };
1327
+ const evaluatedLeaves = evaluateLeavesFromExpression(exportedExpression, ctx, 0);
1328
+ const sourceByLeaf = {};
1329
+ for (const leaf of evaluatedLeaves) {
1330
+ const key = buildLeafKey(leaf.method, leaf.path);
1331
+ const definition = {
1332
+ ...toLocation(leaf.definitionNode),
1333
+ symbolName: getNearestVariableName(leaf.definitionNode)
1334
+ };
1335
+ ctx.visitedFilePaths.add(definition.file);
1336
+ const schemas = {};
1337
+ for (const schemaKey of SCHEMA_KEYS) {
1338
+ const schemaExpression = leaf.schemas[schemaKey];
1339
+ if (!schemaExpression) continue;
1340
+ const schemaMeta = resolveSchemaMetadata(schemaExpression, ctx);
1341
+ ctx.visitedFilePaths.add(schemaMeta.file);
1342
+ schemas[schemaKey] = schemaMeta;
1343
+ }
1344
+ sourceByLeaf[key] = { definition, schemas };
1345
+ }
1346
+ const reason = Object.keys(sourceByLeaf).length > 0 ? void 0 : ctx.unsupportedShapeSeen ? "unsupported_expression_shape" : "resolved_zero_leaves";
1347
+ return {
1348
+ sourceByLeaf,
1349
+ tsconfigPath: parsedConfig.resolvedTsconfig,
1350
+ reason,
1351
+ stats: {
1352
+ visitedSymbols: ctx.visitedSymbolKeys.size,
1353
+ visitedFiles: ctx.visitedFilePaths.size,
1354
+ unresolvedReferences: ctx.unresolvedReferences
1355
+ }
1356
+ };
1357
+ }
1358
+
752
1359
  // src/export/exportFinalizedLeaves.ts
753
1360
  function isRegistry(value) {
754
1361
  return typeof value === "object" && value !== null && "all" in value && "byKey" in value;
@@ -756,10 +1363,11 @@ function isRegistry(value) {
756
1363
  function getLeaves(input) {
757
1364
  return isRegistry(input) ? input.all : input;
758
1365
  }
759
- function buildMeta() {
1366
+ function buildMeta(sourceExtraction) {
760
1367
  return {
761
1368
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
762
1369
  description: "Finalized RRRoutes leaves export with serialized schemas and flattened schema paths for downstream processing.",
1370
+ sourceExtraction,
763
1371
  fieldCatalog: {
764
1372
  leaf: ["key", "method", "path", "cfg"],
765
1373
  cfg: [
@@ -816,20 +1424,20 @@ ${htmlTemplate}`;
816
1424
  }
817
1425
  async function resolveViewerTemplatePath(viewerTemplateFile) {
818
1426
  if (viewerTemplateFile) {
819
- const resolved = path.resolve(viewerTemplateFile);
1427
+ const resolved = path2.resolve(viewerTemplateFile);
820
1428
  await fs.access(resolved);
821
1429
  return resolved;
822
1430
  }
823
1431
  const candidates = [
824
- path.resolve(
1432
+ path2.resolve(
825
1433
  process.cwd(),
826
1434
  "node_modules/@emeryld/rrroutes-contract/tools/finalized-leaves-viewer.html"
827
1435
  ),
828
- path.resolve(
1436
+ path2.resolve(
829
1437
  process.cwd(),
830
1438
  "tools/finalized-leaves-viewer.html"
831
1439
  ),
832
- path.resolve(
1440
+ path2.resolve(
833
1441
  process.cwd(),
834
1442
  "packages/contract/tools/finalized-leaves-viewer.html"
835
1443
  )
@@ -844,8 +1452,8 @@ async function resolveViewerTemplatePath(viewerTemplateFile) {
844
1452
  return void 0;
845
1453
  }
846
1454
  async function writeJsonExport(payload, outFile) {
847
- const resolved = path.resolve(outFile);
848
- await fs.mkdir(path.dirname(resolved), { recursive: true });
1455
+ const resolved = path2.resolve(outFile);
1456
+ await fs.mkdir(path2.dirname(resolved), { recursive: true });
849
1457
  await fs.writeFile(resolved, `${JSON.stringify(payload, null, 2)}
850
1458
  `, "utf8");
851
1459
  return resolved;
@@ -854,13 +1462,13 @@ async function writeBakedHtmlExport(payload, htmlFile, viewerTemplateFile) {
854
1462
  const templatePath = await resolveViewerTemplatePath(viewerTemplateFile);
855
1463
  const template = templatePath ? await fs.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
856
1464
  const baked = injectPayloadIntoViewerHtml(template, payload);
857
- const resolved = path.resolve(htmlFile);
858
- await fs.mkdir(path.dirname(resolved), { recursive: true });
1465
+ const resolved = path2.resolve(htmlFile);
1466
+ await fs.mkdir(path2.dirname(resolved), { recursive: true });
859
1467
  await fs.writeFile(resolved, baked, "utf8");
860
1468
  return resolved;
861
1469
  }
862
1470
  async function openHtmlInBrowser(filePath) {
863
- const resolved = path.resolve(filePath);
1471
+ const resolved = path2.resolve(filePath);
864
1472
  const platform = process.platform;
865
1473
  if (platform === "darwin") {
866
1474
  spawn("open", [resolved], { detached: true, stdio: "ignore" }).unref();
@@ -904,10 +1512,54 @@ async function exportFinalizedLeaves(input, options = {}) {
904
1512
  const schemaFlatByLeaf = Object.fromEntries(
905
1513
  serializedLeaves.map((leaf) => [leaf.key, flattenLeafSchemas(leaf)])
906
1514
  );
1515
+ const sourceByLeaf = {};
1516
+ let sourceExtraction;
1517
+ if (options.includeSource) {
1518
+ const modulePath = options.sourceModulePath;
1519
+ const exportName = options.sourceExportName ?? "leaves";
1520
+ if (modulePath) {
1521
+ const extracted = extractLeafSourceByAst({
1522
+ modulePath,
1523
+ exportName,
1524
+ tsconfigPath: options.tsconfigPath
1525
+ });
1526
+ const allowedLeafKeys = new Set(serializedLeaves.map((leaf) => leaf.key));
1527
+ for (const [key, source] of Object.entries(extracted.sourceByLeaf)) {
1528
+ if (allowedLeafKeys.has(key)) {
1529
+ sourceByLeaf[key] = source;
1530
+ }
1531
+ }
1532
+ sourceExtraction = {
1533
+ mode: "ast",
1534
+ enabled: true,
1535
+ modulePath: path2.resolve(modulePath),
1536
+ exportName,
1537
+ tsconfigPath: extracted.tsconfigPath,
1538
+ resolvedLeafCount: Object.keys(sourceByLeaf).length,
1539
+ reason: extracted.reason,
1540
+ stats: extracted.stats
1541
+ };
1542
+ } else {
1543
+ sourceExtraction = {
1544
+ mode: "ast",
1545
+ enabled: false,
1546
+ exportName,
1547
+ tsconfigPath: options.tsconfigPath ? path2.resolve(options.tsconfigPath) : void 0,
1548
+ resolvedLeafCount: 0,
1549
+ reason: "resolved_zero_leaves",
1550
+ stats: {
1551
+ visitedSymbols: 0,
1552
+ visitedFiles: 0,
1553
+ unresolvedReferences: 0
1554
+ }
1555
+ };
1556
+ }
1557
+ }
907
1558
  const payload = {
908
- _meta: buildMeta(),
1559
+ _meta: buildMeta(sourceExtraction),
909
1560
  leaves: serializedLeaves,
910
- schemaFlatByLeaf
1561
+ schemaFlatByLeaf,
1562
+ sourceByLeaf: options.includeSource && Object.keys(sourceByLeaf).length > 0 ? sourceByLeaf : void 0
911
1563
  };
912
1564
  if (options.outFile || options.htmlFile) {
913
1565
  await writeFinalizedLeavesExport(payload, {
@@ -921,14 +1573,19 @@ async function exportFinalizedLeaves(input, options = {}) {
921
1573
  }
922
1574
 
923
1575
  // src/export/exportFinalizedLeaves.cli.ts
924
- import path2 from "path";
1576
+ import path3 from "path";
925
1577
  import { pathToFileURL } from "url";
926
1578
  function parseFinalizedLeavesCliArgs(argv) {
927
1579
  const args = /* @__PURE__ */ new Map();
1580
+ let withSource = false;
928
1581
  for (let i = 0; i < argv.length; i += 1) {
929
1582
  const key = argv[i];
930
1583
  if (key === "--") continue;
931
1584
  if (!key.startsWith("--")) continue;
1585
+ if (key === "--with-source") {
1586
+ withSource = true;
1587
+ continue;
1588
+ }
932
1589
  const value = argv[i + 1];
933
1590
  if (!value || value.startsWith("--")) {
934
1591
  throw new Error(`Missing value for ${key}`);
@@ -943,14 +1600,16 @@ function parseFinalizedLeavesCliArgs(argv) {
943
1600
  return {
944
1601
  modulePath,
945
1602
  exportName: args.get("--export") ?? "leaves",
946
- outFile: args.get("--out") ?? "finalized-leaves.export.json"
1603
+ outFile: args.get("--out") ?? "finalized-leaves.export.json",
1604
+ withSource,
1605
+ tsconfigPath: args.get("--tsconfig") ?? void 0
947
1606
  };
948
1607
  }
949
1608
  async function loadFinalizedLeavesInput({
950
1609
  modulePath,
951
1610
  exportName
952
1611
  }) {
953
- const resolvedModule = path2.resolve(process.cwd(), modulePath);
1612
+ const resolvedModule = path3.resolve(process.cwd(), modulePath);
954
1613
  const mod = await import(pathToFileURL(resolvedModule).href);
955
1614
  const value = mod[exportName] ?? (mod.default && mod.default[exportName]);
956
1615
  if (!value) {
@@ -961,10 +1620,16 @@ async function loadFinalizedLeavesInput({
961
1620
  async function runExportFinalizedLeavesCli(argv) {
962
1621
  const args = parseFinalizedLeavesCliArgs(argv);
963
1622
  const input = await loadFinalizedLeavesInput(args);
964
- const payload = await exportFinalizedLeaves(input, { outFile: args.outFile });
1623
+ const payload = await exportFinalizedLeaves(input, {
1624
+ outFile: args.outFile,
1625
+ includeSource: args.withSource,
1626
+ tsconfigPath: args.tsconfigPath,
1627
+ sourceModulePath: args.modulePath,
1628
+ sourceExportName: args.exportName
1629
+ });
965
1630
  return {
966
1631
  payload,
967
- outFile: path2.resolve(args.outFile)
1632
+ outFile: path3.resolve(args.outFile)
968
1633
  };
969
1634
  }
970
1635
  export {
@@ -977,6 +1642,7 @@ export {
977
1642
  createSchemaIntrospector,
978
1643
  defineSocketEvents,
979
1644
  exportFinalizedLeaves,
1645
+ extractLeafSourceByAst,
980
1646
  finalize,
981
1647
  flattenLeafSchemas,
982
1648
  flattenSerializableSchema,