@contractspec/lib.source-extractors 0.11.0 → 0.13.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 (86) hide show
  1. package/dist/browser/codegen/index.js +225 -0
  2. package/dist/browser/extractors/index.js +835 -0
  3. package/dist/browser/index.js +1215 -0
  4. package/dist/browser/types.js +0 -0
  5. package/dist/codegen/index.d.ts +9 -11
  6. package/dist/codegen/index.d.ts.map +1 -1
  7. package/dist/codegen/index.js +223 -14
  8. package/dist/codegen/operation-gen.d.ts +9 -8
  9. package/dist/codegen/operation-gen.d.ts.map +1 -1
  10. package/dist/codegen/registry-gen.d.ts +7 -6
  11. package/dist/codegen/registry-gen.d.ts.map +1 -1
  12. package/dist/codegen/schema-gen.d.ts +9 -8
  13. package/dist/codegen/schema-gen.d.ts.map +1 -1
  14. package/dist/codegen/types.d.ts +29 -32
  15. package/dist/codegen/types.d.ts.map +1 -1
  16. package/dist/detect.d.ts +19 -17
  17. package/dist/detect.d.ts.map +1 -1
  18. package/dist/extract.d.ts +10 -8
  19. package/dist/extract.d.ts.map +1 -1
  20. package/dist/extractors/base.d.ts +76 -75
  21. package/dist/extractors/base.d.ts.map +1 -1
  22. package/dist/extractors/elysia/extractor.d.ts +15 -12
  23. package/dist/extractors/elysia/extractor.d.ts.map +1 -1
  24. package/dist/extractors/express/extractor.d.ts +16 -12
  25. package/dist/extractors/express/extractor.d.ts.map +1 -1
  26. package/dist/extractors/fastify/extractor.d.ts +16 -12
  27. package/dist/extractors/fastify/extractor.d.ts.map +1 -1
  28. package/dist/extractors/hono/extractor.d.ts +15 -12
  29. package/dist/extractors/hono/extractor.d.ts.map +1 -1
  30. package/dist/extractors/index.d.ts +16 -17
  31. package/dist/extractors/index.d.ts.map +1 -1
  32. package/dist/extractors/index.js +834 -40
  33. package/dist/extractors/nestjs/extractor.d.ts +28 -23
  34. package/dist/extractors/nestjs/extractor.d.ts.map +1 -1
  35. package/dist/extractors/next-api/extractor.d.ts +16 -13
  36. package/dist/extractors/next-api/extractor.d.ts.map +1 -1
  37. package/dist/extractors/trpc/extractor.d.ts +16 -12
  38. package/dist/extractors/trpc/extractor.d.ts.map +1 -1
  39. package/dist/extractors/zod/extractor.d.ts +15 -13
  40. package/dist/extractors/zod/extractor.d.ts.map +1 -1
  41. package/dist/index.d.ts +30 -7
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +1215 -6
  44. package/dist/node/codegen/index.js +225 -0
  45. package/dist/node/extractors/index.js +835 -0
  46. package/dist/node/index.js +1215 -0
  47. package/dist/node/types.js +0 -0
  48. package/dist/registry.d.ts +69 -68
  49. package/dist/registry.d.ts.map +1 -1
  50. package/dist/types.d.ts +182 -185
  51. package/dist/types.d.ts.map +1 -1
  52. package/dist/types.js +1 -0
  53. package/package.json +60 -21
  54. package/dist/_virtual/_rolldown/runtime.js +0 -18
  55. package/dist/codegen/index.js.map +0 -1
  56. package/dist/codegen/operation-gen.js +0 -91
  57. package/dist/codegen/operation-gen.js.map +0 -1
  58. package/dist/codegen/registry-gen.js +0 -47
  59. package/dist/codegen/registry-gen.js.map +0 -1
  60. package/dist/codegen/schema-gen.js +0 -93
  61. package/dist/codegen/schema-gen.js.map +0 -1
  62. package/dist/detect.js +0 -177
  63. package/dist/detect.js.map +0 -1
  64. package/dist/extract.js +0 -125
  65. package/dist/extract.js.map +0 -1
  66. package/dist/extractors/base.js +0 -152
  67. package/dist/extractors/base.js.map +0 -1
  68. package/dist/extractors/elysia/extractor.js +0 -58
  69. package/dist/extractors/elysia/extractor.js.map +0 -1
  70. package/dist/extractors/express/extractor.js +0 -61
  71. package/dist/extractors/express/extractor.js.map +0 -1
  72. package/dist/extractors/fastify/extractor.js +0 -61
  73. package/dist/extractors/fastify/extractor.js.map +0 -1
  74. package/dist/extractors/hono/extractor.js +0 -57
  75. package/dist/extractors/hono/extractor.js.map +0 -1
  76. package/dist/extractors/index.js.map +0 -1
  77. package/dist/extractors/nestjs/extractor.js +0 -118
  78. package/dist/extractors/nestjs/extractor.js.map +0 -1
  79. package/dist/extractors/next-api/extractor.js +0 -80
  80. package/dist/extractors/next-api/extractor.js.map +0 -1
  81. package/dist/extractors/trpc/extractor.js +0 -71
  82. package/dist/extractors/trpc/extractor.js.map +0 -1
  83. package/dist/extractors/zod/extractor.js +0 -69
  84. package/dist/extractors/zod/extractor.js.map +0 -1
  85. package/dist/registry.js +0 -87
  86. package/dist/registry.js.map +0 -1
