@themeparks/typelib 1.0.4 → 1.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.
@@ -0,0 +1,437 @@
1
+ // Script to generate TypeScript interface files from JSON schemas
2
+
3
+ import { promises as fs } from 'fs';
4
+ import { join } from 'path';
5
+
6
+ // Default directories - can be overridden in generateTypes()
7
+ const DEFAULT_SCHEMA_DIRS = ['./typesrc'];
8
+ const DEFAULT_OUTPUT_DIR = './src/types';
9
+ const FILE_HEADER = '// THIS FILE IS GENERATED - DO NOT EDIT DIRECTLY\n\n';
10
+
11
+ // Simple logger implementation to replace missing './logger.js'
12
+ const log = (...args: any[]) => console.log('[TypeGenerator 📜]', ...args);
13
+ const error = (...args: any[]) => console.error('[TypeGenerator 📜]', ...args);
14
+
15
+ interface JSONSchema {
16
+ $schema?: string;
17
+ title?: string;
18
+ type?: string | string[];
19
+ properties?: {
20
+ [key: string]: JSONSchema;
21
+ };
22
+ items?: JSONSchema;
23
+ required?: string[];
24
+ description?: string;
25
+ $ref?: string;
26
+ [key: string]: any; // Allow string indexing for reference lookup
27
+ }
28
+
29
+ interface TypeRegistry {
30
+ [typeName: string]: {
31
+ sourceFile: string;
32
+ schema: JSONSchema;
33
+ }
34
+ }
35
+
36
+ interface ImportTracker {
37
+ imports: Map<string, Set<string>>; // Map<sourceFile, Set<typeName>>
38
+ referencePath: string[];
39
+ }
40
+
41
+ async function buildTypeRegistry(schemaDirs: string[]): Promise<TypeRegistry> {
42
+ const registry: TypeRegistry = {};
43
+
44
+ // Process all schema directories
45
+ for (const schemaDir of schemaDirs) {
46
+ try {
47
+ const files = await fs.readdir(schemaDir);
48
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
49
+
50
+ for (const file of jsonFiles) {
51
+ const schemaPath = join(schemaDir, file);
52
+ const schemaContent = await fs.readFile(schemaPath, 'utf-8');
53
+ const schema: JSONSchema = JSON.parse(schemaContent);
54
+
55
+ // Register all top-level types from this file
56
+ for (const [name, typeSchema] of Object.entries(schema.properties || {})) {
57
+ // If type already exists, log a warning but allow override (later directories take precedence)
58
+ if (registry[name]) {
59
+ log(`Warning: Type "${name}" already exists, overriding with version from ${schemaPath}`);
60
+ }
61
+ registry[name] = {
62
+ sourceFile: file.replace('.json', ''),
63
+ schema: typeSchema
64
+ };
65
+ }
66
+ }
67
+ } catch (err) {
68
+ // Skip directories that don't exist or can't be read
69
+ log(`Skipping schema directory ${schemaDir}: ${(err as Error).message}`);
70
+ }
71
+ }
72
+
73
+ return registry;
74
+ }
75
+
76
+ function resolveReference(ref: string, schema: JSONSchema, registry: TypeRegistry, tracker: ImportTracker): { typeName: string; sourceFile?: string } {
77
+ const refPath = ref.split('/').slice(1);
78
+
79
+ // Check if we're in a circular reference
80
+ if (tracker.referencePath.includes(ref)) {
81
+ throw new Error(`Circular reference detected: ${tracker.referencePath.join(' -> ')} -> ${ref}`);
82
+ }
83
+ tracker.referencePath.push(ref);
84
+
85
+ try {
86
+ // First try to resolve in current schema
87
+ let referenced: JSONSchema = schema;
88
+ for (const segment of refPath) {
89
+ if (segment === 'properties' && referenced.properties) {
90
+ referenced = referenced.properties;
91
+ } else if (!referenced[segment]) {
92
+ // If segment not found, try alternate paths
93
+ if (segment === 'definitions' && ref.startsWith('#/definitions/')) {
94
+ // Handle #/definitions/* by looking in properties instead
95
+ const typeName = refPath[refPath.length - 1];
96
+ if (schema.properties?.[typeName]) {
97
+ tracker.referencePath.pop();
98
+ return { typeName };
99
+ }
100
+ }
101
+
102
+ // If still not found, check registry
103
+ const registryType = registry[segment];
104
+ if (registryType) {
105
+ if (!tracker.imports.has(registryType.sourceFile)) {
106
+ tracker.imports.set(registryType.sourceFile, new Set());
107
+ }
108
+ tracker.imports.get(registryType.sourceFile)!.add(segment);
109
+ tracker.referencePath.pop();
110
+ return { typeName: segment, sourceFile: registryType.sourceFile };
111
+ }
112
+ throw new Error(`Invalid reference path: ${ref}`);
113
+ } else {
114
+ referenced = referenced[segment];
115
+ }
116
+ }
117
+
118
+ // If we're referencing a property definition, get its type
119
+ const typeName = refPath[refPath.length - 1];
120
+ tracker.referencePath.pop();
121
+ return { typeName };
122
+
123
+ } catch (err) {
124
+ tracker.referencePath.pop();
125
+ throw err;
126
+ }
127
+ }
128
+
129
+ function getTypeFromSchema(schema: JSONSchema, rootSchema: JSONSchema, registry: TypeRegistry, tracker: ImportTracker, typeName?: string): string {
130
+ if (schema.enum && Array.isArray(schema.enum)) {
131
+ // Handle enums specially - create union type of string literals
132
+ return schema.enum.map(value => `'${value}'`).join(' | ');
133
+ }
134
+
135
+ if (schema.oneOf || schema.anyOf) {
136
+ // Handle oneOf/anyOf - create union type
137
+ const variants = schema.oneOf || schema.anyOf;
138
+ if (variants && Array.isArray(variants)) {
139
+ const types = variants.map(variant => getTypeFromSchema(variant, rootSchema, registry, tracker));
140
+ return types.join(' | ');
141
+ }
142
+ }
143
+
144
+ if (schema.$ref) {
145
+ try {
146
+ const resolved = resolveReference(schema.$ref, rootSchema, registry, tracker);
147
+ return resolved.typeName;
148
+ } catch (err) {
149
+ error(`Error resolving reference: ${schema.$ref}`, err);
150
+ return 'any';
151
+ }
152
+ }
153
+
154
+ if (Array.isArray(schema.type)) {
155
+ // Handle union types like ["string", "null"]
156
+ return schema.type.map((t: string) => t === 'null' ? 'null' : t).join(' | ');
157
+ }
158
+
159
+ switch (schema.type) {
160
+ case 'string':
161
+ return schema.nullable ? 'string | null' : 'string';
162
+ case 'number':
163
+ return schema.nullable ? 'number | null' : 'number';
164
+ case 'integer':
165
+ return schema.nullable ? 'number | null' : 'number';
166
+ case 'boolean':
167
+ return schema.nullable ? 'boolean | null' : 'boolean';
168
+ case 'array': {
169
+ let arrayType = schema.items
170
+ ? `${getTypeFromSchema(schema.items, rootSchema, registry, tracker)}[]`
171
+ : 'any[]';
172
+ // Handle nullable arrays
173
+ if (schema.nullable) {
174
+ arrayType += ' | null';
175
+ }
176
+ return arrayType;
177
+ }
178
+ case 'object': {
179
+ if (!schema.properties) {
180
+ // Check if propertyNames constraint exists
181
+ let keyType = 'string';
182
+ if (schema.propertyNames?.$ref) {
183
+ const resolved = resolveReference(schema.propertyNames.$ref, rootSchema, registry, tracker);
184
+ keyType = resolved.typeName;
185
+ }
186
+
187
+ // Check if additionalProperties has a type constraint
188
+ let valueType = 'any';
189
+ if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
190
+ valueType = getTypeFromSchema(schema.additionalProperties, rootSchema, registry, tracker);
191
+ }
192
+
193
+ // Use Partial for propertyNames constraints to allow partial translations
194
+ const recordType = schema.propertyNames?.$ref
195
+ ? `Partial<Record<${keyType}, ${valueType}>>`
196
+ : `Record<${keyType}, ${valueType}>`;
197
+ if (typeName) {
198
+ return `extends ${recordType} {}`;
199
+ }
200
+ return schema.nullable ? `${recordType} | null` : recordType;
201
+ }
202
+ const props: string[] = [];
203
+ for (const [propName, propSchema] of Object.entries(schema.properties || {})) {
204
+ const isRequired = schema.required?.includes(propName);
205
+ const propType = getTypeFromSchema(propSchema, rootSchema, registry, tracker);
206
+ const description = propSchema.description
207
+ ? `\n /** ${propSchema.description} */\n `
208
+ : '';
209
+ props.push(`${description}${propName}${isRequired ? '' : '?'}: ${propType};`);
210
+ }
211
+ const objType = `{\n ${props.join('\n ')}\n}`;
212
+ return schema.nullable ? `(${objType}) | null` : objType;
213
+ }
214
+ default:
215
+ return 'any';
216
+ }
217
+ }
218
+
219
+ function generateRuntimeSchemaExport(schema: JSONSchema, typeRegistryImport: string): string {
220
+ // Generate the runtime schema registration code
221
+ let output = '\n// Runtime Schema Registration\n';
222
+ output += `import { registerTypeSchema } from "${typeRegistryImport}";\n\n`;
223
+
224
+ // For each top-level type, register its schema
225
+ for (const [name, typeSchema] of Object.entries(schema.properties || {})) {
226
+ // remove any properties that have "__private" set to true. Recusively.
227
+ const cleanSchema = JSON.parse(JSON.stringify(typeSchema, (key, value) => {
228
+ // if any value is an object with "__private" set to true, remove it
229
+ if (value && typeof value === 'object' && value.__private) {
230
+ return undefined; // Remove this property
231
+ }
232
+ return value;
233
+ }));
234
+ output += `registerTypeSchema("${name}", ${JSON.stringify(cleanSchema, null, 2)});\n\n`;
235
+ }
236
+
237
+ return output;
238
+ }
239
+
240
+ async function generateTypeFile(schemaPath: string, registry: TypeRegistry, outputDir: string, typeRegistryImport: string): Promise<void> {
241
+ log(`Generating types for schema: ${schemaPath}`);
242
+ const schemaContent = await fs.readFile(schemaPath, 'utf-8');
243
+ const schema: JSONSchema = JSON.parse(schemaContent);
244
+
245
+ let output = FILE_HEADER;
246
+
247
+ // Track imports needed for this file
248
+ const tracker: ImportTracker = {
249
+ imports: new Map<string, Set<string>>(),
250
+ referencePath: []
251
+ };
252
+
253
+ // Generate interfaces and types for top-level properties
254
+ for (const [name, typeSchema] of Object.entries(schema.properties || {})) {
255
+ const description = typeSchema.description
256
+ ? `/** ${typeSchema.description} */\n`
257
+ : '';
258
+
259
+ if (typeSchema.type === 'object' && typeSchema.properties) {
260
+ // Handle objects with const values (like HttpStatusCode)
261
+ const hasConstValues = Object.entries(typeSchema.properties).some(([_, prop]) => prop.const !== undefined);
262
+ if (hasConstValues) {
263
+ output += `export enum ${name}Enum {\n`;
264
+ Object.entries(typeSchema.properties).forEach(([key, prop]) => {
265
+ output += ` ${key} = ${prop.const},\n`;
266
+ });
267
+ output += `}\n\n`;
268
+ output += `${description}export type ${name} = keyof typeof ${name}Enum;\n\n`;
269
+ continue;
270
+ }
271
+ }
272
+
273
+ if (typeSchema.oneOf || typeSchema.anyOf) {
274
+ // Handle oneOf/anyOf - create type alias with union
275
+ const typeStr = getTypeFromSchema(typeSchema, schema, registry, tracker);
276
+ output += `${description}export type ${name} = ${typeStr};\n\n`;
277
+ } else if (typeSchema.enum && Array.isArray(typeSchema.enum)) {
278
+ // generate a native enum for enum types
279
+ output += `export enum ${name}Enum {\n`;
280
+ typeSchema.enum.forEach((value: string) => {
281
+ output += ` "${value}" = '${value}',\n`;
282
+ });
283
+ output += `}\n\n`;
284
+
285
+ // Also generate enum type
286
+ output += `${description}export type ${name} = keyof typeof ${name}Enum;\n\n`;
287
+
288
+ // Also generate a StringTo${name} function
289
+ output += `// Function to convert string to ${name}Enum\n`;
290
+ output += `export function StringTo${name}(value: string): ${name}Enum {\n`;
291
+ output += ` const lowerValue = value.toLowerCase();\n`;
292
+ output += ` switch (lowerValue) {\n`;
293
+ typeSchema.enum.forEach((value: string) => {
294
+ if (value) {
295
+ output += ` case '${value.toLowerCase()}':\n`;
296
+ if (value.includes(' ') || value.includes('-')) {
297
+ // Handle spaces and hyphens in enum values
298
+ output += ` return ${name}Enum["${value}"];\n`;
299
+ } else {
300
+ output += ` return ${name}Enum.${value};\n`;
301
+ }
302
+ }
303
+ });
304
+ output += ` }\n`;
305
+ output += ` throw new Error('Unknown ${name} value: ' + value);\n`;
306
+ output += `}\n\n`;
307
+ } else if (typeSchema.type === 'object' || typeSchema.allOf) {
308
+ // Handle inheritance with allOf
309
+ if (typeSchema.allOf) {
310
+ const baseType = typeSchema.allOf.find((t: JSONSchema) => t.$ref)?.$ref?.split('/').pop();
311
+ const interfaceContent = typeSchema.properties?.data
312
+ ? getTypeFromSchema(typeSchema.properties.data, schema, registry, tracker)
313
+ : '{}';
314
+
315
+ if (baseType === 'AdminResponse') {
316
+ // For types extending AdminResponse, use the data type as generic parameter
317
+ output += `${description}export interface ${name} extends ${baseType}<${interfaceContent}> {}\n\n`;
318
+ } else if (baseType) {
319
+ output += `${description}export interface ${name} extends ${baseType} ${getTypeFromSchema(typeSchema, schema, registry, tracker)}\n\n`;
320
+ } else {
321
+ output += `${description}export interface ${name} ${getTypeFromSchema(typeSchema, schema, registry, tracker, name)}\n\n`;
322
+ }
323
+ } else {
324
+ // Generate regular interface type
325
+ if (name === 'AdminResponse') {
326
+ // Make AdminResponse generic with data type as parameter
327
+ output += `${description}export interface ${name}<T = any> {\n success: boolean;\n data: T;\n}\n\n`;
328
+ } else {
329
+ const typeStr = getTypeFromSchema(typeSchema, schema, registry, tracker, name);
330
+ if (typeStr.startsWith('extends')) {
331
+ output += `${description}export interface ${name} ${typeStr}\n\n`;
332
+ } else {
333
+ output += `${description}export type ${name} = ${typeStr}\n\n`;
334
+ }
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ // Add imports
341
+ const currentFile = schemaPath.split(/[\/\\]/).pop()!.replace('.json', '');
342
+ const imports = Array.from(tracker.imports.entries())
343
+ .filter(([file]) => file !== currentFile)
344
+ .map(([file, typeNames]) => `import { ${Array.from(typeNames).join(', ')} } from './${file}.types.js';`);
345
+
346
+ if (imports.length > 0) {
347
+ // Replace header with imports and re-add header once
348
+ output = output.replace(FILE_HEADER, FILE_HEADER + imports.join('\n') + '\n\n');
349
+ }
350
+
351
+ output += generateRuntimeSchemaExport(schema, typeRegistryImport);
352
+
353
+ // Write to output file
354
+ // Get just the filename without path and convert to .types.ts
355
+ const baseFileName = schemaPath.split(/[\/\\]/).pop()!.replace('.json', '.types.ts');
356
+ const fileName = join(outputDir, baseFileName);
357
+ await fs.writeFile(fileName, output);
358
+ log(`Generated ${fileName}`);
359
+ }
360
+
361
+ async function generateIndexFile(files: string[], outputDir: string): Promise<void> {
362
+ let output = FILE_HEADER;
363
+
364
+ // Add imports to trigger schema registration
365
+ output += '// Import all type files to trigger schema registration\n';
366
+ files.forEach(file => {
367
+ const baseFileName = file.replace('.json', '.types.js');
368
+ output += `import './${baseFileName}';\n`;
369
+ });
370
+ output += '\n';
371
+
372
+ // Add re-exports
373
+ output += '// Re-export types\n';
374
+ files.forEach(file => {
375
+ const baseFileName = file.replace('.json', '.types.js');
376
+ output += `export * from './${baseFileName}';\n`;
377
+ });
378
+
379
+ await fs.writeFile(join(outputDir, 'index.ts'), output);
380
+ log('Generated index.ts');
381
+ }
382
+
383
+ /**
384
+ * Generate TypeScript types from JSON schemas
385
+ * @param schemaDirs Array of directories containing JSON schema files (default: ['./typesrc'])
386
+ * @param outputDir Directory to output generated TypeScript files (default: './src/types')
387
+ * @param typeRegistryImport Import path for the type registry (default: '@themeparks/typelib')
388
+ */
389
+ export async function generateTypes({
390
+ schemaDirs = DEFAULT_SCHEMA_DIRS,
391
+ outputDir = DEFAULT_OUTPUT_DIR,
392
+ typeRegistryImport = "@themeparks/typelib"
393
+ } = {}): Promise<void> {
394
+ try {
395
+ log(`Generating types from schema directories: ${schemaDirs.join(', ')}`);
396
+ log(`Output directory: ${outputDir}`);
397
+
398
+ // First build the type registry from all schema directories
399
+ const registry = await buildTypeRegistry(schemaDirs);
400
+
401
+ // make sure output directory exists
402
+ await fs.mkdir(outputDir, { recursive: true });
403
+
404
+ // Collect all JSON files from all schema directories
405
+ const allJsonFiles: string[] = [];
406
+ const processedFiles = new Set<string>(); // Track processed filenames to avoid duplicates
407
+
408
+ for (const schemaDir of schemaDirs) {
409
+ try {
410
+ const files = await fs.readdir(schemaDir);
411
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
412
+
413
+ // Generate type files with cross-file reference support
414
+ for (const file of jsonFiles) {
415
+ const schemaPath = join(schemaDir, file);
416
+ await generateTypeFile(schemaPath, registry, outputDir, typeRegistryImport);
417
+
418
+ // Track this filename for the index file (avoid duplicates)
419
+ if (!processedFiles.has(file)) {
420
+ allJsonFiles.push(file);
421
+ processedFiles.add(file);
422
+ }
423
+ }
424
+ } catch (err) {
425
+ log(`Skipping schema directory ${schemaDir}: ${(err as Error).message}`);
426
+ }
427
+ }
428
+
429
+ // Generate index.ts with all processed files
430
+ await generateIndexFile(allJsonFiles, outputDir);
431
+
432
+ log('Type generation complete!');
433
+ } catch (err) {
434
+ error('Error generating types:', err);
435
+ throw err;
436
+ }
437
+ }
package/src/hash.ts ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Deterministic object hashing using node:crypto SHA-256.
3
+ *
4
+ * ## Supported types
5
+ * Plain objects, arrays, strings, numbers, booleans, null.
6
+ *
7
+ * ## Unsupported types (throws TypeError)
8
+ * undefined, Date, Map, Set, TypedArrays (Buffer, Uint8Array, etc.),
9
+ * Function, BigInt, Symbol, and circular references.
10
+ *
11
+ * Unsupported types throw rather than silently producing a hash that
12
+ * diverges from the previous object-hash implementation.
13
+ *
14
+ * ## Key ordering
15
+ * Object keys are sorted recursively before serialisation, so
16
+ * `{ b: 2, a: 1 }` and `{ a: 1, b: 2 }` produce the same hash.
17
+ *
18
+ * ## Array ordering
19
+ * Array element order is preserved — `[1, 2]` and `[2, 1]` hash differently.
20
+ */
21
+
22
+ import { createHash } from 'node:crypto';
23
+
24
+ /**
25
+ * Recursively normalise a value for hashing:
26
+ * - Sort object keys alphabetically
27
+ * - Throw TypeError on any unsupported type
28
+ * - Track seen objects to detect circular references
29
+ */
30
+ function normalise(value: unknown, seen: Set<object>): unknown {
31
+ // null is fine
32
+ if (value === null) return null;
33
+
34
+ // Primitives
35
+ if (typeof value === 'string') return value;
36
+ if (typeof value === 'number') {
37
+ if (!Number.isFinite(value)) {
38
+ throw new TypeError(
39
+ `hashObject: non-finite number is not supported (got: ${value})`,
40
+ );
41
+ }
42
+ return value;
43
+ }
44
+ if (typeof value === 'boolean') return value;
45
+
46
+ // Explicitly unsupported primitives
47
+ if (value === undefined) {
48
+ throw new TypeError(
49
+ 'hashObject: undefined is not supported — use null or omit the key instead',
50
+ );
51
+ }
52
+ if (typeof value === 'bigint') {
53
+ throw new TypeError(
54
+ `hashObject: BigInt is not supported — convert to string or number first (got: ${value}n)`,
55
+ );
56
+ }
57
+ if (typeof value === 'symbol') {
58
+ throw new TypeError(
59
+ `hashObject: Symbol is not supported (got: ${String(value)})`,
60
+ );
61
+ }
62
+ if (typeof value === 'function') {
63
+ throw new TypeError(
64
+ `hashObject: function is not supported (got: ${(value as Function).name || 'anonymous'})`,
65
+ );
66
+ }
67
+
68
+ // Object types
69
+ if (typeof value === 'object') {
70
+ // Circular reference detection
71
+ if (seen.has(value)) {
72
+ throw new TypeError('hashObject: circular reference detected');
73
+ }
74
+
75
+ // Explicitly unsupported object types — check before Array/plain-object
76
+ if (value instanceof Date) {
77
+ throw new TypeError(
78
+ `hashObject: Date is not supported — convert to ISO string first (got: ${value.toISOString()})`,
79
+ );
80
+ }
81
+ if (value instanceof Map) {
82
+ throw new TypeError(
83
+ 'hashObject: Map is not supported — convert to a plain object first',
84
+ );
85
+ }
86
+ if (value instanceof Set) {
87
+ throw new TypeError(
88
+ 'hashObject: Set is not supported — convert to an Array first',
89
+ );
90
+ }
91
+ if (ArrayBuffer.isView(value)) {
92
+ throw new TypeError(
93
+ `hashObject: TypedArray/Buffer is not supported — convert to a plain array or base64 string first (got: ${value.constructor.name})`,
94
+ );
95
+ }
96
+
97
+ seen.add(value);
98
+
99
+ let result: unknown;
100
+
101
+ if (Array.isArray(value)) {
102
+ result = value.map((item) => normalise(item, seen));
103
+ } else {
104
+ // Plain object — sort keys for determinism
105
+ const sorted: Record<string, unknown> = {};
106
+ for (const key of Object.keys(value as Record<string, unknown>).sort()) {
107
+ sorted[key] = normalise((value as Record<string, unknown>)[key], seen);
108
+ }
109
+ result = sorted;
110
+ }
111
+
112
+ seen.delete(value); // allow the same object to appear in sibling branches
113
+ return result;
114
+ }
115
+
116
+ // Should be unreachable, but TypeScript needs the exhaustive case
117
+ throw new TypeError(`hashObject: unsupported type: ${typeof value}`);
118
+ }
119
+
120
+ /**
121
+ * Return a deterministic 64-char hex SHA-256 hash of the given value.
122
+ *
123
+ * Throws TypeError if the value contains any unsupported type.
124
+ */
125
+ export function hashObject(value: unknown): string {
126
+ const normalised = normalise(value, new Set());
127
+ return createHash('sha256')
128
+ .update(JSON.stringify(normalised))
129
+ .digest('hex');
130
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ // Main stub. Expose type register functions and all generated types.
2
+
3
+ export { registerTypeSchema, getTypeSchema } from './type_register.js';
4
+ export * from './types/index.js';
@@ -0,0 +1,8 @@
1
+ import { generateTypes } from './generate_types.js';
2
+ import { resolve } from 'path';
3
+
4
+ await generateTypes({
5
+ schemaDirs: [resolve('./typesrc')],
6
+ outputDir: './src/types',
7
+ typeRegistryImport: '../type_register.js',
8
+ });
@@ -0,0 +1,20 @@
1
+ // Store JSON schemas for runtime type information
2
+ const typeMetadataRegistry = new Map<string, any>();
3
+
4
+ /**
5
+ * Register a JSON schema for runtime type checking
6
+ * @param name The type name (e.g. "Entity")
7
+ * @param schema The JSON schema definition
8
+ */
9
+ export function registerTypeSchema(name: string, schema: any): void {
10
+ typeMetadataRegistry.set(name, schema);
11
+ }
12
+
13
+ /**
14
+ * Retrieve the JSON schema for a registered type
15
+ * @param name The type name
16
+ * @returns The JSON schema or undefined if not found
17
+ */
18
+ export function getTypeSchema(name: string): any | undefined {
19
+ return typeMetadataRegistry.get(name);
20
+ }