@oceanum/eidos 0.9.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,604 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { bundle } from "./schema-bundler.js";
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import { fileURLToPath } from "url";
7
+
8
+ // ES6 equivalent of __dirname
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ /**
13
+ * Custom TypeScript interface generator for EIDOS schemas
14
+ *
15
+ * This script takes a bundled schema and converts each root $def to a TypeScript interface.
16
+ * It strips out non-TypeScript relevant fields and uses them for JSDoc documentation.
17
+ * No external dependencies like json-schema-to-typescript.
18
+ */
19
+ class SchemaToTypeScript {
20
+ constructor() {
21
+ this.schemasUrl = "https://schemas.oceanum.io/eidos";
22
+ this.outputDir = path.resolve(__dirname, "../src/schema");
23
+ this.outputFile = path.join(this.outputDir, "interfaces.ts");
24
+ this.processedRefs = new Set(); // Track processed references to avoid duplicates
25
+ }
26
+
27
+ /**
28
+ * Convert a bundled schema to TypeScript interfaces
29
+ * @param {string} rootSchemaPath - Path or URL to the root schema
30
+ * @returns {Promise<void>}
31
+ */
32
+ async convertToTypeScript(rootSchemaPath) {
33
+ console.log("🚀 Converting EIDOS schemas to TypeScript interfaces...");
34
+
35
+ // Ensure output directory exists
36
+ if (!fs.existsSync(this.outputDir)) {
37
+ fs.mkdirSync(this.outputDir, { recursive: true });
38
+ }
39
+
40
+ try {
41
+ console.log(`📥 Bundling schemas from: ${rootSchemaPath}`);
42
+
43
+ // Use our custom schema bundler to bundle all schemas into one
44
+ const bundledSchema = await bundle(rootSchemaPath);
45
+
46
+ console.log("📦 Schema bundling completed successfully!");
47
+
48
+ if (!bundledSchema.$defs || Object.keys(bundledSchema.$defs).length === 0) {
49
+ throw new Error("No $defs found in bundled schema");
50
+ }
51
+
52
+ console.log(`🔄 Converting ${Object.keys(bundledSchema.$defs).length} definitions to TypeScript interfaces...`);
53
+
54
+ // Store interfaces in order: EidosSpec first, then PlotSpec
55
+ const interfaces = [];
56
+ const constants = []; // Track generated constants from const values
57
+ const processedDefs = new Set(); // Track which definitions we've already processed
58
+ const requiredDefs = new Set(); // Track which definitions are required by root or PlotSpec
59
+
60
+ const banner = `/**
61
+ * Auto-generated TypeScript interfaces for EIDOS schemas
62
+ * Generated from: ${rootSchemaPath}
63
+ *
64
+ * Each interface corresponds to a definition in the EIDOS schema bundle.
65
+ * These interfaces can be used for type validation and IDE support.
66
+ *
67
+ * Do not modify this file directly - regenerate using:
68
+ * npx nx run eidos:generate-types
69
+ */
70
+
71
+ `;
72
+
73
+ // First pass - create the root schema interface
74
+ try {
75
+ console.log(" Converting root EidosSpec schema...");
76
+ const { $defs, ...rootSchemaContent } = bundledSchema;
77
+
78
+ // Create a proper schema object for the root
79
+ const rootSpecSchema = {
80
+ title: rootSchemaContent.title || "EidosSpec",
81
+ description: rootSchemaContent.description || "EIDOS specification root schema",
82
+ type: "object",
83
+ properties: rootSchemaContent.properties || {},
84
+ required: rootSchemaContent.required || [],
85
+ };
86
+
87
+ // Find all types directly referenced by the root schema
88
+ this.collectReferencedTypes(rootSpecSchema, requiredDefs);
89
+
90
+ // Generate the root interface
91
+ const rootInterfaceCode = this.generateInterface("EidosSpec", rootSpecSchema, bundledSchema.$defs, constants);
92
+ interfaces.push(rootInterfaceCode);
93
+ processedDefs.add("EidosSpec");
94
+ } catch (error) {
95
+ console.warn(`⚠️ Failed to convert root schema: ${error.message}`);
96
+ }
97
+
98
+ // Add PlotSpec
99
+ if (bundledSchema.$defs.PlotSpec) {
100
+ try {
101
+ console.log(" Converting PlotSpec...");
102
+
103
+ // Find all types directly referenced by PlotSpec
104
+ this.collectReferencedTypes(bundledSchema.$defs.PlotSpec, requiredDefs);
105
+
106
+ const interfaceCode = this.generateInterface("PlotSpec", bundledSchema.$defs.PlotSpec, bundledSchema.$defs, constants);
107
+ interfaces.push(interfaceCode);
108
+ processedDefs.add("PlotSpec");
109
+ } catch (error) {
110
+ console.warn(`⚠️ Failed to convert PlotSpec: ${error.message}`);
111
+ }
112
+ }
113
+
114
+ // Add Node since it's a critical type
115
+ if (bundledSchema.$defs.Node && !processedDefs.has("Node")) {
116
+ try {
117
+ console.log(" Converting Node...");
118
+
119
+ // Find all types directly referenced by Node
120
+ this.collectReferencedTypes(bundledSchema.$defs.Node, requiredDefs);
121
+
122
+ const interfaceCode = this.generateInterface("Node", bundledSchema.$defs.Node, bundledSchema.$defs, constants);
123
+ interfaces.push(interfaceCode);
124
+ processedDefs.add("Node");
125
+ } catch (error) {
126
+ console.warn(`⚠️ Failed to convert Node: ${error.message}`);
127
+ }
128
+ }
129
+
130
+ console.log(`Found ${requiredDefs.size} required definitions`);
131
+
132
+ // Process all required definitions
133
+ const defQueue = Array.from(requiredDefs);
134
+ while (defQueue.length > 0) {
135
+ const defName = defQueue.shift();
136
+
137
+ // Skip if already processed or not in the bundled schema
138
+ if (processedDefs.has(defName) || !bundledSchema.$defs[defName]) {
139
+ continue;
140
+ }
141
+
142
+ console.log(` Converting required type: ${defName}...`);
143
+
144
+ try {
145
+ // Find any additional references in this definition
146
+ this.collectReferencedTypes(bundledSchema.$defs[defName], requiredDefs);
147
+
148
+ // Generate the interface
149
+ const interfaceCode = this.generateInterface(defName, bundledSchema.$defs[defName], bundledSchema.$defs, constants);
150
+ interfaces.push(interfaceCode);
151
+ processedDefs.add(defName);
152
+
153
+ // Add any new required definitions to the queue
154
+ for (const newDef of requiredDefs) {
155
+ if (!processedDefs.has(newDef) && !defQueue.includes(newDef)) {
156
+ defQueue.push(newDef);
157
+ }
158
+ }
159
+ } catch (error) {
160
+ console.warn(`⚠️ Failed to convert ${defName}: ${error.message}`);
161
+ // Create a simple interface as fallback
162
+ interfaces.push(this.generateFallbackInterface(defName, bundledSchema.$defs[defName]));
163
+ processedDefs.add(defName);
164
+ }
165
+ }
166
+
167
+ // Combine all interfaces into a single file
168
+ let finalOutput = banner;
169
+
170
+ // Add constants first if any were generated
171
+ if (constants.length > 0) {
172
+ finalOutput += constants.join('\n') + '\n\n';
173
+ }
174
+
175
+ finalOutput += interfaces.join('\n\n') + '\n';
176
+ fs.writeFileSync(this.outputFile, finalOutput);
177
+
178
+ console.log("✅ TypeScript interface generation completed successfully!");
179
+ console.log(`📄 Generated: ${path.relative(process.cwd(), this.outputFile)}`);
180
+ console.log(`📊 Created ${Object.keys(bundledSchema.$defs).length} TypeScript interfaces`);
181
+
182
+ } catch (error) {
183
+ console.error("❌ Error converting schemas to TypeScript:", error);
184
+ throw error;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Generate a TypeScript interface from a schema definition
190
+ * @param {string} name - Interface name
191
+ * @param {Object} schema - Schema definition
192
+ * @param {Object} allDefs - All definitions for reference resolution
193
+ * @param {Array} constants - Array to collect generated constants
194
+ * @returns {string} - Generated TypeScript interface
195
+ */
196
+ generateInterface(name, schema, allDefs, constants = []) {
197
+ const docs = this.extractDocumentation(schema);
198
+ const properties = this.generateProperties(schema, allDefs, constants);
199
+
200
+ let result = '';
201
+
202
+ // Add JSDoc comment from schema metadata
203
+ if (docs.length > 0) {
204
+ result += '/**\n';
205
+ docs.forEach(doc => {
206
+ result += ` * ${doc}\n`;
207
+ });
208
+ result += ' */\n';
209
+ }
210
+
211
+ // Check if this should be a type alias instead of an interface
212
+ if (schema.oneOf || schema.anyOf || (schema.type && schema.type !== 'object' && !schema.properties)) {
213
+ // Use type alias for union types and primitive types
214
+ const type = this.generateType(schema, allDefs);
215
+ result += `export type ${name} = ${type};`;
216
+ } else if (properties.trim() === '') {
217
+ // Empty interface, make it a type alias to any
218
+ result += `export type ${name} = any;`;
219
+ } else {
220
+ // Use interface for object types with properties
221
+ result += `export interface ${name} {\n`;
222
+ result += properties;
223
+ result += '}';
224
+ }
225
+
226
+ return result;
227
+ }
228
+
229
+ /**
230
+ * Generate fallback interface for schemas that can't be processed
231
+ * @param {string} name - Interface name
232
+ * @param {Object} schema - Schema definition
233
+ * @returns {string} - Simple fallback interface
234
+ */
235
+ generateFallbackInterface(name, schema) {
236
+ const comment = schema.description || schema.title || 'Schema conversion failed, fallback to any';
237
+
238
+ return `/**
239
+ * ${comment}
240
+ */
241
+ export interface ${name} {
242
+ [key: string]: any;
243
+ }`;
244
+ }
245
+
246
+ /**
247
+ * Collect all referenced types from a schema into the provided set
248
+ * @param {Object} schema - The schema to analyze
249
+ * @param {Set} referencedTypes - Set to collect referenced type names
250
+ */
251
+ collectReferencedTypes(schema, referencedTypes) {
252
+ if (!schema || typeof schema !== 'object') {
253
+ return;
254
+ }
255
+
256
+ // Handle $ref - extract type name
257
+ if (schema.$ref) {
258
+ const refParts = schema.$ref.split('/');
259
+ if (refParts.length >= 3 && refParts[1] === '$defs') {
260
+ // Internal reference like #/$defs/TypeName
261
+ referencedTypes.add(refParts[2]);
262
+ } else if (!schema.$ref.startsWith('#/')) {
263
+ // External reference - try to extract type name from URL
264
+ const urlParts = schema.$ref.split('/');
265
+ const filename = urlParts[urlParts.length - 1];
266
+ if (filename.endsWith('.json')) {
267
+ const typeName = this.toPascalCase(filename.replace('.json', ''));
268
+ referencedTypes.add(typeName);
269
+ }
270
+ }
271
+ return; // No need to go deeper if this is a reference
272
+ }
273
+
274
+ // Handle union types (oneOf, anyOf)
275
+ if (schema.oneOf || schema.anyOf) {
276
+ const options = schema.oneOf || schema.anyOf;
277
+ for (const option of options) {
278
+ this.collectReferencedTypes(option, referencedTypes);
279
+ }
280
+ }
281
+
282
+ // Handle array items
283
+ if (schema.type === 'array' && schema.items) {
284
+ this.collectReferencedTypes(schema.items, referencedTypes);
285
+ }
286
+
287
+ // Handle object properties
288
+ if (schema.properties) {
289
+ for (const propSchema of Object.values(schema.properties)) {
290
+ this.collectReferencedTypes(propSchema, referencedTypes);
291
+ }
292
+ }
293
+
294
+ // Handle additional properties
295
+ if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
296
+ this.collectReferencedTypes(schema.additionalProperties, referencedTypes);
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Generate a constant name for a const value
302
+ * @param {Object} schema - Schema object with const value
303
+ * @param {Object} allDefs - All definitions for context
304
+ * @returns {string} - Generated constant name
305
+ */
306
+ generateConstantName(schema, allDefs) {
307
+ // If the schema has a title, use it as the base for the constant name
308
+ if (schema.title) {
309
+ return this.toPascalCase(schema.title);
310
+ }
311
+
312
+ // If the const value is a string, try to generate a meaningful name
313
+ if (typeof schema.const === 'string') {
314
+ // For node types like "world", "plot", etc., generate names like "WorldNodeType", "PlotNodeType"
315
+ if (schema.const.match(/^[a-z]+$/)) {
316
+ return this.toPascalCase(schema.const) + 'NodeType';
317
+ }
318
+ // For other string constants, use the value itself
319
+ return this.toPascalCase(schema.const) + 'Constant';
320
+ }
321
+
322
+ // For non-string constants, generate a generic name
323
+ return 'Constant' + Math.random().toString(36).substr(2, 9);
324
+ }
325
+
326
+ /**
327
+ * Convert a string to PascalCase
328
+ * @param {string} str - Input string
329
+ * @returns {string} - PascalCase string
330
+ */
331
+ toPascalCase(str) {
332
+ if (!str) return '';
333
+
334
+ // Replace non-alphanumeric characters with spaces
335
+ const normalized = str.replace(/[^a-zA-Z0-9]/g, ' ');
336
+
337
+ // Convert to PascalCase
338
+ return normalized
339
+ .split(' ')
340
+ .filter(word => word.length > 0)
341
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
342
+ .join('');
343
+ }
344
+
345
+ /**
346
+ * Extract documentation from schema metadata
347
+ * @param {Object} schema - Schema object
348
+ * @returns {string[]} - Array of documentation lines
349
+ */
350
+ extractDocumentation(schema) {
351
+ const docs = [];
352
+
353
+ if (schema.title && schema.title !== schema.description) {
354
+ docs.push(schema.title);
355
+ }
356
+
357
+ if (schema.description) {
358
+ docs.push(schema.description);
359
+ }
360
+
361
+ if (schema.examples && schema.examples.length > 0) {
362
+ docs.push('');
363
+ docs.push('@example');
364
+ schema.examples.slice(0, 3).forEach(example => {
365
+ docs.push(`${JSON.stringify(example)}`);
366
+ });
367
+ }
368
+
369
+ return docs;
370
+ }
371
+
372
+ /**
373
+ * Generate TypeScript properties from schema
374
+ * @param {Object} schema - Schema object
375
+ * @param {Object} allDefs - All definitions for reference resolution
376
+ * @param {Array} constants - Array to collect generated constants
377
+ * @returns {string} - Generated properties string
378
+ */
379
+ generateProperties(schema, allDefs, constants = []) {
380
+ let result = '';
381
+
382
+ // For union types, don't generate properties
383
+ if (schema.oneOf || schema.anyOf) {
384
+ // For union types, we don't generate properties - the interface will be a type alias
385
+ return '';
386
+ }
387
+
388
+ // Handle different schema types
389
+ if (schema.$ref) {
390
+ // Reference to another definition
391
+ const refName = this.resolveReference(schema.$ref);
392
+ return ` [key: string]: ${refName};\n`;
393
+ }
394
+
395
+ if (schema.type === 'object' && schema.properties) {
396
+ // Object with properties
397
+ const required = schema.required || [];
398
+
399
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
400
+ const isOptional = !required.includes(propName);
401
+
402
+ // Special case for Function.args property which is incorrectly typed
403
+ let propType;
404
+ if (schema.title === 'Function' && propName === 'args') {
405
+ propType = 'object'; // Fix the Function.args type
406
+ } else {
407
+ propType = this.generateType(propSchema, allDefs, constants);
408
+ }
409
+
410
+ const propDocs = this.extractDocumentation(propSchema);
411
+
412
+ // Add property documentation
413
+ if (propDocs.length > 0) {
414
+ result += ' /**\n';
415
+ propDocs.forEach(doc => {
416
+ result += ` * ${doc}\n`;
417
+ });
418
+ result += ' */\n';
419
+ }
420
+
421
+ result += ` ${propName}${isOptional ? '?' : ''}: ${propType};\n`;
422
+ }
423
+
424
+ // Handle additionalProperties
425
+ if (schema.additionalProperties === true) {
426
+ result += ' [key: string]: any;\n';
427
+ } else if (typeof schema.additionalProperties === 'object') {
428
+ const additionalType = this.generateType(schema.additionalProperties, allDefs, constants);
429
+ result += ` [key: string]: ${additionalType};\n`;
430
+ }
431
+ } else if (schema.type && schema.type !== 'object') {
432
+ // For primitive types, this should be a type alias, not an interface
433
+ return '';
434
+ } else {
435
+ // For schemas without clear structure, fallback to index signature
436
+ const type = this.generateType(schema, allDefs, constants);
437
+ result += ` [key: string]: ${type};\n`;
438
+ }
439
+
440
+ return result;
441
+ }
442
+
443
+ /**
444
+ * Generate TypeScript type from schema
445
+ * @param {Object} schema - Schema object
446
+ * @param {Object} allDefs - All definitions for reference resolution
447
+ * @param {Array} constants - Array to collect generated constants
448
+ * @returns {string} - Generated TypeScript type
449
+ */
450
+ generateType(schema, allDefs, constants = []) {
451
+ if (!schema || typeof schema !== 'object') {
452
+ return 'any';
453
+ }
454
+
455
+ // Handle $ref
456
+ if (schema.$ref) {
457
+ return this.resolveReference(schema.$ref);
458
+ }
459
+
460
+ // Handle const values - generate TypeScript constants
461
+ if (schema.const !== undefined) {
462
+ const constantName = this.generateConstantName(schema, allDefs);
463
+ const constantValue = typeof schema.const === 'string' ? `"${schema.const}"` : JSON.stringify(schema.const);
464
+ const constantDeclaration = `const ${constantName} = ${constantValue} as const;`;
465
+
466
+ // Add to constants array if not already present
467
+ if (!constants.some(c => c.includes(constantName))) {
468
+ constants.push(constantDeclaration);
469
+ }
470
+
471
+ return constantName;
472
+ }
473
+
474
+ // Handle union types (oneOf, anyOf)
475
+ if (schema.oneOf || schema.anyOf) {
476
+ const options = schema.oneOf || schema.anyOf;
477
+ const types = options.map(option => {
478
+ // For references, resolve them directly
479
+ if (option.$ref) {
480
+ // For external references (not starting with #/), try to extract type name from URL
481
+ if (!option.$ref.startsWith('#/')) {
482
+ // Extract the type name from the URL
483
+ const urlParts = option.$ref.split('/');
484
+ const filename = urlParts[urlParts.length - 1];
485
+ if (filename.endsWith('.json')) {
486
+ const typeName = filename.replace('.json', '');
487
+ // Convert to PascalCase
488
+ return typeName.charAt(0).toUpperCase() + typeName.slice(1);
489
+ }
490
+ }
491
+ return this.resolveReference(option.$ref);
492
+ }
493
+
494
+ // Handle specific object types with titles that are already defined in allDefs
495
+ if (option.title && option.type === 'object') {
496
+ // Check if this matches a definition we have
497
+ const matchingDefKey = Object.keys(allDefs).find(key => {
498
+ return allDefs[key].title === option.title;
499
+ });
500
+
501
+ if (matchingDefKey) {
502
+ return matchingDefKey;
503
+ }
504
+ }
505
+
506
+ // Generate the type for this option
507
+ return this.generateType(option, allDefs, constants);
508
+ });
509
+
510
+ // Filter out duplicates and empty types
511
+ const uniqueTypes = [...new Set(types)].filter(type => type && type !== 'any');
512
+
513
+ // If we have no valid types, return any
514
+ if (uniqueTypes.length === 0) {
515
+ return 'any';
516
+ }
517
+
518
+ // Format the union with proper spacing
519
+ return uniqueTypes.join(' | ');
520
+ }
521
+
522
+ // Handle allOf (intersection)
523
+ if (schema.allOf) {
524
+ const types = schema.allOf.map(option => this.generateType(option, allDefs, constants));
525
+ return types.join(' & ');
526
+ }
527
+
528
+ // Handle arrays
529
+ if (schema.type === 'array') {
530
+ if (schema.items) {
531
+ const itemType = this.generateType(schema.items, allDefs, constants);
532
+ return `${itemType}[]`;
533
+ }
534
+ return 'any[]';
535
+ }
536
+
537
+ // Handle primitives
538
+ switch (schema.type) {
539
+ case 'string':
540
+ if (schema.enum) {
541
+ return schema.enum.map(val => `"${val}"`).join(' | ');
542
+ }
543
+ return 'string';
544
+ case 'number':
545
+ case 'integer':
546
+ return 'number';
547
+ case 'boolean':
548
+ return 'boolean';
549
+ case 'null':
550
+ return 'null';
551
+ case 'object':
552
+ // For inline object schemas, try to extract a meaningful type name
553
+ if (schema.title) {
554
+ return schema.title;
555
+ }
556
+ return 'object';
557
+ default:
558
+ return 'any';
559
+ }
560
+ }
561
+
562
+ /**
563
+ * Resolve a $ref to a TypeScript type name
564
+ * @param {string} ref - Reference string
565
+ * @returns {string} - TypeScript type name
566
+ */
567
+ resolveReference(ref) {
568
+ if (ref.startsWith('#/$defs/')) {
569
+ return ref.replace('#/$defs/', '');
570
+ }
571
+ if (ref.startsWith('#/definitions/')) {
572
+ return ref.replace('#/definitions/', '');
573
+ }
574
+ // For unresolved refs, return any
575
+ return 'any';
576
+ }
577
+ }
578
+
579
+ /**
580
+ * Export function for converting schemas to TypeScript
581
+ * @param {string} rootSchemaPath - Path or URL to the root schema
582
+ * @returns {Promise<void>}
583
+ */
584
+ export async function convertToTypeScript(rootSchemaPath) {
585
+ const converter = new SchemaToTypeScript();
586
+ return await converter.convertToTypeScript(rootSchemaPath);
587
+ }
588
+
589
+ // CLI support - run converter if called directly
590
+ if (import.meta.url === `file://${process.argv[1]}` ||
591
+ import.meta.url.endsWith(process.argv[1])) {
592
+
593
+ const rootSchema = process.argv[2] || 'https://schemas.oceanum.io/eidos/root.json';
594
+
595
+ console.log('🚀 Running schema to TypeScript converter CLI...');
596
+
597
+ try {
598
+ await convertToTypeScript(rootSchema);
599
+ console.log('✅ Schema to TypeScript conversion completed successfully!');
600
+ } catch (error) {
601
+ console.error('❌ CLI conversion failed:', error);
602
+ process.exit(1);
603
+ }
604
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./lib/render";
2
+ export * from "./schema/interfaces";
3
+ export * from "./lib/react";
@@ -0,0 +1,46 @@
1
+ import Ajv, { ValidateFunction } from "ajv";
2
+
3
+ const ROOT_SCHEMA = "https://schemas.oceanum.io/eidos/root.json";
4
+ let validator: ValidateFunction | null = null;
5
+
6
+ const loadSchema = async (uri: string) => {
7
+ const res = await fetch(uri);
8
+ if (res.statusCode >= 400)
9
+ throw new Error("Loading error: " + res.statusCode);
10
+ return res.body;
11
+ };
12
+
13
+ const validateSchema = async (spec: any): Promise<boolean> => {
14
+ if (!validator) {
15
+ //Load the root schema from URL as JSON
16
+ const schema = await loadSchema(ROOT_SCHEMA);
17
+
18
+ // Create AJV instance with configuration
19
+ const ajv = new Ajv({
20
+ allErrors: true,
21
+ verbose: true,
22
+ strict: false, // Allow additional properties for flexibility
23
+ loadSchema,
24
+ });
25
+
26
+ // Compile the validator
27
+ validator = await ajv.compileAsync(schema);
28
+ }
29
+
30
+ const isValid = validator(spec);
31
+
32
+ if (!isValid && validator.errors) {
33
+ const errorMessages = validator.errors
34
+ .map(
35
+ (error: any) =>
36
+ `${error.instancePath || "root"}: ${error.message} (${JSON.stringify(error.params || {})})`
37
+ )
38
+ .join("; ");
39
+
40
+ throw new Error(`EIDOS spec validation failed: ${errorMessages}`);
41
+ }
42
+
43
+ return true;
44
+ };
45
+
46
+ export { validateSchema };
@@ -0,0 +1,8 @@
1
+ import { useSnapshot } from 'valtio';
2
+ import { Proxy } from 'valtio/vanilla';
3
+ import { EidosSpec } from '../schema/interfaces';
4
+
5
+ export const useEidosSpec = (spec: Proxy<EidosSpec>) => {
6
+ const specSnapshot = useSnapshot(spec);
7
+ return specSnapshot;
8
+ };