@contractspec/lib.contracts-transformers 0.0.0-canary-20260113162409
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/common/index.d.ts +3 -0
- package/dist/common/index.js +3 -0
- package/dist/common/types.d.ts +159 -0
- package/dist/common/types.d.ts.map +1 -0
- package/dist/common/utils.d.ts +52 -0
- package/dist/common/utils.d.ts.map +1 -0
- package/dist/common/utils.js +103 -0
- package/dist/common/utils.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +18 -0
- package/dist/openapi/differ.d.ts +42 -0
- package/dist/openapi/differ.d.ts.map +1 -0
- package/dist/openapi/differ.js +222 -0
- package/dist/openapi/differ.js.map +1 -0
- package/dist/openapi/exporter/data-views.d.ts +38 -0
- package/dist/openapi/exporter/data-views.d.ts.map +1 -0
- package/dist/openapi/exporter/data-views.js +47 -0
- package/dist/openapi/exporter/data-views.js.map +1 -0
- package/dist/openapi/exporter/events.d.ts +28 -0
- package/dist/openapi/exporter/events.d.ts.map +1 -0
- package/dist/openapi/exporter/events.js +39 -0
- package/dist/openapi/exporter/events.js.map +1 -0
- package/dist/openapi/exporter/features.d.ts +37 -0
- package/dist/openapi/exporter/features.d.ts.map +1 -0
- package/dist/openapi/exporter/features.js +46 -0
- package/dist/openapi/exporter/features.js.map +1 -0
- package/dist/openapi/exporter/forms.d.ts +30 -0
- package/dist/openapi/exporter/forms.d.ts.map +1 -0
- package/dist/openapi/exporter/forms.js +49 -0
- package/dist/openapi/exporter/forms.js.map +1 -0
- package/dist/openapi/exporter/index.js +8 -0
- package/dist/openapi/exporter/operations.d.ts +65 -0
- package/dist/openapi/exporter/operations.d.ts.map +1 -0
- package/dist/openapi/exporter/operations.js +143 -0
- package/dist/openapi/exporter/operations.js.map +1 -0
- package/dist/openapi/exporter/presentations.d.ts +32 -0
- package/dist/openapi/exporter/presentations.d.ts.map +1 -0
- package/dist/openapi/exporter/presentations.js +60 -0
- package/dist/openapi/exporter/presentations.js.map +1 -0
- package/dist/openapi/exporter/registries.d.ts +23 -0
- package/dist/openapi/exporter/registries.d.ts.map +1 -0
- package/dist/openapi/exporter/registries.js +29 -0
- package/dist/openapi/exporter/registries.js.map +1 -0
- package/dist/openapi/exporter/workflows.d.ts +36 -0
- package/dist/openapi/exporter/workflows.d.ts.map +1 -0
- package/dist/openapi/exporter/workflows.js +54 -0
- package/dist/openapi/exporter/workflows.js.map +1 -0
- package/dist/openapi/exporter.d.ts +48 -0
- package/dist/openapi/exporter.d.ts.map +1 -0
- package/dist/openapi/exporter.js +122 -0
- package/dist/openapi/exporter.js.map +1 -0
- package/dist/openapi/importer/analyzer.js +28 -0
- package/dist/openapi/importer/analyzer.js.map +1 -0
- package/dist/openapi/importer/events.js +40 -0
- package/dist/openapi/importer/events.js.map +1 -0
- package/dist/openapi/importer/generator.js +105 -0
- package/dist/openapi/importer/generator.js.map +1 -0
- package/dist/openapi/importer/grouping.js +73 -0
- package/dist/openapi/importer/grouping.js.map +1 -0
- package/dist/openapi/importer/index.d.ts +17 -0
- package/dist/openapi/importer/index.d.ts.map +1 -0
- package/dist/openapi/importer/index.js +175 -0
- package/dist/openapi/importer/index.js.map +1 -0
- package/dist/openapi/importer/models.js +22 -0
- package/dist/openapi/importer/models.js.map +1 -0
- package/dist/openapi/importer/schemas.js +60 -0
- package/dist/openapi/importer/schemas.js.map +1 -0
- package/dist/openapi/index.d.ts +16 -0
- package/dist/openapi/index.js +18 -0
- package/dist/openapi/parser/document.d.ts +20 -0
- package/dist/openapi/parser/document.d.ts.map +1 -0
- package/dist/openapi/parser/document.js +95 -0
- package/dist/openapi/parser/document.js.map +1 -0
- package/dist/openapi/parser/index.js +5 -0
- package/dist/openapi/parser/operation.js +59 -0
- package/dist/openapi/parser/operation.js.map +1 -0
- package/dist/openapi/parser/parameters.js +37 -0
- package/dist/openapi/parser/parameters.js.map +1 -0
- package/dist/openapi/parser/resolvers.js +63 -0
- package/dist/openapi/parser/resolvers.js.map +1 -0
- package/dist/openapi/parser/utils.d.ts +19 -0
- package/dist/openapi/parser/utils.d.ts.map +1 -0
- package/dist/openapi/parser/utils.js +48 -0
- package/dist/openapi/parser/utils.js.map +1 -0
- package/dist/openapi/parser.js +6 -0
- package/dist/openapi/schema-converter.d.ts +71 -0
- package/dist/openapi/schema-converter.d.ts.map +1 -0
- package/dist/openapi/schema-converter.js +161 -0
- package/dist/openapi/schema-converter.js.map +1 -0
- package/dist/openapi/schema-generators/index.js +462 -0
- package/dist/openapi/schema-generators/index.js.map +1 -0
- package/dist/openapi/types.d.ts +277 -0
- package/dist/openapi/types.d.ts.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/openapi/importer/index.ts"],"sourcesContent":["import type {\n OpenApiSource,\n OpenApiTransportHints,\n ParsedOperation,\n ParseResult,\n} from '../types';\nimport type { ImportedOperationSpec, ImportResult } from '../../common/types';\nimport { toFileName, toSpecKey } from '../../common/utils';\nimport { generateSchemaModelCode } from '../schema-converter';\nimport { buildInputSchemas, getOutputSchema } from './schemas';\nimport { generateSpecCode } from './generator';\nimport { generateModelCode } from './models';\nimport { generateEventCode } from './events';\nimport {\n resolveEventGroupFolder,\n resolveModelGroupFolder,\n resolveOperationGroupFolder,\n} from './grouping';\nimport type {\n ContractsrcConfig,\n OpenApiSourceConfig,\n} from '@contractspec/lib.contracts';\n\nexport * from './analyzer';\nexport * from './schemas';\nexport * from './generator';\nexport * from './models';\nexport * from './events';\nexport * from './grouping';\n\n/**\n * Import operations from a parsed OpenAPI document.\n */\nexport const importFromOpenApi = (\n parseResult: ParseResult,\n contractspecOptions: ContractsrcConfig,\n importOptions: Partial<OpenApiSourceConfig> = {}\n): ImportResult => {\n const { tags, exclude = [], include } = importOptions;\n const specs: ImportedOperationSpec[] = [];\n const skipped: ImportResult['skipped'] = [];\n const errors: ImportResult['errors'] = [];\n\n for (const operation of parseResult.operations) {\n // Filter by tags if specified\n if (tags && tags.length > 0) {\n const hasMatchingTag = operation.tags.some((t) => tags.includes(t));\n if (!hasMatchingTag) {\n skipped.push({\n sourceId: operation.operationId,\n reason: `No matching tags (has: ${operation.tags.join(', ')})`,\n });\n continue;\n }\n }\n\n // Filter by include/exclude\n if (include && include.length > 0) {\n if (!include.includes(operation.operationId)) {\n skipped.push({\n sourceId: operation.operationId,\n reason: 'Not in include list',\n });\n continue;\n }\n } else if (exclude.includes(operation.operationId)) {\n skipped.push({\n sourceId: operation.operationId,\n reason: 'In exclude list',\n });\n continue;\n }\n\n // Skip deprecated operations by default\n if (\n operation.deprecated &&\n importOptions.defaultStability !== 'deprecated'\n ) {\n skipped.push({\n sourceId: operation.operationId,\n reason: 'Deprecated operation',\n });\n continue;\n }\n\n try {\n // Build input schemas\n const inputSchemas = buildInputSchemas(operation);\n const schemaFormat =\n importOptions.schemaFormat ||\n contractspecOptions.schemaFormat ||\n 'contractspec';\n\n // Generate models for each input source\n const inputModel = inputSchemas.body\n ? generateSchemaModelCode(\n inputSchemas.body,\n `${operation.operationId}Input`,\n schemaFormat,\n contractspecOptions\n )\n : null;\n\n const queryModel = inputSchemas.query\n ? generateSchemaModelCode(\n inputSchemas.query,\n `${operation.operationId}Query`,\n schemaFormat,\n contractspecOptions\n )\n : null;\n\n const paramsModel = inputSchemas.params\n ? generateSchemaModelCode(\n inputSchemas.params,\n `${operation.operationId}Params`,\n schemaFormat,\n contractspecOptions\n )\n : null;\n\n const headersModel = inputSchemas.headers\n ? generateSchemaModelCode(\n inputSchemas.headers,\n `${operation.operationId}Headers`,\n schemaFormat,\n contractspecOptions\n )\n : null;\n\n // Get output schema\n const outputSchema = getOutputSchema(operation);\n let outputModel = outputSchema\n ? generateSchemaModelCode(\n outputSchema,\n `${operation.operationId}Output`,\n schemaFormat,\n contractspecOptions\n )\n : null;\n\n // Filter out empty/comment-only output models ONLY for ContractSpec format\n if (\n outputModel &&\n schemaFormat === 'contractspec' &&\n !outputModel.code.includes('defineSchemaModel')\n ) {\n outputModel = null;\n }\n\n // Generate spec code\n const code = generateSpecCode(\n operation,\n contractspecOptions,\n importOptions,\n inputModel,\n outputModel,\n queryModel,\n paramsModel,\n headersModel\n );\n const specName = toSpecKey(operation.operationId, importOptions.prefix);\n const fileName = toFileName(specName);\n\n // Build transport hints\n const transportHints: OpenApiTransportHints = {\n rest: {\n method:\n operation.method.toUpperCase() as OpenApiTransportHints['rest']['method'],\n path: operation.path,\n params: {\n path: operation.pathParams.map((p) => p.name),\n query: operation.queryParams.map((p) => p.name),\n header: operation.headerParams.map((p) => p.name),\n cookie: operation.cookieParams.map((p) => p.name),\n },\n },\n };\n\n // Build source info\n const source: OpenApiSource = {\n type: 'openapi',\n sourceId: operation.operationId,\n operationId: operation.operationId,\n openApiVersion: parseResult.version,\n importedAt: new Date(),\n };\n\n // Resolve group folder based on config\n const groupFolder = resolveOperationGroupFolder(\n operation,\n contractspecOptions.conventions\n );\n\n specs.push({\n code,\n fileName,\n groupFolder: groupFolder || undefined,\n source,\n transportHints,\n });\n } catch (error) {\n errors.push({\n sourceId: operation.operationId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Import standalone models\n for (const [name, schema] of Object.entries(parseResult.schemas)) {\n try {\n const code = generateModelCode(name, schema, {\n ...contractspecOptions,\n schemaFormat:\n importOptions.schemaFormat || contractspecOptions.schemaFormat,\n });\n const fileName = toFileName(toSpecKey(name, importOptions.prefix));\n const groupFolder = resolveModelGroupFolder(\n name,\n contractspecOptions.conventions\n );\n\n specs.push({\n code,\n fileName,\n groupFolder: groupFolder || undefined,\n source: {\n type: 'openapi',\n sourceId: name,\n operationId: name,\n openApiVersion: parseResult.version,\n importedAt: new Date(),\n } as OpenApiSource,\n transportHints: {},\n });\n } catch (error) {\n errors.push({\n sourceId: name,\n error:\n error instanceof Error\n ? 'Model conversion failed: ' + error.message\n : String(error),\n });\n }\n }\n\n // Import events\n for (const event of parseResult.events) {\n try {\n const code = generateEventCode(event, {\n ...contractspecOptions,\n schemaFormat:\n importOptions.schemaFormat || contractspecOptions.schemaFormat,\n });\n const fileName = toFileName(toSpecKey(event.name, importOptions.prefix));\n const groupFolder = resolveEventGroupFolder(\n event.name,\n contractspecOptions.conventions\n );\n\n specs.push({\n code,\n fileName,\n groupFolder: groupFolder || undefined,\n source: {\n type: 'openapi',\n sourceId: event.name,\n operationId: event.name,\n openApiVersion: parseResult.version,\n importedAt: new Date(),\n } as OpenApiSource,\n transportHints: {},\n });\n } catch (error) {\n errors.push({\n sourceId: event.name,\n error:\n error instanceof Error\n ? 'Event conversion failed: ' + error.message\n : String(error),\n });\n }\n }\n\n return {\n operationSpecs: specs,\n skipped,\n errors,\n summary: {\n total:\n parseResult.operations.length +\n Object.keys(parseResult.schemas).length +\n parseResult.events.length,\n imported: specs.length,\n skipped: skipped.length,\n errors: errors.length,\n },\n };\n};\n\n/**\n * Import a single operation to ContractSpec code.\n */\nexport function importOperation(\n operation: ParsedOperation,\n options: Partial<OpenApiSourceConfig> = {},\n contractspecOptions: ContractsrcConfig\n): string {\n // Build input schemas\n const inputSchemas = buildInputSchemas(operation);\n const schemaFormat =\n options.schemaFormat || contractspecOptions.schemaFormat || 'contractspec';\n\n // Generate models for each input source\n const inputModel = inputSchemas.body\n ? generateSchemaModelCode(\n inputSchemas.body,\n `${operation.operationId}Input`,\n schemaFormat,\n contractspecOptions\n )\n : null;\n\n const queryModel = inputSchemas.query\n ? generateSchemaModelCode(\n inputSchemas.query,\n `${operation.operationId}Query`,\n schemaFormat,\n contractspecOptions\n )\n : null;\n\n const paramsModel = inputSchemas.params\n ? generateSchemaModelCode(\n inputSchemas.params,\n `${operation.operationId}Params`,\n schemaFormat,\n contractspecOptions\n )\n : null;\n\n const headersModel = inputSchemas.headers\n ? generateSchemaModelCode(\n inputSchemas.headers,\n `${operation.operationId}Headers`,\n schemaFormat,\n contractspecOptions\n )\n : null;\n\n const outputSchema = getOutputSchema(operation);\n const outputModel = outputSchema\n ? generateSchemaModelCode(\n outputSchema,\n `${operation.operationId}Output`,\n schemaFormat,\n contractspecOptions\n )\n : null;\n\n return generateSpecCode(\n operation,\n contractspecOptions,\n options,\n inputModel,\n outputModel,\n queryModel,\n paramsModel,\n headersModel\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { toPascalCase, toValidIdentifier } from "../../common/utils.js";
|
|
2
|
+
import { generateImports, generateSchemaModelCode } from "../schema-converter.js";
|
|
3
|
+
|
|
4
|
+
//#region src/openapi/importer/models.ts
|
|
5
|
+
/**
|
|
6
|
+
* Generate code for a standalone model.
|
|
7
|
+
*/
|
|
8
|
+
function generateModelCode(name, schema, options) {
|
|
9
|
+
const model = generateSchemaModelCode(schema, toPascalCase(toValidIdentifier(name)), options.schemaFormat || "contractspec", options);
|
|
10
|
+
let imports = "";
|
|
11
|
+
if (model.imports && model.imports.length > 0) imports = model.imports.join("\n");
|
|
12
|
+
else if (model.fields.length > 0) imports = generateImports(model.fields, options);
|
|
13
|
+
return `
|
|
14
|
+
${imports}
|
|
15
|
+
|
|
16
|
+
${model.code}
|
|
17
|
+
`.trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
export { generateModelCode };
|
|
22
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","names":[],"sources":["../../../src/openapi/importer/models.ts"],"sourcesContent":["import type { OpenApiSchema } from '../types';\nimport { generateImports, generateSchemaModelCode } from '../schema-converter';\nimport { toPascalCase, toValidIdentifier } from '../../common/utils';\nimport type { ContractsrcConfig } from '@contractspec/lib.contracts';\n\n/**\n * Generate code for a standalone model.\n */\nexport function generateModelCode(\n name: string,\n schema: OpenApiSchema,\n options: ContractsrcConfig\n): string {\n const modelName = toPascalCase(toValidIdentifier(name));\n const schemaFormat = options.schemaFormat || 'contractspec';\n\n const model = generateSchemaModelCode(\n schema,\n modelName,\n schemaFormat,\n options\n );\n\n let imports = '';\n if (model.imports && model.imports.length > 0) {\n imports = model.imports.join('\\n');\n } else if (model.fields.length > 0) {\n imports = generateImports(model.fields, options);\n }\n\n return `\n${imports}\n\n${model.code}\n`.trim();\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,kBACd,MACA,QACA,SACQ;CAIR,MAAM,QAAQ,wBACZ,QAJgB,aAAa,kBAAkB,KAAK,CAAC,EAClC,QAAQ,gBAAgB,gBAM3C,QACD;CAED,IAAI,UAAU;AACd,KAAI,MAAM,WAAW,MAAM,QAAQ,SAAS,EAC1C,WAAU,MAAM,QAAQ,KAAK,KAAK;UACzB,MAAM,OAAO,SAAS,EAC/B,WAAU,gBAAgB,MAAM,QAAQ,QAAQ;AAGlD,QAAO;EACP,QAAQ;;EAER,MAAM,KAAK;EACX,MAAM"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//#region src/openapi/importer/schemas.ts
|
|
2
|
+
/**
|
|
3
|
+
* Build separate input schemas for each parameter source.
|
|
4
|
+
*/
|
|
5
|
+
function buildInputSchemas(operation) {
|
|
6
|
+
const result = {};
|
|
7
|
+
if (operation.pathParams.length > 0) result.params = {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: operation.pathParams.reduce((acc, p) => {
|
|
10
|
+
acc[p.name] = p.schema;
|
|
11
|
+
return acc;
|
|
12
|
+
}, {}),
|
|
13
|
+
required: operation.pathParams.map((p) => p.name)
|
|
14
|
+
};
|
|
15
|
+
if (operation.queryParams.length > 0) result.query = {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: operation.queryParams.reduce((acc, p) => {
|
|
18
|
+
acc[p.name] = p.schema;
|
|
19
|
+
return acc;
|
|
20
|
+
}, {}),
|
|
21
|
+
required: operation.queryParams.filter((p) => p.required).map((p) => p.name)
|
|
22
|
+
};
|
|
23
|
+
const excludedHeaders = [
|
|
24
|
+
"authorization",
|
|
25
|
+
"content-type",
|
|
26
|
+
"accept",
|
|
27
|
+
"user-agent"
|
|
28
|
+
];
|
|
29
|
+
const actualHeaders = operation.headerParams.filter((p) => !excludedHeaders.includes(p.name.toLowerCase()));
|
|
30
|
+
if (actualHeaders.length > 0) result.headers = {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: actualHeaders.reduce((acc, p) => {
|
|
33
|
+
acc[p.name] = p.schema;
|
|
34
|
+
return acc;
|
|
35
|
+
}, {}),
|
|
36
|
+
required: actualHeaders.filter((p) => p.required).map((p) => p.name)
|
|
37
|
+
};
|
|
38
|
+
if (operation.requestBody?.schema) result.body = operation.requestBody.schema;
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get the output schema from the operation responses.
|
|
43
|
+
*/
|
|
44
|
+
function getOutputSchema(operation) {
|
|
45
|
+
for (const code of [
|
|
46
|
+
"200",
|
|
47
|
+
"201",
|
|
48
|
+
"202",
|
|
49
|
+
"204"
|
|
50
|
+
]) {
|
|
51
|
+
const response = operation.responses[code];
|
|
52
|
+
if (response?.schema) return response.schema;
|
|
53
|
+
}
|
|
54
|
+
for (const [code, response] of Object.entries(operation.responses)) if (code.startsWith("2") && response.schema) return response.schema;
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
export { buildInputSchemas, getOutputSchema };
|
|
60
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","names":[],"sources":["../../../src/openapi/importer/schemas.ts"],"sourcesContent":["import type { OpenApiSchema, ParsedOperation } from '../types';\n\n/**\n * Build separate input schemas for each parameter source.\n */\nexport function buildInputSchemas(operation: ParsedOperation): {\n body?: OpenApiSchema;\n query?: OpenApiSchema;\n params?: OpenApiSchema;\n headers?: OpenApiSchema;\n} {\n const result: {\n body?: OpenApiSchema;\n query?: OpenApiSchema;\n params?: OpenApiSchema;\n headers?: OpenApiSchema;\n } = {};\n\n // Path parameters -> params\n if (operation.pathParams.length > 0) {\n result.params = {\n type: 'object',\n properties: operation.pathParams.reduce(\n (acc, p) => {\n acc[p.name] = p.schema;\n return acc;\n },\n {} as Record<string, OpenApiSchema>\n ),\n required: operation.pathParams.map((p) => p.name),\n } as unknown as OpenApiSchema;\n }\n\n // Query parameters -> query\n if (operation.queryParams.length > 0) {\n result.query = {\n type: 'object',\n properties: operation.queryParams.reduce(\n (acc, p) => {\n acc[p.name] = p.schema;\n return acc;\n },\n {} as Record<string, OpenApiSchema>\n ),\n required: operation.queryParams\n .filter((p) => p.required)\n .map((p) => p.name),\n } as unknown as OpenApiSchema;\n }\n\n // Header parameters -> headers\n const excludedHeaders = [\n 'authorization',\n 'content-type',\n 'accept',\n 'user-agent',\n ];\n const actualHeaders = operation.headerParams.filter(\n (p) => !excludedHeaders.includes(p.name.toLowerCase())\n );\n if (actualHeaders.length > 0) {\n result.headers = {\n type: 'object',\n properties: actualHeaders.reduce(\n (acc, p) => {\n acc[p.name] = p.schema;\n return acc;\n },\n {} as Record<string, OpenApiSchema>\n ),\n required: actualHeaders.filter((p) => p.required).map((p) => p.name),\n } as unknown as OpenApiSchema;\n }\n\n // Request body -> body\n if (operation.requestBody?.schema) {\n result.body = operation.requestBody.schema;\n }\n\n return result;\n}\n\n/**\n * Get the output schema from the operation responses.\n */\nexport function getOutputSchema(\n operation: ParsedOperation\n): OpenApiSchema | null {\n // Prefer 200, then 201, then 2xx responses\n const successCodes = ['200', '201', '202', '204'];\n\n for (const code of successCodes) {\n const response = operation.responses[code];\n if (response?.schema) {\n return response.schema;\n }\n }\n\n // Check for any 2xx response\n for (const [code, response] of Object.entries(operation.responses)) {\n if (code.startsWith('2') && response.schema) {\n return response.schema;\n }\n }\n\n return null;\n}\n"],"mappings":";;;;AAKA,SAAgB,kBAAkB,WAKhC;CACA,MAAM,SAKF,EAAE;AAGN,KAAI,UAAU,WAAW,SAAS,EAChC,QAAO,SAAS;EACd,MAAM;EACN,YAAY,UAAU,WAAW,QAC9B,KAAK,MAAM;AACV,OAAI,EAAE,QAAQ,EAAE;AAChB,UAAO;KAET,EAAE,CACH;EACD,UAAU,UAAU,WAAW,KAAK,MAAM,EAAE,KAAK;EAClD;AAIH,KAAI,UAAU,YAAY,SAAS,EACjC,QAAO,QAAQ;EACb,MAAM;EACN,YAAY,UAAU,YAAY,QAC/B,KAAK,MAAM;AACV,OAAI,EAAE,QAAQ,EAAE;AAChB,UAAO;KAET,EAAE,CACH;EACD,UAAU,UAAU,YACjB,QAAQ,MAAM,EAAE,SAAS,CACzB,KAAK,MAAM,EAAE,KAAK;EACtB;CAIH,MAAM,kBAAkB;EACtB;EACA;EACA;EACA;EACD;CACD,MAAM,gBAAgB,UAAU,aAAa,QAC1C,MAAM,CAAC,gBAAgB,SAAS,EAAE,KAAK,aAAa,CAAC,CACvD;AACD,KAAI,cAAc,SAAS,EACzB,QAAO,UAAU;EACf,MAAM;EACN,YAAY,cAAc,QACvB,KAAK,MAAM;AACV,OAAI,EAAE,QAAQ,EAAE;AAChB,UAAO;KAET,EAAE,CACH;EACD,UAAU,cAAc,QAAQ,MAAM,EAAE,SAAS,CAAC,KAAK,MAAM,EAAE,KAAK;EACrE;AAIH,KAAI,UAAU,aAAa,OACzB,QAAO,OAAO,UAAU,YAAY;AAGtC,QAAO;;;;;AAMT,SAAgB,gBACd,WACsB;AAItB,MAAK,MAAM,QAFU;EAAC;EAAO;EAAO;EAAO;EAAM,EAEhB;EAC/B,MAAM,WAAW,UAAU,UAAU;AACrC,MAAI,UAAU,OACZ,QAAO,SAAS;;AAKpB,MAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,UAAU,UAAU,CAChE,KAAI,KAAK,WAAW,IAAI,IAAI,SAAS,OACnC,QAAO,SAAS;AAIpB,QAAO"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ContractSpecOpenApiDocument, HttpMethod, OpenApiDocument, OpenApiExportOptions, OpenApiOperation, OpenApiParameter, OpenApiParseOptions, OpenApiSchema, OpenApiServer, OpenApiSource, OpenApiTransportHints, OpenApiVersion, ParameterLocation, ParseResult, ParsedOperation, ParsedParameter } from "./types.js";
|
|
2
|
+
import { detectFormat, detectVersion, parseOpenApiString } from "./parser/utils.js";
|
|
3
|
+
import { parseOpenApi, parseOpenApiDocument } from "./parser/document.js";
|
|
4
|
+
import { OperationsExportResult, defaultRestPath, exportOperations, generateOperationsRegistry, jsonSchemaForSpec, schemaModelToJsonSchema, toHttpMethod, toOperationId, toRestPath, toSchemaName } from "./exporter/operations.js";
|
|
5
|
+
import { ContractSpecRegistries, contractSpecToJson, contractSpecToYaml, exportContractSpec, openApiForRegistry, openApiToJson, openApiToYaml } from "./exporter.js";
|
|
6
|
+
import { ExportedEvent, exportEvents, generateEventsExports } from "./exporter/events.js";
|
|
7
|
+
import { ExportedFeature, exportFeatures, generateFeaturesRegistry } from "./exporter/features.js";
|
|
8
|
+
import { ExportedPresentation, exportPresentations, exportPresentationsFromArray, generatePresentationsRegistry } from "./exporter/presentations.js";
|
|
9
|
+
import { ExportedForm, exportForms, generateFormsRegistry } from "./exporter/forms.js";
|
|
10
|
+
import { ExportedDataView, exportDataViews, generateDataViewsRegistry } from "./exporter/data-views.js";
|
|
11
|
+
import { ExportedWorkflow, exportWorkflows, generateWorkflowsRegistry } from "./exporter/workflows.js";
|
|
12
|
+
import { RegistryGenerationOptions, generateRegistryIndex } from "./exporter/registries.js";
|
|
13
|
+
import { GeneratedModel, SchemaField, TypescriptType, generateImports, generateSchemaModelCode, getScalarType, jsonSchemaToType } from "./schema-converter.js";
|
|
14
|
+
import { importFromOpenApi, importOperation } from "./importer/index.js";
|
|
15
|
+
import { DiffOptions, createSpecDiff, diffAll, diffSpecVsOperation, diffSpecs, formatDiffChanges } from "./differ.js";
|
|
16
|
+
export { type ContractSpecOpenApiDocument, type ContractSpecRegistries, type DiffOptions, ExportedDataView, ExportedEvent, ExportedFeature, ExportedForm, ExportedPresentation, ExportedWorkflow, type GeneratedModel, type HttpMethod, type OpenApiDocument, type OpenApiExportOptions, type OpenApiOperation, type OpenApiParameter, type OpenApiParseOptions, type OpenApiSchema, type OpenApiServer, type OpenApiSource, type OpenApiTransportHints, type OpenApiVersion, OperationsExportResult, type ParameterLocation, type ParseResult, type ParsedOperation, type ParsedParameter, RegistryGenerationOptions, type SchemaField, type TypescriptType, contractSpecToJson, contractSpecToYaml, createSpecDiff, defaultRestPath, detectFormat, detectVersion, diffAll, diffSpecVsOperation, diffSpecs, exportContractSpec, exportDataViews, exportEvents, exportFeatures, exportForms, exportOperations, exportPresentations, exportPresentationsFromArray, exportWorkflows, formatDiffChanges, generateDataViewsRegistry, generateEventsExports, generateFeaturesRegistry, generateFormsRegistry, generateImports, generateOperationsRegistry, generatePresentationsRegistry, generateRegistryIndex, generateSchemaModelCode, generateWorkflowsRegistry, getScalarType, importFromOpenApi, importOperation, jsonSchemaForSpec, jsonSchemaToType, openApiForRegistry, openApiToJson, openApiToYaml, parseOpenApi, parseOpenApiDocument, parseOpenApiString, schemaModelToJsonSchema, toHttpMethod, toOperationId, toRestPath, toSchemaName };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { detectFormat, detectVersion, parseOpenApiString } from "./parser/utils.js";
|
|
2
|
+
import { parseOpenApi, parseOpenApiDocument } from "./parser/document.js";
|
|
3
|
+
import "./parser.js";
|
|
4
|
+
import { defaultRestPath, exportOperations, generateOperationsRegistry, jsonSchemaForSpec, schemaModelToJsonSchema, toHttpMethod, toOperationId, toRestPath, toSchemaName } from "./exporter/operations.js";
|
|
5
|
+
import { exportEvents, generateEventsExports } from "./exporter/events.js";
|
|
6
|
+
import { exportFeatures, generateFeaturesRegistry } from "./exporter/features.js";
|
|
7
|
+
import { exportPresentations, exportPresentationsFromArray, generatePresentationsRegistry } from "./exporter/presentations.js";
|
|
8
|
+
import { exportForms, generateFormsRegistry } from "./exporter/forms.js";
|
|
9
|
+
import { exportDataViews, generateDataViewsRegistry } from "./exporter/data-views.js";
|
|
10
|
+
import { exportWorkflows, generateWorkflowsRegistry } from "./exporter/workflows.js";
|
|
11
|
+
import { generateRegistryIndex } from "./exporter/registries.js";
|
|
12
|
+
import { contractSpecToJson, contractSpecToYaml, exportContractSpec, openApiForRegistry, openApiToJson, openApiToYaml } from "./exporter.js";
|
|
13
|
+
import "./exporter/index.js";
|
|
14
|
+
import { generateImports, generateSchemaModelCode, getScalarType, jsonSchemaToType } from "./schema-converter.js";
|
|
15
|
+
import { importFromOpenApi, importOperation } from "./importer/index.js";
|
|
16
|
+
import { createSpecDiff, diffAll, diffSpecVsOperation, diffSpecs, formatDiffChanges } from "./differ.js";
|
|
17
|
+
|
|
18
|
+
export { contractSpecToJson, contractSpecToYaml, createSpecDiff, defaultRestPath, detectFormat, detectVersion, diffAll, diffSpecVsOperation, diffSpecs, exportContractSpec, exportDataViews, exportEvents, exportFeatures, exportForms, exportOperations, exportPresentations, exportPresentationsFromArray, exportWorkflows, formatDiffChanges, generateDataViewsRegistry, generateEventsExports, generateFeaturesRegistry, generateFormsRegistry, generateImports, generateOperationsRegistry, generatePresentationsRegistry, generateRegistryIndex, generateSchemaModelCode, generateWorkflowsRegistry, getScalarType, importFromOpenApi, importOperation, jsonSchemaForSpec, jsonSchemaToType, openApiForRegistry, openApiToJson, openApiToYaml, parseOpenApi, parseOpenApiDocument, parseOpenApiString, schemaModelToJsonSchema, toHttpMethod, toOperationId, toRestPath, toSchemaName };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OpenApiDocument, OpenApiParseOptions, ParseResult } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/openapi/parser/document.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse an OpenAPI document into a structured result.
|
|
7
|
+
*/
|
|
8
|
+
declare function parseOpenApiDocument(doc: OpenApiDocument, _options?: OpenApiParseOptions): ParseResult;
|
|
9
|
+
/**
|
|
10
|
+
* Parse OpenAPI from a file path or URL.
|
|
11
|
+
* Note: This is an async function that requires I/O adapters.
|
|
12
|
+
* For pure parsing, use parseOpenApiString or parseOpenApiDocument.
|
|
13
|
+
*/
|
|
14
|
+
declare function parseOpenApi(source: string, options?: OpenApiParseOptions & {
|
|
15
|
+
fetch?: typeof globalThis.fetch;
|
|
16
|
+
readFile?: (path: string) => Promise<string>;
|
|
17
|
+
}): Promise<ParseResult>;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { parseOpenApi, parseOpenApiDocument };
|
|
20
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","names":[],"sources":["../../../src/openapi/parser/document.ts"],"sourcesContent":[],"mappings":";;;;;;AAsBA;AACO,iBADS,oBAAA,CACT,GAAA,EAAA,eAAA,EAAA,QAAA,CAAA,EACK,mBADL,CAAA,EAEJ,WAFI;;;;AA0GP;;AAGmB,iBAHG,YAAA,CAGQ,MAAA,EAAA,MAAA,EAAA,OAG3B,CAH2B,EADnB,mBACmB,GAAA;EACG,KAAA,CAAA,EAAA,OADd,UAAA,CAAW,KACG;EAEtB,QAAA,CAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GAFsB,OAEtB,CAAA,MAAA,CAAA;CAAR,CAAA,EAAA,OAAA,CAAQ,WAAR,CAAA"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { HTTP_METHODS, detectFormat, detectVersion, parseOpenApiString } from "./utils.js";
|
|
2
|
+
import { parseOperation } from "./operation.js";
|
|
3
|
+
|
|
4
|
+
//#region src/openapi/parser/document.ts
|
|
5
|
+
/**
|
|
6
|
+
* Parse an OpenAPI document into a structured result.
|
|
7
|
+
*/
|
|
8
|
+
function parseOpenApiDocument(doc, _options = {}) {
|
|
9
|
+
const version = detectVersion(doc);
|
|
10
|
+
const warnings = [];
|
|
11
|
+
const operations = [];
|
|
12
|
+
for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {
|
|
13
|
+
if (!pathItem) continue;
|
|
14
|
+
const pathParams = pathItem.parameters;
|
|
15
|
+
for (const method of HTTP_METHODS) {
|
|
16
|
+
const operation = pathItem[method];
|
|
17
|
+
if (operation) try {
|
|
18
|
+
operations.push(parseOperation(doc, method, path, operation, pathParams));
|
|
19
|
+
} catch (error) {
|
|
20
|
+
warnings.push(`Failed to parse ${method.toUpperCase()} ${path}: ${error}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const schemas = {};
|
|
25
|
+
const components = doc.components;
|
|
26
|
+
if (components?.schemas) for (const [name, schema] of Object.entries(components.schemas)) schemas[name] = schema;
|
|
27
|
+
const servers = (doc.servers ?? []).map((s) => ({
|
|
28
|
+
url: s.url,
|
|
29
|
+
description: s.description,
|
|
30
|
+
variables: s.variables
|
|
31
|
+
}));
|
|
32
|
+
const events = [];
|
|
33
|
+
if ("webhooks" in doc && doc.webhooks) for (const [name, pathItem] of Object.entries(doc.webhooks)) {
|
|
34
|
+
if (typeof pathItem !== "object" || !pathItem) continue;
|
|
35
|
+
const operation = pathItem["post"];
|
|
36
|
+
if (operation && operation.requestBody) {
|
|
37
|
+
if ("$ref" in operation.requestBody) throw new Error(`'$ref' isn't supported`);
|
|
38
|
+
const content = operation.requestBody.content?.["application/json"];
|
|
39
|
+
if (content?.schema) events.push({
|
|
40
|
+
name,
|
|
41
|
+
description: operation.summary || operation.description,
|
|
42
|
+
payload: content.schema
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
document: doc,
|
|
48
|
+
version,
|
|
49
|
+
info: {
|
|
50
|
+
title: doc.info.title,
|
|
51
|
+
version: doc.info.version,
|
|
52
|
+
description: doc.info.description
|
|
53
|
+
},
|
|
54
|
+
operations,
|
|
55
|
+
schemas,
|
|
56
|
+
servers,
|
|
57
|
+
warnings,
|
|
58
|
+
events
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parse OpenAPI from a file path or URL.
|
|
63
|
+
* Note: This is an async function that requires I/O adapters.
|
|
64
|
+
* For pure parsing, use parseOpenApiString or parseOpenApiDocument.
|
|
65
|
+
*/
|
|
66
|
+
async function parseOpenApi(source, options = {}) {
|
|
67
|
+
const { fetch: fetchFn = globalThis.fetch, readFile, timeout = 3e4 } = options;
|
|
68
|
+
let content;
|
|
69
|
+
let format;
|
|
70
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetchFn(source, { signal: controller.signal });
|
|
75
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
76
|
+
content = await response.text();
|
|
77
|
+
} finally {
|
|
78
|
+
clearTimeout(timeoutId);
|
|
79
|
+
}
|
|
80
|
+
if (source.endsWith(".yaml") || source.endsWith(".yml")) format = "yaml";
|
|
81
|
+
else if (source.endsWith(".json")) format = "json";
|
|
82
|
+
else format = detectFormat(content);
|
|
83
|
+
} else {
|
|
84
|
+
if (!readFile) throw new Error("readFile adapter required for file paths");
|
|
85
|
+
content = await readFile(source);
|
|
86
|
+
if (source.endsWith(".yaml") || source.endsWith(".yml")) format = "yaml";
|
|
87
|
+
else if (source.endsWith(".json")) format = "json";
|
|
88
|
+
else format = detectFormat(content);
|
|
89
|
+
}
|
|
90
|
+
return parseOpenApiDocument(parseOpenApiString(content, format), options);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
export { parseOpenApi, parseOpenApiDocument };
|
|
95
|
+
//# sourceMappingURL=document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.js","names":[],"sources":["../../../src/openapi/parser/document.ts"],"sourcesContent":["import type {\n OpenApiDocument,\n OpenApiParameter,\n OpenApiParseOptions,\n OpenApiSchema,\n OpenApiServer,\n ParsedEvent,\n ParsedOperation,\n ParseResult,\n} from '../types';\nimport type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\nimport {\n detectFormat,\n detectVersion,\n HTTP_METHODS,\n parseOpenApiString,\n} from './utils';\nimport { parseOperation } from './operation';\n\n/**\n * Parse an OpenAPI document into a structured result.\n */\nexport function parseOpenApiDocument(\n doc: OpenApiDocument,\n _options: OpenApiParseOptions = {}\n): ParseResult {\n const version = detectVersion(doc);\n const warnings: string[] = [];\n const operations: ParsedOperation[] = [];\n\n // Parse operations from paths\n for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {\n if (!pathItem) continue;\n\n // Get path-level parameters\n const pathParams = (pathItem as OpenAPIV3.PathItemObject).parameters;\n\n for (const method of HTTP_METHODS) {\n const operation = (pathItem as Record<string, unknown>)[method] as\n | OpenAPIV3.OperationObject\n | OpenAPIV3_1.OperationObject\n | undefined;\n\n if (operation) {\n try {\n operations.push(\n parseOperation(\n doc,\n method,\n path,\n operation,\n pathParams as OpenApiParameter[]\n )\n );\n } catch (error) {\n warnings.push(\n `Failed to parse ${method.toUpperCase()} ${path}: ${error}`\n );\n }\n }\n }\n }\n\n // Extract component schemas\n const schemas: Record<string, OpenApiSchema> = {};\n const components = doc.components;\n if (components?.schemas) {\n for (const [name, schema] of Object.entries(components.schemas)) {\n schemas[name] = schema as OpenApiSchema;\n }\n }\n\n // Parse servers\n // Parse servers\n const servers: OpenApiServer[] = (doc.servers ?? []).map((s) => ({\n url: s.url,\n description: s.description,\n variables: s.variables as OpenApiServer['variables'],\n }));\n\n // Parse webhooks (as events)\n const events: ParsedEvent[] = [];\n if ('webhooks' in doc && doc.webhooks) {\n for (const [name, pathItem] of Object.entries(doc.webhooks)) {\n if (typeof pathItem !== 'object' || !pathItem) continue;\n // Webhooks usually have a POST method defining the payload\n const operation = (pathItem as Record<string, unknown>)['post'] as\n | OpenAPIV3.OperationObject\n | OpenAPIV3_1.OperationObject\n | undefined;\n\n if (operation && operation.requestBody) {\n if ('$ref' in operation.requestBody) {\n throw new Error(`'$ref' isn't supported`);\n }\n // Extract payload schema\n const content = operation.requestBody.content?.['application/json'];\n if (content?.schema) {\n events.push({\n name,\n description: operation.summary || operation.description,\n payload: content.schema,\n });\n }\n }\n }\n }\n\n return {\n document: doc,\n version,\n info: {\n title: doc.info.title,\n version: doc.info.version,\n description: doc.info.description,\n },\n operations,\n schemas,\n servers,\n warnings,\n events,\n };\n}\n\n/**\n * Parse OpenAPI from a file path or URL.\n * Note: This is an async function that requires I/O adapters.\n * For pure parsing, use parseOpenApiString or parseOpenApiDocument.\n */\nexport async function parseOpenApi(\n source: string,\n options: OpenApiParseOptions & {\n fetch?: typeof globalThis.fetch;\n readFile?: (path: string) => Promise<string>;\n } = {}\n): Promise<ParseResult> {\n const {\n fetch: fetchFn = globalThis.fetch,\n readFile,\n timeout = 30000,\n } = options;\n\n let content: string;\n let format: 'json' | 'yaml';\n\n if (source.startsWith('http://') || source.startsWith('https://')) {\n // Fetch from URL\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetchFn(source, { signal: controller.signal });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n content = await response.text();\n } finally {\n clearTimeout(timeoutId);\n }\n\n // Detect format from URL or content\n if (source.endsWith('.yaml') || source.endsWith('.yml')) {\n format = 'yaml';\n } else if (source.endsWith('.json')) {\n format = 'json';\n } else {\n format = detectFormat(content);\n }\n } else {\n // Read from file\n if (!readFile) {\n throw new Error('readFile adapter required for file paths');\n }\n content = await readFile(source);\n\n // Detect format from extension or content\n if (source.endsWith('.yaml') || source.endsWith('.yml')) {\n format = 'yaml';\n } else if (source.endsWith('.json')) {\n format = 'json';\n } else {\n format = detectFormat(content);\n }\n }\n\n const doc = parseOpenApiString(content, format);\n return parseOpenApiDocument(doc, options);\n}\n"],"mappings":";;;;;;;AAsBA,SAAgB,qBACd,KACA,WAAgC,EAAE,EACrB;CACb,MAAM,UAAU,cAAc,IAAI;CAClC,MAAM,WAAqB,EAAE;CAC7B,MAAM,aAAgC,EAAE;AAGxC,MAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,IAAI,SAAS,EAAE,CAAC,EAAE;AAC9D,MAAI,CAAC,SAAU;EAGf,MAAM,aAAc,SAAsC;AAE1D,OAAK,MAAM,UAAU,cAAc;GACjC,MAAM,YAAa,SAAqC;AAKxD,OAAI,UACF,KAAI;AACF,eAAW,KACT,eACE,KACA,QACA,MACA,WACA,WACD,CACF;YACM,OAAO;AACd,aAAS,KACP,mBAAmB,OAAO,aAAa,CAAC,GAAG,KAAK,IAAI,QACrD;;;;CAOT,MAAM,UAAyC,EAAE;CACjD,MAAM,aAAa,IAAI;AACvB,KAAI,YAAY,QACd,MAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,WAAW,QAAQ,CAC7D,SAAQ,QAAQ;CAMpB,MAAM,WAA4B,IAAI,WAAW,EAAE,EAAE,KAAK,OAAO;EAC/D,KAAK,EAAE;EACP,aAAa,EAAE;EACf,WAAW,EAAE;EACd,EAAE;CAGH,MAAM,SAAwB,EAAE;AAChC,KAAI,cAAc,OAAO,IAAI,SAC3B,MAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,IAAI,SAAS,EAAE;AAC3D,MAAI,OAAO,aAAa,YAAY,CAAC,SAAU;EAE/C,MAAM,YAAa,SAAqC;AAKxD,MAAI,aAAa,UAAU,aAAa;AACtC,OAAI,UAAU,UAAU,YACtB,OAAM,IAAI,MAAM,yBAAyB;GAG3C,MAAM,UAAU,UAAU,YAAY,UAAU;AAChD,OAAI,SAAS,OACX,QAAO,KAAK;IACV;IACA,aAAa,UAAU,WAAW,UAAU;IAC5C,SAAS,QAAQ;IAClB,CAAC;;;AAMV,QAAO;EACL,UAAU;EACV;EACA,MAAM;GACJ,OAAO,IAAI,KAAK;GAChB,SAAS,IAAI,KAAK;GAClB,aAAa,IAAI,KAAK;GACvB;EACD;EACA;EACA;EACA;EACA;EACD;;;;;;;AAQH,eAAsB,aACpB,QACA,UAGI,EAAE,EACgB;CACtB,MAAM,EACJ,OAAO,UAAU,WAAW,OAC5B,UACA,UAAU,QACR;CAEJ,IAAI;CACJ,IAAI;AAEJ,KAAI,OAAO,WAAW,UAAU,IAAI,OAAO,WAAW,WAAW,EAAE;EAEjE,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,QAAQ;AAE/D,MAAI;GACF,MAAM,WAAW,MAAM,QAAQ,QAAQ,EAAE,QAAQ,WAAW,QAAQ,CAAC;AACrE,OAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,QAAQ,SAAS,OAAO,IAAI,SAAS,aAAa;AAEpE,aAAU,MAAM,SAAS,MAAM;YACvB;AACR,gBAAa,UAAU;;AAIzB,MAAI,OAAO,SAAS,QAAQ,IAAI,OAAO,SAAS,OAAO,CACrD,UAAS;WACA,OAAO,SAAS,QAAQ,CACjC,UAAS;MAET,UAAS,aAAa,QAAQ;QAE3B;AAEL,MAAI,CAAC,SACH,OAAM,IAAI,MAAM,2CAA2C;AAE7D,YAAU,MAAM,SAAS,OAAO;AAGhC,MAAI,OAAO,SAAS,QAAQ,IAAI,OAAO,SAAS,OAAO,CACrD,UAAS;WACA,OAAO,SAAS,QAAQ,CACjC,UAAS;MAET,UAAS,aAAa,QAAQ;;AAKlC,QAAO,qBADK,mBAAmB,SAAS,OAAO,EACd,QAAQ"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { HTTP_METHODS, detectFormat, detectVersion, generateOperationId, parseOpenApiString } from "./utils.js";
|
|
2
|
+
import { dereferenceSchema, isReference, resolveRef } from "./resolvers.js";
|
|
3
|
+
import { parseParameters } from "./parameters.js";
|
|
4
|
+
import { parseOperation } from "./operation.js";
|
|
5
|
+
import { parseOpenApi, parseOpenApiDocument } from "./document.js";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { generateOperationId } from "./utils.js";
|
|
2
|
+
import { dereferenceSchema, isReference, resolveRef } from "./resolvers.js";
|
|
3
|
+
import { parseParameters } from "./parameters.js";
|
|
4
|
+
|
|
5
|
+
//#region src/openapi/parser/operation.ts
|
|
6
|
+
/**
|
|
7
|
+
* Parse a single operation.
|
|
8
|
+
*/
|
|
9
|
+
function parseOperation(doc, method, path, operation, pathParams) {
|
|
10
|
+
const params = parseParameters(doc, [...pathParams ?? [], ...operation.parameters ?? []]);
|
|
11
|
+
let requestBody;
|
|
12
|
+
if (operation.requestBody) {
|
|
13
|
+
const body = isReference(operation.requestBody) ? resolveRef(doc, operation.requestBody.$ref) : operation.requestBody;
|
|
14
|
+
if (body) {
|
|
15
|
+
const contentType = Object.keys(body.content ?? {})[0] ?? "application/json";
|
|
16
|
+
const content = body.content?.[contentType];
|
|
17
|
+
if (content?.schema) requestBody = {
|
|
18
|
+
required: body.required ?? false,
|
|
19
|
+
schema: dereferenceSchema(doc, content.schema) ?? {},
|
|
20
|
+
contentType
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const responses = {};
|
|
25
|
+
for (const [status, response] of Object.entries(operation.responses ?? {})) {
|
|
26
|
+
const resolved = isReference(response) ? resolveRef(doc, response.$ref) : response;
|
|
27
|
+
if (resolved) {
|
|
28
|
+
const contentType = Object.keys(resolved.content ?? {})[0];
|
|
29
|
+
const content = contentType ? resolved.content?.[contentType] : void 0;
|
|
30
|
+
responses[status] = {
|
|
31
|
+
description: resolved.description,
|
|
32
|
+
schema: content?.schema ? dereferenceSchema(doc, content.schema) : void 0,
|
|
33
|
+
contentType
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const contractSpecMeta = operation?.["x-contractspec"];
|
|
38
|
+
return {
|
|
39
|
+
operationId: operation.operationId ?? generateOperationId(method, path),
|
|
40
|
+
method,
|
|
41
|
+
path,
|
|
42
|
+
summary: operation.summary,
|
|
43
|
+
description: operation.description,
|
|
44
|
+
tags: operation.tags ?? [],
|
|
45
|
+
pathParams: params.path,
|
|
46
|
+
queryParams: params.query,
|
|
47
|
+
headerParams: params.header,
|
|
48
|
+
cookieParams: params.cookie,
|
|
49
|
+
requestBody,
|
|
50
|
+
responses,
|
|
51
|
+
deprecated: operation.deprecated ?? false,
|
|
52
|
+
security: operation.security,
|
|
53
|
+
contractSpecMeta
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { parseOperation };
|
|
59
|
+
//# sourceMappingURL=operation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation.js","names":[],"sources":["../../../src/openapi/parser/operation.ts"],"sourcesContent":["import type {\n HttpMethod,\n OpenApiDocument,\n OpenApiParameter,\n OpenApiSchema,\n ParsedOperation,\n} from '../types';\nimport type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\nimport { dereferenceSchema, isReference, resolveRef } from './resolvers';\nimport { parseParameters } from './parameters';\nimport { generateOperationId } from './utils';\n\n/**\n * Parse a single operation.\n */\nexport function parseOperation(\n doc: OpenApiDocument,\n method: HttpMethod,\n path: string,\n operation: OpenAPIV3.OperationObject | OpenAPIV3_1.OperationObject,\n pathParams: OpenApiParameter[] | undefined\n): ParsedOperation {\n // Merge path-level and operation-level parameters\n const allParams = [...(pathParams ?? []), ...(operation.parameters ?? [])];\n const params = parseParameters(doc, allParams as OpenApiParameter[]);\n\n // Parse request body\n let requestBody: ParsedOperation['requestBody'];\n if (operation.requestBody) {\n const body = isReference(operation.requestBody)\n ? resolveRef<OpenAPIV3.RequestBodyObject | OpenAPIV3_1.RequestBodyObject>(\n doc,\n operation.requestBody.$ref\n )\n : operation.requestBody;\n\n if (body) {\n const contentType =\n Object.keys(body.content ?? {})[0] ?? 'application/json';\n const content = body.content?.[contentType];\n if (content?.schema) {\n requestBody = {\n required: body.required ?? false,\n schema:\n dereferenceSchema(doc, content.schema as OpenApiSchema) ??\n ({} as OpenApiSchema),\n contentType,\n };\n }\n }\n }\n\n // Parse responses\n const responses: ParsedOperation['responses'] = {};\n for (const [status, response] of Object.entries(operation.responses ?? {})) {\n const resolved = isReference(response)\n ? resolveRef<OpenAPIV3.ResponseObject | OpenAPIV3_1.ResponseObject>(\n doc,\n response.$ref\n )\n : response;\n\n if (resolved) {\n const contentType = Object.keys(resolved.content ?? {})[0];\n const content = contentType ? resolved.content?.[contentType] : undefined;\n\n responses[status] = {\n description: resolved.description,\n schema: content?.schema\n ? dereferenceSchema(doc, content.schema as OpenApiSchema)\n : undefined,\n contentType,\n };\n }\n }\n\n // Check for x-contractspec extension\n const contractSpecMeta = (operation as Record<string, unknown>)?.[\n 'x-contractspec'\n ] as ParsedOperation['contractSpecMeta'];\n\n return {\n operationId: operation.operationId ?? generateOperationId(method, path),\n method,\n path,\n summary: operation.summary,\n description: operation.description,\n tags: operation.tags ?? [],\n pathParams: params.path,\n queryParams: params.query,\n headerParams: params.header,\n cookieParams: params.cookie,\n requestBody,\n responses,\n deprecated: operation.deprecated ?? false,\n security: operation.security as ParsedOperation['security'],\n contractSpecMeta,\n };\n}\n"],"mappings":";;;;;;;;AAeA,SAAgB,eACd,KACA,QACA,MACA,WACA,YACiB;CAGjB,MAAM,SAAS,gBAAgB,KADb,CAAC,GAAI,cAAc,EAAE,EAAG,GAAI,UAAU,cAAc,EAAE,CAAE,CACN;CAGpE,IAAI;AACJ,KAAI,UAAU,aAAa;EACzB,MAAM,OAAO,YAAY,UAAU,YAAY,GAC3C,WACE,KACA,UAAU,YAAY,KACvB,GACD,UAAU;AAEd,MAAI,MAAM;GACR,MAAM,cACJ,OAAO,KAAK,KAAK,WAAW,EAAE,CAAC,CAAC,MAAM;GACxC,MAAM,UAAU,KAAK,UAAU;AAC/B,OAAI,SAAS,OACX,eAAc;IACZ,UAAU,KAAK,YAAY;IAC3B,QACE,kBAAkB,KAAK,QAAQ,OAAwB,IACtD,EAAE;IACL;IACD;;;CAMP,MAAM,YAA0C,EAAE;AAClD,MAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,UAAU,aAAa,EAAE,CAAC,EAAE;EAC1E,MAAM,WAAW,YAAY,SAAS,GAClC,WACE,KACA,SAAS,KACV,GACD;AAEJ,MAAI,UAAU;GACZ,MAAM,cAAc,OAAO,KAAK,SAAS,WAAW,EAAE,CAAC,CAAC;GACxD,MAAM,UAAU,cAAc,SAAS,UAAU,eAAe;AAEhE,aAAU,UAAU;IAClB,aAAa,SAAS;IACtB,QAAQ,SAAS,SACb,kBAAkB,KAAK,QAAQ,OAAwB,GACvD;IACJ;IACD;;;CAKL,MAAM,mBAAoB,YACxB;AAGF,QAAO;EACL,aAAa,UAAU,eAAe,oBAAoB,QAAQ,KAAK;EACvE;EACA;EACA,SAAS,UAAU;EACnB,aAAa,UAAU;EACvB,MAAM,UAAU,QAAQ,EAAE;EAC1B,YAAY,OAAO;EACnB,aAAa,OAAO;EACpB,cAAc,OAAO;EACrB,cAAc,OAAO;EACrB;EACA;EACA,YAAY,UAAU,cAAc;EACpC,UAAU,UAAU;EACpB;EACD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { dereferenceSchema, isReference, resolveRef } from "./resolvers.js";
|
|
2
|
+
|
|
3
|
+
//#region src/openapi/parser/parameters.ts
|
|
4
|
+
/**
|
|
5
|
+
* Parse parameters from an operation.
|
|
6
|
+
*/
|
|
7
|
+
function parseParameters(doc, params) {
|
|
8
|
+
const result = {
|
|
9
|
+
path: [],
|
|
10
|
+
query: [],
|
|
11
|
+
header: [],
|
|
12
|
+
cookie: []
|
|
13
|
+
};
|
|
14
|
+
if (!params) return result;
|
|
15
|
+
for (const param of params) {
|
|
16
|
+
let resolved;
|
|
17
|
+
if (isReference(param)) {
|
|
18
|
+
const ref = resolveRef(doc, param.$ref);
|
|
19
|
+
if (!ref) continue;
|
|
20
|
+
resolved = ref;
|
|
21
|
+
} else resolved = param;
|
|
22
|
+
const parsed = {
|
|
23
|
+
name: resolved.name,
|
|
24
|
+
in: resolved.in,
|
|
25
|
+
required: resolved.required ?? resolved.in === "path",
|
|
26
|
+
description: resolved.description,
|
|
27
|
+
schema: dereferenceSchema(doc, resolved.schema),
|
|
28
|
+
deprecated: resolved.deprecated ?? false
|
|
29
|
+
};
|
|
30
|
+
result[resolved.in]?.push(parsed);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { parseParameters };
|
|
37
|
+
//# sourceMappingURL=parameters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parameters.js","names":[],"sources":["../../../src/openapi/parser/parameters.ts"],"sourcesContent":["import type {\n OpenApiDocument,\n OpenApiParameter,\n ParsedParameter,\n OpenApiSchema,\n} from '../types';\nimport type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\nimport { isReference, resolveRef, dereferenceSchema } from './resolvers';\n\n/**\n * Parse parameters from an operation.\n */\nexport function parseParameters(\n doc: OpenApiDocument,\n params: OpenApiParameter[] | undefined\n): {\n path: ParsedParameter[];\n query: ParsedParameter[];\n header: ParsedParameter[];\n cookie: ParsedParameter[];\n} {\n const result = {\n path: [] as ParsedParameter[],\n query: [] as ParsedParameter[],\n header: [] as ParsedParameter[],\n cookie: [] as ParsedParameter[],\n };\n\n if (!params) return result;\n\n for (const param of params) {\n let resolved: OpenAPIV3.ParameterObject | OpenAPIV3_1.ParameterObject;\n\n if (isReference(param)) {\n const ref = resolveRef<\n OpenAPIV3.ParameterObject | OpenAPIV3_1.ParameterObject\n >(doc, param.$ref);\n if (!ref) continue;\n resolved = ref;\n } else {\n resolved = param;\n }\n\n const parsed: ParsedParameter = {\n name: resolved.name,\n in: resolved.in as ParsedParameter['in'],\n required: resolved.required ?? resolved.in === 'path',\n description: resolved.description,\n schema: dereferenceSchema(\n doc,\n resolved.schema as OpenApiSchema\n ) as OpenApiSchema,\n deprecated: resolved.deprecated ?? false,\n };\n\n result[resolved.in as keyof typeof result]?.push(parsed);\n }\n\n return result;\n}\n"],"mappings":";;;;;;AAYA,SAAgB,gBACd,KACA,QAMA;CACA,MAAM,SAAS;EACb,MAAM,EAAE;EACR,OAAO,EAAE;EACT,QAAQ,EAAE;EACV,QAAQ,EAAE;EACX;AAED,KAAI,CAAC,OAAQ,QAAO;AAEpB,MAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI;AAEJ,MAAI,YAAY,MAAM,EAAE;GACtB,MAAM,MAAM,WAEV,KAAK,MAAM,KAAK;AAClB,OAAI,CAAC,IAAK;AACV,cAAW;QAEX,YAAW;EAGb,MAAM,SAA0B;GAC9B,MAAM,SAAS;GACf,IAAI,SAAS;GACb,UAAU,SAAS,YAAY,SAAS,OAAO;GAC/C,aAAa,SAAS;GACtB,QAAQ,kBACN,KACA,SAAS,OACV;GACD,YAAY,SAAS,cAAc;GACpC;AAED,SAAO,SAAS,KAA4B,KAAK,OAAO;;AAG1D,QAAO"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//#region src/openapi/parser/resolvers.ts
|
|
2
|
+
/**
|
|
3
|
+
* Check if a value is a reference object.
|
|
4
|
+
*/
|
|
5
|
+
function isReference(obj) {
|
|
6
|
+
return typeof obj === "object" && obj !== null && "$ref" in obj;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolve a $ref reference in the document.
|
|
10
|
+
*/
|
|
11
|
+
function resolveRef(doc, ref) {
|
|
12
|
+
if (!ref.startsWith("#/")) return;
|
|
13
|
+
const path = ref.slice(2).split("/");
|
|
14
|
+
let current = doc;
|
|
15
|
+
for (const part of path) {
|
|
16
|
+
if (current === null || current === void 0) return void 0;
|
|
17
|
+
if (typeof current !== "object") return void 0;
|
|
18
|
+
current = current[part];
|
|
19
|
+
}
|
|
20
|
+
return current;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Recursively dereference a schema.
|
|
24
|
+
* Replaces all $ref with their resolved values.
|
|
25
|
+
* Preserves `_originalRef` to track where the schema came from.
|
|
26
|
+
*/
|
|
27
|
+
function dereferenceSchema(doc, schema, seen = /* @__PURE__ */ new Set()) {
|
|
28
|
+
if (!schema) return void 0;
|
|
29
|
+
if (isReference(schema)) {
|
|
30
|
+
if (seen.has(schema.$ref)) return schema;
|
|
31
|
+
const newSeen = new Set(seen);
|
|
32
|
+
newSeen.add(schema.$ref);
|
|
33
|
+
const resolved = resolveRef(doc, schema.$ref);
|
|
34
|
+
if (!resolved) return schema;
|
|
35
|
+
const dereferenced = dereferenceSchema(doc, resolved, newSeen);
|
|
36
|
+
if (!dereferenced) return schema;
|
|
37
|
+
const refParts = schema.$ref.split("/");
|
|
38
|
+
const typeName = refParts[refParts.length - 1];
|
|
39
|
+
return {
|
|
40
|
+
...dereferenced,
|
|
41
|
+
_originalRef: schema.$ref,
|
|
42
|
+
_originalTypeName: typeName
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const schemaObj = { ...schema };
|
|
46
|
+
if (schemaObj.properties) {
|
|
47
|
+
const props = schemaObj.properties;
|
|
48
|
+
const newProps = {};
|
|
49
|
+
for (const [key, prop] of Object.entries(props)) newProps[key] = dereferenceSchema(doc, prop, seen) ?? prop;
|
|
50
|
+
schemaObj.properties = newProps;
|
|
51
|
+
}
|
|
52
|
+
if (schemaObj.items) schemaObj.items = dereferenceSchema(doc, schemaObj.items, seen);
|
|
53
|
+
for (const comb of [
|
|
54
|
+
"allOf",
|
|
55
|
+
"anyOf",
|
|
56
|
+
"oneOf"
|
|
57
|
+
]) if (Array.isArray(schemaObj[comb])) schemaObj[comb] = schemaObj[comb].map((s) => dereferenceSchema(doc, s, seen) ?? s);
|
|
58
|
+
return schemaObj;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { dereferenceSchema, isReference, resolveRef };
|
|
63
|
+
//# sourceMappingURL=resolvers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolvers.js","names":[],"sources":["../../../src/openapi/parser/resolvers.ts"],"sourcesContent":["import type { OpenApiDocument, OpenApiSchema } from '../types';\nimport type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\n\n/**\n * Check if a value is a reference object.\n */\nexport function isReference(\n obj: unknown\n): obj is OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject {\n return typeof obj === 'object' && obj !== null && '$ref' in obj;\n}\n\n/**\n * Resolve a $ref reference in the document.\n */\nexport function resolveRef<T>(\n doc: OpenApiDocument,\n ref: string\n): T | undefined {\n // Only support local refs for now\n if (!ref.startsWith('#/')) {\n return undefined;\n }\n\n const path = ref.slice(2).split('/');\n let current: unknown = doc;\n\n for (const part of path) {\n if (current === null || current === undefined) return undefined;\n if (typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n\n return current as T;\n}\n\n/**\n * Resolve a schema, following $ref if needed.\n */\nexport function resolveSchema(\n doc: OpenApiDocument,\n schema: OpenApiSchema | undefined\n): OpenApiSchema | undefined {\n if (!schema) return undefined;\n if (isReference(schema)) {\n return resolveRef<OpenApiSchema>(doc, schema.$ref) ?? schema;\n }\n return schema;\n}\n\n/**\n * Recursively dereference a schema.\n * Replaces all $ref with their resolved values.\n * Preserves `_originalRef` to track where the schema came from.\n */\nexport function dereferenceSchema(\n doc: OpenApiDocument,\n schema: OpenApiSchema | undefined,\n seen = new Set<string>()\n): OpenApiSchema | undefined {\n if (!schema) return undefined;\n\n // Handle references\n if (isReference(schema)) {\n // Avoid infinite recursion for cycles\n if (seen.has(schema.$ref)) {\n return schema; // Keep reference for cycles\n }\n\n // Create new seen set for this branch\n const newSeen = new Set(seen);\n newSeen.add(schema.$ref);\n\n const resolved = resolveRef<OpenApiSchema>(doc, schema.$ref);\n if (!resolved) return schema;\n\n // Recursively dereference the resolved schema\n const dereferenced = dereferenceSchema(doc, resolved, newSeen);\n if (!dereferenced) return schema;\n\n // IMPORTANT: Preserve the original $ref so we can generate correct imports\n // Extract the type name from the $ref (e.g., '#/components/schemas/PaginationMeta' -> 'PaginationMeta')\n const refParts = schema.$ref.split('/');\n const typeName = refParts[refParts.length - 1];\n return {\n ...dereferenced,\n _originalRef: schema.$ref,\n _originalTypeName: typeName,\n } as unknown as OpenApiSchema;\n }\n\n // Deep clone to avoid mutating original doc (if we were modifying in place, but here we return new objects mostly)\n // For simplicity, we just walk properties\n const schemaObj = { ...schema } as Record<string, unknown>;\n\n // Handle nested schemas in properties\n if (schemaObj.properties) {\n const props = schemaObj.properties as Record<string, OpenApiSchema>;\n const newProps: Record<string, OpenApiSchema> = {};\n for (const [key, prop] of Object.entries(props)) {\n newProps[key] = dereferenceSchema(doc, prop, seen) ?? prop;\n }\n schemaObj.properties = newProps;\n }\n\n // Handle nested schema in items (array)\n if (schemaObj.items) {\n schemaObj.items = dereferenceSchema(\n doc,\n schemaObj.items as OpenApiSchema,\n seen\n );\n }\n\n // Handle allOf, anyOf, oneOf\n const combinators = ['allOf', 'anyOf', 'oneOf'];\n for (const comb of combinators) {\n if (Array.isArray(schemaObj[comb])) {\n schemaObj[comb] = (schemaObj[comb] as OpenApiSchema[]).map(\n (s) => dereferenceSchema(doc, s, seen) ?? s\n );\n }\n }\n\n return schemaObj as OpenApiSchema;\n}\n"],"mappings":";;;;AAMA,SAAgB,YACd,KACgE;AAChE,QAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,UAAU;;;;;AAM9D,SAAgB,WACd,KACA,KACe;AAEf,KAAI,CAAC,IAAI,WAAW,KAAK,CACvB;CAGF,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC,MAAM,IAAI;CACpC,IAAI,UAAmB;AAEvB,MAAK,MAAM,QAAQ,MAAM;AACvB,MAAI,YAAY,QAAQ,YAAY,OAAW,QAAO;AACtD,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,YAAW,QAAoC;;AAGjD,QAAO;;;;;;;AAsBT,SAAgB,kBACd,KACA,QACA,uBAAO,IAAI,KAAa,EACG;AAC3B,KAAI,CAAC,OAAQ,QAAO;AAGpB,KAAI,YAAY,OAAO,EAAE;AAEvB,MAAI,KAAK,IAAI,OAAO,KAAK,CACvB,QAAO;EAIT,MAAM,UAAU,IAAI,IAAI,KAAK;AAC7B,UAAQ,IAAI,OAAO,KAAK;EAExB,MAAM,WAAW,WAA0B,KAAK,OAAO,KAAK;AAC5D,MAAI,CAAC,SAAU,QAAO;EAGtB,MAAM,eAAe,kBAAkB,KAAK,UAAU,QAAQ;AAC9D,MAAI,CAAC,aAAc,QAAO;EAI1B,MAAM,WAAW,OAAO,KAAK,MAAM,IAAI;EACvC,MAAM,WAAW,SAAS,SAAS,SAAS;AAC5C,SAAO;GACL,GAAG;GACH,cAAc,OAAO;GACrB,mBAAmB;GACpB;;CAKH,MAAM,YAAY,EAAE,GAAG,QAAQ;AAG/B,KAAI,UAAU,YAAY;EACxB,MAAM,QAAQ,UAAU;EACxB,MAAM,WAA0C,EAAE;AAClD,OAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,MAAM,CAC7C,UAAS,OAAO,kBAAkB,KAAK,MAAM,KAAK,IAAI;AAExD,YAAU,aAAa;;AAIzB,KAAI,UAAU,MACZ,WAAU,QAAQ,kBAChB,KACA,UAAU,OACV,KACD;AAKH,MAAK,MAAM,QADS;EAAC;EAAS;EAAS;EAAQ,CAE7C,KAAI,MAAM,QAAQ,UAAU,MAAM,CAChC,WAAU,QAAS,UAAU,MAA0B,KACpD,MAAM,kBAAkB,KAAK,GAAG,KAAK,IAAI,EAC3C;AAIL,QAAO"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { OpenApiDocument, OpenApiVersion } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/openapi/parser/utils.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse an OpenAPI document from a string (JSON or YAML).
|
|
7
|
+
*/
|
|
8
|
+
declare function parseOpenApiString(content: string, format?: 'json' | 'yaml'): OpenApiDocument;
|
|
9
|
+
/**
|
|
10
|
+
* Detect the format of content (JSON or YAML).
|
|
11
|
+
*/
|
|
12
|
+
declare function detectFormat(content: string): 'json' | 'yaml';
|
|
13
|
+
/**
|
|
14
|
+
* Detect OpenAPI version from document.
|
|
15
|
+
*/
|
|
16
|
+
declare function detectVersion(doc: OpenApiDocument): OpenApiVersion;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { detectFormat, detectVersion, parseOpenApiString };
|
|
19
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","names":[],"sources":["../../../src/openapi/parser/utils.ts"],"sourcesContent":[],"mappings":";;;;;AAiBA;AAaA;AAWgB,iBAxBA,kBAAA,CAwBmB,OAAkB,EAAA,MAAA,EAAA,MAAc,CAAA,EAAA,MAAA,GAAA,MAAA,CAAA,EArBhE,eAqBgE;;;;iBAXnD,YAAA;;;;iBAWA,aAAA,MAAmB,kBAAkB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { parse } from "yaml";
|
|
2
|
+
|
|
3
|
+
//#region src/openapi/parser/utils.ts
|
|
4
|
+
const HTTP_METHODS = [
|
|
5
|
+
"get",
|
|
6
|
+
"post",
|
|
7
|
+
"put",
|
|
8
|
+
"delete",
|
|
9
|
+
"patch",
|
|
10
|
+
"head",
|
|
11
|
+
"options",
|
|
12
|
+
"trace"
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Parse an OpenAPI document from a string (JSON or YAML).
|
|
16
|
+
*/
|
|
17
|
+
function parseOpenApiString(content, format = "json") {
|
|
18
|
+
if (format === "yaml") return parse(content);
|
|
19
|
+
return JSON.parse(content);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Detect the format of content (JSON or YAML).
|
|
23
|
+
*/
|
|
24
|
+
function detectFormat(content) {
|
|
25
|
+
const trimmed = content.trim();
|
|
26
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
27
|
+
return "yaml";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Detect OpenAPI version from document.
|
|
31
|
+
*/
|
|
32
|
+
function detectVersion(doc) {
|
|
33
|
+
if (doc.openapi.startsWith("3.1")) return "3.1";
|
|
34
|
+
return "3.0";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generate an operationId if not present.
|
|
38
|
+
*/
|
|
39
|
+
function generateOperationId(method, path) {
|
|
40
|
+
return method + path.split("/").filter(Boolean).map((part) => {
|
|
41
|
+
if (part.startsWith("{") && part.endsWith("}")) return "By" + part.slice(1, -1).charAt(0).toUpperCase() + part.slice(2, -1);
|
|
42
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
43
|
+
}).join("");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { HTTP_METHODS, detectFormat, detectVersion, generateOperationId, parseOpenApiString };
|
|
48
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","names":["parseYaml"],"sources":["../../../src/openapi/parser/utils.ts"],"sourcesContent":["import { parse as parseYaml } from 'yaml';\nimport type { OpenApiDocument, HttpMethod, OpenApiVersion } from '../types';\n\nexport const HTTP_METHODS: HttpMethod[] = [\n 'get',\n 'post',\n 'put',\n 'delete',\n 'patch',\n 'head',\n 'options',\n 'trace',\n];\n\n/**\n * Parse an OpenAPI document from a string (JSON or YAML).\n */\nexport function parseOpenApiString(\n content: string,\n format: 'json' | 'yaml' = 'json'\n): OpenApiDocument {\n if (format === 'yaml') {\n return parseYaml(content) as OpenApiDocument;\n }\n return JSON.parse(content) as OpenApiDocument;\n}\n\n/**\n * Detect the format of content (JSON or YAML).\n */\nexport function detectFormat(content: string): 'json' | 'yaml' {\n const trimmed = content.trim();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n return 'json';\n }\n return 'yaml';\n}\n\n/**\n * Detect OpenAPI version from document.\n */\nexport function detectVersion(doc: OpenApiDocument): OpenApiVersion {\n const version = doc.openapi;\n if (version.startsWith('3.1')) {\n return '3.1';\n }\n return '3.0';\n}\n\n/**\n * Generate an operationId if not present.\n */\nexport function generateOperationId(method: HttpMethod, path: string): string {\n // Convert path to camelCase operationId\n const pathParts = path\n .split('/')\n .filter(Boolean)\n .map((part) => {\n // Remove path parameters\n if (part.startsWith('{') && part.endsWith('}')) {\n return (\n 'By' + part.slice(1, -1).charAt(0).toUpperCase() + part.slice(2, -1)\n );\n }\n return part.charAt(0).toUpperCase() + part.slice(1);\n });\n\n return method + pathParts.join('');\n}\n"],"mappings":";;;AAGA,MAAa,eAA6B;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;AAKD,SAAgB,mBACd,SACA,SAA0B,QACT;AACjB,KAAI,WAAW,OACb,QAAOA,MAAU,QAAQ;AAE3B,QAAO,KAAK,MAAM,QAAQ;;;;;AAM5B,SAAgB,aAAa,SAAkC;CAC7D,MAAM,UAAU,QAAQ,MAAM;AAC9B,KAAI,QAAQ,WAAW,IAAI,IAAI,QAAQ,WAAW,IAAI,CACpD,QAAO;AAET,QAAO;;;;;AAMT,SAAgB,cAAc,KAAsC;AAElE,KADgB,IAAI,QACR,WAAW,MAAM,CAC3B,QAAO;AAET,QAAO;;;;;AAMT,SAAgB,oBAAoB,QAAoB,MAAsB;AAe5E,QAAO,SAbW,KACf,MAAM,IAAI,CACV,OAAO,QAAQ,CACf,KAAK,SAAS;AAEb,MAAI,KAAK,WAAW,IAAI,IAAI,KAAK,SAAS,IAAI,CAC5C,QACE,OAAO,KAAK,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,GAAG,GAAG;AAGxE,SAAO,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE;GACnD,CAEsB,KAAK,GAAG"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { HTTP_METHODS, detectFormat, detectVersion, generateOperationId, parseOpenApiString } from "./parser/utils.js";
|
|
2
|
+
import { dereferenceSchema, isReference, resolveRef } from "./parser/resolvers.js";
|
|
3
|
+
import { parseParameters } from "./parser/parameters.js";
|
|
4
|
+
import { parseOperation } from "./parser/operation.js";
|
|
5
|
+
import { parseOpenApi, parseOpenApiDocument } from "./parser/document.js";
|
|
6
|
+
import "./parser/index.js";
|