@secondlayer/sdk 10.4.3 → 10.6.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.
@@ -1,3 +1,4 @@
1
+ import { Database } from "bun:sqlite";
1
2
  /**
2
3
  * A consumer sink: the destination adapter a consume loop writes through.
3
4
  *
@@ -127,7 +128,6 @@ type SinkRollbackContext = {
127
128
  forkPointHeight: number;
128
129
  rewindCursor: string | null;
129
130
  };
130
- import { Database } from "bun:sqlite";
131
131
  interface BunSqliteSinkOptions {
132
132
  /** Checkpoint identity: the cursor row's primary key. */
133
133
  id: string;
@@ -169,4 +169,4 @@ interface BunSqliteSinkOptions {
169
169
  * ```
170
170
  */
171
171
  declare function bunSqliteSink(db: Database, options: BunSqliteSinkOptions): ConsumerSink<Database>;
172
- export { bunSqliteSink, SinkRollbackContext, BunSqliteSinkOptions };
172
+ export { BunSqliteSinkOptions, SinkRollbackContext, bunSqliteSink };
@@ -215,4 +215,4 @@ declare function quoteIdent(name: string): string;
215
215
  * `transact` itself — which is exactly what the conformance kit probes.
216
216
  */
217
217
  declare function createSink<Tx>(driver: SinkDriver<Tx>, options: CreateSinkOptions<Tx>): ConsumerSink<Tx>;
218
- export { quoteIdent, createSink, assertSqlIdentifier, SinkRollbackContext, SinkDriver, DEFAULT_CHECKPOINT_TABLE, CreateSinkOptions };
218
+ export { CreateSinkOptions, DEFAULT_CHECKPOINT_TABLE, SinkDriver, SinkRollbackContext, assertSqlIdentifier, createSink, quoteIdent };
@@ -213,11 +213,11 @@ function createSink(driver, options) {
213
213
  };
214
214
  }
215
215
  export {
216
- quoteIdent,
217
- createSink,
216
+ DEFAULT_CHECKPOINT_TABLE,
218
217
  assertSqlIdentifier,
219
- DEFAULT_CHECKPOINT_TABLE
218
+ createSink,
219
+ quoteIdent
220
220
  };
221
221
 
222
- //# debugId=5F2FE362F547285B64756E2164756E21
222
+ //# debugId=FEE61043EA8ABE6E64756E2164756E21
223
223
  //# sourceMappingURL=core.js.map
@@ -6,6 +6,6 @@
6
6
  "import { ValidationError } from \"../errors.ts\";\nimport type { ConsumerSink } from \"./types.ts\";\n\n/** Default checkpoint table name, shared by every SQL-backed sink so two\n * sinks on the same database land in one table keyed by `id`. */\nexport const DEFAULT_CHECKPOINT_TABLE = \"sl_consumer_checkpoints\";\n\n/**\n * The driver surface a database adapter implements — the ~7 methods that\n * actually differ between SQL stores. Everything above them (the\n * commit/rollback call sequences, the guards, identifier validation, the\n * checkpoint schema) is policy owned by {@link createSink}, so a driver\n * cannot get the ordering wrong: it never sees a cursor write outside the\n * transaction that carries the rows, and never sees a delete without the\n * rewound cursor riding along.\n *\n * Identifier args (`table`, `column`) arrive pre-validated against\n * `[A-Za-z_][A-Za-z0-9_]*` — safe to interpolate after quoting (see\n * {@link quoteIdent}). Values (cursor, height, id) must still be bound as\n * parameters.\n */\nexport interface SinkDriver<Tx> {\n\t/** Run `fn` inside ONE transaction: begin, run, commit — a throw from\n\t * `fn` must roll the whole transaction back and re-throw. */\n\ttransact<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;\n\t/** Create the checkpoint store if missing: `(id primary key, cursor)`.\n\t * Must be safe under concurrent first start (two consumers racing the\n\t * same CREATE). */\n\tensureCheckpointStore(): Promise<void>;\n\t/** The committed cursor for this sink's `id`, or `null` if none. */\n\treadCursor(): Promise<string | null>;\n\t/** Upsert this sink's cursor row inside `tx`. */\n\twriteCursor(tx: Tx, cursor: string): Promise<void>;\n\t/** Delete this sink's cursor row inside `tx`: a rewind to pre-genesis\n\t * (fork at block 0). Optional for drivers written before it existed;\n\t * without it a genesis rewind throws instead of writing a bad cursor. */\n\tclearCursor?(tx: Tx): Promise<void>;\n\t/** Delete rows with `height column >= height` from `table`, inside `tx`. */\n\tdeleteAtOrAbove(tx: Tx, table: string, height: number): Promise<void>;\n\t/** Whether `table` exists AND carries `column` — the first-use check that\n\t * keeps reorg rollback from being a silent no-op. */\n\thasColumn(table: string, column: string): Promise<boolean>;\n\t/** Optional: assert this consumer is the only live writer for its `id`,\n\t * inside `tx`. Must FAIL LOUDLY (throw) when another writer holds it —\n\t * never block or interleave (contract invariant #13). */\n\tacquireLock?(tx: Tx): Promise<void>;\n}\n\n/** Context handed to {@link CreateSinkOptions.onRollback}. `forkPointHeight`\n * is the undo range (inclusive `>=`); `rewindCursor` is what the checkpoint\n * will become after this transaction — `null` only for a fork at genesis. */\nexport type SinkRollbackContext = {\n\tforkPointHeight: number;\n\trewindCursor: string | null;\n};\n\n/** Options for {@link createSink} — the policy inputs, driver-agnostic.\n * Concrete sinks re-expose these with schema-typed `tables`/`height`. */\nexport interface CreateSinkOptions<Tx = unknown> {\n\t/** Error-message prefix naming the concrete sink (e.g. `\"kyselySink\"`),\n\t * so a thrown guard points at the thing the user actually constructed. */\n\tlabel: string;\n\t/** Checkpoint identity AND concurrency key. */\n\tid: string;\n\t/** Rollback scope: on a reorg, rows at/above the fork point are deleted\n\t * from exactly these tables. Fact tables only — a fold (balances,\n\t * counters) does not belong here; invert it in `onRollback`. */\n\ttables: readonly string[];\n\t/** The block-height stamp column, present on every declared table. */\n\theight: string;\n\t/** Checkpoint table name. Default {@link DEFAULT_CHECKPOINT_TABLE}. */\n\tcheckpointTable?: string;\n\t/** Forwarded to {@link ConsumerSink.capabilities}. */\n\tcapabilities?: ConsumerSink[\"capabilities\"];\n\t/** Runs inside the rollback transaction after the lock, before any\n\t * `DELETE … height >= fork`. Doomed fact rows are still visible.\n\t * Remaining facts are `height < forkPointHeight`. A throw aborts the\n\t * whole rewind (no delete, no cursor write). Called on every\n\t * `rollback`, including re-application after a crash — derive undo\n\t * from the doomed rows so a second pass is a no-op. */\n\tonRollback?: (tx: Tx, ctx: SinkRollbackContext) => Promise<void> | void;\n}\n\n/** Throw unless `name` is a bare SQL identifier (letters, digits,\n * underscores; no leading digit) — the guard that makes interpolating\n * table/column names into DDL and DELETE statements safe. */\nexport function assertSqlIdentifier(name: string, label: string): void {\n\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {\n\t\tthrow new ValidationError(\n\t\t\t`${label}: invalid identifier \"${name}\" (letters, digits, underscores only).`,\n\t\t\t400,\n\t\t);\n\t}\n}\n\n/** Double-quote a (pre-validated) identifier for interpolation. */\nexport function quoteIdent(name: string): string {\n\treturn `\"${name}\"`;\n}\n\n/**\n * Build a {@link ConsumerSink} from a {@link SinkDriver}: the portable ~90\n * lines every SQL sink otherwise re-implements — and re-risks. Owns the two\n * transaction sequences (begin → lock → write rows → write cursor;\n * begin → lock → onRollback? → delete `>=` fork → write rewound cursor), the\n * empty-`tables` guard, identifier validation, and the first-use height-column\n * check.\n *\n * A driver implemented against this base cannot violate contract invariants\n * #4, #5, #8, #9, or #11 (see {@link ConsumerSink}) without breaking\n * `transact` itself — which is exactly what the conformance kit probes.\n */\nexport function createSink<Tx>(\n\tdriver: SinkDriver<Tx>,\n\toptions: CreateSinkOptions<Tx>,\n): ConsumerSink<Tx> {\n\tconst { label } = options;\n\tconst checkpointTable = options.checkpointTable ?? DEFAULT_CHECKPOINT_TABLE;\n\tif (options.tables.length === 0) {\n\t\tthrow new ValidationError(\n\t\t\t`${label}: \\`tables\\` is empty — declare every table the handler writes, or reorg rollback is a no-op and orphaned rows persist forever.`,\n\t\t\t400,\n\t\t);\n\t}\n\tassertSqlIdentifier(checkpointTable, label);\n\tassertSqlIdentifier(options.height, label);\n\tfor (const table of options.tables) assertSqlIdentifier(table, label);\n\n\treturn {\n\t\tcapabilities: options.capabilities,\n\n\t\tasync loadCursor() {\n\t\t\tawait driver.ensureCheckpointStore();\n\t\t\t// First-use validation of the rollback precondition: every declared\n\t\t\t// table must exist and carry the height column. Failing here beats a\n\t\t\t// rollback that silently deletes nothing during a reorg.\n\t\t\tfor (const table of options.tables) {\n\t\t\t\tif (!(await driver.hasColumn(table, options.height))) {\n\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t`${label}: table \"${table}\" has no \"${options.height}\" column — the height stamp is what makes reorg rollback possible. Add the column (or fix \\`height\\`).`,\n\t\t\t\t\t\t400,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn driver.readCursor();\n\t\t},\n\n\t\tasync commitBatch(cursor, write) {\n\t\t\tawait driver.transact(async (tx) => {\n\t\t\t\tawait driver.acquireLock?.(tx);\n\t\t\t\tawait write(tx);\n\t\t\t\tawait driver.writeCursor(tx, cursor);\n\t\t\t});\n\t\t},\n\n\t\tasync rollback(forkPointHeight, rewindCursor) {\n\t\t\tawait driver.transact(async (tx) => {\n\t\t\t\tawait driver.acquireLock?.(tx);\n\t\t\t\t// Folds (balances, last-wins) invert/recompute here, while doomed\n\t\t\t\t// fact rows are still visible. A throw aborts the rewind.\n\t\t\t\tawait options.onRollback?.(tx, { forkPointHeight, rewindCursor });\n\t\t\t\t// INCLUSIVE of the fork block: the new canonical chain re-supplies\n\t\t\t\t// it, and the consumer rewinds to re-read from `fork:0`.\n\t\t\t\tfor (const table of options.tables) {\n\t\t\t\t\tawait driver.deleteAtOrAbove(tx, table, forkPointHeight);\n\t\t\t\t}\n\t\t\t\tif (rewindCursor === null) {\n\t\t\t\t\tif (!driver.clearCursor) {\n\t\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t\t`${label}: this driver has no clearCursor, so a rewind to pre-genesis cannot be committed. Add clearCursor to the driver, or resume with an explicit fromCursor.`,\n\t\t\t\t\t\t\t400,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tawait driver.clearCursor(tx);\n\t\t\t\t} else {\n\t\t\t\t\tawait driver.writeCursor(tx, rewindCursor);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t};\n}\n"
7
7
  ],
8
8
  "mappings": "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",
