import type { Selectable } from 'kysely' import { nanoid } from 'nanoid' import type { Address } from 'viem' import type * as Db from '../Db.js' import type * as db_Schema from '../Schema.js' /** Columns of the `sponsorship_reconciliation_cursors` table. */ export type Table = db_Schema.SponsorshipReconciliationCursor /** A durable failed-sponsorship scan position. */ export type Record = Selectable /** Loads or initializes a versioned scan for one chain and fee payer. */ export async function load(db: Db.Db, options: load.Options): Promise { await db.kysely .insertInto('sponsorship_reconciliation_cursors') .values({ ...options, blockNumber: '0', complete: true, retryCount: 0, retryKey: null, revision: nanoid(), transactionIndex: 0, updatedAt: new Date().toISOString(), }) .onConflict((oc) => oc.columns(['chainId', 'feePayer']).doNothing()) .execute() return db.kysely .selectFrom('sponsorship_reconciliation_cursors') .selectAll() .where('chainId', '=', options.chainId) .where('feePayer', '=', options.feePayer) .executeTakeFirstOrThrow() } export declare namespace load { /** Identity of one failed-sponsorship scan. */ type Options = { /** Chain whose fee-payer transactions are scanned. */ chainId: number /** Lowercase managed fee-payer address. */ feePayer: Address } } /** Saves progress only when the loaded revision is current, returning whether it changed. */ export async function save(db: Db.Db, record: Record): Promise { // Retain completed rows so an older invocation cannot recreate a cleared scan. const result = await db.kysely .updateTable('sponsorship_reconciliation_cursors') .set({ blockNumber: record.blockNumber, complete: record.complete, retryCount: record.retryCount, retryKey: record.retryKey, revision: nanoid(), transactionIndex: record.transactionIndex, updatedAt: new Date().toISOString(), }) .where('chainId', '=', record.chainId) .where('feePayer', '=', record.feePayer) .where('revision', '=', record.revision) .executeTakeFirst() return result.numUpdatedRows > 0n }