@render-foundation/utils 0.0.58 → 0.0.60

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.
@@ -0,0 +1,648 @@
1
+ import { Pool } from 'pg';
2
+ import { Kysely, NoResultError, PostgresDialect, sql, } from 'kysely';
3
+ import { consoleLogger } from '../../logger';
4
+ const transferTypToChan = (t) => {
5
+ if (t == 'open_compute_rewards') {
6
+ return 'open_compute';
7
+ }
8
+ if (t == 'node_operator_rewards') {
9
+ return 'node_operator';
10
+ }
11
+ return 'upgrade';
12
+ };
13
+ const transferFromDB = (o) => {
14
+ return {
15
+ id: o.id,
16
+ solKey: o.sol_key,
17
+ solTx: o.sol_tx,
18
+ sqTx: o.sq_tx,
19
+ createdAt: o.created_at,
20
+ payedAt: o.payed_at,
21
+ amountDue: BigInt(o.amount_due),
22
+ amountPayed: o.amount_payed ? BigInt(o.amount_payed) : undefined,
23
+ operatorRewards: o.transfer_type == 'node_operator_rewards'
24
+ ? {
25
+ epochId: o.epoch_id,
26
+ startTime: o.start_time,
27
+ endTime: o.end_time,
28
+ }
29
+ : undefined,
30
+ computeClientRewards: o.transfer_type == 'open_compute_rewards'
31
+ ? {
32
+ epochId: o.epoch_id,
33
+ deviceId: o.device_id,
34
+ }
35
+ : undefined,
36
+ upgradeRewards: o.transfer_type == 'upgrade_rewards' && !!o.epoch_id
37
+ ? {
38
+ epochId: o.epoch_id,
39
+ epochStartTime: o.epoch_start_time,
40
+ points: BigInt(o.points),
41
+ }
42
+ : undefined,
43
+ };
44
+ };
45
+ export const SOL_TX_TABLE = 'sol_tx';
46
+ export const EPOCH_TABLE = 'epoch';
47
+ export const MANUAL_BURN_TABLE = 'manual_burn';
48
+ export const ENTITY_TABLE = 'entity';
49
+ export const SOL_TRANSFER_TABLE = 'sol_transfer';
50
+ export const SOL_TRANSFER_INFO_TABLE = 'sol_transfer_info';
51
+ export const JOB_ID_TABLE = 'job_id';
52
+ export const JOB_TABLE = 'job';
53
+ export const BURN_TABLE = 'burn';
54
+ export const pgClient = (config) => {
55
+ const dialect = new PostgresDialect({
56
+ pool: new Pool({
57
+ ...config,
58
+ port: config.port ?? 5432,
59
+ max: config.max ?? 10,
60
+ }),
61
+ });
62
+ // Database interface is passed to Kysely's constructor, and from now on, Kysely
63
+ // knows your database structure.
64
+ // Dialect is passed to Kysely's constructor, and from now on, Kysely knows how
65
+ // to communicate with your database.
66
+ const db = new Kysely({
67
+ dialect,
68
+ });
69
+ const getOrInsertEntity = async (db, p, log = consoleLogger) => {
70
+ const res = (await db
71
+ .selectFrom(ENTITY_TABLE)
72
+ .select('id')
73
+ .where('sol_key', '=', p.solKey)
74
+ .executeTakeFirst());
75
+ if (res) {
76
+ log.debug(`already found entity ${res.id}`);
77
+ return res.id;
78
+ }
79
+ const { id } = (await db
80
+ .insertInto(ENTITY_TABLE)
81
+ .values({
82
+ sol_key: p.solKey,
83
+ })
84
+ .onConflict((oc) => oc.column('sol_key').doNothing())
85
+ .returning((eb) => ['id as id'])
86
+ .executeTakeFirst());
87
+ log.debug(`inserted entity ${id}`);
88
+ return id;
89
+ };
90
+ const getOrInsertTx = async (db, p, log = consoleLogger) => {
91
+ const res = await db
92
+ .selectFrom(SOL_TX_TABLE)
93
+ .select('id')
94
+ .where('sig', '=', p.sig)
95
+ .executeTakeFirst();
96
+ if (res) {
97
+ log.debug(`already found tx ${res.id}`);
98
+ return res.id;
99
+ }
100
+ const { id } = (await db
101
+ .insertInto(SOL_TX_TABLE)
102
+ .values({
103
+ sig: p.sig,
104
+ executed_at: p.executedAt,
105
+ })
106
+ .onConflict((oc) => oc.column('sig').doNothing())
107
+ .returning((eb) => ['id as id'])
108
+ .executeTakeFirst());
109
+ log.debug(`inserted tx ${id}`);
110
+ return id;
111
+ };
112
+ const getOrInsertEpoch = async (db, p, log = consoleLogger) => {
113
+ const res = await db
114
+ .selectFrom(EPOCH_TABLE)
115
+ .select('id')
116
+ .where('ui', '=', String(p.uiEpochId))
117
+ .where('channel', '=', p.channel)
118
+ .executeTakeFirst();
119
+ if (res) {
120
+ log.debug(`already found epoch ${res.id}`);
121
+ return res.id;
122
+ }
123
+ const { id } = (await db
124
+ .insertInto(EPOCH_TABLE)
125
+ .values({
126
+ ui: p.uiEpochId,
127
+ channel: p.channel,
128
+ start_time: p.startTime,
129
+ end_time: p.endTime,
130
+ total_rndr: p.totalRndr,
131
+ })
132
+ .onConflict((oc) => oc.columns(['ui', 'channel']).doNothing())
133
+ .returning((eb) => ['id as id'])
134
+ .executeTakeFirst());
135
+ log.debug(`inserted epoch ${id}`);
136
+ return id;
137
+ };
138
+ const createTransfers = async (db, transfers, log = consoleLogger, totalRndr = BigInt(0)) => {
139
+ const epochIds = [];
140
+ for (const t of transfers) {
141
+ let solTxId;
142
+ if (t.solTx) {
143
+ solTxId = await getOrInsertTx(db, {
144
+ sig: t.solTx,
145
+ executedAt: t.payedAt,
146
+ }, log);
147
+ }
148
+ const entityId = await getOrInsertEntity(db, { solKey: t.solKey }, log);
149
+ const { id: transferId } = (await db
150
+ .insertInto(SOL_TRANSFER_TABLE)
151
+ .values({
152
+ sol_tx_id: solTxId,
153
+ entity_id: entityId,
154
+ channel: transferTypToChan((() => {
155
+ if (t.operatorRewards) {
156
+ return 'node_operator_rewards';
157
+ }
158
+ else if (t.upgradeRewards) {
159
+ return 'upgrade_rewards';
160
+ }
161
+ else if (t.computeClientRewards) {
162
+ return 'open_compute_rewards';
163
+ }
164
+ throw new Error('wtf');
165
+ })()),
166
+ created_at: t.createdAt,
167
+ amount_due: BigInt(t.amountDue),
168
+ amount_payed: t.amountPayed ? BigInt(t.amountPayed) : 0,
169
+ })
170
+ .returning((eb) => ['id as id'])
171
+ .executeTakeFirst());
172
+ log.info(`inserted transfer ${JSON.stringify(t, (key, value) => typeof value === 'bigint' ? value.toString() : value)}`);
173
+ let epochId;
174
+ let deviceId;
175
+ let points;
176
+ let startTime;
177
+ let endTime;
178
+ let channel;
179
+ if (t.operatorRewards) {
180
+ log.info(`inserting operator rewards ${transferId}`);
181
+ epochId = t.operatorRewards.epochId;
182
+ startTime = t.operatorRewards.startTime;
183
+ endTime = t.operatorRewards.endTime;
184
+ channel = 'node_operator';
185
+ }
186
+ else if (t.computeClientRewards) {
187
+ log.info(`inserting compute client rewards ${transferId}`);
188
+ epochId = t.computeClientRewards.epochId;
189
+ deviceId = t.computeClientRewards.deviceId;
190
+ channel = 'open_compute';
191
+ }
192
+ else if (t.upgradeRewards) {
193
+ log.info(`inserting upgrade rewards ${transferId}`);
194
+ channel = 'upgrade';
195
+ epochId = t.upgradeRewards.epochId;
196
+ points = t.upgradeRewards.points;
197
+ }
198
+ const epochIdDb = await getOrInsertEpoch(db, {
199
+ channel,
200
+ startTime,
201
+ endTime,
202
+ uiEpochId: epochId,
203
+ totalRndr,
204
+ }, log);
205
+ await db
206
+ .insertInto(SOL_TRANSFER_INFO_TABLE)
207
+ .values({
208
+ transfer_id: transferId,
209
+ device_id: deviceId,
210
+ points,
211
+ epoch_id: BigInt(epochIdDb),
212
+ })
213
+ .executeTakeFirst();
214
+ epochIds.push({ epochDbId: epochIdDb });
215
+ }
216
+ return epochIds;
217
+ };
218
+ return {
219
+ db,
220
+ async saveNodeOperatorEpoch(epochId, transfers, jobIds, totalRndr, log = consoleLogger) {
221
+ await db
222
+ .transaction()
223
+ .setIsolationLevel('repeatable read')
224
+ .execute(async (trx) => {
225
+ const epochDbIds = await createTransfers(trx, transfers, log, totalRndr);
226
+ log.info(`inserted transfers`);
227
+ for (const id of jobIds) {
228
+ await trx
229
+ .insertInto(JOB_ID_TABLE)
230
+ .values({
231
+ id: BigInt(id),
232
+ epoch_id: epochDbIds[0].epochDbId,
233
+ })
234
+ .executeTakeFirstOrThrow();
235
+ }
236
+ log.debug(`saved epoch`);
237
+ /*const now = new Date(Date.now())
238
+ const res = await trx
239
+ .updateTable(EPOCH_TABLE)
240
+ .set({
241
+ saved_at: now,
242
+ total_rndr: totalRndr,
243
+ })
244
+ .where('channel')
245
+ .where('ui', '=', epochId.toString())
246
+ .where('', '=')
247
+ .executeTakeFirstOrThrow()*/
248
+ });
249
+ return;
250
+ },
251
+ async getLatestNodeOperatorEpoch(log = consoleLogger) {
252
+ const now = new Date(Date.now());
253
+ try {
254
+ const res = await db
255
+ .selectFrom(EPOCH_TABLE)
256
+ .selectAll()
257
+ .where('channel', '=', 'node_operator')
258
+ .where('end_time', '<=', now)
259
+ .where('saved_at', 'is', null)
260
+ .orderBy('end_time asc')
261
+ .limit(1)
262
+ .executeTakeFirstOrThrow();
263
+ return {
264
+ epochId: BigInt(res.ui),
265
+ startedAt: res.start_time,
266
+ endedAt: res.end_time,
267
+ savedAt: res.saved_at ?? undefined,
268
+ };
269
+ }
270
+ catch (e) {
271
+ if (e instanceof NoResultError) {
272
+ log.info(`no node operator epoch found`);
273
+ return undefined;
274
+ }
275
+ throw e;
276
+ }
277
+ },
278
+ async getOutstanding(f, log = consoleLogger) {
279
+ const { typ, nullKey, due } = f;
280
+ //select sol_key, sum(amount_due)-sum(amount_payed) from transfer group by sol_key;
281
+ let q = db
282
+ .selectFrom(SOL_TRANSFER_TABLE)
283
+ .innerJoin(ENTITY_TABLE, 'sol_transfer.entity_id', 'entity.id')
284
+ .select(({ fn, val, ref }) => [
285
+ 'entity.sol_key',
286
+ sql `${fn.sum('sol_transfer.amount_due')} -
287
+ ${fn.sum('sol_transfer.amount_payed')}`.as('due'),
288
+ ])
289
+ .where('sol_transfer.channel', '=', transferTypToChan(typ))
290
+ .groupBy(['entity.sol_key']);
291
+ if (!nullKey) {
292
+ q = q.where('entity.sol_key', 'is not', null);
293
+ }
294
+ if (due) {
295
+ q = q.having(({ fn, ref }) => sql `${fn.sum('sol_transfer.amount_due')} -
296
+ ${fn.sum('sol_transfer.amount_payed')}
297
+ >
298
+ 0`);
299
+ }
300
+ const res = await q.execute();
301
+ log.info(`outstanding query ${q.compile().sql} params ${JSON.stringify(f)} ${res.length} rows`);
302
+ const out = {};
303
+ for (const r of res) {
304
+ out[r.sol_key ?? ''] = BigInt(r.due);
305
+ }
306
+ return out;
307
+ },
308
+ async fetchTransfers(f, log = consoleLogger) {
309
+ let base = db
310
+ .selectFrom(SOL_TRANSFER_TABLE)
311
+ .selectAll(SOL_TRANSFER_TABLE)
312
+ .innerJoin(SOL_TRANSFER_INFO_TABLE, 'sol_transfer_info.transfer_id', 'sol_transfer.id')
313
+ .select(['sol_transfer_info.device_id', 'sol_transfer_info.points'])
314
+ .innerJoin(ENTITY_TABLE, 'entity.id', 'sol_transfer.entity_id')
315
+ .select(['entity.sol_key'])
316
+ .where('sol_transfer.channel', '=', transferTypToChan(f.typ));
317
+ if (f.solKey) {
318
+ base = base.where('entity.sol_key', '=', f.solKey);
319
+ }
320
+ if (f.payed != undefined) {
321
+ base = base
322
+ .leftJoin(SOL_TX_TABLE, 'sol_tx.id', 'sol_transfer.sol_tx_id')
323
+ .select(['sol_tx.sig', 'sol_tx.executed_at'])
324
+ .where('sol_transfer.sol_tx_id', f.payed ? 'is not' : 'is', null);
325
+ }
326
+ if (f.createdBefore != undefined) {
327
+ base = base.where('sol_transfer.created_at', '<=', f.createdBefore);
328
+ }
329
+ if (f.epochId) {
330
+ base = base
331
+ .innerJoin(EPOCH_TABLE, 'epoch.id', 'sol_transfer_info.epoch_id')
332
+ .select(['epoch.ui'])
333
+ .where('epoch.ui', '=', String(f.epochId));
334
+ }
335
+ if (f.sort) {
336
+ base = base.orderBy('entity.sol_key asc');
337
+ }
338
+ const res = await base.execute();
339
+ log.info(`fetched ${base.compile().sql} params ${JSON.stringify(f)} ${res.length} transfers`);
340
+ return res.map((r) => transferFromDB(r));
341
+ },
342
+ async payoutTransfers(idToAmt, payedAt, solTx, log = consoleLogger) {
343
+ await db
344
+ .transaction()
345
+ .setIsolationLevel('repeatable read')
346
+ .execute(async (tx) => {
347
+ const alreadyUpdatedIds = (await tx
348
+ .selectFrom(SOL_TRANSFER_TABLE)
349
+ .innerJoin(SOL_TX_TABLE, 'sol_transfer.sol_tx_id', 'sol_tx.id')
350
+ .select(['sol_transfer.id'])
351
+ .where('sol_transfer.id', 'in', Object.keys(idToAmt))
352
+ .where('sol_tx.sig', 'is not', null)
353
+ .execute()).map(({ id }) => id);
354
+ for (const [id, amt] of Object.entries(idToAmt)) {
355
+ if (alreadyUpdatedIds.includes(id)) {
356
+ log.info(`transfer ${id} already marked payed out. skipping`);
357
+ continue;
358
+ }
359
+ const solTxId = await getOrInsertTx(tx, { sig: solTx, executedAt: payedAt }, log);
360
+ const res = await tx
361
+ .updateTable(SOL_TRANSFER_TABLE)
362
+ .set({
363
+ sol_tx_id: solTxId,
364
+ amount_payed: amt,
365
+ })
366
+ .where('id', '=', id.toString())
367
+ .executeTakeFirstOrThrow();
368
+ log.info(`marked ${res.numUpdatedRows} transfer payed out`);
369
+ }
370
+ });
371
+ },
372
+ async payoutTransfer(id, payedAt, solTx, amtPayed, log = consoleLogger) {
373
+ await db
374
+ .transaction()
375
+ .setIsolationLevel('repeatable read')
376
+ .execute(async (tx) => {
377
+ const solTxId = await getOrInsertTx(tx, { sig: solTx, executedAt: payedAt }, log);
378
+ const res = await tx
379
+ .updateTable(SOL_TRANSFER_TABLE)
380
+ .set({
381
+ sol_tx_id: solTxId,
382
+ amount_payed: amtPayed,
383
+ })
384
+ .where('id', '=', id.toString())
385
+ .executeTakeFirst();
386
+ log.info(`marked ${res.numUpdatedRows} transfer payed out`);
387
+ });
388
+ },
389
+ async createTransfers(transfers, log = consoleLogger) {
390
+ await db
391
+ .transaction()
392
+ .setIsolationLevel('repeatable read')
393
+ .execute(async (trx) => {
394
+ await createTransfers(trx, transfers, log);
395
+ });
396
+ },
397
+ async fetchInfoForEpoch(epochId, log = consoleLogger) {
398
+ const q = db
399
+ .selectFrom(JOB_ID_TABLE)
400
+ .innerJoin(EPOCH_TABLE, 'epoch.id', 'job_id.epoch_id')
401
+ .leftJoin(MANUAL_BURN_TABLE, 'manual_burn.epoch_id', 'epoch.id')
402
+ .where('epoch.ui', '=', String(epochId))
403
+ .where('epoch.channel', '=', 'node_operator')
404
+ .select([
405
+ 'job_id.id',
406
+ 'epoch.total_rndr',
407
+ 'epoch.start_time',
408
+ 'epoch.end_time',
409
+ 'manual_burn.burned',
410
+ 'manual_burn.tx_sig',
411
+ ])
412
+ .orderBy('job_id.id asc');
413
+ log.debug(`fetch epoch info q ${q.compile().sql} epochId ${epochId}`);
414
+ const res = await q.execute();
415
+ if (res.length == 0) {
416
+ throw new Error(`epoch ${epochId} not found`);
417
+ }
418
+ return {
419
+ jobIds: res.map((r) => Number(r.id)),
420
+ totalRndr: BigInt(res[0].total_rndr),
421
+ startTime: res[0].start_time,
422
+ endTime: res[0].end_time,
423
+ burned: BigInt(res[0].burned ?? 0),
424
+ burnTx: res[0].tx_sig,
425
+ };
426
+ },
427
+ async updateComputeClientSolKey(deviceId, solKey, log = consoleLogger) {
428
+ const q = db
429
+ .updateTable(`${ENTITY_TABLE} as e`)
430
+ .set({
431
+ sol_key: solKey,
432
+ })
433
+ .from(`${SOL_TRANSFER_TABLE} as t`)
434
+ .innerJoin(SOL_TRANSFER_INFO_TABLE, 'sol_transfer_info.transfer_id', 't.id')
435
+ //.whereRef('t.id', '=', 'o.transfer_id')
436
+ .where('e.sol_key', 'is', null)
437
+ .where('sol_transfer_info.device_id', '=', deviceId);
438
+ const sql = await q.compile();
439
+ log.info(`sql ${sql.sql} params ${sql.parameters}`);
440
+ const res = await q.executeTakeFirstOrThrow();
441
+ log.info(`updated ${res.numUpdatedRows} device-id sol_keys`);
442
+ },
443
+ async markJobsBurnt(trx, p, log = consoleLogger) {
444
+ const solTxId = await getOrInsertTx(trx, { sig: p.solTx, executedAt: p.executedAt }, log);
445
+ const { id } = await trx
446
+ .insertInto(BURN_TABLE)
447
+ .values({
448
+ sol_tx_id: solTxId,
449
+ priced_at: p.pricedAt,
450
+ usdc_spent: p.usdcSpent,
451
+ quote_amt: p.quoteAmt,
452
+ burned: p.burned,
453
+ })
454
+ //.onConflict((oc) => oc.column('sol_tx').doNothing())
455
+ .returning((eb) => ['id as id'])
456
+ .executeTakeFirstOrThrow();
457
+ const jobIdsStr = p.jobsIds.map(String);
458
+ const res = await trx
459
+ .updateTable(JOB_TABLE)
460
+ .where('id', 'in', jobIdsStr)
461
+ .set('burn_id', id)
462
+ .executeTakeFirstOrThrow();
463
+ log.debug(`set burn_id on ${res.numUpdatedRows} jobs`);
464
+ return;
465
+ },
466
+ async produceBurnedJobs(jobs, log = consoleLogger) {
467
+ await db.transaction().execute(async (trx) => {
468
+ let num = BigInt(0);
469
+ for (const { amount, completedAt, jobId } of jobs) {
470
+ const res = await trx
471
+ .insertInto(JOB_TABLE)
472
+ .values({
473
+ id: jobId,
474
+ render_amt: amount,
475
+ completed_at: completedAt,
476
+ inserted_at: new Date(Date.now()),
477
+ })
478
+ .onConflict((oc) => oc.column('id').doNothing())
479
+ .executeTakeFirstOrThrow();
480
+ num += res.numInsertedOrUpdatedRows ?? BigInt(0);
481
+ }
482
+ log.debug(`inserted ${num} of ${jobs.length} burn jobs`);
483
+ });
484
+ },
485
+ async markJobsBurned(p, log = consoleLogger) {
486
+ let { markedAt, solTx } = p;
487
+ let res = await db
488
+ .updateTable(BURN_TABLE)
489
+ .set({ marked_burned_at: markedAt })
490
+ .from(SOL_TX_TABLE)
491
+ .whereRef('burn.sol_tx_id', '=', 'sol_tx.id')
492
+ .where('sol_tx.sig', '=', solTx)
493
+ .executeTakeFirstOrThrow();
494
+ log.debug(`set ${res.numUpdatedRows} ${solTx} -> marked_burned_at ${markedAt.toISOString()}`);
495
+ },
496
+ async consumeAndLockBurnJobs(limit, lowerRndrThreshold, upperRndrThreshold, log = consoleLogger) {
497
+ const trx = await begin(db);
498
+ const q = trx.trx
499
+ .selectFrom(trx.trx
500
+ .selectFrom(JOB_TABLE)
501
+ .select(({ fn, val, ref }) => [
502
+ 'id',
503
+ fn
504
+ .sum('render_amt')
505
+ .over((ob) => ob.orderBy(`completed_at`, `asc`))
506
+ .as('total'),
507
+ ])
508
+ .where('burn_id', 'is', null)
509
+ .as('t'))
510
+ .select('id')
511
+ .where('total', '>', lowerRndrThreshold)
512
+ .where('total', '<', upperRndrThreshold);
513
+ log.debug(`outstanding jobs q: ${q.compile().sql} ${JSON.stringify({
514
+ limit,
515
+ lowerRndrThreshold,
516
+ upperRndrThreshold,
517
+ })}`);
518
+ const idRs = await q.execute();
519
+ const ids = idRs.map((x) => x.id);
520
+ log.debug(`${ids.length} candidate jobs for burn. locking up to ${limit} jobs`);
521
+ if (ids.length == 0) {
522
+ return { trx, jobs: [] };
523
+ }
524
+ const rows = await trx.trx
525
+ .selectFrom(JOB_TABLE)
526
+ .selectAll()
527
+ .where('id', 'in', ids)
528
+ .limit(limit)
529
+ .forUpdate()
530
+ .skipLocked()
531
+ .execute();
532
+ log.debug(`locked ${rows.length} jobs`);
533
+ const jobs = [];
534
+ for (const r of rows) {
535
+ jobs.push({
536
+ id: Number(r.id),
537
+ completedAt: r.completed_at,
538
+ rndrUsed: BigInt(r.render_amt),
539
+ });
540
+ }
541
+ return { trx, jobs };
542
+ },
543
+ async incrBuyBurnMetrics(log = consoleLogger) {
544
+ const res = await db
545
+ .selectFrom(JOB_TABLE)
546
+ .select('completed_at')
547
+ .orderBy('completed_at asc')
548
+ .where('burn_id', 'is', null)
549
+ .execute();
550
+ if (res.length == 0) {
551
+ return { outstandingJobs: 0 };
552
+ }
553
+ return {
554
+ outstandingJobs: res.length,
555
+ oldestJobFinishedAt: res[0].completed_at ?? undefined,
556
+ };
557
+ },
558
+ async deleteAll() {
559
+ if (config.database.includes('render')) {
560
+ throw new Error('cannot nuke from deployed database. call from tests only');
561
+ }
562
+ await Promise.all([
563
+ db
564
+ .deleteFrom(SOL_TX_TABLE)
565
+ .where('id', 'is not', null)
566
+ .executeTakeFirst(),
567
+ db
568
+ .deleteFrom(SOL_TRANSFER_TABLE)
569
+ .where('id', 'is not', null)
570
+ .executeTakeFirst(),
571
+ db
572
+ .deleteFrom(SOL_TRANSFER_INFO_TABLE)
573
+ .where('transfer_id', 'is not', null)
574
+ .executeTakeFirst(),
575
+ db
576
+ .deleteFrom(EPOCH_TABLE)
577
+ .where('id', 'is not', null)
578
+ .executeTakeFirst(),
579
+ db
580
+ .deleteFrom(ENTITY_TABLE)
581
+ .where('id', 'is not', null)
582
+ .executeTakeFirst(),
583
+ db
584
+ .deleteFrom(MANUAL_BURN_TABLE)
585
+ .where('epoch_id', 'is not', null)
586
+ .executeTakeFirst(),
587
+ db
588
+ .deleteFrom(JOB_ID_TABLE)
589
+ .where('id', 'is not', null)
590
+ .executeTakeFirst(),
591
+ db.deleteFrom(JOB_TABLE).where('id', 'is not', null).executeTakeFirst(),
592
+ db
593
+ .deleteFrom(BURN_TABLE)
594
+ .where('id', 'is not', null)
595
+ .executeTakeFirst(),
596
+ ]);
597
+ },
598
+ };
599
+ };
600
+ async function begin(db) {
601
+ const connection = new Deferred();
602
+ const result = new Deferred();
603
+ // Do NOT await this line.
604
+ db.transaction()
605
+ .setIsolationLevel('repeatable read')
606
+ .execute((trx) => {
607
+ connection.resolve(trx);
608
+ return result.promise;
609
+ })
610
+ .catch((err) => {
611
+ // Don't do anything here. Just swallow the exception.
612
+ });
613
+ const trx = await connection.promise;
614
+ return {
615
+ trx,
616
+ commit() {
617
+ result.resolve(null);
618
+ },
619
+ rollback() {
620
+ result.reject(new Error('rollback'));
621
+ },
622
+ };
623
+ }
624
+ export class Deferred {
625
+ #promise;
626
+ #resolve;
627
+ #reject;
628
+ constructor() {
629
+ this.#promise = new Promise((resolve, reject) => {
630
+ this.#reject = reject;
631
+ this.#resolve = resolve;
632
+ });
633
+ }
634
+ get promise() {
635
+ return this.#promise;
636
+ }
637
+ resolve = (value) => {
638
+ if (this.#resolve) {
639
+ this.#resolve(value);
640
+ }
641
+ };
642
+ reject = (reason) => {
643
+ if (this.#reject) {
644
+ this.#reject(reason);
645
+ }
646
+ };
647
+ }
648
+ //# sourceMappingURL=v2.js.map