@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.
- package/.gitattributes +10 -0
- package/NOTICE +15 -0
- package/README.md +14 -18
- package/dist/cast.js +2 -2
- package/dist/cast.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/compatibility.d.ts +3 -1
- package/dist/compatibility.d.ts.map +1 -1
- package/dist/compatibility.js +79 -70
- package/dist/compatibility.js.map +1 -1
- package/dist/server/server.d.ts +2 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +31 -0
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +7 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/store.d.ts +9 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +408 -0
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +14 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/x-gts-ref.d.ts +1 -0
- package/dist/x-gts-ref.d.ts.map +1 -1
- package/dist/x-gts-ref.js +65 -0
- package/dist/x-gts-ref.js.map +1 -1
- package/package.json +4 -5
- package/src/cast.ts +2 -2
- package/src/cli/index.ts +1 -1
- package/src/compatibility.ts +90 -72
- package/src/server/server.ts +44 -0
- package/src/server/types.ts +9 -0
- package/src/store.ts +450 -2
- package/src/types.ts +14 -6
- package/src/x-gts-ref.ts +63 -0
- package/tests/gts.test.ts +5 -3
package/src/compatibility.ts
CHANGED
|
@@ -7,10 +7,10 @@ export class GtsCompatibility {
|
|
|
7
7
|
store: GtsStore,
|
|
8
8
|
oldId: string,
|
|
9
9
|
newId: string,
|
|
10
|
-
|
|
10
|
+
_mode: 'backward' | 'forward' | 'full' = 'full'
|
|
11
11
|
): CompatibilityResult {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
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
|
-
|
|
24
|
-
return
|
|
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
|
-
|
|
29
|
-
return
|
|
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
|
-
|
|
34
|
-
return
|
|
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
|
-
|
|
39
|
-
return
|
|
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
|
-
|
|
46
|
-
|
|
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 (
|
|
50
|
-
|
|
88
|
+
if (!fromGtsId.segments.length || !toGtsId.segments.length) {
|
|
89
|
+
return 'unknown';
|
|
51
90
|
}
|
|
52
91
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
96
|
-
warnings.push(`Optional property '${propName}' removed in new schema`);
|
|
117
|
+
compatible = false;
|
|
97
118
|
}
|
|
98
119
|
} else {
|
|
99
|
-
this.checkPropertyCompatibility(propName,
|
|
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[]
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
121
|
-
warnings.push(`New optional property '${propName}' added`);
|
|
140
|
+
compatible = false;
|
|
122
141
|
}
|
|
123
142
|
} else {
|
|
124
|
-
this.checkPropertyCompatibility(propName, oldProps[propName],
|
|
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
|
-
):
|
|
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 {
|
package/src/server/server.ts
CHANGED
|
@@ -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 {
|
package/src/server/types.ts
CHANGED