@contractual/differs.json-schema 0.1.0-dev.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (C) 2025 Omer Morad
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,180 @@
1
+ # @contractual/differs.json-schema
2
+
3
+ Production-quality breaking change detection for JSON Schema.
4
+
5
+ Detects and classifies structural changes between JSON Schema versions, recommending semantic version bumps (major/minor/patch).
6
+
7
+ ## Why?
8
+
9
+ JSON Schema has 150M+ weekly npm downloads but **no production-quality breaking change detection tool**. This package fills that gap.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @contractual/differs.json-schema
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Compare Schema Files
20
+
21
+ ```typescript
22
+ import { diffJsonSchema } from '@contractual/differs.json-schema';
23
+
24
+ const result = await diffJsonSchema('v1/user.schema.json', 'v2/user.schema.json');
25
+
26
+ console.log(`Suggested bump: ${result.suggestedBump}`);
27
+ console.log(`Breaking: ${result.summary.breaking}`);
28
+ console.log(`Non-breaking: ${result.summary.nonBreaking}`);
29
+
30
+ // List breaking changes
31
+ for (const change of result.changes) {
32
+ if (change.severity === 'breaking') {
33
+ console.log(`❌ ${change.message}`);
34
+ }
35
+ }
36
+ ```
37
+
38
+ ### Compare Schema Objects
39
+
40
+ ```typescript
41
+ import { diffJsonSchemaObjects } from '@contractual/differs.json-schema';
42
+
43
+ const oldSchema = { type: 'object', properties: { name: { type: 'string' } } };
44
+ const newSchema = { type: 'object', properties: { name: { type: 'number' } } };
45
+
46
+ const result = diffJsonSchemaObjects(oldSchema, newSchema);
47
+ // result.suggestedBump === 'major'
48
+ // result.changes[0].message === 'Type changed from "string" to "number"...'
49
+ ```
50
+
51
+ ### Quick Breaking Check
52
+
53
+ ```typescript
54
+ import { hasBreakingChanges } from '@contractual/differs.json-schema';
55
+
56
+ if (hasBreakingChanges(oldSchema, newSchema)) {
57
+ throw new Error('Breaking changes detected!');
58
+ }
59
+ ```
60
+
61
+ ## Change Classifications
62
+
63
+ ### Breaking Changes (→ major bump)
64
+
65
+ | Category | Example |
66
+ |----------|---------|
67
+ | `property-removed` | Field deleted from schema |
68
+ | `required-added` | Existing field became required |
69
+ | `type-changed` | Type changed incompatibly |
70
+ | `type-narrowed` | Union type reduced |
71
+ | `enum-value-removed` | Enum option removed |
72
+ | `constraint-tightened` | minLength/maxLength made stricter |
73
+ | `additional-properties-denied` | Open schema closed |
74
+ | `min-items-increased` | Array minimum increased |
75
+ | `max-items-decreased` | Array maximum decreased |
76
+
77
+ ### Non-Breaking Changes (→ minor bump)
78
+
79
+ | Category | Example |
80
+ |----------|---------|
81
+ | `property-added` | New optional field |
82
+ | `required-removed` | Field became optional |
83
+ | `type-widened` | Type accepts more values |
84
+ | `enum-value-added` | New enum option |
85
+ | `constraint-loosened` | Constraints relaxed |
86
+ | `additional-properties-allowed` | Closed schema opened |
87
+
88
+ ### Patch Changes (→ patch bump)
89
+
90
+ | Category | Example |
91
+ |----------|---------|
92
+ | `description-changed` | Documentation updated |
93
+ | `title-changed` | Title updated |
94
+ | `default-changed` | Default value updated |
95
+ | `examples-changed` | Examples updated |
96
+
97
+ ## Advanced Usage
98
+
99
+ ### Resolve $refs
100
+
101
+ ```typescript
102
+ import { resolveRefs } from '@contractual/differs.json-schema';
103
+
104
+ const schema = {
105
+ type: 'object',
106
+ properties: {
107
+ user: { $ref: '#/$defs/User' }
108
+ },
109
+ $defs: {
110
+ User: { type: 'object', properties: { name: { type: 'string' } } }
111
+ }
112
+ };
113
+
114
+ const { schema: resolved, warnings } = resolveRefs(schema);
115
+ // resolved.properties.user === { type: 'object', properties: { name: { type: 'string' } } }
116
+ ```
117
+
118
+ ### Custom Classification
119
+
120
+ ```typescript
121
+ import { walk, classify, CLASSIFICATION_SETS } from '@contractual/differs.json-schema';
122
+
123
+ // Get raw changes
124
+ const rawChanges = walk(resolvedOld, resolvedNew, '');
125
+
126
+ // Classify with custom rules
127
+ for (const change of rawChanges) {
128
+ if (CLASSIFICATION_SETS.BREAKING.has(change.type)) {
129
+ console.log('Breaking:', change.path);
130
+ }
131
+ }
132
+ ```
133
+
134
+ ## Supported JSON Schema Versions
135
+
136
+ - Draft-07 (primary target)
137
+ - Draft 2019-09
138
+ - Draft 2020-12
139
+
140
+ ## API Reference
141
+
142
+ ### Main Functions
143
+
144
+ - `diffJsonSchema(oldPath, newPath)` - Compare two schema files
145
+ - `diffJsonSchemaObjects(oldSchema, newSchema)` - Compare two schema objects
146
+ - `hasBreakingChanges(oldSchema, newSchema)` - Quick boolean check
147
+
148
+ ### Utilities
149
+
150
+ - `resolveRefs(schema)` - Resolve all $ref pointers
151
+ - `walk(oldSchema, newSchema, basePath)` - Low-level schema walker
152
+ - `classify(change)` - Classify a raw change
153
+ - `classifyPropertyAdded(change, newSchema)` - Context-aware property classification
154
+
155
+ ### Types
156
+
157
+ ```typescript
158
+ interface DiffResult {
159
+ changes: Change[];
160
+ summary: DiffSummary;
161
+ suggestedBump: 'major' | 'minor' | 'patch' | 'none';
162
+ }
163
+
164
+ interface Change {
165
+ path: string;
166
+ severity: 'breaking' | 'non-breaking' | 'patch' | 'unknown';
167
+ category: string;
168
+ message: string;
169
+ oldValue?: unknown;
170
+ newValue?: unknown;
171
+ }
172
+ ```
173
+
174
+ ## Part of Contractual
175
+
176
+ This package is extracted from [Contractual](https://github.com/contractual-dev/contractual), the schema contract lifecycle orchestrator.
177
+
178
+ ## License
179
+
180
+ MIT
@@ -0,0 +1,87 @@
1
+ /**
2
+ * JSON Schema Change Classifiers
3
+ *
4
+ * Classifies raw structural changes into severity levels for semantic versioning.
5
+ * Follows API compatibility principles where breaking changes require major bumps,
6
+ * additive changes require minor bumps, and metadata changes are patches.
7
+ */
8
+ import type { RawChange, ChangeType, ChangeSeverity } from './types.js';
9
+ /**
10
+ * Export classification sets for external analysis
11
+ */
12
+ export declare const CLASSIFICATION_SETS: {
13
+ readonly breaking: ReadonlySet<ChangeType>;
14
+ readonly nonBreaking: ReadonlySet<ChangeType>;
15
+ readonly patch: ReadonlySet<ChangeType>;
16
+ readonly unknown: ReadonlySet<ChangeType>;
17
+ };
18
+ /**
19
+ * Classify a raw change into a severity level
20
+ *
21
+ * Uses the Strands API classification rules where:
22
+ * - Breaking changes require major version bump
23
+ * - Non-breaking changes require minor version bump
24
+ * - Patch changes are metadata/annotation only
25
+ * - Unknown changes require manual review
26
+ *
27
+ * @param change - The raw change to classify
28
+ * @returns The severity classification
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const change: RawChange = {
33
+ * path: '/properties/name',
34
+ * type: 'property-removed',
35
+ * oldValue: { type: 'string' },
36
+ * };
37
+ * const severity = classify(change);
38
+ * // severity === 'breaking'
39
+ * ```
40
+ */
41
+ export declare function classify(change: RawChange): ChangeSeverity;
42
+ /**
43
+ * Classify a property-added change with schema context
44
+ *
45
+ * Property additions are breaking if the property is required,
46
+ * otherwise they are non-breaking (additive).
47
+ *
48
+ * @param change - The property-added change
49
+ * @param newSchema - The new schema for context (to check required array)
50
+ * @returns The severity classification
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const change: RawChange = {
55
+ * path: '/properties/email',
56
+ * type: 'property-added',
57
+ * newValue: { type: 'string', format: 'email' },
58
+ * };
59
+ *
60
+ * const schema = {
61
+ * type: 'object',
62
+ * properties: { email: { type: 'string', format: 'email' } },
63
+ * required: ['email'], // email is required!
64
+ * };
65
+ *
66
+ * const severity = classifyPropertyAdded(change, schema);
67
+ * // severity === 'breaking' (because email is in required[])
68
+ * ```
69
+ */
70
+ export declare function classifyPropertyAdded(change: RawChange, newSchema: unknown): ChangeSeverity;
71
+ /**
72
+ * Batch classify multiple changes
73
+ *
74
+ * @param changes - Array of raw changes
75
+ * @param newSchema - Optional schema for context-aware classification
76
+ * @returns Map of change to severity
77
+ */
78
+ export declare function classifyAll(changes: readonly RawChange[], newSchema?: unknown): Map<RawChange, ChangeSeverity>;
79
+ /**
80
+ * Get a human-readable message for a classified change
81
+ *
82
+ * @param change - The raw change
83
+ * @param severity - The classified severity
84
+ * @returns Human-readable description
85
+ */
86
+ export declare function getChangeMessage(change: RawChange, severity: ChangeSeverity): string;
87
+ //# sourceMappingURL=classifiers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifiers.d.ts","sourceRoot":"","sources":["../src/classifiers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA6HxE;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;;CAKtB,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,cAAc,CAsB1D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,GAAG,cAAc,CA6B3F;AAgGD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,SAAS,SAAS,EAAE,EAC7B,SAAS,CAAC,EAAE,OAAO,GAClB,GAAG,CAAC,SAAS,EAAE,cAAc,CAAC,CAYhC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,CAapF"}
@@ -0,0 +1,357 @@
1
+ /**
2
+ * JSON Schema Change Classifiers
3
+ *
4
+ * Classifies raw structural changes into severity levels for semantic versioning.
5
+ * Follows API compatibility principles where breaking changes require major bumps,
6
+ * additive changes require minor bumps, and metadata changes are patches.
7
+ */
8
+ /**
9
+ * Change types that are always breaking (require major version bump)
10
+ *
11
+ * These changes can break existing consumers:
12
+ * - Removing properties removes data they may depend on
13
+ * - Adding required fields forces consumers to provide new data
14
+ * - Type changes can break parsing/validation
15
+ * - Enum removals can invalidate existing data
16
+ * - Tightened constraints can reject previously valid data
17
+ * - Composition option additions can change validation semantics
18
+ *
19
+ * Aligned with Strands API classification rules.
20
+ */
21
+ const BREAKING_CHANGES = new Set([
22
+ 'property-removed',
23
+ 'required-added',
24
+ 'type-changed',
25
+ 'type-narrowed',
26
+ 'enum-value-removed',
27
+ 'enum-added',
28
+ 'constraint-tightened',
29
+ 'additional-properties-denied',
30
+ 'items-changed',
31
+ 'min-items-increased',
32
+ 'max-items-decreased',
33
+ 'ref-target-changed',
34
+ 'dependent-required-added',
35
+ // Composition breaking changes (per Strands API)
36
+ 'anyof-option-added',
37
+ 'oneof-option-added',
38
+ 'allof-member-added',
39
+ 'not-schema-changed',
40
+ ]);
41
+ /**
42
+ * Change types that are non-breaking (require minor version bump)
43
+ *
44
+ * These changes are backward compatible additions/relaxations:
45
+ * - Adding optional properties extends the schema without breaking
46
+ * - Removing required constraints makes the schema more permissive
47
+ * - Type widening accepts more values
48
+ * - Loosened constraints accept more values
49
+ * - Composition option removals make schema less restrictive
50
+ *
51
+ * Aligned with Strands API classification rules.
52
+ */
53
+ const NON_BREAKING_CHANGES = new Set([
54
+ 'property-added',
55
+ 'required-removed',
56
+ 'type-widened',
57
+ 'enum-value-added',
58
+ 'enum-removed',
59
+ 'constraint-loosened',
60
+ 'additional-properties-allowed',
61
+ 'additional-properties-changed',
62
+ 'dependent-required-removed',
63
+ // Composition non-breaking changes (per Strands API)
64
+ 'anyof-option-removed',
65
+ 'oneof-option-removed',
66
+ 'allof-member-removed',
67
+ ]);
68
+ /**
69
+ * Change types that are patches (documentation/metadata only)
70
+ *
71
+ * These changes don't affect validation behavior:
72
+ * - Description changes are documentation only
73
+ * - Title changes are display metadata
74
+ * - Default/example changes don't affect validation
75
+ * - Format is an annotation (per Strands API) - doesn't affect validation
76
+ * - Annotation keywords (deprecated, readOnly, writeOnly)
77
+ * - Content keywords (contentEncoding, contentMediaType, contentSchema)
78
+ *
79
+ * Aligned with Strands API classification rules.
80
+ */
81
+ const PATCH_CHANGES = new Set([
82
+ // Metadata changes
83
+ 'description-changed',
84
+ 'title-changed',
85
+ 'default-changed',
86
+ 'examples-changed',
87
+ // Format is annotation (patch per Strands API)
88
+ 'format-added',
89
+ 'format-removed',
90
+ 'format-changed',
91
+ // Annotation keywords (Draft 2019-09+)
92
+ 'deprecated-changed',
93
+ 'read-only-changed',
94
+ 'write-only-changed',
95
+ // Content keywords
96
+ 'content-encoding-changed',
97
+ 'content-media-type-changed',
98
+ 'content-schema-changed',
99
+ ]);
100
+ /**
101
+ * Change types that require manual review
102
+ *
103
+ * These changes are too complex to classify automatically:
104
+ * - Generic composition changes require semantic analysis
105
+ * - Complex keywords (propertyNames, dependentSchemas, unevaluated*)
106
+ * - Conditional schema changes (if/then/else)
107
+ * - Unknown changes need human evaluation
108
+ *
109
+ * Aligned with Strands API classification rules.
110
+ */
111
+ const UNKNOWN_CHANGES = new Set([
112
+ // Complex object keywords
113
+ 'property-names-changed',
114
+ 'dependent-schemas-changed',
115
+ 'unevaluated-properties-changed',
116
+ // Complex array keywords
117
+ 'unevaluated-items-changed',
118
+ 'min-contains-changed',
119
+ 'max-contains-changed',
120
+ // Conditional schema
121
+ 'if-then-else-changed',
122
+ // Legacy/generic composition
123
+ 'composition-changed',
124
+ // Catch-all
125
+ 'unknown-change',
126
+ ]);
127
+ /**
128
+ * Export classification sets for external analysis
129
+ */
130
+ export const CLASSIFICATION_SETS = {
131
+ breaking: BREAKING_CHANGES,
132
+ nonBreaking: NON_BREAKING_CHANGES,
133
+ patch: PATCH_CHANGES,
134
+ unknown: UNKNOWN_CHANGES,
135
+ };
136
+ /**
137
+ * Classify a raw change into a severity level
138
+ *
139
+ * Uses the Strands API classification rules where:
140
+ * - Breaking changes require major version bump
141
+ * - Non-breaking changes require minor version bump
142
+ * - Patch changes are metadata/annotation only
143
+ * - Unknown changes require manual review
144
+ *
145
+ * @param change - The raw change to classify
146
+ * @returns The severity classification
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * const change: RawChange = {
151
+ * path: '/properties/name',
152
+ * type: 'property-removed',
153
+ * oldValue: { type: 'string' },
154
+ * };
155
+ * const severity = classify(change);
156
+ * // severity === 'breaking'
157
+ * ```
158
+ */
159
+ export function classify(change) {
160
+ const { type } = change;
161
+ // Check each category in order of specificity
162
+ if (BREAKING_CHANGES.has(type)) {
163
+ return 'breaking';
164
+ }
165
+ if (NON_BREAKING_CHANGES.has(type)) {
166
+ return 'non-breaking';
167
+ }
168
+ if (PATCH_CHANGES.has(type)) {
169
+ return 'patch';
170
+ }
171
+ if (UNKNOWN_CHANGES.has(type)) {
172
+ return 'unknown';
173
+ }
174
+ // Defensive: any unhandled type is unknown
175
+ return 'unknown';
176
+ }
177
+ /**
178
+ * Classify a property-added change with schema context
179
+ *
180
+ * Property additions are breaking if the property is required,
181
+ * otherwise they are non-breaking (additive).
182
+ *
183
+ * @param change - The property-added change
184
+ * @param newSchema - The new schema for context (to check required array)
185
+ * @returns The severity classification
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * const change: RawChange = {
190
+ * path: '/properties/email',
191
+ * type: 'property-added',
192
+ * newValue: { type: 'string', format: 'email' },
193
+ * };
194
+ *
195
+ * const schema = {
196
+ * type: 'object',
197
+ * properties: { email: { type: 'string', format: 'email' } },
198
+ * required: ['email'], // email is required!
199
+ * };
200
+ *
201
+ * const severity = classifyPropertyAdded(change, schema);
202
+ * // severity === 'breaking' (because email is in required[])
203
+ * ```
204
+ */
205
+ export function classifyPropertyAdded(change, newSchema) {
206
+ // Validate change type
207
+ if (change.type !== 'property-added') {
208
+ return classify(change);
209
+ }
210
+ // Extract property name from path
211
+ const propertyName = extractPropertyName(change.path);
212
+ if (!propertyName) {
213
+ // Cannot determine property name, fall back to non-breaking
214
+ return 'non-breaking';
215
+ }
216
+ // Find the parent schema containing this property
217
+ const parentSchema = findParentSchema(change.path, newSchema);
218
+ if (!parentSchema) {
219
+ // Cannot find parent schema, fall back to non-breaking
220
+ return 'non-breaking';
221
+ }
222
+ // Check if property is in the required array
223
+ const required = getRequiredArray(parentSchema);
224
+ if (required.includes(propertyName)) {
225
+ // Adding a required property is breaking
226
+ return 'breaking';
227
+ }
228
+ // Adding an optional property is non-breaking
229
+ return 'non-breaking';
230
+ }
231
+ /**
232
+ * Extract the property name from a JSON Pointer path
233
+ *
234
+ * @param path - JSON Pointer path (e.g., '/properties/name' or '/properties/user/properties/email')
235
+ * @returns The property name or null if not found
236
+ */
237
+ function extractPropertyName(path) {
238
+ // Match the last /properties/NAME segment
239
+ const match = path.match(/\/properties\/([^/]+)$/);
240
+ if (match?.[1] !== undefined) {
241
+ return decodeJsonPointerSegment(match[1]);
242
+ }
243
+ return null;
244
+ }
245
+ /**
246
+ * Decode a JSON Pointer segment (handles ~0 and ~1 escapes)
247
+ *
248
+ * @param segment - The encoded segment
249
+ * @returns The decoded segment
250
+ */
251
+ function decodeJsonPointerSegment(segment) {
252
+ return segment.replace(/~1/g, '/').replace(/~0/g, '~');
253
+ }
254
+ /**
255
+ * Find the parent schema containing a property
256
+ *
257
+ * @param path - JSON Pointer path to the property
258
+ * @param schema - The root schema
259
+ * @returns The parent schema or null if not found
260
+ */
261
+ function findParentSchema(path, schema) {
262
+ if (!isObject(schema)) {
263
+ return null;
264
+ }
265
+ // Remove the last segment to get parent path
266
+ // e.g., '/properties/name' -> '' (root)
267
+ // e.g., '/properties/user/properties/email' -> '/properties/user'
268
+ const segments = path.split('/').filter(Boolean);
269
+ // We need to navigate to the schema containing /properties/NAME
270
+ // So we remove 'properties' and 'NAME' from the end
271
+ if (segments.length < 2) {
272
+ // Path is too short, parent is root
273
+ return schema;
274
+ }
275
+ // Remove 'NAME' and 'properties' from the end
276
+ const parentSegments = segments.slice(0, -2);
277
+ // Navigate to parent
278
+ let current = schema;
279
+ for (const segment of parentSegments) {
280
+ if (!isObject(current)) {
281
+ return null;
282
+ }
283
+ const decoded = decodeJsonPointerSegment(segment);
284
+ current = current[decoded];
285
+ }
286
+ return current;
287
+ }
288
+ /**
289
+ * Get the required array from a schema object
290
+ *
291
+ * @param schema - The schema object
292
+ * @returns Array of required property names
293
+ */
294
+ function getRequiredArray(schema) {
295
+ if (!isObject(schema)) {
296
+ return [];
297
+ }
298
+ const obj = schema;
299
+ const required = obj['required'];
300
+ if (!Array.isArray(required)) {
301
+ return [];
302
+ }
303
+ return required.filter((item) => typeof item === 'string');
304
+ }
305
+ /**
306
+ * Type guard for objects
307
+ */
308
+ function isObject(value) {
309
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
310
+ }
311
+ /**
312
+ * Batch classify multiple changes
313
+ *
314
+ * @param changes - Array of raw changes
315
+ * @param newSchema - Optional schema for context-aware classification
316
+ * @returns Map of change to severity
317
+ */
318
+ export function classifyAll(changes, newSchema) {
319
+ const results = new Map();
320
+ for (const change of changes) {
321
+ if (change.type === 'property-added' && newSchema !== undefined) {
322
+ results.set(change, classifyPropertyAdded(change, newSchema));
323
+ }
324
+ else {
325
+ results.set(change, classify(change));
326
+ }
327
+ }
328
+ return results;
329
+ }
330
+ /**
331
+ * Get a human-readable message for a classified change
332
+ *
333
+ * @param change - The raw change
334
+ * @param severity - The classified severity
335
+ * @returns Human-readable description
336
+ */
337
+ export function getChangeMessage(change, severity) {
338
+ const severityLabel = severity === 'breaking'
339
+ ? 'BREAKING'
340
+ : severity === 'non-breaking'
341
+ ? 'Non-breaking'
342
+ : severity === 'patch'
343
+ ? 'Patch'
344
+ : 'Unknown';
345
+ const typeLabel = formatChangeType(change.type);
346
+ return `[${severityLabel}] ${typeLabel} at ${change.path}`;
347
+ }
348
+ /**
349
+ * Format a change type into a human-readable label
350
+ */
351
+ function formatChangeType(type) {
352
+ return type
353
+ .split('-')
354
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
355
+ .join(' ');
356
+ }
357
+ //# sourceMappingURL=classifiers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifiers.js","sourceRoot":"","sources":["../src/classifiers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Strands-compatible JSON Schema comparison API
3
+ *
4
+ * Provides schema comparison with output format matching the Strands API
5
+ * (https://strands.octue.com/api/compare-schemas)
6
+ */
7
+ import { type CompareResult, type CompareOptions, type StrandsCompatibility } from './types.js';
8
+ /**
9
+ * Compare two JSON Schema objects and return Strands-compatible result
10
+ *
11
+ * @param sourceSchema - The source (old/baseline) schema object
12
+ * @param targetSchema - The target (new/current) schema object
13
+ * @param options - Optional comparison options
14
+ * @returns CompareResult in Strands API format
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const source = { type: 'object', properties: { name: { type: 'string' } } };
19
+ * const target = { type: 'object', properties: { name: { type: 'number' } } };
20
+ *
21
+ * const result = compareSchemas(source, target, { currentVersion: '1.0.0' });
22
+ * console.log(result.version); // 'major'
23
+ * console.log(result.newVersion); // { major: 2, minor: 0, patch: 0, version: '2.0.0' }
24
+ * ```
25
+ */
26
+ export declare function compareSchemas(sourceSchema: unknown, targetSchema: unknown, options?: CompareOptions): CompareResult;
27
+ /**
28
+ * Compare two JSON Schema objects and return a simple compatibility result
29
+ *
30
+ * Convenience function for quick compatibility checks.
31
+ *
32
+ * @param sourceSchema - The source (old/baseline) schema object
33
+ * @param targetSchema - The target (new/current) schema object
34
+ * @returns 'compatible' | 'incompatible' | 'unknown'
35
+ */
36
+ export declare function checkCompatibility(sourceSchema: unknown, targetSchema: unknown): StrandsCompatibility;
37
+ //# sourceMappingURL=compare.d.ts.map