@kronos-ts/prisma 0.1.4 → 0.2.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/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { prismaTransactionManager, type PrismaClientLike, type PrismaTransactionClient, } from "./prisma-transaction-manager.js";
2
2
  export { prismaTokenStore, } from "./prisma-token-store.js";
3
+ export { prismaDeadLetterQueue, type PrismaDeadLetterQueueConfig, } from "./prisma-dead-letter-queue.js";
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,gBAAgB,GACjB,MAAM,yBAAyB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,gBAAgB,GACjB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EACL,qBAAqB,EACrB,KAAK,2BAA2B,GACjC,MAAM,+BAA+B,CAAA"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { prismaTransactionManager, } from "./prisma-transaction-manager.js";
2
2
  export { prismaTokenStore, } from "./prisma-token-store.js";
3
+ export { prismaDeadLetterQueue, } from "./prisma-dead-letter-queue.js";
3
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,GAGzB,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,gBAAgB,GACjB,MAAM,yBAAyB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,GAGzB,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,gBAAgB,GACjB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EACL,qBAAqB,GAEtB,MAAM,+BAA+B,CAAA"}
@@ -0,0 +1,63 @@
1
+ import type { SequencedDeadLetterQueue } from "@kronos-ts/messaging";
2
+ import type { PrismaClientLike } from "./prisma-transaction-manager.js";
3
+ /**
4
+ * Persistent {@link SequencedDeadLetterQueue} backed by Prisma — a faithful
5
+ * translation of the Drizzle reference DLQ (`drizzle-dead-letter-queue.ts`)
6
+ * onto Prisma's delegate API.
7
+ *
8
+ * Like {@link prismaTokenStore} it reads the active transaction via
9
+ * `getActiveTransaction()`, so enqueue/evict/requeue commit in the **same
10
+ * transaction as the token update** — a crash cannot advance the processor's
11
+ * token while losing the parked letter.
12
+ *
13
+ * The table is shared across processors and partitioned by `processingGroup`.
14
+ * Per-sequence FIFO order is held by a monotonic `sequenceIndex`; a
15
+ * `processingStarted` lease column makes `process()` safe across multiple
16
+ * nodes (Axon parity).
17
+ *
18
+ * Expected shape of the `kronos_dead_letters` table in the Prisma schema.
19
+ *
20
+ * ```prisma
21
+ * model KronosDeadLetter {
22
+ * deadLetterId String @id @map("dead_letter_id")
23
+ * processingGroup String @map("processing_group")
24
+ * sequenceIdentifier String @map("sequence_identifier")
25
+ * sequenceIndex Int @map("sequence_index")
26
+ * message String @map("message")
27
+ * causeType String? @map("cause_type")
28
+ * causeMessage String? @map("cause_message")
29
+ * diagnostics String @map("diagnostics")
30
+ * enqueuedAt String @map("enqueued_at")
31
+ * lastTouched String @map("last_touched")
32
+ * processingStarted String? @map("processing_started")
33
+ *
34
+ * @@index([processingGroup, sequenceIdentifier, sequenceIndex], name: "kronos_dl_seq")
35
+ * @@map("kronos_dead_letters")
36
+ * }
37
+ * ```
38
+ */
39
+ export interface PrismaDeadLetterQueueConfig {
40
+ /** Processing group (the processor name) this queue serves. */
41
+ processingGroup: string;
42
+ /** Maximum number of sequences. Default: 1024 (Axon parity). */
43
+ maxSequences?: number;
44
+ /** Maximum letters per sequence. Default: 1024 (Axon parity). */
45
+ maxSequenceSize?: number;
46
+ /** Lease duration for in-flight processing, ms. Default: 30000 (Axon parity). */
47
+ claimDurationMs?: number;
48
+ }
49
+ /**
50
+ * Creates a {@link SequencedDeadLetterQueue} backed by Prisma.
51
+ *
52
+ * Uses the `kronosDeadLetter` model from the Prisma schema. Participates in
53
+ * the active transaction (via `getActiveTransaction()`) so dead-letter writes
54
+ * and token/projection updates are atomic.
55
+ *
56
+ * ```typescript
57
+ * import { prismaDeadLetterQueue } from "@kronos-ts/prisma"
58
+ *
59
+ * const dlq = prismaDeadLetterQueue(prisma, { processingGroup: "my-processor" })
60
+ * ```
61
+ */
62
+ export declare function prismaDeadLetterQueue(prisma: PrismaClientLike, config: PrismaDeadLetterQueueConfig): SequencedDeadLetterQueue;
63
+ //# sourceMappingURL=prisma-dead-letter-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-dead-letter-queue.d.ts","sourceRoot":"","sources":["../src/prisma-dead-letter-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA+B,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAEjG,OAAO,KAAK,EAAE,gBAAgB,EAA2B,MAAM,iCAAiC,CAAA;AAEhG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,WAAW,2BAA2B;IAC1C,+DAA+D;IAC/D,eAAe,EAAE,MAAM,CAAA;IACvB,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AA0BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,2BAA2B,GAClC,wBAAwB,CAgN1B"}
@@ -0,0 +1,219 @@
1
+ import { getActiveTransaction, DeadLetterQueueOverflowError } from "@kronos-ts/messaging";
2
+ /** Reserved diagnostics key carrying the persistent row id across read → evict/requeue. */
3
+ const DL_ID = "__dlqId";
4
+ let idCounter = 0;
5
+ function newId(group) {
6
+ // Unique within the table: time + per-process counter + group.
7
+ idCounter += 1;
8
+ return `${group}:${Date.now()}:${idCounter}`;
9
+ }
10
+ /**
11
+ * Creates a {@link SequencedDeadLetterQueue} backed by Prisma.
12
+ *
13
+ * Uses the `kronosDeadLetter` model from the Prisma schema. Participates in
14
+ * the active transaction (via `getActiveTransaction()`) so dead-letter writes
15
+ * and token/projection updates are atomic.
16
+ *
17
+ * ```typescript
18
+ * import { prismaDeadLetterQueue } from "@kronos-ts/prisma"
19
+ *
20
+ * const dlq = prismaDeadLetterQueue(prisma, { processingGroup: "my-processor" })
21
+ * ```
22
+ */
23
+ export function prismaDeadLetterQueue(prisma, config) {
24
+ const { processingGroup } = config;
25
+ const maxSequences = config.maxSequences ?? 1024;
26
+ const maxSequenceSize = config.maxSequenceSize ?? 1024;
27
+ const claimDurationMs = config.claimDurationMs ?? 30000;
28
+ function getClient() {
29
+ return getActiveTransaction() ?? prisma;
30
+ }
31
+ function rowToLetter(row) {
32
+ const cause = new Error(row.causeMessage ?? "");
33
+ if (row.causeType)
34
+ cause.name = row.causeType;
35
+ return {
36
+ message: JSON.parse(row.message),
37
+ cause,
38
+ enqueuedAt: Number(row.enqueuedAt),
39
+ lastTouched: Number(row.lastTouched),
40
+ diagnostics: { ...JSON.parse(row.diagnostics), [DL_ID]: row.deadLetterId },
41
+ sequenceIdentifier: row.sequenceIdentifier,
42
+ };
43
+ }
44
+ function letterToRow(letter, sequenceIndex, deadLetterId) {
45
+ const { [DL_ID]: _omit, ...diagnostics } = letter.diagnostics;
46
+ return {
47
+ deadLetterId,
48
+ processingGroup,
49
+ sequenceIdentifier: letter.sequenceIdentifier,
50
+ sequenceIndex,
51
+ message: JSON.stringify(letter.message),
52
+ causeType: letter.cause.name,
53
+ causeMessage: letter.cause.message,
54
+ diagnostics: JSON.stringify(diagnostics),
55
+ enqueuedAt: String(letter.enqueuedAt),
56
+ lastTouched: String(letter.lastTouched),
57
+ processingStarted: null,
58
+ };
59
+ }
60
+ async function sequenceRows(client, seqId) {
61
+ return client.kronosDeadLetter.findMany({
62
+ where: { processingGroup, sequenceIdentifier: seqId },
63
+ orderBy: { sequenceIndex: "asc" },
64
+ });
65
+ }
66
+ async function distinctSequences(client) {
67
+ const rows = await client.kronosDeadLetter.findMany({
68
+ where: { processingGroup },
69
+ select: { sequenceIdentifier: true },
70
+ distinct: ["sequenceIdentifier"],
71
+ });
72
+ return rows.map((r) => r.sequenceIdentifier);
73
+ }
74
+ return {
75
+ async enqueue(letter) {
76
+ const client = getClient();
77
+ const existing = await sequenceRows(client, letter.sequenceIdentifier);
78
+ if (existing.length === 0) {
79
+ if ((await distinctSequences(client)).length >= maxSequences) {
80
+ throw new DeadLetterQueueOverflowError(`max sequences ${maxSequences} reached`);
81
+ }
82
+ }
83
+ else if (existing.length >= maxSequenceSize) {
84
+ throw new DeadLetterQueueOverflowError(`sequence "${letter.sequenceIdentifier}" has reached max size ${maxSequenceSize}`);
85
+ }
86
+ const idx = existing.length === 0 ? 0 : existing[existing.length - 1].sequenceIndex + 1;
87
+ await client.kronosDeadLetter.create({ data: letterToRow(letter, idx, newId(processingGroup)) });
88
+ },
89
+ async enqueueIfPresent(sequenceIdentifier, letterSupplier) {
90
+ const client = getClient();
91
+ const existing = await sequenceRows(client, sequenceIdentifier);
92
+ if (existing.length === 0)
93
+ return false;
94
+ if (existing.length >= maxSequenceSize) {
95
+ throw new DeadLetterQueueOverflowError(`sequence "${sequenceIdentifier}" has reached max size ${maxSequenceSize}`);
96
+ }
97
+ const idx = existing[existing.length - 1].sequenceIndex + 1;
98
+ await client.kronosDeadLetter.create({
99
+ data: letterToRow(letterSupplier(), idx, newId(processingGroup)),
100
+ });
101
+ return true;
102
+ },
103
+ async evict(_sequenceIdentifier, letter) {
104
+ const client = getClient();
105
+ const id = letter.diagnostics[DL_ID];
106
+ if (typeof id !== "string")
107
+ return;
108
+ await client.kronosDeadLetter.deleteMany({
109
+ where: { processingGroup, deadLetterId: id },
110
+ });
111
+ },
112
+ async requeue(letter, update) {
113
+ const client = getClient();
114
+ const id = letter.diagnostics[DL_ID];
115
+ if (typeof id !== "string")
116
+ return;
117
+ const { [DL_ID]: _omit, ...baseDiag } = letter.diagnostics;
118
+ const cause = update?.cause ?? letter.cause;
119
+ const diagnostics = update?.diagnostics ? { ...baseDiag, ...update.diagnostics } : baseDiag;
120
+ await client.kronosDeadLetter.updateMany({
121
+ where: { processingGroup, deadLetterId: id },
122
+ data: {
123
+ causeType: cause.name,
124
+ causeMessage: cause.message,
125
+ diagnostics: JSON.stringify(diagnostics),
126
+ lastTouched: String(Date.now()),
127
+ },
128
+ });
129
+ },
130
+ async contains(sequenceIdentifier) {
131
+ const client = getClient();
132
+ const row = await client.kronosDeadLetter.findFirst({
133
+ where: { processingGroup, sequenceIdentifier },
134
+ select: { deadLetterId: true },
135
+ });
136
+ return row != null;
137
+ },
138
+ async deadLetterSequence(sequenceIdentifier) {
139
+ const client = getClient();
140
+ return (await sequenceRows(client, sequenceIdentifier)).map(rowToLetter);
141
+ },
142
+ async sequenceIdentifiers() {
143
+ return distinctSequences(getClient());
144
+ },
145
+ async process(sequenceFilter, processingTask) {
146
+ const client = getClient();
147
+ const candidates = (await distinctSequences(client)).filter(sequenceFilter);
148
+ if (candidates.length === 0)
149
+ return false;
150
+ // Pick the oldest sequence by its head letter's lastTouched, skipping
151
+ // sequences under an unexpired processing lease (multi-node safety).
152
+ const cutoff = Date.now() - claimDurationMs;
153
+ let chosen;
154
+ let oldest = Infinity;
155
+ for (const seqId of candidates) {
156
+ const rows = await sequenceRows(client, seqId);
157
+ if (rows.length === 0)
158
+ continue;
159
+ const head = rows[0];
160
+ const leased = head.processingStarted != null && Number(head.processingStarted) > cutoff;
161
+ if (leased)
162
+ continue;
163
+ if (Number(head.lastTouched) < oldest) {
164
+ oldest = Number(head.lastTouched);
165
+ chosen = seqId;
166
+ }
167
+ }
168
+ if (!chosen)
169
+ return false;
170
+ // Claim the sequence head's lease for the duration of this pass.
171
+ const headRows = await sequenceRows(client, chosen);
172
+ await client.kronosDeadLetter.updateMany({
173
+ where: { processingGroup, deadLetterId: headRows[0].deadLetterId },
174
+ data: { processingStarted: String(Date.now()) },
175
+ });
176
+ try {
177
+ for (const row of headRows) {
178
+ const letter = rowToLetter(row);
179
+ const decision = await processingTask(letter);
180
+ if (decision.shouldEnqueue) {
181
+ await this.requeue(letter, { cause: decision.cause, diagnostics: decision.diagnostics });
182
+ return true;
183
+ }
184
+ await this.evict(chosen, letter);
185
+ }
186
+ return true;
187
+ }
188
+ finally {
189
+ // Release any lease still set on a surviving head.
190
+ const remaining = await sequenceRows(client, chosen);
191
+ if (remaining.length > 0 && remaining[0].processingStarted != null) {
192
+ await client.kronosDeadLetter.updateMany({
193
+ where: { processingGroup, deadLetterId: remaining[0].deadLetterId },
194
+ data: { processingStarted: null },
195
+ });
196
+ }
197
+ }
198
+ },
199
+ async size() {
200
+ const client = getClient();
201
+ return client.kronosDeadLetter.count({ where: { processingGroup } });
202
+ },
203
+ async amountOfSequences() {
204
+ return (await distinctSequences(getClient())).length;
205
+ },
206
+ async clear() {
207
+ const client = getClient();
208
+ await client.kronosDeadLetter.deleteMany({ where: { processingGroup } });
209
+ },
210
+ async isFull(sequenceIdentifier) {
211
+ const client = getClient();
212
+ const rows = await sequenceRows(client, sequenceIdentifier);
213
+ if (rows.length > 0)
214
+ return rows.length >= maxSequenceSize;
215
+ return (await distinctSequences(client)).length >= maxSequences;
216
+ },
217
+ };
218
+ }
219
+ //# sourceMappingURL=prisma-dead-letter-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-dead-letter-queue.js","sourceRoot":"","sources":["../src/prisma-dead-letter-queue.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAA;AAkDzF,2FAA2F;AAC3F,MAAM,KAAK,GAAG,SAAS,CAAA;AAEvB,IAAI,SAAS,GAAG,CAAC,CAAA;AACjB,SAAS,KAAK,CAAC,KAAa;IAC1B,+DAA+D;IAC/D,SAAS,IAAI,CAAC,CAAA;IACd,OAAO,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,SAAS,EAAE,CAAA;AAC9C,CAAC;AAgBD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAwB,EACxB,MAAmC;IAEnC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,CAAA;IAClC,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,IAAI,CAAA;IAChD,MAAM,eAAe,GAAG,MAAM,CAAC,eAAe,IAAI,IAAI,CAAA;IACtD,MAAM,eAAe,GAAG,MAAM,CAAC,eAAe,IAAI,KAAK,CAAA;IAEvD,SAAS,SAAS;QAChB,OAAO,oBAAoB,EAA2B,IAAI,MAAM,CAAA;IAClE,CAAC;IAED,SAAS,WAAW,CAAC,GAAkB;QACrC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC,CAAA;QAC/C,IAAI,GAAG,CAAC,SAAS;YAAE,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,SAAS,CAAA;QAC7C,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;YAChC,KAAK;YACL,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC;YAClC,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;YACpC,WAAW,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,YAAY,EAAE;YAC1E,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;SAC3C,CAAA;IACH,CAAC;IAED,SAAS,WAAW,CAAC,MAAkB,EAAE,aAAqB,EAAE,YAAoB;QAClF,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,WAAW,EAAE,GAAG,MAAM,CAAC,WAAsC,CAAA;QACxF,OAAO;YACL,YAAY;YACZ,eAAe;YACf,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;YAC7C,aAAa;YACb,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC;YACvC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI;YAC5B,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO;YAClC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;YACxC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;YACrC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC;YACvC,iBAAiB,EAAE,IAAI;SACxB,CAAA;IACH,CAAC;IAED,KAAK,UAAU,YAAY,CAAC,MAAW,EAAE,KAAa;QACpD,OAAO,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC;YACtC,KAAK,EAAE,EAAE,eAAe,EAAE,kBAAkB,EAAE,KAAK,EAAE;YACrD,OAAO,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE;SAClC,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,UAAU,iBAAiB,CAAC,MAAW;QAC1C,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC;YAClD,KAAK,EAAE,EAAE,eAAe,EAAE;YAC1B,MAAM,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE;YACpC,QAAQ,EAAE,CAAC,oBAAoB,CAAC;SACjC,CAAC,CAAA;QACF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAiC,EAAE,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAA;IAC9E,CAAC;IAED,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,MAAM;YAClB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;YAC1B,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,kBAAkB,CAAC,CAAA;YACtE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,YAAY,EAAE,CAAC;oBAC7D,MAAM,IAAI,4BAA4B,CAAC,iBAAiB,YAAY,UAAU,CAAC,CAAA;gBACjF,CAAC;YACH,CAAC;iBAAM,IAAI,QAAQ,CAAC,MAAM,IAAI,eAAe,EAAE,CAAC;gBAC9C,MAAM,IAAI,4BAA4B,CACpC,aAAa,MAAM,CAAC,kBAAkB,0BAA0B,eAAe,EAAE,CAClF,CAAA;YACH,CAAC;YACD,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC,CAAA;YACvF,MAAM,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAA;QAClG,CAAC;QAED,KAAK,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,cAAc;YACvD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;YAC1B,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;YAC/D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YACvC,IAAI,QAAQ,CAAC,MAAM,IAAI,eAAe,EAAE,CAAC;gBACvC,MAAM,IAAI,4BAA4B,CACpC,aAAa,kBAAkB,0BAA0B,eAAe,EAAE,CAC3E,CAAA;YACH,CAAC;YACD,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC,CAAA;YAC3D,MAAM,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC;gBACnC,IAAI,EAAE,WAAW,CAAC,cAAc,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;aACjE,CAAC,CAAA;YACF,OAAO,IAAI,CAAA;QACb,CAAC;QAED,KAAK,CAAC,KAAK,CAAC,mBAAmB,EAAE,MAAM;YACrC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;YAC1B,MAAM,EAAE,GAAI,MAAM,CAAC,WAAuC,CAAC,KAAK,CAAC,CAAA;YACjE,IAAI,OAAO,EAAE,KAAK,QAAQ;gBAAE,OAAM;YAClC,MAAM,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC;gBACvC,KAAK,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE,EAAE,EAAE;aAC7C,CAAC,CAAA;QACJ,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM;YAC1B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;YAC1B,MAAM,EAAE,GAAI,MAAM,CAAC,WAAuC,CAAC,KAAK,CAAC,CAAA;YACjE,IAAI,OAAO,EAAE,KAAK,QAAQ;gBAAE,OAAM;YAClC,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,CAAC,WAAsC,CAAA;YACrF,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,MAAM,CAAC,KAAK,CAAA;YAC3C,MAAM,WAAW,GAAG,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAA;YAC3F,MAAM,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC;gBACvC,KAAK,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE,EAAE,EAAE;gBAC5C,IAAI,EAAE;oBACJ,SAAS,EAAE,KAAK,CAAC,IAAI;oBACrB,YAAY,EAAE,KAAK,CAAC,OAAO;oBAC3B,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;oBACxC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;iBAChC;aACF,CAAC,CAAA;QACJ,CAAC;QAED,KAAK,CAAC,QAAQ,CAAC,kBAAkB;YAC/B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;YAC1B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC;gBAClD,KAAK,EAAE,EAAE,eAAe,EAAE,kBAAkB,EAAE;gBAC9C,MAAM,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;aAC/B,CAAC,CAAA;YACF,OAAO,GAAG,IAAI,IAAI,CAAA;QACpB,CAAC;QAED,KAAK,CAAC,kBAAkB,CAAC,kBAAkB;YACzC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;YAC1B,OAAO,CAAC,MAAM,YAAY,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAC1E,CAAC;QAED,KAAK,CAAC,mBAAmB;YACvB,OAAO,iBAAiB,CAAC,SAAS,EAAE,CAAC,CAAA;QACvC,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,cAAc;YAC1C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;YAC1B,MAAM,UAAU,GAAG,CAAC,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;YAC3E,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YAEzC,sEAAsE;YACtE,qEAAqE;YACrE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,CAAA;YAC3C,IAAI,MAA0B,CAAA;YAC9B,IAAI,MAAM,GAAG,QAAQ,CAAA;YACrB,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;gBAC/B,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;gBAC9C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;oBAAE,SAAQ;gBAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;gBACpB,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,MAAM,CAAA;gBACxF,IAAI,MAAM;oBAAE,SAAQ;gBACpB,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,MAAM,EAAE,CAAC;oBACtC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;oBACjC,MAAM,GAAG,KAAK,CAAA;gBAChB,CAAC;YACH,CAAC;YACD,IAAI,CAAC,MAAM;gBAAE,OAAO,KAAK,CAAA;YAEzB,iEAAiE;YACjE,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;YACnD,MAAM,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC;gBACvC,KAAK,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE;gBAClE,IAAI,EAAE,EAAE,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE;aAChD,CAAC,CAAA;YAEF,IAAI,CAAC;gBACH,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;oBAC3B,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAA;oBAC/B,MAAM,QAAQ,GAAoB,MAAM,cAAc,CAAC,MAAM,CAAC,CAAA;oBAC9D,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;wBAC3B,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;wBACxF,OAAO,IAAI,CAAA;oBACb,CAAC;oBACD,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;gBAClC,CAAC;gBACD,OAAO,IAAI,CAAA;YACb,CAAC;oBAAS,CAAC;gBACT,mDAAmD;gBACnD,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;gBACpD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,iBAAiB,IAAI,IAAI,EAAE,CAAC;oBACnE,MAAM,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC;wBACvC,KAAK,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE;wBACnE,IAAI,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE;qBAClC,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAED,KAAK,CAAC,IAAI;YACR,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;YAC1B,OAAO,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,eAAe,EAAE,EAAE,CAAC,CAAA;QACtE,CAAC;QAED,KAAK,CAAC,iBAAiB;YACrB,OAAO,CAAC,MAAM,iBAAiB,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,MAAM,CAAA;QACtD,CAAC;QAED,KAAK,CAAC,KAAK;YACT,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;YAC1B,MAAM,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,eAAe,EAAE,EAAE,CAAC,CAAA;QAC1E,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,kBAAkB;YAC7B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;YAC1B,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;YAC3D,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC,MAAM,IAAI,eAAe,CAAA;YAC1D,OAAO,CAAC,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,YAAY,CAAA;QACjE,CAAC;KACF,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kronos-ts/prisma",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "Prisma extension for Kronos.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -49,8 +49,8 @@
49
49
  }
