@ifc-lite/codegen 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.
Files changed (49) hide show
  1. package/INTEGRATION.md +354 -0
  2. package/LICENSE +373 -0
  3. package/README.md +101 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +35 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/express-parser.d.ts +75 -0
  9. package/dist/express-parser.d.ts.map +1 -0
  10. package/dist/express-parser.js +318 -0
  11. package/dist/express-parser.js.map +1 -0
  12. package/dist/generator.d.ts +10 -0
  13. package/dist/generator.d.ts.map +1 -0
  14. package/dist/generator.js +64 -0
  15. package/dist/generator.js.map +1 -0
  16. package/dist/index.d.ts +9 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +12 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/typescript-generator.d.ts +28 -0
  21. package/dist/typescript-generator.d.ts.map +1 -0
  22. package/dist/typescript-generator.js +400 -0
  23. package/dist/typescript-generator.js.map +1 -0
  24. package/generated/ifc4/entities.ts +6978 -0
  25. package/generated/ifc4/enums.ts +2471 -0
  26. package/generated/ifc4/index.ts +15 -0
  27. package/generated/ifc4/schema-registry.ts +60726 -0
  28. package/generated/ifc4/selects.ts +191 -0
  29. package/generated/ifc4/test-compile.ts +37 -0
  30. package/generated/ifc4/types.ts +3104 -0
  31. package/generated/ifc4x3/entities.ts +7836 -0
  32. package/generated/ifc4x3/enums.ts +3100 -0
  33. package/generated/ifc4x3/index.ts +15 -0
  34. package/generated/ifc4x3/schema-registry.ts +71023 -0
  35. package/generated/ifc4x3/selects.ts +194 -0
  36. package/generated/ifc4x3/types.ts +3707 -0
  37. package/package.json +32 -0
  38. package/schemas/IFC4X3.exp +13984 -0
  39. package/schemas/IFC4_ADD2_TC1.exp +12399 -0
  40. package/src/cli.ts +41 -0
  41. package/src/express-parser.ts +441 -0
  42. package/src/generator.ts +81 -0
  43. package/src/index.ts +31 -0
  44. package/src/typescript-generator.ts +496 -0
  45. package/test/express-parser.test.ts +363 -0
  46. package/test/integration.test.ts +246 -0
  47. package/test/typescript-generator.test.ts +348 -0
  48. package/tsconfig.json +20 -0
  49. package/vitest.config.ts +18 -0
