@lssm/lib.contracts-transformers 0.0.0-canary-20251220030446 → 0.0.0-canary-20251221114240
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/README.md +2 -2
- package/dist/common/index.d.ts +2 -2
- package/dist/common/types.d.ts +14 -10
- package/dist/common/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +3 -2
- package/dist/openapi/differ.d.ts +6 -6
- package/dist/openapi/differ.d.ts.map +1 -1
- package/dist/openapi/differ.js +11 -4
- package/dist/openapi/differ.js.map +1 -1
- package/dist/openapi/exporter.d.ts +8 -8
- package/dist/openapi/exporter.d.ts.map +1 -1
- package/dist/openapi/exporter.js +4 -4
- package/dist/openapi/exporter.js.map +1 -1
- 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/index.d.ts +17 -0
- package/dist/openapi/importer/index.d.ts.map +1 -0
- package/dist/openapi/importer/index.js +154 -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 +5 -4
- package/dist/openapi/index.js +4 -2
- 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 -232
- package/dist/openapi/schema-converter.d.ts +7 -1
- package/dist/openapi/schema-converter.d.ts.map +1 -1
- package/dist/openapi/schema-converter.js +117 -20
- package/dist/openapi/schema-converter.js.map +1 -1
- package/dist/openapi/types.d.ts +14 -20
- package/dist/openapi/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/openapi/importer.d.ts +0 -16
- package/dist/openapi/importer.d.ts.map +0 -1
- package/dist/openapi/importer.js +0 -265
- package/dist/openapi/importer.js.map +0 -1
- package/dist/openapi/parser.d.ts +0 -32
- package/dist/openapi/parser.d.ts.map +0 -1
- package/dist/openapi/parser.js.map +0 -1
package/README.md
CHANGED
|
@@ -18,9 +18,9 @@ bun add @lssm/lib.contracts-transformers
|
|
|
18
18
|
|
|
19
19
|
```typescript
|
|
20
20
|
import { openApiForRegistry } from '@lssm/lib.contracts-transformers/openapi';
|
|
21
|
-
import {
|
|
21
|
+
import { OperationSpecRegistry } from '@lssm/lib.contracts';
|
|
22
22
|
|
|
23
|
-
const registry = new
|
|
23
|
+
const registry = new OperationSpecRegistry();
|
|
24
24
|
// ... register your specs ...
|
|
25
25
|
|
|
26
26
|
const openApiDoc = openApiForRegistry(registry, {
|
package/dist/common/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { DiffChange, DiffChangeType, ImportResult,
|
|
1
|
+
import { DiffChange, DiffChangeType, ImportResult, ImportedOperationSpec, SpecDiff, SpecSource, SyncResult, TransportHints, ValidationResult } from "./types.js";
|
|
2
2
|
import { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecName, toValidIdentifier } from "./utils.js";
|
|
3
|
-
export { DiffChange, DiffChangeType, ImportResult,
|
|
3
|
+
export { DiffChange, DiffChangeType, ImportResult, ImportedOperationSpec, SpecDiff, SpecSource, SyncResult, TransportHints, ValidationResult, deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecName, toValidIdentifier };
|
package/dist/common/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnyOperationSpec } from "@lssm/lib.contracts";
|
|
2
2
|
|
|
3
3
|
//#region src/common/types.d.ts
|
|
4
4
|
|
|
@@ -40,9 +40,13 @@ interface TransportHints {
|
|
|
40
40
|
/**
|
|
41
41
|
* Result of importing a single spec from an external format.
|
|
42
42
|
*/
|
|
43
|
-
interface
|
|
44
|
-
/**
|
|
45
|
-
|
|
43
|
+
interface ImportedOperationSpec {
|
|
44
|
+
/**
|
|
45
|
+
* The generated ContractSpec.
|
|
46
|
+
* Optional because during code generation the actual spec object is not
|
|
47
|
+
* available until the generated code is executed at runtime.
|
|
48
|
+
*/
|
|
49
|
+
operationSpec?: AnyOperationSpec;
|
|
46
50
|
/** Generated TypeScript code for the spec */
|
|
47
51
|
code: string;
|
|
48
52
|
/** Suggested file name for the spec */
|
|
@@ -57,7 +61,7 @@ interface ImportedSpec {
|
|
|
57
61
|
*/
|
|
58
62
|
interface ImportResult {
|
|
59
63
|
/** Successfully imported specs */
|
|
60
|
-
|
|
64
|
+
operationSpecs: ImportedOperationSpec[];
|
|
61
65
|
/** Specs that were skipped (e.g., unsupported features) */
|
|
62
66
|
skipped: {
|
|
63
67
|
sourceId: string;
|
|
@@ -102,9 +106,9 @@ interface SpecDiff {
|
|
|
102
106
|
/** Identifier for the operation */
|
|
103
107
|
operationId: string;
|
|
104
108
|
/** Existing ContractSpec (if any) */
|
|
105
|
-
existing?:
|
|
109
|
+
existing?: AnyOperationSpec;
|
|
106
110
|
/** Incoming imported spec */
|
|
107
|
-
incoming:
|
|
111
|
+
incoming: ImportedOperationSpec;
|
|
108
112
|
/** List of detected changes */
|
|
109
113
|
changes: DiffChange[];
|
|
110
114
|
/** Whether specs are semantically equivalent */
|
|
@@ -117,10 +121,10 @@ interface SpecDiff {
|
|
|
117
121
|
*/
|
|
118
122
|
interface SyncResult {
|
|
119
123
|
/** Specs that were added (new imports) */
|
|
120
|
-
added:
|
|
124
|
+
added: ImportedOperationSpec[];
|
|
121
125
|
/** Specs that were updated */
|
|
122
126
|
updated: {
|
|
123
|
-
spec:
|
|
127
|
+
spec: ImportedOperationSpec;
|
|
124
128
|
changes: DiffChange[];
|
|
125
129
|
}[];
|
|
126
130
|
/** Specs that were kept unchanged */
|
|
@@ -149,5 +153,5 @@ interface ValidationResult {
|
|
|
149
153
|
warnings: string[];
|
|
150
154
|
}
|
|
151
155
|
//#endregion
|
|
152
|
-
export { DiffChange, DiffChangeType, ImportResult,
|
|
156
|
+
export { DiffChange, DiffChangeType, ImportResult, ImportedOperationSpec, SpecDiff, SpecSource, SyncResult, TransportHints, ValidationResult };
|
|
153
157
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/common/types.ts"],"sourcesContent":[],"mappings":";;;;AA0BA;AAoBA;;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/common/types.ts"],"sourcesContent":[],"mappings":";;;;AA0BA;AAoBA;;AAYU,UAjDO,UAAA,CAiDP;EAEQ;EAAc,IAAA,EAAA,SAAA,GAAA,UAAA,GAAA,SAAA,GAAA,UAAA;EAMf;EAyBL,GAAA,CAAA,EAAA,MAAA;EAUK;EAgBA,IAAA,CAAA,EAAA,MAAQ;EAIZ;EAED,QAAA,EAAA,MAAA;EAED;EAAU,UAAA,EA1GP,IA0GO;AAUrB;;;;;AAWqB,UAxHJ,cAAA,CAwHI;EAaJ,IAAA,CAAA,EAAA;;;;;;;;;;;;;;;;;;UAjHA,qBAAA;;;;;;kBAMC;;;;;;UAMR;;kBAEQ;;;;;UAMD,YAAA;;kBAEC;;;;;;;;;;;;;;;;;;;;;;KAuBN,cAAA;;;;UAUK,UAAA;;;;QAIT;;;;;;;;;;;UAYS,QAAA;;;;aAIJ;;YAED;;WAED;;;;;;;;;UAUM,UAAA;;SAER;;;UAGC;aACG;;;;;aAKA;;;;;;;;;;;;UAaI,gBAAA;;;;SAIR"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { DiffChange, DiffChangeType, ImportResult,
|
|
1
|
+
import { DiffChange, DiffChangeType, ImportResult, ImportedOperationSpec, SpecDiff, SpecSource, SyncResult, TransportHints, ValidationResult } from "./common/types.js";
|
|
2
2
|
import { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecName, toValidIdentifier } from "./common/utils.js";
|
|
3
|
-
import { ContractSpecOpenApiDocument, HttpMethod, OpenApiDocument, OpenApiExportOptions,
|
|
4
|
-
import { detectFormat, detectVersion,
|
|
3
|
+
import { ContractSpecOpenApiDocument, HttpMethod, OpenApiDocument, OpenApiExportOptions, OpenApiOperation, OpenApiParameter, OpenApiParseOptions, OpenApiSchema, OpenApiServer, OpenApiSource, OpenApiTransportHints, OpenApiVersion, ParameterLocation, ParseResult, ParsedOperation, ParsedParameter } from "./openapi/types.js";
|
|
4
|
+
import { detectFormat, detectVersion, parseOpenApiString } from "./openapi/parser/utils.js";
|
|
5
|
+
import { parseOpenApi, parseOpenApiDocument } from "./openapi/parser/document.js";
|
|
5
6
|
import { defaultRestPath, openApiForRegistry, openApiToJson, openApiToYaml } from "./openapi/exporter.js";
|
|
6
|
-
import { importFromOpenApi, importOperation } from "./openapi/importer.js";
|
|
7
7
|
import { GeneratedModel, SchemaField, TypescriptType, generateImports, generateSchemaModelCode, getScalarType, jsonSchemaToField, jsonSchemaToType } from "./openapi/schema-converter.js";
|
|
8
|
+
import { importFromOpenApi, importOperation } from "./openapi/importer/index.js";
|
|
8
9
|
import { DiffOptions, createSpecDiff, diffAll, diffSpecVsOperation, diffSpecs, formatDiffChanges } from "./openapi/differ.js";
|
|
9
|
-
export { ContractSpecOpenApiDocument, DiffChange, DiffChangeType, DiffOptions, GeneratedModel, HttpMethod, ImportResult,
|
|
10
|
+
export { ContractSpecOpenApiDocument, DiffChange, DiffChangeType, DiffOptions, GeneratedModel, HttpMethod, ImportResult, ImportedOperationSpec, OpenApiDocument, OpenApiExportOptions, OpenApiOperation, OpenApiParameter, OpenApiParseOptions, OpenApiSchema, OpenApiServer, OpenApiSource, OpenApiTransportHints, OpenApiVersion, ParameterLocation, ParseResult, ParsedOperation, ParsedParameter, SchemaField, SpecDiff, SpecSource, SyncResult, TransportHints, TypescriptType, ValidationResult, createSpecDiff, deepEqual, defaultRestPath, detectFormat, detectVersion, diffAll, diffSpecVsOperation, diffSpecs, extractPathParams, formatDiffChanges, generateImports, generateSchemaModelCode, getByPath, getScalarType, importFromOpenApi, importOperation, jsonSchemaToField, jsonSchemaToType, normalizePath, openApiForRegistry, openApiToJson, openApiToYaml, parseOpenApi, parseOpenApiDocument, parseOpenApiString, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecName, toValidIdentifier };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { detectFormat, detectVersion,
|
|
1
|
+
import { detectFormat, detectVersion, parseOpenApiString } from "./openapi/parser/utils.js";
|
|
2
|
+
import { parseOpenApi, parseOpenApiDocument } from "./openapi/parser/document.js";
|
|
2
3
|
import { defaultRestPath, openApiForRegistry, openApiToJson, openApiToYaml } from "./openapi/exporter.js";
|
|
3
4
|
import { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecName, toValidIdentifier } from "./common/utils.js";
|
|
4
5
|
import { generateImports, generateSchemaModelCode, getScalarType, jsonSchemaToField, jsonSchemaToType } from "./openapi/schema-converter.js";
|
|
5
|
-
import { importFromOpenApi, importOperation } from "./openapi/importer.js";
|
|
6
|
+
import { importFromOpenApi, importOperation } from "./openapi/importer/index.js";
|
|
6
7
|
import { createSpecDiff, diffAll, diffSpecVsOperation, diffSpecs, formatDiffChanges } from "./openapi/differ.js";
|
|
7
8
|
import "./openapi/index.js";
|
|
8
9
|
|
package/dist/openapi/differ.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { DiffChange,
|
|
1
|
+
import { DiffChange, ImportedOperationSpec, SpecDiff } from "../common/types.js";
|
|
2
2
|
import { ParsedOperation } from "./types.js";
|
|
3
|
-
import {
|
|
3
|
+
import { AnyOperationSpec } from "@lssm/lib.contracts";
|
|
4
4
|
|
|
5
5
|
//#region src/openapi/differ.d.ts
|
|
6
6
|
|
|
@@ -20,19 +20,19 @@ interface DiffOptions {
|
|
|
20
20
|
/**
|
|
21
21
|
* Diff a ContractSpec against an OpenAPI operation.
|
|
22
22
|
*/
|
|
23
|
-
declare function diffSpecVsOperation(spec:
|
|
23
|
+
declare function diffSpecVsOperation(spec: AnyOperationSpec, operation: ParsedOperation, options?: DiffOptions): DiffChange[];
|
|
24
24
|
/**
|
|
25
25
|
* Diff two ContractSpecs.
|
|
26
26
|
*/
|
|
27
|
-
declare function diffSpecs(oldSpec:
|
|
27
|
+
declare function diffSpecs(oldSpec: AnyOperationSpec, newSpec: AnyOperationSpec, options?: DiffOptions): DiffChange[];
|
|
28
28
|
/**
|
|
29
29
|
* Create a SpecDiff from an existing spec and an imported spec.
|
|
30
30
|
*/
|
|
31
|
-
declare function createSpecDiff(operationId: string, existing:
|
|
31
|
+
declare function createSpecDiff(operationId: string, existing: AnyOperationSpec | undefined, incoming: ImportedOperationSpec, options?: DiffOptions): SpecDiff;
|
|
32
32
|
/**
|
|
33
33
|
* Batch diff multiple specs against OpenAPI operations.
|
|
34
34
|
*/
|
|
35
|
-
declare function diffAll(existingSpecs: Map<string,
|
|
35
|
+
declare function diffAll(existingSpecs: Map<string, AnyOperationSpec>, importedSpecs: ImportedOperationSpec[], options?: DiffOptions): SpecDiff[];
|
|
36
36
|
/**
|
|
37
37
|
* Format diff changes for display.
|
|
38
38
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"differ.d.ts","names":[],"sources":["../../src/openapi/differ.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAwIG,UAtHc,WAAA,CAsHd;EAAU;EA2EG,kBAAS,CAAA,EAAA,OAAA;EACd;EACA,UAAA,CAAA,EAAA,OAAA;EACA;EACR,eAAA,CAAA,EAAA,OAAA;EAAU;EA+CG,WAAA,CAAA,EAAA,MAAc,EAAA;;;;;AAKnB,iBAvIK,mBAAA,CAuIL,IAAA,EAtIH,
|
|
1
|
+
{"version":3,"file":"differ.d.ts","names":[],"sources":["../../src/openapi/differ.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAwIG,UAtHc,WAAA,CAsHd;EAAU;EA2EG,kBAAS,CAAA,EAAA,OAAA;EACd;EACA,UAAA,CAAA,EAAA,OAAA;EACA;EACR,eAAA,CAAA,EAAA,OAAA;EAAU;EA+CG,WAAA,CAAA,EAAA,MAAc,EAAA;;;;;AAKnB,iBAvIK,mBAAA,CAuIL,IAAA,EAtIH,gBAsIG,EAAA,SAAA,EArIE,eAqIF,EAAA,OAAA,CAAA,EApIA,WAoIA,CAAA,EAnIR,UAmIQ,EAAA;AA4CX;;;AAEiB,iBAtGD,SAAA,CAsGC,OAAA,EArGN,gBAqGM,EAAA,OAAA,EApGN,gBAoGM,EAAA,OAAA,CAAA,EAnGN,WAmGM,CAAA,EAlGd,UAkGc,EAAA;;;;AAsDD,iBAzGA,cAAA,CAyG2B,WAAU,EAAA,MAAA,EAAA,QAAA,EAvGzC,gBAuGyC,GAAA,SAAA,EAAA,QAAA,EAtGzC,qBAsGyC,EAAA,OAAA,CAAA,EArG1C,WAqG0C,CAAA,EApGlD,QAoGkD;;;;iBAxDrC,OAAA,gBACC,YAAY,kCACZ,mCACN,cACR;;;;iBAoDa,iBAAA,UAA2B"}
|
package/dist/openapi/differ.js
CHANGED
|
@@ -136,13 +136,20 @@ function diffSpecs(oldSpec, newSpec, options = {}) {
|
|
|
136
136
|
function createSpecDiff(operationId, existing, incoming, options = {}) {
|
|
137
137
|
let changes = [];
|
|
138
138
|
let isEquivalent = false;
|
|
139
|
-
if (existing) {
|
|
140
|
-
changes = diffSpecs(existing, incoming.
|
|
139
|
+
if (existing && incoming.operationSpec) {
|
|
140
|
+
changes = diffSpecs(existing, incoming.operationSpec, options);
|
|
141
141
|
isEquivalent = changes.length === 0;
|
|
142
|
-
} else changes = [{
|
|
142
|
+
} else if (existing && !incoming.operationSpec) changes = [{
|
|
143
|
+
path: "",
|
|
144
|
+
type: "modified",
|
|
145
|
+
oldValue: existing,
|
|
146
|
+
newValue: incoming.code,
|
|
147
|
+
description: "Spec code imported from OpenAPI (runtime comparison not available)"
|
|
148
|
+
}];
|
|
149
|
+
else changes = [{
|
|
143
150
|
path: "",
|
|
144
151
|
type: "added",
|
|
145
|
-
newValue: incoming.
|
|
152
|
+
newValue: incoming.operationSpec ?? incoming.code,
|
|
146
153
|
description: "New spec imported from OpenAPI"
|
|
147
154
|
}];
|
|
148
155
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"differ.js","names":["changeType: DiffChangeType","changes: DiffChange[]","diffs: SpecDiff[]","existing: AnyContractSpec | undefined","lines: string[]"],"sources":["../../src/openapi/differ.ts"],"sourcesContent":["/**\n * Diff ContractSpec specs against OpenAPI operations.\n * Used for sync operations to detect changes.\n */\n\nimport type { AnyContractSpec } from '@lssm/lib.contracts';\nimport type { ParsedOperation } from './types';\nimport type {\n SpecDiff,\n DiffChange,\n DiffChangeType,\n ImportedSpec,\n} from '../common/types';\nimport { deepEqual } from '../common/utils';\n\n/**\n * Options for diffing specs.\n */\nexport interface DiffOptions {\n /** Ignore description changes */\n ignoreDescriptions?: boolean;\n /** Ignore tag changes */\n ignoreTags?: boolean;\n /** Ignore transport changes (path, method) */\n ignoreTransport?: boolean;\n /** Custom paths to ignore */\n ignorePaths?: string[];\n}\n\n/**\n * Compare two values and generate a diff change if different.\n */\nfunction compareValues(\n path: string,\n oldValue: unknown,\n newValue: unknown,\n description: string\n): DiffChange | null {\n if (deepEqual(oldValue, newValue)) {\n return null;\n }\n\n let changeType: DiffChangeType = 'modified';\n if (oldValue === undefined || oldValue === null) {\n changeType = 'added';\n } else if (newValue === undefined || newValue === null) {\n changeType = 'removed';\n } else if (typeof oldValue !== typeof newValue) {\n changeType = 'type_changed';\n }\n\n return {\n path,\n type: changeType,\n oldValue,\n newValue,\n description,\n };\n}\n\n/**\n * Diff two objects recursively.\n */\nfunction diffObjects(\n path: string,\n oldObj: Record<string, unknown> | undefined,\n newObj: Record<string, unknown> | undefined,\n options: DiffOptions\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n if (!oldObj && !newObj) return changes;\n if (!oldObj) {\n changes.push({\n path,\n type: 'added',\n newValue: newObj,\n description: `Added ${path}`,\n });\n return changes;\n }\n if (!newObj) {\n changes.push({\n path,\n type: 'removed',\n oldValue: oldObj,\n description: `Removed ${path}`,\n });\n return changes;\n }\n\n const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);\n\n for (const key of allKeys) {\n const keyPath = path ? `${path}.${key}` : key;\n\n // Skip ignored paths\n if (options.ignorePaths?.some((p) => keyPath.startsWith(p))) {\n continue;\n }\n\n const oldVal = oldObj[key];\n const newVal = newObj[key];\n\n if (typeof oldVal === 'object' && typeof newVal === 'object') {\n changes.push(\n ...diffObjects(\n keyPath,\n oldVal as Record<string, unknown>,\n newVal as Record<string, unknown>,\n options\n )\n );\n } else {\n const change = compareValues(\n keyPath,\n oldVal,\n newVal,\n `Changed ${keyPath}`\n );\n if (change) {\n changes.push(change);\n }\n }\n }\n\n return changes;\n}\n\n/**\n * Diff a ContractSpec against an OpenAPI operation.\n */\nexport function diffSpecVsOperation(\n spec: AnyContractSpec,\n operation: ParsedOperation,\n options: DiffOptions = {}\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n // Compare basic metadata\n if (!options.ignoreDescriptions) {\n const descChange = compareValues(\n 'meta.description',\n spec.meta.description,\n operation.summary ?? operation.description,\n 'Description changed'\n );\n if (descChange) changes.push(descChange);\n }\n\n if (!options.ignoreTags) {\n const oldTags = [...(spec.meta.tags ?? [])].sort();\n const newTags = [...operation.tags].sort();\n if (!deepEqual(oldTags, newTags)) {\n changes.push({\n path: 'meta.tags',\n type: 'modified',\n oldValue: oldTags,\n newValue: newTags,\n description: 'Tags changed',\n });\n }\n }\n\n // Compare transport\n if (!options.ignoreTransport) {\n const specMethod =\n spec.transport?.rest?.method ??\n (spec.meta.kind === 'query' ? 'GET' : 'POST');\n const opMethod = operation.method.toUpperCase();\n\n if (specMethod !== opMethod) {\n changes.push({\n path: 'transport.rest.method',\n type: 'modified',\n oldValue: specMethod,\n newValue: opMethod,\n description: 'HTTP method changed',\n });\n }\n\n const specPath = spec.transport?.rest?.path;\n if (specPath && specPath !== operation.path) {\n changes.push({\n path: 'transport.rest.path',\n type: 'modified',\n oldValue: specPath,\n newValue: operation.path,\n description: 'Path changed',\n });\n }\n }\n\n // Compare deprecation status\n const specDeprecated = spec.meta.stability === 'deprecated';\n if (specDeprecated !== operation.deprecated) {\n changes.push({\n path: 'meta.stability',\n type: 'modified',\n oldValue: spec.meta.stability,\n newValue: operation.deprecated ? 'deprecated' : 'stable',\n description: 'Deprecation status changed',\n });\n }\n\n return changes;\n}\n\n/**\n * Diff two ContractSpecs.\n */\nexport function diffSpecs(\n oldSpec: AnyContractSpec,\n newSpec: AnyContractSpec,\n options: DiffOptions = {}\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n // Compare meta\n const metaChanges = diffObjects(\n 'meta',\n oldSpec.meta as unknown as Record<string, unknown>,\n newSpec.meta as unknown as Record<string, unknown>,\n {\n ...options,\n ignorePaths: [\n ...(options.ignorePaths ?? []),\n ...(options.ignoreDescriptions\n ? ['meta.description', 'meta.goal', 'meta.context']\n : []),\n ...(options.ignoreTags ? ['meta.tags'] : []),\n ],\n }\n );\n changes.push(...metaChanges);\n\n // Compare transport\n if (!options.ignoreTransport) {\n const transportChanges = diffObjects(\n 'transport',\n oldSpec.transport as unknown as Record<string, unknown>,\n newSpec.transport as unknown as Record<string, unknown>,\n options\n );\n changes.push(...transportChanges);\n }\n\n // Compare policy\n const policyChanges = diffObjects(\n 'policy',\n oldSpec.policy as unknown as Record<string, unknown>,\n newSpec.policy as unknown as Record<string, unknown>,\n options\n );\n changes.push(...policyChanges);\n\n return changes;\n}\n\n/**\n * Create a SpecDiff from an existing spec and an imported spec.\n */\nexport function createSpecDiff(\n operationId: string,\n existing: AnyContractSpec | undefined,\n incoming: ImportedSpec,\n options: DiffOptions = {}\n): SpecDiff {\n let changes: DiffChange[] = [];\n let isEquivalent = false;\n\n if (existing) {\n // Compare existing vs incoming\n changes = diffSpecs(existing, incoming.spec, options);\n isEquivalent = changes.length === 0;\n } else {\n // New spec - mark as added\n changes = [\n {\n path: '',\n type: 'added',\n newValue: incoming.spec,\n description: 'New spec imported from OpenAPI',\n },\n ];\n }\n\n return {\n operationId,\n existing,\n incoming,\n changes,\n isEquivalent,\n };\n}\n\n/**\n * Batch diff multiple specs against OpenAPI operations.\n */\nexport function diffAll(\n existingSpecs: Map<string, AnyContractSpec>,\n importedSpecs: ImportedSpec[],\n options: DiffOptions = {}\n): SpecDiff[] {\n const diffs: SpecDiff[] = [];\n\n // Track which existing specs have been matched\n const matchedExisting = new Set<string>();\n\n for (const imported of importedSpecs) {\n const operationId = imported.source.sourceId;\n\n // Try to find matching existing spec\n // Match by operationId in x-contractspec extension or by name\n let existing: AnyContractSpec | undefined;\n\n for (const [key, spec] of existingSpecs) {\n // Check x-contractspec match or name match\n const specName = spec.meta.name;\n if (key === operationId || specName.includes(operationId)) {\n existing = spec;\n matchedExisting.add(key);\n break;\n }\n }\n\n diffs.push(createSpecDiff(operationId, existing, imported, options));\n }\n\n // Add diffs for existing specs that weren't matched (removed from OpenAPI)\n for (const [key, spec] of existingSpecs) {\n if (!matchedExisting.has(key)) {\n diffs.push({\n operationId: key,\n existing: spec,\n incoming: undefined as unknown as ImportedSpec,\n changes: [\n {\n path: '',\n type: 'removed',\n oldValue: spec,\n description: 'Spec no longer exists in OpenAPI source',\n },\n ],\n isEquivalent: false,\n });\n }\n }\n\n return diffs;\n}\n\n/**\n * Format diff changes for display.\n */\nexport function formatDiffChanges(changes: DiffChange[]): string {\n if (changes.length === 0) {\n return 'No changes detected';\n }\n\n const lines: string[] = [];\n\n for (const change of changes) {\n const prefix = {\n added: '+',\n removed: '-',\n modified: '~',\n type_changed: '!',\n required_changed: '?',\n }[change.type];\n\n lines.push(`${prefix} ${change.path}: ${change.description}`);\n\n if (change.type === 'modified' || change.type === 'type_changed') {\n lines.push(` old: ${JSON.stringify(change.oldValue)}`);\n lines.push(` new: ${JSON.stringify(change.newValue)}`);\n } else if (change.type === 'added') {\n lines.push(` value: ${JSON.stringify(change.newValue)}`);\n } else if (change.type === 'removed') {\n lines.push(` was: ${JSON.stringify(change.oldValue)}`);\n }\n }\n\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;AAgCA,SAAS,cACP,MACA,UACA,UACA,aACmB;AACnB,KAAI,UAAU,UAAU,SAAS,CAC/B,QAAO;CAGT,IAAIA,aAA6B;AACjC,KAAI,aAAa,UAAa,aAAa,KACzC,cAAa;UACJ,aAAa,UAAa,aAAa,KAChD,cAAa;UACJ,OAAO,aAAa,OAAO,SACpC,cAAa;AAGf,QAAO;EACL;EACA,MAAM;EACN;EACA;EACA;EACD;;;;;AAMH,SAAS,YACP,MACA,QACA,QACA,SACc;CACd,MAAMC,UAAwB,EAAE;AAEhC,KAAI,CAAC,UAAU,CAAC,OAAQ,QAAO;AAC/B,KAAI,CAAC,QAAQ;AACX,UAAQ,KAAK;GACX;GACA,MAAM;GACN,UAAU;GACV,aAAa,SAAS;GACvB,CAAC;AACF,SAAO;;AAET,KAAI,CAAC,QAAQ;AACX,UAAQ,KAAK;GACX;GACA,MAAM;GACN,UAAU;GACV,aAAa,WAAW;GACzB,CAAC;AACF,SAAO;;CAGT,MAAM,UAAU,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,OAAO,EAAE,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC;AAEzE,MAAK,MAAM,OAAO,SAAS;EACzB,MAAM,UAAU,OAAO,GAAG,KAAK,GAAG,QAAQ;AAG1C,MAAI,QAAQ,aAAa,MAAM,MAAM,QAAQ,WAAW,EAAE,CAAC,CACzD;EAGF,MAAM,SAAS,OAAO;EACtB,MAAM,SAAS,OAAO;AAEtB,MAAI,OAAO,WAAW,YAAY,OAAO,WAAW,SAClD,SAAQ,KACN,GAAG,YACD,SACA,QACA,QACA,QACD,CACF;OACI;GACL,MAAM,SAAS,cACb,SACA,QACA,QACA,WAAW,UACZ;AACD,OAAI,OACF,SAAQ,KAAK,OAAO;;;AAK1B,QAAO;;;;;AAMT,SAAgB,oBACd,MACA,WACA,UAAuB,EAAE,EACX;CACd,MAAMA,UAAwB,EAAE;AAGhC,KAAI,CAAC,QAAQ,oBAAoB;EAC/B,MAAM,aAAa,cACjB,oBACA,KAAK,KAAK,aACV,UAAU,WAAW,UAAU,aAC/B,sBACD;AACD,MAAI,WAAY,SAAQ,KAAK,WAAW;;AAG1C,KAAI,CAAC,QAAQ,YAAY;EACvB,MAAM,UAAU,CAAC,GAAI,KAAK,KAAK,QAAQ,EAAE,CAAE,CAAC,MAAM;EAClD,MAAM,UAAU,CAAC,GAAG,UAAU,KAAK,CAAC,MAAM;AAC1C,MAAI,CAAC,UAAU,SAAS,QAAQ,CAC9B,SAAQ,KAAK;GACX,MAAM;GACN,MAAM;GACN,UAAU;GACV,UAAU;GACV,aAAa;GACd,CAAC;;AAKN,KAAI,CAAC,QAAQ,iBAAiB;EAC5B,MAAM,aACJ,KAAK,WAAW,MAAM,WACrB,KAAK,KAAK,SAAS,UAAU,QAAQ;EACxC,MAAM,WAAW,UAAU,OAAO,aAAa;AAE/C,MAAI,eAAe,SACjB,SAAQ,KAAK;GACX,MAAM;GACN,MAAM;GACN,UAAU;GACV,UAAU;GACV,aAAa;GACd,CAAC;EAGJ,MAAM,WAAW,KAAK,WAAW,MAAM;AACvC,MAAI,YAAY,aAAa,UAAU,KACrC,SAAQ,KAAK;GACX,MAAM;GACN,MAAM;GACN,UAAU;GACV,UAAU,UAAU;GACpB,aAAa;GACd,CAAC;;AAMN,KADuB,KAAK,KAAK,cAAc,iBACxB,UAAU,WAC/B,SAAQ,KAAK;EACX,MAAM;EACN,MAAM;EACN,UAAU,KAAK,KAAK;EACpB,UAAU,UAAU,aAAa,eAAe;EAChD,aAAa;EACd,CAAC;AAGJ,QAAO;;;;;AAMT,SAAgB,UACd,SACA,SACA,UAAuB,EAAE,EACX;CACd,MAAMA,UAAwB,EAAE;CAGhC,MAAM,cAAc,YAClB,QACA,QAAQ,MACR,QAAQ,MACR;EACE,GAAG;EACH,aAAa;GACX,GAAI,QAAQ,eAAe,EAAE;GAC7B,GAAI,QAAQ,qBACR;IAAC;IAAoB;IAAa;IAAe,GACjD,EAAE;GACN,GAAI,QAAQ,aAAa,CAAC,YAAY,GAAG,EAAE;GAC5C;EACF,CACF;AACD,SAAQ,KAAK,GAAG,YAAY;AAG5B,KAAI,CAAC,QAAQ,iBAAiB;EAC5B,MAAM,mBAAmB,YACvB,aACA,QAAQ,WACR,QAAQ,WACR,QACD;AACD,UAAQ,KAAK,GAAG,iBAAiB;;CAInC,MAAM,gBAAgB,YACpB,UACA,QAAQ,QACR,QAAQ,QACR,QACD;AACD,SAAQ,KAAK,GAAG,cAAc;AAE9B,QAAO;;;;;AAMT,SAAgB,eACd,aACA,UACA,UACA,UAAuB,EAAE,EACf;CACV,IAAIA,UAAwB,EAAE;CAC9B,IAAI,eAAe;AAEnB,KAAI,UAAU;AAEZ,YAAU,UAAU,UAAU,SAAS,MAAM,QAAQ;AACrD,iBAAe,QAAQ,WAAW;OAGlC,WAAU,CACR;EACE,MAAM;EACN,MAAM;EACN,UAAU,SAAS;EACnB,aAAa;EACd,CACF;AAGH,QAAO;EACL;EACA;EACA;EACA;EACA;EACD;;;;;AAMH,SAAgB,QACd,eACA,eACA,UAAuB,EAAE,EACb;CACZ,MAAMC,QAAoB,EAAE;CAG5B,MAAM,kCAAkB,IAAI,KAAa;AAEzC,MAAK,MAAM,YAAY,eAAe;EACpC,MAAM,cAAc,SAAS,OAAO;EAIpC,IAAIC;AAEJ,OAAK,MAAM,CAAC,KAAK,SAAS,eAAe;GAEvC,MAAM,WAAW,KAAK,KAAK;AAC3B,OAAI,QAAQ,eAAe,SAAS,SAAS,YAAY,EAAE;AACzD,eAAW;AACX,oBAAgB,IAAI,IAAI;AACxB;;;AAIJ,QAAM,KAAK,eAAe,aAAa,UAAU,UAAU,QAAQ,CAAC;;AAItE,MAAK,MAAM,CAAC,KAAK,SAAS,cACxB,KAAI,CAAC,gBAAgB,IAAI,IAAI,CAC3B,OAAM,KAAK;EACT,aAAa;EACb,UAAU;EACV,UAAU;EACV,SAAS,CACP;GACE,MAAM;GACN,MAAM;GACN,UAAU;GACV,aAAa;GACd,CACF;EACD,cAAc;EACf,CAAC;AAIN,QAAO;;;;;AAMT,SAAgB,kBAAkB,SAA+B;AAC/D,KAAI,QAAQ,WAAW,EACrB,QAAO;CAGT,MAAMC,QAAkB,EAAE;AAE1B,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,SAAS;GACb,OAAO;GACP,SAAS;GACT,UAAU;GACV,cAAc;GACd,kBAAkB;GACnB,CAAC,OAAO;AAET,QAAM,KAAK,GAAG,OAAO,GAAG,OAAO,KAAK,IAAI,OAAO,cAAc;AAE7D,MAAI,OAAO,SAAS,cAAc,OAAO,SAAS,gBAAgB;AAChE,SAAM,KAAK,YAAY,KAAK,UAAU,OAAO,SAAS,GAAG;AACzD,SAAM,KAAK,YAAY,KAAK,UAAU,OAAO,SAAS,GAAG;aAChD,OAAO,SAAS,QACzB,OAAM,KAAK,cAAc,KAAK,UAAU,OAAO,SAAS,GAAG;WAClD,OAAO,SAAS,UACzB,OAAM,KAAK,YAAY,KAAK,UAAU,OAAO,SAAS,GAAG;;AAI7D,QAAO,MAAM,KAAK,KAAK"}
|
|
1
|
+
{"version":3,"file":"differ.js","names":["changeType: DiffChangeType","changes: DiffChange[]","diffs: SpecDiff[]","existing: AnyOperationSpec | undefined","lines: string[]"],"sources":["../../src/openapi/differ.ts"],"sourcesContent":["/**\n * Diff ContractSpec specs against OpenAPI operations.\n * Used for sync operations to detect changes.\n */\n\nimport type { AnyOperationSpec } from '@lssm/lib.contracts';\nimport type { ParsedOperation } from './types';\nimport type {\n DiffChange,\n DiffChangeType,\n ImportedOperationSpec,\n SpecDiff,\n} from '../common/types';\nimport { deepEqual } from '../common/utils';\n\n/**\n * Options for diffing specs.\n */\nexport interface DiffOptions {\n /** Ignore description changes */\n ignoreDescriptions?: boolean;\n /** Ignore tag changes */\n ignoreTags?: boolean;\n /** Ignore transport changes (path, method) */\n ignoreTransport?: boolean;\n /** Custom paths to ignore */\n ignorePaths?: string[];\n}\n\n/**\n * Compare two values and generate a diff change if different.\n */\nfunction compareValues(\n path: string,\n oldValue: unknown,\n newValue: unknown,\n description: string\n): DiffChange | null {\n if (deepEqual(oldValue, newValue)) {\n return null;\n }\n\n let changeType: DiffChangeType = 'modified';\n if (oldValue === undefined || oldValue === null) {\n changeType = 'added';\n } else if (newValue === undefined || newValue === null) {\n changeType = 'removed';\n } else if (typeof oldValue !== typeof newValue) {\n changeType = 'type_changed';\n }\n\n return {\n path,\n type: changeType,\n oldValue,\n newValue,\n description,\n };\n}\n\n/**\n * Diff two objects recursively.\n */\nfunction diffObjects(\n path: string,\n oldObj: Record<string, unknown> | undefined,\n newObj: Record<string, unknown> | undefined,\n options: DiffOptions\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n if (!oldObj && !newObj) return changes;\n if (!oldObj) {\n changes.push({\n path,\n type: 'added',\n newValue: newObj,\n description: `Added ${path}`,\n });\n return changes;\n }\n if (!newObj) {\n changes.push({\n path,\n type: 'removed',\n oldValue: oldObj,\n description: `Removed ${path}`,\n });\n return changes;\n }\n\n const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);\n\n for (const key of allKeys) {\n const keyPath = path ? `${path}.${key}` : key;\n\n // Skip ignored paths\n if (options.ignorePaths?.some((p) => keyPath.startsWith(p))) {\n continue;\n }\n\n const oldVal = oldObj[key];\n const newVal = newObj[key];\n\n if (typeof oldVal === 'object' && typeof newVal === 'object') {\n changes.push(\n ...diffObjects(\n keyPath,\n oldVal as Record<string, unknown>,\n newVal as Record<string, unknown>,\n options\n )\n );\n } else {\n const change = compareValues(\n keyPath,\n oldVal,\n newVal,\n `Changed ${keyPath}`\n );\n if (change) {\n changes.push(change);\n }\n }\n }\n\n return changes;\n}\n\n/**\n * Diff a ContractSpec against an OpenAPI operation.\n */\nexport function diffSpecVsOperation(\n spec: AnyOperationSpec,\n operation: ParsedOperation,\n options: DiffOptions = {}\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n // Compare basic metadata\n if (!options.ignoreDescriptions) {\n const descChange = compareValues(\n 'meta.description',\n spec.meta.description,\n operation.summary ?? operation.description,\n 'Description changed'\n );\n if (descChange) changes.push(descChange);\n }\n\n if (!options.ignoreTags) {\n const oldTags = [...(spec.meta.tags ?? [])].sort();\n const newTags = [...operation.tags].sort();\n if (!deepEqual(oldTags, newTags)) {\n changes.push({\n path: 'meta.tags',\n type: 'modified',\n oldValue: oldTags,\n newValue: newTags,\n description: 'Tags changed',\n });\n }\n }\n\n // Compare transport\n if (!options.ignoreTransport) {\n const specMethod =\n spec.transport?.rest?.method ??\n (spec.meta.kind === 'query' ? 'GET' : 'POST');\n const opMethod = operation.method.toUpperCase();\n\n if (specMethod !== opMethod) {\n changes.push({\n path: 'transport.rest.method',\n type: 'modified',\n oldValue: specMethod,\n newValue: opMethod,\n description: 'HTTP method changed',\n });\n }\n\n const specPath = spec.transport?.rest?.path;\n if (specPath && specPath !== operation.path) {\n changes.push({\n path: 'transport.rest.path',\n type: 'modified',\n oldValue: specPath,\n newValue: operation.path,\n description: 'Path changed',\n });\n }\n }\n\n // Compare deprecation status\n const specDeprecated = spec.meta.stability === 'deprecated';\n if (specDeprecated !== operation.deprecated) {\n changes.push({\n path: 'meta.stability',\n type: 'modified',\n oldValue: spec.meta.stability,\n newValue: operation.deprecated ? 'deprecated' : 'stable',\n description: 'Deprecation status changed',\n });\n }\n\n return changes;\n}\n\n/**\n * Diff two ContractSpecs.\n */\nexport function diffSpecs(\n oldSpec: AnyOperationSpec,\n newSpec: AnyOperationSpec,\n options: DiffOptions = {}\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n // Compare meta\n const metaChanges = diffObjects(\n 'meta',\n oldSpec.meta as unknown as Record<string, unknown>,\n newSpec.meta as unknown as Record<string, unknown>,\n {\n ...options,\n ignorePaths: [\n ...(options.ignorePaths ?? []),\n ...(options.ignoreDescriptions\n ? ['meta.description', 'meta.goal', 'meta.context']\n : []),\n ...(options.ignoreTags ? ['meta.tags'] : []),\n ],\n }\n );\n changes.push(...metaChanges);\n\n // Compare transport\n if (!options.ignoreTransport) {\n const transportChanges = diffObjects(\n 'transport',\n oldSpec.transport as unknown as Record<string, unknown>,\n newSpec.transport as unknown as Record<string, unknown>,\n options\n );\n changes.push(...transportChanges);\n }\n\n // Compare policy\n const policyChanges = diffObjects(\n 'policy',\n oldSpec.policy as unknown as Record<string, unknown>,\n newSpec.policy as unknown as Record<string, unknown>,\n options\n );\n changes.push(...policyChanges);\n\n return changes;\n}\n\n/**\n * Create a SpecDiff from an existing spec and an imported spec.\n */\nexport function createSpecDiff(\n operationId: string,\n existing: AnyOperationSpec | undefined,\n incoming: ImportedOperationSpec,\n options: DiffOptions = {}\n): SpecDiff {\n let changes: DiffChange[] = [];\n let isEquivalent = false;\n\n if (existing && incoming.operationSpec) {\n // Compare existing vs incoming\n changes = diffSpecs(existing, incoming.operationSpec, options);\n isEquivalent = changes.length === 0;\n } else if (existing && !incoming.operationSpec) {\n // Incoming has code but no runtime spec - can't compare directly\n changes = [\n {\n path: '',\n type: 'modified',\n oldValue: existing,\n newValue: incoming.code,\n description:\n 'Spec code imported from OpenAPI (runtime comparison not available)',\n },\n ];\n } else {\n // New spec - mark as added\n changes = [\n {\n path: '',\n type: 'added',\n newValue: incoming.operationSpec ?? incoming.code,\n description: 'New spec imported from OpenAPI',\n },\n ];\n }\n\n return {\n operationId,\n existing,\n incoming,\n changes,\n isEquivalent,\n };\n}\n\n/**\n * Batch diff multiple specs against OpenAPI operations.\n */\nexport function diffAll(\n existingSpecs: Map<string, AnyOperationSpec>,\n importedSpecs: ImportedOperationSpec[],\n options: DiffOptions = {}\n): SpecDiff[] {\n const diffs: SpecDiff[] = [];\n\n // Track which existing specs have been matched\n const matchedExisting = new Set<string>();\n\n for (const imported of importedSpecs) {\n const operationId = imported.source.sourceId;\n\n // Try to find matching existing spec\n // Match by operationId in x-contractspec extension or by name\n let existing: AnyOperationSpec | undefined;\n\n for (const [key, spec] of existingSpecs) {\n // Check x-contractspec match or name match\n const specName = spec.meta.name;\n if (key === operationId || specName.includes(operationId)) {\n existing = spec;\n matchedExisting.add(key);\n break;\n }\n }\n\n diffs.push(createSpecDiff(operationId, existing, imported, options));\n }\n\n // Add diffs for existing specs that weren't matched (removed from OpenAPI)\n for (const [key, spec] of existingSpecs) {\n if (!matchedExisting.has(key)) {\n diffs.push({\n operationId: key,\n existing: spec,\n incoming: undefined as unknown as ImportedOperationSpec,\n changes: [\n {\n path: '',\n type: 'removed',\n oldValue: spec,\n description: 'Spec no longer exists in OpenAPI source',\n },\n ],\n isEquivalent: false,\n });\n }\n }\n\n return diffs;\n}\n\n/**\n * Format diff changes for display.\n */\nexport function formatDiffChanges(changes: DiffChange[]): string {\n if (changes.length === 0) {\n return 'No changes detected';\n }\n\n const lines: string[] = [];\n\n for (const change of changes) {\n const prefix = {\n added: '+',\n removed: '-',\n modified: '~',\n type_changed: '!',\n required_changed: '?',\n }[change.type];\n\n lines.push(`${prefix} ${change.path}: ${change.description}`);\n\n if (change.type === 'modified' || change.type === 'type_changed') {\n lines.push(` old: ${JSON.stringify(change.oldValue)}`);\n lines.push(` new: ${JSON.stringify(change.newValue)}`);\n } else if (change.type === 'added') {\n lines.push(` value: ${JSON.stringify(change.newValue)}`);\n } else if (change.type === 'removed') {\n lines.push(` was: ${JSON.stringify(change.oldValue)}`);\n }\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContractSpecOpenApiDocument, OpenApiExportOptions, OpenApiServer } from "./types.js";
|
|
2
|
-
import {
|
|
2
|
+
import { OperationSpecRegistry } from "@lssm/lib.contracts";
|
|
3
3
|
|
|
4
4
|
//#region src/openapi/exporter.d.ts
|
|
5
5
|
|
|
@@ -8,21 +8,21 @@ import { SpecRegistry } from "@lssm/lib.contracts";
|
|
|
8
8
|
*/
|
|
9
9
|
declare function defaultRestPath(name: string, version: number): string;
|
|
10
10
|
/**
|
|
11
|
-
* Export a
|
|
11
|
+
* Export a OperationSpecRegistry to an OpenAPI 3.1 document.
|
|
12
12
|
*
|
|
13
|
-
* @param registry - The
|
|
13
|
+
* @param registry - The OperationSpecRegistry containing specs to export
|
|
14
14
|
* @param options - Export options (title, version, description, servers)
|
|
15
15
|
* @returns OpenAPI 3.1 document
|
|
16
16
|
*/
|
|
17
|
-
declare function openApiForRegistry(registry:
|
|
17
|
+
declare function openApiForRegistry(registry: OperationSpecRegistry, options?: OpenApiExportOptions): ContractSpecOpenApiDocument;
|
|
18
18
|
/**
|
|
19
|
-
* Export a
|
|
19
|
+
* Export a OperationSpecRegistry to OpenAPI JSON string.
|
|
20
20
|
*/
|
|
21
|
-
declare function openApiToJson(registry:
|
|
21
|
+
declare function openApiToJson(registry: OperationSpecRegistry, options?: OpenApiExportOptions): string;
|
|
22
22
|
/**
|
|
23
|
-
* Export a
|
|
23
|
+
* Export a OperationSpecRegistry to OpenAPI YAML string.
|
|
24
24
|
*/
|
|
25
|
-
declare function openApiToYaml(registry:
|
|
25
|
+
declare function openApiToYaml(registry: OperationSpecRegistry, options?: OpenApiExportOptions): string;
|
|
26
26
|
//#endregion
|
|
27
27
|
export { defaultRestPath, openApiForRegistry, openApiToJson, openApiToYaml };
|
|
28
28
|
//# sourceMappingURL=exporter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exporter.d.ts","names":[],"sources":["../../src/openapi/exporter.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAqH8B,iBAjEd,eAAA,CAiEc,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AA+F9B;AAWA;;;;;;iBA7GgB,kBAAA,WACJ,
|
|
1
|
+
{"version":3,"file":"exporter.d.ts","names":[],"sources":["../../src/openapi/exporter.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAqH8B,iBAjEd,eAAA,CAiEc,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AA+F9B;AAWA;;;;;;iBA7GgB,kBAAA,WACJ,iCACD,uBACR;;;;iBA+Fa,aAAA,WACJ,iCACD;;;;iBASK,aAAA,WACJ,iCACD"}
|
package/dist/openapi/exporter.js
CHANGED
|
@@ -57,9 +57,9 @@ function jsonSchemaForSpec(spec) {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
* Export a
|
|
60
|
+
* Export a OperationSpecRegistry to an OpenAPI 3.1 document.
|
|
61
61
|
*
|
|
62
|
-
* @param registry - The
|
|
62
|
+
* @param registry - The OperationSpecRegistry containing specs to export
|
|
63
63
|
* @param options - Export options (title, version, description, servers)
|
|
64
64
|
* @returns OpenAPI 3.1 document
|
|
65
65
|
*/
|
|
@@ -120,14 +120,14 @@ function openApiForRegistry(registry, options = {}) {
|
|
|
120
120
|
return doc;
|
|
121
121
|
}
|
|
122
122
|
/**
|
|
123
|
-
* Export a
|
|
123
|
+
* Export a OperationSpecRegistry to OpenAPI JSON string.
|
|
124
124
|
*/
|
|
125
125
|
function openApiToJson(registry, options = {}) {
|
|
126
126
|
const doc = openApiForRegistry(registry, options);
|
|
127
127
|
return JSON.stringify(doc, null, 2);
|
|
128
128
|
}
|
|
129
129
|
/**
|
|
130
|
-
* Export a
|
|
130
|
+
* Export a OperationSpecRegistry to OpenAPI YAML string.
|
|
131
131
|
*/
|
|
132
132
|
function openApiToYaml(registry, options = {}) {
|
|
133
133
|
return jsonToYaml(openApiForRegistry(registry, options));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exporter.js","names":["doc: ContractSpecOpenApiDocument","op: Record<string, unknown>","responses: Record<string, unknown>"],"sources":["../../src/openapi/exporter.ts"],"sourcesContent":["/**\n * Export ContractSpec specs to OpenAPI 3.1 format.\n * Moved from @lssm/lib.contracts/openapi.ts with enhancements.\n */\n\nimport type {\n SpecRegistry,\n AnyContractSpec,\n ContractSpec,\n} from '@lssm/lib.contracts';\nimport type { AnySchemaModel } from '@lssm/lib.schema';\nimport { z } from 'zod';\nimport type {\n OpenApiExportOptions,\n OpenApiServer,\n ContractSpecOpenApiDocument,\n} from './types';\n\ntype OpenApiSchemaObject = Record<string, unknown>;\n\n/**\n * Convert a spec name and version to an operationId.\n */\nfunction toOperationId(name: string, version: number): string {\n return `${name.replace(/\\./g, '_')}_v${version}`;\n}\n\n/**\n * Convert a spec name and version to a schema name.\n */\nfunction toSchemaName(\n prefix: 'Input' | 'Output',\n name: string,\n version: number\n): string {\n return `${prefix}_${toOperationId(name, version)}`;\n}\n\n/**\n * Determine HTTP method from spec kind and override.\n */\nfunction toHttpMethod(\n kind: 'command' | 'query',\n override?: 'GET' | 'POST'\n): string {\n const method = override ?? (kind === 'query' ? 'GET' : 'POST');\n return method.toLowerCase();\n}\n\n/**\n * Generate default REST path from spec name and version.\n */\nexport function defaultRestPath(name: string, version: number): string {\n return `/${name.replace(/\\./g, '/')}/v${version}`;\n}\n\n/**\n * Get REST path from spec, using transport override or default.\n */\nfunction toRestPath(spec: AnyContractSpec): string {\n const path =\n spec.transport?.rest?.path ??\n defaultRestPath(spec.meta.name, spec.meta.version);\n return path.startsWith('/') ? path : `/${path}`;\n}\n\n/**\n * Convert a SchemaModel to JSON Schema using Zod.\n */\nfunction schemaModelToJsonSchema(\n schema: AnySchemaModel | null\n): OpenApiSchemaObject | null {\n if (!schema) return null;\n return z.toJSONSchema(schema.getZod()) as OpenApiSchemaObject;\n}\n\n/**\n * Extract JSON Schema for a spec's input and output.\n */\nfunction jsonSchemaForSpec(\n spec: ContractSpec<AnySchemaModel, AnySchemaModel>\n): {\n input: OpenApiSchemaObject | null;\n output: OpenApiSchemaObject | null;\n meta: {\n name: string;\n version: number;\n kind: 'command' | 'query';\n description: string;\n tags: string[];\n stability: string;\n };\n} {\n return {\n input: schemaModelToJsonSchema(spec.io.input),\n output: schemaModelToJsonSchema(spec.io.output as AnySchemaModel | null),\n meta: {\n name: spec.meta.name,\n version: spec.meta.version,\n kind: spec.meta.kind,\n description: spec.meta.description,\n tags: spec.meta.tags ?? [],\n stability: spec.meta.stability ?? 'stable',\n },\n };\n}\n\n/**\n * Export a SpecRegistry to an OpenAPI 3.1 document.\n *\n * @param registry - The SpecRegistry containing specs to export\n * @param options - Export options (title, version, description, servers)\n * @returns OpenAPI 3.1 document\n */\nexport function openApiForRegistry(\n registry: SpecRegistry,\n options: OpenApiExportOptions = {}\n): ContractSpecOpenApiDocument {\n const specs = registry\n .listSpecs()\n .filter(\n (s): s is AnyContractSpec =>\n s.meta.kind === 'command' || s.meta.kind === 'query'\n )\n .slice()\n .sort((a, b) => {\n const byName = a.meta.name.localeCompare(b.meta.name);\n return byName !== 0 ? byName : a.meta.version - b.meta.version;\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 for (const spec of specs) {\n const schema = jsonSchemaForSpec(\n spec as unknown as ContractSpec<AnySchemaModel, AnySchemaModel>\n );\n const method = toHttpMethod(spec.meta.kind, spec.transport?.rest?.method);\n const path = toRestPath(spec);\n\n const operationId = toOperationId(spec.meta.name, spec.meta.version);\n\n const pathItem = (doc.paths[path] ??= {});\n const op: Record<string, unknown> = {\n operationId,\n summary: spec.meta.description ?? spec.meta.name,\n description: spec.meta.description,\n tags: spec.meta.tags ?? [],\n 'x-contractspec': {\n name: spec.meta.name,\n version: spec.meta.version,\n kind: spec.meta.kind,\n },\n responses: {},\n };\n\n if (schema.input) {\n const inputName = toSchemaName(\n 'Input',\n spec.meta.name,\n spec.meta.version\n );\n doc.components.schemas[inputName] = schema.input;\n op['requestBody'] = {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: `#/components/schemas/${inputName}` },\n },\n },\n };\n }\n\n const responses: Record<string, unknown> = {};\n if (schema.output) {\n const outputName = toSchemaName(\n 'Output',\n spec.meta.name,\n spec.meta.version\n );\n doc.components.schemas[outputName] = schema.output;\n responses['200'] = {\n description: 'OK',\n content: {\n 'application/json': {\n schema: { $ref: `#/components/schemas/${outputName}` },\n },\n },\n };\n } else {\n responses['200'] = { description: 'OK' };\n }\n op['responses'] = responses;\n\n pathItem[method] = op;\n }\n\n return doc;\n}\n\n/**\n * Export a SpecRegistry to OpenAPI JSON string.\n */\nexport function openApiToJson(\n registry: SpecRegistry,\n options: OpenApiExportOptions = {}\n): string {\n const doc = openApiForRegistry(registry, options);\n return JSON.stringify(doc, null, 2);\n}\n\n/**\n * Export a SpecRegistry to OpenAPI YAML string.\n */\nexport function openApiToYaml(\n registry: SpecRegistry,\n options: OpenApiExportOptions = {}\n): string {\n const doc = openApiForRegistry(registry, options);\n return jsonToYaml(doc);\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};\n"],"mappings":";;;;;;AAuBA,SAAS,cAAc,MAAc,SAAyB;AAC5D,QAAO,GAAG,KAAK,QAAQ,OAAO,IAAI,CAAC,IAAI;;;;;AAMzC,SAAS,aACP,QACA,MACA,SACQ;AACR,QAAO,GAAG,OAAO,GAAG,cAAc,MAAM,QAAQ;;;;;AAMlD,SAAS,aACP,MACA,UACQ;AAER,SADe,aAAa,SAAS,UAAU,QAAQ,SACzC,aAAa;;;;;AAM7B,SAAgB,gBAAgB,MAAc,SAAyB;AACrE,QAAO,IAAI,KAAK,QAAQ,OAAO,IAAI,CAAC,IAAI;;;;;AAM1C,SAAS,WAAW,MAA+B;CACjD,MAAM,OACJ,KAAK,WAAW,MAAM,QACtB,gBAAgB,KAAK,KAAK,MAAM,KAAK,KAAK,QAAQ;AACpD,QAAO,KAAK,WAAW,IAAI,GAAG,OAAO,IAAI;;;;;AAM3C,SAAS,wBACP,QAC4B;AAC5B,KAAI,CAAC,OAAQ,QAAO;AACpB,QAAO,EAAE,aAAa,OAAO,QAAQ,CAAC;;;;;AAMxC,SAAS,kBACP,MAYA;AACA,QAAO;EACL,OAAO,wBAAwB,KAAK,GAAG,MAAM;EAC7C,QAAQ,wBAAwB,KAAK,GAAG,OAAgC;EACxE,MAAM;GACJ,MAAM,KAAK,KAAK;GAChB,SAAS,KAAK,KAAK;GACnB,MAAM,KAAK,KAAK;GAChB,aAAa,KAAK,KAAK;GACvB,MAAM,KAAK,KAAK,QAAQ,EAAE;GAC1B,WAAW,KAAK,KAAK,aAAa;GACnC;EACF;;;;;;;;;AAUH,SAAgB,mBACd,UACA,UAAgC,EAAE,EACL;CAC7B,MAAM,QAAQ,SACX,WAAW,CACX,QACE,MACC,EAAE,KAAK,SAAS,aAAa,EAAE,KAAK,SAAS,QAChD,CACA,OAAO,CACP,MAAM,GAAG,MAAM;EACd,MAAM,SAAS,EAAE,KAAK,KAAK,cAAc,EAAE,KAAK,KAAK;AACrD,SAAO,WAAW,IAAI,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK;GACvD;CAEJ,MAAMA,MAAmC;EACvC,SAAS;EACT,MAAM;GACJ,OAAO,QAAQ,SAAS;GACxB,SAAS,QAAQ,WAAW;GAC5B,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,aAAa,GAAG,EAAE;GACpE;EACD,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,SAAS,GAAG,EAAE;EACvD,OAAO,EAAE;EACT,YAAY,EAAE,SAAS,EAAE,EAAE;EAC5B;AAED,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,kBACb,KACD;EACD,MAAM,SAAS,aAAa,KAAK,KAAK,MAAM,KAAK,WAAW,MAAM,OAAO;EACzE,MAAM,OAAO,WAAW,KAAK;EAE7B,MAAM,cAAc,cAAc,KAAK,KAAK,MAAM,KAAK,KAAK,QAAQ;EAEpE,MAAM,WAAY,IAAI,MAAM,UAAU,EAAE;EACxC,MAAMC,KAA8B;GAClC;GACA,SAAS,KAAK,KAAK,eAAe,KAAK,KAAK;GAC5C,aAAa,KAAK,KAAK;GACvB,MAAM,KAAK,KAAK,QAAQ,EAAE;GAC1B,kBAAkB;IAChB,MAAM,KAAK,KAAK;IAChB,SAAS,KAAK,KAAK;IACnB,MAAM,KAAK,KAAK;IACjB;GACD,WAAW,EAAE;GACd;AAED,MAAI,OAAO,OAAO;GAChB,MAAM,YAAY,aAChB,SACA,KAAK,KAAK,MACV,KAAK,KAAK,QACX;AACD,OAAI,WAAW,QAAQ,aAAa,OAAO;AAC3C,MAAG,iBAAiB;IAClB,UAAU;IACV,SAAS,EACP,oBAAoB,EAClB,QAAQ,EAAE,MAAM,wBAAwB,aAAa,EACtD,EACF;IACF;;EAGH,MAAMC,YAAqC,EAAE;AAC7C,MAAI,OAAO,QAAQ;GACjB,MAAM,aAAa,aACjB,UACA,KAAK,KAAK,MACV,KAAK,KAAK,QACX;AACD,OAAI,WAAW,QAAQ,cAAc,OAAO;AAC5C,aAAU,SAAS;IACjB,aAAa;IACb,SAAS,EACP,oBAAoB,EAClB,QAAQ,EAAE,MAAM,wBAAwB,cAAc,EACvD,EACF;IACF;QAED,WAAU,SAAS,EAAE,aAAa,MAAM;AAE1C,KAAG,eAAe;AAElB,WAAS,UAAU;;AAGrB,QAAO;;;;;AAMT,SAAgB,cACd,UACA,UAAgC,EAAE,EAC1B;CACR,MAAM,MAAM,mBAAmB,UAAU,QAAQ;AACjD,QAAO,KAAK,UAAU,KAAK,MAAM,EAAE;;;;;AAMrC,SAAgB,cACd,UACA,UAAgC,EAAE,EAC1B;AAER,QAAO,WADK,mBAAmB,UAAU,QAAQ,CAC3B;;;;;AAMxB,SAAS,WAAW,KAAc,SAAS,GAAW;CACpD,MAAM,SAAS,KAAK,OAAO,OAAO;CAClC,IAAI,OAAO;AAEX,KAAI,MAAM,QAAQ,IAAI,CACpB,MAAK,MAAM,QAAQ,IACjB,KAAI,OAAO,SAAS,YAAY,SAAS,KACvC,SAAQ,GAAG,OAAO,KAAK,WAAW,MAAM,SAAS,EAAE;KAEnD,SAAQ,GAAG,OAAO,IAAI,KAAK,UAAU,KAAK,CAAC;UAGtC,OAAO,QAAQ,YAAY,QAAQ,KAC5C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,CAC5C,KAAI,MAAM,QAAQ,MAAM,CACtB,SAAQ,GAAG,SAAS,IAAI,KAAK,WAAW,OAAO,SAAS,EAAE;UACjD,OAAO,UAAU,YAAY,UAAU,KAChD,SAAQ,GAAG,SAAS,IAAI,KAAK,WAAW,OAAO,SAAS,EAAE;KAE1D,SAAQ,GAAG,SAAS,IAAI,IAAI,KAAK,UAAU,MAAM,CAAC;AAKxD,QAAO"}
|
|
1
|
+
{"version":3,"file":"exporter.js","names":["doc: ContractSpecOpenApiDocument","op: Record<string, unknown>","responses: Record<string, unknown>"],"sources":["../../src/openapi/exporter.ts"],"sourcesContent":["/**\n * Export ContractSpec specs to OpenAPI 3.1 format.\n * Moved from @lssm/lib.contracts/openapi.ts with enhancements.\n */\n\nimport type {\n AnyOperationSpec,\n OperationSpec,\n OperationSpecRegistry,\n} from '@lssm/lib.contracts';\nimport type { AnySchemaModel } from '@lssm/lib.schema';\nimport { z } from 'zod';\nimport type {\n ContractSpecOpenApiDocument,\n OpenApiExportOptions,\n OpenApiServer,\n} from './types';\n\ntype OpenApiSchemaObject = Record<string, unknown>;\n\n/**\n * Convert a spec name and version to an operationId.\n */\nfunction toOperationId(name: string, version: number): string {\n return `${name.replace(/\\./g, '_')}_v${version}`;\n}\n\n/**\n * Convert a spec name and version to a schema name.\n */\nfunction toSchemaName(\n prefix: 'Input' | 'Output',\n name: string,\n version: number\n): string {\n return `${prefix}_${toOperationId(name, version)}`;\n}\n\n/**\n * Determine HTTP method from spec kind and override.\n */\nfunction toHttpMethod(\n kind: 'command' | 'query',\n override?: 'GET' | 'POST'\n): string {\n const method = override ?? (kind === 'query' ? 'GET' : 'POST');\n return method.toLowerCase();\n}\n\n/**\n * Generate default REST path from spec name and version.\n */\nexport function defaultRestPath(name: string, version: number): string {\n return `/${name.replace(/\\./g, '/')}/v${version}`;\n}\n\n/**\n * Get REST path from spec, using transport override or default.\n */\nfunction toRestPath(spec: AnyOperationSpec): string {\n const path =\n spec.transport?.rest?.path ??\n defaultRestPath(spec.meta.name, spec.meta.version);\n return path.startsWith('/') ? path : `/${path}`;\n}\n\n/**\n * Convert a SchemaModel to JSON Schema using Zod.\n */\nfunction schemaModelToJsonSchema(\n schema: AnySchemaModel | null\n): OpenApiSchemaObject | null {\n if (!schema) return null;\n return z.toJSONSchema(schema.getZod()) as OpenApiSchemaObject;\n}\n\n/**\n * Extract JSON Schema for a spec's input and output.\n */\nfunction jsonSchemaForSpec(\n spec: OperationSpec<AnySchemaModel, AnySchemaModel>\n): {\n input: OpenApiSchemaObject | null;\n output: OpenApiSchemaObject | null;\n meta: {\n name: string;\n version: number;\n kind: 'command' | 'query';\n description: string;\n tags: string[];\n stability: string;\n };\n} {\n return {\n input: schemaModelToJsonSchema(spec.io.input),\n output: schemaModelToJsonSchema(spec.io.output as AnySchemaModel | null),\n meta: {\n name: spec.meta.name,\n version: spec.meta.version,\n kind: spec.meta.kind,\n description: spec.meta.description,\n tags: spec.meta.tags ?? [],\n stability: spec.meta.stability ?? 'stable',\n },\n };\n}\n\n/**\n * Export a OperationSpecRegistry to an OpenAPI 3.1 document.\n *\n * @param registry - The OperationSpecRegistry containing specs to export\n * @param options - Export options (title, version, description, servers)\n * @returns OpenAPI 3.1 document\n */\nexport function openApiForRegistry(\n registry: OperationSpecRegistry,\n options: OpenApiExportOptions = {}\n): ContractSpecOpenApiDocument {\n const specs = registry\n .listSpecs()\n .filter(\n (s): s is AnyOperationSpec =>\n s.meta.kind === 'command' || s.meta.kind === 'query'\n )\n .slice()\n .sort((a, b) => {\n const byName = a.meta.name.localeCompare(b.meta.name);\n return byName !== 0 ? byName : a.meta.version - b.meta.version;\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 for (const spec of specs) {\n const schema = jsonSchemaForSpec(\n spec as unknown as OperationSpec<AnySchemaModel, AnySchemaModel>\n );\n const method = toHttpMethod(spec.meta.kind, spec.transport?.rest?.method);\n const path = toRestPath(spec);\n\n const operationId = toOperationId(spec.meta.name, spec.meta.version);\n\n const pathItem = (doc.paths[path] ??= {});\n const op: Record<string, unknown> = {\n operationId,\n summary: spec.meta.description ?? spec.meta.name,\n description: spec.meta.description,\n tags: spec.meta.tags ?? [],\n 'x-contractspec': {\n name: spec.meta.name,\n version: spec.meta.version,\n kind: spec.meta.kind,\n },\n responses: {},\n };\n\n if (schema.input) {\n const inputName = toSchemaName(\n 'Input',\n spec.meta.name,\n spec.meta.version\n );\n doc.components.schemas[inputName] = schema.input;\n op['requestBody'] = {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: `#/components/schemas/${inputName}` },\n },\n },\n };\n }\n\n const responses: Record<string, unknown> = {};\n if (schema.output) {\n const outputName = toSchemaName(\n 'Output',\n spec.meta.name,\n spec.meta.version\n );\n doc.components.schemas[outputName] = schema.output;\n responses['200'] = {\n description: 'OK',\n content: {\n 'application/json': {\n schema: { $ref: `#/components/schemas/${outputName}` },\n },\n },\n };\n } else {\n responses['200'] = { description: 'OK' };\n }\n op['responses'] = responses;\n\n pathItem[method] = op;\n }\n\n return doc;\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 * 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};\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,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":";;;;;;;;;AA6NC,cAlMY,iBAkMZ,EAAA,CAAA,WAAA,EAjMc,WAiMd,EAAA,mBAAA,EAhMsB,iBAgMtB,EAAA,aAAA,CAAA,EA/LgB,OA+LhB,CA/LwB,mBA+LxB,CAAA,EAAA,GA9LE,YA8LF;AAKD;;;AAEW,iBAFK,eAAA,CAEL,SAAA,EADE,eACF,EAAA,OAAA,EAAA,OAAA,CAAQ,mBAAR,CAAA,GAAA,SAAA,EAAA,mBAAA,EACY,iBADZ,CAAA,EAAA,MAAA"}
|