@globaltypesystem/gts-ts 0.1.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 (81) hide show
  1. package/.eslintrc.json +16 -0
  2. package/.github/workflows/ci.yml +198 -0
  3. package/.gitmodules +3 -0
  4. package/.prettierrc +7 -0
  5. package/LICENSE +201 -0
  6. package/Makefile +64 -0
  7. package/README.md +298 -0
  8. package/dist/cast.d.ts +9 -0
  9. package/dist/cast.d.ts.map +1 -0
  10. package/dist/cast.js +153 -0
  11. package/dist/cast.js.map +1 -0
  12. package/dist/cli/index.d.ts +3 -0
  13. package/dist/cli/index.d.ts.map +1 -0
  14. package/dist/cli/index.js +318 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/compatibility.d.ts +11 -0
  17. package/dist/compatibility.d.ts.map +1 -0
  18. package/dist/compatibility.js +176 -0
  19. package/dist/compatibility.js.map +1 -0
  20. package/dist/extract.d.ts +13 -0
  21. package/dist/extract.d.ts.map +1 -0
  22. package/dist/extract.js +194 -0
  23. package/dist/extract.js.map +1 -0
  24. package/dist/gts.d.ts +18 -0
  25. package/dist/gts.d.ts.map +1 -0
  26. package/dist/gts.js +472 -0
  27. package/dist/gts.js.map +1 -0
  28. package/dist/index.d.ts +29 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +97 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/query.d.ts +10 -0
  33. package/dist/query.d.ts.map +1 -0
  34. package/dist/query.js +171 -0
  35. package/dist/query.js.map +1 -0
  36. package/dist/relationships.d.ts +7 -0
  37. package/dist/relationships.d.ts.map +1 -0
  38. package/dist/relationships.js +80 -0
  39. package/dist/relationships.js.map +1 -0
  40. package/dist/server/index.d.ts +2 -0
  41. package/dist/server/index.d.ts.map +1 -0
  42. package/dist/server/index.js +132 -0
  43. package/dist/server/index.js.map +1 -0
  44. package/dist/server/server.d.ts +33 -0
  45. package/dist/server/server.d.ts.map +1 -0
  46. package/dist/server/server.js +678 -0
  47. package/dist/server/server.js.map +1 -0
  48. package/dist/server/types.d.ts +61 -0
  49. package/dist/server/types.d.ts.map +1 -0
  50. package/dist/server/types.js +3 -0
  51. package/dist/server/types.js.map +1 -0
  52. package/dist/store.d.ts +39 -0
  53. package/dist/store.d.ts.map +1 -0
  54. package/dist/store.js +1026 -0
  55. package/dist/store.js.map +1 -0
  56. package/dist/types.d.ts +111 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +29 -0
  59. package/dist/types.js.map +1 -0
  60. package/dist/x-gts-ref.d.ts +35 -0
  61. package/dist/x-gts-ref.d.ts.map +1 -0
  62. package/dist/x-gts-ref.js +304 -0
  63. package/dist/x-gts-ref.js.map +1 -0
  64. package/jest.config.js +13 -0
  65. package/package.json +54 -0
  66. package/src/cast.ts +179 -0
  67. package/src/cli/index.ts +315 -0
  68. package/src/compatibility.ts +201 -0
  69. package/src/extract.ts +213 -0
  70. package/src/gts.ts +550 -0
  71. package/src/index.ts +97 -0
  72. package/src/query.ts +191 -0
  73. package/src/relationships.ts +91 -0
  74. package/src/server/index.ts +112 -0
  75. package/src/server/server.ts +771 -0
  76. package/src/server/types.ts +74 -0
  77. package/src/store.ts +1178 -0
  78. package/src/types.ts +138 -0
  79. package/src/x-gts-ref.ts +349 -0
  80. package/tests/gts.test.ts +525 -0
  81. package/tsconfig.json +32 -0
