@statezero/core 0.1.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 (89) hide show
  1. package/dist/adaptors/react/composables.d.ts +1 -0
  2. package/dist/adaptors/react/composables.js +4 -0
  3. package/dist/adaptors/react/index.d.ts +1 -0
  4. package/dist/adaptors/react/index.js +1 -0
  5. package/dist/adaptors/vue/composables.d.ts +2 -0
  6. package/dist/adaptors/vue/composables.js +36 -0
  7. package/dist/adaptors/vue/index.d.ts +2 -0
  8. package/dist/adaptors/vue/index.js +2 -0
  9. package/dist/adaptors/vue/reactivity.d.ts +18 -0
  10. package/dist/adaptors/vue/reactivity.js +125 -0
  11. package/dist/cli/commands/syncModels.d.ts +132 -0
  12. package/dist/cli/commands/syncModels.js +1040 -0
  13. package/dist/cli/configFileLoader.d.ts +10 -0
  14. package/dist/cli/configFileLoader.js +85 -0
  15. package/dist/cli/index.d.ts +2 -0
  16. package/dist/cli/index.js +14 -0
  17. package/dist/config.d.ts +52 -0
  18. package/dist/config.js +242 -0
  19. package/dist/core/eventReceivers.d.ts +179 -0
  20. package/dist/core/eventReceivers.js +210 -0
  21. package/dist/core/utils.d.ts +8 -0
  22. package/dist/core/utils.js +62 -0
  23. package/dist/filtering/localFiltering.d.ts +116 -0
  24. package/dist/filtering/localFiltering.js +834 -0
  25. package/dist/flavours/django/dates.d.ts +33 -0
  26. package/dist/flavours/django/dates.js +99 -0
  27. package/dist/flavours/django/errors.d.ts +138 -0
  28. package/dist/flavours/django/errors.js +187 -0
  29. package/dist/flavours/django/f.d.ts +6 -0
  30. package/dist/flavours/django/f.js +91 -0
  31. package/dist/flavours/django/files.d.ts +76 -0
  32. package/dist/flavours/django/files.js +338 -0
  33. package/dist/flavours/django/makeApiCall.d.ts +20 -0
  34. package/dist/flavours/django/makeApiCall.js +169 -0
  35. package/dist/flavours/django/manager.d.ts +197 -0
  36. package/dist/flavours/django/manager.js +222 -0
  37. package/dist/flavours/django/model.d.ts +112 -0
  38. package/dist/flavours/django/model.js +253 -0
  39. package/dist/flavours/django/operationFactory.d.ts +65 -0
  40. package/dist/flavours/django/operationFactory.js +216 -0
  41. package/dist/flavours/django/q.d.ts +70 -0
  42. package/dist/flavours/django/q.js +43 -0
  43. package/dist/flavours/django/queryExecutor.d.ts +131 -0
  44. package/dist/flavours/django/queryExecutor.js +468 -0
  45. package/dist/flavours/django/querySet.d.ts +412 -0
  46. package/dist/flavours/django/querySet.js +601 -0
  47. package/dist/flavours/django/tempPk.d.ts +19 -0
  48. package/dist/flavours/django/tempPk.js +48 -0
  49. package/dist/flavours/django/utils.d.ts +19 -0
  50. package/dist/flavours/django/utils.js +29 -0
  51. package/dist/index.d.ts +38 -0
  52. package/dist/index.js +38 -0
  53. package/dist/react-entry.d.ts +2 -0
  54. package/dist/react-entry.js +2 -0
  55. package/dist/reactiveAdaptor.d.ts +24 -0
  56. package/dist/reactiveAdaptor.js +38 -0
  57. package/dist/setup.d.ts +15 -0
  58. package/dist/setup.js +22 -0
  59. package/dist/syncEngine/cache/cache.d.ts +75 -0
  60. package/dist/syncEngine/cache/cache.js +341 -0
  61. package/dist/syncEngine/metrics/metricOptCalcs.d.ts +79 -0
  62. package/dist/syncEngine/metrics/metricOptCalcs.js +284 -0
  63. package/dist/syncEngine/registries/metricRegistry.d.ts +53 -0
  64. package/dist/syncEngine/registries/metricRegistry.js +162 -0
  65. package/dist/syncEngine/registries/modelStoreRegistry.d.ts +11 -0
  66. package/dist/syncEngine/registries/modelStoreRegistry.js +56 -0
  67. package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +55 -0
  68. package/dist/syncEngine/registries/querysetStoreRegistry.js +244 -0
  69. package/dist/syncEngine/stores/metricStore.d.ts +55 -0
  70. package/dist/syncEngine/stores/metricStore.js +222 -0
  71. package/dist/syncEngine/stores/modelStore.d.ts +40 -0
  72. package/dist/syncEngine/stores/modelStore.js +405 -0
  73. package/dist/syncEngine/stores/operation.d.ts +99 -0
  74. package/dist/syncEngine/stores/operation.js +224 -0
  75. package/dist/syncEngine/stores/operationEventHandlers.d.ts +8 -0
  76. package/dist/syncEngine/stores/operationEventHandlers.js +239 -0
  77. package/dist/syncEngine/stores/querysetStore.d.ts +32 -0
  78. package/dist/syncEngine/stores/querysetStore.js +200 -0
  79. package/dist/syncEngine/stores/reactivity.d.ts +3 -0
  80. package/dist/syncEngine/stores/reactivity.js +4 -0
  81. package/dist/syncEngine/stores/utils.d.ts +14 -0
  82. package/dist/syncEngine/stores/utils.js +32 -0
  83. package/dist/syncEngine/sync.d.ts +32 -0
  84. package/dist/syncEngine/sync.js +169 -0
  85. package/dist/vue-entry.d.ts +6 -0
  86. package/dist/vue-entry.js +2 -0
  87. package/license.md +116 -0
  88. package/package.json +123 -0
  89. package/readme.md +222 -0
