@oceanum/eidos 0.9.1 → 0.9.2
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/dist/index.cjs +8 -0
- package/dist/index.js +4631 -0
- package/package.json +4 -1
- package/index.html +0 -13
- package/project.json +0 -4
- package/scripts/debug-schema.js +0 -30
- package/scripts/generate-interfaces.js +0 -48
- package/scripts/schema-bundler.js +0 -609
- package/scripts/schema-to-typescript.js +0 -604
- package/src/index.ts +0 -7
- package/src/lib/eidosmodel.ts +0 -46
- package/src/lib/react.tsx +0 -70
- package/src/lib/render.ts +0 -176
- package/src/schema/interfaces.ts +0 -1171
- package/test-example.js +0 -64
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -25
- package/tsconfig.spec.json +0 -31
- package/vite.config.ts +0 -44
- /package/{public → dist}/favicon.ico +0 -0
|
@@ -1,604 +0,0 @@
|
|
|
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
DELETED
package/src/lib/eidosmodel.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
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 };
|