9
- "debugId": "5F2FE362F547285B64756E2164756E21",
9
+ "debugId": "FEE61043EA8ABE6E64756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -1,3 +1,4 @@
1
+ import { Table } from "drizzle-orm";
1
2
  /**
2
3
  * A consumer sink: the destination adapter a consume loop writes through.
3
4
  *
@@ -127,7 +128,6 @@ type SinkRollbackContext = {
127
128
  forkPointHeight: number;
128
129
  rewindCursor: string | null;
129
130
  };
130
- import { Table } from "drizzle-orm";
131
131
  /**
132
132
  * Structural slice of a drizzle database. Postgres databases expose
133
133
  * `execute`; sqlite databases expose `run`/`all` (sync drivers return
@@ -198,4 +198,4 @@ declare function drizzleSink<
198
198
  TDb extends DrizzleDatabaseLike,
199
199
  T extends Table
200
200
  >(db: TDb, options: DrizzleSinkOptions<T, DrizzleTx<TDb>>): ConsumerSink<DrizzleTx<TDb>>;
201
- export { drizzleSink, SinkRollbackContext, DrizzleTx, DrizzleSinkOptions };
201
+ export { DrizzleSinkOptions, DrizzleTx, SinkRollbackContext, drizzleSink };
@@ -1,3 +1,6 @@
1
+ // src/sinks/drizzle.ts
2
+ import { getTableColumns, getTableName, sql } from "drizzle-orm";
3
+
1
4
  // src/errors.ts
2
5
  var DOCS_BASE = "https://www.secondlayer.tools";
3
6
 
@@ -214,7 +217,6 @@ function createSink(driver, options) {
214
217
  }
215
218
 
216
219
  // src/sinks/drizzle.ts
217
- import { getTableColumns, getTableName, sql } from "drizzle-orm";
218
220
  function drizzleSink(db, options) {
219
221
  const checkpointTable = options.checkpointTable ?? DEFAULT_CHECKPOINT_TABLE;
220
222
  const cp = quoteIdent(checkpointTable);
@@ -288,8 +290,8 @@ function drizzleSink(db, options) {
288
290
  if (!heightCol)
289
291
  return false;
290
292
  if (isPg) {
291
- const found2 = await rows(sql`SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = ${table} AND column_name = ${heightCol}) AS "ok"`);
292
- return found2[0]?.ok === true;
293
+ const found = await rows(sql`SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = ${table} AND column_name = ${heightCol}) AS "ok"`);
294
+ return found[0]?.ok === true;
293
295
  }
294
296
  const found = await rows(sql`SELECT 1 AS ok FROM pragma_table_info(${table}) WHERE name = ${heightCol}`);
295
297
  return found.length > 0;
@@ -317,5 +319,5 @@ export {
317
319
  drizzleSink
318
320
  };
319
321
 
320
- //# debugId=53FB4A01DD45FDF064756E2164756E21
322
+ //# debugId=6278C50044494CBE64756E2164756E21
321
323
  //# sourceMappingURL=drizzle.js.map
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["src/errors.ts", "src/sinks/core.ts", "src/sinks/drizzle.ts"],
3
+ "sources": ["src/sinks/drizzle.ts", "src/errors.ts", "src/sinks/core.ts"],
4
4
  "sourcesContent": [
5
+ "import { type Table, getTableColumns, getTableName, sql } from \"drizzle-orm\";\nimport { ValidationError } from \"../errors.ts\";\nimport {\n\tDEFAULT_CHECKPOINT_TABLE,\n\ttype SinkDriver,\n\ttype SinkRollbackContext,\n\tassertSqlIdentifier,\n\tcreateSink,\n\tquoteIdent,\n} from \"./core.ts\";\nimport type { ConsumerSink } from \"./types.ts\";\n\nexport type { SinkRollbackContext };\n\n/**\n * Structural slice of a drizzle database. Postgres databases expose\n * `execute`; sqlite databases expose `run`/`all` (sync drivers return\n * plainly, async drivers return promises — `await` absorbs both).\n */\ninterface DrizzleDatabaseLike {\n\t// biome-ignore lint/suspicious/noExplicitAny: contravariant position — must accept every concrete drizzle transaction type\n\ttransaction(fn: (tx: any) => Promise<unknown>): Promise<unknown>;\n\texecute?(query: unknown): Promise<unknown>;\n\trun?(query: unknown): unknown;\n\tall?(query: unknown): unknown;\n}\n\n/** The transaction type `db.transaction` lends — what `ctx.tx` is typed as\n * inside `onBatch` (e.g. `PgTransaction<..., TSchema>`). */\nexport type DrizzleTx<TDb> = TDb extends {\n\t// biome-ignore lint/suspicious/noExplicitAny: infer-only position\n\ttransaction(fn: (tx: infer Tx) => any): any;\n}\n\t? Tx\n\t: never;\n\n/** Column key present on EVERY declared table — `keyof` a union of column\n * maps is the intersection, so a key missing from any one table is a\n * compile error at the `height:` option (same trick as kyselySink). */\ntype CommonColumnKey<T extends Table> = keyof T[\"_\"][\"columns\"] & string;\n\nexport interface DrizzleSinkOptions<T extends Table, TTx = unknown> {\n\t/** Checkpoint identity AND (on Postgres) advisory-lock key. */\n\tid: string;\n\t/** Rollback scope: the drizzle table objects the handler writes. Fact\n\t * tables only — invert folds (balances) in `onRollback`. */\n\ttables: readonly T[];\n\t/** The block-height stamp column KEY (the TypeScript property, e.g.\n\t * `blockHeight` for `blockHeight: integer(\"block_height\")`) — resolved\n\t * to each table's real SQL column name. Must exist on every declared\n\t * table; append-only projections only. */\n\theight: CommonColumnKey<T>;\n\t/** Checkpoint table name. Default `sl_consumer_checkpoints`. */\n\tcheckpointTable?: string;\n\t/** Same transaction as the fact-table delete, before it. Doomed rows are\n\t * still visible; a throw aborts the rewind. */\n\tonRollback?: (tx: TTx, ctx: SinkRollbackContext) => Promise<void> | void;\n}\n\n/**\n * A {@link ConsumerSink} over a drizzle database — Postgres or SQLite\n * dialect, detected structurally. `tables` are your schema objects, `height`\n * is compile-checked against every declared table, and `ctx.tx` inside\n * `onBatch` is the fully typed drizzle transaction for YOUR database.\n *\n * Dialect notes:\n * - Postgres (node-postgres, postgres-js, …): a per-id advisory lock makes\n * a second replica fail loudly (contract invariant #13).\n * - SQLite (bun:sqlite, better-sqlite3): commits run under a manual\n * `BEGIN IMMEDIATE` rather than `db.transaction()` — sync drivers would\n * otherwise COMMIT at the handler's first `await`, silently breaking\n * rows+cursor atomicity. `ctx.tx` is the database itself inside the open\n * transaction. Remote/libsql drivers are NOT supported for commits (their\n * statement-per-request model can't hold this transaction open).\n *\n * ```ts\n * const sink = drizzleSink(db, { id: \"sales\", tables: [sales], height: \"blockHeight\" });\n * await index.contractCalls.consume({\n * contractId: MARKETPLACE, functionName: \"purchase-asset\", fromHeight: 0,\n * sink,\n * onBatch: (calls, _env, ctx) =>\n * ctx.tx.insert(sales).values(calls.map(toSale)).onConflictDoNothing(),\n * });\n * ```\n */\nexport function drizzleSink<TDb extends DrizzleDatabaseLike, T extends Table>(\n\tdb: TDb,\n\toptions: DrizzleSinkOptions<T, DrizzleTx<TDb>>,\n): ConsumerSink<DrizzleTx<TDb>> {\n\tconst checkpointTable = options.checkpointTable ?? DEFAULT_CHECKPOINT_TABLE;\n\tconst cp = quoteIdent(checkpointTable);\n\t// Postgres speaks `execute`; sqlite speaks `run`/`all`.\n\tconst isPg = typeof db.execute === \"function\";\n\n\t// Resolve TS column keys to real SQL names per table (they can differ:\n\t// `blockHeight: integer(\"block_height\")`), and validate them here — the\n\t// base only validates what it's given, and it's given the resolved names.\n\tconst heightByTable = new Map<string, string>();\n\tfor (const table of options.tables) {\n\t\tconst tableName = getTableName(table);\n\t\tconst column = getTableColumns(table)[options.height];\n\t\tif (!column) {\n\t\t\tthrow new ValidationError(\n\t\t\t\t`drizzleSink: table \"${tableName}\" has no \"${options.height}\" column key — the height stamp is what makes reorg rollback possible.`,\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\t\tassertSqlIdentifier(column.name, \"drizzleSink\");\n\t\theightByTable.set(tableName, column.name);\n\t}\n\tconst tableNames = [...heightByTable.keys()];\n\n\t/** Run a raw statement on the db or a lent tx, either dialect. */\n\tasync function exec(\n\t\ton: DrizzleDatabaseLike,\n\t\tquery: ReturnType<typeof sql>,\n\t): Promise<void> {\n\t\tif (isPg) await on.execute?.(query);\n\t\telse await on.run?.(query);\n\t}\n\n\t/** Read rows from a raw statement, either dialect. */\n\tasync function rows(query: ReturnType<typeof sql>): Promise<unknown[]> {\n\t\tif (isPg) {\n\t\t\tconst result = (await db.execute?.(query)) as\n\t\t\t\t| { rows?: unknown[] }\n\t\t\t\t| unknown[];\n\t\t\treturn Array.isArray(result) ? result : (result?.rows ?? []);\n\t\t}\n\t\treturn ((await db.all?.(query)) as unknown[]) ?? [];\n\t}\n\n\tconst driver: SinkDriver<DrizzleTx<TDb>> = {\n\t\tasync transact(fn) {\n\t\t\tif (isPg) {\n\t\t\t\treturn (await db.transaction((tx: DrizzleTx<TDb>) =>\n\t\t\t\t\tfn(tx),\n\t\t\t\t)) as Awaited<ReturnType<typeof fn>>;\n\t\t\t}\n\t\t\t// SQLite: manual BEGIN IMMEDIATE (see dialect notes above). The\n\t\t\t// database itself is the lent transaction.\n\t\t\tawait db.run?.(sql`begin immediate`);\n\t\t\ttry {\n\t\t\t\tconst result = await fn(db as unknown as DrizzleTx<TDb>);\n\t\t\t\tawait db.run?.(sql`commit`);\n\t\t\t\treturn result;\n\t\t\t} catch (err) {\n\t\t\t\tawait db.run?.(sql`rollback`);\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\n\t\tasync ensureCheckpointStore() {\n\t\t\ttry {\n\t\t\t\tawait exec(\n\t\t\t\t\tdb,\n\t\t\t\t\tsql.raw(\n\t\t\t\t\t\t`CREATE TABLE IF NOT EXISTS ${cp} (id text PRIMARY KEY, cursor text NOT NULL)`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t} catch (err) {\n\t\t\t\t// Concurrent first start: Postgres IF NOT EXISTS still throws\n\t\t\t\t// under a race (42P07 / 23505); either code means the table\n\t\t\t\t// exists. Drizzle may wrap the driver error, so check the cause.\n\t\t\t\tconst code =\n\t\t\t\t\t(err as { code?: string } | null)?.code ??\n\t\t\t\t\t(err as { cause?: { code?: string } } | null)?.cause?.code ??\n\t\t\t\t\tundefined;\n\t\t\t\tif (code !== \"42P07\" && code !== \"23505\") throw err;\n\t\t\t}\n\t\t},\n\n\t\tasync readCursor() {\n\t\t\tconst found = (await rows(\n\t\t\t\tsql`SELECT cursor FROM ${sql.raw(cp)} WHERE id = ${options.id}`,\n\t\t\t)) as Array<{ cursor: string }>;\n\t\t\treturn found[0]?.cursor ?? null;\n\t\t},\n\n\t\tasync writeCursor(tx, cursor) {\n\t\t\t// `excluded` works in both dialects' upsert grammar.\n\t\t\tawait exec(\n\t\t\t\ttx as unknown as DrizzleDatabaseLike,\n\t\t\t\tsql`INSERT INTO ${sql.raw(cp)} (id, cursor) VALUES (${options.id}, ${cursor}) ON CONFLICT (id) DO UPDATE SET cursor = excluded.cursor`,\n\t\t\t);\n\t\t},\n\n\t\tasync clearCursor(tx) {\n\t\t\tawait exec(\n\t\t\t\ttx as unknown as DrizzleDatabaseLike,\n\t\t\t\tsql`DELETE FROM ${sql.raw(cp)} WHERE id = ${options.id}`,\n\t\t\t);\n\t\t},\n\n\t\tasync deleteAtOrAbove(tx, table, height) {\n\t\t\tconst heightCol = heightByTable.get(table);\n\t\t\tif (!heightCol) return;\n\t\t\tawait exec(\n\t\t\t\ttx as unknown as DrizzleDatabaseLike,\n\t\t\t\tsql`DELETE FROM ${sql.raw(quoteIdent(table))} WHERE ${sql.raw(quoteIdent(heightCol))} >= ${height}`,\n\t\t\t);\n\t\t},\n\n\t\tasync hasColumn(table) {\n\t\t\t// The real SQL name was resolved from the schema object; check the\n\t\t\t// DATABASE carries it — schema objects prove intent, not migrations.\n\t\t\tconst heightCol = heightByTable.get(table);\n\t\t\tif (!heightCol) return false;\n\t\t\tif (isPg) {\n\t\t\t\tconst found = (await rows(\n\t\t\t\t\tsql`SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = ${table} AND column_name = ${heightCol}) AS \"ok\"`,\n\t\t\t\t)) as Array<{ ok: boolean }>;\n\t\t\t\treturn found[0]?.ok === true;\n\t\t\t}\n\t\t\tconst found = await rows(\n\t\t\t\tsql`SELECT 1 AS ok FROM pragma_table_info(${table}) WHERE name = ${heightCol}`,\n\t\t\t);\n\t\t\treturn found.length > 0;\n\t\t},\n\n\t\t// Postgres only: SQLite's single write lock already enforces\n\t\t// single-writer at the file level.\n\t\t...(isPg\n\t\t\t? {\n\t\t\t\t\tasync acquireLock(tx: DrizzleTx<TDb>) {\n\t\t\t\t\t\t// hashtextextended (int8) rather than hashtext (int4): the\n\t\t\t\t\t\t// 32-bit space collides across distinct ids at real scale,\n\t\t\t\t\t\t// and a collision reads as a false \"another consumer\" 409.\n\t\t\t\t\t\tconst found = (await (\n\t\t\t\t\t\t\ttx as unknown as DrizzleDatabaseLike\n\t\t\t\t\t\t).execute?.(\n\t\t\t\t\t\t\tsql`SELECT pg_try_advisory_xact_lock(hashtextextended(${options.id}, 0)) AS \"ok\"`,\n\t\t\t\t\t\t)) as { rows?: Array<{ ok: boolean }> } | Array<{ ok: boolean }>;\n\t\t\t\t\t\tconst row = Array.isArray(found) ? found[0] : found?.rows?.[0];\n\t\t\t\t\t\tif (row?.ok !== true) {\n\t\t\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t\t\t`drizzleSink: another consumer holds the \"${options.id}\" lock — two replicas of the same consumer would interleave commits. Stop the other instance or use a distinct id.`,\n\t\t\t\t\t\t\t\t409,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t};\n\n\treturn createSink(driver, {\n\t\tlabel: \"drizzleSink\",\n\t\tid: options.id,\n\t\ttables: tableNames,\n\t\theight: options.height,\n\t\tcheckpointTable,\n\t\tonRollback: options.onRollback,\n\t});\n}\n",
5
6
  "const DOCS_BASE = \"https://www.secondlayer.tools\";\n\n/** Options accepted by every error in the family. */\nexport interface SecondLayerErrorOptions {\n\tcause?: Error;\n\tdetails?: string;\n\t/** Stable machine-readable code (e.g. `UPGRADE_REQUIRED`). */\n\tcode?: string;\n\t/** Docs page that explains this failure and its fix. */\n\tdocsUrl?: string;\n\t/** Extra context lines appended to the message (DDL to run, next steps). */\n\tmetaMessages?: string[];\n\t/** Whether retrying the SAME request can succeed (429/5xx/network). */\n\tretryable?: boolean;\n\t/** Parsed `Retry-After`, in seconds — set on rate limits that carry it. */\n\tretryAfterSeconds?: number;\n}\n\n/**\n * Root of the SDK error family. Implements the same protocol as `BaseError`\n * in `@secondlayer/stacks` (`shortMessage`, `cause`, `toJSON`) so a failed\n * chain read and a failed platform read serialize and introspect identically.\n * It intentionally does NOT extend that class: cross-package `instanceof` is\n * unreliable under per-package bundling anyway (match on `code`/`name` across\n * packages).\n *\n * `retryable` is the signal the consume loops act on: 429/5xx/network are\n * retryable, 4xx and body-serialization failures are not.\n */\nexport class SecondLayerError extends Error {\n\toverride name = \"SecondLayerError\";\n\t/** The one-line failure, without the appended context blocks. */\n\tshortMessage: string;\n\tcode?: string;\n\tdocsUrl?: string;\n\tmetaMessages?: string[];\n\tretryable: boolean;\n\tretryAfterSeconds?: number;\n\n\tconstructor(shortMessage: string, options: SecondLayerErrorOptions = {}) {\n\t\tconst detailBlocks = [\n\t\t\t...(options.metaMessages ?? []),\n\t\t\t...(options.details ? [options.details] : []),\n\t\t\t...(options.docsUrl ? [`Docs: ${options.docsUrl}`] : []),\n\t\t];\n\t\tconst message = [\n\t\t\tshortMessage,\n\t\t\tdetailBlocks.length > 0 ? `\\n${detailBlocks.join(\"\\n\")}` : \"\",\n\t\t].join(\"\");\n\t\tsuper(message, options.cause ? { cause: options.cause } : undefined);\n\t\tthis.shortMessage = shortMessage;\n\t\tthis.code = options.code;\n\t\tthis.docsUrl = options.docsUrl;\n\t\tthis.metaMessages = options.metaMessages;\n\t\tthis.retryable = options.retryable ?? false;\n\t\tthis.retryAfterSeconds = options.retryAfterSeconds;\n\t}\n\n\t/**\n\t * Walk the cause chain. With a predicate, returns the first error matching\n\t * it (or `null`); without one, returns the deepest cause. Mirrors viem's\n\t * `error.walk()` so cross-library error handling reads the same.\n\t */\n\twalk(fn?: (err: unknown) => boolean): unknown {\n\t\t// biome-ignore lint/suspicious/noExplicitAny: cause chains are untyped by nature\n\t\tlet err: any = this;\n\t\tlet last: unknown = this;\n\t\twhile (err != null) {\n\t\t\tif (fn?.(err)) return err;\n\t\t\tlast = err;\n\t\t\terr = err.cause;\n\t\t}\n\t\treturn fn ? null : last;\n\t}\n\n\ttoJSON(): {\n\t\tname: string;\n\t\tmessage: string;\n\t\tshortMessage: string;\n\t\tcause: string | undefined;\n\t\tcode: string | undefined;\n\t\tdocsUrl: string | undefined;\n\t\tretryable: boolean;\n\t\tretryAfterSeconds: number | undefined;\n\t} {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tmessage: this.message,\n\t\t\tshortMessage: this.shortMessage,\n\t\t\tcause: this.cause instanceof Error ? this.cause.message : undefined,\n\t\t\tcode: this.code,\n\t\t\tdocsUrl: this.docsUrl,\n\t\t\tretryable: this.retryable,\n\t\t\tretryAfterSeconds: this.retryAfterSeconds,\n\t\t};\n\t}\n}\n\n/** Retryability of an HTTP status: 429 and 5xx are transient. */\nfunction statusRetryable(status: number): boolean {\n\treturn status === 429 || status >= 500;\n}\n\n/** Docs page for a status/code pair, when one exists. */\nfunction docsFor(status: number, code?: string): string | undefined {\n\tif (code === \"UPGRADE_REQUIRED\" || status === 402)\n\t\treturn `${DOCS_BASE}/docs/authentication#pay-as-you-go-credits`;\n\tif (status === 429 || status === 401)\n\t\treturn `${DOCS_BASE}/docs/authentication`;\n\treturn undefined;\n}\n\n/**\n * Error thrown by {@link SecondLayer} when an API request fails.\n * Includes the HTTP status code for programmatic error handling.\n *\n * @example\n * ```ts\n * try {\n * await client.subgraphs.status(\"my-subgraph\");\n * } catch (err) {\n * if (err instanceof ApiError && err.status === 404) {\n * console.log(\"Subgraph not found\");\n * }\n * }\n * ```\n */\nexport class ApiError extends SecondLayerError {\n\tconstructor(\n\t\t/** HTTP status code (0 for network errors). */\n\t\tpublic status: number,\n\t\tmessage: string,\n\t\t/** Raw response body (parsed JSON if possible) — preserved for callers that need error details. */\n\t\tpublic body?: unknown,\n\t\t/** Stable machine-readable code from the API's `{error, code}` error envelope. */\n\t\tcode?: string,\n\t\toptions: SecondLayerErrorOptions = {},\n\t) {\n\t\tsuper(message, {\n\t\t\tcode,\n\t\t\tretryable: options.retryable ?? statusRetryable(status),\n\t\t\tdocsUrl: options.docsUrl ?? docsFor(status, code),\n\t\t\t...(options.cause ? { cause: options.cause } : {}),\n\t\t\t...(options.details ? { details: options.details } : {}),\n\t\t\t...(options.metaMessages ? { metaMessages: options.metaMessages } : {}),\n\t\t\t...(options.retryAfterSeconds !== undefined\n\t\t\t\t? { retryAfterSeconds: options.retryAfterSeconds }\n\t\t\t\t: {}),\n\t\t});\n\t\tthis.name = \"ApiError\";\n\t}\n}\n\n/** Thrown on a 401 by both the instance clients and Streams. Carries the\n * server's `{error, code}` envelope when it sent one, so a revoked token reads\n * differently from a missing one. */\nexport class AuthError extends ApiError {\n\toverride readonly status = 401 as const;\n\n\tconstructor(\n\t\tmessage = \"API key invalid or expired.\",\n\t\tbody?: unknown,\n\t\tcode?: string,\n\t) {\n\t\tsuper(401, message, body, code);\n\t\tthis.name = \"AuthError\";\n\t}\n}\n\n/** Thrown on a 429 by both the instance clients and Streams. `retryable`,\n * with `retryAfterSeconds` parsed from the `Retry-After` header when sent. */\nexport class RateLimitError extends ApiError {\n\toverride readonly status = 429 as const;\n\n\tconstructor(\n\t\tmessage = \"Rate limited. Try again later.\",\n\t\t/** Raw `Retry-After` header value (seconds or HTTP-date). */\n\t\treadonly retryAfter?: string,\n\t\tbody?: unknown,\n\t\tcode?: string,\n\t) {\n\t\tsuper(429, message, body, code, {\n\t\t\tretryable: true,\n\t\t\tretryAfterSeconds: parseRetryAfter(retryAfter),\n\t\t});\n\t\tthis.name = \"RateLimitError\";\n\t}\n}\n\n/** Thrown on a 4xx the caller can fix (bad cursor, bad params). Never retried.\n * `code` is the server's machine-readable code when the envelope carried one. */\nexport class ValidationError extends ApiError {\n\tconstructor(message: string, status: number, body?: unknown, code?: string) {\n\t\tsuper(status, message, body, code, { retryable: false });\n\t\tthis.name = \"ValidationError\";\n\t}\n}\n\n/** Thrown when an archive manifest signature is missing or does not verify. */\nexport class ArchiveSignatureError extends SecondLayerError {\n\tconstructor(message = \"Archive manifest signature is missing or invalid.\") {\n\t\tsuper(message, { code: \"ARCHIVE_SIGNATURE\", retryable: false });\n\t\tthis.name = \"ArchiveSignatureError\";\n\t}\n}\n\n/** Thrown on a 401 from the archive ops host (quote/fetch/credits). */\nexport class ArchiveAuthError extends SecondLayerError {\n\tconstructor(message = \"Archive ops request was not authenticated.\") {\n\t\tsuper(message, { code: \"ARCHIVE_AUTH\", retryable: false });\n\t\tthis.name = \"ArchiveAuthError\";\n\t}\n}\n\n/** Thrown on a 402 from archive fetch — prepaid credits do not cover the quote. */\nexport class InsufficientArchiveCreditsError extends SecondLayerError {\n\tshortfallUsdMicros?: number;\n\tconstructor(\n\t\tmessage = \"Insufficient archive credits.\",\n\t\tshortfallUsdMicros?: number,\n\t) {\n\t\tsuper(message, {\n\t\t\tcode: \"INSUFFICIENT_ARCHIVE_CREDITS\",\n\t\t\tretryable: false,\n\t\t});\n\t\tthis.name = \"InsufficientArchiveCreditsError\";\n\t\tthis.shortfallUsdMicros = shortfallUsdMicros;\n\t}\n}\n\n/** Thrown on a 503 when the archive fetch gate is unconfigured. */\nexport class ArchiveGateNotConfiguredError extends SecondLayerError {\n\tconstructor(message = \"The archive gate is not configured on the server.\") {\n\t\tsuper(message, { code: \"ARCHIVE_GATE_NOT_CONFIGURED\", retryable: false });\n\t\tthis.name = \"ArchiveGateNotConfiguredError\";\n\t}\n}\n\n/** Parse a `Retry-After` header: delta-seconds or an HTTP-date. */\nexport function parseRetryAfter(value?: string | null): number | undefined {\n\tif (!value) return undefined;\n\tconst seconds = Number(value);\n\tif (Number.isFinite(seconds) && seconds >= 0) return seconds;\n\tconst date = Date.parse(value);\n\tif (!Number.isNaN(date)) return Math.max(0, (date - Date.now()) / 1000);\n\treturn undefined;\n}\n",
6
- "import { ValidationError } from \"../errors.ts\";\nimport type { ConsumerSink } from \"./types.ts\";\n\n/** Default checkpoint table name, shared by every SQL-backed sink so two\n * sinks on the same database land in one table keyed by `id`. */\nexport const DEFAULT_CHECKPOINT_TABLE = \"sl_consumer_checkpoints\";\n\n/**\n * The driver surface a database adapter implements — the ~7 methods that\n * actually differ between SQL stores. Everything above them (the\n * commit/rollback call sequences, the guards, identifier validation, the\n * checkpoint schema) is policy owned by {@link createSink}, so a driver\n * cannot get the ordering wrong: it never sees a cursor write outside the\n * transaction that carries the rows, and never sees a delete without the\n * rewound cursor riding along.\n *\n * Identifier args (`table`, `column`) arrive pre-validated against\n * `[A-Za-z_][A-Za-z0-9_]*` — safe to interpolate after quoting (see\n * {@link quoteIdent}). Values (cursor, height, id) must still be bound as\n * parameters.\n */\nexport interface SinkDriver<Tx> {\n\t/** Run `fn` inside ONE transaction: begin, run, commit — a throw from\n\t * `fn` must roll the whole transaction back and re-throw. */\n\ttransact<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;\n\t/** Create the checkpoint store if missing: `(id primary key, cursor)`.\n\t * Must be safe under concurrent first start (two consumers racing the\n\t * same CREATE). */\n\tensureCheckpointStore(): Promise<void>;\n\t/** The committed cursor for this sink's `id`, or `null` if none. */\n\treadCursor(): Promise<string | null>;\n\t/** Upsert this sink's cursor row inside `tx`. */\n\twriteCursor(tx: Tx, cursor: string): Promise<void>;\n\t/** Delete this sink's cursor row inside `tx`: a rewind to pre-genesis\n\t * (fork at block 0). Optional for drivers written before it existed;\n\t * without it a genesis rewind throws instead of writing a bad cursor. */\n\tclearCursor?(tx: Tx): Promise<void>;\n\t/** Delete rows with `height column >= height` from `table`, inside `tx`. */\n\tdeleteAtOrAbove(tx: Tx, table: string, height: number): Promise<void>;\n\t/** Whether `table` exists AND carries `column` — the first-use check that\n\t * keeps reorg rollback from being a silent no-op. */\n\thasColumn(table: string, column: string): Promise<boolean>;\n\t/** Optional: assert this consumer is the only live writer for its `id`,\n\t * inside `tx`. Must FAIL LOUDLY (throw) when another writer holds it —\n\t * never block or interleave (contract invariant #13). */\n\tacquireLock?(tx: Tx): Promise<void>;\n}\n\n/** Context handed to {@link CreateSinkOptions.onRollback}. `forkPointHeight`\n * is the undo range (inclusive `>=`); `rewindCursor` is what the checkpoint\n * will become after this transaction — `null` only for a fork at genesis. */\nexport type SinkRollbackContext = {\n\tforkPointHeight: number;\n\trewindCursor: string | null;\n};\n\n/** Options for {@link createSink} — the policy inputs, driver-agnostic.\n * Concrete sinks re-expose these with schema-typed `tables`/`height`. */\nexport interface CreateSinkOptions<Tx = unknown> {\n\t/** Error-message prefix naming the concrete sink (e.g. `\"kyselySink\"`),\n\t * so a thrown guard points at the thing the user actually constructed. */\n\tlabel: string;\n\t/** Checkpoint identity AND concurrency key. */\n\tid: string;\n\t/** Rollback scope: on a reorg, rows at/above the fork point are deleted\n\t * from exactly these tables. Fact tables only — a fold (balances,\n\t * counters) does not belong here; invert it in `onRollback`. */\n\ttables: readonly string[];\n\t/** The block-height stamp column, present on every declared table. */\n\theight: string;\n\t/** Checkpoint table name. Default {@link DEFAULT_CHECKPOINT_TABLE}. */\n\tcheckpointTable?: string;\n\t/** Forwarded to {@link ConsumerSink.capabilities}. */\n\tcapabilities?: ConsumerSink[\"capabilities\"];\n\t/** Runs inside the rollback transaction after the lock, before any\n\t * `DELETE … height >= fork`. Doomed fact rows are still visible.\n\t * Remaining facts are `height < forkPointHeight`. A throw aborts the\n\t * whole rewind (no delete, no cursor write). Called on every\n\t * `rollback`, including re-application after a crash — derive undo\n\t * from the doomed rows so a second pass is a no-op. */\n\tonRollback?: (tx: Tx, ctx: SinkRollbackContext) => Promise<void> | void;\n}\n\n/** Throw unless `name` is a bare SQL identifier (letters, digits,\n * underscores; no leading digit) — the guard that makes interpolating\n * table/column names into DDL and DELETE statements safe. */\nexport function assertSqlIdentifier(name: string, label: string): void {\n\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {\n\t\tthrow new ValidationError(\n\t\t\t`${label}: invalid identifier \"${name}\" (letters, digits, underscores only).`,\n\t\t\t400,\n\t\t);\n\t}\n}\n\n/** Double-quote a (pre-validated) identifier for interpolation. */\nexport function quoteIdent(name: string): string {\n\treturn `\"${name}\"`;\n}\n\n/**\n * Build a {@link ConsumerSink} from a {@link SinkDriver}: the portable ~90\n * lines every SQL sink otherwise re-implements — and re-risks. Owns the two\n * transaction sequences (begin → lock → write rows → write cursor;\n * begin → lock → onRollback? → delete `>=` fork → write rewound cursor), the\n * empty-`tables` guard, identifier validation, and the first-use height-column\n * check.\n *\n * A driver implemented against this base cannot violate contract invariants\n * #4, #5, #8, #9, or #11 (see {@link ConsumerSink}) without breaking\n * `transact` itself — which is exactly what the conformance kit probes.\n */\nexport function createSink<Tx>(\n\tdriver: SinkDriver<Tx>,\n\toptions: CreateSinkOptions<Tx>,\n): ConsumerSink<Tx> {\n\tconst { label } = options;\n\tconst checkpointTable = options.checkpointTable ?? DEFAULT_CHECKPOINT_TABLE;\n\tif (options.tables.length === 0) {\n\t\tthrow new ValidationError(\n\t\t\t`${label}: \\`tables\\` is empty — declare every table the handler writes, or reorg rollback is a no-op and orphaned rows persist forever.`,\n\t\t\t400,\n\t\t);\n\t}\n\tassertSqlIdentifier(checkpointTable, label);\n\tassertSqlIdentifier(options.height, label);\n\tfor (const table of options.tables) assertSqlIdentifier(table, label);\n\n\treturn {\n\t\tcapabilities: options.capabilities,\n\n\t\tasync loadCursor() {\n\t\t\tawait driver.ensureCheckpointStore();\n\t\t\t// First-use validation of the rollback precondition: every declared\n\t\t\t// table must exist and carry the height column. Failing here beats a\n\t\t\t// rollback that silently deletes nothing during a reorg.\n\t\t\tfor (const table of options.tables) {\n\t\t\t\tif (!(await driver.hasColumn(table, options.height))) {\n\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t`${label}: table \"${table}\" has no \"${options.height}\" column — the height stamp is what makes reorg rollback possible. Add the column (or fix \\`height\\`).`,\n\t\t\t\t\t\t400,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn driver.readCursor();\n\t\t},\n\n\t\tasync commitBatch(cursor, write) {\n\t\t\tawait driver.transact(async (tx) => {\n\t\t\t\tawait driver.acquireLock?.(tx);\n\t\t\t\tawait write(tx);\n\t\t\t\tawait driver.writeCursor(tx, cursor);\n\t\t\t});\n\t\t},\n\n\t\tasync rollback(forkPointHeight, rewindCursor) {\n\t\t\tawait driver.transact(async (tx) => {\n\t\t\t\tawait driver.acquireLock?.(tx);\n\t\t\t\t// Folds (balances, last-wins) invert/recompute here, while doomed\n\t\t\t\t// fact rows are still visible. A throw aborts the rewind.\n\t\t\t\tawait options.onRollback?.(tx, { forkPointHeight, rewindCursor });\n\t\t\t\t// INCLUSIVE of the fork block: the new canonical chain re-supplies\n\t\t\t\t// it, and the consumer rewinds to re-read from `fork:0`.\n\t\t\t\tfor (const table of options.tables) {\n\t\t\t\t\tawait driver.deleteAtOrAbove(tx, table, forkPointHeight);\n\t\t\t\t}\n\t\t\t\tif (rewindCursor === null) {\n\t\t\t\t\tif (!driver.clearCursor) {\n\t\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t\t`${label}: this driver has no clearCursor, so a rewind to pre-genesis cannot be committed. Add clearCursor to the driver, or resume with an explicit fromCursor.`,\n\t\t\t\t\t\t\t400,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tawait driver.clearCursor(tx);\n\t\t\t\t} else {\n\t\t\t\t\tawait driver.writeCursor(tx, rewindCursor);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t};\n}\n",
7
- "import { type Table, getTableColumns, getTableName, sql } from \"drizzle-orm\";\nimport { ValidationError } from \"../errors.ts\";\nimport {\n\tDEFAULT_CHECKPOINT_TABLE,\n\ttype SinkDriver,\n\ttype SinkRollbackContext,\n\tassertSqlIdentifier,\n\tcreateSink,\n\tquoteIdent,\n} from \"./core.ts\";\nimport type { ConsumerSink } from \"./types.ts\";\n\nexport type { SinkRollbackContext };\n\n/**\n * Structural slice of a drizzle database. Postgres databases expose\n * `execute`; sqlite databases expose `run`/`all` (sync drivers return\n * plainly, async drivers return promises — `await` absorbs both).\n */\ninterface DrizzleDatabaseLike {\n\t// biome-ignore lint/suspicious/noExplicitAny: contravariant position — must accept every concrete drizzle transaction type\n\ttransaction(fn: (tx: any) => Promise<unknown>): Promise<unknown>;\n\texecute?(query: unknown): Promise<unknown>;\n\trun?(query: unknown): unknown;\n\tall?(query: unknown): unknown;\n}\n\n/** The transaction type `db.transaction` lends — what `ctx.tx` is typed as\n * inside `onBatch` (e.g. `PgTransaction<..., TSchema>`). */\nexport type DrizzleTx<TDb> = TDb extends {\n\t// biome-ignore lint/suspicious/noExplicitAny: infer-only position\n\ttransaction(fn: (tx: infer Tx) => any): any;\n}\n\t? Tx\n\t: never;\n\n/** Column key present on EVERY declared table — `keyof` a union of column\n * maps is the intersection, so a key missing from any one table is a\n * compile error at the `height:` option (same trick as kyselySink). */\ntype CommonColumnKey<T extends Table> = keyof T[\"_\"][\"columns\"] & string;\n\nexport interface DrizzleSinkOptions<T extends Table, TTx = unknown> {\n\t/** Checkpoint identity AND (on Postgres) advisory-lock key. */\n\tid: string;\n\t/** Rollback scope: the drizzle table objects the handler writes. Fact\n\t * tables only — invert folds (balances) in `onRollback`. */\n\ttables: readonly T[];\n\t/** The block-height stamp column KEY (the TypeScript property, e.g.\n\t * `blockHeight` for `blockHeight: integer(\"block_height\")`) — resolved\n\t * to each table's real SQL column name. Must exist on every declared\n\t * table; append-only projections only. */\n\theight: CommonColumnKey<T>;\n\t/** Checkpoint table name. Default `sl_consumer_checkpoints`. */\n\tcheckpointTable?: string;\n\t/** Same transaction as the fact-table delete, before it. Doomed rows are\n\t * still visible; a throw aborts the rewind. */\n\tonRollback?: (tx: TTx, ctx: SinkRollbackContext) => Promise<void> | void;\n}\n\n/**\n * A {@link ConsumerSink} over a drizzle database — Postgres or SQLite\n * dialect, detected structurally. `tables` are your schema objects, `height`\n * is compile-checked against every declared table, and `ctx.tx` inside\n * `onBatch` is the fully typed drizzle transaction for YOUR database.\n *\n * Dialect notes:\n * - Postgres (node-postgres, postgres-js, …): a per-id advisory lock makes\n * a second replica fail loudly (contract invariant #13).\n * - SQLite (bun:sqlite, better-sqlite3): commits run under a manual\n * `BEGIN IMMEDIATE` rather than `db.transaction()` — sync drivers would\n * otherwise COMMIT at the handler's first `await`, silently breaking\n * rows+cursor atomicity. `ctx.tx` is the database itself inside the open\n * transaction. Remote/libsql drivers are NOT supported for commits (their\n * statement-per-request model can't hold this transaction open).\n *\n * ```ts\n * const sink = drizzleSink(db, { id: \"sales\", tables: [sales], height: \"blockHeight\" });\n * await index.contractCalls.consume({\n * contractId: MARKETPLACE, functionName: \"purchase-asset\", fromHeight: 0,\n * sink,\n * onBatch: (calls, _env, ctx) =>\n * ctx.tx.insert(sales).values(calls.map(toSale)).onConflictDoNothing(),\n * });\n * ```\n */\nexport function drizzleSink<TDb extends DrizzleDatabaseLike, T extends Table>(\n\tdb: TDb,\n\toptions: DrizzleSinkOptions<T, DrizzleTx<TDb>>,\n): ConsumerSink<DrizzleTx<TDb>> {\n\tconst checkpointTable = options.checkpointTable ?? DEFAULT_CHECKPOINT_TABLE;\n\tconst cp = quoteIdent(checkpointTable);\n\t// Postgres speaks `execute`; sqlite speaks `run`/`all`.\n\tconst isPg = typeof db.execute === \"function\";\n\n\t// Resolve TS column keys to real SQL names per table (they can differ:\n\t// `blockHeight: integer(\"block_height\")`), and validate them here — the\n\t// base only validates what it's given, and it's given the resolved names.\n\tconst heightByTable = new Map<string, string>();\n\tfor (const table of options.tables) {\n\t\tconst tableName = getTableName(table);\n\t\tconst column = getTableColumns(table)[options.height];\n\t\tif (!column) {\n\t\t\tthrow new ValidationError(\n\t\t\t\t`drizzleSink: table \"${tableName}\" has no \"${options.height}\" column key — the height stamp is what makes reorg rollback possible.`,\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\t\tassertSqlIdentifier(column.name, \"drizzleSink\");\n\t\theightByTable.set(tableName, column.name);\n\t}\n\tconst tableNames = [...heightByTable.keys()];\n\n\t/** Run a raw statement on the db or a lent tx, either dialect. */\n\tasync function exec(\n\t\ton: DrizzleDatabaseLike,\n\t\tquery: ReturnType<typeof sql>,\n\t): Promise<void> {\n\t\tif (isPg) await on.execute?.(query);\n\t\telse await on.run?.(query);\n\t}\n\n\t/** Read rows from a raw statement, either dialect. */\n\tasync function rows(query: ReturnType<typeof sql>): Promise<unknown[]> {\n\t\tif (isPg) {\n\t\t\tconst result = (await db.execute?.(query)) as\n\t\t\t\t| { rows?: unknown[] }\n\t\t\t\t| unknown[];\n\t\t\treturn Array.isArray(result) ? result : (result?.rows ?? []);\n\t\t}\n\t\treturn ((await db.all?.(query)) as unknown[]) ?? [];\n\t}\n\n\tconst driver: SinkDriver<DrizzleTx<TDb>> = {\n\t\tasync transact(fn) {\n\t\t\tif (isPg) {\n\t\t\t\treturn (await db.transaction((tx: DrizzleTx<TDb>) =>\n\t\t\t\t\tfn(tx),\n\t\t\t\t)) as Awaited<ReturnType<typeof fn>>;\n\t\t\t}\n\t\t\t// SQLite: manual BEGIN IMMEDIATE (see dialect notes above). The\n\t\t\t// database itself is the lent transaction.\n\t\t\tawait db.run?.(sql`begin immediate`);\n\t\t\ttry {\n\t\t\t\tconst result = await fn(db as unknown as DrizzleTx<TDb>);\n\t\t\t\tawait db.run?.(sql`commit`);\n\t\t\t\treturn result;\n\t\t\t} catch (err) {\n\t\t\t\tawait db.run?.(sql`rollback`);\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\n\t\tasync ensureCheckpointStore() {\n\t\t\ttry {\n\t\t\t\tawait exec(\n\t\t\t\t\tdb,\n\t\t\t\t\tsql.raw(\n\t\t\t\t\t\t`CREATE TABLE IF NOT EXISTS ${cp} (id text PRIMARY KEY, cursor text NOT NULL)`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t} catch (err) {\n\t\t\t\t// Concurrent first start: Postgres IF NOT EXISTS still throws\n\t\t\t\t// under a race (42P07 / 23505); either code means the table\n\t\t\t\t// exists. Drizzle may wrap the driver error, so check the cause.\n\t\t\t\tconst code =\n\t\t\t\t\t(err as { code?: string } | null)?.code ??\n\t\t\t\t\t(err as { cause?: { code?: string } } | null)?.cause?.code ??\n\t\t\t\t\tundefined;\n\t\t\t\tif (code !== \"42P07\" && code !== \"23505\") throw err;\n\t\t\t}\n\t\t},\n\n\t\tasync readCursor() {\n\t\t\tconst found = (await rows(\n\t\t\t\tsql`SELECT cursor FROM ${sql.raw(cp)} WHERE id = ${options.id}`,\n\t\t\t)) as Array<{ cursor: string }>;\n\t\t\treturn found[0]?.cursor ?? null;\n\t\t},\n\n\t\tasync writeCursor(tx, cursor) {\n\t\t\t// `excluded` works in both dialects' upsert grammar.\n\t\t\tawait exec(\n\t\t\t\ttx as unknown as DrizzleDatabaseLike,\n\t\t\t\tsql`INSERT INTO ${sql.raw(cp)} (id, cursor) VALUES (${options.id}, ${cursor}) ON CONFLICT (id) DO UPDATE SET cursor = excluded.cursor`,\n\t\t\t);\n\t\t},\n\n\t\tasync clearCursor(tx) {\n\t\t\tawait exec(\n\t\t\t\ttx as unknown as DrizzleDatabaseLike,\n\t\t\t\tsql`DELETE FROM ${sql.raw(cp)} WHERE id = ${options.id}`,\n\t\t\t);\n\t\t},\n\n\t\tasync deleteAtOrAbove(tx, table, height) {\n\t\t\tconst heightCol = heightByTable.get(table);\n\t\t\tif (!heightCol) return;\n\t\t\tawait exec(\n\t\t\t\ttx as unknown as DrizzleDatabaseLike,\n\t\t\t\tsql`DELETE FROM ${sql.raw(quoteIdent(table))} WHERE ${sql.raw(quoteIdent(heightCol))} >= ${height}`,\n\t\t\t);\n\t\t},\n\n\t\tasync hasColumn(table) {\n\t\t\t// The real SQL name was resolved from the schema object; check the\n\t\t\t// DATABASE carries it — schema objects prove intent, not migrations.\n\t\t\tconst heightCol = heightByTable.get(table);\n\t\t\tif (!heightCol) return false;\n\t\t\tif (isPg) {\n\t\t\t\tconst found = (await rows(\n\t\t\t\t\tsql`SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = ${table} AND column_name = ${heightCol}) AS \"ok\"`,\n\t\t\t\t)) as Array<{ ok: boolean }>;\n\t\t\t\treturn found[0]?.ok === true;\n\t\t\t}\n\t\t\tconst found = await rows(\n\t\t\t\tsql`SELECT 1 AS ok FROM pragma_table_info(${table}) WHERE name = ${heightCol}`,\n\t\t\t);\n\t\t\treturn found.length > 0;\n\t\t},\n\n\t\t// Postgres only: SQLite's single write lock already enforces\n\t\t// single-writer at the file level.\n\t\t...(isPg\n\t\t\t? {\n\t\t\t\t\tasync acquireLock(tx: DrizzleTx<TDb>) {\n\t\t\t\t\t\t// hashtextextended (int8) rather than hashtext (int4): the\n\t\t\t\t\t\t// 32-bit space collides across distinct ids at real scale,\n\t\t\t\t\t\t// and a collision reads as a false \"another consumer\" 409.\n\t\t\t\t\t\tconst found = (await (\n\t\t\t\t\t\t\ttx as unknown as DrizzleDatabaseLike\n\t\t\t\t\t\t).execute?.(\n\t\t\t\t\t\t\tsql`SELECT pg_try_advisory_xact_lock(hashtextextended(${options.id}, 0)) AS \"ok\"`,\n\t\t\t\t\t\t)) as { rows?: Array<{ ok: boolean }> } | Array<{ ok: boolean }>;\n\t\t\t\t\t\tconst row = Array.isArray(found) ? found[0] : found?.rows?.[0];\n\t\t\t\t\t\tif (row?.ok !== true) {\n\t\t\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t\t\t`drizzleSink: another consumer holds the \"${options.id}\" lock — two replicas of the same consumer would interleave commits. Stop the other instance or use a distinct id.`,\n\t\t\t\t\t\t\t\t409,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t};\n\n\treturn createSink(driver, {\n\t\tlabel: \"drizzleSink\",\n\t\tid: options.id,\n\t\ttables: tableNames,\n\t\theight: options.height,\n\t\tcheckpointTable,\n\t\tonRollback: options.onRollback,\n\t});\n}\n"
7
+ "import { ValidationError } from \"../errors.ts\";\nimport type { ConsumerSink } from \"./types.ts\";\n\n/** Default checkpoint table name, shared by every SQL-backed sink so two\n * sinks on the same database land in one table keyed by `id`. */\nexport const DEFAULT_CHECKPOINT_TABLE = \"sl_consumer_checkpoints\";\n\n/**\n * The driver surface a database adapter implements — the ~7 methods that\n * actually differ between SQL stores. Everything above them (the\n * commit/rollback call sequences, the guards, identifier validation, the\n * checkpoint schema) is policy owned by {@link createSink}, so a driver\n * cannot get the ordering wrong: it never sees a cursor write outside the\n * transaction that carries the rows, and never sees a delete without the\n * rewound cursor riding along.\n *\n * Identifier args (`table`, `column`) arrive pre-validated against\n * `[A-Za-z_][A-Za-z0-9_]*` — safe to interpolate after quoting (see\n * {@link quoteIdent}). Values (cursor, height, id) must still be bound as\n * parameters.\n */\nexport interface SinkDriver<Tx> {\n\t/** Run `fn` inside ONE transaction: begin, run, commit — a throw from\n\t * `fn` must roll the whole transaction back and re-throw. */\n\ttransact<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;\n\t/** Create the checkpoint store if missing: `(id primary key, cursor)`.\n\t * Must be safe under concurrent first start (two consumers racing the\n\t * same CREATE). */\n\tensureCheckpointStore(): Promise<void>;\n\t/** The committed cursor for this sink's `id`, or `null` if none. */\n\treadCursor(): Promise<string | null>;\n\t/** Upsert this sink's cursor row inside `tx`. */\n\twriteCursor(tx: Tx, cursor: string): Promise<void>;\n\t/** Delete this sink's cursor row inside `tx`: a rewind to pre-genesis\n\t * (fork at block 0). Optional for drivers written before it existed;\n\t * without it a genesis rewind throws instead of writing a bad cursor. */\n\tclearCursor?(tx: Tx): Promise<void>;\n\t/** Delete rows with `height column >= height` from `table`, inside `tx`. */\n\tdeleteAtOrAbove(tx: Tx, table: string, height: number): Promise<void>;\n\t/** Whether `table` exists AND carries `column` — the first-use check that\n\t * keeps reorg rollback from being a silent no-op. */\n\thasColumn(table: string, column: string): Promise<boolean>;\n\t/** Optional: assert this consumer is the only live writer for its `id`,\n\t * inside `tx`. Must FAIL LOUDLY (throw) when another writer holds it —\n\t * never block or interleave (contract invariant #13). */\n\tacquireLock?(tx: Tx): Promise<void>;\n}\n\n/** Context handed to {@link CreateSinkOptions.onRollback}. `forkPointHeight`\n * is the undo range (inclusive `>=`); `rewindCursor` is what the checkpoint\n * will become after this transaction — `null` only for a fork at genesis. */\nexport type SinkRollbackContext = {\n\tforkPointHeight: number;\n\trewindCursor: string | null;\n};\n\n/** Options for {@link createSink} — the policy inputs, driver-agnostic.\n * Concrete sinks re-expose these with schema-typed `tables`/`height`. */\nexport interface CreateSinkOptions<Tx = unknown> {\n\t/** Error-message prefix naming the concrete sink (e.g. `\"kyselySink\"`),\n\t * so a thrown guard points at the thing the user actually constructed. */\n\tlabel: string;\n\t/** Checkpoint identity AND concurrency key. */\n\tid: string;\n\t/** Rollback scope: on a reorg, rows at/above the fork point are deleted\n\t * from exactly these tables. Fact tables only — a fold (balances,\n\t * counters) does not belong here; invert it in `onRollback`. */\n\ttables: readonly string[];\n\t/** The block-height stamp column, present on every declared table. */\n\theight: string;\n\t/** Checkpoint table name. Default {@link DEFAULT_CHECKPOINT_TABLE}. */\n\tcheckpointTable?: string;\n\t/** Forwarded to {@link ConsumerSink.capabilities}. */\n\tcapabilities?: ConsumerSink[\"capabilities\"];\n\t/** Runs inside the rollback transaction after the lock, before any\n\t * `DELETE … height >= fork`. Doomed fact rows are still visible.\n\t * Remaining facts are `height < forkPointHeight`. A throw aborts the\n\t * whole rewind (no delete, no cursor write). Called on every\n\t * `rollback`, including re-application after a crash — derive undo\n\t * from the doomed rows so a second pass is a no-op. */\n\tonRollback?: (tx: Tx, ctx: SinkRollbackContext) => Promise<void> | void;\n}\n\n/** Throw unless `name` is a bare SQL identifier (letters, digits,\n * underscores; no leading digit) — the guard that makes interpolating\n * table/column names into DDL and DELETE statements safe. */\nexport function assertSqlIdentifier(name: string, label: string): void {\n\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {\n\t\tthrow new ValidationError(\n\t\t\t`${label}: invalid identifier \"${name}\" (letters, digits, underscores only).`,\n\t\t\t400,\n\t\t);\n\t}\n}\n\n/** Double-quote a (pre-validated) identifier for interpolation. */\nexport function quoteIdent(name: string): string {\n\treturn `\"${name}\"`;\n}\n\n/**\n * Build a {@link ConsumerSink} from a {@link SinkDriver}: the portable ~90\n * lines every SQL sink otherwise re-implements — and re-risks. Owns the two\n * transaction sequences (begin → lock → write rows → write cursor;\n * begin → lock → onRollback? → delete `>=` fork → write rewound cursor), the\n * empty-`tables` guard, identifier validation, and the first-use height-column\n * check.\n *\n * A driver implemented against this base cannot violate contract invariants\n * #4, #5, #8, #9, or #11 (see {@link ConsumerSink}) without breaking\n * `transact` itself — which is exactly what the conformance kit probes.\n */\nexport function createSink<Tx>(\n\tdriver: SinkDriver<Tx>,\n\toptions: CreateSinkOptions<Tx>,\n): ConsumerSink<Tx> {\n\tconst { label } = options;\n\tconst checkpointTable = options.checkpointTable ?? DEFAULT_CHECKPOINT_TABLE;\n\tif (options.tables.length === 0) {\n\t\tthrow new ValidationError(\n\t\t\t`${label}: \\`tables\\` is empty — declare every table the handler writes, or reorg rollback is a no-op and orphaned rows persist forever.`,\n\t\t\t400,\n\t\t);\n\t}\n\tassertSqlIdentifier(checkpointTable, label);\n\tassertSqlIdentifier(options.height, label);\n\tfor (const table of options.tables) assertSqlIdentifier(table, label);\n\n\treturn {\n\t\tcapabilities: options.capabilities,\n\n\t\tasync loadCursor() {\n\t\t\tawait driver.ensureCheckpointStore();\n\t\t\t// First-use validation of the rollback precondition: every declared\n\t\t\t// table must exist and carry the height column. Failing here beats a\n\t\t\t// rollback that silently deletes nothing during a reorg.\n\t\t\tfor (const table of options.tables) {\n\t\t\t\tif (!(await driver.hasColumn(table, options.height))) {\n\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t`${label}: table \"${table}\" has no \"${options.height}\" column — the height stamp is what makes reorg rollback possible. Add the column (or fix \\`height\\`).`,\n\t\t\t\t\t\t400,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn driver.readCursor();\n\t\t},\n\n\t\tasync commitBatch(cursor, write) {\n\t\t\tawait driver.transact(async (tx) => {\n\t\t\t\tawait driver.acquireLock?.(tx);\n\t\t\t\tawait write(tx);\n\t\t\t\tawait driver.writeCursor(tx, cursor);\n\t\t\t});\n\t\t},\n\n\t\tasync rollback(forkPointHeight, rewindCursor) {\n\t\t\tawait driver.transact(async (tx) => {\n\t\t\t\tawait driver.acquireLock?.(tx);\n\t\t\t\t// Folds (balances, last-wins) invert/recompute here, while doomed\n\t\t\t\t// fact rows are still visible. A throw aborts the rewind.\n\t\t\t\tawait options.onRollback?.(tx, { forkPointHeight, rewindCursor });\n\t\t\t\t// INCLUSIVE of the fork block: the new canonical chain re-supplies\n\t\t\t\t// it, and the consumer rewinds to re-read from `fork:0`.\n\t\t\t\tfor (const table of options.tables) {\n\t\t\t\t\tawait driver.deleteAtOrAbove(tx, table, forkPointHeight);\n\t\t\t\t}\n\t\t\t\tif (rewindCursor === null) {\n\t\t\t\t\tif (!driver.clearCursor) {\n\t\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t\t`${label}: this driver has no clearCursor, so a rewind to pre-genesis cannot be committed. Add clearCursor to the driver, or resume with an explicit fromCursor.`,\n\t\t\t\t\t\t\t400,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tawait driver.clearCursor(tx);\n\t\t\t\t} else {\n\t\t\t\t\tawait driver.writeCursor(tx, rewindCursor);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t};\n}\n"
8
8
  ],