package/dist/store.js ADDED
@@ -0,0 +1,1026 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GtsStore = void 0;
7
+ exports.createJsonEntity = createJsonEntity;
8
+ const ajv_1 = __importDefault(require("ajv"));
9
+ const types_1 = require("./types");
10
+ const gts_1 = require("./gts");
11
+ const extract_1 = require("./extract");
12
+ const x_gts_ref_1 = require("./x-gts-ref");
13
+ class GtsStore {
14
+ constructor(config) {
15
+ this.byId = new Map();
16
+ this.config = {
17
+ validateRefs: config?.validateRefs ?? false,
18
+ strictMode: config?.strictMode ?? false,
19
+ };
20
+ this.ajv = new ajv_1.default({
21
+ strict: false,
22
+ validateSchema: false,
23
+ addUsedSchema: false,
24
+ loadSchema: this.loadSchema.bind(this),
25
+ validateFormats: false, // Disable format validation to match Go implementation
26
+ });
27
+ // Don't add format validators since Go uses lenient validation
28
+ }
29
+ async loadSchema(uri) {
30
+ const normalizedUri = uri.startsWith(types_1.GTS_URI_PREFIX) ? uri.substring(types_1.GTS_URI_PREFIX.length) : uri;
31
+ if (gts_1.Gts.isValidGtsID(normalizedUri)) {
32
+ const entity = this.get(normalizedUri);
33
+ if (entity && entity.isSchema) {
34
+ return entity.content;
35
+ }
36
+ }
37
+ throw new Error(`Unresolvable GTS reference: ${uri}`);
38
+ }
39
+ register(entity) {
40
+ if (this.config.validateRefs) {
41
+ for (const ref of entity.references) {
42
+ if (!this.byId.has(ref)) {
43
+ throw new Error(`Unresolved reference: ${ref}`);
44
+ }
45
+ }
46
+ }
47
+ this.byId.set(entity.id, entity);
48
+ // If this is a schema, add it to AJV for reference resolution
49
+ if (entity.isSchema && entity.content) {
50
+ try {
51
+ const normalizedSchema = this.normalizeSchema(entity.content);
52
+ // Set $id to the GTS ID if not already set
53
+ if (!normalizedSchema.$id) {
54
+ normalizedSchema.$id = entity.id;
55
+ }
56
+ this.ajv.addSchema(normalizedSchema, entity.id);
57
+ }
58
+ catch (err) {
59
+ // Ignore errors adding schema - it might already exist or be invalid
60
+ }
61
+ }
62
+ }
63
+ get(id) {
64
+ return this.byId.get(id);
65
+ }
66
+ getAll() {
67
+ return Array.from(this.byId.values());
68
+ }
69
+ query(pattern, limit) {
70
+ const results = [];
71
+ const maxResults = limit ?? Number.MAX_SAFE_INTEGER;
72
+ for (const [id] of this.byId) {
73
+ if (results.length >= maxResults)
74
+ break;
75
+ const matchResult = gts_1.Gts.matchIDPattern(id, pattern);
76
+ if (matchResult.match) {
77
+ results.push(id);
78
+ }
79
+ }
80
+ return results;
81
+ }
82
+ validateInstance(gtsId) {
83
+ try {
84
+ const gid = gts_1.Gts.parseGtsID(gtsId);
85
+ const obj = this.get(gid.id);
86
+ if (!obj) {
87
+ return {
88
+ id: gtsId,
89
+ ok: false,
90
+ valid: false,
91
+ error: `Entity not found: ${gtsId}`,
92
+ };
93
+ }
94
+ if (!obj.schemaId) {
95
+ return {
96
+ id: gtsId,
97
+ ok: false,
98
+ valid: false,
99
+ error: `No schema found for instance: ${gtsId}`,
100
+ };
101
+ }
102
+ const schemaEntity = this.get(obj.schemaId);
103
+ if (!schemaEntity) {
104
+ return {
105
+ id: gtsId,
106
+ ok: false,
107
+ valid: false,
108
+ error: `Schema not found: ${obj.schemaId}`,
109
+ };
110
+ }
111
+ if (!schemaEntity.isSchema) {
112
+ return {
113
+ id: gtsId,
114
+ ok: false,
115
+ valid: false,
116
+ error: `Entity '${obj.schemaId}' is not a schema`,
117
+ };
118
+ }
119
+ const validate = this.ajv.compile(this.normalizeSchema(schemaEntity.content));
120
+ const isValid = validate(obj.content);
121
+ if (!isValid) {
122
+ const errors = validate.errors
123
+ ?.map((e) => {
124
+ if (e.keyword === 'required') {
125
+ return `${e.instancePath || '/'} must have required property '${e.params?.missingProperty}'`;
126
+ }
127
+ return `${e.instancePath} ${e.message}`;
128
+ })
129
+ .join('; ') || 'Validation failed';
130
+ return {
131
+ id: gtsId,
132
+ ok: false,
133
+ valid: false,
134
+ error: errors,
135
+ };
136
+ }
137
+ // Validate x-gts-ref constraints
138
+ const xGtsRefValidator = new x_gts_ref_1.XGtsRefValidator(this);
139
+ const xGtsRefErrors = xGtsRefValidator.validateInstance(obj.content, schemaEntity.content);
140
+ if (xGtsRefErrors.length > 0) {
141
+ const errorMsgs = xGtsRefErrors.map((err) => err.reason).join('; ');
142
+ return {
143
+ id: gtsId,
144
+ ok: false,
145
+ valid: false,
146
+ error: `x-gts-ref validation failed: ${errorMsgs}`,
147
+ };
148
+ }
149
+ return {
150
+ id: gtsId,
151
+ ok: true,
152
+ valid: true,
153
+ error: '',
154
+ };
155
+ }
156
+ catch (error) {
157
+ return {
158
+ id: gtsId,
159
+ ok: false,
160
+ valid: false,
161
+ error: error instanceof Error ? error.message : String(error),
162
+ };
163
+ }
164
+ }
165
+ normalizeSchema(schema) {
166
+ return this.normalizeSchemaRecursive(schema);
167
+ }
168
+ normalizeSchemaRecursive(obj) {
169
+ if (obj === null || typeof obj !== 'object') {
170
+ return obj;
171
+ }
172
+ if (Array.isArray(obj)) {
173
+ return obj.map((item) => this.normalizeSchemaRecursive(item));
174
+ }
175
+ const normalized = {};
176
+ for (const [key, value] of Object.entries(obj)) {
177
+ let newKey = key;
178
+ let newValue = value;
179
+ // Convert $$ prefixed keys to $ prefixed keys
180
+ switch (key) {
181
+ case '$$id':
182
+ newKey = '$id';
183
+ break;
184
+ case '$$schema':
185
+ newKey = '$schema';
186
+ break;
187
+ case '$$ref':
188
+ newKey = '$ref';
189
+ break;
190
+ case '$$defs':
191
+ newKey = '$defs';
192
+ break;
193
+ }
194
+ // Recursively normalize nested objects
195
+ if (value && typeof value === 'object') {
196
+ newValue = this.normalizeSchemaRecursive(value);
197
+ }
198
+ normalized[newKey] = newValue;
199
+ }
200
+ // Normalize $id values
201
+ if (normalized['$id'] && typeof normalized['$id'] === 'string') {
202
+ if (normalized['$id'].startsWith(types_1.GTS_URI_PREFIX)) {
203
+ normalized['$id'] = normalized['$id'].substring(types_1.GTS_URI_PREFIX.length);
204
+ }
205
+ }
206
+ // Normalize $ref values
207
+ if (normalized['$ref'] && typeof normalized['$ref'] === 'string') {
208
+ if (normalized['$ref'].startsWith(types_1.GTS_URI_PREFIX)) {
209
+ normalized['$ref'] = normalized['$ref'].substring(types_1.GTS_URI_PREFIX.length);
210
+ }
211
+ }
212
+ return normalized;
213
+ }
214
+ resolveRelationships(gtsId) {
215
+ const seen = new Set();
216
+ return this.buildSchemaGraphNode(gtsId, seen);
217
+ }
218
+ buildSchemaGraphNode(gtsId, seen) {
219
+ const node = {
220
+ id: gtsId,
221
+ };
222
+ // Check for cycles
223
+ if (seen.has(gtsId)) {
224
+ return node;
225
+ }
226
+ seen.add(gtsId);
227
+ // Get the entity from store
228
+ const entity = this.get(gtsId);
229
+ if (!entity) {
230
+ node.errors = ['Entity not found'];
231
+ return node;
232
+ }
233
+ // Process GTS references found in the entity
234
+ const refs = this.extractGtsReferences(entity.content);
235
+ const nodeRefs = {};
236
+ for (const ref of refs) {
237
+ // Skip self-references
238
+ if (ref.id === gtsId) {
239
+ continue;
240
+ }
241
+ // Skip JSON Schema meta-schema references
242
+ if (this.isJsonSchemaUrl(ref.id)) {
243
+ continue;
244
+ }
245
+ // Recursively build node for this reference
246
+ nodeRefs[ref.sourcePath] = this.buildSchemaGraphNode(ref.id, seen);
247
+ }
248
+ if (Object.keys(nodeRefs).length > 0) {
249
+ node.refs = nodeRefs;
250
+ }
251
+ // Process schema ID if present
252
+ if (entity.schemaId) {
253
+ if (!this.isJsonSchemaUrl(entity.schemaId)) {
254
+ node.schema_id = this.buildSchemaGraphNode(entity.schemaId, seen);
255
+ }
256
+ }
257
+ else if (!entity.isSchema) {
258
+ // Instance without schema ID is an error
259
+ node.errors = node.errors || [];
260
+ node.errors.push('Schema not recognized');
261
+ }
262
+ return node;
263
+ }
264
+ extractGtsReferences(content) {
265
+ const refs = [];
266
+ const seen = new Set();
267
+ const walkAndCollectRefs = (node, path) => {
268
+ if (node === null || node === undefined) {
269
+ return;
270
+ }
271
+ // Check if current node is a GTS ID string
272
+ if (typeof node === 'string') {
273
+ if (gts_1.Gts.isValidGtsID(node)) {
274
+ const sourcePath = path || 'root';
275
+ const key = `${node}|${sourcePath}`;
276
+ if (!seen.has(key)) {
277
+ refs.push({ id: node, sourcePath });
278
+ seen.add(key);
279
+ }
280
+ }
281
+ return;
282
+ }
283
+ // Recurse into object
284
+ if (typeof node === 'object' && !Array.isArray(node)) {
285
+ for (const [k, v] of Object.entries(node)) {
286
+ const nextPath = path ? `${path}.${k}` : k;
287
+ walkAndCollectRefs(v, nextPath);
288
+ }
289
+ return;
290
+ }
291
+ // Recurse into array
292
+ if (Array.isArray(node)) {
293
+ for (let i = 0; i < node.length; i++) {
294
+ const nextPath = path ? `${path}[${i}]` : `[${i}]`;
295
+ walkAndCollectRefs(node[i], nextPath);
296
+ }
297
+ }
298
+ };
299
+ walkAndCollectRefs(content, '');
300
+ return refs;
301
+ }
302
+ isJsonSchemaUrl(s) {
303
+ return (s.startsWith('http://') || s.startsWith('https://')) && s.includes('json-schema.org');
304
+ }
305
+ checkCompatibility(oldSchemaId, newSchemaId, _mode) {
306
+ const oldEntity = this.get(oldSchemaId);
307
+ const newEntity = this.get(newSchemaId);
308
+ if (!oldEntity || !newEntity) {
309
+ return {
310
+ from: oldSchemaId,
311
+ to: newSchemaId,
312
+ old: oldSchemaId,
313
+ new: newSchemaId,
314
+ direction: 'unknown',
315
+ added_properties: [],
316
+ removed_properties: [],
317
+ changed_properties: [],
318
+ is_fully_compatible: false,
319
+ is_backward_compatible: false,
320
+ is_forward_compatible: false,
321
+ incompatibility_reasons: [],
322
+ backward_errors: ['Schema not found'],
323
+ forward_errors: ['Schema not found'],
324
+ };
325
+ }
326
+ const oldSchema = oldEntity.content;
327
+ const newSchema = newEntity.content;
328
+ if (!oldSchema || !newSchema) {
329
+ return {
330
+ from: oldSchemaId,
331
+ to: newSchemaId,
332
+ old: oldSchemaId,
333
+ new: newSchemaId,
334
+ direction: 'unknown',
335
+ added_properties: [],
336
+ removed_properties: [],
337
+ changed_properties: [],
338
+ is_fully_compatible: false,
339
+ is_backward_compatible: false,
340
+ is_forward_compatible: false,
341
+ incompatibility_reasons: [],
342
+ backward_errors: ['Invalid schema content'],
343
+ forward_errors: ['Invalid schema content'],
344
+ };
345
+ }
346
+ // Check compatibility
347
+ const { isBackward, backwardErrors } = this.checkBackwardCompatibility(oldSchema, newSchema);
348
+ const { isForward, forwardErrors } = this.checkForwardCompatibility(oldSchema, newSchema);
349
+ // Determine direction
350
+ const direction = this.inferDirection(oldSchemaId, newSchemaId);
351
+ return {
352
+ from: oldSchemaId,
353
+ to: newSchemaId,
354
+ old: oldSchemaId,
355
+ new: newSchemaId,
356
+ direction,
357
+ added_properties: [],
358
+ removed_properties: [],
359
+ changed_properties: [],
360
+ is_fully_compatible: isBackward && isForward,
361
+ is_backward_compatible: isBackward,
362
+ is_forward_compatible: isForward,
363
+ incompatibility_reasons: [],
364
+ backward_errors: backwardErrors,
365
+ forward_errors: forwardErrors,
366
+ };
367
+ }
368
+ inferDirection(fromId, toId) {
369
+ try {
370
+ const fromGtsId = gts_1.Gts.parseGtsID(fromId);
371
+ const toGtsId = gts_1.Gts.parseGtsID(toId);
372
+ if (!fromGtsId.segments.length || !toGtsId.segments.length) {
373
+ return 'unknown';
374
+ }
375
+ const fromSeg = fromGtsId.segments[fromGtsId.segments.length - 1];
376
+ const toSeg = toGtsId.segments[toGtsId.segments.length - 1];
377
+ if (fromSeg.verMinor !== undefined && toSeg.verMinor !== undefined) {
378
+ if (toSeg.verMinor > fromSeg.verMinor) {
379
+ return 'up';
380
+ }
381
+ if (toSeg.verMinor < fromSeg.verMinor) {
382
+ return 'down';
383
+ }
384
+ return 'none';
385
+ }
386
+ return 'unknown';
387
+ }
388
+ catch {
389
+ return 'unknown';
390
+ }
391
+ }
392
+ checkBackwardCompatibility(oldSchema, newSchema) {
393
+ return this.checkSchemaCompatibility(oldSchema, newSchema, true);
394
+ }
395
+ checkForwardCompatibility(oldSchema, newSchema) {
396
+ return this.checkSchemaCompatibility(oldSchema, newSchema, false);
397
+ }
398
+ checkSchemaCompatibility(oldSchema, newSchema, checkBackward) {
399
+ const errors = [];
400
+ // Flatten schemas to handle allOf
401
+ const oldFlat = this.flattenSchema(oldSchema);
402
+ const newFlat = this.flattenSchema(newSchema);
403
+ const oldProps = oldFlat.properties || {};
404
+ const newProps = newFlat.properties || {};
405
+ const oldRequired = new Set(oldFlat.required || []);
406
+ const newRequired = new Set(newFlat.required || []);
407
+ // Check required properties changes
408
+ if (checkBackward) {
409
+ // Backward: cannot add required properties
410
+ const newlyRequired = Array.from(newRequired).filter((p) => !oldRequired.has(p));
411
+ if (newlyRequired.length > 0) {
412
+ errors.push(`Added required properties: ${newlyRequired.join(', ')}`);
413
+ }
414
+ }
415
+ else {
416
+ // Forward: cannot remove required properties
417
+ const removedRequired = Array.from(oldRequired).filter((p) => !newRequired.has(p));
418
+ if (removedRequired.length > 0) {
419
+ errors.push(`Removed required properties: ${removedRequired.join(', ')}`);
420
+ }
421
+ }
422
+ // Check properties that exist in both schemas
423
+ const commonProps = Object.keys(oldProps).filter((k) => k in newProps);
424
+ for (const prop of commonProps) {
425
+ const oldPropSchema = oldProps[prop] || {};
426
+ const newPropSchema = newProps[prop] || {};
427
+ // Check if type changed
428
+ const oldType = oldPropSchema.type;
429
+ const newType = newPropSchema.type;
430
+ if (oldType && newType && oldType !== newType) {
431
+ errors.push(`Property '${prop}' type changed from ${oldType} to ${newType}`);
432
+ }
433
+ // Check enum constraints
434
+ const oldEnum = oldPropSchema.enum || [];
435
+ const newEnum = newPropSchema.enum || [];
436
+ if (oldEnum.length > 0 && newEnum.length > 0) {
437
+ const oldEnumSet = new Set(oldEnum);
438
+ const newEnumSet = new Set(newEnum);
439
+ if (checkBackward) {
440
+ // Backward: cannot add enum values
441
+ const addedEnumValues = newEnum.filter((v) => !oldEnumSet.has(v));
442
+ if (addedEnumValues.length > 0) {
443
+ errors.push(`Property '${prop}' added enum values: ${addedEnumValues.join(', ')}`);
444
+ }
445
+ }
446
+ else {
447
+ // Forward: cannot remove enum values
448
+ const removedEnumValues = oldEnum.filter((v) => !newEnumSet.has(v));
449
+ if (removedEnumValues.length > 0) {
450
+ errors.push(`Property '${prop}' removed enum values: ${removedEnumValues.join(', ')}`);
451
+ }
452
+ }
453
+ }
454
+ // Check constraint compatibility
455
+ errors.push(...this.checkConstraintCompatibility(prop, oldPropSchema, newPropSchema, checkBackward));
456
+ // Recursively check nested object properties
457
+ if (oldType === 'object' && newType === 'object') {
458
+ const nestedResult = this.checkSchemaCompatibility(oldPropSchema, newPropSchema, checkBackward);
459
+ const nestedErrors = checkBackward ? nestedResult.backwardErrors : nestedResult.forwardErrors;
460
+ if (nestedErrors) {
461
+ errors.push(...nestedErrors.map((e) => `Property '${prop}': ${e}`));
462
+ }
463
+ }
464
+ // Recursively check array item schemas
465
+ if (oldType === 'array' && newType === 'array' && oldPropSchema.items && newPropSchema.items) {
466
+ const itemsResult = this.checkSchemaCompatibility(oldPropSchema.items, newPropSchema.items, checkBackward);
467
+ const itemsErrors = checkBackward ? itemsResult.backwardErrors : itemsResult.forwardErrors;
468
+ if (itemsErrors) {
469
+ errors.push(...itemsErrors.map((e) => `Property '${prop}' array items: ${e}`));
470
+ }
471
+ }
472
+ }
473
+ if (checkBackward) {
474
+ return { isBackward: errors.length === 0, backwardErrors: errors };
475
+ }
476
+ else {
477
+ return { isForward: errors.length === 0, forwardErrors: errors };
478
+ }
479
+ }
480
+ checkConstraintCompatibility(prop, oldPropSchema, newPropSchema, checkTightening) {
481
+ const errors = [];
482
+ const propType = oldPropSchema.type;
483
+ // Numeric constraints
484
+ if (propType === 'number' || propType === 'integer') {
485
+ errors.push(...this.checkMinMaxConstraint(prop, oldPropSchema, newPropSchema, 'minimum', 'maximum', checkTightening));
486
+ }
487
+ // String constraints
488
+ if (propType === 'string') {
489
+ errors.push(...this.checkMinMaxConstraint(prop, oldPropSchema, newPropSchema, 'minLength', 'maxLength', checkTightening));
490
+ }
491
+ // Array constraints
492
+ if (propType === 'array') {
493
+ errors.push(...this.checkMinMaxConstraint(prop, oldPropSchema, newPropSchema, 'minItems', 'maxItems', checkTightening));
494
+ }
495
+ return errors;
496
+ }
497
+ checkMinMaxConstraint(prop, oldSchema, newSchema, minKey, maxKey, checkTightening) {
498
+ const errors = [];
499
+ const oldMin = oldSchema[minKey];
500
+ const newMin = newSchema[minKey];
501
+ const oldMax = oldSchema[maxKey];
502
+ const newMax = newSchema[maxKey];
503
+ // Check minimum constraint
504
+ if (checkTightening) {
505
+ // Backward: cannot increase minimum (tighten)
506
+ if (oldMin !== undefined && newMin !== undefined && newMin > oldMin) {
507
+ errors.push(`Property '${prop}' ${minKey} increased from ${oldMin} to ${newMin}`);
508
+ }
509
+ else if (oldMin === undefined && newMin !== undefined) {
510
+ errors.push(`Property '${prop}' added ${minKey} constraint: ${newMin}`);
511
+ }
512
+ }
513
+ else {
514
+ // Forward: cannot decrease minimum (relax)
515
+ if (oldMin !== undefined && newMin !== undefined && newMin < oldMin) {
516
+ errors.push(`Property '${prop}' ${minKey} decreased from ${oldMin} to ${newMin}`);
517
+ }
518
+ else if (oldMin !== undefined && newMin === undefined) {
519
+ errors.push(`Property '${prop}' removed ${minKey} constraint`);
520
+ }
521
+ }
522
+ // Check maximum constraint
523
+ if (checkTightening) {
524
+ // Backward: cannot decrease maximum (tighten)
525
+ if (oldMax !== undefined && newMax !== undefined && newMax < oldMax) {
526
+ errors.push(`Property '${prop}' ${maxKey} decreased from ${oldMax} to ${newMax}`);
527
+ }
528
+ else if (oldMax === undefined && newMax !== undefined) {
529
+ errors.push(`Property '${prop}' added ${maxKey} constraint: ${newMax}`);
530
+ }
531
+ }
532
+ else {
533
+ // Forward: cannot increase maximum (relax)
534
+ if (oldMax !== undefined && newMax !== undefined && newMax > oldMax) {
535
+ errors.push(`Property '${prop}' ${maxKey} increased from ${oldMax} to ${newMax}`);
536
+ }
537
+ else if (oldMax !== undefined && newMax === undefined) {
538
+ errors.push(`Property '${prop}' removed ${maxKey} constraint`);
539
+ }
540
+ }
541
+ return errors;
542
+ }
543
+ flattenSchema(schema) {
544
+ const result = {
545
+ properties: {},
546
+ required: [],
547
+ };
548
+ // Merge allOf schemas
549
+ if (schema.allOf && Array.isArray(schema.allOf)) {
550
+ for (const subSchema of schema.allOf) {
551
+ const flattened = this.flattenSchema(subSchema);
552
+ // Merge properties
553
+ Object.assign(result.properties, flattened.properties || {});
554
+ // Merge required
555
+ if (flattened.required && Array.isArray(flattened.required)) {
556
+ result.required.push(...flattened.required);
557
+ }
558
+ // Preserve additionalProperties
559
+ if (flattened.additionalProperties !== undefined) {
560
+ result.additionalProperties = flattened.additionalProperties;
561
+ }
562
+ }
563
+ }
564
+ // Add direct properties
565
+ if (schema.properties) {
566
+ Object.assign(result.properties, schema.properties);
567
+ }
568
+ // Add direct required
569
+ if (schema.required && Array.isArray(schema.required)) {
570
+ result.required.push(...schema.required);
571
+ }
572
+ // Top level additionalProperties overrides
573
+ if (schema.additionalProperties !== undefined) {
574
+ result.additionalProperties = schema.additionalProperties;
575
+ }
576
+ return result;
577
+ }
578
+ castInstance(instanceId, toSchemaId) {
579
+ try {
580
+ // Get instance entity
581
+ const instanceEntity = this.get(instanceId);
582
+ if (!instanceEntity) {
583
+ return {
584
+ instance_id: instanceId,
585
+ to_schema_id: toSchemaId,
586
+ ok: false,
587
+ error: `Entity not found: ${instanceId}`,
588
+ };
589
+ }
590
+ // Get target schema
591
+ const toSchema = this.get(toSchemaId);
592
+ if (!toSchema) {
593
+ return {
594
+ instance_id: instanceId,
595
+ to_schema_id: toSchemaId,
596
+ ok: false,
597
+ error: `Schema not found: ${toSchemaId}`,
598
+ };
599
+ }
600
+ // Determine source schema
601
+ let fromSchemaId;
602
+ let fromSchema;
603
+ if (instanceEntity.isSchema) {
604
+ // Not allowed to cast directly from a schema
605
+ return {
606
+ instance_id: instanceId,
607
+ to_schema_id: toSchemaId,
608
+ ok: false,
609
+ error: 'Source must be an instance, not a schema',
610
+ };
611
+ }
612
+ else {
613
+ // Casting an instance - need to find its schema
614
+ fromSchemaId = instanceEntity.schemaId;
615
+ if (!fromSchemaId) {
616
+ return {
617
+ instance_id: instanceId,
618
+ to_schema_id: toSchemaId,
619
+ ok: false,
620
+ error: `Schema not found for instance: ${instanceId}`,
621
+ };
622
+ }
623
+ // Don't try to get a JSON Schema URL as a GTS entity
624
+ if (fromSchemaId.startsWith('http://') || fromSchemaId.startsWith('https://')) {
625
+ return {
626
+ instance_id: instanceId,
627
+ to_schema_id: toSchemaId,
628
+ ok: false,
629
+ error: `Cannot cast instance with schema ${fromSchemaId}`,
630
+ };
631
+ }
632
+ fromSchema = this.get(fromSchemaId);
633
+ if (!fromSchema) {
634
+ return {
635
+ instance_id: instanceId,
636
+ to_schema_id: toSchemaId,
637
+ ok: false,
638
+ error: `Schema not found: ${fromSchemaId}`,
639
+ };
640
+ }
641
+ }
642
+ // Get content
643
+ const instanceContent = instanceEntity.content;
644
+ const fromSchemaContent = fromSchema.content;
645
+ const toSchemaContent = toSchema.content;
646
+ // Perform the cast
647
+ return this.performCast(instanceId, toSchemaId, instanceContent, fromSchemaContent, toSchemaContent);
648
+ }
649
+ catch (error) {
650
+ return {
651
+ instance_id: instanceId,
652
+ to_schema_id: toSchemaId,
653
+ ok: false,
654
+ error: error instanceof Error ? error.message : String(error),
655
+ };
656
+ }
657
+ }
658
+ performCast(fromInstanceId, toSchemaId, fromInstanceContent, fromSchemaContent, toSchemaContent) {
659
+ // Flatten target schema to merge allOf
660
+ const targetSchema = this.flattenSchema(toSchemaContent);
661
+ // Determine direction
662
+ const direction = this.inferDirection(fromInstanceId, toSchemaId);
663
+ // Determine which is old/new based on direction
664
+ let oldSchema;
665
+ let newSchema;
666
+ switch (direction) {
667
+ case 'up':
668
+ oldSchema = fromSchemaContent;
669
+ newSchema = toSchemaContent;
670
+ break;
671
+ case 'down':
672
+ oldSchema = toSchemaContent;
673
+ newSchema = fromSchemaContent;
674
+ break;
675
+ default:
676
+ oldSchema = fromSchemaContent;
677
+ newSchema = toSchemaContent;
678
+ break;
679
+ }
680
+ // Check compatibility
681
+ const { isBackward, backwardErrors } = this.checkBackwardCompatibility(oldSchema, newSchema);
682
+ const { isForward, forwardErrors } = this.checkForwardCompatibility(oldSchema, newSchema);
683
+ // Apply casting rules to transform the instance
684
+ const { casted, added, removed, incompatibilityReasons } = this.castInstanceToSchema(this.deepCopy(fromInstanceContent), targetSchema, '');
685
+ // Validate the casted instance against the target schema
686
+ let isFullyCompatible = false;
687
+ if (casted) {
688
+ try {
689
+ const modifiedSchema = this.removeGtsConstConstraints(toSchemaContent);
690
+ const validate = this.ajv.compile(this.normalizeSchema(modifiedSchema));
691
+ const isValid = validate(casted);
692
+ if (!isValid) {
693
+ const errors = validate.errors?.map((e) => `${e.instancePath} ${e.message}`).join('; ') || 'Validation failed';
694
+ incompatibilityReasons.push(errors);
695
+ }
696
+ else {
697
+ isFullyCompatible = true;
698
+ }
699
+ }
700
+ catch (err) {
701
+ incompatibilityReasons.push(err instanceof Error ? err.message : String(err));
702
+ }
703
+ }
704
+ return {
705
+ from: fromInstanceId,
706
+ to: toSchemaId,
707
+ old: fromInstanceId,
708
+ new: toSchemaId,
709
+ direction,
710
+ added_properties: this.deduplicate(added),
711
+ removed_properties: this.deduplicate(removed),
712
+ changed_properties: [],
713
+ is_fully_compatible: isFullyCompatible,
714
+ is_backward_compatible: isBackward,
715
+ is_forward_compatible: isForward,
716
+ incompatibility_reasons: incompatibilityReasons,
717
+ backward_errors: backwardErrors,
718
+ forward_errors: forwardErrors,
719
+ casted_entity: casted,
720
+ instance_id: fromInstanceId,
721
+ to_schema_id: toSchemaId,
722
+ ok: isFullyCompatible,
723
+ error: isFullyCompatible ? '' : incompatibilityReasons.join('; '),
724
+ };
725
+ }
726
+ castInstanceToSchema(instance, schema, basePath) {
727
+ const added = [];
728
+ const removed = [];
729
+ const incompatibilityReasons = [];
730
+ if (!instance || typeof instance !== 'object' || Array.isArray(instance)) {
731
+ incompatibilityReasons.push('Instance must be an object for casting');
732
+ return { casted: null, added, removed, incompatibilityReasons };
733
+ }
734
+ const targetProps = schema.properties || {};
735
+ const required = new Set(schema.required || []);
736
+ const additional = schema.additionalProperties !== false;
737
+ // Start from current values
738
+ const result = this.deepCopy(instance);
739
+ // 1) Ensure required properties exist (fill defaults if provided)
740
+ for (const reqProp of Array.from(required)) {
741
+ if (!(reqProp in result)) {
742
+ const propSchema = targetProps[reqProp];
743
+ if (propSchema && propSchema.default !== undefined) {
744
+ result[reqProp] = this.deepCopy(propSchema.default);
745
+ const path = this.buildPath(basePath, reqProp);
746
+ added.push(path);
747
+ }
748
+ else {
749
+ const path = this.buildPath(basePath, reqProp);
750
+ incompatibilityReasons.push(`Missing required property '${path}' and no default is defined`);
751
+ }
752
+ }
753
+ }
754
+ // 2) For optional properties with defaults, set if missing
755
+ for (const [prop, propSchema] of Object.entries(targetProps)) {
756
+ if (required.has(prop)) {
757
+ continue;
758
+ }
759
+ if (!(prop in result)) {
760
+ const ps = propSchema;
761
+ if (ps.default !== undefined) {
762
+ result[prop] = this.deepCopy(ps.default);
763
+ const path = this.buildPath(basePath, prop);
764
+ added.push(path);
765
+ }
766
+ }
767
+ }
768
+ // 2.5) Update const values to match target schema (for GTS ID fields)
769
+ for (const [prop, propSchema] of Object.entries(targetProps)) {
770
+ const ps = propSchema;
771
+ if (ps.const !== undefined) {
772
+ const constVal = ps.const;
773
+ const existingVal = result[prop];
774
+ if (typeof constVal === 'string' && typeof existingVal === 'string') {
775
+ // Only update if both are GTS IDs and they differ
776
+ if (gts_1.Gts.isValidGtsID(constVal) && gts_1.Gts.isValidGtsID(existingVal)) {
777
+ if (existingVal !== constVal) {
778
+ result[prop] = constVal;
779
+ }
780
+ }
781
+ }
782
+ }
783
+ }
784
+ // 3) Remove properties not in target schema when additionalProperties is false
785
+ if (!additional) {
786
+ for (const prop of Object.keys(result)) {
787
+ if (!(prop in targetProps)) {
788
+ delete result[prop];
789
+ const path = this.buildPath(basePath, prop);
790
+ removed.push(path);
791
+ }
792
+ }
793
+ }
794
+ // 4) Recurse into nested object properties
795
+ for (const [prop, propSchema] of Object.entries(targetProps)) {
796
+ const val = result[prop];
797
+ if (val === undefined) {
798
+ continue;
799
+ }
800
+ const ps = propSchema;
801
+ const propType = ps.type;
802
+ // Handle nested objects
803
+ if (propType === 'object') {
804
+ if (val && typeof val === 'object' && !Array.isArray(val)) {
805
+ const nestedSchema = this.effectiveObjectSchema(ps);
806
+ const nestedResult = this.castInstanceToSchema(val, nestedSchema, this.buildPath(basePath, prop));
807
+ result[prop] = nestedResult.casted;
808
+ added.push(...nestedResult.added);
809
+ removed.push(...nestedResult.removed);
810
+ incompatibilityReasons.push(...nestedResult.incompatibilityReasons);
811
+ }
812
+ }
813
+ // Handle arrays of objects
814
+ if (propType === 'array') {
815
+ if (Array.isArray(val)) {
816
+ const itemsSchema = ps.items;
817
+ if (itemsSchema && itemsSchema.type === 'object') {
818
+ const nestedSchema = this.effectiveObjectSchema(itemsSchema);
819
+ const newList = [];
820
+ for (let idx = 0; idx < val.length; idx++) {
821
+ const item = val[idx];
822
+ if (item && typeof item === 'object' && !Array.isArray(item)) {
823
+ const nestedResult = this.castInstanceToSchema(item, nestedSchema, this.buildPath(basePath, `${prop}[${idx}]`));
824
+ newList.push(nestedResult.casted);
825
+ added.push(...nestedResult.added);
826
+ removed.push(...nestedResult.removed);
827
+ incompatibilityReasons.push(...nestedResult.incompatibilityReasons);
828
+ }
829
+ else {
830
+ newList.push(item);
831
+ }
832
+ }
833
+ result[prop] = newList;
834
+ }
835
+ }
836
+ }
837
+ }
838
+ return { casted: result, added, removed, incompatibilityReasons };
839
+ }
840
+ effectiveObjectSchema(schema) {
841
+ if (!schema) {
842
+ return {};
843
+ }
844
+ // If it has properties or required directly, use it
845
+ if (schema.properties || schema.required) {
846
+ return schema;
847
+ }
848
+ // Check allOf for object schemas
849
+ if (schema.allOf && Array.isArray(schema.allOf)) {
850
+ for (const part of schema.allOf) {
851
+ if (part.properties || part.required) {
852
+ return part;
853
+ }
854
+ }
855
+ }
856
+ return schema;
857
+ }
858
+ removeGtsConstConstraints(schema) {
859
+ if (schema === null || schema === undefined) {
860
+ return schema;
861
+ }
862
+ if (typeof schema === 'object' && !Array.isArray(schema)) {
863
+ const result = {};
864
+ for (const [key, value] of Object.entries(schema)) {
865
+ if (key === 'const') {
866
+ if (typeof value === 'string' && gts_1.Gts.isValidGtsID(value)) {
867
+ // Replace const with type constraint instead
868
+ result.type = 'string';
869
+ continue;
870
+ }
871
+ }
872
+ result[key] = this.removeGtsConstConstraints(value);
873
+ }
874
+ return result;
875
+ }
876
+ if (Array.isArray(schema)) {
877
+ return schema.map((item) => this.removeGtsConstConstraints(item));
878
+ }
879
+ return schema;
880
+ }
881
+ buildPath(base, prop) {
882
+ if (!base) {
883
+ return prop;
884
+ }
885
+ // Handle array indices that already have brackets
886
+ if (prop.startsWith('[')) {
887
+ return base + prop;
888
+ }
889
+ return base + '.' + prop;
890
+ }
891
+ deepCopy(obj) {
892
+ if (obj === null || obj === undefined) {
893
+ return obj;
894
+ }
895
+ if (typeof obj !== 'object') {
896
+ return obj;
897
+ }
898
+ if (Array.isArray(obj)) {
899
+ return obj.map((item) => this.deepCopy(item));
900
+ }
901
+ const result = {};
902
+ for (const [key, value] of Object.entries(obj)) {
903
+ result[key] = this.deepCopy(value);
904
+ }
905
+ return result;
906
+ }
907
+ deduplicate(arr) {
908
+ const unique = Array.from(new Set(arr));
909
+ return unique.sort();
910
+ }
911
+ getAttribute(gtsId, path) {
912
+ const entity = this.get(gtsId);
913
+ if (!entity) {
914
+ return {
915
+ gts_id: gtsId,
916
+ path,
917
+ resolved: false,
918
+ error: `Entity not found: ${gtsId}`,
919
+ };
920
+ }
921
+ const value = this.getNestedValue(entity.content, path);
922
+ return {
923
+ gts_id: gtsId,
924
+ path,
925
+ resolved: value !== undefined,
926
+ value,
927
+ };
928
+ }
929
+ getNestedValue(obj, path) {
930
+ // Split path by dots but handle array notation
931
+ const parts = [];
932
+ let current = '';
933
+ let inBracket = false;
934
+ for (let i = 0; i < path.length; i++) {
935
+ const char = path[i];
936
+ if (char === '[') {
937
+ if (current) {
938
+ parts.push(current);
939
+ current = '';
940
+ }
941
+ inBracket = true;
942
+ }
943
+ else if (char === ']') {
944
+ if (current) {
945
+ parts.push(`[${current}]`);
946
+ current = '';
947
+ }
948
+ inBracket = false;
949
+ }
950
+ else if (char === '.' && !inBracket) {
951
+ if (current) {
952
+ parts.push(current);
953
+ current = '';
954
+ }
955
+ }
956
+ else {
957
+ current += char;
958
+ }
959
+ }
960
+ if (current) {
961
+ parts.push(current);
962
+ }
963
+ let result = obj;
964
+ for (const part of parts) {
965
+ if (result === null || result === undefined) {
966
+ return undefined;
967
+ }
968
+ // Handle array index notation
969
+ if (part.startsWith('[') && part.endsWith(']')) {
970
+ const index = parseInt(part.slice(1, -1), 10);
971
+ if (Array.isArray(result) && !isNaN(index)) {
972
+ result = result[index];
973
+ }
974
+ else {
975
+ return undefined;
976
+ }
977
+ }
978
+ else {
979
+ // Regular property access
980
+ if (typeof result === 'object' && part in result) {
981
+ result = result[part];
982
+ }
983
+ else {
984
+ return undefined;
985
+ }
986
+ }
987
+ }
988
+ return result;
989
+ }
990
+ }
991
+ exports.GtsStore = GtsStore;
992
+ function createJsonEntity(content, _config) {
993
+ const extractResult = extract_1.GtsExtractor.extractID(content);
994
+ const references = new Set();
995
+ findReferences(content, references);
996
+ return {
997
+ id: extractResult.id,
998
+ schemaId: extractResult.schema_id,
999
+ content,
1000
+ isSchema: extractResult.is_schema,
1001
+ references,
1002
+ };
1003
+ }
1004
+ function findReferences(obj, refs, visited = new Set()) {
1005
+ if (!obj || typeof obj !== 'object' || visited.has(obj)) {
1006
+ return;
1007
+ }
1008
+ visited.add(obj);
1009
+ if ('$ref' in obj && typeof obj['$ref'] === 'string') {
1010
+ const ref = obj['$ref'];
1011
+ const normalized = ref.startsWith(types_1.GTS_URI_PREFIX) ? ref.substring(types_1.GTS_URI_PREFIX.length) : ref;
1012
+ if (gts_1.Gts.isValidGtsID(normalized)) {
1013
+ refs.add(normalized);
1014
+ }
1015
+ }
1016
+ if ('x-gts-ref' in obj && typeof obj['x-gts-ref'] === 'string') {
1017
+ const ref = obj['x-gts-ref'];
1018
+ if (gts_1.Gts.isValidGtsID(ref)) {
1019
+ refs.add(ref);
1020
+ }
1021
+ }
1022
+ for (const value of Object.values(obj)) {
1023
+ findReferences(value, refs, visited);
1024
+ }
1025
+ }
1026
+ //# sourceMappingURL=store.js.map