import type { Selectable } from 'kysely' import type * as Db from '../Db.js' import type * as db_Schema from '../Schema.js' /** Columns of the `funding_transfers` table. */ export type Table = db_Schema.FundingTransfer /** A stored funding transfer. */ export type Record = Selectable /** * Ownership filter derived from the API-key principal, never from the * request. A `projectId` narrows reads to that project; organization-attributed * keys omit it and see every project in their organization and environment. */ export type Owner = { /** Key environment. */ environment: Record['environment'] /** Owning organization id (`org_…`). */ orgId: string /** Attributed project id, for project-attributed keys. */ projectId?: string | undefined } /** Inserts a transfer row. Callers commit the matching event in the same transaction. */ export function insert(db: Db.Db, record: Record): Promise { return db.kysely .insertInto('funding_transfers') .values(record) .returningAll() .executeTakeFirstOrThrow() } /** Gets a transfer by id without ownership scoping (domain and reconciler use). */ export function get(db: Db.Db, id: string): Promise { return db.kysely .selectFrom('funding_transfers') .selectAll() .where('id', '=', id) .executeTakeFirst() } /** Lists processing transfers for one provider, oldest first. */ export function listProcessing(db: Db.Db, options: listProcessing.Options): Promise { let query = db.kysely .selectFrom('funding_transfers') .selectAll() .where('providerId', '=', options.providerId) .where('status', '=', 'processing') if (options.cursor !== undefined) query = query.where('id', '>', options.cursor) return query.orderBy('id', 'asc').limit(options.limit).execute() } export declare namespace listProcessing { /** Provider filter for one recovery scan. */ type Options = { /** Exclusive lower bound from the prior recovery page. */ cursor?: string | undefined /** Maximum transfers returned in one recovery page. */ limit: number /** Funding provider id. */ providerId: string } } /** Gets and locks a transfer for one atomic domain mutation. */ export function getForUpdate(db: Db.Db, id: string): Promise { return db.kysely .selectFrom('funding_transfers') .selectAll() .where('id', '=', id) .forUpdate() .executeTakeFirst() } /** Gets an owner-visible transfer by id; `undefined` for absent or foreign rows. */ export function getOwned(db: Db.Db, owner: Owner, id: string): Promise { let query = db.kysely .selectFrom('funding_transfers') .selectAll() .where('id', '=', id) .where('orgId', '=', owner.orgId) .where('environment', '=', owner.environment) if (owner.projectId !== undefined) query = query.where('projectId', '=', owner.projectId) return query.executeTakeFirst() } /** * Lists an owner's transfers newest-first. Ids embed a timestamp, so lexical * key order is chronological and the cursor is the last returned id. */ export function list(db: Db.Db, owner: Owner, options: list.Options): Promise { let query = db.kysely .selectFrom('funding_transfers') .selectAll() .where('orgId', '=', owner.orgId) .where('environment', '=', owner.environment) if (owner.projectId !== undefined) query = query.where('projectId', '=', owner.projectId) if (options.status !== undefined) query = query.where('status', '=', options.status) if (options.cursor !== undefined) query = query.where('id', '<', options.cursor) return query.orderBy('id', 'desc').limit(options.limit).execute() } export declare namespace list { /** Paging and filter options. */ type Options = { /** Exclusive lower bound: the last id of the previous page. */ cursor?: string | undefined /** Maximum rows to return (fetch one extra to detect a further page). */ limit: number /** Restricts to one lifecycle status. */ status?: Record['status'] | undefined } } /** * Counts an owner's transfers through a capped subquery sharing the list * filter, so the count cannot scan unboundedly. A result above `cap` means * the true count was truncated. */ export async function count(db: Db.Db, owner: Owner, options: count.Options): Promise { const row = await db.kysely .selectFrom((eb) => { let query = eb .selectFrom('funding_transfers') .select('id') .where('orgId', '=', owner.orgId) .where('environment', '=', owner.environment) if (owner.projectId !== undefined) query = query.where('projectId', '=', owner.projectId) if (options.status !== undefined) query = query.where('status', '=', options.status) return query.limit(options.cap + 1).as('page') }) .select(({ fn }) => fn.countAll().as('count')) .executeTakeFirstOrThrow() return Number(row.count) } export declare namespace count { /** Count options sharing the list filter. */ type Options = { /** Row ceiling; a result above it signals a truncated count. */ cap: number /** Restricts to one lifecycle status. */ status?: Record['status'] | undefined } } /** * Applies a version-guarded update; `undefined` when the row moved past * `expectedVersion` (or vanished) so the caller retries against fresh state. */ export function update(db: Db.Db, options: update.Options): Promise { return db.kysely .updateTable('funding_transfers') .set({ providerState: options.providerState, snapshot: options.snapshot, status: options.status, statusReason: options.statusReason, updatedAt: options.updatedAt, version: options.version, }) .where('id', '=', options.id) .where('version', '=', options.expectedVersion) .returningAll() .executeTakeFirst() } export declare namespace update { /** Guarded update fields. */ type Options = { /** The version the update was computed against. */ expectedVersion: number /** Transfer id (`ftr_…`). */ id: string /** Replacement private provider state; omitted keeps the stored value. */ providerState?: Record['providerState'] | undefined /** Replacement public snapshot. */ snapshot: Record['snapshot'] /** New lifecycle status. */ status: Record['status'] /** Customer-safe reason, or null to clear. */ statusReason: Record['statusReason'] /** Mutation timestamp (ISO 8601). */ updatedAt: string /** The incremented version. */ version: number } }