@contractspec/lib.source-extractors 2.7.6 → 2.7.10

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.js CHANGED
@@ -105,6 +105,42 @@ function generateOperationCode(endpoint, specName, options) {
105
105
  return lines.join(`
106
106
  `);
107
107
  }
108
+ // src/codegen/registry-gen.ts
109
+ function generateRegistry(operationFiles) {
110
+ const operationImports = operationFiles.filter((f) => f.type === "operation").map((f) => {
111
+ const name = f.path.replace(".ts", "").replace(/-/g, "_");
112
+ const specName = toPascalCase(name) + "Spec";
113
+ return { path: f.path, name, specName };
114
+ });
115
+ const lines = [
116
+ `/**`,
117
+ ` * Generated operation registry.`,
118
+ ` */`,
119
+ ``,
120
+ `import { OperationSpecRegistry } from '@contractspec/lib.contracts-spec';`,
121
+ ``
122
+ ];
123
+ for (const op of operationImports) {
124
+ const importPath = `./${op.path.replace(".ts", "")}`;
125
+ lines.push(`import { ${op.specName} } from '${importPath}';`);
126
+ }
127
+ lines.push(``);
128
+ lines.push(`export const operationRegistry = new OperationSpecRegistry();`);
129
+ lines.push(``);
130
+ for (const op of operationImports) {
131
+ lines.push(`operationRegistry.register(${op.specName});`);
132
+ }
133
+ lines.push(``);
134
+ return {
135
+ path: "registry.ts",
136
+ content: lines.join(`
137
+ `),
138
+ type: "registry"
139
+ };
140
+ }
141
+ function toPascalCase(str) {
142
+ return str.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
143
+ }
108
144
  // src/codegen/schema-gen.ts
109
145
  function generateSchema(schema, _options) {
110
146
  const fileName = `${toFileName2(schema.name)}.ts`;
@@ -185,42 +221,6 @@ function mapToZodType(tsType, optional) {
185
221
  }
186
222
  return optional ? `${zodType}.optional()` : zodType;
187
223
  }
188
- // src/codegen/registry-gen.ts
189
- function generateRegistry(operationFiles) {
190
- const operationImports = operationFiles.filter((f) => f.type === "operation").map((f) => {
191
- const name = f.path.replace(".ts", "").replace(/-/g, "_");
192
- const specName = toPascalCase(name) + "Spec";
193
- return { path: f.path, name, specName };
194
- });
195
- const lines = [
196
- `/**`,
197
- ` * Generated operation registry.`,
198
- ` */`,
199
- ``,
200
- `import { OperationSpecRegistry } from '@contractspec/lib.contracts-spec';`,
201
- ``
202
- ];
203
- for (const op of operationImports) {
204
- const importPath = `./${op.path.replace(".ts", "")}`;
205
- lines.push(`import { ${op.specName} } from '${importPath}';`);
206
- }
207
- lines.push(``);
208
- lines.push(`export const operationRegistry = new OperationSpecRegistry();`);
209
- lines.push(``);
210
- for (const op of operationImports) {
211
- lines.push(`operationRegistry.register(${op.specName});`);
212
- }
213
- lines.push(``);
214
- return {
215
- path: "registry.ts",
216
- content: lines.join(`
217
- `),
218
- type: "registry"
219
- };
220
- }
221
- function toPascalCase(str) {
222
- return str.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
223
- }
224
224
  // src/extractors/index.ts
225
225
  var exports_extractors = {};