@@ -0,0 +1,1040 @@
1
+ import axios from "axios";
2
+ import * as fs from "fs/promises";
3
+ import * as path from "path";
4
+ import cliProgress from "cli-progress";
5
+ import Handlebars from "handlebars";
6
+ import _ from "lodash-es";
7
+ import { configInstance } from "../../config.js"; // Global config singleton
8
+ import { loadConfigFromFile } from "../configFileLoader.js";
9
+ // --------------------
10
+ // JSDoc Type Definitions
11
+ // --------------------
12
+ /**
13
+ * @typedef {Object} GenerateArgs
14
+ * // Additional arguments for generation if needed.
15
+ */
16
+ /**
17
+ * @typedef {Object} SchemaProperty
18
+ * @property {string} type
19
+ * @property {string} [format]
20
+ * @property {SchemaProperty} [items]
21
+ * @property {Object.<string, SchemaProperty>} [properties]
22
+ * @property {string[]} [required]
23
+ * @property {string[]} [enum]
24
+ * @property {string} [description]
25
+ * @property {boolean} [nullable]
26
+ * @property {any} [default]
27
+ * @property {string} [ref]
28
+ */
29
+ /**
30
+ * @typedef {Object} RelationshipField
31
+ * @property {string} field - Name of the relationship field
32
+ * @property {string} ModelClass - The class name of the related model
33
+ * @property {string} relationshipType - Type of relationship (e.g., "many-to-many", "foreign-key")
34
+ */
35
+ /**
36
+ * @typedef {Object} RelationshipData
37
+ * @property {string} type
38
+ * @property {string} model - e.g. "django_app.deepmodellevel1"
39
+ * @property {string} class_name - e.g. "DeepModelLevel1"
40
+ * @property {string} primary_key_field
41
+ */
42
+ /**
43
+ * @typedef {Object} SchemaDefinition
44
+ * @property {string} type
45
+ * @property {Object.<string, SchemaProperty>} properties
46
+ * @property {string[]} [required]
47
+ * @property {string} [description]
48
+ * @property {Object.<string, SchemaDefinition>} [definitions]
49
+ * @property {string} model_name
50
+ * @property {string} class_name
51
+ * @property {string} [primary_key_field]
52
+ * @property {Object.<string, RelationshipData>} [relationships]
53
+ */
54
+ /**
55
+ * @typedef {Object} PropertyDefinition
56
+ * @property {string} name
57
+ * @property {string} type
58
+ * @property {boolean} required
59
+ * @property {string} defaultValue
60
+ * @property {boolean} [isRelationship]
61
+ * @property {string} [relationshipClassName]
62
+ * @property {boolean} [isArrayRelationship]
63
+ * @property {string} [relationshipPrimaryKeyField]
64
+ * @property {boolean} [isString]
65
+ * @property {boolean} [isNumber]
66
+ * @property {boolean} [isBoolean]
67
+ * @property {boolean} [isDate]
68
+ * @property {boolean} [isPrimaryKey]
69
+ */
70
+ /**
71
+ * @typedef {Object} TemplateData
72
+ * @property {string} modulePath - Dynamic module path for imports.
73
+ * @property {string} className - Exported full model class name (from schema.class_name).
74
+ * @property {string} interfaceName - Full model fields interface name (e.g. DeepModelLevel1Fields).
75
+ * @property {string} modelName - Raw schema.model_name (including app label path).
76
+ * @property {PropertyDefinition[]} properties
77
+ * @property {RelationshipField[]} relationshipFields - List of relationship fields for the model
78
+ * @property {string} [description]
79
+ * @property {string[]} [definitions]
80
+ * @property {string[]} [jsImports] - For JS generation: full class imports.
81
+ * @property {string[]} [tsImports] - For TS generation: type imports (Fields).
82
+ * @property {string} configKey - The backend config key.
83
+ * @property {string} primaryKeyField - Primary key field from schema.
84
+ */
85
+ /**
86
+ * @typedef {Object} BackendConfig
87
+ * @property {string} NAME
88
+ * @property {string} API_URL
89
+ * @property {string} GENERATED_TYPES_DIR
90
+ */
91
+ /**
92
+ * @typedef {Object} SelectedModel
93
+ * @property {BackendConfig} backend
94
+ * @property {string} model
95
+ */
96
+ // --------------------
97
+ // Handlebars Templates & Helpers
98
+ // --------------------
99
+ // Updated JS_MODEL_TEMPLATE with getters and setters
100
+ const JS_MODEL_TEMPLATE = `/**
101
+ * This file was auto-generated. Do not make direct changes to the file.
102
+ {{#if description}}
103
+ * {{description}}
104
+ {{/if}}
105
+ */
106
+
107
+ import { Model, Manager, QuerySet, getModelClass } from '{{modulePath}}';
108
+ import { wrapReactiveModel } from '{{modulePath}}';
109
+ import schemaData from './{{className}}.schema.json';
110
+
111
+ /**
112
+ * Model-specific QuerySet implementation
113
+ */
114
+ export class {{className}}QuerySet extends QuerySet {
115
+ // QuerySet implementation with model-specific typing
116
+ }
117
+
118
+ /**
119
+ * Model-specific Manager implementation
120
+ */
121
+ export class {{className}}Manager extends Manager {
122
+ constructor(ModelClass) {
123
+ super(ModelClass, {{className}}QuerySet);
124
+ }
125
+
126
+ newQuerySet() {
127
+ return new {{className}}QuerySet(this.ModelClass);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Implementation of the {{className}} model
133
+ */
134
+ export class {{className}} extends Model {
135
+ // Bind this model to its backend
136
+ static configKey = '{{configKey}}';
137
+ static modelName = '{{modelName}}';
138
+ static primaryKeyField = '{{primaryKeyField}}';
139
+ static objects = new {{className}}Manager({{className}});
140
+ static fields = [{{#each properties}}'{{name}}'{{#unless @last}}, {{/unless}}{{/each}}];
141
+ static schema = schemaData;
142
+ static relationshipFields = new Map([
143
+ {{#each relationshipFields}}
144
+ ['{{field}}', { 'ModelClass': () => getModelClass('{{modelName}}', '{{../configKey}}'), 'relationshipType': '{{relationshipType}}' }]{{#unless @last}},{{/unless}}
145
+ {{/each}}
146
+ ]);
147
+
148
+ constructor(data) {
149
+ {{className}}.validateFields(data);
150
+ super(data);
151
+
152
+ // Define getters and setters for all fields
153
+ this._defineProperties();
154
+
155
+ return wrapReactiveModel(this);
156
+ }
157
+
158
+ /**
159
+ * Define property getters and setters for all model fields
160
+ * @private
161
+ */
162
+ _defineProperties() {
163
+ // For each field, define a property that gets/sets from internal storage
164
+ {{className}}.fields.forEach(field => {
165
+ Object.defineProperty(this, field, {
166
+ get: function() {
167
+ return this.getField(field);
168
+ },
169
+ set: function(value) {
170
+ this.setField(field, value);
171
+ },
172
+ enumerable: true, // Make sure fields are enumerable for serialization
173
+ configurable: true
174
+ });
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Serialize the model's data.
180
+ * Returns a plain object containing the current values of all fields,
181
+ * including raw IDs for relationship fields.
182
+ * @returns {Object}
183
+ */
184
+ serialize() {
185
+ const data = {};
186
+ // Simply assign the current value of each field.
187
+ // No special handling or PK extraction for relationships here.
188
+ {{#each properties}}
189
+ data.{{name}} = this.{{name}};
190
+ {{/each}}
191
+ return data;
192
+ }
193
+ }
194
+ `;
195
+ // Updated TS_DECLARATION_TEMPLATE with improved relationship handling
196
+ const TS_DECLARATION_TEMPLATE = `/**
197
+ * This file was auto-generated. Do not make direct changes to the file.
198
+ {{#if description}}
199
+ * {{description}}
200
+ {{/if}}
201
+ */
202
+
203
+ import { Model, Manager } from '{{modulePath}}';
204
+ import { StringOperators, NumberOperators, BooleanOperators, DateOperators } from '{{modulePath}}';
205
+ import { QuerySet, LiveQuerySet, LiveQuerySetOptions, MetricResult, ResultTuple, SerializerOptions, NestedPaths } from '{{modulePath}}';
206
+
207
+ // Re-export the real Manager for runtime use
208
+ import { Manager as RuntimeManager } from '{{modulePath}}';
209
+ {{#if tsImports}}
210
+ {{#each tsImports}}
211
+ {{{this}}}
212
+ {{/each}}
213
+ {{/if}}
214
+
215
+ /**
216
+ * Base fields interface - defines the shape of a model instance
217
+ * This is the single source of truth for the model's data structure
218
+ */
219
+ export interface {{interfaceName}} {
220
+ {{#each properties}}
221
+ {{name}}{{#unless required}}?{{/unless}}: {{{type}}};
222
+ {{/each}}
223
+ // Read-only representation field
224
+ readonly repr: {
225
+ str: string;
226
+ img: string | null;
227
+ };
228
+ }
229
+
230
+ /**
231
+ * Relationship field structure
232
+ */
233
+ export interface RelationshipField {
234
+ ModelClass: any;
235
+ relationshipType: string;
236
+ }
237
+
238
+ /**
239
+ * Relationship fields map type
240
+ */
241
+ export type RelationshipFieldsMap = Map<string, RelationshipField>;
242
+
243
+ /**
244
+ * Type for creating new instances
245
+ * Similar to base fields but makes ID fields optional
246
+ */
247
+ export type {{className}}CreateData = {
248
+ {{#each properties}}
249
+ {{name}}{{#unless isPrimaryKey}}{{#unless required}}?{{/unless}}{{else}}?{{/unless}}: {{{type}}};
250
+ {{/each}}
251
+ };
252
+
253
+ /**
254
+ * Type for updating instances
255
+ * All fields are optional since updates can be partial
256
+ */
257
+ export type {{className}}UpdateData = Partial<{{interfaceName}}>;
258
+
259
+ /**
260
+ * Type for filtering with field lookups
261
+ * Supports advanced filtering with operators like __gte, __contains, etc.
262
+ */
263
+ export interface {{className}}FilterData {
264
+ {{#each properties}}
265
+ {{#if isRelationship}}
266
+ {{#if isArrayRelationship}}
267
+ // Many-to-many relationship field
268
+ {{name}}?: number; // Exact match by ID
269
+ {{name}}__in?: number[]; // Match any of these IDs
270
+ {{name}}__isnull?: boolean; // Check if relation exists
271
+ {{else}}
272
+ // Foreign key relationship field
273
+ {{name}}?: number; // Exact match by ID
274
+ {{name}}__isnull?: boolean; // Check if relation exists
275
+ {{/if}}
276
+ {{else}}
277
+ {{#if isString}}
278
+ {{name}}?: string | StringOperators;
279
+ {{name}}__contains?: string;
280
+ {{name}}__icontains?: string;
281
+ {{name}}__startswith?: string;
282
+ {{name}}__istartswith?: string;
283
+ {{name}}__endswith?: string;
284
+ {{name}}__iendswith?: string;
285
+ {{name}}__exact?: string;
286
+ {{name}}__iexact?: string;
287
+ {{name}}__in?: string[];
288
+ {{name}}__isnull?: boolean;
289
+ {{/if}}
290
+ {{#if isNumber}}
291
+ {{name}}?: number | NumberOperators;
292
+ {{name}}__gt?: number;
293
+ {{name}}__gte?: number;
294
+ {{name}}__lt?: number;
295
+ {{name}}__lte?: number;
296
+ {{name}}__exact?: number;
297
+ {{name}}__in?: number[];
298
+ {{name}}__isnull?: boolean;
299
+ {{/if}}
300
+ {{#if isBoolean}}
301
+ {{name}}?: boolean | BooleanOperators;
302
+ {{name}}__exact?: boolean;
303
+ {{name}}__isnull?: boolean;
304
+ {{/if}}
305
+ {{#if isDate}}
306
+ {{name}}?: Date | DateOperators;
307
+ {{name}}__gt?: Date;
308
+ {{name}}__gte?: Date;
309
+ {{name}}__lt?: Date;
310
+ {{name}}__lte?: Date;
311
+ {{name}}__exact?: Date;
312
+ {{name}}__in?: Date[];
313
+ {{name}}__isnull?: boolean;
314
+ {{/if}}
315
+ {{/if}}
316
+ {{/each}}
317
+ // Support for nested filtering on related fields
318
+ [key: string]: any;
319
+
320
+ // Support for Q objects
321
+ Q?: Array<any>;
322
+ }
323
+
324
+ /**
325
+ * Model-specific QuerySet with strictly typed methods
326
+ */
327
+ export declare class {{className}}QuerySet extends QuerySet<any> {
328
+ // Chain methods
329
+ filter(conditions: {{className}}FilterData): {{className}}QuerySet;
330
+ exclude(conditions: {{className}}FilterData): {{className}}QuerySet;
331
+ orderBy(...fields: Array<keyof {{interfaceName}} | string>): {{className}}QuerySet;
332
+ search(searchQuery: string, searchFields?: Array<string>): {{className}}QuerySet;
333
+
334
+ // Terminal methods
335
+ get(filters?: {{className}}FilterData, serializerOptions?: SerializerOptions): Promise<{{className}}>;
336
+ first(serializerOptions?: SerializerOptions): Promise<{{className}} | null>;
337
+ last(serializerOptions?: SerializerOptions): Promise<{{className}} | null>;
338
+ all(): {{className}}QuerySet;
339
+ count(field?: string): Promise<number>;
340
+ update(updates: {{className}}UpdateData): Promise<[number, Record<string, number>]>;
341
+ delete(): Promise<[number, Record<string, number>]>;
342
+ exists(): Promise<boolean>;
343
+ fetch(serializerOptions?: SerializerOptions): Promise<{{className}}[]>;
344
+ }
345
+
346
+ /**
347
+ * Model-specific Manager with strictly typed methods
348
+ */
349
+ export declare class {{className}}Manager extends Manager {
350
+ newQuerySet(): {{className}}QuerySet;
351
+ filter(conditions: {{className}}FilterData): {{className}}QuerySet;
352
+ exclude(conditions: {{className}}FilterData): {{className}}QuerySet;
353
+ all(): {{className}}QuerySet;
354
+ get(filters?: {{className}}FilterData, serializerOptions?: SerializerOptions): Promise<{{className}}>;
355
+ create(data: {{className}}CreateData): Promise<{{className}}>;
356
+ delete(): Promise<[number, Record<string, number>]>;
357
+ }
358
+
359
+ /**
360
+ * Model-specific LiveQuerySet with strictly typed methods
361
+ */
362
+ export declare class {{className}}LiveQuerySet extends LiveQuerySet {
363
+ // Data access
364
+ get data(): {{className}}[];
365
+
366
+ // Chain methods
367
+ filter(conditions: {{className}}FilterData): {{className}}LiveQuerySet;
368
+
369
+ // Terminal methods
370
+ fetch(serializerOptions?: SerializerOptions): Promise<{{className}}[]>;
371
+ get(filters?: {{className}}FilterData, serializerOptions?: SerializerOptions): Promise<{{className}}>;
372
+ create(item: {{className}}CreateData): Promise<{{className}}>;
373
+ update(updates: {{className}}UpdateData): Promise<{{className}}[]>;
374
+ delete(): Promise<void>;
375
+ count(field?: string): Promise<MetricResult<number>>;
376
+ sum(field: string): Promise<MetricResult<number>>;
377
+ avg(field: string): Promise<MetricResult<number>>;
378
+ min(field: string): Promise<MetricResult<any>>;
379
+ max(field: string): Promise<MetricResult<any>>;
380
+ }
381
+
382
+ /**
383
+ * Enhanced RuntimeManager to provide TypeScript typings
384
+ * This creates a concrete class that both extends RuntimeManager and matches type expectations
385
+ */
386
+ export class {{className}}Manager extends RuntimeManager {
387
+ filter(conditions: {{className}}FilterData): ReturnType<RuntimeManager['filter']> {
388
+ return super.filter(conditions as any);
389
+ }
390
+
391
+ get(filters?: {{className}}FilterData, serializerOptions?: SerializerOptions): Promise<{{className}}> {
392
+ return super.get(filters as any, serializerOptions);
393
+ }
394
+
395
+ all() {
396
+ return super.all();
397
+ }
398
+
399
+ create(data: {{className}}CreateData): Promise<{{className}}> {
400
+ return super.create(data);
401
+ }
402
+
403
+ update(data: {{className}}UpdateData): Promise<any> {
404
+ return super.update(data);
405
+ }
406
+ }
407
+
408
+ // Class declarations
409
+ export declare class {{className}} extends Model implements {{interfaceName}} {
410
+ {{#each properties}}
411
+ {{name}}{{#unless required}}?:{{else}}:{{/unless}} {{{type}}};
412
+ {{/each}}
413
+ readonly repr: {
414
+ str: string;
415
+ img: string | null;
416
+ };
417
+
418
+ static configKey: string;
419
+ static modelName: string;
420
+ static primaryKeyField: string;
421
+ static relationshipFields: RelationshipFieldsMap;
422
+
423
+ // Use model-specific manager class instead of generic manager
424
+ static objects: {{className}}Manager;
425
+
426
+ constructor(data: Partial<{{interfaceName}}>);
427
+ serialize(): Partial<{{interfaceName}}>;
428
+ }
429
+
430
+ /**
431
+ * Runtime initialization
432
+ */
433
+ {{className}}.objects = new {{className}}Manager({{className}});
434
+ `;
435
+ // --------------------
436
+ // Handlebars Helpers
437
+ // --------------------
438
+ Handlebars.registerHelper("ifDefaultProvided", function (defaultValue, options) {
439
+ if (defaultValue !== "null") {
440
+ return options.fn(this);
441
+ }
442
+ else {
443
+ return options.inverse(this);
444
+ }
445
+ });
446
+ Handlebars.registerHelper("isRequired", function (required) {
447
+ return required ? "" : "?";
448
+ });
449
+ const jsTemplate = Handlebars.compile(JS_MODEL_TEMPLATE);
450
+ const dtsTemplate = Handlebars.compile(TS_DECLARATION_TEMPLATE);
451
+ // --------------------
452
+ // Core Generation Functions
453
+ // --------------------
454
+ /**
455
+ * Generates the schema for a given model.
456
+ * @param {BackendConfig} backend
457
+ * @param {string} model
458
+ * @returns {Promise<{model: string, relativePath: string}>}
459
+ */
460
+ async function generateSchemaForModel(backend, model) {
461
+ const schemaUrl = `${backend.API_URL}/${model}/get-schema/`;
462
+ const schemaResponse = await axios.get(schemaUrl);
463
+ /** @type {SchemaDefinition} */
464
+ let schema;
465
+ if (schemaResponse.data.components?.schemas?.[model]) {
466
+ schema = schemaResponse.data.components.schemas[model];
467
+ }
468
+ else if (schemaResponse.data.properties) {
469
+ schema = schemaResponse.data;
470
+ }
471
+ else {
472
+ console.error("Unexpected schema structure for model:", model);
473
+ throw new Error(`Invalid schema structure for model: ${model}`);
474
+ }
475
+ if (!schema.model_name) {
476
+ console.error(`Missing model_name attribute in schema for model: ${model}`);
477
+ process.exit(1);
478
+ }
479
+ const rawModelName = schema.model_name;
480
+ const className = schema.class_name;
481
+ const interfaceName = `${className}Fields`;
482
+ const parts = model.split(".");
483
+ const currentApp = parts.length > 1 ? parts[0] : "";
484
+ const modulePath = process.env.NODE_ENV === "test" ? "../../../src" : "@statezero/core";
485
+ const templateData = prepareTemplateData(modulePath, className, interfaceName, rawModelName, schema, currentApp, backend.NAME);
486
+ let outDir = backend.GENERATED_TYPES_DIR;
487
+ if (parts.length > 1) {
488
+ outDir = path.join(outDir, ...parts.slice(0, -1).map((p) => p.toLowerCase()));
489
+ }
490
+ await fs.mkdir(outDir, { recursive: true });
491
+ const schemaFilePath = path.join(outDir, `${className.toLowerCase()}.schema.json`);
492
+ await fs.writeFile(schemaFilePath, JSON.stringify(schema, null, 2));
493
+ const jsContent = jsTemplate(templateData);
494
+ const baseName = parts[parts.length - 1].toLowerCase();
495
+ const jsFilePath = path.join(outDir, `${baseName}.js`);
496
+ await fs.writeFile(jsFilePath, jsContent);
497
+ const dtsContent = dtsTemplate(templateData);
498
+ const dtsFilePath = path.join(outDir, `${baseName}.d.ts`);
499
+ await fs.writeFile(dtsFilePath, dtsContent);
500
+ const relativePath = "./" +
501
+ path
502
+ .relative(backend.GENERATED_TYPES_DIR, jsFilePath)
503
+ .replace(/\\/g, "/")
504
+ .replace(/\.js$/, "");
505
+ return { model, relativePath, className };
506
+ }
507
+ /**
508
+ * Given a related model string (e.g. "django_app.deepmodellevel1"),
509
+ * extract the app label and model name to construct an import path.
510
+ * @param {string} currentApp
511
+ * @param {string} relModel
512
+ * @returns {string}
513
+ */
514
+ function getImportPath(currentApp, relModel) {
515
+ const parts = relModel.split(".");
516
+ const appLabel = parts[0];
517
+ const fileName = parts[parts.length - 1].toLowerCase();
518
+ return currentApp === appLabel
519
+ ? `./${fileName}`
520
+ : `../${appLabel}/${fileName}`;
521
+ }
522
+ /**
523
+ * Prepares template data for Handlebars.
524
+ * @param {string} modulePath
525
+ * @param {string} className
526
+ * @param {string} interfaceName
527
+ * @param {string} rawModelName
528
+ * @param {SchemaDefinition} schema
529
+ * @param {string} currentApp
530
+ * @param {string} configKey
531
+ * @returns {TemplateData}
532
+ */
533
+ function prepareTemplateData(modulePath, className, interfaceName, rawModelName, schema, currentApp, configKey) {
534
+ /** @type {PropertyDefinition[]} */
535
+ const properties = [];
536
+ /** @type {RelationshipField[]} */
537
+ const relationshipFields = [];
538
+ const usedDefs = new Set();
539
+ for (const [propName, prop] of Object.entries(schema.properties)) {
540
+ const propType = generateTypeForProperty(prop, schema.definitions, schema.relationships, propName);
541
+ const isRelationship = schema.relationships && schema.relationships[propName] !== undefined;
542
+ const isString = prop.type === "string";
543
+ const isNumber = prop.type === "integer" || prop.type === "number";
544
+ const isBoolean = prop.type === "boolean";
545
+ const isDate = prop.type === "string" && prop.format === "date-time";
546
+ const isPrimaryKey = schema.primary_key_field === propName;
547
+ const propDef = {
548
+ name: propName,
549
+ type: propType,
550
+ required: schema.required?.includes(propName) ?? false,
551
+ defaultValue: getDefaultValueForType(prop),
552
+ isRelationship,
553
+ isArrayRelationship: isRelationship
554
+ ? propType.startsWith("Array<")
555
+ : false,
556
+ isString,
557
+ isNumber,
558
+ isBoolean,
559
+ isDate,
560
+ isPrimaryKey,
561
+ };
562
+ if (isRelationship) {
563
+ const relData = schema.relationships[propName];
564
+ propDef.relationshipClassName = relData.class_name;
565
+ propDef.relationshipPrimaryKeyField = relData.primary_key_field;
566
+ // Add to relationshipFields array with modelName for dynamic loading
567
+ relationshipFields.push({
568
+ field: propName,
569
+ ModelClass: relData.class_name,
570
+ relationshipType: prop.format, // Use the format value directly
571
+ modelName: relData.model, // Add the full model name for getModelClass
572
+ });
573
+ }
574
+ properties.push(propDef);
575
+ const match = propType.match(/^(\w+)Fields$/);
576
+ if (match && schema.definitions && schema.definitions[match[1]]) {
577
+ usedDefs.add(match[1]);
578
+ }
579
+ const arrMatch = propType.match(/^Array<(\w+)Fields>$/);
580
+ if (arrMatch && schema.definitions && schema.definitions[arrMatch[1]]) {
581
+ usedDefs.add(arrMatch[1]);
582
+ }
583
+ }
584
+ const definitionsTs = [];
585
+ if (schema.definitions) {
586
+ for (const [defKey, defSchema] of Object.entries(schema.definitions)) {
587
+ if (!usedDefs.has(defKey))
588
+ continue;
589
+ let tsInterface = `export interface ${defKey}Fields {`;
590
+ const req = defSchema.required || [];
591
+ for (const [propName, prop] of Object.entries(defSchema.properties)) {
592
+ tsInterface += `\n ${propName}${req.includes(propName) ? "" : "?"}: ${generateTypeForProperty(prop, schema.definitions)};`;
593
+ }
594
+ tsInterface += `\n}`;
595
+ definitionsTs.push(tsInterface);
596
+ }
597
+ }
598
+ // Only add TypeScript definition imports - JS imports are handled dynamically
599
+ const tsImportSet = new Set();
600
+ if (schema.relationships) {
601
+ for (const [propName, rel] of Object.entries(schema.relationships)) {
602
+ const importPath = getImportPath(currentApp, rel.model);
603
+ tsImportSet.add(`import { ${rel.class_name}Fields, ${rel.class_name}QuerySet, ${rel.class_name}LiveQuerySet } from '${importPath}';`);
604
+ }
605
+ }
606
+ // Convert Sets to Arrays
607
+ const jsImports = []; // No static JS imports needed anymore
608
+ const tsImports = Array.from(tsImportSet);
609
+ let primaryKeyField = "id";
610
+ if (schema.primary_key_field !== undefined) {
611
+ primaryKeyField = schema.primary_key_field;
612
+ }
613
+ return {
614
+ modulePath,
615
+ className,
616
+ interfaceName,
617
+ modelName: rawModelName,
618
+ properties,
619
+ relationshipFields,
620
+ description: schema.description,
621
+ definitions: definitionsTs.length > 0 ? definitionsTs : undefined,
622
+ jsImports,
623
+ tsImports,
624
+ configKey,
625
+ primaryKeyField,
626
+ };
627
+ }
628
+ /**
629
+ * Generates a TypeScript type for a property.
630
+ * @param {SchemaProperty} prop
631
+ * @param {Object.<string, SchemaDefinition>} [definitions]
632
+ * @param {Object.<string, RelationshipData>} [relationships]
633
+ * @param {string} [propName]
634
+ * @returns {string}
635
+ */
636
+ function generateTypeForProperty(prop, definitions, relationships, propName) {
637
+ if (relationships && propName && relationships[propName]) {
638
+ const relData = relationships[propName];
639
+ const idType = prop.type === "integer" || prop.type === "number"
640
+ ? "number"
641
+ : prop.type === "string"
642
+ ? "string"
643
+ : "any";
644
+ return prop.format === "many-to-many"
645
+ ? `Array<${relData.class_name}Fields | ${idType}>`
646
+ : `${relData.class_name}Fields | ${idType}`;
647
+ }
648
+ if (prop.ref && prop.ref.startsWith("#/components/schemas/")) {
649
+ const defName = prop.ref.split("/").pop() || prop.ref;
650
+ return `${defName}Fields`;
651
+ }
652
+ if (prop.ref) {
653
+ return prop.format === "many-to-many"
654
+ ? `Array<${prop.ref}Fields>`
655
+ : `${prop.ref}Fields`;
656
+ }
657
+ let tsType;
658
+ switch (prop.type) {
659
+ case "string":
660
+ tsType = prop.enum
661
+ ? prop.enum.map((v) => `'${v}'`).join(" | ")
662
+ : "string";
663
+ break;
664
+ case "number":
665
+ case "integer":
666
+ tsType = "number";
667
+ break;
668
+ case "boolean":
669
+ tsType = "boolean";
670
+ break;
671
+ case "array":
672
+ tsType = prop.items
673
+ ? `Array<${generateTypeForProperty(prop.items, definitions)}>`
674
+ : "any[]";
675
+ break;
676
+ case "object":
677
+ if (prop.format === "json") {
678
+ tsType = "any";
679
+ }
680
+ else if (prop.properties) {
681
+ const nestedProps = Object.entries(prop.properties).map(([key, value]) => {
682
+ const isRequired = prop.required?.includes(key) ? "" : "?";
683
+ return `${key}${isRequired}: ${generateTypeForProperty(value, definitions)}`;
684
+ });
685
+ tsType = `{ ${nestedProps.join("; ")} }`;
686
+ }
687
+ else {
688
+ tsType = "Record<string, any>";
689
+ }
690
+ break;
691
+ default:
692
+ tsType = "any";
693
+ break;
694
+ }
695
+ if (prop.nullable) {
696
+ tsType = `${tsType} | null`;
697
+ }
698
+ return tsType;
699
+ }
700
+ /**
701
+ * Gets the default value for a property.
702
+ * @param {SchemaProperty} prop
703
+ * @returns {string}
704
+ */
705
+ function getDefaultValueForType(prop) {
706
+ return prop.default !== undefined ? JSON.stringify(prop.default) : "null";
707
+ }
708
+ /**
709
+ * Creates a unique alias for importing a model class based on backend, model path, and class name.
710
+ * Example: ('default', 'django_app.level1.deepmodel', 'DeepModel') => 'Default__django_app__level1__DeepModel'
711
+ * Ensures uniqueness by including sanitized path components.
712
+ *
713
+ * @param {string} backendKey - The backend configuration key (e.g., 'default', 'microservice').
714
+ * @param {string} modelName - The full model name string (e.g., 'django_app.level1.deepmodel').
715
+ * @param {string} className - The base class name (e.g., 'DeepModel').
716
+ * @returns {string} - A unique alias (e.g., 'Default__django_app__level1__DeepModel').
717
+ */
718
+ function generateImportAlias(backendKey, modelName, className) {
719
+ // 1. Sanitize Backend Key
720
+ const sanitizedBackendKey = (backendKey.charAt(0).toUpperCase() + backendKey.slice(1)).replace(/[^a-zA-Z0-9_]/g, ""); // Allow underscore
721
+ // 2. Sanitize Model Path Parts (all parts except the last, which relates to className)
722
+ const modelPathParts = modelName.split(".").slice(0, -1); // Get path parts like ['django_app', 'level1']
723
+ const sanitizedModelPath = modelPathParts
724
+ .map((part) => part.replace(/[^a-zA-Z0-9_]/g, "_")) // Replace invalid chars with underscore
725
+ .join("__"); // Join parts with double underscore -> "django_app__level1"
726
+ // 3. Combine: Backend__Path__ClassName
727
+ // Ensure className itself is sanitized just in case, although usually it should be a valid identifier
728
+ const sanitizedClassName = className.replace(/[^a-zA-Z0-9_]/g, "_");
729
+ if (sanitizedModelPath) {
730
+ // If path parts exist, include them: Default__django_app__level1__DeepModel
731
+ return `${sanitizedBackendKey}__${sanitizedModelPath}__${sanitizedClassName}`;
732
+ }
733
+ else {
734
+ // If no path parts (e.g., modelName is just 'simplemodel'), use: Default__SimpleModel
735
+ return `${sanitizedBackendKey}__${sanitizedClassName}`;
736
+ }
737
+ }
738
+ /**
739
+ * Generates a model registry file...
740
+ *
741
+ * @param {Array<{model: string, relativePath: string, backend: string, className: string}>} generatedFiles
742
+ * @param {Object.<string, BackendConfig>} backendConfigs
743
+ * @returns {Promise<void>}
744
+ */
745
+ async function generateModelRegistry(generatedFiles, backendConfigs) {
746
+ const registryByBackend = {};
747
+ const allUniqueImports = new Set();
748
+ for (const file of generatedFiles) {
749
+ const backendKey = file.backend;
750
+ const modelName = file.model; // e.g., 'django_app.dummymodel'
751
+ const originalClassName = file.className; // e.g., 'DummyModel'
752
+ const typesDir = backendConfigs[backendKey].GENERATED_TYPES_DIR;
753
+ const importPath = "./" +
754
+ path
755
+ .relative("./", path.join(typesDir, file.relativePath))
756
+ .replace(/\\/g, "/");
757
+ const importAlias = generateImportAlias(backendKey, modelName, originalClassName);
758
+ // Example result: 'Default__django_app__DummyModel'
759
+ registryByBackend[backendKey] = registryByBackend[backendKey] || {
760
+ imports: [],
761
+ models: {},
762
+ };
763
+ const importStatement = `import { ${originalClassName} as ${importAlias} } from '${importPath}.js';`;
764
+ registryByBackend[backendKey].imports.push(importStatement);
765
+ allUniqueImports.add(importStatement);
766
+ registryByBackend[backendKey].models[modelName] = importAlias; // Store mapping: 'django_app.dummymodel': 'Default__django_app__DummyModel'
767
+ }
768
+ // --- Generate registry content ---
769
+ let registryContent = `/**
770
+ * This file was auto-generated. Do not make direct changes to the file.
771
+ * It provides a registry of all models organized by config key and model name.
772
+ * Uses import aliases incorporating model paths to ensure uniqueness.
773
+ */
774
+
775
+ // --- Imports ---
776
+ `;
777
+ // Add all unique import statements collected
778
+ registryContent += Array.from(allUniqueImports).sort().join("\n");
779
+ registryContent += "\n\n";
780
+ // --- Create the registry object ---
781
+ registryContent += `/**
782
+ * Model registry mapped by configKey and modelName.
783
+ * Values are the aliased imported classes.
784
+ * @type {Object.<string, Object.<string, Function>>}
785
+ */
786
+ export const MODEL_REGISTRY = {
787
+ `;
788
+ // Add entries for each backend
789
+ const backendEntries = Object.entries(registryByBackend);
790
+ backendEntries.forEach(([backendKey, data], backendIndex) => {
791
+ registryContent += ` '${backendKey}': {\n`;
792
+ // Add model entries: 'model.name': Alias (e.g., 'django_app.dummymodel': Default__django_app__DummyModel)
793
+ const modelEntries = Object.entries(data.models);
794
+ modelEntries.forEach(([modelName, alias], modelIndex) => {
795
+ registryContent += ` '${modelName}': ${alias}`; // Use the generated alias here
796
+ if (modelIndex < modelEntries.length - 1) {
797
+ registryContent += ",";
798
+ }
799
+ registryContent += "\n";
800
+ });
801
+ registryContent += ` }`;
802
+ if (backendIndex < backendEntries.length - 1) {
803
+ registryContent += ",";
804
+ }
805
+ registryContent += "\n";
806
+ });
807
+ registryContent += `};
808
+
809
+ /**
810
+ * Get a model class by name.
811
+ * This remains synchronous.
812
+ *
813
+ * @param {string} modelName - The model name (e.g., 'django_app.dummymodel')
814
+ * @param {string} configKey - The config key (backend name, e.g., 'default')
815
+ * @returns {Function|null} - The model class (via its alias) or null if not found.
816
+ */
817
+ export function getModelClass(modelName, configKey) {
818
+ if (MODEL_REGISTRY[configKey] && MODEL_REGISTRY[configKey][modelName]) {
819
+ // Returns the specific aliased class for that backend/model combination
820
+ return MODEL_REGISTRY[configKey][modelName];
821
+ }
822
+
823
+ console.warn(\`Model class not found for '\${modelName}' in config '\${configKey}'\`);
824
+ return null;
825
+ }
826
+ `;
827
+ // --- Write the file ---
828
+ const rootDir = process.cwd();
829
+ const registryFilePath = path.join(rootDir, "model-registry.js");
830
+ try {
831
+ await fs.writeFile(registryFilePath, registryContent);
832
+ console.log(`✨ Generated model registry with path-based aliases at ${registryFilePath}`);
833
+ }
834
+ catch (err) {
835
+ console.error(`❌ Failed to write model registry at ${registryFilePath}:`, err);
836
+ }
837
+ }
838
+ /**
839
+ * Generates app-level index files and a root index file that imports from app indexes.
840
+ * @param {Array<{model: string, relativePath: string, backend: string}>} generatedFiles
841
+ * @param {Object.<string, BackendConfig>} backendConfigs
842
+ * @returns {Promise<void>}
843
+ */
844
+ async function generateAppLevelIndexFiles(generatedFiles, backendConfigs) {
845
+ // Group files by backend and app
846
+ const filesByBackendAndApp = generatedFiles.reduce((acc, file) => {
847
+ const backend = file.backend;
848
+ const parts = file.model.split(".");
849
+ const app = parts.length > 1 ? parts[0] : "root"; // Use 'root' for models without an app
850
+ acc[backend] = acc[backend] || {};
851
+ acc[backend][app] = acc[backend][app] || [];
852
+ acc[backend][app].push(file);
853
+ return acc;
854
+ }, {});
855
+ const indexTemplate = Handlebars.compile(`{{#each files}}
856
+ export * from '{{this.relativePath}}';
857
+ {{/each}}`);
858
+ // Generate app-level index files for each backend and app
859
+ for (const [backendName, appGroups] of Object.entries(filesByBackendAndApp)) {
860
+ const backend = backendConfigs[backendName];
861
+ const rootExports = [];
862
+ for (const [app, files] of Object.entries(appGroups)) {
863
+ if (app === "root") {
864
+ // Handle models without an app prefix
865
+ for (const file of files) {
866
+ rootExports.push(`export * from '${file.relativePath}';`);
867
+ }
868
+ }
869
+ else {
870
+ // Create app-level index files with proper relative paths
871
+ const appDir = path.join(backend.GENERATED_TYPES_DIR, app.toLowerCase());
872
+ // Create relative paths for imports within the app directory
873
+ // These should be relative to the app directory, not the backend root
874
+ const appFiles = files.map((file) => {
875
+ // Get the last part of the path (the actual file name without extension)
876
+ const fileName = path.basename(file.relativePath);
877
+ return {
878
+ ...file,
879
+ relativePath: "./" + fileName,
880
+ };
881
+ });
882
+ const indexContent = indexTemplate({ files: appFiles });
883
+ await fs.writeFile(path.join(appDir, "index.js"), indexContent.trim());
884
+ await fs.writeFile(path.join(appDir, "index.d.ts"), indexContent.trim());
885
+ // Add an export for this app's index to the backend root index
886
+ rootExports.push(`export * from './${app.toLowerCase()}/index';`);
887
+ }
888
+ }
889
+ // Write the backend root index file (add FileObject export)
890
+ const backendIndexContent = [
891
+ "export * from './fileobject';", // Add this line
892
+ ...rootExports,
893
+ ].join("\n");
894
+ await fs.writeFile(path.join(backend.GENERATED_TYPES_DIR, "index.js"), backendIndexContent);
895
+ await fs.writeFile(path.join(backend.GENERATED_TYPES_DIR, "index.d.ts"), backendIndexContent);
896
+ }
897
+ }
898
+ // FileObject template
899
+ const FILEOBJECT_TEMPLATE = `/**
900
+ * This file was auto-generated. Do not make direct changes to the file.
901
+ * Backend-specific FileObject class for {{backendName}}
902
+ */
903
+
904
+ import { FileObject as BaseFileObject } from '{{modulePath}}';
905
+
906
+ export class {{backendName}}FileObject extends BaseFileObject {
907
+ static configKey = '{{backendName}}';
908
+ }
909
+
910
+ export const FileObject = {{backendName}}FileObject;
911
+ `;
912
+ const fileObjectTemplate = Handlebars.compile(FILEOBJECT_TEMPLATE);
913
+ /**
914
+ * Generates a FileObject class for a backend.
915
+ * @param {BackendConfig} backend
916
+ * @returns {Promise<void>}
917
+ */
918
+ async function generateFileObjectForBackend(backend) {
919
+ const modulePath = process.env.NODE_ENV === "test" ? "../../../src" : "@statezero/core";
920
+ const templateData = {
921
+ backendName: backend.NAME,
922
+ modulePath: modulePath,
923
+ };
924
+ const fileObjectContent = fileObjectTemplate(templateData);
925
+ const fileObjectPath = path.join(backend.GENERATED_TYPES_DIR, "fileobject.js");
926
+ await fs.writeFile(fileObjectPath, fileObjectContent);
927
+ // Also generate TypeScript declaration
928
+ const dtsContent = `/**
929
+ * This file was auto-generated. Do not make direct changes to the file.
930
+ * Backend-specific FileObject class for ${backend.NAME}
931
+ */
932
+
933
+ import { FileObject as BaseFileObject } from '${modulePath}';
934
+
935
+ export declare class ${backend.NAME}FileObject extends BaseFileObject {
936
+ static configKey: string;
937
+ }
938
+
939
+ export declare const FileObject: typeof ${backend.NAME}FileObject;
940
+ `;
941
+ const dtsPath = path.join(backend.GENERATED_TYPES_DIR, "fileobject.d.ts");
942
+ await fs.writeFile(dtsPath, dtsContent);
943
+ }
944
+ // --------------------
945
+ // Main Runner: Fetch models and prompt selection
946
+ // --------------------
947
+ // Update main function to use this new approach
948
+ async function main() {
949
+ // Load configuration from file (CLI-only or tests) before any other operations.
950
+ loadConfigFromFile();
951
+ // Retrieve the validated configuration from the global config singleton.
952
+ const configData = configInstance.getConfig();
953
+ const backendConfigs = configData.backendConfigs;
954
+ const inquirer = (await import("inquirer")).default;
955
+ const fetchPromises = Object.keys(backendConfigs).map(async (key) => {
956
+ const backend = backendConfigs[key];
957
+ backend.NAME = key;
958
+ try {
959
+ const response = await axios.get(`${backend.API_URL}/models/`);
960
+ return { backend, models: response.data };
961
+ }
962
+ catch (error) {
963
+ console.error(`Error fetching models from backend ${backend.NAME}:`, error.message);
964
+ return { backend, models: [] };
965
+ }
966
+ });
967
+ const backendModels = await Promise.all(fetchPromises);
968
+ const choices = [];
969
+ for (const { backend, models } of backendModels) {
970
+ choices.push(new inquirer.Separator(`\n=== ${backend.NAME} ===\n`));
971
+ for (const model of models) {
972
+ choices.push({
973
+ name: model,
974
+ value: { backend, model },
975
+ checked: true,
976
+ });
977
+ }
978
+ }
979
+ if (choices.length === 0) {
980
+ console.log("No models to synchronise");
981
+ process.exit(0);
982
+ }
983
+ const { selectedModels } = await inquirer.prompt([
984
+ {
985
+ type: "checkbox",
986
+ name: "selectedModels",
987
+ message: "Select models to synchronise:",
988
+ choices,
989
+ pageSize: 20,
990
+ },
991
+ ]);
992
+ if (!selectedModels || selectedModels.length === 0) {
993
+ console.log("No models selected. Exiting.");
994
+ process.exit(0);
995
+ }
996
+ const modelsByBackend = selectedModels.reduce((acc, item) => {
997
+ const key = item.backend.NAME;
998
+ acc[key] = acc[key] || { backend: item.backend, models: [] };
999
+ acc[key].models.push(item.model);
1000
+ return acc;
1001
+ }, {});
1002
+ const allGeneratedFiles = [];
1003
+ for (const group of Object.values(modelsByBackend)) {
1004
+ console.log(`\nProcessing backend: ${group.backend.NAME}`);
1005
+ const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
1006
+ progressBar.start(group.models.length, 0);
1007
+ for (const model of group.models) {
1008
+ try {
1009
+ const result = await generateSchemaForModel(group.backend, model);
1010
+ allGeneratedFiles.push({ ...result, backend: group.backend.NAME });
1011
+ }
1012
+ catch (error) {
1013
+ console.error(`Error generating schema for model ${model} from backend ${group.backend.NAME}:`, error.message);
1014
+ }
1015
+ progressBar.increment();
1016
+ }
1017
+ progressBar.stop();
1018
+ // Generate FileObject for this backend
1019
+ try {
1020
+ await generateFileObjectForBackend(group.backend);
1021
+ console.log(`✨ Generated FileObject for backend: ${group.backend.NAME}`);
1022
+ }
1023
+ catch (error) {
1024
+ console.error(`Error generating FileObject for backend ${group.backend.NAME}:`, error.message);
1025
+ }
1026
+ }
1027
+ // Generate an index file per app
1028
+ await generateAppLevelIndexFiles(allGeneratedFiles, backendConfigs);
1029
+ // Generate model registry
1030
+ await generateModelRegistry(allGeneratedFiles, backendConfigs);
1031
+ console.log(`✨ Generated JavaScript files with TypeScript declarations for ${selectedModels.length} models across ${Object.keys(backendConfigs).length} backends.`);
1032
+ }
1033
+ /**
1034
+ * Main exported function to generate schema.
1035
+ * @param {GenerateArgs} args
1036
+ * @returns {Promise<void>}
1037
+ */
1038
+ export async function generateSchema(args) {
1039
+ await main();
1040
+ }