@solidxai/core 0.1.11-beta.1 → 0.1.11-beta.12
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 +46 -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/controllers/service.controller.d.ts +1 -0
- package/dist/controllers/service.controller.d.ts.map +1 -1
- package/dist/controllers/service.controller.js +2 -2
- package/dist/controllers/service.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/environment.helper.d.ts +1 -0
- package/dist/helpers/environment.helper.d.ts.map +1 -1
- package/dist/helpers/environment.helper.js +4 -0
- package/dist/helpers/environment.helper.js.map +1 -1
- package/dist/helpers/field-crud-managers/IntFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/IntFieldCrudManager.js +4 -1
- package/dist/helpers/field-crud-managers/IntFieldCrudManager.js.map +1 -1
- package/dist/helpers/model-metadata-helper.service.d.ts +2 -1
- package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
- package/dist/helpers/model-metadata-helper.service.js +9 -5
- package/dist/helpers/model-metadata-helper.service.js.map +1 -1
- package/dist/helpers/module-metadata-helper.service.js +1 -1
- package/dist/helpers/module-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/helpers/solid-registry.d.ts +2 -1
- package/dist/helpers/solid-registry.d.ts.map +1 -1
- package/dist/helpers/solid-registry.js.map +1 -1
- package/dist/helpers/typeorm-db-helper.d.ts +1 -0
- package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
- package/dist/helpers/typeorm-db-helper.js +12 -0
- package/dist/helpers/typeorm-db-helper.js.map +1 -1
- 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/interfaces.d.ts +1 -0
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/repository/scheduled-job.repository.d.ts +2 -2
- package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
- package/dist/repository/scheduled-job.repository.js +6 -3
- package/dist/repository/scheduled-job.repository.js.map +1 -1
- package/dist/repository/security-rule.repository.d.ts +2 -2
- package/dist/repository/security-rule.repository.d.ts.map +1 -1
- package/dist/repository/security-rule.repository.js +6 -3
- package/dist/repository/security-rule.repository.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts +11 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +163 -3
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +128 -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/authentication.service.d.ts +2 -0
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +32 -11
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts.map +1 -1
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js +7 -5
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.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-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +25 -12
- package/dist/services/crud-helper.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 +63 -2
- package/dist/services/datasource-introspection.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection.service.js +665 -188
- 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/locale.service.d.ts +14 -0
- package/dist/services/locale.service.d.ts.map +1 -1
- package/dist/services/locale.service.js +83 -0
- package/dist/services/locale.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/menu-item-metadata.service.d.ts.map +1 -1
- package/dist/services/menu-item-metadata.service.js +1 -0
- package/dist/services/menu-item-metadata.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts +3 -0
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +78 -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 +28 -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/services/role-metadata.service.d.ts +1 -0
- package/dist/services/role-metadata.service.d.ts.map +1 -1
- package/dist/services/role-metadata.service.js +23 -2
- package/dist/services/role-metadata.service.js.map +1 -1
- package/dist/services/settings/default-settings-provider.service.d.ts +244 -0
- package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
- package/dist/services/settings/default-settings-provider.service.js +120 -0
- package/dist/services/settings/default-settings-provider.service.js.map +1 -1
- package/dist/services/solid-ts-morph.service.d.ts +6 -0
- package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
- package/dist/services/solid-ts-morph.service.js +43 -0
- package/dist/services/solid-ts-morph.service.js.map +1 -1
- package/dist/services/view-metadata.service.d.ts.map +1 -1
- package/dist/services/view-metadata.service.js +4 -7
- package/dist/services/view-metadata.service.js.map +1 -1
- 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 +5 -1
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +76 -12
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.d.ts +9 -1
- package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.js +45 -11
- package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
- package/dist/subscribers/created-by-updated-by.subscriber.d.ts.map +1 -1
- package/dist/subscribers/created-by-updated-by.subscriber.js.map +1 -1
- package/dist/subscribers/field-metadata.subscriber.d.ts.map +1 -1
- package/dist/subscribers/field-metadata.subscriber.js.map +1 -1
- package/dist/subscribers/scheduled-job.subscriber.d.ts.map +1 -1
- package/dist/subscribers/scheduled-job.subscriber.js +3 -2
- package/dist/subscribers/scheduled-job.subscriber.js.map +1 -1
- package/dist/subscribers/security-rule.subscriber.d.ts.map +1 -1
- package/dist/subscribers/security-rule.subscriber.js +5 -3
- package/dist/subscribers/security-rule.subscriber.js.map +1 -1
- 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/controllers/service.controller.ts +3 -2
- 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/environment.helper.ts +11 -0
- package/src/helpers/field-crud-managers/IntFieldCrudManager.ts +4 -1
- package/src/helpers/model-metadata-helper.service.ts +8 -3
- package/src/helpers/module-metadata-helper.service.ts +1 -1
- package/src/helpers/nodemon-heartbeat.ts +59 -0
- package/src/helpers/solid-registry.ts +7 -1
- package/src/helpers/typeorm-db-helper.ts +15 -0
- package/src/index.ts +3 -2
- package/src/interfaces/datasource-introspection-provider.interface.ts +1 -0
- package/src/interfaces.ts +1 -0
- package/src/repository/scheduled-job.repository.ts +10 -4
- package/src/repository/security-rule.repository.ts +10 -4
- package/src/seeders/module-metadata-seeder.service.ts +192 -2
- package/src/seeders/seed-data/solid-core-metadata.json +128 -34
- package/src/services/api-key.service.ts +36 -36
- package/src/services/authentication.service.ts +46 -12
- package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +11 -5
- package/src/services/computed-fields/entity/concat-entity-computed-field-provider.service.ts +15 -1
- package/src/services/crud-helper.service.ts +25 -12
- 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 +827 -164
- package/src/services/import-transaction.service.ts +5 -0
- package/src/services/locale.service.ts +111 -3
- package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +2 -2
- package/src/services/menu-item-metadata.service.ts +2 -1
- package/src/services/model-metadata.service.ts +109 -1
- package/src/services/module-metadata.service.ts +36 -0
- package/src/services/removed-field-migration.service.ts +334 -0
- package/src/services/role-metadata.service.ts +26 -2
- package/src/services/settings/default-settings-provider.service.ts +120 -0
- package/src/services/solid-ts-morph.service.ts +61 -1
- package/src/services/view-metadata.service.ts +4 -8
- package/src/solid-core.module.ts +4 -0
- package/src/subscribers/audit.subscriber.ts +101 -18
- package/src/subscribers/computed-entity-field.subscriber.ts +81 -28
- package/src/subscribers/created-by-updated-by.subscriber.ts +3 -0
- package/src/subscribers/field-metadata.subscriber.ts +3 -0
- package/src/subscribers/scheduled-job.subscriber.ts +3 -2
- package/src/subscribers/security-rule.subscriber.ts +8 -3
- 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/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";
|
|
@@ -39,11 +43,16 @@ type TableInventoryRecord = {
|
|
|
39
43
|
suggestedUserKeyField: string;
|
|
40
44
|
columnCount: number;
|
|
41
45
|
physicalColumnCount: number;
|
|
46
|
+
hasPrimaryKey: boolean;
|
|
47
|
+
primaryKeyColumnCount: number;
|
|
48
|
+
primaryKeyColumnNames: string[];
|
|
49
|
+
mappingBlockedReason: string | null;
|
|
42
50
|
};
|
|
43
51
|
|
|
44
52
|
type TableDetailColumnRecord = {
|
|
45
53
|
columnName: string;
|
|
46
54
|
fieldName: string;
|
|
55
|
+
displayName: string;
|
|
47
56
|
dataType: string;
|
|
48
57
|
solidFieldType: string;
|
|
49
58
|
ormType: string;
|
|
@@ -54,6 +63,7 @@ type TableDetailColumnRecord = {
|
|
|
54
63
|
numericScale: number | null;
|
|
55
64
|
ordinalPosition: number | null;
|
|
56
65
|
isAutoGenerated: boolean;
|
|
66
|
+
isPrimaryKey: boolean;
|
|
57
67
|
handledBySuperclass: boolean;
|
|
58
68
|
superclassFieldName: string | null;
|
|
59
69
|
};
|
|
@@ -81,6 +91,13 @@ type MappingPreviewRecord = {
|
|
|
81
91
|
note: string;
|
|
82
92
|
missingSystemColumns: string[];
|
|
83
93
|
};
|
|
94
|
+
datasourceFile: {
|
|
95
|
+
filePath: string;
|
|
96
|
+
created: boolean;
|
|
97
|
+
updated: boolean;
|
|
98
|
+
entityRegistered: boolean;
|
|
99
|
+
migrationGlobRegistered: boolean;
|
|
100
|
+
};
|
|
84
101
|
summary: {
|
|
85
102
|
includedColumnNames: string[];
|
|
86
103
|
removedColumnNames: string[];
|
|
@@ -119,6 +136,105 @@ type SystemColumnDefinition = {
|
|
|
119
136
|
isUnique?: boolean;
|
|
120
137
|
};
|
|
121
138
|
|
|
139
|
+
type DatasourceTypeFamily =
|
|
140
|
+
| "boolean"
|
|
141
|
+
| "bigint"
|
|
142
|
+
| "int"
|
|
143
|
+
| "numeric"
|
|
144
|
+
| "decimal"
|
|
145
|
+
| "float"
|
|
146
|
+
| "date"
|
|
147
|
+
| "time"
|
|
148
|
+
| "datetime"
|
|
149
|
+
| "datetimeWithTimezone"
|
|
150
|
+
| "json"
|
|
151
|
+
| "jsonb"
|
|
152
|
+
| "uuid"
|
|
153
|
+
| "longText"
|
|
154
|
+
| "mediumText"
|
|
155
|
+
| "shortText"
|
|
156
|
+
| "unicodeShortText";
|
|
157
|
+
|
|
158
|
+
const DATASOURCE_TYPE_FAMILY_ALIASES: Record<DatasourceTypeFamily, string[]> = {
|
|
159
|
+
boolean: ["bit", "bool", "boolean"],
|
|
160
|
+
bigint: ["bigint"],
|
|
161
|
+
int: ["int", "integer", "smallint", "tinyint"],
|
|
162
|
+
numeric: ["numeric"],
|
|
163
|
+
decimal: ["decimal", "money", "smallmoney"],
|
|
164
|
+
float: ["float", "double", "double precision", "real", "float4", "float8"],
|
|
165
|
+
date: ["date"],
|
|
166
|
+
time: ["time", "time without time zone", "time with time zone"],
|
|
167
|
+
datetime: ["datetime", "datetime2", "smalldatetime", "timestamp", "timestamp without time zone"],
|
|
168
|
+
datetimeWithTimezone: ["timestamptz", "timestamp with time zone"],
|
|
169
|
+
json: ["json", "simple-json", "simplejson"],
|
|
170
|
+
jsonb: ["jsonb"],
|
|
171
|
+
uuid: ["uniqueidentifier", "uuid"],
|
|
172
|
+
longText: ["text", "ntext", "longtext", "clob"],
|
|
173
|
+
mediumText: ["mediumtext"],
|
|
174
|
+
shortText: ["varchar", "char", "citext"],
|
|
175
|
+
unicodeShortText: ["nvarchar", "nchar"],
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const ORM_TYPE_BY_DATASOURCE_FAMILY: Record<string, Partial<Record<DatasourceTypeFamily, string>>> = {
|
|
179
|
+
mssql: {
|
|
180
|
+
boolean: "bit",
|
|
181
|
+
bigint: "bigint",
|
|
182
|
+
int: "int",
|
|
183
|
+
numeric: "numeric",
|
|
184
|
+
decimal: "decimal",
|
|
185
|
+
float: "decimal",
|
|
186
|
+
date: "date",
|
|
187
|
+
time: "time",
|
|
188
|
+
datetime: "datetime",
|
|
189
|
+
datetimeWithTimezone: "datetime",
|
|
190
|
+
json: "simple-json",
|
|
191
|
+
jsonb: "simple-json",
|
|
192
|
+
uuid: "uniqueidentifier",
|
|
193
|
+
longText: "text",
|
|
194
|
+
mediumText: "text",
|
|
195
|
+
shortText: "varchar",
|
|
196
|
+
unicodeShortText: "nvarchar",
|
|
197
|
+
},
|
|
198
|
+
mysql: {
|
|
199
|
+
boolean: "boolean",
|
|
200
|
+
bigint: "int",
|
|
201
|
+
int: "int",
|
|
202
|
+
numeric: "decimal",
|
|
203
|
+
decimal: "decimal",
|
|
204
|
+
float: "double",
|
|
205
|
+
date: "date",
|
|
206
|
+
time: "time",
|
|
207
|
+
datetime: "datetime",
|
|
208
|
+
datetimeWithTimezone: "datetime",
|
|
209
|
+
json: "json",
|
|
210
|
+
jsonb: "json",
|
|
211
|
+
uuid: "varchar",
|
|
212
|
+
longText: "longtext",
|
|
213
|
+
mediumText: "mediumtext",
|
|
214
|
+
shortText: "varchar",
|
|
215
|
+
unicodeShortText: "varchar",
|
|
216
|
+
},
|
|
217
|
+
postgres: {
|
|
218
|
+
boolean: "boolean",
|
|
219
|
+
bigint: "bigint",
|
|
220
|
+
int: "integer",
|
|
221
|
+
numeric: "decimal",
|
|
222
|
+
decimal: "decimal",
|
|
223
|
+
float: "decimal",
|
|
224
|
+
date: "date",
|
|
225
|
+
time: "time",
|
|
226
|
+
datetime: "timestamp",
|
|
227
|
+
datetimeWithTimezone: "timestamptz",
|
|
228
|
+
json: "json",
|
|
229
|
+
jsonb: "jsonb",
|
|
230
|
+
uuid: "uuid",
|
|
231
|
+
longText: "text",
|
|
232
|
+
mediumText: "text",
|
|
233
|
+
shortText: "varchar",
|
|
234
|
+
unicodeShortText: "varchar",
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
122
238
|
@Injectable()
|
|
123
239
|
export class DatasourceIntrospectionService {
|
|
124
240
|
private readonly supportedDatasourceTypes = new Set(["mssql", "mysql", "postgres"]);
|
|
@@ -132,6 +248,8 @@ export class DatasourceIntrospectionService {
|
|
|
132
248
|
private readonly modelMetadataHelperService: ModelMetadataHelperService,
|
|
133
249
|
private readonly fieldMetadataService: FieldMetadataService,
|
|
134
250
|
private readonly modelMetadataService: ModelMetadataService,
|
|
251
|
+
private readonly moduleMetadataService: ModuleMetadataService,
|
|
252
|
+
private readonly commandService: CommandService,
|
|
135
253
|
private readonly mssqlProvider: MssqlDatasourceIntrospectionProviderService,
|
|
136
254
|
private readonly mysqlProvider: MysqlDatasourceIntrospectionProviderService,
|
|
137
255
|
private readonly postgresProvider: PostgresDatasourceIntrospectionProviderService,
|
|
@@ -266,11 +384,6 @@ export class DatasourceIntrospectionService {
|
|
|
266
384
|
? await this.modelMetadataService.update(mappingDto.modelId, plan.modelPayload as UpdateModelMetaDataDto)
|
|
267
385
|
: await this.modelMetadataService.create(plan.modelPayload as CreateModelMetadataDto);
|
|
268
386
|
|
|
269
|
-
if (plan.migration.willGenerate && plan.migration.content && plan.migration.filePath) {
|
|
270
|
-
await fs.mkdir(path.dirname(plan.migration.filePath), { recursive: true });
|
|
271
|
-
await fs.writeFile(plan.migration.filePath, plan.migration.content, "utf8");
|
|
272
|
-
}
|
|
273
|
-
|
|
274
387
|
const persistedModelId = mappingDto.modelId ?? (persistedModel as ModelMetadata | undefined)?.id ?? null;
|
|
275
388
|
|
|
276
389
|
return {
|
|
@@ -278,15 +391,106 @@ export class DatasourceIntrospectionService {
|
|
|
278
391
|
modelId: persistedModelId,
|
|
279
392
|
operation: plan.operation,
|
|
280
393
|
metadataJson: plan.metadataJson,
|
|
394
|
+
summary: plan.summary,
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async createMigrationArtifacts(moduleId: number, mappingDto: DatasourceIntrospectionMappingDto) {
|
|
400
|
+
const plan = await this.buildMappingPreview(moduleId, mappingDto);
|
|
401
|
+
const module = await this.loadModule(moduleId);
|
|
402
|
+
const datasource = this.requireDatasource(mappingDto.dataSource);
|
|
403
|
+
|
|
404
|
+
if (!mappingDto.modelId) {
|
|
405
|
+
throw new BadRequestException("Persist the model mapping first so the generated entity can be registered before creating migration artifacts.");
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const migrationWritten = await this.writeMigrationFile(plan.migration);
|
|
409
|
+
|
|
410
|
+
const datasourceFile = await this.syncTypeormDatasourceFile(
|
|
411
|
+
module.name,
|
|
412
|
+
datasource,
|
|
413
|
+
plan.modelPayload.singularName,
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
data: {
|
|
418
|
+
modelId: mappingDto.modelId,
|
|
419
|
+
operation: plan.operation,
|
|
281
420
|
migration: {
|
|
282
421
|
...plan.migration,
|
|
283
|
-
written:
|
|
422
|
+
written: migrationWritten,
|
|
284
423
|
},
|
|
424
|
+
datasourceFile,
|
|
285
425
|
summary: plan.summary,
|
|
286
426
|
},
|
|
287
427
|
};
|
|
288
428
|
}
|
|
289
429
|
|
|
430
|
+
async generateCode(moduleId: number) {
|
|
431
|
+
await this.loadModule(moduleId);
|
|
432
|
+
const output = await this.moduleMetadataService.generateCodeViaCtl(moduleId);
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
data: {
|
|
436
|
+
moduleId,
|
|
437
|
+
output,
|
|
438
|
+
message: "Module code generation completed.",
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async runMigration(moduleId: number, datasourceName: string) {
|
|
444
|
+
const module = await this.loadModule(moduleId);
|
|
445
|
+
const datasource = this.requireDatasource(datasourceName);
|
|
446
|
+
const cwd = path.join(process.cwd(), "..");
|
|
447
|
+
const outputs: string[] = [];
|
|
448
|
+
|
|
449
|
+
const migrationOutput = await this.commandService.executeCommandWithArgs({
|
|
450
|
+
command: "npx",
|
|
451
|
+
args: ["-y", "@solidxai/solidctl@latest", "migration", "-d", datasource.name, "run"],
|
|
452
|
+
cwd,
|
|
453
|
+
});
|
|
454
|
+
outputs.push([
|
|
455
|
+
`npx @solidxai/solidctl@latest migration -d ${datasource.name} run`,
|
|
456
|
+
migrationOutput,
|
|
457
|
+
].filter(Boolean).join("\n"));
|
|
458
|
+
|
|
459
|
+
const buildOutput = await this.commandService.executeCommandWithArgs({
|
|
460
|
+
command: "npx",
|
|
461
|
+
args: ["-y", "@solidxai/solidctl@latest", "build"],
|
|
462
|
+
cwd,
|
|
463
|
+
});
|
|
464
|
+
outputs.push([
|
|
465
|
+
"npx @solidxai/solidctl@latest build",
|
|
466
|
+
buildOutput,
|
|
467
|
+
].filter(Boolean).join("\n"));
|
|
468
|
+
|
|
469
|
+
const seedOutput = await this.commandService.executeCommandWithArgs({
|
|
470
|
+
command: "npx",
|
|
471
|
+
args: ["-y", "@solidxai/solidctl@latest", "seed", "--modules-to-seed", module.name],
|
|
472
|
+
cwd,
|
|
473
|
+
});
|
|
474
|
+
outputs.push([
|
|
475
|
+
`npx @solidxai/solidctl@latest seed --modules-to-seed ${module.name}`,
|
|
476
|
+
seedOutput,
|
|
477
|
+
].filter(Boolean).join("\n"));
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
data: {
|
|
481
|
+
moduleId,
|
|
482
|
+
datasource: datasource.name,
|
|
483
|
+
output: outputs.join("\n\n"),
|
|
484
|
+
command: [
|
|
485
|
+
`npx @solidxai/solidctl@latest migration -d ${datasource.name} run`,
|
|
486
|
+
"npx @solidxai/solidctl@latest build",
|
|
487
|
+
`npx @solidxai/solidctl@latest seed --modules-to-seed ${module.name}`,
|
|
488
|
+
].join("\n"),
|
|
489
|
+
message: `Migration, build, and seed completed successfully for datasource "${datasource.name}".`,
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
290
494
|
private async buildMappingPreview(moduleId: number, mappingDto: DatasourceIntrospectionMappingDto): Promise<MappingPreviewRecord> {
|
|
291
495
|
const module = await this.loadModule(moduleId);
|
|
292
496
|
const datasource = this.requireDatasource(mappingDto.dataSource);
|
|
@@ -301,6 +505,8 @@ export class DatasourceIntrospectionService {
|
|
|
301
505
|
if (!tableRecord) {
|
|
302
506
|
throw new NotFoundException(`Table "${mappingDto.tableName}" was not found on datasource "${datasource.name}".`);
|
|
303
507
|
}
|
|
508
|
+
const tableColumns = inventory.columnsByKey.get(tableKey) ?? [];
|
|
509
|
+
this.validateTablePrimaryKeyForMapping(tableRecord, tableColumns, mappingDto);
|
|
304
510
|
|
|
305
511
|
const existingModel = mappingDto.mapped && mappingDto.modelId
|
|
306
512
|
? await this.modelMetadataRepository.findOne({
|
|
@@ -318,14 +524,14 @@ export class DatasourceIntrospectionService {
|
|
|
318
524
|
throw new NotFoundException(`Mapped model "${mappingDto.modelId}" was not found.`);
|
|
319
525
|
}
|
|
320
526
|
|
|
321
|
-
const reviewedModel = this.normalizeReviewedMetadataModel(mappingDto.reviewedModel, mappingDto);
|
|
527
|
+
const reviewedModel = this.normalizeReviewedMetadataModel(mappingDto.reviewedModel, mappingDto, tableColumns);
|
|
322
528
|
const modelPayload = reviewedModel
|
|
323
529
|
? this.buildModelPayloadFromReviewedModel(moduleId, mappingDto, existingModel, reviewedModel)
|
|
324
530
|
: this.buildModelPayload(moduleId, mappingDto, existingModel);
|
|
325
531
|
const metadataJson = reviewedModel
|
|
326
532
|
? await this.buildMetadataJsonPreviewFromReviewedModel(module.name, reviewedModel)
|
|
327
533
|
: await this.buildMetadataJsonPreview(module.name, mappingDto, existingModel);
|
|
328
|
-
const migration = await this.buildMigrationPreview(module.name, datasource, mappingDto,
|
|
534
|
+
const migration = await this.buildMigrationPreview(module.name, datasource, mappingDto, tableColumns);
|
|
329
535
|
const includedColumnNames = mappingDto.columns
|
|
330
536
|
.filter((column) => !column.handledBySuperclass && column.include)
|
|
331
537
|
.map((column) => column.columnName);
|
|
@@ -338,6 +544,13 @@ export class DatasourceIntrospectionService {
|
|
|
338
544
|
mappedModelId: mappingDto.modelId ?? null,
|
|
339
545
|
metadataJson,
|
|
340
546
|
migration,
|
|
547
|
+
datasourceFile: {
|
|
548
|
+
filePath: path.resolve(process.cwd(), "src", `typeorm-${datasource.name}-datasource.ts`),
|
|
549
|
+
created: false,
|
|
550
|
+
updated: false,
|
|
551
|
+
entityRegistered: false,
|
|
552
|
+
migrationGlobRegistered: false,
|
|
553
|
+
},
|
|
341
554
|
summary: {
|
|
342
555
|
includedColumnNames,
|
|
343
556
|
removedColumnNames,
|
|
@@ -399,6 +612,11 @@ export class DatasourceIntrospectionService {
|
|
|
399
612
|
existingModel: ModelMetadata | null,
|
|
400
613
|
reviewedModel: ReviewedMetadataModel,
|
|
401
614
|
): CreateModelMetadataDto | UpdateModelMetaDataDto {
|
|
615
|
+
const resolvedUserKeyFieldName = this.resolveReviewedUserKeyFieldName(
|
|
616
|
+
reviewedModel.fields,
|
|
617
|
+
reviewedModel.userKeyFieldUserKey,
|
|
618
|
+
mappingDto.userKeyField,
|
|
619
|
+
);
|
|
402
620
|
const systemFieldPayloads = existingModel
|
|
403
621
|
? existingModel.fields
|
|
404
622
|
.filter((field) => field.isSystem)
|
|
@@ -410,7 +628,7 @@ export class DatasourceIntrospectionService {
|
|
|
410
628
|
return {
|
|
411
629
|
...field,
|
|
412
630
|
...(matchedExistingField?.id ? { id: matchedExistingField.id } : {}),
|
|
413
|
-
isUserKey: field.name ===
|
|
631
|
+
isUserKey: field.name === resolvedUserKeyFieldName,
|
|
414
632
|
isSystem: field.isSystem ?? false,
|
|
415
633
|
};
|
|
416
634
|
});
|
|
@@ -444,6 +662,11 @@ export class DatasourceIntrospectionService {
|
|
|
444
662
|
) {
|
|
445
663
|
const filePath = await this.moduleMetadataHelperService.getModuleMetadataFilePath(moduleName);
|
|
446
664
|
const previewFields = await this.buildMetadataPreviewFields(mappingDto, existingModel);
|
|
665
|
+
const resolvedUserKeyFieldName = this.resolveReviewedUserKeyFieldName(
|
|
666
|
+
previewFields,
|
|
667
|
+
mappingDto.userKeyField,
|
|
668
|
+
mappingDto.userKeyField,
|
|
669
|
+
) ?? mappingDto.userKeyField;
|
|
447
670
|
|
|
448
671
|
return {
|
|
449
672
|
filePath,
|
|
@@ -455,7 +678,7 @@ export class DatasourceIntrospectionService {
|
|
|
455
678
|
dataSource: mappingDto.dataSource,
|
|
456
679
|
dataSourceType: mappingDto.dataSourceType,
|
|
457
680
|
tableName: mappingDto.tableName,
|
|
458
|
-
userKeyFieldUserKey:
|
|
681
|
+
userKeyFieldUserKey: resolvedUserKeyFieldName,
|
|
459
682
|
isChild: false,
|
|
460
683
|
legacyTableType: mappingDto.legacyTableType,
|
|
461
684
|
parentModelUserKey: "",
|
|
@@ -523,10 +746,6 @@ export class DatasourceIntrospectionService {
|
|
|
523
746
|
|
|
524
747
|
const serialized = Array.from(finalFields.values())
|
|
525
748
|
.filter((field) => !field.isSystem)
|
|
526
|
-
.map((field) => ({
|
|
527
|
-
...field,
|
|
528
|
-
isUserKey: field.name === mappingDto.userKeyField,
|
|
529
|
-
}))
|
|
530
749
|
.sort((left, right) => {
|
|
531
750
|
const leftOrder = typeof left.id === "number" ? left.id : Number.MAX_SAFE_INTEGER;
|
|
532
751
|
const rightOrder = typeof right.id === "number" ? right.id : Number.MAX_SAFE_INTEGER;
|
|
@@ -542,6 +761,16 @@ export class DatasourceIntrospectionService {
|
|
|
542
761
|
previewFields.push(await this.fieldMetadataService.createFieldConfig(field as any));
|
|
543
762
|
}
|
|
544
763
|
|
|
764
|
+
const resolvedUserKeyFieldName = this.resolveReviewedUserKeyFieldName(
|
|
765
|
+
previewFields,
|
|
766
|
+
mappingDto.userKeyField,
|
|
767
|
+
mappingDto.userKeyField,
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
for (const field of previewFields) {
|
|
771
|
+
field.isUserKey = field.name === resolvedUserKeyFieldName;
|
|
772
|
+
}
|
|
773
|
+
|
|
545
774
|
return previewFields;
|
|
546
775
|
}
|
|
547
776
|
|
|
@@ -608,9 +837,18 @@ export class DatasourceIntrospectionService {
|
|
|
608
837
|
return rest;
|
|
609
838
|
}
|
|
610
839
|
|
|
840
|
+
private sanitizeConfiguredFieldPayload(fieldConfig: Record<string, any> | undefined) {
|
|
841
|
+
if (!fieldConfig || typeof fieldConfig !== "object" || Array.isArray(fieldConfig)) {
|
|
842
|
+
return {};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return this.sanitizeExistingFieldPayload(fieldConfig);
|
|
846
|
+
}
|
|
847
|
+
|
|
611
848
|
private normalizeReviewedMetadataModel(
|
|
612
849
|
reviewedModel: Record<string, any> | undefined,
|
|
613
850
|
mappingDto: DatasourceIntrospectionMappingDto,
|
|
851
|
+
tableColumns: DatasourceIntrospectionColumn[],
|
|
614
852
|
): ReviewedMetadataModel | null {
|
|
615
853
|
if (!reviewedModel) {
|
|
616
854
|
return null;
|
|
@@ -670,11 +908,24 @@ export class DatasourceIntrospectionService {
|
|
|
670
908
|
fieldNames.add(loweredName);
|
|
671
909
|
}
|
|
672
910
|
|
|
673
|
-
const
|
|
674
|
-
|
|
911
|
+
const requestedUserKeyFieldUserKey = readString("userKeyFieldUserKey");
|
|
912
|
+
const resolvedUserKeyFieldName = this.resolveReviewedUserKeyFieldName(
|
|
913
|
+
normalizedFields,
|
|
914
|
+
requestedUserKeyFieldUserKey,
|
|
915
|
+
mappingDto.userKeyField,
|
|
916
|
+
);
|
|
917
|
+
const inheritedGeneratedIdUserKey = this.isGeneratedIdInheritedUserKey(
|
|
918
|
+
mappingDto.legacyTableType,
|
|
919
|
+
requestedUserKeyFieldUserKey,
|
|
920
|
+
);
|
|
921
|
+
if (!resolvedUserKeyFieldName && !inheritedGeneratedIdUserKey) {
|
|
675
922
|
throw new BadRequestException("Reviewed metadata JSON must keep the selected userKeyFieldUserKey in the fields array.");
|
|
676
923
|
}
|
|
677
924
|
|
|
925
|
+
this.validateReviewedPrimaryKeyFields(normalizedFields, tableColumns);
|
|
926
|
+
|
|
927
|
+
const userKeyFieldUserKey = resolvedUserKeyFieldName ?? requestedUserKeyFieldUserKey;
|
|
928
|
+
|
|
678
929
|
return {
|
|
679
930
|
singularName: readString("singularName"),
|
|
680
931
|
pluralName: readString("pluralName"),
|
|
@@ -693,11 +944,54 @@ export class DatasourceIntrospectionService {
|
|
|
693
944
|
internationalisation: readBoolean("internationalisation"),
|
|
694
945
|
fields: normalizedFields.map((field) => ({
|
|
695
946
|
...field,
|
|
696
|
-
isUserKey: field.name ===
|
|
947
|
+
isUserKey: field.name === resolvedUserKeyFieldName,
|
|
697
948
|
})),
|
|
698
949
|
};
|
|
699
950
|
}
|
|
700
951
|
|
|
952
|
+
private resolveReviewedUserKeyFieldName(
|
|
953
|
+
reviewedFields: Array<Record<string, any>>,
|
|
954
|
+
requestedUserKeyFieldName: string,
|
|
955
|
+
fallbackUserKeyFieldName?: string,
|
|
956
|
+
) {
|
|
957
|
+
const activeFields = reviewedFields.filter((field) => !field.isMarkedForRemoval);
|
|
958
|
+
const normalizedRequested = `${requestedUserKeyFieldName ?? ""}`.trim().toLowerCase();
|
|
959
|
+
const normalizedFallback = `${fallbackUserKeyFieldName ?? ""}`.trim().toLowerCase();
|
|
960
|
+
|
|
961
|
+
const byExactRequested = activeFields.find((field) => field.name?.toLowerCase?.() === normalizedRequested);
|
|
962
|
+
if (byExactRequested) {
|
|
963
|
+
return byExactRequested.name;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const byExistingUserKeyFlag = activeFields.find((field) => field.isUserKey);
|
|
967
|
+
if (byExistingUserKeyFlag) {
|
|
968
|
+
return byExistingUserKeyFlag.name;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
const byPrimaryKeyFallback = activeFields.find((field) => {
|
|
972
|
+
if (!field.isPrimaryKey) {
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const normalizedName = `${field.name ?? ""}`.trim().toLowerCase();
|
|
977
|
+
return normalizedName === normalizedFallback || normalizedName === "id" || normalizedName === "legacyid";
|
|
978
|
+
});
|
|
979
|
+
if (byPrimaryKeyFallback) {
|
|
980
|
+
return byPrimaryKeyFallback.name;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const byFallbackFieldName = activeFields.find((field) => field.name?.toLowerCase?.() === normalizedFallback);
|
|
984
|
+
return byFallbackFieldName?.name ?? null;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
private isGeneratedIdInheritedUserKey(
|
|
988
|
+
legacyTableType: LegacyTableType,
|
|
989
|
+
requestedUserKeyFieldName: string | null | undefined,
|
|
990
|
+
) {
|
|
991
|
+
return legacyTableType === LegacyTableType.GENERATED_ID
|
|
992
|
+
&& `${requestedUserKeyFieldName ?? ""}`.trim().toLowerCase() === "id";
|
|
993
|
+
}
|
|
994
|
+
|
|
701
995
|
private normalizeReviewedField(field: any, index: number) {
|
|
702
996
|
if (!field || typeof field !== "object" || Array.isArray(field)) {
|
|
703
997
|
throw new BadRequestException(`Reviewed metadata JSON field at index ${index} is not a valid object.`);
|
|
@@ -762,34 +1056,39 @@ export class DatasourceIntrospectionService {
|
|
|
762
1056
|
existingField?: any,
|
|
763
1057
|
) {
|
|
764
1058
|
const preserved = existingField ? this.sanitizeExistingFieldPayload(existingField) : {};
|
|
1059
|
+
const configured = this.sanitizeConfiguredFieldPayload(column.fieldConfig);
|
|
1060
|
+
const merged = {
|
|
1061
|
+
...preserved,
|
|
1062
|
+
...configured,
|
|
1063
|
+
};
|
|
765
1064
|
const maxLength =
|
|
766
1065
|
column.characterMaximumLength && ["shortText", "longText"].includes(column.solidFieldType)
|
|
767
1066
|
? column.characterMaximumLength
|
|
768
|
-
:
|
|
1067
|
+
: merged.max;
|
|
769
1068
|
|
|
770
1069
|
return {
|
|
771
|
-
...
|
|
1070
|
+
...merged,
|
|
772
1071
|
...(column.fieldId ? { id: column.fieldId } : {}),
|
|
773
1072
|
name: column.fieldName,
|
|
774
|
-
displayName:
|
|
775
|
-
description:
|
|
1073
|
+
displayName: column.displayName?.trim() || merged.displayName || startCase(column.fieldName),
|
|
1074
|
+
description: merged.description ?? "",
|
|
776
1075
|
type: column.solidFieldType,
|
|
777
1076
|
ormType: column.ormType,
|
|
778
|
-
length:
|
|
779
|
-
defaultValue:
|
|
780
|
-
required:
|
|
781
|
-
unique:
|
|
782
|
-
encrypt:
|
|
783
|
-
index:
|
|
1077
|
+
length: merged.length ?? column.characterMaximumLength ?? undefined,
|
|
1078
|
+
defaultValue: merged.defaultValue ?? column.defaultValue ?? undefined,
|
|
1079
|
+
required: merged.required ?? (!column.nullable || column.isPrimaryKey),
|
|
1080
|
+
unique: merged.unique ?? false,
|
|
1081
|
+
encrypt: merged.encrypt ?? false,
|
|
1082
|
+
index: merged.index ?? false,
|
|
784
1083
|
max: maxLength ?? undefined,
|
|
785
|
-
min:
|
|
786
|
-
private:
|
|
787
|
-
isSystem:
|
|
1084
|
+
min: merged.min ?? undefined,
|
|
1085
|
+
private: merged.private ?? false,
|
|
1086
|
+
isSystem: merged.isSystem ?? false,
|
|
788
1087
|
isMarkedForRemoval: false,
|
|
789
1088
|
columnName: column.columnName,
|
|
790
1089
|
isUserKey: mappingDto.userKeyField === column.fieldName,
|
|
791
|
-
enableAuditTracking:
|
|
792
|
-
isMultiSelect:
|
|
1090
|
+
enableAuditTracking: merged.enableAuditTracking ?? mappingDto.enableAuditTracking,
|
|
1091
|
+
isMultiSelect: merged.isMultiSelect ?? false,
|
|
793
1092
|
isPrimaryKey: column.isPrimaryKey,
|
|
794
1093
|
};
|
|
795
1094
|
}
|
|
@@ -891,11 +1190,8 @@ export class DatasourceIntrospectionService {
|
|
|
891
1190
|
|
|
892
1191
|
const upColumnStatements = missingSystemColumns.map((definition) => {
|
|
893
1192
|
const columnLiteral = this.renderTableColumnLiteral(definition);
|
|
894
|
-
return
|
|
895
|
-
`
|
|
896
|
-
` await queryRunner.addColumn(tableName, new TableColumn(${columnLiteral}));`,
|
|
897
|
-
" }",
|
|
898
|
-
].join("\n");
|
|
1193
|
+
return ` if (!(await queryRunner.hasColumn(tableName, ${JSON.stringify(definition.columnName)})))\n`
|
|
1194
|
+
+ ` await queryRunner.addColumn(tableName, new TableColumn(${columnLiteral}));`;
|
|
899
1195
|
}).join("\n\n");
|
|
900
1196
|
|
|
901
1197
|
const postUpStatements: string[] = [];
|
|
@@ -904,12 +1200,11 @@ export class DatasourceIntrospectionService {
|
|
|
904
1200
|
[
|
|
905
1201
|
" {",
|
|
906
1202
|
" const table = await queryRunner.getTable(tableName);",
|
|
907
|
-
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(deletedAtIndexName)}))
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
" }",
|
|
1203
|
+
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(deletedAtIndexName)}))`,
|
|
1204
|
+
` await queryRunner.createIndex(tableName, new TableIndex(${this.renderTableIndexLiteral({
|
|
1205
|
+
name: deletedAtIndexName,
|
|
1206
|
+
columnNames: ["ss_deleted_at"],
|
|
1207
|
+
})}));`,
|
|
913
1208
|
" }",
|
|
914
1209
|
].join("\n"),
|
|
915
1210
|
);
|
|
@@ -919,13 +1214,12 @@ export class DatasourceIntrospectionService {
|
|
|
919
1214
|
[
|
|
920
1215
|
" {",
|
|
921
1216
|
" const table = await queryRunner.getTable(tableName);",
|
|
922
|
-
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(generatedIdUniqueName)}))
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
" }",
|
|
1217
|
+
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(generatedIdUniqueName)}))`,
|
|
1218
|
+
` await queryRunner.createIndex(tableName, new TableIndex(${this.renderTableIndexLiteral({
|
|
1219
|
+
name: generatedIdUniqueName,
|
|
1220
|
+
columnNames: ["ss_id"],
|
|
1221
|
+
isUnique: true,
|
|
1222
|
+
})}));`,
|
|
929
1223
|
" }",
|
|
930
1224
|
].join("\n"),
|
|
931
1225
|
);
|
|
@@ -938,9 +1232,8 @@ export class DatasourceIntrospectionService {
|
|
|
938
1232
|
" {",
|
|
939
1233
|
" const table = await queryRunner.getTable(tableName);",
|
|
940
1234
|
` const index = table?.indices.find((entry) => entry.name === ${JSON.stringify(generatedIdUniqueName)});`,
|
|
941
|
-
" if (index)
|
|
1235
|
+
" if (index)",
|
|
942
1236
|
" await queryRunner.dropIndex(tableName, index);",
|
|
943
|
-
" }",
|
|
944
1237
|
" }",
|
|
945
1238
|
].join("\n"),
|
|
946
1239
|
);
|
|
@@ -951,9 +1244,8 @@ export class DatasourceIntrospectionService {
|
|
|
951
1244
|
" {",
|
|
952
1245
|
" const table = await queryRunner.getTable(tableName);",
|
|
953
1246
|
` const index = table?.indices.find((entry) => entry.name === ${JSON.stringify(deletedAtIndexName)});`,
|
|
954
|
-
" if (index)
|
|
1247
|
+
" if (index)",
|
|
955
1248
|
" await queryRunner.dropIndex(tableName, index);",
|
|
956
|
-
" }",
|
|
957
1249
|
" }",
|
|
958
1250
|
].join("\n"),
|
|
959
1251
|
);
|
|
@@ -962,11 +1254,8 @@ export class DatasourceIntrospectionService {
|
|
|
962
1254
|
...missingSystemColumns
|
|
963
1255
|
.slice()
|
|
964
1256
|
.reverse()
|
|
965
|
-
.map((definition) =>
|
|
966
|
-
`
|
|
967
|
-
` await queryRunner.dropColumn(tableName, ${JSON.stringify(definition.columnName)});`,
|
|
968
|
-
" }",
|
|
969
|
-
].join("\n")),
|
|
1257
|
+
.map((definition) => ` if (await queryRunner.hasColumn(tableName, ${JSON.stringify(definition.columnName)}))\n`
|
|
1258
|
+
+ ` await queryRunner.dropColumn(tableName, ${JSON.stringify(definition.columnName)});`),
|
|
970
1259
|
);
|
|
971
1260
|
|
|
972
1261
|
return [
|
|
@@ -992,29 +1281,41 @@ export class DatasourceIntrospectionService {
|
|
|
992
1281
|
|
|
993
1282
|
private renderTableColumnLiteral(definition: SystemColumnDefinition) {
|
|
994
1283
|
const properties: string[] = [
|
|
995
|
-
`
|
|
996
|
-
`
|
|
1284
|
+
`name: ${JSON.stringify(definition.columnName)}`,
|
|
1285
|
+
`type: ${JSON.stringify(definition.type)}`,
|
|
997
1286
|
];
|
|
998
1287
|
|
|
999
1288
|
if (definition.length) {
|
|
1000
|
-
properties.push(`
|
|
1289
|
+
properties.push(`length: ${JSON.stringify(definition.length)}`);
|
|
1001
1290
|
}
|
|
1002
1291
|
if (typeof definition.isNullable === "boolean") {
|
|
1003
|
-
properties.push(`
|
|
1292
|
+
properties.push(`isNullable: ${definition.isNullable}`);
|
|
1004
1293
|
}
|
|
1005
1294
|
if (definition.defaultExpression) {
|
|
1006
|
-
properties.push(`
|
|
1295
|
+
properties.push(`default: ${JSON.stringify(definition.defaultExpression)}`);
|
|
1007
1296
|
}
|
|
1008
1297
|
if (definition.isGenerated) {
|
|
1009
|
-
properties.push("
|
|
1010
|
-
properties.push("
|
|
1298
|
+
properties.push("isGenerated: true");
|
|
1299
|
+
properties.push("generationStrategy: \"increment\"");
|
|
1011
1300
|
}
|
|
1012
1301
|
if (definition.isUnique) {
|
|
1013
|
-
properties.push("
|
|
1302
|
+
properties.push("isUnique: true");
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
return `{ ${properties.join(", ")}, }`;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
private renderTableIndexLiteral(definition: { name: string; columnNames: string[]; isUnique?: boolean }) {
|
|
1309
|
+
const properties: string[] = [
|
|
1310
|
+
`name: ${JSON.stringify(definition.name)}`,
|
|
1311
|
+
`columnNames: ${JSON.stringify(definition.columnNames)}`,
|
|
1312
|
+
];
|
|
1313
|
+
|
|
1314
|
+
if (definition.isUnique) {
|
|
1315
|
+
properties.push("isUnique: true");
|
|
1014
1316
|
}
|
|
1015
1317
|
|
|
1016
|
-
properties.
|
|
1017
|
-
return properties.join("\n");
|
|
1318
|
+
return `{ ${properties.join(", ")}, }`;
|
|
1018
1319
|
}
|
|
1019
1320
|
|
|
1020
1321
|
private buildMigrationObjectName(prefix: string, tableName: string, columnName: string) {
|
|
@@ -1050,13 +1351,15 @@ export class DatasourceIntrospectionService {
|
|
|
1050
1351
|
private async buildTableInventory(datasource: DatasourceRecord) {
|
|
1051
1352
|
const provider = this.getProvider(datasource.type);
|
|
1052
1353
|
const runtimeDataSource = this.resolveDataSourceByName(datasource.name);
|
|
1053
|
-
const [
|
|
1354
|
+
const [rawTables, rawColumns, models] = await Promise.all([
|
|
1054
1355
|
provider.listTables(runtimeDataSource),
|
|
1055
1356
|
provider.listColumns(runtimeDataSource),
|
|
1056
1357
|
this.modelMetadataRepository.find({ relations: { module: true } as any }),
|
|
1057
1358
|
]);
|
|
1359
|
+
const tables = rawTables.filter((table) => !this.isSolidxInternalTableName(table.tableName));
|
|
1360
|
+
const columns = rawColumns.filter((column) => !this.isSolidxInternalTableName(column.tableName));
|
|
1058
1361
|
|
|
1059
|
-
const
|
|
1362
|
+
const modelLookup = this.buildModelLookup(models);
|
|
1060
1363
|
const columnsByKey = this.groupColumnsByTable(columns);
|
|
1061
1364
|
const tableKeys = new Set<string>();
|
|
1062
1365
|
|
|
@@ -1080,8 +1383,10 @@ export class DatasourceIntrospectionService {
|
|
|
1080
1383
|
|
|
1081
1384
|
const suggestedLegacyTableType = this.detectLegacyTableType(datasource.type, tableColumns);
|
|
1082
1385
|
const suggestedIdColumn = this.getSuggestedIdColumn(datasource.type, tableColumns, suggestedLegacyTableType);
|
|
1083
|
-
const mappedModel =
|
|
1386
|
+
const mappedModel = this.resolveMappedModel(modelLookup, datasource.name, tableName);
|
|
1084
1387
|
const physicalColumns = tableColumns.filter((column) => !this.isHandledBySuperclass(column.columnName, suggestedLegacyTableType));
|
|
1388
|
+
const primaryKeyColumns = tableColumns.filter((column) => column.isPrimaryKey);
|
|
1389
|
+
const hasPrimaryKey = primaryKeyColumns.length > 0;
|
|
1085
1390
|
|
|
1086
1391
|
return {
|
|
1087
1392
|
schema,
|
|
@@ -1099,6 +1404,12 @@ export class DatasourceIntrospectionService {
|
|
|
1099
1404
|
suggestedUserKeyField: this.buildUserKeyCandidates(tableColumns)[0]?.name ?? "id",
|
|
1100
1405
|
columnCount: tableColumns.length,
|
|
1101
1406
|
physicalColumnCount: physicalColumns.length,
|
|
1407
|
+
hasPrimaryKey,
|
|
1408
|
+
primaryKeyColumnCount: primaryKeyColumns.length,
|
|
1409
|
+
primaryKeyColumnNames: primaryKeyColumns.map((column) => column.columnName),
|
|
1410
|
+
mappingBlockedReason: hasPrimaryKey
|
|
1411
|
+
? null
|
|
1412
|
+
: this.buildMissingPrimaryKeyMessage(tableName),
|
|
1102
1413
|
} satisfies TableInventoryRecord;
|
|
1103
1414
|
})
|
|
1104
1415
|
.filter((record): record is TableInventoryRecord => Boolean(record))
|
|
@@ -1119,19 +1430,52 @@ export class DatasourceIntrospectionService {
|
|
|
1119
1430
|
};
|
|
1120
1431
|
}
|
|
1121
1432
|
|
|
1122
|
-
private
|
|
1123
|
-
const
|
|
1433
|
+
private buildModelLookup(models: ModelMetadata[]) {
|
|
1434
|
+
const byDatasourceAndTable = new Map<string, ModelMetadata>();
|
|
1435
|
+
const byTableName = new Map<string, ModelMetadata[]>();
|
|
1124
1436
|
|
|
1125
1437
|
for (const model of models) {
|
|
1126
1438
|
if (!model.tableName) {
|
|
1127
1439
|
continue;
|
|
1128
1440
|
}
|
|
1129
1441
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1442
|
+
byDatasourceAndTable.set(this.toModelMapKey(model.dataSource, model.tableName), model);
|
|
1443
|
+
|
|
1444
|
+
const normalizedTableName = model.tableName.trim().toLowerCase();
|
|
1445
|
+
const bucket = byTableName.get(normalizedTableName);
|
|
1446
|
+
if (bucket) {
|
|
1447
|
+
bucket.push(model);
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
byTableName.set(normalizedTableName, [model]);
|
|
1132
1452
|
}
|
|
1133
1453
|
|
|
1134
|
-
return
|
|
1454
|
+
return {
|
|
1455
|
+
byDatasourceAndTable,
|
|
1456
|
+
byTableName,
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
private resolveMappedModel(
|
|
1461
|
+
lookup: {
|
|
1462
|
+
byDatasourceAndTable: Map<string, ModelMetadata>;
|
|
1463
|
+
byTableName: Map<string, ModelMetadata[]>;
|
|
1464
|
+
},
|
|
1465
|
+
datasourceName: string,
|
|
1466
|
+
tableName: string,
|
|
1467
|
+
) {
|
|
1468
|
+
const exactMatch = lookup.byDatasourceAndTable.get(this.toModelMapKey(datasourceName, tableName));
|
|
1469
|
+
if (exactMatch) {
|
|
1470
|
+
return exactMatch;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
const fallbackMatches = lookup.byTableName.get(tableName.trim().toLowerCase()) ?? [];
|
|
1474
|
+
if (fallbackMatches.length === 1) {
|
|
1475
|
+
return fallbackMatches[0];
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
return fallbackMatches[0] ?? null;
|
|
1135
1479
|
}
|
|
1136
1480
|
|
|
1137
1481
|
private groupColumnsByTable(columns: DatasourceIntrospectionColumn[]) {
|
|
@@ -1151,6 +1495,10 @@ export class DatasourceIntrospectionService {
|
|
|
1151
1495
|
return grouped;
|
|
1152
1496
|
}
|
|
1153
1497
|
|
|
1498
|
+
private isSolidxInternalTableName(tableName: string | null | undefined) {
|
|
1499
|
+
return typeof tableName === "string" && tableName.toLowerCase().startsWith("ss_");
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1154
1502
|
private detectLegacyTableType(datasourceType: string, columns: DatasourceIntrospectionColumn[]): LegacyTableType {
|
|
1155
1503
|
const normalizedDatasourceType = datasourceType.toLowerCase();
|
|
1156
1504
|
const hasExistingGeneratedId = columns.some((column) => column.isAutoGenerated);
|
|
@@ -1239,13 +1587,17 @@ export class DatasourceIntrospectionService {
|
|
|
1239
1587
|
datasourceType: string,
|
|
1240
1588
|
): TableDetailColumnRecord {
|
|
1241
1589
|
const superclassFieldName = this.getSuperclassFieldName(column.columnName, legacyTableType);
|
|
1590
|
+
const normalizedColumnName = column.columnName.toLowerCase();
|
|
1242
1591
|
const fieldName = legacyTableType === LegacyTableType.EXISTING_ID && column.isAutoGenerated
|
|
1243
1592
|
? "id"
|
|
1244
|
-
:
|
|
1593
|
+
: legacyTableType === LegacyTableType.GENERATED_ID && normalizedColumnName === "id"
|
|
1594
|
+
? "legacyId"
|
|
1595
|
+
: this.toFieldName(column.columnName);
|
|
1245
1596
|
|
|
1246
1597
|
return {
|
|
1247
1598
|
columnName: column.columnName,
|
|
1248
1599
|
fieldName,
|
|
1600
|
+
displayName: startCase(fieldName),
|
|
1249
1601
|
dataType: column.dataType,
|
|
1250
1602
|
solidFieldType: this.toSolidFieldType(column),
|
|
1251
1603
|
ormType: this.toOrmFieldType(column, datasourceType),
|
|
@@ -1256,11 +1608,72 @@ export class DatasourceIntrospectionService {
|
|
|
1256
1608
|
numericScale: column.numericScale,
|
|
1257
1609
|
ordinalPosition: column.ordinalPosition,
|
|
1258
1610
|
isAutoGenerated: column.isAutoGenerated,
|
|
1611
|
+
isPrimaryKey: column.isPrimaryKey,
|
|
1259
1612
|
handledBySuperclass: Boolean(superclassFieldName),
|
|
1260
1613
|
superclassFieldName,
|
|
1261
1614
|
};
|
|
1262
1615
|
}
|
|
1263
1616
|
|
|
1617
|
+
private validateTablePrimaryKeyForMapping(
|
|
1618
|
+
tableRecord: TableInventoryRecord,
|
|
1619
|
+
tableColumns: DatasourceIntrospectionColumn[],
|
|
1620
|
+
mappingDto: DatasourceIntrospectionMappingDto,
|
|
1621
|
+
) {
|
|
1622
|
+
const detectedPrimaryKeyColumns = tableColumns.filter((column) => column.isPrimaryKey);
|
|
1623
|
+
if (!detectedPrimaryKeyColumns.length) {
|
|
1624
|
+
throw new BadRequestException(tableRecord.mappingBlockedReason ?? this.buildMissingPrimaryKeyMessage(tableRecord.tableName));
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
const requestColumnsByName = new Map(
|
|
1628
|
+
mappingDto.columns.map((column) => [column.columnName.trim().toLowerCase(), column]),
|
|
1629
|
+
);
|
|
1630
|
+
|
|
1631
|
+
for (const primaryKeyColumn of detectedPrimaryKeyColumns) {
|
|
1632
|
+
const matchingRequestColumn = requestColumnsByName.get(primaryKeyColumn.columnName.trim().toLowerCase());
|
|
1633
|
+
if (!matchingRequestColumn) {
|
|
1634
|
+
throw new BadRequestException(`Primary key column "${primaryKeyColumn.columnName}" must remain present in the mapping request for table "${tableRecord.tableName}".`);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
if (!matchingRequestColumn.include) {
|
|
1638
|
+
throw new BadRequestException(`Primary key column "${primaryKeyColumn.columnName}" cannot be excluded from the mapping for table "${tableRecord.tableName}".`);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
if (!matchingRequestColumn.isPrimaryKey) {
|
|
1642
|
+
throw new BadRequestException(`Primary key column "${primaryKeyColumn.columnName}" must stay marked as isPrimaryKey for table "${tableRecord.tableName}".`);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
private validateReviewedPrimaryKeyFields(
|
|
1648
|
+
reviewedFields: Array<Record<string, any>>,
|
|
1649
|
+
tableColumns: DatasourceIntrospectionColumn[],
|
|
1650
|
+
) {
|
|
1651
|
+
const detectedPrimaryKeyColumns = tableColumns.filter((column) => column.isPrimaryKey);
|
|
1652
|
+
if (!detectedPrimaryKeyColumns.length) {
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
const activeReviewedFields = reviewedFields.filter((field) => !field.isMarkedForRemoval);
|
|
1657
|
+
|
|
1658
|
+
for (const primaryKeyColumn of detectedPrimaryKeyColumns) {
|
|
1659
|
+
const matchingReviewedField = activeReviewedFields.find((field) =>
|
|
1660
|
+
`${field.columnName ?? ""}`.trim().toLowerCase() === primaryKeyColumn.columnName.trim().toLowerCase(),
|
|
1661
|
+
);
|
|
1662
|
+
|
|
1663
|
+
if (!matchingReviewedField) {
|
|
1664
|
+
throw new BadRequestException(`Reviewed metadata JSON must keep datasource primary key column "${primaryKeyColumn.columnName}" in the fields array.`);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
if (!matchingReviewedField.isPrimaryKey) {
|
|
1668
|
+
throw new BadRequestException(`Reviewed metadata JSON must keep datasource primary key column "${primaryKeyColumn.columnName}" marked with isPrimaryKey: true.`);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
private buildMissingPrimaryKeyMessage(tableName: string) {
|
|
1674
|
+
return `Table "${tableName}" cannot be mapped because the datasource table does not define any primary key. Add a real primary key to the table before continuing with SolidX mapping.`;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1264
1677
|
private getSuperclassFieldName(columnName: string, legacyTableType: LegacyTableType) {
|
|
1265
1678
|
return this.getLegacySuperclassFieldName(columnName, legacyTableType);
|
|
1266
1679
|
}
|
|
@@ -1298,109 +1711,80 @@ export class DatasourceIntrospectionService {
|
|
|
1298
1711
|
return "LegacyCommonEntityWithExistingId";
|
|
1299
1712
|
}
|
|
1300
1713
|
|
|
1301
|
-
private
|
|
1302
|
-
const normalized =
|
|
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
|
-
}
|
|
1714
|
+
private getDatasourceTypeFamily(dataType: string): DatasourceTypeFamily | null {
|
|
1715
|
+
const normalized = dataType.toLowerCase();
|
|
1312
1716
|
|
|
1313
|
-
|
|
1314
|
-
|
|
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";
|
|
1717
|
+
for (const [family, aliases] of Object.entries(DATASOURCE_TYPE_FAMILY_ALIASES) as Array<[DatasourceTypeFamily, string[]]>) {
|
|
1718
|
+
if (aliases.includes(normalized)) {
|
|
1719
|
+
return family;
|
|
1720
|
+
}
|
|
1323
1721
|
}
|
|
1324
1722
|
|
|
1325
|
-
if (
|
|
1326
|
-
return "
|
|
1723
|
+
if (normalized.includes("json")) {
|
|
1724
|
+
return "json";
|
|
1327
1725
|
}
|
|
1328
1726
|
|
|
1329
|
-
if (
|
|
1727
|
+
if (normalized.includes("date")) {
|
|
1330
1728
|
return "datetime";
|
|
1331
1729
|
}
|
|
1332
1730
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
if (["uniqueidentifier", "uuid"].includes(normalized)) {
|
|
1338
|
-
return "uuid";
|
|
1339
|
-
}
|
|
1731
|
+
return null;
|
|
1732
|
+
}
|
|
1340
1733
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1734
|
+
private toSolidFieldType(column: DatasourceIntrospectionColumn) {
|
|
1735
|
+
const family = this.getDatasourceTypeFamily(column.dataType);
|
|
1736
|
+
const charLength = column.characterMaximumLength ?? null;
|
|
1344
1737
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1738
|
+
switch (family) {
|
|
1739
|
+
case "boolean":
|
|
1740
|
+
return "boolean";
|
|
1741
|
+
case "bigint":
|
|
1742
|
+
return "bigint";
|
|
1743
|
+
case "int":
|
|
1744
|
+
return "int";
|
|
1745
|
+
case "numeric":
|
|
1746
|
+
case "decimal":
|
|
1747
|
+
case "float":
|
|
1748
|
+
return "decimal";
|
|
1749
|
+
case "date":
|
|
1750
|
+
return "date";
|
|
1751
|
+
case "time":
|
|
1752
|
+
return "time";
|
|
1753
|
+
case "datetime":
|
|
1754
|
+
case "datetimeWithTimezone":
|
|
1755
|
+
return "datetime";
|
|
1756
|
+
case "json":
|
|
1757
|
+
case "jsonb":
|
|
1758
|
+
return "json";
|
|
1759
|
+
case "uuid":
|
|
1760
|
+
return "uuid";
|
|
1761
|
+
case "mediumText":
|
|
1762
|
+
case "longText":
|
|
1347
1763
|
return "longText";
|
|
1348
|
-
|
|
1764
|
+
case "shortText":
|
|
1765
|
+
case "unicodeShortText":
|
|
1766
|
+
if (charLength !== null && charLength > 255) {
|
|
1767
|
+
return "longText";
|
|
1768
|
+
}
|
|
1349
1769
|
|
|
1350
|
-
|
|
1770
|
+
return "shortText";
|
|
1771
|
+
default:
|
|
1772
|
+
return "shortText";
|
|
1351
1773
|
}
|
|
1352
|
-
|
|
1353
|
-
return "shortText";
|
|
1354
1774
|
}
|
|
1355
1775
|
|
|
1356
1776
|
private toOrmFieldType(column: DatasourceIntrospectionColumn, datasourceType: string) {
|
|
1357
|
-
const
|
|
1777
|
+
const family = this.getDatasourceTypeFamily(column.dataType);
|
|
1358
1778
|
const normalizedDatasourceType = datasourceType.toLowerCase();
|
|
1779
|
+
const ormType = family
|
|
1780
|
+
? ORM_TYPE_BY_DATASOURCE_FAMILY[normalizedDatasourceType]?.[family]
|
|
1781
|
+
: undefined;
|
|
1782
|
+
|
|
1783
|
+
if (ormType) {
|
|
1784
|
+
return ormType;
|
|
1785
|
+
}
|
|
1359
1786
|
|
|
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";
|
|
1787
|
+
return normalizedDatasourceType === "postgres" ? "varchar" : "varchar";
|
|
1404
1788
|
}
|
|
1405
1789
|
|
|
1406
1790
|
private toFieldName(columnName: string) {
|
|
@@ -1449,6 +1833,285 @@ export class DatasourceIntrospectionService {
|
|
|
1449
1833
|
return `${datasourceName.toLowerCase()}::${tableName.toLowerCase()}`;
|
|
1450
1834
|
}
|
|
1451
1835
|
|
|
1836
|
+
private async syncTypeormDatasourceFile(moduleName: string, datasource: DatasourceRecord, singularName: string) {
|
|
1837
|
+
const datasourceFilePath = path.resolve(process.cwd(), "src", `typeorm-${datasource.name}-datasource.ts`);
|
|
1838
|
+
const entityClassName = classify(singularName);
|
|
1839
|
+
const entityImportPath = `./${moduleName}/entities/${kebabCase(singularName)}.entity`;
|
|
1840
|
+
const migrationGlob = `join(__dirname, './${moduleName}/migrations/${datasource.name}/*.{ts,js}')`;
|
|
1841
|
+
|
|
1842
|
+
const existingContent = await fs.readFile(datasourceFilePath, "utf8").catch(() => null);
|
|
1843
|
+
if (!existingContent) {
|
|
1844
|
+
const rendered = this.renderTypeormDatasourceTemplate({
|
|
1845
|
+
datasource,
|
|
1846
|
+
entityClassName,
|
|
1847
|
+
entityImportPath,
|
|
1848
|
+
migrationGlob,
|
|
1849
|
+
});
|
|
1850
|
+
await fs.writeFile(datasourceFilePath, rendered, "utf8");
|
|
1851
|
+
return {
|
|
1852
|
+
filePath: datasourceFilePath,
|
|
1853
|
+
created: true,
|
|
1854
|
+
updated: false,
|
|
1855
|
+
entityRegistered: true,
|
|
1856
|
+
migrationGlobRegistered: true,
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
const project = new Project({
|
|
1861
|
+
skipAddingFilesFromTsConfig: true,
|
|
1862
|
+
manipulationSettings: {
|
|
1863
|
+
quoteKind: QuoteKind.Single,
|
|
1864
|
+
indentationText: IndentationText.FourSpaces,
|
|
1865
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
|
1866
|
+
},
|
|
1867
|
+
});
|
|
1868
|
+
const sourceFile = project.createSourceFile(datasourceFilePath, existingContent, { overwrite: true });
|
|
1869
|
+
|
|
1870
|
+
const entityImportDeclaration = sourceFile.getImportDeclarations().find((declaration) =>
|
|
1871
|
+
declaration.getModuleSpecifierValue() === entityImportPath,
|
|
1872
|
+
);
|
|
1873
|
+
if (entityImportDeclaration) {
|
|
1874
|
+
const alreadyImported = entityImportDeclaration.getNamedImports().some((namedImport) => namedImport.getName() === entityClassName);
|
|
1875
|
+
if (!alreadyImported) {
|
|
1876
|
+
entityImportDeclaration.addNamedImport(entityClassName);
|
|
1877
|
+
}
|
|
1878
|
+
} else {
|
|
1879
|
+
sourceFile.addImportDeclaration({
|
|
1880
|
+
moduleSpecifier: entityImportPath,
|
|
1881
|
+
namedImports: [entityClassName],
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
const entitiesArray = sourceFile
|
|
1886
|
+
.getVariableDeclarationOrThrow("entities")
|
|
1887
|
+
.getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
1888
|
+
const entityAlreadyRegistered = this.arrayLiteralContainsValue(entitiesArray, entityClassName);
|
|
1889
|
+
if (!entityAlreadyRegistered) {
|
|
1890
|
+
entitiesArray.addElement(entityClassName);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
const dataSourceOptions = this.getTypeormDatasourceOptions(sourceFile);
|
|
1894
|
+
const migrationsArray = this.getObjectPropertyArrayLiteral(dataSourceOptions, "migrations");
|
|
1895
|
+
const migrationAlreadyRegistered = this.arrayLiteralContainsValue(migrationsArray, migrationGlob);
|
|
1896
|
+
if (!migrationAlreadyRegistered) {
|
|
1897
|
+
migrationsArray.addElement(migrationGlob);
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
sourceFile.organizeImports();
|
|
1901
|
+
sourceFile.formatText();
|
|
1902
|
+
|
|
1903
|
+
const nextContent = sourceFile.getFullText();
|
|
1904
|
+
const updated = nextContent !== existingContent;
|
|
1905
|
+
if (updated) {
|
|
1906
|
+
await fs.writeFile(datasourceFilePath, nextContent, "utf8");
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
return {
|
|
1910
|
+
filePath: datasourceFilePath,
|
|
1911
|
+
created: false,
|
|
1912
|
+
updated,
|
|
1913
|
+
entityRegistered: true,
|
|
1914
|
+
migrationGlobRegistered: true,
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
private async writeMigrationFile(migration: MappingPreviewRecord["migration"]) {
|
|
1919
|
+
if (!migration.willGenerate || !migration.content || !migration.filePath) {
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
await fs.mkdir(path.dirname(migration.filePath), { recursive: true });
|
|
1924
|
+
await fs.writeFile(migration.filePath, migration.content, "utf8");
|
|
1925
|
+
|
|
1926
|
+
const writtenContent = await fs.readFile(migration.filePath, "utf8").catch(() => null);
|
|
1927
|
+
if (writtenContent !== migration.content) {
|
|
1928
|
+
throw new BadRequestException(`Migration file could not be verified after writing: ${migration.filePath}`);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
return true;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
private renderTypeormDatasourceTemplate(input: {
|
|
1935
|
+
datasource: DatasourceRecord;
|
|
1936
|
+
entityClassName: string;
|
|
1937
|
+
entityImportPath: string;
|
|
1938
|
+
migrationGlob: string;
|
|
1939
|
+
}) {
|
|
1940
|
+
const { datasource, entityClassName, entityImportPath, migrationGlob } = input;
|
|
1941
|
+
const envPrefix = datasource.envPrefix?.toUpperCase?.() || "DEFAULT";
|
|
1942
|
+
const portFallback = datasource.type === "postgres" ? 5432 : datasource.type === "mysql" ? 3306 : 1433;
|
|
1943
|
+
const project = new Project({
|
|
1944
|
+
skipAddingFilesFromTsConfig: true,
|
|
1945
|
+
manipulationSettings: {
|
|
1946
|
+
quoteKind: QuoteKind.Single,
|
|
1947
|
+
indentationText: IndentationText.FourSpaces,
|
|
1948
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
|
1949
|
+
},
|
|
1950
|
+
});
|
|
1951
|
+
const sourceFile = project.createSourceFile("typeorm-template.ts", "", { overwrite: true });
|
|
1952
|
+
|
|
1953
|
+
sourceFile.addImportDeclaration({ moduleSpecifier: "reflect-metadata" });
|
|
1954
|
+
sourceFile.addImportDeclaration({ moduleSpecifier: "path", namedImports: ["join"] });
|
|
1955
|
+
sourceFile.addImportDeclaration({
|
|
1956
|
+
moduleSpecifier: "dotenv",
|
|
1957
|
+
namedImports: [{ name: "config", alias: "dotenvConfig" }],
|
|
1958
|
+
});
|
|
1959
|
+
sourceFile.addStatements("dotenvConfig({ path: join(__dirname, '../.env') });");
|
|
1960
|
+
sourceFile.addStatements("");
|
|
1961
|
+
|
|
1962
|
+
sourceFile.addImportDeclaration({ moduleSpecifier: "typeorm", namedImports: ["DataSource"] });
|
|
1963
|
+
sourceFile.addImportDeclaration({
|
|
1964
|
+
moduleSpecifier: "@solidxai/core",
|
|
1965
|
+
namespaceImport: "SolidCoreModuleExports",
|
|
1966
|
+
});
|
|
1967
|
+
sourceFile.addImportDeclaration({
|
|
1968
|
+
moduleSpecifier: "@solidxai/core",
|
|
1969
|
+
namedImports: ["getDynamicModuleNames", "parseBooleanEnv"],
|
|
1970
|
+
});
|
|
1971
|
+
sourceFile.addImportDeclaration({
|
|
1972
|
+
moduleSpecifier: "typeorm",
|
|
1973
|
+
namedImports: ["getMetadataArgsStorage"],
|
|
1974
|
+
});
|
|
1975
|
+
sourceFile.addImportDeclaration({
|
|
1976
|
+
moduleSpecifier: "typeorm-naming-strategies",
|
|
1977
|
+
namedImports: ["SnakeNamingStrategy"],
|
|
1978
|
+
});
|
|
1979
|
+
sourceFile.addImportDeclaration({
|
|
1980
|
+
moduleSpecifier: entityImportPath,
|
|
1981
|
+
namedImports: [entityClassName],
|
|
1982
|
+
});
|
|
1983
|
+
|
|
1984
|
+
sourceFile.addFunction({
|
|
1985
|
+
name: "getEntitiesFromExports",
|
|
1986
|
+
parameters: [{ name: "exports", type: "Record<string, any>" }],
|
|
1987
|
+
statements: (writer) => {
|
|
1988
|
+
writer.writeLine("const metadataStorage = getMetadataArgsStorage();");
|
|
1989
|
+
writer.writeLine("return Object.values(exports).filter((item) =>");
|
|
1990
|
+
writer.indent(() => {
|
|
1991
|
+
writer.writeLine("metadataStorage.tables.some((table) => table.target === item),");
|
|
1992
|
+
});
|
|
1993
|
+
writer.write(");");
|
|
1994
|
+
},
|
|
1995
|
+
});
|
|
1996
|
+
|
|
1997
|
+
sourceFile.addVariableStatement({
|
|
1998
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
1999
|
+
declarations: [
|
|
2000
|
+
{
|
|
2001
|
+
name: "coreEntities",
|
|
2002
|
+
initializer: "getEntitiesFromExports(SolidCoreModuleExports)",
|
|
2003
|
+
},
|
|
2004
|
+
],
|
|
2005
|
+
});
|
|
2006
|
+
sourceFile.addVariableStatement({
|
|
2007
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
2008
|
+
declarations: [
|
|
2009
|
+
{
|
|
2010
|
+
name: "dynamicModules",
|
|
2011
|
+
initializer: "getDynamicModuleNames()",
|
|
2012
|
+
},
|
|
2013
|
+
],
|
|
2014
|
+
});
|
|
2015
|
+
sourceFile.addVariableStatement({
|
|
2016
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
2017
|
+
declarations: [
|
|
2018
|
+
{
|
|
2019
|
+
name: "entities",
|
|
2020
|
+
initializer: `[\n ...coreEntities,\n ${entityClassName},\n]`,
|
|
2021
|
+
},
|
|
2022
|
+
],
|
|
2023
|
+
});
|
|
2024
|
+
sourceFile.addVariableStatement({
|
|
2025
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
2026
|
+
declarations: [
|
|
2027
|
+
{
|
|
2028
|
+
name: "logging",
|
|
2029
|
+
initializer: `parseBooleanEnv('${envPrefix}_DATABASE_LOGGING')`,
|
|
2030
|
+
},
|
|
2031
|
+
],
|
|
2032
|
+
});
|
|
2033
|
+
|
|
2034
|
+
sourceFile.addStatements("// IMPORTANT: synchronize must be false when using migrations");
|
|
2035
|
+
sourceFile.addVariableStatement({
|
|
2036
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
2037
|
+
isExported: true,
|
|
2038
|
+
declarations: [
|
|
2039
|
+
{
|
|
2040
|
+
name: "DefaultDataSource",
|
|
2041
|
+
initializer: (writer) => {
|
|
2042
|
+
writer.write("new DataSource(");
|
|
2043
|
+
writer.inlineBlock(() => {
|
|
2044
|
+
writer.writeLine(`type: '${datasource.type}',`);
|
|
2045
|
+
writer.blankLine();
|
|
2046
|
+
writer.writeLine(`host: process.env.${envPrefix}_DATABASE_HOST,`);
|
|
2047
|
+
writer.writeLine(`port: +(process.env.${envPrefix}_DATABASE_PORT || ${portFallback}),`);
|
|
2048
|
+
writer.writeLine(`username: process.env.${envPrefix}_DATABASE_USER,`);
|
|
2049
|
+
writer.writeLine(`password: process.env.${envPrefix}_DATABASE_PASSWORD,`);
|
|
2050
|
+
writer.writeLine(`database: process.env.${envPrefix}_DATABASE_NAME,`);
|
|
2051
|
+
writer.blankLine();
|
|
2052
|
+
writer.writeLine("entities,");
|
|
2053
|
+
writer.writeLine("migrations: [");
|
|
2054
|
+
writer.indent(() => {
|
|
2055
|
+
writer.writeLine(`${migrationGlob},`);
|
|
2056
|
+
});
|
|
2057
|
+
writer.writeLine("],");
|
|
2058
|
+
writer.writeLine("synchronize: false,");
|
|
2059
|
+
writer.writeLine("logging,");
|
|
2060
|
+
writer.writeLine("namingStrategy: new SnakeNamingStrategy(),");
|
|
2061
|
+
if (datasource.type === "mssql") {
|
|
2062
|
+
writer.writeLine("options: {");
|
|
2063
|
+
writer.indent(() => {
|
|
2064
|
+
writer.writeLine("encrypt: false,");
|
|
2065
|
+
writer.writeLine("trustServerCertificate: true,");
|
|
2066
|
+
});
|
|
2067
|
+
writer.writeLine("},");
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
writer.write(")");
|
|
2071
|
+
},
|
|
2072
|
+
},
|
|
2073
|
+
],
|
|
2074
|
+
});
|
|
2075
|
+
|
|
2076
|
+
sourceFile.organizeImports();
|
|
2077
|
+
sourceFile.formatText();
|
|
2078
|
+
return sourceFile.getFullText();
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
private arrayLiteralContainsValue(arrayLiteral: ArrayLiteralExpression, rawValue: string) {
|
|
2082
|
+
const normalizedTarget = rawValue.replace(/\s+/g, "");
|
|
2083
|
+
return arrayLiteral.getElements().some((element) => element.getText().replace(/\s+/g, "") === normalizedTarget);
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
private getTypeormDatasourceOptions(sourceFile: import("ts-morph").SourceFile) {
|
|
2087
|
+
const defaultDataSourceDeclaration = sourceFile.getVariableDeclarationOrThrow("DefaultDataSource");
|
|
2088
|
+
const initializer = defaultDataSourceDeclaration.getInitializer();
|
|
2089
|
+
if (!initializer || !Node.isNewExpression(initializer)) {
|
|
2090
|
+
throw new BadRequestException("Unable to locate DefaultDataSource initializer in datasource file.");
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
const options = initializer.getArguments()[0];
|
|
2094
|
+
if (!options || !Node.isObjectLiteralExpression(options)) {
|
|
2095
|
+
throw new BadRequestException("Unable to locate DefaultDataSource options object in datasource file.");
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
return options;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
private getObjectPropertyArrayLiteral(objectLiteral: ObjectLiteralExpression, propertyName: string) {
|
|
2102
|
+
const property = objectLiteral.getProperty(propertyName);
|
|
2103
|
+
if (!property || !Node.isPropertyAssignment(property)) {
|
|
2104
|
+
throw new BadRequestException(`Unable to locate "${propertyName}" property in datasource file.`);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
const initializer = property.getInitializer();
|
|
2108
|
+
if (!initializer || !Node.isArrayLiteralExpression(initializer)) {
|
|
2109
|
+
throw new BadRequestException(`Unable to locate "${propertyName}" array in datasource file.`);
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
return initializer;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
1452
2115
|
private buildSynchronizeBlockedMessage(datasource: DatasourceRecord) {
|
|
1453
2116
|
return `Datasource "${datasource.displayName}" has synchronize enabled. Set ${datasource.envPrefix}_DATABASE_SYNCHRONIZE=false before starting legacy table introspection.`;
|
|
1454
2117
|
}
|