@solidxai/core 0.1.6-beta.0 → 0.1.6-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/dashboard-layout.controller.d.ts +47 -0
- package/dist/controllers/dashboard-layout.controller.d.ts.map +1 -0
- package/dist/controllers/dashboard-layout.controller.js +204 -0
- package/dist/controllers/dashboard-layout.controller.js.map +1 -0
- package/dist/controllers/scheduled-job.controller.d.ts +1 -0
- package/dist/controllers/scheduled-job.controller.d.ts.map +1 -1
- package/dist/controllers/scheduled-job.controller.js +12 -0
- package/dist/controllers/scheduled-job.controller.js.map +1 -1
- package/dist/dtos/create-dashboard-layout.dto.d.ts +8 -0
- package/dist/dtos/create-dashboard-layout.dto.d.ts.map +1 -0
- package/dist/dtos/create-dashboard-layout.dto.js +53 -0
- package/dist/dtos/create-dashboard-layout.dto.js.map +1 -0
- package/dist/dtos/create-dashboard-variable.dto.d.ts +1 -0
- package/dist/dtos/create-dashboard-variable.dto.d.ts.map +1 -1
- package/dist/dtos/create-dashboard-variable.dto.js +7 -1
- package/dist/dtos/create-dashboard-variable.dto.js.map +1 -1
- package/dist/dtos/update-dashboard-layout.dto.d.ts +8 -0
- package/dist/dtos/update-dashboard-layout.dto.d.ts.map +1 -0
- package/dist/dtos/update-dashboard-layout.dto.js +53 -0
- package/dist/dtos/update-dashboard-layout.dto.js.map +1 -0
- package/dist/dtos/update-dashboard-variable.dto.d.ts +1 -0
- package/dist/dtos/update-dashboard-variable.dto.d.ts.map +1 -1
- package/dist/dtos/update-dashboard-variable.dto.js +7 -1
- package/dist/dtos/update-dashboard-variable.dto.js.map +1 -1
- package/dist/entities/action-metadata.entity.d.ts.map +1 -1
- package/dist/entities/action-metadata.entity.js.map +1 -1
- package/dist/entities/ai-interaction.entity.d.ts.map +1 -1
- package/dist/entities/ai-interaction.entity.js +5 -4
- package/dist/entities/ai-interaction.entity.js.map +1 -1
- package/dist/entities/chatter-message-details.entity.d.ts +1 -0
- package/dist/entities/chatter-message-details.entity.d.ts.map +1 -1
- package/dist/entities/chatter-message-details.entity.js +9 -4
- package/dist/entities/chatter-message-details.entity.js.map +1 -1
- package/dist/entities/chatter-message.entity.d.ts.map +1 -1
- package/dist/entities/chatter-message.entity.js +4 -3
- package/dist/entities/chatter-message.entity.js.map +1 -1
- package/dist/entities/common.entity.js +1 -1
- package/dist/entities/common.entity.js.map +1 -1
- package/dist/entities/dashboard-layout.entity.d.ts +9 -0
- package/dist/entities/dashboard-layout.entity.d.ts.map +1 -0
- package/dist/entities/dashboard-layout.entity.js +41 -0
- package/dist/entities/dashboard-layout.entity.js.map +1 -0
- package/dist/entities/dashboard-question-sql-dataset-config.entity.d.ts.map +1 -1
- package/dist/entities/dashboard-question-sql-dataset-config.entity.js +5 -4
- package/dist/entities/dashboard-question-sql-dataset-config.entity.js.map +1 -1
- package/dist/entities/dashboard-question.entity.d.ts.map +1 -1
- package/dist/entities/dashboard-question.entity.js +5 -4
- package/dist/entities/dashboard-question.entity.js.map +1 -1
- package/dist/entities/dashboard-variable.entity.d.ts +1 -0
- package/dist/entities/dashboard-variable.entity.d.ts.map +1 -1
- package/dist/entities/dashboard-variable.entity.js +10 -4
- package/dist/entities/dashboard-variable.entity.js.map +1 -1
- package/dist/entities/dashboard.entity.d.ts +2 -0
- package/dist/entities/dashboard.entity.d.ts.map +1 -1
- package/dist/entities/dashboard.entity.js +9 -3
- package/dist/entities/dashboard.entity.js.map +1 -1
- package/dist/entities/email-attachment.entity.d.ts.map +1 -1
- package/dist/entities/email-attachment.entity.js +2 -1
- package/dist/entities/email-attachment.entity.js.map +1 -1
- package/dist/entities/email-template.entity.js +1 -1
- package/dist/entities/email-template.entity.js.map +1 -1
- package/dist/entities/export-transaction.entity.d.ts.map +1 -1
- package/dist/entities/export-transaction.entity.js +2 -1
- package/dist/entities/export-transaction.entity.js.map +1 -1
- package/dist/entities/field-metadata.entity.js +2 -2
- package/dist/entities/field-metadata.entity.js.map +1 -1
- package/dist/entities/import-transaction-error-log.entity.d.ts.map +1 -1
- package/dist/entities/import-transaction-error-log.entity.js +3 -2
- package/dist/entities/import-transaction-error-log.entity.js.map +1 -1
- package/dist/entities/import-transaction.entity.d.ts.map +1 -1
- package/dist/entities/import-transaction.entity.js +2 -1
- package/dist/entities/import-transaction.entity.js.map +1 -1
- package/dist/entities/legacy-common.entity.d.ts.map +1 -1
- package/dist/entities/legacy-common.entity.js +1 -1
- package/dist/entities/legacy-common.entity.js.map +1 -1
- package/dist/entities/mq-message.entity.d.ts.map +1 -1
- package/dist/entities/mq-message.entity.js +4 -3
- package/dist/entities/mq-message.entity.js.map +1 -1
- package/dist/entities/saved-filters.entity.d.ts.map +1 -1
- package/dist/entities/saved-filters.entity.js +3 -2
- package/dist/entities/saved-filters.entity.js.map +1 -1
- package/dist/entities/security-rule.entity.d.ts.map +1 -1
- package/dist/entities/security-rule.entity.js +2 -1
- package/dist/entities/security-rule.entity.js.map +1 -1
- package/dist/entities/sms-template.entity.js +1 -1
- package/dist/entities/sms-template.entity.js.map +1 -1
- package/dist/entities/user-view-metadata.entity.d.ts.map +1 -1
- package/dist/entities/user-view-metadata.entity.js +2 -1
- package/dist/entities/user-view-metadata.entity.js.map +1 -1
- package/dist/entities/view-metadata.entity.d.ts.map +1 -1
- package/dist/entities/view-metadata.entity.js.map +1 -1
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts +1 -0
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +8 -9
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
- package/dist/helpers/solid-registry.d.ts +3 -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.map +1 -1
- package/dist/helpers/typeorm-db-helper.js +21 -0
- package/dist/helpers/typeorm-db-helper.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +4 -1
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/repository/dashboard-layout.repository.d.ts +12 -0
- package/dist/repository/dashboard-layout.repository.d.ts.map +1 -0
- package/dist/repository/dashboard-layout.repository.js +34 -0
- package/dist/repository/dashboard-layout.repository.js.map +1 -0
- package/dist/seeders/module-metadata-seeder.service.js +4 -4
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +445 -35
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +45 -21
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +26 -0
- package/dist/services/chatter-message.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/computed-fields/entity/sequence-num-computed-field-provider.d.ts.map +1 -1
- package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.js +9 -10
- package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.js.map +1 -1
- package/dist/services/dashboard-layout.service.d.ts +20 -0
- package/dist/services/dashboard-layout.service.d.ts.map +1 -0
- package/dist/services/dashboard-layout.service.js +120 -0
- package/dist/services/dashboard-layout.service.js.map +1 -0
- package/dist/services/dashboard-question.service.d.ts +4 -0
- package/dist/services/dashboard-question.service.d.ts.map +1 -1
- package/dist/services/dashboard-question.service.js +22 -8
- package/dist/services/dashboard-question.service.js.map +1 -1
- package/dist/services/dashboard.service.d.ts +2 -0
- package/dist/services/dashboard.service.d.ts.map +1 -1
- package/dist/services/dashboard.service.js +4 -0
- package/dist/services/dashboard.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts +3 -1
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +122 -8
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/question-data-providers/chartjs-sql-data-provider.service.d.ts +2 -4
- package/dist/services/question-data-providers/chartjs-sql-data-provider.service.d.ts.map +1 -1
- package/dist/services/question-data-providers/chartjs-sql-data-provider.service.js +2 -1
- package/dist/services/question-data-providers/chartjs-sql-data-provider.service.js.map +1 -1
- package/dist/services/question-data-providers/interfaces.d.ts +1 -0
- package/dist/services/question-data-providers/interfaces.d.ts.map +1 -0
- package/dist/services/question-data-providers/interfaces.js +1 -0
- package/dist/services/question-data-providers/interfaces.js.map +1 -0
- package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.d.ts +2 -5
- package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.d.ts.map +1 -1
- package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.js +2 -1
- package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.js.map +1 -1
- package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.d.ts +2 -5
- package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.d.ts.map +1 -1
- package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.js +2 -1
- package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.js.map +1 -1
- package/dist/services/queues/database-subscriber.service.d.ts +4 -2
- package/dist/services/queues/database-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/database-subscriber.service.js +15 -2
- package/dist/services/queues/database-subscriber.service.js.map +1 -1
- package/dist/services/queues/publisher-factory.service.d.ts.map +1 -1
- package/dist/services/queues/publisher-factory.service.js +4 -6
- package/dist/services/queues/publisher-factory.service.js.map +1 -1
- package/dist/services/queues/rabbitmq-subscriber.service.d.ts +8 -3
- package/dist/services/queues/rabbitmq-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/rabbitmq-subscriber.service.js +72 -5
- package/dist/services/queues/rabbitmq-subscriber.service.js.map +1 -1
- package/dist/services/scheduled-job.service.d.ts +6 -1
- package/dist/services/scheduled-job.service.d.ts.map +1 -1
- package/dist/services/scheduled-job.service.js +26 -2
- package/dist/services/scheduled-job.service.js.map +1 -1
- package/dist/services/scheduled-jobs/scheduler.interface.d.ts +2 -0
- package/dist/services/scheduled-jobs/scheduler.interface.d.ts.map +1 -1
- package/dist/services/scheduled-jobs/scheduler.interface.js.map +1 -1
- package/dist/services/scheduled-jobs/scheduler.service.d.ts +6 -2
- package/dist/services/scheduled-jobs/scheduler.service.d.ts.map +1 -1
- package/dist/services/scheduled-jobs/scheduler.service.js +75 -17
- package/dist/services/scheduled-jobs/scheduler.service.js.map +1 -1
- package/dist/services/selection-providers/list-of-dashboard-question-providers-selection-provider.service.d.ts.map +1 -1
- package/dist/services/selection-providers/list-of-dashboard-question-providers-selection-provider.service.js +4 -1
- package/dist/services/selection-providers/list-of-dashboard-question-providers-selection-provider.service.js.map +1 -1
- package/dist/services/solid-ts-morph.service.d.ts +9 -0
- package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
- package/dist/services/solid-ts-morph.service.js +76 -0
- package/dist/services/solid-ts-morph.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +8 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.js +9 -1
- package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
- package/dist/transformers/typeorm/local-date-time-transformer.d.ts +4 -4
- package/dist/transformers/typeorm/local-date-time-transformer.d.ts.map +1 -1
- package/dist/transformers/typeorm/local-date-time-transformer.js +25 -28
- package/dist/transformers/typeorm/local-date-time-transformer.js.map +1 -1
- package/dist-tests/api/authenticate.spec.js +119 -0
- package/dist-tests/api/authenticate.spec.js.map +1 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +97 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +1 -0
- package/dist-tests/api/ping.spec.js +21 -0
- package/dist-tests/api/ping.spec.js.map +1 -0
- package/dist-tests/helpers/auth.js +41 -0
- package/dist-tests/helpers/auth.js.map +1 -0
- package/dist-tests/helpers/env.js +11 -0
- package/dist-tests/helpers/env.js.map +1 -0
- package/package.json +1 -1
- package/sql/default/mariadb/proc_CleanupModelMetadata.sql +153 -0
- package/sql/default/mariadb/proc_CleanupModuleMetadata.sql +56 -0
- package/sql/default/mysql/proc_CleanupModelMetadata.sql +153 -0
- package/sql/default/mysql/proc_CleanupModuleMetadata.sql +56 -0
- package/src/controllers/dashboard-layout.controller.ts +106 -0
- package/src/controllers/scheduled-job.controller.ts +6 -0
- package/src/dtos/create-dashboard-layout.dto.ts +31 -0
- package/src/dtos/create-dashboard-variable.dto.ts +4 -0
- package/src/dtos/update-dashboard-layout.dto.ts +30 -0
- package/src/dtos/update-dashboard-variable.dto.ts +5 -1
- package/src/entities/action-metadata.entity.ts +3 -2
- package/src/entities/ai-interaction.entity.ts +5 -4
- package/src/entities/chatter-message-details.entity.ts +7 -3
- package/src/entities/chatter-message.entity.ts +4 -3
- package/src/entities/common.entity.ts +2 -2
- package/src/entities/dashboard-layout.entity.ts +18 -0
- package/src/entities/dashboard-question-sql-dataset-config.entity.ts +5 -4
- package/src/entities/dashboard-question.entity.ts +5 -4
- package/src/entities/dashboard-variable.entity.ts +9 -4
- package/src/entities/dashboard.entity.ts +7 -2
- package/src/entities/email-attachment.entity.ts +2 -1
- package/src/entities/email-template.entity.ts +1 -1
- package/src/entities/export-transaction.entity.ts +2 -1
- package/src/entities/field-metadata.entity.ts +2 -2
- package/src/entities/import-transaction-error-log.entity.ts +3 -2
- package/src/entities/import-transaction.entity.ts +2 -1
- package/src/entities/legacy-common.entity.ts +3 -4
- package/src/entities/mq-message.entity.ts +4 -3
- package/src/entities/saved-filters.entity.ts +3 -2
- package/src/entities/security-rule.entity.ts +2 -1
- package/src/entities/sms-template.entity.ts +1 -1
- package/src/entities/user-view-metadata.entity.ts +2 -1
- package/src/entities/view-metadata.entity.ts +3 -0
- package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +9 -9
- package/src/helpers/solid-registry.ts +3 -2
- package/src/helpers/typeorm-db-helper.ts +26 -0
- package/src/index.ts +1 -0
- package/src/interfaces.ts +7 -1
- package/src/repository/dashboard-layout.repository.ts +17 -0
- package/src/seeders/module-metadata-seeder.service.ts +5 -5
- package/src/seeders/seed-data/solid-core-metadata.json +446 -36
- package/src/services/authentication.service.ts +47 -24
- package/src/services/chatter-message.service.ts +26 -0
- package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +6 -5
- package/src/services/computed-fields/entity/sequence-num-computed-field-provider.ts +17 -22
- package/src/services/dashboard-layout.service.ts +111 -0
- package/src/services/dashboard-question.service.ts +23 -4
- package/src/services/dashboard.service.ts +7 -0
- package/src/services/model-metadata.service.ts +131 -50
- package/src/services/question-data-providers/chartjs-sql-data-provider.service.ts +3 -7
- package/src/services/question-data-providers/interfaces.ts +0 -0
- package/src/services/question-data-providers/prime-react-datatable-sql-data-provider.service.ts +4 -8
- package/src/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.ts +4 -8
- package/src/services/queues/database-subscriber.service.ts +19 -2
- package/src/services/queues/publisher-factory.service.ts +8 -6
- package/src/services/queues/rabbitmq-subscriber.service.ts +115 -5
- package/src/services/scheduled-job.service.ts +31 -2
- package/src/services/scheduled-jobs/scheduler.interface.ts +4 -1
- package/src/services/scheduled-jobs/scheduler.service.ts +82 -20
- package/src/services/selection-providers/list-of-dashboard-question-providers-selection-provider.service.ts +4 -1
- package/src/services/solid-ts-morph.service.ts +98 -0
- package/src/solid-core.module.ts +12 -0
- package/src/subscribers/computed-entity-field.subscriber.ts +9 -3
- package/src/transformers/typeorm/local-date-time-transformer.ts +41 -33
- package/.claude/settings.local.json +0 -15
- package/src/services/1.js +0 -6
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BadRequestException, forwardRef, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
|
2
2
|
import { InjectDataSource } from '@nestjs/typeorm';
|
|
3
3
|
import * as fs from 'fs/promises'; // Use the Promise-based version of fs for async/await
|
|
4
|
+
import * as path from 'path';
|
|
4
5
|
import { DataSource, EntityManager, In, Repository, SelectQueryBuilder } from 'typeorm';
|
|
5
6
|
import { CreateModelMetadataDto } from '../dtos/create-model-metadata.dto';
|
|
6
7
|
import { ModelMetadata } from '../entities/model-metadata.entity';
|
|
@@ -33,6 +34,7 @@ import { RoleMetadataService } from './role-metadata.service';
|
|
|
33
34
|
import { NavigationDto } from 'src/dtos/navigation.dto';
|
|
34
35
|
import { SolidIntrospectService } from './solid-introspect.service';
|
|
35
36
|
import { CRUDService } from './crud.service';
|
|
37
|
+
import { SolidTsMorphService } from './solid-ts-morph.service';
|
|
36
38
|
|
|
37
39
|
@Injectable()
|
|
38
40
|
export class ModelMetadataService {
|
|
@@ -54,8 +56,9 @@ export class ModelMetadataService {
|
|
|
54
56
|
private readonly roleService: RoleMetadataService,
|
|
55
57
|
private readonly moduleMetadataHelperService: ModuleMetadataHelperService,
|
|
56
58
|
readonly introspectService: SolidIntrospectService,
|
|
59
|
+
private readonly solidTsMorphService: SolidTsMorphService,
|
|
57
60
|
|
|
58
|
-
// No longer used.
|
|
61
|
+
// No longer used.
|
|
59
62
|
// private readonly generateCodePublihser: GenerateCodePublisherDatabase,
|
|
60
63
|
) { }
|
|
61
64
|
|
|
@@ -225,6 +228,7 @@ export class ModelMetadataService {
|
|
|
225
228
|
let userKeyField = null;
|
|
226
229
|
const listViewLayout = [];
|
|
227
230
|
const formViewLayout = [];
|
|
231
|
+
const treeViewLayout = [];
|
|
228
232
|
|
|
229
233
|
for (let k = 0; k < fieldsMetadata.length; k++) {
|
|
230
234
|
const fieldMetadata = fieldsMetadata[k];
|
|
@@ -245,6 +249,7 @@ export class ModelMetadataService {
|
|
|
245
249
|
}
|
|
246
250
|
listViewLayout.push({ type: "field", attrs: { name: `${affectedField.name}` } })
|
|
247
251
|
formViewLayout.push({ type: "field", attrs: { name: `${affectedField.name}` } })
|
|
252
|
+
treeViewLayout.push({ type: "field", attrs: { name: `${affectedField.name}` } })
|
|
248
253
|
|
|
249
254
|
}
|
|
250
255
|
|
|
@@ -705,48 +710,24 @@ export class ModelMetadataService {
|
|
|
705
710
|
await fs.writeFile(filePath, updatedContent);
|
|
706
711
|
}
|
|
707
712
|
|
|
708
|
-
// <moduleName>.module.ts | Remove all references and imports of the
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
// const objectLiteral = moduleDecorator?.getCallExpression()?.getArguments()?.[0];
|
|
727
|
-
|
|
728
|
-
// if (objectLiteral && objectLiteral.getKind() === SyntaxKind.ObjectLiteralExpression) {
|
|
729
|
-
// const objectLiteralExpr = objectLiteral.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
730
|
-
|
|
731
|
-
// for (const propName of ['imports', 'providers', 'controllers', 'exports']) {
|
|
732
|
-
// const prop = objectLiteralExpr.getProperty(propName);
|
|
733
|
-
// if (prop && prop.getKind() === SyntaxKind.PropertyAssignment) {
|
|
734
|
-
// const elements = prop.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
|
|
735
|
-
// elements?.getElements().forEach(el => {
|
|
736
|
-
// const text = el.getText();
|
|
737
|
-
// if (filesToDelete.some(file => text.toLowerCase().includes(file.split('.')[0]))) {
|
|
738
|
-
// // @ts-ignore
|
|
739
|
-
// el.remove();
|
|
740
|
-
// }
|
|
741
|
-
// });
|
|
742
|
-
// }
|
|
743
|
-
// }
|
|
744
|
-
// }
|
|
745
|
-
|
|
746
|
-
// // Save changes
|
|
747
|
-
// sourceFile.saveSync();
|
|
748
|
-
|
|
749
|
-
// Run seeder to reflect the removal.
|
|
713
|
+
// <moduleName>.module.ts | Remove all references and imports of the deleted model files. | Automatic
|
|
714
|
+
if (modulePath) {
|
|
715
|
+
const moduleFilePath = path.resolve(modulePath, `${dasherize(modelEntity.module?.name)}.module.ts`);
|
|
716
|
+
this.logger.log(`Removing model '${modelEntity.singularName}' references from module file: ${moduleFilePath}`);
|
|
717
|
+
try {
|
|
718
|
+
this.solidTsMorphService.begin();
|
|
719
|
+
const modelPathSegment = `/${dasherize(modelEntity.singularName)}.`;
|
|
720
|
+
const { removedIdentifiers } = this.solidTsMorphService.removeImports(
|
|
721
|
+
moduleFilePath,
|
|
722
|
+
spec => spec.includes(modelPathSegment)
|
|
723
|
+
);
|
|
724
|
+
this.solidTsMorphService.removeModuleMembers(moduleFilePath, removedIdentifiers);
|
|
725
|
+
await this.solidTsMorphService.commit();
|
|
726
|
+
} catch (error) {
|
|
727
|
+
this.solidTsMorphService.rollback();
|
|
728
|
+
this.logger.error(`Failed to clean up module file for model '${modelEntity.singularName}':`, error);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
750
731
|
|
|
751
732
|
// - | Drop database table | Removes the database table from the DB, this is a very risky step. Best to review all relations to other models etc and then do this manually | Manual (X)
|
|
752
733
|
|
|
@@ -818,6 +799,7 @@ export class ModelMetadataService {
|
|
|
818
799
|
const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
|
|
819
800
|
|
|
820
801
|
const listViewLayoutFields = [{ type: "field", attrs: { name: `id` } }];
|
|
802
|
+
const treeViewLayoutFields = [{ type: "field", attrs: { name: `id` } }];
|
|
821
803
|
const formViewLayoutFields = [];
|
|
822
804
|
|
|
823
805
|
for (let i = 0; i < model.fields.length; i++) {
|
|
@@ -825,8 +807,9 @@ export class ModelMetadataService {
|
|
|
825
807
|
if (field.isSystem) continue;
|
|
826
808
|
listViewLayoutFields.push({ type: "field", attrs: { name: `${field.name}` } })
|
|
827
809
|
formViewLayoutFields.push({ type: "field", attrs: { name: `${field.name}` } })
|
|
810
|
+
treeViewLayoutFields.push({ type: "field", attrs: { name: `${field.name}` } })
|
|
828
811
|
}
|
|
829
|
-
this.populateVAMConfigInFileInternal(formViewLayoutFields, model, listViewLayoutFields, metaData);
|
|
812
|
+
this.populateVAMConfigInFileInternal(formViewLayoutFields, model, listViewLayoutFields, treeViewLayoutFields, metaData);
|
|
830
813
|
// Write the updated object back to the file
|
|
831
814
|
const updatedContent = JSON.stringify(metaData, null, 2);
|
|
832
815
|
await fs.writeFile(filePath, updatedContent);
|
|
@@ -839,7 +822,7 @@ export class ModelMetadataService {
|
|
|
839
822
|
}
|
|
840
823
|
|
|
841
824
|
// Populate the View, Actions and Menus in the config file
|
|
842
|
-
private populateVAMConfigInFileInternal(formViewLayoutFields: any[], model: ModelMetadata, listViewLayoutFields: { type: string; attrs: { name: string; }; }[], metaData: any) {
|
|
825
|
+
private populateVAMConfigInFileInternal(formViewLayoutFields: any[], model: ModelMetadata, listViewLayoutFields: { type: string; attrs: { name: string; }; }[], treeViewLayoutFields: { type: string; attrs: { name: string; }; }[], metaData: any) {
|
|
843
826
|
const column1Fields = [];
|
|
844
827
|
const column2Fields = [];
|
|
845
828
|
|
|
@@ -852,7 +835,9 @@ export class ModelMetadataService {
|
|
|
852
835
|
}
|
|
853
836
|
}
|
|
854
837
|
const actionName = `${model.singularName}-list-action`;
|
|
855
|
-
const
|
|
838
|
+
const treeViewActionName = `${model.singularName}-tree-view-action`;
|
|
839
|
+
const listViewName = `${model.singularName}-list-view`;
|
|
840
|
+
const treeViewName = `${model.singularName}-tree-view`;
|
|
856
841
|
const formViewName = `${model.singularName}-form-view`;
|
|
857
842
|
const menuName = `${model.singularName}-menu-item`;
|
|
858
843
|
|
|
@@ -865,7 +850,21 @@ export class ModelMetadataService {
|
|
|
865
850
|
customComponent: ``,
|
|
866
851
|
customIsModal: true,
|
|
867
852
|
serverEndpoint: "",
|
|
868
|
-
viewUserKey:
|
|
853
|
+
viewUserKey: listViewName,
|
|
854
|
+
moduleUserKey: `${model.module.name}`,
|
|
855
|
+
modelUserKey: `${model.singularName}`
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
const treeViewAction = {
|
|
859
|
+
displayName: `${model.displayName} Tree View Action`,
|
|
860
|
+
name: treeViewActionName,
|
|
861
|
+
type: "solid",
|
|
862
|
+
domain: "",
|
|
863
|
+
context: "",
|
|
864
|
+
customComponent: ``,
|
|
865
|
+
customIsModal: true,
|
|
866
|
+
serverEndpoint: "",
|
|
867
|
+
viewUserKey: treeViewName,
|
|
869
868
|
moduleUserKey: `${model.module.name}`,
|
|
870
869
|
modelUserKey: `${model.singularName}`
|
|
871
870
|
};
|
|
@@ -877,11 +876,11 @@ export class ModelMetadataService {
|
|
|
877
876
|
actionUserKey: actionName,
|
|
878
877
|
moduleUserKey: `${model.module.name}`,
|
|
879
878
|
parentMenuItemUserKey: "",
|
|
880
|
-
iconName
|
|
879
|
+
iconName: ""
|
|
881
880
|
};
|
|
882
881
|
|
|
883
882
|
const modelListview = {
|
|
884
|
-
name:
|
|
883
|
+
name: listViewName,
|
|
885
884
|
displayName: `${model.displayName}`,
|
|
886
885
|
type: "list",
|
|
887
886
|
context: "{}",
|
|
@@ -905,6 +904,31 @@ export class ModelMetadataService {
|
|
|
905
904
|
}
|
|
906
905
|
};
|
|
907
906
|
|
|
907
|
+
const modelTreeview = {
|
|
908
|
+
name: treeViewName,
|
|
909
|
+
displayName: `${model.displayName}`,
|
|
910
|
+
type: "tree",
|
|
911
|
+
context: "{}",
|
|
912
|
+
moduleUserKey: `${model.module.name}`,
|
|
913
|
+
modelUserKey: `${model.singularName}`,
|
|
914
|
+
layout: {
|
|
915
|
+
type: "tree",
|
|
916
|
+
attrs: {
|
|
917
|
+
pagination: true,
|
|
918
|
+
pageSizeOptions: [
|
|
919
|
+
10,
|
|
920
|
+
25,
|
|
921
|
+
50
|
|
922
|
+
],
|
|
923
|
+
enableGlobalSearch: true,
|
|
924
|
+
create: true,
|
|
925
|
+
edit: true,
|
|
926
|
+
delete: true
|
|
927
|
+
},
|
|
928
|
+
children: treeViewLayoutFields
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
|
|
908
932
|
|
|
909
933
|
const modelFormView = {
|
|
910
934
|
name: formViewName,
|
|
@@ -954,10 +978,18 @@ export class ModelMetadataService {
|
|
|
954
978
|
metaData.actions.push(action);
|
|
955
979
|
}
|
|
956
980
|
|
|
957
|
-
if (notExists(metaData.
|
|
981
|
+
if (notExists(metaData.actions, treeViewActionName)) {
|
|
982
|
+
metaData.actions.push(treeViewAction);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
if (notExists(metaData.views, listViewName)) {
|
|
958
986
|
metaData.views.push(modelListview);
|
|
959
987
|
}
|
|
960
988
|
|
|
989
|
+
if (notExists(metaData.views, treeViewName)) {
|
|
990
|
+
metaData.views.push(modelTreeview);
|
|
991
|
+
}
|
|
992
|
+
|
|
961
993
|
if (notExists(metaData.views, formViewName)) {
|
|
962
994
|
metaData.views.push(modelFormView);
|
|
963
995
|
}
|
|
@@ -979,6 +1011,14 @@ export class ModelMetadataService {
|
|
|
979
1011
|
}
|
|
980
1012
|
}));
|
|
981
1013
|
|
|
1014
|
+
const treeViewLayout = jsonFieldsList.map(field => ({
|
|
1015
|
+
type: "field",
|
|
1016
|
+
attrs: {
|
|
1017
|
+
name: `${field.name}`,
|
|
1018
|
+
isSearchable: true,
|
|
1019
|
+
}
|
|
1020
|
+
}));
|
|
1021
|
+
|
|
982
1022
|
const formViewLayout = jsonFieldsList.map(field => ({
|
|
983
1023
|
type: "field",
|
|
984
1024
|
attrs: {
|
|
@@ -1019,6 +1059,26 @@ export class ModelMetadataService {
|
|
|
1019
1059
|
children: listViewLayout
|
|
1020
1060
|
}, null, 3)
|
|
1021
1061
|
},
|
|
1062
|
+
{
|
|
1063
|
+
name: `${model.singularName}-tree-view`,
|
|
1064
|
+
displayName: `${model.displayName}`,
|
|
1065
|
+
type: 'tree',
|
|
1066
|
+
context: "{}",
|
|
1067
|
+
module: resolvedModule,
|
|
1068
|
+
model: model,
|
|
1069
|
+
layout: JSON.stringify({
|
|
1070
|
+
type: "tree",
|
|
1071
|
+
attrs: {
|
|
1072
|
+
pagination: true,
|
|
1073
|
+
pageSizeOptions: [10, 25, 50],
|
|
1074
|
+
enableGlobalSearch: true,
|
|
1075
|
+
create: true,
|
|
1076
|
+
edit: true,
|
|
1077
|
+
delete: true
|
|
1078
|
+
},
|
|
1079
|
+
children: treeViewLayout
|
|
1080
|
+
}, null, 3)
|
|
1081
|
+
},
|
|
1022
1082
|
{
|
|
1023
1083
|
name: `${model.singularName}-form-view`,
|
|
1024
1084
|
displayName: `${model.displayName}`,
|
|
@@ -1067,6 +1127,7 @@ export class ModelMetadataService {
|
|
|
1067
1127
|
}
|
|
1068
1128
|
|
|
1069
1129
|
let view = await viewRepo.findOne({ where: { name: `${model.singularName}-list-view` } });
|
|
1130
|
+
let treeView = await viewRepo.findOne({ where: { name: `${model.singularName}-tree-view` } });
|
|
1070
1131
|
|
|
1071
1132
|
const actionData = {
|
|
1072
1133
|
displayName: `${model.displayName} List Action`,
|
|
@@ -1082,13 +1143,33 @@ export class ModelMetadataService {
|
|
|
1082
1143
|
model: model
|
|
1083
1144
|
};
|
|
1084
1145
|
|
|
1146
|
+
const treeViewActionData = {
|
|
1147
|
+
displayName: `${model.displayName} Tree View Action`,
|
|
1148
|
+
name: `${model.singularName}-tree-view-action`,
|
|
1149
|
+
type: "solid",
|
|
1150
|
+
domain: "",
|
|
1151
|
+
context: "",
|
|
1152
|
+
customComponent: ``,
|
|
1153
|
+
customIsModal: true,
|
|
1154
|
+
serverEndpoint: "",
|
|
1155
|
+
view: treeView,
|
|
1156
|
+
module: resolvedModule,
|
|
1157
|
+
model: model
|
|
1158
|
+
};
|
|
1159
|
+
|
|
1085
1160
|
let existingAction = await actionRepo.findOne({ where: { name: actionData.name } });
|
|
1161
|
+
let existingTreeViewAction = await actionRepo.findOne({ where: { name: treeViewActionData.name } });
|
|
1086
1162
|
|
|
1087
1163
|
if (!existingAction) {
|
|
1088
1164
|
const createdAction = actionRepo.create(actionData);
|
|
1089
1165
|
existingAction = await actionRepo.save(createdAction);
|
|
1090
1166
|
}
|
|
1091
1167
|
|
|
1168
|
+
if (!existingTreeViewAction) {
|
|
1169
|
+
const createdTreeViewAction = actionRepo.create(treeViewActionData);
|
|
1170
|
+
existingTreeViewAction = await actionRepo.save(createdTreeViewAction);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1092
1173
|
const adminRole = await this.roleService.findRoleByName('Admin');
|
|
1093
1174
|
|
|
1094
1175
|
const menuData = {
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { Injectable, Logger } from "@nestjs/common";
|
|
2
2
|
import { DashboardQuestionDataProvider } from "src/decorators/dashboard-question-data-provider.decorator";
|
|
3
3
|
import { DashboardQuestion } from "src/entities/dashboard-question.entity";
|
|
4
|
-
import { IDashboardQuestionDataProvider } from "src/interfaces";
|
|
4
|
+
import { IDashboardQuestionDataProvider, QuestionSqlDataProviderContext } from "src/interfaces";
|
|
5
5
|
import { EntityManager } from "typeorm";
|
|
6
6
|
import { SqlExpressionResolverService } from "../sql-expression-resolver.service";
|
|
7
7
|
import { getKpi, getLabels } from "./helpers";
|
|
8
8
|
|
|
9
|
-
export interface QuestionSqlDataProviderContext {
|
|
10
|
-
// questionSqlDatasetConfig: QuestionSqlDatasetConfig;
|
|
11
|
-
// questionId: number;
|
|
12
|
-
// question: Question;
|
|
13
|
-
}
|
|
14
9
|
|
|
15
10
|
export enum SqlExpressionOperator {
|
|
16
11
|
EQUALS = '$equals',
|
|
@@ -49,7 +44,8 @@ export class ChartJsSqlDataProvider implements IDashboardQuestionDataProvider<Qu
|
|
|
49
44
|
return "ChartJsSqlDataProvider";
|
|
50
45
|
}
|
|
51
46
|
|
|
52
|
-
async getData(question: DashboardQuestion,
|
|
47
|
+
async getData(question: DashboardQuestion, context?: QuestionSqlDataProviderContext): Promise<any> {
|
|
48
|
+
const expressions: SqlExpression[] = context?.expressions || [];
|
|
53
49
|
// TODO: put some validation to check if the results of each SQL in each dataset returns the same number of rows
|
|
54
50
|
|
|
55
51
|
// This is what we have to return.
|
|
File without changes
|
package/src/services/question-data-providers/prime-react-datatable-sql-data-provider.service.ts
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import { Injectable } from "@nestjs/common";
|
|
2
2
|
import { DashboardQuestionDataProvider } from "src/decorators/dashboard-question-data-provider.decorator";
|
|
3
3
|
import { DashboardQuestion } from "src/entities/dashboard-question.entity";
|
|
4
|
-
import { IDashboardQuestionDataProvider } from "src/interfaces";
|
|
4
|
+
import { IDashboardQuestionDataProvider, QuestionSqlDataProviderContext } from "src/interfaces";
|
|
5
5
|
import { EntityManager } from "typeorm";
|
|
6
6
|
import { SqlExpressionResolverService } from "../sql-expression-resolver.service";
|
|
7
7
|
import { Logger } from '@nestjs/common';
|
|
8
8
|
import { SqlExpression } from "./chartjs-sql-data-provider.service";
|
|
9
9
|
import { getKpi } from "./helpers";
|
|
10
10
|
|
|
11
|
-
export interface QuestionSqlDataProviderContext {
|
|
12
|
-
// questionSqlDatasetConfig: QuestionSqlDatasetConfig;
|
|
13
|
-
// questionId: number;
|
|
14
|
-
// question: Question;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
@DashboardQuestionDataProvider()
|
|
18
12
|
@Injectable()
|
|
19
13
|
export class PrimeReactDatatableSqlDataProvider implements IDashboardQuestionDataProvider<QuestionSqlDataProviderContext, any> {
|
|
@@ -29,7 +23,9 @@ export class PrimeReactDatatableSqlDataProvider implements IDashboardQuestionDat
|
|
|
29
23
|
return "PrimeReactDatatableSqlDataProvider";
|
|
30
24
|
}
|
|
31
25
|
|
|
32
|
-
async getData(question: DashboardQuestion,
|
|
26
|
+
async getData(question: DashboardQuestion, context?: QuestionSqlDataProviderContext): Promise<any> {
|
|
27
|
+
const expressions: SqlExpression[] = context?.expressions || [];
|
|
28
|
+
|
|
33
29
|
// TODO: put some validation to check if the results of each SQL in each dataset returns the same number of rows
|
|
34
30
|
|
|
35
31
|
// Check the expected response for prime react data tables to understand what is going on here...
|
package/src/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.ts
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import { Injectable } from "@nestjs/common";
|
|
2
2
|
import { DashboardQuestionDataProvider } from "src/decorators/dashboard-question-data-provider.decorator";
|
|
3
3
|
import { DashboardQuestion } from "src/entities/dashboard-question.entity";
|
|
4
|
-
import { IDashboardQuestionDataProvider } from "src/interfaces";
|
|
4
|
+
import { IDashboardQuestionDataProvider, QuestionSqlDataProviderContext } from "src/interfaces";
|
|
5
5
|
import { EntityManager } from "typeorm";
|
|
6
6
|
import { SqlExpressionResolverService } from "../sql-expression-resolver.service";
|
|
7
7
|
import { Logger } from '@nestjs/common';
|
|
8
8
|
import { SqlExpression } from "./chartjs-sql-data-provider.service";
|
|
9
9
|
import { getKpi } from "./helpers";
|
|
10
10
|
|
|
11
|
-
export interface QuestionSqlDataProviderContext {
|
|
12
|
-
// questionSqlDatasetConfig: QuestionSqlDatasetConfig;
|
|
13
|
-
// questionId: number;
|
|
14
|
-
// question: Question;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
@DashboardQuestionDataProvider()
|
|
18
12
|
@Injectable()
|
|
19
13
|
export class PrimeReactMeterGroupSqlDataProvider implements IDashboardQuestionDataProvider<QuestionSqlDataProviderContext, any> {
|
|
@@ -58,7 +52,9 @@ export class PrimeReactMeterGroupSqlDataProvider implements IDashboardQuestionDa
|
|
|
58
52
|
return colors;
|
|
59
53
|
}
|
|
60
54
|
|
|
61
|
-
async getData(question: DashboardQuestion,
|
|
55
|
+
async getData(question: DashboardQuestion, context?: QuestionSqlDataProviderContext): Promise<any> {
|
|
56
|
+
const expressions: SqlExpression[] = context?.expressions || [];
|
|
57
|
+
|
|
62
58
|
// TODO: put some validation to check if the results of each SQL in each dataset returns the same number of rows
|
|
63
59
|
|
|
64
60
|
// This is what we have to return.
|
|
@@ -7,7 +7,7 @@ import { PollerService } from '../poller.service';
|
|
|
7
7
|
import { buildNamespacedQueueName } from './common';
|
|
8
8
|
|
|
9
9
|
export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscriber<T> {
|
|
10
|
-
private
|
|
10
|
+
private _loggerInstance?: Logger;
|
|
11
11
|
private readonly url: string;
|
|
12
12
|
private readonly serviceRole: string;
|
|
13
13
|
|
|
@@ -23,6 +23,17 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
23
23
|
// this.logger.debug(`DatabaseSubscriber instance created with options: ${JSON.stringify(this.options())}`);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
protected get loggerContext(): string {
|
|
27
|
+
return this.constructor.name;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
protected get logger(): Logger {
|
|
31
|
+
if (!this._loggerInstance) {
|
|
32
|
+
this._loggerInstance = new Logger(this.loggerContext);
|
|
33
|
+
}
|
|
34
|
+
return this._loggerInstance;
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
abstract subscribe(message: QueueMessage<T>);
|
|
27
38
|
|
|
28
39
|
abstract options(): QueuesModuleOptions;
|
|
@@ -78,9 +89,15 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
78
89
|
const defaultBroker = process.env.QUEUES_DEFAULT_BROKER || 'database';
|
|
79
90
|
const solidCliRunning = process.env.SOLID_CLI_RUNNING || "false";
|
|
80
91
|
const queueNameRegex = (process.env.QUEUES_QUEUE_NAME_REGEX_TO_ENABLE || '').trim();
|
|
92
|
+
const roleAllowed = ['both', 'subscriber'].includes(this.serviceRole);
|
|
93
|
+
|
|
94
|
+
if (!roleAllowed) {
|
|
95
|
+
this.logger.log(`DatabaseSubscriber is disabled because QUEUES_SERVICE_ROLE is "${this.serviceRole}". Expected "both" or "subscriber".`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
81
98
|
|
|
82
99
|
// we will start subscriber only if the current service role is subscriber.
|
|
83
|
-
if (
|
|
100
|
+
if (defaultBroker === 'database' && solidCliRunning === "false") {
|
|
84
101
|
const options = this.options();
|
|
85
102
|
const queueName = options.queueName;
|
|
86
103
|
|
|
@@ -23,14 +23,16 @@ export class PublisherFactory<T> {
|
|
|
23
23
|
// Register all ISolidDatabaseModules implementations
|
|
24
24
|
let actualPublisherToUse = this.solidIntrospectionService.getProvider(resolvedPublisherName);
|
|
25
25
|
if (!actualPublisherToUse) {
|
|
26
|
+
// Relaxed extra check in place to make sure we do not have to refactor old publishers or publishers named without the ____RabbitMq or ____Database convention
|
|
27
|
+
actualPublisherToUse = this.solidIntrospectionService.getProvider(publisherName);
|
|
26
28
|
|
|
27
29
|
// Extra check in place to make sure we do not have to refactor old publishers which have been created earlier.
|
|
28
|
-
if (defaultBrokerToUse === 'rabbitmq') {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
30
|
+
// if (defaultBrokerToUse === 'rabbitmq') {
|
|
31
|
+
// actualPublisherToUse = this.solidIntrospectionService.getProvider(publisherName);
|
|
32
|
+
// }
|
|
33
|
+
}
|
|
34
|
+
if (!actualPublisherToUse) {
|
|
35
|
+
throw new Error(`Unable to locate publisher with name ${resolvedPublisherName}`);
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
// type safe
|
|
@@ -6,9 +6,20 @@ import { MqMessageQueueService } from '../mq-message-queue.service';
|
|
|
6
6
|
import { MqMessageService } from '../mq-message.service';
|
|
7
7
|
import { buildNamespacedQueueName } from './common';
|
|
8
8
|
|
|
9
|
+
class ConsumerProcessingTimeoutError extends Error {
|
|
10
|
+
constructor(
|
|
11
|
+
readonly queueName: string,
|
|
12
|
+
readonly messageId: string,
|
|
13
|
+
readonly timeoutMs: number,
|
|
14
|
+
) {
|
|
15
|
+
super(`Subscriber processing timed out after ${timeoutMs}ms for queue ${queueName} and messageId ${messageId}`);
|
|
16
|
+
this.name = 'ConsumerProcessingTimeoutError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
|
|
10
21
|
export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscriber<T> { // TODO This can be made a generic type for better type visibility
|
|
11
|
-
private
|
|
22
|
+
private _loggerInstance?: Logger;
|
|
12
23
|
private readonly url: string;
|
|
13
24
|
private readonly serviceRole: string;
|
|
14
25
|
private connection: amqp.Connection | null = null;
|
|
@@ -30,6 +41,17 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
30
41
|
// this.logger.debug(`RabbitMqSubscriber instance created with options: ${JSON.stringify(this.options())} and url: ${this.url}`);
|
|
31
42
|
}
|
|
32
43
|
|
|
44
|
+
protected get loggerContext(): string {
|
|
45
|
+
return this.constructor.name;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected get logger(): Logger {
|
|
49
|
+
if (!this._loggerInstance) {
|
|
50
|
+
this._loggerInstance = new Logger(this.loggerContext);
|
|
51
|
+
}
|
|
52
|
+
return this._loggerInstance;
|
|
53
|
+
}
|
|
54
|
+
|
|
33
55
|
abstract subscribe(message: QueueMessage<T>);
|
|
34
56
|
|
|
35
57
|
abstract options(): QueuesModuleOptions;
|
|
@@ -62,9 +84,15 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
62
84
|
const defaultBroker = process.env.QUEUES_DEFAULT_BROKER || 'rabbitmq';
|
|
63
85
|
const solidCliRunning = process.env.SOLID_CLI_RUNNING || "false";
|
|
64
86
|
const queueNameRegex = (process.env.QUEUES_QUEUE_NAME_REGEX_TO_ENABLE || '').trim();
|
|
87
|
+
const roleAllowed = ['both', 'subscriber'].includes(this.serviceRole);
|
|
88
|
+
|
|
89
|
+
if (!roleAllowed) {
|
|
90
|
+
this.logger.log(`RabbitMqSubscriber is disabled because QUEUES_SERVICE_ROLE is "${this.serviceRole}". Expected "both" or "subscriber".`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
65
93
|
|
|
66
94
|
// we will start subscriber only if the current service role is subscriber.
|
|
67
|
-
if (this.url &&
|
|
95
|
+
if (this.url && solidCliRunning === "false" && defaultBroker === 'rabbitmq') {
|
|
68
96
|
const options = this.options();
|
|
69
97
|
const queueName = options.queueName;
|
|
70
98
|
|
|
@@ -102,6 +130,10 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
102
130
|
if (prefetch < 1) {
|
|
103
131
|
throw new Error(`RabbitMqSubscriber prefetch must be >= 1 for queue ${queueName}`);
|
|
104
132
|
}
|
|
133
|
+
const processingTimeoutMs = this.resolveProcessingTimeoutMs();
|
|
134
|
+
if (processingTimeoutMs > 0) {
|
|
135
|
+
this.logger.log(`RabbitMqSubscriber using processing timeout ${processingTimeoutMs}ms for queue ${queueName}`);
|
|
136
|
+
}
|
|
105
137
|
|
|
106
138
|
let connection: amqp.Connection;
|
|
107
139
|
try {
|
|
@@ -186,7 +218,7 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
186
218
|
if (!message.currentRetry) message.currentRetry = 0;
|
|
187
219
|
|
|
188
220
|
try {
|
|
189
|
-
await this.processMessage(message, rawMessage, channel);
|
|
221
|
+
await this.processMessage(message, rawMessage, channel, queueName);
|
|
190
222
|
} catch (error) {
|
|
191
223
|
await this.handleProcessingError(message, rawMessage, channel, error, queueName);
|
|
192
224
|
}
|
|
@@ -322,11 +354,11 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
322
354
|
/**
|
|
323
355
|
* Abstract method for message processing logic.
|
|
324
356
|
*/
|
|
325
|
-
protected async processMessage(message: QueueMessage<T>, rawMessage, channel): Promise<void> {
|
|
357
|
+
protected async processMessage(message: QueueMessage<T>, rawMessage, channel, queueName: string): Promise<void> {
|
|
326
358
|
await this.updateStatusInDatabase('started', message);
|
|
327
359
|
|
|
328
360
|
// Capture the results of handling the task.
|
|
329
|
-
const result = await this.
|
|
361
|
+
const result = await this.subscribeWithTimeout(message, queueName);
|
|
330
362
|
|
|
331
363
|
// Ack the message.
|
|
332
364
|
channel.ack(rawMessage);
|
|
@@ -370,4 +402,82 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
370
402
|
|
|
371
403
|
}
|
|
372
404
|
|
|
405
|
+
private resolveProcessingTimeoutMs(): number {
|
|
406
|
+
// Broker-side delivery-ack timeout (ms). If not provided, assume RabbitMQ default
|
|
407
|
+
// behavior used in this project: 30 minutes.
|
|
408
|
+
// Example (RabbitMQ broker):
|
|
409
|
+
// - Broker ack timeout: 30m => 1,800,000ms (QUEUES_RABBITMQ_CONSUMER_ACK_TIMEOUT_MS)
|
|
410
|
+
// - App soft timeout should be slightly lower, e.g. 29m30s => 1,770,000ms
|
|
411
|
+
// (QUEUES_RABBITMQ_SUBSCRIBER_PROCESSING_TIMEOUT_MS), so application code fails first,
|
|
412
|
+
// records DB state/error, and avoids broker-forced channel close as primary failure signal.
|
|
413
|
+
const brokerTimeoutMs = this.parsePositiveInt(process.env.QUEUES_RABBITMQ_CONSUMER_ACK_TIMEOUT_MS, 30 * 60 * 1000);
|
|
414
|
+
|
|
415
|
+
// Soft timeout should fire *before* broker timeout so we can fail explicitly,
|
|
416
|
+
// persist status/error, and avoid broker-forced channel closure as primary signal.
|
|
417
|
+
// Keep at least 1s to avoid zero/negative values when broker timeout is very small.
|
|
418
|
+
const defaultSoftTimeoutMs = Math.max(1_000, brokerTimeoutMs - 30_000);
|
|
419
|
+
|
|
420
|
+
// Final timeout precedence:
|
|
421
|
+
// 1) QUEUES_RABBITMQ_SUBSCRIBER_PROCESSING_TIMEOUT_MS (if valid positive int)
|
|
422
|
+
// 2) Derived defaultSoftTimeoutMs (broker timeout - 30s)
|
|
423
|
+
return this.parsePositiveInt(process.env.QUEUES_RABBITMQ_SUBSCRIBER_PROCESSING_TIMEOUT_MS, defaultSoftTimeoutMs);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private parsePositiveInt(value: string | undefined, fallback: number): number {
|
|
427
|
+
// Shared env parsing helper:
|
|
428
|
+
// - missing/invalid/non-positive => fallback
|
|
429
|
+
// - valid positive integer => parsed value
|
|
430
|
+
if (!value) return fallback;
|
|
431
|
+
const parsed = Number.parseInt(value, 10);
|
|
432
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private async subscribeWithTimeout(message: QueueMessage<T>, queueName: string): Promise<any> {
|
|
436
|
+
const timeoutMs = this.resolveProcessingTimeoutMs();
|
|
437
|
+
const messageId = message?.messageId || 'unknown';
|
|
438
|
+
|
|
439
|
+
// Allow an escape hatch: non-positive timeout means run without a soft timeout.
|
|
440
|
+
if (timeoutMs <= 0) {
|
|
441
|
+
return this.subscribe(message);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
let timedOut = false;
|
|
445
|
+
let timeoutHandle: NodeJS.Timeout | null = null;
|
|
446
|
+
|
|
447
|
+
// Main subscriber work promise.
|
|
448
|
+
// If timeout has already fired, suppress rethrow to avoid unhandled rejection noise
|
|
449
|
+
// (the timeout error is already the authoritative failure we track).
|
|
450
|
+
const subscribePromise = this.subscribe(message).catch((error) => {
|
|
451
|
+
if (timedOut) {
|
|
452
|
+
this.logger.error(
|
|
453
|
+
`Subscriber promise rejected after timeout for queue ${queueName} and messageId ${messageId}: ${(error as Error)?.message || String(error)}`,
|
|
454
|
+
(error as Error)?.stack,
|
|
455
|
+
);
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
throw error;
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Timeout promise rejects after timeoutMs with an explicit domain-specific error.
|
|
462
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
463
|
+
timeoutHandle = setTimeout(() => {
|
|
464
|
+
timedOut = true;
|
|
465
|
+
reject(new ConsumerProcessingTimeoutError(queueName, messageId, timeoutMs));
|
|
466
|
+
}, timeoutMs);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
try {
|
|
470
|
+
// Promise.race settles as soon as the *first* promise settles.
|
|
471
|
+
// - If subscribePromise resolves/rejects first, we use that outcome.
|
|
472
|
+
// - If timeoutPromise rejects first, we fail fast with timeout error.
|
|
473
|
+
// This ensures we mark DB status via normal error handling before broker ack-timeout.
|
|
474
|
+
return await Promise.race([subscribePromise, timeoutPromise]);
|
|
475
|
+
} finally {
|
|
476
|
+
// Always clear timer once race settles to avoid timer leaks.
|
|
477
|
+
if (timeoutHandle) {
|
|
478
|
+
clearTimeout(timeoutHandle);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
373
483
|
}
|