@@ -0,0 +1,496 @@
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
+ let code = `/**
47
+ * IFC Entity Interfaces
48
+ * Generated from EXPRESS schema: ${schema.name}
49
+ *
50
+ * DO NOT EDIT - This file is auto-generated
51
+ */
52
+
53
+ `;
54
+
55
+ // Sort entities by dependency order (parents before children)
56
+ const sortedEntities = topologicalSort(schema.entities);
57
+
58
+ for (const entity of sortedEntities) {
59
+ code += generateEntityInterface(entity, schema);
60
+ code += '\n\n';
61
+ }
62
+
63
+ return code;
64
+ }
65
+
66
+ /**
67
+ * Generate a single entity interface
68
+ */
69
+ function generateEntityInterface(
70
+ entity: EntityDefinition,
71
+ schema: ExpressSchema
72
+ ): string {
73
+ let code = '';
74
+
75
+ // Add JSDoc comment
76
+ code += `/**\n * ${entity.name}\n`;
77
+ if (entity.isAbstract) {
78
+ code += ` * @abstract\n`;
79
+ }
80
+ if (entity.supertype) {
81
+ code += ` * @extends ${entity.supertype}\n`;
82
+ }
83
+ code += ` */\n`;
84
+
85
+ // Generate interface
86
+ code += `export interface ${entity.name}`;
87
+
88
+ // Add extends clause
89
+ if (entity.supertype) {
90
+ code += ` extends ${entity.supertype}`;
91
+ }
92
+
93
+ code += ` {\n`;
94
+
95
+ // Add attributes (only this level, not inherited)
96
+ for (const attr of entity.attributes) {
97
+ code += generateAttribute(attr);
98
+ }
99
+
100
+ code += `}`;
101
+
102
+ return code;
103
+ }
104
+
105
+ /**
106
+ * Generate an attribute declaration
107
+ */
108
+ function generateAttribute(attr: AttributeDefinition): string {
109
+ let code = ` ${attr.name}`;
110
+
111
+ // Add optional marker
112
+ if (attr.optional) {
113
+ code += '?';
114
+ }
115
+
116
+ code += ': ';
117
+
118
+ // Map EXPRESS type to TypeScript type
119
+ let tsType = mapExpressTypeToTypeScript(attr.type);
120
+
121
+ // Wrap in array if needed
122
+ // Note: attr.type may already contain [] for nested collections from the parser
123
+ if (attr.isArray || attr.isList || attr.isSet) {
124
+ tsType = `${tsType}[]`;
125
+ }
126
+
127
+ code += tsType;
128
+ code += ';\n';
129
+
130
+ return code;
131
+ }
132
+
133
+ /**
134
+ * Map EXPRESS types to TypeScript types
135
+ */
136
+ function mapExpressTypeToTypeScript(expressType: string): string {
137
+ // Handle basic EXPRESS types
138
+ const typeMap: Record<string, string> = {
139
+ REAL: 'number',
140
+ INTEGER: 'number',
141
+ NUMBER: 'number',
142
+ BOOLEAN: 'boolean',
143
+ LOGICAL: 'boolean | null',
144
+ STRING: 'string',
145
+ BINARY: 'string',
146
+ };
147
+
148
+ // Check if it's a measure type (ends with Measure)
149
+ if (expressType.endsWith('Measure')) {
150
+ return 'number';
151
+ }
152
+
153
+ // Check if it's a simple type in our map
154
+ const upperType = expressType.toUpperCase();
155
+ if (typeMap[upperType]) {
156
+ return typeMap[upperType];
157
+ }
158
+
159
+ // Check for IFC types
160
+ if (expressType.startsWith('Ifc')) {
161
+ return expressType;
162
+ }
163
+
164
+ // Default: use as-is (likely a custom type or entity reference)
165
+ return expressType;
166
+ }
167
+
168
+ /**
169
+ * Generate type aliases
170
+ */
171
+ function generateTypeAliases(schema: ExpressSchema): string {
172
+ let code = `/**
173
+ * IFC Type Aliases
174
+ * Generated from EXPRESS schema: ${schema.name}
175
+ *
176
+ * DO NOT EDIT - This file is auto-generated
177
+ */
178
+
179
+ `;
180
+
181
+ for (const type of schema.types) {
182
+ code += `/** ${type.name} */\n`;
183
+ code += `export type ${type.name} = ${mapExpressTypeToTypeScript(type.underlyingType)};\n\n`;
184
+ }
185
+
186
+ return code;
187
+ }
188
+
189
+ /**
190
+ * Generate enum definitions
191
+ */
192
+ function generateEnums(schema: ExpressSchema): string {
193
+ let code = `/**
194
+ * IFC Enumerations
195
+ * Generated from EXPRESS schema: ${schema.name}
196
+ *
197
+ * DO NOT EDIT - This file is auto-generated
198
+ */
199
+
200
+ `;
201
+
202
+ for (const enumDef of schema.enums) {
203
+ code += generateEnum(enumDef);
204
+ code += '\n\n';
205
+ }
206
+
207
+ return code;
208
+ }
209
+
210
+ /**
211
+ * Generate a single enum
212
+ */
213
+ function generateEnum(enumDef: EnumDefinition): string {
214
+ let code = `/** ${enumDef.name} */\n`;
215
+ code += `export enum ${enumDef.name} {\n`;
216
+
217
+ for (const value of enumDef.values) {
218
+ // Convert to PascalCase for enum member
219
+ const memberName = value.toUpperCase();
220
+ code += ` ${memberName} = '${value}',\n`;
221
+ }
222
+
223
+ code += `}`;
224
+
225
+ return code;
226
+ }
227
+
228
+ /**
229
+ * Generate SELECT type unions
230
+ */
231
+ function generateSelectTypes(schema: ExpressSchema): string {
232
+ let code = `/**
233
+ * IFC SELECT Types (Unions)
234
+ * Generated from EXPRESS schema: ${schema.name}
235
+ *
236
+ * DO NOT EDIT - This file is auto-generated
237
+ */
238
+
239
+ `;
240
+
241
+ for (const select of schema.selects) {
242
+ code += `/** ${select.name} */\n`;
243
+ code += `export type ${select.name} = `;
244
+
245
+ // Join types with |
246
+ const tsTypes = select.types.map(t => mapExpressTypeToTypeScript(t));
247
+ code += tsTypes.join(' | ');
248
+
249
+ code += ';\n\n';
250
+ }
251
+
252
+ return code;
253
+ }
254
+
255
+ /**
256
+ * Generate schema registry with runtime metadata
257
+ */
258
+ function generateSchemaRegistry(schema: ExpressSchema): string {
259
+ let code = `/**
260
+ * IFC Schema Registry
261
+ * Generated from EXPRESS schema: ${schema.name}
262
+ *
263
+ * Runtime metadata for IFC entities, types, and relationships.
264
+ *
265
+ * DO NOT EDIT - This file is auto-generated
266
+ */
267
+
268
+ export interface EntityMetadata {
269
+ name: string;
270
+ isAbstract: boolean;
271
+ parent?: string;
272
+ attributes: AttributeMetadata[];
273
+ allAttributes?: AttributeMetadata[]; // Including inherited
274
+ inheritanceChain?: string[]; // From root to entity
275
+ }
276
+
277
+ export interface AttributeMetadata {
278
+ name: string;
279
+ type: string;
280
+ optional: boolean;
281
+ isArray: boolean;
282
+ isList: boolean;
283
+ isSet: boolean;
284
+ arrayBounds?: [number, number];
285
+ }
286
+
287
+ export interface SchemaRegistry {
288
+ name: string;
289
+ entities: Record<string, EntityMetadata>;
290
+ types: Record<string, string>; // name -> underlying type
291
+ enums: Record<string, string[]>; // name -> values
292
+ selects: Record<string, string[]>; // name -> types
293
+ }
294
+
295
+ export const SCHEMA_REGISTRY: SchemaRegistry = {
296
+ name: '${schema.name}',
297
+
298
+ entities: {
299
+ `;
300
+
301
+ // Generate entity metadata
302
+ for (const entity of schema.entities) {
303
+ code += generateEntityMetadata(entity, schema);
304
+ }
305
+
306
+ code += ` },
307
+
308
+ types: {
309
+ `;
310
+
311
+ // Generate type metadata (escape single quotes in underlying types)
312
+ for (const type of schema.types) {
313
+ const escapedType = type.underlyingType.replace(/'/g, "\\'").replace(/\n/g, ' ');
314
+ code += ` ${type.name}: '${escapedType}',\n`;
315
+ }
316
+
317
+ code += ` },
318
+
319
+ enums: {
320
+ `;
321
+
322
+ // Generate enum metadata (escape single quotes in values)
323
+ for (const enumDef of schema.enums) {
324
+ const escapedValues = enumDef.values.map(v => `'${v.replace(/'/g, "\\'")}'`);
325
+ code += ` ${enumDef.name}: [${escapedValues.join(', ')}],\n`;
326
+ }
327
+
328
+ code += ` },
329
+
330
+ selects: {
331
+ `;
332
+
333
+ // Generate select metadata (escape single quotes in types)
334
+ for (const select of schema.selects) {
335
+ const escapedTypes = select.types.map(t => `'${t.replace(/'/g, "\\'")}'`);
336
+ code += ` ${select.name}: [${escapedTypes.join(', ')}],\n`;
337
+ }
338
+
339
+ code += ` },
340
+ };
341
+
342
+ /**
343
+ * Get entity metadata by name (case-insensitive)
344
+ */
345
+ export function getEntityMetadata(typeName: string): EntityMetadata | undefined {
346
+ // Normalize to IfcXxx format
347
+ const normalized = normalizeTypeName(typeName);
348
+ return SCHEMA_REGISTRY.entities[normalized];
349
+ }
350
+
351
+ /**
352
+ * Get all attributes for an entity (including inherited)
353
+ */
354
+ export function getAllAttributesForEntity(typeName: string): AttributeMetadata[] {
355
+ const metadata = getEntityMetadata(typeName);
356
+ return metadata?.allAttributes || [];
357
+ }
358
+
359
+ /**
360
+ * Get inheritance chain for an entity
361
+ */
362
+ export function getInheritanceChainForEntity(typeName: string): string[] {
363
+ const metadata = getEntityMetadata(typeName);
364
+ return metadata?.inheritanceChain || [];
365
+ }
366
+
367
+ /**
368
+ * Check if a type is a known entity
369
+ */
370
+ export function isKnownEntity(typeName: string): boolean {
371
+ const normalized = normalizeTypeName(typeName);
372
+ return normalized in SCHEMA_REGISTRY.entities;
373
+ }
374
+
375
+ /**
376
+ * Normalize type name to IfcXxx format
377
+ */
378
+ function normalizeTypeName(name: string): string {
379
+ // Convert IFCWALL -> IfcWall
380
+ if (name.toUpperCase().startsWith('IFC')) {
381
+ return 'Ifc' + name.substring(3).charAt(0).toUpperCase() +
382
+ name.substring(4).toLowerCase();
383
+ }
384
+ return name;
385
+ }
386
+ `;
387
+
388
+ return code;
389
+ }
390
+
391
+ /**
392
+ * Generate metadata for a single entity
393
+ */
394
+ function generateEntityMetadata(
395
+ entity: EntityDefinition,
396
+ schema: ExpressSchema
397
+ ): string {
398
+ let code = ` ${entity.name}: {\n`;
399
+ code += ` name: '${entity.name}',\n`;
400
+ code += ` isAbstract: ${entity.isAbstract},\n`;
401
+
402
+ if (entity.supertype) {
403
+ code += ` parent: '${entity.supertype}',\n`;
404
+ }
405
+
406
+ code += ` attributes: [\n`;
407
+ for (const attr of entity.attributes) {
408
+ code += ` {\n`;
409
+ code += ` name: '${attr.name}',\n`;
410
+ code += ` type: '${attr.type}',\n`;
411
+ code += ` optional: ${attr.optional},\n`;
412
+ code += ` isArray: ${attr.isArray},\n`;
413
+ code += ` isList: ${attr.isList},\n`;
414
+ code += ` isSet: ${attr.isSet},\n`;
415
+ if (attr.arrayBounds) {
416
+ code += ` arrayBounds: [${attr.arrayBounds[0]}, ${attr.arrayBounds[1]}],\n`;
417
+ }
418
+ code += ` },\n`;
419
+ }
420
+ code += ` ],\n`;
421
+
422
+ // Add all attributes (including inherited)
423
+ const allAttrs = getAllAttributes(entity, schema);
424
+ code += ` allAttributes: [\n`;
425
+ for (const attr of allAttrs) {
426
+ code += ` {\n`;
427
+ code += ` name: '${attr.name}',\n`;
428
+ code += ` type: '${attr.type}',\n`;
429
+ code += ` optional: ${attr.optional},\n`;
430
+ code += ` isArray: ${attr.isArray},\n`;
431
+ code += ` isList: ${attr.isList},\n`;
432
+ code += ` isSet: ${attr.isSet},\n`;
433
+ if (attr.arrayBounds) {
434
+ code += ` arrayBounds: [${attr.arrayBounds[0]}, ${attr.arrayBounds[1]}],\n`;
435
+ }
436
+ code += ` },\n`;
437
+ }
438
+ code += ` ],\n`;
439
+
440
+ // Add inheritance chain
441
+ const chain = getInheritanceChain(entity, schema);
442
+ code += ` inheritanceChain: [${chain.map(c => `'${c}'`).join(', ')}],\n`;
443
+
444
+ code += ` },\n`;
445
+
446
+ return code;
447
+ }
448
+
449
+ /**
450
+ * Topological sort of entities by dependency order
451
+ * Ensures parent entities are generated before children
452
+ */
453
+ function topologicalSort(entities: EntityDefinition[]): EntityDefinition[] {
454
+ const sorted: EntityDefinition[] = [];
455
+ const visited = new Set<string>();
456
+
457
+ function visit(entity: EntityDefinition) {
458
+ if (visited.has(entity.name)) {
459
+ return;
460
+ }
461
+
462
+ visited.add(entity.name);
463
+
464
+ // Visit parent first
465
+ if (entity.supertype) {
466
+ const parent = entities.find(e => e.name === entity.supertype);
467
+ if (parent) {
468
+ visit(parent);
469
+ }
470
+ }
471
+
472
+ sorted.push(entity);
473
+ }
474
+
475
+ for (const entity of entities) {
476
+ visit(entity);
477
+ }
478
+
479
+ return sorted;
480
+ }
481
+
482
+ /**
483
+ * Write generated code to files
484
+ */
485
+ export function writeGeneratedFiles(
486
+ code: GeneratedCode,
487
+ outputDir: string
488
+ ): { entities: string; types: string; enums: string; selects: string; schema: string } {
489
+ return {
490
+ entities: `${outputDir}/entities.ts`,
491
+ types: `${outputDir}/types.ts`,
492
+ enums: `${outputDir}/enums.ts`,
493
+ selects: `${outputDir}/selects.ts`,
494
+ schema: `${outputDir}/schema-registry.ts`,
495
+ };
496
+ }