rebackend-drizzle 0.1.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/README.md +21 -0
- package/dist/drizzle/index.js +21 -0
- package/dist/drizzle/index.js.map +1 -0
- package/dist/drizzle/outbox.js +173 -0
- package/dist/drizzle/outbox.js.map +1 -0
- package/dist/drizzle/presets.js +83 -0
- package/dist/drizzle/presets.js.map +1 -0
- package/dist/drizzle/repository.js +76 -0
- package/dist/drizzle/repository.js.map +1 -0
- package/dist/drizzle/transaction.js +20 -0
- package/dist/drizzle/transaction.js.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/src/drizzle/index.ts +4 -0
- package/src/drizzle/outbox.ts +387 -0
- package/src/drizzle/presets.ts +274 -0
- package/src/drizzle/repository.ts +221 -0
- package/src/drizzle/transaction.ts +31 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import type { InferInsertModel, InferSelectModel, Table } from 'drizzle-orm'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AnyDomainEventAction,
|
|
5
|
+
EventOutbox,
|
|
6
|
+
OutboxEntryInput,
|
|
7
|
+
OutboxRecord,
|
|
8
|
+
OutboxRecordStatus,
|
|
9
|
+
OutboxSourceType,
|
|
10
|
+
} from 'rebackend'
|
|
11
|
+
|
|
12
|
+
import { resolveDrizzleExecutor } from './transaction'
|
|
13
|
+
|
|
14
|
+
export type DrizzleOutboxSelectRow<TTable extends Table> = InferSelectModel<TTable>
|
|
15
|
+
export type DrizzleOutboxInsertRowShape<TTable extends Table> = InferInsertModel<TTable>
|
|
16
|
+
export type DrizzleOutboxSelectKey<TTable extends Table> = Extract<
|
|
17
|
+
keyof DrizzleOutboxSelectRow<TTable>,
|
|
18
|
+
string
|
|
19
|
+
>
|
|
20
|
+
|
|
21
|
+
export type DrizzleOutboxColumnMap<
|
|
22
|
+
TTable extends Table,
|
|
23
|
+
TIdKey extends DrizzleOutboxSelectKey<TTable>,
|
|
24
|
+
TEventNameKey extends DrizzleOutboxSelectKey<TTable>,
|
|
25
|
+
TPayloadKey extends DrizzleOutboxSelectKey<TTable>,
|
|
26
|
+
TSourceTypeKey extends DrizzleOutboxSelectKey<TTable>,
|
|
27
|
+
TSourceNameKey extends DrizzleOutboxSelectKey<TTable>,
|
|
28
|
+
TStatusKey extends DrizzleOutboxSelectKey<TTable>,
|
|
29
|
+
TAttemptsKey extends DrizzleOutboxSelectKey<TTable>,
|
|
30
|
+
TCreatedAtKey extends DrizzleOutboxSelectKey<TTable>,
|
|
31
|
+
TDispatchedAtKey extends DrizzleOutboxSelectKey<TTable> | undefined = undefined,
|
|
32
|
+
TLastErrorKey extends DrizzleOutboxSelectKey<TTable> | undefined = undefined,
|
|
33
|
+
> = {
|
|
34
|
+
id: TIdKey
|
|
35
|
+
eventName: TEventNameKey
|
|
36
|
+
payload: TPayloadKey
|
|
37
|
+
sourceType: TSourceTypeKey
|
|
38
|
+
sourceName: TSourceNameKey
|
|
39
|
+
status: TStatusKey
|
|
40
|
+
attempts: TAttemptsKey
|
|
41
|
+
createdAt: TCreatedAtKey
|
|
42
|
+
dispatchedAt?: TDispatchedAtKey
|
|
43
|
+
lastError?: TLastErrorKey
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type DrizzleOutboxInsertRow = {
|
|
47
|
+
eventName: string
|
|
48
|
+
payload: string
|
|
49
|
+
sourceType: OutboxSourceType
|
|
50
|
+
sourceName: string
|
|
51
|
+
status: OutboxRecordStatus
|
|
52
|
+
attempts: number
|
|
53
|
+
createdAt: Date
|
|
54
|
+
dispatchedAt: Date | null
|
|
55
|
+
lastError: string | null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type DrizzleOutboxStoredRow = DrizzleOutboxInsertRow & {
|
|
59
|
+
id: number
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type DrizzleOutboxCodec<TRow, TInsertRow> = {
|
|
63
|
+
toInsertRow(entry: OutboxEntryInput, now: Date): TInsertRow
|
|
64
|
+
fromStoredRow(row: TRow): OutboxRecord
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const createJsonDrizzleOutboxCodec = (): DrizzleOutboxCodec<
|
|
68
|
+
DrizzleOutboxStoredRow,
|
|
69
|
+
DrizzleOutboxInsertRow
|
|
70
|
+
> => {
|
|
71
|
+
return {
|
|
72
|
+
toInsertRow(entry, now) {
|
|
73
|
+
return {
|
|
74
|
+
eventName: entry.event.name,
|
|
75
|
+
payload: JSON.stringify({
|
|
76
|
+
key: entry.event.key,
|
|
77
|
+
version: entry.event.version,
|
|
78
|
+
payload: entry.event.payload,
|
|
79
|
+
}),
|
|
80
|
+
sourceType: entry.sourceType,
|
|
81
|
+
sourceName: entry.sourceName,
|
|
82
|
+
status: 'pending',
|
|
83
|
+
attempts: 0,
|
|
84
|
+
createdAt: now,
|
|
85
|
+
dispatchedAt: null,
|
|
86
|
+
lastError: null,
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
fromStoredRow(row) {
|
|
90
|
+
const envelope = JSON.parse(row.payload) as {
|
|
91
|
+
key: string
|
|
92
|
+
version: number
|
|
93
|
+
payload: AnyDomainEventAction['payload']
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
id: row.id,
|
|
98
|
+
event: {
|
|
99
|
+
kind: 'DomainEventAction',
|
|
100
|
+
key: envelope.key,
|
|
101
|
+
name: row.eventName,
|
|
102
|
+
version: envelope.version,
|
|
103
|
+
payload: envelope.payload,
|
|
104
|
+
},
|
|
105
|
+
sourceType: row.sourceType,
|
|
106
|
+
sourceName: row.sourceName,
|
|
107
|
+
status: row.status,
|
|
108
|
+
attempts: row.attempts,
|
|
109
|
+
createdAt: row.createdAt,
|
|
110
|
+
dispatchedAt: row.dispatchedAt ?? undefined,
|
|
111
|
+
lastError: row.lastError ?? undefined,
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type DrizzleOutboxOptions<TDb, TTransaction, TStoredRow, TInsertRow> = {
|
|
118
|
+
db: TDb
|
|
119
|
+
codec: DrizzleOutboxCodec<TStoredRow, TInsertRow>
|
|
120
|
+
insertRows: (executor: TDb | TTransaction, rows: readonly TInsertRow[]) => Promise<void>
|
|
121
|
+
listRows: (executor: TDb | TTransaction) => Promise<readonly TStoredRow[]>
|
|
122
|
+
listPendingRows: (executor: TDb | TTransaction, limit?: number) => Promise<readonly TStoredRow[]>
|
|
123
|
+
markRowsDispatched: (
|
|
124
|
+
executor: TDb | TTransaction,
|
|
125
|
+
ids: readonly number[],
|
|
126
|
+
dispatchedAt: Date,
|
|
127
|
+
) => Promise<void>
|
|
128
|
+
markRowsFailed: (
|
|
129
|
+
executor: TDb | TTransaction,
|
|
130
|
+
ids: readonly number[],
|
|
131
|
+
error: string,
|
|
132
|
+
) => Promise<void>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const createDrizzleOutbox = <TDb, TTransaction, TStoredRow, TInsertRow>(
|
|
136
|
+
options: DrizzleOutboxOptions<TDb, TTransaction, TStoredRow, TInsertRow>,
|
|
137
|
+
): EventOutbox => {
|
|
138
|
+
return {
|
|
139
|
+
async append(entries, context) {
|
|
140
|
+
if (entries.length === 0) {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const executor = resolveDrizzleExecutor<TDb, TTransaction>(options.db, context)
|
|
145
|
+
const now = new Date()
|
|
146
|
+
const rows = entries.map((entry) => options.codec.toInsertRow(entry, now))
|
|
147
|
+
|
|
148
|
+
await options.insertRows(executor, rows)
|
|
149
|
+
},
|
|
150
|
+
async list() {
|
|
151
|
+
const executor = resolveDrizzleExecutor<TDb, TTransaction>(options.db)
|
|
152
|
+
const rows = await options.listRows(executor)
|
|
153
|
+
|
|
154
|
+
return rows.map((row) => options.codec.fromStoredRow(row))
|
|
155
|
+
},
|
|
156
|
+
async listPending(limit) {
|
|
157
|
+
const executor = resolveDrizzleExecutor<TDb, TTransaction>(options.db)
|
|
158
|
+
const rows = await options.listPendingRows(executor, limit)
|
|
159
|
+
|
|
160
|
+
return rows.map((row) => options.codec.fromStoredRow(row))
|
|
161
|
+
},
|
|
162
|
+
async markDispatched(ids) {
|
|
163
|
+
if (ids.length === 0) {
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const executor = resolveDrizzleExecutor<TDb, TTransaction>(options.db)
|
|
168
|
+
await options.markRowsDispatched(executor, ids, new Date())
|
|
169
|
+
},
|
|
170
|
+
async markFailed(ids, error) {
|
|
171
|
+
if (ids.length === 0) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const executor = resolveDrizzleExecutor<TDb, TTransaction>(options.db)
|
|
176
|
+
await options.markRowsFailed(executor, ids, error)
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type DrizzleTableOutboxOptions<
|
|
182
|
+
TDb,
|
|
183
|
+
TTransaction,
|
|
184
|
+
TTable extends Table,
|
|
185
|
+
TIdKey extends DrizzleOutboxSelectKey<TTable>,
|
|
186
|
+
TEventNameKey extends DrizzleOutboxSelectKey<TTable>,
|
|
187
|
+
TPayloadKey extends DrizzleOutboxSelectKey<TTable>,
|
|
188
|
+
TSourceTypeKey extends DrizzleOutboxSelectKey<TTable>,
|
|
189
|
+
TSourceNameKey extends DrizzleOutboxSelectKey<TTable>,
|
|
190
|
+
TStatusKey extends DrizzleOutboxSelectKey<TTable>,
|
|
191
|
+
TAttemptsKey extends DrizzleOutboxSelectKey<TTable>,
|
|
192
|
+
TCreatedAtKey extends DrizzleOutboxSelectKey<TTable>,
|
|
193
|
+
TDispatchedAtKey extends DrizzleOutboxSelectKey<TTable> | undefined = undefined,
|
|
194
|
+
TLastErrorKey extends DrizzleOutboxSelectKey<TTable> | undefined = undefined,
|
|
195
|
+
> = {
|
|
196
|
+
db: TDb
|
|
197
|
+
table: TTable
|
|
198
|
+
columns: DrizzleOutboxColumnMap<
|
|
199
|
+
TTable,
|
|
200
|
+
TIdKey,
|
|
201
|
+
TEventNameKey,
|
|
202
|
+
TPayloadKey,
|
|
203
|
+
TSourceTypeKey,
|
|
204
|
+
TSourceNameKey,
|
|
205
|
+
TStatusKey,
|
|
206
|
+
TAttemptsKey,
|
|
207
|
+
TCreatedAtKey,
|
|
208
|
+
TDispatchedAtKey,
|
|
209
|
+
TLastErrorKey
|
|
210
|
+
>
|
|
211
|
+
operations: {
|
|
212
|
+
insertRows(input: {
|
|
213
|
+
executor: TDb | TTransaction
|
|
214
|
+
table: TTable
|
|
215
|
+
rows: readonly DrizzleOutboxInsertRowShape<TTable>[]
|
|
216
|
+
}): Promise<void>
|
|
217
|
+
listRows(input: {
|
|
218
|
+
executor: TDb | TTransaction
|
|
219
|
+
table: TTable
|
|
220
|
+
}): Promise<readonly DrizzleOutboxSelectRow<TTable>[]>
|
|
221
|
+
listPendingRows(input: {
|
|
222
|
+
executor: TDb | TTransaction
|
|
223
|
+
table: TTable
|
|
224
|
+
statusKey: TStatusKey
|
|
225
|
+
pendingStatus: OutboxRecordStatus
|
|
226
|
+
limit?: number
|
|
227
|
+
}): Promise<readonly DrizzleOutboxSelectRow<TTable>[]>
|
|
228
|
+
markRowsDispatched(input: {
|
|
229
|
+
executor: TDb | TTransaction
|
|
230
|
+
table: TTable
|
|
231
|
+
ids: readonly number[]
|
|
232
|
+
idKey: TIdKey
|
|
233
|
+
statusKey: TStatusKey
|
|
234
|
+
attemptsKey: TAttemptsKey
|
|
235
|
+
dispatchedAtKey?: TDispatchedAtKey
|
|
236
|
+
dispatchedAt: Date
|
|
237
|
+
}): Promise<void>
|
|
238
|
+
markRowsFailed(input: {
|
|
239
|
+
executor: TDb | TTransaction
|
|
240
|
+
table: TTable
|
|
241
|
+
ids: readonly number[]
|
|
242
|
+
idKey: TIdKey
|
|
243
|
+
statusKey: TStatusKey
|
|
244
|
+
attemptsKey: TAttemptsKey
|
|
245
|
+
lastErrorKey?: TLastErrorKey
|
|
246
|
+
error: string
|
|
247
|
+
}): Promise<void>
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const createDrizzleTableOutbox = <
|
|
252
|
+
TDb,
|
|
253
|
+
TTransaction,
|
|
254
|
+
TTable extends Table,
|
|
255
|
+
TIdKey extends DrizzleOutboxSelectKey<TTable>,
|
|
256
|
+
TEventNameKey extends DrizzleOutboxSelectKey<TTable>,
|
|
257
|
+
TPayloadKey extends DrizzleOutboxSelectKey<TTable>,
|
|
258
|
+
TSourceTypeKey extends DrizzleOutboxSelectKey<TTable>,
|
|
259
|
+
TSourceNameKey extends DrizzleOutboxSelectKey<TTable>,
|
|
260
|
+
TStatusKey extends DrizzleOutboxSelectKey<TTable>,
|
|
261
|
+
TAttemptsKey extends DrizzleOutboxSelectKey<TTable>,
|
|
262
|
+
TCreatedAtKey extends DrizzleOutboxSelectKey<TTable>,
|
|
263
|
+
TDispatchedAtKey extends DrizzleOutboxSelectKey<TTable> | undefined = undefined,
|
|
264
|
+
TLastErrorKey extends DrizzleOutboxSelectKey<TTable> | undefined = undefined,
|
|
265
|
+
>(
|
|
266
|
+
options: DrizzleTableOutboxOptions<
|
|
267
|
+
TDb,
|
|
268
|
+
TTransaction,
|
|
269
|
+
TTable,
|
|
270
|
+
TIdKey,
|
|
271
|
+
TEventNameKey,
|
|
272
|
+
TPayloadKey,
|
|
273
|
+
TSourceTypeKey,
|
|
274
|
+
TSourceNameKey,
|
|
275
|
+
TStatusKey,
|
|
276
|
+
TAttemptsKey,
|
|
277
|
+
TCreatedAtKey,
|
|
278
|
+
TDispatchedAtKey,
|
|
279
|
+
TLastErrorKey
|
|
280
|
+
>,
|
|
281
|
+
): EventOutbox => {
|
|
282
|
+
const codec: DrizzleOutboxCodec<
|
|
283
|
+
DrizzleOutboxSelectRow<TTable>,
|
|
284
|
+
DrizzleOutboxInsertRowShape<TTable>
|
|
285
|
+
> = {
|
|
286
|
+
toInsertRow(entry, now) {
|
|
287
|
+
const row = {
|
|
288
|
+
[options.columns.eventName]: entry.event.name,
|
|
289
|
+
[options.columns.payload]: JSON.stringify({
|
|
290
|
+
key: entry.event.key,
|
|
291
|
+
version: entry.event.version,
|
|
292
|
+
payload: entry.event.payload,
|
|
293
|
+
}),
|
|
294
|
+
[options.columns.sourceType]: entry.sourceType,
|
|
295
|
+
[options.columns.sourceName]: entry.sourceName,
|
|
296
|
+
[options.columns.status]: 'pending',
|
|
297
|
+
[options.columns.attempts]: 0,
|
|
298
|
+
[options.columns.createdAt]: now,
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return row as DrizzleOutboxInsertRowShape<TTable>
|
|
302
|
+
},
|
|
303
|
+
fromStoredRow(row) {
|
|
304
|
+
const dispatchedAtKey = options.columns.dispatchedAt
|
|
305
|
+
const lastErrorKey = options.columns.lastError
|
|
306
|
+
const rowRecord = row as Record<string, unknown>
|
|
307
|
+
const envelope = JSON.parse(row[options.columns.payload] as string) as {
|
|
308
|
+
key: string
|
|
309
|
+
version: number
|
|
310
|
+
payload: AnyDomainEventAction['payload']
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
id: row[options.columns.id] as number,
|
|
315
|
+
event: {
|
|
316
|
+
kind: 'DomainEventAction',
|
|
317
|
+
key: envelope.key,
|
|
318
|
+
name: row[options.columns.eventName] as string,
|
|
319
|
+
version: envelope.version,
|
|
320
|
+
payload: envelope.payload,
|
|
321
|
+
},
|
|
322
|
+
sourceType: row[options.columns.sourceType] as OutboxSourceType,
|
|
323
|
+
sourceName: row[options.columns.sourceName] as string,
|
|
324
|
+
status: row[options.columns.status] as OutboxRecordStatus,
|
|
325
|
+
attempts: row[options.columns.attempts] as number,
|
|
326
|
+
createdAt: row[options.columns.createdAt] as OutboxRecord['createdAt'],
|
|
327
|
+
dispatchedAt: dispatchedAtKey
|
|
328
|
+
? (rowRecord[dispatchedAtKey] as OutboxRecord['dispatchedAt'])
|
|
329
|
+
: undefined,
|
|
330
|
+
lastError: lastErrorKey
|
|
331
|
+
? (rowRecord[lastErrorKey] as OutboxRecord['lastError'])
|
|
332
|
+
: undefined,
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return createDrizzleOutbox<
|
|
338
|
+
TDb,
|
|
339
|
+
TTransaction,
|
|
340
|
+
DrizzleOutboxSelectRow<TTable>,
|
|
341
|
+
DrizzleOutboxInsertRowShape<TTable>
|
|
342
|
+
>({
|
|
343
|
+
db: options.db,
|
|
344
|
+
codec,
|
|
345
|
+
insertRows: (executor, rows) =>
|
|
346
|
+
options.operations.insertRows({
|
|
347
|
+
executor,
|
|
348
|
+
table: options.table,
|
|
349
|
+
rows,
|
|
350
|
+
}),
|
|
351
|
+
listRows: (executor) =>
|
|
352
|
+
options.operations.listRows({
|
|
353
|
+
executor,
|
|
354
|
+
table: options.table,
|
|
355
|
+
}),
|
|
356
|
+
listPendingRows: (executor, limit) =>
|
|
357
|
+
options.operations.listPendingRows({
|
|
358
|
+
executor,
|
|
359
|
+
table: options.table,
|
|
360
|
+
statusKey: options.columns.status,
|
|
361
|
+
pendingStatus: 'pending',
|
|
362
|
+
limit,
|
|
363
|
+
}),
|
|
364
|
+
markRowsDispatched: (executor, ids, dispatchedAt) =>
|
|
365
|
+
options.operations.markRowsDispatched({
|
|
366
|
+
executor,
|
|
367
|
+
table: options.table,
|
|
368
|
+
ids,
|
|
369
|
+
idKey: options.columns.id,
|
|
370
|
+
statusKey: options.columns.status,
|
|
371
|
+
attemptsKey: options.columns.attempts,
|
|
372
|
+
dispatchedAtKey: options.columns.dispatchedAt,
|
|
373
|
+
dispatchedAt,
|
|
374
|
+
}),
|
|
375
|
+
markRowsFailed: (executor, ids, error) =>
|
|
376
|
+
options.operations.markRowsFailed({
|
|
377
|
+
executor,
|
|
378
|
+
table: options.table,
|
|
379
|
+
ids,
|
|
380
|
+
idKey: options.columns.id,
|
|
381
|
+
statusKey: options.columns.status,
|
|
382
|
+
attemptsKey: options.columns.attempts,
|
|
383
|
+
lastErrorKey: options.columns.lastError,
|
|
384
|
+
error,
|
|
385
|
+
}),
|
|
386
|
+
})
|
|
387
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {
|
|
2
|
+
and,
|
|
3
|
+
eq,
|
|
4
|
+
getTableColumns,
|
|
5
|
+
inArray,
|
|
6
|
+
sql,
|
|
7
|
+
type InferSelectModel,
|
|
8
|
+
type Table,
|
|
9
|
+
} from 'drizzle-orm'
|
|
10
|
+
|
|
11
|
+
import type { AggregateState, AnyAggregateDefinition } from 'rebackend'
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
createDrizzleTableAggregateRepository,
|
|
15
|
+
type DrizzleSelectKey,
|
|
16
|
+
type DrizzleTableAggregateRepositoryOptions,
|
|
17
|
+
} from './repository'
|
|
18
|
+
import {
|
|
19
|
+
createDrizzleTableOutbox,
|
|
20
|
+
type DrizzleOutboxColumnMap,
|
|
21
|
+
type DrizzleTableOutboxOptions,
|
|
22
|
+
} from './outbox'
|
|
23
|
+
|
|
24
|
+
type QueryRows<TRow> = PromiseLike<readonly TRow[]>
|
|
25
|
+
|
|
26
|
+
type SelectWhereQuery<TRow> = QueryRows<TRow> & {
|
|
27
|
+
limit(limit: number): QueryRows<TRow>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type SelectFromQuery<TTable extends Table> = QueryRows<InferSelectModel<TTable>> & {
|
|
31
|
+
where(condition: unknown): SelectWhereQuery<InferSelectModel<TTable>>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type BasicDrizzleTableExecutor<TTable extends Table> = {
|
|
35
|
+
select(): {
|
|
36
|
+
from(table: TTable): SelectFromQuery<TTable>
|
|
37
|
+
}
|
|
38
|
+
insert(table: TTable): {
|
|
39
|
+
values(values: unknown): unknown
|
|
40
|
+
}
|
|
41
|
+
update(table: TTable): {
|
|
42
|
+
set(values: Record<string, unknown>): {
|
|
43
|
+
where(condition: unknown): unknown
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type DrizzleMutationSuccessJudge = (result: unknown) => boolean
|
|
49
|
+
|
|
50
|
+
export type BasicDrizzleTableAggregateRepositoryOptions<
|
|
51
|
+
TAggregate extends AnyAggregateDefinition,
|
|
52
|
+
TDb extends BasicDrizzleTableExecutor<TTable>,
|
|
53
|
+
TTransaction extends BasicDrizzleTableExecutor<TTable>,
|
|
54
|
+
TTable extends Table,
|
|
55
|
+
TIdentityRowKey extends DrizzleSelectKey<TTable>,
|
|
56
|
+
TIdentityStateKey extends Extract<keyof AggregateState<TAggregate>, string>,
|
|
57
|
+
TVersionRowKey extends DrizzleSelectKey<TTable> | undefined = undefined,
|
|
58
|
+
TVersionStateKey extends Extract<keyof AggregateState<TAggregate>, string> | undefined =
|
|
59
|
+
undefined,
|
|
60
|
+
> = Omit<
|
|
61
|
+
DrizzleTableAggregateRepositoryOptions<
|
|
62
|
+
TAggregate,
|
|
63
|
+
TDb,
|
|
64
|
+
TTransaction,
|
|
65
|
+
TTable,
|
|
66
|
+
TIdentityRowKey,
|
|
67
|
+
TIdentityStateKey,
|
|
68
|
+
TVersionRowKey,
|
|
69
|
+
TVersionStateKey
|
|
70
|
+
>,
|
|
71
|
+
'operations'
|
|
72
|
+
> & {
|
|
73
|
+
didUpdateSucceed: DrizzleMutationSuccessJudge
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const createBasicDrizzleTableAggregateRepository = <
|
|
77
|
+
TAggregate extends AnyAggregateDefinition,
|
|
78
|
+
TDb extends BasicDrizzleTableExecutor<TTable>,
|
|
79
|
+
TTransaction extends BasicDrizzleTableExecutor<TTable>,
|
|
80
|
+
TTable extends Table,
|
|
81
|
+
TIdentityRowKey extends DrizzleSelectKey<TTable>,
|
|
82
|
+
TIdentityStateKey extends Extract<keyof AggregateState<TAggregate>, string>,
|
|
83
|
+
TVersionRowKey extends DrizzleSelectKey<TTable> | undefined = undefined,
|
|
84
|
+
TVersionStateKey extends Extract<keyof AggregateState<TAggregate>, string> | undefined =
|
|
85
|
+
undefined,
|
|
86
|
+
>(
|
|
87
|
+
options: BasicDrizzleTableAggregateRepositoryOptions<
|
|
88
|
+
TAggregate,
|
|
89
|
+
TDb,
|
|
90
|
+
TTransaction,
|
|
91
|
+
TTable,
|
|
92
|
+
TIdentityRowKey,
|
|
93
|
+
TIdentityStateKey,
|
|
94
|
+
TVersionRowKey,
|
|
95
|
+
TVersionStateKey
|
|
96
|
+
>,
|
|
97
|
+
) => {
|
|
98
|
+
const columns = getTableColumns(options.table)
|
|
99
|
+
const identityColumn = columns[options.identity.rowKey]
|
|
100
|
+
const versionRowKey = options.version?.rowKey
|
|
101
|
+
const versionColumn = versionRowKey ? columns[versionRowKey] : undefined
|
|
102
|
+
|
|
103
|
+
const repositoryOptions: DrizzleTableAggregateRepositoryOptions<
|
|
104
|
+
TAggregate,
|
|
105
|
+
TDb,
|
|
106
|
+
TTransaction,
|
|
107
|
+
TTable,
|
|
108
|
+
TIdentityRowKey,
|
|
109
|
+
TIdentityStateKey,
|
|
110
|
+
TVersionRowKey,
|
|
111
|
+
TVersionStateKey
|
|
112
|
+
> = {
|
|
113
|
+
...options,
|
|
114
|
+
operations: {
|
|
115
|
+
async findById({ executor, table, id }) {
|
|
116
|
+
const rows = await executor.select().from(table).where(eq(identityColumn, id)).limit(1)
|
|
117
|
+
return rows[0] ?? null
|
|
118
|
+
},
|
|
119
|
+
async insertOne({ executor, table, row }) {
|
|
120
|
+
await executor.insert(table).values(row)
|
|
121
|
+
},
|
|
122
|
+
async updateById({ executor, table, id, row, optimisticLock, expectedVersion }) {
|
|
123
|
+
const whereCondition =
|
|
124
|
+
optimisticLock && versionColumn && expectedVersion !== undefined
|
|
125
|
+
? and(eq(identityColumn, id), eq(versionColumn, expectedVersion))
|
|
126
|
+
: eq(identityColumn, id)
|
|
127
|
+
|
|
128
|
+
const result = await executor.update(table).set(row).where(whereCondition)
|
|
129
|
+
return options.didUpdateSucceed(result)
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return createDrizzleTableAggregateRepository(repositoryOptions)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export type BasicDrizzleTableOutboxOptions<
|
|
138
|
+
TDb extends BasicDrizzleTableExecutor<TTable>,
|
|
139
|
+
_TTransaction extends BasicDrizzleTableExecutor<TTable>,
|
|
140
|
+
TTable extends Table,
|
|
141
|
+
TIdKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
142
|
+
TEventNameKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
143
|
+
TPayloadKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
144
|
+
TSourceTypeKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
145
|
+
TSourceNameKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
146
|
+
TStatusKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
147
|
+
TAttemptsKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
148
|
+
TCreatedAtKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
149
|
+
TDispatchedAtKey extends Extract<keyof InferSelectModel<TTable>, string> | undefined = undefined,
|
|
150
|
+
TLastErrorKey extends Extract<keyof InferSelectModel<TTable>, string> | undefined = undefined,
|
|
151
|
+
> = {
|
|
152
|
+
db: TDb
|
|
153
|
+
table: TTable
|
|
154
|
+
columns: DrizzleOutboxColumnMap<
|
|
155
|
+
TTable,
|
|
156
|
+
TIdKey,
|
|
157
|
+
TEventNameKey,
|
|
158
|
+
TPayloadKey,
|
|
159
|
+
TSourceTypeKey,
|
|
160
|
+
TSourceNameKey,
|
|
161
|
+
TStatusKey,
|
|
162
|
+
TAttemptsKey,
|
|
163
|
+
TCreatedAtKey,
|
|
164
|
+
TDispatchedAtKey,
|
|
165
|
+
TLastErrorKey
|
|
166
|
+
>
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const createBasicDrizzleTableOutbox = <
|
|
170
|
+
TDb extends BasicDrizzleTableExecutor<TTable>,
|
|
171
|
+
_TTransaction extends BasicDrizzleTableExecutor<TTable>,
|
|
172
|
+
TTable extends Table,
|
|
173
|
+
TIdKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
174
|
+
TEventNameKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
175
|
+
TPayloadKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
176
|
+
TSourceTypeKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
177
|
+
TSourceNameKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
178
|
+
TStatusKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
179
|
+
TAttemptsKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
180
|
+
TCreatedAtKey extends Extract<keyof InferSelectModel<TTable>, string>,
|
|
181
|
+
TDispatchedAtKey extends Extract<keyof InferSelectModel<TTable>, string> | undefined = undefined,
|
|
182
|
+
TLastErrorKey extends Extract<keyof InferSelectModel<TTable>, string> | undefined = undefined,
|
|
183
|
+
>(
|
|
184
|
+
options: BasicDrizzleTableOutboxOptions<
|
|
185
|
+
TDb,
|
|
186
|
+
_TTransaction,
|
|
187
|
+
TTable,
|
|
188
|
+
TIdKey,
|
|
189
|
+
TEventNameKey,
|
|
190
|
+
TPayloadKey,
|
|
191
|
+
TSourceTypeKey,
|
|
192
|
+
TSourceNameKey,
|
|
193
|
+
TStatusKey,
|
|
194
|
+
TAttemptsKey,
|
|
195
|
+
TCreatedAtKey,
|
|
196
|
+
TDispatchedAtKey,
|
|
197
|
+
TLastErrorKey
|
|
198
|
+
>,
|
|
199
|
+
) => {
|
|
200
|
+
const columns = getTableColumns(options.table)
|
|
201
|
+
const idColumn = columns[options.columns.id]
|
|
202
|
+
const attemptsColumn = columns[options.columns.attempts]
|
|
203
|
+
const statusColumn = columns[options.columns.status]
|
|
204
|
+
|
|
205
|
+
const outboxOptions: DrizzleTableOutboxOptions<
|
|
206
|
+
TDb,
|
|
207
|
+
_TTransaction,
|
|
208
|
+
TTable,
|
|
209
|
+
TIdKey,
|
|
210
|
+
TEventNameKey,
|
|
211
|
+
TPayloadKey,
|
|
212
|
+
TSourceTypeKey,
|
|
213
|
+
TSourceNameKey,
|
|
214
|
+
TStatusKey,
|
|
215
|
+
TAttemptsKey,
|
|
216
|
+
TCreatedAtKey,
|
|
217
|
+
TDispatchedAtKey,
|
|
218
|
+
TLastErrorKey
|
|
219
|
+
> = {
|
|
220
|
+
...options,
|
|
221
|
+
operations: {
|
|
222
|
+
async insertRows({ executor, table, rows }) {
|
|
223
|
+
await executor.insert(table).values(rows)
|
|
224
|
+
},
|
|
225
|
+
async listRows({ executor, table }) {
|
|
226
|
+
return executor.select().from(table)
|
|
227
|
+
},
|
|
228
|
+
async listPendingRows({ executor, table, pendingStatus, limit }) {
|
|
229
|
+
const query = executor.select().from(table).where(eq(statusColumn, pendingStatus))
|
|
230
|
+
return limit === undefined ? query : query.limit(limit)
|
|
231
|
+
},
|
|
232
|
+
async markRowsDispatched({
|
|
233
|
+
executor,
|
|
234
|
+
table,
|
|
235
|
+
ids,
|
|
236
|
+
statusKey,
|
|
237
|
+
attemptsKey,
|
|
238
|
+
dispatchedAtKey,
|
|
239
|
+
dispatchedAt,
|
|
240
|
+
}) {
|
|
241
|
+
const updateRow: Record<string, unknown> = {
|
|
242
|
+
[statusKey]: 'dispatched',
|
|
243
|
+
[attemptsKey]: sql`${attemptsColumn} + 1`,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (dispatchedAtKey) {
|
|
247
|
+
updateRow[dispatchedAtKey] = dispatchedAt
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
await executor
|
|
251
|
+
.update(table)
|
|
252
|
+
.set(updateRow)
|
|
253
|
+
.where(inArray(idColumn, [...ids]))
|
|
254
|
+
},
|
|
255
|
+
async markRowsFailed({ executor, table, ids, statusKey, attemptsKey, lastErrorKey, error }) {
|
|
256
|
+
const updateRow: Record<string, unknown> = {
|
|
257
|
+
[statusKey]: 'failed',
|
|
258
|
+
[attemptsKey]: sql`${attemptsColumn} + 1`,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (lastErrorKey) {
|
|
262
|
+
updateRow[lastErrorKey] = error
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await executor
|
|
266
|
+
.update(table)
|
|
267
|
+
.set(updateRow)
|
|
268
|
+
.where(inArray(idColumn, [...ids]))
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return createDrizzleTableOutbox(outboxOptions)
|
|
274
|
+
}
|