@secondlayer/shared 11.4.2 → 11.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/src/archive/derived-stage-report.d.ts +2 -2
- package/dist/src/archive/derived-stage-report.js.map +1 -1
- package/dist/src/archive/instance-diagnosis.js +2 -2
- package/dist/src/archive/instance-diagnosis.js.map +2 -2
- package/dist/src/archive/range-digest.d.ts +25 -25
- package/dist/src/archive/semantic-digest-builder.d.ts +25 -25
- package/dist/src/archive/status.d.ts +32 -7
- package/dist/src/archive/status.js +20 -3
- package/dist/src/archive/status.js.map +3 -3
- package/dist/src/coverage/index.d.ts +25 -25
- package/dist/src/coverage/index.js.map +1 -1
- package/dist/src/crypto/secondlayer-webhook.d.ts +2 -2
- package/dist/src/crypto/secondlayer-webhook.js.map +1 -1
- package/dist/src/crypto/secrets.js.map +1 -1
- package/dist/src/db/index.d.ts +37 -37
- package/dist/src/db/index.js +4 -4
- package/dist/src/db/index.js.map +3 -3
- package/dist/src/db/queries/chain-reorgs.d.ts +25 -25
- package/dist/src/db/queries/chain-reorgs.js +4 -4
- package/dist/src/db/queries/chain-reorgs.js.map +3 -3
- package/dist/src/db/queries/contracts.d.ts +25 -25
- package/dist/src/db/queries/instance.d.ts +25 -25
- package/dist/src/db/queries/integrity.d.ts +25 -25
- package/dist/src/db/queries/subgraph-gaps.d.ts +25 -25
- package/dist/src/db/queries/subgraph-operations.d.ts +25 -25
- package/dist/src/db/queries/subgraphs.d.ts +25 -25
- package/dist/src/db/queries/subgraphs.js +4 -4
- package/dist/src/db/queries/subgraphs.js.map +3 -3
- package/dist/src/db/queries/sync-scope.d.ts +25 -25
- package/dist/src/db/queries/{subscriptions.d.ts → webhooks.d.ts} +58 -58
- package/dist/src/db/queries/{subscriptions.js → webhooks.js} +38 -38
- package/dist/src/db/queries/webhooks.js.map +13 -0
- package/dist/src/db/schema.d.ts +34 -34
- package/dist/src/index.d.ts +109 -107
- package/dist/src/index.js +51 -54
- package/dist/src/index.js.map +6 -6
- package/dist/src/leader.d.ts +4 -4
- package/dist/src/leader.js +3 -3
- package/dist/src/leader.js.map +3 -3
- package/dist/src/node/local-client.d.ts +25 -25
- package/dist/src/queue/listener.d.ts +2 -2
- package/dist/src/queue/listener.js.map +1 -1
- package/dist/src/runtime/index.js +2 -6
- package/dist/src/runtime/index.js.map +4 -4
- package/dist/src/schemas/index.d.ts +61 -61
- package/dist/src/schemas/index.js +48 -51
- package/dist/src/schemas/index.js.map +5 -5
- package/dist/src/schemas/subgraphs.d.ts +2 -2
- package/dist/src/schemas/subgraphs.js.map +1 -1
- package/dist/src/schemas/{subscriptions.d.ts → webhooks.d.ts} +63 -63
- package/dist/src/schemas/{subscriptions.js → webhooks.js} +49 -52
- package/dist/src/schemas/webhooks.js.map +10 -0
- package/migrations/0126_rename_subscriptions_to_webhooks.ts +96 -0
- package/migrations/0127_rename_webhook_catalog_names.ts +55 -0
- package/package.json +8 -8
- package/dist/src/db/queries/subscriptions.js.map +0 -13
- package/dist/src/schemas/subscriptions.js.map +0 -10
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Instance modes for the Secondlayer platform.\n *\n * - `oss`: self-hosted, single-tenant. No auth middleware, no platform routes\n * (projects, admin). Everything runs against a single `DATABASE_URL`.\n * Intended for `docker compose up`.\n *\n * - `platform`: the metered archive deployment. Magic-link auth, API keys,\n * accounts, credits, and metered `/v1` reads (Index + Streams + subgraphs)\n * against a single shared DB. No paid tier ladder — one metered `free`\n * tier plus first-party internal service credentials.\n */\n\nexport type InstanceMode = \"oss\" | \"platform\";\n\n/** What the operator wrote in the env — `archive` is the honest name for the\n * metered public-archive deployment and aliases `platform` behaviorally. */\nexport type DeclaredInstanceMode = InstanceMode | \"archive\";\n\nconst VALID_MODES: readonly DeclaredInstanceMode[] = [\n\t\"oss\",\n\t\"platform\",\n\t\"archive\",\n];\n\nfunction declaredMode(): DeclaredInstanceMode {\n\tconst raw = process.env.INSTANCE_MODE?.trim().toLowerCase();\n\tif (raw && (VALID_MODES as readonly string[]).includes(raw)) {\n\t\treturn raw as DeclaredInstanceMode;\n\t}\n\treturn \"oss\";\n}\n\n/**\n * Resolve the active instance mode from `process.env.INSTANCE_MODE`.\n * Defaults to `\"oss\"` — the safest default for self-hosters who deploy\n * without setting the variable. `archive` normalizes to `platform` here so\n * every behavioral branch stays a two-way choice; use\n * `getDeclaredInstanceMode()` for display surfaces.\n */\nexport function getInstanceMode(): InstanceMode {\n\tconst declared = declaredMode();\n\treturn declared === \"archive\" ? \"platform\" : declared;\n}\n\n/** The mode as declared in the env, for status/identity surfaces. */\nexport function getDeclaredInstanceMode(): DeclaredInstanceMode {\n\treturn declaredMode();\n}\n\n/**\n * True when public reads are credit-metered (the free window, retention, and\n * credits gates arm). Platform/archive deployments meter by default;\n * self-host never does unless the operator explicitly sets\n * `METERED_READS=true`. `METERED_READS=false` disarms a metered deployment.\n */\nexport function isMeteredReads(): boolean {\n\tconst override = process.env.METERED_READS?.trim().toLowerCase();\n\tif (override === \"true\") return true;\n\tif (override === \"false\") return false;\n\treturn getInstanceMode() === \"platform\";\n}\n\n/** True when the active mode is `\"platform\"` (shared multi-tenant). */\nexport function isPlatformMode(): boolean {\n\treturn getInstanceMode() === \"platform\";\n}\n\n/** True when the active mode is `\"oss\"` (self-hosted). */\nexport function isOssMode(): boolean {\n\treturn getInstanceMode() === \"oss\";\n}\n",
|
|
6
6
|
"import { type RawBuilder, sql } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n *\n * Generic parameter lets callers set the RawBuilder's output type so they\n * don't need to cast at the insert site. Default is `unknown` — widen at\n * the call site when the column type is narrower, e.g. `jsonb<MyShape>(...)`.\n */\nexport function jsonb<T = unknown>(value: T): RawBuilder<T> {\n\tconst escaped = JSON.stringify(value, (_k, v) =>\n\t\ttypeof v === \"bigint\" ? v.toString() : v,\n\t).replace(/'/g, \"''\");\n\treturn sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\treturn JSON.parse(value) as T;\n\t\t} catch {\n\t\t\treturn value as T;\n\t\t}\n\t}\n\treturn (value ?? {}) as T;\n}\n",
|
|
7
|
-
"import { type Kysely, sql } from \"kysely\";\nimport { isPlatformMode } from \"../../mode.ts\";\nimport { jsonb } from \"../jsonb.ts\";\nimport type { Database, Subgraph } from \"../types.ts\";\n\n/**\n * Convert a subgraph name to its PostgreSQL schema name (legacy form).\n * Pre shared-rip every tenant DB had its own schema namespace so disambiguation\n * was implicit. Kept for oss mode (single-tenant) and legacy-row fallback.\n * Platform-mode deploys use `pgSchemaNameFor(accountId, name)`.\n */\nexport function pgSchemaName(subgraphName: string): string {\n\tconst safeName = subgraphName.replace(/-/g, \"_\");\n\treturn `subgraph_${safeName}`;\n}\n\n/** OSS writes empty account_id — name is the local unique key. */\nexport function localSubgraphAccountId(accountId?: string): string {\n\tif (!isPlatformMode()) return \"\";\n\treturn accountId ?? \"\";\n}\n\n/**\n * Account-scoped schema name. Matches migration 0028's rename pattern:\n * subgraph_{first8charsOfAccountId, dashes-as-underscores}_{name}\n * Empty accountId falls back to legacy form (oss mode).\n */\nexport function pgSchemaNameFor(\n\taccountId: string,\n\tsubgraphName: string,\n): string {\n\tif (!accountId) return pgSchemaName(subgraphName);\n\tconst accountPrefix = accountId.slice(0, 8).replace(/-/g, \"_\");\n\tconst safeName = subgraphName.replace(/-/g, \"_\");\n\treturn `subgraph_${accountPrefix}_${safeName}`;\n}\n\nexport async function registerSubgraph(\n\tdb: Kysely<Database>,\n\tdata: {\n\t\tname: string;\n\t\tversion: string;\n\t\tdefinition: Record<string, unknown>;\n\t\tschemaHash: string;\n\t\thandlerPath: string;\n\t\tapiKeyId?: string;\n\t\taccountId?: string;\n\t\tschemaName?: string;\n\t\tstartBlock?: number;\n\t\thandlerCode?: string;\n\t\tsourceCode?: string;\n\t},\n): Promise<Subgraph> {\n\tconst accountId = localSubgraphAccountId(data.accountId);\n\tconst values = {\n\t\tname: data.name,\n\t\tversion: data.version,\n\t\tdefinition: jsonb<Record<string, unknown>>(data.definition),\n\t\tschema_hash: data.schemaHash,\n\t\thandler_path: data.handlerPath,\n\t\taccount_id: accountId,\n\t\thandler_code: data.handlerCode ?? null,\n\t\tsource_code: data.sourceCode ?? null,\n\t\tschema_name: data.schemaName ?? null,\n\t\tstart_block: data.startBlock ?? 0,\n\t\tdatabase_url_enc: null,\n\t};\n\tconst updateSet = {\n\t\tversion: data.version,\n\t\tdefinition: jsonb<Record<string, unknown>>(data.definition),\n\t\tschema_hash: data.schemaHash,\n\t\thandler_path: data.handlerPath,\n\t\thandler_code: data.handlerCode ?? null,\n\t\tsource_code: data.sourceCode ?? null,\n\t\tschema_name: data.schemaName ?? null,\n\t\tstart_block: data.startBlock ?? 0,\n\t\tdatabase_url_enc: null,\n\t\tupdated_at: new Date(),\n\t};\n\n\t// OSS: name is unique. Upsert by name so leftover rows with a non-empty\n\t// account_id (pre-local-namespace deploys) don't insert a second row.\n\tif (!isPlatformMode()) {\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"subgraphs\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"name\", \"=\", data.name)\n\t\t\t.executeTakeFirst();\n\t\tif (existing) {\n\t\t\treturn await db\n\t\t\t\t.updateTable(\"subgraphs\")\n\t\t\t\t.set({ ...updateSet, account_id: \"\" })\n\t\t\t\t.where(\"id\", \"=\", existing.id)\n\t\t\t\t.returningAll()\n\t\t\t\t.executeTakeFirstOrThrow();\n\t\t}\n\t\treturn await db\n\t\t\t.insertInto(\"subgraphs\")\n\t\t\t.values(values)\n\t\t\t.returningAll()\n\t\t\t.executeTakeFirstOrThrow();\n\t}\n\n\treturn await db\n\t\t.insertInto(\"subgraphs\")\n\t\t.values(values)\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"name\", \"account_id\"]).doUpdateSet(updateSet),\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\taccountId?: string,\n): Promise<Subgraph | null> {\n\tlet query = db.selectFrom(\"subgraphs\").selectAll().where(\"name\", \"=\", name);\n\n\tif (isPlatformMode() && accountId !== undefined) {\n\t\tquery = query.where(\"account_id\", \"=\", accountId);\n\t}\n\n\treturn (await query.executeTakeFirst()) ?? null;\n}\n\nexport async function listSubgraphs(\n\tdb: Kysely<Database>,\n\taccountId?: string,\n): Promise<Subgraph[]> {\n\tlet query = db.selectFrom(\"subgraphs\").selectAll();\n\tif (isPlatformMode() && accountId !== undefined) {\n\t\tquery = query.where(\"account_id\", \"=\", accountId);\n\t}\n\treturn query.execute();\n}\n\nexport async function updateSubgraphStatus(\n\tdb: Kysely<Database>,\n\tname: string,\n\tstatus: string,\n\tlastProcessedBlock?: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\tstatus,\n\t\t\t...(lastProcessedBlock !== undefined\n\t\t\t\t? { last_processed_block: lastProcessedBlock }\n\t\t\t\t: {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\n/** Account-scoped pause. Only active/reindexing rows flip; name-only\n * {@link updateSubgraphStatus} is unsafe on platform (name, account_id). */\nexport async function pauseSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\taccountId: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({ status: \"paused\", updated_at: new Date() })\n\t\t.where(\"name\", \"=\", name)\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"in\", [\"active\", \"reindexing\"])\n\t\t.execute();\n}\n\n/** Resume every paused subgraph for an account (claim / Stripe top-up). */\nexport async function resumePausedSubgraphs(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<number> {\n\tconst res = await db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({ status: \"active\", updated_at: new Date() })\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"=\", \"paused\")\n\t\t.executeTakeFirst();\n\treturn Number(res.numUpdatedRows ?? 0);\n}\n\n/** Live-walk ERROR write (f069): a block whose handlers ALL failed\n * (`processed === 0 && errors > 0`, `block-processor.ts`'s `applyProgress`)\n * is stamped 'error' and its height is recorded as processed, same as\n * `updateSubgraphStatus(db, name, \"error\", blockHeight)` — except the\n * cursor half of that write now goes through the same conditional `<`\n * guard as {@link recordLiveProgress}. Marking a subgraph 'error' is not\n * itself replay-sensitive, but it shares the `last_processed_block` column\n * with the replay guard: an unconditional write here could regress the\n * cursor a racing (successful) writer already advanced past, which is\n * exactly the regress-then-re-walk loop this guard exists to close. In\n * practice `ctx.rollbackTo` (`runner.ts`) means an all-failed block never\n * flushes writes, so a lost race here is always a silent no-op, never a\n * case that needs `CursorRaceLostError`-style tx abort. Returns whether\n * the write applied. Live path only — reindex/backfill's error stamping\n * goes through {@link updateSubgraphStatus} unconditionally, as before. */\nexport async function recordLiveError(\n\tdb: Kysely<Database>,\n\tname: string,\n\tblockHeight: number,\n): Promise<boolean> {\n\tconst result = await db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\tstatus: \"error\",\n\t\t\tlast_processed_block: blockHeight,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.where(\"last_processed_block\", \"<\", blockHeight)\n\t\t.executeTakeFirst();\n\treturn Number(result.numUpdatedRows ?? 0n) > 0;\n}\n\nexport async function recordSubgraphProcessed(\n\tdb: Kysely<Database>,\n\tname: string,\n\tprocessed: number,\n\terrors: number,\n\tlastError?: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\ttotal_processed: sql`total_processed + ${processed}`,\n\t\t\ttotal_errors: sql`total_errors + ${errors}`,\n\t\t\t...(lastError\n\t\t\t\t? { last_error: lastError, last_error_at: new Date() }\n\t\t\t\t: {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function updateSubgraphHandlerPath(\n\tdb: Kysely<Database>,\n\tname: string,\n\thandlerPath: string,\n\topts?: { handlerCode?: string; sourceCode?: string },\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\thandler_path: handlerPath,\n\t\t\t...(opts?.handlerCode != null ? { handler_code: opts.handlerCode } : {}),\n\t\t\t...(opts?.sourceCode != null ? { source_code: opts.sourceCode } : {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function deleteSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\taccountId?: string,\n): Promise<Subgraph | null> {\n\tconst subgraph = await getSubgraph(db, name, accountId);\n\tif (!subgraph) return null;\n\n\t// Use stored schema_name if available, otherwise compute\n\tconst schemaName = subgraph.schema_name ?? pgSchemaName(name);\n\n\t// Cascade to subscriptions: a subscription pointing at a deleted\n\t// subgraph + table will throw `relation does not exist` on every\n\t// subsequent emission. Pause active subs and purge any pending outbox\n\t// rows so receivers don't get phantom replays. We don't delete the\n\t// subscriptions themselves — operators may want to repoint them at a\n\t// resurrected subgraph; we just stop them firing.\n\tawait db\n\t\t.updateTable(\"subscriptions\")\n\t\t.set({\n\t\t\tstatus: \"paused\",\n\t\t\tlast_error: `Subgraph \"${name}\" deleted; subscription auto-paused.`,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"subgraph_name\", \"=\", name)\n\t\t.execute();\n\tawait db\n\t\t.deleteFrom(\"subscription_outbox\")\n\t\t.where(\"status\", \"=\", \"pending\")\n\t\t.where(\"subscription_id\", \"in\", (qb) =>\n\t\t\tqb\n\t\t\t\t.selectFrom(\"subscriptions\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"subgraph_name\", \"=\", name),\n\t\t)\n\t\t.execute();\n\n\t// Drop the subgraph's schema (CASCADE drops all tables within).\n\tawait sql`DROP SCHEMA IF EXISTS ${sql.raw(`\"${schemaName}\"`)} CASCADE`.execute(\n\t\tdb,\n\t);\n\n\t// Remove from registry\n\tawait db.deleteFrom(\"subgraphs\").where(\"id\", \"=\", subgraph.id).execute();\n\n\treturn subgraph;\n}\n\n/** Set or clear a paid subgraph's expiry (NULL = no expiry, e.g. on claim). */\nexport async function updateSubgraphExpiry(\n\tdb: Kysely<Database>,\n\tname: string,\n\taccountId: string,\n\texpiresAt: Date | null,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({ expires_at: expiresAt })\n\t\t.where(\"name\", \"=\", name)\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.execute();\n}\n\n/** Live-walk progress write: advances the cursor and PROMOTES toward\n * 'active' (deploying/error → active keeps reorg eligibility, reorg.ts gates\n * on it) but never overwrites an explicit 'reindexing' park — unconditional\n * status stamping let catch-up flap a parked subgraph back into its own path\n * per block, fighting the queued reindex op.\n *\n * `last_processed_block` is now written CONDITIONALLY — `WHERE\n * last_processed_block < lastProcessedBlock`, mirroring\n * `advanceOperationCursor`'s (`subgraph-operations.ts`) comparison shape —\n * and returns whether THIS call actually advanced it. This used to be\n * unconditional by design, on the premise that the only two writers able to\n * advance/rewind a given subgraph's cursor concurrently (the catch-up walk\n * and the reorg handler) serialize on an in-process per-subgraph lock +\n * reorg epoch guard (`packages/subgraphs/src/runtime/catchup.ts`,\n * `subgraphBlockLockHeld` / `reorgEpoch`; f057). That guard is in-process\n * only. The f068 investigation\n * (`docs/internal/audits/asset-holdings-unreproducible-balances-f068.md`)\n * found a cross-process sequence it does not cover: a catch-up leader that\n * loses its advisory-lock connection mid-walk keeps writing (leadership is\n * checked only at walk entry, not per-iteration) while a new leader takes\n * over and walks the same range — both commit increments for overlapping\n * heights, and every regressed cursor write from the laggard re-triggers a\n * full re-walk, growing per-height application without bound. The\n * conditional advance here (paired with the fast-path replay guard armed on\n * the live path in `block-processor.ts`) is now the cross-process\n * guarantee: a block at/below the current cursor can never re-advance it,\n * so a laggard's commits can no longer regress-then-retrigger.\n *\n * This conditionality is why the reorg rewind can no longer go through this\n * function — a rewind's whole point is to move the cursor *backward*, which\n * the `<` guard would silently refuse. Reorg mode calls\n * {@link rewindLiveProgress} instead, which keeps the old unconditional\n * semantics and is safe specifically because the reorg path takes a\n * transaction-scoped advisory lock (`reorg.ts`) other writers can't cross. */\nexport async function recordLiveProgress(\n\tdb: Kysely<Database>,\n\tname: string,\n\tlastProcessedBlock: number,\n): Promise<boolean> {\n\tconst result = await db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\tlast_processed_block: lastProcessedBlock,\n\t\t\tstatus: sql`CASE WHEN status = 'reindexing' THEN status ELSE 'active' END`,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.where(\"last_processed_block\", \"<\", lastProcessedBlock)\n\t\t.executeTakeFirst();\n\treturn Number(result.numUpdatedRows ?? 0n) > 0;\n}\n\n/** Reorg-rewind progress write: the OLD unconditional `recordLiveProgress`\n * semantics, verbatim — moves `last_processed_block` to the fork height\n * regardless of its current value, because that is by definition a\n * backward move the conditional `<` guard in `recordLiveProgress` would\n * refuse. Same status-promotion CASE (never unparks an explicit\n * 'reindexing').\n *\n * Callers: exclusively the subgraph-reorg path\n * (`packages/subgraphs/src/runtime/reorg.ts`, via `block-processor.ts`'s\n * `reorgRewind` mode), which is safe from the cross-process race this\n * function reintroduces because it holds a transaction-scoped Postgres\n * advisory lock (`pg_advisory_xact_lock`) for the whole\n * delete+reprocess+rewind sequence — no other writer can interleave a\n * forward write between the rewind and the fork block's reprocess. Do not\n * call this from the live catch-up path. */\nexport async function rewindLiveProgress(\n\tdb: Kysely<Database>,\n\tname: string,\n\tlastProcessedBlock: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\tlast_processed_block: lastProcessedBlock,\n\t\t\tstatus: sql`CASE WHEN status = 'reindexing' THEN status ELSE 'active' END`,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n"
|
|
7
|
+
"import { type Kysely, sql } from \"kysely\";\nimport { isPlatformMode } from \"../../mode.ts\";\nimport { jsonb } from \"../jsonb.ts\";\nimport type { Database, Subgraph } from \"../types.ts\";\n\n/**\n * Convert a subgraph name to its PostgreSQL schema name (legacy form).\n * Pre shared-rip every tenant DB had its own schema namespace so disambiguation\n * was implicit. Kept for oss mode (single-tenant) and legacy-row fallback.\n * Platform-mode deploys use `pgSchemaNameFor(accountId, name)`.\n */\nexport function pgSchemaName(subgraphName: string): string {\n\tconst safeName = subgraphName.replace(/-/g, \"_\");\n\treturn `subgraph_${safeName}`;\n}\n\n/** OSS writes empty account_id — name is the local unique key. */\nexport function localSubgraphAccountId(accountId?: string): string {\n\tif (!isPlatformMode()) return \"\";\n\treturn accountId ?? \"\";\n}\n\n/**\n * Account-scoped schema name. Matches migration 0028's rename pattern:\n * subgraph_{first8charsOfAccountId, dashes-as-underscores}_{name}\n * Empty accountId falls back to legacy form (oss mode).\n */\nexport function pgSchemaNameFor(\n\taccountId: string,\n\tsubgraphName: string,\n): string {\n\tif (!accountId) return pgSchemaName(subgraphName);\n\tconst accountPrefix = accountId.slice(0, 8).replace(/-/g, \"_\");\n\tconst safeName = subgraphName.replace(/-/g, \"_\");\n\treturn `subgraph_${accountPrefix}_${safeName}`;\n}\n\nexport async function registerSubgraph(\n\tdb: Kysely<Database>,\n\tdata: {\n\t\tname: string;\n\t\tversion: string;\n\t\tdefinition: Record<string, unknown>;\n\t\tschemaHash: string;\n\t\thandlerPath: string;\n\t\tapiKeyId?: string;\n\t\taccountId?: string;\n\t\tschemaName?: string;\n\t\tstartBlock?: number;\n\t\thandlerCode?: string;\n\t\tsourceCode?: string;\n\t},\n): Promise<Subgraph> {\n\tconst accountId = localSubgraphAccountId(data.accountId);\n\tconst values = {\n\t\tname: data.name,\n\t\tversion: data.version,\n\t\tdefinition: jsonb<Record<string, unknown>>(data.definition),\n\t\tschema_hash: data.schemaHash,\n\t\thandler_path: data.handlerPath,\n\t\taccount_id: accountId,\n\t\thandler_code: data.handlerCode ?? null,\n\t\tsource_code: data.sourceCode ?? null,\n\t\tschema_name: data.schemaName ?? null,\n\t\tstart_block: data.startBlock ?? 0,\n\t\tdatabase_url_enc: null,\n\t};\n\tconst updateSet = {\n\t\tversion: data.version,\n\t\tdefinition: jsonb<Record<string, unknown>>(data.definition),\n\t\tschema_hash: data.schemaHash,\n\t\thandler_path: data.handlerPath,\n\t\thandler_code: data.handlerCode ?? null,\n\t\tsource_code: data.sourceCode ?? null,\n\t\tschema_name: data.schemaName ?? null,\n\t\tstart_block: data.startBlock ?? 0,\n\t\tdatabase_url_enc: null,\n\t\tupdated_at: new Date(),\n\t};\n\n\t// OSS: name is unique. Upsert by name so leftover rows with a non-empty\n\t// account_id (pre-local-namespace deploys) don't insert a second row.\n\tif (!isPlatformMode()) {\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"subgraphs\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"name\", \"=\", data.name)\n\t\t\t.executeTakeFirst();\n\t\tif (existing) {\n\t\t\treturn await db\n\t\t\t\t.updateTable(\"subgraphs\")\n\t\t\t\t.set({ ...updateSet, account_id: \"\" })\n\t\t\t\t.where(\"id\", \"=\", existing.id)\n\t\t\t\t.returningAll()\n\t\t\t\t.executeTakeFirstOrThrow();\n\t\t}\n\t\treturn await db\n\t\t\t.insertInto(\"subgraphs\")\n\t\t\t.values(values)\n\t\t\t.returningAll()\n\t\t\t.executeTakeFirstOrThrow();\n\t}\n\n\treturn await db\n\t\t.insertInto(\"subgraphs\")\n\t\t.values(values)\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"name\", \"account_id\"]).doUpdateSet(updateSet),\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\taccountId?: string,\n): Promise<Subgraph | null> {\n\tlet query = db.selectFrom(\"subgraphs\").selectAll().where(\"name\", \"=\", name);\n\n\tif (isPlatformMode() && accountId !== undefined) {\n\t\tquery = query.where(\"account_id\", \"=\", accountId);\n\t}\n\n\treturn (await query.executeTakeFirst()) ?? null;\n}\n\nexport async function listSubgraphs(\n\tdb: Kysely<Database>,\n\taccountId?: string,\n): Promise<Subgraph[]> {\n\tlet query = db.selectFrom(\"subgraphs\").selectAll();\n\tif (isPlatformMode() && accountId !== undefined) {\n\t\tquery = query.where(\"account_id\", \"=\", accountId);\n\t}\n\treturn query.execute();\n}\n\nexport async function updateSubgraphStatus(\n\tdb: Kysely<Database>,\n\tname: string,\n\tstatus: string,\n\tlastProcessedBlock?: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\tstatus,\n\t\t\t...(lastProcessedBlock !== undefined\n\t\t\t\t? { last_processed_block: lastProcessedBlock }\n\t\t\t\t: {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\n/** Account-scoped pause. Only active/reindexing rows flip; name-only\n * {@link updateSubgraphStatus} is unsafe on platform (name, account_id). */\nexport async function pauseSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\taccountId: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({ status: \"paused\", updated_at: new Date() })\n\t\t.where(\"name\", \"=\", name)\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"in\", [\"active\", \"reindexing\"])\n\t\t.execute();\n}\n\n/** Resume every paused subgraph for an account (claim / Stripe top-up). */\nexport async function resumePausedSubgraphs(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<number> {\n\tconst res = await db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({ status: \"active\", updated_at: new Date() })\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"=\", \"paused\")\n\t\t.executeTakeFirst();\n\treturn Number(res.numUpdatedRows ?? 0);\n}\n\n/** Live-walk ERROR write (f069): a block whose handlers ALL failed\n * (`processed === 0 && errors > 0`, `block-processor.ts`'s `applyProgress`)\n * is stamped 'error' and its height is recorded as processed, same as\n * `updateSubgraphStatus(db, name, \"error\", blockHeight)` — except the\n * cursor half of that write now goes through the same conditional `<`\n * guard as {@link recordLiveProgress}. Marking a subgraph 'error' is not\n * itself replay-sensitive, but it shares the `last_processed_block` column\n * with the replay guard: an unconditional write here could regress the\n * cursor a racing (successful) writer already advanced past, which is\n * exactly the regress-then-re-walk loop this guard exists to close. In\n * practice `ctx.rollbackTo` (`runner.ts`) means an all-failed block never\n * flushes writes, so a lost race here is always a silent no-op, never a\n * case that needs `CursorRaceLostError`-style tx abort. Returns whether\n * the write applied. Live path only — reindex/backfill's error stamping\n * goes through {@link updateSubgraphStatus} unconditionally, as before. */\nexport async function recordLiveError(\n\tdb: Kysely<Database>,\n\tname: string,\n\tblockHeight: number,\n): Promise<boolean> {\n\tconst result = await db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\tstatus: \"error\",\n\t\t\tlast_processed_block: blockHeight,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.where(\"last_processed_block\", \"<\", blockHeight)\n\t\t.executeTakeFirst();\n\treturn Number(result.numUpdatedRows ?? 0n) > 0;\n}\n\nexport async function recordSubgraphProcessed(\n\tdb: Kysely<Database>,\n\tname: string,\n\tprocessed: number,\n\terrors: number,\n\tlastError?: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\ttotal_processed: sql`total_processed + ${processed}`,\n\t\t\ttotal_errors: sql`total_errors + ${errors}`,\n\t\t\t...(lastError\n\t\t\t\t? { last_error: lastError, last_error_at: new Date() }\n\t\t\t\t: {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function updateSubgraphHandlerPath(\n\tdb: Kysely<Database>,\n\tname: string,\n\thandlerPath: string,\n\topts?: { handlerCode?: string; sourceCode?: string },\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\thandler_path: handlerPath,\n\t\t\t...(opts?.handlerCode != null ? { handler_code: opts.handlerCode } : {}),\n\t\t\t...(opts?.sourceCode != null ? { source_code: opts.sourceCode } : {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function deleteSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\taccountId?: string,\n): Promise<Subgraph | null> {\n\tconst subgraph = await getSubgraph(db, name, accountId);\n\tif (!subgraph) return null;\n\n\t// Use stored schema_name if available, otherwise compute\n\tconst schemaName = subgraph.schema_name ?? pgSchemaName(name);\n\n\t// Cascade to webhooks: a webhook pointing at a deleted\n\t// subgraph + table will throw `relation does not exist` on every\n\t// subsequent emission. Pause active subs and purge any pending outbox\n\t// rows so receivers don't get phantom replays. We don't delete the\n\t// webhooks themselves — operators may want to repoint them at a\n\t// resurrected subgraph; we just stop them firing.\n\tawait db\n\t\t.updateTable(\"webhooks\")\n\t\t.set({\n\t\t\tstatus: \"paused\",\n\t\t\tlast_error: `Subgraph \"${name}\" deleted; webhook auto-paused.`,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"subgraph_name\", \"=\", name)\n\t\t.execute();\n\tawait db\n\t\t.deleteFrom(\"webhook_outbox\")\n\t\t.where(\"status\", \"=\", \"pending\")\n\t\t.where(\"webhook_id\", \"in\", (qb) =>\n\t\t\tqb.selectFrom(\"webhooks\").select(\"id\").where(\"subgraph_name\", \"=\", name),\n\t\t)\n\t\t.execute();\n\n\t// Drop the subgraph's schema (CASCADE drops all tables within).\n\tawait sql`DROP SCHEMA IF EXISTS ${sql.raw(`\"${schemaName}\"`)} CASCADE`.execute(\n\t\tdb,\n\t);\n\n\t// Remove from registry\n\tawait db.deleteFrom(\"subgraphs\").where(\"id\", \"=\", subgraph.id).execute();\n\n\treturn subgraph;\n}\n\n/** Set or clear a paid subgraph's expiry (NULL = no expiry, e.g. on claim). */\nexport async function updateSubgraphExpiry(\n\tdb: Kysely<Database>,\n\tname: string,\n\taccountId: string,\n\texpiresAt: Date | null,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({ expires_at: expiresAt })\n\t\t.where(\"name\", \"=\", name)\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.execute();\n}\n\n/** Live-walk progress write: advances the cursor and PROMOTES toward\n * 'active' (deploying/error → active keeps reorg eligibility, reorg.ts gates\n * on it) but never overwrites an explicit 'reindexing' park — unconditional\n * status stamping let catch-up flap a parked subgraph back into its own path\n * per block, fighting the queued reindex op.\n *\n * `last_processed_block` is now written CONDITIONALLY — `WHERE\n * last_processed_block < lastProcessedBlock`, mirroring\n * `advanceOperationCursor`'s (`subgraph-operations.ts`) comparison shape —\n * and returns whether THIS call actually advanced it. This used to be\n * unconditional by design, on the premise that the only two writers able to\n * advance/rewind a given subgraph's cursor concurrently (the catch-up walk\n * and the reorg handler) serialize on an in-process per-subgraph lock +\n * reorg epoch guard (`packages/subgraphs/src/runtime/catchup.ts`,\n * `subgraphBlockLockHeld` / `reorgEpoch`; f057). That guard is in-process\n * only. The f068 investigation\n * (`docs/internal/audits/asset-holdings-unreproducible-balances-f068.md`)\n * found a cross-process sequence it does not cover: a catch-up leader that\n * loses its advisory-lock connection mid-walk keeps writing (leadership is\n * checked only at walk entry, not per-iteration) while a new leader takes\n * over and walks the same range — both commit increments for overlapping\n * heights, and every regressed cursor write from the laggard re-triggers a\n * full re-walk, growing per-height application without bound. The\n * conditional advance here (paired with the fast-path replay guard armed on\n * the live path in `block-processor.ts`) is now the cross-process\n * guarantee: a block at/below the current cursor can never re-advance it,\n * so a laggard's commits can no longer regress-then-retrigger.\n *\n * This conditionality is why the reorg rewind can no longer go through this\n * function — a rewind's whole point is to move the cursor *backward*, which\n * the `<` guard would silently refuse. Reorg mode calls\n * {@link rewindLiveProgress} instead, which keeps the old unconditional\n * semantics and is safe specifically because the reorg path takes a\n * transaction-scoped advisory lock (`reorg.ts`) other writers can't cross. */\nexport async function recordLiveProgress(\n\tdb: Kysely<Database>,\n\tname: string,\n\tlastProcessedBlock: number,\n): Promise<boolean> {\n\tconst result = await db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\tlast_processed_block: lastProcessedBlock,\n\t\t\tstatus: sql`CASE WHEN status = 'reindexing' THEN status ELSE 'active' END`,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.where(\"last_processed_block\", \"<\", lastProcessedBlock)\n\t\t.executeTakeFirst();\n\treturn Number(result.numUpdatedRows ?? 0n) > 0;\n}\n\n/** Reorg-rewind progress write: the OLD unconditional `recordLiveProgress`\n * semantics, verbatim — moves `last_processed_block` to the fork height\n * regardless of its current value, because that is by definition a\n * backward move the conditional `<` guard in `recordLiveProgress` would\n * refuse. Same status-promotion CASE (never unparks an explicit\n * 'reindexing').\n *\n * Callers: exclusively the subgraph-reorg path\n * (`packages/subgraphs/src/runtime/reorg.ts`, via `block-processor.ts`'s\n * `reorgRewind` mode), which is safe from the cross-process race this\n * function reintroduces because it holds a transaction-scoped Postgres\n * advisory lock (`pg_advisory_xact_lock`) for the whole\n * delete+reprocess+rewind sequence — no other writer can interleave a\n * forward write between the rewind and the fork block's reprocess. Do not\n * call this from the live catch-up path. */\nexport async function rewindLiveProgress(\n\tdb: Kysely<Database>,\n\tname: string,\n\tlastProcessedBlock: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\tlast_processed_block: lastProcessedBlock,\n\t\t\tstatus: sql`CASE WHEN status = 'reindexing' THEN status ELSE 'active' END`,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": "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
|
|
10
|
-
"debugId": "
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "0C7267DDF02AF5D764756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
|
@@ -799,9 +799,9 @@ interface Database {
|
|
|
799
799
|
account_spend_caps: AccountSpendCapsTable;
|
|
800
800
|
account_credits: AccountCreditsTable;
|
|
801
801
|
hosted_meter_days: HostedMeterDaysTable;
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
802
|
+
webhooks: WebhooksTable;
|
|
803
|
+
webhook_outbox: WebhookOutboxTable;
|
|
804
|
+
webhook_deliveries: WebhookDeliveriesTable;
|
|
805
805
|
trigger_evaluator_state: TriggerEvaluatorStateTable;
|
|
806
806
|
decoded_events: DecodedEventsTable;
|
|
807
807
|
decoder_checkpoints: DecoderCheckpointsTable;
|
|
@@ -921,31 +921,31 @@ interface ArchiveFetchesTable {
|
|
|
921
921
|
via_allowance: Generated<boolean>;
|
|
922
922
|
charged_at: Generated<Date>;
|
|
923
923
|
}
|
|
924
|
-
type
|
|
925
|
-
/** Polymorphic
|
|
924
|
+
type WebhookStatus = "active" | "paused" | "error";
|
|
925
|
+
/** Polymorphic webhook mode: `subgraph` reacts to processed table rows;
|
|
926
926
|
* `chain` reacts to raw chain events matched directly off the Index/Streams
|
|
927
927
|
* clock (no subgraph). See migration 0088. */
|
|
928
|
-
type
|
|
929
|
-
type
|
|
930
|
-
type
|
|
931
|
-
interface
|
|
928
|
+
type WebhookKind = "subgraph" | "chain";
|
|
929
|
+
type WebhookFormat = "standard-webhooks" | "inngest" | "trigger" | "cloudflare" | "cloudevents" | "raw";
|
|
930
|
+
type WebhookRuntime = "inngest" | "trigger" | "cloudflare" | "node";
|
|
931
|
+
interface WebhooksTable {
|
|
932
932
|
id: Generated<string>;
|
|
933
933
|
account_id: string;
|
|
934
934
|
project_id: string | null;
|
|
935
935
|
name: string;
|
|
936
|
-
status: ColumnType<
|
|
937
|
-
kind: ColumnType<
|
|
938
|
-
/** Null for chain
|
|
936
|
+
status: ColumnType<WebhookStatus, WebhookStatus | undefined, WebhookStatus>;
|
|
937
|
+
kind: ColumnType<WebhookKind, WebhookKind | undefined, WebhookKind>;
|
|
938
|
+
/** Null for chain webhooks (CHECK webhooks_kind_shape). */
|
|
939
939
|
subgraph_name: string | null;
|
|
940
|
-
/** Null for chain
|
|
940
|
+
/** Null for chain webhooks (CHECK webhooks_kind_shape). */
|
|
941
941
|
table_name: string | null;
|
|
942
942
|
/** Chain-trigger filter array (the `SubgraphFilter` shape, JSON). Null for
|
|
943
|
-
* subgraph
|
|
944
|
-
* import cycle; the Zod schema in schemas/
|
|
943
|
+
* subgraph webhooks. Typed loosely here to avoid a shared→subgraphs
|
|
944
|
+
* import cycle; the Zod schema in schemas/webhooks.ts owns the shape. */
|
|
945
945
|
triggers: unknown | null;
|
|
946
946
|
filter: Generated<unknown>;
|
|
947
|
-
format: ColumnType<
|
|
948
|
-
runtime:
|
|
947
|
+
format: ColumnType<WebhookFormat, WebhookFormat | undefined, WebhookFormat>;
|
|
948
|
+
runtime: WebhookRuntime | null;
|
|
949
949
|
url: string;
|
|
950
950
|
signing_secret_enc: Buffer;
|
|
951
951
|
auth_config: Generated<unknown>;
|
|
@@ -961,13 +961,13 @@ interface SubscriptionsTable {
|
|
|
961
961
|
updated_at: Generated<Date>;
|
|
962
962
|
}
|
|
963
963
|
type OutboxStatus = "pending" | "delivered" | "dead";
|
|
964
|
-
interface
|
|
964
|
+
interface WebhookOutboxTable {
|
|
965
965
|
id: Generated<string>;
|
|
966
|
-
|
|
967
|
-
kind: ColumnType<
|
|
968
|
-
/** Null for chain-
|
|
966
|
+
webhook_id: string;
|
|
967
|
+
kind: ColumnType<WebhookKind, WebhookKind | undefined, WebhookKind>;
|
|
968
|
+
/** Null for chain-webhook rows. */
|
|
969
969
|
subgraph_name: string | null;
|
|
970
|
-
/** Null for chain-
|
|
970
|
+
/** Null for chain-webhook rows. */
|
|
971
971
|
table_name: string | null;
|
|
972
972
|
block_height: number | bigint;
|
|
973
973
|
tx_id: string | null;
|
|
@@ -985,12 +985,12 @@ interface SubscriptionOutboxTable {
|
|
|
985
985
|
locked_until: Date | null;
|
|
986
986
|
created_at: Generated<Date>;
|
|
987
987
|
}
|
|
988
|
-
interface
|
|
988
|
+
interface WebhookDeliveriesTable {
|
|
989
989
|
id: Generated<string>;
|
|
990
990
|
/** Nullable after migration 0077 — outbox row may be cleaned up while
|
|
991
991
|
* delivery telemetry is retained. */
|
|
992
992
|
outbox_id: string | null;
|
|
993
|
-
|
|
993
|
+
webhook_id: string;
|
|
994
994
|
attempt: number;
|
|
995
995
|
status_code: number | null;
|
|
996
996
|
response_headers: unknown | null;
|
|
@@ -1000,7 +1000,7 @@ interface SubscriptionDeliveriesTable {
|
|
|
1000
1000
|
dispatched_at: Generated<Date>;
|
|
1001
1001
|
}
|
|
1002
1002
|
/** Single-row (id always TRUE) high-water mark for the chain-trigger evaluator.
|
|
1003
|
-
* One loop serves all chain
|
|
1003
|
+
* One loop serves all chain webhooks, so the cursor is global. */
|
|
1004
1004
|
interface TriggerEvaluatorStateTable {
|
|
1005
1005
|
id: Generated<boolean>;
|
|
1006
1006
|
last_processed_block: ColumnType<bigint, bigint | number | undefined, bigint | number>;
|
|
@@ -789,9 +789,9 @@ interface Database {
|
|
|
789
789
|
account_spend_caps: AccountSpendCapsTable;
|
|
790
790
|
account_credits: AccountCreditsTable;
|
|
791
791
|
hosted_meter_days: HostedMeterDaysTable;
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
792
|
+
webhooks: WebhooksTable;
|
|
793
|
+
webhook_outbox: WebhookOutboxTable;
|
|
794
|
+
webhook_deliveries: WebhookDeliveriesTable;
|
|
795
795
|
trigger_evaluator_state: TriggerEvaluatorStateTable;
|
|
796
796
|
decoded_events: DecodedEventsTable;
|
|
797
797
|
decoder_checkpoints: DecoderCheckpointsTable;
|
|
@@ -911,31 +911,31 @@ interface ArchiveFetchesTable {
|
|
|
911
911
|
via_allowance: Generated<boolean>;
|
|
912
912
|
charged_at: Generated<Date>;
|
|
913
913
|
}
|
|
914
|
-
type
|
|
915
|
-
/** Polymorphic
|
|
914
|
+
type WebhookStatus = "active" | "paused" | "error";
|
|
915
|
+
/** Polymorphic webhook mode: `subgraph` reacts to processed table rows;
|
|
916
916
|
* `chain` reacts to raw chain events matched directly off the Index/Streams
|
|
917
917
|
* clock (no subgraph). See migration 0088. */
|
|
918
|
-
type
|
|
919
|
-
type
|
|
920
|
-
type
|
|
921
|
-
interface
|
|
918
|
+
type WebhookKind = "subgraph" | "chain";
|
|
919
|
+
type WebhookFormat = "standard-webhooks" | "inngest" | "trigger" | "cloudflare" | "cloudevents" | "raw";
|
|
920
|
+
type WebhookRuntime = "inngest" | "trigger" | "cloudflare" | "node";
|
|
921
|
+
interface WebhooksTable {
|
|
922
922
|
id: Generated<string>;
|
|
923
923
|
account_id: string;
|
|
924
924
|
project_id: string | null;
|
|
925
925
|
name: string;
|
|
926
|
-
status: ColumnType<
|
|
927
|
-
kind: ColumnType<
|
|
928
|
-
/** Null for chain
|
|
926
|
+
status: ColumnType<WebhookStatus, WebhookStatus | undefined, WebhookStatus>;
|
|
927
|
+
kind: ColumnType<WebhookKind, WebhookKind | undefined, WebhookKind>;
|
|
928
|
+
/** Null for chain webhooks (CHECK webhooks_kind_shape). */
|
|
929
929
|
subgraph_name: string | null;
|
|
930
|
-
/** Null for chain
|
|
930
|
+
/** Null for chain webhooks (CHECK webhooks_kind_shape). */
|
|
931
931
|
table_name: string | null;
|
|
932
932
|
/** Chain-trigger filter array (the `SubgraphFilter` shape, JSON). Null for
|
|
933
|
-
* subgraph
|
|
934
|
-
* import cycle; the Zod schema in schemas/
|
|
933
|
+
* subgraph webhooks. Typed loosely here to avoid a shared→subgraphs
|
|
934
|
+
* import cycle; the Zod schema in schemas/webhooks.ts owns the shape. */
|
|
935
935
|
triggers: unknown | null;
|
|
936
936
|
filter: Generated<unknown>;
|
|
937
|
-
format: ColumnType<
|
|
938
|
-
runtime:
|
|
937
|
+
format: ColumnType<WebhookFormat, WebhookFormat | undefined, WebhookFormat>;
|
|
938
|
+
runtime: WebhookRuntime | null;
|
|
939
939
|
url: string;
|
|
940
940
|
signing_secret_enc: Buffer;
|
|
941
941
|
auth_config: Generated<unknown>;
|
|
@@ -950,15 +950,15 @@ interface SubscriptionsTable {
|
|
|
950
950
|
created_at: Generated<Date>;
|
|
951
951
|
updated_at: Generated<Date>;
|
|
952
952
|
}
|
|
953
|
-
type
|
|
953
|
+
type Webhook = Selectable<WebhooksTable>;
|
|
954
954
|
type OutboxStatus = "pending" | "delivered" | "dead";
|
|
955
|
-
interface
|
|
955
|
+
interface WebhookOutboxTable {
|
|
956
956
|
id: Generated<string>;
|
|
957
|
-
|
|
958
|
-
kind: ColumnType<
|
|
959
|
-
/** Null for chain-
|
|
957
|
+
webhook_id: string;
|
|
958
|
+
kind: ColumnType<WebhookKind, WebhookKind | undefined, WebhookKind>;
|
|
959
|
+
/** Null for chain-webhook rows. */
|
|
960
960
|
subgraph_name: string | null;
|
|
961
|
-
/** Null for chain-
|
|
961
|
+
/** Null for chain-webhook rows. */
|
|
962
962
|
table_name: string | null;
|
|
963
963
|
block_height: number | bigint;
|
|
964
964
|
tx_id: string | null;
|
|
@@ -976,12 +976,12 @@ interface SubscriptionOutboxTable {
|
|
|
976
976
|
locked_until: Date | null;
|
|
977
977
|
created_at: Generated<Date>;
|
|
978
978
|
}
|
|
979
|
-
interface
|
|
979
|
+
interface WebhookDeliveriesTable {
|
|
980
980
|
id: Generated<string>;
|
|
981
981
|
/** Nullable after migration 0077 — outbox row may be cleaned up while
|
|
982
982
|
* delivery telemetry is retained. */
|
|
983
983
|
outbox_id: string | null;
|
|
984
|
-
|
|
984
|
+
webhook_id: string;
|
|
985
985
|
attempt: number;
|
|
986
986
|
status_code: number | null;
|
|
987
987
|
response_headers: unknown | null;
|
|
@@ -991,7 +991,7 @@ interface SubscriptionDeliveriesTable {
|
|
|
991
991
|
dispatched_at: Generated<Date>;
|
|
992
992
|
}
|
|
993
993
|
/** Single-row (id always TRUE) high-water mark for the chain-trigger evaluator.
|
|
994
|
-
* One loop serves all chain
|
|
994
|
+
* One loop serves all chain webhooks, so the cursor is global. */
|
|
995
995
|
interface TriggerEvaluatorStateTable {
|
|
996
996
|
id: Generated<boolean>;
|
|
997
997
|
last_processed_block: ColumnType<bigint, bigint | number | undefined, bigint | number>;
|
|
@@ -1001,70 +1001,70 @@ interface TriggerEvaluatorStateTable {
|
|
|
1001
1001
|
updated_at: Generated<Date>;
|
|
1002
1002
|
}
|
|
1003
1003
|
/**
|
|
1004
|
-
*
|
|
1004
|
+
* Webhook CRUD. `signing_secret_enc` is transparently encrypted via
|
|
1005
1005
|
* `encryptSecret`/`decryptSecret`. Plaintext secrets only leave via the
|
|
1006
1006
|
* return value of `create` (one-time display) and `rotateSecret`.
|
|
1007
1007
|
*/
|
|
1008
|
-
interface
|
|
1008
|
+
interface CreateWebhookInput {
|
|
1009
1009
|
accountId: string;
|
|
1010
1010
|
projectId?: string | null;
|
|
1011
1011
|
name: string;
|
|
1012
|
-
/** Defaults to "subgraph". Chain
|
|
1013
|
-
kind?:
|
|
1014
|
-
/** Required for subgraph
|
|
1012
|
+
/** Defaults to "subgraph". Chain webhooks set kind="chain" + triggers. */
|
|
1013
|
+
kind?: WebhookKind;
|
|
1014
|
+
/** Required for subgraph webhooks; omitted for chain. */
|
|
1015
1015
|
subgraphName?: string | null;
|
|
1016
|
-
/** Required for subgraph
|
|
1016
|
+
/** Required for subgraph webhooks; omitted for chain. */
|
|
1017
1017
|
tableName?: string | null;
|
|
1018
|
-
/** Chain-trigger filter array. Required for chain
|
|
1018
|
+
/** Chain-trigger filter array. Required for chain webhooks. */
|
|
1019
1019
|
triggers?: unknown;
|
|
1020
1020
|
filter?: unknown;
|
|
1021
|
-
format?:
|
|
1022
|
-
runtime?:
|
|
1021
|
+
format?: WebhookFormat;
|
|
1022
|
+
runtime?: WebhookRuntime | null;
|
|
1023
1023
|
url: string;
|
|
1024
1024
|
authConfig?: unknown;
|
|
1025
1025
|
maxRetries?: number;
|
|
1026
1026
|
timeoutMs?: number;
|
|
1027
1027
|
concurrency?: number;
|
|
1028
1028
|
}
|
|
1029
|
-
interface
|
|
1030
|
-
|
|
1029
|
+
interface CreateWebhookResult {
|
|
1030
|
+
webhook: Webhook;
|
|
1031
1031
|
/** Plaintext signing secret — surfaced once, never stored decrypted. */
|
|
1032
1032
|
signingSecret: string;
|
|
1033
1033
|
}
|
|
1034
|
-
declare function
|
|
1035
|
-
declare function
|
|
1034
|
+
declare function createWebhook(db: Kysely<Database>, input: CreateWebhookInput): Promise<CreateWebhookResult>;
|
|
1035
|
+
declare function listWebhooks(db: Kysely<Database>, accountId: string, opts?: {
|
|
1036
1036
|
limit?: number
|
|
1037
1037
|
offset?: number
|
|
1038
|
-
}): Promise<
|
|
1038
|
+
}): Promise<Webhook[]>;
|
|
1039
1039
|
/**
|
|
1040
|
-
* All active chain
|
|
1041
|
-
* trigger evaluator (one loop serves them all). Subgraph
|
|
1040
|
+
* All active chain webhooks across every account — the input to the global
|
|
1041
|
+
* trigger evaluator (one loop serves them all). Subgraph webhooks are
|
|
1042
1042
|
* excluded; they emit via the subgraph flush path.
|
|
1043
1043
|
*/
|
|
1044
|
-
declare function
|
|
1045
|
-
declare function
|
|
1046
|
-
declare function
|
|
1047
|
-
interface
|
|
1044
|
+
declare function listActiveChainWebhooks(db: Kysely<Database>): Promise<Webhook[]>;
|
|
1045
|
+
declare function getWebhook(db: Kysely<Database>, accountId: string, id: string): Promise<Webhook | null>;
|
|
1046
|
+
declare function getWebhookByName(db: Kysely<Database>, accountId: string, name: string): Promise<Webhook | null>;
|
|
1047
|
+
interface UpdateWebhookInput {
|
|
1048
1048
|
name?: string;
|
|
1049
1049
|
filter?: unknown;
|
|
1050
|
-
format?:
|
|
1051
|
-
runtime?:
|
|
1050
|
+
format?: WebhookFormat;
|
|
1051
|
+
runtime?: WebhookRuntime | null;
|
|
1052
1052
|
url?: string;
|
|
1053
1053
|
authConfig?: unknown;
|
|
1054
1054
|
maxRetries?: number;
|
|
1055
1055
|
timeoutMs?: number;
|
|
1056
1056
|
concurrency?: number;
|
|
1057
1057
|
}
|
|
1058
|
-
declare function
|
|
1059
|
-
declare function
|
|
1060
|
-
declare function
|
|
1058
|
+
declare function updateWebhook(db: Kysely<Database>, accountId: string, id: string, patch: UpdateWebhookInput): Promise<Webhook | null>;
|
|
1059
|
+
declare function toggleWebhookStatus(db: Kysely<Database>, accountId: string, id: string, status: WebhookStatus): Promise<Webhook | null>;
|
|
1060
|
+
declare function deleteWebhook(db: Kysely<Database>, accountId: string, id: string): Promise<boolean>;
|
|
1061
1061
|
interface RotateSecretResult {
|
|
1062
|
-
|
|
1062
|
+
webhook: Webhook;
|
|
1063
1063
|
signingSecret: string;
|
|
1064
1064
|
}
|
|
1065
|
-
declare function
|
|
1066
|
-
/** Decrypt a
|
|
1067
|
-
declare function
|
|
1068
|
-
/** Fire `
|
|
1069
|
-
declare function
|
|
1070
|
-
export {
|
|
1065
|
+
declare function rotateWebhookSecret(db: Kysely<Database>, accountId: string, id: string): Promise<RotateSecretResult | null>;
|
|
1066
|
+
/** Decrypt a webhook's signing secret for HMAC signing at emit time. */
|
|
1067
|
+
declare function getWebhookSigningSecret(webhook: Webhook): string;
|
|
1068
|
+
/** Fire `webhooks:changed` notify so the emitter hot-reloads its cache. */
|
|
1069
|
+
declare function notifyWebhooksChanged(db: Kysely<Database>, accountId: string): Promise<void>;
|
|
1070
|
+
export { updateWebhook, toggleWebhookStatus, rotateWebhookSecret, notifyWebhooksChanged, listWebhooks, listActiveChainWebhooks, getWebhookSigningSecret, getWebhookByName, getWebhook, deleteWebhook, createWebhook, UpdateWebhookInput, RotateSecretResult, CreateWebhookResult, CreateWebhookInput };
|
|
@@ -227,9 +227,9 @@ function generateSecretsKey() {
|
|
|
227
227
|
return randomBytes2(32).toString("hex");
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
// src/db/queries/
|
|
230
|
+
// src/db/queries/webhooks.ts
|
|
231
231
|
import { sql } from "kysely";
|
|
232
|
-
async function
|
|
232
|
+
async function createWebhook(db, input) {
|
|
233
233
|
const signingSecret = generateSecret();
|
|
234
234
|
const kind = input.kind ?? "subgraph";
|
|
235
235
|
const row = {
|
|
@@ -251,11 +251,11 @@ async function createSubscription(db, input) {
|
|
|
251
251
|
...input.timeoutMs !== undefined ? { timeout_ms: input.timeoutMs } : {},
|
|
252
252
|
...input.concurrency !== undefined ? { concurrency: input.concurrency } : {}
|
|
253
253
|
};
|
|
254
|
-
const
|
|
255
|
-
return {
|
|
254
|
+
const webhook = await db.insertInto("webhooks").values(row).returningAll().executeTakeFirstOrThrow();
|
|
255
|
+
return { webhook, signingSecret };
|
|
256
256
|
}
|
|
257
|
-
async function
|
|
258
|
-
let q = db.selectFrom("
|
|
257
|
+
async function listWebhooks(db, accountId, opts) {
|
|
258
|
+
let q = db.selectFrom("webhooks").selectAll().orderBy("created_at", "desc");
|
|
259
259
|
if (isPlatformMode())
|
|
260
260
|
q = q.where("account_id", "=", accountId);
|
|
261
261
|
if (opts?.limit !== undefined)
|
|
@@ -264,22 +264,22 @@ async function listSubscriptions(db, accountId, opts) {
|
|
|
264
264
|
q = q.offset(opts.offset);
|
|
265
265
|
return q.execute();
|
|
266
266
|
}
|
|
267
|
-
async function
|
|
268
|
-
return db.selectFrom("
|
|
267
|
+
async function listActiveChainWebhooks(db) {
|
|
268
|
+
return db.selectFrom("webhooks").selectAll().where("kind", "=", "chain").where("status", "=", "active").execute();
|
|
269
269
|
}
|
|
270
|
-
async function
|
|
271
|
-
let q = db.selectFrom("
|
|
270
|
+
async function getWebhook(db, accountId, id) {
|
|
271
|
+
let q = db.selectFrom("webhooks").selectAll().where("id", "=", id);
|
|
272
272
|
if (isPlatformMode())
|
|
273
273
|
q = q.where("account_id", "=", accountId);
|
|
274
274
|
return await q.executeTakeFirst() ?? null;
|
|
275
275
|
}
|
|
276
|
-
async function
|
|
277
|
-
let q = db.selectFrom("
|
|
276
|
+
async function getWebhookByName(db, accountId, name) {
|
|
277
|
+
let q = db.selectFrom("webhooks").selectAll().where("name", "=", name);
|
|
278
278
|
if (isPlatformMode())
|
|
279
279
|
q = q.where("account_id", "=", accountId);
|
|
280
280
|
return await q.executeTakeFirst() ?? null;
|
|
281
281
|
}
|
|
282
|
-
async function
|
|
282
|
+
async function updateWebhook(db, accountId, id, patch) {
|
|
283
283
|
const update = { updated_at: new Date };
|
|
284
284
|
if (patch.name !== undefined)
|
|
285
285
|
update.name = patch.name;
|
|
@@ -299,13 +299,13 @@ async function updateSubscription(db, accountId, id, patch) {
|
|
|
299
299
|
update.timeout_ms = patch.timeoutMs;
|
|
300
300
|
if (patch.concurrency !== undefined)
|
|
301
301
|
update.concurrency = patch.concurrency;
|
|
302
|
-
let q = db.updateTable("
|
|
302
|
+
let q = db.updateTable("webhooks").set(update).where("id", "=", id);
|
|
303
303
|
if (isPlatformMode())
|
|
304
304
|
q = q.where("account_id", "=", accountId);
|
|
305
305
|
return await q.returningAll().executeTakeFirst() ?? null;
|
|
306
306
|
}
|
|
307
|
-
async function
|
|
308
|
-
let q = db.updateTable("
|
|
307
|
+
async function toggleWebhookStatus(db, accountId, id, status) {
|
|
308
|
+
let q = db.updateTable("webhooks").set({
|
|
309
309
|
status,
|
|
310
310
|
updated_at: new Date,
|
|
311
311
|
...status === "active" ? {
|
|
@@ -317,16 +317,16 @@ async function toggleSubscriptionStatus(db, accountId, id, status) {
|
|
|
317
317
|
q = q.where("account_id", "=", accountId);
|
|
318
318
|
return await q.returningAll().executeTakeFirst() ?? null;
|
|
319
319
|
}
|
|
320
|
-
async function
|
|
321
|
-
let q = db.deleteFrom("
|
|
320
|
+
async function deleteWebhook(db, accountId, id) {
|
|
321
|
+
let q = db.deleteFrom("webhooks").where("id", "=", id);
|
|
322
322
|
if (isPlatformMode())
|
|
323
323
|
q = q.where("account_id", "=", accountId);
|
|
324
324
|
const res = await q.executeTakeFirst();
|
|
325
325
|
return Number(res.numDeletedRows ?? 0) > 0;
|
|
326
326
|
}
|
|
327
|
-
async function
|
|
327
|
+
async function rotateWebhookSecret(db, accountId, id) {
|
|
328
328
|
const signingSecret = generateSecret();
|
|
329
|
-
let q = db.updateTable("
|
|
329
|
+
let q = db.updateTable("webhooks").set({
|
|
330
330
|
signing_secret_enc: encryptSecret(signingSecret),
|
|
331
331
|
updated_at: new Date
|
|
332
332
|
}).where("id", "=", id);
|
|
@@ -335,27 +335,27 @@ async function rotateSubscriptionSecret(db, accountId, id) {
|
|
|
335
335
|
const row = await q.returningAll().executeTakeFirst();
|
|
336
336
|
if (!row)
|
|
337
337
|
return null;
|
|
338
|
-
return {
|
|
338
|
+
return { webhook: row, signingSecret };
|
|
339
339
|
}
|
|
340
|
-
function
|
|
341
|
-
return decryptSecret(
|
|
340
|
+
function getWebhookSigningSecret(webhook) {
|
|
341
|
+
return decryptSecret(webhook.signing_secret_enc);
|
|
342
342
|
}
|
|
343
|
-
async function
|
|
344
|
-
await sql`SELECT pg_notify('
|
|
343
|
+
async function notifyWebhooksChanged(db, accountId) {
|
|
344
|
+
await sql`SELECT pg_notify('webhooks:changed', ${accountId})`.execute(db);
|
|
345
345
|
}
|
|
346
346
|
export {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
347
|
+
updateWebhook,
|
|
348
|
+
toggleWebhookStatus,
|
|
349
|
+
rotateWebhookSecret,
|
|
350
|
+
notifyWebhooksChanged,
|
|
351
|
+
listWebhooks,
|
|
352
|
+
listActiveChainWebhooks,
|
|
353
|
+
getWebhookSigningSecret,
|
|
354
|
+
getWebhookByName,
|
|
355
|
+
getWebhook,
|
|
356
|
+
deleteWebhook,
|
|
357
|
+
createWebhook
|
|
358
358
|
};
|
|
359
359
|
|
|
360
|
-
//# debugId=
|
|
361
|
-
//# sourceMappingURL=
|
|
360
|
+
//# debugId=8CB695561D569ACB64756E2164756E21
|
|
361
|
+
//# sourceMappingURL=webhooks.js.map
|