@solidxai/core 0.1.11-beta.2 → 0.1.11-beta.4

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 (60) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/dist/controllers/datasource-introspection.controller.d.ts +38 -1
  3. package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -1
  4. package/dist/controllers/datasource-introspection.controller.js +38 -0
  5. package/dist/controllers/datasource-introspection.controller.js.map +1 -1
  6. package/dist/dtos/datasource-introspection-mapping.dto.d.ts +4 -0
  7. package/dist/dtos/datasource-introspection-mapping.dto.d.ts.map +1 -1
  8. package/dist/dtos/datasource-introspection-mapping.dto.js +18 -2
  9. package/dist/dtos/datasource-introspection-mapping.dto.js.map +1 -1
  10. package/dist/entities/{legacy-common-with-id.entity.d.ts → legacy-common-with-generated-id.entity.d.ts} +2 -2
  11. package/dist/entities/legacy-common-with-generated-id.entity.d.ts.map +1 -0
  12. package/dist/entities/{legacy-common-with-id.entity.js → legacy-common-with-generated-id.entity.js} +4 -4
  13. package/dist/entities/legacy-common-with-generated-id.entity.js.map +1 -0
  14. package/dist/entities/{legacy-common.entity.d.ts → legacy-common.entity-with-existing-id.entity.d.ts} +1 -1
  15. package/dist/entities/legacy-common.entity-with-existing-id.entity.d.ts.map +1 -0
  16. package/dist/entities/{legacy-common.entity.js → legacy-common.entity-with-existing-id.entity.js} +1 -1
  17. package/dist/entities/legacy-common.entity-with-existing-id.entity.js.map +1 -0
  18. package/dist/helpers/model-metadata-helper.service.js +3 -3
  19. package/dist/helpers/model-metadata-helper.service.js.map +1 -1
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/interfaces/datasource-introspection-provider.interface.d.ts +1 -0
  25. package/dist/interfaces/datasource-introspection-provider.interface.d.ts.map +1 -1
  26. package/dist/interfaces/datasource-introspection-provider.interface.js.map +1 -1
  27. package/dist/seeders/seed-data/solid-core-metadata.json +12 -3
  28. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.d.ts.map +1 -1
  29. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js +12 -0
  30. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js.map +1 -1
  31. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts.map +1 -1
  32. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js +2 -0
  33. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js.map +1 -1
  34. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts.map +1 -1
  35. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js +28 -15
  36. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js.map +1 -1
  37. package/dist/services/datasource-introspection.service.d.ts +52 -2
  38. package/dist/services/datasource-introspection.service.d.ts.map +1 -1
  39. package/dist/services/datasource-introspection.service.js +534 -160
  40. package/dist/services/datasource-introspection.service.js.map +1 -1
  41. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +10 -10
  42. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/controllers/datasource-introspection.controller.ts +27 -1
  45. package/src/dtos/datasource-introspection-mapping.dto.ts +10 -0
  46. package/src/entities/{legacy-common-with-id.entity.ts → legacy-common-with-generated-id.entity.ts} +1 -1
  47. package/src/helpers/model-metadata-helper.service.ts +1 -1
  48. package/src/index.ts +2 -2
  49. package/src/interfaces/datasource-introspection-provider.interface.ts +1 -0
  50. package/src/seeders/seed-data/solid-core-metadata.json +12 -3
  51. package/src/services/datasource-introspection/mssql-datasource-introspection-provider.service.ts +12 -0
  52. package/src/services/datasource-introspection/mysql-datasource-introspection-provider.service.ts +2 -0
  53. package/src/services/datasource-introspection/postgres-datasource-introspection-provider.service.ts +28 -15
  54. package/src/services/datasource-introspection.service.ts +626 -136
  55. package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +2 -2
  56. package/dist/entities/legacy-common-with-id.entity.d.ts.map +0 -1
  57. package/dist/entities/legacy-common-with-id.entity.js.map +0 -1
  58. package/dist/entities/legacy-common.entity.d.ts.map +0 -1
  59. package/dist/entities/legacy-common.entity.js.map +0 -1
  60. /package/src/entities/{legacy-common.entity.ts → legacy-common.entity-with-existing-id.entity.ts} +0 -0
@@ -4,12 +4,14 @@ import { getDataSourceToken } from "@nestjs/typeorm";
4
4
  import { createHash } from "crypto";
5
5
  import * as fs from "fs/promises";
6
6
  import * as path from "path";
7
- import { camelCase, startCase } from "lodash";
7
+ import { camelCase, kebabCase, startCase } from "lodash";
8
+ import { ArrayLiteralExpression, IndentationText, Node, ObjectLiteralExpression, Project, QuoteKind, SyntaxKind, VariableDeclarationKind } from "ts-morph";
8
9
  import { DataSource } from "typeorm";
9
10
  import { CreateModelMetadataDto } from "src/dtos/create-model-metadata.dto";
10
11
  import { DatasourceIntrospectionMappingColumnDto, DatasourceIntrospectionMappingDto } from "src/dtos/datasource-introspection-mapping.dto";
11
12
  import { UpdateModelMetaDataDto } from "src/dtos/update-model-metadata.dto";
12
13
  import { LegacyTableType } from "src/enums/legacy-table-type.enum";
14
+ import { classify } from "src/helpers/string.helper";
13
15
  import type { DatasourceIntrospectionColumn, DatasourceIntrospectionProvider, DatasourceIntrospectionTableRef } from "src/interfaces/datasource-introspection-provider.interface";
14
16
  import { ModuleMetadataHelperService } from "src/helpers/module-metadata-helper.service";
15
17
  import { ModelMetadataHelperService } from "src/helpers/model-metadata-helper.service";
@@ -17,8 +19,10 @@ import { ModelMetadataRepository } from "src/repository/model-metadata.repositor
17
19
  import { ModuleMetadataRepository } from "src/repository/module-metadata.repository";
18
20
  import type { ModelMetadata } from "src/entities/model-metadata.entity";
19
21
  import { DatasourceManagementService, type DatasourceRecord } from "./datasource-management.service";
22
+ import { CommandService } from "src/helpers/command.service";
20
23
  import { FieldMetadataService } from "./field-metadata.service";
21
24
  import { ModelMetadataService } from "./model-metadata.service";
25
+ import { ModuleMetadataService } from "./module-metadata.service";
22
26
  import { MssqlDatasourceIntrospectionProviderService } from "./datasource-introspection/mssql-datasource-introspection-provider.service";
23
27
  import { MysqlDatasourceIntrospectionProviderService } from "./datasource-introspection/mysql-datasource-introspection-provider.service";
24
28
  import { PostgresDatasourceIntrospectionProviderService } from "./datasource-introspection/postgres-datasource-introspection-provider.service";
