@solidxai/core 0.1.11-beta.1 → 0.1.11-beta.11
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/.claude/settings.local.json +16 -0
- package/CLAUDE.md +26 -0
- 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/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 +62 -2
- package/dist/services/datasource-introspection.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection.service.js +654 -186
- 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/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/1.js +6 -0
- 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 +811 -161
- 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/dist-tests/api/authenticate.spec.js +0 -119
- package/dist-tests/api/authenticate.spec.js.map +0 -1
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +0 -97
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +0 -1
- package/dist-tests/api/ping.spec.js +0 -21
- package/dist-tests/api/ping.spec.js.map +0 -1
- package/dist-tests/helpers/auth.js +0 -41
- package/dist-tests/helpers/auth.js.map +0 -1
- package/dist-tests/helpers/env.js +0 -11
- package/dist-tests/helpers/env.js.map +0 -1
- package/docs/agent-hub-grooming.md +0 -301
- package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +0 -438
- package/docs/dashboards/dashboard-curl-smoke-tests.txt +0 -146
- package/docs/dashboards/delete-legacy-dashboard-metadata.sql +0 -172
- package/docs/datasource-introspection-ddl-analysis.md +0 -326
- package/docs/datasource-introspection-implementation-plan.md +0 -306
- package/docs/grouping-enhancements.md +0 -89
- package/docs/java-spring/README.md +0 -3
- package/docs/java-spring/solid-core-module-deep-dive-report.md +0 -1317
- package/docs/module-package-import-handoff.md +0 -691
- package/docs/seed-changes.md +0 -65
- package/docs/test-data-workflow.md +0 -200
- package/docs/type-declaration-import-issue.md +0 -24
- /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,27 +384,116 @@ 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
|
|
|
387
|
+
const persistedModelId = mappingDto.modelId ?? (persistedModel as ModelMetadata | undefined)?.id ?? null;
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
data: {
|
|
391
|
+
modelId: persistedModelId,
|
|
392
|
+
operation: plan.operation,
|
|
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
|
+
|
|
269
408
|
if (plan.migration.willGenerate && plan.migration.content && plan.migration.filePath) {
|
|
270
409
|
await fs.mkdir(path.dirname(plan.migration.filePath), { recursive: true });
|
|
271
410
|
await fs.writeFile(plan.migration.filePath, plan.migration.content, "utf8");
|
|
272
411
|
}
|
|
273
412
|
|
|
274
|
-
const
|
|
413
|
+
const datasourceFile = await this.syncTypeormDatasourceFile(
|
|
414
|
+
module.name,
|
|
415
|
+
datasource,
|
|
416
|
+
plan.modelPayload.singularName,
|
|
417
|
+
);
|
|
275
418
|
|
|
276
419
|
return {
|
|
277
420
|
data: {
|
|
278
|
-
modelId:
|
|
421
|
+
modelId: mappingDto.modelId,
|
|
279
422
|
operation: plan.operation,
|
|
280
|
-
metadataJson: plan.metadataJson,
|
|
281
423
|
migration: {
|
|
282
424
|
...plan.migration,
|
|
283
425
|
written: Boolean(plan.migration.willGenerate && plan.migration.filePath),
|
|
284
426
|
},
|
|
427
|
+
datasourceFile,
|
|
285
428
|
summary: plan.summary,
|
|
286
429
|
},
|
|
287
430
|
};
|
|
288
431
|
}
|
|
289
432
|
|
|
433
|
+
async generateCode(moduleId: number) {
|
|
434
|
+
await this.loadModule(moduleId);
|
|
435
|
+
const output = await this.moduleMetadataService.generateCodeViaCtl(moduleId);
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
data: {
|
|
439
|
+
moduleId,
|
|
440
|
+
output,
|
|
441
|
+
message: "Module code generation completed.",
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async runMigration(moduleId: number, datasourceName: string) {
|
|
447
|
+
const module = await this.loadModule(moduleId);
|
|
448
|
+
const datasource = this.requireDatasource(datasourceName);
|
|
449
|
+
const cwd = path.join(process.cwd(), "..");
|
|
450
|
+
const outputs: string[] = [];
|
|
451
|
+
|
|
452
|
+
const migrationOutput = await this.commandService.executeCommandWithArgs({
|
|
453
|
+
command: "npx",
|
|
454
|
+
args: ["-y", "@solidxai/solidctl@latest", "migration", "-d", datasource.name, "run"],
|
|
455
|
+
cwd,
|
|
456
|
+
});
|
|
457
|
+
outputs.push([
|
|
458
|
+
`npx @solidxai/solidctl@latest migration -d ${datasource.name} run`,
|
|
459
|
+
migrationOutput,
|
|
460
|
+
].filter(Boolean).join("\n"));
|
|
461
|
+
|
|
462
|
+
const buildOutput = await this.commandService.executeCommandWithArgs({
|
|
463
|
+
command: "npx",
|
|
464
|
+
args: ["-y", "@solidxai/solidctl@latest", "build"],
|
|
465
|
+
cwd,
|
|
466
|
+
});
|
|
467
|
+
outputs.push([
|
|
468
|
+
"npx @solidxai/solidctl@latest build",
|
|
469
|
+
buildOutput,
|
|
470
|
+
].filter(Boolean).join("\n"));
|
|
471
|
+
|
|
472
|
+
const seedOutput = await this.commandService.executeCommandWithArgs({
|
|
473
|
+
command: "npx",
|
|
474
|
+
args: ["-y", "@solidxai/solidctl@latest", "seed", "--modules-to-seed", module.name],
|
|
475
|
+
cwd,
|
|
476
|
+
});
|
|
477
|
+
outputs.push([
|
|
478
|
+
`npx @solidxai/solidctl@latest seed --modules-to-seed ${module.name}`,
|
|
479
|
+
seedOutput,
|
|
480
|
+
].filter(Boolean).join("\n"));
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
data: {
|
|
484
|
+
moduleId,
|
|
485
|
+
datasource: datasource.name,
|
|
486
|
+
output: outputs.join("\n\n"),
|
|
487
|
+
command: [
|
|
488
|
+
`npx @solidxai/solidctl@latest migration -d ${datasource.name} run`,
|
|
489
|
+
"npx @solidxai/solidctl@latest build",
|
|
490
|
+
`npx @solidxai/solidctl@latest seed --modules-to-seed ${module.name}`,
|
|
491
|
+
].join("\n"),
|
|
492
|
+
message: `Migration, build, and seed completed successfully for datasource "${datasource.name}".`,
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
290
497
|
private async buildMappingPreview(moduleId: number, mappingDto: DatasourceIntrospectionMappingDto): Promise<MappingPreviewRecord> {
|
|
291
498
|
const module = await this.loadModule(moduleId);
|
|
292
499
|
const datasource = this.requireDatasource(mappingDto.dataSource);
|
|
@@ -301,6 +508,8 @@ export class DatasourceIntrospectionService {
|
|
|
301
508
|
if (!tableRecord) {
|
|
302
509
|
throw new NotFoundException(`Table "${mappingDto.tableName}" was not found on datasource "${datasource.name}".`);
|
|
303
510
|
}
|
|
511
|
+
const tableColumns = inventory.columnsByKey.get(tableKey) ?? [];
|
|
512
|
+
this.validateTablePrimaryKeyForMapping(tableRecord, tableColumns, mappingDto);
|
|
304
513
|
|
|
305
514
|
const existingModel = mappingDto.mapped && mappingDto.modelId
|
|
306
515
|
? await this.modelMetadataRepository.findOne({
|
|
@@ -318,14 +527,14 @@ export class DatasourceIntrospectionService {
|
|
|
318
527
|
throw new NotFoundException(`Mapped model "${mappingDto.modelId}" was not found.`);
|
|
319
528
|
}
|
|
320
529
|
|
|
321
|
-
const reviewedModel = this.normalizeReviewedMetadataModel(mappingDto.reviewedModel, mappingDto);
|
|
530
|
+
const reviewedModel = this.normalizeReviewedMetadataModel(mappingDto.reviewedModel, mappingDto, tableColumns);
|
|
322
531
|
const modelPayload = reviewedModel
|
|
323
532
|
? this.buildModelPayloadFromReviewedModel(moduleId, mappingDto, existingModel, reviewedModel)
|
|
324
533
|
: this.buildModelPayload(moduleId, mappingDto, existingModel);
|
|
325
534
|
const metadataJson = reviewedModel
|
|
326
535
|
? await this.buildMetadataJsonPreviewFromReviewedModel(module.name, reviewedModel)
|
|
327
536
|
: await this.buildMetadataJsonPreview(module.name, mappingDto, existingModel);
|
|
328
|
-
const migration = await this.buildMigrationPreview(module.name, datasource, mappingDto,
|
|
537
|
+
const migration = await this.buildMigrationPreview(module.name, datasource, mappingDto, tableColumns);
|
|
329
538
|
const includedColumnNames = mappingDto.columns
|
|
330
539
|
.filter((column) => !column.handledBySuperclass && column.include)
|
|
331
540
|
.map((column) => column.columnName);
|
|
@@ -338,6 +547,13 @@ export class DatasourceIntrospectionService {
|
|
|
338
547
|
mappedModelId: mappingDto.modelId ?? null,
|
|
339
548
|
metadataJson,
|
|
340
549
|
migration,
|
|
550
|
+
datasourceFile: {
|
|
551
|
+
filePath: path.resolve(process.cwd(), "src", `typeorm-${datasource.name}-datasource.ts`),
|
|
552
|
+
created: false,
|
|
553
|
+
updated: false,
|
|
554
|
+
entityRegistered: false,
|
|
555
|
+
migrationGlobRegistered: false,
|
|
556
|
+
},
|
|
341
557
|
summary: {
|
|
342
558
|
includedColumnNames,
|
|
343
559
|
removedColumnNames,
|
|
@@ -399,6 +615,11 @@ export class DatasourceIntrospectionService {
|
|
|
399
615
|
existingModel: ModelMetadata | null,
|
|
400
616
|
reviewedModel: ReviewedMetadataModel,
|
|
401
617
|
): CreateModelMetadataDto | UpdateModelMetaDataDto {
|
|
618
|
+
const resolvedUserKeyFieldName = this.resolveReviewedUserKeyFieldName(
|
|
619
|
+
reviewedModel.fields,
|
|
620
|
+
reviewedModel.userKeyFieldUserKey,
|
|
621
|
+
mappingDto.userKeyField,
|
|
622
|
+
);
|
|
402
623
|
const systemFieldPayloads = existingModel
|
|
403
624
|
? existingModel.fields
|
|
404
625
|
.filter((field) => field.isSystem)
|
|
@@ -410,7 +631,7 @@ export class DatasourceIntrospectionService {
|
|
|
410
631
|
return {
|
|
411
632
|
...field,
|
|
412
633
|
...(matchedExistingField?.id ? { id: matchedExistingField.id } : {}),
|
|
413
|
-
isUserKey: field.name ===
|
|
634
|
+
isUserKey: field.name === resolvedUserKeyFieldName,
|
|
414
635
|
isSystem: field.isSystem ?? false,
|
|
415
636
|
};
|
|
416
637
|
});
|
|
@@ -444,6 +665,11 @@ export class DatasourceIntrospectionService {
|
|
|
444
665
|
) {
|
|
445
666
|
const filePath = await this.moduleMetadataHelperService.getModuleMetadataFilePath(moduleName);
|
|
446
667
|
const previewFields = await this.buildMetadataPreviewFields(mappingDto, existingModel);
|
|
668
|
+
const resolvedUserKeyFieldName = this.resolveReviewedUserKeyFieldName(
|
|
669
|
+
previewFields,
|
|
670
|
+
mappingDto.userKeyField,
|
|
671
|
+
mappingDto.userKeyField,
|
|
672
|
+
) ?? mappingDto.userKeyField;
|
|
447
673
|
|
|
448
674
|
return {
|
|
449
675
|
filePath,
|
|
@@ -455,7 +681,7 @@ export class DatasourceIntrospectionService {
|
|
|
455
681
|
dataSource: mappingDto.dataSource,
|
|
456
682
|
dataSourceType: mappingDto.dataSourceType,
|
|
457
683
|
tableName: mappingDto.tableName,
|
|
458
|
-
userKeyFieldUserKey:
|
|
684
|
+
userKeyFieldUserKey: resolvedUserKeyFieldName,
|
|
459
685
|
isChild: false,
|
|
460
686
|
legacyTableType: mappingDto.legacyTableType,
|
|
461
687
|
parentModelUserKey: "",
|
|
@@ -523,10 +749,6 @@ export class DatasourceIntrospectionService {
|
|
|
523
749
|
|
|
524
750
|
const serialized = Array.from(finalFields.values())
|
|
525
751
|
.filter((field) => !field.isSystem)
|
|
526
|
-
.map((field) => ({
|
|
527
|
-
...field,
|
|
528
|
-
isUserKey: field.name === mappingDto.userKeyField,
|
|
529
|
-
}))
|
|
530
752
|
.sort((left, right) => {
|
|
531
753
|
const leftOrder = typeof left.id === "number" ? left.id : Number.MAX_SAFE_INTEGER;
|
|
532
754
|
const rightOrder = typeof right.id === "number" ? right.id : Number.MAX_SAFE_INTEGER;
|
|
@@ -542,6 +764,16 @@ export class DatasourceIntrospectionService {
|
|
|
542
764
|
previewFields.push(await this.fieldMetadataService.createFieldConfig(field as any));
|
|
543
765
|
}
|
|
544
766
|
|
|
767
|
+
const resolvedUserKeyFieldName = this.resolveReviewedUserKeyFieldName(
|
|
768
|
+
previewFields,
|
|
769
|
+
mappingDto.userKeyField,
|
|
770
|
+
mappingDto.userKeyField,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
for (const field of previewFields) {
|
|
774
|
+
field.isUserKey = field.name === resolvedUserKeyFieldName;
|
|
775
|
+
}
|
|
776
|
+
|
|
545
777
|
return previewFields;
|
|
546
778
|
}
|
|
547
779
|
|
|
@@ -608,9 +840,18 @@ export class DatasourceIntrospectionService {
|
|
|
608
840
|
return rest;
|
|
609
841
|
}
|
|
610
842
|
|
|
843
|
+
private sanitizeConfiguredFieldPayload(fieldConfig: Record<string, any> | undefined) {
|
|
844
|
+
if (!fieldConfig || typeof fieldConfig !== "object" || Array.isArray(fieldConfig)) {
|
|
845
|
+
return {};
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
return this.sanitizeExistingFieldPayload(fieldConfig);
|
|
849
|
+
}
|
|
850
|
+
|
|
611
851
|
private normalizeReviewedMetadataModel(
|
|
612
852
|
reviewedModel: Record<string, any> | undefined,
|
|
613
853
|
mappingDto: DatasourceIntrospectionMappingDto,
|
|
854
|
+
tableColumns: DatasourceIntrospectionColumn[],
|
|
614
855
|
): ReviewedMetadataModel | null {
|
|
615
856
|
if (!reviewedModel) {
|
|
616
857
|
return null;
|
|
@@ -670,11 +911,24 @@ export class DatasourceIntrospectionService {
|
|
|
670
911
|
fieldNames.add(loweredName);
|
|
671
912
|
}
|
|
672
913
|
|
|
673
|
-
const
|
|
674
|
-
|
|
914
|
+
const requestedUserKeyFieldUserKey = readString("userKeyFieldUserKey");
|
|
915
|
+
const resolvedUserKeyFieldName = this.resolveReviewedUserKeyFieldName(
|
|
916
|
+
normalizedFields,
|
|
917
|
+
requestedUserKeyFieldUserKey,
|
|
918
|
+
mappingDto.userKeyField,
|
|
919
|
+
);
|
|
920
|
+
const inheritedGeneratedIdUserKey = this.isGeneratedIdInheritedUserKey(
|
|
921
|
+
mappingDto.legacyTableType,
|
|
922
|
+
requestedUserKeyFieldUserKey,
|
|
923
|
+
);
|
|
924
|
+
if (!resolvedUserKeyFieldName && !inheritedGeneratedIdUserKey) {
|
|
675
925
|
throw new BadRequestException("Reviewed metadata JSON must keep the selected userKeyFieldUserKey in the fields array.");
|
|
676
926
|
}
|
|
677
927
|
|
|
928
|
+
this.validateReviewedPrimaryKeyFields(normalizedFields, tableColumns);
|
|
929
|
+
|
|
930
|
+
const userKeyFieldUserKey = resolvedUserKeyFieldName ?? requestedUserKeyFieldUserKey;
|
|
931
|
+
|
|
678
932
|
return {
|
|
679
933
|
singularName: readString("singularName"),
|
|
680
934
|
pluralName: readString("pluralName"),
|
|
@@ -693,11 +947,54 @@ export class DatasourceIntrospectionService {
|
|
|
693
947
|
internationalisation: readBoolean("internationalisation"),
|
|
694
948
|
fields: normalizedFields.map((field) => ({
|
|
695
949
|
...field,
|
|
696
|
-
isUserKey: field.name ===
|
|
950
|
+
isUserKey: field.name === resolvedUserKeyFieldName,
|
|
697
951
|
})),
|
|
698
952
|
};
|
|
699
953
|
}
|
|
700
954
|
|
|
955
|
+
private resolveReviewedUserKeyFieldName(
|
|
956
|
+
reviewedFields: Array<Record<string, any>>,
|
|
957
|
+
requestedUserKeyFieldName: string,
|
|
958
|
+
fallbackUserKeyFieldName?: string,
|
|
959
|
+
) {
|
|
960
|
+
const activeFields = reviewedFields.filter((field) => !field.isMarkedForRemoval);
|
|
961
|
+
const normalizedRequested = `${requestedUserKeyFieldName ?? ""}`.trim().toLowerCase();
|
|
962
|
+
const normalizedFallback = `${fallbackUserKeyFieldName ?? ""}`.trim().toLowerCase();
|
|
963
|
+
|
|
964
|
+
const byExactRequested = activeFields.find((field) => field.name?.toLowerCase?.() === normalizedRequested);
|
|
965
|
+
if (byExactRequested) {
|
|
966
|
+
return byExactRequested.name;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const byExistingUserKeyFlag = activeFields.find((field) => field.isUserKey);
|
|
970
|
+
if (byExistingUserKeyFlag) {
|
|
971
|
+
return byExistingUserKeyFlag.name;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const byPrimaryKeyFallback = activeFields.find((field) => {
|
|
975
|
+
if (!field.isPrimaryKey) {
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const normalizedName = `${field.name ?? ""}`.trim().toLowerCase();
|
|
980
|
+
return normalizedName === normalizedFallback || normalizedName === "id" || normalizedName === "legacyid";
|
|
981
|
+
});
|
|
982
|
+
if (byPrimaryKeyFallback) {
|
|
983
|
+
return byPrimaryKeyFallback.name;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const byFallbackFieldName = activeFields.find((field) => field.name?.toLowerCase?.() === normalizedFallback);
|
|
987
|
+
return byFallbackFieldName?.name ?? null;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
private isGeneratedIdInheritedUserKey(
|
|
991
|
+
legacyTableType: LegacyTableType,
|
|
992
|
+
requestedUserKeyFieldName: string | null | undefined,
|
|
993
|
+
) {
|
|
994
|
+
return legacyTableType === LegacyTableType.GENERATED_ID
|
|
995
|
+
&& `${requestedUserKeyFieldName ?? ""}`.trim().toLowerCase() === "id";
|
|
996
|
+
}
|
|
997
|
+
|
|
701
998
|
private normalizeReviewedField(field: any, index: number) {
|
|
702
999
|
if (!field || typeof field !== "object" || Array.isArray(field)) {
|
|
703
1000
|
throw new BadRequestException(`Reviewed metadata JSON field at index ${index} is not a valid object.`);
|
|
@@ -762,34 +1059,39 @@ export class DatasourceIntrospectionService {
|
|
|
762
1059
|
existingField?: any,
|
|
763
1060
|
) {
|
|
764
1061
|
const preserved = existingField ? this.sanitizeExistingFieldPayload(existingField) : {};
|
|
1062
|
+
const configured = this.sanitizeConfiguredFieldPayload(column.fieldConfig);
|
|
1063
|
+
const merged = {
|
|
1064
|
+
...preserved,
|
|
1065
|
+
...configured,
|
|
1066
|
+
};
|
|
765
1067
|
const maxLength =
|
|
766
1068
|
column.characterMaximumLength && ["shortText", "longText"].includes(column.solidFieldType)
|
|
767
1069
|
? column.characterMaximumLength
|
|
768
|
-
:
|
|
1070
|
+
: merged.max;
|
|
769
1071
|
|
|
770
1072
|
return {
|
|
771
|
-
...
|
|
1073
|
+
...merged,
|
|
772
1074
|
...(column.fieldId ? { id: column.fieldId } : {}),
|
|
773
1075
|
name: column.fieldName,
|
|
774
|
-
displayName:
|
|
775
|
-
description:
|
|
1076
|
+
displayName: column.displayName?.trim() || merged.displayName || startCase(column.fieldName),
|
|
1077
|
+
description: merged.description ?? "",
|
|
776
1078
|
type: column.solidFieldType,
|
|
777
1079
|
ormType: column.ormType,
|
|
778
|
-
length:
|
|
779
|
-
defaultValue:
|
|
780
|
-
required:
|
|
781
|
-
unique:
|
|
782
|
-
encrypt:
|
|
783
|
-
index:
|
|
1080
|
+
length: merged.length ?? column.characterMaximumLength ?? undefined,
|
|
1081
|
+
defaultValue: merged.defaultValue ?? column.defaultValue ?? undefined,
|
|
1082
|
+
required: merged.required ?? (!column.nullable || column.isPrimaryKey),
|
|
1083
|
+
unique: merged.unique ?? false,
|
|
1084
|
+
encrypt: merged.encrypt ?? false,
|
|
1085
|
+
index: merged.index ?? false,
|
|
784
1086
|
max: maxLength ?? undefined,
|
|
785
|
-
min:
|
|
786
|
-
private:
|
|
787
|
-
isSystem:
|
|
1087
|
+
min: merged.min ?? undefined,
|
|
1088
|
+
private: merged.private ?? false,
|
|
1089
|
+
isSystem: merged.isSystem ?? false,
|
|
788
1090
|
isMarkedForRemoval: false,
|
|
789
1091
|
columnName: column.columnName,
|
|
790
1092
|
isUserKey: mappingDto.userKeyField === column.fieldName,
|
|
791
|
-
enableAuditTracking:
|
|
792
|
-
isMultiSelect:
|
|
1093
|
+
enableAuditTracking: merged.enableAuditTracking ?? mappingDto.enableAuditTracking,
|
|
1094
|
+
isMultiSelect: merged.isMultiSelect ?? false,
|
|
793
1095
|
isPrimaryKey: column.isPrimaryKey,
|
|
794
1096
|
};
|
|
795
1097
|
}
|
|
@@ -891,11 +1193,8 @@ export class DatasourceIntrospectionService {
|
|
|
891
1193
|
|
|
892
1194
|
const upColumnStatements = missingSystemColumns.map((definition) => {
|
|
893
1195
|
const columnLiteral = this.renderTableColumnLiteral(definition);
|
|
894
|
-
return
|
|
895
|
-
`
|
|
896
|
-
` await queryRunner.addColumn(tableName, new TableColumn(${columnLiteral}));`,
|
|
897
|
-
" }",
|
|
898
|
-
].join("\n");
|
|
1196
|
+
return ` if (!(await queryRunner.hasColumn(tableName, ${JSON.stringify(definition.columnName)})))\n`
|
|
1197
|
+
+ ` await queryRunner.addColumn(tableName, new TableColumn(${columnLiteral}));`;
|
|
899
1198
|
}).join("\n\n");
|
|
900
1199
|
|
|
901
1200
|
const postUpStatements: string[] = [];
|
|
@@ -904,12 +1203,11 @@ export class DatasourceIntrospectionService {
|
|
|
904
1203
|
[
|
|
905
1204
|
" {",
|
|
906
1205
|
" const table = await queryRunner.getTable(tableName);",
|
|
907
|
-
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(deletedAtIndexName)}))
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
" }",
|
|
1206
|
+
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(deletedAtIndexName)}))`,
|
|
1207
|
+
` await queryRunner.createIndex(tableName, new TableIndex(${this.renderTableIndexLiteral({
|
|
1208
|
+
name: deletedAtIndexName,
|
|
1209
|
+
columnNames: ["ss_deleted_at"],
|
|
1210
|
+
})}));`,
|
|
913
1211
|
" }",
|
|
914
1212
|
].join("\n"),
|
|
915
1213
|
);
|
|
@@ -919,13 +1217,12 @@ export class DatasourceIntrospectionService {
|
|
|
919
1217
|
[
|
|
920
1218
|
" {",
|
|
921
1219
|
" const table = await queryRunner.getTable(tableName);",
|
|
922
|
-
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(generatedIdUniqueName)}))
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
" }",
|
|
1220
|
+
` if (table && !table.indices.some((index) => index.name === ${JSON.stringify(generatedIdUniqueName)}))`,
|
|
1221
|
+
` await queryRunner.createIndex(tableName, new TableIndex(${this.renderTableIndexLiteral({
|
|
1222
|
+
name: generatedIdUniqueName,
|
|
1223
|
+
columnNames: ["ss_id"],
|
|
1224
|
+
isUnique: true,
|
|
1225
|
+
})}));`,
|
|
929
1226
|
" }",
|
|
930
1227
|
].join("\n"),
|
|
931
1228
|
);
|
|
@@ -938,9 +1235,8 @@ export class DatasourceIntrospectionService {
|
|
|
938
1235
|
" {",
|
|
939
1236
|
" const table = await queryRunner.getTable(tableName);",
|
|
940
1237
|
` const index = table?.indices.find((entry) => entry.name === ${JSON.stringify(generatedIdUniqueName)});`,
|
|
941
|
-
" if (index)
|
|
1238
|
+
" if (index)",
|
|
942
1239
|
" await queryRunner.dropIndex(tableName, index);",
|
|
943
|
-
" }",
|
|
944
1240
|
" }",
|
|
945
1241
|
].join("\n"),
|
|
946
1242
|
);
|
|
@@ -951,9 +1247,8 @@ export class DatasourceIntrospectionService {
|
|
|
951
1247
|
" {",
|
|
952
1248
|
" const table = await queryRunner.getTable(tableName);",
|
|
953
1249
|
` const index = table?.indices.find((entry) => entry.name === ${JSON.stringify(deletedAtIndexName)});`,
|
|
954
|
-
" if (index)
|
|
1250
|
+
" if (index)",
|
|
955
1251
|
" await queryRunner.dropIndex(tableName, index);",
|
|
956
|
-
" }",
|
|
957
1252
|
" }",
|
|
958
1253
|
].join("\n"),
|
|
959
1254
|
);
|
|
@@ -962,11 +1257,8 @@ export class DatasourceIntrospectionService {
|
|
|
962
1257
|
...missingSystemColumns
|
|
963
1258
|
.slice()
|
|
964
1259
|
.reverse()
|
|
965
|
-
.map((definition) =>
|
|
966
|
-
`
|
|
967
|
-
` await queryRunner.dropColumn(tableName, ${JSON.stringify(definition.columnName)});`,
|
|
968
|
-
" }",
|
|
969
|
-
].join("\n")),
|
|
1260
|
+
.map((definition) => ` if (await queryRunner.hasColumn(tableName, ${JSON.stringify(definition.columnName)}))\n`
|
|
1261
|
+
+ ` await queryRunner.dropColumn(tableName, ${JSON.stringify(definition.columnName)});`),
|
|
970
1262
|
);
|
|
971
1263
|
|
|
972
1264
|
return [
|
|
@@ -992,29 +1284,41 @@ export class DatasourceIntrospectionService {
|
|
|
992
1284
|
|
|
993
1285
|
private renderTableColumnLiteral(definition: SystemColumnDefinition) {
|
|
994
1286
|
const properties: string[] = [
|
|
995
|
-
`
|
|
996
|
-
`
|
|
1287
|
+
`name: ${JSON.stringify(definition.columnName)}`,
|
|
1288
|
+
`type: ${JSON.stringify(definition.type)}`,
|
|
997
1289
|
];
|
|
998
1290
|
|
|
999
1291
|
if (definition.length) {
|
|
1000
|
-
properties.push(`
|
|
1292
|
+
properties.push(`length: ${JSON.stringify(definition.length)}`);
|
|
1001
1293
|
}
|
|
1002
1294
|
if (typeof definition.isNullable === "boolean") {
|
|
1003
|
-
properties.push(`
|
|
1295
|
+
properties.push(`isNullable: ${definition.isNullable}`);
|
|
1004
1296
|
}
|
|
1005
1297
|
if (definition.defaultExpression) {
|
|
1006
|
-
properties.push(`
|
|
1298
|
+
properties.push(`default: ${JSON.stringify(definition.defaultExpression)}`);
|
|
1007
1299
|
}
|
|
1008
1300
|
if (definition.isGenerated) {
|
|
1009
|
-
properties.push("
|
|
1010
|
-
properties.push("
|
|
1301
|
+
properties.push("isGenerated: true");
|
|
1302
|
+
properties.push("generationStrategy: \"increment\"");
|
|
1011
1303
|
}
|
|
1012
1304
|
if (definition.isUnique) {
|
|
1013
|
-
properties.push("
|
|
1305
|
+
properties.push("isUnique: true");
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
return `{ ${properties.join(", ")}, }`;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
private renderTableIndexLiteral(definition: { name: string; columnNames: string[]; isUnique?: boolean }) {
|
|
1312
|
+
const properties: string[] = [
|
|
1313
|
+
`name: ${JSON.stringify(definition.name)}`,
|
|
1314
|
+
`columnNames: ${JSON.stringify(definition.columnNames)}`,
|
|
1315
|
+
];
|
|
1316
|
+
|
|
1317
|
+
if (definition.isUnique) {
|
|
1318
|
+
properties.push("isUnique: true");
|
|
1014
1319
|
}
|
|
1015
1320
|
|
|
1016
|
-
properties.
|
|
1017
|
-
return properties.join("\n");
|
|
1321
|
+
return `{ ${properties.join(", ")}, }`;
|
|
1018
1322
|
}
|
|
1019
1323
|
|
|
1020
1324
|
private buildMigrationObjectName(prefix: string, tableName: string, columnName: string) {
|
|
@@ -1050,13 +1354,15 @@ export class DatasourceIntrospectionService {
|
|
|
1050
1354
|
private async buildTableInventory(datasource: DatasourceRecord) {
|
|
1051
1355
|
const provider = this.getProvider(datasource.type);
|
|
1052
1356
|
const runtimeDataSource = this.resolveDataSourceByName(datasource.name);
|
|
1053
|
-
const [
|
|
1357
|
+
const [rawTables, rawColumns, models] = await Promise.all([
|
|
1054
1358
|
provider.listTables(runtimeDataSource),
|
|
1055
1359
|
provider.listColumns(runtimeDataSource),
|
|
1056
1360
|
this.modelMetadataRepository.find({ relations: { module: true } as any }),
|
|
1057
1361
|
]);
|
|
1362
|
+
const tables = rawTables.filter((table) => !this.isSolidxInternalTableName(table.tableName));
|
|
1363
|
+
const columns = rawColumns.filter((column) => !this.isSolidxInternalTableName(column.tableName));
|
|
1058
1364
|
|
|
1059
|
-
const
|
|
1365
|
+
const modelLookup = this.buildModelLookup(models);
|
|
1060
1366
|
const columnsByKey = this.groupColumnsByTable(columns);
|
|
1061
1367
|
const tableKeys = new Set<string>();
|
|
1062
1368
|
|
|
@@ -1080,8 +1386,10 @@ export class DatasourceIntrospectionService {
|
|
|
1080
1386
|
|
|
1081
1387
|
const suggestedLegacyTableType = this.detectLegacyTableType(datasource.type, tableColumns);
|
|
1082
1388
|
const suggestedIdColumn = this.getSuggestedIdColumn(datasource.type, tableColumns, suggestedLegacyTableType);
|
|
1083
|
-
const mappedModel =
|
|
1389
|
+
const mappedModel = this.resolveMappedModel(modelLookup, datasource.name, tableName);
|
|
1084
1390
|
const physicalColumns = tableColumns.filter((column) => !this.isHandledBySuperclass(column.columnName, suggestedLegacyTableType));
|
|
1391
|
+
const primaryKeyColumns = tableColumns.filter((column) => column.isPrimaryKey);
|
|
1392
|
+
const hasPrimaryKey = primaryKeyColumns.length > 0;
|
|
1085
1393
|
|
|
1086
1394
|
return {
|
|
1087
1395
|
schema,
|
|
@@ -1099,6 +1407,12 @@ export class DatasourceIntrospectionService {
|
|
|
1099
1407
|
suggestedUserKeyField: this.buildUserKeyCandidates(tableColumns)[0]?.name ?? "id",
|
|
1100
1408
|
columnCount: tableColumns.length,
|
|
1101
1409
|
physicalColumnCount: physicalColumns.length,
|
|
1410
|
+
hasPrimaryKey,
|
|
1411
|
+
primaryKeyColumnCount: primaryKeyColumns.length,
|
|
1412
|
+
primaryKeyColumnNames: primaryKeyColumns.map((column) => column.columnName),
|
|
1413
|
+
mappingBlockedReason: hasPrimaryKey
|
|
1414
|
+
? null
|
|
1415
|
+
: this.buildMissingPrimaryKeyMessage(tableName),
|
|
1102
1416
|
} satisfies TableInventoryRecord;
|
|
1103
1417
|
})
|
|
1104
1418
|
.filter((record): record is TableInventoryRecord => Boolean(record))
|
|
@@ -1119,19 +1433,52 @@ export class DatasourceIntrospectionService {
|
|
|
1119
1433
|
};
|
|
1120
1434
|
}
|
|
1121
1435
|
|
|
1122
|
-
private
|
|
1123
|
-
const
|
|
1436
|
+
private buildModelLookup(models: ModelMetadata[]) {
|
|
1437
|
+
const byDatasourceAndTable = new Map<string, ModelMetadata>();
|
|
1438
|
+
const byTableName = new Map<string, ModelMetadata[]>();
|
|
1124
1439
|
|
|
1125
1440
|
for (const model of models) {
|
|
1126
1441
|
if (!model.tableName) {
|
|
1127
1442
|
continue;
|
|
1128
1443
|
}
|
|
1129
1444
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1445
|
+
byDatasourceAndTable.set(this.toModelMapKey(model.dataSource, model.tableName), model);
|
|
1446
|
+
|
|
1447
|
+
const normalizedTableName = model.tableName.trim().toLowerCase();
|
|
1448
|
+
const bucket = byTableName.get(normalizedTableName);
|
|
1449
|
+
if (bucket) {
|
|
1450
|
+
bucket.push(model);
|
|
1451
|
+
continue;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
byTableName.set(normalizedTableName, [model]);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
return {
|
|
1458
|
+
byDatasourceAndTable,
|
|
1459
|
+
byTableName,
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
private resolveMappedModel(
|
|
1464
|
+
lookup: {
|
|
1465
|
+
byDatasourceAndTable: Map<string, ModelMetadata>;
|
|
1466
|
+
byTableName: Map<string, ModelMetadata[]>;
|
|
1467
|
+
},
|
|
1468
|
+
datasourceName: string,
|
|
1469
|
+
tableName: string,
|
|
1470
|
+
) {
|
|
1471
|
+
const exactMatch = lookup.byDatasourceAndTable.get(this.toModelMapKey(datasourceName, tableName));
|
|
1472
|
+
if (exactMatch) {
|
|
1473
|
+
return exactMatch;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
const fallbackMatches = lookup.byTableName.get(tableName.trim().toLowerCase()) ?? [];
|
|
1477
|
+
if (fallbackMatches.length === 1) {
|
|
1478
|
+
return fallbackMatches[0];
|
|
1132
1479
|
}
|
|
1133
1480
|
|
|
1134
|
-
return
|
|
1481
|
+
return fallbackMatches[0] ?? null;
|
|
1135
1482
|
}
|
|
1136
1483
|
|
|
1137
1484
|
private groupColumnsByTable(columns: DatasourceIntrospectionColumn[]) {
|
|
@@ -1151,6 +1498,10 @@ export class DatasourceIntrospectionService {
|
|
|
1151
1498
|
return grouped;
|
|
1152
1499
|
}
|
|
1153
1500
|
|
|
1501
|
+
private isSolidxInternalTableName(tableName: string | null | undefined) {
|
|
1502
|
+
return typeof tableName === "string" && tableName.toLowerCase().startsWith("ss_");
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1154
1505
|
private detectLegacyTableType(datasourceType: string, columns: DatasourceIntrospectionColumn[]): LegacyTableType {
|
|
1155
1506
|
const normalizedDatasourceType = datasourceType.toLowerCase();
|
|
1156
1507
|
const hasExistingGeneratedId = columns.some((column) => column.isAutoGenerated);
|
|
@@ -1239,13 +1590,17 @@ export class DatasourceIntrospectionService {
|
|
|
1239
1590
|
datasourceType: string,
|
|
1240
1591
|
): TableDetailColumnRecord {
|
|
1241
1592
|
const superclassFieldName = this.getSuperclassFieldName(column.columnName, legacyTableType);
|
|
1593
|
+
const normalizedColumnName = column.columnName.toLowerCase();
|
|
1242
1594
|
const fieldName = legacyTableType === LegacyTableType.EXISTING_ID && column.isAutoGenerated
|
|
1243
1595
|
? "id"
|
|
1244
|
-
:
|
|
1596
|
+
: legacyTableType === LegacyTableType.GENERATED_ID && normalizedColumnName === "id"
|
|
1597
|
+
? "legacyId"
|
|
1598
|
+
: this.toFieldName(column.columnName);
|
|
1245
1599
|
|
|
1246
1600
|
return {
|
|
1247
1601
|
columnName: column.columnName,
|
|
1248
1602
|
fieldName,
|
|
1603
|
+
displayName: startCase(fieldName),
|
|
1249
1604
|
dataType: column.dataType,
|
|
1250
1605
|
solidFieldType: this.toSolidFieldType(column),
|
|
1251
1606
|
ormType: this.toOrmFieldType(column, datasourceType),
|
|
@@ -1256,11 +1611,72 @@ export class DatasourceIntrospectionService {
|
|
|
1256
1611
|
numericScale: column.numericScale,
|
|
1257
1612
|
ordinalPosition: column.ordinalPosition,
|
|
1258
1613
|
isAutoGenerated: column.isAutoGenerated,
|
|
1614
|
+
isPrimaryKey: column.isPrimaryKey,
|
|
1259
1615
|
handledBySuperclass: Boolean(superclassFieldName),
|
|
1260
1616
|
superclassFieldName,
|
|
1261
1617
|
};
|
|
1262
1618
|
}
|
|
1263
1619
|
|
|
1620
|
+
private validateTablePrimaryKeyForMapping(
|
|
1621
|
+
tableRecord: TableInventoryRecord,
|
|
1622
|
+
tableColumns: DatasourceIntrospectionColumn[],
|
|
1623
|
+
mappingDto: DatasourceIntrospectionMappingDto,
|
|
1624
|
+
) {
|
|
1625
|
+
const detectedPrimaryKeyColumns = tableColumns.filter((column) => column.isPrimaryKey);
|
|
1626
|
+
if (!detectedPrimaryKeyColumns.length) {
|
|
1627
|
+
throw new BadRequestException(tableRecord.mappingBlockedReason ?? this.buildMissingPrimaryKeyMessage(tableRecord.tableName));
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
const requestColumnsByName = new Map(
|
|
1631
|
+
mappingDto.columns.map((column) => [column.columnName.trim().toLowerCase(), column]),
|
|
1632
|
+
);
|
|
1633
|
+
|
|
1634
|
+
for (const primaryKeyColumn of detectedPrimaryKeyColumns) {
|
|
1635
|
+
const matchingRequestColumn = requestColumnsByName.get(primaryKeyColumn.columnName.trim().toLowerCase());
|
|
1636
|
+
if (!matchingRequestColumn) {
|
|
1637
|
+
throw new BadRequestException(`Primary key column "${primaryKeyColumn.columnName}" must remain present in the mapping request for table "${tableRecord.tableName}".`);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
if (!matchingRequestColumn.include) {
|
|
1641
|
+
throw new BadRequestException(`Primary key column "${primaryKeyColumn.columnName}" cannot be excluded from the mapping for table "${tableRecord.tableName}".`);
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
if (!matchingRequestColumn.isPrimaryKey) {
|
|
1645
|
+
throw new BadRequestException(`Primary key column "${primaryKeyColumn.columnName}" must stay marked as isPrimaryKey for table "${tableRecord.tableName}".`);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
private validateReviewedPrimaryKeyFields(
|
|
1651
|
+
reviewedFields: Array<Record<string, any>>,
|
|
1652
|
+
tableColumns: DatasourceIntrospectionColumn[],
|
|
1653
|
+
) {
|
|
1654
|
+
const detectedPrimaryKeyColumns = tableColumns.filter((column) => column.isPrimaryKey);
|
|
1655
|
+
if (!detectedPrimaryKeyColumns.length) {
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
const activeReviewedFields = reviewedFields.filter((field) => !field.isMarkedForRemoval);
|
|
1660
|
+
|
|
1661
|
+
for (const primaryKeyColumn of detectedPrimaryKeyColumns) {
|
|
1662
|
+
const matchingReviewedField = activeReviewedFields.find((field) =>
|
|
1663
|
+
`${field.columnName ?? ""}`.trim().toLowerCase() === primaryKeyColumn.columnName.trim().toLowerCase(),
|
|
1664
|
+
);
|
|
1665
|
+
|
|
1666
|
+
if (!matchingReviewedField) {
|
|
1667
|
+
throw new BadRequestException(`Reviewed metadata JSON must keep datasource primary key column "${primaryKeyColumn.columnName}" in the fields array.`);
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
if (!matchingReviewedField.isPrimaryKey) {
|
|
1671
|
+
throw new BadRequestException(`Reviewed metadata JSON must keep datasource primary key column "${primaryKeyColumn.columnName}" marked with isPrimaryKey: true.`);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
private buildMissingPrimaryKeyMessage(tableName: string) {
|
|
1677
|
+
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.`;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1264
1680
|
private getSuperclassFieldName(columnName: string, legacyTableType: LegacyTableType) {
|
|
1265
1681
|
return this.getLegacySuperclassFieldName(columnName, legacyTableType);
|
|
1266
1682
|
}
|
|
@@ -1298,109 +1714,80 @@ export class DatasourceIntrospectionService {
|
|
|
1298
1714
|
return "LegacyCommonEntityWithExistingId";
|
|
1299
1715
|
}
|
|
1300
1716
|
|
|
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
|
-
}
|
|
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
|
-
}
|
|
1717
|
+
private getDatasourceTypeFamily(dataType: string): DatasourceTypeFamily | null {
|
|
1718
|
+
const normalized = dataType.toLowerCase();
|
|
1320
1719
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1720
|
+
for (const [family, aliases] of Object.entries(DATASOURCE_TYPE_FAMILY_ALIASES) as Array<[DatasourceTypeFamily, string[]]>) {
|
|
1721
|
+
if (aliases.includes(normalized)) {
|
|
1722
|
+
return family;
|
|
1723
|
+
}
|
|
1323
1724
|
}
|
|
1324
1725
|
|
|
1325
|
-
if (
|
|
1326
|
-
return "
|
|
1726
|
+
if (normalized.includes("json")) {
|
|
1727
|
+
return "json";
|
|
1327
1728
|
}
|
|
1328
1729
|
|
|
1329
|
-
if (
|
|
1730
|
+
if (normalized.includes("date")) {
|
|
1330
1731
|
return "datetime";
|
|
1331
1732
|
}
|
|
1332
1733
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
if (["uniqueidentifier", "uuid"].includes(normalized)) {
|
|
1338
|
-
return "uuid";
|
|
1339
|
-
}
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1340
1736
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1737
|
+
private toSolidFieldType(column: DatasourceIntrospectionColumn) {
|
|
1738
|
+
const family = this.getDatasourceTypeFamily(column.dataType);
|
|
1739
|
+
const charLength = column.characterMaximumLength ?? null;
|
|
1344
1740
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1741
|
+
switch (family) {
|
|
1742
|
+
case "boolean":
|
|
1743
|
+
return "boolean";
|
|
1744
|
+
case "bigint":
|
|
1745
|
+
return "bigint";
|
|
1746
|
+
case "int":
|
|
1747
|
+
return "int";
|
|
1748
|
+
case "numeric":
|
|
1749
|
+
case "decimal":
|
|
1750
|
+
case "float":
|
|
1751
|
+
return "decimal";
|
|
1752
|
+
case "date":
|
|
1753
|
+
return "date";
|
|
1754
|
+
case "time":
|
|
1755
|
+
return "time";
|
|
1756
|
+
case "datetime":
|
|
1757
|
+
case "datetimeWithTimezone":
|
|
1758
|
+
return "datetime";
|
|
1759
|
+
case "json":
|
|
1760
|
+
case "jsonb":
|
|
1761
|
+
return "json";
|
|
1762
|
+
case "uuid":
|
|
1763
|
+
return "uuid";
|
|
1764
|
+
case "mediumText":
|
|
1765
|
+
case "longText":
|
|
1347
1766
|
return "longText";
|
|
1348
|
-
|
|
1767
|
+
case "shortText":
|
|
1768
|
+
case "unicodeShortText":
|
|
1769
|
+
if (charLength !== null && charLength > 255) {
|
|
1770
|
+
return "longText";
|
|
1771
|
+
}
|
|
1349
1772
|
|
|
1350
|
-
|
|
1773
|
+
return "shortText";
|
|
1774
|
+
default:
|
|
1775
|
+
return "shortText";
|
|
1351
1776
|
}
|
|
1352
|
-
|
|
1353
|
-
return "shortText";
|
|
1354
1777
|
}
|
|
1355
1778
|
|
|
1356
1779
|
private toOrmFieldType(column: DatasourceIntrospectionColumn, datasourceType: string) {
|
|
1357
|
-
const
|
|
1780
|
+
const family = this.getDatasourceTypeFamily(column.dataType);
|
|
1358
1781
|
const normalizedDatasourceType = datasourceType.toLowerCase();
|
|
1782
|
+
const ormType = family
|
|
1783
|
+
? ORM_TYPE_BY_DATASOURCE_FAMILY[normalizedDatasourceType]?.[family]
|
|
1784
|
+
: undefined;
|
|
1359
1785
|
|
|
1360
|
-
if (
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
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";
|
|
1786
|
+
if (ormType) {
|
|
1787
|
+
return ormType;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
return normalizedDatasourceType === "postgres" ? "varchar" : "varchar";
|
|
1404
1791
|
}
|
|
1405
1792
|
|
|
1406
1793
|
private toFieldName(columnName: string) {
|
|
@@ -1449,6 +1836,269 @@ export class DatasourceIntrospectionService {
|
|
|
1449
1836
|
return `${datasourceName.toLowerCase()}::${tableName.toLowerCase()}`;
|
|
1450
1837
|
}
|
|
1451
1838
|
|
|
1839
|
+
private async syncTypeormDatasourceFile(moduleName: string, datasource: DatasourceRecord, singularName: string) {
|
|
1840
|
+
const datasourceFilePath = path.resolve(process.cwd(), "src", `typeorm-${datasource.name}-datasource.ts`);
|
|
1841
|
+
const entityClassName = classify(singularName);
|
|
1842
|
+
const entityImportPath = `./${moduleName}/entities/${kebabCase(singularName)}.entity`;
|
|
1843
|
+
const migrationGlob = `join(__dirname, './${moduleName}/migrations/${datasource.name}/*.{ts,js}')`;
|
|
1844
|
+
|
|
1845
|
+
const existingContent = await fs.readFile(datasourceFilePath, "utf8").catch(() => null);
|
|
1846
|
+
if (!existingContent) {
|
|
1847
|
+
const rendered = this.renderTypeormDatasourceTemplate({
|
|
1848
|
+
datasource,
|
|
1849
|
+
entityClassName,
|
|
1850
|
+
entityImportPath,
|
|
1851
|
+
migrationGlob,
|
|
1852
|
+
});
|
|
1853
|
+
await fs.writeFile(datasourceFilePath, rendered, "utf8");
|
|
1854
|
+
return {
|
|
1855
|
+
filePath: datasourceFilePath,
|
|
1856
|
+
created: true,
|
|
1857
|
+
updated: false,
|
|
1858
|
+
entityRegistered: true,
|
|
1859
|
+
migrationGlobRegistered: true,
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
const project = new Project({
|
|
1864
|
+
skipAddingFilesFromTsConfig: true,
|
|
1865
|
+
manipulationSettings: {
|
|
1866
|
+
quoteKind: QuoteKind.Single,
|
|
1867
|
+
indentationText: IndentationText.FourSpaces,
|
|
1868
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
|
1869
|
+
},
|
|
1870
|
+
});
|
|
1871
|
+
const sourceFile = project.createSourceFile(datasourceFilePath, existingContent, { overwrite: true });
|
|
1872
|
+
|
|
1873
|
+
const entityImportDeclaration = sourceFile.getImportDeclarations().find((declaration) =>
|
|
1874
|
+
declaration.getModuleSpecifierValue() === entityImportPath,
|
|
1875
|
+
);
|
|
1876
|
+
if (entityImportDeclaration) {
|
|
1877
|
+
const alreadyImported = entityImportDeclaration.getNamedImports().some((namedImport) => namedImport.getName() === entityClassName);
|
|
1878
|
+
if (!alreadyImported) {
|
|
1879
|
+
entityImportDeclaration.addNamedImport(entityClassName);
|
|
1880
|
+
}
|
|
1881
|
+
} else {
|
|
1882
|
+
sourceFile.addImportDeclaration({
|
|
1883
|
+
moduleSpecifier: entityImportPath,
|
|
1884
|
+
namedImports: [entityClassName],
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
const entitiesArray = sourceFile
|
|
1889
|
+
.getVariableDeclarationOrThrow("entities")
|
|
1890
|
+
.getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
1891
|
+
const entityAlreadyRegistered = this.arrayLiteralContainsValue(entitiesArray, entityClassName);
|
|
1892
|
+
if (!entityAlreadyRegistered) {
|
|
1893
|
+
entitiesArray.addElement(entityClassName);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
const dataSourceOptions = this.getTypeormDatasourceOptions(sourceFile);
|
|
1897
|
+
const migrationsArray = this.getObjectPropertyArrayLiteral(dataSourceOptions, "migrations");
|
|
1898
|
+
const migrationAlreadyRegistered = this.arrayLiteralContainsValue(migrationsArray, migrationGlob);
|
|
1899
|
+
if (!migrationAlreadyRegistered) {
|
|
1900
|
+
migrationsArray.addElement(migrationGlob);
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
sourceFile.organizeImports();
|
|
1904
|
+
sourceFile.formatText();
|
|
1905
|
+
|
|
1906
|
+
const nextContent = sourceFile.getFullText();
|
|
1907
|
+
const updated = nextContent !== existingContent;
|
|
1908
|
+
if (updated) {
|
|
1909
|
+
await fs.writeFile(datasourceFilePath, nextContent, "utf8");
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
return {
|
|
1913
|
+
filePath: datasourceFilePath,
|
|
1914
|
+
created: false,
|
|
1915
|
+
updated,
|
|
1916
|
+
entityRegistered: true,
|
|
1917
|
+
migrationGlobRegistered: true,
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
private renderTypeormDatasourceTemplate(input: {
|
|
1922
|
+
datasource: DatasourceRecord;
|
|
1923
|
+
entityClassName: string;
|
|
1924
|
+
entityImportPath: string;
|
|
1925
|
+
migrationGlob: string;
|
|
1926
|
+
}) {
|
|
1927
|
+
const { datasource, entityClassName, entityImportPath, migrationGlob } = input;
|
|
1928
|
+
const envPrefix = datasource.envPrefix?.toUpperCase?.() || "DEFAULT";
|
|
1929
|
+
const portFallback = datasource.type === "postgres" ? 5432 : datasource.type === "mysql" ? 3306 : 1433;
|
|
1930
|
+
const project = new Project({
|
|
1931
|
+
skipAddingFilesFromTsConfig: true,
|
|
1932
|
+
manipulationSettings: {
|
|
1933
|
+
quoteKind: QuoteKind.Single,
|
|
1934
|
+
indentationText: IndentationText.FourSpaces,
|
|
1935
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
|
1936
|
+
},
|
|
1937
|
+
});
|
|
1938
|
+
const sourceFile = project.createSourceFile("typeorm-template.ts", "", { overwrite: true });
|
|
1939
|
+
|
|
1940
|
+
sourceFile.addImportDeclaration({ moduleSpecifier: "reflect-metadata" });
|
|
1941
|
+
sourceFile.addImportDeclaration({ moduleSpecifier: "path", namedImports: ["join"] });
|
|
1942
|
+
sourceFile.addImportDeclaration({
|
|
1943
|
+
moduleSpecifier: "dotenv",
|
|
1944
|
+
namedImports: [{ name: "config", alias: "dotenvConfig" }],
|
|
1945
|
+
});
|
|
1946
|
+
sourceFile.addStatements("dotenvConfig({ path: join(__dirname, '../.env') });");
|
|
1947
|
+
sourceFile.addStatements("");
|
|
1948
|
+
|
|
1949
|
+
sourceFile.addImportDeclaration({ moduleSpecifier: "typeorm", namedImports: ["DataSource"] });
|
|
1950
|
+
sourceFile.addImportDeclaration({
|
|
1951
|
+
moduleSpecifier: "@solidxai/core",
|
|
1952
|
+
namespaceImport: "SolidCoreModuleExports",
|
|
1953
|
+
});
|
|
1954
|
+
sourceFile.addImportDeclaration({
|
|
1955
|
+
moduleSpecifier: "@solidxai/core",
|
|
1956
|
+
namedImports: ["getDynamicModuleNames", "parseBooleanEnv"],
|
|
1957
|
+
});
|
|
1958
|
+
sourceFile.addImportDeclaration({
|
|
1959
|
+
moduleSpecifier: "typeorm",
|
|
1960
|
+
namedImports: ["getMetadataArgsStorage"],
|
|
1961
|
+
});
|
|
1962
|
+
sourceFile.addImportDeclaration({
|
|
1963
|
+
moduleSpecifier: "typeorm-naming-strategies",
|
|
1964
|
+
namedImports: ["SnakeNamingStrategy"],
|
|
1965
|
+
});
|
|
1966
|
+
sourceFile.addImportDeclaration({
|
|
1967
|
+
moduleSpecifier: entityImportPath,
|
|
1968
|
+
namedImports: [entityClassName],
|
|
1969
|
+
});
|
|
1970
|
+
|
|
1971
|
+
sourceFile.addFunction({
|
|
1972
|
+
name: "getEntitiesFromExports",
|
|
1973
|
+
parameters: [{ name: "exports", type: "Record<string, any>" }],
|
|
1974
|
+
statements: (writer) => {
|
|
1975
|
+
writer.writeLine("const metadataStorage = getMetadataArgsStorage();");
|
|
1976
|
+
writer.writeLine("return Object.values(exports).filter((item) =>");
|
|
1977
|
+
writer.indent(() => {
|
|
1978
|
+
writer.writeLine("metadataStorage.tables.some((table) => table.target === item),");
|
|
1979
|
+
});
|
|
1980
|
+
writer.write(");");
|
|
1981
|
+
},
|
|
1982
|
+
});
|
|
1983
|
+
|
|
1984
|
+
sourceFile.addVariableStatement({
|
|
1985
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
1986
|
+
declarations: [
|
|
1987
|
+
{
|
|
1988
|
+
name: "coreEntities",
|
|
1989
|
+
initializer: "getEntitiesFromExports(SolidCoreModuleExports)",
|
|
1990
|
+
},
|
|
1991
|
+
],
|
|
1992
|
+
});
|
|
1993
|
+
sourceFile.addVariableStatement({
|
|
1994
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
1995
|
+
declarations: [
|
|
1996
|
+
{
|
|
1997
|
+
name: "dynamicModules",
|
|
1998
|
+
initializer: "getDynamicModuleNames()",
|
|
1999
|
+
},
|
|
2000
|
+
],
|
|
2001
|
+
});
|
|
2002
|
+
sourceFile.addVariableStatement({
|
|
2003
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
2004
|
+
declarations: [
|
|
2005
|
+
{
|
|
2006
|
+
name: "entities",
|
|
2007
|
+
initializer: `[\n ...coreEntities,\n ${entityClassName},\n]`,
|
|
2008
|
+
},
|
|
2009
|
+
],
|
|
2010
|
+
});
|
|
2011
|
+
sourceFile.addVariableStatement({
|
|
2012
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
2013
|
+
declarations: [
|
|
2014
|
+
{
|
|
2015
|
+
name: "logging",
|
|
2016
|
+
initializer: `parseBooleanEnv('${envPrefix}_DATABASE_LOGGING')`,
|
|
2017
|
+
},
|
|
2018
|
+
],
|
|
2019
|
+
});
|
|
2020
|
+
|
|
2021
|
+
sourceFile.addStatements("// IMPORTANT: synchronize must be false when using migrations");
|
|
2022
|
+
sourceFile.addVariableStatement({
|
|
2023
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
2024
|
+
isExported: true,
|
|
2025
|
+
declarations: [
|
|
2026
|
+
{
|
|
2027
|
+
name: "DefaultDataSource",
|
|
2028
|
+
initializer: (writer) => {
|
|
2029
|
+
writer.write("new DataSource(");
|
|
2030
|
+
writer.inlineBlock(() => {
|
|
2031
|
+
writer.writeLine(`type: '${datasource.type}',`);
|
|
2032
|
+
writer.blankLine();
|
|
2033
|
+
writer.writeLine(`host: process.env.${envPrefix}_DATABASE_HOST,`);
|
|
2034
|
+
writer.writeLine(`port: +(process.env.${envPrefix}_DATABASE_PORT || ${portFallback}),`);
|
|
2035
|
+
writer.writeLine(`username: process.env.${envPrefix}_DATABASE_USER,`);
|
|
2036
|
+
writer.writeLine(`password: process.env.${envPrefix}_DATABASE_PASSWORD,`);
|
|
2037
|
+
writer.writeLine(`database: process.env.${envPrefix}_DATABASE_NAME,`);
|
|
2038
|
+
writer.blankLine();
|
|
2039
|
+
writer.writeLine("entities,");
|
|
2040
|
+
writer.writeLine("migrations: [");
|
|
2041
|
+
writer.indent(() => {
|
|
2042
|
+
writer.writeLine(`${migrationGlob},`);
|
|
2043
|
+
});
|
|
2044
|
+
writer.writeLine("],");
|
|
2045
|
+
writer.writeLine("synchronize: false,");
|
|
2046
|
+
writer.writeLine("logging,");
|
|
2047
|
+
writer.writeLine("namingStrategy: new SnakeNamingStrategy(),");
|
|
2048
|
+
if (datasource.type === "mssql") {
|
|
2049
|
+
writer.writeLine("options: {");
|
|
2050
|
+
writer.indent(() => {
|
|
2051
|
+
writer.writeLine("encrypt: false,");
|
|
2052
|
+
writer.writeLine("trustServerCertificate: true,");
|
|
2053
|
+
});
|
|
2054
|
+
writer.writeLine("},");
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2057
|
+
writer.write(")");
|
|
2058
|
+
},
|
|
2059
|
+
},
|
|
2060
|
+
],
|
|
2061
|
+
});
|
|
2062
|
+
|
|
2063
|
+
sourceFile.organizeImports();
|
|
2064
|
+
sourceFile.formatText();
|
|
2065
|
+
return sourceFile.getFullText();
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
private arrayLiteralContainsValue(arrayLiteral: ArrayLiteralExpression, rawValue: string) {
|
|
2069
|
+
const normalizedTarget = rawValue.replace(/\s+/g, "");
|
|
2070
|
+
return arrayLiteral.getElements().some((element) => element.getText().replace(/\s+/g, "") === normalizedTarget);
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
private getTypeormDatasourceOptions(sourceFile: import("ts-morph").SourceFile) {
|
|
2074
|
+
const defaultDataSourceDeclaration = sourceFile.getVariableDeclarationOrThrow("DefaultDataSource");
|
|
2075
|
+
const initializer = defaultDataSourceDeclaration.getInitializer();
|
|
2076
|
+
if (!initializer || !Node.isNewExpression(initializer)) {
|
|
2077
|
+
throw new BadRequestException("Unable to locate DefaultDataSource initializer in datasource file.");
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
const options = initializer.getArguments()[0];
|
|
2081
|
+
if (!options || !Node.isObjectLiteralExpression(options)) {
|
|
2082
|
+
throw new BadRequestException("Unable to locate DefaultDataSource options object in datasource file.");
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
return options;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
private getObjectPropertyArrayLiteral(objectLiteral: ObjectLiteralExpression, propertyName: string) {
|
|
2089
|
+
const property = objectLiteral.getProperty(propertyName);
|
|
2090
|
+
if (!property || !Node.isPropertyAssignment(property)) {
|
|
2091
|
+
throw new BadRequestException(`Unable to locate "${propertyName}" property in datasource file.`);
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
const initializer = property.getInitializer();
|
|
2095
|
+
if (!initializer || !Node.isArrayLiteralExpression(initializer)) {
|
|
2096
|
+
throw new BadRequestException(`Unable to locate "${propertyName}" array in datasource file.`);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
return initializer;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
1452
2102
|
private buildSynchronizeBlockedMessage(datasource: DatasourceRecord) {
|
|
1453
2103
|
return `Datasource "${datasource.displayName}" has synchronize enabled. Set ${datasource.envPrefix}_DATABASE_SYNCHRONIZE=false before starting legacy table introspection.`;
|
|
1454
2104
|
}
|