@lssm/lib.contracts-transformers 1.41.1 → 1.42.1
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 +5 -2
- package/dist/common/index.d.ts +3 -0
- package/dist/common/index.js +3 -1
- 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 -1
- package/dist/common/utils.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +18 -1
- package/dist/openapi/differ.d.ts +42 -0
- package/dist/openapi/differ.d.ts.map +1 -0
- package/dist/openapi/differ.js +222 -2
- 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 +142 -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 -1
- 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 +36 -0
- package/dist/openapi/importer/events.js.map +1 -0
- package/dist/openapi/importer/generator.js +71 -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 +161 -0
- package/dist/openapi/importer/index.js.map +1 -0
- package/dist/openapi/importer/models.js +19 -0
- package/dist/openapi/importer/models.js.map +1 -0
- package/dist/openapi/importer/schemas.js +80 -0
- package/dist/openapi/importer/schemas.js.map +1 -0
- package/dist/openapi/index.d.ts +16 -0
- package/dist/openapi/index.js +18 -1
- 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 -1
- package/dist/openapi/schema-converter.d.ts +76 -0
- package/dist/openapi/schema-converter.d.ts.map +1 -0
- package/dist/openapi/schema-converter.js +298 -4
- package/dist/openapi/schema-converter.js.map +1 -0
- package/dist/openapi/types.d.ts +277 -0
- package/dist/openapi/types.d.ts.map +1 -0
- package/package.json +18 -11
- package/dist/openapi/importer.js +0 -2
package/dist/openapi/exporter.js
CHANGED
|
@@ -1 +1,122 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { defaultRestPath, exportOperations, generateOperationsRegistry } from "./exporter/operations.js";
|
|
2
|
+
import { exportEvents, generateEventsExports } from "./exporter/events.js";
|
|
3
|
+
import { exportFeatures, generateFeaturesRegistry } from "./exporter/features.js";
|
|
4
|
+
import { exportPresentations, generatePresentationsRegistry } from "./exporter/presentations.js";
|
|
5
|
+
import { exportForms, generateFormsRegistry } from "./exporter/forms.js";
|
|
6
|
+
import { exportDataViews, generateDataViewsRegistry } from "./exporter/data-views.js";
|
|
7
|
+
import { exportWorkflows, generateWorkflowsRegistry } from "./exporter/workflows.js";
|
|
8
|
+
import { generateRegistryIndex } from "./exporter/registries.js";
|
|
9
|
+
|
|
10
|
+
//#region src/openapi/exporter.ts
|
|
11
|
+
/**
|
|
12
|
+
* Export a OperationSpecRegistry to an OpenAPI 3.1 document.
|
|
13
|
+
* @deprecated Use exportContractSpec for full surface support.
|
|
14
|
+
*/
|
|
15
|
+
function openApiForRegistry(registry, options = {}) {
|
|
16
|
+
const { paths, schemas } = exportOperations(registry);
|
|
17
|
+
return {
|
|
18
|
+
openapi: "3.1.0",
|
|
19
|
+
info: {
|
|
20
|
+
title: options.title ?? "ContractSpec API",
|
|
21
|
+
version: options.version ?? "0.0.0",
|
|
22
|
+
...options.description ? { description: options.description } : {}
|
|
23
|
+
},
|
|
24
|
+
...options.servers ? { servers: options.servers } : {},
|
|
25
|
+
paths,
|
|
26
|
+
components: { schemas }
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Export all ContractSpec surfaces to OpenAPI document with extensions.
|
|
31
|
+
*/
|
|
32
|
+
function exportContractSpec(registries, options = {}) {
|
|
33
|
+
const { operations: includeOps = true, events: includeEvents = true, features: includeFeatures = true, presentations: includePresentations = true, forms: includeForms = true, dataViews: includeDataViews = true, workflows: includeWorkflows = true, generateRegistries = true } = options;
|
|
34
|
+
let paths = {};
|
|
35
|
+
let schemas = {};
|
|
36
|
+
if (includeOps && registries.operations) {
|
|
37
|
+
const opResult = exportOperations(registries.operations);
|
|
38
|
+
paths = opResult.paths;
|
|
39
|
+
schemas = opResult.schemas;
|
|
40
|
+
}
|
|
41
|
+
const doc = {
|
|
42
|
+
openapi: "3.1.0",
|
|
43
|
+
info: {
|
|
44
|
+
title: options.title ?? "ContractSpec API",
|
|
45
|
+
version: options.version ?? "0.0.0",
|
|
46
|
+
...options.description ? { description: options.description } : {}
|
|
47
|
+
},
|
|
48
|
+
...options.servers ? { servers: options.servers } : {},
|
|
49
|
+
paths,
|
|
50
|
+
components: { schemas }
|
|
51
|
+
};
|
|
52
|
+
if (includeEvents && registries.events?.length) doc["x-contractspec-events"] = exportEvents(registries.events);
|
|
53
|
+
if (includeFeatures && registries.features) doc["x-contractspec-features"] = exportFeatures(registries.features);
|
|
54
|
+
if (includePresentations && registries.presentations) doc["x-contractspec-presentations"] = exportPresentations(registries.presentations);
|
|
55
|
+
if (includeForms && registries.forms) doc["x-contractspec-forms"] = exportForms(registries.forms);
|
|
56
|
+
if (includeDataViews && registries.dataViews) doc["x-contractspec-dataviews"] = exportDataViews(registries.dataViews);
|
|
57
|
+
if (includeWorkflows && registries.workflows) doc["x-contractspec-workflows"] = exportWorkflows(registries.workflows);
|
|
58
|
+
const result = { openApi: doc };
|
|
59
|
+
if (generateRegistries) {
|
|
60
|
+
result.registries = {};
|
|
61
|
+
if (includeOps && registries.operations) result.registries.operations = generateOperationsRegistry(registries.operations);
|
|
62
|
+
if (includeEvents && registries.events?.length) result.registries.events = generateEventsExports(registries.events);
|
|
63
|
+
if (includeFeatures && registries.features) result.registries.features = generateFeaturesRegistry(registries.features);
|
|
64
|
+
if (includePresentations && registries.presentations) result.registries.presentations = generatePresentationsRegistry(registries.presentations);
|
|
65
|
+
if (includeForms && registries.forms) result.registries.forms = generateFormsRegistry(registries.forms);
|
|
66
|
+
if (includeDataViews && registries.dataViews) result.registries.dataViews = generateDataViewsRegistry(registries.dataViews);
|
|
67
|
+
if (includeWorkflows && registries.workflows) result.registries.workflows = generateWorkflowsRegistry(registries.workflows);
|
|
68
|
+
result.registries.index = generateRegistryIndex({
|
|
69
|
+
operations: includeOps && !!registries.operations,
|
|
70
|
+
events: includeEvents && !!registries.events?.length,
|
|
71
|
+
features: includeFeatures && !!registries.features,
|
|
72
|
+
presentations: includePresentations && !!registries.presentations,
|
|
73
|
+
forms: includeForms && !!registries.forms,
|
|
74
|
+
dataViews: includeDataViews && !!registries.dataViews,
|
|
75
|
+
workflows: includeWorkflows && !!registries.workflows
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Export a OperationSpecRegistry to OpenAPI JSON string.
|
|
82
|
+
*/
|
|
83
|
+
function openApiToJson(registry, options = {}) {
|
|
84
|
+
const doc = openApiForRegistry(registry, options);
|
|
85
|
+
return JSON.stringify(doc, null, 2);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Export a OperationSpecRegistry to OpenAPI YAML string.
|
|
89
|
+
*/
|
|
90
|
+
function openApiToYaml(registry, options = {}) {
|
|
91
|
+
return jsonToYaml(openApiForRegistry(registry, options));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Export ContractSpec to JSON string (all surfaces).
|
|
95
|
+
*/
|
|
96
|
+
function contractSpecToJson(registries, options = {}) {
|
|
97
|
+
const result = exportContractSpec(registries, options);
|
|
98
|
+
return JSON.stringify(result.openApi, null, 2);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Export ContractSpec to YAML string (all surfaces).
|
|
102
|
+
*/
|
|
103
|
+
function contractSpecToYaml(registries, options = {}) {
|
|
104
|
+
return jsonToYaml(exportContractSpec(registries, options).openApi);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Simple JSON to YAML conversion.
|
|
108
|
+
*/
|
|
109
|
+
function jsonToYaml(obj, indent = 0) {
|
|
110
|
+
const spaces = " ".repeat(indent);
|
|
111
|
+
let yaml = "";
|
|
112
|
+
if (Array.isArray(obj)) for (const item of obj) if (typeof item === "object" && item !== null) yaml += `${spaces}-\n${jsonToYaml(item, indent + 1)}`;
|
|
113
|
+
else yaml += `${spaces}- ${JSON.stringify(item)}\n`;
|
|
114
|
+
else if (typeof obj === "object" && obj !== null) for (const [key, value] of Object.entries(obj)) if (Array.isArray(value)) yaml += `${spaces}${key}:\n${jsonToYaml(value, indent + 1)}`;
|
|
115
|
+
else if (typeof value === "object" && value !== null) yaml += `${spaces}${key}:\n${jsonToYaml(value, indent + 1)}`;
|
|
116
|
+
else yaml += `${spaces}${key}: ${JSON.stringify(value)}\n`;
|
|
117
|
+
return yaml;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
export { contractSpecToJson, contractSpecToYaml, exportContractSpec, openApiForRegistry, openApiToJson, openApiToYaml };
|
|
122
|
+
//# sourceMappingURL=exporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exporter.js","names":["paths: Record<string, Record<string, unknown>>","schemas: Record<string, Record<string, unknown>>","doc: ContractSpecOpenApiDocument","result: ContractSpecExportResult"],"sources":["../../src/openapi/exporter.ts"],"sourcesContent":["/**\n * Export ContractSpec specs to OpenAPI 3.1 format.\n * Refactored to use modular exporters for all surfaces.\n */\nimport type {\n OperationSpecRegistry,\n FeatureRegistry,\n PresentationRegistry,\n FormRegistry,\n DataViewRegistry,\n WorkflowRegistry,\n EventSpec,\n PresentationSpec,\n} from '@lssm/lib.contracts';\nimport type { AnySchemaModel } from '@lssm/lib.schema';\nimport type {\n ContractSpecOpenApiDocument,\n OpenApiExportOptions,\n OpenApiServer,\n ContractSpecExportOptions,\n ContractSpecExportResult,\n} from './types';\nimport {\n exportOperations,\n generateOperationsRegistry,\n defaultRestPath as defaultRestPathFn,\n} from './exporter/operations';\nimport { exportEvents, generateEventsExports } from './exporter/events';\nimport { exportFeatures, generateFeaturesRegistry } from './exporter/features';\nimport {\n exportPresentations,\n generatePresentationsRegistry,\n} from './exporter/presentations';\nimport { exportForms, generateFormsRegistry } from './exporter/forms';\nimport {\n exportDataViews,\n generateDataViewsRegistry,\n} from './exporter/data-views';\nimport {\n exportWorkflows,\n generateWorkflowsRegistry,\n} from './exporter/workflows';\nimport { generateRegistryIndex } from './exporter/registries';\n\n/**\n * Input registries for unified export.\n */\nexport interface ContractSpecRegistries {\n operations?: OperationSpecRegistry;\n events?: EventSpec<AnySchemaModel>[];\n features?: FeatureRegistry;\n presentations?: PresentationRegistry;\n presentationsArray?: PresentationSpec[];\n forms?: FormRegistry;\n dataViews?: DataViewRegistry;\n workflows?: WorkflowRegistry;\n}\n\n// Re-export for backwards compatibility\nexport { defaultRestPathFn as defaultRestPath };\n\n/**\n * Export a OperationSpecRegistry to an OpenAPI 3.1 document.\n * @deprecated Use exportContractSpec for full surface support.\n */\nexport function openApiForRegistry(\n registry: OperationSpecRegistry,\n options: OpenApiExportOptions = {}\n): ContractSpecOpenApiDocument {\n const { paths, schemas } = exportOperations(registry);\n\n return {\n openapi: '3.1.0',\n info: {\n title: options.title ?? 'ContractSpec API',\n version: options.version ?? '0.0.0',\n ...(options.description ? { description: options.description } : {}),\n },\n ...(options.servers ? { servers: options.servers } : {}),\n paths,\n components: { schemas },\n };\n}\n\n/**\n * Export all ContractSpec surfaces to OpenAPI document with extensions.\n */\nexport function exportContractSpec(\n registries: ContractSpecRegistries,\n options: ContractSpecExportOptions = {}\n): ContractSpecExportResult {\n const {\n operations: includeOps = true,\n events: includeEvents = true,\n features: includeFeatures = true,\n presentations: includePresentations = true,\n forms: includeForms = true,\n dataViews: includeDataViews = true,\n workflows: includeWorkflows = true,\n generateRegistries = true,\n } = options;\n\n // Build OpenAPI document\n let paths: Record<string, Record<string, unknown>> = {};\n let schemas: Record<string, Record<string, unknown>> = {};\n\n // Export operations\n if (includeOps && registries.operations) {\n const opResult = exportOperations(registries.operations);\n paths = opResult.paths;\n schemas = opResult.schemas;\n }\n\n const doc: ContractSpecOpenApiDocument = {\n openapi: '3.1.0',\n info: {\n title: options.title ?? 'ContractSpec API',\n version: options.version ?? '0.0.0',\n ...(options.description ? { description: options.description } : {}),\n },\n ...(options.servers ? { servers: options.servers } : {}),\n paths,\n components: { schemas },\n };\n\n // Add extensions for other surfaces\n if (includeEvents && registries.events?.length) {\n doc['x-contractspec-events'] = exportEvents(registries.events);\n }\n\n if (includeFeatures && registries.features) {\n doc['x-contractspec-features'] = exportFeatures(registries.features);\n }\n\n if (includePresentations && registries.presentations) {\n doc['x-contractspec-presentations'] = exportPresentations(\n registries.presentations\n );\n }\n\n if (includeForms && registries.forms) {\n doc['x-contractspec-forms'] = exportForms(registries.forms);\n }\n\n if (includeDataViews && registries.dataViews) {\n doc['x-contractspec-dataviews'] = exportDataViews(registries.dataViews);\n }\n\n if (includeWorkflows && registries.workflows) {\n doc['x-contractspec-workflows'] = exportWorkflows(registries.workflows);\n }\n\n const result: ContractSpecExportResult = {\n openApi: doc,\n };\n\n // Generate registry code if requested\n if (generateRegistries) {\n result.registries = {};\n\n if (includeOps && registries.operations) {\n result.registries.operations = generateOperationsRegistry(\n registries.operations\n );\n }\n\n if (includeEvents && registries.events?.length) {\n result.registries.events = generateEventsExports(registries.events);\n }\n\n if (includeFeatures && registries.features) {\n result.registries.features = generateFeaturesRegistry(\n registries.features\n );\n }\n\n if (includePresentations && registries.presentations) {\n result.registries.presentations = generatePresentationsRegistry(\n registries.presentations\n );\n }\n\n if (includeForms && registries.forms) {\n result.registries.forms = generateFormsRegistry(registries.forms);\n }\n\n if (includeDataViews && registries.dataViews) {\n result.registries.dataViews = generateDataViewsRegistry(\n registries.dataViews\n );\n }\n\n if (includeWorkflows && registries.workflows) {\n result.registries.workflows = generateWorkflowsRegistry(\n registries.workflows\n );\n }\n\n // Generate index file\n result.registries.index = generateRegistryIndex({\n operations: includeOps && !!registries.operations,\n events: includeEvents && !!registries.events?.length,\n features: includeFeatures && !!registries.features,\n presentations: includePresentations && !!registries.presentations,\n forms: includeForms && !!registries.forms,\n dataViews: includeDataViews && !!registries.dataViews,\n workflows: includeWorkflows && !!registries.workflows,\n });\n }\n\n return result;\n}\n\n/**\n * Export a OperationSpecRegistry to OpenAPI JSON string.\n */\nexport function openApiToJson(\n registry: OperationSpecRegistry,\n options: OpenApiExportOptions = {}\n): string {\n const doc = openApiForRegistry(registry, options);\n return JSON.stringify(doc, null, 2);\n}\n\n/**\n * Export a OperationSpecRegistry to OpenAPI YAML string.\n */\nexport function openApiToYaml(\n registry: OperationSpecRegistry,\n options: OpenApiExportOptions = {}\n): string {\n const doc = openApiForRegistry(registry, options);\n return jsonToYaml(doc);\n}\n\n/**\n * Export ContractSpec to JSON string (all surfaces).\n */\nexport function contractSpecToJson(\n registries: ContractSpecRegistries,\n options: ContractSpecExportOptions = {}\n): string {\n const result = exportContractSpec(registries, options);\n return JSON.stringify(result.openApi, null, 2);\n}\n\n/**\n * Export ContractSpec to YAML string (all surfaces).\n */\nexport function contractSpecToYaml(\n registries: ContractSpecRegistries,\n options: ContractSpecExportOptions = {}\n): string {\n const result = exportContractSpec(registries, options);\n return jsonToYaml(result.openApi);\n}\n\n/**\n * Simple JSON to YAML conversion.\n */\nfunction jsonToYaml(obj: unknown, indent = 0): string {\n const spaces = ' '.repeat(indent);\n let yaml = '';\n\n if (Array.isArray(obj)) {\n for (const item of obj) {\n if (typeof item === 'object' && item !== null) {\n yaml += `${spaces}-\\n${jsonToYaml(item, indent + 1)}`;\n } else {\n yaml += `${spaces}- ${JSON.stringify(item)}\\n`;\n }\n }\n } else if (typeof obj === 'object' && obj !== null) {\n for (const [key, value] of Object.entries(obj)) {\n if (Array.isArray(value)) {\n yaml += `${spaces}${key}:\\n${jsonToYaml(value, indent + 1)}`;\n } else if (typeof value === 'object' && value !== null) {\n yaml += `${spaces}${key}:\\n${jsonToYaml(value, indent + 1)}`;\n } else {\n yaml += `${spaces}${key}: ${JSON.stringify(value)}\\n`;\n }\n }\n }\n\n return yaml;\n}\n\n// Re-export types for convenience\nexport type {\n OpenApiExportOptions,\n OpenApiServer,\n ContractSpecOpenApiDocument,\n ContractSpecExportOptions,\n ContractSpecExportResult,\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/openapi/importer/analyzer.ts
|
|
2
|
+
/**
|
|
3
|
+
* HTTP methods that typically indicate a command (state-changing).
|
|
4
|
+
*/
|
|
5
|
+
const COMMAND_METHODS = [
|
|
6
|
+
"post",
|
|
7
|
+
"put",
|
|
8
|
+
"delete",
|
|
9
|
+
"patch"
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Determine if an operation is a command or query based on HTTP method.
|
|
13
|
+
*/
|
|
14
|
+
function inferOpKind(method) {
|
|
15
|
+
return COMMAND_METHODS.includes(method.toLowerCase()) ? "command" : "query";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Determine auth level based on security requirements.
|
|
19
|
+
*/
|
|
20
|
+
function inferAuthLevel(operation, defaultAuth) {
|
|
21
|
+
if (!operation.security || operation.security.length === 0) return defaultAuth;
|
|
22
|
+
for (const sec of operation.security) if (Object.keys(sec).length === 0) return "anonymous";
|
|
23
|
+
return "user";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { COMMAND_METHODS, inferAuthLevel, inferOpKind };
|
|
28
|
+
//# sourceMappingURL=analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.js","names":[],"sources":["../../../src/openapi/importer/analyzer.ts"],"sourcesContent":["import type { ParsedOperation } from '../types';\n\n/**\n * HTTP methods that typically indicate a command (state-changing).\n */\nexport const COMMAND_METHODS = ['post', 'put', 'delete', 'patch'];\n\n/**\n * Determine if an operation is a command or query based on HTTP method.\n */\nexport function inferOpKind(method: string): 'command' | 'query' {\n return COMMAND_METHODS.includes(method.toLowerCase()) ? 'command' : 'query';\n}\n\n/**\n * Determine auth level based on security requirements.\n */\nexport function inferAuthLevel(\n operation: ParsedOperation,\n defaultAuth: 'anonymous' | 'user' | 'admin'\n): 'anonymous' | 'user' | 'admin' {\n if (!operation.security || operation.security.length === 0) {\n // Check if any security scheme is present\n return defaultAuth;\n }\n\n // If there's an empty security requirement, it's anonymous\n for (const sec of operation.security) {\n if (Object.keys(sec).length === 0) {\n return 'anonymous';\n }\n }\n\n return 'user';\n}\n"],"mappings":";;;;AAKA,MAAa,kBAAkB;CAAC;CAAQ;CAAO;CAAU;CAAQ;;;;AAKjE,SAAgB,YAAY,QAAqC;AAC/D,QAAO,gBAAgB,SAAS,OAAO,aAAa,CAAC,GAAG,YAAY;;;;;AAMtE,SAAgB,eACd,WACA,aACgC;AAChC,KAAI,CAAC,UAAU,YAAY,UAAU,SAAS,WAAW,EAEvD,QAAO;AAIT,MAAK,MAAM,OAAO,UAAU,SAC1B,KAAI,OAAO,KAAK,IAAI,CAAC,WAAW,EAC9B,QAAO;AAIX,QAAO"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { toPascalCase, toValidIdentifier } from "../../common/utils.js";
|
|
2
|
+
import { generateImports, generateSchemaModelCode } from "../schema-converter.js";
|
|
3
|
+
|
|
4
|
+
//#region src/openapi/importer/events.ts
|
|
5
|
+
/**
|
|
6
|
+
* Generate code for an event.
|
|
7
|
+
*/
|
|
8
|
+
function generateEventCode(event, options) {
|
|
9
|
+
const eventName = toValidIdentifier(event.name);
|
|
10
|
+
const modelName = toPascalCase(eventName) + "Payload";
|
|
11
|
+
const payloadModel = generateSchemaModelCode(event.payload, modelName);
|
|
12
|
+
const imports = /* @__PURE__ */ new Set();
|
|
13
|
+
imports.add("import { defineEvent, type EventSpec } from '@lssm/lib.contracts';");
|
|
14
|
+
generateImports(payloadModel.fields, options).split("\n").filter(Boolean).forEach((i) => imports.add(i));
|
|
15
|
+
if (payloadModel.name !== modelName) {
|
|
16
|
+
const modelsDir = `../${options.conventions.models}`;
|
|
17
|
+
const kebabName = payloadModel.name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
18
|
+
imports.add(`import { ${payloadModel.name} } from '${modelsDir}/${kebabName}';`);
|
|
19
|
+
}
|
|
20
|
+
return `
|
|
21
|
+
${Array.from(imports).join("\n")}
|
|
22
|
+
|
|
23
|
+
${payloadModel.code}
|
|
24
|
+
|
|
25
|
+
export const ${eventName} = defineEvent({
|
|
26
|
+
name: '${event.name}',
|
|
27
|
+
version: 1,
|
|
28
|
+
description: ${JSON.stringify(event.description ?? "")},
|
|
29
|
+
payload: ${payloadModel.name},
|
|
30
|
+
});
|
|
31
|
+
`.trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { generateEventCode };
|
|
36
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../../../src/openapi/importer/events.ts"],"sourcesContent":["import type { ParsedEvent } from '../types';\nimport { generateImports, generateSchemaModelCode } from '../schema-converter';\nimport { toPascalCase, toValidIdentifier } from '../../common/utils';\nimport type { ContractsrcConfig } from '@lssm/lib.contracts';\n\n/**\n * Generate code for an event.\n */\nexport function generateEventCode(\n event: ParsedEvent,\n options: ContractsrcConfig\n): string {\n const eventName = toValidIdentifier(event.name);\n const modelName = toPascalCase(eventName) + 'Payload';\n\n // Generate payload model inline or referenced?\n // Let's generate the payload schema definition first\n const payloadModel = generateSchemaModelCode(event.payload, modelName);\n\n const imports = new Set<string>();\n imports.add(\n \"import { defineEvent, type EventSpec } from '@lssm/lib.contracts';\"\n );\n\n const modelImports = generateImports(payloadModel.fields, options);\n // Merge imports - this is a bit hacky string manipulation but works for now\n modelImports\n .split('\\n')\n .filter(Boolean)\n .forEach((i) => imports.add(i));\n\n // If payloadModel is a reference (empty fields and different name), import it\n if (payloadModel.name !== modelName) {\n const modelsDir = `../${options.conventions.models}`;\n const kebabName = payloadModel.name\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .toLowerCase();\n imports.add(\n `import { ${payloadModel.name} } from '${modelsDir}/${kebabName}';`\n );\n }\n\n const allImports = Array.from(imports).join('\\n');\n\n return `\n${allImports}\n\n${payloadModel.code}\n\nexport const ${eventName} = defineEvent({\n name: '${event.name}',\n version: 1,\n description: ${JSON.stringify(event.description ?? '')},\n payload: ${payloadModel.name},\n});\n`.trim();\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,kBACd,OACA,SACQ;CACR,MAAM,YAAY,kBAAkB,MAAM,KAAK;CAC/C,MAAM,YAAY,aAAa,UAAU,GAAG;CAI5C,MAAM,eAAe,wBAAwB,MAAM,SAAS,UAAU;CAEtE,MAAM,0BAAU,IAAI,KAAa;AACjC,SAAQ,IACN,qEACD;AAID,CAFqB,gBAAgB,aAAa,QAAQ,QAAQ,CAG/D,MAAM,KAAK,CACX,OAAO,QAAQ,CACf,SAAS,MAAM,QAAQ,IAAI,EAAE,CAAC;AAGjC,KAAI,aAAa,SAAS,WAAW;EACnC,MAAM,YAAY,MAAM,QAAQ,YAAY;EAC5C,MAAM,YAAY,aAAa,KAC5B,QAAQ,sBAAsB,QAAQ,CACtC,aAAa;AAChB,UAAQ,IACN,YAAY,aAAa,KAAK,WAAW,UAAU,GAAG,UAAU,IACjE;;AAKH,QAAO;EAFY,MAAM,KAAK,QAAQ,CAAC,KAAK,KAAK,CAGtC;;EAEX,aAAa,KAAK;;eAEL,UAAU;WACd,MAAM,KAAK;;iBAEL,KAAK,UAAU,MAAM,eAAe,GAAG,CAAC;aAC5C,aAAa,KAAK;;EAE7B,MAAM"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { toPascalCase, toSpecName, toValidIdentifier } from "../../common/utils.js";
|
|
2
|
+
import { generateImports } from "../schema-converter.js";
|
|
3
|
+
import { inferAuthLevel, inferOpKind } from "./analyzer.js";
|
|
4
|
+
|
|
5
|
+
//#region src/openapi/importer/generator.ts
|
|
6
|
+
/**
|
|
7
|
+
* Generate ContractSpec TypeScript code for an operation.
|
|
8
|
+
*/
|
|
9
|
+
function generateSpecCode(operation, contractspecConfig, options = {}, inputModel, outputModel) {
|
|
10
|
+
const specName = toSpecName(operation.operationId, options.prefix);
|
|
11
|
+
const kind = inferOpKind(operation.method);
|
|
12
|
+
const auth = inferAuthLevel(operation, options.defaultAuth ?? "user");
|
|
13
|
+
const lines = [];
|
|
14
|
+
lines.push("import { defineCommand, defineQuery } from '@lssm/lib.contracts';");
|
|
15
|
+
if (inputModel || outputModel) lines.push(generateImports([...inputModel?.fields ?? [], ...outputModel?.fields ?? []], contractspecConfig, false));
|
|
16
|
+
lines.push("");
|
|
17
|
+
if (inputModel && inputModel.code) {
|
|
18
|
+
lines.push("// Input schema");
|
|
19
|
+
lines.push(inputModel.code);
|
|
20
|
+
lines.push("");
|
|
21
|
+
}
|
|
22
|
+
if (outputModel && outputModel.code) {
|
|
23
|
+
lines.push("// Output schema");
|
|
24
|
+
lines.push(outputModel.code);
|
|
25
|
+
lines.push("");
|
|
26
|
+
}
|
|
27
|
+
const defineFunc = kind === "command" ? "defineCommand" : "defineQuery";
|
|
28
|
+
const safeName = toValidIdentifier(toPascalCase(operation.operationId));
|
|
29
|
+
lines.push(`/**`);
|
|
30
|
+
lines.push(` * ${operation.summary ?? operation.operationId}`);
|
|
31
|
+
if (operation.description) {
|
|
32
|
+
lines.push(` *`);
|
|
33
|
+
lines.push(` * ${operation.description}`);
|
|
34
|
+
}
|
|
35
|
+
lines.push(` *`);
|
|
36
|
+
lines.push(` * @source OpenAPI: ${operation.method.toUpperCase()} ${operation.path}`);
|
|
37
|
+
lines.push(` */`);
|
|
38
|
+
lines.push(`export const ${safeName}Spec = ${defineFunc}({`);
|
|
39
|
+
lines.push(" meta: {");
|
|
40
|
+
lines.push(` name: '${specName}',`);
|
|
41
|
+
lines.push(" version: 1,");
|
|
42
|
+
lines.push(` stability: '${options.defaultStability ?? "stable"}',`);
|
|
43
|
+
lines.push(` owners: [${(options.defaultOwners ?? []).map((o) => `'${o}'`).join(", ")}],`);
|
|
44
|
+
lines.push(` tags: [${operation.tags.map((t) => `'${t}'`).join(", ")}],`);
|
|
45
|
+
lines.push(` description: ${JSON.stringify(operation.summary ?? operation.operationId)},`);
|
|
46
|
+
lines.push(` goal: ${JSON.stringify(operation.description ?? "Imported from OpenAPI")},`);
|
|
47
|
+
lines.push(` context: 'Imported from OpenAPI: ${operation.method.toUpperCase()} ${operation.path}',`);
|
|
48
|
+
lines.push(" },");
|
|
49
|
+
lines.push(" io: {");
|
|
50
|
+
if (inputModel) lines.push(` input: ${inputModel.name},`);
|
|
51
|
+
else lines.push(" input: null,");
|
|
52
|
+
if (outputModel) lines.push(` output: ${outputModel.name},`);
|
|
53
|
+
else lines.push(" output: null, // TODO: Define output schema");
|
|
54
|
+
lines.push(" },");
|
|
55
|
+
lines.push(" policy: {");
|
|
56
|
+
lines.push(` auth: '${auth}',`);
|
|
57
|
+
lines.push(" },");
|
|
58
|
+
const restMethod = operation.method.toUpperCase() === "GET" ? "GET" : "POST";
|
|
59
|
+
lines.push(" transport: {");
|
|
60
|
+
lines.push(" rest: {");
|
|
61
|
+
lines.push(` method: '${restMethod}',`);
|
|
62
|
+
lines.push(` path: '${operation.path}',`);
|
|
63
|
+
lines.push(" },");
|
|
64
|
+
lines.push(" },");
|
|
65
|
+
lines.push("});");
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { generateSpecCode };
|
|
71
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.js","names":["lines: string[]"],"sources":["../../../src/openapi/importer/generator.ts"],"sourcesContent":["import type { ParsedOperation } from '../types';\nimport {\n toPascalCase,\n toSpecName,\n toValidIdentifier,\n} from '../../common/utils';\nimport { type GeneratedModel, generateImports } from '../schema-converter';\nimport { inferAuthLevel, inferOpKind } from './analyzer';\nimport type {\n ContractsrcConfig,\n OpenApiSourceConfig,\n} from '@lssm/lib.contracts';\n\n/**\n * Generate ContractSpec TypeScript code for an operation.\n */\nexport function generateSpecCode(\n operation: ParsedOperation,\n contractspecConfig: ContractsrcConfig,\n options: Partial<OpenApiSourceConfig> = {},\n inputModel: GeneratedModel | null,\n outputModel: GeneratedModel | null\n): string {\n const specName = toSpecName(operation.operationId, options.prefix);\n const kind = inferOpKind(operation.method);\n const auth = inferAuthLevel(operation, options.defaultAuth ?? 'user');\n\n const lines: string[] = [];\n\n // Imports\n lines.push(\n \"import { defineCommand, defineQuery } from '@lssm/lib.contracts';\"\n );\n if (inputModel || outputModel) {\n lines.push(\n generateImports(\n [...(inputModel?.fields ?? []), ...(outputModel?.fields ?? [])],\n contractspecConfig,\n false // operations import from ../models, not same directory\n )\n );\n }\n lines.push('');\n\n // Generate input model if present\n if (inputModel && inputModel.code) {\n lines.push('// Input schema');\n lines.push(inputModel.code);\n lines.push('');\n }\n\n // Generate output model if present\n if (outputModel && outputModel.code) {\n lines.push('// Output schema');\n lines.push(outputModel.code);\n lines.push('');\n }\n\n // Generate spec\n const defineFunc = kind === 'command' ? 'defineCommand' : 'defineQuery';\n const safeName = toValidIdentifier(toPascalCase(operation.operationId));\n\n lines.push(`/**`);\n lines.push(` * ${operation.summary ?? operation.operationId}`);\n if (operation.description) {\n lines.push(` *`);\n lines.push(` * ${operation.description}`);\n }\n lines.push(` *`);\n lines.push(\n ` * @source OpenAPI: ${operation.method.toUpperCase()} ${operation.path}`\n );\n lines.push(` */`);\n lines.push(`export const ${safeName}Spec = ${defineFunc}({`);\n\n // Meta\n lines.push(' meta: {');\n lines.push(` name: '${specName}',`);\n lines.push(' version: 1,');\n lines.push(` stability: '${options.defaultStability ?? 'stable'}',`);\n lines.push(\n ` owners: [${(options.defaultOwners ?? []).map((o) => `'${o}'`).join(', ')}],`\n );\n lines.push(` tags: [${operation.tags.map((t) => `'${t}'`).join(', ')}],`);\n lines.push(\n ` description: ${JSON.stringify(operation.summary ?? operation.operationId)},`\n );\n lines.push(\n ` goal: ${JSON.stringify(operation.description ?? 'Imported from OpenAPI')},`\n );\n lines.push(\n ` context: 'Imported from OpenAPI: ${operation.method.toUpperCase()} ${operation.path}',`\n );\n lines.push(' },');\n\n // IO\n lines.push(' io: {');\n if (inputModel) {\n lines.push(` input: ${inputModel.name},`);\n } else {\n lines.push(' input: null,');\n }\n if (outputModel) {\n lines.push(` output: ${outputModel.name},`);\n } else {\n lines.push(' output: null, // TODO: Define output schema');\n }\n lines.push(' },');\n\n // Policy\n lines.push(' policy: {');\n lines.push(` auth: '${auth}',`);\n lines.push(' },');\n\n // Transport hints\n // ContractSpec only supports GET and POST - map other methods appropriately\n const httpMethod = operation.method.toUpperCase();\n const restMethod = httpMethod === 'GET' ? 'GET' : 'POST'; // GET stays GET, everything else becomes POST\n lines.push(' transport: {');\n lines.push(' rest: {');\n lines.push(` method: '${restMethod}',`);\n lines.push(` path: '${operation.path}',`);\n lines.push(' },');\n lines.push(' },');\n\n lines.push('});');\n\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;;AAgBA,SAAgB,iBACd,WACA,oBACA,UAAwC,EAAE,EAC1C,YACA,aACQ;CACR,MAAM,WAAW,WAAW,UAAU,aAAa,QAAQ,OAAO;CAClE,MAAM,OAAO,YAAY,UAAU,OAAO;CAC1C,MAAM,OAAO,eAAe,WAAW,QAAQ,eAAe,OAAO;CAErE,MAAMA,QAAkB,EAAE;AAG1B,OAAM,KACJ,oEACD;AACD,KAAI,cAAc,YAChB,OAAM,KACJ,gBACE,CAAC,GAAI,YAAY,UAAU,EAAE,EAAG,GAAI,aAAa,UAAU,EAAE,CAAE,EAC/D,oBACA,MACD,CACF;AAEH,OAAM,KAAK,GAAG;AAGd,KAAI,cAAc,WAAW,MAAM;AACjC,QAAM,KAAK,kBAAkB;AAC7B,QAAM,KAAK,WAAW,KAAK;AAC3B,QAAM,KAAK,GAAG;;AAIhB,KAAI,eAAe,YAAY,MAAM;AACnC,QAAM,KAAK,mBAAmB;AAC9B,QAAM,KAAK,YAAY,KAAK;AAC5B,QAAM,KAAK,GAAG;;CAIhB,MAAM,aAAa,SAAS,YAAY,kBAAkB;CAC1D,MAAM,WAAW,kBAAkB,aAAa,UAAU,YAAY,CAAC;AAEvE,OAAM,KAAK,MAAM;AACjB,OAAM,KAAK,MAAM,UAAU,WAAW,UAAU,cAAc;AAC9D,KAAI,UAAU,aAAa;AACzB,QAAM,KAAK,KAAK;AAChB,QAAM,KAAK,MAAM,UAAU,cAAc;;AAE3C,OAAM,KAAK,KAAK;AAChB,OAAM,KACJ,uBAAuB,UAAU,OAAO,aAAa,CAAC,GAAG,UAAU,OACpE;AACD,OAAM,KAAK,MAAM;AACjB,OAAM,KAAK,gBAAgB,SAAS,SAAS,WAAW,IAAI;AAG5D,OAAM,KAAK,YAAY;AACvB,OAAM,KAAK,cAAc,SAAS,IAAI;AACtC,OAAM,KAAK,kBAAkB;AAC7B,OAAM,KAAK,mBAAmB,QAAQ,oBAAoB,SAAS,IAAI;AACvE,OAAM,KACJ,iBAAiB,QAAQ,iBAAiB,EAAE,EAAE,KAAK,MAAM,IAAI,EAAE,GAAG,CAAC,KAAK,KAAK,CAAC,IAC/E;AACD,OAAM,KAAK,cAAc,UAAU,KAAK,KAAK,MAAM,IAAI,EAAE,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI;AAC5E,OAAM,KACJ,oBAAoB,KAAK,UAAU,UAAU,WAAW,UAAU,YAAY,CAAC,GAChF;AACD,OAAM,KACJ,aAAa,KAAK,UAAU,UAAU,eAAe,wBAAwB,CAAC,GAC/E;AACD,OAAM,KACJ,wCAAwC,UAAU,OAAO,aAAa,CAAC,GAAG,UAAU,KAAK,IAC1F;AACD,OAAM,KAAK,OAAO;AAGlB,OAAM,KAAK,UAAU;AACrB,KAAI,WACF,OAAM,KAAK,cAAc,WAAW,KAAK,GAAG;KAE5C,OAAM,KAAK,mBAAmB;AAEhC,KAAI,YACF,OAAM,KAAK,eAAe,YAAY,KAAK,GAAG;KAE9C,OAAM,KAAK,kDAAkD;AAE/D,OAAM,KAAK,OAAO;AAGlB,OAAM,KAAK,cAAc;AACzB,OAAM,KAAK,cAAc,KAAK,IAAI;AAClC,OAAM,KAAK,OAAO;CAKlB,MAAM,aADa,UAAU,OAAO,aAAa,KACf,QAAQ,QAAQ;AAClD,OAAM,KAAK,iBAAiB;AAC5B,OAAM,KAAK,cAAc;AACzB,OAAM,KAAK,kBAAkB,WAAW,IAAI;AAC5C,OAAM,KAAK,gBAAgB,UAAU,KAAK,IAAI;AAC9C,OAAM,KAAK,SAAS;AACpB,OAAM,KAAK,OAAO;AAElB,OAAM,KAAK,MAAM;AAEjB,QAAO,MAAM,KAAK,KAAK"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/openapi/importer/grouping.ts
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the group folder for an operation based on grouping config.
|
|
4
|
+
*/
|
|
5
|
+
function resolveOperationGroupFolder(operation, conventions) {
|
|
6
|
+
const groupingRule = conventions.operationsGrouping;
|
|
7
|
+
if (!groupingRule || groupingRule.strategy === "none") return "";
|
|
8
|
+
return applyGroupingStrategy(groupingRule, {
|
|
9
|
+
name: operation.operationId,
|
|
10
|
+
tags: operation.tags,
|
|
11
|
+
path: operation.path
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the group folder for a model based on grouping config.
|
|
16
|
+
*/
|
|
17
|
+
function resolveModelGroupFolder(modelName, conventions, relatedPath, relatedTags) {
|
|
18
|
+
const groupingRule = conventions.modelsGrouping;
|
|
19
|
+
if (!groupingRule || groupingRule.strategy === "none") return "";
|
|
20
|
+
return applyGroupingStrategy(groupingRule, {
|
|
21
|
+
name: modelName,
|
|
22
|
+
tags: relatedTags ?? [],
|
|
23
|
+
path: relatedPath
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the group folder for an event based on grouping config.
|
|
28
|
+
*/
|
|
29
|
+
function resolveEventGroupFolder(eventName, conventions, relatedTags) {
|
|
30
|
+
const groupingRule = conventions.eventsGrouping;
|
|
31
|
+
if (!groupingRule || groupingRule.strategy === "none") return "";
|
|
32
|
+
return applyGroupingStrategy(groupingRule, {
|
|
33
|
+
name: eventName,
|
|
34
|
+
tags: relatedTags ?? []
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Apply grouping strategy to extract folder name.
|
|
39
|
+
*/
|
|
40
|
+
function applyGroupingStrategy(rule, context) {
|
|
41
|
+
switch (rule.strategy) {
|
|
42
|
+
case "by-tag": return context.tags?.[0] ?? "untagged";
|
|
43
|
+
case "by-owner": return context.owners?.[0] ?? "unowned";
|
|
44
|
+
case "by-domain": return extractDomain(context.name);
|
|
45
|
+
case "by-url-path-single": return extractUrlPathLevel(context.path, 1);
|
|
46
|
+
case "by-url-path-multi": return extractUrlPathLevel(context.path, rule.urlPathLevel ?? 2);
|
|
47
|
+
case "by-feature": return extractDomain(context.name);
|
|
48
|
+
case "none":
|
|
49
|
+
default: return "";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Extract domain from operation/model name.
|
|
54
|
+
* e.g., "users.create" -> "users"
|
|
55
|
+
*/
|
|
56
|
+
function extractDomain(name) {
|
|
57
|
+
if (name.includes(".")) return name.split(".")[0] ?? "default";
|
|
58
|
+
if (name.includes("_")) return name.split("_")[0] ?? "default";
|
|
59
|
+
return name.match(/^([a-z]+)/i)?.[1]?.toLowerCase() ?? "default";
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extract URL path segments for grouping.
|
|
63
|
+
*/
|
|
64
|
+
function extractUrlPathLevel(path, level) {
|
|
65
|
+
if (!path) return "root";
|
|
66
|
+
const nonParamSegments = path.split("/").filter(Boolean).filter((s) => !s.startsWith("{"));
|
|
67
|
+
if (nonParamSegments.length === 0) return "root";
|
|
68
|
+
return nonParamSegments.slice(0, level).join("/");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { resolveEventGroupFolder, resolveModelGroupFolder, resolveOperationGroupFolder };
|
|
73
|
+
//# sourceMappingURL=grouping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grouping.js","names":[],"sources":["../../../src/openapi/importer/grouping.ts"],"sourcesContent":["/**\n * Grouping utilities for OpenAPI import/export.\n * Determines output folder structure based on configuration.\n */\nimport type { FolderConventions } from '@lssm/lib.contracts';\nimport type { ParsedOperation } from '../types';\n\n/**\n * Grouping strategy type (matches ContractSpec config).\n */\ntype GroupingStrategy =\n | 'by-tag'\n | 'by-owner'\n | 'by-domain'\n | 'by-url-path-single'\n | 'by-url-path-multi'\n | 'by-feature'\n | 'none';\n\n/**\n * Grouping rule configuration.\n */\ninterface GroupingRule {\n strategy: GroupingStrategy;\n urlPathLevel?: number;\n pattern?: string;\n}\n\n/**\n * Resolve the group folder for an operation based on grouping config.\n */\nexport function resolveOperationGroupFolder(\n operation: ParsedOperation,\n conventions: FolderConventions\n): string {\n const groupingRule = conventions.operationsGrouping as\n | GroupingRule\n | undefined;\n\n if (!groupingRule || groupingRule.strategy === 'none') {\n return '';\n }\n\n return applyGroupingStrategy(groupingRule, {\n name: operation.operationId,\n tags: operation.tags,\n path: operation.path,\n });\n}\n\n/**\n * Resolve the group folder for a model based on grouping config.\n */\nexport function resolveModelGroupFolder(\n modelName: string,\n conventions: FolderConventions,\n relatedPath?: string,\n relatedTags?: string[]\n): string {\n const groupingRule = conventions.modelsGrouping as GroupingRule | undefined;\n\n if (!groupingRule || groupingRule.strategy === 'none') {\n return '';\n }\n\n return applyGroupingStrategy(groupingRule, {\n name: modelName,\n tags: relatedTags ?? [],\n path: relatedPath,\n });\n}\n\n/**\n * Resolve the group folder for an event based on grouping config.\n */\nexport function resolveEventGroupFolder(\n eventName: string,\n conventions: FolderConventions,\n relatedTags?: string[]\n): string {\n const groupingRule = conventions.eventsGrouping as GroupingRule | undefined;\n\n if (!groupingRule || groupingRule.strategy === 'none') {\n return '';\n }\n\n return applyGroupingStrategy(groupingRule, {\n name: eventName,\n tags: relatedTags ?? [],\n });\n}\n\n/**\n * Apply grouping strategy to extract folder name.\n */\nfunction applyGroupingStrategy(\n rule: GroupingRule,\n context: {\n name: string;\n tags?: string[];\n path?: string;\n owners?: string[];\n }\n): string {\n switch (rule.strategy) {\n case 'by-tag':\n return context.tags?.[0] ?? 'untagged';\n\n case 'by-owner':\n return context.owners?.[0] ?? 'unowned';\n\n case 'by-domain':\n return extractDomain(context.name);\n\n case 'by-url-path-single':\n return extractUrlPathLevel(context.path, 1);\n\n case 'by-url-path-multi':\n return extractUrlPathLevel(context.path, rule.urlPathLevel ?? 2);\n\n case 'by-feature':\n // Use domain extraction for feature grouping\n return extractDomain(context.name);\n\n case 'none':\n default:\n return '';\n }\n}\n\n/**\n * Extract domain from operation/model name.\n * e.g., \"users.create\" -> \"users\"\n */\nfunction extractDomain(name: string): string {\n // Handle camelCase/PascalCase names\n if (name.includes('.')) {\n return name.split('.')[0] ?? 'default';\n }\n if (name.includes('_')) {\n return name.split('_')[0] ?? 'default';\n }\n // Extract from camelCase like \"createUser\" -> \"create\"\n const match = name.match(/^([a-z]+)/i);\n return match?.[1]?.toLowerCase() ?? 'default';\n}\n\n/**\n * Extract URL path segments for grouping.\n */\nfunction extractUrlPathLevel(path: string | undefined, level: number): string {\n if (!path) return 'root';\n const segments = path.split('/').filter(Boolean);\n\n // Filter out path parameters like {id}\n const nonParamSegments = segments.filter((s) => !s.startsWith('{'));\n\n if (nonParamSegments.length === 0) return 'root';\n\n return nonParamSegments.slice(0, level).join('/');\n}\n\n/**\n * Build full output path including group folder.\n */\nexport function buildOutputPath(\n baseDir: string,\n surfaceDir: string,\n groupFolder: string,\n fileName: string\n): string {\n const parts = [baseDir, surfaceDir];\n\n if (groupFolder) {\n parts.push(groupFolder);\n }\n\n parts.push(fileName);\n\n return parts.filter(Boolean).join('/');\n}\n\n/**\n * Determine if feature-based grouping should be applied.\n */\nexport function shouldGroupByFeature(conventions: FolderConventions): boolean {\n return conventions.groupByFeature;\n}\n"],"mappings":";;;;AA+BA,SAAgB,4BACd,WACA,aACQ;CACR,MAAM,eAAe,YAAY;AAIjC,KAAI,CAAC,gBAAgB,aAAa,aAAa,OAC7C,QAAO;AAGT,QAAO,sBAAsB,cAAc;EACzC,MAAM,UAAU;EAChB,MAAM,UAAU;EAChB,MAAM,UAAU;EACjB,CAAC;;;;;AAMJ,SAAgB,wBACd,WACA,aACA,aACA,aACQ;CACR,MAAM,eAAe,YAAY;AAEjC,KAAI,CAAC,gBAAgB,aAAa,aAAa,OAC7C,QAAO;AAGT,QAAO,sBAAsB,cAAc;EACzC,MAAM;EACN,MAAM,eAAe,EAAE;EACvB,MAAM;EACP,CAAC;;;;;AAMJ,SAAgB,wBACd,WACA,aACA,aACQ;CACR,MAAM,eAAe,YAAY;AAEjC,KAAI,CAAC,gBAAgB,aAAa,aAAa,OAC7C,QAAO;AAGT,QAAO,sBAAsB,cAAc;EACzC,MAAM;EACN,MAAM,eAAe,EAAE;EACxB,CAAC;;;;;AAMJ,SAAS,sBACP,MACA,SAMQ;AACR,SAAQ,KAAK,UAAb;EACE,KAAK,SACH,QAAO,QAAQ,OAAO,MAAM;EAE9B,KAAK,WACH,QAAO,QAAQ,SAAS,MAAM;EAEhC,KAAK,YACH,QAAO,cAAc,QAAQ,KAAK;EAEpC,KAAK,qBACH,QAAO,oBAAoB,QAAQ,MAAM,EAAE;EAE7C,KAAK,oBACH,QAAO,oBAAoB,QAAQ,MAAM,KAAK,gBAAgB,EAAE;EAElE,KAAK,aAEH,QAAO,cAAc,QAAQ,KAAK;EAEpC,KAAK;EACL,QACE,QAAO;;;;;;;AAQb,SAAS,cAAc,MAAsB;AAE3C,KAAI,KAAK,SAAS,IAAI,CACpB,QAAO,KAAK,MAAM,IAAI,CAAC,MAAM;AAE/B,KAAI,KAAK,SAAS,IAAI,CACpB,QAAO,KAAK,MAAM,IAAI,CAAC,MAAM;AAI/B,QADc,KAAK,MAAM,aAAa,GACvB,IAAI,aAAa,IAAI;;;;;AAMtC,SAAS,oBAAoB,MAA0B,OAAuB;AAC5E,KAAI,CAAC,KAAM,QAAO;CAIlB,MAAM,mBAHW,KAAK,MAAM,IAAI,CAAC,OAAO,QAAQ,CAGd,QAAQ,MAAM,CAAC,EAAE,WAAW,IAAI,CAAC;AAEnE,KAAI,iBAAiB,WAAW,EAAG,QAAO;AAE1C,QAAO,iBAAiB,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ImportResult } from "../../common/types.js";
|
|
2
|
+
import { ParseResult, ParsedOperation } from "../types.js";
|
|
3
|
+
import { ContractsrcConfig, OpenApiSourceConfig } from "@lssm/lib.contracts";
|
|
4
|
+
|
|
5
|
+
//#region src/openapi/importer/index.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Import operations from a parsed OpenAPI document.
|
|
9
|
+
*/
|
|
10
|
+
declare const importFromOpenApi: (parseResult: ParseResult, contractspecOptions: ContractsrcConfig, importOptions?: Partial<OpenApiSourceConfig>) => ImportResult;
|
|
11
|
+
/**
|
|
12
|
+
* Import a single operation to ContractSpec code.
|
|
13
|
+
*/
|
|
14
|
+
declare function importOperation(operation: ParsedOperation, options: Partial<OpenApiSourceConfig> | undefined, contractspecOptions: ContractsrcConfig): string;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { importFromOpenApi, importOperation };
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/openapi/importer/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAyPgB,cAxNH,iBAwNkB,EAAA,CAAA,WAAA,EAvNhB,WAuNgB,EAAA,mBAAA,EAtNR,iBAsNQ,EAAA,aAAA,CAAA,EArNd,OAqNc,CArNN,mBAqNM,CAAA,EAAA,GApN5B,YAoN4B;;;;AAGR,iBAHP,eAAA,CAGO,SAAA,EAFV,eAEU,EAAA,OAAA,EADZ,OACY,CADJ,mBACI,CAAA,GAAA,SAAA,EAAA,mBAAA,EAAA,iBAAA,CAAA,EAAA,MAAA"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { toFileName, toSpecName } from "../../common/utils.js";
|
|
2
|
+
import { generateSchemaModelCode } from "../schema-converter.js";
|
|
3
|
+
import { buildInputSchema, getOutputSchema } from "./schemas.js";
|
|
4
|
+
import { COMMAND_METHODS, inferAuthLevel, inferOpKind } from "./analyzer.js";
|
|
5
|
+
import { generateSpecCode } from "./generator.js";
|
|
6
|
+
import { generateModelCode } from "./models.js";
|
|
7
|
+
import { generateEventCode } from "./events.js";
|
|
8
|
+
import { resolveEventGroupFolder, resolveModelGroupFolder, resolveOperationGroupFolder } from "./grouping.js";
|
|
9
|
+
|
|
10
|
+
//#region src/openapi/importer/index.ts
|
|
11
|
+
/**
|
|
12
|
+
* Import operations from a parsed OpenAPI document.
|
|
13
|
+
*/
|
|
14
|
+
const importFromOpenApi = (parseResult, contractspecOptions, importOptions = {}) => {
|
|
15
|
+
const { tags, exclude = [], include } = importOptions;
|
|
16
|
+
const specs = [];
|
|
17
|
+
const skipped = [];
|
|
18
|
+
const errors = [];
|
|
19
|
+
for (const operation of parseResult.operations) {
|
|
20
|
+
if (tags && tags.length > 0) {
|
|
21
|
+
if (!operation.tags.some((t) => tags.includes(t))) {
|
|
22
|
+
skipped.push({
|
|
23
|
+
sourceId: operation.operationId,
|
|
24
|
+
reason: `No matching tags (has: ${operation.tags.join(", ")})`
|
|
25
|
+
});
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (include && include.length > 0) {
|
|
30
|
+
if (!include.includes(operation.operationId)) {
|
|
31
|
+
skipped.push({
|
|
32
|
+
sourceId: operation.operationId,
|
|
33
|
+
reason: "Not in include list"
|
|
34
|
+
});
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
} else if (exclude.includes(operation.operationId)) {
|
|
38
|
+
skipped.push({
|
|
39
|
+
sourceId: operation.operationId,
|
|
40
|
+
reason: "In exclude list"
|
|
41
|
+
});
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (operation.deprecated && importOptions.defaultStability !== "deprecated") {
|
|
45
|
+
skipped.push({
|
|
46
|
+
sourceId: operation.operationId,
|
|
47
|
+
reason: "Deprecated operation"
|
|
48
|
+
});
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const { schema: inputSchema } = buildInputSchema(operation);
|
|
53
|
+
const inputModel = inputSchema ? generateSchemaModelCode(inputSchema, `${operation.operationId}Input`) : null;
|
|
54
|
+
const outputSchema = getOutputSchema(operation);
|
|
55
|
+
let outputModel = outputSchema ? generateSchemaModelCode(outputSchema, `${operation.operationId}Output`) : null;
|
|
56
|
+
if (outputModel && !outputModel.code.includes("defineSchemaModel")) outputModel = null;
|
|
57
|
+
const code = generateSpecCode(operation, contractspecOptions, importOptions, inputModel, outputModel);
|
|
58
|
+
const fileName = toFileName(toSpecName(operation.operationId, importOptions.prefix));
|
|
59
|
+
const transportHints = { rest: {
|
|
60
|
+
method: operation.method.toUpperCase(),
|
|
61
|
+
path: operation.path,
|
|
62
|
+
params: {
|
|
63
|
+
path: operation.pathParams.map((p) => p.name),
|
|
64
|
+
query: operation.queryParams.map((p) => p.name),
|
|
65
|
+
header: operation.headerParams.map((p) => p.name),
|
|
66
|
+
cookie: operation.cookieParams.map((p) => p.name)
|
|
67
|
+
}
|
|
68
|
+
} };
|
|
69
|
+
const source = {
|
|
70
|
+
type: "openapi",
|
|
71
|
+
sourceId: operation.operationId,
|
|
72
|
+
operationId: operation.operationId,
|
|
73
|
+
openApiVersion: parseResult.version,
|
|
74
|
+
importedAt: /* @__PURE__ */ new Date()
|
|
75
|
+
};
|
|
76
|
+
const groupFolder = resolveOperationGroupFolder(operation, contractspecOptions.conventions);
|
|
77
|
+
specs.push({
|
|
78
|
+
code,
|
|
79
|
+
fileName,
|
|
80
|
+
groupFolder: groupFolder || void 0,
|
|
81
|
+
source,
|
|
82
|
+
transportHints
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
errors.push({
|
|
86
|
+
sourceId: operation.operationId,
|
|
87
|
+
error: error instanceof Error ? error.message : String(error)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const [name, schema] of Object.entries(parseResult.schemas)) try {
|
|
92
|
+
const code = generateModelCode(name, schema, contractspecOptions);
|
|
93
|
+
const fileName = toFileName(toSpecName(name, importOptions.prefix));
|
|
94
|
+
const groupFolder = resolveModelGroupFolder(name, contractspecOptions.conventions);
|
|
95
|
+
specs.push({
|
|
96
|
+
code,
|
|
97
|
+
fileName,
|
|
98
|
+
groupFolder: groupFolder || void 0,
|
|
99
|
+
source: {
|
|
100
|
+
type: "openapi",
|
|
101
|
+
sourceId: name,
|
|
102
|
+
operationId: name,
|
|
103
|
+
openApiVersion: parseResult.version,
|
|
104
|
+
importedAt: /* @__PURE__ */ new Date()
|
|
105
|
+
},
|
|
106
|
+
transportHints: {}
|
|
107
|
+
});
|
|
108
|
+
} catch (error) {
|
|
109
|
+
errors.push({
|
|
110
|
+
sourceId: name,
|
|
111
|
+
error: error instanceof Error ? "Model conversion failed: " + error.message : String(error)
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
for (const event of parseResult.events) try {
|
|
115
|
+
const code = generateEventCode(event, contractspecOptions);
|
|
116
|
+
const fileName = toFileName(toSpecName(event.name, importOptions.prefix));
|
|
117
|
+
const groupFolder = resolveEventGroupFolder(event.name, contractspecOptions.conventions);
|
|
118
|
+
specs.push({
|
|
119
|
+
code,
|
|
120
|
+
fileName,
|
|
121
|
+
groupFolder: groupFolder || void 0,
|
|
122
|
+
source: {
|
|
123
|
+
type: "openapi",
|
|
124
|
+
sourceId: event.name,
|
|
125
|
+
operationId: event.name,
|
|
126
|
+
openApiVersion: parseResult.version,
|
|
127
|
+
importedAt: /* @__PURE__ */ new Date()
|
|
128
|
+
},
|
|
129
|
+
transportHints: {}
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
errors.push({
|
|
133
|
+
sourceId: event.name,
|
|
134
|
+
error: error instanceof Error ? "Event conversion failed: " + error.message : String(error)
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
operationSpecs: specs,
|
|
139
|
+
skipped,
|
|
140
|
+
errors,
|
|
141
|
+
summary: {
|
|
142
|
+
total: parseResult.operations.length + Object.keys(parseResult.schemas).length + parseResult.events.length,
|
|
143
|
+
imported: specs.length,
|
|
144
|
+
skipped: skipped.length,
|
|
145
|
+
errors: errors.length
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Import a single operation to ContractSpec code.
|
|
151
|
+
*/
|
|
152
|
+
function importOperation(operation, options = {}, contractspecOptions) {
|
|
153
|
+
const { schema: inputSchema } = buildInputSchema(operation);
|
|
154
|
+
const inputModel = inputSchema ? generateSchemaModelCode(inputSchema, `${operation.operationId}Input`) : null;
|
|
155
|
+
const outputSchema = getOutputSchema(operation);
|
|
156
|
+
return generateSpecCode(operation, contractspecOptions, options, inputModel, outputSchema ? generateSchemaModelCode(outputSchema, `${operation.operationId}Output`) : null);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
export { importFromOpenApi, importOperation };
|
|
161
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["specs: ImportedOperationSpec[]","skipped: ImportResult['skipped']","errors: ImportResult['errors']","transportHints: OpenApiTransportHints","source: OpenApiSource"],"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, toSpecName } from '../../common/utils';\nimport { generateSchemaModelCode } from '../schema-converter';\nimport { buildInputSchema, getOutputSchema } from './schemas';\nimport { generateSpecCode } from './generator';\nimport { generateModelCode } from './models';\nimport { generateEventCode } from './events';\nimport {\n resolveOperationGroupFolder,\n resolveModelGroupFolder,\n resolveEventGroupFolder,\n} from './grouping';\nimport type {\n ContractsrcConfig,\n OpenApiSourceConfig,\n} from '@lssm/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 schema\n const { schema: inputSchema } = buildInputSchema(operation);\n const inputModel = inputSchema\n ? generateSchemaModelCode(inputSchema, `${operation.operationId}Input`)\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 )\n : null;\n\n // Filter out empty/comment-only output models\n if (outputModel && !outputModel.code.includes('defineSchemaModel')) {\n outputModel = null;\n }\n\n // Generate spec code\n const code = generateSpecCode(\n operation,\n contractspecOptions,\n importOptions,\n inputModel,\n outputModel\n );\n const specName = toSpecName(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, contractspecOptions);\n const fileName = toFileName(toSpecName(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, contractspecOptions);\n const fileName = toFileName(toSpecName(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 const { schema: inputSchema } = buildInputSchema(operation);\n const inputModel = inputSchema\n ? generateSchemaModelCode(inputSchema, `${operation.operationId}Input`)\n : null;\n\n const outputSchema = getOutputSchema(operation);\n const outputModel = outputSchema\n ? generateSchemaModelCode(outputSchema, `${operation.operationId}Output`)\n : null;\n\n return generateSpecCode(\n operation,\n contractspecOptions,\n options,\n inputModel,\n outputModel\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
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)));
|
|
10
|
+
return `
|
|
11
|
+
${generateImports(model.fields, options)}
|
|
12
|
+
|
|
13
|
+
${model.code}
|
|
14
|
+
`.trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { generateModelCode };
|
|
19
|
+
//# sourceMappingURL=models.js.map
|