50
50
  },
51
51
  "dependencies": {
52
- "@kronos-ts/common": "0.1.0",
53
- "@kronos-ts/messaging": "0.1.0"
52
+ "@kronos-ts/common": "0.1.1",
53
+ "@kronos-ts/messaging": "0.5.0"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@prisma/client": ">=5.0.0"
package/src/index.ts CHANGED
@@ -7,3 +7,8 @@ export {
7
7
  export {
8
8
  prismaTokenStore,
9
9
  } from "./prisma-token-store.js"
10
+
11
+ export {
12
+ prismaDeadLetterQueue,
13
+ type PrismaDeadLetterQueueConfig,
14
+ } from "./prisma-dead-letter-queue.js"
@@ -0,0 +1,300 @@
1
+ import type { DeadLetter, EnqueueDecision, SequencedDeadLetterQueue } from "@kronos-ts/messaging"
2
+ import { getActiveTransaction, DeadLetterQueueOverflowError } from "@kronos-ts/messaging"
3
+ import type { PrismaClientLike, PrismaTransactionClient } from "./prisma-transaction-manager.js"
4
+
5
+ /**
6
+ * Persistent {@link SequencedDeadLetterQueue} backed by Prisma — a faithful
7
+ * translation of the Drizzle reference DLQ (`drizzle-dead-letter-queue.ts`)
8
+ * onto Prisma's delegate API.
9
+ *
10
+ * Like {@link prismaTokenStore} it reads the active transaction via
11
+ * `getActiveTransaction()`, so enqueue/evict/requeue commit in the **same
12
+ * transaction as the token update** — a crash cannot advance the processor's
13
+ * token while losing the parked letter.
14
+ *
15
+ * The table is shared across processors and partitioned by `processingGroup`.
16
+ * Per-sequence FIFO order is held by a monotonic `sequenceIndex`; a
17
+ * `processingStarted` lease column makes `process()` safe across multiple
18
+ * nodes (Axon parity).
19
+ *
20
+ * Expected shape of the `kronos_dead_letters` table in the Prisma schema.
21
+ *
22
+ * ```prisma
23
+ * model KronosDeadLetter {
24
+ * deadLetterId String @id @map("dead_letter_id")
25
+ * processingGroup String @map("processing_group")
26
+ * sequenceIdentifier String @map("sequence_identifier")
27
+ * sequenceIndex Int @map("sequence_index")
28
+ * message String @map("message")
29
+ * causeType String? @map("cause_type")
30
+ * causeMessage String? @map("cause_message")
31
+ * diagnostics String @map("diagnostics")
32
+ * enqueuedAt String @map("enqueued_at")
33
+ * lastTouched String @map("last_touched")
34
+ * processingStarted String? @map("processing_started")
35
+ *
36
+ * @@index([processingGroup, sequenceIdentifier, sequenceIndex], name: "kronos_dl_seq")
37
+ * @@map("kronos_dead_letters")
38
+ * }
39
+ * ```
40
+ */
41
+ export interface PrismaDeadLetterQueueConfig {
42
+ /** Processing group (the processor name) this queue serves. */
43
+ processingGroup: string
44
+ /** Maximum number of sequences. Default: 1024 (Axon parity). */
45
+ maxSequences?: number
46
+ /** Maximum letters per sequence. Default: 1024 (Axon parity). */
47
+ maxSequenceSize?: number
48
+ /** Lease duration for in-flight processing, ms. Default: 30000 (Axon parity). */
49
+ claimDurationMs?: number
50
+ }
51
+
52
+ /** Reserved diagnostics key carrying the persistent row id across read → evict/requeue. */
53
+ const DL_ID = "__dlqId"
54
+
55
+ let idCounter = 0
56
+ function newId(group: string): string {
57
+ // Unique within the table: time + per-process counter + group.
58
+ idCounter += 1
59
+ return `${group}:${Date.now()}:${idCounter}`
60
+ }
61
+
62
+ interface DeadLetterRow {
63
+ deadLetterId: string
64
+ processingGroup: string
65
+ sequenceIdentifier: string
66
+ sequenceIndex: number
67
+ message: string
68
+ causeType: string | null
69
+ causeMessage: string | null
70
+ diagnostics: string
71
+ enqueuedAt: string
72
+ lastTouched: string
73
+ processingStarted: string | null
74
+ }
75
+
76
+ /**
77
+ * Creates a {@link SequencedDeadLetterQueue} backed by Prisma.
78
+ *
79
+ * Uses the `kronosDeadLetter` model from the Prisma schema. Participates in
80
+ * the active transaction (via `getActiveTransaction()`) so dead-letter writes
81
+ * and token/projection updates are atomic.
82
+ *
83
+ * ```typescript
84
+ * import { prismaDeadLetterQueue } from "@kronos-ts/prisma"
85
+ *
86
+ * const dlq = prismaDeadLetterQueue(prisma, { processingGroup: "my-processor" })
87
+ * ```
88
+ */
89
+ export function prismaDeadLetterQueue(
90
+ prisma: PrismaClientLike,
91
+ config: PrismaDeadLetterQueueConfig,
92
+ ): SequencedDeadLetterQueue {
93
+ const { processingGroup } = config
94
+ const maxSequences = config.maxSequences ?? 1024
95
+ const maxSequenceSize = config.maxSequenceSize ?? 1024
96
+ const claimDurationMs = config.claimDurationMs ?? 30000
97
+
98
+ function getClient(): any {
99
+ return getActiveTransaction<PrismaTransactionClient>() ?? prisma
100
+ }
101
+
102
+ function rowToLetter(row: DeadLetterRow): DeadLetter {
103
+ const cause = new Error(row.causeMessage ?? "")
104
+ if (row.causeType) cause.name = row.causeType
105
+ return {
106
+ message: JSON.parse(row.message),
107
+ cause,
108
+ enqueuedAt: Number(row.enqueuedAt),
109
+ lastTouched: Number(row.lastTouched),
110
+ diagnostics: { ...JSON.parse(row.diagnostics), [DL_ID]: row.deadLetterId },
111
+ sequenceIdentifier: row.sequenceIdentifier,
112
+ }
113
+ }
114
+
115
+ function letterToRow(letter: DeadLetter, sequenceIndex: number, deadLetterId: string): DeadLetterRow {
116
+ const { [DL_ID]: _omit, ...diagnostics } = letter.diagnostics as Record<string, unknown>
117
+ return {
118
+ deadLetterId,
119
+ processingGroup,
120
+ sequenceIdentifier: letter.sequenceIdentifier,
121
+ sequenceIndex,
122
+ message: JSON.stringify(letter.message),
123
+ causeType: letter.cause.name,
124
+ causeMessage: letter.cause.message,
125
+ diagnostics: JSON.stringify(diagnostics),
126
+ enqueuedAt: String(letter.enqueuedAt),
127
+ lastTouched: String(letter.lastTouched),
128
+ processingStarted: null,
129
+ }
130
+ }
131
+
132
+ async function sequenceRows(client: any, seqId: string): Promise<DeadLetterRow[]> {
133
+ return client.kronosDeadLetter.findMany({
134
+ where: { processingGroup, sequenceIdentifier: seqId },
135
+ orderBy: { sequenceIndex: "asc" },
136
+ })
137
+ }
138
+
139
+ async function distinctSequences(client: any): Promise<string[]> {
140
+ const rows = await client.kronosDeadLetter.findMany({
141
+ where: { processingGroup },
142
+ select: { sequenceIdentifier: true },
143
+ distinct: ["sequenceIdentifier"],
144
+ })
145
+ return rows.map((r: { sequenceIdentifier: string }) => r.sequenceIdentifier)
146
+ }
147
+
148
+ return {
149
+ async enqueue(letter) {
150
+ const client = getClient()
151
+ const existing = await sequenceRows(client, letter.sequenceIdentifier)
152
+ if (existing.length === 0) {
153
+ if ((await distinctSequences(client)).length >= maxSequences) {
154
+ throw new DeadLetterQueueOverflowError(`max sequences ${maxSequences} reached`)
155
+ }
156
+ } else if (existing.length >= maxSequenceSize) {
157
+ throw new DeadLetterQueueOverflowError(
158
+ `sequence "${letter.sequenceIdentifier}" has reached max size ${maxSequenceSize}`,
159
+ )
160
+ }
161
+ const idx = existing.length === 0 ? 0 : existing[existing.length - 1].sequenceIndex + 1
162
+ await client.kronosDeadLetter.create({ data: letterToRow(letter, idx, newId(processingGroup)) })
163
+ },
164
+
165
+ async enqueueIfPresent(sequenceIdentifier, letterSupplier) {
166
+ const client = getClient()
167
+ const existing = await sequenceRows(client, sequenceIdentifier)
168
+ if (existing.length === 0) return false
169
+ if (existing.length >= maxSequenceSize) {
170
+ throw new DeadLetterQueueOverflowError(
171
+ `sequence "${sequenceIdentifier}" has reached max size ${maxSequenceSize}`,
172
+ )
173
+ }
174
+ const idx = existing[existing.length - 1].sequenceIndex + 1
175
+ await client.kronosDeadLetter.create({
176
+ data: letterToRow(letterSupplier(), idx, newId(processingGroup)),
177
+ })
178
+ return true
179
+ },
180
+
181
+ async evict(_sequenceIdentifier, letter) {
182
+ const client = getClient()
183
+ const id = (letter.diagnostics as Record<string, unknown>)[DL_ID]
184
+ if (typeof id !== "string") return
185
+ await client.kronosDeadLetter.deleteMany({
186
+ where: { processingGroup, deadLetterId: id },
187
+ })
188
+ },
189
+
190
+ async requeue(letter, update) {
191
+ const client = getClient()
192
+ const id = (letter.diagnostics as Record<string, unknown>)[DL_ID]
193
+ if (typeof id !== "string") return
194
+ const { [DL_ID]: _omit, ...baseDiag } = letter.diagnostics as Record<string, unknown>
195
+ const cause = update?.cause ?? letter.cause
196
+ const diagnostics = update?.diagnostics ? { ...baseDiag, ...update.diagnostics } : baseDiag
197
+ await client.kronosDeadLetter.updateMany({
198
+ where: { processingGroup, deadLetterId: id },
199
+ data: {
200
+ causeType: cause.name,
201
+ causeMessage: cause.message,
202
+ diagnostics: JSON.stringify(diagnostics),
203
+ lastTouched: String(Date.now()),
204
+ },
205
+ })
206
+ },
207
+
208
+ async contains(sequenceIdentifier) {
209
+ const client = getClient()
210
+ const row = await client.kronosDeadLetter.findFirst({
211
+ where: { processingGroup, sequenceIdentifier },
212
+ select: { deadLetterId: true },
213
+ })
214
+ return row != null
215
+ },
216
+
217
+ async deadLetterSequence(sequenceIdentifier) {
218
+ const client = getClient()
219
+ return (await sequenceRows(client, sequenceIdentifier)).map(rowToLetter)
220
+ },
221
+
222
+ async sequenceIdentifiers() {
223
+ return distinctSequences(getClient())
224
+ },
225
+
226
+ async process(sequenceFilter, processingTask) {
227
+ const client = getClient()
228
+ const candidates = (await distinctSequences(client)).filter(sequenceFilter)
229
+ if (candidates.length === 0) return false
230
+
231
+ // Pick the oldest sequence by its head letter's lastTouched, skipping
232
+ // sequences under an unexpired processing lease (multi-node safety).
233
+ const cutoff = Date.now() - claimDurationMs
234
+ let chosen: string | undefined
235
+ let oldest = Infinity
236
+ for (const seqId of candidates) {
237
+ const rows = await sequenceRows(client, seqId)
238
+ if (rows.length === 0) continue
239
+ const head = rows[0]
240
+ const leased = head.processingStarted != null && Number(head.processingStarted) > cutoff
241
+ if (leased) continue
242
+ if (Number(head.lastTouched) < oldest) {
243
+ oldest = Number(head.lastTouched)
244
+ chosen = seqId
245
+ }
246
+ }
247
+ if (!chosen) return false
248
+
249
+ // Claim the sequence head's lease for the duration of this pass.
250
+ const headRows = await sequenceRows(client, chosen)
251
+ await client.kronosDeadLetter.updateMany({
252
+ where: { processingGroup, deadLetterId: headRows[0].deadLetterId },
253
+ data: { processingStarted: String(Date.now()) },
254
+ })
255
+
256
+ try {
257
+ for (const row of headRows) {
258
+ const letter = rowToLetter(row)
259
+ const decision: EnqueueDecision = await processingTask(letter)
260
+ if (decision.shouldEnqueue) {
261
+ await this.requeue(letter, { cause: decision.cause, diagnostics: decision.diagnostics })
262
+ return true
263
+ }
264
+ await this.evict(chosen, letter)
265
+ }
266
+ return true
267
+ } finally {
268
+ // Release any lease still set on a surviving head.
269
+ const remaining = await sequenceRows(client, chosen)
270
+ if (remaining.length > 0 && remaining[0].processingStarted != null) {
271
+ await client.kronosDeadLetter.updateMany({
272
+ where: { processingGroup, deadLetterId: remaining[0].deadLetterId },
273
+ data: { processingStarted: null },
274
+ })
275
+ }
276
+ }
277
+ },
278
+
279
+ async size() {
280
+ const client = getClient()
281
+ return client.kronosDeadLetter.count({ where: { processingGroup } })
282
+ },
283
+
284
+ async amountOfSequences() {
285
+ return (await distinctSequences(getClient())).length
286
+ },
287
+
288
+ async clear() {
289
+ const client = getClient()
290
+ await client.kronosDeadLetter.deleteMany({ where: { processingGroup } })
291
+ },
292
+
293
+ async isFull(sequenceIdentifier) {
294
+ const client = getClient()
295
+ const rows = await sequenceRows(client, sequenceIdentifier)
296
+ if (rows.length > 0) return rows.length >= maxSequenceSize
297
+ return (await distinctSequences(client)).length >= maxSequences
298
+ },
299
+ }
300
+ }