@emeryld/rrroutes-contract 2.7.4 → 2.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,63 @@ 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 =
763
+ 'definition: ' +
764
+ source.definition.file +
765
+ ':' +
766
+ source.definition.line +
767
+ ':' +
768
+ source.definition.column
769
+ label.appendChild(link)
770
+ sourceWrap.appendChild(label)
771
+ }
772
+
773
+ if (source.schemas && typeof source.schemas === 'object') {
774
+ Object.entries(source.schemas).forEach(([name, schema]) => {
775
+ if (!schema) return
776
+ const href = toHref(schema)
777
+ const row = document.createElement('div')
778
+ if (href) {
779
+ const link = document.createElement('a')
780
+ link.href = href
781
+ link.target = '_blank'
782
+ link.rel = 'noopener noreferrer'
783
+ link.textContent =
784
+ name +
785
+ ': ' +
786
+ (schema.sourceName || schema.tag || '<anonymous>') +
787
+ ' (' +
788
+ schema.file +
789
+ ':' +
790
+ schema.line +
791
+ ':' +
792
+ schema.column +
793
+ ')'
794
+ row.appendChild(link)
795
+ } else {
796
+ row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
797
+ }
798
+ sourceWrap.appendChild(row)
799
+ })
800
+ }
801
+
802
+ }
803
+
742
804
  details.appendChild(summary)
805
+ if (sourceWrap) details.appendChild(sourceWrap)
743
806
  details.appendChild(pre)
744
807
  resultsEl.appendChild(details)
745
808
  })
@@ -749,6 +812,393 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
749
812
  </html>