package/dist/extract.js DELETED
@@ -1,125 +0,0 @@
1
- import { extractorRegistry } from "./registry.js";
2
-
3
- //#region src/extract.ts
4
- /**
5
- * Extract contracts from a project.
6
- *
7
- * @param project Project information (from detectFramework)
8
- * @param options Extraction options
9
- * @returns Extraction result with IR
10
- */
11
- async function extractFromProject(project, options = {}) {
12
- let extractors;
13
- if (options.framework) {
14
- extractors = extractorRegistry.findByFramework(options.framework);
15
- if (extractors.length === 0) return {
16
- success: false,
17
- errors: [{
18
- code: "EXTRACTOR_NOT_FOUND",
19
- message: `No extractor found for framework: ${options.framework}`,
20
- recoverable: false
21
- }]
22
- };
23
- } else {
24
- extractors = await extractorRegistry.findMatching(project);
25
- if (extractors.length === 0) return {
26
- success: false,
27
- errors: [{
28
- code: "NO_FRAMEWORK_DETECTED",
29
- message: "No supported framework detected in project",
30
- recoverable: false
31
- }]
32
- };
33
- }
34
- const extractor = extractors[0];
35
- if (!extractor) return {
36
- success: false,
37
- errors: [{
38
- code: "NO_EXTRACTOR",
39
- message: "No extractor available",
40
- recoverable: false
41
- }]
42
- };
43
- return await extractor.extract(project, options);
44
- }
45
- /**
46
- * Merge multiple IRs into one.
47
- * Useful when extracting from multiple sources or frameworks.
48
- */
49
- function mergeIRs(irs) {
50
- if (irs.length === 0) throw new Error("Cannot merge empty IR array");
51
- if (irs.length === 1) {
52
- if (!irs[0]) throw new Error("First IR is undefined");
53
- return irs[0];
54
- }
55
- const first = irs[0];
56
- if (!first) throw new Error("First IR is undefined");
57
- const merged = {
58
- version: "1.0",
59
- extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
60
- project: first.project,
61
- endpoints: [],
62
- schemas: [],
63
- errors: [],
64
- events: [],
65
- ambiguities: [],
66
- stats: {
67
- filesScanned: 0,
68
- endpointsFound: 0,
69
- schemasFound: 0,
70
- errorsFound: 0,
71
- eventsFound: 0,
72
- ambiguitiesFound: 0,
73
- highConfidence: 0,
74
- mediumConfidence: 0,
75
- lowConfidence: 0
76
- }
77
- };
78
- for (const ir of irs) {
79
- merged.endpoints.push(...ir.endpoints);
80
- merged.schemas.push(...ir.schemas);
81
- merged.errors.push(...ir.errors);
82
- merged.events.push(...ir.events);
83
- merged.ambiguities.push(...ir.ambiguities);
84
- merged.stats.filesScanned += ir.stats.filesScanned;
85
- merged.stats.endpointsFound += ir.stats.endpointsFound;
86
- merged.stats.schemasFound += ir.stats.schemasFound;
87
- merged.stats.errorsFound += ir.stats.errorsFound;
88
- merged.stats.eventsFound += ir.stats.eventsFound;
89
- merged.stats.ambiguitiesFound += ir.stats.ambiguitiesFound;
90
- merged.stats.highConfidence += ir.stats.highConfidence;
91
- merged.stats.mediumConfidence += ir.stats.mediumConfidence;
92
- merged.stats.lowConfidence += ir.stats.lowConfidence;
93
- }
94
- return merged;
95
- }
96
- /**
97
- * Create an empty IR structure.
98
- */
99
- function createEmptyIR(project) {
100
- return {
101
- version: "1.0",
102
- extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
103
- project,
104
- endpoints: [],
105
- schemas: [],
106
- errors: [],
107
- events: [],
108
- ambiguities: [],
109
- stats: {
110
- filesScanned: 0,
111
- endpointsFound: 0,
112
- schemasFound: 0,
113
- errorsFound: 0,
114
- eventsFound: 0,
115
- ambiguitiesFound: 0,
116
- highConfidence: 0,
117
- mediumConfidence: 0,
118
- lowConfidence: 0
119
- }
120
- };
121
- }
122
-
123
- //#endregion
124
- export { createEmptyIR, extractFromProject, mergeIRs };
125
- //# sourceMappingURL=extract.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extract.js","names":[],"sources":["../src/extract.ts"],"sourcesContent":["/**\n * Main extraction function.\n *\n * Orchestrates the extraction process using detected frameworks\n * and registered extractors.\n */\n\nimport type {\n ExtractOptions,\n ExtractResult,\n ImportIR,\n ProjectInfo,\n} from './types';\nimport { extractorRegistry } from './registry';\n\n/**\n * Extract contracts from a project.\n *\n * @param project Project information (from detectFramework)\n * @param options Extraction options\n * @returns Extraction result with IR\n */\nexport async function extractFromProject(\n project: ProjectInfo,\n options: ExtractOptions = {}\n): Promise<ExtractResult> {\n // Find matching extractors\n let extractors;\n\n if (options.framework) {\n // Use specific framework if requested\n extractors = extractorRegistry.findByFramework(options.framework);\n if (extractors.length === 0) {\n return {\n success: false,\n errors: [\n {\n code: 'EXTRACTOR_NOT_FOUND',\n message: `No extractor found for framework: ${options.framework}`,\n recoverable: false,\n },\n ],\n };\n }\n } else {\n // Auto-detect\n extractors = await extractorRegistry.findMatching(project);\n if (extractors.length === 0) {\n return {\n success: false,\n errors: [\n {\n code: 'NO_FRAMEWORK_DETECTED',\n message: 'No supported framework detected in project',\n recoverable: false,\n },\n ],\n };\n }\n }\n\n // Use the highest priority extractor\n const extractor = extractors[0];\n if (!extractor) {\n return {\n success: false,\n errors: [\n {\n code: 'NO_EXTRACTOR',\n message: 'No extractor available',\n recoverable: false,\n },\n ],\n };\n }\n\n // Run extraction\n const result = await extractor.extract(project, options);\n\n return result;\n}\n\n/**\n * Merge multiple IRs into one.\n * Useful when extracting from multiple sources or frameworks.\n */\nexport function mergeIRs(irs: ImportIR[]): ImportIR {\n if (irs.length === 0) {\n throw new Error('Cannot merge empty IR array');\n }\n\n if (irs.length === 1) {\n if (!irs[0]) throw new Error('First IR is undefined');\n return irs[0];\n }\n\n const first = irs[0];\n if (!first) throw new Error('First IR is undefined');\n const merged: ImportIR = {\n version: '1.0',\n extractedAt: new Date().toISOString(),\n project: first.project,\n endpoints: [],\n schemas: [],\n errors: [],\n events: [],\n ambiguities: [],\n stats: {\n filesScanned: 0,\n endpointsFound: 0,\n schemasFound: 0,\n errorsFound: 0,\n eventsFound: 0,\n ambiguitiesFound: 0,\n highConfidence: 0,\n mediumConfidence: 0,\n lowConfidence: 0,\n },\n };\n\n for (const ir of irs) {\n merged.endpoints.push(...ir.endpoints);\n merged.schemas.push(...ir.schemas);\n merged.errors.push(...ir.errors);\n merged.events.push(...ir.events);\n merged.ambiguities.push(...ir.ambiguities);\n\n merged.stats.filesScanned += ir.stats.filesScanned;\n merged.stats.endpointsFound += ir.stats.endpointsFound;\n merged.stats.schemasFound += ir.stats.schemasFound;\n merged.stats.errorsFound += ir.stats.errorsFound;\n merged.stats.eventsFound += ir.stats.eventsFound;\n merged.stats.ambiguitiesFound += ir.stats.ambiguitiesFound;\n merged.stats.highConfidence += ir.stats.highConfidence;\n merged.stats.mediumConfidence += ir.stats.mediumConfidence;\n merged.stats.lowConfidence += ir.stats.lowConfidence;\n }\n\n return merged;\n}\n\n/**\n * Create an empty IR structure.\n */\nexport function createEmptyIR(project: ProjectInfo): ImportIR {\n return {\n version: '1.0',\n extractedAt: new Date().toISOString(),\n project,\n endpoints: [],\n schemas: [],\n errors: [],\n events: [],\n ambiguities: [],\n stats: {\n filesScanned: 0,\n endpointsFound: 0,\n schemasFound: 0,\n errorsFound: 0,\n eventsFound: 0,\n ambiguitiesFound: 0,\n highConfidence: 0,\n mediumConfidence: 0,\n lowConfidence: 0,\n },\n };\n}\n"],"mappings":";;;;;;;;;;AAsBA,eAAsB,mBACpB,SACA,UAA0B,EAAE,EACJ;CAExB,IAAI;AAEJ,KAAI,QAAQ,WAAW;AAErB,eAAa,kBAAkB,gBAAgB,QAAQ,UAAU;AACjE,MAAI,WAAW,WAAW,EACxB,QAAO;GACL,SAAS;GACT,QAAQ,CACN;IACE,MAAM;IACN,SAAS,qCAAqC,QAAQ;IACtD,aAAa;IACd,CACF;GACF;QAEE;AAEL,eAAa,MAAM,kBAAkB,aAAa,QAAQ;AAC1D,MAAI,WAAW,WAAW,EACxB,QAAO;GACL,SAAS;GACT,QAAQ,CACN;IACE,MAAM;IACN,SAAS;IACT,aAAa;IACd,CACF;GACF;;CAKL,MAAM,YAAY,WAAW;AAC7B,KAAI,CAAC,UACH,QAAO;EACL,SAAS;EACT,QAAQ,CACN;GACE,MAAM;GACN,SAAS;GACT,aAAa;GACd,CACF;EACF;AAMH,QAFe,MAAM,UAAU,QAAQ,SAAS,QAAQ;;;;;;AAS1D,SAAgB,SAAS,KAA2B;AAClD,KAAI,IAAI,WAAW,EACjB,OAAM,IAAI,MAAM,8BAA8B;AAGhD,KAAI,IAAI,WAAW,GAAG;AACpB,MAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,wBAAwB;AACrD,SAAO,IAAI;;CAGb,MAAM,QAAQ,IAAI;AAClB,KAAI,CAAC,MAAO,OAAM,IAAI,MAAM,wBAAwB;CACpD,MAAM,SAAmB;EACvB,SAAS;EACT,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,SAAS,MAAM;EACf,WAAW,EAAE;EACb,SAAS,EAAE;EACX,QAAQ,EAAE;EACV,QAAQ,EAAE;EACV,aAAa,EAAE;EACf,OAAO;GACL,cAAc;GACd,gBAAgB;GAChB,cAAc;GACd,aAAa;GACb,aAAa;GACb,kBAAkB;GAClB,gBAAgB;GAChB,kBAAkB;GAClB,eAAe;GAChB;EACF;AAED,MAAK,MAAM,MAAM,KAAK;AACpB,SAAO,UAAU,KAAK,GAAG,GAAG,UAAU;AACtC,SAAO,QAAQ,KAAK,GAAG,GAAG,QAAQ;AAClC,SAAO,OAAO,KAAK,GAAG,GAAG,OAAO;AAChC,SAAO,OAAO,KAAK,GAAG,GAAG,OAAO;AAChC,SAAO,YAAY,KAAK,GAAG,GAAG,YAAY;AAE1C,SAAO,MAAM,gBAAgB,GAAG,MAAM;AACtC,SAAO,MAAM,kBAAkB,GAAG,MAAM;AACxC,SAAO,MAAM,gBAAgB,GAAG,MAAM;AACtC,SAAO,MAAM,eAAe,GAAG,MAAM;AACrC,SAAO,MAAM,eAAe,GAAG,MAAM;AACrC,SAAO,MAAM,oBAAoB,GAAG,MAAM;AAC1C,SAAO,MAAM,kBAAkB,GAAG,MAAM;AACxC,SAAO,MAAM,oBAAoB,GAAG,MAAM;AAC1C,SAAO,MAAM,iBAAiB,GAAG,MAAM;;AAGzC,QAAO;;;;;AAMT,SAAgB,cAAc,SAAgC;AAC5D,QAAO;EACL,SAAS;EACT,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC;EACA,WAAW,EAAE;EACb,SAAS,EAAE;EACX,QAAQ,EAAE;EACV,QAAQ,EAAE;EACV,aAAa,EAAE;EACf,OAAO;GACL,cAAc;GACd,gBAAgB;GAChB,cAAc;GACd,aAAa;GACb,aAAa;GACb,kBAAkB;GAClB,gBAAgB;GAChB,kBAAkB;GAClB,eAAe;GAChB;EACF"}
@@ -1,152 +0,0 @@
1
- import { createEmptyIR } from "../extract.js";
2
-
3
- //#region src/extractors/base.ts
4
- /**
5
- * Abstract base class for source extractors.
6
- * Provides common utilities and structure for framework-specific extractors.
7
- */
8
- var BaseExtractor = class {
9
- priority = 10;
10
- /**
11
- * File system adapter - must be set before extraction.
12
- */
13
- fs;
14
- /**
15
- * Set the file system adapter.
16
- */
17
- setFs(fs) {
18
- this.fs = fs;
19
- }
20
- /**
21
- * Detect if this extractor can handle the given project.
22
- * Default implementation checks if any supported framework is detected.
23
- */
24
- async detect(project) {
25
- return project.frameworks.some((fw) => this.frameworks.includes(fw.id));
26
- }
27
- /**
28
- * Main extraction method.
29
- */
30
- async extract(project, options) {
31
- if (!this.fs) return {
32
- success: false,
33
- errors: [{
34
- code: "NO_FS_ADAPTER",
35
- message: "File system adapter not configured",
36
- recoverable: false
37
- }]
38
- };
39
- const ir = createEmptyIR(project);
40
- const ctx = {
41
- project,
42
- options,
43
- fs: this.fs,
44
- ir
45
- };
46
- try {
47
- await this.doExtract(ctx);
48
- this.calculateStats(ir);
49
- return {
50
- success: true,
51
- ir
52
- };
53
- } catch (error) {
54
- return {
55
- success: false,
56
- errors: [{
57
- code: "EXTRACTION_ERROR",
58
- message: error instanceof Error ? error.message : String(error),
59
- recoverable: false
60
- }]
61
- };
62
- }
63
- }
64
- /**
65
- * Calculate statistics for the IR.
66
- */
67
- calculateStats(ir) {
68
- ir.stats.endpointsFound = ir.endpoints.length;
69
- ir.stats.schemasFound = ir.schemas.length;
70
- ir.stats.errorsFound = ir.errors.length;
71
- ir.stats.eventsFound = ir.events.length;
72
- ir.stats.ambiguitiesFound = ir.ambiguities.length;
73
- const allItems = [
74
- ...ir.endpoints,
75
- ...ir.schemas,
76
- ...ir.errors,
77
- ...ir.events
78
- ];
79
- for (const item of allItems) switch (item.confidence.level) {
80
- case "high":
81
- ir.stats.highConfidence++;
82
- break;
83
- case "medium":
84
- ir.stats.mediumConfidence++;
85
- break;
86
- case "low":
87
- case "ambiguous":
88
- ir.stats.lowConfidence++;
89
- break;
90
- }
91
- }
92
- /**
93
- * Generate a unique ID for an endpoint.
94
- */
95
- generateEndpointId(method, path, handlerName) {
96
- const pathPart = path.replace(/^\//, "").replace(/\//g, ".").replace(/:/g, "").replace(/\{/g, "").replace(/\}/g, "");
97
- const base = `${method.toLowerCase()}.${pathPart}`;
98
- return handlerName ? `${base}.${handlerName}` : base;
99
- }
100
- /**
101
- * Generate a unique ID for a schema.
102
- */
103
- generateSchemaId(name, file) {
104
- return `${file.replace(/\.ts$/, "").replace(/\//g, ".").replace(/^\.+/, "")}.${name}`;
105
- }
106
- /**
107
- * Determine operation kind from HTTP method.
108
- */
109
- methodToOpKind(method) {
110
- switch (method) {
111
- case "GET":
112
- case "HEAD":
113
- case "OPTIONS": return "query";
114
- default: return "command";
115
- }
116
- }
117
- /**
118
- * Create a source location object.
119
- */
120
- createLocation(file, startLine, endLine) {
121
- return {
122
- file,
123
- startLine,
124
- endLine
125
- };
126
- }
127
- /**
128
- * Create confidence metadata.
129
- */
130
- createConfidence(level, ...reasons) {
131
- return {
132
- level,
133
- reasons
134
- };
135
- }
136
- /**
137
- * Add an endpoint to the IR.
138
- */
139
- addEndpoint(ctx, endpoint) {
140
- ctx.ir.endpoints.push(endpoint);
141
- }
142
- /**
143
- * Add a schema to the IR.
144
- */
145
- addSchema(ctx, schema) {
146
- ctx.ir.schemas.push(schema);
147
- }
148
- };
149
-
150
- //#endregion
151
- export { BaseExtractor };
152
- //# sourceMappingURL=base.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"base.js","names":[],"sources":["../../src/extractors/base.ts"],"sourcesContent":["/**\n * Base extractor class.\n *\n * Provides common functionality for all framework extractors.\n */\n\nimport type { SourceExtractor } from '../registry';\nimport type {\n ConfidenceLevel,\n ConfidenceMeta,\n EndpointCandidate,\n ExtractOptions,\n ExtractResult,\n HttpMethod,\n ImportIR,\n OpKind,\n ProjectInfo,\n SchemaCandidate,\n SourceLocation,\n} from '../types';\nimport { createEmptyIR } from '../extract';\n\n/**\n * File system adapter interface for extractors.\n */\nexport interface ExtractorFsAdapter {\n readFile(path: string): Promise<string>;\n glob(pattern: string, options?: { cwd?: string }): Promise<string[]>;\n exists(path: string): Promise<boolean>;\n}\n\n/**\n * Context passed to extraction methods.\n */\nexport interface ExtractionContext {\n project: ProjectInfo;\n options: ExtractOptions;\n fs: ExtractorFsAdapter;\n ir: ImportIR;\n}\n\n/**\n * Abstract base class for source extractors.\n * Provides common utilities and structure for framework-specific extractors.\n */\nexport abstract class BaseExtractor implements SourceExtractor {\n abstract id: string;\n abstract name: string;\n abstract frameworks: string[];\n priority = 10;\n\n /**\n * File system adapter - must be set before extraction.\n */\n protected fs?: ExtractorFsAdapter;\n\n /**\n * Set the file system adapter.\n */\n setFs(fs: ExtractorFsAdapter): void {\n this.fs = fs;\n }\n\n /**\n * Detect if this extractor can handle the given project.\n * Default implementation checks if any supported framework is detected.\n */\n async detect(project: ProjectInfo): Promise<boolean> {\n return project.frameworks.some((fw) => this.frameworks.includes(fw.id));\n }\n\n /**\n * Main extraction method.\n */\n async extract(\n project: ProjectInfo,\n options: ExtractOptions\n ): Promise<ExtractResult> {\n if (!this.fs) {\n return {\n success: false,\n errors: [\n {\n code: 'NO_FS_ADAPTER',\n message: 'File system adapter not configured',\n recoverable: false,\n },\n ],\n };\n }\n\n const ir = createEmptyIR(project);\n const ctx: ExtractionContext = {\n project,\n options,\n fs: this.fs,\n ir,\n };\n\n try {\n await this.doExtract(ctx);\n this.calculateStats(ir);\n return { success: true, ir };\n } catch (error) {\n return {\n success: false,\n errors: [\n {\n code: 'EXTRACTION_ERROR',\n message: error instanceof Error ? error.message : String(error),\n recoverable: false,\n },\n ],\n };\n }\n }\n\n /**\n * Implement this method in subclasses to perform actual extraction.\n */\n protected abstract doExtract(ctx: ExtractionContext): Promise<void>;\n\n /**\n * Calculate statistics for the IR.\n */\n protected calculateStats(ir: ImportIR): void {\n ir.stats.endpointsFound = ir.endpoints.length;\n ir.stats.schemasFound = ir.schemas.length;\n ir.stats.errorsFound = ir.errors.length;\n ir.stats.eventsFound = ir.events.length;\n ir.stats.ambiguitiesFound = ir.ambiguities.length;\n\n const allItems = [\n ...ir.endpoints,\n ...ir.schemas,\n ...ir.errors,\n ...ir.events,\n ];\n\n for (const item of allItems) {\n switch (item.confidence.level) {\n case 'high':\n ir.stats.highConfidence++;\n break;\n case 'medium':\n ir.stats.mediumConfidence++;\n break;\n case 'low':\n case 'ambiguous':\n ir.stats.lowConfidence++;\n break;\n }\n }\n }\n\n // ───────────────────────────────────────────────────────────────────\n // Helper methods for subclasses\n // ───────────────────────────────────────────────────────────────────\n\n /**\n * Generate a unique ID for an endpoint.\n */\n protected generateEndpointId(\n method: HttpMethod,\n path: string,\n handlerName?: string\n ): string {\n const pathPart = path\n .replace(/^\\//, '')\n .replace(/\\//g, '.')\n .replace(/:/g, '')\n .replace(/\\{/g, '')\n .replace(/\\}/g, '');\n const base = `${method.toLowerCase()}.${pathPart}`;\n return handlerName ? `${base}.${handlerName}` : base;\n }\n\n /**\n * Generate a unique ID for a schema.\n */\n protected generateSchemaId(name: string, file: string): string {\n const filePart = file\n .replace(/\\.ts$/, '')\n .replace(/\\//g, '.')\n .replace(/^\\.+/, '');\n return `${filePart}.${name}`;\n }\n\n /**\n * Determine operation kind from HTTP method.\n */\n protected methodToOpKind(method: HttpMethod): OpKind {\n switch (method) {\n case 'GET':\n case 'HEAD':\n case 'OPTIONS':\n return 'query';\n default:\n return 'command';\n }\n }\n\n /**\n * Create a source location object.\n */\n protected createLocation(\n file: string,\n startLine: number,\n endLine: number\n ): SourceLocation {\n return { file, startLine, endLine };\n }\n\n /**\n * Create confidence metadata.\n */\n protected createConfidence(\n level: ConfidenceLevel,\n ...reasons: ConfidenceMeta['reasons']\n ): ConfidenceMeta {\n return { level, reasons };\n }\n\n /**\n * Add an endpoint to the IR.\n */\n protected addEndpoint(\n ctx: ExtractionContext,\n endpoint: EndpointCandidate\n ): void {\n ctx.ir.endpoints.push(endpoint);\n }\n\n /**\n * Add a schema to the IR.\n */\n protected addSchema(ctx: ExtractionContext, schema: SchemaCandidate): void {\n ctx.ir.schemas.push(schema);\n }\n}\n"],"mappings":";;;;;;;AA6CA,IAAsB,gBAAtB,MAA+D;CAI7D,WAAW;;;;CAKX,AAAU;;;;CAKV,MAAM,IAA8B;AAClC,OAAK,KAAK;;;;;;CAOZ,MAAM,OAAO,SAAwC;AACnD,SAAO,QAAQ,WAAW,MAAM,OAAO,KAAK,WAAW,SAAS,GAAG,GAAG,CAAC;;;;;CAMzE,MAAM,QACJ,SACA,SACwB;AACxB,MAAI,CAAC,KAAK,GACR,QAAO;GACL,SAAS;GACT,QAAQ,CACN;IACE,MAAM;IACN,SAAS;IACT,aAAa;IACd,CACF;GACF;EAGH,MAAM,KAAK,cAAc,QAAQ;EACjC,MAAM,MAAyB;GAC7B;GACA;GACA,IAAI,KAAK;GACT;GACD;AAED,MAAI;AACF,SAAM,KAAK,UAAU,IAAI;AACzB,QAAK,eAAe,GAAG;AACvB,UAAO;IAAE,SAAS;IAAM;IAAI;WACrB,OAAO;AACd,UAAO;IACL,SAAS;IACT,QAAQ,CACN;KACE,MAAM;KACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;KAC/D,aAAa;KACd,CACF;IACF;;;;;;CAYL,AAAU,eAAe,IAAoB;AAC3C,KAAG,MAAM,iBAAiB,GAAG,UAAU;AACvC,KAAG,MAAM,eAAe,GAAG,QAAQ;AACnC,KAAG,MAAM,cAAc,GAAG,OAAO;AACjC,KAAG,MAAM,cAAc,GAAG,OAAO;AACjC,KAAG,MAAM,mBAAmB,GAAG,YAAY;EAE3C,MAAM,WAAW;GACf,GAAG,GAAG;GACN,GAAG,GAAG;GACN,GAAG,GAAG;GACN,GAAG,GAAG;GACP;AAED,OAAK,MAAM,QAAQ,SACjB,SAAQ,KAAK,WAAW,OAAxB;GACE,KAAK;AACH,OAAG,MAAM;AACT;GACF,KAAK;AACH,OAAG,MAAM;AACT;GACF,KAAK;GACL,KAAK;AACH,OAAG,MAAM;AACT;;;;;;CAYR,AAAU,mBACR,QACA,MACA,aACQ;EACR,MAAM,WAAW,KACd,QAAQ,OAAO,GAAG,CAClB,QAAQ,OAAO,IAAI,CACnB,QAAQ,MAAM,GAAG,CACjB,QAAQ,OAAO,GAAG,CAClB,QAAQ,OAAO,GAAG;EACrB,MAAM,OAAO,GAAG,OAAO,aAAa,CAAC,GAAG;AACxC,SAAO,cAAc,GAAG,KAAK,GAAG,gBAAgB;;;;;CAMlD,AAAU,iBAAiB,MAAc,MAAsB;AAK7D,SAAO,GAJU,KACd,QAAQ,SAAS,GAAG,CACpB,QAAQ,OAAO,IAAI,CACnB,QAAQ,QAAQ,GAAG,CACH,GAAG;;;;;CAMxB,AAAU,eAAe,QAA4B;AACnD,UAAQ,QAAR;GACE,KAAK;GACL,KAAK;GACL,KAAK,UACH,QAAO;GACT,QACE,QAAO;;;;;;CAOb,AAAU,eACR,MACA,WACA,SACgB;AAChB,SAAO;GAAE;GAAM;GAAW;GAAS;;;;;CAMrC,AAAU,iBACR,OACA,GAAG,SACa;AAChB,SAAO;GAAE;GAAO;GAAS;;;;;CAM3B,AAAU,YACR,KACA,UACM;AACN,MAAI,GAAG,UAAU,KAAK,SAAS;;;;;CAMjC,AAAU,UAAU,KAAwB,QAA+B;AACzE,MAAI,GAAG,QAAQ,KAAK,OAAO"}
@@ -1,58 +0,0 @@
1
- import { BaseExtractor } from "../base.js";
2
-
3
- //#region src/extractors/elysia/extractor.ts
4
- /**
5
- * Elysia extractor.
6
- *
7
- * Extracts contract candidates from Elysia applications by parsing:
8
- * - app.get(), app.post(), etc. route handlers
9
- * - t.* schema definitions
10
- */
11
- const PATTERNS = {
12
- route: /\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
13
- tSchema: /body:\s*t\.\w+/g
14
- };
15
- var ElysiaExtractor = class extends BaseExtractor {
16
- id = "elysia";
17
- name = "Elysia Extractor";
18
- frameworks = ["elysia"];
19
- priority = 15;
20
- async doExtract(ctx) {
21
- const { project, options, fs } = ctx;
22
- const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
23
- const files = await fs.glob(pattern, { cwd: project.rootPath });
24
- ctx.ir.stats.filesScanned = files.length;
25
- for (const file of files) {
26
- if (file.includes("node_modules") || file.includes(".test.")) continue;
27
- const fullPath = `${project.rootPath}/${file}`;
28
- const content = await fs.readFile(fullPath);
29
- if (!content.includes("elysia")) continue;
30
- await this.extractRoutes(ctx, file, content);
31
- }
32
- }
33
- async extractRoutes(ctx, file, content) {
34
- const matches = [...content.matchAll(PATTERNS.route)];
35
- for (const match of matches) {
36
- const method = match[1]?.toUpperCase() ?? "GET";
37
- const path = match[2] ?? "/";
38
- const index = match.index ?? 0;
39
- const lineNumber = content.slice(0, index).split("\n").length;
40
- const afterMatch = content.slice(index, index + 500);
41
- const hasTSchema = PATTERNS.tSchema.test(afterMatch);
42
- const endpoint = {
43
- id: this.generateEndpointId(method, path),
44
- method,
45
- path,
46
- kind: this.methodToOpKind(method),
47
- handlerName: "handler",
48
- source: this.createLocation(file, lineNumber, lineNumber + 5),
49
- confidence: this.createConfidence(hasTSchema ? "high" : "medium", hasTSchema ? "explicit-schema" : "decorator-hints")
50
- };
51
- this.addEndpoint(ctx, endpoint);
52
- }
53
- }
54
- };
55
-
56
- //#endregion
57
- export { ElysiaExtractor };
58
- //# sourceMappingURL=extractor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/elysia/extractor.ts"],"sourcesContent":["/**\n * Elysia extractor.\n *\n * Extracts contract candidates from Elysia applications by parsing:\n * - app.get(), app.post(), etc. route handlers\n * - t.* schema definitions\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n route: /\\.(get|post|put|patch|delete)\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/gi,\n tSchema: /body:\\s*t\\.\\w+/g,\n};\n\nexport class ElysiaExtractor extends BaseExtractor {\n id = 'elysia';\n name = 'Elysia Extractor';\n frameworks = ['elysia'];\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('elysia')) continue;\n\n await this.extractRoutes(ctx, file, content);\n }\n }\n\n private async extractRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const matches = [...content.matchAll(PATTERNS.route)];\n\n for (const match of matches) {\n const method = (match[1]?.toUpperCase() ?? 'GET') as HttpMethod;\n const path = match[2] ?? '/';\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 hasTSchema = PATTERNS.tSchema.test(afterMatch);\n\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, path),\n method,\n path,\n kind: this.methodToOpKind(method),\n handlerName: 'handler',\n source: this.createLocation(file, lineNumber, lineNumber + 5),\n confidence: this.createConfidence(\n hasTSchema ? 'high' : 'medium',\n hasTSchema ? 'explicit-schema' : 'decorator-hints'\n ),\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAM,WAAW;CACf,OAAO;CACP,SAAS;CACV;AAED,IAAa,kBAAb,cAAqC,cAAc;CACjD,KAAK;CACL,OAAO;CACP,aAAa,CAAC,SAAS;CACvB,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,SAAS,CAAE;AAEjC,SAAM,KAAK,cAAc,KAAK,MAAM,QAAQ;;;CAIhD,MAAc,cACZ,KACA,MACA,SACe;EACf,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,MAAM,CAAC;AAErD,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAU,MAAM,IAAI,aAAa,IAAI;GAC3C,MAAM,OAAO,MAAM,MAAM;GACzB,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,aAAa,SAAS,QAAQ,KAAK,WAAW;GAEpD,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,KAAK;IACzC;IACA;IACA,MAAM,KAAK,eAAe,OAAO;IACjC,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,EAAE;IAC7D,YAAY,KAAK,iBACf,aAAa,SAAS,UACtB,aAAa,oBAAoB,kBAClC;IACF;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -1,61 +0,0 @@
1
- import { BaseExtractor } from "../base.js";
2
-
3
- //#region src/extractors/express/extractor.ts
4
- /**
5
- * Express extractor.
6
- *
7
- * Extracts contract candidates from Express applications by parsing:
8
- * - app.get(), app.post(), etc. route handlers
9
- * - Router instances
10
- * - Zod validation middleware
11
- */
12
- const PATTERNS = {
13
- route: /(?:app|router)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
14
- routerUse: /(?:app|router)\.use\s*\(\s*['"`]([^'"`]+)['"`]/gi,
15
- zodValidate: /validate\s*\(\s*(\w+)\s*\)/g
16
- };
17
- var ExpressExtractor = class extends BaseExtractor {
18
- id = "express";
19
- name = "Express Extractor";
20
- frameworks = ["express"];
21
- priority = 15;
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
- await this.extractRoutes(ctx, file, content);
32
- }
33
- }
34
- async extractRoutes(ctx, file, content) {
35
- const matches = [...content.matchAll(PATTERNS.route)];
36
- for (const match of matches) {
37
- const method = match[1]?.toUpperCase() ?? "GET";
38
- const path = match[2] ?? "/";
39
- const index = match.index ?? 0;
40
- const lineNumber = content.slice(0, index).split("\n").length;
41
- const afterMatch = content.slice(index, index + 500);
42
- const handlerMatch = afterMatch.match(/(?:async\s+)?(?:function\s+)?(\w+)|,\s*(\w+)\s*\)/);
43
- const handlerName = handlerMatch?.[1] ?? handlerMatch?.[2] ?? "handler";
44
- const hasZodValidation = PATTERNS.zodValidate.test(afterMatch);
45
- const endpoint = {
46
- id: this.generateEndpointId(method, path, handlerName),
47
- method,
48
- path,
49
- kind: this.methodToOpKind(method),
50
- handlerName,
51
- source: this.createLocation(file, lineNumber, lineNumber + 5),
52
- confidence: this.createConfidence(hasZodValidation ? "high" : "medium", hasZodValidation ? "explicit-schema" : "decorator-hints")
53
- };
54
- this.addEndpoint(ctx, endpoint);
55
- }
56
- }
57
- };
58
-
59
- //#endregion
60
- export { ExpressExtractor };
61
- //# sourceMappingURL=extractor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/express/extractor.ts"],"sourcesContent":["/**\n * Express extractor.\n *\n * Extracts contract candidates from Express applications by parsing:\n * - app.get(), app.post(), etc. route handlers\n * - Router instances\n * - Zod validation middleware\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n route:\n /(?:app|router)\\.(get|post|put|patch|delete|head|options)\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/gi,\n routerUse: /(?:app|router)\\.use\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/gi,\n zodValidate: /validate\\s*\\(\\s*(\\w+)\\s*\\)/g,\n};\n\nexport class ExpressExtractor extends BaseExtractor {\n id = 'express';\n name = 'Express Extractor';\n frameworks = ['express'];\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 await this.extractRoutes(ctx, file, content);\n }\n }\n\n private async extractRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const matches = [...content.matchAll(PATTERNS.route)];\n\n for (const match of matches) {\n const method = (match[1]?.toUpperCase() ?? 'GET') as HttpMethod;\n const path = match[2] ?? '/';\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 handlerMatch = afterMatch.match(\n /(?:async\\s+)?(?:function\\s+)?(\\w+)|,\\s*(\\w+)\\s*\\)/\n );\n const handlerName = handlerMatch?.[1] ?? handlerMatch?.[2] ?? 'handler';\n\n const hasZodValidation = PATTERNS.zodValidate.test(afterMatch);\n\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, path, handlerName),\n method,\n path,\n kind: this.methodToOpKind(method),\n handlerName,\n source: this.createLocation(file, lineNumber, lineNumber + 5),\n confidence: this.createConfidence(\n hasZodValidation ? 'high' : 'medium',\n hasZodValidation ? 'explicit-schema' : 'decorator-hints'\n ),\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAM,WAAW;CACf,OACE;CACF,WAAW;CACX,aAAa;CACd;AAED,IAAa,mBAAb,cAAsC,cAAc;CAClD,KAAK;CACL,OAAO;CACP,aAAa,CAAC,UAAU;CACxB,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,SAAM,KAAK,cAAc,KAAK,MAAM,QAAQ;;;CAIhD,MAAc,cACZ,KACA,MACA,SACe;EACf,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,MAAM,CAAC;AAErD,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAU,MAAM,IAAI,aAAa,IAAI;GAC3C,MAAM,OAAO,MAAM,MAAM;GACzB,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,eAAe,WAAW,MAC9B,oDACD;GACD,MAAM,cAAc,eAAe,MAAM,eAAe,MAAM;GAE9D,MAAM,mBAAmB,SAAS,YAAY,KAAK,WAAW;GAE9D,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,MAAM,YAAY;IACtD;IACA;IACA,MAAM,KAAK,eAAe,OAAO;IACjC;IACA,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,EAAE;IAC7D,YAAY,KAAK,iBACf,mBAAmB,SAAS,UAC5B,mBAAmB,oBAAoB,kBACxC;IACF;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -1,61 +0,0 @@
1
- import { BaseExtractor } from "../base.js";
2
-
3
- //#region src/extractors/fastify/extractor.ts
4
- /**
5
- * Fastify extractor.
6
- *
7
- * Extracts contract candidates from Fastify applications by parsing:
8
- * - fastify.get(), fastify.post(), etc. route handlers
9
- * - JSON Schema definitions
10
- * - Route options with schema
11
- */
12
- const PATTERNS = {
13
- route: /(?:fastify|app|server)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
14
- schemaOption: /schema\s*:\s*\{/g,
15
- bodySchema: /body\s*:\s*(\w+)/g,
16
- responseSchema: /response\s*:\s*\{/g
17
- };
18
- var FastifyExtractor = class extends BaseExtractor {
19
- id = "fastify";
20
- name = "Fastify Extractor";
21
- frameworks = ["fastify"];
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
- await this.extractRoutes(ctx, file, content);
33
- }
34
- }
35
- async extractRoutes(ctx, file, content) {
36
- const matches = [...content.matchAll(PATTERNS.route)];
37
- for (const match of matches) {
38
- const method = match[1]?.toUpperCase() ?? "GET";
39
- const path = match[2] ?? "/";
40
- const index = match.index ?? 0;
41
- const lineNumber = content.slice(0, index).split("\n").length;
42
- const afterMatch = content.slice(index, index + 1e3);
43
- const hasSchema = PATTERNS.schemaOption.test(afterMatch);
44
- const endpoint = {
45
- id: this.generateEndpointId(method, path),
46
- method,
47
- path,
48
- kind: this.methodToOpKind(method),
49
- handlerName: "handler",
50
- source: this.createLocation(file, lineNumber, lineNumber + 10),
51
- confidence: this.createConfidence(hasSchema ? "high" : "medium", hasSchema ? "explicit-schema" : "decorator-hints"),
52
- frameworkMeta: { hasSchema }
53
- };
54
- this.addEndpoint(ctx, endpoint);
55
- }
56
- }
57
- };
58
-
59
- //#endregion
60
- export { FastifyExtractor };
61
- //# sourceMappingURL=extractor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/fastify/extractor.ts"],"sourcesContent":["/**\n * Fastify extractor.\n *\n * Extracts contract candidates from Fastify applications by parsing:\n * - fastify.get(), fastify.post(), etc. route handlers\n * - JSON Schema definitions\n * - Route options with schema\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n route:\n /(?:fastify|app|server)\\.(get|post|put|patch|delete|head|options)\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/gi,\n schemaOption: /schema\\s*:\\s*\\{/g,\n bodySchema: /body\\s*:\\s*(\\w+)/g,\n responseSchema: /response\\s*:\\s*\\{/g,\n};\n\nexport class FastifyExtractor extends BaseExtractor {\n id = 'fastify';\n name = 'Fastify Extractor';\n frameworks = ['fastify'];\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 await this.extractRoutes(ctx, file, content);\n }\n }\n\n private async extractRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const matches = [...content.matchAll(PATTERNS.route)];\n\n for (const match of matches) {\n const method = (match[1]?.toUpperCase() ?? 'GET') as HttpMethod;\n const path = match[2] ?? '/';\n const index = match.index ?? 0;\n const lineNumber = content.slice(0, index).split('\\n').length;\n\n const afterMatch = content.slice(index, index + 1000);\n const hasSchema = PATTERNS.schemaOption.test(afterMatch);\n\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, path),\n method,\n path,\n kind: this.methodToOpKind(method),\n handlerName: 'handler',\n source: this.createLocation(file, lineNumber, lineNumber + 10),\n confidence: this.createConfidence(\n hasSchema ? 'high' : 'medium',\n hasSchema ? 'explicit-schema' : 'decorator-hints'\n ),\n frameworkMeta: { hasSchema },\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAM,WAAW;CACf,OACE;CACF,cAAc;CACd,YAAY;CACZ,gBAAgB;CACjB;AAED,IAAa,mBAAb,cAAsC,cAAc;CAClD,KAAK;CACL,OAAO;CACP,aAAa,CAAC,UAAU;CACxB,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,SAAM,KAAK,cAAc,KAAK,MAAM,QAAQ;;;CAIhD,MAAc,cACZ,KACA,MACA,SACe;EACf,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,MAAM,CAAC;AAErD,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAU,MAAM,IAAI,aAAa,IAAI;GAC3C,MAAM,OAAO,MAAM,MAAM;GACzB,MAAM,QAAQ,MAAM,SAAS;GAC7B,MAAM,aAAa,QAAQ,MAAM,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC;GAEvD,MAAM,aAAa,QAAQ,MAAM,OAAO,QAAQ,IAAK;GACrD,MAAM,YAAY,SAAS,aAAa,KAAK,WAAW;GAExD,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,KAAK;IACzC;IACA;IACA,MAAM,KAAK,eAAe,OAAO;IACjC,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,GAAG;IAC9D,YAAY,KAAK,iBACf,YAAY,SAAS,UACrB,YAAY,oBAAoB,kBACjC;IACD,eAAe,EAAE,WAAW;IAC7B;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -1,57 +0,0 @@
1
- import { BaseExtractor } from "../base.js";
2
-
3
- //#region src/extractors/hono/extractor.ts
4
- /**
5
- * Hono extractor.
6
- *
7
- * Extracts contract candidates from Hono applications by parsing:
8
- * - app.get(), app.post(), etc. route handlers
9
- * - Zod validator middleware
10
- */
11
- const PATTERNS = {
12
- route: /(?:app|hono)\.(get|post|put|patch|delete|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
13
- zodValidator: /zValidator\s*\(\s*['"`](\w+)['"`]\s*,\s*(\w+)/g
14
- };
15
- var HonoExtractor = class extends BaseExtractor {
16
- id = "hono";
17
- name = "Hono Extractor";
18
- frameworks = ["hono"];
19
- priority = 15;
20
- async doExtract(ctx) {
21
- const { project, options, fs } = ctx;
22
- const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
23
- const files = await fs.glob(pattern, { cwd: project.rootPath });
24
- ctx.ir.stats.filesScanned = files.length;
25
- for (const file of files) {
26
- if (file.includes("node_modules") || file.includes(".test.")) continue;
27
- const fullPath = `${project.rootPath}/${file}`;
28
- const content = await fs.readFile(fullPath);
29
- await this.extractRoutes(ctx, file, content);
30
- }
31
- }
32
- async extractRoutes(ctx, file, content) {
33
- const matches = [...content.matchAll(PATTERNS.route)];
34
- for (const match of matches) {
35
- const method = match[1]?.toUpperCase() ?? "GET";
36
- const path = match[2] ?? "/";
37
- const index = match.index ?? 0;
38
- const lineNumber = content.slice(0, index).split("\n").length;
39
- const afterMatch = content.slice(index, index + 500);
40
- const hasZodValidator = PATTERNS.zodValidator.test(afterMatch);
41
- const endpoint = {
42
- id: this.generateEndpointId(method, path),
43
- method,
44
- path,
45
- kind: this.methodToOpKind(method),
46
- handlerName: "handler",
47
- source: this.createLocation(file, lineNumber, lineNumber + 5),
48
- confidence: this.createConfidence(hasZodValidator ? "high" : "medium", hasZodValidator ? "explicit-schema" : "decorator-hints")
49
- };
50
- this.addEndpoint(ctx, endpoint);
51
- }
52
- }
53
- };
54
-
55
- //#endregion
56
- export { HonoExtractor };
57
- //# sourceMappingURL=extractor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/hono/extractor.ts"],"sourcesContent":["/**\n * Hono extractor.\n *\n * Extracts contract candidates from Hono applications by parsing:\n * - app.get(), app.post(), etc. route handlers\n * - Zod validator middleware\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n route:\n /(?:app|hono)\\.(get|post|put|patch|delete|all)\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/gi,\n zodValidator: /zValidator\\s*\\(\\s*['\"`](\\w+)['\"`]\\s*,\\s*(\\w+)/g,\n};\n\nexport class HonoExtractor extends BaseExtractor {\n id = 'hono';\n name = 'Hono Extractor';\n frameworks = ['hono'];\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 await this.extractRoutes(ctx, file, content);\n }\n }\n\n private async extractRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const matches = [...content.matchAll(PATTERNS.route)];\n\n for (const match of matches) {\n const method = (match[1]?.toUpperCase() ?? 'GET') as HttpMethod;\n const path = match[2] ?? '/';\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 hasZodValidator = PATTERNS.zodValidator.test(afterMatch);\n\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, path),\n method,\n path,\n kind: this.methodToOpKind(method),\n handlerName: 'handler',\n source: this.createLocation(file, lineNumber, lineNumber + 5),\n confidence: this.createConfidence(\n hasZodValidator ? 'high' : 'medium',\n hasZodValidator ? 'explicit-schema' : 'decorator-hints'\n ),\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAM,WAAW;CACf,OACE;CACF,cAAc;CACf;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,SAAM,KAAK,cAAc,KAAK,MAAM,QAAQ;;;CAIhD,MAAc,cACZ,KACA,MACA,SACe;EACf,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,MAAM,CAAC;AAErD,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAU,MAAM,IAAI,aAAa,IAAI;GAC3C,MAAM,OAAO,MAAM,MAAM;GACzB,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,kBAAkB,SAAS,aAAa,KAAK,WAAW;GAE9D,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,KAAK;IACzC;IACA;IACA,MAAM,KAAK,eAAe,OAAO;IACjC,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,EAAE;IAC7D,YAAY,KAAK,iBACf,kBAAkB,SAAS,UAC3B,kBAAkB,oBAAoB,kBACvC;IACF;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/extractors/index.ts"],"sourcesContent":["/**\n * Source extractors module.\n *\n * Exports all available framework extractors.\n */\n\n// Base extractor class\nexport { BaseExtractor } from './base';\nexport type { ExtractorFsAdapter, ExtractionContext } from './base';\n\n// Framework-specific extractors\nexport { NestJsExtractor } from './nestjs/extractor';\nexport { ExpressExtractor } from './express/extractor';\nexport { FastifyExtractor } from './fastify/extractor';\nexport { HonoExtractor } from './hono/extractor';\nexport { ElysiaExtractor } from './elysia/extractor';\nexport { TrpcExtractor } from './trpc/extractor';\nexport { NextApiExtractor } from './next-api/extractor';\n\n// Schema extractors\nexport { ZodSchemaExtractor } from './zod/extractor';\n\n// Registration helper\nimport { extractorRegistry } from '../registry';\nimport { NestJsExtractor } from './nestjs/extractor';\nimport { ExpressExtractor } from './express/extractor';\nimport { FastifyExtractor } from './fastify/extractor';\nimport { HonoExtractor } from './hono/extractor';\nimport { ElysiaExtractor } from './elysia/extractor';\nimport { TrpcExtractor } from './trpc/extractor';\nimport { NextApiExtractor } from './next-api/extractor';\nimport { ZodSchemaExtractor } from './zod/extractor';\n\n/**\n * Register all built-in extractors with the global registry.\n */\nexport function registerAllExtractors(): void {\n extractorRegistry.register(new NestJsExtractor());\n extractorRegistry.register(new ExpressExtractor());\n extractorRegistry.register(new FastifyExtractor());\n extractorRegistry.register(new HonoExtractor());\n extractorRegistry.register(new ElysiaExtractor());\n extractorRegistry.register(new TrpcExtractor());\n extractorRegistry.register(new NextApiExtractor());\n extractorRegistry.register(new ZodSchemaExtractor());\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,wBAA8B;AAC5C,mBAAkB,SAAS,IAAI,iBAAiB,CAAC;AACjD,mBAAkB,SAAS,IAAI,kBAAkB,CAAC;AAClD,mBAAkB,SAAS,IAAI,kBAAkB,CAAC;AAClD,mBAAkB,SAAS,IAAI,eAAe,CAAC;AAC/C,mBAAkB,SAAS,IAAI,iBAAiB,CAAC;AACjD,mBAAkB,SAAS,IAAI,eAAe,CAAC;AAC/C,mBAAkB,SAAS,IAAI,kBAAkB,CAAC;AAClD,mBAAkB,SAAS,IAAI,oBAAoB,CAAC"}