@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/src/extract.ts ADDED
@@ -0,0 +1,213 @@
1
+ import { ExtractResult, GTS_URI_PREFIX, GTS_PREFIX } from './types';
2
+ import { Gts } from './gts';
3
+
4
+ export interface GtsConfig {
5
+ entityIdFields: string[];
6
+ schemaIdFields: string[];
7
+ }
8
+
9
+ export function getDefaultConfig(): GtsConfig {
10
+ return {
11
+ entityIdFields: ['$id', '$$id', 'gtsId', 'gtsIid', 'gtsOid', 'gtsI', 'gts_id', 'gts_oid', 'gts_iid', 'id'],
12
+ schemaIdFields: [
13
+ '$schema',
14
+ '$$schema',
15
+ 'gtsTid',
16
+ 'gtsType',
17
+ 'gtsT',
18
+ 'gts_t',
19
+ 'gts_tid',
20
+ 'gts_type',
21
+ 'type',
22
+ 'schema',
23
+ ],
24
+ };
25
+ }
26
+
27
+ export class GtsExtractor {
28
+ private static normalizeValue(value: string, fieldName?: string): string {
29
+ let normalized = value.trim();
30
+
31
+ // Strip the "gts://" URI prefix for $id field (JSON Schema compatibility)
32
+ if (fieldName === '$id' && normalized.startsWith(GTS_URI_PREFIX)) {
33
+ normalized = normalized.substring(GTS_URI_PREFIX.length);
34
+ } else if (normalized.startsWith(GTS_URI_PREFIX)) {
35
+ normalized = normalized.substring(GTS_URI_PREFIX.length);
36
+ }
37
+
38
+ return normalized;
39
+ }
40
+
41
+ private static findFirstValidField(
42
+ content: any,
43
+ fields: string[],
44
+ requireValid: boolean = false
45
+ ): { field: string; value: string } | null {
46
+ if (typeof content !== 'object' || content === null) {
47
+ return null;
48
+ }
49
+
50
+ // Look for any field with a value
51
+ for (const field of fields) {
52
+ if (field in content && typeof content[field] === 'string') {
53
+ const value = this.normalizeValue(content[field], field);
54
+ if (value) {
55
+ // If requireValid is true, only return valid GTS IDs
56
+ if (requireValid) {
57
+ if (Gts.isValidGtsID(value)) {
58
+ return { field, value };
59
+ }
60
+ } else {
61
+ // Return any non-empty value
62
+ return { field, value };
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ return null;
69
+ }
70
+
71
+ private static isJsonSchema(content: any): boolean {
72
+ if (typeof content !== 'object' || content === null) {
73
+ return false;
74
+ }
75
+
76
+ // Check for JSON Schema meta-schema
77
+ // Issue #25: A document is a schema ONLY if $schema field is present
78
+ const schemaField = content['$schema'] || content['$$schema'];
79
+ if (typeof schemaField === 'string') {
80
+ // Standard JSON Schema meta-schema URLs
81
+ if (schemaField.includes('json-schema.org')) {
82
+ return true;
83
+ }
84
+ // GTS schema reference (ends with ~)
85
+ if (schemaField.startsWith(GTS_URI_PREFIX) || schemaField.startsWith(GTS_PREFIX)) {
86
+ return true;
87
+ }
88
+ }
89
+
90
+ return false;
91
+ }
92
+
93
+ static extractID(content: any, schemaContent?: any): ExtractResult {
94
+ const config = getDefaultConfig();
95
+ let id = '';
96
+ let schemaId: string | null = null;
97
+ let selectedEntityField: string | undefined;
98
+ let selectedSchemaIdField: string | undefined;
99
+ const isSchema = this.isJsonSchema(content);
100
+
101
+ if (typeof content === 'object' && content !== null) {
102
+ // Extract entity ID (look for any non-empty value, preferring valid GTS IDs)
103
+ const entityResult = this.findFirstValidField(content, config.entityIdFields);
104
+ if (entityResult) {
105
+ id = entityResult.value;
106
+ selectedEntityField = entityResult.field;
107
+ }
108
+
109
+ // Check if entity ID is a valid GTS ID
110
+ const isValidGtsId = id && Gts.isValidGtsID(id);
111
+
112
+ // An ID has a "chain" if there's a ~ somewhere in the middle (not just at the end)
113
+ // e.g., "gts.a.b.c.d.v1~x.y.z.w.v2" or "gts.a.b.c.d.v1~x.y.z.w.v2~" both have chains
114
+ // but "gts.a.b.c.d.v1~" does NOT have a chain (it's a base type)
115
+ const hasChain =
116
+ isValidGtsId &&
117
+ (() => {
118
+ // Find first ~
119
+ const firstTilde = id.indexOf('~');
120
+ if (firstTilde === -1) return false;
121
+ // Check if there's anything meaningful after the first ~
122
+ const afterTilde = id.substring(firstTilde + 1);
123
+ // If ends with ~, remove it for chain check
124
+ const checkPart = afterTilde.endsWith('~') ? afterTilde.slice(0, -1) : afterTilde;
125
+ return checkPart.length > 0;
126
+ })();
127
+
128
+ if (isSchema) {
129
+ // For schemas: extract schema_id based on rules
130
+ // Rule: For base schemas, schema_id is the $schema field value
131
+ // Rule: For derived schemas (chained $id), schema_id is the parent type from the chain
132
+ if (hasChain && id.endsWith('~')) {
133
+ // Derived schema - extract parent type from chain
134
+ // e.g., "gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.0~"
135
+ // -> schema_id = "gts.x.core.events.type.v1~"
136
+ const withoutTrailingTilde = id.slice(0, -1);
137
+ const lastTilde = withoutTrailingTilde.lastIndexOf('~');
138
+ if (lastTilde > 0) {
139
+ schemaId = id.substring(0, lastTilde + 1);
140
+ selectedSchemaIdField = selectedEntityField;
141
+ }
142
+ } else if (hasChain && !id.endsWith('~')) {
143
+ // Chained instance ID in schema (shouldn't happen, but handle it)
144
+ const lastTilde = id.lastIndexOf('~');
145
+ if (lastTilde > 0) {
146
+ schemaId = id.substring(0, lastTilde + 1);
147
+ selectedSchemaIdField = selectedEntityField;
148
+ }
149
+ } else {
150
+ // Base schema (single segment type or no $id) - use $schema field value
151
+ const schemaResult = this.findFirstValidField(content, ['$schema', '$$schema']);
152
+ if (schemaResult) {
153
+ schemaId = schemaResult.value;
154
+ selectedSchemaIdField = schemaResult.field;
155
+ }
156
+ }
157
+ } else {
158
+ // For instances (non-schemas):
159
+ // $id without $schema means the doc is an instance, NOT a schema
160
+ // Even if $id ends with ~, without $schema it's not treated as a schema
161
+ // So we should NOT derive schema_id from $id alone
162
+
163
+ // Skip $id for non-schemas - $id without $schema should not be used for schema_id
164
+ const isIdFromDollarId = selectedEntityField === '$id' || selectedEntityField === '$$id';
165
+
166
+ if (hasChain && !isIdFromDollarId) {
167
+ // Extract schema ID from chain (only if not from $id)
168
+ const lastTilde = id.lastIndexOf('~');
169
+ if (lastTilde > 0 && !id.endsWith('~')) {
170
+ schemaId = id.substring(0, lastTilde + 1);
171
+ selectedSchemaIdField = selectedEntityField;
172
+ }
173
+ }
174
+
175
+ if (schemaId === null && !isIdFromDollarId) {
176
+ // No chain or chain didn't provide schema_id - try explicit schema fields
177
+ // But don't use schema_id_fields that are $id variants (they were already checked above)
178
+ const explicitSchemaFields = config.schemaIdFields.filter((f) => f !== '$id' && f !== '$$id');
179
+ const schemaResult = this.findFirstValidField(content, explicitSchemaFields, true);
180
+ if (schemaResult) {
181
+ schemaId = schemaResult.value;
182
+ selectedSchemaIdField = schemaResult.field;
183
+ }
184
+ }
185
+
186
+ // If schema_id is still null, check regular chained ID (including from $id)
187
+ if (schemaId === null && hasChain && !id.endsWith('~')) {
188
+ const lastTilde = id.lastIndexOf('~');
189
+ if (lastTilde > 0) {
190
+ schemaId = id.substring(0, lastTilde + 1);
191
+ selectedSchemaIdField = selectedEntityField;
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ // Try to extract from schemaContent if provided and schemaId is still null
198
+ if (schemaId === null && schemaContent && typeof schemaContent === 'object') {
199
+ const schemaEntityResult = this.findFirstValidField(schemaContent, config.entityIdFields);
200
+ if (schemaEntityResult) {
201
+ schemaId = schemaEntityResult.value;
202
+ }
203
+ }
204
+
205
+ return {
206
+ id,
207
+ schema_id: schemaId,
208
+ selected_entity_field: selectedEntityField,
209
+ selected_schema_id_field: selectedSchemaIdField,
210
+ is_schema: isSchema,
211
+ };
212
+ }
213
+ }