@@ -44,6 +48,7 @@ type TableInventoryRecord = {
44
48
  type TableDetailColumnRecord = {
45
49
  columnName: string;
46
50
  fieldName: string;
51
+ displayName: string;
47
52
  dataType: string;
48
53
  solidFieldType: string;
49
54
  ormType: string;
@@ -54,6 +59,7 @@ type TableDetailColumnRecord = {
54
59
  numericScale: number | null;
55
60
  ordinalPosition: number | null;
56
61
  isAutoGenerated: boolean;
62
+ isPrimaryKey: boolean;
57
63
  handledBySuperclass: boolean;
58
64
  superclassFieldName: string | null;
59
65
  };
@@ -81,6 +87,13 @@ type MappingPreviewRecord = {
81
87
  note: string;
82
88
  missingSystemColumns: string[];
83
89
  };
90
+ datasourceFile: {
91
+ filePath: string;
92
+ created: boolean;
93
+ updated: boolean;
94
+ entityRegistered: boolean;
95
+ migrationGlobRegistered: boolean;
96
+ };
84
97
  summary: {
85
98
  includedColumnNames: string[];
86
99
  removedColumnNames: string[];
@@ -119,6 +132,105 @@ type SystemColumnDefinition = {
119
132
  isUnique?: boolean;
120
133
  };
121
134
 
135
+ type DatasourceTypeFamily =
136
+ | "boolean"
137
+ | "bigint"
138
+ | "int"
139
+ | "numeric"
140
+ | "decimal"
141
+ | "float"
142
+ | "date"
143
+ | "time"
144
+ | "datetime"
145
+ | "datetimeWithTimezone"
146
+ | "json"
147
+ | "jsonb"
148
+ | "uuid"
149
+ | "longText"
150
+ | "mediumText"
151
+ | "shortText"
152
+ | "unicodeShortText";
153
+
154
+ const DATASOURCE_TYPE_FAMILY_ALIASES: Record<DatasourceTypeFamily, string[]> = {
155
+ boolean: ["bit", "bool", "boolean"],
156
+ bigint: ["bigint"],
157
+ int: ["int", "integer", "smallint", "tinyint"],
158
+ numeric: ["numeric"],
159
+ decimal: ["decimal", "money", "smallmoney"],
160
+ float: ["float", "double", "real"],
161
+ date: ["date"],
162
+ time: ["time", "time without time zone", "time with time zone"],
163
+ datetime: ["datetime", "datetime2", "smalldatetime", "timestamp", "timestamp without time zone"],
164
+ datetimeWithTimezone: ["timestamptz", "timestamp with time zone"],
165
+ json: ["json", "simple-json", "simplejson"],
166
+ jsonb: ["jsonb"],
167
+ uuid: ["uniqueidentifier", "uuid"],
168
+ longText: ["text", "ntext", "longtext", "clob"],
169
+ mediumText: ["mediumtext"],
170
+ shortText: ["varchar", "char", "citext"],
171
+ unicodeShortText: ["nvarchar", "nchar"],
172
+ };
173
+
174
+ const ORM_TYPE_BY_DATASOURCE_FAMILY: Record<string, Partial<Record<DatasourceTypeFamily, string>>> = {
175
+ mssql: {
176
+ boolean: "bit",
177
+ bigint: "bigint",
178
+ int: "int",
179
+ numeric: "numeric",
180
+ decimal: "decimal",
181
+ float: "decimal",
182
+ date: "date",
183
+ time: "time",
184
+ datetime: "datetime",
185
+ datetimeWithTimezone: "datetime",
186
+ json: "simple-json",
187
+ jsonb: "simple-json",
188
+ uuid: "uniqueidentifier",
189
+ longText: "text",
190
+ mediumText: "text",
191
+ shortText: "varchar",
192
+ unicodeShortText: "nvarchar",
193
+ },
194
+ mysql: {
195
+ boolean: "boolean",
196
+ bigint: "int",
197
+ int: "int",
198
+ numeric: "decimal",
199
+ decimal: "decimal",
200
+ float: "double",
201
+ date: "date",
202
+ time: "time",
203
+ datetime: "datetime",
204
+ datetimeWithTimezone: "datetime",
205
+ json: "json",
206
+ jsonb: "json",
207
+ uuid: "varchar",
208
+ longText: "longtext",
209
+ mediumText: "mediumtext",
210
+ shortText: "varchar",
211
+ unicodeShortText: "varchar",
212
+ },
213
+ postgres: {
214
+ boolean: "boolean",
215
+ bigint: "bigint",
216
+ int: "integer",
217
+ numeric: "decimal",
218
+ decimal: "decimal",
219
+ float: "decimal",
220
+ date: "date",
221
+ time: "time",
222
+ datetime: "timestamp",
223
+ datetimeWithTimezone: "timestamptz",
224
+ json: "json",
225
+ jsonb: "jsonb",
226
+ uuid: "uuid",
227
+ longText: "text",
228
+ mediumText: "text",
229
+ shortText: "varchar",
230
+ unicodeShortText: "varchar",
231
+ },
232
+ };
233
+
122
234
  @Injectable()
123
235
  export class DatasourceIntrospectionService {
124
236
  private readonly supportedDatasourceTypes = new Set(["mssql", "mysql", "postgres"]);
@@ -132,6 +244,8 @@ export class DatasourceIntrospectionService {
132
244
  private readonly modelMetadataHelperService: ModelMetadataHelperService,
133
245
  private readonly fieldMetadataService: FieldMetadataService,
134
246
  private readonly modelMetadataService: ModelMetadataService,
247
+ private readonly moduleMetadataService: ModuleMetadataService,
248
+ private readonly commandService: CommandService,
135
249
  private readonly mssqlProvider: MssqlDatasourceIntrospectionProviderService,
136
250
  private readonly mysqlProvider: MysqlDatasourceIntrospectionProviderService,
137
251
  private readonly postgresProvider: PostgresDatasourceIntrospectionProviderService,
@@ -266,27 +380,116 @@ export class DatasourceIntrospectionService {
266
380
  ? await this.modelMetadataService.update(mappingDto.modelId, plan.modelPayload as UpdateModelMetaDataDto)
267
381
  : await this.modelMetadataService.create(plan.modelPayload as CreateModelMetadataDto);
268
382
 
383
+ const persistedModelId = mappingDto.modelId ?? (persistedModel as ModelMetadata | undefined)?.id ?? null;
384
+
385
+ return {
386
+ data: {
387
+ modelId: persistedModelId,
388
+ operation: plan.operation,
389
+ metadataJson: plan.metadataJson,
390
+ summary: plan.summary,
391
+ },
392
+ };
393
+ }
394
+
395
+ async createMigrationArtifacts(moduleId: number, mappingDto: DatasourceIntrospectionMappingDto) {
396
+ const plan = await this.buildMappingPreview(moduleId, mappingDto);
397
+ const module = await this.loadModule(moduleId);
398
+ const datasource = this.requireDatasource(mappingDto.dataSource);
399
+
400
+ if (!mappingDto.modelId) {
401
+ throw new BadRequestException("Persist the model mapping first so the generated entity can be registered before creating migration artifacts.");
402
+ }
403
+
269
404
  if (plan.migration.willGenerate && plan.migration.content && plan.migration.filePath) {
270
405
  await fs.mkdir(path.dirname(plan.migration.filePath), { recursive: true });
271
406
  await fs.writeFile(plan.migration.filePath, plan.migration.content, "utf8");
272
407
  }
273
408
 
274
- const persistedModelId = mappingDto.modelId ?? (persistedModel as ModelMetadata | undefined)?.id ?? null;
409
+ const datasourceFile = await this.syncTypeormDatasourceFile(
410
+ module.name,
411
+ datasource,
412
+ plan.modelPayload.singularName,
413
+ );
275
414
 
276
415
  return {
277
416
  data: {
278
- modelId: persistedModelId,
417
+ modelId: mappingDto.modelId,
279
418
  operation: plan.operation,
280
- metadataJson: plan.metadataJson,
281
419
  migration: {
282
420
  ...plan.migration,
283
421
  written: Boolean(plan.migration.willGenerate && plan.migration.filePath),
284
422
  },
423
+ datasourceFile,
285
424
  summary: plan.summary,
286
425
  },
287
426
  };
288
427
  }
289
428
 
429
+ async generateCode(moduleId: number) {
430
+ await this.loadModule(moduleId);
431
+ const output = await this.moduleMetadataService.generateCodeViaCtl(moduleId);
432
+
433
+ return {
434
+ data: {
435
+ moduleId,
436
+ output,
437
+ message: "Module code generation completed.",
438
+ },
439
+ };
440
+ }
441
+
442
+ async runMigration(moduleId: number, datasourceName: string) {
443
+ const module = await this.loadModule(moduleId);
444
+ const datasource = this.requireDatasource(datasourceName);
445
+ const cwd = path.join(process.cwd(), "..");
446
+ const outputs: string[] = [];
447
+
448
+ const migrationOutput = await this.commandService.executeCommandWithArgs({
449
+ command: "npx",
450
+ args: ["-y", "@solidxai/solidctl@latest", "migration", "-d", datasource.name, "run"],
451
+ cwd,
452
+ });
453
+ outputs.push([
454
+ `npx @solidxai/solidctl@latest migration -d ${datasource.name} run`,
455
+ migrationOutput,
456
+ ].filter(Boolean).join("\n"));
457
+
458
+ const buildOutput = await this.commandService.executeCommandWithArgs({
459
+ command: "npx",
460
+ args: ["-y", "@solidxai/solidctl@latest", "build"],
461
+ cwd,
462
+ });
463
+ outputs.push([
464
+ "npx @solidxai/solidctl@latest build",
465
+ buildOutput,
466
+ ].filter(Boolean).join("\n"));
467
+
468
+ const seedOutput = await this.commandService.executeCommandWithArgs({
469
+ command: "npx",
470
+ args: ["-y", "@solidxai/solidctl@latest", "seed", "--modules-to-seed", module.name],
471
+ cwd,
472
+ });
473
+ outputs.push([
474
+ `npx @solidxai/solidctl@latest seed --modules-to-seed ${module.name}`,
475
+ seedOutput,
476
+ ].filter(Boolean).join("\n"));
477
+
478
+ return {
479
+ data: {
480
+ moduleId,
481
+ datasource: datasource.name,
482
+ output: outputs.join("\n\n"),
483
+ command: [
484
+ `npx @solidxai/solidctl@latest migration -d ${datasource.name} run`,
485
+ "npx @solidxai/solidctl@latest build",
486
+ `npx @solidxai/solidctl@latest seed --modules-to-seed ${module.name}`,
487
+ ].join("\n"),
488
+ message: `Migration, build, and seed completed successfully for datasource "${datasource.name}".`,
489
+ },
490
+ };
491
+ }
492
+
290
493
  private async buildMappingPreview(moduleId: number, mappingDto: DatasourceIntrospectionMappingDto): Promise<MappingPreviewRecord> {
291
494
  const module = await this.loadModule(moduleId);
292
495
  const datasource = this.requireDatasource(mappingDto.dataSource);
@@ -338,6 +541,13 @@ export class DatasourceIntrospectionService {
338
541
  mappedModelId: mappingDto.modelId ?? null,
339
542
  metadataJson,
340
543
  migration,
544
+ datasourceFile: {
545
+ filePath: path.resolve(process.cwd(), "src", `typeorm-${datasource.name}-datasource.ts`),
546
+ created: false,
547
+ updated: false,
548
+ entityRegistered: false,
549
+ migrationGlobRegistered: false,
550
+ },
341
551
  summary: {
342
552
  includedColumnNames,
343
553
  removedColumnNames,
@@ -771,7 +981,7 @@ export class DatasourceIntrospectionService {
771
981
  ...preserved,
772
982
  ...(column.fieldId ? { id: column.fieldId } : {}),
773
983
  name: column.fieldName,
774
- displayName: preserved.displayName ?? startCase(column.fieldName),
984
+ displayName: column.displayName?.trim() || preserved.displayName || startCase(column.fieldName),
775
985
  description: preserved.description ?? "",
776
986
  type: column.solidFieldType,
777
987
  ormType: column.ormType,
@@ -891,11 +1101,8 @@ export class DatasourceIntrospectionService {
891
1101
 
892
1102
  const upColumnStatements = missingSystemColumns.map((definition) => {
893
1103
  const columnLiteral = this.renderTableColumnLiteral(definition);
894
- return [
895
- ` if (!(await queryRunner.hasColumn(tableName, ${JSON.stringify(definition.columnName)}))) {`,
896
- ` await queryRunner.addColumn(tableName, new TableColumn(${columnLiteral}));`,
897
- " }",
898
- ].join("\n");
1104
+ return ` if (!(await queryRunner.hasColumn(tableName, ${JSON.stringify(definition.columnName)})))\n`
1105
+ + ` await queryRunner.addColumn(tableName, new TableColumn(${columnLiteral}));`;
899
1106
  }).join("\n\n");
900
1107
 
901
1108
  const postUpStatements: string[] = [];
@@ -904,12 +1111,11 @@ export class DatasourceIntrospectionService {
904
1111
  [
905
1112
  " {",
906
1113
  " const table = await queryRunner.getTable(tableName);",
907
- ` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(deletedAtIndexName)})) {`,
908
- " await queryRunner.createIndex(tableName, new TableIndex({",
909
- ` name: ${JSON.stringify(deletedAtIndexName)},`,
910
- " columnNames: [\"ss_deleted_at\"],",
911
- " }));",
912
- " }",
1114
+ ` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(deletedAtIndexName)}))`,
1115
+ ` await queryRunner.createIndex(tableName, new TableIndex(${this.renderTableIndexLiteral({
1116
+ name: deletedAtIndexName,
1117
+ columnNames: ["ss_deleted_at"],
1118
+ })}));`,
913
1119
  " }",
914
1120
  ].join("\n"),
915
1121
  );
@@ -919,13 +1125,12 @@ export class DatasourceIntrospectionService {
919
1125
  [
920
1126
  " {",
921
1127
  " const table = await queryRunner.getTable(tableName);",
922
- ` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(generatedIdUniqueName)})) {`,
923
- " await queryRunner.createIndex(tableName, new TableIndex({",
924
- ` name: ${JSON.stringify(generatedIdUniqueName)},`,
925
- " columnNames: [\"ss_id\"],",
926
- " isUnique: true,",
927
- " }));",
928
- " }",
1128
+ ` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(generatedIdUniqueName)}))`,
1129
+ ` await queryRunner.createIndex(tableName, new TableIndex(${this.renderTableIndexLiteral({
1130
+ name: generatedIdUniqueName,
1131
+ columnNames: ["ss_id"],
1132
+ isUnique: true,
1133
+ })}));`,
929
1134
  " }",
930
1135
  ].join("\n"),
931
1136
  );
@@ -938,9 +1143,8 @@ export class DatasourceIntrospectionService {
938
1143
  " {",
939
1144
  " const table = await queryRunner.getTable(tableName);",
940
1145
  ` const index = table?.indices.find((entry) => entry.name === ${JSON.stringify(generatedIdUniqueName)});`,
941
- " if (index) {",
1146
+ " if (index)",
942
1147
  " await queryRunner.dropIndex(tableName, index);",
943
- " }",
944
1148
  " }",
945
1149
  ].join("\n"),
946
1150
  );
@@ -951,9 +1155,8 @@ export class DatasourceIntrospectionService {
951
1155
  " {",
952
1156
  " const table = await queryRunner.getTable(tableName);",
953
1157
  ` const index = table?.indices.find((entry) => entry.name === ${JSON.stringify(deletedAtIndexName)});`,
954
- " if (index) {",
1158
+ " if (index)",
955
1159
  " await queryRunner.dropIndex(tableName, index);",
956
- " }",
957
1160
  " }",
958
1161
  ].join("\n"),
959
1162
  );
@@ -962,11 +1165,8 @@ export class DatasourceIntrospectionService {
962
1165
  ...missingSystemColumns
963
1166
  .slice()
964
1167
  .reverse()
965
- .map((definition) => [
966
- ` if (await queryRunner.hasColumn(tableName, ${JSON.stringify(definition.columnName)})) {`,
967
- ` await queryRunner.dropColumn(tableName, ${JSON.stringify(definition.columnName)});`,
968
- " }",
969
- ].join("\n")),
1168
+ .map((definition) => ` if (await queryRunner.hasColumn(tableName, ${JSON.stringify(definition.columnName)}))\n`
1169
+ + ` await queryRunner.dropColumn(tableName, ${JSON.stringify(definition.columnName)});`),
970
1170
  );
971
1171
 
972
1172
  return [
@@ -992,29 +1192,41 @@ export class DatasourceIntrospectionService {
992
1192
 
993
1193
  private renderTableColumnLiteral(definition: SystemColumnDefinition) {
994
1194
  const properties: string[] = [
995
- `{\n name: ${JSON.stringify(definition.columnName)},`,
996
- ` type: ${JSON.stringify(definition.type)},`,
1195
+ `name: ${JSON.stringify(definition.columnName)}`,
1196
+ `type: ${JSON.stringify(definition.type)}`,
997
1197
  ];
998
1198
 
999
1199
  if (definition.length) {
1000
- properties.push(` length: ${JSON.stringify(definition.length)},`);
1200
+ properties.push(`length: ${JSON.stringify(definition.length)}`);
1001
1201
  }
1002
1202
  if (typeof definition.isNullable === "boolean") {
1003
- properties.push(` isNullable: ${definition.isNullable},`);
1203
+ properties.push(`isNullable: ${definition.isNullable}`);
1004
1204
  }
1005
1205
  if (definition.defaultExpression) {
1006
- properties.push(` default: ${JSON.stringify(definition.defaultExpression)},`);
1206
+ properties.push(`default: ${JSON.stringify(definition.defaultExpression)}`);
1007
1207
  }
1008
1208
  if (definition.isGenerated) {
1009
- properties.push(" isGenerated: true,");
1010
- properties.push(" generationStrategy: \"increment\",");
1209
+ properties.push("isGenerated: true");
1210
+ properties.push("generationStrategy: \"increment\"");
1011
1211
  }
1012
1212
  if (definition.isUnique) {
1013
- properties.push(" isUnique: true,");
1213
+ properties.push("isUnique: true");
1214
+ }
1215
+
1216
+ return `{ ${properties.join(", ")}, }`;
1217
+ }
1218
+
1219
+ private renderTableIndexLiteral(definition: { name: string; columnNames: string[]; isUnique?: boolean }) {
1220
+ const properties: string[] = [
1221
+ `name: ${JSON.stringify(definition.name)}`,
1222
+ `columnNames: ${JSON.stringify(definition.columnNames)}`,
1223
+ ];
1224
+
1225
+ if (definition.isUnique) {
1226
+ properties.push("isUnique: true");
1014
1227
  }
1015
1228
 
1016
- properties.push(" }");
1017
- return properties.join("\n");
1229
+ return `{ ${properties.join(", ")}, }`;
1018
1230
  }
1019
1231
 
1020
1232
  private buildMigrationObjectName(prefix: string, tableName: string, columnName: string) {
@@ -1050,13 +1262,15 @@ export class DatasourceIntrospectionService {
1050
1262
  private async buildTableInventory(datasource: DatasourceRecord) {
1051
1263
  const provider = this.getProvider(datasource.type);
1052
1264
  const runtimeDataSource = this.resolveDataSourceByName(datasource.name);
1053
- const [tables, columns, models] = await Promise.all([
1265
+ const [rawTables, rawColumns, models] = await Promise.all([
1054
1266
  provider.listTables(runtimeDataSource),
1055
1267
  provider.listColumns(runtimeDataSource),
1056
1268
  this.modelMetadataRepository.find({ relations: { module: true } as any }),
1057
1269
  ]);
1270
+ const tables = rawTables.filter((table) => !this.isSolidxInternalTableName(table.tableName));
1271
+ const columns = rawColumns.filter((column) => !this.isSolidxInternalTableName(column.tableName));
1058
1272
 
1059
- const modelMap = this.buildModelMap(models);
1273
+ const modelLookup = this.buildModelLookup(models);
1060
1274
  const columnsByKey = this.groupColumnsByTable(columns);
1061
1275
  const tableKeys = new Set<string>();
1062
1276
 
@@ -1080,7 +1294,7 @@ export class DatasourceIntrospectionService {
1080
1294
 
1081
1295
  const suggestedLegacyTableType = this.detectLegacyTableType(datasource.type, tableColumns);
1082
1296
  const suggestedIdColumn = this.getSuggestedIdColumn(datasource.type, tableColumns, suggestedLegacyTableType);
1083
- const mappedModel = modelMap.get(this.toModelMapKey(datasource.name, tableName)) ?? null;
1297
+ const mappedModel = this.resolveMappedModel(modelLookup, datasource.name, tableName);
1084
1298
  const physicalColumns = tableColumns.filter((column) => !this.isHandledBySuperclass(column.columnName, suggestedLegacyTableType));
1085
1299
 
1086
1300
  return {
@@ -1119,19 +1333,52 @@ export class DatasourceIntrospectionService {
1119
1333
  };
1120
1334
  }
1121
1335
 
1122
- private buildModelMap(models: ModelMetadata[]) {
1123
- const map = new Map<string, ModelMetadata>();
1336
+ private buildModelLookup(models: ModelMetadata[]) {
1337
+ const byDatasourceAndTable = new Map<string, ModelMetadata>();
1338
+ const byTableName = new Map<string, ModelMetadata[]>();
1124
1339
 
1125
1340
  for (const model of models) {
1126
1341
  if (!model.tableName) {
1127
1342
  continue;
1128
1343
  }
1129
1344
 
1130
- map.set(this.toModelMapKey(model.dataSource, model.tableName), model);
1131
- map.set(this.toModelMapKey("*", model.tableName), model);
1345
+ byDatasourceAndTable.set(this.toModelMapKey(model.dataSource, model.tableName), model);
1346
+
1347
+ const normalizedTableName = model.tableName.trim().toLowerCase();
1348
+ const bucket = byTableName.get(normalizedTableName);
1349
+ if (bucket) {
1350
+ bucket.push(model);
1351
+ continue;
1352
+ }
1353
+
1354
+ byTableName.set(normalizedTableName, [model]);
1132
1355
  }
1133
1356
 
1134
- return map;
1357
+ return {
1358
+ byDatasourceAndTable,
1359
+ byTableName,
1360
+ };
1361
+ }
1362
+
1363
+ private resolveMappedModel(
1364
+ lookup: {
1365
+ byDatasourceAndTable: Map<string, ModelMetadata>;
1366
+ byTableName: Map<string, ModelMetadata[]>;
1367
+ },
1368
+ datasourceName: string,
1369
+ tableName: string,
1370
+ ) {
1371
+ const exactMatch = lookup.byDatasourceAndTable.get(this.toModelMapKey(datasourceName, tableName));
1372
+ if (exactMatch) {
1373
+ return exactMatch;
1374
+ }
1375
+
1376
+ const fallbackMatches = lookup.byTableName.get(tableName.trim().toLowerCase()) ?? [];
1377
+ if (fallbackMatches.length === 1) {
1378
+ return fallbackMatches[0];
1379
+ }
1380
+
1381
+ return fallbackMatches[0] ?? null;
1135
1382
  }
1136
1383
 
1137
1384
  private groupColumnsByTable(columns: DatasourceIntrospectionColumn[]) {
@@ -1151,6 +1398,10 @@ export class DatasourceIntrospectionService {
1151
1398
  return grouped;
1152
1399
  }
1153
1400
 
1401
+ private isSolidxInternalTableName(tableName: string | null | undefined) {
1402
+ return typeof tableName === "string" && tableName.toLowerCase().startsWith("ss_");
1403
+ }
1404
+
1154
1405
  private detectLegacyTableType(datasourceType: string, columns: DatasourceIntrospectionColumn[]): LegacyTableType {
1155
1406
  const normalizedDatasourceType = datasourceType.toLowerCase();
1156
1407
  const hasExistingGeneratedId = columns.some((column) => column.isAutoGenerated);
@@ -1239,13 +1490,17 @@ export class DatasourceIntrospectionService {
1239
1490
  datasourceType: string,
1240
1491
  ): TableDetailColumnRecord {
1241
1492
  const superclassFieldName = this.getSuperclassFieldName(column.columnName, legacyTableType);
1493
+ const normalizedColumnName = column.columnName.toLowerCase();
1242
1494
  const fieldName = legacyTableType === LegacyTableType.EXISTING_ID && column.isAutoGenerated
1243
1495
  ? "id"
1244
- : this.toFieldName(column.columnName);
1496
+ : legacyTableType === LegacyTableType.GENERATED_ID && normalizedColumnName === "id"
1497
+ ? "legacyId"
1498
+ : this.toFieldName(column.columnName);
1245
1499
 
1246
1500
  return {
1247
1501
  columnName: column.columnName,
1248
1502
  fieldName,
1503
+ displayName: startCase(fieldName),
1249
1504
  dataType: column.dataType,
1250
1505
  solidFieldType: this.toSolidFieldType(column),
1251
1506
  ormType: this.toOrmFieldType(column, datasourceType),
@@ -1256,6 +1511,7 @@ export class DatasourceIntrospectionService {
1256
1511
  numericScale: column.numericScale,
1257
1512
  ordinalPosition: column.ordinalPosition,
1258
1513
  isAutoGenerated: column.isAutoGenerated,
1514
+ isPrimaryKey: column.isPrimaryKey,
1259
1515
  handledBySuperclass: Boolean(superclassFieldName),
1260
1516
  superclassFieldName,
1261
1517
  };
@@ -1298,109 +1554,80 @@ export class DatasourceIntrospectionService {
1298
1554
  return "LegacyCommonEntityWithExistingId";
1299
1555
  }
1300
1556
 
1301
- private toSolidFieldType(column: DatasourceIntrospectionColumn) {
1302
- const normalized = column.dataType.toLowerCase();
1303
- const charLength = column.characterMaximumLength ?? null;
1304
-
1305
- if (["bit", "bool", "boolean"].includes(normalized)) {
1306
- return "boolean";
1307
- }
1308
-
1309
- if (["bigint"].includes(normalized)) {
1310
- return "bigint";
1311
- }
1312
-
1313
- if (["int", "integer", "smallint", "tinyint"].includes(normalized)) {
1314
- return "int";
1315
- }
1316
-
1317
- if (["numeric", "decimal", "float", "double", "real", "money", "smallmoney"].includes(normalized)) {
1318
- return "decimal";
1319
- }
1557
+ private getDatasourceTypeFamily(dataType: string): DatasourceTypeFamily | null {
1558
+ const normalized = dataType.toLowerCase();
1320
1559
 
1321
- if (["date"].includes(normalized)) {
1322
- return "date";
1560
+ for (const [family, aliases] of Object.entries(DATASOURCE_TYPE_FAMILY_ALIASES) as Array<[DatasourceTypeFamily, string[]]>) {
1561
+ if (aliases.includes(normalized)) {
1562
+ return family;
1563
+ }
1323
1564
  }
1324
1565
 
1325
- if (["time"].includes(normalized)) {
1326
- return "time";
1566
+ if (normalized.includes("json")) {
1567
+ return "json";
1327
1568
  }
1328
1569
 
1329
- if (["datetime", "datetime2", "timestamp", "timestamptz", "smalldatetime"].includes(normalized) || normalized.includes("date")) {
1570
+ if (normalized.includes("date")) {
1330
1571
  return "datetime";
1331
1572
  }
1332
1573
 
1333
- if (["json", "jsonb", "simple-json", "simplejson"].includes(normalized) || normalized.includes("json")) {
1334
- return "json";
1335
- }
1336
-
1337
- if (["uniqueidentifier", "uuid"].includes(normalized)) {
1338
- return "uuid";
1339
- }
1574
+ return null;
1575
+ }
1340
1576
 
1341
- if (["text", "ntext", "mediumtext", "longtext", "clob"].includes(normalized)) {
1342
- return "longText";
1343
- }
1577
+ private toSolidFieldType(column: DatasourceIntrospectionColumn) {
1578
+ const family = this.getDatasourceTypeFamily(column.dataType);
1579
+ const charLength = column.characterMaximumLength ?? null;
1344
1580
 
1345
- if (["varchar", "nvarchar", "char", "nchar", "citext"].includes(normalized)) {
1346
- if (charLength !== null && charLength > 255) {
1581
+ switch (family) {
1582
+ case "boolean":
1583
+ return "boolean";
1584
+ case "bigint":
1585
+ return "bigint";
1586
+ case "int":
1587
+ return "int";
1588
+ case "numeric":
1589
+ case "decimal":
1590
+ case "float":
1591
+ return "decimal";
1592
+ case "date":
1593
+ return "date";
1594
+ case "time":
1595
+ return "time";
1596
+ case "datetime":
1597
+ case "datetimeWithTimezone":
1598
+ return "datetime";
1599
+ case "json":
1600
+ case "jsonb":
1601
+ return "json";
1602
+ case "uuid":
1603
+ return "uuid";
1604
+ case "mediumText":
1605
+ case "longText":
1347
1606
  return "longText";
1348
- }
1607
+ case "shortText":
1608
+ case "unicodeShortText":
1609
+ if (charLength !== null && charLength > 255) {
1610
+ return "longText";
1611
+ }
1349
1612
 
1350
- return "shortText";
1613
+ return "shortText";
1614
+ default:
1615
+ return "shortText";
1351
1616
  }
1352
-
1353
- return "shortText";
1354
1617
  }
1355
1618
 
1356
1619
  private toOrmFieldType(column: DatasourceIntrospectionColumn, datasourceType: string) {
1357
- const normalized = column.dataType.toLowerCase();
1620
+ const family = this.getDatasourceTypeFamily(column.dataType);
1358
1621
  const normalizedDatasourceType = datasourceType.toLowerCase();
1622
+ const ormType = family
1623
+ ? ORM_TYPE_BY_DATASOURCE_FAMILY[normalizedDatasourceType]?.[family]
1624
+ : undefined;
1625
+
1626
+ if (ormType) {
1627
+ return ormType;
1628
+ }
1359
1629
 
1360
- if (normalizedDatasourceType === "mssql") {
1361
- if (["bit", "bool", "boolean"].includes(normalized)) return "bit";
1362
- if (["bigint"].includes(normalized)) return "bigint";
1363
- if (["int", "integer", "smallint", "tinyint"].includes(normalized)) return "int";
1364
- if (["numeric"].includes(normalized)) return "numeric";
1365
- if (["decimal", "float", "double", "real", "money", "smallmoney"].includes(normalized)) return "decimal";
1366
- if (["date"].includes(normalized)) return "date";
1367
- if (["time"].includes(normalized)) return "time";
1368
- if (["datetime2"].includes(normalized)) return "datetime2";
1369
- if (["datetime", "smalldatetime", "timestamp"].includes(normalized) || normalized.includes("date")) return "datetime";
1370
- if (["json", "jsonb", "simple-json", "simplejson"].includes(normalized) || normalized.includes("json")) return "simple-json";
1371
- if (["uniqueidentifier", "uuid"].includes(normalized)) return "uniqueidentifier";
1372
- if (["text", "ntext", "mediumtext", "longtext", "clob"].includes(normalized)) return "text";
1373
- if (["nvarchar", "nchar"].includes(normalized)) return "nvarchar";
1374
- return "varchar";
1375
- }
1376
-
1377
- if (normalizedDatasourceType === "mysql") {
1378
- if (["bigint"].includes(normalized)) return "int";
1379
- if (["int", "integer", "smallint", "tinyint"].includes(normalized)) return "int";
1380
- if (["decimal", "numeric"].includes(normalized)) return "decimal";
1381
- if (["float", "double", "real", "money", "smallmoney"].includes(normalized)) return "double";
1382
- if (["bit", "bool", "boolean"].includes(normalized)) return "boolean";
1383
- if (["date"].includes(normalized)) return "date";
1384
- if (["time"].includes(normalized)) return "time";
1385
- if (["datetime", "timestamp", "smalldatetime"].includes(normalized) || normalized.includes("date")) return "datetime";
1386
- if (["json", "jsonb", "simple-json", "simplejson"].includes(normalized) || normalized.includes("json")) return "json";
1387
- if (["mediumtext"].includes(normalized)) return "mediumtext";
1388
- if (["longtext", "text", "ntext", "clob"].includes(normalized)) return "longtext";
1389
- return "varchar";
1390
- }
1391
-
1392
- if (["bigint"].includes(normalized)) return "bigint";
1393
- if (["int", "integer", "smallint", "tinyint"].includes(normalized)) return "integer";
1394
- if (["decimal", "numeric", "float", "double", "real", "money", "smallmoney"].includes(normalized)) return "decimal";
1395
- if (["bit", "bool", "boolean"].includes(normalized)) return "boolean";
1396
- if (["date"].includes(normalized)) return "date";
1397
- if (["time"].includes(normalized)) return "time";
1398
- if (["timestamptz"].includes(normalized)) return "timestamptz";
1399
- if (["datetime", "datetime2", "timestamp", "smalldatetime"].includes(normalized) || normalized.includes("date")) return "timestamp";
1400
- if (["jsonb"].includes(normalized)) return "jsonb";
1401
- if (["json", "simple-json", "simplejson"].includes(normalized) || normalized.includes("json")) return "json";
1402
- if (["text", "ntext", "mediumtext", "longtext", "clob"].includes(normalized)) return "text";
1403
- return "varchar";
1630
+ return normalizedDatasourceType === "postgres" ? "varchar" : "varchar";
1404
1631
  }
1405
1632
 
1406
1633
  private toFieldName(columnName: string) {
@@ -1449,6 +1676,269 @@ export class DatasourceIntrospectionService {
1449
1676
  return `${datasourceName.toLowerCase()}::${tableName.toLowerCase()}`;
1450
1677
  }
1451
1678
 
1679
+ private async syncTypeormDatasourceFile(moduleName: string, datasource: DatasourceRecord, singularName: string) {
1680
+ const datasourceFilePath = path.resolve(process.cwd(), "src", `typeorm-${datasource.name}-datasource.ts`);
1681
+ const entityClassName = classify(singularName);
1682
+ const entityImportPath = `./${moduleName}/entities/${kebabCase(singularName)}.entity`;
1683
+ const migrationGlob = `join(__dirname, './${moduleName}/migrations/${datasource.name}/*.{ts,js}')`;
1684
+
1685
+ const existingContent = await fs.readFile(datasourceFilePath, "utf8").catch(() => null);
1686
+ if (!existingContent) {
1687
+ const rendered = this.renderTypeormDatasourceTemplate({
1688
+ datasource,
1689
+ entityClassName,
1690
+ entityImportPath,
1691
+ migrationGlob,
1692
+ });
1693
+ await fs.writeFile(datasourceFilePath, rendered, "utf8");
1694
+ return {
1695
+ filePath: datasourceFilePath,
1696
+ created: true,
1697
+ updated: false,
1698
+ entityRegistered: true,
1699
+ migrationGlobRegistered: true,
1700
+ };
1701
+ }
1702
+
1703
+ const project = new Project({
1704
+ skipAddingFilesFromTsConfig: true,
1705
+ manipulationSettings: {
1706
+ quoteKind: QuoteKind.Single,
1707
+ indentationText: IndentationText.FourSpaces,
1708
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
1709
+ },
1710
+ });
1711
+ const sourceFile = project.createSourceFile(datasourceFilePath, existingContent, { overwrite: true });
1712
+
1713
+ const entityImportDeclaration = sourceFile.getImportDeclarations().find((declaration) =>
1714
+ declaration.getModuleSpecifierValue() === entityImportPath,
1715
+ );
1716
+ if (entityImportDeclaration) {
1717
+ const alreadyImported = entityImportDeclaration.getNamedImports().some((namedImport) => namedImport.getName() === entityClassName);
1718
+ if (!alreadyImported) {
1719
+ entityImportDeclaration.addNamedImport(entityClassName);
1720
+ }
1721
+ } else {
1722
+ sourceFile.addImportDeclaration({
1723
+ moduleSpecifier: entityImportPath,
1724
+ namedImports: [entityClassName],
1725
+ });
1726
+ }
1727
+
1728
+ const entitiesArray = sourceFile
1729
+ .getVariableDeclarationOrThrow("entities")
1730
+ .getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression);
1731
+ const entityAlreadyRegistered = this.arrayLiteralContainsValue(entitiesArray, entityClassName);
1732
+ if (!entityAlreadyRegistered) {
1733
+ entitiesArray.addElement(entityClassName);
1734
+ }
1735
+
1736
+ const dataSourceOptions = this.getTypeormDatasourceOptions(sourceFile);
1737
+ const migrationsArray = this.getObjectPropertyArrayLiteral(dataSourceOptions, "migrations");
1738
+ const migrationAlreadyRegistered = this.arrayLiteralContainsValue(migrationsArray, migrationGlob);
1739
+ if (!migrationAlreadyRegistered) {
1740
+ migrationsArray.addElement(migrationGlob);
1741
+ }
1742
+
1743
+ sourceFile.organizeImports();
1744
+ sourceFile.formatText();
1745
+
1746
+ const nextContent = sourceFile.getFullText();
1747
+ const updated = nextContent !== existingContent;
1748
+ if (updated) {
1749
+ await fs.writeFile(datasourceFilePath, nextContent, "utf8");
1750
+ }
1751
+
1752
+ return {
1753
+ filePath: datasourceFilePath,
1754
+ created: false,
1755
+ updated,
1756
+ entityRegistered: true,
1757
+ migrationGlobRegistered: true,
1758
+ };
1759
+ }
1760
+
1761
+ private renderTypeormDatasourceTemplate(input: {
1762
+ datasource: DatasourceRecord;
1763
+ entityClassName: string;
1764
+ entityImportPath: string;
1765
+ migrationGlob: string;
1766
+ }) {
1767
+ const { datasource, entityClassName, entityImportPath, migrationGlob } = input;
1768
+ const envPrefix = datasource.envPrefix?.toUpperCase?.() || "DEFAULT";
1769
+ const portFallback = datasource.type === "postgres" ? 5432 : datasource.type === "mysql" ? 3306 : 1433;
1770
+ const project = new Project({
1771
+ skipAddingFilesFromTsConfig: true,
1772
+ manipulationSettings: {
1773
+ quoteKind: QuoteKind.Single,
1774
+ indentationText: IndentationText.FourSpaces,
1775
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
1776
+ },
1777
+ });
1778
+ const sourceFile = project.createSourceFile("typeorm-template.ts", "", { overwrite: true });
1779
+
1780
+ sourceFile.addImportDeclaration({ moduleSpecifier: "reflect-metadata" });
1781
+ sourceFile.addImportDeclaration({ moduleSpecifier: "path", namedImports: ["join"] });
1782
+ sourceFile.addImportDeclaration({
1783
+ moduleSpecifier: "dotenv",
1784
+ namedImports: [{ name: "config", alias: "dotenvConfig" }],
1785
+ });
1786
+ sourceFile.addStatements("dotenvConfig({ path: join(__dirname, '../.env') });");
1787
+ sourceFile.addStatements("");
1788
+
1789
+ sourceFile.addImportDeclaration({ moduleSpecifier: "typeorm", namedImports: ["DataSource"] });
1790
+ sourceFile.addImportDeclaration({
1791
+ moduleSpecifier: "@solidxai/core",
1792
+ namespaceImport: "SolidCoreModuleExports",
1793
+ });
1794
+ sourceFile.addImportDeclaration({
1795
+ moduleSpecifier: "@solidxai/core",
1796
+ namedImports: ["getDynamicModuleNames", "parseBooleanEnv"],
1797
+ });
1798
+ sourceFile.addImportDeclaration({
1799
+ moduleSpecifier: "typeorm",
1800
+ namedImports: ["getMetadataArgsStorage"],
1801
+ });
1802
+ sourceFile.addImportDeclaration({
1803
+ moduleSpecifier: "typeorm-naming-strategies",
1804
+ namedImports: ["SnakeNamingStrategy"],
1805
+ });
1806
+ sourceFile.addImportDeclaration({
1807
+ moduleSpecifier: entityImportPath,
1808
+ namedImports: [entityClassName],
1809
+ });
1810
+
1811
+ sourceFile.addFunction({
1812
+ name: "getEntitiesFromExports",
1813
+ parameters: [{ name: "exports", type: "Record<string, any>" }],
1814
+ statements: (writer) => {
1815
+ writer.writeLine("const metadataStorage = getMetadataArgsStorage();");
1816
+ writer.writeLine("return Object.values(exports).filter((item) =>");
1817
+ writer.indent(() => {
1818
+ writer.writeLine("metadataStorage.tables.some((table) => table.target === item),");
1819
+ });
1820
+ writer.write(");");
1821
+ },
1822
+ });
1823
+
1824
+ sourceFile.addVariableStatement({
1825
+ declarationKind: VariableDeclarationKind.Const,
1826
+ declarations: [
1827
+ {
1828
+ name: "coreEntities",
1829
+ initializer: "getEntitiesFromExports(SolidCoreModuleExports)",
1830
+ },
1831
+ ],
1832
+ });
1833
+ sourceFile.addVariableStatement({
1834
+ declarationKind: VariableDeclarationKind.Const,
1835
+ declarations: [
1836
+ {
1837
+ name: "dynamicModules",
1838
+ initializer: "getDynamicModuleNames()",
1839
+ },
1840
+ ],
1841
+ });
1842
+ sourceFile.addVariableStatement({
1843
+ declarationKind: VariableDeclarationKind.Const,
1844
+ declarations: [
1845
+ {
1846
+ name: "entities",
1847
+ initializer: `[\n ...coreEntities,\n ${entityClassName},\n]`,
1848
+ },
1849
+ ],
1850
+ });
1851
+ sourceFile.addVariableStatement({
1852
+ declarationKind: VariableDeclarationKind.Const,
1853
+ declarations: [
1854
+ {
1855
+ name: "logging",
1856
+ initializer: `parseBooleanEnv('${envPrefix}_DATABASE_LOGGING')`,
1857
+ },
1858
+ ],
1859
+ });
1860
+
1861
+ sourceFile.addStatements("// IMPORTANT: synchronize must be false when using migrations");
1862
+ sourceFile.addVariableStatement({
1863
+ declarationKind: VariableDeclarationKind.Const,
1864
+ isExported: true,
1865
+ declarations: [
1866
+ {
1867
+ name: "DefaultDataSource",
1868
+ initializer: (writer) => {
1869
+ writer.write("new DataSource(");
1870
+ writer.inlineBlock(() => {
1871
+ writer.writeLine(`type: '${datasource.type}',`);
1872
+ writer.blankLine();
1873
+ writer.writeLine(`host: process.env.${envPrefix}_DATABASE_HOST,`);
1874
+ writer.writeLine(`port: +(process.env.${envPrefix}_DATABASE_PORT || ${portFallback}),`);
1875
+ writer.writeLine(`username: process.env.${envPrefix}_DATABASE_USER,`);
1876
+ writer.writeLine(`password: process.env.${envPrefix}_DATABASE_PASSWORD,`);
1877
+ writer.writeLine(`database: process.env.${envPrefix}_DATABASE_NAME,`);
1878
+ writer.blankLine();
1879
+ writer.writeLine("entities,");
1880
+ writer.writeLine("migrations: [");
1881
+ writer.indent(() => {
1882
+ writer.writeLine(`${migrationGlob},`);
1883
+ });
1884
+ writer.writeLine("],");
1885
+ writer.writeLine("synchronize: false,");
1886
+ writer.writeLine("logging,");
1887
+ writer.writeLine("namingStrategy: new SnakeNamingStrategy(),");
1888
+ if (datasource.type === "mssql") {
1889
+ writer.writeLine("options: {");
1890
+ writer.indent(() => {
1891
+ writer.writeLine("encrypt: false,");
1892
+ writer.writeLine("trustServerCertificate: true,");
1893
+ });
1894
+ writer.writeLine("},");
1895
+ }
1896
+ });
1897
+ writer.write(")");
1898
+ },
1899
+ },
1900
+ ],
1901
+ });
1902
+
1903
+ sourceFile.organizeImports();
1904
+ sourceFile.formatText();
1905
+ return sourceFile.getFullText();
1906
+ }
1907
+
1908
+ private arrayLiteralContainsValue(arrayLiteral: ArrayLiteralExpression, rawValue: string) {
1909
+ const normalizedTarget = rawValue.replace(/\s+/g, "");
1910
+ return arrayLiteral.getElements().some((element) => element.getText().replace(/\s+/g, "") === normalizedTarget);
1911
+ }
1912
+
1913
+ private getTypeormDatasourceOptions(sourceFile: import("ts-morph").SourceFile) {
1914
+ const defaultDataSourceDeclaration = sourceFile.getVariableDeclarationOrThrow("DefaultDataSource");
1915
+ const initializer = defaultDataSourceDeclaration.getInitializer();
1916
+ if (!initializer || !Node.isNewExpression(initializer)) {
1917
+ throw new BadRequestException("Unable to locate DefaultDataSource initializer in datasource file.");
1918
+ }
1919
+
1920
+ const options = initializer.getArguments()[0];
1921
+ if (!options || !Node.isObjectLiteralExpression(options)) {
1922
+ throw new BadRequestException("Unable to locate DefaultDataSource options object in datasource file.");
1923
+ }
1924
+
1925
+ return options;
1926
+ }
1927
+
1928
+ private getObjectPropertyArrayLiteral(objectLiteral: ObjectLiteralExpression, propertyName: string) {
1929
+ const property = objectLiteral.getProperty(propertyName);
1930
+ if (!property || !Node.isPropertyAssignment(property)) {
1931
+ throw new BadRequestException(`Unable to locate "${propertyName}" property in datasource file.`);
1932
+ }
1933
+
1934
+ const initializer = property.getInitializer();
1935
+ if (!initializer || !Node.isArrayLiteralExpression(initializer)) {
1936
+ throw new BadRequestException(`Unable to locate "${propertyName}" array in datasource file.`);
1937
+ }
1938
+
1939
+ return initializer;
1940
+ }
1941
+
1452
1942
  private buildSynchronizeBlockedMessage(datasource: DatasourceRecord) {
1453
1943
  return `Datasource "${datasource.displayName}" has synchronize enabled. Set ${datasource.envPrefix}_DATABASE_SYNCHRONIZE=false before starting legacy table introspection.`;
1454
1944
  }