9
- "mappings": "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",
10
- "debugId": "53FB4A01DD45FDF064756E2164756E21",
9
+ "mappings": "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",
10
+ "debugId": "6278C50044494CBE64756E2164756E21",
11
11
  "names": []
12
12
  }
@@ -1,3 +1,4 @@
1
+ import { Kysely, Transaction } from "kysely";
1
2
  /**
2
3
  * A consumer sink: the destination adapter a consume loop writes through.
3
4
  *
@@ -127,7 +128,6 @@ type SinkRollbackContext = {
127
128
  forkPointHeight: number;
128
129
  rewindCursor: string | null;
129
130
  };
130
- import { Kysely, Transaction } from "kysely";
131
131
  /**
132
132
  * Column present on EVERY declared table — `keyof` a union of row types is
133
133
  * the intersection of their keys, so a column missing from any one table is
@@ -205,4 +205,4 @@ declare function kyselySink<
205
205
  DB,
206
206
  T extends keyof DB & string
207
207
  >(db: Kysely<DB>, options: KyselySinkOptions<DB, T>): ConsumerSink<Transaction<DB>>;
208
- export { kyselySink, SinkRollbackContext, KyselySinkOptions };
208
+ export { KyselySinkOptions, SinkRollbackContext, kyselySink };
@@ -1,3 +1,6 @@
1
+ // src/sinks/kysely.ts
2
+ import { sql } from "kysely";
3
+
1
4
  // src/errors.ts
2
5
  var DOCS_BASE = "https://www.secondlayer.tools";
3
6
 
@@ -214,7 +217,6 @@ function createSink(driver, options) {
214
217
  }
215
218
 
216
219
  // src/sinks/kysely.ts
217
- import { sql } from "kysely";
218
220
  function kyselySink(db, options) {
219
221
  const checkpointTable = options.checkpointTable ?? DEFAULT_CHECKPOINT_TABLE;
220
222
  const cp = quoteIdent(checkpointTable);
@@ -282,5 +284,5 @@ export {
282
284
  kyselySink
283
285
  };
284
286
 
285
- //# debugId=21FF4D87E9320E4A64756E2164756E21
287
+ //# debugId=4A548117F590C0CE64756E2164756E21
286
288
  //# sourceMappingURL=kysely.js.map
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["src/errors.ts", "src/sinks/core.ts", "src/sinks/kysely.ts"],
3
+ "sources": ["src/sinks/kysely.ts", "src/errors.ts", "src/sinks/core.ts"],
4
4
  "sourcesContent": [
5
+ "import { type Kysely, type Transaction, sql } from \"kysely\";\nimport { ValidationError } from \"../errors.ts\";\nimport {\n\tDEFAULT_CHECKPOINT_TABLE,\n\ttype SinkDriver,\n\ttype SinkRollbackContext,\n\tcreateSink,\n\tquoteIdent,\n} from \"./core.ts\";\nimport type { ConsumerSink } from \"./types.ts\";\n\nexport type { SinkRollbackContext };\n\n/**\n * Column present on EVERY declared table — `keyof` a union of row types is\n * the intersection of their keys, so a column missing from any one table is\n * a compile error at the `height:` option.\n */\ntype CommonColumn<DB, T extends keyof DB> = keyof DB[T] & string;\n\nexport interface KyselySinkOptions<DB, T extends keyof DB & string> {\n\t/** Checkpoint identity AND concurrency key: the cursor row's primary key,\n\t * and the input to `pg_try_advisory_xact_lock(hashtextextended(id, 0))`.\n\t * The lock is transaction-scoped: it stops two replicas from\n\t * INTERLEAVING inside a commit, but is released at each commit boundary\n\t * — it is NOT a fence, so two replicas alternating batches both pass.\n\t * Run exactly one consumer per id; the lock turns the racy overlap loud,\n\t * it does not make a second replica safe. */\n\tid: string;\n\t/** Rollback scope. On a reorg, rows AT OR ABOVE the fork point are deleted\n\t * from exactly these tables (inclusive `>=` — the new chain re-supplies\n\t * the fork block). Fact tables only: a fold (balances, counters) does\n\t * not belong here — invert it in `onRollback`. A table you write but\n\t * don't declare keeps orphaned rows forever. */\n\ttables: readonly T[];\n\t/** The block-height stamp column, present on every declared table (compile\n\t * and first-use checked). Height-stamp rollback is correct for\n\t * append-only projections (one row per event/call). */\n\theight: CommonColumn<DB, T>;\n\t/** Checkpoint table name. Default `sl_consumer_checkpoints` (created on\n\t * first use). */\n\tcheckpointTable?: string;\n\t/** Same transaction as the fact-table delete, before it. Doomed rows are\n\t * still visible; remaining facts are `height < forkPointHeight`. A throw\n\t * aborts the rewind. Derive undo from the doomed rows so a re-applied\n\t * rollback is a no-op. */\n\tonRollback?: (\n\t\ttx: Transaction<DB>,\n\t\tctx: SinkRollbackContext,\n\t) => Promise<void> | void;\n}\n\n/**\n * A {@link ConsumerSink} over a Kysely Postgres database — keyed on the\n * QUERY BUILDER (not the driver) so the `DB` schema generic flows through:\n * `tables` is `(keyof DB)[]`, `height` must exist on every declared table,\n * and `ctx.tx` inside `onBatch` is a fully typed `Transaction<DB>`.\n *\n * What it owns (so your indexer doesn't):\n * - the checkpoint table + cursor load on start;\n * - rows AND cursor committed in ONE transaction per batch — a handler\n * throw aborts both, so a crashed batch is re-read on restart with no\n * gaps and no double-writes;\n * - reorg rollback: `onRollback` (doomed facts still visible), then delete\n * `>= fork_point_height` from every declared table, then the rewound\n * cursor — one transaction (the crash-between-the-two-writes gap is\n * unrepresentable);\n * - a per-id advisory lock, so two replicas of the same consumer can't\n * interleave commits.\n *\n * Structurally it is a 7-method {@link SinkDriver} on the shared\n * `createSink` base (`@secondlayer/sdk/sinks/core`), which owns the\n * transaction sequences and guards — this file only knows how to speak\n * Postgres-flavored Kysely.\n *\n * ```ts\n * const sink = kyselySink(db, { id: \"sales\", tables: [\"sales\"], height: \"block_height\" });\n * await index.contractCalls.consume({\n * contractId: MARKETPLACE, functionName: \"purchase-asset\", fromHeight: 0,\n * sink,\n * onBatch: (calls, _env, ctx) =>\n * ctx.tx.insertInto(\"sales\").values(calls.map(toSale)).execute(),\n * });\n * ```\n */\nexport function kyselySink<DB, T extends keyof DB & string>(\n\tdb: Kysely<DB>,\n\toptions: KyselySinkOptions<DB, T>,\n): ConsumerSink<Transaction<DB>> {\n\tconst checkpointTable = options.checkpointTable ?? DEFAULT_CHECKPOINT_TABLE;\n\tconst cp = quoteIdent(checkpointTable);\n\n\tconst driver: SinkDriver<Transaction<DB>> = {\n\t\ttransact: (fn) => db.transaction().execute(fn),\n\n\t\tasync ensureCheckpointStore() {\n\t\t\ttry {\n\t\t\t\tawait sql\n\t\t\t\t\t.raw(\n\t\t\t\t\t\t`CREATE TABLE IF NOT EXISTS ${cp} (id text PRIMARY KEY, cursor text NOT NULL)`,\n\t\t\t\t\t)\n\t\t\t\t\t.execute(db);\n\t\t\t} catch (err) {\n\t\t\t\t// Two consumers racing the same first start: IF NOT EXISTS still\n\t\t\t\t// throws under concurrency (42P07 duplicate_table, or 23505 on the\n\t\t\t\t// pg_type catalog). Either way the table exists — the outcome we\n\t\t\t\t// were creating.\n\t\t\t\tconst code = (err as { code?: string } | null)?.code;\n\t\t\t\tif (code !== \"42P07\" && code !== \"23505\") throw err;\n\t\t\t}\n\t\t},\n\n\t\tasync readCursor() {\n\t\t\tconst row = await sql<{ cursor: string }>`\n\t\t\t\tSELECT cursor FROM ${sql.raw(cp)} WHERE id = ${options.id}\n\t\t\t`.execute(db);\n\t\t\treturn row.rows[0]?.cursor ?? null;\n\t\t},\n\n\t\tasync writeCursor(tx, cursor) {\n\t\t\t// Identifiers via validated sql.raw; VALUES via bound parameters.\n\t\t\tawait sql`\n\t\t\t\tINSERT INTO ${sql.raw(cp)} (id, cursor) VALUES (${options.id}, ${cursor})\n\t\t\t\tON CONFLICT (id) DO UPDATE SET cursor = EXCLUDED.cursor\n\t\t\t`.execute(tx);\n\t\t},\n\n\t\tasync clearCursor(tx) {\n\t\t\tawait sql`DELETE FROM ${sql.raw(cp)} WHERE id = ${options.id}`.execute(\n\t\t\t\ttx,\n\t\t\t);\n\t\t},\n\n\t\tasync deleteAtOrAbove(tx, table, height) {\n\t\t\t// Identifiers via validated sql.raw; the height bound as a parameter.\n\t\t\tawait sql`\n\t\t\t\tDELETE FROM ${sql.raw(quoteIdent(table))}\n\t\t\t\tWHERE ${sql.raw(quoteIdent(options.height as string))} >= ${height}\n\t\t\t`.execute(tx);\n\t\t},\n\n\t\tasync hasColumn(table, column) {\n\t\t\tconst col = await sql<{ ok: boolean }>`\n\t\t\t\tSELECT EXISTS (\n\t\t\t\t\tSELECT 1 FROM information_schema.columns\n\t\t\t\t\tWHERE table_schema = current_schema()\n\t\t\t\t\t\tAND table_name = ${table} AND column_name = ${column}\n\t\t\t\t) AS \"ok\"\n\t\t\t`.execute(db);\n\t\t\treturn col.rows[0]?.ok ?? false;\n\t\t},\n\n\t\tasync acquireLock(tx) {\n\t\t\t// hashtextextended (int8), not hashtext (int4): the 32-bit space\n\t\t\t// collides across distinct ids at real scale, and a collision reads\n\t\t\t// as a false \"another consumer\" 409 between unrelated sinks.\n\t\t\tconst locked = await sql<{ ok: boolean }>`\n\t\t\t\tSELECT pg_try_advisory_xact_lock(hashtextextended(${options.id}, 0)) AS \"ok\"\n\t\t\t`.execute(tx);\n\t\t\tif (!locked.rows[0]?.ok) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t`kyselySink: another consumer holds the \"${options.id}\" lock — two replicas of the same consumer would interleave commits. Stop the other instance or use a distinct id.`,\n\t\t\t\t\t409,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t};\n\n\treturn createSink(driver, {\n\t\tlabel: \"kyselySink\",\n\t\tid: options.id,\n\t\ttables: options.tables,\n\t\theight: options.height as string,\n\t\tcheckpointTable,\n\t\tonRollback: options.onRollback,\n\t});\n}\n",
5
6
  "const DOCS_BASE = \"https://www.secondlayer.tools\";\n\n/** Options accepted by every error in the family. */\nexport interface SecondLayerErrorOptions {\n\tcause?: Error;\n\tdetails?: string;\n\t/** Stable machine-readable code (e.g. `UPGRADE_REQUIRED`). */\n\tcode?: string;\n\t/** Docs page that explains this failure and its fix. */\n\tdocsUrl?: string;\n\t/** Extra context lines appended to the message (DDL to run, next steps). */\n\tmetaMessages?: string[];\n\t/** Whether retrying the SAME request can succeed (429/5xx/network). */\n\tretryable?: boolean;\n\t/** Parsed `Retry-After`, in seconds — set on rate limits that carry it. */\n\tretryAfterSeconds?: number;\n}\n\n/**\n * Root of the SDK error family. Implements the same protocol as `BaseError`\n * in `@secondlayer/stacks` (`shortMessage`, `cause`, `toJSON`) so a failed\n * chain read and a failed platform read serialize and introspect identically.\n * It intentionally does NOT extend that class: cross-package `instanceof` is\n * unreliable under per-package bundling anyway (match on `code`/`name` across\n * packages).\n *\n * `retryable` is the signal the consume loops act on: 429/5xx/network are\n * retryable, 4xx and body-serialization failures are not.\n */\nexport class SecondLayerError extends Error {\n\toverride name = \"SecondLayerError\";\n\t/** The one-line failure, without the appended context blocks. */\n\tshortMessage: string;\n\tcode?: string;\n\tdocsUrl?: string;\n\tmetaMessages?: string[];\n\tretryable: boolean;\n\tretryAfterSeconds?: number;\n\n\tconstructor(shortMessage: string, options: SecondLayerErrorOptions = {}) {\n\t\tconst detailBlocks = [\n\t\t\t...(options.metaMessages ?? []),\n\t\t\t...(options.details ? [options.details] : []),\n\t\t\t...(options.docsUrl ? [`Docs: ${options.docsUrl}`] : []),\n\t\t];\n\t\tconst message = [\n\t\t\tshortMessage,\n\t\t\tdetailBlocks.length > 0 ? `\\n${detailBlocks.join(\"\\n\")}` : \"\",\n\t\t].join(\"\");\n\t\tsuper(message, options.cause ? { cause: options.cause } : undefined);\n\t\tthis.shortMessage = shortMessage;\n\t\tthis.code = options.code;\n\t\tthis.docsUrl = options.docsUrl;\n\t\tthis.metaMessages = options.metaMessages;\n\t\tthis.retryable = options.retryable ?? false;\n\t\tthis.retryAfterSeconds = options.retryAfterSeconds;\n\t}\n\n\t/**\n\t * Walk the cause chain. With a predicate, returns the first error matching\n\t * it (or `null`); without one, returns the deepest cause. Mirrors viem's\n\t * `error.walk()` so cross-library error handling reads the same.\n\t */\n\twalk(fn?: (err: unknown) => boolean): unknown {\n\t\t// biome-ignore lint/suspicious/noExplicitAny: cause chains are untyped by nature\n\t\tlet err: any = this;\n\t\tlet last: unknown = this;\n\t\twhile (err != null) {\n\t\t\tif (fn?.(err)) return err;\n\t\t\tlast = err;\n\t\t\terr = err.cause;\n\t\t}\n\t\treturn fn ? null : last;\n\t}\n\n\ttoJSON(): {\n\t\tname: string;\n\t\tmessage: string;\n\t\tshortMessage: string;\n\t\tcause: string | undefined;\n\t\tcode: string | undefined;\n\t\tdocsUrl: string | undefined;\n\t\tretryable: boolean;\n\t\tretryAfterSeconds: number | undefined;\n\t} {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tmessage: this.message,\n\t\t\tshortMessage: this.shortMessage,\n\t\t\tcause: this.cause instanceof Error ? this.cause.message : undefined,\n\t\t\tcode: this.code,\n\t\t\tdocsUrl: this.docsUrl,\n\t\t\tretryable: this.retryable,\n\t\t\tretryAfterSeconds: this.retryAfterSeconds,\n\t\t};\n\t}\n}\n\n/** Retryability of an HTTP status: 429 and 5xx are transient. */\nfunction statusRetryable(status: number): boolean {\n\treturn status === 429 || status >= 500;\n}\n\n/** Docs page for a status/code pair, when one exists. */\nfunction docsFor(status: number, code?: string): string | undefined {\n\tif (code === \"UPGRADE_REQUIRED\" || status === 402)\n\t\treturn `${DOCS_BASE}/docs/authentication#pay-as-you-go-credits`;\n\tif (status === 429 || status === 401)\n\t\treturn `${DOCS_BASE}/docs/authentication`;\n\treturn undefined;\n}\n\n/**\n * Error thrown by {@link SecondLayer} when an API request fails.\n * Includes the HTTP status code for programmatic error handling.\n *\n * @example\n * ```ts\n * try {\n * await client.subgraphs.status(\"my-subgraph\");\n * } catch (err) {\n * if (err instanceof ApiError && err.status === 404) {\n * console.log(\"Subgraph not found\");\n * }\n * }\n * ```\n */\nexport class ApiError extends SecondLayerError {\n\tconstructor(\n\t\t/** HTTP status code (0 for network errors). */\n\t\tpublic status: number,\n\t\tmessage: string,\n\t\t/** Raw response body (parsed JSON if possible) — preserved for callers that need error details. */\n\t\tpublic body?: unknown,\n\t\t/** Stable machine-readable code from the API's `{error, code}` error envelope. */\n\t\tcode?: string,\n\t\toptions: SecondLayerErrorOptions = {},\n\t) {\n\t\tsuper(message, {\n\t\t\tcode,\n\t\t\tretryable: options.retryable ?? statusRetryable(status),\n\t\t\tdocsUrl: options.docsUrl ?? docsFor(status, code),\n\t\t\t...(options.cause ? { cause: options.cause } : {}),\n\t\t\t...(options.details ? { details: options.details } : {}),\n\t\t\t...(options.metaMessages ? { metaMessages: options.metaMessages } : {}),\n\t\t\t...(options.retryAfterSeconds !== undefined\n\t\t\t\t? { retryAfterSeconds: options.retryAfterSeconds }\n\t\t\t\t: {}),\n\t\t});\n\t\tthis.name = \"ApiError\";\n\t}\n}\n\n/** Thrown on a 401 by both the instance clients and Streams. Carries the\n * server's `{error, code}` envelope when it sent one, so a revoked token reads\n * differently from a missing one. */\nexport class AuthError extends ApiError {\n\toverride readonly status = 401 as const;\n\n\tconstructor(\n\t\tmessage = \"API key invalid or expired.\",\n\t\tbody?: unknown,\n\t\tcode?: string,\n\t) {\n\t\tsuper(401, message, body, code);\n\t\tthis.name = \"AuthError\";\n\t}\n}\n\n/** Thrown on a 429 by both the instance clients and Streams. `retryable`,\n * with `retryAfterSeconds` parsed from the `Retry-After` header when sent. */\nexport class RateLimitError extends ApiError {\n\toverride readonly status = 429 as const;\n\n\tconstructor(\n\t\tmessage = \"Rate limited. Try again later.\",\n\t\t/** Raw `Retry-After` header value (seconds or HTTP-date). */\n\t\treadonly retryAfter?: string,\n\t\tbody?: unknown,\n\t\tcode?: string,\n\t) {\n\t\tsuper(429, message, body, code, {\n\t\t\tretryable: true,\n\t\t\tretryAfterSeconds: parseRetryAfter(retryAfter),\n\t\t});\n\t\tthis.name = \"RateLimitError\";\n\t}\n}\n\n/** Thrown on a 4xx the caller can fix (bad cursor, bad params). Never retried.\n * `code` is the server's machine-readable code when the envelope carried one. */\nexport class ValidationError extends ApiError {\n\tconstructor(message: string, status: number, body?: unknown, code?: string) {\n\t\tsuper(status, message, body, code, { retryable: false });\n\t\tthis.name = \"ValidationError\";\n\t}\n}\n\n/** Thrown when an archive manifest signature is missing or does not verify. */\nexport class ArchiveSignatureError extends SecondLayerError {\n\tconstructor(message = \"Archive manifest signature is missing or invalid.\") {\n\t\tsuper(message, { code: \"ARCHIVE_SIGNATURE\", retryable: false });\n\t\tthis.name = \"ArchiveSignatureError\";\n\t}\n}\n\n/** Thrown on a 401 from the archive ops host (quote/fetch/credits). */\nexport class ArchiveAuthError extends SecondLayerError {\n\tconstructor(message = \"Archive ops request was not authenticated.\") {\n\t\tsuper(message, { code: \"ARCHIVE_AUTH\", retryable: false });\n\t\tthis.name = \"ArchiveAuthError\";\n\t}\n}\n\n/** Thrown on a 402 from archive fetch — prepaid credits do not cover the quote. */\nexport class InsufficientArchiveCreditsError extends SecondLayerError {\n\tshortfallUsdMicros?: number;\n\tconstructor(\n\t\tmessage = \"Insufficient archive credits.\",\n\t\tshortfallUsdMicros?: number,\n\t) {\n\t\tsuper(message, {\n\t\t\tcode: \"INSUFFICIENT_ARCHIVE_CREDITS\",\n\t\t\tretryable: false,\n\t\t});\n\t\tthis.name = \"InsufficientArchiveCreditsError\";\n\t\tthis.shortfallUsdMicros = shortfallUsdMicros;\n\t}\n}\n\n/** Thrown on a 503 when the archive fetch gate is unconfigured. */\nexport class ArchiveGateNotConfiguredError extends SecondLayerError {\n\tconstructor(message = \"The archive gate is not configured on the server.\") {\n\t\tsuper(message, { code: \"ARCHIVE_GATE_NOT_CONFIGURED\", retryable: false });\n\t\tthis.name = \"ArchiveGateNotConfiguredError\";\n\t}\n}\n\n/** Parse a `Retry-After` header: delta-seconds or an HTTP-date. */\nexport function parseRetryAfter(value?: string | null): number | undefined {\n\tif (!value) return undefined;\n\tconst seconds = Number(value);\n\tif (Number.isFinite(seconds) && seconds >= 0) return seconds;\n\tconst date = Date.parse(value);\n\tif (!Number.isNaN(date)) return Math.max(0, (date - Date.now()) / 1000);\n\treturn undefined;\n}\n",
6
- "import { ValidationError } from \"../errors.ts\";\nimport type { ConsumerSink } from \"./types.ts\";\n\n/** Default checkpoint table name, shared by every SQL-backed sink so two\n * sinks on the same database land in one table keyed by `id`. */\nexport const DEFAULT_CHECKPOINT_TABLE = \"sl_consumer_checkpoints\";\n\n/**\n * The driver surface a database adapter implements — the ~7 methods that\n * actually differ between SQL stores. Everything above them (the\n * commit/rollback call sequences, the guards, identifier validation, the\n * checkpoint schema) is policy owned by {@link createSink}, so a driver\n * cannot get the ordering wrong: it never sees a cursor write outside the\n * transaction that carries the rows, and never sees a delete without the\n * rewound cursor riding along.\n *\n * Identifier args (`table`, `column`) arrive pre-validated against\n * `[A-Za-z_][A-Za-z0-9_]*` — safe to interpolate after quoting (see\n * {@link quoteIdent}). Values (cursor, height, id) must still be bound as\n * parameters.\n */\nexport interface SinkDriver<Tx> {\n\t/** Run `fn` inside ONE transaction: begin, run, commit — a throw from\n\t * `fn` must roll the whole transaction back and re-throw. */\n\ttransact<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;\n\t/** Create the checkpoint store if missing: `(id primary key, cursor)`.\n\t * Must be safe under concurrent first start (two consumers racing the\n\t * same CREATE). */\n\tensureCheckpointStore(): Promise<void>;\n\t/** The committed cursor for this sink's `id`, or `null` if none. */\n\treadCursor(): Promise<string | null>;\n\t/** Upsert this sink's cursor row inside `tx`. */\n\twriteCursor(tx: Tx, cursor: string): Promise<void>;\n\t/** Delete this sink's cursor row inside `tx`: a rewind to pre-genesis\n\t * (fork at block 0). Optional for drivers written before it existed;\n\t * without it a genesis rewind throws instead of writing a bad cursor. */\n\tclearCursor?(tx: Tx): Promise<void>;\n\t/** Delete rows with `height column >= height` from `table`, inside `tx`. */\n\tdeleteAtOrAbove(tx: Tx, table: string, height: number): Promise<void>;\n\t/** Whether `table` exists AND carries `column` — the first-use check that\n\t * keeps reorg rollback from being a silent no-op. */\n\thasColumn(table: string, column: string): Promise<boolean>;\n\t/** Optional: assert this consumer is the only live writer for its `id`,\n\t * inside `tx`. Must FAIL LOUDLY (throw) when another writer holds it —\n\t * never block or interleave (contract invariant #13). */\n\tacquireLock?(tx: Tx): Promise<void>;\n}\n\n/** Context handed to {@link CreateSinkOptions.onRollback}. `forkPointHeight`\n * is the undo range (inclusive `>=`); `rewindCursor` is what the checkpoint\n * will become after this transaction — `null` only for a fork at genesis. */\nexport type SinkRollbackContext = {\n\tforkPointHeight: number;\n\trewindCursor: string | null;\n};\n\n/** Options for {@link createSink} — the policy inputs, driver-agnostic.\n * Concrete sinks re-expose these with schema-typed `tables`/`height`. */\nexport interface CreateSinkOptions<Tx = unknown> {\n\t/** Error-message prefix naming the concrete sink (e.g. `\"kyselySink\"`),\n\t * so a thrown guard points at the thing the user actually constructed. */\n\tlabel: string;\n\t/** Checkpoint identity AND concurrency key. */\n\tid: string;\n\t/** Rollback scope: on a reorg, rows at/above the fork point are deleted\n\t * from exactly these tables. Fact tables only — a fold (balances,\n\t * counters) does not belong here; invert it in `onRollback`. */\n\ttables: readonly string[];\n\t/** The block-height stamp column, present on every declared table. */\n\theight: string;\n\t/** Checkpoint table name. Default {@link DEFAULT_CHECKPOINT_TABLE}. */\n\tcheckpointTable?: string;\n\t/** Forwarded to {@link ConsumerSink.capabilities}. */\n\tcapabilities?: ConsumerSink[\"capabilities\"];\n\t/** Runs inside the rollback transaction after the lock, before any\n\t * `DELETE … height >= fork`. Doomed fact rows are still visible.\n\t * Remaining facts are `height < forkPointHeight`. A throw aborts the\n\t * whole rewind (no delete, no cursor write). Called on every\n\t * `rollback`, including re-application after a crash — derive undo\n\t * from the doomed rows so a second pass is a no-op. */\n\tonRollback?: (tx: Tx, ctx: SinkRollbackContext) => Promise<void> | void;\n}\n\n/** Throw unless `name` is a bare SQL identifier (letters, digits,\n * underscores; no leading digit) — the guard that makes interpolating\n * table/column names into DDL and DELETE statements safe. */\nexport function assertSqlIdentifier(name: string, label: string): void {\n\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {\n\t\tthrow new ValidationError(\n\t\t\t`${label}: invalid identifier \"${name}\" (letters, digits, underscores only).`,\n\t\t\t400,\n\t\t);\n\t}\n}\n\n/** Double-quote a (pre-validated) identifier for interpolation. */\nexport function quoteIdent(name: string): string {\n\treturn `\"${name}\"`;\n}\n\n/**\n * Build a {@link ConsumerSink} from a {@link SinkDriver}: the portable ~90\n * lines every SQL sink otherwise re-implements — and re-risks. Owns the two\n * transaction sequences (begin → lock → write rows → write cursor;\n * begin → lock → onRollback? → delete `>=` fork → write rewound cursor), the\n * empty-`tables` guard, identifier validation, and the first-use height-column\n * check.\n *\n * A driver implemented against this base cannot violate contract invariants\n * #4, #5, #8, #9, or #11 (see {@link ConsumerSink}) without breaking\n * `transact` itself — which is exactly what the conformance kit probes.\n */\nexport function createSink<Tx>(\n\tdriver: SinkDriver<Tx>,\n\toptions: CreateSinkOptions<Tx>,\n): ConsumerSink<Tx> {\n\tconst { label } = options;\n\tconst checkpointTable = options.checkpointTable ?? DEFAULT_CHECKPOINT_TABLE;\n\tif (options.tables.length === 0) {\n\t\tthrow new ValidationError(\n\t\t\t`${label}: \\`tables\\` is empty — declare every table the handler writes, or reorg rollback is a no-op and orphaned rows persist forever.`,\n\t\t\t400,\n\t\t);\n\t}\n\tassertSqlIdentifier(checkpointTable, label);\n\tassertSqlIdentifier(options.height, label);\n\tfor (const table of options.tables) assertSqlIdentifier(table, label);\n\n\treturn {\n\t\tcapabilities: options.capabilities,\n\n\t\tasync loadCursor() {\n\t\t\tawait driver.ensureCheckpointStore();\n\t\t\t// First-use validation of the rollback precondition: every declared\n\t\t\t// table must exist and carry the height column. Failing here beats a\n\t\t\t// rollback that silently deletes nothing during a reorg.\n\t\t\tfor (const table of options.tables) {\n\t\t\t\tif (!(await driver.hasColumn(table, options.height))) {\n\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t`${label}: table \"${table}\" has no \"${options.height}\" column — the height stamp is what makes reorg rollback possible. Add the column (or fix \\`height\\`).`,\n\t\t\t\t\t\t400,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn driver.readCursor();\n\t\t},\n\n\t\tasync commitBatch(cursor, write) {\n\t\t\tawait driver.transact(async (tx) => {\n\t\t\t\tawait driver.acquireLock?.(tx);\n\t\t\t\tawait write(tx);\n\t\t\t\tawait driver.writeCursor(tx, cursor);\n\t\t\t});\n\t\t},\n\n\t\tasync rollback(forkPointHeight, rewindCursor) {\n\t\t\tawait driver.transact(async (tx) => {\n\t\t\t\tawait driver.acquireLock?.(tx);\n\t\t\t\t// Folds (balances, last-wins) invert/recompute here, while doomed\n\t\t\t\t// fact rows are still visible. A throw aborts the rewind.\n\t\t\t\tawait options.onRollback?.(tx, { forkPointHeight, rewindCursor });\n\t\t\t\t// INCLUSIVE of the fork block: the new canonical chain re-supplies\n\t\t\t\t// it, and the consumer rewinds to re-read from `fork:0`.\n\t\t\t\tfor (const table of options.tables) {\n\t\t\t\t\tawait driver.deleteAtOrAbove(tx, table, forkPointHeight);\n\t\t\t\t}\n\t\t\t\tif (rewindCursor === null) {\n\t\t\t\t\tif (!driver.clearCursor) {\n\t\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t\t`${label}: this driver has no clearCursor, so a rewind to pre-genesis cannot be committed. Add clearCursor to the driver, or resume with an explicit fromCursor.`,\n\t\t\t\t\t\t\t400,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tawait driver.clearCursor(tx);\n\t\t\t\t} else {\n\t\t\t\t\tawait driver.writeCursor(tx, rewindCursor);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t};\n}\n",
7
- "import { type Kysely, type Transaction, sql } from \"kysely\";\nimport { ValidationError } from \"../errors.ts\";\nimport {\n\tDEFAULT_CHECKPOINT_TABLE,\n\ttype SinkDriver,\n\ttype SinkRollbackContext,\n\tcreateSink,\n\tquoteIdent,\n} from \"./core.ts\";\nimport type { ConsumerSink } from \"./types.ts\";\n\nexport type { SinkRollbackContext };\n\n/**\n * Column present on EVERY declared table — `keyof` a union of row types is\n * the intersection of their keys, so a column missing from any one table is\n * a compile error at the `height:` option.\n */\ntype CommonColumn<DB, T extends keyof DB> = keyof DB[T] & string;\n\nexport interface KyselySinkOptions<DB, T extends keyof DB & string> {\n\t/** Checkpoint identity AND concurrency key: the cursor row's primary key,\n\t * and the input to `pg_try_advisory_xact_lock(hashtextextended(id, 0))`.\n\t * The lock is transaction-scoped: it stops two replicas from\n\t * INTERLEAVING inside a commit, but is released at each commit boundary\n\t * — it is NOT a fence, so two replicas alternating batches both pass.\n\t * Run exactly one consumer per id; the lock turns the racy overlap loud,\n\t * it does not make a second replica safe. */\n\tid: string;\n\t/** Rollback scope. On a reorg, rows AT OR ABOVE the fork point are deleted\n\t * from exactly these tables (inclusive `>=` — the new chain re-supplies\n\t * the fork block). Fact tables only: a fold (balances, counters) does\n\t * not belong here — invert it in `onRollback`. A table you write but\n\t * don't declare keeps orphaned rows forever. */\n\ttables: readonly T[];\n\t/** The block-height stamp column, present on every declared table (compile\n\t * and first-use checked). Height-stamp rollback is correct for\n\t * append-only projections (one row per event/call). */\n\theight: CommonColumn<DB, T>;\n\t/** Checkpoint table name. Default `sl_consumer_checkpoints` (created on\n\t * first use). */\n\tcheckpointTable?: string;\n\t/** Same transaction as the fact-table delete, before it. Doomed rows are\n\t * still visible; remaining facts are `height < forkPointHeight`. A throw\n\t * aborts the rewind. Derive undo from the doomed rows so a re-applied\n\t * rollback is a no-op. */\n\tonRollback?: (\n\t\ttx: Transaction<DB>,\n\t\tctx: SinkRollbackContext,\n\t) => Promise<void> | void;\n}\n\n/**\n * A {@link ConsumerSink} over a Kysely Postgres database — keyed on the\n * QUERY BUILDER (not the driver) so the `DB` schema generic flows through:\n * `tables` is `(keyof DB)[]`, `height` must exist on every declared table,\n * and `ctx.tx` inside `onBatch` is a fully typed `Transaction<DB>`.\n *\n * What it owns (so your indexer doesn't):\n * - the checkpoint table + cursor load on start;\n * - rows AND cursor committed in ONE transaction per batch — a handler\n * throw aborts both, so a crashed batch is re-read on restart with no\n * gaps and no double-writes;\n * - reorg rollback: `onRollback` (doomed facts still visible), then delete\n * `>= fork_point_height` from every declared table, then the rewound\n * cursor — one transaction (the crash-between-the-two-writes gap is\n * unrepresentable);\n * - a per-id advisory lock, so two replicas of the same consumer can't\n * interleave commits.\n *\n * Structurally it is a 7-method {@link SinkDriver} on the shared\n * `createSink` base (`@secondlayer/sdk/sinks/core`), which owns the\n * transaction sequences and guards — this file only knows how to speak\n * Postgres-flavored Kysely.\n *\n * ```ts\n * const sink = kyselySink(db, { id: \"sales\", tables: [\"sales\"], height: \"block_height\" });\n * await index.contractCalls.consume({\n * contractId: MARKETPLACE, functionName: \"purchase-asset\", fromHeight: 0,\n * sink,\n * onBatch: (calls, _env, ctx) =>\n * ctx.tx.insertInto(\"sales\").values(calls.map(toSale)).execute(),\n * });\n * ```\n */\nexport function kyselySink<DB, T extends keyof DB & string>(\n\tdb: Kysely<DB>,\n\toptions: KyselySinkOptions<DB, T>,\n): ConsumerSink<Transaction<DB>> {\n\tconst checkpointTable = options.checkpointTable ?? DEFAULT_CHECKPOINT_TABLE;\n\tconst cp = quoteIdent(checkpointTable);\n\n\tconst driver: SinkDriver<Transaction<DB>> = {\n\t\ttransact: (fn) => db.transaction().execute(fn),\n\n\t\tasync ensureCheckpointStore() {\n\t\t\ttry {\n\t\t\t\tawait sql\n\t\t\t\t\t.raw(\n\t\t\t\t\t\t`CREATE TABLE IF NOT EXISTS ${cp} (id text PRIMARY KEY, cursor text NOT NULL)`,\n\t\t\t\t\t)\n\t\t\t\t\t.execute(db);\n\t\t\t} catch (err) {\n\t\t\t\t// Two consumers racing the same first start: IF NOT EXISTS still\n\t\t\t\t// throws under concurrency (42P07 duplicate_table, or 23505 on the\n\t\t\t\t// pg_type catalog). Either way the table exists — the outcome we\n\t\t\t\t// were creating.\n\t\t\t\tconst code = (err as { code?: string } | null)?.code;\n\t\t\t\tif (code !== \"42P07\" && code !== \"23505\") throw err;\n\t\t\t}\n\t\t},\n\n\t\tasync readCursor() {\n\t\t\tconst row = await sql<{ cursor: string }>`\n\t\t\t\tSELECT cursor FROM ${sql.raw(cp)} WHERE id = ${options.id}\n\t\t\t`.execute(db);\n\t\t\treturn row.rows[0]?.cursor ?? null;\n\t\t},\n\n\t\tasync writeCursor(tx, cursor) {\n\t\t\t// Identifiers via validated sql.raw; VALUES via bound parameters.\n\t\t\tawait sql`\n\t\t\t\tINSERT INTO ${sql.raw(cp)} (id, cursor) VALUES (${options.id}, ${cursor})\n\t\t\t\tON CONFLICT (id) DO UPDATE SET cursor = EXCLUDED.cursor\n\t\t\t`.execute(tx);\n\t\t},\n\n\t\tasync clearCursor(tx) {\n\t\t\tawait sql`DELETE FROM ${sql.raw(cp)} WHERE id = ${options.id}`.execute(\n\t\t\t\ttx,\n\t\t\t);\n\t\t},\n\n\t\tasync deleteAtOrAbove(tx, table, height) {\n\t\t\t// Identifiers via validated sql.raw; the height bound as a parameter.\n\t\t\tawait sql`\n\t\t\t\tDELETE FROM ${sql.raw(quoteIdent(table))}\n\t\t\t\tWHERE ${sql.raw(quoteIdent(options.height as string))} >= ${height}\n\t\t\t`.execute(tx);\n\t\t},\n\n\t\tasync hasColumn(table, column) {\n\t\t\tconst col = await sql<{ ok: boolean }>`\n\t\t\t\tSELECT EXISTS (\n\t\t\t\t\tSELECT 1 FROM information_schema.columns\n\t\t\t\t\tWHERE table_schema = current_schema()\n\t\t\t\t\t\tAND table_name = ${table} AND column_name = ${column}\n\t\t\t\t) AS \"ok\"\n\t\t\t`.execute(db);\n\t\t\treturn col.rows[0]?.ok ?? false;\n\t\t},\n\n\t\tasync acquireLock(tx) {\n\t\t\t// hashtextextended (int8), not hashtext (int4): the 32-bit space\n\t\t\t// collides across distinct ids at real scale, and a collision reads\n\t\t\t// as a false \"another consumer\" 409 between unrelated sinks.\n\t\t\tconst locked = await sql<{ ok: boolean }>`\n\t\t\t\tSELECT pg_try_advisory_xact_lock(hashtextextended(${options.id}, 0)) AS \"ok\"\n\t\t\t`.execute(tx);\n\t\t\tif (!locked.rows[0]?.ok) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t`kyselySink: another consumer holds the \"${options.id}\" lock — two replicas of the same consumer would interleave commits. Stop the other instance or use a distinct id.`,\n\t\t\t\t\t409,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t};\n\n\treturn createSink(driver, {\n\t\tlabel: \"kyselySink\",\n\t\tid: options.id,\n\t\ttables: options.tables,\n\t\theight: options.height as string,\n\t\tcheckpointTable,\n\t\tonRollback: options.onRollback,\n\t});\n}\n"
7
+ "import { ValidationError } from \"../errors.ts\";\nimport type { ConsumerSink } from \"./types.ts\";\n\n/** Default checkpoint table name, shared by every SQL-backed sink so two\n * sinks on the same database land in one table keyed by `id`. */\nexport const DEFAULT_CHECKPOINT_TABLE = \"sl_consumer_checkpoints\";\n\n/**\n * The driver surface a database adapter implements — the ~7 methods that\n * actually differ between SQL stores. Everything above them (the\n * commit/rollback call sequences, the guards, identifier validation, the\n * checkpoint schema) is policy owned by {@link createSink}, so a driver\n * cannot get the ordering wrong: it never sees a cursor write outside the\n * transaction that carries the rows, and never sees a delete without the\n * rewound cursor riding along.\n *\n * Identifier args (`table`, `column`) arrive pre-validated against\n * `[A-Za-z_][A-Za-z0-9_]*` — safe to interpolate after quoting (see\n * {@link quoteIdent}). Values (cursor, height, id) must still be bound as\n * parameters.\n */\nexport interface SinkDriver<Tx> {\n\t/** Run `fn` inside ONE transaction: begin, run, commit — a throw from\n\t * `fn` must roll the whole transaction back and re-throw. */\n\ttransact<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;\n\t/** Create the checkpoint store if missing: `(id primary key, cursor)`.\n\t * Must be safe under concurrent first start (two consumers racing the\n\t * same CREATE). */\n\tensureCheckpointStore(): Promise<void>;\n\t/** The committed cursor for this sink's `id`, or `null` if none. */\n\treadCursor(): Promise<string | null>;\n\t/** Upsert this sink's cursor row inside `tx`. */\n\twriteCursor(tx: Tx, cursor: string): Promise<void>;\n\t/** Delete this sink's cursor row inside `tx`: a rewind to pre-genesis\n\t * (fork at block 0). Optional for drivers written before it existed;\n\t * without it a genesis rewind throws instead of writing a bad cursor. */\n\tclearCursor?(tx: Tx): Promise<void>;\n\t/** Delete rows with `height column >= height` from `table`, inside `tx`. */\n\tdeleteAtOrAbove(tx: Tx, table: string, height: number): Promise<void>;\n\t/** Whether `table` exists AND carries `column` — the first-use check that\n\t * keeps reorg rollback from being a silent no-op. */\n\thasColumn(table: string, column: string): Promise<boolean>;\n\t/** Optional: assert this consumer is the only live writer for its `id`,\n\t * inside `tx`. Must FAIL LOUDLY (throw) when another writer holds it —\n\t * never block or interleave (contract invariant #13). */\n\tacquireLock?(tx: Tx): Promise<void>;\n}\n\n/** Context handed to {@link CreateSinkOptions.onRollback}. `forkPointHeight`\n * is the undo range (inclusive `>=`); `rewindCursor` is what the checkpoint\n * will become after this transaction — `null` only for a fork at genesis. */\nexport type SinkRollbackContext = {\n\tforkPointHeight: number;\n\trewindCursor: string | null;\n};\n\n/** Options for {@link createSink} — the policy inputs, driver-agnostic.\n * Concrete sinks re-expose these with schema-typed `tables`/`height`. */\nexport interface CreateSinkOptions<Tx = unknown> {\n\t/** Error-message prefix naming the concrete sink (e.g. `\"kyselySink\"`),\n\t * so a thrown guard points at the thing the user actually constructed. */\n\tlabel: string;\n\t/** Checkpoint identity AND concurrency key. */\n\tid: string;\n\t/** Rollback scope: on a reorg, rows at/above the fork point are deleted\n\t * from exactly these tables. Fact tables only — a fold (balances,\n\t * counters) does not belong here; invert it in `onRollback`. */\n\ttables: readonly string[];\n\t/** The block-height stamp column, present on every declared table. */\n\theight: string;\n\t/** Checkpoint table name. Default {@link DEFAULT_CHECKPOINT_TABLE}. */\n\tcheckpointTable?: string;\n\t/** Forwarded to {@link ConsumerSink.capabilities}. */\n\tcapabilities?: ConsumerSink[\"capabilities\"];\n\t/** Runs inside the rollback transaction after the lock, before any\n\t * `DELETE … height >= fork`. Doomed fact rows are still visible.\n\t * Remaining facts are `height < forkPointHeight`. A throw aborts the\n\t * whole rewind (no delete, no cursor write). Called on every\n\t * `rollback`, including re-application after a crash — derive undo\n\t * from the doomed rows so a second pass is a no-op. */\n\tonRollback?: (tx: Tx, ctx: SinkRollbackContext) => Promise<void> | void;\n}\n\n/** Throw unless `name` is a bare SQL identifier (letters, digits,\n * underscores; no leading digit) — the guard that makes interpolating\n * table/column names into DDL and DELETE statements safe. */\nexport function assertSqlIdentifier(name: string, label: string): void {\n\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {\n\t\tthrow new ValidationError(\n\t\t\t`${label}: invalid identifier \"${name}\" (letters, digits, underscores only).`,\n\t\t\t400,\n\t\t);\n\t}\n}\n\n/** Double-quote a (pre-validated) identifier for interpolation. */\nexport function quoteIdent(name: string): string {\n\treturn `\"${name}\"`;\n}\n\n/**\n * Build a {@link ConsumerSink} from a {@link SinkDriver}: the portable ~90\n * lines every SQL sink otherwise re-implements — and re-risks. Owns the two\n * transaction sequences (begin → lock → write rows → write cursor;\n * begin → lock → onRollback? → delete `>=` fork → write rewound cursor), the\n * empty-`tables` guard, identifier validation, and the first-use height-column\n * check.\n *\n * A driver implemented against this base cannot violate contract invariants\n * #4, #5, #8, #9, or #11 (see {@link ConsumerSink}) without breaking\n * `transact` itself — which is exactly what the conformance kit probes.\n */\nexport function createSink<Tx>(\n\tdriver: SinkDriver<Tx>,\n\toptions: CreateSinkOptions<Tx>,\n): ConsumerSink<Tx> {\n\tconst { label } = options;\n\tconst checkpointTable = options.checkpointTable ?? DEFAULT_CHECKPOINT_TABLE;\n\tif (options.tables.length === 0) {\n\t\tthrow new ValidationError(\n\t\t\t`${label}: \\`tables\\` is empty — declare every table the handler writes, or reorg rollback is a no-op and orphaned rows persist forever.`,\n\t\t\t400,\n\t\t);\n\t}\n\tassertSqlIdentifier(checkpointTable, label);\n\tassertSqlIdentifier(options.height, label);\n\tfor (const table of options.tables) assertSqlIdentifier(table, label);\n\n\treturn {\n\t\tcapabilities: options.capabilities,\n\n\t\tasync loadCursor() {\n\t\t\tawait driver.ensureCheckpointStore();\n\t\t\t// First-use validation of the rollback precondition: every declared\n\t\t\t// table must exist and carry the height column. Failing here beats a\n\t\t\t// rollback that silently deletes nothing during a reorg.\n\t\t\tfor (const table of options.tables) {\n\t\t\t\tif (!(await driver.hasColumn(table, options.height))) {\n\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t`${label}: table \"${table}\" has no \"${options.height}\" column — the height stamp is what makes reorg rollback possible. Add the column (or fix \\`height\\`).`,\n\t\t\t\t\t\t400,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn driver.readCursor();\n\t\t},\n\n\t\tasync commitBatch(cursor, write) {\n\t\t\tawait driver.transact(async (tx) => {\n\t\t\t\tawait driver.acquireLock?.(tx);\n\t\t\t\tawait write(tx);\n\t\t\t\tawait driver.writeCursor(tx, cursor);\n\t\t\t});\n\t\t},\n\n\t\tasync rollback(forkPointHeight, rewindCursor) {\n\t\t\tawait driver.transact(async (tx) => {\n\t\t\t\tawait driver.acquireLock?.(tx);\n\t\t\t\t// Folds (balances, last-wins) invert/recompute here, while doomed\n\t\t\t\t// fact rows are still visible. A throw aborts the rewind.\n\t\t\t\tawait options.onRollback?.(tx, { forkPointHeight, rewindCursor });\n\t\t\t\t// INCLUSIVE of the fork block: the new canonical chain re-supplies\n\t\t\t\t// it, and the consumer rewinds to re-read from `fork:0`.\n\t\t\t\tfor (const table of options.tables) {\n\t\t\t\t\tawait driver.deleteAtOrAbove(tx, table, forkPointHeight);\n\t\t\t\t}\n\t\t\t\tif (rewindCursor === null) {\n\t\t\t\t\tif (!driver.clearCursor) {\n\t\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t\t`${label}: this driver has no clearCursor, so a rewind to pre-genesis cannot be committed. Add clearCursor to the driver, or resume with an explicit fromCursor.`,\n\t\t\t\t\t\t\t400,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tawait driver.clearCursor(tx);\n\t\t\t\t} else {\n\t\t\t\t\tawait driver.writeCursor(tx, rewindCursor);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t};\n}\n"
8
8
  ],
9
- "mappings": "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",
10
- "debugId": "21FF4D87E9320E4A64756E2164756E21",
9
+ "mappings": "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",
10
+ "debugId": "4A548117F590C0CE64756E2164756E21",
11
11
  "names": []
12
12
  }
@@ -183,4 +183,4 @@ declare function sinkConformanceProbes<Tx>(harness: SinkConformanceHarness<Tx>):
183
183
  * `(name, fn)` signature). One probe becomes one test case.
184
184
  */
185
185
  declare function attachSinkConformance<Tx>(test: (name: string, fn: () => Promise<void>) => void, harness: SinkConformanceHarness<Tx>): void;
186
- export { sinkConformanceProbes, attachSinkConformance, SinkConformanceHarness, ConformanceProbe };
186
+ export { ConformanceProbe, SinkConformanceHarness, attachSinkConformance, sinkConformanceProbes };
@@ -199,9 +199,9 @@ function attachSinkConformance(test, harness) {
199
199
  }
200
200
  }
