@solidxai/core 0.1.1 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/run-tests.command.d.ts +37 -0
- package/dist/commands/run-tests.command.d.ts.map +1 -0
- package/dist/commands/run-tests.command.js +345 -0
- package/dist/commands/run-tests.command.js.map +1 -0
- package/dist/commands/test-data.command.d.ts +6 -6
- package/dist/commands/test-data.command.d.ts.map +1 -1
- package/dist/commands/test-data.command.js +25 -25
- package/dist/commands/test-data.command.js.map +1 -1
- package/dist/commands/test.command.d.ts +5 -0
- package/dist/commands/test.command.d.ts.map +1 -0
- package/dist/commands/test.command.js +26 -0
- package/dist/commands/test.command.js.map +1 -0
- package/dist/controllers/service.controller.d.ts +0 -9
- package/dist/controllers/service.controller.d.ts.map +1 -1
- package/dist/controllers/service.controller.js +0 -45
- package/dist/controllers/service.controller.js.map +1 -1
- package/dist/dtos/basic-filters.dto.d.ts.map +1 -1
- package/dist/dtos/basic-filters.dto.js.map +1 -1
- package/dist/dtos/create-user.dto.d.ts +1 -0
- package/dist/dtos/create-user.dto.d.ts.map +1 -1
- package/dist/dtos/create-user.dto.js +2 -1
- package/dist/dtos/create-user.dto.js.map +1 -1
- package/dist/helpers/schematic.service.js +1 -1
- package/dist/helpers/schematic.service.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +3 -21
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/module-test-data.service.d.ts.map +1 -1
- package/dist/seeders/module-test-data.service.js +3 -3
- package/dist/seeders/module-test-data.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +34 -9
- package/dist/services/chatter-message.service.d.ts +2 -0
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +18 -2
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +2 -1
- 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 +2 -1
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/services/queues/common.d.ts +3 -0
- package/dist/services/queues/common.d.ts.map +1 -0
- package/dist/services/queues/common.js +39 -0
- package/dist/services/queues/common.js.map +1 -0
- package/dist/services/queues/database-publisher.service.d.ts.map +1 -1
- package/dist/services/queues/database-publisher.service.js +3 -1
- package/dist/services/queues/database-publisher.service.js.map +1 -1
- package/dist/services/queues/database-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/database-subscriber.service.js +5 -2
- package/dist/services/queues/database-subscriber.service.js.map +1 -1
- package/dist/services/queues/rabbitmq-publisher.service.d.ts.map +1 -1
- package/dist/services/queues/rabbitmq-publisher.service.js +13 -6
- package/dist/services/queues/rabbitmq-publisher.service.js.map +1 -1
- package/dist/services/queues/rabbitmq-subscriber.service.d.ts +14 -1
- package/dist/services/queues/rabbitmq-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/rabbitmq-subscriber.service.js +197 -65
- package/dist/services/queues/rabbitmq-subscriber.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/testing/__examples__/register-example-specs.d.ts +3 -0
- package/dist/testing/__examples__/register-example-specs.d.ts.map +1 -0
- package/dist/testing/__examples__/register-example-specs.js +8 -0
- package/dist/testing/__examples__/register-example-specs.js.map +1 -0
- package/dist/testing/__examples__/specs/custom-health.spec.d.ts +17 -0
- package/dist/testing/__examples__/specs/custom-health.spec.d.ts.map +1 -0
- package/dist/testing/__examples__/specs/custom-health.spec.js +30 -0
- package/dist/testing/__examples__/specs/custom-health.spec.js.map +1 -0
- package/dist/testing/adapters/api/api-adapter.d.ts +9 -0
- package/dist/testing/adapters/api/api-adapter.d.ts.map +1 -0
- package/dist/testing/adapters/api/api-adapter.js +76 -0
- package/dist/testing/adapters/api/api-adapter.js.map +1 -0
- package/dist/testing/adapters/api/api.types.d.ts +14 -0
- package/dist/testing/adapters/api/api.types.d.ts.map +1 -0
- package/dist/testing/adapters/api/api.types.js +3 -0
- package/dist/testing/adapters/api/api.types.js.map +1 -0
- package/dist/testing/adapters/ui/playwright-adapter.d.ts +14 -0
- package/dist/testing/adapters/ui/playwright-adapter.d.ts.map +1 -0
- package/dist/testing/adapters/ui/playwright-adapter.js +47 -0
- package/dist/testing/adapters/ui/playwright-adapter.js.map +1 -0
- package/dist/testing/adapters/ui/ui.types.d.ts +5 -0
- package/dist/testing/adapters/ui/ui.types.d.ts.map +1 -0
- package/dist/testing/adapters/ui/ui.types.js +3 -0
- package/dist/testing/adapters/ui/ui.types.js.map +1 -0
- package/dist/testing/contracts/runtime-context.types.d.ts +35 -0
- package/dist/testing/contracts/runtime-context.types.d.ts.map +1 -0
- package/dist/testing/contracts/runtime-context.types.js +3 -0
- package/dist/testing/contracts/runtime-context.types.js.map +1 -0
- package/dist/testing/contracts/test-spec.types.d.ts +21 -0
- package/dist/testing/contracts/test-spec.types.d.ts.map +1 -0
- package/dist/testing/contracts/test-spec.types.js +3 -0
- package/dist/testing/contracts/test-spec.types.js.map +1 -0
- package/dist/testing/contracts/testing-metadata.types.d.ts +41 -0
- package/dist/testing/contracts/testing-metadata.types.d.ts.map +1 -0
- package/dist/testing/contracts/testing-metadata.types.js +3 -0
- package/dist/testing/contracts/testing-metadata.types.js.map +1 -0
- package/dist/testing/core/interpolation.d.ts +4 -0
- package/dist/testing/core/interpolation.d.ts.map +1 -0
- package/dist/testing/core/interpolation.js +180 -0
- package/dist/testing/core/interpolation.js.map +1 -0
- package/dist/testing/core/normalize-steps.d.ts +7 -0
- package/dist/testing/core/normalize-steps.d.ts.map +1 -0
- package/dist/testing/core/normalize-steps.js +20 -0
- package/dist/testing/core/normalize-steps.js.map +1 -0
- package/dist/testing/core/resource-store.d.ts +8 -0
- package/dist/testing/core/resource-store.d.ts.map +1 -0
- package/dist/testing/core/resource-store.js +41 -0
- package/dist/testing/core/resource-store.js.map +1 -0
- package/dist/testing/core/spec-registry.d.ts +10 -0
- package/dist/testing/core/spec-registry.d.ts.map +1 -0
- package/dist/testing/core/spec-registry.js +32 -0
- package/dist/testing/core/spec-registry.js.map +1 -0
- package/dist/testing/core/step-registry.d.ts +10 -0
- package/dist/testing/core/step-registry.d.ts.map +1 -0
- package/dist/testing/core/step-registry.js +26 -0
- package/dist/testing/core/step-registry.js.map +1 -0
- package/dist/testing/core/testing-engine.d.ts +14 -0
- package/dist/testing/core/testing-engine.d.ts.map +1 -0
- package/dist/testing/core/testing-engine.js +97 -0
- package/dist/testing/core/testing-engine.js.map +1 -0
- package/dist/testing/core/timeout.d.ts +2 -0
- package/dist/testing/core/timeout.d.ts.map +1 -0
- package/dist/testing/core/timeout.js +18 -0
- package/dist/testing/core/timeout.js.map +1 -0
- package/dist/testing/reporter/attachments.d.ts +4 -0
- package/dist/testing/reporter/attachments.d.ts.map +1 -0
- package/dist/testing/reporter/attachments.js +25 -0
- package/dist/testing/reporter/attachments.js.map +1 -0
- package/dist/testing/reporter/console-reporter.d.ts +45 -0
- package/dist/testing/reporter/console-reporter.d.ts.map +1 -0
- package/dist/testing/reporter/console-reporter.js +189 -0
- package/dist/testing/reporter/console-reporter.js.map +1 -0
- package/dist/testing/reporter/reporter.types.d.ts +37 -0
- package/dist/testing/reporter/reporter.types.d.ts.map +1 -0
- package/dist/testing/reporter/reporter.types.js +3 -0
- package/dist/testing/reporter/reporter.types.js.map +1 -0
- package/dist/testing/runner/lifecycle.d.ts +9 -0
- package/dist/testing/runner/lifecycle.d.ts.map +1 -0
- package/dist/testing/runner/lifecycle.js +33 -0
- package/dist/testing/runner/lifecycle.js.map +1 -0
- package/dist/testing/runner/run-from-metadata.d.ts +24 -0
- package/dist/testing/runner/run-from-metadata.d.ts.map +1 -0
- package/dist/testing/runner/run-from-metadata.js +70 -0
- package/dist/testing/runner/run-from-metadata.js.map +1 -0
- package/dist/testing/runner/scenario-filter.d.ts +9 -0
- package/dist/testing/runner/scenario-filter.d.ts.map +1 -0
- package/dist/testing/runner/scenario-filter.js +22 -0
- package/dist/testing/runner/scenario-filter.js.map +1 -0
- package/dist/testing/steps/api/auth.step.d.ts +3 -0
- package/dist/testing/steps/api/auth.step.d.ts.map +1 -0
- package/dist/testing/steps/api/auth.step.js +38 -0
- package/dist/testing/steps/api/auth.step.js.map +1 -0
- package/dist/testing/steps/api/index.d.ts +3 -0
- package/dist/testing/steps/api/index.d.ts.map +1 -0
- package/dist/testing/steps/api/index.js +10 -0
- package/dist/testing/steps/api/index.js.map +1 -0
- package/dist/testing/steps/api/request.step.d.ts +3 -0
- package/dist/testing/steps/api/request.step.d.ts.map +1 -0
- package/dist/testing/steps/api/request.step.js +281 -0
- package/dist/testing/steps/api/request.step.js.map +1 -0
- package/dist/testing/steps/assert/http.step.d.ts +3 -0
- package/dist/testing/steps/assert/http.step.d.ts.map +1 -0
- package/dist/testing/steps/assert/http.step.js +27 -0
- package/dist/testing/steps/assert/http.step.js.map +1 -0
- package/dist/testing/steps/assert/index.d.ts +3 -0
- package/dist/testing/steps/assert/index.d.ts.map +1 -0
- package/dist/testing/steps/assert/index.js +12 -0
- package/dist/testing/steps/assert/index.js.map +1 -0
- package/dist/testing/steps/assert/jsonpath.step.d.ts +3 -0
- package/dist/testing/steps/assert/jsonpath.step.d.ts.map +1 -0
- package/dist/testing/steps/assert/jsonpath.step.js +40 -0
- package/dist/testing/steps/assert/jsonpath.step.js.map +1 -0
- package/dist/testing/steps/assert/primitives.step.d.ts +3 -0
- package/dist/testing/steps/assert/primitives.step.d.ts.map +1 -0
- package/dist/testing/steps/assert/primitives.step.js +43 -0
- package/dist/testing/steps/assert/primitives.step.js.map +1 -0
- package/dist/testing/steps/test/index.d.ts +3 -0
- package/dist/testing/steps/test/index.d.ts.map +1 -0
- package/dist/testing/steps/test/index.js +8 -0
- package/dist/testing/steps/test/index.js.map +1 -0
- package/dist/testing/steps/test/test-spec.step.d.ts +3 -0
- package/dist/testing/steps/test/test-spec.step.d.ts.map +1 -0
- package/dist/testing/steps/test/test-spec.step.js +41 -0
- package/dist/testing/steps/test/test-spec.step.js.map +1 -0
- package/dist/testing/steps/ui/actions.step.d.ts +3 -0
- package/dist/testing/steps/ui/actions.step.d.ts.map +1 -0
- package/dist/testing/steps/ui/actions.step.js +31 -0
- package/dist/testing/steps/ui/actions.step.js.map +1 -0
- package/dist/testing/steps/ui/assertions.step.d.ts +3 -0
- package/dist/testing/steps/ui/assertions.step.d.ts.map +1 -0
- package/dist/testing/steps/ui/assertions.step.js +41 -0
- package/dist/testing/steps/ui/assertions.step.js.map +1 -0
- package/dist/testing/steps/ui/form.step.d.ts +3 -0
- package/dist/testing/steps/ui/form.step.d.ts.map +1 -0
- package/dist/testing/steps/ui/form.step.js +34 -0
- package/dist/testing/steps/ui/form.step.js.map +1 -0
- package/dist/testing/steps/ui/index.d.ts +3 -0
- package/dist/testing/steps/ui/index.d.ts.map +1 -0
- package/dist/testing/steps/ui/index.js +14 -0
- package/dist/testing/steps/ui/index.js.map +1 -0
- package/dist/testing/steps/ui/navigation.step.d.ts +3 -0
- package/dist/testing/steps/ui/navigation.step.d.ts.map +1 -0
- package/dist/testing/steps/ui/navigation.step.js +39 -0
- package/dist/testing/steps/ui/navigation.step.js.map +1 -0
- package/dist/testing/steps/util/index.d.ts +3 -0
- package/dist/testing/steps/util/index.d.ts.map +1 -0
- package/dist/testing/steps/util/index.js +12 -0
- package/dist/testing/steps/util/index.js.map +1 -0
- package/dist/testing/steps/util/log.step.d.ts +3 -0
- package/dist/testing/steps/util/log.step.d.ts.map +1 -0
- package/dist/testing/steps/util/log.step.js +18 -0
- package/dist/testing/steps/util/log.step.js.map +1 -0
- package/dist/testing/steps/util/require.step.d.ts +3 -0
- package/dist/testing/steps/util/require.step.d.ts.map +1 -0
- package/dist/testing/steps/util/require.step.js +16 -0
- package/dist/testing/steps/util/require.step.js.map +1 -0
- package/dist/testing/steps/util/sleep.step.d.ts +3 -0
- package/dist/testing/steps/util/sleep.step.d.ts.map +1 -0
- package/dist/testing/steps/util/sleep.step.js +13 -0
- package/dist/testing/steps/util/sleep.step.js.map +1 -0
- package/docs/test-data-workflow.md +51 -11
- package/package.json +4 -2
- package/src/commands/run-tests.command.ts +278 -0
- package/src/commands/test-data.command.ts +26 -26
- package/src/commands/test.command.ts +14 -0
- package/src/controllers/service.controller.ts +58 -59
- package/src/dtos/basic-filters.dto.ts +0 -2
- package/src/dtos/create-user.dto.ts +1 -0
- package/src/helpers/schematic.service.ts +1 -1
- package/src/index.ts +3 -0
- package/src/seeders/module-metadata-seeder.service.ts +5 -25
- package/src/seeders/module-test-data.service.ts +5 -3
- package/src/seeders/seed-data/solid-core-metadata.json +34 -9
- package/src/services/chatter-message.service.ts +18 -1
- package/src/services/crud.service.ts +1 -0
- package/src/services/model-metadata.service.ts +2 -1
- package/src/services/module-metadata.service.ts +2 -1
- package/src/services/queues/common.ts +75 -0
- package/src/services/queues/database-publisher.service.ts +4 -1
- package/src/services/queues/database-subscriber.service.ts +5 -3
- package/src/services/queues/rabbitmq-publisher.service.ts +17 -7
- package/src/services/queues/rabbitmq-subscriber.service.ts +223 -95
- package/src/solid-core.module.ts +4 -0
- package/src/testing/README.md +364 -0
- package/src/testing/__examples__/register-example-specs.ts +6 -0
- package/src/testing/__examples__/specs/custom-health.spec.ts +29 -0
- package/src/testing/__examples__/testing.sample.json +82 -0
- package/src/testing/adapters/api/api-adapter.ts +85 -0
- package/src/testing/adapters/api/api.types.ts +15 -0
- package/src/testing/adapters/ui/playwright-adapter.ts +54 -0
- package/src/testing/adapters/ui/ui.types.ts +4 -0
- package/src/testing/contracts/runtime-context.types.ts +36 -0
- package/src/testing/contracts/test-spec.types.ts +24 -0
- package/src/testing/contracts/testing-metadata.types.ts +46 -0
- package/src/testing/core/interpolation.ts +189 -0
- package/src/testing/core/normalize-steps.ts +21 -0
- package/src/testing/core/resource-store.ts +38 -0
- package/src/testing/core/spec-registry.ts +33 -0
- package/src/testing/core/step-registry.ts +27 -0
- package/src/testing/core/testing-engine.ts +127 -0
- package/src/testing/core/timeout.ts +19 -0
- package/src/testing/reporter/attachments.ts +25 -0
- package/src/testing/reporter/console-reporter.ts +229 -0
- package/src/testing/reporter/reporter.types.ts +36 -0
- package/src/testing/runner/lifecycle.ts +31 -0
- package/src/testing/runner/run-from-metadata.ts +87 -0
- package/src/testing/runner/scenario-filter.ts +33 -0
- package/src/testing/steps/api/auth.step.ts +66 -0
- package/src/testing/steps/api/index.ts +10 -0
- package/src/testing/steps/api/request.step.ts +358 -0
- package/src/testing/steps/assert/http.step.ts +33 -0
- package/src/testing/steps/assert/index.ts +12 -0
- package/src/testing/steps/assert/jsonpath.step.ts +50 -0
- package/src/testing/steps/assert/primitives.step.ts +69 -0
- package/src/testing/steps/test/index.ts +8 -0
- package/src/testing/steps/test/test-spec.step.ts +52 -0
- package/src/testing/steps/ui/actions.step.ts +36 -0
- package/src/testing/steps/ui/assertions.step.ts +54 -0
- package/src/testing/steps/ui/form.step.ts +39 -0
- package/src/testing/steps/ui/index.ts +12 -0
- package/src/testing/steps/ui/navigation.step.ts +53 -0
- package/src/testing/steps/util/index.ts +10 -0
- package/src/testing/steps/util/log.step.ts +19 -0
- package/src/testing/steps/util/require.step.ts +16 -0
- package/src/testing/steps/util/sleep.step.ts +15 -0
- package/tsconfig.json +35 -25
- package/tsconfig.tests.json +14 -0
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -5,6 +5,7 @@ import { QueuesModuleOptions } from "../../interfaces";
|
|
|
5
5
|
import { QueueMessage, QueuePublisher } from '../../interfaces/mq';
|
|
6
6
|
import { MqMessageQueueService } from '../mq-message-queue.service';
|
|
7
7
|
import { MqMessageService } from '../mq-message.service';
|
|
8
|
+
import { buildNamespacedQueueName } from './common';
|
|
8
9
|
|
|
9
10
|
export abstract class RabbitMqPublisher<T> implements OnModuleDestroy, QueuePublisher<T> {
|
|
10
11
|
private readonly logger = new Logger(RabbitMqPublisher.name);
|
|
@@ -70,13 +71,21 @@ export abstract class RabbitMqPublisher<T> implements OnModuleDestroy, QueuePubl
|
|
|
70
71
|
|
|
71
72
|
const channel = await conn.createChannel();
|
|
72
73
|
|
|
74
|
+
channel.on('return', (msg) => {
|
|
75
|
+
const content = msg.content?.toString?.() ?? '';
|
|
76
|
+
this.logger.warn(
|
|
77
|
+
`RabbitMqPublisher message returned from exchange ${msg.fields.exchange} with routingKey ${msg.fields.routingKey}: ${content}`,
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
73
81
|
const options = this.options();
|
|
74
82
|
const queueName = options.queueName;
|
|
75
|
-
const
|
|
76
|
-
const
|
|
83
|
+
const namespacedQueueName = buildNamespacedQueueName(queueName);
|
|
84
|
+
const exchangeName = `${namespacedQueueName}.exchange`;
|
|
85
|
+
const routingKey = `${namespacedQueueName}.routing-key`;
|
|
77
86
|
|
|
78
87
|
await channel.assertExchange(exchangeName, 'direct', {});
|
|
79
|
-
const queue = await channel.assertQueue(
|
|
88
|
+
const queue = await channel.assertQueue(namespacedQueueName, {});
|
|
80
89
|
await channel.bindQueue(queue.queue, exchangeName, routingKey);
|
|
81
90
|
|
|
82
91
|
this.connection = conn;
|
|
@@ -127,7 +136,6 @@ export abstract class RabbitMqPublisher<T> implements OnModuleDestroy, QueuePubl
|
|
|
127
136
|
}
|
|
128
137
|
}
|
|
129
138
|
|
|
130
|
-
|
|
131
139
|
async publish(message: QueueMessage<T>): Promise<string> {
|
|
132
140
|
if (!this.url) {
|
|
133
141
|
this.logger.error('RabbitMqPublisher url is not defined in the environment variables');
|
|
@@ -148,8 +156,10 @@ export abstract class RabbitMqPublisher<T> implements OnModuleDestroy, QueuePubl
|
|
|
148
156
|
const options = this.options();
|
|
149
157
|
|
|
150
158
|
const queueName = options.queueName;
|
|
151
|
-
const
|
|
152
|
-
|
|
159
|
+
const namespacedQueueName = buildNamespacedQueueName(queueName);
|
|
160
|
+
|
|
161
|
+
const exchangeName = `${namespacedQueueName}.exchange`;
|
|
162
|
+
const routingKey = `${namespacedQueueName}.routing-key`;
|
|
153
163
|
|
|
154
164
|
// Set default values for retry.
|
|
155
165
|
// by default there are no retries.
|
|
@@ -160,7 +170,7 @@ export abstract class RabbitMqPublisher<T> implements OnModuleDestroy, QueuePubl
|
|
|
160
170
|
message.messageId = uuidv4();
|
|
161
171
|
|
|
162
172
|
// Save the message to the DB so that we can then change its status in the subscriber...
|
|
163
|
-
await this.persistToDatabase(
|
|
173
|
+
await this.persistToDatabase(namespacedQueueName, message);
|
|
164
174
|
|
|
165
175
|
// wait for the channel to confirm
|
|
166
176
|
try {
|
|
@@ -4,17 +4,21 @@ import { QueuesModuleOptions } from "../../interfaces";
|
|
|
4
4
|
import { QueueMessage, QueueSubscriber } from '../../interfaces/mq';
|
|
5
5
|
import { MqMessageQueueService } from '../mq-message-queue.service';
|
|
6
6
|
import { MqMessageService } from '../mq-message.service';
|
|
7
|
+
import { buildNamespacedQueueName } from './common';
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscriber<T> { // TODO This can be made a generic type for better type visibility
|
|
10
11
|
private readonly logger = new Logger(RabbitMqSubscriber.name);
|
|
11
12
|
private readonly url: string;
|
|
12
13
|
private readonly serviceRole: string;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
private connection: amqp.Connection | null = null;
|
|
15
|
+
private channel: amqp.Channel | null = null;
|
|
16
|
+
private consumerTag: string | null = null;
|
|
17
|
+
private reconnectPromise: Promise<void> | null = null;
|
|
18
|
+
private reconnectAttempt = 0;
|
|
19
|
+
private stopping = false;
|
|
20
|
+
|
|
21
|
+
constructor(protected readonly mqMessageService: MqMessageService, protected readonly mqMessageQueueService: MqMessageQueueService) {
|
|
18
22
|
this.url = process.env.QUEUES_RABBIT_MQ_URL;
|
|
19
23
|
this.serviceRole = process.env.QUEUES_SERVICE_ROLE;
|
|
20
24
|
if (!this.url) {
|
|
@@ -46,6 +50,7 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
46
50
|
username: url.username,
|
|
47
51
|
password: decodeURIComponent(url.password),
|
|
48
52
|
frameMax: 131072,
|
|
53
|
+
heartbeat: 30,
|
|
49
54
|
});
|
|
50
55
|
|
|
51
56
|
return connection
|
|
@@ -76,86 +81,238 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
83
|
|
|
79
|
-
|
|
80
|
-
// const connection = await amqp.connect(this.url);
|
|
81
|
-
|
|
82
|
-
let connection;
|
|
84
|
+
const namespacedQueueName = buildNamespacedQueueName(queueName);
|
|
83
85
|
try {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
await this.connectAndConsume(namespacedQueueName);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
this.logger.error(`Failed to connect to RabbitMQ for queue ${namespacedQueueName}: ${(err as Error).message}`, (err as Error).stack);
|
|
89
|
+
this.triggerReconnect(namespacedQueueName, 'initial connection failure');
|
|
86
90
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
|
|
92
|
+
this.logger.log(`RabbitMqSubscriber ready to consume messages: ${JSON.stringify(options)} and url: ${this.url}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async connectAndConsume(queueName: string): Promise<void> {
|
|
97
|
+
await this.cleanup();
|
|
98
|
+
this.logger.log(`RabbitMqSubscriber in connectAndConsume for queue: ${queueName} and url: ${this.url}`);
|
|
99
|
+
|
|
100
|
+
let connection: amqp.Connection;
|
|
101
|
+
try {
|
|
102
|
+
connection = await this.establishConnection();
|
|
103
|
+
} catch (err) {
|
|
104
|
+
this.logger.error(`Failed to connect to RabbitMQ for queue ${queueName}: ${(err as Error).message}`, (err as Error).stack);
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.connection = connection;
|
|
109
|
+
|
|
110
|
+
connection.on('error', (err) => {
|
|
111
|
+
if (connection !== this.connection) return;
|
|
112
|
+
this.logger.error(`RabbitMqSubscriber connection error for queue ${queueName}: ${(err as Error).message}`);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
connection.on('close', () => {
|
|
116
|
+
if (connection !== this.connection) return;
|
|
117
|
+
this.logger.warn(`RabbitMqSubscriber connection closed for queue ${queueName}`);
|
|
118
|
+
this.triggerReconnect(queueName, 'connection closed');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const channel = await connection.createChannel();
|
|
122
|
+
this.channel = channel;
|
|
123
|
+
|
|
124
|
+
channel.on('error', (err) => {
|
|
125
|
+
if (channel !== this.channel) return;
|
|
126
|
+
this.logger.error(`RabbitMqSubscriber channel error for queue ${queueName}: ${(err as Error).message}`);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
channel.on('close', () => {
|
|
130
|
+
if (channel !== this.channel) return;
|
|
131
|
+
this.logger.warn(`RabbitMqSubscriber channel closed for queue ${queueName}`);
|
|
132
|
+
this.triggerReconnect(queueName, 'channel closed');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Process one message at a time per consumer to avoid parallel work on the same subscriber instance.
|
|
136
|
+
await channel.prefetch(1);
|
|
137
|
+
|
|
138
|
+
// Use a direct exchange with a stable routing key so retry DLX can route back to the main queue.
|
|
139
|
+
const exchangeName = `${queueName}.exchange`;
|
|
140
|
+
const routingKey = `${queueName}.routing-key`;
|
|
141
|
+
const retryQueue = `${queueName}.retry`;
|
|
142
|
+
const failedQueue = `${queueName}.failed`;
|
|
143
|
+
|
|
144
|
+
await channel.assertExchange(exchangeName, 'direct', {});
|
|
145
|
+
await channel.assertQueue(queueName, {});
|
|
146
|
+
await channel.bindQueue(queueName, exchangeName, routingKey);
|
|
147
|
+
|
|
148
|
+
// Retry queue uses DLX to route expired messages back to the main exchange/routing key.
|
|
149
|
+
await channel.assertQueue(retryQueue, {
|
|
150
|
+
arguments: {
|
|
151
|
+
'x-dead-letter-exchange': exchangeName,
|
|
152
|
+
'x-dead-letter-routing-key': routingKey,
|
|
90
153
|
}
|
|
154
|
+
});
|
|
91
155
|
|
|
92
|
-
|
|
93
|
-
|
|
156
|
+
await channel.assertQueue(failedQueue, {});
|
|
157
|
+
|
|
158
|
+
const consumeResult = await channel.consume(
|
|
159
|
+
queueName,
|
|
160
|
+
async (rawMessage) => {
|
|
161
|
+
if (!rawMessage) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
94
164
|
|
|
95
|
-
|
|
96
|
-
const routingKey = `${queueName}.routing-key`;
|
|
165
|
+
let message: QueueMessage<T> = null;
|
|
97
166
|
|
|
98
|
-
|
|
99
|
-
|
|
167
|
+
try {
|
|
168
|
+
const messageContentString = rawMessage.content.toString();
|
|
169
|
+
message = JSON.parse(messageContentString) as QueueMessage<T>;
|
|
170
|
+
this.logger.debug(`rabbitmq subscriber received message with id: ${message.messageId} for queue ${queueName}`);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
this.logger.error(`Invalid JSON message on queue ${queueName}: ${(error as Error).message}`);
|
|
173
|
+
await this.publishToFailedQueue(queueName, rawMessage.content, channel, error);
|
|
174
|
+
channel.ack(rawMessage);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
100
177
|
|
|
101
|
-
|
|
102
|
-
|
|
178
|
+
if (!message.retryCount) message.retryCount = 0;
|
|
179
|
+
if (!message.retryInterval) message.retryInterval = 1000;
|
|
180
|
+
if (!message.currentRetry) message.currentRetry = 0;
|
|
103
181
|
|
|
104
|
-
|
|
105
|
-
|
|
182
|
+
try {
|
|
183
|
+
await this.processMessage(message, rawMessage, channel);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
await this.handleProcessingError(message, rawMessage, channel, error, queueName);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
// Explicit ack enables reliable processing and retry routing.
|
|
189
|
+
{ noAck: false },
|
|
190
|
+
);
|
|
106
191
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
queue.queue,
|
|
110
|
-
async (rawMessage) => {
|
|
111
|
-
if (rawMessage) {
|
|
112
|
-
const messageContentString = rawMessage.content.toString();
|
|
113
|
-
// this.logger.debug(`RabbitMqSubscriber Received raw message: ${messageContentString}`);
|
|
192
|
+
this.consumerTag = consumeResult.consumerTag;
|
|
193
|
+
}
|
|
114
194
|
|
|
115
|
-
|
|
195
|
+
// Retry flow: update DB -> increment retry -> send to retry queue with per-message expiration -> ack original.
|
|
196
|
+
private async handleProcessingError(message: QueueMessage<T>, rawMessage: amqp.ConsumeMessage, channel: amqp.Channel, error: any, queueName: string): Promise<void> {
|
|
197
|
+
const errorMessage = (error as Error)?.message || String(error);
|
|
198
|
+
this.logger.error(`Error processing message on queue ${queueName}: ${errorMessage}`);
|
|
116
199
|
|
|
117
|
-
|
|
118
|
-
|
|
200
|
+
if (message.currentRetry < message.retryCount) {
|
|
201
|
+
await this.updateStatusInDatabase('retrying', message);
|
|
119
202
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (!message.currentRetry) message.currentRetry = 0;
|
|
203
|
+
message.currentRetry++;
|
|
204
|
+
const retryQueue = `${queueName}.retry`;
|
|
205
|
+
const payload = Buffer.from(JSON.stringify(message));
|
|
124
206
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
207
|
+
// Per-message expiration keeps the message in the retry queue until TTL, then DLX routes it back.
|
|
208
|
+
channel.sendToQueue(retryQueue, payload, {
|
|
209
|
+
expiration: String(message.retryInterval || 1000),
|
|
210
|
+
headers: {
|
|
211
|
+
'x-error': errorMessage,
|
|
212
|
+
}
|
|
213
|
+
});
|
|
129
214
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
215
|
+
channel.ack(rawMessage);
|
|
216
|
+
this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms on queue ${queueName}`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
134
219
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
} else {
|
|
141
|
-
await this.updateStatusInDatabase('failed', message, error.message, '');
|
|
220
|
+
await this.updateStatusInDatabase('failed', message, errorMessage, '');
|
|
221
|
+
channel.ack(rawMessage);
|
|
222
|
+
await this.publishToFailedQueue(queueName, Buffer.from(JSON.stringify(message)), channel, error);
|
|
223
|
+
this.logger.error(`Message failed after ${message.retryCount} attempts on queue ${queueName}: ${errorMessage}`);
|
|
224
|
+
}
|
|
142
225
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
226
|
+
private async publishToFailedQueue(queueName: string, payload: Buffer | string, channel: amqp.Channel, error?: any): Promise<void> {
|
|
227
|
+
const failedQueue = `${queueName}.failed`;
|
|
228
|
+
const body = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
229
|
+
const errorMessage = (error as Error)?.message || String(error || '');
|
|
147
230
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
);
|
|
231
|
+
try {
|
|
232
|
+
channel.sendToQueue(failedQueue, body, errorMessage ? {
|
|
233
|
+
headers: { 'x-error': errorMessage }
|
|
234
|
+
} : undefined);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
this.logger.error(`Failed to publish to failed queue ${failedQueue}: ${(err as Error).message}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
154
239
|
|
|
155
|
-
|
|
240
|
+
private triggerReconnect(queueName: string, reason: string) {
|
|
241
|
+
if (this.stopping) return;
|
|
242
|
+
if (this.reconnectPromise) return;
|
|
243
|
+
|
|
244
|
+
this.reconnectPromise = this.reconnectLoop(queueName, reason)
|
|
245
|
+
.finally(() => {
|
|
246
|
+
this.reconnectPromise = null;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Reconnect with backoff to avoid hammering the broker during outages.
|
|
251
|
+
private async reconnectLoop(queueName: string, reason: string): Promise<void> {
|
|
252
|
+
this.logger.warn(`RabbitMqSubscriber reconnecting for queue ${queueName}: ${reason}`);
|
|
253
|
+
|
|
254
|
+
while (!this.stopping) {
|
|
255
|
+
try {
|
|
256
|
+
await this.connectAndConsume(queueName);
|
|
257
|
+
this.reconnectAttempt = 0;
|
|
258
|
+
this.logger.log(`RabbitMqSubscriber reconnected for queue ${queueName}`);
|
|
259
|
+
return;
|
|
260
|
+
} catch (err) {
|
|
261
|
+
this.reconnectAttempt += 1;
|
|
262
|
+
const delay = this.backoff();
|
|
263
|
+
this.logger.warn(`RabbitMqSubscriber reconnect failed for queue ${queueName}; retrying in ${delay}ms`);
|
|
264
|
+
await this.sleep(delay);
|
|
265
|
+
}
|
|
156
266
|
}
|
|
157
267
|
}
|
|
158
268
|
|
|
269
|
+
private async cleanup(): Promise<void> {
|
|
270
|
+
const channel = this.channel;
|
|
271
|
+
const connection = this.connection;
|
|
272
|
+
const consumerTag = this.consumerTag;
|
|
273
|
+
|
|
274
|
+
this.channel = null;
|
|
275
|
+
this.connection = null;
|
|
276
|
+
this.consumerTag = null;
|
|
277
|
+
|
|
278
|
+
if (channel) {
|
|
279
|
+
try {
|
|
280
|
+
if (consumerTag) {
|
|
281
|
+
await channel.cancel(consumerTag);
|
|
282
|
+
}
|
|
283
|
+
} catch (_) {
|
|
284
|
+
// ignore
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
await channel.close();
|
|
289
|
+
} catch (_) {
|
|
290
|
+
// ignore
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (connection) {
|
|
295
|
+
try {
|
|
296
|
+
await connection.close();
|
|
297
|
+
} catch (_) {
|
|
298
|
+
// ignore
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private sleep(ms: number): Promise<void> {
|
|
304
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Exponential backoff with jitter, capped to 30s.
|
|
308
|
+
private backoff(): number {
|
|
309
|
+
const baseMs = 1000;
|
|
310
|
+
const maxMs = 30_000;
|
|
311
|
+
const exp = Math.min(maxMs, baseMs * Math.pow(2, this.reconnectAttempt));
|
|
312
|
+
const jitter = Math.floor(Math.random() * (exp * 0.2));
|
|
313
|
+
return Math.min(maxMs, exp + jitter);
|
|
314
|
+
}
|
|
315
|
+
|
|
159
316
|
/**
|
|
160
317
|
* Abstract method for message processing logic.
|
|
161
318
|
*/
|
|
@@ -168,43 +325,14 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
168
325
|
// Ack the message.
|
|
169
326
|
channel.ack(rawMessage);
|
|
170
327
|
|
|
171
|
-
//
|
|
328
|
+
// Persist success output and timing.
|
|
172
329
|
await this.updateStatusInDatabase('succeeded', message, '', result ? JSON.stringify(result, null, 2) : '');
|
|
173
330
|
|
|
174
331
|
}
|
|
175
332
|
|
|
176
|
-
/**
|
|
177
|
-
* Retry the message by invoking the processing logic again.
|
|
178
|
-
*/
|
|
179
|
-
private async retryMessage(message: QueueMessage<T>, rawMessage, channel) {
|
|
180
|
-
try {
|
|
181
|
-
await this.processMessage(message, rawMessage, channel);
|
|
182
|
-
} catch (error) {
|
|
183
|
-
if (message.currentRetry < message.retryCount) {
|
|
184
|
-
await this.updateStatusInDatabase('retrying', message);
|
|
185
|
-
|
|
186
|
-
message.currentRetry++;
|
|
187
|
-
this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms: ${error.message}`);
|
|
188
|
-
setTimeout(() => {
|
|
189
|
-
this.retryMessage(message, rawMessage, channel);
|
|
190
|
-
}, message.retryInterval);
|
|
191
|
-
} else {
|
|
192
|
-
|
|
193
|
-
this.logger.error(`Message failed after ${message.retryCount} attempts: ${error.message}`);
|
|
194
|
-
|
|
195
|
-
// Discard the message after max retries
|
|
196
|
-
channel.ack(rawMessage);
|
|
197
|
-
|
|
198
|
-
// TODO: Store the error in the database and update the status accordingly.
|
|
199
|
-
await this.updateStatusInDatabase('failed', message, error.message, '');
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
333
|
private async updateStatusInDatabase(stage: string, message: QueueMessage<T>, error: string = '', result: string = '') {
|
|
206
334
|
|
|
207
|
-
//
|
|
335
|
+
// Update the existing message record by messageId; creation happens upstream.
|
|
208
336
|
try {
|
|
209
337
|
// 1. resolve the queue first
|
|
210
338
|
const mqMessage = await this.mqMessageService.repo.findOne({
|
package/src/solid-core.module.ts
CHANGED
|
@@ -58,6 +58,8 @@ import { HttpModule } from '@nestjs/axios';
|
|
|
58
58
|
import { JwtModule } from '@nestjs/jwt';
|
|
59
59
|
import { SeedCommand } from './commands/seed.command';
|
|
60
60
|
import { TestDataCommand } from './commands/test-data.command';
|
|
61
|
+
import { TestRunCommand } from './commands/run-tests.command';
|
|
62
|
+
import { TestCommand } from './commands/test.command';
|
|
61
63
|
import { AuthenticationController } from './controllers/authentication.controller';
|
|
62
64
|
import { EmailTemplateController } from './controllers/email-template.controller';
|
|
63
65
|
import { GoogleAuthenticationController } from './controllers/google-authentication.controller';
|
|
@@ -506,7 +508,9 @@ import { ListOfRolesSelectionProvider } from './services/selection-providers/lis
|
|
|
506
508
|
TextractService,
|
|
507
509
|
SolidRegistry,
|
|
508
510
|
SeedCommand,
|
|
511
|
+
TestCommand,
|
|
509
512
|
TestDataCommand,
|
|
513
|
+
TestRunCommand,
|
|
510
514
|
McpCommand,
|
|
511
515
|
IngestCommand,
|
|
512
516
|
IngestMetadataService,
|