@ifc-lite/codegen 1.15.4 → 1.15.6
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 +18 -15
- package/package.json +16 -6
- package/.turbo/turbo-build.log +0 -4
- package/CHANGELOG.md +0 -165
- package/INTEGRATION.md +0 -360
- package/generated/ifc4/entities.ts +0 -7400
- package/generated/ifc4/enums.ts +0 -2467
- package/generated/ifc4/index.ts +0 -17
- package/generated/ifc4/schema-registry.ts +0 -63186
- package/generated/ifc4/selects.ts +0 -315
- package/generated/ifc4/serializers.ts +0 -51
- package/generated/ifc4/test-compile.ts +0 -49
- package/generated/ifc4/type-ids.ts +0 -1682
- package/generated/ifc4/types.ts +0 -401
- package/generated/ifc4x3/entities.ts +0 -8300
- package/generated/ifc4x3/enums.ts +0 -3096
- package/generated/ifc4x3/index.ts +0 -17
- package/generated/ifc4x3/schema-registry.ts +0 -71489
- package/generated/ifc4x3/selects.ts +0 -312
- package/generated/ifc4x3/serializers.ts +0 -51
- package/generated/ifc4x3/test-compile.ts +0 -49
- package/generated/ifc4x3/type-ids.ts +0 -1882
- package/generated/ifc4x3/types.ts +0 -407
- package/src/cli.ts +0 -72
- package/src/crc32.ts +0 -77
- package/src/express-parser.ts +0 -457
- package/src/generator.ts +0 -268
- package/src/index.ts +0 -57
- package/src/rust-generator.ts +0 -714
- package/src/serialization-generator.ts +0 -74
- package/src/type-ids-generator.ts +0 -166
- package/src/typescript-generator.ts +0 -631
- package/test/express-parser.test.ts +0 -363
- package/test/integration.test.ts +0 -246
- package/test/serialization-generator.test.ts +0 -65
- package/test/typescript-generator.test.ts +0 -348
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -18
|
@@ -1,631 +0,0 @@
|
|
|
1
|
-
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
-
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* TypeScript Code Generator
|
|
7
|
-
*
|
|
8
|
-
* Generates TypeScript interfaces, types, and schema registry from parsed EXPRESS schemas.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type {
|
|
12
|
-
ExpressSchema,
|
|
13
|
-
EntityDefinition,
|
|
14
|
-
AttributeDefinition,
|
|
15
|
-
EnumDefinition,
|
|
16
|
-
SelectDefinition,
|
|
17
|
-
TypeDefinition,
|
|
18
|
-
} from './express-parser.js';
|
|
19
|
-
import { getAllAttributes, getInheritanceChain } from './express-parser.js';
|
|
20
|
-
|
|
21
|
-
export interface GeneratedCode {
|
|
22
|
-
entities: string; // Entity interfaces
|
|
23
|
-
types: string; // Type aliases
|
|
24
|
-
enums: string; // Enum definitions
|
|
25
|
-
selects: string; // Union types
|
|
26
|
-
schemaRegistry: string; // Runtime schema metadata
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Generate all TypeScript code from EXPRESS schema
|
|
31
|
-
*/
|
|
32
|
-
export function generateTypeScript(schema: ExpressSchema): GeneratedCode {
|
|
33
|
-
return {
|
|
34
|
-
entities: generateEntityInterfaces(schema),
|
|
35
|
-
types: generateTypeAliases(schema),
|
|
36
|
-
enums: generateEnums(schema),
|
|
37
|
-
selects: generateSelectTypes(schema),
|
|
38
|
-
schemaRegistry: generateSchemaRegistry(schema),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Generate entity interfaces
|
|
44
|
-
*/
|
|
45
|
-
function generateEntityInterfaces(schema: ExpressSchema): string {
|
|
46
|
-
// Collect type, enum, and select names - filtering types that are also enums/selects
|
|
47
|
-
const enumNamesSet = new Set(schema.enums.map(e => e.name));
|
|
48
|
-
const selectNamesSet = new Set(schema.selects.map(s => s.name));
|
|
49
|
-
|
|
50
|
-
// Types to import from types.ts (exclude those that are enums or selects)
|
|
51
|
-
const typeNames = schema.types
|
|
52
|
-
.filter(t => !enumNamesSet.has(t.name) && !selectNamesSet.has(t.name))
|
|
53
|
-
.map(t => t.name);
|
|
54
|
-
const enumNames = schema.enums.map(e => e.name);
|
|
55
|
-
const selectNames = schema.selects.map(s => s.name);
|
|
56
|
-
|
|
57
|
-
let code = `/**
|
|
58
|
-
* IFC Entity Interfaces
|
|
59
|
-
* Generated from EXPRESS schema: ${schema.name}
|
|
60
|
-
*
|
|
61
|
-
* DO NOT EDIT - This file is auto-generated
|
|
62
|
-
*/
|
|
63
|
-
|
|
64
|
-
`;
|
|
65
|
-
|
|
66
|
-
// Add imports for types, enums, and selects
|
|
67
|
-
if (typeNames.length > 0) {
|
|
68
|
-
code += `import type {\n ${typeNames.join(',\n ')},\n} from './types.js';\n\n`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (enumNames.length > 0) {
|
|
72
|
-
code += `import type {\n ${enumNames.join(',\n ')},\n} from './enums.js';\n\n`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (selectNames.length > 0) {
|
|
76
|
-
code += `import type {\n ${selectNames.join(',\n ')},\n} from './selects.js';\n\n`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Sort entities by dependency order (parents before children)
|
|
80
|
-
const sortedEntities = topologicalSort(schema.entities);
|
|
81
|
-
|
|
82
|
-
for (const entity of sortedEntities) {
|
|
83
|
-
code += generateEntityInterface(entity, schema);
|
|
84
|
-
code += '\n\n';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return code;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Generate a single entity interface
|
|
92
|
-
*/
|
|
93
|
-
function generateEntityInterface(
|
|
94
|
-
entity: EntityDefinition,
|
|
95
|
-
schema: ExpressSchema
|
|
96
|
-
): string {
|
|
97
|
-
let code = '';
|
|
98
|
-
|
|
99
|
-
// Add JSDoc comment
|
|
100
|
-
code += `/**\n * ${entity.name}\n`;
|
|
101
|
-
if (entity.isAbstract) {
|
|
102
|
-
code += ` * @abstract\n`;
|
|
103
|
-
}
|
|
104
|
-
if (entity.supertype) {
|
|
105
|
-
code += ` * @extends ${entity.supertype}\n`;
|
|
106
|
-
}
|
|
107
|
-
code += ` */\n`;
|
|
108
|
-
|
|
109
|
-
// Generate interface
|
|
110
|
-
code += `export interface ${entity.name}`;
|
|
111
|
-
|
|
112
|
-
// Add extends clause
|
|
113
|
-
if (entity.supertype) {
|
|
114
|
-
code += ` extends ${entity.supertype}`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
code += ` {\n`;
|
|
118
|
-
|
|
119
|
-
// Add attributes (only this level, not inherited)
|
|
120
|
-
for (const attr of entity.attributes) {
|
|
121
|
-
code += generateAttribute(attr);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
code += `}`;
|
|
125
|
-
|
|
126
|
-
return code;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Generate an attribute declaration
|
|
131
|
-
*/
|
|
132
|
-
function generateAttribute(attr: AttributeDefinition): string {
|
|
133
|
-
let code = ` ${attr.name}`;
|
|
134
|
-
|
|
135
|
-
// Add optional marker
|
|
136
|
-
if (attr.optional) {
|
|
137
|
-
code += '?';
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
code += ': ';
|
|
141
|
-
|
|
142
|
-
// Map EXPRESS type to TypeScript type
|
|
143
|
-
let tsType = mapExpressTypeToTypeScript(attr.type);
|
|
144
|
-
|
|
145
|
-
// Wrap in array if needed
|
|
146
|
-
// Note: attr.type may already contain [] for nested collections from the parser
|
|
147
|
-
if (attr.isArray || attr.isList || attr.isSet) {
|
|
148
|
-
// Parenthesize union element types so `boolean | null` becomes
|
|
149
|
-
// `(boolean | null)[]` rather than the precedence-misparsed `boolean | null[]`.
|
|
150
|
-
tsType = /\|/.test(tsType) ? `(${tsType})[]` : `${tsType}[]`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
code += tsType;
|
|
154
|
-
code += ';\n';
|
|
155
|
-
|
|
156
|
-
return code;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Map EXPRESS types to TypeScript types
|
|
161
|
-
*/
|
|
162
|
-
function mapExpressTypeToTypeScript(expressType: string): string {
|
|
163
|
-
// Handle basic EXPRESS types
|
|
164
|
-
const typeMap: Record<string, string> = {
|
|
165
|
-
REAL: 'number',
|
|
166
|
-
INTEGER: 'number',
|
|
167
|
-
NUMBER: 'number',
|
|
168
|
-
BOOLEAN: 'boolean',
|
|
169
|
-
LOGICAL: 'boolean | null',
|
|
170
|
-
STRING: 'string',
|
|
171
|
-
BINARY: 'string',
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
// Handle STRING(N) and STRING(N) FIXED patterns
|
|
175
|
-
if (/^STRING\s*\(/i.test(expressType)) {
|
|
176
|
-
return 'string';
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Handle BINARY(N) patterns
|
|
180
|
-
if (/^BINARY\s*\(/i.test(expressType)) {
|
|
181
|
-
return 'string';
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Handle LIST [N:?] OF X, SET [N:?] OF X, ARRAY [N:M] OF X patterns
|
|
185
|
-
const collectionMatch = expressType.match(/^(LIST|SET|ARRAY)\s*\[.*?\]\s*OF\s+(.+)$/i);
|
|
186
|
-
if (collectionMatch) {
|
|
187
|
-
const innerType = mapExpressTypeToTypeScript(collectionMatch[2].trim());
|
|
188
|
-
// Parenthesize union element types to preserve `(union)[]` precedence.
|
|
189
|
-
return /\|/.test(innerType) ? `(${innerType})[]` : `${innerType}[]`;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Handle ENUMERATION OF (...) - these should be handled by enum generation
|
|
193
|
-
if (/^ENUMERATION\s+OF/i.test(expressType)) {
|
|
194
|
-
return 'string'; // Fallback to string for inline enumerations
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Check if it's a measure type (ends with Measure)
|
|
198
|
-
if (expressType.endsWith('Measure')) {
|
|
199
|
-
return 'number';
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Check if it's a simple type in our map
|
|
203
|
-
const upperType = expressType.toUpperCase();
|
|
204
|
-
if (typeMap[upperType]) {
|
|
205
|
-
return typeMap[upperType];
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Check for IFC types
|
|
209
|
-
if (expressType.startsWith('Ifc')) {
|
|
210
|
-
return expressType;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Default: use as-is (likely a custom type or entity reference)
|
|
214
|
-
return expressType;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Generate type aliases
|
|
219
|
-
*/
|
|
220
|
-
function generateTypeAliases(schema: ExpressSchema): string {
|
|
221
|
-
// Collect entity names and type names
|
|
222
|
-
const entityNames = new Set(schema.entities.map(e => e.name));
|
|
223
|
-
const enumNames = new Set(schema.enums.map(e => e.name));
|
|
224
|
-
const selectNames = new Set(schema.selects.map(s => s.name));
|
|
225
|
-
|
|
226
|
-
// Filter out types that are also defined as enums or selects (they have separate files)
|
|
227
|
-
const typesToGenerate = schema.types.filter(
|
|
228
|
-
t => !enumNames.has(t.name) && !selectNames.has(t.name)
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
// Track referenced entities
|
|
232
|
-
const referencedEntities = new Set<string>();
|
|
233
|
-
|
|
234
|
-
for (const type of typesToGenerate) {
|
|
235
|
-
const mapped = mapExpressTypeToTypeScript(type.underlyingType);
|
|
236
|
-
// Check if the underlying type or array element type is an entity
|
|
237
|
-
const baseType = mapped.replace(/\[\]$/, '');
|
|
238
|
-
if (entityNames.has(baseType)) {
|
|
239
|
-
referencedEntities.add(baseType);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
let code = `/**
|
|
244
|
-
* IFC Type Aliases
|
|
245
|
-
* Generated from EXPRESS schema: ${schema.name}
|
|
246
|
-
*
|
|
247
|
-
* DO NOT EDIT - This file is auto-generated
|
|
248
|
-
*/
|
|
249
|
-
|
|
250
|
-
`;
|
|
251
|
-
|
|
252
|
-
// Add imports for referenced entities (circular but allowed with import type)
|
|
253
|
-
if (referencedEntities.size > 0) {
|
|
254
|
-
const sortedEntities = Array.from(referencedEntities).sort();
|
|
255
|
-
code += `import type {\n ${sortedEntities.join(',\n ')},\n} from './entities.js';\n\n`;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
for (const type of typesToGenerate) {
|
|
259
|
-
code += `/** ${type.name} */\n`;
|
|
260
|
-
code += `export type ${type.name} = ${mapExpressTypeToTypeScript(type.underlyingType)};\n\n`;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return code;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Generate enum definitions
|
|
268
|
-
*/
|
|
269
|
-
function generateEnums(schema: ExpressSchema): string {
|
|
270
|
-
let code = `/**
|
|
271
|
-
* IFC Enumerations
|
|
272
|
-
* Generated from EXPRESS schema: ${schema.name}
|
|
273
|
-
*
|
|
274
|
-
* DO NOT EDIT - This file is auto-generated
|
|
275
|
-
*/
|
|
276
|
-
|
|
277
|
-
`;
|
|
278
|
-
|
|
279
|
-
for (const enumDef of schema.enums) {
|
|
280
|
-
code += generateEnum(enumDef);
|
|
281
|
-
code += '\n\n';
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return code;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Generate a single enum
|
|
289
|
-
*/
|
|
290
|
-
function generateEnum(enumDef: EnumDefinition): string {
|
|
291
|
-
let code = `/** ${enumDef.name} */\n`;
|
|
292
|
-
code += `export enum ${enumDef.name} {\n`;
|
|
293
|
-
|
|
294
|
-
for (const value of enumDef.values) {
|
|
295
|
-
// Convert to PascalCase for enum member
|
|
296
|
-
const memberName = value.toUpperCase();
|
|
297
|
-
code += ` ${memberName} = '${value}',\n`;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
code += `}`;
|
|
301
|
-
|
|
302
|
-
return code;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Generate SELECT type unions
|
|
307
|
-
*/
|
|
308
|
-
function generateSelectTypes(schema: ExpressSchema): string {
|
|
309
|
-
// Collect referenced types from selects
|
|
310
|
-
const entityNames = new Set(schema.entities.map(e => e.name));
|
|
311
|
-
const enumNames = new Set(schema.enums.map(e => e.name));
|
|
312
|
-
const selectNames = new Set(schema.selects.map(s => s.name));
|
|
313
|
-
// Types in types.ts (filtered: not enums, not selects)
|
|
314
|
-
const typeNames = new Set(
|
|
315
|
-
schema.types
|
|
316
|
-
.filter(t => !enumNames.has(t.name) && !selectNames.has(t.name))
|
|
317
|
-
.map(t => t.name)
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
// Track which names are referenced in selects
|
|
321
|
-
const referencedEntities = new Set<string>();
|
|
322
|
-
const referencedTypes = new Set<string>();
|
|
323
|
-
const referencedEnums = new Set<string>();
|
|
324
|
-
|
|
325
|
-
for (const select of schema.selects) {
|
|
326
|
-
for (const typeName of select.types) {
|
|
327
|
-
const mapped = mapExpressTypeToTypeScript(typeName);
|
|
328
|
-
if (entityNames.has(mapped)) {
|
|
329
|
-
referencedEntities.add(mapped);
|
|
330
|
-
} else if (typeNames.has(mapped)) {
|
|
331
|
-
referencedTypes.add(mapped);
|
|
332
|
-
} else if (enumNames.has(mapped)) {
|
|
333
|
-
referencedEnums.add(mapped);
|
|
334
|
-
}
|
|
335
|
-
// Selects referencing other selects are fine - they're in the same file
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
let code = `/**
|
|
340
|
-
* IFC SELECT Types (Unions)
|
|
341
|
-
* Generated from EXPRESS schema: ${schema.name}
|
|
342
|
-
*
|
|
343
|
-
* DO NOT EDIT - This file is auto-generated
|
|
344
|
-
*/
|
|
345
|
-
|
|
346
|
-
`;
|
|
347
|
-
|
|
348
|
-
// Add imports for entities (circular but allowed with import type)
|
|
349
|
-
if (referencedEntities.size > 0) {
|
|
350
|
-
const sortedEntities = Array.from(referencedEntities).sort();
|
|
351
|
-
code += `import type {\n ${sortedEntities.join(',\n ')},\n} from './entities.js';\n\n`;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Add imports for types
|
|
355
|
-
if (referencedTypes.size > 0) {
|
|
356
|
-
const sortedTypes = Array.from(referencedTypes).sort();
|
|
357
|
-
code += `import type {\n ${sortedTypes.join(',\n ')},\n} from './types.js';\n\n`;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Add imports for enums
|
|
361
|
-
if (referencedEnums.size > 0) {
|
|
362
|
-
const sortedEnums = Array.from(referencedEnums).sort();
|
|
363
|
-
code += `import type {\n ${sortedEnums.join(',\n ')},\n} from './enums.js';\n\n`;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
for (const select of schema.selects) {
|
|
367
|
-
code += `/** ${select.name} */\n`;
|
|
368
|
-
code += `export type ${select.name} = `;
|
|
369
|
-
|
|
370
|
-
// Join types with |
|
|
371
|
-
const tsTypes = select.types.map(t => mapExpressTypeToTypeScript(t));
|
|
372
|
-
code += tsTypes.join(' | ');
|
|
373
|
-
|
|
374
|
-
code += ';\n\n';
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
return code;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Generate schema registry with runtime metadata
|
|
382
|
-
*/
|
|
383
|
-
function generateSchemaRegistry(schema: ExpressSchema): string {
|
|
384
|
-
let code = `/**
|
|
385
|
-
* IFC Schema Registry
|
|
386
|
-
* Generated from EXPRESS schema: ${schema.name}
|
|
387
|
-
*
|
|
388
|
-
* Runtime metadata for IFC entities, types, and relationships.
|
|
389
|
-
*
|
|
390
|
-
* DO NOT EDIT - This file is auto-generated
|
|
391
|
-
*/
|
|
392
|
-
|
|
393
|
-
export interface EntityMetadata {
|
|
394
|
-
name: string;
|
|
395
|
-
isAbstract: boolean;
|
|
396
|
-
parent?: string;
|
|
397
|
-
attributes: AttributeMetadata[];
|
|
398
|
-
allAttributes?: AttributeMetadata[]; // Including inherited
|
|
399
|
-
inheritanceChain?: string[]; // From root to entity
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
export interface AttributeMetadata {
|
|
403
|
-
name: string;
|
|
404
|
-
type: string;
|
|
405
|
-
optional: boolean;
|
|
406
|
-
isArray: boolean;
|
|
407
|
-
isList: boolean;
|
|
408
|
-
isSet: boolean;
|
|
409
|
-
arrayBounds?: [number, number];
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
export interface SchemaRegistry {
|
|
413
|
-
name: string;
|
|
414
|
-
entities: Record<string, EntityMetadata>;
|
|
415
|
-
types: Record<string, string>; // name -> underlying type
|
|
416
|
-
enums: Record<string, string[]>; // name -> values
|
|
417
|
-
selects: Record<string, string[]>; // name -> types
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
export const SCHEMA_REGISTRY: SchemaRegistry = {
|
|
421
|
-
name: '${schema.name}',
|
|
422
|
-
|
|
423
|
-
entities: {
|
|
424
|
-
`;
|
|
425
|
-
|
|
426
|
-
// Generate entity metadata
|
|
427
|
-
for (const entity of schema.entities) {
|
|
428
|
-
code += generateEntityMetadata(entity, schema);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
code += ` },
|
|
432
|
-
|
|
433
|
-
types: {
|
|
434
|
-
`;
|
|
435
|
-
|
|
436
|
-
// Generate type metadata (escape single quotes in underlying types)
|
|
437
|
-
for (const type of schema.types) {
|
|
438
|
-
const escapedType = type.underlyingType.replace(/'/g, "\\'").replace(/\n/g, ' ');
|
|
439
|
-
code += ` ${type.name}: '${escapedType}',\n`;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
code += ` },
|
|
443
|
-
|
|
444
|
-
enums: {
|
|
445
|
-
`;
|
|
446
|
-
|
|
447
|
-
// Generate enum metadata (escape single quotes in values)
|
|
448
|
-
for (const enumDef of schema.enums) {
|
|
449
|
-
const escapedValues = enumDef.values.map(v => `'${v.replace(/'/g, "\\'")}'`);
|
|
450
|
-
code += ` ${enumDef.name}: [${escapedValues.join(', ')}],\n`;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
code += ` },
|
|
454
|
-
|
|
455
|
-
selects: {
|
|
456
|
-
`;
|
|
457
|
-
|
|
458
|
-
// Generate select metadata (escape single quotes in types)
|
|
459
|
-
for (const select of schema.selects) {
|
|
460
|
-
const escapedTypes = select.types.map(t => `'${t.replace(/'/g, "\\'")}'`);
|
|
461
|
-
code += ` ${select.name}: [${escapedTypes.join(', ')}],\n`;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
code += ` },
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Get entity metadata by name (case-insensitive)
|
|
469
|
-
*/
|
|
470
|
-
export function getEntityMetadata(typeName: string): EntityMetadata | undefined {
|
|
471
|
-
// Normalize to IfcXxx format
|
|
472
|
-
const normalized = normalizeTypeName(typeName);
|
|
473
|
-
return SCHEMA_REGISTRY.entities[normalized];
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Get all attributes for an entity (including inherited)
|
|
478
|
-
*/
|
|
479
|
-
export function getAllAttributesForEntity(typeName: string): AttributeMetadata[] {
|
|
480
|
-
const metadata = getEntityMetadata(typeName);
|
|
481
|
-
return metadata?.allAttributes || [];
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Get inheritance chain for an entity
|
|
486
|
-
*/
|
|
487
|
-
export function getInheritanceChainForEntity(typeName: string): string[] {
|
|
488
|
-
const metadata = getEntityMetadata(typeName);
|
|
489
|
-
return metadata?.inheritanceChain || [];
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Check if a type is a known entity
|
|
494
|
-
*/
|
|
495
|
-
export function isKnownEntity(typeName: string): boolean {
|
|
496
|
-
const normalized = normalizeTypeName(typeName);
|
|
497
|
-
return normalized in SCHEMA_REGISTRY.entities;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* Normalize type name to IfcXxx format
|
|
502
|
-
*/
|
|
503
|
-
function normalizeTypeName(name: string): string {
|
|
504
|
-
// Already in IfcPascalCase — return as-is
|
|
505
|
-
if (name.startsWith('Ifc') && name.length > 3 && name[3] >= 'A' && name[3] <= 'Z') {
|
|
506
|
-
return name;
|
|
507
|
-
}
|
|
508
|
-
// Convert UPPERCASE STEP names: IFCWALL -> IfcWall, IFCWALLTYPE -> IfcWallType
|
|
509
|
-
const upper = name.toUpperCase();
|
|
510
|
-
if (upper.startsWith('IFC')) {
|
|
511
|
-
const rest = upper.substring(3);
|
|
512
|
-
// Lookup by matching uppercase keys in the registry
|
|
513
|
-
for (const key of Object.keys(SCHEMA_REGISTRY.entities)) {
|
|
514
|
-
if (key.toUpperCase() === 'IFC' + rest) return key;
|
|
515
|
-
}
|
|
516
|
-
// Fallback: best-effort single-word conversion
|
|
517
|
-
return 'Ifc' + rest.charAt(0) + rest.substring(1).toLowerCase();
|
|
518
|
-
}
|
|
519
|
-
return name;
|
|
520
|
-
}
|
|
521
|
-
`;
|
|
522
|
-
|
|
523
|
-
return code;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
/**
|
|
527
|
-
* Generate metadata for a single entity
|
|
528
|
-
*/
|
|
529
|
-
function generateEntityMetadata(
|
|
530
|
-
entity: EntityDefinition,
|
|
531
|
-
schema: ExpressSchema
|
|
532
|
-
): string {
|
|
533
|
-
let code = ` ${entity.name}: {\n`;
|
|
534
|
-
code += ` name: '${entity.name}',\n`;
|
|
535
|
-
code += ` isAbstract: ${entity.isAbstract},\n`;
|
|
536
|
-
|
|
537
|
-
if (entity.supertype) {
|
|
538
|
-
code += ` parent: '${entity.supertype}',\n`;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
code += ` attributes: [\n`;
|
|
542
|
-
for (const attr of entity.attributes) {
|
|
543
|
-
code += ` {\n`;
|
|
544
|
-
code += ` name: '${attr.name}',\n`;
|
|
545
|
-
code += ` type: '${attr.type}',\n`;
|
|
546
|
-
code += ` optional: ${attr.optional},\n`;
|
|
547
|
-
code += ` isArray: ${attr.isArray},\n`;
|
|
548
|
-
code += ` isList: ${attr.isList},\n`;
|
|
549
|
-
code += ` isSet: ${attr.isSet},\n`;
|
|
550
|
-
if (attr.arrayBounds) {
|
|
551
|
-
code += ` arrayBounds: [${attr.arrayBounds[0]}, ${attr.arrayBounds[1]}],\n`;
|
|
552
|
-
}
|
|
553
|
-
code += ` },\n`;
|
|
554
|
-
}
|
|
555
|
-
code += ` ],\n`;
|
|
556
|
-
|
|
557
|
-
// Add all attributes (including inherited)
|
|
558
|
-
const allAttrs = getAllAttributes(entity, schema);
|
|
559
|
-
code += ` allAttributes: [\n`;
|
|
560
|
-
for (const attr of allAttrs) {
|
|
561
|
-
code += ` {\n`;
|
|
562
|
-
code += ` name: '${attr.name}',\n`;
|
|
563
|
-
code += ` type: '${attr.type}',\n`;
|
|
564
|
-
code += ` optional: ${attr.optional},\n`;
|
|
565
|
-
code += ` isArray: ${attr.isArray},\n`;
|
|
566
|
-
code += ` isList: ${attr.isList},\n`;
|
|
567
|
-
code += ` isSet: ${attr.isSet},\n`;
|
|
568
|
-
if (attr.arrayBounds) {
|
|
569
|
-
code += ` arrayBounds: [${attr.arrayBounds[0]}, ${attr.arrayBounds[1]}],\n`;
|
|
570
|
-
}
|
|
571
|
-
code += ` },\n`;
|
|
572
|
-
}
|
|
573
|
-
code += ` ],\n`;
|
|
574
|
-
|
|
575
|
-
// Add inheritance chain
|
|
576
|
-
const chain = getInheritanceChain(entity, schema);
|
|
577
|
-
code += ` inheritanceChain: [${chain.map(c => `'${c}'`).join(', ')}],\n`;
|
|
578
|
-
|
|
579
|
-
code += ` },\n`;
|
|
580
|
-
|
|
581
|
-
return code;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* Topological sort of entities by dependency order
|
|
586
|
-
* Ensures parent entities are generated before children
|
|
587
|
-
*/
|
|
588
|
-
function topologicalSort(entities: EntityDefinition[]): EntityDefinition[] {
|
|
589
|
-
const sorted: EntityDefinition[] = [];
|
|
590
|
-
const visited = new Set<string>();
|
|
591
|
-
|
|
592
|
-
function visit(entity: EntityDefinition) {
|
|
593
|
-
if (visited.has(entity.name)) {
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
visited.add(entity.name);
|
|
598
|
-
|
|
599
|
-
// Visit parent first
|
|
600
|
-
if (entity.supertype) {
|
|
601
|
-
const parent = entities.find(e => e.name === entity.supertype);
|
|
602
|
-
if (parent) {
|
|
603
|
-
visit(parent);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
sorted.push(entity);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
for (const entity of entities) {
|
|
611
|
-
visit(entity);
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
return sorted;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* Write generated code to files
|
|
619
|
-
*/
|
|
620
|
-
export function writeGeneratedFiles(
|
|
621
|
-
code: GeneratedCode,
|
|
622
|
-
outputDir: string
|
|
623
|
-
): { entities: string; types: string; enums: string; selects: string; schema: string } {
|
|
624
|
-
return {
|
|
625
|
-
entities: `${outputDir}/entities.ts`,
|
|
626
|
-
types: `${outputDir}/types.ts`,
|
|
627
|
-
enums: `${outputDir}/enums.ts`,
|
|
628
|
-
selects: `${outputDir}/selects.ts`,
|
|
629
|
-
schema: `${outputDir}/schema-registry.ts`,
|
|
630
|
-
};
|
|
631
|
-
}
|