@nestjs/workflows 0.0.1
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/LICENSE +23 -0
- package/README.md +50 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +8 -0
- package/dist/core/codecs/aes-gcm-payload.codec.d.ts +42 -0
- package/dist/core/codecs/aes-gcm-payload.codec.js +119 -0
- package/dist/core/codecs/index.d.ts +2 -0
- package/dist/core/codecs/index.js +2 -0
- package/dist/core/codecs/payload-codecs.d.ts +59 -0
- package/dist/core/codecs/payload-codecs.js +130 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/index.js +21 -0
- package/dist/core/interfaces/clock.interface.d.ts +18 -0
- package/dist/core/interfaces/clock.interface.js +1 -0
- package/dist/core/interfaces/index.d.ts +9 -0
- package/dist/core/interfaces/index.js +9 -0
- package/dist/core/interfaces/leased-worker-options.interface.d.ts +94 -0
- package/dist/core/interfaces/leased-worker-options.interface.js +1 -0
- package/dist/core/interfaces/limits.interface.d.ts +92 -0
- package/dist/core/interfaces/limits.interface.js +1 -0
- package/dist/core/interfaces/payload-codec.interface.d.ts +64 -0
- package/dist/core/interfaces/payload-codec.interface.js +1 -0
- package/dist/core/interfaces/result-waiter-options.interface.d.ts +52 -0
- package/dist/core/interfaces/result-waiter-options.interface.js +1 -0
- package/dist/core/interfaces/retry-settings.interface.d.ts +71 -0
- package/dist/core/interfaces/retry-settings.interface.js +1 -0
- package/dist/core/interfaces/schedule-store.interface.d.ts +143 -0
- package/dist/core/interfaces/schedule-store.interface.js +1 -0
- package/dist/core/interfaces/schedule.interface.d.ts +106 -0
- package/dist/core/interfaces/schedule.interface.js +1 -0
- package/dist/core/interfaces/scheduler-options.interface.d.ts +152 -0
- package/dist/core/interfaces/scheduler-options.interface.js +1 -0
- package/dist/core/limits/index.d.ts +1 -0
- package/dist/core/limits/index.js +1 -0
- package/dist/core/limits/limits.d.ts +61 -0
- package/dist/core/limits/limits.js +130 -0
- package/dist/core/results/index.d.ts +1 -0
- package/dist/core/results/index.js +1 -0
- package/dist/core/results/result-waiter.d.ts +54 -0
- package/dist/core/results/result-waiter.js +140 -0
- package/dist/core/retries/index.d.ts +1 -0
- package/dist/core/retries/index.js +1 -0
- package/dist/core/retries/retry.d.ts +60 -0
- package/dist/core/retries/retry.js +107 -0
- package/dist/core/scheduling/cron.util.d.ts +52 -0
- package/dist/core/scheduling/cron.util.js +262 -0
- package/dist/core/scheduling/encoded-schedule.store.d.ts +26 -0
- package/dist/core/scheduling/encoded-schedule.store.js +54 -0
- package/dist/core/scheduling/in-memory-schedule.store.d.ts +20 -0
- package/dist/core/scheduling/in-memory-schedule.store.js +90 -0
- package/dist/core/scheduling/index.d.ts +3 -0
- package/dist/core/scheduling/index.js +3 -0
- package/dist/core/scheduling/rrule.util.d.ts +55 -0
- package/dist/core/scheduling/rrule.util.js +371 -0
- package/dist/core/scheduling/schedule-spec.d.ts +57 -0
- package/dist/core/scheduling/schedule-spec.js +233 -0
- package/dist/core/scheduling/scheduler.d.ts +134 -0
- package/dist/core/scheduling/scheduler.js +450 -0
- package/dist/core/scheduling/time-zone.util.d.ts +18 -0
- package/dist/core/scheduling/time-zone.util.js +88 -0
- package/dist/core/time/clock.d.ts +28 -0
- package/dist/core/time/clock.js +35 -0
- package/dist/core/time/duration.d.ts +21 -0
- package/dist/core/time/duration.js +30 -0
- package/dist/core/time/index.d.ts +2 -0
- package/dist/core/time/index.js +2 -0
- package/dist/core/utils/canonical.util.d.ts +2 -0
- package/dist/core/utils/canonical.util.js +4 -0
- package/dist/core/utils/maybe.util.d.ts +8 -0
- package/dist/core/utils/maybe.util.js +14 -0
- package/dist/core/utils/payload-place.util.d.ts +10 -0
- package/dist/core/utils/payload-place.util.js +32 -0
- package/dist/core/workers/index.d.ts +1 -0
- package/dist/core/workers/index.js +1 -0
- package/dist/core/workers/leased-worker.d.ts +86 -0
- package/dist/core/workers/leased-worker.js +280 -0
- package/dist/cqrs/decorators/index.d.ts +2 -0
- package/dist/cqrs/decorators/index.js +2 -0
- package/dist/cqrs/decorators/signal-on.decorator.d.ts +18 -0
- package/dist/cqrs/decorators/signal-on.decorator.js +34 -0
- package/dist/cqrs/decorators/start-on.decorator.d.ts +18 -0
- package/dist/cqrs/decorators/start-on.decorator.js +49 -0
- package/dist/cqrs/index.d.ts +3 -0
- package/dist/cqrs/index.js +5 -0
- package/dist/cqrs/interfaces/index.d.ts +4 -0
- package/dist/cqrs/interfaces/index.js +4 -0
- package/dist/cqrs/interfaces/signal-on-options.interface.d.ts +22 -0
- package/dist/cqrs/interfaces/signal-on-options.interface.js +1 -0
- package/dist/cqrs/interfaces/start-on-options.interface.d.ts +23 -0
- package/dist/cqrs/interfaces/start-on-options.interface.js +1 -0
- package/dist/cqrs/interfaces/workflow-dispatcher-context.interface.d.ts +10 -0
- package/dist/cqrs/interfaces/workflow-dispatcher-context.interface.js +1 -0
- package/dist/cqrs/interfaces/workflow-event-route.interface.d.ts +20 -0
- package/dist/cqrs/interfaces/workflow-event-route.interface.js +1 -0
- package/dist/cqrs/utils/event-routes.util.d.ts +7 -0
- package/dist/cqrs/utils/event-routes.util.js +22 -0
- package/dist/cqrs/workflow-event.publisher.d.ts +52 -0
- package/dist/cqrs/workflow-event.publisher.js +257 -0
- package/dist/cqrs/workflow-events.explorer.d.ts +27 -0
- package/dist/cqrs/workflow-events.explorer.js +92 -0
- package/dist/cqrs/workflows-cqrs.module.d.ts +10 -0
- package/dist/cqrs/workflows-cqrs.module.js +27 -0
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.js +1 -0
- package/dist/decorators/workflow.decorator.d.ts +7 -0
- package/dist/decorators/workflow.decorator.js +52 -0
- package/dist/errors/child-workflow-failed.error.d.ts +13 -0
- package/dist/errors/child-workflow-failed.error.js +14 -0
- package/dist/errors/index.d.ts +13 -0
- package/dist/errors/index.js +13 -0
- package/dist/errors/non-retryable-step.error.d.ts +4 -0
- package/dist/errors/non-retryable-step.error.js +7 -0
- package/dist/errors/step-failed.error.d.ts +15 -0
- package/dist/errors/step-failed.error.js +16 -0
- package/dist/errors/step-timeout.error.d.ts +5 -0
- package/dist/errors/step-timeout.error.js +8 -0
- package/dist/errors/workflow-definition.error.d.ts +10 -0
- package/dist/errors/workflow-definition.error.js +13 -0
- package/dist/errors/workflow-failed.error.d.ts +20 -0
- package/dist/errors/workflow-failed.error.js +17 -0
- package/dist/errors/workflow-id-conflict.error.d.ts +6 -0
- package/dist/errors/workflow-id-conflict.error.js +9 -0
- package/dist/errors/workflow-interrupt.error.d.ts +32 -0
- package/dist/errors/workflow-interrupt.error.js +19 -0
- package/dist/errors/workflow-non-determinism.error.d.ts +4 -0
- package/dist/errors/workflow-non-determinism.error.js +7 -0
- package/dist/errors/workflow-not-found.error.d.ts +10 -0
- package/dist/errors/workflow-not-found.error.js +13 -0
- package/dist/errors/workflow-result-timeout.error.d.ts +11 -0
- package/dist/errors/workflow-result-timeout.error.js +13 -0
- package/dist/errors/workflow-state.error.d.ts +11 -0
- package/dist/errors/workflow-state.error.js +14 -0
- package/dist/errors/workflow.error.d.ts +7 -0
- package/dist/errors/workflow.error.js +7 -0
- package/dist/events/index.d.ts +3 -0
- package/dist/events/index.js +3 -0
- package/dist/events/workflow-events.interface.d.ts +145 -0
- package/dist/events/workflow-events.interface.js +1 -0
- package/dist/events/workflow-events.service.d.ts +17 -0
- package/dist/events/workflow-events.service.js +38 -0
- package/dist/events/workflows.channels.d.ts +3 -0
- package/dist/events/workflows.channels.js +9 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +23 -0
- package/dist/interfaces/index.d.ts +13 -0
- package/dist/interfaces/index.js +13 -0
- package/dist/interfaces/serialized-workflow-error.interface.d.ts +6 -0
- package/dist/interfaces/serialized-workflow-error.interface.js +1 -0
- package/dist/interfaces/workflow-client.interface.d.ts +156 -0
- package/dist/interfaces/workflow-client.interface.js +1 -0
- package/dist/interfaces/workflow-clock.interface.d.ts +7 -0
- package/dist/interfaces/workflow-clock.interface.js +1 -0
- package/dist/interfaces/workflow-context.interface.d.ts +241 -0
- package/dist/interfaces/workflow-context.interface.js +1 -0
- package/dist/interfaces/workflow-decorator-options.interface.d.ts +101 -0
- package/dist/interfaces/workflow-decorator-options.interface.js +1 -0
- package/dist/interfaces/workflow-instance.interface.d.ts +83 -0
- package/dist/interfaces/workflow-instance.interface.js +1 -0
- package/dist/interfaces/workflow-payload-codec.interface.d.ts +32 -0
- package/dist/interfaces/workflow-payload-codec.interface.js +1 -0
- package/dist/interfaces/workflow-retry-options.interface.d.ts +9 -0
- package/dist/interfaces/workflow-retry-options.interface.js +1 -0
- package/dist/interfaces/workflow-runner.interface.d.ts +11 -0
- package/dist/interfaces/workflow-runner.interface.js +1 -0
- package/dist/interfaces/workflow-schedule.interface.d.ts +104 -0
- package/dist/interfaces/workflow-schedule.interface.js +1 -0
- package/dist/interfaces/workflow-storage.interface.d.ts +4 -0
- package/dist/interfaces/workflow-storage.interface.js +1 -0
- package/dist/interfaces/workflow-store.interface.d.ts +488 -0
- package/dist/interfaces/workflow-store.interface.js +1 -0
- package/dist/interfaces/workflows-module-options.interface.d.ts +80 -0
- package/dist/interfaces/workflows-module-options.interface.js +1 -0
- package/dist/mysql/index.d.ts +5 -0
- package/dist/mysql/index.js +8 -0
- package/dist/mysql/interfaces/index.d.ts +1 -0
- package/dist/mysql/interfaces/index.js +1 -0
- package/dist/mysql/interfaces/mysql-workflow-store-options.interface.d.ts +25 -0
- package/dist/mysql/interfaces/mysql-workflow-store-options.interface.js +1 -0
- package/dist/mysql/key-limits.d.ts +30 -0
- package/dist/mysql/key-limits.js +41 -0
- package/dist/mysql/migrations/index.d.ts +7 -0
- package/dist/mysql/migrations/index.js +16 -0
- package/dist/mysql/migrations/initial.migration.d.ts +9 -0
- package/dist/mysql/migrations/initial.migration.js +104 -0
- package/dist/mysql/mysql-workflow.store.d.ts +197 -0
- package/dist/mysql/mysql-workflow.store.js +828 -0
- package/dist/postgres/errors/index.d.ts +1 -0
- package/dist/postgres/errors/index.js +2 -0
- package/dist/postgres/index.d.ts +5 -0
- package/dist/postgres/index.js +8 -0
- package/dist/postgres/interfaces/index.d.ts +1 -0
- package/dist/postgres/interfaces/index.js +1 -0
- package/dist/postgres/interfaces/postgres-workflow-store-options.interface.d.ts +22 -0
- package/dist/postgres/interfaces/postgres-workflow-store-options.interface.js +1 -0
- package/dist/postgres/migrations/index.d.ts +6 -0
- package/dist/postgres/migrations/index.js +15 -0
- package/dist/postgres/migrations/initial.migration.d.ts +6 -0
- package/dist/postgres/migrations/initial.migration.js +106 -0
- package/dist/postgres/postgres-workflow.store.d.ts +132 -0
- package/dist/postgres/postgres-workflow.store.js +586 -0
- package/dist/services/index.d.ts +6 -0
- package/dist/services/index.js +6 -0
- package/dist/services/workflow-client.service.d.ts +146 -0
- package/dist/services/workflow-client.service.js +436 -0
- package/dist/services/workflow-execution.service.d.ts +267 -0
- package/dist/services/workflow-execution.service.js +1076 -0
- package/dist/services/workflow-registry.service.d.ts +56 -0
- package/dist/services/workflow-registry.service.js +172 -0
- package/dist/services/workflow-scheduler.service.d.ts +54 -0
- package/dist/services/workflow-scheduler.service.js +173 -0
- package/dist/services/workflow-schedules.service.d.ts +60 -0
- package/dist/services/workflow-schedules.service.js +193 -0
- package/dist/services/workflow-worker.service.d.ts +92 -0
- package/dist/services/workflow-worker.service.js +468 -0
- package/dist/signals/index.d.ts +1 -0
- package/dist/signals/index.js +1 -0
- package/dist/signals/workflow.signal.d.ts +17 -0
- package/dist/signals/workflow.signal.js +28 -0
- package/dist/sql/rate-windows.d.ts +41 -0
- package/dist/sql/rate-windows.js +53 -0
- package/dist/sql/workflow-rows.d.ts +16 -0
- package/dist/sql/workflow-rows.js +102 -0
- package/dist/sql/workflow-schema.error.d.ts +21 -0
- package/dist/sql/workflow-schema.error.js +15 -0
- package/dist/storage/encoded-workflow.store.d.ts +71 -0
- package/dist/storage/encoded-workflow.store.js +201 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/workflow.storage.d.ts +55 -0
- package/dist/storage/workflow.storage.js +159 -0
- package/dist/stores/in-memory-workflow.store.d.ts +75 -0
- package/dist/stores/in-memory-workflow.store.js +380 -0
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.js +1 -0
- package/dist/testing/assertions.d.ts +10 -0
- package/dist/testing/assertions.js +72 -0
- package/dist/testing/index.d.ts +38 -0
- package/dist/testing/index.js +1089 -0
- package/dist/testing/schedule-store.contract.d.ts +33 -0
- package/dist/testing/schedule-store.contract.js +161 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/journal-limits.util.d.ts +6 -0
- package/dist/utils/journal-limits.util.js +25 -0
- package/dist/utils/new-instance.util.d.ts +26 -0
- package/dist/utils/new-instance.util.js +87 -0
- package/dist/utils/normalize.util.d.ts +5 -0
- package/dist/utils/normalize.util.js +11 -0
- package/dist/utils/run-timeout.util.d.ts +3 -0
- package/dist/utils/run-timeout.util.js +15 -0
- package/dist/utils/schedule-spec.util.d.ts +16 -0
- package/dist/utils/schedule-spec.util.js +9 -0
- package/dist/utils/schedule-store.util.d.ts +15 -0
- package/dist/utils/schedule-store.util.js +44 -0
- package/dist/utils/serialize-error.util.d.ts +7 -0
- package/dist/utils/serialize-error.util.js +22 -0
- package/dist/utils/step-scope.util.d.ts +16 -0
- package/dist/utils/step-scope.util.js +38 -0
- package/dist/workflows.constants.d.ts +10 -0
- package/dist/workflows.constants.js +10 -0
- package/dist/workflows.module-definition.d.ts +4 -0
- package/dist/workflows.module-definition.js +6 -0
- package/dist/workflows.module.d.ts +24 -0
- package/dist/workflows.module.js +68 -0
- package/package.json +123 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// What the first-party SQL stores share, free of either dialect: the columns they read, and how a row they read (every
|
|
2
|
+
// column cast to text) becomes an instance or a schedule. PostgresWorkflowStore and MySqlWorkflowStore name the same
|
|
3
|
+
// columns, and the kit's readers (`toText()`, `toInt()`, `toBool()`, `toJson()`) read both dialects' text.
|
|
4
|
+
import { toBool, toInt, toJson } from '@nestjs/store-kit';
|
|
5
|
+
/** The statuses a claim takes: an instance that hasn't finished. */
|
|
6
|
+
export const RUNNABLE_STATUSES = ['pending', 'running', 'suspended', 'compensating'];
|
|
7
|
+
/** The statuses a cancel applies to (a terminate: `RUNNABLE_STATUSES`). */
|
|
8
|
+
export const CANCELLABLE_STATUSES = ['pending', 'running', 'suspended'];
|
|
9
|
+
/** An instance's columns, as `toInstance()` reads them. */
|
|
10
|
+
export const INSTANCE_COLUMNS = [
|
|
11
|
+
'id',
|
|
12
|
+
'workflow',
|
|
13
|
+
'version',
|
|
14
|
+
'parent_id',
|
|
15
|
+
'parent_close',
|
|
16
|
+
'concurrency_key',
|
|
17
|
+
'rate_limit_key',
|
|
18
|
+
'priority',
|
|
19
|
+
'schedule_id',
|
|
20
|
+
'scheduled_at',
|
|
21
|
+
'status',
|
|
22
|
+
'input',
|
|
23
|
+
'output',
|
|
24
|
+
'error',
|
|
25
|
+
'wake_at',
|
|
26
|
+
'lease_owner',
|
|
27
|
+
'lease_until',
|
|
28
|
+
'cancel_requested',
|
|
29
|
+
'terminate_requested',
|
|
30
|
+
'cancel_reason',
|
|
31
|
+
'deadline',
|
|
32
|
+
'custom_status',
|
|
33
|
+
'signal_cursor',
|
|
34
|
+
'runs',
|
|
35
|
+
'created_at',
|
|
36
|
+
'updated_at',
|
|
37
|
+
];
|
|
38
|
+
/** A schedule's columns, as `toSchedule()` reads them. */
|
|
39
|
+
export const SCHEDULE_COLUMNS = [
|
|
40
|
+
'id',
|
|
41
|
+
'workflow',
|
|
42
|
+
'declared',
|
|
43
|
+
'spec',
|
|
44
|
+
'input',
|
|
45
|
+
'paused',
|
|
46
|
+
'wake_at',
|
|
47
|
+
'state',
|
|
48
|
+
'revision',
|
|
49
|
+
'lease_owner',
|
|
50
|
+
'lease_until',
|
|
51
|
+
'created_at',
|
|
52
|
+
'updated_at',
|
|
53
|
+
];
|
|
54
|
+
/** The columns of a signal as `WorkflowStore.signals()` returns it. */
|
|
55
|
+
export const SIGNAL_COLUMNS = ['id', 'name', 'key', 'payload', 'created_at'];
|
|
56
|
+
export function toInstance(row) {
|
|
57
|
+
return {
|
|
58
|
+
id: row.id,
|
|
59
|
+
workflow: row.workflow,
|
|
60
|
+
version: toInt(row.version),
|
|
61
|
+
parentId: row.parent_id,
|
|
62
|
+
parentClose: row.parent_close,
|
|
63
|
+
concurrencyKey: row.concurrency_key,
|
|
64
|
+
rateLimitKey: row.rate_limit_key,
|
|
65
|
+
priority: toInt(row.priority),
|
|
66
|
+
scheduleId: row.schedule_id,
|
|
67
|
+
scheduledAt: toInt(row.scheduled_at),
|
|
68
|
+
status: row.status,
|
|
69
|
+
input: toJson(row.input),
|
|
70
|
+
output: toJson(row.output),
|
|
71
|
+
error: toJson(row.error),
|
|
72
|
+
wakeAt: toInt(row.wake_at),
|
|
73
|
+
leaseOwner: row.lease_owner,
|
|
74
|
+
leaseUntil: toInt(row.lease_until),
|
|
75
|
+
cancelRequested: toBool(row.cancel_requested),
|
|
76
|
+
terminateRequested: toBool(row.terminate_requested),
|
|
77
|
+
cancelReason: row.cancel_reason,
|
|
78
|
+
deadline: toInt(row.deadline),
|
|
79
|
+
customStatus: toJson(row.custom_status),
|
|
80
|
+
signalCursor: toInt(row.signal_cursor),
|
|
81
|
+
runs: toInt(row.runs),
|
|
82
|
+
createdAt: toInt(row.created_at),
|
|
83
|
+
updatedAt: toInt(row.updated_at),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export function toSchedule(row) {
|
|
87
|
+
return {
|
|
88
|
+
id: row.id,
|
|
89
|
+
workflow: row.workflow,
|
|
90
|
+
declared: toBool(row.declared),
|
|
91
|
+
spec: toJson(row.spec),
|
|
92
|
+
input: toJson(row.input),
|
|
93
|
+
paused: toBool(row.paused),
|
|
94
|
+
wakeAt: toInt(row.wake_at),
|
|
95
|
+
state: toJson(row.state),
|
|
96
|
+
revision: toInt(row.revision),
|
|
97
|
+
leaseOwner: row.lease_owner,
|
|
98
|
+
leaseUntil: toInt(row.lease_until),
|
|
99
|
+
createdAt: toInt(row.created_at),
|
|
100
|
+
updatedAt: toInt(row.updated_at),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { WorkflowError } from '../errors/workflow.error.js';
|
|
2
|
+
/**
|
|
3
|
+
* A first-party SQL store's schema can't serve this version of the package (`PostgresWorkflowStore`,
|
|
4
|
+
* `MySqlWorkflowStore`): it is behind the store's migrations (and `migrate` is off), or applying them failed (`cause`).
|
|
5
|
+
* The store refuses every call until it's fixed.
|
|
6
|
+
*/
|
|
7
|
+
export declare class WorkflowSchemaError extends WorkflowError {
|
|
8
|
+
name: string;
|
|
9
|
+
/** The store's schema: a PostgreSQL schema, or on MySQL the prefix of its tables' names. */
|
|
10
|
+
readonly schema: string;
|
|
11
|
+
/** The schema's version: the last migration applied to it, `0` for none. */
|
|
12
|
+
readonly version: number;
|
|
13
|
+
/** The version this version of the package needs: its last migration. */
|
|
14
|
+
readonly requiredVersion: number;
|
|
15
|
+
constructor(message: string, details: {
|
|
16
|
+
schema: string;
|
|
17
|
+
version: number;
|
|
18
|
+
requiredVersion: number;
|
|
19
|
+
cause?: unknown;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { WorkflowError } from '../errors/workflow.error.js';
|
|
2
|
+
/**
|
|
3
|
+
* A first-party SQL store's schema can't serve this version of the package (`PostgresWorkflowStore`,
|
|
4
|
+
* `MySqlWorkflowStore`): it is behind the store's migrations (and `migrate` is off), or applying them failed (`cause`).
|
|
5
|
+
* The store refuses every call until it's fixed.
|
|
6
|
+
*/
|
|
7
|
+
export class WorkflowSchemaError extends WorkflowError {
|
|
8
|
+
constructor(message, details) {
|
|
9
|
+
super(message, details.cause === undefined ? undefined : { cause: details.cause });
|
|
10
|
+
this.name = 'WorkflowSchemaError';
|
|
11
|
+
this.schema = details.schema;
|
|
12
|
+
this.version = details.version;
|
|
13
|
+
this.requiredVersion = details.requiredVersion;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { WorkflowPayloadContext } from '../interfaces/workflow-payload-codec.interface.js';
|
|
2
|
+
import { type PayloadCodecs } from '../core/codecs/payload-codecs.js';
|
|
3
|
+
import type { WorkflowScheduleMethods } from '../utils/schedule-store.util.js';
|
|
4
|
+
import type { WorkflowInstance, WorkflowStatus } from '../interfaces/workflow-instance.interface.js';
|
|
5
|
+
import type { NewWorkflowInstance, NewWorkflowSignal, WorkflowCancelRequest, WorkflowClaim, WorkflowClaimRequest, WorkflowInstanceDetails, WorkflowListQuery, WorkflowPurgeQuery, WorkflowPurgeResult, WorkflowReopen, WorkflowSignalQuery, WorkflowSignalRecord, WorkflowSignalResult, WorkflowStore, WorkflowWrite } from '../interfaces/workflow-store.interface.js';
|
|
6
|
+
/** @internal A `WorkflowStore` without its schedules, which the engine reaches through the core's `Scheduler`. */
|
|
7
|
+
export type WorkflowInstanceStore = Omit<WorkflowStore, WorkflowScheduleMethods>;
|
|
8
|
+
/**
|
|
9
|
+
* @internal The store as the engine sees it with a codec: payloads encoded on the way in, decoded on the way out, so
|
|
10
|
+
* the store sees only what the codec returns, and the engine only plain values. Ids, names, keys, statuses and
|
|
11
|
+
* times pass as they are: the store matches on them. Schedules aren't here: the `Scheduler` encodes their inputs.
|
|
12
|
+
*/
|
|
13
|
+
export declare class EncodedWorkflowStore implements WorkflowInstanceStore {
|
|
14
|
+
/** The store itself: what it holds, undecoded. */
|
|
15
|
+
readonly inner: WorkflowStore;
|
|
16
|
+
readonly codecs: PayloadCodecs<WorkflowPayloadContext>;
|
|
17
|
+
private static readonly logger;
|
|
18
|
+
private readonly unreadable;
|
|
19
|
+
readonly createInTransaction?: WorkflowStore['createInTransaction'];
|
|
20
|
+
readonly signalInTransaction?: WorkflowStore['signalInTransaction'];
|
|
21
|
+
constructor(
|
|
22
|
+
/** The store itself: what it holds, undecoded. */
|
|
23
|
+
inner: WorkflowStore, codecs: PayloadCodecs<WorkflowPayloadContext>);
|
|
24
|
+
/** Whether a codec encodes what is written. */
|
|
25
|
+
get encodes(): boolean;
|
|
26
|
+
create(instance: NewWorkflowInstance): Promise<{
|
|
27
|
+
instance: WorkflowInstance;
|
|
28
|
+
created: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
get(id: string, options?: {
|
|
31
|
+
journal?: boolean;
|
|
32
|
+
}): Promise<WorkflowInstanceDetails | null>;
|
|
33
|
+
list(query: WorkflowListQuery): Promise<WorkflowInstance[]>;
|
|
34
|
+
requestCancel(id: string, request: WorkflowCancelRequest): Promise<boolean>;
|
|
35
|
+
reopen(id: string, reopen: WorkflowReopen): Promise<boolean>;
|
|
36
|
+
delete(id: string, statuses: WorkflowStatus[]): Promise<boolean>;
|
|
37
|
+
signal(signal: NewWorkflowSignal): Promise<WorkflowSignalResult>;
|
|
38
|
+
signals(query: WorkflowSignalQuery): Promise<WorkflowSignalRecord[]>;
|
|
39
|
+
purge(query: WorkflowPurgeQuery): Promise<WorkflowPurgeResult>;
|
|
40
|
+
claim(request: WorkflowClaimRequest): Promise<WorkflowClaim>;
|
|
41
|
+
/**
|
|
42
|
+
* Hands a leased instance back untouched, with its waits, due when its lease would have ended: its concurrency slot
|
|
43
|
+
* is free at once, and the instances due before then go first, instead of this one taking the slot at every claim.
|
|
44
|
+
*/
|
|
45
|
+
handBack(id: string, token: string, lease: {
|
|
46
|
+
now: number;
|
|
47
|
+
leaseUntil: number;
|
|
48
|
+
signalCursor: number;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
renew(id: string, token: string, leaseUntil: number): Promise<{
|
|
51
|
+
cancelRequested: boolean;
|
|
52
|
+
terminateRequested: boolean;
|
|
53
|
+
} | null>;
|
|
54
|
+
write(id: string, token: string, write: WorkflowWrite): Promise<boolean>;
|
|
55
|
+
private encodeNew;
|
|
56
|
+
private encodeSignal;
|
|
57
|
+
private encodeEntries;
|
|
58
|
+
private decodeEntry;
|
|
59
|
+
/**
|
|
60
|
+
* An instance as stored, decoded; one no listed codec can decode any more, without its payloads (its error's name
|
|
61
|
+
* kept), logged once: a listing shows it instead of failing whole.
|
|
62
|
+
*/
|
|
63
|
+
readableInstance<T extends WorkflowInstance>(instance: T): Promise<T>;
|
|
64
|
+
private decodeInstance;
|
|
65
|
+
private instanceOf;
|
|
66
|
+
/**
|
|
67
|
+
* What a claim leased, decoded. One that can't be (a codec that is gone) is left out, logged, and handed back to
|
|
68
|
+
* be claimed again later, and read once the codec is back, while the others run.
|
|
69
|
+
*/
|
|
70
|
+
private readable;
|
|
71
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { Logger } from '@nestjs/common';
|
|
2
|
+
import { isEncodedPayload as encoded } from '../core/codecs/payload-codecs.js';
|
|
3
|
+
import { andThen, toPromise } from '../core/utils/maybe.util.js';
|
|
4
|
+
/**
|
|
5
|
+
* @internal The store as the engine sees it with a codec: payloads encoded on the way in, decoded on the way out, so
|
|
6
|
+
* the store sees only what the codec returns, and the engine only plain values. Ids, names, keys, statuses and
|
|
7
|
+
* times pass as they are: the store matches on them. Schedules aren't here: the `Scheduler` encodes their inputs.
|
|
8
|
+
*/
|
|
9
|
+
export class EncodedWorkflowStore {
|
|
10
|
+
constructor(
|
|
11
|
+
/** The store itself: what it holds, undecoded. */
|
|
12
|
+
inner, codecs) {
|
|
13
|
+
this.inner = inner;
|
|
14
|
+
this.codecs = codecs;
|
|
15
|
+
this.unreadable = new Set();
|
|
16
|
+
// Only when the store has them: the client tells a store that can't join transactions by their absence.
|
|
17
|
+
// Nothing is awaited before the store's call when the codec is synchronous (see WorkflowClient.start()).
|
|
18
|
+
if (typeof inner.createInTransaction === 'function') {
|
|
19
|
+
this.createInTransaction = (transaction, instance) => this.instanceOf(andThen(this.encodeNew(instance), (encoded) => inner.createInTransaction(transaction, encoded)));
|
|
20
|
+
}
|
|
21
|
+
if (typeof inner.signalInTransaction === 'function') {
|
|
22
|
+
this.signalInTransaction = (transaction, signal) => toPromise(andThen(this.encodeSignal(signal), (encoded) => inner.signalInTransaction(transaction, encoded)));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Whether a codec encodes what is written. */
|
|
26
|
+
get encodes() {
|
|
27
|
+
return this.codecs.encodes;
|
|
28
|
+
}
|
|
29
|
+
create(instance) {
|
|
30
|
+
return this.instanceOf(andThen(this.encodeNew(instance), (encoded) => this.inner.create(encoded)));
|
|
31
|
+
}
|
|
32
|
+
async get(id, options) {
|
|
33
|
+
const details = await this.inner.get(id, options);
|
|
34
|
+
if (!details) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const decoded = await this.decodeInstance(details);
|
|
38
|
+
if (details.journal?.some(entryEncoded)) {
|
|
39
|
+
decoded.journal = await Promise.all(details.journal.map((entry) => this.decodeEntry(entry, id)));
|
|
40
|
+
}
|
|
41
|
+
return decoded;
|
|
42
|
+
}
|
|
43
|
+
async list(query) {
|
|
44
|
+
return Promise.all((await this.inner.list(query)).map((instance) => this.decodeInstance(instance)));
|
|
45
|
+
}
|
|
46
|
+
async requestCancel(id, request) {
|
|
47
|
+
const reason = (await this.codecs.encode(request.reason, { field: 'cancelReason', instanceId: id }));
|
|
48
|
+
return this.inner.requestCancel(id, { ...request, reason });
|
|
49
|
+
}
|
|
50
|
+
async reopen(id, reopen) {
|
|
51
|
+
const [error, entries] = await Promise.all([this.codecs.encodeError(reopen.error, { field: 'error', instanceId: id }), this.encodeEntries(reopen.entries, id)]);
|
|
52
|
+
return this.inner.reopen(id, { ...reopen, error, entries });
|
|
53
|
+
}
|
|
54
|
+
delete(id, statuses) {
|
|
55
|
+
return this.inner.delete(id, statuses);
|
|
56
|
+
}
|
|
57
|
+
signal(signal) {
|
|
58
|
+
return toPromise(andThen(this.encodeSignal(signal), (encoded) => this.inner.signal(encoded)));
|
|
59
|
+
}
|
|
60
|
+
async signals(query) {
|
|
61
|
+
const records = await this.inner.signals(query);
|
|
62
|
+
return Promise.all(records.map(async (record) => (encoded(record.payload) ? { ...record, payload: await this.codecs.decode(record.payload, { field: 'payload', signal: record.name }) } : record)));
|
|
63
|
+
}
|
|
64
|
+
purge(query) {
|
|
65
|
+
return this.inner.purge(query);
|
|
66
|
+
}
|
|
67
|
+
async claim(request) {
|
|
68
|
+
const claim = await this.inner.claim(request);
|
|
69
|
+
const instances = await this.readable(claim.instances, (instance) => this.decodeInstance(instance), 'Instance', (instance) => this.handBack(instance.id, request.token, { now: request.now, leaseUntil: request.leaseUntil, signalCursor: claim.lastSignalId }));
|
|
70
|
+
return { ...claim, instances };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Hands a leased instance back untouched, with its waits, due when its lease would have ended: its concurrency slot
|
|
74
|
+
* is free at once, and the instances due before then go first, instead of this one taking the slot at every claim.
|
|
75
|
+
*/
|
|
76
|
+
async handBack(id, token, lease) {
|
|
77
|
+
const waits = (await this.inner.get(id))?.waits ?? [];
|
|
78
|
+
await this.inner.write(id, token, {
|
|
79
|
+
now: lease.now,
|
|
80
|
+
entries: [],
|
|
81
|
+
release: { wakeAt: lease.leaseUntil, waits, signalCursor: lease.signalCursor },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
renew(id, token, leaseUntil) {
|
|
85
|
+
return this.inner.renew(id, token, leaseUntil);
|
|
86
|
+
}
|
|
87
|
+
async write(id, token, write) {
|
|
88
|
+
const context = (field) => ({ field, instanceId: id });
|
|
89
|
+
const [entries, output, error, customStatus, signal] = await Promise.all([
|
|
90
|
+
this.encodeEntries(write.entries, id),
|
|
91
|
+
this.codecs.encode(write.output, context('output')),
|
|
92
|
+
this.codecs.encodeError(write.error, context('error')),
|
|
93
|
+
this.codecs.encode(write.customStatus, context('customStatus')),
|
|
94
|
+
write.signal ? this.encodeSignal(write.signal) : undefined,
|
|
95
|
+
]);
|
|
96
|
+
return this.inner.write(id, token, {
|
|
97
|
+
...write,
|
|
98
|
+
entries,
|
|
99
|
+
...(write.output === undefined ? {} : { output }),
|
|
100
|
+
...(write.error === undefined ? {} : { error }),
|
|
101
|
+
...(write.customStatus === undefined ? {} : { customStatus }),
|
|
102
|
+
...(signal ? { signal } : {}),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// ---------------------------------------------------------------- payloads
|
|
106
|
+
encodeNew(instance) {
|
|
107
|
+
return andThen(this.codecs.encode(instance.input, { field: 'input', instanceId: instance.id }), (input) => ({ ...instance, input }));
|
|
108
|
+
}
|
|
109
|
+
encodeSignal(signal) {
|
|
110
|
+
return andThen(this.codecs.encode(signal.payload, { field: 'payload', signal: signal.name }), (payload) => ({ ...signal, payload }));
|
|
111
|
+
}
|
|
112
|
+
encodeEntries(entries, instanceId) {
|
|
113
|
+
return Promise.all(entries.map(async (entry) => {
|
|
114
|
+
const context = (field) => ({ field, instanceId, entry: entry.name });
|
|
115
|
+
const [result, progress, data, error] = await Promise.all([
|
|
116
|
+
this.codecs.encode(entry.result, context('result')),
|
|
117
|
+
this.codecs.encode(entry.progress, context('progress')),
|
|
118
|
+
this.codecs.encode(entry.data, context('data')),
|
|
119
|
+
this.codecs.encodeError(entry.error, context('error')),
|
|
120
|
+
]);
|
|
121
|
+
return { ...entry, result, progress, data, error };
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
async decodeEntry(entry, instanceId) {
|
|
125
|
+
if (!entryEncoded(entry)) {
|
|
126
|
+
return entry;
|
|
127
|
+
}
|
|
128
|
+
const context = (field) => ({ field, instanceId, entry: entry.name });
|
|
129
|
+
const [result, progress, data, error] = await Promise.all([
|
|
130
|
+
this.codecs.decode(entry.result, context('result')),
|
|
131
|
+
this.codecs.decode(entry.progress, context('progress')),
|
|
132
|
+
this.codecs.decode(entry.data, context('data')),
|
|
133
|
+
this.codecs.decodeError(entry.error, context('error')),
|
|
134
|
+
]);
|
|
135
|
+
// Only the fields the entry has: a store keeps `null` and `undefined` apart, and so does the engine.
|
|
136
|
+
return {
|
|
137
|
+
...entry,
|
|
138
|
+
...('result' in entry ? { result } : {}),
|
|
139
|
+
...('progress' in entry ? { progress } : {}),
|
|
140
|
+
...('data' in entry ? { data } : {}),
|
|
141
|
+
...('error' in entry ? { error } : {}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* An instance as stored, decoded; one no listed codec can decode any more, without its payloads (its error's name
|
|
146
|
+
* kept), logged once: a listing shows it instead of failing whole.
|
|
147
|
+
*/
|
|
148
|
+
async readableInstance(instance) {
|
|
149
|
+
try {
|
|
150
|
+
return await this.decodeInstance(instance);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (!this.unreadable.has(instance.id)) {
|
|
154
|
+
this.unreadable.add(instance.id);
|
|
155
|
+
EncodedWorkflowStore.logger.warn(`Instance "${instance.id}" is listed without its payloads, which can't be read: ${error.message}`);
|
|
156
|
+
}
|
|
157
|
+
const { name } = instance.error ?? {};
|
|
158
|
+
return { ...instance, input: undefined, output: undefined, customStatus: null, cancelReason: null, error: name === undefined ? null : { name, message: '(unreadable)' } };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async decodeInstance(instance) {
|
|
162
|
+
if (![instance.input, instance.output, instance.error?.message, instance.customStatus, instance.cancelReason].some(encoded)) {
|
|
163
|
+
return instance;
|
|
164
|
+
}
|
|
165
|
+
const context = (field) => ({ field, instanceId: instance.id });
|
|
166
|
+
const [input, output, error, customStatus, cancelReason] = await Promise.all([
|
|
167
|
+
this.codecs.decode(instance.input, context('input')),
|
|
168
|
+
this.codecs.decode(instance.output, context('output')),
|
|
169
|
+
this.codecs.decodeError(instance.error, context('error')),
|
|
170
|
+
this.codecs.decode(instance.customStatus, context('customStatus')),
|
|
171
|
+
this.codecs.decode(instance.cancelReason, context('cancelReason')),
|
|
172
|
+
]);
|
|
173
|
+
return { ...instance, input, ...('output' in instance ? { output } : {}), error, customStatus, cancelReason: cancelReason };
|
|
174
|
+
}
|
|
175
|
+
async instanceOf(stored) {
|
|
176
|
+
const { instance, created } = await stored;
|
|
177
|
+
return { instance: await this.decodeInstance(instance), created };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* What a claim leased, decoded. One that can't be (a codec that is gone) is left out, logged, and handed back to
|
|
181
|
+
* be claimed again later, and read once the codec is back, while the others run.
|
|
182
|
+
*/
|
|
183
|
+
async readable(claimed, decode, kind, handBack) {
|
|
184
|
+
const decoded = [];
|
|
185
|
+
for (const item of claimed) {
|
|
186
|
+
try {
|
|
187
|
+
decoded.push(await decode(item));
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
EncodedWorkflowStore.logger.error(`${kind} "${item.id}" can't be read, so it isn't run; it is claimed again later. ${error.message}`);
|
|
191
|
+
// At worst its lease expires, as if its worker had died.
|
|
192
|
+
await handBack(item).catch(() => undefined);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return decoded;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
EncodedWorkflowStore.logger = new Logger('Workflows');
|
|
199
|
+
function entryEncoded(entry) {
|
|
200
|
+
return encoded(entry.result) || encoded(entry.progress) || encoded(entry.data) || encoded(entry.error?.message);
|
|
201
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './workflow.storage.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './workflow.storage.js';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { WorkflowPayloadCodec, WorkflowPayloadContext } from '../interfaces/workflow-payload-codec.interface.js';
|
|
2
|
+
import type { WorkflowStore } from '../interfaces/workflow-store.interface.js';
|
|
3
|
+
import { EncodedWorkflowStore } from './encoded-workflow.store.js';
|
|
4
|
+
import { PayloadCodecs } from '../core/codecs/payload-codecs.js';
|
|
5
|
+
import type { WorkflowsModuleOptions } from '../interfaces/workflows-module-options.interface.js';
|
|
6
|
+
import type { WorkflowStorageRegisterOptions } from '../interfaces/workflow-storage.interface.js';
|
|
7
|
+
/** Internal: locks the registry. `WorkflowsModule.onModuleInit()` and the first read call it. */
|
|
8
|
+
export declare const LOCK_STORAGE: unique symbol;
|
|
9
|
+
/** Internal: the store the engine uses, `source` with the module's codecs applied to the payloads. */
|
|
10
|
+
export declare const ENGINE_STORE: unique symbol;
|
|
11
|
+
/** Internal: the module's codecs, for the schedules, which the engine encodes apart from the instances. */
|
|
12
|
+
export declare const PAYLOAD_CODECS: unique symbol;
|
|
13
|
+
/**
|
|
14
|
+
* Where the application tells the package which `WorkflowStore` to use. A provider that
|
|
15
|
+
* implements `WorkflowStore` injects it and registers itself in its constructor:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* @Injectable()
|
|
19
|
+
* export class DrizzleWorkflowStore implements WorkflowStore {
|
|
20
|
+
* constructor(@InjectDrizzle() private readonly db: Database, storage: WorkflowStorage) {
|
|
21
|
+
* storage.registerSource(this);
|
|
22
|
+
* }
|
|
23
|
+
* // ...
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* With no source registered, the package uses an `InMemoryWorkflowStore`. The registry locks
|
|
28
|
+
* in `WorkflowsModule`'s `onModuleInit`, when every provider constructor has run and before
|
|
29
|
+
* the worker starts, or at the first read of the store if that is earlier (another module's
|
|
30
|
+
* `onModuleInit`). In production, it refuses the in-memory default unless
|
|
31
|
+
* `allowInMemoryStorage` is set.
|
|
32
|
+
*/
|
|
33
|
+
export declare class WorkflowStorage {
|
|
34
|
+
private readonly options?;
|
|
35
|
+
private readonly logger;
|
|
36
|
+
private registered?;
|
|
37
|
+
private active?;
|
|
38
|
+
private engine?;
|
|
39
|
+
private readonly codecs;
|
|
40
|
+
constructor(options?: WorkflowsModuleOptions | undefined, codecs?: WorkflowPayloadCodec[]);
|
|
41
|
+
/**
|
|
42
|
+
* Makes `source` the store. Call it from the constructor of a singleton provider. Throws
|
|
43
|
+
* when `source` lacks a method of `WorkflowStore`, when another source is registered
|
|
44
|
+
* (unless `replace`: tests, wrappers), and once the registry has locked.
|
|
45
|
+
*/
|
|
46
|
+
registerSource(source: WorkflowStore, options?: WorkflowStorageRegisterOptions): void;
|
|
47
|
+
/** The store in use: the registered one, or the in-memory default. Reading it locks the registry. */
|
|
48
|
+
get source(): WorkflowStore;
|
|
49
|
+
/** @internal The module's codecs. */
|
|
50
|
+
get [PAYLOAD_CODECS](): PayloadCodecs<WorkflowPayloadContext>;
|
|
51
|
+
/** @internal `source`, as the engine uses it: payloads encoded with the module's codecs on the way in, and decoded on the way out. */
|
|
52
|
+
get [ENGINE_STORE](): EncodedWorkflowStore;
|
|
53
|
+
/** Fixes the source, logs it, and enforces the production guard (which leaves the registry open). */
|
|
54
|
+
[LOCK_STORAGE](): void;
|
|
55
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { Inject, Injectable, Logger, Optional } from '@nestjs/common';
|
|
14
|
+
import { InMemoryWorkflowStore } from '../stores/in-memory-workflow.store.js';
|
|
15
|
+
import { WORKFLOWS_MODULE_OPTIONS } from '../workflows.module-definition.js';
|
|
16
|
+
import { WORKFLOW_PAYLOAD_CODECS } from '../workflows.constants.js';
|
|
17
|
+
import { EncodedWorkflowStore } from './encoded-workflow.store.js';
|
|
18
|
+
import { PayloadCodecs } from '../core/codecs/payload-codecs.js';
|
|
19
|
+
/** Internal: locks the registry. `WorkflowsModule.onModuleInit()` and the first read call it. */
|
|
20
|
+
export const LOCK_STORAGE = Symbol('WorkflowStorage.lock');
|
|
21
|
+
/** Internal: the store the engine uses, `source` with the module's codecs applied to the payloads. */
|
|
22
|
+
export const ENGINE_STORE = Symbol('WorkflowStorage.engineStore');
|
|
23
|
+
/** Internal: the module's codecs, for the schedules, which the engine encodes apart from the instances. */
|
|
24
|
+
export const PAYLOAD_CODECS = Symbol('WorkflowStorage.payloadCodecs');
|
|
25
|
+
const REQUIRED = [
|
|
26
|
+
'create',
|
|
27
|
+
'get',
|
|
28
|
+
'list',
|
|
29
|
+
'requestCancel',
|
|
30
|
+
'reopen',
|
|
31
|
+
'delete',
|
|
32
|
+
'signal',
|
|
33
|
+
'signals',
|
|
34
|
+
'purge',
|
|
35
|
+
'saveSchedule',
|
|
36
|
+
'getSchedule',
|
|
37
|
+
'listSchedules',
|
|
38
|
+
'deleteSchedule',
|
|
39
|
+
'claimSchedules',
|
|
40
|
+
'writeSchedule',
|
|
41
|
+
'claim',
|
|
42
|
+
'renew',
|
|
43
|
+
'write',
|
|
44
|
+
];
|
|
45
|
+
const OPTIONAL = ['createInTransaction', 'signalInTransaction'];
|
|
46
|
+
const DEFAULT = 'InMemoryWorkflowStore (the default: state is lost on restart and not shared between instances)';
|
|
47
|
+
/**
|
|
48
|
+
* Where the application tells the package which `WorkflowStore` to use. A provider that
|
|
49
|
+
* implements `WorkflowStore` injects it and registers itself in its constructor:
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* @Injectable()
|
|
53
|
+
* export class DrizzleWorkflowStore implements WorkflowStore {
|
|
54
|
+
* constructor(@InjectDrizzle() private readonly db: Database, storage: WorkflowStorage) {
|
|
55
|
+
* storage.registerSource(this);
|
|
56
|
+
* }
|
|
57
|
+
* // ...
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* With no source registered, the package uses an `InMemoryWorkflowStore`. The registry locks
|
|
62
|
+
* in `WorkflowsModule`'s `onModuleInit`, when every provider constructor has run and before
|
|
63
|
+
* the worker starts, or at the first read of the store if that is earlier (another module's
|
|
64
|
+
* `onModuleInit`). In production, it refuses the in-memory default unless
|
|
65
|
+
* `allowInMemoryStorage` is set.
|
|
66
|
+
*/
|
|
67
|
+
let WorkflowStorage = class WorkflowStorage {
|
|
68
|
+
constructor(options, codecs) {
|
|
69
|
+
this.options = options;
|
|
70
|
+
this.logger = new Logger('WorkflowsModule');
|
|
71
|
+
this.codecs = new PayloadCodecs(codecs ?? [], { name: "WorkflowsModule's codec", type: 'WorkflowPayloadCodec' });
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Makes `source` the store. Call it from the constructor of a singleton provider. Throws
|
|
75
|
+
* when `source` lacks a method of `WorkflowStore`, when another source is registered
|
|
76
|
+
* (unless `replace`: tests, wrappers), and once the registry has locked.
|
|
77
|
+
*/
|
|
78
|
+
registerSource(source, options = {}) {
|
|
79
|
+
validate(source);
|
|
80
|
+
if (this.active) {
|
|
81
|
+
throw new Error(`WorkflowStorage.registerSource(): ${nameOf(source)} registered after WorkflowsModule initialized (or after ` +
|
|
82
|
+
`its storage was first read), which already uses ${this.registered ? nameOf(this.active) : DEFAULT}. ` +
|
|
83
|
+
'Register from the constructor of a singleton provider: providers of lazy-loaded modules, request-scoped ' +
|
|
84
|
+
'and transient providers, and lifecycle hooks run too late.');
|
|
85
|
+
}
|
|
86
|
+
if (this.registered && !options.replace) {
|
|
87
|
+
throw new Error(`WorkflowStorage.registerSource(): ${nameOf(source)} can't register, ` +
|
|
88
|
+
`${this.registered === source ? 'it already did (the same instance, twice)' : `${nameOf(this.registered)} already did`}. ` +
|
|
89
|
+
'Register one store, or pass { replace: true } to replace it on purpose (tests, wrappers).');
|
|
90
|
+
}
|
|
91
|
+
this.registered = source;
|
|
92
|
+
}
|
|
93
|
+
/** The store in use: the registered one, or the in-memory default. Reading it locks the registry. */
|
|
94
|
+
get source() {
|
|
95
|
+
if (!this.active) {
|
|
96
|
+
this[LOCK_STORAGE]();
|
|
97
|
+
}
|
|
98
|
+
return this.active;
|
|
99
|
+
}
|
|
100
|
+
/** @internal The module's codecs. */
|
|
101
|
+
get [PAYLOAD_CODECS]() {
|
|
102
|
+
return this.codecs;
|
|
103
|
+
}
|
|
104
|
+
/** @internal `source`, as the engine uses it: payloads encoded with the module's codecs on the way in, and decoded on the way out. */
|
|
105
|
+
get [ENGINE_STORE]() {
|
|
106
|
+
return (this.engine ??= new EncodedWorkflowStore(this.source, this.codecs));
|
|
107
|
+
}
|
|
108
|
+
/** Fixes the source, logs it, and enforces the production guard (which leaves the registry open). */
|
|
109
|
+
[LOCK_STORAGE]() {
|
|
110
|
+
if (this.active) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (!this.registered && process.env.NODE_ENV === 'production' && !this.options?.allowInMemoryStorage) {
|
|
114
|
+
throw new Error('WorkflowStorage: no WorkflowStore is registered, and NODE_ENV is "production": in memory, running workflows ' +
|
|
115
|
+
'would be lost on restart and not shared between instances. Register a store on your database: ' +
|
|
116
|
+
'PostgresWorkflowStore (@nestjs/workflows/postgres), MySqlWorkflowStore (@nestjs/workflows/mysql), or your own ' +
|
|
117
|
+
'WorkflowStore in a provider that injects WorkflowStorage and calls `storage.registerSource(this)` in its ' +
|
|
118
|
+
'constructor. Or set `allowInMemoryStorage: true` in the WorkflowsModule options to run in memory anyway.');
|
|
119
|
+
}
|
|
120
|
+
this.active = this.registered ?? new InMemoryWorkflowStore();
|
|
121
|
+
this.logger.log(`WorkflowStorage: ${this.registered ? nameOf(this.registered) : DEFAULT}`);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
WorkflowStorage = __decorate([
|
|
125
|
+
Injectable(),
|
|
126
|
+
__param(0, Optional()),
|
|
127
|
+
__param(0, Inject(WORKFLOWS_MODULE_OPTIONS)),
|
|
128
|
+
__param(1, Optional()),
|
|
129
|
+
__param(1, Inject(WORKFLOW_PAYLOAD_CODECS)),
|
|
130
|
+
__metadata("design:paramtypes", [Object, Array])
|
|
131
|
+
], WorkflowStorage);
|
|
132
|
+
export { WorkflowStorage };
|
|
133
|
+
/** Throws unless `source` has every method of `WorkflowStore` (the optional ones may be absent). */
|
|
134
|
+
function validate(source) {
|
|
135
|
+
if (source === null || typeof source !== 'object') {
|
|
136
|
+
throw new TypeError(`WorkflowStorage.registerSource(): expected an object implementing WorkflowStore, got ${nameOf(source)}.`);
|
|
137
|
+
}
|
|
138
|
+
const candidate = source;
|
|
139
|
+
const missing = REQUIRED.filter((method) => typeof candidate[method] !== 'function');
|
|
140
|
+
const invalid = OPTIONAL.filter((method) => candidate[method] !== undefined && typeof candidate[method] !== 'function');
|
|
141
|
+
if (missing.length > 0 || invalid.length > 0) {
|
|
142
|
+
const problems = [
|
|
143
|
+
...(missing.length ? [`${missing.map((m) => `${m}()`).join(', ')} ${missing.length === 1 ? 'is' : 'are'} missing`] : []),
|
|
144
|
+
...(invalid.length ? [`${invalid.join(', ')} ${invalid.length === 1 ? 'is' : 'are'} set but not a method`] : []),
|
|
145
|
+
];
|
|
146
|
+
throw new TypeError(`WorkflowStorage.registerSource(): ${nameOf(source)} doesn't implement WorkflowStore: ${problems.join('; ')}.`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** How a message names a value: its class, or what it is instead of an instance. */
|
|
150
|
+
function nameOf(value) {
|
|
151
|
+
if (typeof value === 'function') {
|
|
152
|
+
return `the class ${value.name || '(anonymous)'} (pass an instance)`;
|
|
153
|
+
}
|
|
154
|
+
if (value === null || typeof value !== 'object') {
|
|
155
|
+
return String(value);
|
|
156
|
+
}
|
|
157
|
+
const name = value.constructor?.name;
|
|
158
|
+
return name && name !== 'Object' ? name : 'an object';
|
|
159
|
+
}
|