@solidxai/core 0.1.11-beta.0 → 0.1.11-beta.2
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.md +26 -0
- 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/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/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/repository/scheduled-job.repository.d.ts +3 -0
- package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
- package/dist/repository/scheduled-job.repository.js +60 -0
- package/dist/repository/scheduled-job.repository.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts +37 -2
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +1071 -232
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +7 -0
- 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/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 +6 -5
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.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/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/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/subscribers/audit.subscriber.d.ts +2 -0
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +25 -6
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.js +1 -0
- 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/package.json +1 -1
- package/src/controllers/service.controller.ts +3 -2
- package/src/helpers/field-crud-managers/IntFieldCrudManager.ts +4 -1
- package/src/helpers/solid-registry.ts +7 -1
- package/src/repository/scheduled-job.repository.ts +73 -0
- package/src/seeders/module-metadata-seeder.service.ts +1295 -264
- package/src/seeders/seed-data/solid-core-metadata.json +7 -0
- package/src/services/action-metadata.service.ts +87 -2
- package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +10 -5
- package/src/services/locale.service.ts +111 -3
- package/src/services/role-metadata.service.ts +112 -49
- package/src/services/view-metadata.service.ts +83 -9
- package/src/subscribers/audit.subscriber.ts +34 -14
- package/src/subscribers/computed-entity-field.subscriber.ts +1 -0
- package/src/subscribers/created-by-updated-by.subscriber.ts +3 -0
- package/src/subscribers/field-metadata.subscriber.ts +3 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"created-by-updated-by.subscriber.js","sourceRoot":"","sources":["../../src/subscribers/created-by-updated-by.subscriber.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAmD;AACnD,6CAAmD;AACnD,iFAA6E;AAC7E,qCAA0F;AAInF,IAAM,4BAA4B,GAAlC,MAAM,4BAA4B;IAErC,YAEqB,iBAA6B,EAC7B,qBAA4C;QAD5C,sBAAiB,GAAjB,iBAAiB,CAAY;QAC7B,0BAAqB,GAArB,qBAAqB,CAAuB;IAGjE,CAAC;IAED,gBAAgB,CAAC,UAAsB;QACnC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAuB;QACtC,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAuB;QACtC,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC5C,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,KAA0C,EAAE,QAAiB;QACtF,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAChB,OAAO;QACX,CAAC;QAED,MAAM,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,CAAC,aAAa,EAAE,CAAC;QACzE,IAAI,CAAC,qBAAqB,EAAE,CAAC;YACzB,OAAO;QACX,CAAC;
|
|
1
|
+
{"version":3,"file":"created-by-updated-by.subscriber.js","sourceRoot":"","sources":["../../src/subscribers/created-by-updated-by.subscriber.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAmD;AACnD,6CAAmD;AACnD,iFAA6E;AAC7E,qCAA0F;AAInF,IAAM,4BAA4B,GAAlC,MAAM,4BAA4B;IAErC,YAEqB,iBAA6B,EAC7B,qBAA4C;QAD5C,sBAAiB,GAAjB,iBAAiB,CAAY;QAC7B,0BAAqB,GAArB,qBAAqB,CAAuB;IAGjE,CAAC;IAED,gBAAgB,CAAC,UAAsB;QACnC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAuB;QACtC,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAuB;QACtC,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC5C,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,KAA0C,EAAE,QAAiB;QACtF,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAChB,OAAO;QACX,CAAC;QAED,MAAM,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,CAAC,aAAa,EAAE,CAAC;QACzE,IAAI,CAAC,qBAAqB,EAAE,CAAC;YACzB,OAAO;QACX,CAAC;QAcD,IAAI,QAAQ,EAAE,CAAC;YACX,KAAK,CAAC,MAAM,CAAC,SAAS,GAAG,qBAAqB,EAAE,GAAG,CAAC;YACpD,KAAK,CAAC,MAAM,CAAC,SAAS,GAAG,qBAAqB,EAAE,GAAG,CAAC;QACxD,CAAC;aACI,CAAC;YACF,KAAK,CAAC,MAAM,CAAC,SAAS,GAAG,qBAAqB,EAAE,GAAG,CAAC;QACxD,CAAC;IACL,CAAC;CASJ,CAAA;AA7DY,oEAA4B;uCAA5B,4BAA4B;IAFxC,IAAA,mBAAU,EAAC,EAAE,KAAK,EAAE,cAAK,CAAC,SAAS,EAAE,CAAC;IAK9B,WAAA,IAAA,0BAAgB,GAAE,CAAA;qCACiB,oBAAU;QACN,+CAAqB;GALxD,4BAA4B,CA6DxC","sourcesContent":["import { Injectable, Scope } from \"@nestjs/common\";\nimport { InjectDataSource } from \"@nestjs/typeorm\";\nimport { RequestContextService } from \"src/services/request-context.service\";\nimport { DataSource, EntitySubscriberInterface, InsertEvent, UpdateEvent } from \"typeorm\";\n\n@Injectable({ scope: Scope.TRANSIENT })\n// @EventSubscriber()\nexport class CreatedByUpdatedBySubscriber implements EntitySubscriberInterface {\n private dataSource: DataSource;\n constructor(\n @InjectDataSource()\n private readonly defaultDataSource: DataSource,\n private readonly requestContextService: RequestContextService,\n ) {\n // this.dataSource.subscribers.push(this);\n }\n\n bindToDataSource(dataSource: DataSource) {\n this.dataSource = dataSource;\n this.dataSource.subscribers.push(this);\n }\n\n async beforeInsert(event: InsertEvent<any>) {\n await this.stampUserField(event, true);\n }\n\n async beforeUpdate(event: UpdateEvent<any>) {\n await this.stampUserField(event, false);\n }\n\n private async stampUserField(event: InsertEvent<any> | UpdateEvent<any>, isInsert: boolean) {\n if (!event.entity) {\n return;\n }\n // Get the current active user details from the request context\n const activeUserOrUndefined = this.requestContextService.getActiveUser();\n if (!activeUserOrUndefined) {\n return;\n }\n\n // NOTE: If re-enabling loadUser below, query via `event.queryRunner.manager`, NOT\n // `this.defaultDataSource.getRepository(...)`. A second connection opened mid-transaction\n // deadlocks single-threaded engines (PGlite).\n // const loadedUser = await this.loadUser(activeUserOrUndefined as unknown as ActiveUserData);\n // if (isInsert) {\n // event.entity.createdBy = loadedUser?.id;\n // event.entity.updatedBy = loadedUser?.id; // For insert, we set both createdBy and updatedBy to the same user\n // }\n // else {\n // event.entity.updatedBy = loadedUser?.id;\n // }\n\n if (isInsert) {\n event.entity.createdBy = activeUserOrUndefined?.sub;\n event.entity.updatedBy = activeUserOrUndefined?.sub; // For insert, we set both createdBy and updatedBy to the same user\n }\n else {\n event.entity.updatedBy = activeUserOrUndefined?.sub;\n }\n }\n\n // private async loadUser(activeUser: ActiveUserData): Promise<User> {\n // const userRepo = this.defaultDataSource.getRepository(User); // Assuming 'User' is the entity name for users in your application\n // const loadedUser = await userRepo.findOne({\n // where: { id: activeUser.sub }, // Assuming 'sub' is the user ID in the JWT token\n // });\n // return loadedUser;;\n // }\n}"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-metadata.subscriber.d.ts","sourceRoot":"","sources":["../../src/subscribers/field-metadata.subscriber.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,yBAAyB,EAAgC,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAGlE,qBACa,uBAAwB,YAAW,yBAAyB,CAAC,aAAa,CAAC;IAKpF,OAAO,CAAC,QAAQ,CAAC,UAAU;IAJ7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4C;gBAIhD,UAAU,EAAE,UAAU;IAKzC,QAAQ;IAIF,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,aAAa,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"field-metadata.subscriber.d.ts","sourceRoot":"","sources":["../../src/subscribers/field-metadata.subscriber.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,yBAAyB,EAAgC,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAGlE,qBACa,uBAAwB,YAAW,yBAAyB,CAAC,aAAa,CAAC;IAKpF,OAAO,CAAC,QAAQ,CAAC,UAAU;IAJ7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4C;gBAIhD,UAAU,EAAE,UAAU;IAKzC,QAAQ;IAIF,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,aAAa,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAgCpE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-metadata.subscriber.js","sourceRoot":"","sources":["../../src/subscribers/field-metadata.subscriber.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAuE;AACvE,6CAAmD;AAEnD,qCAA2G;AAC3G,6EAAkE;AAI3D,IAAM,uBAAuB,+BAA7B,MAAM,uBAAuB;IAGlC,YAEE,UAAuC;QAAtB,eAAU,GAAV,UAAU,CAAY;QAJxB,WAAM,GAAG,IAAI,eAAM,CAAC,yBAAuB,CAAC,IAAI,CAAC,CAAC;QAMjE,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,QAAQ;QACN,OAAO,qCAAa,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAAiC;
|
|
1
|
+
{"version":3,"file":"field-metadata.subscriber.js","sourceRoot":"","sources":["../../src/subscribers/field-metadata.subscriber.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAuE;AACvE,6CAAmD;AAEnD,qCAA2G;AAC3G,6EAAkE;AAI3D,IAAM,uBAAuB,+BAA7B,MAAM,uBAAuB;IAGlC,YAEE,UAAuC;QAAtB,eAAU,GAAV,UAAU,CAAY;QAJxB,WAAM,GAAG,IAAI,eAAM,CAAC,yBAAuB,CAAC,IAAI,CAAC,CAAC;QAMjE,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,QAAQ;QACN,OAAO,qCAAa,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAAiC;IA+BnD,CAAC;CACF,CAAA;AA9CY,0DAAuB;kCAAvB,uBAAuB;IADnC,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,0BAAgB,GAAE,CAAA;qCACU,oBAAU;GAL9B,uBAAuB,CA8CnC","sourcesContent":["import { Injectable, Logger, NotFoundException } from '@nestjs/common';\nimport { InjectDataSource } from '@nestjs/typeorm';\nimport { ModelMetadataHelperService } from 'src/helpers/model-metadata-helper.service';\nimport { DataSource, EntitySubscriberInterface, EventSubscriber, InsertEvent, UpdateEvent } from 'typeorm';\nimport { FieldMetadata } from '../entities/field-metadata.entity';\n\n// @EventSubscriber()\n@Injectable()\nexport class FieldMetadataSubscriber implements EntitySubscriberInterface<FieldMetadata> {\n private readonly logger = new Logger(FieldMetadataSubscriber.name);\n\n constructor(\n @InjectDataSource()\n private readonly dataSource: DataSource,\n ) {\n this.dataSource.subscribers.push(this);\n }\n\n listenTo() {\n return FieldMetadata;\n }\n\n async afterUpdate(event: UpdateEvent<FieldMetadata>): Promise<void> {\n // NOTE: If re-enabling the block below, use `event.queryRunner.manager` (the active\n // transaction's connection), NOT `this.dataSource.getRepository(...)`. A second\n // connection opened here mid-transaction deadlocks single-threaded engines (PGlite).\n // If a field of type relation.many-to-one is marked for removal, and this field had an inverse relation created \n // It means there is a field of type relation.one-to-many in the co-model, which needs to be removed\n // if (event.entity && event.entity.isMarkedForRemoval && event.entity.type === 'relation' && event.entity.relationType === 'many-to-one' && event.entity.relationCreateInverse === true) {\n // const relationCoModelSingularName = event.entity.relationCoModelSingularName;\n // const relationCoModelFieldName = event.entity.relationCoModelFieldName;\n\n // // Load the field metadata for the co-model.\n // const coModelFieldMetadata = await this.dataSource\n // .getRepository(FieldMetadata)\n // .findOne({\n // where: {\n // name: relationCoModelFieldName,\n // type: 'relation',\n // relationType: 'one-to-many',\n // model: {\n // singularName: relationCoModelSingularName\n // }\n // },\n // });\n\n // // Mark the co-model isMarkedForRemoval field for removal.\n // if (coModelFieldMetadata) {\n // coModelFieldMetadata.isMarkedForRemoval = true;\n // await this.dataSource.getRepository(FieldMetadata).save(coModelFieldMetadata);\n // this.logger.debug(`Marked field ${coModelFieldMetadata.name} in model ${relationCoModelSingularName} for removal.`);\n // }\n // }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidxai/core",
|
|
3
|
-
"version": "0.1.11-beta.
|
|
3
|
+
"version": "0.1.11-beta.2",
|
|
4
4
|
"description": "This module is a NestJS module containing all the required core providers required by a Solid application",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,6 +10,7 @@ import { SolidRegistry } from '../helpers/solid-registry';
|
|
|
10
10
|
export interface PostProcessCodeGenConfig {
|
|
11
11
|
runModuleMetadataSeeder?: boolean; // If true, regenerate module metadata
|
|
12
12
|
runSolidIngestion?: boolean; // If true, run solid ingestion command
|
|
13
|
+
modulesToSeed?: string[]; // If provided, only seed these specific modules
|
|
13
14
|
}
|
|
14
15
|
@Controller('')
|
|
15
16
|
@ApiTags("Solid Core")
|
|
@@ -101,7 +102,7 @@ export class ServiceController {
|
|
|
101
102
|
return;
|
|
102
103
|
}
|
|
103
104
|
this.logger.log(`Running the seed() method for seeder :${seeder.constructor.name}`);
|
|
104
|
-
await seeder.seed();
|
|
105
|
+
await seeder.seed({ modulesToSeed: seedData?.modulesToSeed });
|
|
105
106
|
return { message: `seed data for ${seederName}` };
|
|
106
107
|
}
|
|
107
108
|
|
|
@@ -123,7 +124,7 @@ export class ServiceController {
|
|
|
123
124
|
if (!seeder) {
|
|
124
125
|
this.logger.error(`Seeder service ModuleMetadataSeederService not found. Does your service have a seed() method?`);
|
|
125
126
|
} else {
|
|
126
|
-
await seeder.seed();
|
|
127
|
+
await seeder.seed({ modulesToSeed: config.modulesToSeed });
|
|
127
128
|
}
|
|
128
129
|
} else {
|
|
129
130
|
this.logger.debug(`Skipping the Module Metadata Seeder Service as part of post-process code generation`);
|
|
@@ -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[] {
|
|
@@ -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> {
|
|
@@ -34,6 +34,71 @@ 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.
|
|
39
104
|
*/
|
|
@@ -91,9 +156,17 @@ export class ScheduledJobRepository extends SolidBaseRepository<ScheduledJob> {
|
|
|
91
156
|
};
|
|
92
157
|
const existing = await this.findOne({
|
|
93
158
|
where: { scheduleName: dto.scheduleName },
|
|
159
|
+
relations: {
|
|
160
|
+
module: true,
|
|
161
|
+
},
|
|
94
162
|
});
|
|
95
163
|
|
|
96
164
|
if (existing) {
|
|
165
|
+
if (!this.hasChanges(existing, dto, moduleEntity)) {
|
|
166
|
+
this.logger.debug(`Skipping scheduled job upsert for ${dto.scheduleName}; no changes detected.`);
|
|
167
|
+
return existing;
|
|
168
|
+
}
|
|
169
|
+
|
|
97
170
|
const merged = this.merge(existing, jobData);
|
|
98
171
|
this.logger.debug(`Updating scheduled job: ${dto.scheduleName}`);
|
|
99
172
|
return this.save(merged);
|