@themeparks/typelib 1.0.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/README.md +131 -0
- package/dist/generate_types.d.ts +12 -0
- package/dist/generate_types.d.ts.map +1 -0
- package/dist/generate_types.js +342 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/run_generate_types.d.ts +2 -0
- package/dist/run_generate_types.d.ts.map +1 -0
- package/dist/run_generate_types.js +7 -0
- package/dist/type_register.d.ts +13 -0
- package/dist/type_register.d.ts.map +1 -0
- package/dist/type_register.js +18 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/v1.types.d.ts +9 -0
- package/dist/types/v1.types.d.ts.map +1 -0
- package/dist/types/v1.types.js +25 -0
- package/package.json +42 -0
- package/typesrc/v1.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @themeparks/typelib
|
|
2
|
+
|
|
3
|
+
TypeScript definition system for ThemeParks.wiki
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@themeparks/typelib` is a TypeScript types package that generates types from JSON schemas and provides runtime type validation. It is designed for the internal ThemeParks.wiki systems and generating client libraries. You likely do not want to interact with this library directly.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @themeparks/typelib
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Basic Usage
|
|
16
|
+
|
|
17
|
+
### Using pre-built types
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { V1RootResponse, registerTypeSchema, getTypeSchema } from '@themeparks/typelib';
|
|
21
|
+
|
|
22
|
+
// Use generated types
|
|
23
|
+
const response: V1RootResponse = {
|
|
24
|
+
success: true,
|
|
25
|
+
message: "API is running",
|
|
26
|
+
version: "1.0.0"
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Access runtime schemas
|
|
30
|
+
const schema = getTypeSchema('V1RootResponse');
|
|
31
|
+
console.log(schema); // Returns the JSON schema for validation
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Generating types from custom schemas
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { generateTypes } from '@themeparks/typelib/generate';
|
|
38
|
+
import { resolve } from 'path';
|
|
39
|
+
|
|
40
|
+
await generateTypes({
|
|
41
|
+
schemaDirs: [resolve('./typesrc')],
|
|
42
|
+
outputDir: './src/types'
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Runtime Schema Registry
|
|
47
|
+
|
|
48
|
+
**`registerTypeSchema(name: string, schema: any)`**
|
|
49
|
+
Register a schema for runtime use.
|
|
50
|
+
|
|
51
|
+
**`getTypeSchema(name: string): any`**
|
|
52
|
+
Retrieve a registered schema.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { registerTypeSchema, getTypeSchema } from '@themeparks/typelib';
|
|
56
|
+
|
|
57
|
+
// Schemas are automatically registered when importing types
|
|
58
|
+
import { V1RootResponse } from '@themeparks/typelib';
|
|
59
|
+
|
|
60
|
+
// Access the schema at runtime
|
|
61
|
+
const schema = getTypeSchema('V1RootResponse');
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Schema Format
|
|
65
|
+
|
|
66
|
+
Schemas follow JSON Schema Draft 7 specification:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
71
|
+
"title": "MyTypes",
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {
|
|
74
|
+
"MyType": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"required": ["id", "name"],
|
|
77
|
+
"properties": {
|
|
78
|
+
"id": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"description": "Unique identifier"
|
|
81
|
+
},
|
|
82
|
+
"name": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"description": "Display name"
|
|
85
|
+
},
|
|
86
|
+
"status": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"enum": ["active", "inactive", "pending"],
|
|
89
|
+
"description": "Status of the item"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Generated Output
|
|
98
|
+
|
|
99
|
+
The generator creates:
|
|
100
|
+
- **Type definitions** - TypeScript interfaces and types
|
|
101
|
+
- **Enum types** - Native TypeScript enums with conversion functions
|
|
102
|
+
- **Runtime registration** - Automatic schema registration
|
|
103
|
+
- **Re-export index** - Convenient imports from a single file
|
|
104
|
+
|
|
105
|
+
Example generated output:
|
|
106
|
+
```typescript
|
|
107
|
+
// Generated: my-types.types.ts
|
|
108
|
+
|
|
109
|
+
export interface MyType {
|
|
110
|
+
/** Unique identifier */
|
|
111
|
+
id: string;
|
|
112
|
+
/** Display name */
|
|
113
|
+
name: string;
|
|
114
|
+
/** Status of the item */
|
|
115
|
+
status?: 'active' | 'inactive' | 'pending';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export enum StatusEnum {
|
|
119
|
+
"active" = 'active',
|
|
120
|
+
"inactive" = 'inactive',
|
|
121
|
+
"pending" = 'pending'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Runtime schema registration
|
|
125
|
+
registerTypeSchema("MyType", { /* schema */ });
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Package Exports
|
|
129
|
+
|
|
130
|
+
- `@themeparks/typelib` - Main package with types and registry functions
|
|
131
|
+
- `@themeparks/typelib/generate` - Type generation functionality
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate TypeScript types from JSON schemas
|
|
3
|
+
* @param schemaDirs Array of directories containing JSON schema files (default: ['./typesrc'])
|
|
4
|
+
* @param outputDir Directory to output generated TypeScript files (default: './src/types')
|
|
5
|
+
* @param typeRegistryImport Import path for the type registry (default: '@themeparks/typelib')
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateTypes({ schemaDirs, outputDir, typeRegistryImport }?: {
|
|
8
|
+
schemaDirs?: string[] | undefined;
|
|
9
|
+
outputDir?: string | undefined;
|
|
10
|
+
typeRegistryImport?: string | undefined;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=generate_types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate_types.d.ts","sourceRoot":"","sources":["../src/generate_types.ts"],"names":[],"mappings":"AA8VA;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,EAChC,UAAgC,EAChC,SAA8B,EAC9B,kBAA0C,EAC7C;;;;CAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CrB"}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// Script to generate TypeScript interface files from JSON schemas
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
// Default directories - can be overridden in generateTypes()
|
|
5
|
+
const DEFAULT_SCHEMA_DIRS = ['./typesrc'];
|
|
6
|
+
const DEFAULT_OUTPUT_DIR = './src/types';
|
|
7
|
+
const FILE_HEADER = '// THIS FILE IS GENERATED - DO NOT EDIT DIRECTLY\n\n';
|
|
8
|
+
// Simple logger implementation to replace missing './logger.js'
|
|
9
|
+
const log = (...args) => console.log('[TypeGenerator 📜]', ...args);
|
|
10
|
+
const error = (...args) => console.error('[TypeGenerator 📜]', ...args);
|
|
11
|
+
async function buildTypeRegistry(schemaDirs) {
|
|
12
|
+
const registry = {};
|
|
13
|
+
// Process all schema directories
|
|
14
|
+
for (const schemaDir of schemaDirs) {
|
|
15
|
+
try {
|
|
16
|
+
const files = await fs.readdir(schemaDir);
|
|
17
|
+
const jsonFiles = files.filter(f => f.endsWith('.json'));
|
|
18
|
+
for (const file of jsonFiles) {
|
|
19
|
+
const schemaPath = join(schemaDir, file);
|
|
20
|
+
const schemaContent = await fs.readFile(schemaPath, 'utf-8');
|
|
21
|
+
const schema = JSON.parse(schemaContent);
|
|
22
|
+
// Register all top-level types from this file
|
|
23
|
+
for (const [name, typeSchema] of Object.entries(schema.properties || {})) {
|
|
24
|
+
// If type already exists, log a warning but allow override (later directories take precedence)
|
|
25
|
+
if (registry[name]) {
|
|
26
|
+
log(`Warning: Type "${name}" already exists, overriding with version from ${schemaPath}`);
|
|
27
|
+
}
|
|
28
|
+
registry[name] = {
|
|
29
|
+
sourceFile: file.replace('.json', ''),
|
|
30
|
+
schema: typeSchema
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
// Skip directories that don't exist or can't be read
|
|
37
|
+
log(`Skipping schema directory ${schemaDir}: ${err.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return registry;
|
|
41
|
+
}
|
|
42
|
+
function resolveReference(ref, schema, registry, tracker) {
|
|
43
|
+
const refPath = ref.split('/').slice(1);
|
|
44
|
+
// Check if we're in a circular reference
|
|
45
|
+
if (tracker.referencePath.includes(ref)) {
|
|
46
|
+
throw new Error(`Circular reference detected: ${tracker.referencePath.join(' -> ')} -> ${ref}`);
|
|
47
|
+
}
|
|
48
|
+
tracker.referencePath.push(ref);
|
|
49
|
+
try {
|
|
50
|
+
// First try to resolve in current schema
|
|
51
|
+
let referenced = schema;
|
|
52
|
+
for (const segment of refPath) {
|
|
53
|
+
if (segment === 'properties' && referenced.properties) {
|
|
54
|
+
referenced = referenced.properties;
|
|
55
|
+
}
|
|
56
|
+
else if (!referenced[segment]) {
|
|
57
|
+
// If segment not found, try alternate paths
|
|
58
|
+
if (segment === 'definitions' && ref.startsWith('#/definitions/')) {
|
|
59
|
+
// Handle #/definitions/* by looking in properties instead
|
|
60
|
+
const typeName = refPath[refPath.length - 1];
|
|
61
|
+
if (schema.properties?.[typeName]) {
|
|
62
|
+
tracker.referencePath.pop();
|
|
63
|
+
return { typeName };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// If still not found, check registry
|
|
67
|
+
const registryType = registry[segment];
|
|
68
|
+
if (registryType) {
|
|
69
|
+
tracker.imports.set(registryType.sourceFile, segment);
|
|
70
|
+
tracker.referencePath.pop();
|
|
71
|
+
return { typeName: segment, sourceFile: registryType.sourceFile };
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`Invalid reference path: ${ref}`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
referenced = referenced[segment];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// If we're referencing a property definition, get its type
|
|
80
|
+
const typeName = refPath[refPath.length - 1];
|
|
81
|
+
tracker.referencePath.pop();
|
|
82
|
+
return { typeName };
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
tracker.referencePath.pop();
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function getTypeFromSchema(schema, rootSchema, registry, tracker, typeName) {
|
|
90
|
+
if (schema.enum && Array.isArray(schema.enum)) {
|
|
91
|
+
// Handle enums specially - create union type of string literals
|
|
92
|
+
return schema.enum.map(value => `'${value}'`).join(' | ');
|
|
93
|
+
}
|
|
94
|
+
if (schema.$ref) {
|
|
95
|
+
try {
|
|
96
|
+
const resolved = resolveReference(schema.$ref, rootSchema, registry, tracker);
|
|
97
|
+
return resolved.typeName;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
error(`Error resolving reference: ${schema.$ref}`, err);
|
|
101
|
+
return 'any';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(schema.type)) {
|
|
105
|
+
// Handle union types like ["string", "null"]
|
|
106
|
+
return schema.type.map((t) => t === 'null' ? 'null' : t).join(' | ');
|
|
107
|
+
}
|
|
108
|
+
switch (schema.type) {
|
|
109
|
+
case 'string':
|
|
110
|
+
return schema.nullable ? 'string | null' : 'string';
|
|
111
|
+
case 'number':
|
|
112
|
+
return schema.nullable ? 'number | null' : 'number';
|
|
113
|
+
case 'integer':
|
|
114
|
+
return schema.nullable ? 'number | null' : 'number';
|
|
115
|
+
case 'boolean':
|
|
116
|
+
return schema.nullable ? 'boolean | null' : 'boolean';
|
|
117
|
+
case 'array': {
|
|
118
|
+
let arrayType = schema.items
|
|
119
|
+
? `${getTypeFromSchema(schema.items, rootSchema, registry, tracker)}[]`
|
|
120
|
+
: 'any[]';
|
|
121
|
+
// Handle nullable arrays
|
|
122
|
+
if (schema.nullable) {
|
|
123
|
+
arrayType += ' | null';
|
|
124
|
+
}
|
|
125
|
+
return arrayType;
|
|
126
|
+
}
|
|
127
|
+
case 'object': {
|
|
128
|
+
if (!schema.properties) {
|
|
129
|
+
const recordType = `Record<string, any>`;
|
|
130
|
+
if (typeName) {
|
|
131
|
+
return `extends ${recordType} {}`;
|
|
132
|
+
}
|
|
133
|
+
return schema.nullable ? `${recordType} | null` : recordType;
|
|
134
|
+
}
|
|
135
|
+
const props = [];
|
|
136
|
+
for (const [propName, propSchema] of Object.entries(schema.properties || {})) {
|
|
137
|
+
const isRequired = schema.required?.includes(propName);
|
|
138
|
+
const propType = getTypeFromSchema(propSchema, rootSchema, registry, tracker);
|
|
139
|
+
const description = propSchema.description
|
|
140
|
+
? `\n /** ${propSchema.description} */\n `
|
|
141
|
+
: '';
|
|
142
|
+
props.push(`${description}${propName}${isRequired ? '' : '?'}: ${propType};`);
|
|
143
|
+
}
|
|
144
|
+
const objType = `{\n ${props.join('\n ')}\n}`;
|
|
145
|
+
return schema.nullable ? `(${objType}) | null` : objType;
|
|
146
|
+
}
|
|
147
|
+
default:
|
|
148
|
+
return 'any';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function generateRuntimeSchemaExport(schema, typeRegistryImport) {
|
|
152
|
+
// Generate the runtime schema registration code
|
|
153
|
+
let output = '\n// Runtime Schema Registration\n';
|
|
154
|
+
output += `import { registerTypeSchema } from "${typeRegistryImport}";\n\n`;
|
|
155
|
+
// For each top-level type, register its schema
|
|
156
|
+
for (const [name, typeSchema] of Object.entries(schema.properties || {})) {
|
|
157
|
+
// remove any properties that have "__private" set to true. Recusively.
|
|
158
|
+
const cleanSchema = JSON.parse(JSON.stringify(typeSchema, (key, value) => {
|
|
159
|
+
// if any value is an object with "__private" set to true, remove it
|
|
160
|
+
if (value && typeof value === 'object' && value.__private) {
|
|
161
|
+
return undefined; // Remove this property
|
|
162
|
+
}
|
|
163
|
+
return value;
|
|
164
|
+
}));
|
|
165
|
+
output += `registerTypeSchema("${name}", ${JSON.stringify(cleanSchema, null, 2)});\n\n`;
|
|
166
|
+
}
|
|
167
|
+
return output;
|
|
168
|
+
}
|
|
169
|
+
async function generateTypeFile(schemaPath, registry, outputDir, typeRegistryImport) {
|
|
170
|
+
log(`Generating types for schema: ${schemaPath}`);
|
|
171
|
+
const schemaContent = await fs.readFile(schemaPath, 'utf-8');
|
|
172
|
+
const schema = JSON.parse(schemaContent);
|
|
173
|
+
let output = FILE_HEADER;
|
|
174
|
+
// Track imports needed for this file
|
|
175
|
+
const tracker = {
|
|
176
|
+
imports: new Map(),
|
|
177
|
+
referencePath: []
|
|
178
|
+
};
|
|
179
|
+
// Generate interfaces and types for top-level properties
|
|
180
|
+
for (const [name, typeSchema] of Object.entries(schema.properties || {})) {
|
|
181
|
+
const description = typeSchema.description
|
|
182
|
+
? `/** ${typeSchema.description} */\n`
|
|
183
|
+
: '';
|
|
184
|
+
if (typeSchema.type === 'object' && typeSchema.properties) {
|
|
185
|
+
// Handle objects with const values (like HttpStatusCode)
|
|
186
|
+
const hasConstValues = Object.entries(typeSchema.properties).some(([_, prop]) => prop.const !== undefined);
|
|
187
|
+
if (hasConstValues) {
|
|
188
|
+
output += `export enum ${name}Enum {\n`;
|
|
189
|
+
Object.entries(typeSchema.properties).forEach(([key, prop]) => {
|
|
190
|
+
output += ` ${key} = ${prop.const},\n`;
|
|
191
|
+
});
|
|
192
|
+
output += `}\n\n`;
|
|
193
|
+
output += `${description}export type ${name} = keyof typeof ${name}Enum;\n\n`;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (typeSchema.enum && Array.isArray(typeSchema.enum)) {
|
|
198
|
+
// generate a native enum for enum types
|
|
199
|
+
output += `export enum ${name}Enum {\n`;
|
|
200
|
+
typeSchema.enum.forEach((value) => {
|
|
201
|
+
output += ` "${value}" = '${value}',\n`;
|
|
202
|
+
});
|
|
203
|
+
output += `}\n\n`;
|
|
204
|
+
// Also generate enum type
|
|
205
|
+
output += `${description}export type ${name} = keyof typeof ${name}Enum;\n\n`;
|
|
206
|
+
// Also generate a StringTo${name} function
|
|
207
|
+
output += `// Function to convert string to ${name}Enum\n`;
|
|
208
|
+
output += `export function StringTo${name}(value: string): ${name}Enum {\n`;
|
|
209
|
+
output += ` const lowerValue = value.toLowerCase();\n`;
|
|
210
|
+
output += ` switch (lowerValue) {\n`;
|
|
211
|
+
typeSchema.enum.forEach((value) => {
|
|
212
|
+
if (value) {
|
|
213
|
+
output += ` case '${value.toLowerCase()}':\n`;
|
|
214
|
+
if (value.includes(' ')) {
|
|
215
|
+
// Handle spaces in enum values
|
|
216
|
+
output += ` return ${name}Enum["${value}"];\n`;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
output += ` return ${name}Enum.${value};\n`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
output += ` }\n`;
|
|
224
|
+
output += ` throw new Error('Unknown ${name} value: ' + value);\n`;
|
|
225
|
+
output += `}\n\n`;
|
|
226
|
+
}
|
|
227
|
+
else if (typeSchema.type === 'object' || typeSchema.allOf) {
|
|
228
|
+
// Handle inheritance with allOf
|
|
229
|
+
if (typeSchema.allOf) {
|
|
230
|
+
const baseType = typeSchema.allOf.find((t) => t.$ref)?.$ref?.split('/').pop();
|
|
231
|
+
const interfaceContent = typeSchema.properties?.data
|
|
232
|
+
? getTypeFromSchema(typeSchema.properties.data, schema, registry, tracker)
|
|
233
|
+
: '{}';
|
|
234
|
+
if (baseType === 'AdminResponse') {
|
|
235
|
+
// For types extending AdminResponse, use the data type as generic parameter
|
|
236
|
+
output += `${description}export interface ${name} extends ${baseType}<${interfaceContent}> {}\n\n`;
|
|
237
|
+
}
|
|
238
|
+
else if (baseType) {
|
|
239
|
+
output += `${description}export interface ${name} extends ${baseType} ${getTypeFromSchema(typeSchema, schema, registry, tracker)}\n\n`;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
output += `${description}export interface ${name} ${getTypeFromSchema(typeSchema, schema, registry, tracker, name)}\n\n`;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Generate regular interface type
|
|
247
|
+
if (name === 'AdminResponse') {
|
|
248
|
+
// Make AdminResponse generic with data type as parameter
|
|
249
|
+
output += `${description}export interface ${name}<T = any> {\n success: boolean;\n data: T;\n}\n\n`;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
const typeStr = getTypeFromSchema(typeSchema, schema, registry, tracker, name);
|
|
253
|
+
if (typeStr.startsWith('extends')) {
|
|
254
|
+
output += `${description}export interface ${name} ${typeStr}\n\n`;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
output += `${description}export type ${name} = ${typeStr}\n\n`;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Add imports
|
|
264
|
+
const currentFile = schemaPath.split(/[\/\\]/).pop().replace('.json', '');
|
|
265
|
+
const imports = Array.from(tracker.imports.entries())
|
|
266
|
+
.filter(([file]) => file !== currentFile)
|
|
267
|
+
.map(([file, typeName]) => `import { ${typeName} } from './${file}.types.js';`);
|
|
268
|
+
if (imports.length > 0) {
|
|
269
|
+
// Replace header with imports and re-add header once
|
|
270
|
+
output = output.replace(FILE_HEADER, FILE_HEADER + imports.join('\n') + '\n\n');
|
|
271
|
+
}
|
|
272
|
+
output += generateRuntimeSchemaExport(schema, typeRegistryImport);
|
|
273
|
+
// Write to output file
|
|
274
|
+
// Get just the filename without path and convert to .types.ts
|
|
275
|
+
const baseFileName = schemaPath.split(/[\/\\]/).pop().replace('.json', '.types.ts');
|
|
276
|
+
const fileName = join(outputDir, baseFileName);
|
|
277
|
+
await fs.writeFile(fileName, output);
|
|
278
|
+
log(`Generated ${fileName}`);
|
|
279
|
+
}
|
|
280
|
+
async function generateIndexFile(files, outputDir) {
|
|
281
|
+
let output = FILE_HEADER;
|
|
282
|
+
// Add imports to trigger schema registration
|
|
283
|
+
output += '// Import all type files to trigger schema registration\n';
|
|
284
|
+
files.forEach(file => {
|
|
285
|
+
const baseFileName = file.replace('.json', '.types.js');
|
|
286
|
+
output += `import './${baseFileName}';\n`;
|
|
287
|
+
});
|
|
288
|
+
output += '\n';
|
|
289
|
+
// Add re-exports
|
|
290
|
+
output += '// Re-export types\n';
|
|
291
|
+
files.forEach(file => {
|
|
292
|
+
const baseFileName = file.replace('.json', '.types.js');
|
|
293
|
+
output += `export * from './${baseFileName}';\n`;
|
|
294
|
+
});
|
|
295
|
+
await fs.writeFile(join(outputDir, 'index.ts'), output);
|
|
296
|
+
log('Generated index.ts');
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Generate TypeScript types from JSON schemas
|
|
300
|
+
* @param schemaDirs Array of directories containing JSON schema files (default: ['./typesrc'])
|
|
301
|
+
* @param outputDir Directory to output generated TypeScript files (default: './src/types')
|
|
302
|
+
* @param typeRegistryImport Import path for the type registry (default: '@themeparks/typelib')
|
|
303
|
+
*/
|
|
304
|
+
export async function generateTypes({ schemaDirs = DEFAULT_SCHEMA_DIRS, outputDir = DEFAULT_OUTPUT_DIR, typeRegistryImport = "@themeparks/typelib" } = {}) {
|
|
305
|
+
try {
|
|
306
|
+
log(`Generating types from schema directories: ${schemaDirs.join(', ')}`);
|
|
307
|
+
log(`Output directory: ${outputDir}`);
|
|
308
|
+
// First build the type registry from all schema directories
|
|
309
|
+
const registry = await buildTypeRegistry(schemaDirs);
|
|
310
|
+
// make sure output directory exists
|
|
311
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
312
|
+
// Collect all JSON files from all schema directories
|
|
313
|
+
const allJsonFiles = [];
|
|
314
|
+
const processedFiles = new Set(); // Track processed filenames to avoid duplicates
|
|
315
|
+
for (const schemaDir of schemaDirs) {
|
|
316
|
+
try {
|
|
317
|
+
const files = await fs.readdir(schemaDir);
|
|
318
|
+
const jsonFiles = files.filter(f => f.endsWith('.json'));
|
|
319
|
+
// Generate type files with cross-file reference support
|
|
320
|
+
for (const file of jsonFiles) {
|
|
321
|
+
const schemaPath = join(schemaDir, file);
|
|
322
|
+
await generateTypeFile(schemaPath, registry, outputDir, typeRegistryImport);
|
|
323
|
+
// Track this filename for the index file (avoid duplicates)
|
|
324
|
+
if (!processedFiles.has(file)) {
|
|
325
|
+
allJsonFiles.push(file);
|
|
326
|
+
processedFiles.add(file);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
log(`Skipping schema directory ${schemaDir}: ${err.message}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// Generate index.ts with all processed files
|
|
335
|
+
await generateIndexFile(allJsonFiles, outputDir);
|
|
336
|
+
log('Type generation complete!');
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
error('Error generating types:', err);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACvE,cAAc,kBAAkB,CAAC;AAGjC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Main stub. Expose type register functions and all generated types.
|
|
2
|
+
export { registerTypeSchema, getTypeSchema } from './type_register.js';
|
|
3
|
+
export * from './types/index.js';
|
|
4
|
+
// export the generate types function, for use in build scripts or other projects
|
|
5
|
+
export { generateTypes } from './generate_types.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run_generate_types.d.ts","sourceRoot":"","sources":["../src/run_generate_types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register an OpenAPI schema for runtime type checking
|
|
3
|
+
* @param name The type name (e.g. "DestinationsResponse")
|
|
4
|
+
* @param schema The OpenAPI schema definition
|
|
5
|
+
*/
|
|
6
|
+
export declare function registerTypeSchema(name: string, schema: any): void;
|
|
7
|
+
/**
|
|
8
|
+
* Retrieve the OpenAPI schema for a registered type
|
|
9
|
+
* @param name The type name
|
|
10
|
+
* @returns The OpenAPI schema or undefined if not found
|
|
11
|
+
*/
|
|
12
|
+
export declare function getTypeSchema(name: string): any | undefined;
|
|
13
|
+
//# sourceMappingURL=type_register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type_register.d.ts","sourceRoot":"","sources":["../src/type_register.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI,CAElE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAE3D"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Store OpenAPI schemas for runtime type information
|
|
2
|
+
const typeMetadataRegistry = new Map();
|
|
3
|
+
/**
|
|
4
|
+
* Register an OpenAPI schema for runtime type checking
|
|
5
|
+
* @param name The type name (e.g. "DestinationsResponse")
|
|
6
|
+
* @param schema The OpenAPI schema definition
|
|
7
|
+
*/
|
|
8
|
+
export function registerTypeSchema(name, schema) {
|
|
9
|
+
typeMetadataRegistry.set(name, schema);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Retrieve the OpenAPI schema for a registered type
|
|
13
|
+
* @param name The type name
|
|
14
|
+
* @returns The OpenAPI schema or undefined if not found
|
|
15
|
+
*/
|
|
16
|
+
export function getTypeSchema(name) {
|
|
17
|
+
return typeMetadataRegistry.get(name);
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAGA,OAAO,eAAe,CAAC;AAGvB,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v1.types.d.ts","sourceRoot":"","sources":["../../src/types/v1.types.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAEzB,yCAAyC;IACzC,OAAO,EAAE,OAAO,CAAC;IAEjB,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAEhB,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;CACnB,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// THIS FILE IS GENERATED - DO NOT EDIT DIRECTLY
|
|
2
|
+
// Runtime Schema Registration
|
|
3
|
+
import { registerTypeSchema } from "../type_register.js";
|
|
4
|
+
registerTypeSchema("V1RootResponse", {
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": [
|
|
7
|
+
"success",
|
|
8
|
+
"message",
|
|
9
|
+
"version"
|
|
10
|
+
],
|
|
11
|
+
"properties": {
|
|
12
|
+
"success": {
|
|
13
|
+
"type": "boolean",
|
|
14
|
+
"description": "Whether the request was successful"
|
|
15
|
+
},
|
|
16
|
+
"message": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Welcome message for the API"
|
|
19
|
+
},
|
|
20
|
+
"version": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "API version number"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@themeparks/typelib",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"module": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"description": "TypeScript definitions for ThemeParks.wiki API",
|
|
9
|
+
"keywords": ["typescript", "types", "api", "schema", "generation"],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./generate": {
|
|
17
|
+
"types": "./dist/generate_types.d.ts",
|
|
18
|
+
"import": "./dist/generate_types.js",
|
|
19
|
+
"require": "./dist/generate_types.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/**/*",
|
|
24
|
+
"typesrc/**/*",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
29
|
+
"generate:types": "npx tsx ./src/run_generate_types.ts",
|
|
30
|
+
"build": "npm run generate:types && tsc",
|
|
31
|
+
"prepublishOnly": "npm run build",
|
|
32
|
+
"publish": "npm publish --access public"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^24.5.2",
|
|
36
|
+
"tsx": "^4.7.1",
|
|
37
|
+
"typescript": "^5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=24.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/typesrc/v1.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "V1Root",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"V1RootResponse": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"required": ["success", "message", "version"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"success": {
|
|
11
|
+
"type": "boolean",
|
|
12
|
+
"description": "Whether the request was successful"
|
|
13
|
+
},
|
|
14
|
+
"message": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Welcome message for the API"
|
|
17
|
+
},
|
|
18
|
+
"version": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "API version number"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|