750
813
  `;
751
814
 
815
+ // src/export/extractLeafSourceByAst.ts
816
+ import path from "path";
817
+ import ts from "typescript";
818
+ var SCHEMA_KEYS = [
819
+ "bodySchema",
820
+ "querySchema",
821
+ "paramsSchema",
822
+ "outputSchema",
823
+ "outputMetaSchema",
824
+ "queryExtensionSchema"
825
+ ];
826
+ var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
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 trimPreview(text, max = 80) {
839
+ const normalized = text.replace(/\s+/g, " ").trim();
840
+ return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
841
+ }
842
+ function unwrapExpression(expression) {
843
+ let current = expression;
844
+ while (true) {
845
+ if (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isTypeAssertionExpression(current) || ts.isNonNullExpression(current) || ts.isSatisfiesExpression(current)) {
846
+ current = current.expression;
847
+ continue;
848
+ }
849
+ break;
850
+ }
851
+ return current;
852
+ }
853
+ function getTextName(name) {
854
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
855
+ return name.text;
856
+ }
857
+ return void 0;
858
+ }
859
+ function joinPaths2(parent, child) {
860
+ if (!parent) return child;
861
+ if (!child) return parent;
862
+ const trimmedParent = parent.endsWith("/") ? parent.replace(/\/+$/, "") : parent;
863
+ const trimmedChild = child.startsWith("/") ? child.replace(/^\/+/, "") : child;
864
+ if (!trimmedChild) return trimmedParent;
865
+ return `${trimmedParent}/${trimmedChild}`;
866
+ }
867
+ function buildLeafKey(method, leafPath) {
868
+ return `${method.toUpperCase()} ${leafPath}`;
869
+ }
870
+ function normalizeResourceBase(raw) {
871
+ if (raw === "") return "";
872
+ if (raw.startsWith("/") || raw.startsWith(":") || raw.startsWith("*")) {
873
+ return raw;
874
+ }
875
+ return `/${raw}`;
876
+ }
877
+ function getModuleSymbol(checker, sourceFile) {
878
+ return checker.getSymbolAtLocation(sourceFile);
879
+ }
880
+ function getAliasedSymbolIfNeeded(checker, symbol) {
881
+ if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) {
882
+ try {
883
+ return checker.getAliasedSymbol(symbol);
884
+ } catch {
885
+ return symbol;
886
+ }
887
+ }
888
+ return symbol;
889
+ }
890
+ function findSourceFile(program, filePath) {
891
+ const wanted = path.resolve(filePath);
892
+ const normalizedWanted = path.normalize(wanted);
893
+ return program.getSourceFiles().find((file) => path.normalize(path.resolve(file.fileName)) === normalizedWanted);
894
+ }
895
+ function expressionFromDeclaration(declaration) {
896
+ if (!declaration) return void 0;
897
+ if (ts.isVariableDeclaration(declaration)) {
898
+ return declaration.initializer;
899
+ }
900
+ if (ts.isExportAssignment(declaration)) {
901
+ return declaration.expression;
902
+ }
903
+ if (ts.isPropertyAssignment(declaration)) {
904
+ return declaration.initializer;
905
+ }
906
+ if (ts.isShorthandPropertyAssignment(declaration)) {
907
+ return declaration.name;
908
+ }
909
+ if (ts.isBindingElement(declaration)) {
910
+ return declaration.initializer;
911
+ }
912
+ return void 0;
913
+ }
914
+ function resolveIdentifierExpression(identifier, ctx) {
915
+ const symbol = ctx.checker.getSymbolAtLocation(identifier);
916
+ if (!symbol) return void 0;
917
+ const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
918
+ const declaration = target.declarations?.[0];
919
+ return expressionFromDeclaration(declaration);
920
+ }
921
+ function resolveExportExpression(sourceFile, exportName, checker) {
922
+ const moduleSymbol = getModuleSymbol(checker, sourceFile);
923
+ if (!moduleSymbol) return void 0;
924
+ const exports = checker.getExportsOfModule(moduleSymbol);
925
+ const explicit = exports.find((entry) => entry.getName() === exportName);
926
+ if (explicit) {
927
+ const declaration = getAliasedSymbolIfNeeded(checker, explicit).declarations?.[0];
928
+ return expressionFromDeclaration(declaration);
929
+ }
930
+ const defaultExport = exports.find((entry) => entry.getName() === "default");
931
+ if (!defaultExport) return void 0;
932
+ const defaultDecl = getAliasedSymbolIfNeeded(checker, defaultExport).declarations?.[0];
933
+ const defaultExpr = expressionFromDeclaration(defaultDecl);
934
+ if (!defaultExpr) return void 0;
935
+ const resolved = unwrapExpression(defaultExpr);
936
+ if (!ts.isObjectLiteralExpression(resolved)) return void 0;
937
+ for (const property of resolved.properties) {
938
+ if (!ts.isPropertyAssignment(property)) continue;
939
+ const propertyName = getTextName(property.name);
940
+ if (propertyName !== exportName) continue;
941
+ return property.initializer;
942
+ }
943
+ return void 0;
944
+ }
945
+ function maybeObjectLiteral(expression, ctx) {
946
+ if (!expression) return void 0;
947
+ const resolved = unwrapExpression(expression);
948
+ if (ts.isObjectLiteralExpression(resolved)) return resolved;
949
+ if (ts.isIdentifier(resolved)) {
950
+ const target = resolveIdentifierExpression(resolved, ctx);
951
+ if (!target) return void 0;
952
+ return maybeObjectLiteral(target, ctx);
953
+ }
954
+ return void 0;
955
+ }
956
+ function collectSchemaExpressionsFromObject(objectLiteral, ctx) {
957
+ const schemas = {};
958
+ for (const property of objectLiteral.properties) {
959
+ if (ts.isSpreadAssignment(property)) {
960
+ const spreadObject = maybeObjectLiteral(property.expression, ctx);
961
+ if (!spreadObject) continue;
962
+ Object.assign(schemas, collectSchemaExpressionsFromObject(spreadObject, ctx));
963
+ continue;
964
+ }
965
+ if (ts.isPropertyAssignment(property)) {
966
+ const key = getTextName(property.name);
967
+ if (!key || !SCHEMA_KEYS.includes(key)) continue;
968
+ schemas[key] = property.initializer;
969
+ continue;
970
+ }
971
+ if (ts.isShorthandPropertyAssignment(property)) {
972
+ const key = property.name.text;
973
+ if (!SCHEMA_KEYS.includes(key)) continue;
974
+ schemas[key] = property.name;
975
+ }
976
+ }
977
+ return schemas;
978
+ }
979
+ function extractSchemaExpressions(cfgExpression, ctx) {
980
+ const objectLiteral = maybeObjectLiteral(cfgExpression, ctx);
981
+ if (!objectLiteral) return {};
982
+ return collectSchemaExpressionsFromObject(objectLiteral, ctx);
983
+ }
984
+ function getNearestVariableName(node) {
985
+ let current = node;
986
+ while (current) {
987
+ if (ts.isVariableDeclaration(current) && ts.isIdentifier(current.name)) {
988
+ return current.name.text;
989
+ }
990
+ current = current.parent;
991
+ }
992
+ return void 0;
993
+ }
994
+ function evaluateBranchExpression(expression, ctx) {
995
+ const resolved = unwrapExpression(expression);
996
+ if (ts.isIdentifier(resolved)) {
997
+ const valueExpr = resolveIdentifierExpression(resolved, ctx);
998
+ if (!valueExpr) return void 0;
999
+ return evaluateBranchExpression(valueExpr, ctx);
1000
+ }
1001
+ if (!ts.isCallExpression(resolved)) return void 0;
1002
+ const call = resolved;
1003
+ if (ts.isIdentifier(call.expression) && call.expression.text === "resource") {
1004
+ const firstArg = call.arguments[0];
1005
+ if (!firstArg || !ts.isStringLiteralLike(firstArg)) return void 0;
1006
+ return { base: normalizeResourceBase(firstArg.text), leaves: [] };
1007
+ }
1008
+ if (!ts.isPropertyAccessExpression(call.expression)) return void 0;
1009
+ const owner = call.expression.expression;
1010
+ const method = call.expression.name.text;
1011
+ const branch = evaluateBranchExpression(owner, ctx);
1012
+ if (!branch) return void 0;
1013
+ if (method === "with") {
1014
+ return branch;
1015
+ }
1016
+ if (HTTP_METHODS.has(method)) {
1017
+ const cfgExpression = call.arguments[0];
1018
+ const schemas = extractSchemaExpressions(cfgExpression, ctx);
1019
+ const nextLeaf = {
1020
+ method,
1021
+ path: branch.base,
1022
+ definitionNode: call.expression.name,
1023
+ schemas
1024
+ };
1025
+ return {
1026
+ ...branch,
1027
+ leaves: [...branch.leaves, nextLeaf]
1028
+ };
1029
+ }
1030
+ if (method === "sub") {
1031
+ const mountedLeaves = call.arguments.flatMap(
1032
+ (arg) => evaluateLeavesFromExpression(arg, ctx)
1033
+ );
1034
+ const prefixed = mountedLeaves.map((leaf) => ({
1035
+ ...leaf,
1036
+ path: joinPaths2(branch.base, leaf.path)
1037
+ }));
1038
+ return {
1039
+ ...branch,
1040
+ leaves: [...branch.leaves, ...prefixed]
1041
+ };
1042
+ }
1043
+ return void 0;
1044
+ }
1045
+ function evaluateLeavesFromExpression(expression, ctx) {
1046
+ const resolved = unwrapExpression(expression);
1047
+ const key = resolved.getStart(resolved.getSourceFile());
1048
+ if (ctx.visitedExpressionStarts.has(key)) {
1049
+ return [];
1050
+ }
1051
+ ctx.visitedExpressionStarts.add(key);
1052
+ try {
1053
+ if (ts.isIdentifier(resolved)) {
1054
+ const valueExpr = resolveIdentifierExpression(resolved, ctx);
1055
+ if (!valueExpr) return [];
1056
+ return evaluateLeavesFromExpression(valueExpr, ctx);
1057
+ }
1058
+ if (ts.isArrayLiteralExpression(resolved)) {
1059
+ const leaves = [];
1060
+ for (const element of resolved.elements) {
1061
+ if (ts.isSpreadElement(element)) {
1062
+ leaves.push(...evaluateLeavesFromExpression(element.expression, ctx));
1063
+ continue;
1064
+ }
1065
+ leaves.push(...evaluateLeavesFromExpression(element, ctx));
1066
+ }
1067
+ return leaves;
1068
+ }
1069
+ if (ts.isCallExpression(resolved)) {
1070
+ if (ts.isIdentifier(resolved.expression)) {
1071
+ const callName = resolved.expression.text;
1072
+ if (callName === "finalize") {
1073
+ const arg = resolved.arguments[0];
1074
+ if (!arg) return [];
1075
+ return evaluateLeavesFromExpression(arg, ctx);
1076
+ }
1077
+ if (callName === "mergeArrays") {
1078
+ return resolved.arguments.flatMap(
1079
+ (arg) => evaluateLeavesFromExpression(arg, ctx)
1080
+ );
1081
+ }
1082
+ }
1083
+ if (ts.isPropertyAccessExpression(resolved.expression)) {
1084
+ const prop = resolved.expression.name.text;
1085
+ if (prop === "done") {
1086
+ const branch2 = evaluateBranchExpression(resolved.expression.expression, ctx);
1087
+ return branch2?.leaves ?? [];
1088
+ }
1089
+ }
1090
+ }
1091
+ const branch = evaluateBranchExpression(resolved, ctx);
1092
+ return branch?.leaves ?? [];
1093
+ } finally {
1094
+ ctx.visitedExpressionStarts.delete(key);
1095
+ }
1096
+ }
1097
+ function resolveSchemaMetadata(expression, ctx) {
1098
+ const resolved = unwrapExpression(expression);
1099
+ if (ts.isIdentifier(resolved)) {
1100
+ const symbol = ctx.checker.getSymbolAtLocation(resolved);
1101
+ const target = symbol ? getAliasedSymbolIfNeeded(ctx.checker, symbol) : void 0;
1102
+ const declaration = target?.declarations?.[0];
1103
+ const locationNode = declaration ? ts.isVariableDeclaration(declaration) ? declaration.name : declaration : resolved;
1104
+ const sourceName = declaration && ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name) ? declaration.name.text : resolved.text;
1105
+ return {
1106
+ ...toLocation(locationNode),
1107
+ sourceName,
1108
+ tag: declaration ? void 0 : "<anonymous>"
1109
+ };
1110
+ }
1111
+ if (ts.isPropertyAccessExpression(resolved) || ts.isElementAccessExpression(resolved)) {
1112
+ return {
1113
+ ...toLocation(resolved),
1114
+ sourceName: trimPreview(resolved.getText()),
1115
+ tag: "<expression>"
1116
+ };
1117
+ }
1118
+ return {
1119
+ ...toLocation(resolved),
1120
+ sourceName: trimPreview(resolved.getText()),
1121
+ tag: "<inline>"
1122
+ };
1123
+ }
1124
+ function parseTsConfig(cwd, tsconfigPath) {
1125
+ const resolvedTsconfig = tsconfigPath ? path.resolve(cwd, tsconfigPath) : ts.findConfigFile(cwd, ts.sys.fileExists, "tsconfig.json");
1126
+ if (!resolvedTsconfig) {
1127
+ return void 0;
1128
+ }
1129
+ const read = ts.readConfigFile(resolvedTsconfig, ts.sys.readFile);
1130
+ if (read.error) {
1131
+ throw new Error(ts.flattenDiagnosticMessageText(read.error.messageText, "\n"));
1132
+ }
1133
+ const parsed = ts.parseJsonConfigFileContent(
1134
+ read.config,
1135
+ ts.sys,
1136
+ path.dirname(resolvedTsconfig),
1137
+ void 0,
1138
+ resolvedTsconfig
1139
+ );
1140
+ if (parsed.errors.length > 0) {
1141
+ throw new Error(
1142
+ parsed.errors.map((entry) => ts.flattenDiagnosticMessageText(entry.messageText, "\n")).join("\n")
1143
+ );
1144
+ }
1145
+ return { resolvedTsconfig, parsed };
1146
+ }
1147
+ function extractLeafSourceByAst({
1148
+ modulePath,
1149
+ exportName,
1150
+ tsconfigPath,
1151
+ cwd = process.cwd()
1152
+ }) {
1153
+ const parsedConfig = parseTsConfig(cwd, tsconfigPath);
1154
+ if (!parsedConfig) {
1155
+ return { sourceByLeaf: {} };
1156
+ }
1157
+ const program = ts.createProgram({
1158
+ rootNames: parsedConfig.parsed.fileNames,
1159
+ options: parsedConfig.parsed.options
1160
+ });
1161
+ const checker = program.getTypeChecker();
1162
+ const moduleFile = findSourceFile(program, path.resolve(cwd, modulePath));
1163
+ if (!moduleFile) {
1164
+ return {
1165
+ sourceByLeaf: {},
1166
+ tsconfigPath: parsedConfig.resolvedTsconfig
1167
+ };
1168
+ }
1169
+ const exportedExpression = resolveExportExpression(moduleFile, exportName, checker);
1170
+ if (!exportedExpression) {
1171
+ return {
1172
+ sourceByLeaf: {},
1173
+ tsconfigPath: parsedConfig.resolvedTsconfig
1174
+ };
1175
+ }
1176
+ const ctx = {
1177
+ checker,
1178
+ visitedExpressionStarts: /* @__PURE__ */ new Set()
1179
+ };
1180
+ const evaluatedLeaves = evaluateLeavesFromExpression(exportedExpression, ctx);
1181
+ const sourceByLeaf = {};
1182
+ for (const leaf of evaluatedLeaves) {
1183
+ const key = buildLeafKey(leaf.method, leaf.path);
1184
+ const definition = {
1185
+ ...toLocation(leaf.definitionNode),
1186
+ symbolName: getNearestVariableName(leaf.definitionNode)
1187
+ };
1188
+ const schemas = {};
1189
+ for (const schemaKey of SCHEMA_KEYS) {
1190
+ const schemaExpression = leaf.schemas[schemaKey];
1191
+ if (!schemaExpression) continue;
1192
+ schemas[schemaKey] = resolveSchemaMetadata(schemaExpression, ctx);
1193
+ }
1194
+ sourceByLeaf[key] = { definition, schemas };
1195
+ }
1196
+ return {
1197
+ sourceByLeaf,
1198
+ tsconfigPath: parsedConfig.resolvedTsconfig
1199
+ };
1200
+ }
1201
+
752
1202
  // src/export/exportFinalizedLeaves.ts
753
1203
  function isRegistry(value) {
754
1204
  return typeof value === "object" && value !== null && "all" in value && "byKey" in value;
@@ -756,10 +1206,11 @@ function isRegistry(value) {
756
1206
  function getLeaves(input) {
757
1207
  return isRegistry(input) ? input.all : input;
758
1208
  }
759
- function buildMeta() {
1209
+ function buildMeta(sourceExtraction) {
760
1210
  return {
761
1211
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
762
1212
  description: "Finalized RRRoutes leaves export with serialized schemas and flattened schema paths for downstream processing.",
1213
+ sourceExtraction,
763
1214
  fieldCatalog: {
764
1215
  leaf: ["key", "method", "path", "cfg"],
765
1216
  cfg: [
@@ -816,20 +1267,20 @@ ${htmlTemplate}`;
816
1267
  }
