@contractspec/lib.source-extractors 0.2.0

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.
Files changed (78) hide show
  1. package/README.md +86 -0
  2. package/dist/_virtual/rolldown_runtime.js +18 -0
  3. package/dist/codegen/index.d.ts +12 -0
  4. package/dist/codegen/index.d.ts.map +1 -0
  5. package/dist/codegen/index.js +17 -0
  6. package/dist/codegen/index.js.map +1 -0
  7. package/dist/codegen/operation-gen.d.ts +16 -0
  8. package/dist/codegen/operation-gen.d.ts.map +1 -0
  9. package/dist/codegen/operation-gen.js +91 -0
  10. package/dist/codegen/operation-gen.js.map +1 -0
  11. package/dist/codegen/registry-gen.d.ts +11 -0
  12. package/dist/codegen/registry-gen.d.ts.map +1 -0
  13. package/dist/codegen/registry-gen.js +47 -0
  14. package/dist/codegen/registry-gen.js.map +1 -0
  15. package/dist/codegen/schema-gen.d.ts +16 -0
  16. package/dist/codegen/schema-gen.d.ts.map +1 -0
  17. package/dist/codegen/schema-gen.js +93 -0
  18. package/dist/codegen/schema-gen.js.map +1 -0
  19. package/dist/codegen/types.d.ts +48 -0
  20. package/dist/codegen/types.d.ts.map +1 -0
  21. package/dist/detect.d.ts +47 -0
  22. package/dist/detect.d.ts.map +1 -0
  23. package/dist/detect.js +177 -0
  24. package/dist/detect.js.map +1 -0
  25. package/dist/extract.d.ts +24 -0
  26. package/dist/extract.d.ts.map +1 -0
  27. package/dist/extract.js +125 -0
  28. package/dist/extract.js.map +1 -0
  29. package/dist/extractors/base.d.ts +90 -0
  30. package/dist/extractors/base.d.ts.map +1 -0
  31. package/dist/extractors/base.js +152 -0
  32. package/dist/extractors/base.js.map +1 -0
  33. package/dist/extractors/elysia/extractor.d.ts +15 -0
  34. package/dist/extractors/elysia/extractor.d.ts.map +1 -0
  35. package/dist/extractors/elysia/extractor.js +58 -0
  36. package/dist/extractors/elysia/extractor.js.map +1 -0
  37. package/dist/extractors/express/extractor.d.ts +15 -0
  38. package/dist/extractors/express/extractor.d.ts.map +1 -0
  39. package/dist/extractors/express/extractor.js +61 -0
  40. package/dist/extractors/express/extractor.js.map +1 -0
  41. package/dist/extractors/fastify/extractor.d.ts +15 -0
  42. package/dist/extractors/fastify/extractor.d.ts.map +1 -0
  43. package/dist/extractors/fastify/extractor.js +61 -0
  44. package/dist/extractors/fastify/extractor.js.map +1 -0
  45. package/dist/extractors/hono/extractor.d.ts +15 -0
  46. package/dist/extractors/hono/extractor.d.ts.map +1 -0
  47. package/dist/extractors/hono/extractor.js +57 -0
  48. package/dist/extractors/hono/extractor.js.map +1 -0
  49. package/dist/extractors/index.d.ts +21 -0
  50. package/dist/extractors/index.d.ts.map +1 -0
  51. package/dist/extractors/index.js +42 -0
  52. package/dist/extractors/index.js.map +1 -0
  53. package/dist/extractors/nestjs/extractor.d.ts +29 -0
  54. package/dist/extractors/nestjs/extractor.d.ts.map +1 -0
  55. package/dist/extractors/nestjs/extractor.js +118 -0
  56. package/dist/extractors/nestjs/extractor.js.map +1 -0
  57. package/dist/extractors/next-api/extractor.d.ts +16 -0
  58. package/dist/extractors/next-api/extractor.d.ts.map +1 -0
  59. package/dist/extractors/next-api/extractor.js +80 -0
  60. package/dist/extractors/next-api/extractor.js.map +1 -0
  61. package/dist/extractors/trpc/extractor.d.ts +15 -0
  62. package/dist/extractors/trpc/extractor.d.ts.map +1 -0
  63. package/dist/extractors/trpc/extractor.js +71 -0
  64. package/dist/extractors/trpc/extractor.js.map +1 -0
  65. package/dist/extractors/zod/extractor.d.ts +16 -0
  66. package/dist/extractors/zod/extractor.d.ts.map +1 -0
  67. package/dist/extractors/zod/extractor.js +69 -0
  68. package/dist/extractors/zod/extractor.js.map +1 -0
  69. package/dist/index.d.ts +7 -0
  70. package/dist/index.js +7 -0
  71. package/dist/registry.d.ts +85 -0
  72. package/dist/registry.d.ts.map +1 -0
  73. package/dist/registry.js +87 -0
  74. package/dist/registry.js.map +1 -0
  75. package/dist/types.d.ts +272 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +0 -0
  78. package/package.json +71 -0
