@tstdl/base 0.93.87 → 0.93.90
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/ai/genkit/helpers.d.ts +3 -1
- package/ai/genkit/helpers.js +3 -3
- package/api/server/gateway.d.ts +3 -0
- package/api/server/gateway.js +15 -4
- package/api/server/middlewares/catch-error.middleware.js +2 -4
- package/api/server/middlewares/cors.middleware.js +2 -3
- package/api/server/middlewares/csrf.middleware.d.ts +41 -0
- package/api/server/middlewares/csrf.middleware.js +108 -0
- package/api/server/middlewares/index.d.ts +1 -0
- package/api/server/middlewares/index.js +1 -0
- package/api/server/module.d.ts +8 -2
- package/api/server/module.js +14 -8
- package/api/server/tests/csrf.middleware.test.js +91 -0
- package/audit/drizzle/{0000_bored_stick.sql → 0000_lumpy_thunderball.sql} +3 -3
- package/audit/drizzle/meta/0000_snapshot.json +4 -4
- package/audit/drizzle/meta/_journal.json +2 -9
- package/audit/module.d.ts +4 -1
- package/audit/module.js +3 -2
- package/audit/schemas.d.ts +1 -1
- package/audit/types.d.ts +1 -1
- package/audit/types.js +1 -1
- package/authentication/client/authentication.service.d.ts +14 -1
- package/authentication/client/authentication.service.js +82 -23
- package/authentication/client/http-client.middleware.d.ts +6 -0
- package/authentication/client/http-client.middleware.js +36 -0
- package/authentication/client/module.js +8 -2
- package/authentication/models/service-account.model.d.ts +2 -2
- package/authentication/models/service-account.model.js +10 -5
- package/authentication/models/subject.model.d.ts +20 -5
- package/authentication/models/subject.model.js +34 -29
- package/authentication/models/system-account.model.d.ts +3 -2
- package/authentication/models/system-account.model.js +11 -5
- package/authentication/models/user.model.d.ts +2 -11
- package/authentication/models/user.model.js +5 -16
- package/authentication/server/authentication-api-request-token.provider.d.ts +0 -2
- package/authentication/server/authentication-api-request-token.provider.js +3 -11
- package/authentication/server/authentication.api-controller.d.ts +1 -2
- package/authentication/server/authentication.api-controller.js +8 -9
- package/authentication/server/authentication.audit.d.ts +3 -2
- package/authentication/server/authentication.service.d.ts +27 -1
- package/authentication/server/authentication.service.js +67 -18
- package/authentication/server/drizzle/{0000_normal_paper_doll.sql → 0000_soft_tag.sql} +25 -32
- package/authentication/server/drizzle/meta/0000_snapshot.json +180 -205
- package/authentication/server/drizzle/meta/_journal.json +2 -2
- package/authentication/server/helper.js +9 -2
- package/authentication/server/module.d.ts +4 -1
- package/authentication/server/module.js +9 -5
- package/authentication/server/schemas.d.ts +2 -1
- package/authentication/server/schemas.js +2 -2
- package/authentication/server/subject.service.d.ts +17 -11
- package/authentication/server/subject.service.js +86 -84
- package/authentication/tests/authentication-ancillary.service.test.d.ts +1 -0
- package/authentication/tests/authentication-ancillary.service.test.js +13 -0
- package/authentication/tests/authentication-secret-requirements.validator.test.d.ts +1 -0
- package/authentication/tests/authentication-secret-requirements.validator.test.js +29 -0
- package/authentication/tests/authentication.api-controller.test.d.ts +1 -0
- package/authentication/tests/authentication.api-controller.test.js +88 -0
- package/authentication/tests/authentication.api-request-token.provider.test.d.ts +1 -0
- package/authentication/tests/authentication.api-request-token.provider.test.js +48 -0
- package/authentication/tests/authentication.client-middleware.test.d.ts +1 -0
- package/authentication/tests/authentication.client-middleware.test.js +23 -0
- package/authentication/tests/authentication.client-service.test.d.ts +1 -0
- package/authentication/tests/authentication.client-service.test.js +70 -0
- package/authentication/tests/authentication.service.test.d.ts +1 -0
- package/authentication/tests/authentication.service.test.js +186 -0
- package/authentication/tests/authentication.test-ancillary-service.d.ts +9 -0
- package/authentication/tests/authentication.test-ancillary-service.js +27 -0
- package/authentication/tests/helper.test.d.ts +1 -0
- package/authentication/tests/helper.test.js +107 -0
- package/authentication/tests/secret-requirements.error.test.d.ts +1 -0
- package/authentication/tests/secret-requirements.error.test.js +14 -0
- package/authentication/tests/subject.service.test.d.ts +1 -0
- package/authentication/tests/subject.service.test.js +140 -0
- package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +1 -1
- package/circuit-breaker/postgres/drizzle/meta/_journal.json +2 -2
- package/circuit-breaker/postgres/module.d.ts +7 -1
- package/circuit-breaker/postgres/module.js +8 -6
- package/circuit-breaker/tests/circuit-breaker.test.js +2 -22
- package/document-management/api/document-management.api.js +2 -6
- package/document-management/server/services/document-validation.service.js +6 -5
- package/document-management/server/services/document-workflow.service.js +5 -5
- package/document-management/service-models/document-folders.view-model.d.ts +5 -2
- package/document-management/service-models/document-folders.view-model.js +42 -9
- package/document-management/service-models/enriched/enriched-document-management-data.view.js +1 -1
- package/examples/document-management/main.js +4 -4
- package/http/client/adapters/undici.adapter.d.ts +7 -5
- package/http/client/adapters/undici.adapter.js +13 -10
- package/http/client/module.d.ts +3 -1
- package/http/client/module.js +8 -9
- package/http/server/http-server.d.ts +2 -0
- package/http/server/node/module.d.ts +6 -2
- package/http/server/node/module.js +6 -4
- package/http/server/node/node-http-server.d.ts +2 -0
- package/http/server/node/node-http-server.js +7 -0
- package/http/types.d.ts +1 -1
- package/key-value-store/postgres/module.d.ts +7 -1
- package/key-value-store/postgres/module.js +7 -3
- package/lock/postgres/lock.js +0 -1
- package/lock/postgres/module.d.ts +7 -1
- package/lock/postgres/module.js +9 -5
- package/logger/formatter.d.ts +2 -0
- package/logger/formatters/json.js +2 -2
- package/logger/formatters/pretty-print.js +8 -10
- package/logger/logger.d.ts +1 -1
- package/logger/logger.js +15 -12
- package/message-bus/local/module.d.ts +5 -2
- package/message-bus/local/module.js +5 -4
- package/module/module.d.ts +2 -1
- package/module/module.js +3 -0
- package/module/modules/web-server.module.d.ts +11 -6
- package/module/modules/web-server.module.js +15 -10
- package/orm/decorators.d.ts +24 -1
- package/orm/decorators.js +40 -4
- package/orm/query/base.d.ts +17 -17
- package/orm/query/base.js +1 -1
- package/orm/repository.types.d.ts +45 -1
- package/orm/schemas/tsvector.js +1 -1
- package/orm/server/drizzle/schema-converter.d.ts +3 -1
- package/orm/server/drizzle/schema-converter.js +120 -14
- package/orm/server/index.d.ts +1 -0
- package/orm/server/index.js +1 -0
- package/orm/server/module.d.ts +4 -2
- package/orm/server/module.js +6 -5
- package/orm/server/query-converter.d.ts +6 -3
- package/orm/server/query-converter.js +32 -20
- package/orm/server/repository-config.d.ts +8 -0
- package/orm/server/repository-config.js +8 -0
- package/orm/server/repository.d.ts +117 -43
- package/orm/server/repository.js +757 -253
- package/orm/server/transaction.d.ts +4 -2
- package/orm/server/transaction.js +14 -5
- package/orm/server/transactional.d.ts +6 -2
- package/orm/server/transactional.js +39 -9
- package/orm/server/types.d.ts +2 -0
- package/orm/sqls/case-when.d.ts +3 -3
- package/orm/sqls/case-when.js +2 -2
- package/orm/sqls/sqls.d.ts +31 -5
- package/orm/sqls/sqls.js +69 -6
- package/orm/tests/data-types.test.d.ts +1 -0
- package/orm/tests/data-types.test.js +39 -0
- package/orm/tests/decorators.test.d.ts +1 -0
- package/orm/tests/decorators.test.js +77 -0
- package/orm/tests/encryption.test.d.ts +1 -0
- package/orm/tests/encryption.test.js +34 -0
- package/orm/tests/query-complex.test.d.ts +1 -0
- package/orm/tests/query-complex.test.js +203 -0
- package/orm/tests/query-converter-complex.test.d.ts +1 -0
- package/orm/tests/query-converter-complex.test.js +126 -0
- package/orm/tests/query-converter.test.d.ts +1 -0
- package/orm/tests/query-converter.test.js +123 -0
- package/orm/tests/repository-advanced.test.d.ts +1 -0
- package/orm/tests/repository-advanced.test.js +232 -0
- package/orm/tests/repository-attributes.test.d.ts +1 -0
- package/orm/tests/repository-attributes.test.js +99 -0
- package/orm/tests/repository-comprehensive.test.d.ts +1 -0
- package/orm/tests/repository-comprehensive.test.js +187 -0
- package/orm/tests/repository-coverage.test.d.ts +1 -0
- package/orm/tests/repository-coverage.test.js +303 -0
- package/orm/tests/repository-cti-complex.test.d.ts +1 -0
- package/orm/tests/repository-cti-complex.test.js +170 -0
- package/orm/tests/repository-cti-embedded.test.d.ts +1 -0
- package/orm/tests/repository-cti-embedded.test.js +188 -0
- package/orm/tests/repository-cti-extensive.test.d.ts +1 -0
- package/orm/tests/repository-cti-extensive.test.js +308 -0
- package/orm/tests/repository-cti-mapping.test.d.ts +1 -0
- package/orm/tests/repository-cti-mapping.test.js +121 -0
- package/orm/tests/repository-cti-search.test.d.ts +1 -0
- package/orm/tests/repository-cti-search.test.js +152 -0
- package/orm/tests/repository-cti-soft-delete.test.d.ts +1 -0
- package/orm/tests/repository-cti-soft-delete.test.js +115 -0
- package/orm/tests/repository-cti-transactions.test.d.ts +1 -0
- package/orm/tests/repository-cti-transactions.test.js +126 -0
- package/orm/tests/repository-cti-upsert-many.test.d.ts +1 -0
- package/orm/tests/repository-cti-upsert-many.test.js +127 -0
- package/orm/tests/repository-cti.test.d.ts +1 -0
- package/orm/tests/repository-cti.test.js +456 -0
- package/orm/tests/repository-edge-cases.test.d.ts +1 -0
- package/orm/tests/repository-edge-cases.test.js +216 -0
- package/orm/tests/repository-expiration.test.d.ts +1 -0
- package/orm/tests/repository-expiration.test.js +153 -0
- package/orm/tests/repository-extra-coverage.test.d.ts +1 -0
- package/orm/tests/repository-extra-coverage.test.js +546 -0
- package/orm/tests/repository-mapping.test.d.ts +1 -0
- package/orm/tests/repository-mapping.test.js +71 -0
- package/orm/tests/repository-regression.test.d.ts +1 -0
- package/orm/tests/repository-regression.test.js +330 -0
- package/orm/tests/repository-search-coverage.test.d.ts +1 -0
- package/orm/tests/repository-search-coverage.test.js +129 -0
- package/orm/tests/repository-search.test.d.ts +1 -0
- package/orm/tests/repository-search.test.js +116 -0
- package/orm/tests/repository-soft-delete.test.d.ts +1 -0
- package/orm/tests/repository-soft-delete.test.js +143 -0
- package/orm/tests/repository-transactions-nested.test.d.ts +1 -0
- package/orm/tests/repository-transactions-nested.test.js +202 -0
- package/orm/tests/repository-types.test.d.ts +1 -0
- package/orm/tests/repository-types.test.js +218 -0
- package/orm/tests/schema-converter.test.d.ts +1 -0
- package/orm/tests/schema-converter.test.js +81 -0
- package/orm/tests/schema-generation.test.d.ts +1 -0
- package/orm/tests/schema-generation.test.js +127 -0
- package/orm/tests/sql-helpers.test.d.ts +1 -0
- package/orm/tests/sql-helpers.test.js +67 -0
- package/orm/tests/transaction-safety.test.d.ts +1 -0
- package/orm/tests/transaction-safety.test.js +81 -0
- package/orm/tests/transactional.test.d.ts +1 -0
- package/orm/tests/transactional.test.js +224 -0
- package/orm/tests/utils.test.d.ts +1 -0
- package/orm/tests/utils.test.js +70 -0
- package/orm/utils.d.ts +7 -0
- package/orm/utils.js +26 -6
- package/package.json +12 -7
- package/pool/pool.js +1 -1
- package/rate-limit/index.d.ts +2 -0
- package/rate-limit/index.js +2 -0
- package/rate-limit/postgres/drizzle/0000_watery_rage.sql +7 -0
- package/{queue → rate-limit}/postgres/drizzle/meta/0000_snapshot.json +14 -39
- package/rate-limit/postgres/drizzle/meta/_journal.json +13 -0
- package/{queue → rate-limit}/postgres/drizzle.config.js +1 -1
- package/rate-limit/postgres/index.d.ts +4 -0
- package/rate-limit/postgres/index.js +4 -0
- package/rate-limit/postgres/module.d.ts +12 -0
- package/rate-limit/postgres/module.js +28 -0
- package/rate-limit/postgres/postgres-rate-limiter.d.ts +9 -0
- package/rate-limit/postgres/postgres-rate-limiter.js +56 -0
- package/rate-limit/postgres/rate-limit.model.d.ts +8 -0
- package/rate-limit/postgres/rate-limit.model.js +35 -0
- package/rate-limit/postgres/rate-limiter.provider.d.ts +6 -0
- package/rate-limit/postgres/rate-limiter.provider.js +21 -0
- package/rate-limit/postgres/schemas.d.ts +3 -0
- package/rate-limit/postgres/schemas.js +4 -0
- package/rate-limit/provider.d.ts +9 -0
- package/rate-limit/provider.js +2 -0
- package/rate-limit/rate-limiter.d.ts +35 -0
- package/rate-limit/rate-limiter.js +3 -0
- package/rate-limit/tests/postgres-rate-limiter.test.d.ts +1 -0
- package/rate-limit/tests/postgres-rate-limiter.test.js +92 -0
- package/signals/implementation/configure.d.ts +3 -0
- package/signals/implementation/configure.js +3 -0
- package/sse/data-stream-source.d.ts +1 -1
- package/sse/data-stream-source.js +6 -6
- package/task-queue/enqueue-batch.d.ts +17 -0
- package/task-queue/enqueue-batch.js +24 -0
- package/{queue → task-queue}/index.d.ts +1 -1
- package/{queue → task-queue}/index.js +1 -1
- package/task-queue/postgres/drizzle/0000_thin_black_panther.sql +74 -0
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +592 -0
- package/task-queue/postgres/drizzle/meta/_journal.json +13 -0
- package/task-queue/postgres/drizzle.config.d.ts +2 -0
- package/task-queue/postgres/drizzle.config.js +11 -0
- package/task-queue/postgres/index.d.ts +4 -0
- package/task-queue/postgres/index.js +4 -0
- package/task-queue/postgres/module.d.ts +12 -0
- package/task-queue/postgres/module.js +28 -0
- package/task-queue/postgres/schemas.d.ts +16 -0
- package/task-queue/postgres/schemas.js +8 -0
- package/task-queue/postgres/task-queue.d.ts +83 -0
- package/task-queue/postgres/task-queue.js +1054 -0
- package/task-queue/postgres/task-queue.provider.d.ts +7 -0
- package/{queue/postgres/queue.provider.js → task-queue/postgres/task-queue.provider.js} +8 -8
- package/task-queue/postgres/task.model.d.ts +39 -0
- package/task-queue/postgres/task.model.js +178 -0
- package/{queue → task-queue}/provider.d.ts +3 -3
- package/task-queue/provider.js +2 -0
- package/{queue → task-queue}/task-context.d.ts +7 -7
- package/{queue → task-queue}/task-context.js +8 -8
- package/{queue/queue.d.ts → task-queue/task-queue.d.ts} +128 -59
- package/task-queue/task-queue.js +200 -0
- package/task-queue/tests/complex.test.d.ts +1 -0
- package/task-queue/tests/complex.test.js +299 -0
- package/task-queue/tests/dependencies.test.d.ts +1 -0
- package/task-queue/tests/dependencies.test.js +174 -0
- package/task-queue/tests/queue.test.d.ts +1 -0
- package/task-queue/tests/queue.test.js +334 -0
- package/task-queue/tests/worker.test.d.ts +1 -0
- package/task-queue/tests/worker.test.js +163 -0
- package/test1.js +1 -1
- package/test4.js +2 -2
- package/unit-test/index.d.ts +1 -0
- package/unit-test/index.js +1 -0
- package/unit-test/integration-setup.d.ts +55 -0
- package/unit-test/integration-setup.js +182 -0
- package/utils/patterns.d.ts +3 -0
- package/utils/patterns.js +6 -1
- package/audit/drizzle/0001_previous_network.sql +0 -2
- package/audit/drizzle/meta/0001_snapshot.json +0 -195
- package/queue/enqueue-batch.d.ts +0 -17
- package/queue/enqueue-batch.js +0 -18
- package/queue/postgres/drizzle/0000_zippy_moondragon.sql +0 -11
- package/queue/postgres/drizzle/0001_certain_wild_pack.sql +0 -2
- package/queue/postgres/drizzle/0002_dear_meggan.sql +0 -2
- package/queue/postgres/drizzle/0003_tricky_venom.sql +0 -30
- package/queue/postgres/drizzle/meta/0001_snapshot.json +0 -103
- package/queue/postgres/drizzle/meta/0002_snapshot.json +0 -90
- package/queue/postgres/drizzle/meta/0003_snapshot.json +0 -288
- package/queue/postgres/drizzle/meta/_journal.json +0 -34
- package/queue/postgres/index.d.ts +0 -4
- package/queue/postgres/index.js +0 -4
- package/queue/postgres/module.d.ts +0 -9
- package/queue/postgres/module.js +0 -29
- package/queue/postgres/queue.d.ts +0 -60
- package/queue/postgres/queue.js +0 -681
- package/queue/postgres/queue.provider.d.ts +0 -7
- package/queue/postgres/schemas.d.ts +0 -14
- package/queue/postgres/schemas.js +0 -6
- package/queue/postgres/task.model.d.ts +0 -24
- package/queue/postgres/task.model.js +0 -115
- package/queue/provider.js +0 -2
- package/queue/queue.js +0 -131
- package/queue/tests/queue.test.js +0 -623
- package/test3.d.ts +0 -1
- package/test3.js +0 -47
- /package/{queue/tests/queue.test.d.ts → api/server/tests/csrf.middleware.test.d.ts} +0 -0
- /package/circuit-breaker/postgres/drizzle/{0000_hard_shocker.sql → 0000_cooing_korath.sql} +0 -0
- /package/{queue → rate-limit}/postgres/drizzle.config.d.ts +0 -0
|
@@ -1,623 +0,0 @@
|
|
|
1
|
-
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { configurePostgresCircuitBreaker, migratePostgresCircuitBreaker } from '../../circuit-breaker/postgres/module.js';
|
|
3
|
-
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
4
|
-
import { ConsoleLogTransport, LogFormatter, Logger, LogTransport, PrettyPrintLogFormatter } from '../../logger/index.js';
|
|
5
|
-
import { configureLocalMessageBus } from '../../message-bus/index.js';
|
|
6
|
-
import { configureOrm } from '../../orm/server/index.js';
|
|
7
|
-
import { QueueProvider, TaskState, UniqueTagStrategy } from '../../queue/index.js';
|
|
8
|
-
import { configurePostgresQueue, migratePostgresQueueSchema } from '../../queue/postgres/index.js';
|
|
9
|
-
import * as configParser from '../../utils/config-parser.js';
|
|
10
|
-
import { currentTimestamp } from '../../utils/date-time.js';
|
|
11
|
-
import { timeout } from '../../utils/timing.js';
|
|
12
|
-
/**
|
|
13
|
-
* Encapsulates the boilerplate for setting up the integration environment.
|
|
14
|
-
*/
|
|
15
|
-
async function setupIntegrationTest() {
|
|
16
|
-
const injector = new Injector('TestInjector');
|
|
17
|
-
injector.register(LogFormatter, { useToken: PrettyPrintLogFormatter });
|
|
18
|
-
injector.register(LogTransport, { useToken: ConsoleLogTransport });
|
|
19
|
-
// 2. Configuration
|
|
20
|
-
const dbConfig = {
|
|
21
|
-
host: configParser.string('DATABASE_HOST', '127.0.0.1'),
|
|
22
|
-
port: configParser.positiveInteger('DATABASE_PORT', 5432),
|
|
23
|
-
user: configParser.string('DATABASE_USER', 'tstdl'),
|
|
24
|
-
password: configParser.string('DATABASE_PASS', 'wf7rq6glrk5jykne'),
|
|
25
|
-
database: configParser.string('DATABASE_NAME', 'tstdl'),
|
|
26
|
-
};
|
|
27
|
-
// 3. Configure Modules
|
|
28
|
-
configureOrm({
|
|
29
|
-
repositoryConfig: { schema: 'test' },
|
|
30
|
-
connection: dbConfig,
|
|
31
|
-
});
|
|
32
|
-
configureLocalMessageBus();
|
|
33
|
-
configurePostgresQueue();
|
|
34
|
-
configurePostgresCircuitBreaker();
|
|
35
|
-
// 4. Run Migrations
|
|
36
|
-
// We need to resolve the module config once to ensure migrations run against the correct DB
|
|
37
|
-
await runInInjectionContext(injector, migratePostgresQueueSchema);
|
|
38
|
-
await runInInjectionContext(injector, migratePostgresCircuitBreaker);
|
|
39
|
-
return injector;
|
|
40
|
-
}
|
|
41
|
-
describe('Queue Integration Tests', () => {
|
|
42
|
-
let injector;
|
|
43
|
-
let queue;
|
|
44
|
-
let logger;
|
|
45
|
-
const queueName = `test-queue-${Date.now()}`;
|
|
46
|
-
// Helper to verify state in DB
|
|
47
|
-
async function assertTaskState(id, state, message) {
|
|
48
|
-
const task = await queue.get(id);
|
|
49
|
-
expect(task?.status, message).toBe(state);
|
|
50
|
-
}
|
|
51
|
-
beforeAll(async () => {
|
|
52
|
-
injector = await setupIntegrationTest();
|
|
53
|
-
logger = injector.resolve(Logger);
|
|
54
|
-
// Use the QueueProvider to Create/Retrieve the queue.
|
|
55
|
-
// This is the cleanest way to get a configured queue instance without
|
|
56
|
-
// messing with `injectArgument` manually in the test body.
|
|
57
|
-
const queueProvider = injector.resolve(QueueProvider);
|
|
58
|
-
queue = queueProvider.get(queueName, {
|
|
59
|
-
processTimeout: 5000,
|
|
60
|
-
retryDelayMinimum: 100, // Fast retries for testing
|
|
61
|
-
retryDelayGrowth: 1, // Linear/No growth for predictable tests
|
|
62
|
-
});
|
|
63
|
-
logger.info(`Queue initialized: ${queueName}`);
|
|
64
|
-
});
|
|
65
|
-
afterEach(async () => {
|
|
66
|
-
// Drain the queue to prevent state leakage between tests
|
|
67
|
-
await queue.clear();
|
|
68
|
-
});
|
|
69
|
-
afterAll(async () => {
|
|
70
|
-
try {
|
|
71
|
-
await queue.clear();
|
|
72
|
-
await injector.dispose();
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
// Ignore known double-dispose issue from MessageBus interaction
|
|
76
|
-
if (error instanceof Error && error.message === 'MessageBus is disposed.') {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
throw error;
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
it('Basic FIFO Flow', async () => {
|
|
83
|
-
logger.info('--- Test: Basic FIFO Flow ---');
|
|
84
|
-
const t1 = await queue.enqueue({ value: 'first' });
|
|
85
|
-
const t2 = await queue.enqueue({ value: 'second' });
|
|
86
|
-
const d1 = await queue.dequeue();
|
|
87
|
-
expect(d1?.id).toBe(t1.id);
|
|
88
|
-
expect(d1?.data.value).toBe('first');
|
|
89
|
-
await queue.acknowledge(d1, { success: true });
|
|
90
|
-
await assertTaskState(t1.id, TaskState.Completed, 'Task 1 completed');
|
|
91
|
-
const d2 = await queue.dequeue();
|
|
92
|
-
expect(d2?.id).toBe(t2.id);
|
|
93
|
-
await queue.acknowledge(d2, { success: true });
|
|
94
|
-
});
|
|
95
|
-
it('Priorities', async () => {
|
|
96
|
-
logger.info('--- Test: Prioritization ---');
|
|
97
|
-
// Priority 1000 (default)
|
|
98
|
-
const low = await queue.enqueue({ value: 'low' });
|
|
99
|
-
// Priority 1
|
|
100
|
-
const high = await queue.enqueue({ value: 'high' }, { priority: 1 });
|
|
101
|
-
const first = await queue.dequeue();
|
|
102
|
-
expect(first?.id).toBe(high.id);
|
|
103
|
-
await queue.acknowledge(first);
|
|
104
|
-
const second = await queue.dequeue();
|
|
105
|
-
expect(second?.id).toBe(low.id);
|
|
106
|
-
await queue.acknowledge(second);
|
|
107
|
-
});
|
|
108
|
-
it('Deduplication (Unique Tags)', async () => {
|
|
109
|
-
logger.info('--- Test: Deduplication (Unique Tags) ---');
|
|
110
|
-
const tag = `unique-${Date.now()}`;
|
|
111
|
-
// 1. Initial Insert
|
|
112
|
-
const t1 = await queue.enqueue({ value: 'original' }, { tag, uniqueTag: UniqueTagStrategy.KeepOld });
|
|
113
|
-
// 2. KeepOld Strategy: Should return existing task, ignore new data
|
|
114
|
-
const t2 = await queue.enqueue({ value: 'ignored' }, { tag, uniqueTag: UniqueTagStrategy.KeepOld });
|
|
115
|
-
expect(t1.id, 'KeepOld returned same ID').toBe(t2.id);
|
|
116
|
-
const check1 = await queue.get(t1.id);
|
|
117
|
-
expect(check1?.data.value).toBe('original');
|
|
118
|
-
// 3. TakeNew Strategy: Should replace existing task with new data (new ID generated)
|
|
119
|
-
const t3 = await queue.enqueue({ value: 'updated' }, { tag, uniqueTag: UniqueTagStrategy.TakeNew });
|
|
120
|
-
expect(t1.id, 'TakeNew generated a NEW ID').not.toBe(t3.id);
|
|
121
|
-
// Old ID should be gone (updated to new ID)
|
|
122
|
-
const checkOld = await queue.get(t1.id);
|
|
123
|
-
expect(checkOld).toBeUndefined();
|
|
124
|
-
// New ID should have new data
|
|
125
|
-
const checkNew = await queue.get(t3.id);
|
|
126
|
-
expect(checkNew?.data.value).toBe('updated');
|
|
127
|
-
expect(checkNew?.tries).toBe(0);
|
|
128
|
-
});
|
|
129
|
-
it('Retries and Failures', async () => {
|
|
130
|
-
logger.info('--- Test: Retries and Dead Letter ---');
|
|
131
|
-
const task = await queue.enqueue({ value: 'fail-me' });
|
|
132
|
-
// Try 1
|
|
133
|
-
const attempt1 = await queue.dequeue();
|
|
134
|
-
expect(attempt1?.id).toBe(task.id);
|
|
135
|
-
await queue.fail(attempt1, { message: 'oops' });
|
|
136
|
-
// Force reschedule to now to bypass retryDelay
|
|
137
|
-
await queue.reschedule(task.id, Date.now());
|
|
138
|
-
// Try 2
|
|
139
|
-
const attempt2 = await queue.dequeue();
|
|
140
|
-
expect(attempt2?.id).toBe(task.id);
|
|
141
|
-
expect(attempt2?.tries).toBe(2);
|
|
142
|
-
// Fail fatally
|
|
143
|
-
await queue.fail(attempt2, { message: 'fatal error' }, true);
|
|
144
|
-
await assertTaskState(task.id, TaskState.Dead, 'Task is Dead after fatal error');
|
|
145
|
-
});
|
|
146
|
-
it('Hierarchy (Parent/Child)', async () => {
|
|
147
|
-
logger.info('--- Test: Hierarchy (Parent/Child) ---');
|
|
148
|
-
// A. Create Parent
|
|
149
|
-
const p = await queue.enqueue({ value: 'parent-manual' });
|
|
150
|
-
// B. Dequeue Parent
|
|
151
|
-
const pTask = await queue.dequeue();
|
|
152
|
-
expect(pTask?.id).toBe(p.id);
|
|
153
|
-
// C. Parent spawns child
|
|
154
|
-
const child = await queue.enqueue({ value: 'child-manual' }, { parentId: p.id });
|
|
155
|
-
// D. "Finish" Parent execution. It should enter WAITING.
|
|
156
|
-
await queue.acknowledge(pTask);
|
|
157
|
-
await assertTaskState(p.id, TaskState.Waiting, 'Parent entered WAITING state');
|
|
158
|
-
// E. Dequeue & Finish Child
|
|
159
|
-
const cTask = await queue.dequeue();
|
|
160
|
-
expect(cTask?.id).toBe(child.id);
|
|
161
|
-
await queue.acknowledge(cTask);
|
|
162
|
-
// F. Check Parent State -> PENDING (Fan-In triggered)
|
|
163
|
-
await assertTaskState(p.id, TaskState.Pending, 'Parent rescheduled (PENDING)');
|
|
164
|
-
// G. Dequeue Parent Again (Resume) & Finish
|
|
165
|
-
const pTaskResumed = await queue.dequeue();
|
|
166
|
-
expect(pTaskResumed?.id).toBe(p.id);
|
|
167
|
-
await queue.acknowledge(pTaskResumed);
|
|
168
|
-
await assertTaskState(p.id, TaskState.Completed, 'Parent COMPLETED');
|
|
169
|
-
});
|
|
170
|
-
it('Batching', async () => {
|
|
171
|
-
logger.info('--- Test: Batch Operations ---');
|
|
172
|
-
const batch = queue.batch();
|
|
173
|
-
for (let i = 0; i < 5; i++) {
|
|
174
|
-
batch.add({ value: `batch-${i}` });
|
|
175
|
-
}
|
|
176
|
-
const tasks = await batch.enqueue(true);
|
|
177
|
-
expect(tasks.length).toBe(5);
|
|
178
|
-
const dequeuedBatch = await queue.dequeueMany(5);
|
|
179
|
-
expect(dequeuedBatch.length).toBe(5);
|
|
180
|
-
await queue.acknowledgeMany(dequeuedBatch);
|
|
181
|
-
const leftover = await queue.dequeue();
|
|
182
|
-
expect(leftover).toBeUndefined();
|
|
183
|
-
});
|
|
184
|
-
it('Partial Success: Parent resumes after one child fails and another succeeds', async () => {
|
|
185
|
-
logger.info('--- Test: Partial Success ---');
|
|
186
|
-
// 1. Create Parent
|
|
187
|
-
const p = await queue.enqueue({ value: 'parent' });
|
|
188
|
-
// 2. Dequeue Parent
|
|
189
|
-
const pTask = await queue.dequeue();
|
|
190
|
-
expect(pTask?.id).toBe(p.id);
|
|
191
|
-
// 3. Parent spawns two children
|
|
192
|
-
const child1 = await queue.enqueue({ value: 'child-success' }, { parentId: p.id });
|
|
193
|
-
const child2 = await queue.enqueue({ value: 'child-fail' }, { parentId: p.id });
|
|
194
|
-
// 4. Acknowledge Parent -> Waiting
|
|
195
|
-
await queue.acknowledge(pTask);
|
|
196
|
-
await assertTaskState(p.id, TaskState.Waiting, 'Parent is WAITING');
|
|
197
|
-
// 5. Dequeue & Succeed Child 1
|
|
198
|
-
const c1Task = await queue.dequeue();
|
|
199
|
-
expect(c1Task?.id).toBe(child1.id);
|
|
200
|
-
await queue.acknowledge(c1Task);
|
|
201
|
-
// Parent should still be waiting
|
|
202
|
-
await assertTaskState(p.id, TaskState.Waiting, 'Parent still WAITING after first child');
|
|
203
|
-
// 6. Dequeue & Fail Child 2 (Fatal)
|
|
204
|
-
const c2Task = await queue.dequeue();
|
|
205
|
-
expect(c2Task?.id).toBe(child2.id);
|
|
206
|
-
await queue.fail(c2Task, { message: 'intentional failure' }, true);
|
|
207
|
-
// 7. Check Parent State -> PENDING (Woken up despite child failure)
|
|
208
|
-
await assertTaskState(p.id, TaskState.Pending, 'Parent woken up (PENDING) despite child failure');
|
|
209
|
-
// 8. Dequeue Parent Again & Complete
|
|
210
|
-
const pTaskResumed = await queue.dequeue();
|
|
211
|
-
expect(pTaskResumed?.id).toBe(p.id);
|
|
212
|
-
await queue.acknowledge(pTaskResumed);
|
|
213
|
-
await assertTaskState(p.id, TaskState.Completed, 'Parent COMPLETED successfully');
|
|
214
|
-
});
|
|
215
|
-
it('Acknowledge with pre-existing failed child: Parent completes', async () => {
|
|
216
|
-
logger.info('--- Test: Acknowledge with pre-existing failed child ---');
|
|
217
|
-
// 1. Create Parent
|
|
218
|
-
const p = await queue.enqueue({ value: 'parent-2' });
|
|
219
|
-
// 2. Dequeue Parent
|
|
220
|
-
const pTask = await queue.dequeue();
|
|
221
|
-
// 3. Parent spawns a child that fails
|
|
222
|
-
const child = await queue.enqueue({ value: 'child-fail-early' }, { parentId: p.id });
|
|
223
|
-
const cTask = await queue.dequeue();
|
|
224
|
-
await queue.fail(cTask, { message: 'fail early' }, true);
|
|
225
|
-
await assertTaskState(child.id, TaskState.Dead, 'Child is DEAD');
|
|
226
|
-
// 4. Parent acknowledges its own execution.
|
|
227
|
-
// It should COMPLETE now, even though child is dead, because there are no ACTIVE children.
|
|
228
|
-
await queue.acknowledge(pTask);
|
|
229
|
-
await assertTaskState(p.id, TaskState.Completed, 'Parent COMPLETED even with dead child');
|
|
230
|
-
});
|
|
231
|
-
it('Fan-In & Result Persistence', async () => {
|
|
232
|
-
logger.info('--- Test: Fan-In & Result Persistence ---');
|
|
233
|
-
// 1. Create Parent
|
|
234
|
-
const p = await queue.enqueue({ value: 'parent' });
|
|
235
|
-
// 2. Dequeue Parent
|
|
236
|
-
const pTask = await queue.dequeue();
|
|
237
|
-
expect(pTask?.id).toBe(p.id);
|
|
238
|
-
// 3. Parent spawns children
|
|
239
|
-
await queue.enqueueMany([
|
|
240
|
-
{ data: { value: 'child-1' }, parentId: p.id },
|
|
241
|
-
{ data: { value: 'child-2' }, parentId: p.id },
|
|
242
|
-
]);
|
|
243
|
-
// 4. Acknowledge Parent with a result. This result should be saved as 'state' since we move to Waiting.
|
|
244
|
-
const partialResult = { step: 'children-spawned' };
|
|
245
|
-
await queue.acknowledge(pTask, partialResult);
|
|
246
|
-
// 5. Verify Parent is WAITING and has the state
|
|
247
|
-
const pWaiting = await queue.get(p.id);
|
|
248
|
-
expect(pWaiting?.status).toBe(TaskState.Waiting);
|
|
249
|
-
expect(pWaiting?.state).toEqual(partialResult);
|
|
250
|
-
// 6. Complete children
|
|
251
|
-
const c1 = await queue.dequeue();
|
|
252
|
-
const c2 = await queue.dequeue();
|
|
253
|
-
await queue.acknowledge(c1);
|
|
254
|
-
await queue.acknowledge(c2);
|
|
255
|
-
// 7. Verify Parent is PENDING
|
|
256
|
-
await assertTaskState(p.id, TaskState.Pending, 'Parent woken up');
|
|
257
|
-
// 8. Dequeue Parent again and check its state
|
|
258
|
-
const pResumed = await queue.dequeue();
|
|
259
|
-
expect(pResumed?.id).toBe(p.id);
|
|
260
|
-
expect(pResumed?.state).toEqual(partialResult);
|
|
261
|
-
await queue.acknowledge(pResumed);
|
|
262
|
-
await assertTaskState(p.id, TaskState.Completed, 'Parent COMPLETED');
|
|
263
|
-
});
|
|
264
|
-
it('Circuit Breaker Probe Logic', async () => {
|
|
265
|
-
logger.info('--- Test: Circuit Breaker Probe Logic ---');
|
|
266
|
-
const cbQueueName = `cb-test-${Date.now()}`;
|
|
267
|
-
const queueProvider = injector.resolve(QueueProvider);
|
|
268
|
-
const cbQueue = queueProvider.get(cbQueueName, {
|
|
269
|
-
circuitBreakerThreshold: 2,
|
|
270
|
-
circuitBreakerResetTimeout: 500,
|
|
271
|
-
});
|
|
272
|
-
// 1. Trip the breaker (Threshold = 2)
|
|
273
|
-
await cbQueue.enqueueMany([
|
|
274
|
-
{ data: { value: 'fail-1' } },
|
|
275
|
-
{ data: { value: 'fail-2' } },
|
|
276
|
-
]);
|
|
277
|
-
const fail1 = await cbQueue.dequeue();
|
|
278
|
-
const fail2 = await cbQueue.dequeue();
|
|
279
|
-
await cbQueue.fail(fail1, 'fail');
|
|
280
|
-
await cbQueue.fail(fail2, 'fail');
|
|
281
|
-
// 2. Enqueue more tasks
|
|
282
|
-
await cbQueue.enqueueMany([
|
|
283
|
-
{ data: { value: 't1' } },
|
|
284
|
-
{ data: { value: 't2' } },
|
|
285
|
-
]);
|
|
286
|
-
// 3. Dequeue should return nothing while Open
|
|
287
|
-
const tasksOpen = await cbQueue.dequeueMany(5);
|
|
288
|
-
expect(tasksOpen.length).toBe(0);
|
|
289
|
-
// 4. Wait for reset timeout
|
|
290
|
-
await timeout(600);
|
|
291
|
-
// 5. First dequeue after timeout should be a PROBE (only 1 task)
|
|
292
|
-
const tasksProbe = await cbQueue.dequeueMany(5);
|
|
293
|
-
expect(tasksProbe.length).toBe(1);
|
|
294
|
-
expect(tasksProbe[0]?.data.value).toBe('t1');
|
|
295
|
-
// 6. While Half-Open (waiting for probe result), subsequent dequeues should return nothing
|
|
296
|
-
const tasksHalfOpen = await cbQueue.dequeueMany(5);
|
|
297
|
-
expect(tasksHalfOpen.length).toBe(0);
|
|
298
|
-
// 7. Acknowledge probe -> Breaker closes
|
|
299
|
-
await cbQueue.acknowledge(tasksProbe[0]);
|
|
300
|
-
// 8. Now closed, we should get the rest
|
|
301
|
-
const remaining = await cbQueue.dequeueMany(5);
|
|
302
|
-
expect(remaining.length).toBe(1);
|
|
303
|
-
expect(remaining[0]?.data.value).toBe('t2');
|
|
304
|
-
await cbQueue.clear();
|
|
305
|
-
});
|
|
306
|
-
it('Bulk failMany & Circuit Breaker', async () => {
|
|
307
|
-
logger.info('--- Test: Bulk failMany & Circuit Breaker ---');
|
|
308
|
-
const cbQueueName = `bulk-fail-test-${Date.now()}`;
|
|
309
|
-
const queueProvider = injector.resolve(QueueProvider);
|
|
310
|
-
const cbQueue = queueProvider.get(cbQueueName, {
|
|
311
|
-
circuitBreakerThreshold: 5,
|
|
312
|
-
circuitBreakerResetTimeout: 5000,
|
|
313
|
-
maxTries: 1, // Fail immediately
|
|
314
|
-
});
|
|
315
|
-
// 1. Enqueue 5 tasks (Threshold)
|
|
316
|
-
const tasks = await cbQueue.enqueueMany([
|
|
317
|
-
{ data: { value: 'f1' } },
|
|
318
|
-
{ data: { value: 'f2' } },
|
|
319
|
-
{ data: { value: 'f3' } },
|
|
320
|
-
{ data: { value: 'f4' } },
|
|
321
|
-
{ data: { value: 'f5' } },
|
|
322
|
-
], { returnTasks: true });
|
|
323
|
-
// 2. Dequeue all
|
|
324
|
-
const dequeued = await cbQueue.dequeueMany(5);
|
|
325
|
-
expect(dequeued.length).toBe(5);
|
|
326
|
-
// 3. Fail all
|
|
327
|
-
await cbQueue.failMany(dequeued, ['e1', 'e2', 'e3', 'e4', 'e5']);
|
|
328
|
-
// 4. Verify all are Dead
|
|
329
|
-
for (const t of tasks) {
|
|
330
|
-
const stored = await cbQueue.get(t.id);
|
|
331
|
-
expect(stored?.status, `Task ${t.id} is dead`).toBe(TaskState.Dead);
|
|
332
|
-
expect(stored?.error?.message).toMatch(/e\d/);
|
|
333
|
-
}
|
|
334
|
-
// 5. Verify Circuit Breaker is OPEN
|
|
335
|
-
// Enqueue a new task
|
|
336
|
-
await cbQueue.enqueue({ value: 'probe' });
|
|
337
|
-
// Try to dequeue - should fail because CB is open (Threshold 5 reached by batch of 5)
|
|
338
|
-
const probe = await cbQueue.dequeue();
|
|
339
|
-
expect(probe).toBeUndefined();
|
|
340
|
-
await cbQueue.clear();
|
|
341
|
-
});
|
|
342
|
-
it('Bulk failMany with Fan-In', async () => {
|
|
343
|
-
logger.info('--- Test: Bulk failMany with Fan-In ---');
|
|
344
|
-
// 1. Create Parent
|
|
345
|
-
const p = await queue.enqueue({ value: 'parent-bulk' });
|
|
346
|
-
const pTask = await queue.dequeue();
|
|
347
|
-
// 2. Create Children
|
|
348
|
-
const children = await queue.enqueueMany([
|
|
349
|
-
{ data: { value: 'c1' }, parentId: p.id },
|
|
350
|
-
{ data: { value: 'c2' }, parentId: p.id },
|
|
351
|
-
{ data: { value: 'c3' }, parentId: p.id },
|
|
352
|
-
], { returnTasks: true });
|
|
353
|
-
await queue.acknowledge(pTask); // Parent -> Waiting
|
|
354
|
-
// 3. Dequeue Children
|
|
355
|
-
const dequeuedChildren = await queue.dequeueMany(3);
|
|
356
|
-
expect(dequeuedChildren.length).toBe(3);
|
|
357
|
-
// 4. Fail all children (Fatal)
|
|
358
|
-
const fiQueueName = `fanin-test-${Date.now()}`;
|
|
359
|
-
const queueProvider = injector.resolve(QueueProvider);
|
|
360
|
-
const fiQueue = queueProvider.get(fiQueueName, {
|
|
361
|
-
maxTries: 1,
|
|
362
|
-
});
|
|
363
|
-
const p2 = await fiQueue.enqueue({ value: 'parent-fi' });
|
|
364
|
-
const p2Task = await fiQueue.dequeue();
|
|
365
|
-
const children2 = await fiQueue.enqueueMany([
|
|
366
|
-
{ data: { value: 'c1' }, parentId: p2.id },
|
|
367
|
-
{ data: { value: 'c2' }, parentId: p2.id },
|
|
368
|
-
], { returnTasks: true });
|
|
369
|
-
await fiQueue.acknowledge(p2Task); // Waiting
|
|
370
|
-
const dequeued2 = await fiQueue.dequeueMany(2);
|
|
371
|
-
// Fail many
|
|
372
|
-
await fiQueue.failMany(dequeued2, ['error', 'error']);
|
|
373
|
-
// Check children are dead
|
|
374
|
-
const c1State = await fiQueue.get(children2[0].id);
|
|
375
|
-
expect(c1State?.status).toBe(TaskState.Dead);
|
|
376
|
-
// Check Parent Fan-In -> Should be Pending
|
|
377
|
-
const p2State = await fiQueue.get(p2.id);
|
|
378
|
-
expect(p2State?.status).toBe(TaskState.Pending);
|
|
379
|
-
await fiQueue.clear();
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
describe('PostgresQueue (Distributed Task Orchestration)', () => {
|
|
383
|
-
let injector;
|
|
384
|
-
let queue;
|
|
385
|
-
beforeAll(async () => {
|
|
386
|
-
injector = await setupIntegrationTest();
|
|
387
|
-
});
|
|
388
|
-
beforeEach(() => {
|
|
389
|
-
const queueProvider = injector.resolve(QueueProvider);
|
|
390
|
-
const queueName = `pg-test-queue-${Date.now()}-${Math.random()}`;
|
|
391
|
-
queue = queueProvider.get(queueName, {
|
|
392
|
-
processTimeout: 200, // Short timeout for testing
|
|
393
|
-
retryDelayMinimum: 50,
|
|
394
|
-
retryDelayGrowth: 1,
|
|
395
|
-
circuitBreakerThreshold: 2,
|
|
396
|
-
circuitBreakerResetTimeout: 200,
|
|
397
|
-
});
|
|
398
|
-
});
|
|
399
|
-
afterEach(async () => {
|
|
400
|
-
await queue.clear();
|
|
401
|
-
});
|
|
402
|
-
afterAll(async () => {
|
|
403
|
-
await injector?.dispose();
|
|
404
|
-
});
|
|
405
|
-
describe('Basic Lifecycle', () => {
|
|
406
|
-
it('should enqueue and dequeue a task', async () => {
|
|
407
|
-
await queue.enqueue({ foo: 'bar' });
|
|
408
|
-
const task = await queue.dequeue();
|
|
409
|
-
expect(task).toBeDefined();
|
|
410
|
-
expect(task?.data).toEqual({ foo: 'bar' });
|
|
411
|
-
expect(task?.status).toBe(TaskState.Running);
|
|
412
|
-
expect(task?.tries).toBe(1);
|
|
413
|
-
});
|
|
414
|
-
it('should acknowledge a task successfully', async () => {
|
|
415
|
-
const task = await queue.enqueue({ foo: 'bar' });
|
|
416
|
-
const dequeued = await queue.dequeue();
|
|
417
|
-
await queue.acknowledge(dequeued, { result: true });
|
|
418
|
-
const updated = await queue.get(task.id);
|
|
419
|
-
expect(updated?.status).toBe(TaskState.Completed);
|
|
420
|
-
expect(updated?.result).toEqual({ result: true });
|
|
421
|
-
expect(updated?.completeTimestamp).toBeGreaterThan(0);
|
|
422
|
-
});
|
|
423
|
-
it('should fail a task and increment tries', async () => {
|
|
424
|
-
const task = await queue.enqueue({ foo: 'bar' });
|
|
425
|
-
const dequeued = await queue.dequeue();
|
|
426
|
-
await queue.fail(dequeued, new Error('temp failure'));
|
|
427
|
-
const updated = await queue.get(task.id);
|
|
428
|
-
expect(updated?.status).toBe(TaskState.Pending);
|
|
429
|
-
expect(updated?.tries).toBe(1);
|
|
430
|
-
expect(updated?.error).toBeDefined();
|
|
431
|
-
});
|
|
432
|
-
});
|
|
433
|
-
describe('Deduplication (Unique Tags)', () => {
|
|
434
|
-
it('should keep the old task when using UniqueTagStrategy.KeepOld', async () => {
|
|
435
|
-
const tag = 'unique-1';
|
|
436
|
-
await queue.enqueue({ foo: 'first' }, { tag, uniqueTag: UniqueTagStrategy.KeepOld });
|
|
437
|
-
await queue.enqueue({ foo: 'second' }, { tag, uniqueTag: UniqueTagStrategy.KeepOld });
|
|
438
|
-
const count = await queue.countByTag(tag);
|
|
439
|
-
const task = (await queue.getByTag(tag))[0];
|
|
440
|
-
expect(count).toBe(1);
|
|
441
|
-
expect(task?.data).toEqual({ foo: 'first' });
|
|
442
|
-
});
|
|
443
|
-
it('should overwrite with new data when using UniqueTagStrategy.TakeNew', async () => {
|
|
444
|
-
const tag = 'unique-2';
|
|
445
|
-
await queue.enqueue({ foo: 'first' }, { tag, uniqueTag: UniqueTagStrategy.TakeNew });
|
|
446
|
-
await queue.enqueue({ foo: 'second' }, { tag, uniqueTag: UniqueTagStrategy.TakeNew });
|
|
447
|
-
const count = await queue.countByTag(tag);
|
|
448
|
-
const task = (await queue.getByTag(tag))[0];
|
|
449
|
-
expect(count).toBe(1);
|
|
450
|
-
expect(task?.data).toEqual({ foo: 'second' });
|
|
451
|
-
expect(task?.tries).toBe(0); // Should reset
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
describe('Priority and Scheduling', () => {
|
|
455
|
-
it('should dequeue tasks in priority order (lower number first)', async () => {
|
|
456
|
-
await queue.enqueue({ foo: 'low' }, { priority: 2000 });
|
|
457
|
-
await queue.enqueue({ foo: 'high' }, { priority: 10 });
|
|
458
|
-
await queue.enqueue({ foo: 'mid' }, { priority: 1000 });
|
|
459
|
-
const t1 = await queue.dequeue();
|
|
460
|
-
const t2 = await queue.dequeue();
|
|
461
|
-
const t3 = await queue.dequeue();
|
|
462
|
-
expect(t1?.data.foo).toBe('high');
|
|
463
|
-
expect(t2?.data.foo).toBe('mid');
|
|
464
|
-
expect(t3?.data.foo).toBe('low');
|
|
465
|
-
});
|
|
466
|
-
it('should not dequeue a task scheduled in the future', async () => {
|
|
467
|
-
const future = currentTimestamp() + 500;
|
|
468
|
-
await queue.enqueue({ foo: 'future' }, { scheduleTimestamp: future });
|
|
469
|
-
const task = await queue.dequeue();
|
|
470
|
-
expect(task).toBeUndefined();
|
|
471
|
-
await timeout(600);
|
|
472
|
-
const taskLater = await queue.dequeue();
|
|
473
|
-
expect(taskLater).toBeDefined();
|
|
474
|
-
});
|
|
475
|
-
});
|
|
476
|
-
describe('Concurrency Control', () => {
|
|
477
|
-
it('should respect global concurrency limits', async () => {
|
|
478
|
-
const queueProvider = injector.resolve(QueueProvider);
|
|
479
|
-
const limitedQueue = queueProvider.get(`limit-test-${Date.now()}`, { globalConcurrency: 2 });
|
|
480
|
-
await limitedQueue.enqueueMany([{ data: { foo: '1' } }, { data: { foo: '2' } }, { data: { foo: '3' } }]);
|
|
481
|
-
const t1 = await limitedQueue.dequeue();
|
|
482
|
-
const t2 = await limitedQueue.dequeue();
|
|
483
|
-
const t3 = await limitedQueue.dequeue();
|
|
484
|
-
expect(t1).toBeDefined();
|
|
485
|
-
expect(t2).toBeDefined();
|
|
486
|
-
expect(t3).toBeUndefined(); // Limit reached
|
|
487
|
-
await limitedQueue.acknowledge(t1);
|
|
488
|
-
const t3Retry = await limitedQueue.dequeue();
|
|
489
|
-
expect(t3Retry).toBeDefined(); // Slot opened
|
|
490
|
-
await limitedQueue.clear();
|
|
491
|
-
});
|
|
492
|
-
});
|
|
493
|
-
describe('Circuit Breaker', () => {
|
|
494
|
-
it('should trip the breaker after threshold failures', async () => {
|
|
495
|
-
// Config: circuitBreakerThreshold: 2 (set in beforeEach)
|
|
496
|
-
await queue.enqueue({ foo: '1' });
|
|
497
|
-
await queue.enqueue({ foo: '2' });
|
|
498
|
-
await queue.enqueue({ foo: '3' });
|
|
499
|
-
await queue.fail((await queue.dequeue()), 'err');
|
|
500
|
-
await queue.fail((await queue.dequeue()), 'err');
|
|
501
|
-
// Breaker should be Open
|
|
502
|
-
const t3Attempt = await queue.dequeue();
|
|
503
|
-
expect(t3Attempt).toBeUndefined();
|
|
504
|
-
});
|
|
505
|
-
it('should allow a single probe in Half-Open state', async () => {
|
|
506
|
-
await queue.enqueueMany([{ data: { foo: '1' } }, { data: { foo: '2' } }]);
|
|
507
|
-
await queue.fail((await queue.dequeue()), 'err');
|
|
508
|
-
await queue.fail((await queue.dequeue()), 'err');
|
|
509
|
-
// Breaker is Open. Wait for reset timeout (200ms)
|
|
510
|
-
await timeout(250);
|
|
511
|
-
const probe = await queue.dequeue();
|
|
512
|
-
expect(probe).toBeDefined();
|
|
513
|
-
const secondAttempt = await queue.dequeue();
|
|
514
|
-
expect(secondAttempt).toBeUndefined(); // Only 1 probe allowed in Half-Open
|
|
515
|
-
});
|
|
516
|
-
});
|
|
517
|
-
describe('Task Hierarchy (Orchestration)', () => {
|
|
518
|
-
it('should move parent to Waiting if children are spawned', async () => {
|
|
519
|
-
const parent = await queue.enqueue({ foo: 'parent' });
|
|
520
|
-
const pDequeued = await queue.dequeue();
|
|
521
|
-
// Spawn child
|
|
522
|
-
await queue.enqueue({ foo: 'child' }, { parentId: pDequeued.id });
|
|
523
|
-
await queue.acknowledge(pDequeued);
|
|
524
|
-
const pStatus = await queue.get(parent.id);
|
|
525
|
-
expect(pStatus?.status).toBe(TaskState.Waiting);
|
|
526
|
-
});
|
|
527
|
-
it('should wake up parent when all children are completed', async () => {
|
|
528
|
-
const parent = await queue.enqueue({ foo: 'parent' });
|
|
529
|
-
const child = await queue.enqueue({ foo: 'child' }, { parentId: parent.id });
|
|
530
|
-
// Parent is in Waiting (simulated by manual acknowledge)
|
|
531
|
-
const pDequeued = await queue.dequeue();
|
|
532
|
-
await queue.acknowledge(pDequeued);
|
|
533
|
-
// Complete child
|
|
534
|
-
const cDequeued = await queue.dequeue();
|
|
535
|
-
await queue.acknowledge(cDequeued);
|
|
536
|
-
const pStatus = await queue.get(parent.id);
|
|
537
|
-
expect(pStatus?.status).toBe(TaskState.Pending); // Woken up
|
|
538
|
-
});
|
|
539
|
-
});
|
|
540
|
-
describe('Timeouts and Maintenance (Pruning)', () => {
|
|
541
|
-
it('should recover "Zombie" tasks (crashed workers)', async () => {
|
|
542
|
-
const task = await queue.enqueue({ foo: 'zombie' });
|
|
543
|
-
await queue.dequeue(); // Task is now Running with a lease
|
|
544
|
-
// processTimeout is 200ms. Wait for it to expire.
|
|
545
|
-
await timeout(300);
|
|
546
|
-
const recovered = await queue.dequeue();
|
|
547
|
-
expect(recovered?.id).toBe(task.id);
|
|
548
|
-
expect(recovered?.tries).toBe(2);
|
|
549
|
-
});
|
|
550
|
-
it('should fail tasks that exceed Hard Execution Timeout via prune', async () => {
|
|
551
|
-
// Re-configure queue with very short execution timeout
|
|
552
|
-
const queueProvider = injector.resolve(QueueProvider);
|
|
553
|
-
const shortQueue = queueProvider.get(`prune-test-${Date.now()}`, { executionTimeout: 100 });
|
|
554
|
-
const task = await shortQueue.enqueue({ foo: 'long-running' });
|
|
555
|
-
await shortQueue.dequeue();
|
|
556
|
-
await timeout(200);
|
|
557
|
-
await shortQueue.prune();
|
|
558
|
-
const updated = await shortQueue.get(task.id);
|
|
559
|
-
expect(updated?.status).toBe(TaskState.Dead);
|
|
560
|
-
expect(updated?.error?.message).toContain('Hard Execution Timeout');
|
|
561
|
-
await shortQueue.clear();
|
|
562
|
-
});
|
|
563
|
-
it('should touch a task to extend lease', async () => {
|
|
564
|
-
const task = await queue.enqueue({ foo: 'work' });
|
|
565
|
-
const dequeued = await queue.dequeue();
|
|
566
|
-
const initialLock = dequeued.lockExpirationTimestamp;
|
|
567
|
-
await timeout(50);
|
|
568
|
-
const touched = await queue.touch(dequeued);
|
|
569
|
-
expect(touched?.lockExpirationTimestamp).toBeGreaterThan(initialLock);
|
|
570
|
-
});
|
|
571
|
-
it('should prevent touching if lease is lost (stolen by another worker)', async () => {
|
|
572
|
-
await queue.enqueue({ foo: 'work' });
|
|
573
|
-
const dequeued = await queue.dequeue();
|
|
574
|
-
expect(dequeued).toBeDefined();
|
|
575
|
-
// processTimeout is 200ms. Wait for it to expire.
|
|
576
|
-
await timeout(300);
|
|
577
|
-
await queue.dequeue(); // Stolen by another worker (tries=2)
|
|
578
|
-
// Original worker tries to touch
|
|
579
|
-
const touchResult = await queue.touch(dequeued);
|
|
580
|
-
expect(touchResult).toBeUndefined();
|
|
581
|
-
});
|
|
582
|
-
});
|
|
583
|
-
describe('Batch Operations', () => {
|
|
584
|
-
it('should acknowledge many tasks efficiently', async () => {
|
|
585
|
-
const tasks = await queue.enqueueMany([
|
|
586
|
-
{ data: { foo: '1' } },
|
|
587
|
-
{ data: { foo: '2' } },
|
|
588
|
-
], { returnTasks: true });
|
|
589
|
-
const d1 = await queue.dequeue();
|
|
590
|
-
const d2 = await queue.dequeue();
|
|
591
|
-
await queue.acknowledgeMany([d1, d2]);
|
|
592
|
-
const t1 = await queue.get(tasks[0].id);
|
|
593
|
-
const t2 = await queue.get(tasks[1].id);
|
|
594
|
-
expect(t1?.status).toBe(TaskState.Completed);
|
|
595
|
-
expect(t2?.status).toBe(TaskState.Completed);
|
|
596
|
-
});
|
|
597
|
-
});
|
|
598
|
-
describe('Rescheduling', () => {
|
|
599
|
-
it('should reschedule and refund tries if running', async () => {
|
|
600
|
-
const task = await queue.enqueue({ foo: 'reschedule-me' });
|
|
601
|
-
const dequeued = await queue.dequeue();
|
|
602
|
-
expect(dequeued?.tries).toBe(1);
|
|
603
|
-
const inFuture = currentTimestamp() + 1000;
|
|
604
|
-
await queue.reschedule(dequeued.id, inFuture);
|
|
605
|
-
const updated = await queue.get(task.id);
|
|
606
|
-
expect(updated?.status).toBe(TaskState.Pending);
|
|
607
|
-
expect(updated?.tries).toBe(0); // Refunded
|
|
608
|
-
expect(updated?.scheduleTimestamp).toBe(inFuture);
|
|
609
|
-
});
|
|
610
|
-
});
|
|
611
|
-
describe('TaskContext (Worker DX)', () => {
|
|
612
|
-
it('checkpoint() should update progress and handle lease loss', async () => {
|
|
613
|
-
const task = await queue.enqueue({ foo: 'progress' });
|
|
614
|
-
const dequeued = await queue.dequeue();
|
|
615
|
-
// In real scenarios TaskContext wraps the queue logic.
|
|
616
|
-
// Here we just verify touch/checkpoint effects on the DB.
|
|
617
|
-
await queue.touch(dequeued, { progress: 0.5, state: { step: 1 } });
|
|
618
|
-
const updated = await queue.get(task.id);
|
|
619
|
-
expect(updated?.progress).toBe(0.5);
|
|
620
|
-
expect(updated?.state).toEqual({ step: 1 });
|
|
621
|
-
});
|
|
622
|
-
});
|
|
623
|
-
});
|
package/test3.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import './polyfills.js';
|
package/test3.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import './polyfills.js';
|
|
2
|
-
import { Application } from './application/application.js';
|
|
3
|
-
import { provideInitializer, provideModule, provideSignalHandler } from './application/providers.js';
|
|
4
|
-
import { inject } from './injector/inject.js';
|
|
5
|
-
import { configureLocalMessageBus } from './message-bus/index.js';
|
|
6
|
-
import { configureOrm } from './orm/server/index.js';
|
|
7
|
-
import { configurePostgresQueue, migratePostgresQueueSchema } from './queue/postgres/module.js';
|
|
8
|
-
import { Queue, UniqueTagStrategy } from './queue/queue.js';
|
|
9
|
-
import * as configParser from './utils/config-parser.js';
|
|
10
|
-
if (1 + 1 == 2)
|
|
11
|
-
process.exit();
|
|
12
|
-
const config = {
|
|
13
|
-
database: {
|
|
14
|
-
host: configParser.string('DATABASE_HOST', '127.0.0.1'),
|
|
15
|
-
port: configParser.positiveInteger('DATABASE_PORT', 5432),
|
|
16
|
-
user: configParser.string('DATABASE_USER', 'vitrass'),
|
|
17
|
-
pass: configParser.string('DATABASE_PASS', '6zv7edvqv9vvzz6u4kuk'),
|
|
18
|
-
database: configParser.string('DATABASE_NAME', 'vitrass'),
|
|
19
|
-
schema: configParser.string('DATABASE_SCHEMA', 'vitrass')
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
async function bootstrap() {
|
|
23
|
-
configureLocalMessageBus();
|
|
24
|
-
configureOrm({
|
|
25
|
-
connection: {
|
|
26
|
-
host: config.database.host,
|
|
27
|
-
port: config.database.port,
|
|
28
|
-
user: config.database.user,
|
|
29
|
-
password: config.database.pass,
|
|
30
|
-
database: config.database.database
|
|
31
|
-
},
|
|
32
|
-
repositoryConfig: { schema: 'vitrass' }
|
|
33
|
-
});
|
|
34
|
-
configurePostgresQueue({});
|
|
35
|
-
await migratePostgresQueueSchema();
|
|
36
|
-
}
|
|
37
|
-
async function main(_cancellationSignal) {
|
|
38
|
-
const queue = inject((Queue), 'echos');
|
|
39
|
-
await queue.enqueue({ name: 'Max 1' }, { tag: 'foo', uniqueTag: UniqueTagStrategy.TakeNew });
|
|
40
|
-
await queue.enqueue({ name: 'Max 2' }, { tag: 'foo', uniqueTag: UniqueTagStrategy.TakeNew });
|
|
41
|
-
await queue.enqueue({ name: 'Max 3' }, { tag: 'foo', uniqueTag: UniqueTagStrategy.TakeNew });
|
|
42
|
-
}
|
|
43
|
-
Application.run('Test', [
|
|
44
|
-
provideInitializer(bootstrap),
|
|
45
|
-
provideModule(main),
|
|
46
|
-
provideSignalHandler(),
|
|
47
|
-
]);
|
|
File without changes
|
|
File without changes
|