201
201
  export {
202
- sinkConformanceProbes,
203
- attachSinkConformance
202
+ attachSinkConformance,
203
+ sinkConformanceProbes
204
204
  };
205
205
 
206
- //# debugId=2A425478905AA1C864756E2164756E21
206
+ //# debugId=5FD030D46A8EBDFD64756E2164756E21
207
207
  //# sourceMappingURL=testing.js.map
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "import type { ConsumerSink } from \"./types.ts\";\n\n/**\n * The sink conformance kit: executable probes for the {@link ConsumerSink}\n * contract invariants — the ones that are SILENT when violated in\n * production (torn batches, silent gaps, under-deleted forks) and only\n * surface weeks later as missing or duplicated rows.\n *\n * Run every sink you author through it, in CI, against the real store:\n *\n * ```ts\n * import { test } from \"bun:test\";\n * import { attachSinkConformance } from \"@secondlayer/sdk/sinks/testing\";\n *\n * attachSinkConformance(test, {\n * makeSink: () => mySink(db, { id: \"conformance\", tables: [\"rows\"], height: \"height\" }),\n * reset: () => db.deleteFrom(...)...,\n * insertRow: (tx, height, key) => ..., // MUST be replay-safe (upsert on key)\n * readRows: () => ...,\n * readCursor: () => ...,\n * });\n * ```\n *\n * Framework-agnostic: probes throw plain `Error`s, so they run under\n * bun:test, vitest, jest, or a bare loop.\n */\nexport interface SinkConformanceHarness<Tx = unknown> {\n\t/** A fresh sink instance bound to the SAME store and the SAME checkpoint\n\t * id each call — \"new instance\" is how the kit simulates a restart. */\n\tmakeSink(): ConsumerSink<Tx> | Promise<ConsumerSink<Tx>>;\n\t/** Wipe the store back to empty: projection rows AND this id's\n\t * checkpoint. Called before every probe. */\n\treset(): Promise<void> | void;\n\t/** Insert one projection row through the lent transaction, exactly as a\n\t * real `onBatch` handler would. MUST be replay-safe (upsert / insert-\n\t * on-conflict-do-nothing keyed on `key`) — the same requirement the\n\t * contract's at-least-once delivery puts on real handlers. */\n\tinsertRow(tx: Tx, height: number, key: string): Promise<void> | void;\n\t/** Every committed projection row, read OUTSIDE any transaction. */\n\treadRows(): Promise<Array<{ height: number; key: string }>>;\n\t/** The committed checkpoint cursor as visible to a restarted process, or\n\t * `null` when none was ever committed. */\n\treadCursor(): Promise<string | null>;\n\t/** Optional (sinks with a writer lock): hold the lock for this sink's id\n\t * — as a second live consumer would — while `during` runs. Enables the\n\t * lock-contention probe (invariant #13: fail loudly, never interleave). */\n\twithLockHeld?(during: () => Promise<void>): Promise<void>;\n}\n\n/** One executable contract probe; `run` throws on violation. */\nexport interface ConformanceProbe {\n\tname: string;\n\trun(): Promise<void>;\n}\n\nfunction fail(probe: string, detail: string): never {\n\tthrow new Error(`sink conformance — ${probe}: ${detail}`);\n}\n\nfunction sortRows(rows: Array<{ height: number; key: string }>) {\n\treturn [...rows].sort(\n\t\t(a, b) => a.height - b.height || a.key.localeCompare(b.key),\n\t);\n}\n\nfunction expectRows(\n\tprobe: string,\n\tactual: Array<{ height: number; key: string }>,\n\texpected: Array<{ height: number; key: string }>,\n): void {\n\tconst got = JSON.stringify(sortRows(actual));\n\tconst want = JSON.stringify(sortRows(expected));\n\tif (got !== want) fail(probe, `rows ${got}, expected ${want}`);\n}\n\nfunction expectCursor(\n\tprobe: string,\n\tactual: string | null,\n\texpected: string | null,\n): void {\n\tif (actual !== expected) {\n\t\tfail(\n\t\t\tprobe,\n\t\t\t`cursor ${JSON.stringify(actual)}, expected ${JSON.stringify(expected)}`,\n\t\t);\n\t}\n}\n\nasync function expectRejects(\n\tprobe: string,\n\tpromise: Promise<unknown>,\n\twhy: string,\n): Promise<void> {\n\tlet rejected = false;\n\ttry {\n\t\tawait promise;\n\t} catch {\n\t\trejected = true;\n\t}\n\tif (!rejected) fail(probe, why);\n}\n\n/** Build the probe list for a harness. Prefer {@link attachSinkConformance}\n * inside a test framework; use this directly for a bare runner. */\nexport function sinkConformanceProbes<Tx>(\n\tharness: SinkConformanceHarness<Tx>,\n): ConformanceProbe[] {\n\t// Every probe starts from a wiped store and an initialized fresh sink.\n\tasync function start(): Promise<ConsumerSink<Tx>> {\n\t\tawait harness.reset();\n\t\tconst sink = await harness.makeSink();\n\t\tawait sink.loadCursor();\n\t\treturn sink;\n\t}\n\tconst commit = (\n\t\tsink: ConsumerSink<Tx>,\n\t\tcursor: string,\n\t\trows: Array<[number, string]>,\n\t) =>\n\t\tsink.commitBatch(cursor, async (tx) => {\n\t\t\tfor (const [height, key] of rows)\n\t\t\t\tawait harness.insertRow(tx, height, key);\n\t\t});\n\n\tconst probes: ConformanceProbe[] = [\n\t\t{\n\t\t\tname: \"fresh store: loadCursor initializes and returns null\",\n\t\t\tasync run() {\n\t\t\t\tawait harness.reset();\n\t\t\t\tconst sink = await harness.makeSink();\n\t\t\t\tconst cursor = await sink.loadCursor();\n\t\t\t\texpectCursor(\"fresh loadCursor\", cursor, null);\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"checkpoint persists: a restarted sink resumes from the committed cursor\",\n\t\t\tasync run() {\n\t\t\t\tconst sink = await start();\n\t\t\t\tawait commit(sink, \"10:0\", [[10, \"a\"]]);\n\t\t\t\tconst restarted = await harness.makeSink();\n\t\t\t\tconst resumed = await restarted.loadCursor();\n\t\t\t\texpectCursor(\"restart resume\", resumed, \"10:0\");\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"crash between rows and cursor: a throw aborts both (invariants #4/#5)\",\n\t\t\tasync run() {\n\t\t\t\tconst sink = await start();\n\t\t\t\tawait expectRejects(\n\t\t\t\t\t\"torn batch\",\n\t\t\t\t\tsink.commitBatch(\"10:0\", async (tx) => {\n\t\t\t\t\t\tawait harness.insertRow(tx, 10, \"a\");\n\t\t\t\t\t\tthrow new Error(\"kill -9\");\n\t\t\t\t\t}),\n\t\t\t\t\t\"commitBatch swallowed the handler throw — it must propagate (invariant #12)\",\n\t\t\t\t);\n\t\t\t\texpectRows(\"torn batch\", await harness.readRows(), []);\n\t\t\t\texpectCursor(\"torn batch\", await harness.readCursor(), null);\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"replaying the same cursor is safe: cursor works as an idempotency key (#7)\",\n\t\t\tasync run() {\n\t\t\t\tconst sink = await start();\n\t\t\t\tawait commit(sink, \"10:0\", [[10, \"a\"]]);\n\t\t\t\t// At-least-once redelivery: same cursor, same rows (deterministic\n\t\t\t\t// replay). Neither duplicate rows nor a corrupted checkpoint.\n\t\t\t\tawait commit(sink, \"10:0\", [[10, \"a\"]]);\n\t\t\t\texpectRows(\"replay\", await harness.readRows(), [\n\t\t\t\t\t{ height: 10, key: \"a\" },\n\t\t\t\t]);\n\t\t\t\texpectCursor(\"replay\", await harness.readCursor(), \"10:0\");\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"rollback deletes at-or-above the fork — inclusive >= (#8/#9)\",\n\t\t\tasync run() {\n\t\t\t\tconst sink = await start();\n\t\t\t\tawait commit(sink, \"11:0\", [\n\t\t\t\t\t[10, \"a\"],\n\t\t\t\t\t[11, \"b\"],\n\t\t\t\t]);\n\t\t\t\tawait sink.rollback(11, \"10:2147483647\");\n\t\t\t\t// The fork block itself (height 11) is deleted: the new canonical\n\t\t\t\t// chain re-supplies it.\n\t\t\t\texpectRows(\"inclusive >=\", await harness.readRows(), [\n\t\t\t\t\t{ height: 10, key: \"a\" },\n\t\t\t\t]);\n\t\t\t\texpectCursor(\n\t\t\t\t\t\"inclusive >=\",\n\t\t\t\t\tawait harness.readCursor(),\n\t\t\t\t\t\"10:2147483647\",\n\t\t\t\t);\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"rollback to genesis (null rewind) deletes every row and clears the checkpoint\",\n\t\t\tasync run() {\n\t\t\t\tconst sink = await start();\n\t\t\t\tawait commit(sink, \"11:0\", [\n\t\t\t\t\t[10, \"a\"],\n\t\t\t\t\t[11, \"b\"],\n\t\t\t\t]);\n\t\t\t\t// A fork at block 0 has no cursor to rewind to: the checkpoint row\n\t\t\t\t// goes away, so a restart resumes from the very beginning.\n\t\t\t\tawait sink.rollback(0, null);\n\t\t\t\texpectRows(\"genesis rewind\", await harness.readRows(), []);\n\t\t\t\texpectCursor(\"genesis rewind\", await harness.readCursor(), null);\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"rollback then re-read: canonical rows land cleanly after the rewind\",\n\t\t\tasync run() {\n\t\t\t\tconst sink = await start();\n\t\t\t\tawait commit(sink, \"12:0\", [\n\t\t\t\t\t[10, \"a\"],\n\t\t\t\t\t[11, \"b\"],\n\t\t\t\t\t[12, \"c\"],\n\t\t\t\t]);\n\t\t\t\tawait sink.rollback(11, \"10:2147483647\");\n\t\t\t\texpectRows(\"rollback\", await harness.readRows(), [\n\t\t\t\t\t{ height: 10, key: \"a\" },\n\t\t\t\t]);\n\t\t\t\t// The re-read: the new canonical chain replaces the orphaned rows.\n\t\t\t\tawait commit(sink, \"12:0\", [\n\t\t\t\t\t[11, \"b2\"],\n\t\t\t\t\t[12, \"c2\"],\n\t\t\t\t]);\n\t\t\t\texpectRows(\"re-read\", await harness.readRows(), [\n\t\t\t\t\t{ height: 10, key: \"a\" },\n\t\t\t\t\t{ height: 11, key: \"b2\" },\n\t\t\t\t\t{ height: 12, key: \"c2\" },\n\t\t\t\t]);\n\t\t\t\texpectCursor(\"re-read\", await harness.readCursor(), \"12:0\");\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"rollback is idempotent: re-application after a crash is a no-op (#10)\",\n\t\t\tasync run() {\n\t\t\t\tconst sink = await start();\n\t\t\t\tawait commit(sink, \"11:0\", [\n\t\t\t\t\t[10, \"a\"],\n\t\t\t\t\t[11, \"b\"],\n\t\t\t\t]);\n\t\t\t\tawait sink.rollback(11, \"10:2147483647\");\n\t\t\t\t// The loop dedups reorgs in memory only — a restart re-detects the\n\t\t\t\t// same fork and re-applies the same rollback.\n\t\t\t\tawait sink.rollback(11, \"10:2147483647\");\n\t\t\t\texpectRows(\"idempotent rollback\", await harness.readRows(), [\n\t\t\t\t\t{ height: 10, key: \"a\" },\n\t\t\t\t]);\n\t\t\t\texpectCursor(\n\t\t\t\t\t\"idempotent rollback\",\n\t\t\t\t\tawait harness.readCursor(),\n\t\t\t\t\t\"10:2147483647\",\n\t\t\t\t);\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"rollback scopes deletion by forkPointHeight, never rewindCursor (#11)\",\n\t\t\tasync run() {\n\t\t\t\tconst sink = await start();\n\t\t\t\tawait commit(sink, \"13:0\", [\n\t\t\t\t\t[9, \"a\"],\n\t\t\t\t\t[10, \"b\"],\n\t\t\t\t\t[11, \"c\"],\n\t\t\t\t\t[12, \"d\"],\n\t\t\t\t\t[13, \"e\"],\n\t\t\t\t]);\n\t\t\t\t// A multi-fork page: every call carries the SAME rewindCursor (the\n\t\t\t\t// lowest fork point) with its OWN forkPointHeight. This call's pair\n\t\t\t\t// is deliberately mismatched — fork at 12, rewind below 10. A sink\n\t\t\t\t// deriving the undo range from the cursor deletes 10-11 here.\n\t\t\t\tawait sink.rollback(12, \"9:2147483647\");\n\t\t\t\texpectRows(\"scope by height\", await harness.readRows(), [\n\t\t\t\t\t{ height: 9, key: \"a\" },\n\t\t\t\t\t{ height: 10, key: \"b\" },\n\t\t\t\t\t{ height: 11, key: \"c\" },\n\t\t\t\t]);\n\t\t\t\tawait sink.rollback(10, \"9:2147483647\");\n\t\t\t\texpectRows(\"scope by height (second fork)\", await harness.readRows(), [\n\t\t\t\t\t{ height: 9, key: \"a\" },\n\t\t\t\t]);\n\t\t\t\texpectCursor(\n\t\t\t\t\t\"scope by height\",\n\t\t\t\t\tawait harness.readCursor(),\n\t\t\t\t\t\"9:2147483647\",\n\t\t\t\t);\n\t\t\t},\n\t\t},\n\t];\n\n\tif (harness.withLockHeld) {\n\t\tconst withLockHeld = harness.withLockHeld.bind(harness);\n\t\tprobes.push({\n\t\t\tname: \"lock contention: a second writer fails loudly, never interleaves (#13)\",\n\t\t\tasync run() {\n\t\t\t\tconst sink = await start();\n\t\t\t\tawait withLockHeld(async () => {\n\t\t\t\t\tawait expectRejects(\n\t\t\t\t\t\t\"lock contention\",\n\t\t\t\t\t\tsink.commitBatch(\"10:0\", async (tx) => {\n\t\t\t\t\t\t\tawait harness.insertRow(tx, 10, \"a\");\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t\"commitBatch succeeded while another writer held the lock — two replicas would interleave commits\",\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\texpectRows(\"lock contention\", await harness.readRows(), []);\n\t\t\t\texpectCursor(\"lock contention\", await harness.readCursor(), null);\n\t\t\t},\n\t\t});\n\t}\n\n\treturn probes;\n}\n\n/**\n * Register every conformance probe with a test framework: pass your\n * framework's `test` function (bun:test, vitest, jest — anything with a\n * `(name, fn)` signature). One probe becomes one test case.\n */\nexport function attachSinkConformance<Tx>(\n\ttest: (name: string, fn: () => Promise<void>) => void,\n\tharness: SinkConformanceHarness<Tx>,\n): void {\n\tfor (const probe of sinkConformanceProbes(harness)) {\n\t\ttest(probe.name, () => probe.run());\n\t}\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "2A425478905AA1C864756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "5FD030D46A8EBDFD64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1020,4 +1020,4 @@ declare const Cursor: {
1020
1020
  eventIndex: number;
1021
1021
  };