226
226
  __export(exports_extractors, {
@@ -525,101 +525,54 @@ class BaseExtractor {
525
525
  ctx.ir.schemas.push(schema);
526
526
  }
527
527
  }
528
- // src/extractors/nestjs/extractor.ts
528
+ // src/extractors/elysia/extractor.ts
529
529
  var PATTERNS = {
530
- controller: /@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/g,
531
- route: /@(Get|Post|Put|Patch|Delete|Head|Options)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,
532
- body: /@Body\s*\(\s*\)/g,
533
- param: /@Param\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,
534
- query: /@Query\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,
535
- dto: /class\s+(\w+(?:Dto|DTO|Request|Response|Input|Output))\s*\{/g,
536
- classValidator: /@(IsString|IsNumber|IsBoolean|IsArray|IsOptional|IsNotEmpty|Min|Max|Length|Matches)/g
530
+ route: /\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
531
+ tSchema: /body:\s*t\.\w+/g
537
532
  };
538
533
 
539
- class NestJsExtractor extends BaseExtractor {
540
- id = "nestjs";
541
- name = "NestJS Extractor";
542
- frameworks = ["nestjs"];
543
- priority = 20;
534
+ class ElysiaExtractor extends BaseExtractor {
535
+ id = "elysia";
536
+ name = "Elysia Extractor";
537
+ frameworks = ["elysia"];
538
+ priority = 15;
544
539
  async doExtract(ctx) {
545
540
  const { project, options, fs } = ctx;
546
541
  const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
547
542
  const files = await fs.glob(pattern, { cwd: project.rootPath });
548
543
  ctx.ir.stats.filesScanned = files.length;
549
544
  for (const file of files) {
550
- if (file.includes("node_modules") || file.includes(".spec.") || file.includes(".test.")) {
545
+ if (file.includes("node_modules") || file.includes(".test."))
551
546
  continue;
552
- }
553
547
  const fullPath = `${project.rootPath}/${file}`;
554
548
  const content = await fs.readFile(fullPath);
555
- await this.extractControllers(ctx, file, content);
556
- await this.extractDtos(ctx, file, content);
557
- }
558
- }
559
- async extractControllers(ctx, file, content) {
560
- const controllerMatches = [...content.matchAll(PATTERNS.controller)];
561
- for (const controllerMatch of controllerMatches) {
562
- const basePath = controllerMatch[1] || "";
563
- const controllerIndex = controllerMatch.index ?? 0;
564
- const afterDecorator = content.slice(controllerIndex);
565
- const classMatch = afterDecorator.match(/class\s+(\w+)/);
566
- const controllerName = classMatch?.[1] ?? "UnknownController";
567
- const nextController = content.indexOf("@Controller", controllerIndex + 1);
568
- const controllerBlock = nextController > 0 ? content.slice(controllerIndex, nextController) : content.slice(controllerIndex);
569
- const routeMatches = [...controllerBlock.matchAll(PATTERNS.route)];
570
- for (const routeMatch of routeMatches) {
571
- const method = routeMatch[1]?.toUpperCase();
572
- const routePath = routeMatch[2] || "";
573
- const fullPath = this.normalizePath(`/${basePath}/${routePath}`);
574
- const afterRoute = controllerBlock.slice(routeMatch.index ?? 0);
575
- const methodMatch = afterRoute.match(/(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+(?:<[^>]+>)?)?\s*\{/);
576
- const handlerName = methodMatch?.[1] ?? "unknownHandler";
577
- const absoluteIndex = controllerIndex + (routeMatch.index ?? 0);
578
- const lineNumber = content.slice(0, absoluteIndex).split(`
579
- `).length;
580
- const hasBody = PATTERNS.body.test(afterRoute.slice(0, 200));
581
- const hasParams = PATTERNS.param.test(afterRoute.slice(0, 200));
582
- const hasQuery = PATTERNS.query.test(afterRoute.slice(0, 200));
583
- const endpoint = {
584
- id: this.generateEndpointId(method, fullPath, handlerName),
585
- method,
586
- path: fullPath,
587
- kind: this.methodToOpKind(method),
588
- handlerName,
589
- controllerName,
590
- source: this.createLocation(file, lineNumber, lineNumber + 10),
591
- confidence: this.createConfidence("medium", "decorator-hints"),
592
- frameworkMeta: {
593
- hasBody,
594
- hasParams,
595
- hasQuery
596
- }
597
- };
598
- this.addEndpoint(ctx, endpoint);
599
- }
549
+ if (!content.includes("elysia"))
550
+ continue;
551
+ await this.extractRoutes(ctx, file, content);
600
552
  }
601
553
  }
602
- async extractDtos(ctx, file, content) {
603
- const dtoMatches = [...content.matchAll(PATTERNS.dto)];
604
- for (const match of dtoMatches) {
605
- const name = match[1] ?? "UnknownDto";
554
+ async extractRoutes(ctx, file, content) {
555
+ const matches = [...content.matchAll(PATTERNS.route)];
556
+ for (const match of matches) {
557
+ const method = match[1]?.toUpperCase() ?? "GET";
558
+ const path = match[2] ?? "/";
606
559
  const index = match.index ?? 0;
607
560
  const lineNumber = content.slice(0, index).split(`
608
561
  `).length;
609
- const hasClassValidator = content.includes("class-validator") || content.includes("@IsString") || content.includes("@IsNumber");
610
- const schema = {
611
- id: this.generateSchemaId(name, file),
612
- name,
613
- schemaType: hasClassValidator ? "class-validator" : "typescript",
614
- source: this.createLocation(file, lineNumber, lineNumber + 20),
615
- confidence: this.createConfidence(hasClassValidator ? "high" : "medium", hasClassValidator ? "explicit-schema" : "inferred-types")
562
+ const afterMatch = content.slice(index, index + 500);
563
+ const hasTSchema = PATTERNS.tSchema.test(afterMatch);
564
+ const endpoint = {
565
+ id: this.generateEndpointId(method, path),
566
+ method,
567
+ path,
568
+ kind: this.methodToOpKind(method),
569
+ handlerName: "handler",
570
+ source: this.createLocation(file, lineNumber, lineNumber + 5),
571
+ confidence: this.createConfidence(hasTSchema ? "high" : "medium", hasTSchema ? "explicit-schema" : "decorator-hints")
616
572
  };
617
- this.addSchema(ctx, schema);
573
+ this.addEndpoint(ctx, endpoint);
618
574
  }
619
575
  }
620
- normalizePath(path) {
621
- return "/" + path.replace(/\/+/g, "/").replace(/^\/+|\/+$/g, "");
622
- }
623
576
  }
624
577
  // src/extractors/express/extractor.ts
625
578
  var PATTERNS2 = {
@@ -768,119 +721,104 @@ class HonoExtractor extends BaseExtractor {
768
721
  }
769
722
  }
770
723
  }
771
- // src/extractors/elysia/extractor.ts
724
+ // src/extractors/nestjs/extractor.ts
772
725
  var PATTERNS5 = {
773
- route: /\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
774
- tSchema: /body:\s*t\.\w+/g
726
+ controller: /@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/g,
727
+ route: /@(Get|Post|Put|Patch|Delete|Head|Options)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,
728
+ body: /@Body\s*\(\s*\)/g,
729
+ param: /@Param\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,
730
+ query: /@Query\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,
731
+ dto: /class\s+(\w+(?:Dto|DTO|Request|Response|Input|Output))\s*\{/g,
732
+ classValidator: /@(IsString|IsNumber|IsBoolean|IsArray|IsOptional|IsNotEmpty|Min|Max|Length|Matches)/g
775
733
  };
776
734
 
777
- class ElysiaExtractor extends BaseExtractor {
778
- id = "elysia";
779
- name = "Elysia Extractor";
780
- frameworks = ["elysia"];
781
- priority = 15;
735
+ class NestJsExtractor extends BaseExtractor {
736
+ id = "nestjs";
737
+ name = "NestJS Extractor";
738
+ frameworks = ["nestjs"];
739
+ priority = 20;
782
740
  async doExtract(ctx) {
783
741
  const { project, options, fs } = ctx;
784
742
  const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
785
743
  const files = await fs.glob(pattern, { cwd: project.rootPath });
786
744
  ctx.ir.stats.filesScanned = files.length;
787
745
  for (const file of files) {
788
- if (file.includes("node_modules") || file.includes(".test."))
746
+ if (file.includes("node_modules") || file.includes(".spec.") || file.includes(".test.")) {
789
747
  continue;
748
+ }
790
749
  const fullPath = `${project.rootPath}/${file}`;
791
750
  const content = await fs.readFile(fullPath);
792
- if (!content.includes("elysia"))
793
- continue;
794
- await this.extractRoutes(ctx, file, content);
751
+ await this.extractControllers(ctx, file, content);
752
+ await this.extractDtos(ctx, file, content);
795
753
  }
796
754
  }
797
- async extractRoutes(ctx, file, content) {
798
- const matches = [...content.matchAll(PATTERNS5.route)];
799
- for (const match of matches) {
800
- const method = match[1]?.toUpperCase() ?? "GET";
801
- const path = match[2] ?? "/";
802
- const index = match.index ?? 0;
803
- const lineNumber = content.slice(0, index).split(`
755
+ async extractControllers(ctx, file, content) {
756
+ const controllerMatches = [...content.matchAll(PATTERNS5.controller)];
757
+ for (const controllerMatch of controllerMatches) {
758
+ const basePath = controllerMatch[1] || "";
759
+ const controllerIndex = controllerMatch.index ?? 0;
760
+ const afterDecorator = content.slice(controllerIndex);
761
+ const classMatch = afterDecorator.match(/class\s+(\w+)/);
762
+ const controllerName = classMatch?.[1] ?? "UnknownController";
763
+ const nextController = content.indexOf("@Controller", controllerIndex + 1);
764
+ const controllerBlock = nextController > 0 ? content.slice(controllerIndex, nextController) : content.slice(controllerIndex);
765
+ const routeMatches = [...controllerBlock.matchAll(PATTERNS5.route)];
766
+ for (const routeMatch of routeMatches) {
767
+ const method = routeMatch[1]?.toUpperCase();
768
+ const routePath = routeMatch[2] || "";
769
+ const fullPath = this.normalizePath(`/${basePath}/${routePath}`);
770
+ const afterRoute = controllerBlock.slice(routeMatch.index ?? 0);
771
+ const methodMatch = afterRoute.match(/(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+(?:<[^>]+>)?)?\s*\{/);
772
+ const handlerName = methodMatch?.[1] ?? "unknownHandler";
773
+ const absoluteIndex = controllerIndex + (routeMatch.index ?? 0);
774
+ const lineNumber = content.slice(0, absoluteIndex).split(`
804
775
  `).length;
805
- const afterMatch = content.slice(index, index + 500);
806
- const hasTSchema = PATTERNS5.tSchema.test(afterMatch);
807
- const endpoint = {
808
- id: this.generateEndpointId(method, path),
809
- method,
810
- path,
811
- kind: this.methodToOpKind(method),
812
- handlerName: "handler",
813
- source: this.createLocation(file, lineNumber, lineNumber + 5),
814
- confidence: this.createConfidence(hasTSchema ? "high" : "medium", hasTSchema ? "explicit-schema" : "decorator-hints")
815
- };
816
- this.addEndpoint(ctx, endpoint);
817
- }
818
- }
819
- }
820
- // src/extractors/trpc/extractor.ts
821
- var PATTERNS6 = {
822
- procedure: /\.(query|mutation)\s*\(\s*(?:\{[^}]*\}|[^)]+)\)/gi,
823
- procedureName: /(\w+)\s*:\s*(?:publicProcedure|protectedProcedure|procedure)/g,
824
- zodInput: /\.input\s*\(\s*(\w+)/g,
825
- zodOutput: /\.output\s*\(\s*(\w+)/g
826
- };
827
-
828
- class TrpcExtractor extends BaseExtractor {
829
- id = "trpc";
830
- name = "tRPC Extractor";
831
- frameworks = ["trpc"];
832
- priority = 15;
833
- async doExtract(ctx) {
834
- const { project, options, fs } = ctx;
835
- const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
836
- const files = await fs.glob(pattern, { cwd: project.rootPath });
837
- ctx.ir.stats.filesScanned = files.length;
838
- for (const file of files) {
839
- if (file.includes("node_modules") || file.includes(".test."))
840
- continue;
841
- const fullPath = `${project.rootPath}/${file}`;
842
- const content = await fs.readFile(fullPath);
843
- if (!content.includes("trpc") && !content.includes("Procedure"))
844
- continue;
845
- await this.extractProcedures(ctx, file, content);
776
+ const hasBody = PATTERNS5.body.test(afterRoute.slice(0, 200));
777
+ const hasParams = PATTERNS5.param.test(afterRoute.slice(0, 200));
778
+ const hasQuery = PATTERNS5.query.test(afterRoute.slice(0, 200));
779
+ const endpoint = {
780
+ id: this.generateEndpointId(method, fullPath, handlerName),
781
+ method,
782
+ path: fullPath,
783
+ kind: this.methodToOpKind(method),
784
+ handlerName,
785
+ controllerName,
786
+ source: this.createLocation(file, lineNumber, lineNumber + 10),
787
+ confidence: this.createConfidence("medium", "decorator-hints"),
788
+ frameworkMeta: {
789
+ hasBody,
790
+ hasParams,
791
+ hasQuery
792
+ }
793
+ };
794
+ this.addEndpoint(ctx, endpoint);
795
+ }
846
796
  }
847
797
  }
848
- async extractProcedures(ctx, file, content) {
849
- const nameMatches = [...content.matchAll(PATTERNS6.procedureName)];
850
- for (const match of nameMatches) {
851
- const procedureName = match[1] ?? "unknownProcedure";
798
+ async extractDtos(ctx, file, content) {
799
+ const dtoMatches = [...content.matchAll(PATTERNS5.dto)];
800
+ for (const match of dtoMatches) {
801
+ const name = match[1] ?? "UnknownDto";
852
802
  const index = match.index ?? 0;
853
803
  const lineNumber = content.slice(0, index).split(`
854
804
  `).length;
855
- const afterMatch = content.slice(index, index + 500);
856
- const isQuery = afterMatch.includes(".query(");
857
- const isMutation = afterMatch.includes(".mutation(");
858
- if (!isQuery && !isMutation)
859
- continue;
860
- const hasZodInput = PATTERNS6.zodInput.test(afterMatch);
861
- const hasZodOutput = PATTERNS6.zodOutput.test(afterMatch);
862
- const hasSchema = hasZodInput || hasZodOutput;
863
- const method = isMutation ? "POST" : "GET";
864
- const endpoint = {
865
- id: `trpc.${procedureName}`,
866
- method,
867
- path: `/trpc/${procedureName}`,
868
- kind: isMutation ? "command" : "query",
869
- handlerName: procedureName,
870
- source: this.createLocation(file, lineNumber, lineNumber + 10),
871
- confidence: this.createConfidence(hasSchema ? "high" : "medium", hasSchema ? "explicit-schema" : "inferred-types"),
872
- frameworkMeta: {
873
- procedureType: isMutation ? "mutation" : "query",
874
- hasInput: hasZodInput,
875
- hasOutput: hasZodOutput
876
- }
805
+ const hasClassValidator = content.includes("class-validator") || content.includes("@IsString") || content.includes("@IsNumber");
806
+ const schema = {
807
+ id: this.generateSchemaId(name, file),
808
+ name,
809
+ schemaType: hasClassValidator ? "class-validator" : "typescript",
810
+ source: this.createLocation(file, lineNumber, lineNumber + 20),
811
+ confidence: this.createConfidence(hasClassValidator ? "high" : "medium", hasClassValidator ? "explicit-schema" : "inferred-types")
877
812
  };
878
- this.addEndpoint(ctx, endpoint);
813
+ this.addSchema(ctx, schema);
879
814
  }
880
815
  }
816
+ normalizePath(path) {
817
+ return "/" + path.replace(/\/+/g, "/").replace(/^\/+|\/+$/g, "");
818
+ }
881
819
  }
882
820
  // src/extractors/next-api/extractor.ts
883
- var PATTERNS7 = {
821
+ var PATTERNS6 = {
884
822
  appRouterExport: /export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/gi,
885
823
  pagesHandler: /export\s+default\s+(?:async\s+)?function/g,
886
824
  zodSchema: /z\.\w+\(/g
@@ -914,13 +852,13 @@ class NextApiExtractor extends BaseExtractor {
914
852
  async extractAppRoutes(ctx, file, content) {
915
853
  const pathMatch = file.match(/app\/api\/(.+)\/route\.ts$/);
916
854
  const routePath = pathMatch ? `/api/${pathMatch[1]}` : "/api";
917
- const matches = [...content.matchAll(PATTERNS7.appRouterExport)];
855
+ const matches = [...content.matchAll(PATTERNS6.appRouterExport)];
918
856
  for (const match of matches) {
919
857
  const method = match[1]?.toUpperCase() ?? "GET";
920
858
  const index = match.index ?? 0;
921
859
  const lineNumber = content.slice(0, index).split(`
922
860
  `).length;
923
- const hasZod = PATTERNS7.zodSchema.test(content);
861
+ const hasZod = PATTERNS6.zodSchema.test(content);
924
862
  const endpoint = {
925
863
  id: this.generateEndpointId(method, routePath),
926
864
  method,
@@ -937,10 +875,10 @@ class NextApiExtractor extends BaseExtractor {
937
875
  async extractPagesRoutes(ctx, file, content) {
938
876
  const pathMatch = file.match(/pages\/api\/(.+)\.ts$/);
939
877
  const routePath = pathMatch ? `/api/${pathMatch[1]}` : "/api";
940
- if (!PATTERNS7.pagesHandler.test(content))
878
+ if (!PATTERNS6.pagesHandler.test(content))
941
879
  return;
942
880
  const lineNumber = 1;
943
- const _hasZod = PATTERNS7.zodSchema.test(content);
881
+ const _hasZod = PATTERNS6.zodSchema.test(content);
944
882
  const methods = ["GET", "POST"];
945
883
  for (const method of methods) {
946
884
  const endpoint = {
@@ -957,6 +895,68 @@ class NextApiExtractor extends BaseExtractor {
957
895
  }
958
896
  }
959
897
  }
898
+ // src/extractors/trpc/extractor.ts
899
+ var PATTERNS7 = {
900
+ procedure: /\.(query|mutation)\s*\(\s*(?:\{[^}]*\}|[^)]+)\)/gi,
901
+ procedureName: /(\w+)\s*:\s*(?:publicProcedure|protectedProcedure|procedure)/g,
902
+ zodInput: /\.input\s*\(\s*(\w+)/g,
903
+ zodOutput: /\.output\s*\(\s*(\w+)/g
904
+ };
905
+
906
+ class TrpcExtractor extends BaseExtractor {
907
+ id = "trpc";
908
+ name = "tRPC Extractor";
909
+ frameworks = ["trpc"];
910
+ priority = 15;
911
+ async doExtract(ctx) {
912
+ const { project, options, fs } = ctx;
913
+ const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
914
+ const files = await fs.glob(pattern, { cwd: project.rootPath });
915
+ ctx.ir.stats.filesScanned = files.length;
916
+ for (const file of files) {
917
+ if (file.includes("node_modules") || file.includes(".test."))
918
+ continue;
919
+ const fullPath = `${project.rootPath}/${file}`;
920
+ const content = await fs.readFile(fullPath);
921
+ if (!content.includes("trpc") && !content.includes("Procedure"))
922
+ continue;
923
+ await this.extractProcedures(ctx, file, content);
924
+ }
925
+ }
926
+ async extractProcedures(ctx, file, content) {
927
+ const nameMatches = [...content.matchAll(PATTERNS7.procedureName)];
928
+ for (const match of nameMatches) {
929
+ const procedureName = match[1] ?? "unknownProcedure";
930
+ const index = match.index ?? 0;
931
+ const lineNumber = content.slice(0, index).split(`
932
+ `).length;
933
+ const afterMatch = content.slice(index, index + 500);
934
+ const isQuery = afterMatch.includes(".query(");
935
+ const isMutation = afterMatch.includes(".mutation(");
936
+ if (!isQuery && !isMutation)
937
+ continue;
938
+ const hasZodInput = PATTERNS7.zodInput.test(afterMatch);
939
+ const hasZodOutput = PATTERNS7.zodOutput.test(afterMatch);
940
+ const hasSchema = hasZodInput || hasZodOutput;
941
+ const method = isMutation ? "POST" : "GET";
942
+ const endpoint = {
943
+ id: `trpc.${procedureName}`,
944
+ method,
945
+ path: `/trpc/${procedureName}`,
946
+ kind: isMutation ? "command" : "query",
947
+ handlerName: procedureName,
948
+ source: this.createLocation(file, lineNumber, lineNumber + 10),
949
+ confidence: this.createConfidence(hasSchema ? "high" : "medium", hasSchema ? "explicit-schema" : "inferred-types"),
950
+ frameworkMeta: {
951
+ procedureType: isMutation ? "mutation" : "query",
952
+ hasInput: hasZodInput,
953
+ hasOutput: hasZodOutput
954
+ }
955
+ };
956
+ this.addEndpoint(ctx, endpoint);
957
+ }
958
+ }
959
+ }
960
960
  // src/extractors/zod/extractor.ts
961
961
  var PATTERNS8 = {
962
962
  zodSchema: /(?:export\s+)?const\s+(\w+)\s*=\s*z\.(?:object|string|number|boolean|array|enum|union|intersection|literal|tuple|record)/g,
@@ -104,6 +104,42 @@ function generateOperationCode(endpoint, specName, options) {
104
104
  return lines.join(`
105
105
  `);
106
106
  }
107
+ // src/codegen/registry-gen.ts
108
+ function generateRegistry(operationFiles) {
109
+ const operationImports = operationFiles.filter((f) => f.type === "operation").map((f) => {
110
+ const name = f.path.replace(".ts", "").replace(/-/g, "_");
111
+ const specName = toPascalCase(name) + "Spec";
112
+ return { path: f.path, name, specName };
113
+ });
114
+ const lines = [
115
+ `/**`,
116
+ ` * Generated operation registry.`,
117
+ ` */`,
118
+ ``,
119
+ `import { OperationSpecRegistry } from '@contractspec/lib.contracts-spec';`,
120
+ ``
121
+ ];
122
+ for (const op of operationImports) {
123
+ const importPath = `./${op.path.replace(".ts", "")}`;
124
+ lines.push(`import { ${op.specName} } from '${importPath}';`);
125
+ }
126
+ lines.push(``);
127
+ lines.push(`export const operationRegistry = new OperationSpecRegistry();`);
128
+ lines.push(``);
129
+ for (const op of operationImports) {
130
+ lines.push(`operationRegistry.register(${op.specName});`);
131
+ }
132
+ lines.push(``);
133
+ return {
134
+ path: "registry.ts",
135
+ content: lines.join(`
136
+ `),
137
+ type: "registry"
138
+ };
139
+ }
140
+ function toPascalCase(str) {
141
+ return str.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
142
+ }
107
143
  // src/codegen/schema-gen.ts
108
144
  function generateSchema(schema, _options) {
109
145
  const fileName = `${toFileName2(schema.name)}.ts`;
@@ -184,42 +220,6 @@ function mapToZodType(tsType, optional) {
184
220
  }
185
221
  return optional ? `${zodType}.optional()` : zodType;
186
222
  }
187
- // src/codegen/registry-gen.ts
188
- function generateRegistry(operationFiles) {
189
- const operationImports = operationFiles.filter((f) => f.type === "operation").map((f) => {
190
- const name = f.path.replace(".ts", "").replace(/-/g, "_");
191
- const specName = toPascalCase(name) + "Spec";
192
- return { path: f.path, name, specName };
193
- });
194
- const lines = [
195
- `/**`,
196
- ` * Generated operation registry.`,
197
- ` */`,
198
- ``,
199
- `import { OperationSpecRegistry } from '@contractspec/lib.contracts-spec';`,
200
- ``
201
- ];
202
- for (const op of operationImports) {
203
- const importPath = `./${op.path.replace(".ts", "")}`;
204
- lines.push(`import { ${op.specName} } from '${importPath}';`);
205
- }
206
- lines.push(``);
207
- lines.push(`export const operationRegistry = new OperationSpecRegistry();`);
208
- lines.push(``);
209
- for (const op of operationImports) {
210
- lines.push(`operationRegistry.register(${op.specName});`);
211
- }
212
- lines.push(``);
213
- return {
214
- path: "registry.ts",
215
- content: lines.join(`
216
- `),
217
- type: "registry"
218
- };
219
- }
220
- function toPascalCase(str) {
221
- return str.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
222
- }
223
223
  export {
224
224
  generateSchemas,
225
225
  generateSchema,