@@ -0,0 +1,80 @@
1
+ import { BaseExtractor } from "../base.js";
2
+
3
+ //#region src/extractors/next-api/extractor.ts
4
+ /**
5
+ * Next.js API Routes extractor.
6
+ *
7
+ * Extracts contract candidates from Next.js applications by parsing:
8
+ * - App Router API routes (app/api/.../route.ts)
9
+ * - Pages Router API routes (pages/api/.../*.ts)
10
+ */
11
+ const PATTERNS = {
12
+ appRouterExport: /export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/gi,
13
+ pagesHandler: /export\s+default\s+(?:async\s+)?function/g,
14
+ zodSchema: /z\.\w+\(/g
15
+ };
16
+ var NextApiExtractor = class extends BaseExtractor {
17
+ id = "next-api";
18
+ name = "Next.js API Extractor";
19
+ frameworks = ["next-api"];
20
+ priority = 15;
21
+ async doExtract(ctx) {
22
+ const { project, fs } = ctx;
23
+ const appRoutes = await fs.glob("**/app/api/**/route.ts", { cwd: project.rootPath });
24
+ const pagesRoutes = await fs.glob("**/pages/api/**/*.ts", { cwd: project.rootPath });
25
+ const allRoutes = [...appRoutes, ...pagesRoutes];
26
+ ctx.ir.stats.filesScanned = allRoutes.length;
27
+ for (const file of allRoutes) {
28
+ const fullPath = `${project.rootPath}/${file}`;
29
+ const content = await fs.readFile(fullPath);
30
+ if (file.includes("/app/api/")) await this.extractAppRoutes(ctx, file, content);
31
+ else await this.extractPagesRoutes(ctx, file, content);
32
+ }
33
+ }
34
+ async extractAppRoutes(ctx, file, content) {
35
+ const pathMatch = file.match(/app\/api\/(.+)\/route\.ts$/);
36
+ const routePath = pathMatch ? `/api/${pathMatch[1]}` : "/api";
37
+ const matches = [...content.matchAll(PATTERNS.appRouterExport)];
38
+ for (const match of matches) {
39
+ const method = match[1]?.toUpperCase() ?? "GET";
40
+ const index = match.index ?? 0;
41
+ const lineNumber = content.slice(0, index).split("\n").length;
42
+ const hasZod = PATTERNS.zodSchema.test(content);
43
+ const endpoint = {
44
+ id: this.generateEndpointId(method, routePath),
45
+ method,
46
+ path: routePath.replace(/\[(\w+)\]/g, ":$1"),
47
+ kind: this.methodToOpKind(method),
48
+ handlerName: method,
49
+ source: this.createLocation(file, lineNumber, lineNumber + 10),
50
+ confidence: this.createConfidence(hasZod ? "high" : "medium", hasZod ? "explicit-schema" : "inferred-types"),
51
+ frameworkMeta: { routeType: "app-router" }
52
+ };
53
+ this.addEndpoint(ctx, endpoint);
54
+ }
55
+ }
56
+ async extractPagesRoutes(ctx, file, content) {
57
+ const pathMatch = file.match(/pages\/api\/(.+)\.ts$/);
58
+ const routePath = pathMatch ? `/api/${pathMatch[1]}` : "/api";
59
+ if (!PATTERNS.pagesHandler.test(content)) return;
60
+ const lineNumber = 1;
61
+ PATTERNS.zodSchema.test(content);
62
+ for (const method of ["GET", "POST"]) {
63
+ const endpoint = {
64
+ id: this.generateEndpointId(method, routePath),
65
+ method,
66
+ path: routePath.replace(/\[(\w+)\]/g, ":$1"),
67
+ kind: this.methodToOpKind(method),
68
+ handlerName: "handler",
69
+ source: this.createLocation(file, lineNumber, lineNumber + 20),
70
+ confidence: this.createConfidence("low", "naming-convention"),
71
+ frameworkMeta: { routeType: "pages-router" }
72
+ };
73
+ this.addEndpoint(ctx, endpoint);
74
+ }
75
+ }
76
+ };
77
+
78
+ //#endregion
79
+ export { NextApiExtractor };
80
+ //# sourceMappingURL=extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/next-api/extractor.ts"],"sourcesContent":["/**\n * Next.js API Routes extractor.\n *\n * Extracts contract candidates from Next.js applications by parsing:\n * - App Router API routes (app/api/.../route.ts)\n * - Pages Router API routes (pages/api/.../*.ts)\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n appRouterExport:\n /export\\s+(?:async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/gi,\n pagesHandler: /export\\s+default\\s+(?:async\\s+)?function/g,\n zodSchema: /z\\.\\w+\\(/g,\n};\n\nexport class NextApiExtractor extends BaseExtractor {\n id = 'next-api';\n name = 'Next.js API Extractor';\n frameworks = ['next-api'];\n priority = 15;\n\n protected async doExtract(ctx: ExtractionContext): Promise<void> {\n const { project, fs } = ctx;\n\n // Find App Router API routes\n const appRoutes = await fs.glob('**/app/api/**/route.ts', {\n cwd: project.rootPath,\n });\n // Find Pages Router API routes\n const pagesRoutes = await fs.glob('**/pages/api/**/*.ts', {\n cwd: project.rootPath,\n });\n\n const allRoutes = [...appRoutes, ...pagesRoutes];\n ctx.ir.stats.filesScanned = allRoutes.length;\n\n for (const file of allRoutes) {\n const fullPath = `${project.rootPath}/${file}`;\n const content = await fs.readFile(fullPath);\n\n if (file.includes('/app/api/')) {\n await this.extractAppRoutes(ctx, file, content);\n } else {\n await this.extractPagesRoutes(ctx, file, content);\n }\n }\n }\n\n private async extractAppRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n // Derive path from file path\n const pathMatch = file.match(/app\\/api\\/(.+)\\/route\\.ts$/);\n const routePath = pathMatch ? `/api/${pathMatch[1]}` : '/api';\n\n const matches = [...content.matchAll(PATTERNS.appRouterExport)];\n\n for (const match of matches) {\n const method = (match[1]?.toUpperCase() ?? 'GET') as HttpMethod;\n const index = match.index ?? 0;\n const lineNumber = content.slice(0, index).split('\\n').length;\n\n const hasZod = PATTERNS.zodSchema.test(content);\n\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, routePath),\n method,\n path: routePath.replace(/\\[(\\w+)\\]/g, ':$1'),\n kind: this.methodToOpKind(method),\n handlerName: method,\n source: this.createLocation(file, lineNumber, lineNumber + 10),\n confidence: this.createConfidence(\n hasZod ? 'high' : 'medium',\n hasZod ? 'explicit-schema' : 'inferred-types'\n ),\n frameworkMeta: { routeType: 'app-router' },\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n\n private async extractPagesRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const pathMatch = file.match(/pages\\/api\\/(.+)\\.ts$/);\n const routePath = pathMatch ? `/api/${pathMatch[1]}` : '/api';\n\n if (!PATTERNS.pagesHandler.test(content)) return;\n\n const lineNumber = 1;\n const _hasZod = PATTERNS.zodSchema.test(content);\n\n // Pages API routes can handle multiple methods\n const methods: HttpMethod[] = ['GET', 'POST'];\n\n for (const method of methods) {\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, routePath),\n method,\n path: routePath.replace(/\\[(\\w+)\\]/g, ':$1'),\n kind: this.methodToOpKind(method),\n handlerName: 'handler',\n source: this.createLocation(file, lineNumber, lineNumber + 20),\n confidence: this.createConfidence('low', 'naming-convention'),\n frameworkMeta: { routeType: 'pages-router' },\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAM,WAAW;CACf,iBACE;CACF,cAAc;CACd,WAAW;CACZ;AAED,IAAa,mBAAb,cAAsC,cAAc;CAClD,KAAK;CACL,OAAO;CACP,aAAa,CAAC,WAAW;CACzB,WAAW;CAEX,MAAgB,UAAU,KAAuC;EAC/D,MAAM,EAAE,SAAS,OAAO;EAGxB,MAAM,YAAY,MAAM,GAAG,KAAK,0BAA0B,EACxD,KAAK,QAAQ,UACd,CAAC;EAEF,MAAM,cAAc,MAAM,GAAG,KAAK,wBAAwB,EACxD,KAAK,QAAQ,UACd,CAAC;EAEF,MAAM,YAAY,CAAC,GAAG,WAAW,GAAG,YAAY;AAChD,MAAI,GAAG,MAAM,eAAe,UAAU;AAEtC,OAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,WAAW,GAAG,QAAQ,SAAS,GAAG;GACxC,MAAM,UAAU,MAAM,GAAG,SAAS,SAAS;AAE3C,OAAI,KAAK,SAAS,YAAY,CAC5B,OAAM,KAAK,iBAAiB,KAAK,MAAM,QAAQ;OAE/C,OAAM,KAAK,mBAAmB,KAAK,MAAM,QAAQ;;;CAKvD,MAAc,iBACZ,KACA,MACA,SACe;EAEf,MAAM,YAAY,KAAK,MAAM,6BAA6B;EAC1D,MAAM,YAAY,YAAY,QAAQ,UAAU,OAAO;EAEvD,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,gBAAgB,CAAC;AAE/D,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAU,MAAM,IAAI,aAAa,IAAI;GAC3C,MAAM,QAAQ,MAAM,SAAS;GAC7B,MAAM,aAAa,QAAQ,MAAM,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC;GAEvD,MAAM,SAAS,SAAS,UAAU,KAAK,QAAQ;GAE/C,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,UAAU;IAC9C;IACA,MAAM,UAAU,QAAQ,cAAc,MAAM;IAC5C,MAAM,KAAK,eAAe,OAAO;IACjC,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,GAAG;IAC9D,YAAY,KAAK,iBACf,SAAS,SAAS,UAClB,SAAS,oBAAoB,iBAC9B;IACD,eAAe,EAAE,WAAW,cAAc;IAC3C;AAED,QAAK,YAAY,KAAK,SAAS;;;CAInC,MAAc,mBACZ,KACA,MACA,SACe;EACf,MAAM,YAAY,KAAK,MAAM,wBAAwB;EACrD,MAAM,YAAY,YAAY,QAAQ,UAAU,OAAO;AAEvD,MAAI,CAAC,SAAS,aAAa,KAAK,QAAQ,CAAE;EAE1C,MAAM,aAAa;AACH,WAAS,UAAU,KAAK,QAAQ;AAKhD,OAAK,MAAM,UAFmB,CAAC,OAAO,OAAO,EAEf;GAC5B,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,UAAU;IAC9C;IACA,MAAM,UAAU,QAAQ,cAAc,MAAM;IAC5C,MAAM,KAAK,eAAe,OAAO;IACjC,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,GAAG;IAC9D,YAAY,KAAK,iBAAiB,OAAO,oBAAoB;IAC7D,eAAe,EAAE,WAAW,gBAAgB;IAC7C;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -0,0 +1,15 @@
1
+ import { BaseExtractor, ExtractionContext } from "../base.js";
2
+
3
+ //#region src/extractors/trpc/extractor.d.ts
4
+
5
+ declare class TrpcExtractor extends BaseExtractor {
6
+ id: string;
7
+ name: string;
8
+ frameworks: string[];
9
+ priority: number;
10
+ protected doExtract(ctx: ExtractionContext): Promise<void>;
11
+ private extractProcedures;
12
+ }
13
+ //#endregion
14
+ export { TrpcExtractor };
15
+ //# sourceMappingURL=extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.d.ts","names":[],"sources":["../../../src/extractors/trpc/extractor.ts"],"sourcesContent":[],"mappings":";;;;cAoBa,aAAA,SAAsB,aAAA;;;;;2BAMF,oBAAoB"}
@@ -0,0 +1,71 @@
1
+ import { BaseExtractor } from "../base.js";
2
+
3
+ //#region src/extractors/trpc/extractor.ts
4
+ /**
5
+ * tRPC extractor.
6
+ *
7
+ * Extracts contract candidates from tRPC applications by parsing:
8
+ * - router definitions
9
+ * - procedure definitions (query, mutation)
10
+ * - Zod input/output schemas
11
+ */
12
+ const PATTERNS = {
13
+ procedure: /\.(query|mutation)\s*\(\s*(?:\{[^}]*\}|[^)]+)\)/gi,
14
+ procedureName: /(\w+)\s*:\s*(?:publicProcedure|protectedProcedure|procedure)/g,
15
+ zodInput: /\.input\s*\(\s*(\w+)/g,
16
+ zodOutput: /\.output\s*\(\s*(\w+)/g
17
+ };
18
+ var TrpcExtractor = class extends BaseExtractor {
19
+ id = "trpc";
20
+ name = "tRPC Extractor";
21
+ frameworks = ["trpc"];
22
+ priority = 15;
23
+ async doExtract(ctx) {
24
+ const { project, options, fs } = ctx;
25
+ const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
26
+ const files = await fs.glob(pattern, { cwd: project.rootPath });
27
+ ctx.ir.stats.filesScanned = files.length;
28
+ for (const file of files) {
29
+ if (file.includes("node_modules") || file.includes(".test.")) continue;
30
+ const fullPath = `${project.rootPath}/${file}`;
31
+ const content = await fs.readFile(fullPath);
32
+ if (!content.includes("trpc") && !content.includes("Procedure")) continue;
33
+ await this.extractProcedures(ctx, file, content);
34
+ }
35
+ }
36
+ async extractProcedures(ctx, file, content) {
37
+ const nameMatches = [...content.matchAll(PATTERNS.procedureName)];
38
+ for (const match of nameMatches) {
39
+ const procedureName = match[1] ?? "unknownProcedure";
40
+ const index = match.index ?? 0;
41
+ const lineNumber = content.slice(0, index).split("\n").length;
42
+ const afterMatch = content.slice(index, index + 500);
43
+ const isQuery = afterMatch.includes(".query(");
44
+ const isMutation = afterMatch.includes(".mutation(");
45
+ if (!isQuery && !isMutation) continue;
46
+ const hasZodInput = PATTERNS.zodInput.test(afterMatch);
47
+ const hasZodOutput = PATTERNS.zodOutput.test(afterMatch);
48
+ const hasSchema = hasZodInput || hasZodOutput;
49
+ const method = isMutation ? "POST" : "GET";
50
+ const endpoint = {
51
+ id: `trpc.${procedureName}`,
52
+ method,
53
+ path: `/trpc/${procedureName}`,
54
+ kind: isMutation ? "command" : "query",
55
+ handlerName: procedureName,
56
+ source: this.createLocation(file, lineNumber, lineNumber + 10),
57
+ confidence: this.createConfidence(hasSchema ? "high" : "medium", hasSchema ? "explicit-schema" : "inferred-types"),
58
+ frameworkMeta: {
59
+ procedureType: isMutation ? "mutation" : "query",
60
+ hasInput: hasZodInput,
61
+ hasOutput: hasZodOutput
62
+ }
63
+ };
64
+ this.addEndpoint(ctx, endpoint);
65
+ }
66
+ }
67
+ };
68
+
69
+ //#endregion
70
+ export { TrpcExtractor };
71
+ //# sourceMappingURL=extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/trpc/extractor.ts"],"sourcesContent":["/**\n * tRPC extractor.\n *\n * Extracts contract candidates from tRPC applications by parsing:\n * - router definitions\n * - procedure definitions (query, mutation)\n * - Zod input/output schemas\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n procedure: /\\.(query|mutation)\\s*\\(\\s*(?:\\{[^}]*\\}|[^)]+)\\)/gi,\n procedureName:\n /(\\w+)\\s*:\\s*(?:publicProcedure|protectedProcedure|procedure)/g,\n zodInput: /\\.input\\s*\\(\\s*(\\w+)/g,\n zodOutput: /\\.output\\s*\\(\\s*(\\w+)/g,\n};\n\nexport class TrpcExtractor extends BaseExtractor {\n id = 'trpc';\n name = 'tRPC Extractor';\n frameworks = ['trpc'];\n priority = 15;\n\n protected async doExtract(ctx: ExtractionContext): Promise<void> {\n const { project, options, fs } = ctx;\n\n const pattern = options.scope?.length\n ? options.scope.map((s) => `${s}/**/*.ts`).join(',')\n : '**/*.ts';\n\n const files = await fs.glob(pattern, { cwd: project.rootPath });\n ctx.ir.stats.filesScanned = files.length;\n\n for (const file of files) {\n if (file.includes('node_modules') || file.includes('.test.')) continue;\n\n const fullPath = `${project.rootPath}/${file}`;\n const content = await fs.readFile(fullPath);\n\n if (!content.includes('trpc') && !content.includes('Procedure')) continue;\n\n await this.extractProcedures(ctx, file, content);\n }\n }\n\n private async extractProcedures(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const nameMatches = [...content.matchAll(PATTERNS.procedureName)];\n\n for (const match of nameMatches) {\n const procedureName = match[1] ?? 'unknownProcedure';\n const index = match.index ?? 0;\n const lineNumber = content.slice(0, index).split('\\n').length;\n\n const afterMatch = content.slice(index, index + 500);\n const isQuery = afterMatch.includes('.query(');\n const isMutation = afterMatch.includes('.mutation(');\n\n if (!isQuery && !isMutation) continue;\n\n const hasZodInput = PATTERNS.zodInput.test(afterMatch);\n const hasZodOutput = PATTERNS.zodOutput.test(afterMatch);\n const hasSchema = hasZodInput || hasZodOutput;\n\n const method: HttpMethod = isMutation ? 'POST' : 'GET';\n\n const endpoint: EndpointCandidate = {\n id: `trpc.${procedureName}`,\n method,\n path: `/trpc/${procedureName}`,\n kind: isMutation ? 'command' : 'query',\n handlerName: procedureName,\n source: this.createLocation(file, lineNumber, lineNumber + 10),\n confidence: this.createConfidence(\n hasSchema ? 'high' : 'medium',\n hasSchema ? 'explicit-schema' : 'inferred-types'\n ),\n frameworkMeta: {\n procedureType: isMutation ? 'mutation' : 'query',\n hasInput: hasZodInput,\n hasOutput: hasZodOutput,\n },\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAM,WAAW;CACf,WAAW;CACX,eACE;CACF,UAAU;CACV,WAAW;CACZ;AAED,IAAa,gBAAb,cAAmC,cAAc;CAC/C,KAAK;CACL,OAAO;CACP,aAAa,CAAC,OAAO;CACrB,WAAW;CAEX,MAAgB,UAAU,KAAuC;EAC/D,MAAM,EAAE,SAAS,SAAS,OAAO;EAEjC,MAAM,UAAU,QAAQ,OAAO,SAC3B,QAAQ,MAAM,KAAK,MAAM,GAAG,EAAE,UAAU,CAAC,KAAK,IAAI,GAClD;EAEJ,MAAM,QAAQ,MAAM,GAAG,KAAK,SAAS,EAAE,KAAK,QAAQ,UAAU,CAAC;AAC/D,MAAI,GAAG,MAAM,eAAe,MAAM;AAElC,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,KAAK,SAAS,eAAe,IAAI,KAAK,SAAS,SAAS,CAAE;GAE9D,MAAM,WAAW,GAAG,QAAQ,SAAS,GAAG;GACxC,MAAM,UAAU,MAAM,GAAG,SAAS,SAAS;AAE3C,OAAI,CAAC,QAAQ,SAAS,OAAO,IAAI,CAAC,QAAQ,SAAS,YAAY,CAAE;AAEjE,SAAM,KAAK,kBAAkB,KAAK,MAAM,QAAQ;;;CAIpD,MAAc,kBACZ,KACA,MACA,SACe;EACf,MAAM,cAAc,CAAC,GAAG,QAAQ,SAAS,SAAS,cAAc,CAAC;AAEjE,OAAK,MAAM,SAAS,aAAa;GAC/B,MAAM,gBAAgB,MAAM,MAAM;GAClC,MAAM,QAAQ,MAAM,SAAS;GAC7B,MAAM,aAAa,QAAQ,MAAM,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC;GAEvD,MAAM,aAAa,QAAQ,MAAM,OAAO,QAAQ,IAAI;GACpD,MAAM,UAAU,WAAW,SAAS,UAAU;GAC9C,MAAM,aAAa,WAAW,SAAS,aAAa;AAEpD,OAAI,CAAC,WAAW,CAAC,WAAY;GAE7B,MAAM,cAAc,SAAS,SAAS,KAAK,WAAW;GACtD,MAAM,eAAe,SAAS,UAAU,KAAK,WAAW;GACxD,MAAM,YAAY,eAAe;GAEjC,MAAM,SAAqB,aAAa,SAAS;GAEjD,MAAM,WAA8B;IAClC,IAAI,QAAQ;IACZ;IACA,MAAM,SAAS;IACf,MAAM,aAAa,YAAY;IAC/B,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,GAAG;IAC9D,YAAY,KAAK,iBACf,YAAY,SAAS,UACrB,YAAY,oBAAoB,iBACjC;IACD,eAAe;KACb,eAAe,aAAa,aAAa;KACzC,UAAU;KACV,WAAW;KACZ;IACF;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -0,0 +1,16 @@
1
+ import { BaseExtractor, ExtractionContext } from "../base.js";
2
+
3
+ //#region src/extractors/zod/extractor.d.ts
4
+
5
+ declare class ZodSchemaExtractor extends BaseExtractor {
6
+ id: string;
7
+ name: string;
8
+ frameworks: string[];
9
+ priority: number;
10
+ detect(): Promise<boolean>;
11
+ protected doExtract(ctx: ExtractionContext): Promise<void>;
12
+ private extractSchemas;
13
+ }
14
+ //#endregion
15
+ export { ZodSchemaExtractor };
16
+ //# sourceMappingURL=extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.d.ts","names":[],"sources":["../../../src/extractors/zod/extractor.ts"],"sourcesContent":[],"mappings":";;;;AAgBwC,cAA3B,kBAAA,SAA2B,aAAA,CAAA;EAAa,EAAA,EAAA,MAAA;;;;YAMnC;2BAKe,oBAAoB"}
@@ -0,0 +1,69 @@
1
+ import { BaseExtractor } from "../base.js";
2
+
3
+ //#region src/extractors/zod/extractor.ts
4
+ /**
5
+ * Zod schema extractor.
6
+ *
7
+ * Extracts standalone Zod schema definitions that aren't tied to a specific framework.
8
+ * Useful for shared validation schemas.
9
+ */
10
+ const PATTERNS = {
11
+ zodSchema: /(?:export\s+)?const\s+(\w+)\s*=\s*z\.(?:object|string|number|boolean|array|enum|union|intersection|literal|tuple|record)/g,
12
+ zodInfer: /type\s+(\w+)\s*=\s*z\.infer<typeof\s+(\w+)>/g
13
+ };
14
+ var ZodSchemaExtractor = class extends BaseExtractor {
15
+ id = "zod";
16
+ name = "Zod Schema Extractor";
17
+ frameworks = ["zod"];
18
+ priority = 5;
19
+ async detect() {
20
+ return true;
21
+ }
22
+ async doExtract(ctx) {
23
+ const { project, options, fs } = ctx;
24
+ const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
25
+ const files = await fs.glob(pattern, { cwd: project.rootPath });
26
+ ctx.ir.stats.filesScanned = files.length;
27
+ for (const file of files) {
28
+ if (file.includes("node_modules") || file.includes(".test.")) continue;
29
+ const fullPath = `${project.rootPath}/${file}`;
30
+ const content = await fs.readFile(fullPath);
31
+ if (!content.includes("z.")) continue;
32
+ await this.extractSchemas(ctx, file, content);
33
+ }
34
+ }
35
+ async extractSchemas(ctx, file, content) {
36
+ const matches = [...content.matchAll(PATTERNS.zodSchema)];
37
+ for (const match of matches) {
38
+ const name = match[1] ?? "unknownSchema";
39
+ const index = match.index ?? 0;
40
+ const lineNumber = content.slice(0, index).split("\n").length;
41
+ let depth = 0;
42
+ let endIndex = index;
43
+ for (let i = index; i < content.length; i++) {
44
+ const char = content[i];
45
+ if (char === "(" || char === "{" || char === "[") depth++;
46
+ if (char === ")" || char === "}" || char === "]") depth--;
47
+ if (depth === 0 && (char === ";" || char === "\n")) {
48
+ endIndex = i;
49
+ break;
50
+ }
51
+ }
52
+ const rawDefinition = content.slice(index, endIndex + 1);
53
+ const endLineNumber = lineNumber + rawDefinition.split("\n").length - 1;
54
+ const schema = {
55
+ id: this.generateSchemaId(name, file),
56
+ name,
57
+ schemaType: "zod",
58
+ rawDefinition,
59
+ source: this.createLocation(file, lineNumber, endLineNumber),
60
+ confidence: this.createConfidence("high", "explicit-schema")
61
+ };
62
+ this.addSchema(ctx, schema);
63
+ }
64
+ }
65
+ };
66
+
67
+ //#endregion
68
+ export { ZodSchemaExtractor };
69
+ //# sourceMappingURL=extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/zod/extractor.ts"],"sourcesContent":["/**\n * Zod schema extractor.\n *\n * Extracts standalone Zod schema definitions that aren't tied to a specific framework.\n * Useful for shared validation schemas.\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { SchemaCandidate } from '../../types';\n\nconst PATTERNS = {\n zodSchema:\n /(?:export\\s+)?const\\s+(\\w+)\\s*=\\s*z\\.(?:object|string|number|boolean|array|enum|union|intersection|literal|tuple|record)/g,\n zodInfer: /type\\s+(\\w+)\\s*=\\s*z\\.infer<typeof\\s+(\\w+)>/g,\n};\n\nexport class ZodSchemaExtractor extends BaseExtractor {\n id = 'zod';\n name = 'Zod Schema Extractor';\n frameworks = ['zod'];\n priority = 5; // Lower priority, runs after framework extractors\n\n async detect(): Promise<boolean> {\n // Always available as a fallback\n return true;\n }\n\n protected async doExtract(ctx: ExtractionContext): Promise<void> {\n const { project, options, fs } = ctx;\n\n const pattern = options.scope?.length\n ? options.scope.map((s) => `${s}/**/*.ts`).join(',')\n : '**/*.ts';\n\n const files = await fs.glob(pattern, { cwd: project.rootPath });\n ctx.ir.stats.filesScanned = files.length;\n\n for (const file of files) {\n if (file.includes('node_modules') || file.includes('.test.')) continue;\n\n const fullPath = `${project.rootPath}/${file}`;\n const content = await fs.readFile(fullPath);\n\n if (!content.includes('z.')) continue;\n\n await this.extractSchemas(ctx, file, content);\n }\n }\n\n private async extractSchemas(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const matches = [...content.matchAll(PATTERNS.zodSchema)];\n\n for (const match of matches) {\n const name = match[1] ?? 'unknownSchema';\n const index = match.index ?? 0;\n const lineNumber = content.slice(0, index).split('\\n').length;\n\n // Find the end of the schema definition (roughly)\n let depth = 0;\n let endIndex = index;\n for (let i = index; i < content.length; i++) {\n const char = content[i];\n if (char === '(' || char === '{' || char === '[') depth++;\n if (char === ')' || char === '}' || char === ']') depth--;\n if (depth === 0 && (char === ';' || char === '\\n')) {\n endIndex = i;\n break;\n }\n }\n\n const rawDefinition = content.slice(index, endIndex + 1);\n const endLineNumber = lineNumber + rawDefinition.split('\\n').length - 1;\n\n const schema: SchemaCandidate = {\n id: this.generateSchemaId(name, file),\n name,\n schemaType: 'zod',\n rawDefinition,\n source: this.createLocation(file, lineNumber, endLineNumber),\n confidence: this.createConfidence('high', 'explicit-schema'),\n };\n\n this.addSchema(ctx, schema);\n }\n }\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,WAAW;CACf,WACE;CACF,UAAU;CACX;AAED,IAAa,qBAAb,cAAwC,cAAc;CACpD,KAAK;CACL,OAAO;CACP,aAAa,CAAC,MAAM;CACpB,WAAW;CAEX,MAAM,SAA2B;AAE/B,SAAO;;CAGT,MAAgB,UAAU,KAAuC;EAC/D,MAAM,EAAE,SAAS,SAAS,OAAO;EAEjC,MAAM,UAAU,QAAQ,OAAO,SAC3B,QAAQ,MAAM,KAAK,MAAM,GAAG,EAAE,UAAU,CAAC,KAAK,IAAI,GAClD;EAEJ,MAAM,QAAQ,MAAM,GAAG,KAAK,SAAS,EAAE,KAAK,QAAQ,UAAU,CAAC;AAC/D,MAAI,GAAG,MAAM,eAAe,MAAM;AAElC,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,KAAK,SAAS,eAAe,IAAI,KAAK,SAAS,SAAS,CAAE;GAE9D,MAAM,WAAW,GAAG,QAAQ,SAAS,GAAG;GACxC,MAAM,UAAU,MAAM,GAAG,SAAS,SAAS;AAE3C,OAAI,CAAC,QAAQ,SAAS,KAAK,CAAE;AAE7B,SAAM,KAAK,eAAe,KAAK,MAAM,QAAQ;;;CAIjD,MAAc,eACZ,KACA,MACA,SACe;EACf,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,UAAU,CAAC;AAEzD,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,OAAO,MAAM,MAAM;GACzB,MAAM,QAAQ,MAAM,SAAS;GAC7B,MAAM,aAAa,QAAQ,MAAM,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC;GAGvD,IAAI,QAAQ;GACZ,IAAI,WAAW;AACf,QAAK,IAAI,IAAI,OAAO,IAAI,QAAQ,QAAQ,KAAK;IAC3C,MAAM,OAAO,QAAQ;AACrB,QAAI,SAAS,OAAO,SAAS,OAAO,SAAS,IAAK;AAClD,QAAI,SAAS,OAAO,SAAS,OAAO,SAAS,IAAK;AAClD,QAAI,UAAU,MAAM,SAAS,OAAO,SAAS,OAAO;AAClD,gBAAW;AACX;;;GAIJ,MAAM,gBAAgB,QAAQ,MAAM,OAAO,WAAW,EAAE;GACxD,MAAM,gBAAgB,aAAa,cAAc,MAAM,KAAK,CAAC,SAAS;GAEtE,MAAM,SAA0B;IAC9B,IAAI,KAAK,iBAAiB,MAAM,KAAK;IACrC;IACA,YAAY;IACZ;IACA,QAAQ,KAAK,eAAe,MAAM,YAAY,cAAc;IAC5D,YAAY,KAAK,iBAAiB,QAAQ,kBAAkB;IAC7D;AAED,QAAK,UAAU,KAAK,OAAO"}
@@ -0,0 +1,7 @@
1
+ import { Ambiguity, ConfidenceLevel, ConfidenceMeta, ConfidenceReason, EndpointCandidate, ErrorCandidate, ErrorRef, EventCandidate, ExtractError, ExtractOptions, ExtractResult, FrameworkInfo, HttpMethod, ImportIR, OpKind, ProjectInfo, SchemaCandidate, SchemaField, SchemaRef, SourceLocation } from "./types.js";
2
+ import { index_d_exports } from "./codegen/index.js";
3
+ import { ExtractorRegistry, SourceExtractor, extractorRegistry, registerBuiltInExtractors } from "./registry.js";
4
+ import { index_d_exports as index_d_exports$1 } from "./extractors/index.js";
5
+ import { detectFramework, detectFrameworksFromCode, detectFrameworksFromPackageJson, detectFrameworksFromPaths, getSupportedFrameworks, isFrameworkSupported, mergeFrameworkDetections } from "./detect.js";
6
+ import { createEmptyIR, extractFromProject, mergeIRs } from "./extract.js";
7
+ export { Ambiguity, ConfidenceLevel, ConfidenceMeta, ConfidenceReason, EndpointCandidate, ErrorCandidate, ErrorRef, EventCandidate, ExtractError, ExtractOptions, ExtractResult, ExtractorRegistry, FrameworkInfo, HttpMethod, ImportIR, OpKind, ProjectInfo, SchemaCandidate, SchemaField, SchemaRef, SourceExtractor, SourceLocation, index_d_exports as codegen, createEmptyIR, detectFramework, detectFrameworksFromCode, detectFrameworksFromPackageJson, detectFrameworksFromPaths, extractFromProject, extractorRegistry, index_d_exports$1 as extractors, getSupportedFrameworks, isFrameworkSupported, mergeFrameworkDetections, mergeIRs, registerBuiltInExtractors };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import { ExtractorRegistry, extractorRegistry, registerBuiltInExtractors } from "./registry.js";
2
+ import { detectFramework, detectFrameworksFromCode, detectFrameworksFromPackageJson, detectFrameworksFromPaths, getSupportedFrameworks, isFrameworkSupported, mergeFrameworkDetections } from "./detect.js";
3
+ import { createEmptyIR, extractFromProject, mergeIRs } from "./extract.js";
4
+ import { extractors_exports } from "./extractors/index.js";
5
+ import { codegen_exports } from "./codegen/index.js";
6
+
7
+ export { ExtractorRegistry, codegen_exports as codegen, createEmptyIR, detectFramework, detectFrameworksFromCode, detectFrameworksFromPackageJson, detectFrameworksFromPaths, extractFromProject, extractorRegistry, extractors_exports as extractors, getSupportedFrameworks, isFrameworkSupported, mergeFrameworkDetections, mergeIRs, registerBuiltInExtractors };
@@ -0,0 +1,85 @@
1
+ import { ExtractOptions, ExtractResult, ProjectInfo } from "./types.js";
2
+
3
+ //#region src/registry.d.ts
4
+
5
+ /**
6
+ * Interface that all source extractors must implement.
7
+ */
8
+ interface SourceExtractor {
9
+ /** Unique identifier for this extractor */
10
+ id: string;
11
+ /** Human-readable name */
12
+ name: string;
13
+ /** Frameworks this extractor supports */
14
+ frameworks: string[];
15
+ /** Priority (higher = preferred when multiple match) */
16
+ priority: number;
17
+ /**
18
+ * Detect if this extractor can handle the given project.
19
+ * @param project Project information
20
+ * @returns true if this extractor should be used
21
+ */
22
+ detect(project: ProjectInfo): Promise<boolean>;
23
+ /**
24
+ * Extract contract candidates from the project.
25
+ * @param project Project information
26
+ * @param options Extraction options
27
+ * @returns Extraction result with IR
28
+ */
29
+ extract(project: ProjectInfo, options: ExtractOptions): Promise<ExtractResult>;
30
+ }
31
+ /**
32
+ * Registry for source extractor plugins.
33
+ */
34
+ declare class ExtractorRegistry {
35
+ private extractors;
36
+ /**
37
+ * Register an extractor plugin.
38
+ */
39
+ register(extractor: SourceExtractor): void;
40
+ /**
41
+ * Unregister an extractor plugin.
42
+ */
43
+ unregister(id: string): boolean;
44
+ /**
45
+ * Get an extractor by ID.
46
+ */
47
+ get(id: string): SourceExtractor | undefined;
48
+ /**
49
+ * Get all registered extractors.
50
+ */
51
+ getAll(): SourceExtractor[];
52
+ /**
53
+ * Find extractors that can handle the given project.
54
+ * Returns extractors sorted by priority (highest first).
55
+ */
56
+ findMatching(project: ProjectInfo): Promise<SourceExtractor[]>;
57
+ /**
58
+ * Find extractors for a specific framework.
59
+ */
60
+ findByFramework(framework: string): SourceExtractor[];
61
+ /**
62
+ * Alias for findByFramework.
63
+ */
64
+ findForFramework(framework: string): SourceExtractor[];
65
+ /**
66
+ * Check if an extractor exists for the given framework.
67
+ */
68
+ hasExtractorFor(framework: string): boolean;
69
+ /**
70
+ * Get list of all supported framework IDs.
71
+ */
72
+ getSupportedFrameworks(): string[];
73
+ }
74
+ /**
75
+ * Global extractor registry instance.
76
+ */
77
+ declare const extractorRegistry: ExtractorRegistry;
78
+ /**
79
+ * Register built-in extractors.
80
+ * Called automatically when the module is imported.
81
+ */
82
+ declare function registerBuiltInExtractors(): void;
83
+ //#endregion
84
+ export { ExtractorRegistry, SourceExtractor, extractorRegistry, registerBuiltInExtractors };
85
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","names":[],"sources":["../src/registry.ts"],"sourcesContent":[],"mappings":";;;;;;;AAqCK,UA1BY,eAAA,CA0BZ;EAAO;EAMC,EAAA,EAAA,MAAA;EAMS;EAcH,IAAA,EAAA,MAAA;EAOP;EAQkB,UAAA,EAAA,MAAA,EAAA;EAAsB;EAAR,QAAA,EAAA,MAAA;EAmBN;;;AAoDtC;AAMA;kBAjIkB,cAAc;;;;;;;mBASnB,sBACA,iBACR,QAAQ;;;;;cAMA,iBAAA;;;;;sBAMS;;;;;;;;mBAcH;;;;YAOP;;;;;wBAQkB,cAAc,QAAQ;;;;sCAmBd;;;;uCAeC;;;;;;;;;;;;;cAqC1B,mBAAiB;;;;;iBAMd,yBAAA,CAAA"}
@@ -0,0 +1,87 @@
1
+ //#region src/registry.ts
2
+ /**
3
+ * Registry for source extractor plugins.
4
+ */
5
+ var ExtractorRegistry = class {
6
+ extractors = /* @__PURE__ */ new Map();
7
+ /**
8
+ * Register an extractor plugin.
9
+ */
10
+ register(extractor) {
11
+ this.extractors.set(extractor.id, extractor);
12
+ }
13
+ /**
14
+ * Unregister an extractor plugin.
15
+ */
16
+ unregister(id) {
17
+ return this.extractors.delete(id);
18
+ }
19
+ /**
20
+ * Get an extractor by ID.
21
+ */
22
+ get(id) {
23
+ return this.extractors.get(id);
24
+ }
25
+ /**
26
+ * Get all registered extractors.
27
+ */
28
+ getAll() {
29
+ return Array.from(this.extractors.values());
30
+ }
31
+ /**
32
+ * Find extractors that can handle the given project.
33
+ * Returns extractors sorted by priority (highest first).
34
+ */
35
+ async findMatching(project) {
36
+ const matches = [];
37
+ for (const extractor of this.extractors.values()) try {
38
+ if (await extractor.detect(project)) matches.push(extractor);
39
+ } catch {}
40
+ return matches.sort((a, b) => b.priority - a.priority);
41
+ }
42
+ /**
43
+ * Find extractors for a specific framework.
44
+ */
45
+ findByFramework(framework) {
46
+ const matches = [];
47
+ for (const extractor of this.extractors.values()) if (extractor.frameworks.includes(framework)) matches.push(extractor);
48
+ return matches.sort((a, b) => b.priority - a.priority);
49
+ }
50
+ /**
51
+ * Alias for findByFramework.
52
+ */
53
+ findForFramework(framework) {
54
+ return this.findByFramework(framework);
55
+ }
56
+ /**
57
+ * Check if an extractor exists for the given framework.
58
+ */
59
+ hasExtractorFor(framework) {
60
+ for (const extractor of this.extractors.values()) if (extractor.frameworks.includes(framework) || extractor.id === framework) return true;
61
+ return false;
62
+ }
63
+ /**
64
+ * Get list of all supported framework IDs.
65
+ */
66
+ getSupportedFrameworks() {
67
+ const frameworks = /* @__PURE__ */ new Set();
68
+ for (const extractor of this.extractors.values()) {
69
+ frameworks.add(extractor.id);
70
+ for (const fw of extractor.frameworks) frameworks.add(fw);
71
+ }
72
+ return Array.from(frameworks);
73
+ }
74
+ };
75
+ /**
76
+ * Global extractor registry instance.
77
+ */
78
+ const extractorRegistry = new ExtractorRegistry();
79
+ /**
80
+ * Register built-in extractors.
81
+ * Called automatically when the module is imported.
82
+ */
83
+ function registerBuiltInExtractors() {}
84
+
85
+ //#endregion
86
+ export { ExtractorRegistry, extractorRegistry, registerBuiltInExtractors };
87
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../src/registry.ts"],"sourcesContent":["/**\n * Source extractor registry.\n *\n * Manages extractor plugins and provides lookup by framework.\n */\n\nimport type { ExtractOptions, ExtractResult, ProjectInfo } from './types';\n\n/**\n * Interface that all source extractors must implement.\n */\nexport interface SourceExtractor {\n /** Unique identifier for this extractor */\n id: string;\n /** Human-readable name */\n name: string;\n /** Frameworks this extractor supports */\n frameworks: string[];\n /** Priority (higher = preferred when multiple match) */\n priority: number;\n\n /**\n * Detect if this extractor can handle the given project.\n * @param project Project information\n * @returns true if this extractor should be used\n */\n detect(project: ProjectInfo): Promise<boolean>;\n\n /**\n * Extract contract candidates from the project.\n * @param project Project information\n * @param options Extraction options\n * @returns Extraction result with IR\n */\n extract(\n project: ProjectInfo,\n options: ExtractOptions\n ): Promise<ExtractResult>;\n}\n\n/**\n * Registry for source extractor plugins.\n */\nexport class ExtractorRegistry {\n private extractors = new Map<string, SourceExtractor>();\n\n /**\n * Register an extractor plugin.\n */\n register(extractor: SourceExtractor): void {\n this.extractors.set(extractor.id, extractor);\n }\n\n /**\n * Unregister an extractor plugin.\n */\n unregister(id: string): boolean {\n return this.extractors.delete(id);\n }\n\n /**\n * Get an extractor by ID.\n */\n get(id: string): SourceExtractor | undefined {\n return this.extractors.get(id);\n }\n\n /**\n * Get all registered extractors.\n */\n getAll(): SourceExtractor[] {\n return Array.from(this.extractors.values());\n }\n\n /**\n * Find extractors that can handle the given project.\n * Returns extractors sorted by priority (highest first).\n */\n async findMatching(project: ProjectInfo): Promise<SourceExtractor[]> {\n const matches: SourceExtractor[] = [];\n\n for (const extractor of this.extractors.values()) {\n try {\n if (await extractor.detect(project)) {\n matches.push(extractor);\n }\n } catch {\n // Skip extractors that fail detection\n }\n }\n\n return matches.sort((a, b) => b.priority - a.priority);\n }\n\n /**\n * Find extractors for a specific framework.\n */\n findByFramework(framework: string): SourceExtractor[] {\n const matches: SourceExtractor[] = [];\n\n for (const extractor of this.extractors.values()) {\n if (extractor.frameworks.includes(framework)) {\n matches.push(extractor);\n }\n }\n\n return matches.sort((a, b) => b.priority - a.priority);\n }\n\n /**\n * Alias for findByFramework.\n */\n findForFramework(framework: string): SourceExtractor[] {\n return this.findByFramework(framework);\n }\n\n /**\n * Check if an extractor exists for the given framework.\n */\n hasExtractorFor(framework: string): boolean {\n for (const extractor of this.extractors.values()) {\n if (\n extractor.frameworks.includes(framework) ||\n extractor.id === framework\n ) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Get list of all supported framework IDs.\n */\n getSupportedFrameworks(): string[] {\n const frameworks = new Set<string>();\n for (const extractor of this.extractors.values()) {\n frameworks.add(extractor.id);\n for (const fw of extractor.frameworks) {\n frameworks.add(fw);\n }\n }\n return Array.from(frameworks);\n }\n}\n\n/**\n * Global extractor registry instance.\n */\nexport const extractorRegistry = new ExtractorRegistry();\n\n/**\n * Register built-in extractors.\n * Called automatically when the module is imported.\n */\nexport function registerBuiltInExtractors(): void {\n // Built-in extractors are registered in extractors/index.ts\n // This function is called after all extractors are imported\n}\n"],"mappings":";;;;AA2CA,IAAa,oBAAb,MAA+B;CAC7B,AAAQ,6BAAa,IAAI,KAA8B;;;;CAKvD,SAAS,WAAkC;AACzC,OAAK,WAAW,IAAI,UAAU,IAAI,UAAU;;;;;CAM9C,WAAW,IAAqB;AAC9B,SAAO,KAAK,WAAW,OAAO,GAAG;;;;;CAMnC,IAAI,IAAyC;AAC3C,SAAO,KAAK,WAAW,IAAI,GAAG;;;;;CAMhC,SAA4B;AAC1B,SAAO,MAAM,KAAK,KAAK,WAAW,QAAQ,CAAC;;;;;;CAO7C,MAAM,aAAa,SAAkD;EACnE,MAAM,UAA6B,EAAE;AAErC,OAAK,MAAM,aAAa,KAAK,WAAW,QAAQ,CAC9C,KAAI;AACF,OAAI,MAAM,UAAU,OAAO,QAAQ,CACjC,SAAQ,KAAK,UAAU;UAEnB;AAKV,SAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS;;;;;CAMxD,gBAAgB,WAAsC;EACpD,MAAM,UAA6B,EAAE;AAErC,OAAK,MAAM,aAAa,KAAK,WAAW,QAAQ,CAC9C,KAAI,UAAU,WAAW,SAAS,UAAU,CAC1C,SAAQ,KAAK,UAAU;AAI3B,SAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS;;;;;CAMxD,iBAAiB,WAAsC;AACrD,SAAO,KAAK,gBAAgB,UAAU;;;;;CAMxC,gBAAgB,WAA4B;AAC1C,OAAK,MAAM,aAAa,KAAK,WAAW,QAAQ,CAC9C,KACE,UAAU,WAAW,SAAS,UAAU,IACxC,UAAU,OAAO,UAEjB,QAAO;AAGX,SAAO;;;;;CAMT,yBAAmC;EACjC,MAAM,6BAAa,IAAI,KAAa;AACpC,OAAK,MAAM,aAAa,KAAK,WAAW,QAAQ,EAAE;AAChD,cAAW,IAAI,UAAU,GAAG;AAC5B,QAAK,MAAM,MAAM,UAAU,WACzB,YAAW,IAAI,GAAG;;AAGtB,SAAO,MAAM,KAAK,WAAW;;;;;;AAOjC,MAAa,oBAAoB,IAAI,mBAAmB;;;;;AAMxD,SAAgB,4BAAkC"}