@solidxai/core 0.1.11-beta.2 → 0.1.11-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/migrate-removed-fields.command.d.ts +19 -0
- package/dist/commands/migrate-removed-fields.command.d.ts.map +1 -0
- package/dist/commands/migrate-removed-fields.command.js +77 -0
- package/dist/commands/migrate-removed-fields.command.js.map +1 -0
- package/dist/controllers/datasource-introspection.controller.d.ts +38 -1
- package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -1
- package/dist/controllers/datasource-introspection.controller.js +38 -0
- package/dist/controllers/datasource-introspection.controller.js.map +1 -1
- package/dist/dtos/datasource-introspection-mapping.dto.d.ts +5 -0
- package/dist/dtos/datasource-introspection-mapping.dto.d.ts.map +1 -1
- package/dist/dtos/datasource-introspection-mapping.dto.js +24 -2
- package/dist/dtos/datasource-introspection-mapping.dto.js.map +1 -1
- package/dist/entities/{legacy-common-with-id.entity.d.ts → legacy-common-with-generated-id.entity.d.ts} +2 -2
- package/dist/entities/legacy-common-with-generated-id.entity.d.ts.map +1 -0
- package/dist/entities/{legacy-common-with-id.entity.js → legacy-common-with-generated-id.entity.js} +4 -4
- package/dist/entities/legacy-common-with-generated-id.entity.js.map +1 -0
- package/dist/entities/{legacy-common.entity.d.ts → legacy-common.entity-with-existing-id.entity.d.ts} +1 -1
- package/dist/entities/legacy-common.entity-with-existing-id.entity.d.ts.map +1 -0
- package/dist/entities/{legacy-common.entity.js → legacy-common.entity-with-existing-id.entity.js} +1 -1
- package/dist/entities/legacy-common.entity-with-existing-id.entity.js.map +1 -0
- package/dist/helpers/command.service.d.ts +2 -0
- package/dist/helpers/command.service.d.ts.map +1 -1
- package/dist/helpers/command.service.js +6 -2
- package/dist/helpers/command.service.js.map +1 -1
- package/dist/helpers/model-metadata-helper.service.js +3 -3
- package/dist/helpers/model-metadata-helper.service.js.map +1 -1
- package/dist/helpers/nodemon-heartbeat.d.ts +5 -0
- package/dist/helpers/nodemon-heartbeat.d.ts.map +1 -0
- package/dist/helpers/nodemon-heartbeat.js +67 -0
- package/dist/helpers/nodemon-heartbeat.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/interfaces/datasource-introspection-provider.interface.d.ts +1 -0
- package/dist/interfaces/datasource-introspection-provider.interface.d.ts.map +1 -1
- package/dist/interfaces/datasource-introspection-provider.interface.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts +7 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +101 -1
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +121 -34
- package/dist/services/api-key.service.d.ts.map +1 -1
- package/dist/services/api-key.service.js.map +1 -1
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.d.ts +4 -0
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.d.ts.map +1 -1
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.js +18 -1
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.js.map +1 -1
- package/dist/services/crud.service.d.ts +1 -0
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +27 -0
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js +12 -0
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js.map +1 -1
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js +2 -0
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js.map +1 -1
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js +28 -15
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js.map +1 -1
- package/dist/services/datasource-introspection.service.d.ts +53 -2
- package/dist/services/datasource-introspection.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection.service.js +559 -174
- package/dist/services/datasource-introspection.service.js.map +1 -1
- package/dist/services/import-transaction.service.d.ts.map +1 -1
- package/dist/services/import-transaction.service.js +3 -0
- package/dist/services/import-transaction.service.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +10 -10
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts +1 -0
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +28 -0
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/module-metadata.service.d.ts.map +1 -1
- package/dist/services/module-metadata.service.js +27 -0
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/services/removed-field-migration.service.d.ts +30 -0
- package/dist/services/removed-field-migration.service.d.ts.map +1 -0
- package/dist/services/removed-field-migration.service.js +319 -0
- package/dist/services/removed-field-migration.service.js.map +1 -0
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +4 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts +4 -1
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +54 -6
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist-tests/api/authenticate.spec.js +119 -0
- package/dist-tests/api/authenticate.spec.js.map +1 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +97 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +1 -0
- package/dist-tests/api/ping.spec.js +21 -0
- package/dist-tests/api/ping.spec.js.map +1 -0
- package/dist-tests/helpers/auth.js +41 -0
- package/dist-tests/helpers/auth.js.map +1 -0
- package/dist-tests/helpers/env.js +11 -0
- package/dist-tests/helpers/env.js.map +1 -0
- package/docs/agent-hub-grooming.md +301 -0
- package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +438 -0
- package/docs/dashboards/dashboard-curl-smoke-tests.txt +146 -0
- package/docs/dashboards/delete-legacy-dashboard-metadata.sql +172 -0
- package/docs/datasource-introspection-ddl-analysis.md +326 -0
- package/docs/datasource-introspection-implementation-plan.md +306 -0
- package/docs/grouping-enhancements.md +89 -0
- package/docs/java-spring/README.md +3 -0
- package/docs/java-spring/solid-core-module-deep-dive-report.md +1317 -0
- package/docs/module-package-import-handoff.md +691 -0
- package/docs/seed-changes.md +65 -0
- package/docs/test-data-workflow.md +200 -0
- package/docs/type-declaration-import-issue.md +24 -0
- package/package.json +1 -1
- package/src/commands/migrate-removed-fields.command.ts +81 -0
- package/src/controllers/datasource-introspection.controller.ts +27 -1
- package/src/dtos/datasource-introspection-mapping.dto.ts +15 -0
- package/src/entities/{legacy-common-with-id.entity.ts → legacy-common-with-generated-id.entity.ts} +1 -1
- package/src/helpers/command.service.ts +10 -2
- package/src/helpers/model-metadata-helper.service.ts +1 -1
- package/src/helpers/nodemon-heartbeat.ts +59 -0
- package/src/index.ts +3 -2
- package/src/interfaces/datasource-introspection-provider.interface.ts +1 -0
- package/src/seeders/module-metadata-seeder.service.ts +118 -0
- package/src/seeders/seed-data/solid-core-metadata.json +121 -34
- package/src/services/api-key.service.ts +36 -36
- package/src/services/computed-fields/entity/concat-entity-computed-field-provider.service.ts +15 -1
- package/src/services/crud.service.ts +36 -11
- package/src/services/datasource-introspection/mssql-datasource-introspection-provider.service.ts +12 -0
- package/src/services/datasource-introspection/mysql-datasource-introspection-provider.service.ts +2 -0
- package/src/services/datasource-introspection/postgres-datasource-introspection-provider.service.ts +28 -15
- package/src/services/datasource-introspection.service.ts +653 -150
- package/src/services/import-transaction.service.ts +5 -0
- package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +2 -2
- package/src/services/model-metadata.service.ts +45 -1
- package/src/services/module-metadata.service.ts +35 -0
- package/src/services/removed-field-migration.service.ts +334 -0
- package/src/solid-core.module.ts +4 -0
- package/src/subscribers/audit.subscriber.ts +70 -5
- package/.claude/settings.local.json +0 -15
- package/CLAUDE.md +0 -26
- package/dist/entities/legacy-common-with-id.entity.d.ts.map +0 -1
- package/dist/entities/legacy-common-with-id.entity.js.map +0 -1
- package/dist/entities/legacy-common.entity.d.ts.map +0 -1
- package/dist/entities/legacy-common.entity.js.map +0 -1
- package/src/services/1.js +0 -6
- /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", "double precision", "real", "float4", "float8"],
|
|
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
|
|
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:
|
|
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,
|
|
@@ -608,6 +818,14 @@ export class DatasourceIntrospectionService {
|
|
|
608
818
|
return rest;
|
|
609
819
|
}
|
|
610
820
|
|
|
821
|
+
private sanitizeConfiguredFieldPayload(fieldConfig: Record<string, any> | undefined) {
|
|
822
|
+
if (!fieldConfig || typeof fieldConfig !== "object" || Array.isArray(fieldConfig)) {
|
|
823
|
+
return {};
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return this.sanitizeExistingFieldPayload(fieldConfig);
|
|
827
|
+
}
|
|
828
|
+
|
|
611
829
|
private normalizeReviewedMetadataModel(
|
|
612
830
|
reviewedModel: Record<string, any> | undefined,
|
|
613
831
|
mappingDto: DatasourceIntrospectionMappingDto,
|
|
@@ -762,34 +980,39 @@ export class DatasourceIntrospectionService {
|
|
|
762
980
|
existingField?: any,
|
|
763
981
|
) {
|
|
764
982
|
const preserved = existingField ? this.sanitizeExistingFieldPayload(existingField) : {};
|
|
983
|
+
const configured = this.sanitizeConfiguredFieldPayload(column.fieldConfig);
|
|
984
|
+
const merged = {
|
|
985
|
+
...preserved,
|
|
986
|
+
...configured,
|
|
987
|
+
};
|
|
765
988
|
const maxLength =
|
|
766
989
|
column.characterMaximumLength && ["shortText", "longText"].includes(column.solidFieldType)
|
|
767
990
|
? column.characterMaximumLength
|
|
768
|
-
:
|
|
991
|
+
: merged.max;
|
|
769
992
|
|
|
770
993
|
return {
|
|
771
|
-
...
|
|
994
|
+
...merged,
|
|
772
995
|
...(column.fieldId ? { id: column.fieldId } : {}),
|
|
773
996
|
name: column.fieldName,
|
|
774
|
-
displayName:
|
|
775
|
-
description:
|
|
997
|
+
displayName: column.displayName?.trim() || merged.displayName || startCase(column.fieldName),
|
|
998
|
+
description: merged.description ?? "",
|
|
776
999
|
type: column.solidFieldType,
|
|
777
1000
|
ormType: column.ormType,
|
|
778
|
-
length:
|
|
779
|
-
defaultValue:
|
|
780
|
-
required:
|
|
781
|
-
unique:
|
|
782
|
-
encrypt:
|
|
783
|
-
index:
|
|
1001
|
+
length: merged.length ?? column.characterMaximumLength ?? undefined,
|
|
1002
|
+
defaultValue: merged.defaultValue ?? column.defaultValue ?? undefined,
|
|
1003
|
+
required: merged.required ?? (!column.nullable || column.isPrimaryKey),
|
|
1004
|
+
unique: merged.unique ?? false,
|
|
1005
|
+
encrypt: merged.encrypt ?? false,
|
|
1006
|
+
index: merged.index ?? false,
|
|
784
1007
|
max: maxLength ?? undefined,
|
|
785
|
-
min:
|
|
786
|
-
private:
|
|
787
|
-
isSystem:
|
|
1008
|
+
min: merged.min ?? undefined,
|
|
1009
|
+
private: merged.private ?? false,
|
|
1010
|
+
isSystem: merged.isSystem ?? false,
|
|
788
1011
|
isMarkedForRemoval: false,
|
|
789
1012
|
columnName: column.columnName,
|
|
790
1013
|
isUserKey: mappingDto.userKeyField === column.fieldName,
|
|
791
|
-
enableAuditTracking:
|
|
792
|
-
isMultiSelect:
|
|
1014
|
+
enableAuditTracking: merged.enableAuditTracking ?? mappingDto.enableAuditTracking,
|
|
1015
|
+
isMultiSelect: merged.isMultiSelect ?? false,
|
|
793
1016
|
isPrimaryKey: column.isPrimaryKey,
|
|
794
1017
|
};
|
|
795
1018
|
}
|
|
@@ -891,11 +1114,8 @@ export class DatasourceIntrospectionService {
|
|
|
891
1114
|
|
|
892
1115
|
const upColumnStatements = missingSystemColumns.map((definition) => {
|
|
893
1116
|
const columnLiteral = this.renderTableColumnLiteral(definition);
|
|
894
|
-
return
|
|
895
|
-
`
|
|
896
|
-
` await queryRunner.addColumn(tableName, new TableColumn(${columnLiteral}));`,
|
|
897
|
-
" }",
|
|
898
|
-
].join("\n");
|
|
1117
|
+
return ` if (!(await queryRunner.hasColumn(tableName, ${JSON.stringify(definition.columnName)})))\n`
|
|
1118
|
+
+ ` await queryRunner.addColumn(tableName, new TableColumn(${columnLiteral}));`;
|
|
899
1119
|
}).join("\n\n");
|
|
900
1120
|
|
|
901
1121
|
const postUpStatements: string[] = [];
|
|
@@ -904,12 +1124,11 @@ export class DatasourceIntrospectionService {
|
|
|
904
1124
|
[
|
|
905
1125
|
" {",
|
|
906
1126
|
" const table = await queryRunner.getTable(tableName);",
|
|
907
|
-
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(deletedAtIndexName)}))
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
" }",
|
|
1127
|
+
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(deletedAtIndexName)}))`,
|
|
1128
|
+
` await queryRunner.createIndex(tableName, new TableIndex(${this.renderTableIndexLiteral({
|
|
1129
|
+
name: deletedAtIndexName,
|
|
1130
|
+
columnNames: ["ss_deleted_at"],
|
|
1131
|
+
})}));`,
|
|
913
1132
|
" }",
|
|
914
1133
|
].join("\n"),
|
|
915
1134
|
);
|
|
@@ -919,13 +1138,12 @@ export class DatasourceIntrospectionService {
|
|
|
919
1138
|
[
|
|
920
1139
|
" {",
|
|
921
1140
|
" const table = await queryRunner.getTable(tableName);",
|
|
922
|
-
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(generatedIdUniqueName)}))
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
" }",
|
|
1141
|
+
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(generatedIdUniqueName)}))`,
|
|
1142
|
+
` await queryRunner.createIndex(tableName, new TableIndex(${this.renderTableIndexLiteral({
|
|
1143
|
+
name: generatedIdUniqueName,
|
|
1144
|
+
columnNames: ["ss_id"],
|
|
1145
|
+
isUnique: true,
|
|
1146
|
+
})}));`,
|
|
929
1147
|
" }",
|
|
930
1148
|
].join("\n"),
|
|
931
1149
|
);
|
|
@@ -938,9 +1156,8 @@ export class DatasourceIntrospectionService {
|
|
|
938
1156
|
" {",
|
|
939
1157
|
" const table = await queryRunner.getTable(tableName);",
|
|
940
1158
|
` const index = table?.indices.find((entry) => entry.name === ${JSON.stringify(generatedIdUniqueName)});`,
|
|
941
|
-
" if (index)
|
|
1159
|
+
" if (index)",
|
|
942
1160
|
" await queryRunner.dropIndex(tableName, index);",
|
|
943
|
-
" }",
|
|
944
1161
|
" }",
|
|
945
1162
|
].join("\n"),
|
|
946
1163
|
);
|
|
@@ -951,9 +1168,8 @@ export class DatasourceIntrospectionService {
|
|
|
951
1168
|
" {",
|
|
952
1169
|
" const table = await queryRunner.getTable(tableName);",
|
|
953
1170
|
` const index = table?.indices.find((entry) => entry.name === ${JSON.stringify(deletedAtIndexName)});`,
|
|
954
|
-
" if (index)
|
|
1171
|
+
" if (index)",
|
|
955
1172
|
" await queryRunner.dropIndex(tableName, index);",
|
|
956
|
-
" }",
|
|
957
1173
|
" }",
|
|
958
1174
|
].join("\n"),
|
|
959
1175
|
);
|
|
@@ -962,11 +1178,8 @@ export class DatasourceIntrospectionService {
|
|
|
962
1178
|
...missingSystemColumns
|
|
963
1179
|
.slice()
|
|
964
1180
|
.reverse()
|
|
965
|
-
.map((definition) =>
|
|
966
|
-
`
|
|
967
|
-
` await queryRunner.dropColumn(tableName, ${JSON.stringify(definition.columnName)});`,
|
|
968
|
-
" }",
|
|
969
|
-
].join("\n")),
|
|
1181
|
+
.map((definition) => ` if (await queryRunner.hasColumn(tableName, ${JSON.stringify(definition.columnName)}))\n`
|
|
1182
|
+
+ ` await queryRunner.dropColumn(tableName, ${JSON.stringify(definition.columnName)});`),
|
|
970
1183
|
);
|
|
971
1184
|
|
|
972
1185
|
return [
|
|
@@ -992,29 +1205,41 @@ export class DatasourceIntrospectionService {
|
|
|
992
1205
|
|
|
993
1206
|
private renderTableColumnLiteral(definition: SystemColumnDefinition) {
|
|
994
1207
|
const properties: string[] = [
|
|
995
|
-
`
|
|
996
|
-
`
|
|
1208
|
+
`name: ${JSON.stringify(definition.columnName)}`,
|
|
1209
|
+
`type: ${JSON.stringify(definition.type)}`,
|
|
997
1210
|
];
|
|
998
1211
|
|
|
999
1212
|
if (definition.length) {
|
|
1000
|
-
properties.push(`
|
|
1213
|
+
properties.push(`length: ${JSON.stringify(definition.length)}`);
|
|
1001
1214
|
}
|
|
1002
1215
|
if (typeof definition.isNullable === "boolean") {
|
|
1003
|
-
properties.push(`
|
|
1216
|
+
properties.push(`isNullable: ${definition.isNullable}`);
|
|
1004
1217
|
}
|
|
1005
1218
|
if (definition.defaultExpression) {
|
|
1006
|
-
properties.push(`
|
|
1219
|
+
properties.push(`default: ${JSON.stringify(definition.defaultExpression)}`);
|
|
1007
1220
|
}
|
|
1008
1221
|
if (definition.isGenerated) {
|
|
1009
|
-
properties.push("
|
|
1010
|
-
properties.push("
|
|
1222
|
+
properties.push("isGenerated: true");
|
|
1223
|
+
properties.push("generationStrategy: \"increment\"");
|
|
1011
1224
|
}
|
|
1012
1225
|
if (definition.isUnique) {
|
|
1013
|
-
properties.push("
|
|
1226
|
+
properties.push("isUnique: true");
|
|
1014
1227
|
}
|
|
1015
1228
|
|
|
1016
|
-
properties.
|
|
1017
|
-
|
|
1229
|
+
return `{ ${properties.join(", ")}, }`;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
private renderTableIndexLiteral(definition: { name: string; columnNames: string[]; isUnique?: boolean }) {
|
|
1233
|
+
const properties: string[] = [
|
|
1234
|
+
`name: ${JSON.stringify(definition.name)}`,
|
|
1235
|
+
`columnNames: ${JSON.stringify(definition.columnNames)}`,
|
|
1236
|
+
];
|
|
1237
|
+
|
|
1238
|
+
if (definition.isUnique) {
|
|
1239
|
+
properties.push("isUnique: true");
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
return `{ ${properties.join(", ")}, }`;
|
|
1018
1243
|
}
|
|
1019
1244
|
|
|
1020
1245
|
private buildMigrationObjectName(prefix: string, tableName: string, columnName: string) {
|
|
@@ -1050,13 +1275,15 @@ export class DatasourceIntrospectionService {
|
|
|
1050
1275
|
private async buildTableInventory(datasource: DatasourceRecord) {
|
|
1051
1276
|
const provider = this.getProvider(datasource.type);
|
|
1052
1277
|
const runtimeDataSource = this.resolveDataSourceByName(datasource.name);
|
|
1053
|
-
const [
|
|
1278
|
+
const [rawTables, rawColumns, models] = await Promise.all([
|
|
1054
1279
|
provider.listTables(runtimeDataSource),
|
|
1055
1280
|
provider.listColumns(runtimeDataSource),
|
|
1056
1281
|
this.modelMetadataRepository.find({ relations: { module: true } as any }),
|
|
1057
1282
|
]);
|
|
1283
|
+
const tables = rawTables.filter((table) => !this.isSolidxInternalTableName(table.tableName));
|
|
1284
|
+
const columns = rawColumns.filter((column) => !this.isSolidxInternalTableName(column.tableName));
|
|
1058
1285
|
|
|
1059
|
-
const
|
|
1286
|
+
const modelLookup = this.buildModelLookup(models);
|
|
1060
1287
|
const columnsByKey = this.groupColumnsByTable(columns);
|
|
1061
1288
|
const tableKeys = new Set<string>();
|
|
1062
1289
|
|
|
@@ -1080,7 +1307,7 @@ export class DatasourceIntrospectionService {
|
|
|
1080
1307
|
|
|
1081
1308
|
const suggestedLegacyTableType = this.detectLegacyTableType(datasource.type, tableColumns);
|
|
1082
1309
|
const suggestedIdColumn = this.getSuggestedIdColumn(datasource.type, tableColumns, suggestedLegacyTableType);
|
|
1083
|
-
const mappedModel =
|
|
1310
|
+
const mappedModel = this.resolveMappedModel(modelLookup, datasource.name, tableName);
|
|
1084
1311
|
const physicalColumns = tableColumns.filter((column) => !this.isHandledBySuperclass(column.columnName, suggestedLegacyTableType));
|
|
1085
1312
|
|
|
1086
1313
|
return {
|
|
@@ -1119,19 +1346,52 @@ export class DatasourceIntrospectionService {
|
|
|
1119
1346
|
};
|
|
1120
1347
|
}
|
|
1121
1348
|
|
|
1122
|
-
private
|
|
1123
|
-
const
|
|
1349
|
+
private buildModelLookup(models: ModelMetadata[]) {
|
|
1350
|
+
const byDatasourceAndTable = new Map<string, ModelMetadata>();
|
|
1351
|
+
const byTableName = new Map<string, ModelMetadata[]>();
|
|
1124
1352
|
|
|
1125
1353
|
for (const model of models) {
|
|
1126
1354
|
if (!model.tableName) {
|
|
1127
1355
|
continue;
|
|
1128
1356
|
}
|
|
1129
1357
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1358
|
+
byDatasourceAndTable.set(this.toModelMapKey(model.dataSource, model.tableName), model);
|
|
1359
|
+
|
|
1360
|
+
const normalizedTableName = model.tableName.trim().toLowerCase();
|
|
1361
|
+
const bucket = byTableName.get(normalizedTableName);
|
|
1362
|
+
if (bucket) {
|
|
1363
|
+
bucket.push(model);
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
byTableName.set(normalizedTableName, [model]);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
return {
|
|
1371
|
+
byDatasourceAndTable,
|
|
1372
|
+
byTableName,
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
private resolveMappedModel(
|
|
1377
|
+
lookup: {
|
|
1378
|
+
byDatasourceAndTable: Map<string, ModelMetadata>;
|
|
1379
|
+
byTableName: Map<string, ModelMetadata[]>;
|
|
1380
|
+
},
|
|
1381
|
+
datasourceName: string,
|
|
1382
|
+
tableName: string,
|
|
1383
|
+
) {
|
|
1384
|
+
const exactMatch = lookup.byDatasourceAndTable.get(this.toModelMapKey(datasourceName, tableName));
|
|
1385
|
+
if (exactMatch) {
|
|
1386
|
+
return exactMatch;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
const fallbackMatches = lookup.byTableName.get(tableName.trim().toLowerCase()) ?? [];
|
|
1390
|
+
if (fallbackMatches.length === 1) {
|
|
1391
|
+
return fallbackMatches[0];
|
|
1132
1392
|
}
|
|
1133
1393
|
|
|
1134
|
-
return
|
|
1394
|
+
return fallbackMatches[0] ?? null;
|
|
1135
1395
|
}
|
|
1136
1396
|
|
|
1137
1397
|
private groupColumnsByTable(columns: DatasourceIntrospectionColumn[]) {
|
|
@@ -1151,6 +1411,10 @@ export class DatasourceIntrospectionService {
|
|
|
1151
1411
|
return grouped;
|
|
1152
1412
|
}
|
|
1153
1413
|
|
|
1414
|
+
private isSolidxInternalTableName(tableName: string | null | undefined) {
|
|
1415
|
+
return typeof tableName === "string" && tableName.toLowerCase().startsWith("ss_");
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1154
1418
|
private detectLegacyTableType(datasourceType: string, columns: DatasourceIntrospectionColumn[]): LegacyTableType {
|
|
1155
1419
|
const normalizedDatasourceType = datasourceType.toLowerCase();
|
|
1156
1420
|
const hasExistingGeneratedId = columns.some((column) => column.isAutoGenerated);
|
|
@@ -1239,13 +1503,17 @@ export class DatasourceIntrospectionService {
|
|
|
1239
1503
|
datasourceType: string,
|
|
1240
1504
|
): TableDetailColumnRecord {
|
|
1241
1505
|
const superclassFieldName = this.getSuperclassFieldName(column.columnName, legacyTableType);
|
|
1506
|
+
const normalizedColumnName = column.columnName.toLowerCase();
|
|
1242
1507
|
const fieldName = legacyTableType === LegacyTableType.EXISTING_ID && column.isAutoGenerated
|
|
1243
1508
|
? "id"
|
|
1244
|
-
:
|
|
1509
|
+
: legacyTableType === LegacyTableType.GENERATED_ID && normalizedColumnName === "id"
|
|
1510
|
+
? "legacyId"
|
|
1511
|
+
: this.toFieldName(column.columnName);
|
|
1245
1512
|
|
|
1246
1513
|
return {
|
|
1247
1514
|
columnName: column.columnName,
|
|
1248
1515
|
fieldName,
|
|
1516
|
+
displayName: startCase(fieldName),
|
|
1249
1517
|
dataType: column.dataType,
|
|
1250
1518
|
solidFieldType: this.toSolidFieldType(column),
|
|
1251
1519
|
ormType: this.toOrmFieldType(column, datasourceType),
|
|
@@ -1256,6 +1524,7 @@ export class DatasourceIntrospectionService {
|
|
|
1256
1524
|
numericScale: column.numericScale,
|
|
1257
1525
|
ordinalPosition: column.ordinalPosition,
|
|
1258
1526
|
isAutoGenerated: column.isAutoGenerated,
|
|
1527
|
+
isPrimaryKey: column.isPrimaryKey,
|
|
1259
1528
|
handledBySuperclass: Boolean(superclassFieldName),
|
|
1260
1529
|
superclassFieldName,
|
|
1261
1530
|
};
|
|
@@ -1298,109 +1567,80 @@ export class DatasourceIntrospectionService {
|
|
|
1298
1567
|
return "LegacyCommonEntityWithExistingId";
|
|
1299
1568
|
}
|
|
1300
1569
|
|
|
1301
|
-
private
|
|
1302
|
-
const normalized =
|
|
1303
|
-
const charLength = column.characterMaximumLength ?? null;
|
|
1570
|
+
private getDatasourceTypeFamily(dataType: string): DatasourceTypeFamily | null {
|
|
1571
|
+
const normalized = dataType.toLowerCase();
|
|
1304
1572
|
|
|
1305
|
-
|
|
1306
|
-
|
|
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
|
-
}
|
|
1320
|
-
|
|
1321
|
-
if (["date"].includes(normalized)) {
|
|
1322
|
-
return "date";
|
|
1573
|
+
for (const [family, aliases] of Object.entries(DATASOURCE_TYPE_FAMILY_ALIASES) as Array<[DatasourceTypeFamily, string[]]>) {
|
|
1574
|
+
if (aliases.includes(normalized)) {
|
|
1575
|
+
return family;
|
|
1576
|
+
}
|
|
1323
1577
|
}
|
|
1324
1578
|
|
|
1325
|
-
if (
|
|
1326
|
-
return "
|
|
1579
|
+
if (normalized.includes("json")) {
|
|
1580
|
+
return "json";
|
|
1327
1581
|
}
|
|
1328
1582
|
|
|
1329
|
-
if (
|
|
1583
|
+
if (normalized.includes("date")) {
|
|
1330
1584
|
return "datetime";
|
|
1331
1585
|
}
|
|
1332
1586
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
if (["uniqueidentifier", "uuid"].includes(normalized)) {
|
|
1338
|
-
return "uuid";
|
|
1339
|
-
}
|
|
1587
|
+
return null;
|
|
1588
|
+
}
|
|
1340
1589
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1590
|
+
private toSolidFieldType(column: DatasourceIntrospectionColumn) {
|
|
1591
|
+
const family = this.getDatasourceTypeFamily(column.dataType);
|
|
1592
|
+
const charLength = column.characterMaximumLength ?? null;
|
|
1344
1593
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1594
|
+
switch (family) {
|
|
1595
|
+
case "boolean":
|
|
1596
|
+
return "boolean";
|
|
1597
|
+
case "bigint":
|
|
1598
|
+
return "bigint";
|
|
1599
|
+
case "int":
|
|
1600
|
+
return "int";
|
|
1601
|
+
case "numeric":
|
|
1602
|
+
case "decimal":
|
|
1603
|
+
case "float":
|
|
1604
|
+
return "decimal";
|
|
1605
|
+
case "date":
|
|
1606
|
+
return "date";
|
|
1607
|
+
case "time":
|
|
1608
|
+
return "time";
|
|
1609
|
+
case "datetime":
|
|
1610
|
+
case "datetimeWithTimezone":
|
|
1611
|
+
return "datetime";
|
|
1612
|
+
case "json":
|
|
1613
|
+
case "jsonb":
|
|
1614
|
+
return "json";
|
|
1615
|
+
case "uuid":
|
|
1616
|
+
return "uuid";
|
|
1617
|
+
case "mediumText":
|
|
1618
|
+
case "longText":
|
|
1347
1619
|
return "longText";
|
|
1348
|
-
|
|
1620
|
+
case "shortText":
|
|
1621
|
+
case "unicodeShortText":
|
|
1622
|
+
if (charLength !== null && charLength > 255) {
|
|
1623
|
+
return "longText";
|
|
1624
|
+
}
|
|
1349
1625
|
|
|
1350
|
-
|
|
1626
|
+
return "shortText";
|
|
1627
|
+
default:
|
|
1628
|
+
return "shortText";
|
|
1351
1629
|
}
|
|
1352
|
-
|
|
1353
|
-
return "shortText";
|
|
1354
1630
|
}
|
|
1355
1631
|
|
|
1356
1632
|
private toOrmFieldType(column: DatasourceIntrospectionColumn, datasourceType: string) {
|
|
1357
|
-
const
|
|
1633
|
+
const family = this.getDatasourceTypeFamily(column.dataType);
|
|
1358
1634
|
const normalizedDatasourceType = datasourceType.toLowerCase();
|
|
1635
|
+
const ormType = family
|
|
1636
|
+
? ORM_TYPE_BY_DATASOURCE_FAMILY[normalizedDatasourceType]?.[family]
|
|
1637
|
+
: undefined;
|
|
1638
|
+
|
|
1639
|
+
if (ormType) {
|
|
1640
|
+
return ormType;
|
|
1641
|
+
}
|
|
1359
1642
|
|
|
1360
|
-
|
|
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";
|
|
1643
|
+
return normalizedDatasourceType === "postgres" ? "varchar" : "varchar";
|
|
1404
1644
|
}
|
|
1405
1645
|
|
|
1406
1646
|
private toFieldName(columnName: string) {
|
|
@@ -1449,6 +1689,269 @@ export class DatasourceIntrospectionService {
|
|
|
1449
1689
|
return `${datasourceName.toLowerCase()}::${tableName.toLowerCase()}`;
|
|
1450
1690
|
}
|
|
1451
1691
|
|
|
1692
|
+
private async syncTypeormDatasourceFile(moduleName: string, datasource: DatasourceRecord, singularName: string) {
|
|
1693
|
+
const datasourceFilePath = path.resolve(process.cwd(), "src", `typeorm-${datasource.name}-datasource.ts`);
|
|
1694
|
+
const entityClassName = classify(singularName);
|
|
1695
|
+
const entityImportPath = `./${moduleName}/entities/${kebabCase(singularName)}.entity`;
|
|
1696
|
+
const migrationGlob = `join(__dirname, './${moduleName}/migrations/${datasource.name}/*.{ts,js}')`;
|
|
1697
|
+
|
|
1698
|
+
const existingContent = await fs.readFile(datasourceFilePath, "utf8").catch(() => null);
|
|
1699
|
+
if (!existingContent) {
|
|
1700
|
+
const rendered = this.renderTypeormDatasourceTemplate({
|
|
1701
|
+
datasource,
|
|
1702
|
+
entityClassName,
|
|
1703
|
+
entityImportPath,
|
|
1704
|
+
migrationGlob,
|
|
1705
|
+
});
|
|
1706
|
+
await fs.writeFile(datasourceFilePath, rendered, "utf8");
|
|
1707
|
+
return {
|
|
1708
|
+
filePath: datasourceFilePath,
|
|
1709
|
+
created: true,
|
|
1710
|
+
updated: false,
|
|
1711
|
+
entityRegistered: true,
|
|
1712
|
+
migrationGlobRegistered: true,
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
const project = new Project({
|
|
1717
|
+
skipAddingFilesFromTsConfig: true,
|
|
1718
|
+
manipulationSettings: {
|
|
1719
|
+
quoteKind: QuoteKind.Single,
|
|
1720
|
+
indentationText: IndentationText.FourSpaces,
|
|
1721
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
|
1722
|
+
},
|
|
1723
|
+
});
|
|
1724
|
+
const sourceFile = project.createSourceFile(datasourceFilePath, existingContent, { overwrite: true });
|
|
1725
|
+
|
|
1726
|
+
const entityImportDeclaration = sourceFile.getImportDeclarations().find((declaration) =>
|
|
1727
|
+
declaration.getModuleSpecifierValue() === entityImportPath,
|
|
1728
|
+
);
|
|
1729
|
+
if (entityImportDeclaration) {
|
|
1730
|
+
const alreadyImported = entityImportDeclaration.getNamedImports().some((namedImport) => namedImport.getName() === entityClassName);
|
|
1731
|
+
if (!alreadyImported) {
|
|
1732
|
+
entityImportDeclaration.addNamedImport(entityClassName);
|
|
1733
|
+
}
|
|
1734
|
+
} else {
|
|
1735
|
+
sourceFile.addImportDeclaration({
|
|
1736
|
+
moduleSpecifier: entityImportPath,
|
|
1737
|
+
namedImports: [entityClassName],
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
const entitiesArray = sourceFile
|
|
1742
|
+
.getVariableDeclarationOrThrow("entities")
|
|
1743
|
+
.getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
1744
|
+
const entityAlreadyRegistered = this.arrayLiteralContainsValue(entitiesArray, entityClassName);
|
|
1745
|
+
if (!entityAlreadyRegistered) {
|
|
1746
|
+
entitiesArray.addElement(entityClassName);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
const dataSourceOptions = this.getTypeormDatasourceOptions(sourceFile);
|
|
1750
|
+
const migrationsArray = this.getObjectPropertyArrayLiteral(dataSourceOptions, "migrations");
|
|
1751
|
+
const migrationAlreadyRegistered = this.arrayLiteralContainsValue(migrationsArray, migrationGlob);
|
|
1752
|
+
if (!migrationAlreadyRegistered) {
|
|
1753
|
+
migrationsArray.addElement(migrationGlob);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
sourceFile.organizeImports();
|
|
1757
|
+
sourceFile.formatText();
|
|
1758
|
+
|
|
1759
|
+
const nextContent = sourceFile.getFullText();
|
|
1760
|
+
const updated = nextContent !== existingContent;
|
|
1761
|
+
if (updated) {
|
|
1762
|
+
await fs.writeFile(datasourceFilePath, nextContent, "utf8");
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
return {
|
|
1766
|
+
filePath: datasourceFilePath,
|
|
1767
|
+
created: false,
|
|
1768
|
+
updated,
|
|
1769
|
+
entityRegistered: true,
|
|
1770
|
+
migrationGlobRegistered: true,
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
private renderTypeormDatasourceTemplate(input: {
|
|
1775
|
+
datasource: DatasourceRecord;
|
|
1776
|
+
entityClassName: string;
|
|
1777
|
+
entityImportPath: string;
|
|
1778
|
+
migrationGlob: string;
|
|
1779
|
+
}) {
|
|
1780
|
+
const { datasource, entityClassName, entityImportPath, migrationGlob } = input;
|
|
1781
|
+
const envPrefix = datasource.envPrefix?.toUpperCase?.() || "DEFAULT";
|
|
1782
|
+
const portFallback = datasource.type === "postgres" ? 5432 : datasource.type === "mysql" ? 3306 : 1433;
|
|
1783
|
+
const project = new Project({
|
|
1784
|
+
skipAddingFilesFromTsConfig: true,
|
|
1785
|
+
manipulationSettings: {
|
|
1786
|
+
quoteKind: QuoteKind.Single,
|
|
1787
|
+
indentationText: IndentationText.FourSpaces,
|
|
1788
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
|
1789
|
+
},
|
|
1790
|
+
});
|
|
1791
|
+
const sourceFile = project.createSourceFile("typeorm-template.ts", "", { overwrite: true });
|
|
1792
|
+
|
|
1793
|
+
sourceFile.addImportDeclaration({ moduleSpecifier: "reflect-metadata" });
|
|
1794
|
+
sourceFile.addImportDeclaration({ moduleSpecifier: "path", namedImports: ["join"] });
|
|
1795
|
+
sourceFile.addImportDeclaration({
|
|
1796
|
+
moduleSpecifier: "dotenv",
|
|
1797
|
+
namedImports: [{ name: "config", alias: "dotenvConfig" }],
|
|
1798
|
+
});
|
|
1799
|
+
sourceFile.addStatements("dotenvConfig({ path: join(__dirname, '../.env') });");
|
|
1800
|
+
sourceFile.addStatements("");
|
|
1801
|
+
|
|
1802
|
+
sourceFile.addImportDeclaration({ moduleSpecifier: "typeorm", namedImports: ["DataSource"] });
|
|
1803
|
+
sourceFile.addImportDeclaration({
|
|
1804
|
+
moduleSpecifier: "@solidxai/core",
|
|
1805
|
+
namespaceImport: "SolidCoreModuleExports",
|
|
1806
|
+
});
|
|
1807
|
+
sourceFile.addImportDeclaration({
|
|
1808
|
+
moduleSpecifier: "@solidxai/core",
|
|
1809
|
+
namedImports: ["getDynamicModuleNames", "parseBooleanEnv"],
|
|
1810
|
+
});
|
|
1811
|
+
sourceFile.addImportDeclaration({
|
|
1812
|
+
moduleSpecifier: "typeorm",
|
|
1813
|
+
namedImports: ["getMetadataArgsStorage"],
|
|
1814
|
+
});
|
|
1815
|
+
sourceFile.addImportDeclaration({
|
|
1816
|
+
moduleSpecifier: "typeorm-naming-strategies",
|
|
1817
|
+
namedImports: ["SnakeNamingStrategy"],
|
|
1818
|
+
});
|
|
1819
|
+
sourceFile.addImportDeclaration({
|
|
1820
|
+
moduleSpecifier: entityImportPath,
|
|
1821
|
+
namedImports: [entityClassName],
|
|
1822
|
+
});
|
|
1823
|
+
|
|
1824
|
+
sourceFile.addFunction({
|
|
1825
|
+
name: "getEntitiesFromExports",
|
|
1826
|
+
parameters: [{ name: "exports", type: "Record<string, any>" }],
|
|
1827
|
+
statements: (writer) => {
|
|
1828
|
+
writer.writeLine("const metadataStorage = getMetadataArgsStorage();");
|
|
1829
|
+
writer.writeLine("return Object.values(exports).filter((item) =>");
|
|
1830
|
+
writer.indent(() => {
|
|
1831
|
+
writer.writeLine("metadataStorage.tables.some((table) => table.target === item),");
|
|
1832
|
+
});
|
|
1833
|
+
writer.write(");");
|
|
1834
|
+
},
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
sourceFile.addVariableStatement({
|
|
1838
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
1839
|
+
declarations: [
|
|
1840
|
+
{
|
|
1841
|
+
name: "coreEntities",
|
|
1842
|
+
initializer: "getEntitiesFromExports(SolidCoreModuleExports)",
|
|
1843
|
+
},
|
|
1844
|
+
],
|
|
1845
|
+
});
|
|
1846
|
+
sourceFile.addVariableStatement({
|
|
1847
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
1848
|
+
declarations: [
|
|
1849
|
+
{
|
|
1850
|
+
name: "dynamicModules",
|
|
1851
|
+
initializer: "getDynamicModuleNames()",
|
|
1852
|
+
},
|
|
1853
|
+
],
|
|
1854
|
+
});
|
|
1855
|
+
sourceFile.addVariableStatement({
|
|
1856
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
1857
|
+
declarations: [
|
|
1858
|
+
{
|
|
1859
|
+
name: "entities",
|
|
1860
|
+
initializer: `[\n ...coreEntities,\n ${entityClassName},\n]`,
|
|
1861
|
+
},
|
|
1862
|
+
],
|
|
1863
|
+
});
|
|
1864
|
+
sourceFile.addVariableStatement({
|
|
1865
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
1866
|
+
declarations: [
|
|
1867
|
+
{
|
|
1868
|
+
name: "logging",
|
|
1869
|
+
initializer: `parseBooleanEnv('${envPrefix}_DATABASE_LOGGING')`,
|
|
1870
|
+
},
|
|
1871
|
+
],
|
|
1872
|
+
});
|
|
1873
|
+
|
|
1874
|
+
sourceFile.addStatements("// IMPORTANT: synchronize must be false when using migrations");
|
|
1875
|
+
sourceFile.addVariableStatement({
|
|
1876
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
1877
|
+
isExported: true,
|
|
1878
|
+
declarations: [
|
|
1879
|
+
{
|
|
1880
|
+
name: "DefaultDataSource",
|
|
1881
|
+
initializer: (writer) => {
|
|
1882
|
+
writer.write("new DataSource(");
|
|
1883
|
+
writer.inlineBlock(() => {
|
|
1884
|
+
writer.writeLine(`type: '${datasource.type}',`);
|
|
1885
|
+
writer.blankLine();
|
|
1886
|
+
writer.writeLine(`host: process.env.${envPrefix}_DATABASE_HOST,`);
|
|
1887
|
+
writer.writeLine(`port: +(process.env.${envPrefix}_DATABASE_PORT || ${portFallback}),`);
|
|
1888
|
+
writer.writeLine(`username: process.env.${envPrefix}_DATABASE_USER,`);
|
|
1889
|
+
writer.writeLine(`password: process.env.${envPrefix}_DATABASE_PASSWORD,`);
|
|
1890
|
+
writer.writeLine(`database: process.env.${envPrefix}_DATABASE_NAME,`);
|
|
1891
|
+
writer.blankLine();
|
|
1892
|
+
writer.writeLine("entities,");
|
|
1893
|
+
writer.writeLine("migrations: [");
|
|
1894
|
+
writer.indent(() => {
|
|
1895
|
+
writer.writeLine(`${migrationGlob},`);
|
|
1896
|
+
});
|
|
1897
|
+
writer.writeLine("],");
|
|
1898
|
+
writer.writeLine("synchronize: false,");
|
|
1899
|
+
writer.writeLine("logging,");
|
|
1900
|
+
writer.writeLine("namingStrategy: new SnakeNamingStrategy(),");
|
|
1901
|
+
if (datasource.type === "mssql") {
|
|
1902
|
+
writer.writeLine("options: {");
|
|
1903
|
+
writer.indent(() => {
|
|
1904
|
+
writer.writeLine("encrypt: false,");
|
|
1905
|
+
writer.writeLine("trustServerCertificate: true,");
|
|
1906
|
+
});
|
|
1907
|
+
writer.writeLine("},");
|
|
1908
|
+
}
|
|
1909
|
+
});
|
|
1910
|
+
writer.write(")");
|
|
1911
|
+
},
|
|
1912
|
+
},
|
|
1913
|
+
],
|
|
1914
|
+
});
|
|
1915
|
+
|
|
1916
|
+
sourceFile.organizeImports();
|
|
1917
|
+
sourceFile.formatText();
|
|
1918
|
+
return sourceFile.getFullText();
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
private arrayLiteralContainsValue(arrayLiteral: ArrayLiteralExpression, rawValue: string) {
|
|
1922
|
+
const normalizedTarget = rawValue.replace(/\s+/g, "");
|
|
1923
|
+
return arrayLiteral.getElements().some((element) => element.getText().replace(/\s+/g, "") === normalizedTarget);
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
private getTypeormDatasourceOptions(sourceFile: import("ts-morph").SourceFile) {
|
|
1927
|
+
const defaultDataSourceDeclaration = sourceFile.getVariableDeclarationOrThrow("DefaultDataSource");
|
|
1928
|
+
const initializer = defaultDataSourceDeclaration.getInitializer();
|
|
1929
|
+
if (!initializer || !Node.isNewExpression(initializer)) {
|
|
1930
|
+
throw new BadRequestException("Unable to locate DefaultDataSource initializer in datasource file.");
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
const options = initializer.getArguments()[0];
|
|
1934
|
+
if (!options || !Node.isObjectLiteralExpression(options)) {
|
|
1935
|
+
throw new BadRequestException("Unable to locate DefaultDataSource options object in datasource file.");
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
return options;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
private getObjectPropertyArrayLiteral(objectLiteral: ObjectLiteralExpression, propertyName: string) {
|
|
1942
|
+
const property = objectLiteral.getProperty(propertyName);
|
|
1943
|
+
if (!property || !Node.isPropertyAssignment(property)) {
|
|
1944
|
+
throw new BadRequestException(`Unable to locate "${propertyName}" property in datasource file.`);
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
const initializer = property.getInitializer();
|
|
1948
|
+
if (!initializer || !Node.isArrayLiteralExpression(initializer)) {
|
|
1949
|
+
throw new BadRequestException(`Unable to locate "${propertyName}" array in datasource file.`);
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
return initializer;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1452
1955
|
private buildSynchronizeBlockedMessage(datasource: DatasourceRecord) {
|
|
1453
1956
|
return `Datasource "${datasource.displayName}" has synchronize enabled. Set ${datasource.envPrefix}_DATABASE_SYNCHRONIZE=false before starting legacy table introspection.`;
|
|
1454
1957
|
}
|