1022
1022
  };
1023
- export { isStxTransfer, isStxMint, isStxLock, isStxBurn, isPrint, isNftTransfer, isNftMint, isNftBurn, isFtTransfer, isFtMint, isFtBurn, decodeStxTransfer, decodeStxMint, decodeStxLock, decodeStxBurn, decodePrint, decodeNftTransfer, decodeNftMint, decodeNftBurn, decodeFtTransfer, decodeFtMint, decodeFtBurn, decode, createStreamsClient, VmStreamsEvent, ValidationError, StreamsWireEvent2 as StreamsWireEvent, StreamsTip, StreamsSubscription, StreamsSignatureError, StreamsServerError, StreamsReorgsListParams, StreamsReorgsListEnvelope, StreamsReorgContext, StreamsReorg, StreamsEventsSubscribeParams, StreamsEventsStreamParams, StreamsEventsListParams, StreamsEventsListEnvelope, StreamsEventsEnvelope, StreamsEventsConsumeResult, StreamsEventsConsumeParams, StreamsEventType, StreamsEventPayload, StreamsEvent, StreamsDumpsManifest, StreamsDumps, StreamsDumpFile, StreamsConsumeParams, StreamsClient, StreamsCanonicalBlock, StreamsBatchContext, StreamsBatch, STREAMS_EVENT_TYPES, RateLimitError, NftTransferPayload2 as NftTransferPayload, NftTransferEvent, FtTransferPayload2 as FtTransferPayload, FtTransferEvent, FetchLike2 as FetchLike, DecodedStxTransferPayload, DecodedStxTransfer, DecodedStxMintPayload, DecodedStxMint, DecodedStxLockPayload, DecodedStxLock, DecodedStxBurnPayload, DecodedStxBurn, DecodedPrintValue, DecodedPrintPayload, DecodedPrint, DecodedNftTransferPayload, DecodedNftTransfer, DecodedNftMintPayload, DecodedNftMint, DecodedNftBurnPayload, DecodedNftBurn, DecodedFtTransferPayload, DecodedFtTransfer, DecodedFtMintPayload, DecodedFtMint, DecodedFtBurnPayload, DecodedFtBurn, DecodedEventRow, DecodedEventColumns, Cursor, ConsumerBatchContext, AuthError };
1023
+ export { AuthError, ConsumerBatchContext, Cursor, DecodedEventColumns, DecodedEventRow, DecodedFtBurn, DecodedFtBurnPayload, DecodedFtMint, DecodedFtMintPayload, DecodedFtTransfer, DecodedFtTransferPayload, DecodedNftBurn, DecodedNftBurnPayload, DecodedNftMint, DecodedNftMintPayload, DecodedNftTransfer, DecodedNftTransferPayload, DecodedPrint, DecodedPrintPayload, DecodedPrintValue, DecodedStxBurn, DecodedStxBurnPayload, DecodedStxLock, DecodedStxLockPayload, DecodedStxMint, DecodedStxMintPayload, DecodedStxTransfer, DecodedStxTransferPayload, FetchLike2 as FetchLike, FtTransferEvent, FtTransferPayload2 as FtTransferPayload, NftTransferEvent, NftTransferPayload2 as NftTransferPayload, RateLimitError, STREAMS_EVENT_TYPES, StreamsBatch, StreamsBatchContext, StreamsCanonicalBlock, StreamsClient, StreamsConsumeParams, StreamsDumpFile, StreamsDumps, StreamsDumpsManifest, StreamsEvent, StreamsEventPayload, StreamsEventType, StreamsEventsConsumeParams, StreamsEventsConsumeResult, StreamsEventsEnvelope, StreamsEventsListEnvelope, StreamsEventsListParams, StreamsEventsStreamParams, StreamsEventsSubscribeParams, StreamsReorg, StreamsReorgContext, StreamsReorgsListEnvelope, StreamsReorgsListParams, StreamsServerError, StreamsSignatureError, StreamsSubscription, StreamsTip, StreamsWireEvent2 as StreamsWireEvent, ValidationError, VmStreamsEvent, createStreamsClient, decode, decodeFtBurn, decodeFtMint, decodeFtTransfer, decodeNftBurn, decodeNftMint, decodeNftTransfer, decodePrint, decodeStxBurn, decodeStxLock, decodeStxMint, decodeStxTransfer, isFtBurn, isFtMint, isFtTransfer, isNftBurn, isNftMint, isNftTransfer, isPrint, isStxBurn, isStxLock, isStxMint, isStxTransfer };