817
1268
  async function resolveViewerTemplatePath(viewerTemplateFile) {
818
1269
  if (viewerTemplateFile) {
819
- const resolved = path.resolve(viewerTemplateFile);
1270
+ const resolved = path2.resolve(viewerTemplateFile);
820
1271
  await fs.access(resolved);
821
1272
  return resolved;
822
1273
  }
823
1274
  const candidates = [
824
- path.resolve(
1275
+ path2.resolve(
825
1276
  process.cwd(),
826
1277
  "node_modules/@emeryld/rrroutes-contract/tools/finalized-leaves-viewer.html"
827
1278
  ),
828
- path.resolve(
1279
+ path2.resolve(
829
1280
  process.cwd(),
830
1281
  "tools/finalized-leaves-viewer.html"
831
1282
  ),
832
- path.resolve(
1283
+ path2.resolve(
833
1284
  process.cwd(),
834
1285
  "packages/contract/tools/finalized-leaves-viewer.html"
835
1286
  )
@@ -844,8 +1295,8 @@ async function resolveViewerTemplatePath(viewerTemplateFile) {
844
1295
  return void 0;
845
1296
  }
846
1297
  async function writeJsonExport(payload, outFile) {
847
- const resolved = path.resolve(outFile);
848
- await fs.mkdir(path.dirname(resolved), { recursive: true });
1298
+ const resolved = path2.resolve(outFile);
1299
+ await fs.mkdir(path2.dirname(resolved), { recursive: true });
849
1300
  await fs.writeFile(resolved, `${JSON.stringify(payload, null, 2)}
850
1301
  `, "utf8");
851
1302
  return resolved;
@@ -854,13 +1305,13 @@ async function writeBakedHtmlExport(payload, htmlFile, viewerTemplateFile) {
854
1305
  const templatePath = await resolveViewerTemplatePath(viewerTemplateFile);
855
1306
  const template = templatePath ? await fs.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
856
1307
  const baked = injectPayloadIntoViewerHtml(template, payload);
857
- const resolved = path.resolve(htmlFile);
858
- await fs.mkdir(path.dirname(resolved), { recursive: true });
1308
+ const resolved = path2.resolve(htmlFile);
1309
+ await fs.mkdir(path2.dirname(resolved), { recursive: true });
859
1310
  await fs.writeFile(resolved, baked, "utf8");
860
1311
  return resolved;
861
1312
  }
862
1313
  async function openHtmlInBrowser(filePath) {
863
- const resolved = path.resolve(filePath);
1314
+ const resolved = path2.resolve(filePath);
864
1315
  const platform = process.platform;
865
1316
  if (platform === "darwin") {
866
1317
  spawn("open", [resolved], { detached: true, stdio: "ignore" }).unref();
@@ -904,10 +1355,46 @@ async function exportFinalizedLeaves(input, options = {}) {
904
1355
  const schemaFlatByLeaf = Object.fromEntries(
905
1356
  serializedLeaves.map((leaf) => [leaf.key, flattenLeafSchemas(leaf)])
906
1357
  );
1358
+ const sourceByLeaf = {};
1359
+ let sourceExtraction;
1360
+ if (options.includeSource) {
1361
+ const modulePath = options.sourceModulePath;
1362
+ const exportName = options.sourceExportName ?? "leaves";
1363
+ if (modulePath) {
1364
+ const extracted = extractLeafSourceByAst({
1365
+ modulePath,
1366
+ exportName,
1367
+ tsconfigPath: options.tsconfigPath
1368
+ });
1369
+ const allowedLeafKeys = new Set(serializedLeaves.map((leaf) => leaf.key));
1370
+ for (const [key, source] of Object.entries(extracted.sourceByLeaf)) {
1371
+ if (allowedLeafKeys.has(key)) {
1372
+ sourceByLeaf[key] = source;
1373
+ }
1374
+ }
1375
+ sourceExtraction = {
1376
+ mode: "ast",
1377
+ enabled: true,
1378
+ modulePath: path2.resolve(modulePath),
1379
+ exportName,
1380
+ tsconfigPath: extracted.tsconfigPath,
1381
+ resolvedLeafCount: Object.keys(sourceByLeaf).length
1382
+ };
1383
+ } else {
1384
+ sourceExtraction = {
1385
+ mode: "ast",
1386
+ enabled: false,
1387
+ exportName,
1388
+ tsconfigPath: options.tsconfigPath ? path2.resolve(options.tsconfigPath) : void 0,
1389
+ resolvedLeafCount: 0
1390
+ };
1391
+ }
1392
+ }
907
1393
  const payload = {
908
- _meta: buildMeta(),
1394
+ _meta: buildMeta(sourceExtraction),
909
1395
  leaves: serializedLeaves,
910
- schemaFlatByLeaf
1396
+ schemaFlatByLeaf,
1397
+ sourceByLeaf: options.includeSource && Object.keys(sourceByLeaf).length > 0 ? sourceByLeaf : void 0
911
1398
  };
912
1399
  if (options.outFile || options.htmlFile) {
913
1400
  await writeFinalizedLeavesExport(payload, {
@@ -921,14 +1408,19 @@ async function exportFinalizedLeaves(input, options = {}) {
921
1408
  }
922
1409
 
923
1410
  // src/export/exportFinalizedLeaves.cli.ts
924
- import path2 from "path";
1411
+ import path3 from "path";
925
1412
  import { pathToFileURL } from "url";
926
1413
  function parseFinalizedLeavesCliArgs(argv) {
927
1414
  const args = /* @__PURE__ */ new Map();
1415
+ let withSource = false;
928
1416
  for (let i = 0; i < argv.length; i += 1) {
929
1417
  const key = argv[i];
930
1418
  if (key === "--") continue;
931
1419
  if (!key.startsWith("--")) continue;
1420
+ if (key === "--with-source") {
1421
+ withSource = true;
1422
+ continue;
1423
+ }
932
1424
  const value = argv[i + 1];
933
1425
  if (!value || value.startsWith("--")) {
934
1426
  throw new Error(`Missing value for ${key}`);
@@ -943,14 +1435,16 @@ function parseFinalizedLeavesCliArgs(argv) {
943
1435
  return {
944
1436
  modulePath,
945
1437
  exportName: args.get("--export") ?? "leaves",
946
- outFile: args.get("--out") ?? "finalized-leaves.export.json"
1438
+ outFile: args.get("--out") ?? "finalized-leaves.export.json",
1439
+ withSource,
1440
+ tsconfigPath: args.get("--tsconfig") ?? void 0
947
1441
  };
948
1442
  }
949
1443
  async function loadFinalizedLeavesInput({
950
1444
  modulePath,
951
1445
  exportName
952
1446
  }) {
953
- const resolvedModule = path2.resolve(process.cwd(), modulePath);
1447
+ const resolvedModule = path3.resolve(process.cwd(), modulePath);
954
1448
  const mod = await import(pathToFileURL(resolvedModule).href);
955
1449
  const value = mod[exportName] ?? (mod.default && mod.default[exportName]);
956
1450
  if (!value) {
@@ -961,10 +1455,16 @@ async function loadFinalizedLeavesInput({
961
1455
  async function runExportFinalizedLeavesCli(argv) {
962
1456
  const args = parseFinalizedLeavesCliArgs(argv);
963
1457
  const input = await loadFinalizedLeavesInput(args);
964
- const payload = await exportFinalizedLeaves(input, { outFile: args.outFile });
1458
+ const payload = await exportFinalizedLeaves(input, {
1459
+ outFile: args.outFile,
1460
+ includeSource: args.withSource,
1461
+ tsconfigPath: args.tsconfigPath,
1462
+ sourceModulePath: args.modulePath,
1463
+ sourceExportName: args.exportName
1464
+ });
965
1465
  return {
966
1466
  payload,
967
- outFile: path2.resolve(args.outFile)
1467
+ outFile: path3.resolve(args.outFile)
968
1468
  };
969
1469
  }
970
1470
  export {
@@ -977,6 +1477,7 @@ export {
977
1477
  createSchemaIntrospector,
978
1478
  defineSocketEvents,
979
1479
  exportFinalizedLeaves,
1480
+ extractLeafSourceByAst,
980
1481
  finalize,
981
1482
  flattenLeafSchemas,
982
1483
  flattenSerializableSchema,