@nestarc/inbox 0.0.0-stage → 0.2.0
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/CHANGELOG.md +72 -0
- package/LICENSE +21 -0
- package/README.md +309 -2
- package/dist/contracts/errors.d.ts +34 -0
- package/dist/contracts/errors.js +73 -0
- package/dist/contracts/errors.js.map +1 -0
- package/dist/contracts/types.d.ts +174 -0
- package/dist/contracts/types.js +3 -0
- package/dist/contracts/types.js.map +1 -0
- package/dist/inbox-admin.service.d.ts +44 -0
- package/dist/inbox-admin.service.js +102 -0
- package/dist/inbox-admin.service.js.map +1 -0
- package/dist/inbox.module.d.ts +13 -0
- package/dist/inbox.module.js +59 -0
- package/dist/inbox.module.js.map +1 -0
- package/dist/inbox.service.d.ts +22 -0
- package/dist/inbox.service.js +74 -0
- package/dist/inbox.service.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/tenancy.d.ts +25 -0
- package/dist/integrations/tenancy.js +56 -0
- package/dist/integrations/tenancy.js.map +1 -0
- package/dist/internal/handler-context.d.ts +3 -0
- package/dist/internal/handler-context.js +39 -0
- package/dist/internal/handler-context.js.map +1 -0
- package/dist/internal/options.d.ts +5 -0
- package/dist/internal/options.js +86 -0
- package/dist/internal/options.js.map +1 -0
- package/dist/internal/reliability-core/abort.d.ts +2 -0
- package/dist/internal/reliability-core/abort.js +21 -0
- package/dist/internal/reliability-core/abort.js.map +1 -0
- package/dist/internal/reliability-core/index.d.ts +3 -0
- package/dist/internal/reliability-core/index.js +14 -0
- package/dist/internal/reliability-core/index.js.map +1 -0
- package/dist/internal/reliability-core/json.d.ts +13 -0
- package/dist/internal/reliability-core/json.js +125 -0
- package/dist/internal/reliability-core/json.js.map +1 -0
- package/dist/internal/reliability-core/retry.d.ts +8 -0
- package/dist/internal/reliability-core/retry.js +26 -0
- package/dist/internal/reliability-core/retry.js.map +1 -0
- package/dist/internal/validation.d.ts +6 -0
- package/dist/internal/validation.js +123 -0
- package/dist/internal/validation.js.map +1 -0
- package/dist/storage/prisma/prisma-inbox.storage.d.ts +30 -0
- package/dist/storage/prisma/prisma-inbox.storage.js +369 -0
- package/dist/storage/prisma/prisma-inbox.storage.js.map +1 -0
- package/dist/testing/index.d.ts +40 -0
- package/dist/testing/index.js +120 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/worker/inbox-worker.d.ts +31 -0
- package/dist/worker/inbox-worker.js +343 -0
- package/dist/worker/inbox-worker.js.map +1 -0
- package/docs/adr/0001-durable-inbox.md +40 -0
- package/docs/benchmarks/2026-10-27-local.md +31 -0
- package/docs/benchmarks/2026-10-28-preview1-local.md +87 -0
- package/docs/benchmarks/2026-10-28-preview1-run1.json +120 -0
- package/docs/benchmarks/2026-10-28-preview1-run2.json +120 -0
- package/docs/benchmarks/2026-10-28-preview1-run3.json +120 -0
- package/docs/benchmarks/2026-10-28-sustained-local.md +104 -0
- package/docs/benchmarks/2026-10-28-sustained-run1.json +2553 -0
- package/docs/benchmarks/2026-10-28-sustained-run2.json +2553 -0
- package/docs/broker-recovery-results.json +52 -0
- package/docs/broker-recovery.md +30 -0
- package/docs/db-operations.md +41 -0
- package/docs/implementation-roadmap.md +129 -0
- package/docs/operations/2026-10-28-database.json +34 -0
- package/docs/package-plan.md +250 -0
- package/docs/releasing.md +91 -0
- package/docs/testing.md +86 -0
- package/docs/usage.md +266 -0
- package/docs/validation.md +174 -0
- package/examples/README.md +124 -0
- package/examples/compose.yaml +26 -0
- package/examples/package.json +42 -0
- package/examples/prisma/schema.prisma +29 -0
- package/examples/prisma.config.ts +10 -0
- package/examples/scripts/prepare-db.cjs +45 -0
- package/examples/scripts/smoke.cjs +37 -0
- package/examples/scripts/with-brokers.cjs +249 -0
- package/examples/scripts/with-rabbitmq.cjs +131 -0
- package/examples/sql/application.sql +14 -0
- package/examples/sql/tenancy.sql +26 -0
- package/examples/src/broker-common.ts +79 -0
- package/examples/src/bullmq-ingress/main.ts +165 -0
- package/examples/src/common.ts +112 -0
- package/examples/src/idempotency-ingress/main.ts +211 -0
- package/examples/src/kafka-ingress/main.ts +209 -0
- package/examples/src/outbox-to-inbox/main.ts +198 -0
- package/examples/src/rabbitmq-ingress/main.ts +203 -0
- package/examples/src/sqs-ingress/main.ts +273 -0
- package/examples/src/tenancy/main.ts +210 -0
- package/examples/src/webhook-ingress/main.ts +172 -0
- package/examples/tsconfig.json +16 -0
- package/package.json +99 -4
- package/src/sql/create-inbox-table.sql +100 -0
- package/src/sql/migrate-v1-to-v2.sql +53 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PrismaInboxStorage = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const errors_1 = require("../../contracts/errors");
|
|
6
|
+
const STATUSES = [
|
|
7
|
+
"PENDING",
|
|
8
|
+
"PROCESSING",
|
|
9
|
+
"RETRYABLE",
|
|
10
|
+
"COMPLETED",
|
|
11
|
+
"DEAD",
|
|
12
|
+
];
|
|
13
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
14
|
+
function record(row) {
|
|
15
|
+
return {
|
|
16
|
+
id: row.id,
|
|
17
|
+
scope: row.scope_kind === "global"
|
|
18
|
+
? { kind: "global" }
|
|
19
|
+
: { kind: "tenant", id: row.scope_id },
|
|
20
|
+
source: row.source,
|
|
21
|
+
consumer: row.consumer,
|
|
22
|
+
messageId: row.message_id,
|
|
23
|
+
eventType: row.event_type,
|
|
24
|
+
schemaVersion: row.schema_version,
|
|
25
|
+
payload: row.payload,
|
|
26
|
+
payloadPurged: row.payload_purged,
|
|
27
|
+
fingerprint: row.fingerprint,
|
|
28
|
+
fingerprintVersion: row.fingerprint_version,
|
|
29
|
+
status: row.status,
|
|
30
|
+
claimToken: String(row.claim_token),
|
|
31
|
+
rowVersion: String(row.row_version),
|
|
32
|
+
attemptCount: row.attempt_count,
|
|
33
|
+
cycleAttemptCount: row.cycle_attempt_count,
|
|
34
|
+
requeueCount: row.requeue_count,
|
|
35
|
+
receivedAt: row.received_at,
|
|
36
|
+
completedAt: row.completed_at,
|
|
37
|
+
nextAttemptAt: row.next_attempt_at,
|
|
38
|
+
leaseExpiresAt: row.lease_expires_at,
|
|
39
|
+
workerId: row.worker_id,
|
|
40
|
+
lastErrorCode: row.last_error_code,
|
|
41
|
+
lastErrorMessage: row.last_error_message,
|
|
42
|
+
lastRequeueReason: row.last_requeue_reason,
|
|
43
|
+
lastRequeueActor: row.last_requeue_actor,
|
|
44
|
+
lastRequeueAt: row.last_requeue_at,
|
|
45
|
+
...(row.correlation_id === null
|
|
46
|
+
? {}
|
|
47
|
+
: { correlationId: row.correlation_id }),
|
|
48
|
+
...(row.causation_id === null ? {} : { causationId: row.causation_id }),
|
|
49
|
+
headers: row.headers,
|
|
50
|
+
...(row.replay_of === null || row.replay_of === undefined
|
|
51
|
+
? {}
|
|
52
|
+
: {
|
|
53
|
+
replay: {
|
|
54
|
+
originalInboxId: row.replay_of,
|
|
55
|
+
requestId: row.replay_request_id,
|
|
56
|
+
sourceVersion: String(row.replay_source_version),
|
|
57
|
+
actor: row.replay_actor,
|
|
58
|
+
reason: row.replay_reason,
|
|
59
|
+
requestedAt: row.replayed_at,
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function scopeValues(scope) {
|
|
65
|
+
return scope
|
|
66
|
+
? [scope.kind, scope.kind === "tenant" ? scope.id : ""]
|
|
67
|
+
: [null, null];
|
|
68
|
+
}
|
|
69
|
+
/** Internal PostgreSQL implementation. Undefined scope is reserved for privileged administration. */
|
|
70
|
+
class PrismaInboxStorage {
|
|
71
|
+
prisma;
|
|
72
|
+
options;
|
|
73
|
+
constructor(prisma, options = {}) {
|
|
74
|
+
this.prisma = prisma;
|
|
75
|
+
this.options = options;
|
|
76
|
+
}
|
|
77
|
+
async assertSchema() {
|
|
78
|
+
try {
|
|
79
|
+
const versions = await this.prisma.$queryRawUnsafe("SELECT version FROM public.nestarc_inbox_schema WHERE singleton = true");
|
|
80
|
+
if (versions.length !== 1 || versions[0]?.version !== 2)
|
|
81
|
+
throw new errors_1.InboxSchemaError();
|
|
82
|
+
// Reading the complete projection detects incomplete/old migrations without modifying schema.
|
|
83
|
+
await this.prisma
|
|
84
|
+
.$queryRawUnsafe(`SELECT id, scope_kind, scope_id, source, consumer,
|
|
85
|
+
message_id, event_type, schema_version, payload, payload_purged, fingerprint,
|
|
86
|
+
fingerprint_version, status, claim_token, row_version, attempt_count,
|
|
87
|
+
cycle_attempt_count, requeue_count, received_at, completed_at, next_attempt_at,
|
|
88
|
+
lease_expires_at, worker_id, last_error_code, last_error_message, last_requeue_reason,
|
|
89
|
+
last_requeue_actor, last_requeue_at, correlation_id, causation_id, headers,
|
|
90
|
+
replay_of, replay_request_id, replay_source_version, replay_actor, replay_reason, replayed_at
|
|
91
|
+
FROM public.nestarc_inbox LIMIT 0`);
|
|
92
|
+
const constraints = await this.prisma.$queryRawUnsafe(`
|
|
93
|
+
SELECT EXISTS (
|
|
94
|
+
SELECT 1 FROM pg_constraint c
|
|
95
|
+
WHERE c.conrelid = 'public.nestarc_inbox'::regclass
|
|
96
|
+
AND c.contype = 'u' AND NOT c.condeferrable
|
|
97
|
+
AND ARRAY(SELECT a.attname::text FROM unnest(c.conkey) WITH ORDINALITY k(attnum, n)
|
|
98
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum ORDER BY k.n)
|
|
99
|
+
= ARRAY['scope_kind','scope_id','source','consumer','message_id']::text[]
|
|
100
|
+
) AS valid`);
|
|
101
|
+
if (!constraints[0]?.valid)
|
|
102
|
+
throw new errors_1.InboxSchemaError();
|
|
103
|
+
const replayConstraints = await this.prisma.$queryRawUnsafe(`
|
|
104
|
+
SELECT EXISTS (
|
|
105
|
+
SELECT 1 FROM pg_constraint c
|
|
106
|
+
WHERE c.conrelid = 'public.nestarc_inbox'::regclass
|
|
107
|
+
AND c.contype = 'u' AND NOT c.condeferrable
|
|
108
|
+
AND ARRAY(SELECT a.attname::text FROM unnest(c.conkey) WITH ORDINALITY k(attnum, n)
|
|
109
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum ORDER BY k.n)
|
|
110
|
+
= ARRAY['scope_kind','scope_id','replay_request_id']::text[]
|
|
111
|
+
) AND EXISTS (
|
|
112
|
+
SELECT 1 FROM pg_constraint WHERE conrelid='public.nestarc_inbox'::regclass
|
|
113
|
+
AND conname='nestarc_inbox_replay_check' AND contype='c' AND convalidated
|
|
114
|
+
) AND EXISTS (
|
|
115
|
+
SELECT 1 FROM pg_constraint WHERE conrelid='public.nestarc_inbox'::regclass
|
|
116
|
+
AND conname='nestarc_inbox_replay_original_fk' AND contype='f' AND convalidated
|
|
117
|
+
AND confrelid='public.nestarc_inbox'::regclass
|
|
118
|
+
) AS valid`);
|
|
119
|
+
if (!replayConstraints[0]?.valid)
|
|
120
|
+
throw new errors_1.InboxSchemaError();
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
throw new errors_1.InboxSchemaError();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async accept(envelope) {
|
|
127
|
+
const identity = [
|
|
128
|
+
envelope.scope.kind,
|
|
129
|
+
envelope.scope.kind === "tenant" ? envelope.scope.id : "",
|
|
130
|
+
envelope.source,
|
|
131
|
+
envelope.consumer,
|
|
132
|
+
envelope.messageId,
|
|
133
|
+
];
|
|
134
|
+
return this.prisma.$transaction(async (tx) => {
|
|
135
|
+
const inserted = await tx.$queryRawUnsafe(`INSERT INTO public.nestarc_inbox
|
|
136
|
+
(id,scope_kind,scope_id,source,consumer,message_id,event_type,schema_version,payload,
|
|
137
|
+
fingerprint,fingerprint_version,correlation_id,causation_id,headers)
|
|
138
|
+
VALUES ($1::uuid,$2,$3,$4,$5,$6,$7,$8::integer,$9::jsonb,$10,$11::integer,$12,$13,$14::jsonb)
|
|
139
|
+
ON CONFLICT (scope_kind,scope_id,source,consumer,message_id) DO NOTHING RETURNING *`, (0, node_crypto_1.randomUUID)(), ...identity, envelope.eventType, envelope.schemaVersion, envelope.payloadJson, envelope.fingerprint, envelope.fingerprintVersion, envelope.correlationId ?? null, envelope.causationId ?? null, JSON.stringify(envelope.headers ?? {}));
|
|
140
|
+
if (inserted[0]) {
|
|
141
|
+
// Admission applies only to new records. A duplicate must still succeed at capacity.
|
|
142
|
+
// Counting our uncommitted row makes rejection roll back this insertion, never an ACK.
|
|
143
|
+
if (this.options.maxPendingMessages !== undefined) {
|
|
144
|
+
const count = await tx.$queryRawUnsafe(`SELECT count(*) > $1::integer AS full
|
|
145
|
+
FROM (SELECT 1 FROM public.nestarc_inbox
|
|
146
|
+
WHERE status IN ('PENDING','PROCESSING','RETRYABLE') LIMIT ($1::bigint + 1)) pending`, this.options.maxPendingMessages);
|
|
147
|
+
if (count[0]?.full)
|
|
148
|
+
throw new errors_1.InboxBackpressureError();
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
kind: "accepted",
|
|
152
|
+
id: inserted[0].id,
|
|
153
|
+
status: inserted[0].status,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// Separate READ COMMITTED statement sees a concurrent winner after ON CONFLICT waits for it.
|
|
157
|
+
const winner = await tx.$queryRawUnsafe(`SELECT * FROM public.nestarc_inbox
|
|
158
|
+
WHERE scope_kind=$1 AND scope_id=$2 AND source=$3 AND consumer=$4 AND message_id=$5`, ...identity);
|
|
159
|
+
if (!winner[0])
|
|
160
|
+
throw new errors_1.InboxStateConflictError();
|
|
161
|
+
return this.duplicate(winner[0], envelope);
|
|
162
|
+
}, { isolationLevel: "ReadCommitted", timeout: 10_000, maxWait: 5_000 });
|
|
163
|
+
}
|
|
164
|
+
duplicate(row, envelope) {
|
|
165
|
+
if (row.fingerprint !== envelope.fingerprint ||
|
|
166
|
+
row.fingerprint_version !== envelope.fingerprintVersion) {
|
|
167
|
+
throw new errors_1.InboxConflictError();
|
|
168
|
+
}
|
|
169
|
+
return { kind: "duplicate", id: row.id, status: row.status };
|
|
170
|
+
}
|
|
171
|
+
async claim(consumers, limit, workerId, leaseMs, maxAttempts) {
|
|
172
|
+
if (consumers.length === 0 || limit < 1)
|
|
173
|
+
return [];
|
|
174
|
+
return this.prisma.$transaction(async (tx) => {
|
|
175
|
+
await tx.$executeRawUnsafe(`WITH exhausted AS (
|
|
176
|
+
SELECT id FROM public.nestarc_inbox WHERE consumer = ANY($1::text[])
|
|
177
|
+
AND cycle_attempt_count >= $2::integer
|
|
178
|
+
AND ((status = 'PROCESSING' AND lease_expires_at <= clock_timestamp())
|
|
179
|
+
OR (status IN ('PENDING','RETRYABLE') AND next_attempt_at <= clock_timestamp()))
|
|
180
|
+
ORDER BY next_attempt_at,id LIMIT $3::integer FOR UPDATE SKIP LOCKED
|
|
181
|
+
) UPDATE public.nestarc_inbox i SET status='DEAD',lease_expires_at=NULL,worker_id=NULL,
|
|
182
|
+
last_error_code='ATTEMPTS_EXHAUSTED',last_error_message='The processing attempt limit was reached.',
|
|
183
|
+
row_version=i.row_version+1 FROM exhausted e WHERE i.id=e.id`, consumers, maxAttempts, Math.min(1000, Math.max(limit, 100)));
|
|
184
|
+
const rows = await tx.$queryRawUnsafe(`WITH candidates AS (
|
|
185
|
+
SELECT id FROM public.nestarc_inbox WHERE consumer = ANY($1::text[])
|
|
186
|
+
AND cycle_attempt_count < $2::integer
|
|
187
|
+
AND ((status IN ('PENDING','RETRYABLE') AND next_attempt_at <= clock_timestamp())
|
|
188
|
+
OR (status='PROCESSING' AND lease_expires_at <= clock_timestamp()))
|
|
189
|
+
ORDER BY next_attempt_at,id LIMIT $3::integer FOR UPDATE SKIP LOCKED
|
|
190
|
+
) UPDATE public.nestarc_inbox i SET status='PROCESSING',claim_token=i.claim_token+1,
|
|
191
|
+
row_version=i.row_version+1,attempt_count=i.attempt_count+1,
|
|
192
|
+
cycle_attempt_count=i.cycle_attempt_count+1,worker_id=$4,
|
|
193
|
+
lease_expires_at=clock_timestamp()+($5::double precision * interval '1 millisecond')
|
|
194
|
+
FROM candidates c WHERE i.id=c.id RETURNING i.*`, consumers, maxAttempts, limit, workerId, leaseMs);
|
|
195
|
+
return rows.map(record);
|
|
196
|
+
}, { isolationLevel: "ReadCommitted", timeout: 10_000, maxWait: 5_000 });
|
|
197
|
+
}
|
|
198
|
+
async run(claimed, fn, options) {
|
|
199
|
+
await this.prisma.$transaction(async (tx) => {
|
|
200
|
+
let timer;
|
|
201
|
+
const deadline = new Promise((_, reject) => {
|
|
202
|
+
timer = setTimeout(() => reject(new errors_1.InboxTimeoutError()), options.timeoutMs);
|
|
203
|
+
});
|
|
204
|
+
const work = async () => {
|
|
205
|
+
await tx.$queryRawUnsafe(`SELECT
|
|
206
|
+
set_config('statement_timeout',$1,true),
|
|
207
|
+
set_config('idle_in_transaction_session_timeout',$1,true),
|
|
208
|
+
set_config('lock_timeout',$2,true)`, `${options.timeoutMs}ms`, `${options.lockTimeoutMs}ms`);
|
|
209
|
+
await fn(tx);
|
|
210
|
+
const changed = await tx.$executeRawUnsafe(`UPDATE public.nestarc_inbox
|
|
211
|
+
SET status='COMPLETED',completed_at=clock_timestamp(),lease_expires_at=NULL,worker_id=NULL,
|
|
212
|
+
row_version=row_version+1,last_error_code=NULL,last_error_message=NULL
|
|
213
|
+
WHERE id=$1::uuid AND status='PROCESSING' AND claim_token=$2::bigint
|
|
214
|
+
AND lease_expires_at > clock_timestamp()`, claimed.id, claimed.claimToken);
|
|
215
|
+
if (changed !== 1)
|
|
216
|
+
throw new errors_1.InboxLeaseLostError();
|
|
217
|
+
};
|
|
218
|
+
try {
|
|
219
|
+
// Rejection returns from the interactive transaction, closing it before late callback writes.
|
|
220
|
+
await Promise.race([work(), deadline]);
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
if (timer)
|
|
224
|
+
clearTimeout(timer);
|
|
225
|
+
}
|
|
226
|
+
}, {
|
|
227
|
+
isolationLevel: "ReadCommitted",
|
|
228
|
+
timeout: options.timeoutMs,
|
|
229
|
+
maxWait: options.maxWaitMs,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
async heartbeat(id, token, leaseMs) {
|
|
233
|
+
return ((await this.prisma.$executeRawUnsafe(`UPDATE public.nestarc_inbox
|
|
234
|
+
SET lease_expires_at=clock_timestamp()+($3::double precision * interval '1 millisecond')
|
|
235
|
+
WHERE id=$1::uuid AND claim_token=$2::bigint AND status='PROCESSING'
|
|
236
|
+
AND lease_expires_at > clock_timestamp()`, id, token, leaseMs)) === 1);
|
|
237
|
+
}
|
|
238
|
+
async fail(id, token, error, permanent, delayMs, maxAttempts) {
|
|
239
|
+
return ((await this.prisma.$executeRawUnsafe(`UPDATE public.nestarc_inbox
|
|
240
|
+
SET status=CASE WHEN $3::boolean OR cycle_attempt_count >= $4::integer THEN 'DEAD' ELSE 'RETRYABLE' END,
|
|
241
|
+
next_attempt_at=clock_timestamp()+($5::double precision * interval '1 millisecond'),
|
|
242
|
+
lease_expires_at=NULL,worker_id=NULL,row_version=row_version+1,
|
|
243
|
+
last_error_code=$6,last_error_message=$7
|
|
244
|
+
WHERE id=$1::uuid AND claim_token=$2::bigint AND status='PROCESSING'
|
|
245
|
+
AND lease_expires_at > clock_timestamp()`, id, token, permanent, maxAttempts, delayMs, error.code, error.message)) === 1);
|
|
246
|
+
}
|
|
247
|
+
async get(id, scope) {
|
|
248
|
+
const rows = await this.prisma.$queryRawUnsafe(`SELECT * FROM public.nestarc_inbox
|
|
249
|
+
WHERE id=$1::uuid AND ($2::text IS NULL OR (scope_kind=$2 AND scope_id=$3))`, id, ...scopeValues(scope));
|
|
250
|
+
return rows[0] ? record(rows[0]) : null;
|
|
251
|
+
}
|
|
252
|
+
async list(options = {}, scope) {
|
|
253
|
+
const limit = options.limit ?? 50;
|
|
254
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1000)
|
|
255
|
+
throw new errors_1.InboxValidationError("List limit must be between 1 and 1000.");
|
|
256
|
+
if (options.cursor !== undefined && !UUID.test(options.cursor))
|
|
257
|
+
throw new errors_1.InboxValidationError("Invalid inbox cursor.");
|
|
258
|
+
if (options.status !== undefined && !STATUSES.includes(options.status))
|
|
259
|
+
throw new errors_1.InboxValidationError("Invalid inbox status.");
|
|
260
|
+
const rows = await this.prisma.$queryRawUnsafe(`SELECT * FROM public.nestarc_inbox
|
|
261
|
+
WHERE ($1::text IS NULL OR (scope_kind=$1 AND scope_id=$2))
|
|
262
|
+
AND ($3::text IS NULL OR status=$3) AND ($4::text IS NULL OR consumer=$4)
|
|
263
|
+
AND ($5::uuid IS NULL OR id > $5::uuid)
|
|
264
|
+
ORDER BY id LIMIT $6::integer`, ...scopeValues(scope), options.status ?? null, options.consumer ?? null, options.cursor ?? null, limit + 1);
|
|
265
|
+
const page = rows.slice(0, limit);
|
|
266
|
+
return {
|
|
267
|
+
records: page.map(record),
|
|
268
|
+
nextCursor: rows.length > limit ? page[page.length - 1].id : null,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
async requeue(id, scope, expectedVersion, reason, actor) {
|
|
272
|
+
const rows = await this.prisma.$queryRawUnsafe(`UPDATE public.nestarc_inbox
|
|
273
|
+
SET status='PENDING',cycle_attempt_count=0,requeue_count=requeue_count+1,
|
|
274
|
+
next_attempt_at=clock_timestamp(),row_version=row_version+1,
|
|
275
|
+
last_requeue_reason=$5,last_requeue_actor=$6,last_requeue_at=clock_timestamp()
|
|
276
|
+
WHERE id=$1::uuid AND ($2::text IS NULL OR (scope_kind=$2 AND scope_id=$3))
|
|
277
|
+
AND status='DEAD' AND row_version=$4::bigint RETURNING *`, id, ...scopeValues(scope), expectedVersion, reason, actor);
|
|
278
|
+
if (!rows[0])
|
|
279
|
+
throw new errors_1.InboxStateConflictError();
|
|
280
|
+
return record(rows[0]);
|
|
281
|
+
}
|
|
282
|
+
async replay(options, scope) {
|
|
283
|
+
return this.prisma.$transaction(async (tx) => {
|
|
284
|
+
// Serialize replay admission against cleanup and other requests for this original.
|
|
285
|
+
const originals = await tx.$queryRawUnsafe(`SELECT * FROM public.nestarc_inbox
|
|
286
|
+
WHERE id=$1::uuid AND ($2::text IS NULL OR (scope_kind=$2 AND scope_id=$3)) FOR UPDATE`, options.id, ...scopeValues(scope));
|
|
287
|
+
const original = originals[0];
|
|
288
|
+
if (!original)
|
|
289
|
+
throw new errors_1.InboxStateConflictError();
|
|
290
|
+
const existing = async () => (await tx.$queryRawUnsafe(`SELECT * FROM public.nestarc_inbox
|
|
291
|
+
WHERE scope_kind=$1 AND scope_id=$2 AND replay_request_id=$3::uuid`, original.scope_kind, original.scope_id, options.requestId))[0];
|
|
292
|
+
const duplicate = (row) => {
|
|
293
|
+
if (row.replay_of !== original.id ||
|
|
294
|
+
row.consumer !== options.targetConsumer ||
|
|
295
|
+
String(row.replay_source_version) !== options.expectedVersion ||
|
|
296
|
+
row.replay_actor !== options.actor ||
|
|
297
|
+
row.replay_reason !== options.reason)
|
|
298
|
+
throw new errors_1.InboxReplayConflictError();
|
|
299
|
+
return { kind: "duplicate", record: record(row) };
|
|
300
|
+
};
|
|
301
|
+
// Return an already committed request even if cleanup advanced the source version.
|
|
302
|
+
const prior = await existing();
|
|
303
|
+
if (prior)
|
|
304
|
+
return duplicate(prior);
|
|
305
|
+
if (original.status !== "COMPLETED" ||
|
|
306
|
+
original.payload_purged ||
|
|
307
|
+
String(original.row_version) !== options.expectedVersion)
|
|
308
|
+
throw new errors_1.InboxStateConflictError();
|
|
309
|
+
const rows = await tx.$queryRawUnsafe(`INSERT INTO public.nestarc_inbox
|
|
310
|
+
(id,scope_kind,scope_id,source,consumer,message_id,event_type,schema_version,payload,
|
|
311
|
+
fingerprint,fingerprint_version,correlation_id,causation_id,headers,
|
|
312
|
+
replay_of,replay_request_id,replay_source_version,replay_actor,replay_reason,replayed_at)
|
|
313
|
+
VALUES ($1::uuid,$2,$3,$4,$5,$6,$7,$8::integer,$9::jsonb,$10,$11::integer,$12,$13,$14::jsonb,
|
|
314
|
+
$15::uuid,$16::uuid,$17::bigint,$18,$19,clock_timestamp())
|
|
315
|
+
ON CONFLICT DO NOTHING RETURNING *`, (0, node_crypto_1.randomUUID)(), original.scope_kind, original.scope_id, original.source, options.targetConsumer, `replay:${original.id}:${options.requestId}`, original.event_type, original.schema_version, JSON.stringify(original.payload), original.fingerprint, original.fingerprint_version, original.correlation_id, original.causation_id, JSON.stringify(original.headers ?? {}), original.id, options.requestId, options.expectedVersion, options.actor, options.reason);
|
|
316
|
+
if (!rows[0]) {
|
|
317
|
+
const winner = await existing();
|
|
318
|
+
if (winner)
|
|
319
|
+
return duplicate(winner);
|
|
320
|
+
// An externally accepted message occupied the synthetic delivery identity.
|
|
321
|
+
throw new errors_1.InboxReplayConflictError();
|
|
322
|
+
}
|
|
323
|
+
if (this.options.maxPendingMessages !== undefined) {
|
|
324
|
+
const count = await tx.$queryRawUnsafe(`SELECT count(*) > $1::integer AS full FROM (SELECT 1 FROM public.nestarc_inbox
|
|
325
|
+
WHERE status IN ('PENDING','PROCESSING','RETRYABLE') LIMIT ($1::bigint + 1)) pending`, this.options.maxPendingMessages);
|
|
326
|
+
if (count[0]?.full)
|
|
327
|
+
throw new errors_1.InboxBackpressureError();
|
|
328
|
+
}
|
|
329
|
+
return { kind: "accepted", record: record(rows[0]) };
|
|
330
|
+
}, { isolationLevel: "ReadCommitted", timeout: 10_000, maxWait: 5_000 });
|
|
331
|
+
}
|
|
332
|
+
async stats(scope) {
|
|
333
|
+
const rows = await this.prisma.$queryRawUnsafe(`
|
|
334
|
+
SELECT status,count(*) AS count,min(received_at) AS oldest FROM public.nestarc_inbox
|
|
335
|
+
WHERE ($1::text IS NULL OR (scope_kind=$1 AND scope_id=$2)) GROUP BY status`, ...scopeValues(scope));
|
|
336
|
+
const counts = {
|
|
337
|
+
PENDING: 0,
|
|
338
|
+
PROCESSING: 0,
|
|
339
|
+
RETRYABLE: 0,
|
|
340
|
+
COMPLETED: 0,
|
|
341
|
+
DEAD: 0,
|
|
342
|
+
};
|
|
343
|
+
let oldestPendingAt = null;
|
|
344
|
+
for (const row of rows) {
|
|
345
|
+
counts[row.status] = Number(row.count);
|
|
346
|
+
if (["PENDING", "PROCESSING", "RETRYABLE"].includes(row.status) &&
|
|
347
|
+
row.oldest &&
|
|
348
|
+
(!oldestPendingAt || row.oldest < oldestPendingAt))
|
|
349
|
+
oldestPendingAt = row.oldest;
|
|
350
|
+
}
|
|
351
|
+
return { counts, oldestPendingAt };
|
|
352
|
+
}
|
|
353
|
+
async purgeCompletedPayload(before, limit, scope) {
|
|
354
|
+
if (!Number.isInteger(limit) ||
|
|
355
|
+
limit < 1 ||
|
|
356
|
+
limit > 1000 ||
|
|
357
|
+
!Number.isFinite(before.getTime())) {
|
|
358
|
+
throw new errors_1.InboxValidationError("Payload cleanup requires a valid date and a limit between 1 and 1000.");
|
|
359
|
+
}
|
|
360
|
+
return this.prisma.$executeRawUnsafe(`WITH candidates AS (
|
|
361
|
+
SELECT id FROM public.nestarc_inbox WHERE status='COMPLETED' AND NOT payload_purged
|
|
362
|
+
AND completed_at < $1::timestamptz AND ($2::text IS NULL OR (scope_kind=$2 AND scope_id=$3))
|
|
363
|
+
ORDER BY completed_at,id LIMIT $4::integer FOR UPDATE SKIP LOCKED
|
|
364
|
+
) UPDATE public.nestarc_inbox i SET payload=NULL,payload_purged=true,row_version=i.row_version+1
|
|
365
|
+
FROM candidates c WHERE i.id=c.id`, before, ...scopeValues(scope), limit);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
exports.PrismaInboxStorage = PrismaInboxStorage;
|
|
369
|
+
//# sourceMappingURL=prisma-inbox.storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-inbox.storage.js","sourceRoot":"","sources":["../../../src/storage/prisma/prisma-inbox.storage.ts"],"names":[],"mappings":";;;AAAA,6CAAyC;AACzC,mDASgC;AA4DhC,MAAM,QAAQ,GAAkB;IAC9B,SAAS;IACT,YAAY;IACZ,WAAW;IACX,WAAW;IACX,MAAM;CACP,CAAC;AACF,MAAM,IAAI,GAAG,iEAAiE,CAAC;AAE/E,SAAS,MAAM,CAAC,GAAQ;IACtB,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,KAAK,EACH,GAAG,CAAC,UAAU,KAAK,QAAQ;YACzB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE;YACpB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE;QAC1C,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,aAAa,EAAE,GAAG,CAAC,cAAc;QACjC,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,aAAa,EAAE,GAAG,CAAC,cAAc;QACjC,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,kBAAkB,EAAE,GAAG,CAAC,mBAAmB;QAC3C,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;QACnC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;QACnC,YAAY,EAAE,GAAG,CAAC,aAAa;QAC/B,iBAAiB,EAAE,GAAG,CAAC,mBAAmB;QAC1C,YAAY,EAAE,GAAG,CAAC,aAAa;QAC/B,UAAU,EAAE,GAAG,CAAC,WAAW;QAC3B,WAAW,EAAE,GAAG,CAAC,YAAY;QAC7B,aAAa,EAAE,GAAG,CAAC,eAAe;QAClC,cAAc,EAAE,GAAG,CAAC,gBAAgB;QACpC,QAAQ,EAAE,GAAG,CAAC,SAAS;QACvB,aAAa,EAAE,GAAG,CAAC,eAAe;QAClC,gBAAgB,EAAE,GAAG,CAAC,kBAAkB;QACxC,iBAAiB,EAAE,GAAG,CAAC,mBAAmB;QAC1C,gBAAgB,EAAE,GAAG,CAAC,kBAAkB;QACxC,aAAa,EAAE,GAAG,CAAC,eAAe;QAClC,GAAG,CAAC,GAAG,CAAC,cAAc,KAAK,IAAI;YAC7B,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC;QAC1C,GAAG,CAAC,GAAG,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;QACvE,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,GAAG,CAAC,GAAG,CAAC,SAAS,KAAK,IAAI,IAAI,GAAG,CAAC,SAAS,KAAK,SAAS;YACvD,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,MAAM,EAAE;oBACN,eAAe,EAAE,GAAG,CAAC,SAAS;oBAC9B,SAAS,EAAE,GAAG,CAAC,iBAAkB;oBACjC,aAAa,EAAE,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC;oBAChD,KAAK,EAAE,GAAG,CAAC,YAAa;oBACxB,MAAM,EAAE,GAAG,CAAC,aAAc;oBAC1B,WAAW,EAAE,GAAG,CAAC,WAAY;iBAC9B;aACF,CAAC;KACP,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAkB;IACrC,OAAO,KAAK;QACV,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACnB,CAAC;AAED,qGAAqG;AACrG,MAAa,kBAAkB;IAEV;IACA;IAFnB,YACmB,MAAyB,EACzB,UAA2C,EAAE;QAD7C,WAAM,GAAN,MAAM,CAAmB;QACzB,YAAO,GAAP,OAAO,CAAsC;IAC7D,CAAC;IAEJ,KAAK,CAAC,YAAY;QAChB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAChD,wEAAwE,CACzE,CAAC;YACF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC;gBACrD,MAAM,IAAI,yBAAgB,EAAE,CAAC;YAC/B,8FAA8F;YAC9F,MAAM,IAAI,CAAC,MAAM;iBACd,eAAe,CAAC;;;;;;;0CAOiB,CAAC,CAAC;YACtC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAEnD;;;;;;;;mBAQW,CAAC,CAAC;YACf,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK;gBAAE,MAAM,IAAI,yBAAgB,EAAE,CAAC;YACzD,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAEzD;;;;;;;;;;;;;;;mBAeW,CAAC,CAAC;YACf,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,KAAK;gBAAE,MAAM,IAAI,yBAAgB,EAAE,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,yBAAgB,EAAE,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAA4B;QACvC,MAAM,QAAQ,GAAG;YACf,QAAQ,CAAC,KAAK,CAAC,IAAI;YACnB,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;YACzD,QAAQ,CAAC,MAAM;YACf,QAAQ,CAAC,QAAQ;YACjB,QAAQ,CAAC,SAAS;SACnB,CAAC;QACF,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAC7B,KAAK,EAAE,EAAE,EAAE,EAAE;YACX,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,eAAe,CACvC;;;;4FAIkF,EAClF,IAAA,wBAAU,GAAE,EACZ,GAAG,QAAQ,EACX,QAAQ,CAAC,SAAS,EAClB,QAAQ,CAAC,aAAa,EACtB,QAAQ,CAAC,WAAW,EACpB,QAAQ,CAAC,WAAW,EACpB,QAAQ,CAAC,kBAAkB,EAC3B,QAAQ,CAAC,aAAa,IAAI,IAAI,EAC9B,QAAQ,CAAC,WAAW,IAAI,IAAI,EAC5B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CACvC,CAAC;YACF,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChB,qFAAqF;gBACrF,uFAAuF;gBACvF,IAAI,IAAI,CAAC,OAAO,CAAC,kBAAkB,KAAK,SAAS,EAAE,CAAC;oBAClD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,eAAe,CACpC;;mGAEqF,EACrF,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAChC,CAAC;oBACF,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI;wBAAE,MAAM,IAAI,+BAAsB,EAAE,CAAC;gBACzD,CAAC;gBACD,OAAO;oBACL,IAAI,EAAE,UAAmB;oBACzB,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;oBAClB,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM;iBAC3B,CAAC;YACJ,CAAC;YACD,6FAA6F;YAC7F,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,eAAe,CACrC;4FACkF,EAClF,GAAG,QAAQ,CACZ,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;gBAAE,MAAM,IAAI,gCAAuB,EAAE,CAAC;YACpD,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC7C,CAAC,EACD,EAAE,cAAc,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CACrE,CAAC;IACJ,CAAC;IAEO,SAAS,CAAC,GAAQ,EAAE,QAA4B;QACtD,IACE,GAAG,CAAC,WAAW,KAAK,QAAQ,CAAC,WAAW;YACxC,GAAG,CAAC,mBAAmB,KAAK,QAAQ,CAAC,kBAAkB,EACvD,CAAC;YACD,MAAM,IAAI,2BAAkB,EAAE,CAAC;QACjC,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,KAAK,CACT,SAAmB,EACnB,KAAa,EACb,QAAgB,EAChB,OAAe,EACf,WAAmB;QAEnB,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;YAAE,OAAO,EAAE,CAAC;QACnD,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAC7B,KAAK,EAAE,EAAE,EAAE,EAAE;YACX,MAAM,EAAE,CAAC,iBAAiB,CACxB;;;;;;;;qEAQ2D,EAC3D,SAAS,EACT,WAAW,EACX,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CACrC,CAAC;YACF,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,eAAe,CACnC;;;;;;;;;;wDAU8C,EAC9C,SAAS,EACT,WAAW,EACX,KAAK,EACL,QAAQ,EACR,OAAO,CACR,CAAC;YACF,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC,EACD,EAAE,cAAc,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CACrE,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,GAAG,CACP,OAAoB,EACpB,EAA2C,EAC3C,OAAwE;QAExE,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAC5B,KAAK,EAAE,EAAE,EAAE,EAAE;YACX,IAAI,KAAgD,CAAC;YACrD,MAAM,QAAQ,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;gBAChD,KAAK,GAAG,UAAU,CAChB,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,0BAAiB,EAAE,CAAC,EACrC,OAAO,CAAC,SAAS,CAClB,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;gBACtB,MAAM,EAAE,CAAC,eAAe,CACtB;;;6CAGiC,EACjC,GAAG,OAAO,CAAC,SAAS,IAAI,EACxB,GAAG,OAAO,CAAC,aAAa,IAAI,CAC7B,CAAC;gBACF,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;gBACb,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,iBAAiB,CACxC;;;;qDAIyC,EACzC,OAAO,CAAC,EAAE,EACV,OAAO,CAAC,UAAU,CACnB,CAAC;gBACF,IAAI,OAAO,KAAK,CAAC;oBAAE,MAAM,IAAI,4BAAmB,EAAE,CAAC;YACrD,CAAC,CAAC;YACF,IAAI,CAAC;gBACH,8FAA8F;gBAC9F,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;YACzC,CAAC;oBAAS,CAAC;gBACT,IAAI,KAAK;oBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC;QACH,CAAC,EACD;YACE,cAAc,EAAE,eAAe;YAC/B,OAAO,EAAE,OAAO,CAAC,SAAS;YAC1B,OAAO,EAAE,OAAO,CAAC,SAAS;SAC3B,CACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CACb,EAAU,EACV,KAAa,EACb,OAAe;QAEf,OAAO,CACL,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAClC;;;iDAGyC,EACzC,EAAE,EACF,KAAK,EACL,OAAO,CACR,CAAC,KAAK,CAAC,CACT,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI,CACR,EAAU,EACV,KAAa,EACb,KAAwC,EACxC,SAAkB,EAClB,OAAe,EACf,WAAmB;QAEnB,OAAO,CACL,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAClC;;;;;;iDAMyC,EACzC,EAAE,EACF,KAAK,EACL,SAAS,EACT,WAAW,EACX,OAAO,EACP,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,OAAO,CACd,CAAC,KAAK,CAAC,CACT,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAU,EAAE,KAAkB;QACtC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAC5C;kFAC4E,EAC5E,EAAE,EACF,GAAG,WAAW,CAAC,KAAK,CAAC,CACtB,CAAC;QACF,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,IAAI,CACR,UAA4B,EAAE,EAC9B,KAAkB;QAElB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,IAAI;YACvD,MAAM,IAAI,6BAAoB,CAAC,wCAAwC,CAAC,CAAC;QAC3E,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;YAC5D,MAAM,IAAI,6BAAoB,CAAC,uBAAuB,CAAC,CAAC;QAC1D,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC;YACpE,MAAM,IAAI,6BAAoB,CAAC,uBAAuB,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAC5C;;;;oCAI8B,EAC9B,GAAG,WAAW,CAAC,KAAK,CAAC,EACrB,OAAO,CAAC,MAAM,IAAI,IAAI,EACtB,OAAO,CAAC,QAAQ,IAAI,IAAI,EACxB,OAAO,CAAC,MAAM,IAAI,IAAI,EACtB,KAAK,GAAG,CAAC,CACV,CAAC;QACF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAClC,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YACzB,UAAU,EAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;SACnE,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CACX,EAAU,EACV,KAA6B,EAC7B,eAAuB,EACvB,MAAc,EACd,KAAa;QAEb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAC5C;;;;;iEAK2D,EAC3D,EAAE,EACF,GAAG,WAAW,CAAC,KAAK,CAAC,EACrB,eAAe,EACf,MAAM,EACN,KAAK,CACN,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,gCAAuB,EAAE,CAAC;QAClD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,MAAM,CACV,OAA2B,EAC3B,KAAkB;QAElB,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAC7B,KAAK,EAAE,EAAE,EAAE,EAAE;YACX,mFAAmF;YACnF,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,eAAe,CACxC;+FACqF,EACrF,OAAO,CAAC,EAAE,EACV,GAAG,WAAW,CAAC,KAAK,CAAC,CACtB,CAAC;YACF,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;YAC9B,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,gCAAuB,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE,CAC1B,CACE,MAAM,EAAE,CAAC,eAAe,CACtB;2EAC6D,EAC7D,QAAQ,CAAC,UAAU,EACnB,QAAQ,CAAC,QAAQ,EACjB,OAAO,CAAC,SAAS,CAClB,CACF,CAAC,CAAC,CAAC,CAAC;YACP,MAAM,SAAS,GAAG,CAAC,GAAQ,EAAqB,EAAE;gBAChD,IACE,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,EAAE;oBAC7B,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC,cAAc;oBACvC,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC,KAAK,OAAO,CAAC,eAAe;oBAC7D,GAAG,CAAC,YAAY,KAAK,OAAO,CAAC,KAAK;oBAClC,GAAG,CAAC,aAAa,KAAK,OAAO,CAAC,MAAM;oBAEpC,MAAM,IAAI,iCAAwB,EAAE,CAAC;gBACvC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACpD,CAAC,CAAC;YACF,mFAAmF;YACnF,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAC;YAC/B,IAAI,KAAK;gBAAE,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;YACnC,IACE,QAAQ,CAAC,MAAM,KAAK,WAAW;gBAC/B,QAAQ,CAAC,cAAc;gBACvB,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,OAAO,CAAC,eAAe;gBAExD,MAAM,IAAI,gCAAuB,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,eAAe,CACnC;;;;;;2CAMiC,EACjC,IAAA,wBAAU,GAAE,EACZ,QAAQ,CAAC,UAAU,EACnB,QAAQ,CAAC,QAAQ,EACjB,QAAQ,CAAC,MAAM,EACf,OAAO,CAAC,cAAc,EACtB,UAAU,QAAQ,CAAC,EAAE,IAAI,OAAO,CAAC,SAAS,EAAE,EAC5C,QAAQ,CAAC,UAAU,EACnB,QAAQ,CAAC,cAAc,EACvB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,EAChC,QAAQ,CAAC,WAAW,EACpB,QAAQ,CAAC,mBAAmB,EAC5B,QAAQ,CAAC,cAAc,EACvB,QAAQ,CAAC,YAAY,EACrB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,EACtC,QAAQ,CAAC,EAAE,EACX,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,eAAe,EACvB,OAAO,CAAC,KAAK,EACb,OAAO,CAAC,MAAM,CACf,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;gBACb,MAAM,MAAM,GAAG,MAAM,QAAQ,EAAE,CAAC;gBAChC,IAAI,MAAM;oBAAE,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC;gBACrC,2EAA2E;gBAC3E,MAAM,IAAI,iCAAwB,EAAE,CAAC;YACvC,CAAC;YACD,IAAI,IAAI,CAAC,OAAO,CAAC,kBAAkB,KAAK,SAAS,EAAE,CAAC;gBAClD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,eAAe,CACpC;iGACqF,EACrF,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAChC,CAAC;gBACF,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI;oBAAE,MAAM,IAAI,+BAAsB,EAAE,CAAC;YACzD,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,CAAC,EACD,EAAE,cAAc,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CACrE,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAkB;QAC5B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAG5C;;kFAE4E,EAC5E,GAAG,WAAW,CAAC,KAAK,CAAC,CACtB,CAAC;QACF,MAAM,MAAM,GAAyB;YACnC,OAAO,EAAE,CAAC;YACV,UAAU,EAAE,CAAC;YACb,SAAS,EAAE,CAAC;YACZ,SAAS,EAAE,CAAC;YACZ,IAAI,EAAE,CAAC;SACR,CAAC;QACF,IAAI,eAAe,GAAgB,IAAI,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACvC,IACE,CAAC,SAAS,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;gBAC3D,GAAG,CAAC,MAAM;gBACV,CAAC,CAAC,eAAe,IAAI,GAAG,CAAC,MAAM,GAAG,eAAe,CAAC;gBAElD,eAAe,GAAG,GAAG,CAAC,MAAM,CAAC;QACjC,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,qBAAqB,CACzB,MAAY,EACZ,KAAa,EACb,KAAkB;QAElB,IACE,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;YACxB,KAAK,GAAG,CAAC;YACT,KAAK,GAAG,IAAI;YACZ,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAClC,CAAC;YACD,MAAM,IAAI,6BAAoB,CAC5B,uEAAuE,CACxE,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAClC;;;;;wCAKkC,EAClC,MAAM,EACN,GAAG,WAAW,CAAC,KAAK,CAAC,EACrB,KAAK,CACN,CAAC;IACJ,CAAC;CACF;AApeD,gDAoeC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { InboxRecord, InboxStatus } from "../contracts/types";
|
|
2
|
+
import type { InboxAdminView } from "../inbox-admin.service";
|
|
3
|
+
import type { InboxService } from "../inbox.service";
|
|
4
|
+
export interface InboxTestOptions {
|
|
5
|
+
/** Overall wait budget, including database reads and worker execution. Default: 5,000. */
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
/** Delay between state checks. Default: 10. */
|
|
8
|
+
pollIntervalMs?: number;
|
|
9
|
+
/** Maximum state-check passes. Default: 1,000. */
|
|
10
|
+
maxPasses?: number;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
export interface InboxWaitOptions extends InboxTestOptions {
|
|
14
|
+
/** The terminal state to wait for. Default: COMPLETED. */
|
|
15
|
+
status?: "COMPLETED" | "DEAD";
|
|
16
|
+
}
|
|
17
|
+
/** Safe diagnostics; deliberately excludes payloads, scope and handler error text. */
|
|
18
|
+
export interface InboxTestSnapshot {
|
|
19
|
+
id: string;
|
|
20
|
+
status: InboxStatus | null;
|
|
21
|
+
attemptCount: number | null;
|
|
22
|
+
cycleAttemptCount: number | null;
|
|
23
|
+
}
|
|
24
|
+
export type InboxTestErrorCode = "INVALID_OPTIONS" | "TIMEOUT" | "ABORTED" | "PASS_LIMIT" | "UNEXPECTED_TERMINAL_STATE";
|
|
25
|
+
export declare class InboxTestError extends Error {
|
|
26
|
+
readonly code: InboxTestErrorCode;
|
|
27
|
+
readonly records: readonly InboxTestSnapshot[];
|
|
28
|
+
constructor(code: InboxTestErrorCode, message: string, records?: readonly InboxTestSnapshot[]);
|
|
29
|
+
}
|
|
30
|
+
type Reader = Pick<InboxAdminView, "get">;
|
|
31
|
+
type Runner = Pick<InboxService, "runOnce">;
|
|
32
|
+
/**
|
|
33
|
+
* Run the real worker until the requested receipts are COMPLETED.
|
|
34
|
+
* IDs select assertions, not claims: runOnce may process other owned receipts.
|
|
35
|
+
* Timeout/cancellation stops waiting and new polls; it cannot cancel an in-flight transaction.
|
|
36
|
+
*/
|
|
37
|
+
export declare function drainInbox(inbox: Runner, admin: Reader, ids: readonly string[], options?: InboxTestOptions): Promise<InboxRecord[]>;
|
|
38
|
+
/** Observe an existing worker without starting processing or changing the receipt. */
|
|
39
|
+
export declare function waitForInboxRecord(admin: Reader, id: string, options?: InboxWaitOptions): Promise<InboxRecord>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InboxTestError = void 0;
|
|
4
|
+
exports.drainInbox = drainInbox;
|
|
5
|
+
exports.waitForInboxRecord = waitForInboxRecord;
|
|
6
|
+
const abort_1 = require("../internal/reliability-core/abort");
|
|
7
|
+
class InboxTestError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
records;
|
|
10
|
+
constructor(code, message, records = []) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.name = "InboxTestError";
|
|
14
|
+
this.records = records.map((record) => ({ ...record }));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.InboxTestError = InboxTestError;
|
|
18
|
+
function positiveInteger(value, name) {
|
|
19
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 2147483647)
|
|
20
|
+
throw new InboxTestError("INVALID_OPTIONS", `${name} must be a positive 32-bit integer.`);
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function receiptIds(ids) {
|
|
24
|
+
if (!Array.isArray(ids) ||
|
|
25
|
+
ids.length > 1000 ||
|
|
26
|
+
Array.from(ids).some((id) => typeof id !== "string" ||
|
|
27
|
+
!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)))
|
|
28
|
+
throw new InboxTestError("INVALID_OPTIONS", "Provide at most 1,000 receipt UUIDs.");
|
|
29
|
+
return [...new Set(ids.map((id) => id.toLowerCase()))];
|
|
30
|
+
}
|
|
31
|
+
function pause(ms, signal) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const aborted = () => {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
signal.removeEventListener("abort", aborted);
|
|
36
|
+
reject(signal.reason);
|
|
37
|
+
};
|
|
38
|
+
const timer = setTimeout(() => {
|
|
39
|
+
signal.removeEventListener("abort", aborted);
|
|
40
|
+
resolve();
|
|
41
|
+
}, ms);
|
|
42
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
43
|
+
if (signal.aborted)
|
|
44
|
+
aborted();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function awaitRecords(reader, ids, options, runner) {
|
|
48
|
+
const timeoutMs = positiveInteger(options.timeoutMs ?? 5000, "timeoutMs");
|
|
49
|
+
const pollIntervalMs = positiveInteger(options.pollIntervalMs ?? 10, "pollIntervalMs");
|
|
50
|
+
const maxPasses = positiveInteger(options.maxPasses ?? 1000, "maxPasses");
|
|
51
|
+
const status = options.status ?? "COMPLETED";
|
|
52
|
+
if (status !== "COMPLETED" && status !== "DEAD")
|
|
53
|
+
throw new InboxTestError("INVALID_OPTIONS", "status must be COMPLETED or DEAD.");
|
|
54
|
+
const snapshots = ids.map((id) => ({
|
|
55
|
+
id,
|
|
56
|
+
status: null,
|
|
57
|
+
attemptCount: null,
|
|
58
|
+
cycleAttemptCount: null,
|
|
59
|
+
}));
|
|
60
|
+
const controller = new AbortController();
|
|
61
|
+
const fail = (code, message) => new InboxTestError(code, message, snapshots);
|
|
62
|
+
const aborted = () => controller.abort(fail("ABORTED", "Inbox test wait was aborted."));
|
|
63
|
+
options.signal?.addEventListener("abort", aborted, { once: true });
|
|
64
|
+
if (options.signal?.aborted)
|
|
65
|
+
aborted();
|
|
66
|
+
const timer = setTimeout(() => controller.abort(fail("TIMEOUT", `Inbox test did not reach ${status} within ${timeoutMs}ms.`)), timeoutMs);
|
|
67
|
+
const run = (fn) => (0, abort_1.withAbort)(controller.signal, fn);
|
|
68
|
+
try {
|
|
69
|
+
return await run(async () => {
|
|
70
|
+
if (ids.length === 0)
|
|
71
|
+
return [];
|
|
72
|
+
for (let pass = 0; pass < maxPasses; pass++) {
|
|
73
|
+
const records = [];
|
|
74
|
+
for (let index = 0; index < ids.length; index++) {
|
|
75
|
+
const record = await run(() => reader.get(ids[index]));
|
|
76
|
+
snapshots[index] = {
|
|
77
|
+
id: ids[index],
|
|
78
|
+
status: record?.status ?? null,
|
|
79
|
+
attemptCount: record?.attemptCount ?? null,
|
|
80
|
+
cycleAttemptCount: record?.cycleAttemptCount ?? null,
|
|
81
|
+
};
|
|
82
|
+
if (record)
|
|
83
|
+
records.push(record);
|
|
84
|
+
if (record &&
|
|
85
|
+
record.status !== status &&
|
|
86
|
+
(record.status === "DEAD" || record.status === "COMPLETED"))
|
|
87
|
+
throw fail("UNEXPECTED_TERMINAL_STATE", `Inbox receipt ${record.id} reached ${record.status}; expected ${status}.`);
|
|
88
|
+
}
|
|
89
|
+
if (records.length === ids.length &&
|
|
90
|
+
records.every((record) => record.status === status))
|
|
91
|
+
return records;
|
|
92
|
+
if (pass + 1 === maxPasses)
|
|
93
|
+
break;
|
|
94
|
+
if (runner)
|
|
95
|
+
await run(() => runner.runOnce());
|
|
96
|
+
// A zero-claim poll may mean a scheduled retry or a live lease, not completion.
|
|
97
|
+
await pause(pollIntervalMs, controller.signal);
|
|
98
|
+
}
|
|
99
|
+
throw fail("PASS_LIMIT", `Inbox test did not reach ${status} after ${maxPasses} state checks.`);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
options.signal?.removeEventListener("abort", aborted);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Run the real worker until the requested receipts are COMPLETED.
|
|
109
|
+
* IDs select assertions, not claims: runOnce may process other owned receipts.
|
|
110
|
+
* Timeout/cancellation stops waiting and new polls; it cannot cancel an in-flight transaction.
|
|
111
|
+
*/
|
|
112
|
+
async function drainInbox(inbox, admin, ids, options = {}) {
|
|
113
|
+
return awaitRecords(admin, receiptIds(ids), { ...options, status: "COMPLETED" }, inbox);
|
|
114
|
+
}
|
|
115
|
+
/** Observe an existing worker without starting processing or changing the receipt. */
|
|
116
|
+
async function waitForInboxRecord(admin, id, options = {}) {
|
|
117
|
+
const records = await awaitRecords(admin, receiptIds([id]), options);
|
|
118
|
+
return records[0];
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { InboxHandler, InboxOptions, ResolvedWorkerOptions } from "../contracts/types";
|
|
2
|
+
import type { PrismaInboxStorage } from "../storage/prisma/prisma-inbox.storage";
|
|
3
|
+
/** Internal polling worker. Business effects and completion are committed by storage.run(). */
|
|
4
|
+
export declare class InboxWorker {
|
|
5
|
+
private readonly storage;
|
|
6
|
+
private readonly options;
|
|
7
|
+
private readonly observer?;
|
|
8
|
+
private readonly handlerContext?;
|
|
9
|
+
private readonly workerId;
|
|
10
|
+
private readonly handlers;
|
|
11
|
+
private readonly active;
|
|
12
|
+
private readonly controllers;
|
|
13
|
+
private claimPromise;
|
|
14
|
+
private timer;
|
|
15
|
+
private running;
|
|
16
|
+
private stopped;
|
|
17
|
+
private stopPromise;
|
|
18
|
+
constructor(storage: PrismaInboxStorage, handlers: InboxHandler<any, any>[], options: ResolvedWorkerOptions, observer?: InboxOptions["observer"], handlerContext?: InboxOptions["handlerContext"]);
|
|
19
|
+
/** Start background polling. Schema validation must have succeeded first. */
|
|
20
|
+
start(): void;
|
|
21
|
+
/** Claim only free slots and wait for this batch. Concurrent calls cannot oversubscribe. */
|
|
22
|
+
tick(): Promise<number>;
|
|
23
|
+
/** Stop is terminal and idempotent. Expired in-flight work is recovered via its lease. */
|
|
24
|
+
stop(): Promise<void>;
|
|
25
|
+
private schedule;
|
|
26
|
+
private drain;
|
|
27
|
+
private process;
|
|
28
|
+
private recordFailure;
|
|
29
|
+
private retryDelay;
|
|
30
|
+
private observe;
|
|
31
|
+
}
|