@globaltypesystem/gts-ts 0.1.0 → 0.2.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.
@@ -7,10 +7,10 @@ export class GtsCompatibility {
7
7
  store: GtsStore,
8
8
  oldId: string,
9
9
  newId: string,
10
- mode: 'backward' | 'forward' | 'full' = 'full'
10
+ _mode: 'backward' | 'forward' | 'full' = 'full'
11
11
  ): CompatibilityResult {
12
- const errors: string[] = [];
13
- const warnings: string[] = [];
12
+ const backwardErrors: string[] = [];
13
+ const forwardErrors: string[] = [];
14
14
 
15
15
  try {
16
16
  const oldGtsId = Gts.parseGtsID(oldId);
@@ -20,110 +20,133 @@ export class GtsCompatibility {
20
20
  const newEntity = store.get(newGtsId.id);
21
21
 
22
22
  if (!oldEntity) {
23
- errors.push(`Old schema not found: ${oldId}`);
24
- return { compatible: false, oldId, newId, mode, errors, warnings };
23
+ backwardErrors.push(`Old schema not found: ${oldId}`);
24
+ return this.buildResult(oldId, newId, false, false, false, backwardErrors, forwardErrors);
25
25
  }
26
26
 
27
27
  if (!newEntity) {
28
- errors.push(`New schema not found: ${newId}`);
29
- return { compatible: false, oldId, newId, mode, errors, warnings };
28
+ backwardErrors.push(`New schema not found: ${newId}`);
29
+ return this.buildResult(oldId, newId, false, false, false, backwardErrors, forwardErrors);
30
30
  }
31
31
 
32
32
  if (!oldEntity.isSchema) {
33
- errors.push(`Old entity is not a schema: ${oldId}`);
34
- return { compatible: false, oldId, newId, mode, errors, warnings };
33
+ backwardErrors.push(`Old entity is not a schema: ${oldId}`);
34
+ return this.buildResult(oldId, newId, false, false, false, backwardErrors, forwardErrors);
35
35
  }
36
36
 
37
37
  if (!newEntity.isSchema) {
38
- errors.push(`New entity is not a schema: ${newId}`);
39
- return { compatible: false, oldId, newId, mode, errors, warnings };
38
+ backwardErrors.push(`New entity is not a schema: ${newId}`);
39
+ return this.buildResult(oldId, newId, false, false, false, backwardErrors, forwardErrors);
40
40
  }
41
41
 
42
42
  const oldSchema = oldEntity.content;
43
43
  const newSchema = newEntity.content;
44
44
 
45
- if (mode === 'backward' || mode === 'full') {
46
- this.checkBackwardCompatibility(oldSchema, newSchema, errors, warnings);
47
- }
45
+ const isBackward = this.checkBackwardCompatibility(oldSchema, newSchema, backwardErrors);
46
+ const isForward = this.checkForwardCompatibility(oldSchema, newSchema, forwardErrors);
47
+ const isFullyCompatible = isBackward && isForward;
48
+
49
+ return this.buildResult(oldId, newId, isFullyCompatible, isBackward, isForward, backwardErrors, forwardErrors);
50
+ } catch (error) {
51
+ backwardErrors.push(error instanceof Error ? error.message : String(error));
52
+ return this.buildResult(oldId, newId, false, false, false, backwardErrors, forwardErrors);
53
+ }
54
+ }
55
+
56
+ private static buildResult(
57
+ oldId: string,
58
+ newId: string,
59
+ isFullyCompatible: boolean,
60
+ isBackward: boolean,
61
+ isForward: boolean,
62
+ backwardErrors: string[],
63
+ forwardErrors: string[]
64
+ ): CompatibilityResult {
65
+ return {
66
+ from: oldId,
67
+ to: newId,
68
+ old: oldId,
69
+ new: newId,
70
+ direction: this.inferDirection(oldId, newId),
71
+ added_properties: [],
72
+ removed_properties: [],
73
+ changed_properties: [],
74
+ is_fully_compatible: isFullyCompatible,
75
+ is_backward_compatible: isBackward,
76
+ is_forward_compatible: isForward,
77
+ incompatibility_reasons: [...backwardErrors, ...forwardErrors],
78
+ backward_errors: backwardErrors,
79
+ forward_errors: forwardErrors,
80
+ };
81
+ }
82
+
83
+ private static inferDirection(fromId: string, toId: string): string {
84
+ try {
85
+ const fromGtsId = Gts.parseGtsID(fromId);
86
+ const toGtsId = Gts.parseGtsID(toId);
48
87
 
49
- if (mode === 'forward' || mode === 'full') {
50
- this.checkForwardCompatibility(oldSchema, newSchema, errors, warnings);
88
+ if (!fromGtsId.segments.length || !toGtsId.segments.length) {
89
+ return 'unknown';
51
90
  }
52
91
 
53
- return {
54
- compatible: errors.length === 0,
55
- oldId,
56
- newId,
57
- mode,
58
- errors,
59
- warnings,
60
- };
61
- } catch (error) {
62
- errors.push(error instanceof Error ? error.message : String(error));
63
- return {
64
- compatible: false,
65
- oldId,
66
- newId,
67
- mode,
68
- errors,
69
- warnings,
70
- };
92
+ const fromSeg = fromGtsId.segments[fromGtsId.segments.length - 1];
93
+ const toSeg = toGtsId.segments[toGtsId.segments.length - 1];
94
+
95
+ if (fromSeg.verMajor < toSeg.verMajor) return 'upgrade';
96
+ if (fromSeg.verMajor > toSeg.verMajor) return 'downgrade';
97
+ if ((fromSeg.verMinor || 0) < (toSeg.verMinor || 0)) return 'upgrade';
98
+ if ((fromSeg.verMinor || 0) > (toSeg.verMinor || 0)) return 'downgrade';
99
+
100
+ return 'same';
101
+ } catch {
102
+ return 'unknown';
71
103
  }
72
104
  }
73
105
 
74
- private static checkBackwardCompatibility(
75
- oldSchema: any,
76
- newSchema: any,
77
- errors: string[],
78
- warnings: string[]
79
- ): void {
106
+ private static checkBackwardCompatibility(oldSchema: any, newSchema: any, errors: string[]): boolean {
80
107
  const oldProps = oldSchema.properties || {};
81
108
  const newProps = newSchema.properties || {};
82
109
  const oldRequired = new Set(oldSchema.required || []);
83
- const newRequired = new Set(newSchema.required || []);
84
110
 
85
- for (const prop of oldRequired) {
86
- if (!newRequired.has(prop)) {
87
- warnings.push(`Property '${prop}' is no longer required in new schema`);
88
- }
89
- }
111
+ let compatible = true;
90
112
 
91
- for (const [propName, propSchema] of Object.entries(oldProps)) {
113
+ for (const propName of Object.keys(oldProps)) {
92
114
  if (!(propName in newProps)) {
93
115
  if (oldRequired.has(propName)) {
94
116
  errors.push(`Required property '${propName}' removed in new schema`);
95
- } else {
96
- warnings.push(`Optional property '${propName}' removed in new schema`);
117
+ compatible = false;
97
118
  }
98
119
  } else {
99
- this.checkPropertyCompatibility(propName, propSchema, newProps[propName], errors, warnings, 'backward');
120
+ if (!this.checkPropertyCompatibility(propName, oldProps[propName], newProps[propName], errors, 'backward')) {
121
+ compatible = false;
122
+ }
100
123
  }
101
124
  }
125
+
126
+ return compatible;
102
127
  }
103
128
 
104
- private static checkForwardCompatibility(oldSchema: any, newSchema: any, errors: string[], warnings: string[]): void {
129
+ private static checkForwardCompatibility(oldSchema: any, newSchema: any, errors: string[]): boolean {
105
130
  const oldProps = oldSchema.properties || {};
106
131
  const newProps = newSchema.properties || {};
107
- const oldRequired = new Set(oldSchema.required || []);
108
132
  const newRequired = new Set(newSchema.required || []);
109
133
 
110
- for (const prop of newRequired) {
111
- if (!oldRequired.has(prop)) {
112
- errors.push(`New required property '${prop}' added in new schema`);
113
- }
114
- }
134
+ let compatible = true;
115
135
 
116
- for (const [propName, propSchema] of Object.entries(newProps)) {
136
+ for (const propName of Object.keys(newProps)) {
117
137
  if (!(propName in oldProps)) {
118
138
  if (newRequired.has(propName)) {
119
139
  errors.push(`New required property '${propName}' added`);
120
- } else {
121
- warnings.push(`New optional property '${propName}' added`);
140
+ compatible = false;
122
141
  }
123
142
  } else {
124
- this.checkPropertyCompatibility(propName, oldProps[propName], propSchema, errors, warnings, 'forward');
143
+ if (!this.checkPropertyCompatibility(propName, oldProps[propName], newProps[propName], errors, 'forward')) {
144
+ compatible = false;
145
+ }
125
146
  }
126
147
  }
148
+
149
+ return compatible;
127
150
  }
128
151
 
129
152
  private static checkPropertyCompatibility(
@@ -131,17 +154,15 @@ export class GtsCompatibility {
131
154
  oldProp: any,
132
155
  newProp: any,
133
156
  errors: string[],
134
- warnings: string[],
135
157
  direction: 'backward' | 'forward'
136
- ): void {
158
+ ): boolean {
137
159
  const oldType = this.normalizeType(oldProp.type);
138
160
  const newType = this.normalizeType(newProp.type);
139
161
 
140
162
  if (oldType !== newType) {
141
- if (this.areTypesCompatible(oldType, newType, direction)) {
142
- warnings.push(`Property '${propName}' type changed from ${oldType} to ${newType}`);
143
- } else {
163
+ if (!this.areTypesCompatible(oldType, newType, direction)) {
144
164
  errors.push(`Property '${propName}' type incompatibly changed from ${oldType} to ${newType}`);
165
+ return false;
145
166
  }
146
167
  }
147
168
 
@@ -153,16 +174,13 @@ export class GtsCompatibility {
153
174
  for (const value of oldEnum) {
154
175
  if (!newEnum.has(value)) {
155
176
  errors.push(`Enum value '${value}' removed from property '${propName}'`);
156
- }
157
- }
158
- } else {
159
- for (const value of newEnum) {
160
- if (!oldEnum.has(value)) {
161
- warnings.push(`Enum value '${value}' added to property '${propName}'`);
177
+ return false;
162
178
  }
163
179
  }
164
180
  }
165
181
  }
182
+
183
+ return true;
166
184
  }
167
185
 
168
186
  private static normalizeType(type: any): string {
@@ -13,6 +13,8 @@ import {
13
13
  CompatibilityParams,
14
14
  CastBody,
15
15
  QueryParams,
16
+ ValidateSchemaBody,
17
+ ValidateEntityBody,
16
18
  } from './types';
17
19
  import * as gts from '../index';
18
20
 
@@ -99,6 +101,12 @@ export class GtsServer {
99
101
  // OP#11 - Attribute Access
100
102
  this.fastify.get('/attr', this.handleAttribute.bind(this));
101
103
 
104
+ // OP#12 - Validate Schema
105
+ this.fastify.post('/validate-schema', this.handleValidateSchema.bind(this));
106
+
107
+ // OP#12 - Validate Entity (unified)
108
+ this.fastify.post('/validate-entity', this.handleValidateEntity.bind(this));
109
+
102
110
  // OpenAPI spec
103
111
  this.fastify.get('/openapi', this.handleOpenAPI.bind(this));
104
112
  }
@@ -584,6 +592,42 @@ export class GtsServer {
584
592
  return this.store['store'].getAttribute(gtsId, path);
585
593
  }
586
594
 
595
+ // OP#12 - Validate Schema
596
+ private async handleValidateSchema(
597
+ request: FastifyRequest<{ Body: ValidateSchemaBody }>,
598
+ _reply: FastifyReply
599
+ ): Promise<any> {
600
+ const { schema_id } = request.body;
601
+ if (!schema_id) {
602
+ return { ok: false, error: 'Missing required field: schema_id' };
603
+ }
604
+ return this.store['store'].validateSchemaAgainstParent(schema_id);
605
+ }
606
+
607
+ // OP#12 - Validate Entity (unified)
608
+ private async handleValidateEntity(
609
+ request: FastifyRequest<{ Body: ValidateEntityBody }>,
610
+ _reply: FastifyReply
611
+ ): Promise<any> {
612
+ const id = request.body.entity_id || request.body.gts_id;
613
+ if (!id) {
614
+ return { ok: false, error: 'Missing required field: entity_id or gts_id' };
615
+ }
616
+
617
+ const entity = this.store['store'].get(id);
618
+ if (!entity) {
619
+ return { ok: false, error: `Entity not found: ${id}` };
620
+ }
621
+
622
+ if (entity.isSchema) {
623
+ const result = this.store['store'].validateSchemaAgainstParent(id);
624
+ return { ...result, entity_type: 'schema' };
625
+ } else {
626
+ const result = this.store.validateInstance(id);
627
+ return { ...result, entity_type: 'instance' };
628
+ }
629
+ }
630
+
587
631
  // OpenAPI Specification
588
632
  private async handleOpenAPI(): Promise<any> {
589
633
  return {
@@ -72,3 +72,12 @@ export interface AttributeParams {
72
72
  gts_id: string;
73
73
  path: string;
74
74
  }
75
+
76
+ export interface ValidateSchemaBody {
77
+ schema_id: string;
78
+ }
79
+
80
+ export interface ValidateEntityBody {
81
+ entity_id?: string;
82
+ gts_id?: string;
83
+ }