@contractspec/lib.contracts-transformers 1.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -0
  3. package/dist/common/index.d.ts +3 -0
  4. package/dist/common/index.js +3 -0
  5. package/dist/common/types.d.ts +159 -0
  6. package/dist/common/types.d.ts.map +1 -0
  7. package/dist/common/utils.d.ts +52 -0
  8. package/dist/common/utils.d.ts.map +1 -0
  9. package/dist/common/utils.js +103 -0
  10. package/dist/common/utils.js.map +1 -0
  11. package/dist/index.d.ts +18 -0
  12. package/dist/index.js +18 -0
  13. package/dist/openapi/differ.d.ts +42 -0
  14. package/dist/openapi/differ.d.ts.map +1 -0
  15. package/dist/openapi/differ.js +222 -0
  16. package/dist/openapi/differ.js.map +1 -0
  17. package/dist/openapi/exporter/data-views.d.ts +38 -0
  18. package/dist/openapi/exporter/data-views.d.ts.map +1 -0
  19. package/dist/openapi/exporter/data-views.js +47 -0
  20. package/dist/openapi/exporter/data-views.js.map +1 -0
  21. package/dist/openapi/exporter/events.d.ts +28 -0
  22. package/dist/openapi/exporter/events.d.ts.map +1 -0
  23. package/dist/openapi/exporter/events.js +39 -0
  24. package/dist/openapi/exporter/events.js.map +1 -0
  25. package/dist/openapi/exporter/features.d.ts +37 -0
  26. package/dist/openapi/exporter/features.d.ts.map +1 -0
  27. package/dist/openapi/exporter/features.js +46 -0
  28. package/dist/openapi/exporter/features.js.map +1 -0
  29. package/dist/openapi/exporter/forms.d.ts +30 -0
  30. package/dist/openapi/exporter/forms.d.ts.map +1 -0
  31. package/dist/openapi/exporter/forms.js +49 -0
  32. package/dist/openapi/exporter/forms.js.map +1 -0
  33. package/dist/openapi/exporter/index.js +8 -0
  34. package/dist/openapi/exporter/operations.d.ts +65 -0
  35. package/dist/openapi/exporter/operations.d.ts.map +1 -0
  36. package/dist/openapi/exporter/operations.js +142 -0
  37. package/dist/openapi/exporter/operations.js.map +1 -0
  38. package/dist/openapi/exporter/presentations.d.ts +32 -0
  39. package/dist/openapi/exporter/presentations.d.ts.map +1 -0
  40. package/dist/openapi/exporter/presentations.js +60 -0
  41. package/dist/openapi/exporter/presentations.js.map +1 -0
  42. package/dist/openapi/exporter/registries.d.ts +23 -0
  43. package/dist/openapi/exporter/registries.d.ts.map +1 -0
  44. package/dist/openapi/exporter/registries.js +29 -0
  45. package/dist/openapi/exporter/registries.js.map +1 -0
  46. package/dist/openapi/exporter/workflows.d.ts +36 -0
  47. package/dist/openapi/exporter/workflows.d.ts.map +1 -0
  48. package/dist/openapi/exporter/workflows.js +54 -0
  49. package/dist/openapi/exporter/workflows.js.map +1 -0
  50. package/dist/openapi/exporter.d.ts +48 -0
  51. package/dist/openapi/exporter.d.ts.map +1 -0
  52. package/dist/openapi/exporter.js +122 -0
  53. package/dist/openapi/exporter.js.map +1 -0
  54. package/dist/openapi/importer/analyzer.js +28 -0
  55. package/dist/openapi/importer/analyzer.js.map +1 -0
  56. package/dist/openapi/importer/events.js +40 -0
  57. package/dist/openapi/importer/events.js.map +1 -0
  58. package/dist/openapi/importer/generator.js +105 -0
  59. package/dist/openapi/importer/generator.js.map +1 -0
  60. package/dist/openapi/importer/grouping.js +73 -0
  61. package/dist/openapi/importer/grouping.js.map +1 -0
  62. package/dist/openapi/importer/index.d.ts +17 -0
  63. package/dist/openapi/importer/index.d.ts.map +1 -0
  64. package/dist/openapi/importer/index.js +175 -0
  65. package/dist/openapi/importer/index.js.map +1 -0
  66. package/dist/openapi/importer/models.js +22 -0
  67. package/dist/openapi/importer/models.js.map +1 -0
  68. package/dist/openapi/importer/schemas.js +60 -0
  69. package/dist/openapi/importer/schemas.js.map +1 -0
  70. package/dist/openapi/index.d.ts +16 -0
  71. package/dist/openapi/index.js +18 -0
  72. package/dist/openapi/parser/document.d.ts +20 -0
  73. package/dist/openapi/parser/document.d.ts.map +1 -0
  74. package/dist/openapi/parser/document.js +95 -0
  75. package/dist/openapi/parser/document.js.map +1 -0
  76. package/dist/openapi/parser/index.js +5 -0
  77. package/dist/openapi/parser/operation.js +59 -0
  78. package/dist/openapi/parser/operation.js.map +1 -0
  79. package/dist/openapi/parser/parameters.js +37 -0
  80. package/dist/openapi/parser/parameters.js.map +1 -0
  81. package/dist/openapi/parser/resolvers.js +63 -0
  82. package/dist/openapi/parser/resolvers.js.map +1 -0
  83. package/dist/openapi/parser/utils.d.ts +19 -0
  84. package/dist/openapi/parser/utils.d.ts.map +1 -0
  85. package/dist/openapi/parser/utils.js +48 -0
  86. package/dist/openapi/parser/utils.js.map +1 -0
  87. package/dist/openapi/parser.js +6 -0
  88. package/dist/openapi/schema-converter.d.ts +71 -0
  89. package/dist/openapi/schema-converter.d.ts.map +1 -0
  90. package/dist/openapi/schema-converter.js +161 -0
  91. package/dist/openapi/schema-converter.js.map +1 -0
  92. package/dist/openapi/schema-generators/index.js +462 -0
  93. package/dist/openapi/schema-generators/index.js.map +1 -0
  94. package/dist/openapi/types.d.ts +277 -0
  95. package/dist/openapi/types.d.ts.map +1 -0
  96. package/package.json +62 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Chaman Ventures, SASU
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @contractspec/lib.contracts-transformers
2
+
3
+ Website: https://contractspec.io/
4
+
5
+
6
+ Contract format transformations: bidirectional import/export between ContractSpec and external API specification formats.
7
+
8
+ ## Supported Formats
9
+
10
+ - **OpenAPI 3.x** - Import from and export to OpenAPI specifications (JSON/YAML, URL/file)
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ bun add @contractspec/lib.contracts-transformers
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### Export ContractSpec to OpenAPI
21
+
22
+ ```typescript
23
+ import { openApiForRegistry } from '@contractspec/lib.contracts-transformers/openapi';
24
+ import { OperationSpecRegistry } from '@contractspec/lib.contracts';
25
+
26
+ const registry = new OperationSpecRegistry();
27
+ // ... register your specs ...
28
+
29
+ const openApiDoc = openApiForRegistry(registry, {
30
+ title: 'My API',
31
+ version: '1.0.0',
32
+ description: 'API generated from ContractSpec',
33
+ servers: [{ url: 'https://api.example.com' }],
34
+ });
35
+ ```
36
+
37
+ ### Import from OpenAPI
38
+
39
+ ```typescript
40
+ import { parseOpenApi, importFromOpenApi } from '@contractspec/lib.contracts-transformers/openapi';
41
+
42
+ // Parse OpenAPI from file or URL
43
+ const openApiDoc = await parseOpenApi('./api.yaml');
44
+ // Or from URL
45
+ const openApiDoc = await parseOpenApi('https://api.example.com/openapi.json');
46
+
47
+ // Convert to ContractSpec specs
48
+ const importResult = importFromOpenApi(openApiDoc, {
49
+ prefix: 'myApi',
50
+ tags: ['users', 'orders'], // Optional: filter by tags
51
+ exclude: ['deprecated_endpoint'], // Optional: exclude by operationId
52
+ schemaFormat: 'contractspec', // Optional: 'contractspec' | 'zod' | 'json-schema' | 'graphql'
53
+ });
54
+
55
+ // importResult contains generated spec code as strings
56
+ for (const spec of importResult.specs) {
57
+ console.log(spec.name, spec.code);
58
+ }
59
+ ```
60
+
61
+ ### Diff ContractSpec vs OpenAPI
62
+
63
+ ```typescript
64
+ import { diffSpecs } from '@contractspec/lib.contracts-transformers/openapi';
65
+
66
+ const diffs = diffSpecs(existingSpecs, importedSpecs);
67
+
68
+ for (const diff of diffs) {
69
+ console.log(`${diff.operationId}: ${diff.changes.length} changes`);
70
+ for (const change of diff.changes) {
71
+ console.log(` - ${change.path}: ${change.type}`);
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Architecture
77
+
78
+ This library is organized by format:
79
+
80
+ - `openapi/` - OpenAPI 3.x transformations
81
+ - `parser.ts` - Parse OpenAPI from JSON/YAML/URL
82
+ - `importer.ts` - Convert OpenAPI to ContractSpec
83
+ - `exporter.ts` - Convert ContractSpec to OpenAPI
84
+ - `differ.ts` - Diff specs for sync operations
85
+ - `schema-converter.ts` - JSON Schema <-> SchemaModel conversion
86
+ - `common/` - Shared utilities and types
87
+
88
+ ## Future Formats
89
+
90
+ The library is designed to be extensible for additional formats:
91
+
92
+ - AsyncAPI (event-driven APIs)
93
+ - gRPC/Protobuf
94
+ - GraphQL Schema
95
+
@@ -0,0 +1,3 @@
1
+ import { DiffChange, DiffChangeType, ImportResult, ImportedOperationSpec, SpecDiff, SpecSource, SyncResult, TransportHints, ValidationResult } from "./types.js";
2
+ import { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecKey, toValidIdentifier } from "./utils.js";
3
+ export { DiffChange, DiffChangeType, ImportResult, ImportedOperationSpec, SpecDiff, SpecSource, SyncResult, TransportHints, ValidationResult, deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecKey, toValidIdentifier };
@@ -0,0 +1,3 @@
1
+ import { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecKey, toValidIdentifier } from "./utils.js";
2
+
3
+ export { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecKey, toValidIdentifier };
@@ -0,0 +1,159 @@
1
+ import { AnyOperationSpec } from "@contractspec/lib.contracts";
2
+
3
+ //#region src/common/types.d.ts
4
+
5
+ /**
6
+ * Source information for imported specs.
7
+ */
8
+ interface SpecSource {
9
+ /** The format the spec was imported from */
10
+ type: 'openapi' | 'asyncapi' | 'graphql' | 'protobuf';
11
+ /** URL if fetched from remote */
12
+ url?: string;
13
+ /** File path if loaded from local file */
14
+ file?: string;
15
+ /** Original identifier in source format */
16
+ sourceId: string;
17
+ /** Timestamp of import */
18
+ importedAt: Date;
19
+ }
20
+ /**
21
+ * Transport hints preserved from external formats.
22
+ * These enable accurate round-trip transformations.
23
+ */
24
+ interface TransportHints {
25
+ rest?: {
26
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
27
+ path: string;
28
+ params?: {
29
+ path?: string[];
30
+ query?: string[];
31
+ header?: string[];
32
+ cookie?: string[];
33
+ };
34
+ };
35
+ graphql?: {
36
+ type: 'query' | 'mutation' | 'subscription';
37
+ fieldName: string;
38
+ };
39
+ }
40
+ /**
41
+ * Result of importing a single spec from an external format.
42
+ */
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;
50
+ /** Generated TypeScript code for the spec */
51
+ code: string;
52
+ /** Suggested file name for the spec */
53
+ fileName: string;
54
+ /** Group folder for organizing the file (based on grouping config) */
55
+ groupFolder?: string;
56
+ /** Source information for provenance tracking */
57
+ source: SpecSource;
58
+ /** Transport hints for accurate round-trips */
59
+ transportHints: TransportHints;
60
+ }
61
+ /**
62
+ * Result of an import operation.
63
+ */
64
+ interface ImportResult {
65
+ /** Successfully imported specs */
66
+ operationSpecs: ImportedOperationSpec[];
67
+ /** Specs that were skipped (e.g., unsupported features) */
68
+ skipped: {
69
+ sourceId: string;
70
+ reason: string;
71
+ }[];
72
+ /** Errors encountered during import */
73
+ errors: {
74
+ sourceId: string;
75
+ error: string;
76
+ }[];
77
+ /** Summary statistics */
78
+ summary: {
79
+ total: number;
80
+ imported: number;
81
+ skipped: number;
82
+ errors: number;
83
+ };
84
+ }
85
+ /**
86
+ * Type of change detected during diff.
87
+ */
88
+ type DiffChangeType = 'added' | 'removed' | 'modified' | 'type_changed' | 'required_changed';
89
+ /**
90
+ * A single change detected during diff.
91
+ */
92
+ interface DiffChange {
93
+ /** JSON path to the changed property */
94
+ path: string;
95
+ /** Type of change */
96
+ type: DiffChangeType;
97
+ /** Previous value (for modified/removed) */
98
+ oldValue?: unknown;
99
+ /** New value (for modified/added) */
100
+ newValue?: unknown;
101
+ /** Human-readable description of the change */
102
+ description: string;
103
+ }
104
+ /**
105
+ * Result of diffing two specs.
106
+ */
107
+ interface SpecDiff {
108
+ /** Identifier for the operation */
109
+ operationId: string;
110
+ /** Existing ContractSpec (if any) */
111
+ existing?: AnyOperationSpec;
112
+ /** Incoming imported spec */
113
+ incoming: ImportedOperationSpec;
114
+ /** List of detected changes */
115
+ changes: DiffChange[];
116
+ /** Whether specs are semantically equivalent */
117
+ isEquivalent: boolean;
118
+ /** User's resolution choice (for interactive sync) */
119
+ resolution?: 'keep' | 'replace' | 'merge' | 'skip';
120
+ }
121
+ /**
122
+ * Result of a sync operation.
123
+ */
124
+ interface SyncResult {
125
+ /** Specs that were added (new imports) */
126
+ added: ImportedOperationSpec[];
127
+ /** Specs that were updated */
128
+ updated: {
129
+ spec: ImportedOperationSpec;
130
+ changes: DiffChange[];
131
+ }[];
132
+ /** Specs that were kept unchanged */
133
+ unchanged: string[];
134
+ /** Specs that had conflicts requiring resolution */
135
+ conflicts: SpecDiff[];
136
+ /** Summary statistics */
137
+ summary: {
138
+ added: number;
139
+ updated: number;
140
+ unchanged: number;
141
+ conflicts: number;
142
+ };
143
+ }
144
+ /**
145
+ * Validation result for a single spec.
146
+ */
147
+ interface ValidationResult {
148
+ /** Whether the spec is valid against the source */
149
+ valid: boolean;
150
+ /** Detected differences */
151
+ diffs: DiffChange[];
152
+ /** Validation errors */
153
+ errors: string[];
154
+ /** Validation warnings */
155
+ warnings: string[];
156
+ }
157
+ //#endregion
158
+ export { DiffChange, DiffChangeType, ImportResult, ImportedOperationSpec, SpecDiff, SpecSource, SyncResult, TransportHints, ValidationResult };
159
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/common/types.ts"],"sourcesContent":[],"mappings":";;;;AA0BA;AAoBA;;AAcU,UAnDO,UAAA,CAmDP;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,EA5GP,IA4GO;AAUrB;;;;;AAWqB,UA1HJ,cAAA,CA0HI;EAaJ,IAAA,CAAA,EAAA;;;;;;;;;;;;;;;;;;UAnHA,qBAAA;;;;;;kBAMC;;;;;;;;UAQR;;kBAEQ;;;;;UAMD,YAAA;;kBAEC;;;;;;;;;;;;;;;;;;;;;;KAuBN,cAAA;;;;UAUK,UAAA;;;;QAIT;;;;;;;;;;;UAYS,QAAA;;;;aAIJ;;YAED;;WAED;;;;;;;;;UAUM,UAAA;;SAER;;;UAGC;aACG;;;;;aAKA;;;;;;;;;;;;UAaI,gBAAA;;;;SAIR"}
@@ -0,0 +1,52 @@
1
+ //#region src/common/utils.d.ts
2
+ /**
3
+ * Common utilities for contract transformations.
4
+ */
5
+ /**
6
+ * Convert a string to PascalCase.
7
+ */
8
+ declare function toPascalCase(str: string): string;
9
+ /**
10
+ * Convert a string to camelCase.
11
+ */
12
+ declare function toCamelCase(str: string): string;
13
+ /**
14
+ * Convert a string to kebab-case.
15
+ */
16
+ declare function toKebabCase(str: string): string;
17
+ /**
18
+ * Convert a string to snake_case.
19
+ */
20
+ declare function toSnakeCase(str: string): string;
21
+ /**
22
+ * Sanitize a string to be a valid TypeScript identifier.
23
+ */
24
+ declare function toValidIdentifier(str: string): string;
25
+ /**
26
+ * Generate a ContractSpec key from an operation identifier.
27
+ */
28
+ declare function toSpecKey(operationId: string, prefix?: string): string;
29
+ /**
30
+ * Generate a file name from a spec name.
31
+ */
32
+ declare function toFileName(specName: string): string;
33
+ /**
34
+ * Deep equality check for objects.
35
+ */
36
+ declare function deepEqual(a: unknown, b: unknown): boolean;
37
+ /**
38
+ * Get a value from an object by JSON path.
39
+ */
40
+ declare function getByPath(obj: unknown, path: string): unknown;
41
+ /**
42
+ * Extract path parameters from a URL path template.
43
+ * e.g., "/users/{userId}/orders/{orderId}" -> ["userId", "orderId"]
44
+ */
45
+ declare function extractPathParams(path: string): string[];
46
+ /**
47
+ * Normalize a URL path for comparison.
48
+ */
49
+ declare function normalizePath(path: string): string;
50
+ //#endregion
51
+ export { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecKey, toValidIdentifier };
52
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","names":[],"sources":["../../src/common/utils.ts"],"sourcesContent":[],"mappings":";;AAOA;AASA;AAQA;AAUA;AAUA;AAagB,iBAlDA,YAAA,CAkDS,GAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAQzB;AAOA;AA4BA;AAiBgB,iBArGA,WAAA,CAqGiB,GAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAQjC;;;iBArGgB,WAAA;;;;iBAUA,WAAA;;;;iBAUA,iBAAA;;;;iBAaA,SAAA;;;;iBAQA,UAAA;;;;iBAOA,SAAA;;;;iBA4BA,SAAA;;;;;iBAiBA,iBAAA;;;;iBAQA,aAAA"}
@@ -0,0 +1,103 @@
1
+ //#region src/common/utils.ts
2
+ /**
3
+ * Common utilities for contract transformations.
4
+ */
5
+ /**
6
+ * Convert a string to PascalCase.
7
+ */
8
+ function toPascalCase(str) {
9
+ return str.replace(/[-_./\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toUpperCase());
10
+ }
11
+ /**
12
+ * Convert a string to camelCase.
13
+ */
14
+ function toCamelCase(str) {
15
+ const pascal = toPascalCase(str);
16
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
17
+ }
18
+ /**
19
+ * Convert a string to kebab-case.
20
+ */
21
+ function toKebabCase(str) {
22
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_./]+/g, "-").toLowerCase();
23
+ }
24
+ /**
25
+ * Convert a string to snake_case.
26
+ */
27
+ function toSnakeCase(str) {
28
+ return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s\-./]+/g, "_").toLowerCase();
29
+ }
30
+ /**
31
+ * Sanitize a string to be a valid TypeScript identifier.
32
+ */
33
+ function toValidIdentifier(str) {
34
+ let result = str.replace(/[^a-zA-Z0-9_$]/g, "_");
35
+ if (/^[0-9]/.test(result)) result = "_" + result;
36
+ return result;
37
+ }
38
+ /**
39
+ * Generate a ContractSpec key from an operation identifier.
40
+ */
41
+ function toSpecKey(operationId, prefix) {
42
+ const key = toCamelCase(operationId);
43
+ return prefix ? `${prefix}.${key}` : key;
44
+ }
45
+ /**
46
+ * Generate a file name from a spec name.
47
+ */
48
+ function toFileName(specName) {
49
+ return toKebabCase(specName.replace(/\./g, "-")) + ".ts";
50
+ }
51
+ /**
52
+ * Deep equality check for objects.
53
+ */
54
+ function deepEqual(a, b) {
55
+ if (a === b) return true;
56
+ if (a === null || b === null) return false;
57
+ if (typeof a !== typeof b) return false;
58
+ if (typeof a === "object") {
59
+ const aObj = a;
60
+ const bObj = b;
61
+ const aKeys = Object.keys(aObj);
62
+ const bKeys = Object.keys(bObj);
63
+ if (aKeys.length !== bKeys.length) return false;
64
+ for (const key of aKeys) {
65
+ if (!bKeys.includes(key)) return false;
66
+ if (!deepEqual(aObj[key], bObj[key])) return false;
67
+ }
68
+ return true;
69
+ }
70
+ return false;
71
+ }
72
+ /**
73
+ * Get a value from an object by JSON path.
74
+ */
75
+ function getByPath(obj, path) {
76
+ const parts = path.split(".").filter(Boolean);
77
+ let current = obj;
78
+ for (const part of parts) {
79
+ if (current === null || current === void 0) return void 0;
80
+ if (typeof current !== "object") return void 0;
81
+ current = current[part];
82
+ }
83
+ return current;
84
+ }
85
+ /**
86
+ * Extract path parameters from a URL path template.
87
+ * e.g., "/users/{userId}/orders/{orderId}" -> ["userId", "orderId"]
88
+ */
89
+ function extractPathParams(path) {
90
+ return (path.match(/\{([^}]+)\}/g) || []).map((m) => m.slice(1, -1));
91
+ }
92
+ /**
93
+ * Normalize a URL path for comparison.
94
+ */
95
+ function normalizePath(path) {
96
+ let normalized = path.replace(/^\/+|\/+$/g, "");
97
+ normalized = normalized.replace(/\/+/g, "/");
98
+ return "/" + normalized;
99
+ }
100
+
101
+ //#endregion
102
+ export { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecKey, toValidIdentifier };
103
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","names":["current: unknown"],"sources":["../../src/common/utils.ts"],"sourcesContent":["/**\n * Common utilities for contract transformations.\n */\n\n/**\n * Convert a string to PascalCase.\n */\nexport function toPascalCase(str: string): string {\n return str\n .replace(/[-_./\\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))\n .replace(/^./, (c) => c.toUpperCase());\n}\n\n/**\n * Convert a string to camelCase.\n */\nexport function toCamelCase(str: string): string {\n const pascal = toPascalCase(str);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/**\n * Convert a string to kebab-case.\n */\nexport function toKebabCase(str: string): string {\n return str\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/[\\s_./]+/g, '-')\n .toLowerCase();\n}\n\n/**\n * Convert a string to snake_case.\n */\nexport function toSnakeCase(str: string): string {\n return str\n .replace(/([a-z])([A-Z])/g, '$1_$2')\n .replace(/[\\s\\-./]+/g, '_')\n .toLowerCase();\n}\n\n/**\n * Sanitize a string to be a valid TypeScript identifier.\n */\nexport function toValidIdentifier(str: string): string {\n // Remove invalid characters\n let result = str.replace(/[^a-zA-Z0-9_$]/g, '_');\n // Ensure it doesn't start with a number\n if (/^[0-9]/.test(result)) {\n result = '_' + result;\n }\n return result;\n}\n\n/**\n * Generate a ContractSpec key from an operation identifier.\n */\nexport function toSpecKey(operationId: string, prefix?: string): string {\n const key = toCamelCase(operationId);\n return prefix ? `${prefix}.${key}` : key;\n}\n\n/**\n * Generate a file name from a spec name.\n */\nexport function toFileName(specName: string): string {\n return toKebabCase(specName.replace(/\\./g, '-')) + '.ts';\n}\n\n/**\n * Deep equality check for objects.\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n\n if (typeof a === 'object') {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n\n if (aKeys.length !== bKeys.length) return false;\n\n for (const key of aKeys) {\n if (!bKeys.includes(key)) return false;\n if (!deepEqual(aObj[key], bObj[key])) return false;\n }\n\n return true;\n }\n\n return false;\n}\n\n/**\n * Get a value from an object by JSON path.\n */\nexport function getByPath(obj: unknown, path: string): unknown {\n const parts = path.split('.').filter(Boolean);\n let current: unknown = obj;\n\n for (const part of parts) {\n if (current === null || current === undefined) return undefined;\n if (typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n\n return current;\n}\n\n/**\n * Extract path parameters from a URL path template.\n * e.g., \"/users/{userId}/orders/{orderId}\" -> [\"userId\", \"orderId\"]\n */\nexport function extractPathParams(path: string): string[] {\n const matches = path.match(/\\{([^}]+)\\}/g) || [];\n return matches.map((m) => m.slice(1, -1));\n}\n\n/**\n * Normalize a URL path for comparison.\n */\nexport function normalizePath(path: string): string {\n // Remove leading/trailing slashes\n let normalized = path.replace(/^\\/+|\\/+$/g, '');\n // Replace multiple slashes with single\n normalized = normalized.replace(/\\/+/g, '/');\n // Add leading slash\n return '/' + normalized;\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,aAAa,KAAqB;AAChD,QAAO,IACJ,QAAQ,mBAAmB,GAAG,MAAO,IAAI,EAAE,aAAa,GAAG,GAAI,CAC/D,QAAQ,OAAO,MAAM,EAAE,aAAa,CAAC;;;;;AAM1C,SAAgB,YAAY,KAAqB;CAC/C,MAAM,SAAS,aAAa,IAAI;AAChC,QAAO,OAAO,OAAO,EAAE,CAAC,aAAa,GAAG,OAAO,MAAM,EAAE;;;;;AAMzD,SAAgB,YAAY,KAAqB;AAC/C,QAAO,IACJ,QAAQ,mBAAmB,QAAQ,CACnC,QAAQ,aAAa,IAAI,CACzB,aAAa;;;;;AAMlB,SAAgB,YAAY,KAAqB;AAC/C,QAAO,IACJ,QAAQ,mBAAmB,QAAQ,CACnC,QAAQ,cAAc,IAAI,CAC1B,aAAa;;;;;AAMlB,SAAgB,kBAAkB,KAAqB;CAErD,IAAI,SAAS,IAAI,QAAQ,mBAAmB,IAAI;AAEhD,KAAI,SAAS,KAAK,OAAO,CACvB,UAAS,MAAM;AAEjB,QAAO;;;;;AAMT,SAAgB,UAAU,aAAqB,QAAyB;CACtE,MAAM,MAAM,YAAY,YAAY;AACpC,QAAO,SAAS,GAAG,OAAO,GAAG,QAAQ;;;;;AAMvC,SAAgB,WAAW,UAA0B;AACnD,QAAO,YAAY,SAAS,QAAQ,OAAO,IAAI,CAAC,GAAG;;;;;AAMrD,SAAgB,UAAU,GAAY,GAAqB;AACzD,KAAI,MAAM,EAAG,QAAO;AACpB,KAAI,MAAM,QAAQ,MAAM,KAAM,QAAO;AACrC,KAAI,OAAO,MAAM,OAAO,EAAG,QAAO;AAElC,KAAI,OAAO,MAAM,UAAU;EACzB,MAAM,OAAO;EACb,MAAM,OAAO;EAEb,MAAM,QAAQ,OAAO,KAAK,KAAK;EAC/B,MAAM,QAAQ,OAAO,KAAK,KAAK;AAE/B,MAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAE1C,OAAK,MAAM,OAAO,OAAO;AACvB,OAAI,CAAC,MAAM,SAAS,IAAI,CAAE,QAAO;AACjC,OAAI,CAAC,UAAU,KAAK,MAAM,KAAK,KAAK,CAAE,QAAO;;AAG/C,SAAO;;AAGT,QAAO;;;;;AAMT,SAAgB,UAAU,KAAc,MAAuB;CAC7D,MAAM,QAAQ,KAAK,MAAM,IAAI,CAAC,OAAO,QAAQ;CAC7C,IAAIA,UAAmB;AAEvB,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,YAAY,QAAQ,YAAY,OAAW,QAAO;AACtD,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,YAAW,QAAoC;;AAGjD,QAAO;;;;;;AAOT,SAAgB,kBAAkB,MAAwB;AAExD,SADgB,KAAK,MAAM,eAAe,IAAI,EAAE,EACjC,KAAK,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC;;;;;AAM3C,SAAgB,cAAc,MAAsB;CAElD,IAAI,aAAa,KAAK,QAAQ,cAAc,GAAG;AAE/C,cAAa,WAAW,QAAQ,QAAQ,IAAI;AAE5C,QAAO,MAAM"}
@@ -0,0 +1,18 @@
1
+ import { DiffChange, DiffChangeType, ImportResult, ImportedOperationSpec, SpecDiff, SpecSource, SyncResult, TransportHints, ValidationResult } from "./common/types.js";
2
+ import { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecKey, toValidIdentifier } from "./common/utils.js";
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";
6
+ import { OperationsExportResult, defaultRestPath, exportOperations, generateOperationsRegistry, jsonSchemaForSpec, schemaModelToJsonSchema, toHttpMethod, toOperationId, toRestPath, toSchemaName } from "./openapi/exporter/operations.js";
7
+ import { ContractSpecRegistries, contractSpecToJson, contractSpecToYaml, exportContractSpec, openApiForRegistry, openApiToJson, openApiToYaml } from "./openapi/exporter.js";
8
+ import { ExportedEvent, exportEvents, generateEventsExports } from "./openapi/exporter/events.js";
9
+ import { ExportedFeature, exportFeatures, generateFeaturesRegistry } from "./openapi/exporter/features.js";
10
+ import { ExportedPresentation, exportPresentations, exportPresentationsFromArray, generatePresentationsRegistry } from "./openapi/exporter/presentations.js";
11
+ import { ExportedForm, exportForms, generateFormsRegistry } from "./openapi/exporter/forms.js";
12
+ import { ExportedDataView, exportDataViews, generateDataViewsRegistry } from "./openapi/exporter/data-views.js";
13
+ import { ExportedWorkflow, exportWorkflows, generateWorkflowsRegistry } from "./openapi/exporter/workflows.js";
14
+ import { RegistryGenerationOptions, generateRegistryIndex } from "./openapi/exporter/registries.js";
15
+ import { GeneratedModel, SchemaField, TypescriptType, generateImports, generateSchemaModelCode, getScalarType, jsonSchemaToType } from "./openapi/schema-converter.js";
16
+ import { importFromOpenApi, importOperation } from "./openapi/importer/index.js";
17
+ import { DiffOptions, createSpecDiff, diffAll, diffSpecVsOperation, diffSpecs, formatDiffChanges } from "./openapi/differ.js";
18
+ export { ContractSpecOpenApiDocument, ContractSpecRegistries, DiffChange, DiffChangeType, DiffOptions, ExportedDataView, ExportedEvent, ExportedFeature, ExportedForm, ExportedPresentation, ExportedWorkflow, GeneratedModel, HttpMethod, ImportResult, ImportedOperationSpec, OpenApiDocument, OpenApiExportOptions, OpenApiOperation, OpenApiParameter, OpenApiParseOptions, OpenApiSchema, OpenApiServer, OpenApiSource, OpenApiTransportHints, OpenApiVersion, OperationsExportResult, ParameterLocation, ParseResult, ParsedOperation, ParsedParameter, RegistryGenerationOptions, SchemaField, SpecDiff, SpecSource, SyncResult, TransportHints, TypescriptType, ValidationResult, contractSpecToJson, contractSpecToYaml, createSpecDiff, deepEqual, defaultRestPath, detectFormat, detectVersion, diffAll, diffSpecVsOperation, diffSpecs, exportContractSpec, exportDataViews, exportEvents, exportFeatures, exportForms, exportOperations, exportPresentations, exportPresentationsFromArray, exportWorkflows, extractPathParams, formatDiffChanges, generateDataViewsRegistry, generateEventsExports, generateFeaturesRegistry, generateFormsRegistry, generateImports, generateOperationsRegistry, generatePresentationsRegistry, generateRegistryIndex, generateSchemaModelCode, generateWorkflowsRegistry, getByPath, getScalarType, importFromOpenApi, importOperation, jsonSchemaForSpec, jsonSchemaToType, normalizePath, openApiForRegistry, openApiToJson, openApiToYaml, parseOpenApi, parseOpenApiDocument, parseOpenApiString, schemaModelToJsonSchema, toCamelCase, toFileName, toHttpMethod, toKebabCase, toOperationId, toPascalCase, toRestPath, toSchemaName, toSnakeCase, toSpecKey, toValidIdentifier };
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ import { detectFormat, detectVersion, parseOpenApiString } from "./openapi/parser/utils.js";
2
+ import { parseOpenApi, parseOpenApiDocument } from "./openapi/parser/document.js";
3
+ import { defaultRestPath, exportOperations, generateOperationsRegistry, jsonSchemaForSpec, schemaModelToJsonSchema, toHttpMethod, toOperationId, toRestPath, toSchemaName } from "./openapi/exporter/operations.js";
4
+ import { exportEvents, generateEventsExports } from "./openapi/exporter/events.js";
5
+ import { exportFeatures, generateFeaturesRegistry } from "./openapi/exporter/features.js";
6
+ import { exportPresentations, exportPresentationsFromArray, generatePresentationsRegistry } from "./openapi/exporter/presentations.js";
7
+ import { exportForms, generateFormsRegistry } from "./openapi/exporter/forms.js";
8
+ import { exportDataViews, generateDataViewsRegistry } from "./openapi/exporter/data-views.js";
9
+ import { exportWorkflows, generateWorkflowsRegistry } from "./openapi/exporter/workflows.js";
10
+ import { generateRegistryIndex } from "./openapi/exporter/registries.js";
11
+ import { contractSpecToJson, contractSpecToYaml, exportContractSpec, openApiForRegistry, openApiToJson, openApiToYaml } from "./openapi/exporter.js";
12
+ import { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecKey, toValidIdentifier } from "./common/utils.js";
13
+ import { generateImports, generateSchemaModelCode, getScalarType, jsonSchemaToType } from "./openapi/schema-converter.js";
14
+ import { importFromOpenApi, importOperation } from "./openapi/importer/index.js";
15
+ import { createSpecDiff, diffAll, diffSpecVsOperation, diffSpecs, formatDiffChanges } from "./openapi/differ.js";
16
+ import "./openapi/index.js";
17
+
18
+ export { contractSpecToJson, contractSpecToYaml, createSpecDiff, deepEqual, defaultRestPath, detectFormat, detectVersion, diffAll, diffSpecVsOperation, diffSpecs, exportContractSpec, exportDataViews, exportEvents, exportFeatures, exportForms, exportOperations, exportPresentations, exportPresentationsFromArray, exportWorkflows, extractPathParams, formatDiffChanges, generateDataViewsRegistry, generateEventsExports, generateFeaturesRegistry, generateFormsRegistry, generateImports, generateOperationsRegistry, generatePresentationsRegistry, generateRegistryIndex, generateSchemaModelCode, generateWorkflowsRegistry, getByPath, getScalarType, importFromOpenApi, importOperation, jsonSchemaForSpec, jsonSchemaToType, normalizePath, openApiForRegistry, openApiToJson, openApiToYaml, parseOpenApi, parseOpenApiDocument, parseOpenApiString, schemaModelToJsonSchema, toCamelCase, toFileName, toHttpMethod, toKebabCase, toOperationId, toPascalCase, toRestPath, toSchemaName, toSnakeCase, toSpecKey, toValidIdentifier };
@@ -0,0 +1,42 @@
1
+ import { DiffChange, ImportedOperationSpec, SpecDiff } from "../common/types.js";
2
+ import { ParsedOperation } from "./types.js";
3
+ import { AnyOperationSpec } from "@contractspec/lib.contracts";
4
+
5
+ //#region src/openapi/differ.d.ts
6
+
7
+ /**
8
+ * Options for diffing specs.
9
+ */
10
+ interface DiffOptions {
11
+ /** Ignore description changes */
12
+ ignoreDescriptions?: boolean;
13
+ /** Ignore tag changes */
14
+ ignoreTags?: boolean;
15
+ /** Ignore transport changes (path, method) */
16
+ ignoreTransport?: boolean;
17
+ /** Custom paths to ignore */
18
+ ignorePaths?: string[];
19
+ }
20
+ /**
21
+ * Diff a ContractSpec against an OpenAPI operation.
22
+ */
23
+ declare function diffSpecVsOperation(spec: AnyOperationSpec, operation: ParsedOperation, options?: DiffOptions): DiffChange[];
24
+ /**
25
+ * Diff two ContractSpecs.
26
+ */
27
+ declare function diffSpecs(oldSpec: AnyOperationSpec, newSpec: AnyOperationSpec, options?: DiffOptions): DiffChange[];
28
+ /**
29
+ * Create a SpecDiff from an existing spec and an imported spec.
30
+ */
31
+ declare function createSpecDiff(operationId: string, existing: AnyOperationSpec | undefined, incoming: ImportedOperationSpec, options?: DiffOptions): SpecDiff;
32
+ /**
33
+ * Batch diff multiple specs against OpenAPI operations.
34
+ */
35
+ declare function diffAll(existingSpecs: Map<string, AnyOperationSpec>, importedSpecs: ImportedOperationSpec[], options?: DiffOptions): SpecDiff[];
36
+ /**
37
+ * Format diff changes for display.
38
+ */
39
+ declare function formatDiffChanges(changes: DiffChange[]): string;
40
+ //#endregion
41
+ export { DiffOptions, createSpecDiff, diffAll, diffSpecVsOperation, diffSpecs, formatDiffChanges };
42
+ //# sourceMappingURL=differ.d.ts.map
@@ -0,0 +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,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"}