@solidxai/core 0.1.11-beta.0 → 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 +2 -1
- 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 +5 -2
- package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
- package/dist/repository/scheduled-job.repository.js +66 -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 +43 -2
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +1173 -232
- 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/action-metadata.service.d.ts +7 -0
- package/dist/services/action-metadata.service.d.ts.map +1 -1
- package/dist/services/action-metadata.service.js +76 -3
- package/dist/services/action-metadata.service.js.map +1 -1
- 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 +5 -0
- package/dist/services/role-metadata.service.d.ts.map +1 -1
- package/dist/services/role-metadata.service.js +95 -33
- 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 +6 -0
- package/dist/services/view-metadata.service.d.ts.map +1 -1
- package/dist/services/view-metadata.service.js +70 -8
- 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 +83 -4
- package/src/repository/security-rule.repository.ts +10 -4
- package/src/seeders/module-metadata-seeder.service.ts +1415 -264
- package/src/seeders/seed-data/solid-core-metadata.json +128 -34
- package/src/services/action-metadata.service.ts +87 -2
- 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 +112 -49
- 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 +83 -9
- 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
|
@@ -22,6 +22,10 @@ export class DatasourceIntrospectionMappingColumnDto {
|
|
|
22
22
|
@IsString()
|
|
23
23
|
fieldName: string;
|
|
24
24
|
|
|
25
|
+
@ApiProperty()
|
|
26
|
+
@IsString()
|
|
27
|
+
displayName: string;
|
|
28
|
+
|
|
25
29
|
@ApiProperty()
|
|
26
30
|
@IsString()
|
|
27
31
|
dataType: string;
|
|
@@ -91,6 +95,11 @@ export class DatasourceIntrospectionMappingColumnDto {
|
|
|
91
95
|
@ApiProperty()
|
|
92
96
|
@IsBoolean()
|
|
93
97
|
isPrimaryKey: boolean;
|
|
98
|
+
|
|
99
|
+
@ApiProperty({ required: false, type: "object", additionalProperties: true })
|
|
100
|
+
@IsOptional()
|
|
101
|
+
@IsObject()
|
|
102
|
+
fieldConfig?: Record<string, any>;
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
export class DatasourceIntrospectionMappingDto {
|
|
@@ -180,3 +189,9 @@ export class DatasourceIntrospectionMappingDto {
|
|
|
180
189
|
@IsObject()
|
|
181
190
|
reviewedModel?: Record<string, any>;
|
|
182
191
|
}
|
|
192
|
+
|
|
193
|
+
export class DatasourceIntrospectionRunMigrationDto {
|
|
194
|
+
@ApiProperty()
|
|
195
|
+
@IsString()
|
|
196
|
+
datasource: string;
|
|
197
|
+
}
|
package/src/entities/{legacy-common-with-id.entity.ts → legacy-common-with-generated-id.entity.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Exclude, Expose } from "class-transformer";
|
|
2
2
|
import { Column, Generated } from "typeorm";
|
|
3
|
-
import { LEGACY_TABLE_FIELDS_PREFIX, LegacyCommonEntityWithExistingId } from "./legacy-common.entity";
|
|
3
|
+
import { LEGACY_TABLE_FIELDS_PREFIX, LegacyCommonEntityWithExistingId } from "./legacy-common.entity-with-existing-id.entity";
|
|
4
4
|
|
|
5
5
|
@Exclude()
|
|
6
6
|
export abstract class LegacyCommonEntityWithGeneratedId extends LegacyCommonEntityWithExistingId {
|
|
@@ -10,6 +10,10 @@ export type CommandWithArgs = {
|
|
|
10
10
|
@Injectable()
|
|
11
11
|
export class CommandService {
|
|
12
12
|
private readonly logger = new Logger(CommandService.name);
|
|
13
|
+
private readonly ansiEscapePattern =
|
|
14
|
+
// Covers standard ANSI CSI, OSC, and related terminal escape sequences.
|
|
15
|
+
// eslint-disable-next-line no-control-regex
|
|
16
|
+
/[\u001B\u009B][[\]()#;?]*(?:(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]|(?:][^\u0007]*(?:\u0007|\u001B\\)))/g;
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* Escape an argument for Windows CMD shell
|
|
@@ -51,11 +55,11 @@ export class CommandService {
|
|
|
51
55
|
let stderr = '';
|
|
52
56
|
|
|
53
57
|
child.stdout.on('data', (data) => {
|
|
54
|
-
stdout += data.toString();
|
|
58
|
+
stdout += this.stripAnsi(data.toString());
|
|
55
59
|
});
|
|
56
60
|
|
|
57
61
|
child.stderr.on('data', (data) => {
|
|
58
|
-
stderr += data.toString();
|
|
62
|
+
stderr += this.stripAnsi(data.toString());
|
|
59
63
|
});
|
|
60
64
|
|
|
61
65
|
child.on('error', (error) => {
|
|
@@ -73,4 +77,8 @@ export class CommandService {
|
|
|
73
77
|
});
|
|
74
78
|
});
|
|
75
79
|
}
|
|
80
|
+
|
|
81
|
+
private stripAnsi(value: string): string {
|
|
82
|
+
return value.replace(this.ansiEscapePattern, '');
|
|
83
|
+
}
|
|
76
84
|
}
|
|
@@ -13,4 +13,15 @@ export function isRedisConfigured(configService: ConfigService): boolean {
|
|
|
13
13
|
const host = configService.get<string>('REDIS_HOST');
|
|
14
14
|
const port = configService.get<string>('REDIS_PORT');
|
|
15
15
|
return host && port && isNumber(parseInt(port));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns true when the project is configured to use an embedded PGlite
|
|
20
|
+
* database with a single-connection pool. Several TypeORM subscriber and
|
|
21
|
+
* code-generation workarounds are only necessary (and only applied) under
|
|
22
|
+
* this constraint; on a regular multi-connection Postgres pool the original
|
|
23
|
+
* behaviour is preserved.
|
|
24
|
+
*/
|
|
25
|
+
export function isEmbeddedDb(): boolean {
|
|
26
|
+
return process.env.DEFAULT_DATABASE_DRIVER === 'pglite';
|
|
16
27
|
}
|
|
@@ -15,7 +15,10 @@ export class IntFieldCrudManager implements FieldCrudManager {
|
|
|
15
15
|
|
|
16
16
|
validate(createDto: any): ValidationError[] {
|
|
17
17
|
const fieldValue: any = createDto[this.options.fieldName];
|
|
18
|
-
|
|
18
|
+
if (typeof fieldValue === 'string' && /^\d+$/.test(fieldValue.trim())) {
|
|
19
|
+
createDto[this.options.fieldName] = Number.parseInt(fieldValue, 10);
|
|
20
|
+
}
|
|
21
|
+
return this.applyValidations(createDto[this.options.fieldName]);
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
private applyValidations(fieldValue: any): ValidationError[] {
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { forwardRef, Inject, Injectable, Logger } from "@nestjs/common";
|
|
4
4
|
import { _ } from "lodash";
|
|
5
|
-
import { LEGACY_TABLE_FIELDS_PREFIX } from "src/entities/legacy-common.entity";
|
|
5
|
+
import { LEGACY_TABLE_FIELDS_PREFIX } from "src/entities/legacy-common.entity-with-existing-id.entity";
|
|
6
6
|
import { LegacyTableType } from "src/enums/legacy-table-type.enum";
|
|
7
|
+
import { ModelMetadata } from "src/entities/model-metadata.entity";
|
|
7
8
|
import { ModelMetadataRepository } from "src/repository/model-metadata.repository";
|
|
8
9
|
import { SolidRegistry } from "./solid-registry";
|
|
10
|
+
import { EntityManager } from "typeorm";
|
|
9
11
|
|
|
10
12
|
@Injectable()
|
|
11
13
|
export class ModelMetadataHelperService {
|
|
@@ -143,8 +145,11 @@ export class ModelMetadataHelperService {
|
|
|
143
145
|
return systemFieldsMetadata;
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
async loadFieldHierarchy(modelName: any) {
|
|
147
|
-
const
|
|
148
|
+
async loadFieldHierarchy(modelName: any, manager?: EntityManager) {
|
|
149
|
+
const repo = manager
|
|
150
|
+
? manager.getRepository(ModelMetadata)
|
|
151
|
+
: this.modelMetadataRepo;
|
|
152
|
+
const model = await repo.findOne({
|
|
148
153
|
where: {
|
|
149
154
|
singularName: modelName,
|
|
150
155
|
},
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as fsSync from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The heartbeat file is placed inside the nodemon-watched src/ directory so
|
|
6
|
+
* that touching it resets nodemon's restart delay. This prevents nodemon from
|
|
7
|
+
* restarting the API server while in-process code generation is writing files
|
|
8
|
+
* (which is especially important for embedded PGlite projects where the
|
|
9
|
+
* generate-code endpoint runs schematics in-process).
|
|
10
|
+
*
|
|
11
|
+
* The file uses a .json extension because nodemon is configured to watch
|
|
12
|
+
* ts,json files. It is placed at the src/ root (not inside any metadata
|
|
13
|
+
* directory) so it is not caught by the metadata JSON ignore rule.
|
|
14
|
+
*/
|
|
15
|
+
const HEARTBEAT_FILENAME = 'solidx-nodemon-heartbeat.json';
|
|
16
|
+
const HEARTBEAT_INTERVAL_MS = 1500;
|
|
17
|
+
|
|
18
|
+
export interface NodemonHeartbeatHandle {
|
|
19
|
+
stop: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Starts a heartbeat that touches a file in the nodemon-watched directory
|
|
24
|
+
* every ~1.5 s. Call `stop()` after the response has been flushed to allow
|
|
25
|
+
* nodemon to restart.
|
|
26
|
+
*
|
|
27
|
+
* @param srcDir Absolute path to the `src/` directory nodemon watches.
|
|
28
|
+
*/
|
|
29
|
+
export function startNodemonHeartbeat(srcDir: string): NodemonHeartbeatHandle {
|
|
30
|
+
const filePath = path.join(srcDir, HEARTBEAT_FILENAME);
|
|
31
|
+
let stopped = false;
|
|
32
|
+
|
|
33
|
+
const touch = () => {
|
|
34
|
+
try {
|
|
35
|
+
fsSync.writeFileSync(filePath, String(Date.now()));
|
|
36
|
+
} catch {
|
|
37
|
+
// best-effort — if the file can't be written, nodemon will simply
|
|
38
|
+
// restart as usual, which is the safe fallback.
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Write immediately so the delay resets before any schematic output lands.
|
|
43
|
+
touch();
|
|
44
|
+
|
|
45
|
+
const timer = setInterval(touch, HEARTBEAT_INTERVAL_MS);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
stop() {
|
|
49
|
+
if (stopped) return;
|
|
50
|
+
stopped = true;
|
|
51
|
+
clearInterval(timer);
|
|
52
|
+
try {
|
|
53
|
+
fsSync.unlinkSync(filePath);
|
|
54
|
+
} catch {
|
|
55
|
+
// file may not exist — ignore
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -8,7 +8,7 @@ import { Locale } from 'src/entities/locale.entity';
|
|
|
8
8
|
import { SecurityRule } from 'src/entities/security-rule.entity';
|
|
9
9
|
import { IScheduledJob } from 'src/services/scheduled-jobs/scheduled-job.interface';
|
|
10
10
|
import { IDashboardWidgetDataProvider, IErrorCodeProvider, ISecurityRuleConfigProvider, ISelectionProvider, ISelectionProviderContext, ISolidDatabaseModule } from "../interfaces";
|
|
11
|
-
import { ObjectLiteral } from 'typeorm';
|
|
11
|
+
import { EntityManager, ObjectLiteral } from 'typeorm';
|
|
12
12
|
|
|
13
13
|
type ControllerMetadata = {
|
|
14
14
|
name: string;
|
|
@@ -49,6 +49,12 @@ export interface TypeOrmEventContext {
|
|
|
49
49
|
metadataName?: string;
|
|
50
50
|
updatedColumns?: string[];
|
|
51
51
|
updatedRelations?: string[];
|
|
52
|
+
// Transaction-bound EntityManager from the originating TypeORM event. Providers that
|
|
53
|
+
// need to query the DB during a pre-compute hook must use this (not an injected
|
|
54
|
+
// DataSource/EntityManager) so the query runs on the active transaction's connection.
|
|
55
|
+
// On single-threaded embedded engines (PGlite), a second connection mid-transaction
|
|
56
|
+
// can deadlock.
|
|
57
|
+
manager?: EntityManager;
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
export interface ComputedFieldMetadata<TContext = any> {
|
|
@@ -45,6 +45,21 @@ const solidCoreDbType: DatasourceType =
|
|
|
45
45
|
? (process.env.SOLID_CORE_DB_TYPE as DatasourceType)
|
|
46
46
|
: DatasourceType.postgres;
|
|
47
47
|
|
|
48
|
+
// Must use ANSI CAST() syntax — not Postgres-specific `::text` — because TypeORM's
|
|
49
|
+
// replacePropertyNamesForTheWholeQuery regex treats ':' as part of the property name,
|
|
50
|
+
// so `alias.col::text` is parsed as property `col::text` (no match → no column substitution).
|
|
51
|
+
export function buildCastToText(driver: string, colExpr: string): string {
|
|
52
|
+
switch (driver) {
|
|
53
|
+
case DatasourceType.mssql:
|
|
54
|
+
return `CAST(${colExpr} AS NVARCHAR(MAX))`;
|
|
55
|
+
case DatasourceType.mysql:
|
|
56
|
+
case DatasourceType.mariadb:
|
|
57
|
+
return `CAST(${colExpr} AS CHAR)`;
|
|
58
|
+
default:
|
|
59
|
+
return `CAST(${colExpr} AS TEXT)`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
export function getColumnType(solidType: string): ColumnOptions {
|
|
49
64
|
switch (solidType) {
|
|
50
65
|
case "longText":
|
package/src/index.ts
CHANGED
|
@@ -119,8 +119,8 @@ export * from './dtos/update-dashboard-user-layout.dto'
|
|
|
119
119
|
|
|
120
120
|
export * from './entities/action-metadata.entity'
|
|
121
121
|
export * from './entities/common.entity'
|
|
122
|
-
export * from './entities/legacy-common.entity'
|
|
123
|
-
export * from './entities/legacy-common-with-id.entity'
|
|
122
|
+
export * from './entities/legacy-common.entity-with-existing-id.entity'
|
|
123
|
+
export * from './entities/legacy-common-with-generated-id.entity'
|
|
124
124
|
export * from './entities/email-attachment.entity'
|
|
125
125
|
export * from './entities/email-template.entity'
|
|
126
126
|
export * from './entities/field-metadata.entity'
|
|
@@ -171,6 +171,7 @@ export * from './guards/permissions.guard'
|
|
|
171
171
|
export * from './guards/roles.guard'
|
|
172
172
|
|
|
173
173
|
export * from './helpers/command.service'
|
|
174
|
+
export * from './helpers/nodemon-heartbeat'
|
|
174
175
|
export * from './helpers/module.helper'
|
|
175
176
|
export * from './helpers/schematic.service'
|
|
176
177
|
export * from './helpers/solid-registry'
|
package/src/interfaces.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { CreateScheduledJobDto } from "src/dtos/create-scheduled-job.dto";
|
|
|
3
3
|
import { ModuleMetadata } from "src/entities/module-metadata.entity";
|
|
4
4
|
import { ScheduledJob } from "src/entities/scheduled-job.entity";
|
|
5
5
|
import { RequestContextService } from "src/services/request-context.service";
|
|
6
|
-
import { DataSource } from "typeorm";
|
|
6
|
+
import { DataSource, EntityManager } from "typeorm";
|
|
7
7
|
import { SecurityRuleRepository } from "./security-rule.repository";
|
|
8
8
|
import { SolidBaseRepository } from "./solid-base.repository";
|
|
9
9
|
|
|
@@ -34,21 +34,92 @@ export class ScheduledJobRepository extends SolidBaseRepository<ScheduledJob> {
|
|
|
34
34
|
super(ScheduledJob, dataSource, requestContextService, securityRuleRepository);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
private normalizeComparableValue(value: any): any {
|
|
38
|
+
if (value instanceof Date) {
|
|
39
|
+
return value.toISOString();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (typeof value === "string") {
|
|
43
|
+
const trimmedValue = value.trim();
|
|
44
|
+
if (
|
|
45
|
+
(trimmedValue.startsWith("{") && trimmedValue.endsWith("}")) ||
|
|
46
|
+
(trimmedValue.startsWith("[") && trimmedValue.endsWith("]"))
|
|
47
|
+
) {
|
|
48
|
+
try {
|
|
49
|
+
return this.normalizeComparableValue(JSON.parse(trimmedValue));
|
|
50
|
+
} catch {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (Array.isArray(value)) {
|
|
58
|
+
return value.map((item) => this.normalizeComparableValue(item));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (value && typeof value === "object") {
|
|
62
|
+
return Object.keys(value)
|
|
63
|
+
.sort()
|
|
64
|
+
.reduce((result, key) => {
|
|
65
|
+
result[key] = this.normalizeComparableValue(value[key]);
|
|
66
|
+
return result;
|
|
67
|
+
}, {} as Record<string, any>);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private areValuesDifferent(currentValue: any, nextValue: any): boolean {
|
|
74
|
+
const normalizedCurrentValue = this.normalizeComparableValue(currentValue);
|
|
75
|
+
const normalizedNextValue = this.normalizeComparableValue(nextValue);
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
(normalizedCurrentValue && typeof normalizedCurrentValue === "object") ||
|
|
79
|
+
(normalizedNextValue && typeof normalizedNextValue === "object")
|
|
80
|
+
) {
|
|
81
|
+
return JSON.stringify(normalizedCurrentValue ?? null) !== JSON.stringify(normalizedNextValue ?? null);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return normalizedCurrentValue !== normalizedNextValue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private hasChanges(existing: ScheduledJob, dto: CreateScheduledJobDto, moduleEntity: ModuleMetadata): boolean {
|
|
88
|
+
return existing.scheduleName !== dto.scheduleName
|
|
89
|
+
|| existing.isActive !== dto.isActive
|
|
90
|
+
|| this.areValuesDifferent(existing.frequency, dto.frequency)
|
|
91
|
+
|| this.areValuesDifferent(existing.startTime, dto.startTime)
|
|
92
|
+
|| this.areValuesDifferent(existing.endTime, dto.endTime)
|
|
93
|
+
|| this.areValuesDifferent(existing.startDate, dto.startDate)
|
|
94
|
+
|| this.areValuesDifferent(existing.endDate, dto.endDate)
|
|
95
|
+
|| this.areValuesDifferent(existing.dayOfMonth, dto.dayOfMonth)
|
|
96
|
+
|| this.areValuesDifferent(existing.dayOfWeek, dto.dayOfWeek)
|
|
97
|
+
|| this.areValuesDifferent(existing.job, dto.job)
|
|
98
|
+
|| this.areValuesDifferent(existing.cronExpression, dto.cronExpression)
|
|
99
|
+
|| existing.module?.id !== moduleEntity.id;
|
|
100
|
+
}
|
|
101
|
+
|
|
37
102
|
/**
|
|
38
103
|
* Converts an entity to a plain DTO object.
|
|
104
|
+
* When manager is provided (e.g. from a TypeORM subscriber event), the
|
|
105
|
+
* fallback relation load uses it so the query runs on the active
|
|
106
|
+
* transaction's connection instead of the default DataSource pool.
|
|
39
107
|
*/
|
|
40
|
-
async toDto(scheduledJob: ScheduledJob): Promise<CreateScheduledJobDto> {
|
|
108
|
+
async toDto(scheduledJob: ScheduledJob, manager?: EntityManager): Promise<CreateScheduledJobDto> {
|
|
41
109
|
let populatedScheduledJob: ScheduledJob = scheduledJob;
|
|
42
110
|
// If the scheduled job does not have the module relation loaded, load it
|
|
43
111
|
if (!scheduledJob.module) {
|
|
44
|
-
|
|
112
|
+
const repo = manager
|
|
113
|
+
? manager.getRepository(ScheduledJob)
|
|
114
|
+
: this;
|
|
115
|
+
populatedScheduledJob = await repo.findOne({
|
|
45
116
|
where: {
|
|
46
117
|
id: scheduledJob.id,
|
|
47
118
|
},
|
|
48
119
|
relations: {
|
|
49
120
|
module: true,
|
|
50
121
|
},
|
|
51
|
-
});
|
|
122
|
+
}) ?? scheduledJob;
|
|
52
123
|
}
|
|
53
124
|
|
|
54
125
|
if (!populatedScheduledJob.module) {
|
|
@@ -91,9 +162,17 @@ export class ScheduledJobRepository extends SolidBaseRepository<ScheduledJob> {
|
|
|
91
162
|
};
|
|
92
163
|
const existing = await this.findOne({
|
|
93
164
|
where: { scheduleName: dto.scheduleName },
|
|
165
|
+
relations: {
|
|
166
|
+
module: true,
|
|
167
|
+
},
|
|
94
168
|
});
|
|
95
169
|
|
|
96
170
|
if (existing) {
|
|
171
|
+
if (!this.hasChanges(existing, dto, moduleEntity)) {
|
|
172
|
+
this.logger.debug(`Skipping scheduled job upsert for ${dto.scheduleName}; no changes detected.`);
|
|
173
|
+
return existing;
|
|
174
|
+
}
|
|
175
|
+
|
|
97
176
|
const merged = this.merge(existing, jobData);
|
|
98
177
|
this.logger.debug(`Updating scheduled job: ${dto.scheduleName}`);
|
|
99
178
|
return this.save(merged);
|
|
@@ -9,7 +9,7 @@ import { SecurityRule } from 'src/entities/security-rule.entity';
|
|
|
9
9
|
import { SolidRegistry } from 'src/helpers/solid-registry';
|
|
10
10
|
import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
|
|
11
11
|
import { CrudHelperService } from 'src/services/crud-helper.service';
|
|
12
|
-
import { Brackets, DataSource, SelectQueryBuilder } from 'typeorm';
|
|
12
|
+
import { Brackets, DataSource, EntityManager, SelectQueryBuilder } from 'typeorm';
|
|
13
13
|
import { SolidBaseRepository } from './solid-base.repository';
|
|
14
14
|
|
|
15
15
|
@Injectable()
|
|
@@ -84,12 +84,18 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
|
|
|
84
84
|
return configString.replace('$activeUserId', activeUser.sub.toString());
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
async toDto(securityRule: SecurityRule): Promise<UpdateSecurityRuleDto> {
|
|
87
|
+
async toDto(securityRule: SecurityRule, manager?: EntityManager): Promise<UpdateSecurityRuleDto> {
|
|
88
88
|
// load the role and model relations for the security rule
|
|
89
89
|
let populatedSecurityRule: SecurityRule = securityRule;
|
|
90
90
|
// If the security rule does not have the role and model relations loaded, load them
|
|
91
91
|
if (!securityRule.role || !securityRule.modelMetadata) {
|
|
92
|
-
|
|
92
|
+
// When a manager is provided (e.g. from a TypeORM subscriber event),
|
|
93
|
+
// use it so the query runs on the active transaction's connection
|
|
94
|
+
// instead of trying to acquire a second connection from the pool.
|
|
95
|
+
const repo = manager
|
|
96
|
+
? manager.getRepository(SecurityRule)
|
|
97
|
+
: this;
|
|
98
|
+
populatedSecurityRule = await repo.findOne({
|
|
93
99
|
where: {
|
|
94
100
|
id: securityRule.id,
|
|
95
101
|
},
|
|
@@ -97,7 +103,7 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
|
|
|
97
103
|
role: true,
|
|
98
104
|
modelMetadata: true,
|
|
99
105
|
},
|
|
100
|
-
});
|
|
106
|
+
}) ?? securityRule;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
return {
|