@secondlayer/shared 11.7.1 → 11.8.1
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/dist/src/archive/copy-loader.d.ts +1 -1
- package/dist/src/archive/copy-loader.js +4 -20
- package/dist/src/archive/copy-loader.js.map +2 -2
- package/dist/src/archive/derived-stage-report.d.ts +1 -1
- package/dist/src/archive/derived-stage-report.js +3 -19
- package/dist/src/archive/derived-stage-report.js.map +2 -2
- package/dist/src/archive/disk-guard.d.ts +1 -1
- package/dist/src/archive/disk-guard.js +3 -19
- package/dist/src/archive/disk-guard.js.map +2 -2
- package/dist/src/archive/instance-diagnosis.d.ts +1 -1
- package/dist/src/archive/instance-diagnosis.js +6 -22
- package/dist/src/archive/instance-diagnosis.js.map +2 -2
- package/dist/src/archive/key-registry.d.ts +1 -1
- package/dist/src/archive/key-registry.js +12 -12
- package/dist/src/archive/key-registry.js.map +1 -1
- package/dist/src/archive/per-height-digest.d.ts +1 -1
- package/dist/src/archive/per-height-digest.js +3 -17
- package/dist/src/archive/per-height-digest.js.map +5 -5
- package/dist/src/archive/progress.d.ts +1 -1
- package/dist/src/archive/progress.js +4 -20
- package/dist/src/archive/progress.js.map +2 -2
- package/dist/src/archive/range-digest.d.ts +28 -3
- package/dist/src/archive/range-digest.js +4 -20
- package/dist/src/archive/range-digest.js.map +2 -2
- package/dist/src/archive/reference.d.ts +1 -1
- package/dist/src/archive/reference.js +14 -12
- package/dist/src/archive/reference.js.map +5 -5
- package/dist/src/archive/root-key.d.ts +1 -1
- package/dist/src/archive/root-key.js +3 -19
- package/dist/src/archive/root-key.js.map +2 -2
- package/dist/src/archive/semantic-digest-builder.d.ts +28 -3
- package/dist/src/archive/semantic-digest-builder.js +5 -19
- package/dist/src/archive/semantic-digest-builder.js.map +5 -5
- package/dist/src/archive/semantic-digest.d.ts +1 -1
- package/dist/src/archive/semantic-digest.js +4 -20
- package/dist/src/archive/semantic-digest.js.map +2 -2
- package/dist/src/archive/status.d.ts +1 -1
- package/dist/src/archive/status.js +4 -20
- package/dist/src/archive/status.js.map +2 -2
- package/dist/src/coverage/index.d.ts +27 -2
- package/dist/src/coverage/index.js +80 -96
- package/dist/src/coverage/index.js.map +2 -2
- package/dist/src/crypto/ed25519.d.ts +1 -1
- package/dist/src/crypto/ed25519.js +13 -13
- package/dist/src/crypto/ed25519.js.map +1 -1
- package/dist/src/crypto/hmac.d.ts +1 -1
- package/dist/src/crypto/hmac.js +9 -9
- package/dist/src/crypto/hmac.js.map +1 -1
- package/dist/src/crypto/secondlayer-webhook.d.ts +1 -1
- package/dist/src/crypto/secondlayer-webhook.js +14 -14
- package/dist/src/crypto/secondlayer-webhook.js.map +2 -2
- package/dist/src/crypto/secrets.d.ts +1 -1
- package/dist/src/crypto/secrets.js +15 -29
- package/dist/src/crypto/secrets.js.map +5 -5
- package/dist/src/crypto/standard-webhooks.d.ts +1 -1
- package/dist/src/crypto/standard-webhooks.js +3 -19
- package/dist/src/crypto/standard-webhooks.js.map +2 -2
- package/dist/src/db/index.d.ts +48 -19
- package/dist/src/db/index.js +41 -55
- package/dist/src/db/index.js.map +6 -6
- package/dist/src/db/jsonb.d.ts +1 -1
- package/dist/src/db/jsonb.js +3 -19
- package/dist/src/db/jsonb.js.map +2 -2
- package/dist/src/db/queries/chain-reorgs.d.ts +27 -2
- package/dist/src/db/queries/chain-reorgs.js +28 -42
- package/dist/src/db/queries/chain-reorgs.js.map +6 -6
- package/dist/src/db/queries/contracts.d.ts +27 -2
- package/dist/src/db/queries/contracts.js +9 -23
- package/dist/src/db/queries/contracts.js.map +5 -5
- package/dist/src/db/queries/instance.d.ts +27 -2
- package/dist/src/db/queries/instance.js +7 -23
- package/dist/src/db/queries/instance.js.map +2 -2
- package/dist/src/db/queries/integrity.d.ts +27 -2
- package/dist/src/db/queries/integrity.js +5 -21
- package/dist/src/db/queries/integrity.js.map +2 -2
- package/dist/src/db/queries/subgraph-gaps.d.ts +27 -2
- package/dist/src/db/queries/subgraph-gaps.js +5 -21
- package/dist/src/db/queries/subgraph-gaps.js.map +2 -2
- package/dist/src/db/queries/subgraph-operations.d.ts +27 -2
- package/dist/src/db/queries/subgraph-operations.js +17 -33
- package/dist/src/db/queries/subgraph-operations.js.map +2 -2
- package/dist/src/db/queries/subgraphs.d.ts +28 -14
- package/dist/src/db/queries/subgraphs.js +15 -37
- package/dist/src/db/queries/subgraphs.js.map +5 -5
- package/dist/src/db/queries/sync-scope.d.ts +27 -2
- package/dist/src/db/queries/sync-scope.js +6 -22
- package/dist/src/db/queries/sync-scope.js.map +2 -2
- package/dist/src/db/queries/webhooks.d.ts +27 -2
- package/dist/src/db/queries/webhooks.js +54 -50
- package/dist/src/db/queries/webhooks.js.map +5 -5
- package/dist/src/db/schema.d.ts +30 -2
- package/dist/src/env.d.ts +1 -1
- package/dist/src/env.js +7 -23
- package/dist/src/env.js.map +2 -2
- package/dist/src/errors.d.ts +4 -4
- package/dist/src/errors.js +15 -31
- package/dist/src/errors.js.map +3 -3
- package/dist/src/index-http.d.ts +1 -1
- package/dist/src/index-http.js +4 -20
- package/dist/src/index-http.js.map +3 -3
- package/dist/src/index-internal-auth.d.ts +4 -7
- package/dist/src/index-internal-auth.js +8 -24
- package/dist/src/index-internal-auth.js.map +3 -3
- package/dist/src/index.d.ts +176 -152
- package/dist/src/index.js +305 -317
- package/dist/src/index.js.map +15 -15
- package/dist/src/leader.d.ts +1 -1
- package/dist/src/leader.js +7 -21
- package/dist/src/leader.js.map +5 -5
- package/dist/src/logger.js +1 -17
- package/dist/src/logger.js.map +2 -2
- package/dist/src/mode.d.ts +1 -1
- package/dist/src/mode.js +5 -21
- package/dist/src/mode.js.map +2 -2
- package/dist/src/node/archive-client.d.ts +1 -1
- package/dist/src/node/archive-client.js +9 -23
- package/dist/src/node/archive-client.js.map +5 -5
- package/dist/src/node/client.d.ts +1 -1
- package/dist/src/node/client.js +4 -18
- package/dist/src/node/client.js.map +5 -5
- package/dist/src/node/consensus.d.ts +1 -1
- package/dist/src/node/consensus.js +5 -21
- package/dist/src/node/consensus.js.map +2 -2
- package/dist/src/node/hiro-client.d.ts +1 -1
- package/dist/src/node/hiro-client.js +1 -17
- package/dist/src/node/hiro-client.js.map +2 -2
- package/dist/src/node/hiro-pg-client.js +1 -17
- package/dist/src/node/hiro-pg-client.js.map +2 -2
- package/dist/src/node/local-client.d.ts +27 -2
- package/dist/src/node/local-client.js +3 -19
- package/dist/src/node/local-client.js.map +2 -2
- package/dist/src/node/nakamoto.d.ts +1 -1
- package/dist/src/node/nakamoto.js +12 -28
- package/dist/src/node/nakamoto.js.map +2 -2
- package/dist/src/queue/listener.d.ts +1 -1
- package/dist/src/queue/listener.js +4 -20
- package/dist/src/queue/listener.js.map +2 -2
- package/dist/src/runtime/index.d.ts +1 -1
- package/dist/src/runtime/index.js +51 -67
- package/dist/src/runtime/index.js.map +2 -2
- package/dist/src/schemas/index.d.ts +8 -4
- package/dist/src/schemas/index.js +64 -41
- package/dist/src/schemas/index.js.map +4 -4
- package/dist/src/schemas/subgraphs.d.ts +1 -4
- package/dist/src/schemas/subgraphs.js +1 -18
- package/dist/src/schemas/subgraphs.js.map +3 -3
- package/dist/src/schemas/webhooks.d.ts +19 -1
- package/dist/src/schemas/webhooks.js +70 -41
- package/dist/src/schemas/webhooks.js.map +3 -3
- package/dist/src/streams-bulk-manifest.d.ts +1 -1
- package/dist/src/streams-bulk-manifest.js +9 -9
- package/dist/src/streams-bulk-manifest.js.map +1 -1
- package/dist/src/streams-rows/index.d.ts +1 -1
- package/dist/src/streams-rows/index.js +32 -48
- package/dist/src/streams-rows/index.js.map +2 -2
- package/dist/src/subgraphs/spec.d.ts +1 -11
- package/dist/src/subgraphs/spec.js +27 -93
- package/dist/src/subgraphs/spec.js.map +3 -3
- package/dist/src/types.d.ts +1 -1
- package/migrations/0136_drop_subgraph_visibility.ts +34 -0
- package/migrations/0137_webhook_outbox_last_error.ts +27 -0
- package/migrations/0138_usage_ledger.ts +46 -0
- package/package.json +1 -1
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
|
|
17
1
|
// src/db/queries/instance.ts
|
|
18
2
|
var INSTANCE_NETWORKS = ["mainnet", "testnet", "devnet"];
|
|
19
3
|
|
|
@@ -79,14 +63,14 @@ function toInstance(row) {
|
|
|
79
63
|
};
|
|
80
64
|
}
|
|
81
65
|
export {
|
|
82
|
-
|
|
83
|
-
instanceNetworkFromEnv,
|
|
84
|
-
getInstance,
|
|
85
|
-
ensureInstance,
|
|
86
|
-
InvalidInstanceNetworkError,
|
|
66
|
+
INSTANCE_NETWORKS,
|
|
87
67
|
InstanceNetworkMismatchError,
|
|
88
|
-
|
|
68
|
+
InvalidInstanceNetworkError,
|
|
69
|
+
ensureInstance,
|
|
70
|
+
getInstance,
|
|
71
|
+
instanceNetworkFromEnv,
|
|
72
|
+
parseInstanceNetwork
|
|
89
73
|
};
|
|
90
74
|
|
|
91
|
-
//# debugId=
|
|
75
|
+
//# debugId=2C46A70E1317C50664756E2164756E21
|
|
92
76
|
//# sourceMappingURL=instance.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import type { Kysely } from \"kysely\";\nimport type { Database } from \"../types.ts\";\n\nexport const INSTANCE_NETWORKS = [\"mainnet\", \"testnet\", \"devnet\"] as const;\nexport type InstanceNetwork = (typeof INSTANCE_NETWORKS)[number];\n\nexport type InstanceRow = {\n\tid: string;\n\tnetwork: InstanceNetwork;\n\tcreated_at: Date;\n};\n\nexport class InstanceNetworkMismatchError extends Error {\n\treadonly name = \"InstanceNetworkMismatchError\";\n\tconstructor(\n\t\treadonly existing: InstanceNetwork,\n\t\treadonly requested: InstanceNetwork,\n\t) {\n\t\tsuper(`instance network is ${existing}; cannot change to ${requested}`);\n\t}\n}\n\nexport class InvalidInstanceNetworkError extends Error {\n\treadonly name = \"InvalidInstanceNetworkError\";\n\tconstructor(readonly value: string) {\n\t\tsuper(\n\t\t\t`instance network must be mainnet, testnet, or devnet (got ${value})`,\n\t\t);\n\t}\n}\n\nexport function parseInstanceNetwork(value: string): InstanceNetwork {\n\tconst network = value.trim().toLowerCase();\n\tif ((INSTANCE_NETWORKS as readonly string[]).includes(network)) {\n\t\treturn network as InstanceNetwork;\n\t}\n\tthrow new InvalidInstanceNetworkError(value);\n}\n\n/** STACKS_NETWORK, then NETWORK, then mainnet. */\nexport function instanceNetworkFromEnv(\n\tenv: NodeJS.ProcessEnv = process.env,\n): InstanceNetwork {\n\treturn parseInstanceNetwork(env.STACKS_NETWORK ?? env.NETWORK ?? \"mainnet\");\n}\n\nexport async function getInstance(\n\tdb: Kysely<Database>,\n): Promise<InstanceRow | null> {\n\tconst row = await db\n\t\t.selectFrom(\"instances\")\n\t\t.select([\"id\", \"network\", \"created_at\"])\n\t\t.executeTakeFirst();\n\treturn row ? toInstance(row) : null;\n}\n\n/**\n * Return the singleton instance, creating it on first call.\n * A second call with a different network is refused — the network is\n * immutable for the life of the database.\n */\nexport async function ensureInstance(\n\tdb: Kysely<Database>,\n\tinput: { network: InstanceNetwork },\n): Promise<InstanceRow> {\n\tconst existing = await getInstance(db);\n\tif (existing) {\n\t\tif (existing.network !== input.network) {\n\t\t\tthrow new InstanceNetworkMismatchError(existing.network, input.network);\n\t\t}\n\t\treturn existing;\n\t}\n\ttry {\n\t\tconst inserted = await db\n\t\t\t.insertInto(\"instances\")\n\t\t\t.values({ network: input.network })\n\t\t\t.returning([\"id\", \"network\", \"created_at\"])\n\t\t\t.executeTakeFirstOrThrow();\n\t\treturn toInstance(inserted);\n\t} catch {\n\t\t// Lost the singleton race — the other writer won.\n\t\tconst raced = await getInstance(db);\n\t\tif (!raced) throw new Error(\"failed to create instance\");\n\t\tif (raced.network !== input.network) {\n\t\t\tthrow new InstanceNetworkMismatchError(raced.network, input.network);\n\t\t}\n\t\treturn raced;\n\t}\n}\n\nfunction toInstance(row: {\n\tid: string;\n\tnetwork: string;\n\tcreated_at: Date;\n}): InstanceRow {\n\treturn {\n\t\tid: row.id,\n\t\tnetwork: parseInstanceNetwork(row.network),\n\t\tcreated_at: row.created_at,\n\t};\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AAGO,IAAM,oBAAoB,CAAC,WAAW,WAAW,QAAQ;AAAA;AASzD,MAAM,qCAAqC,MAAM;AAAA,EAG7C;AAAA,EACA;AAAA,EAHD,OAAO;AAAA,EAChB,WAAW,CACD,UACA,WACR;AAAA,IACD,MAAM,uBAAuB,8BAA8B,WAAW;AAAA,IAH7D;AAAA,IACA;AAAA;AAIX;AAAA;AAEO,MAAM,oCAAoC,MAAM;AAAA,EAEjC;AAAA,EADZ,OAAO;AAAA,EAChB,WAAW,CAAU,OAAe;AAAA,IACnC,MACC,6DAA6D,QAC9D;AAAA,IAHoB;AAAA;AAKtB;AAEO,SAAS,oBAAoB,CAAC,OAAgC;AAAA,EACpE,MAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AAAA,EACzC,IAAK,kBAAwC,SAAS,OAAO,GAAG;AAAA,IAC/D,OAAO;AAAA,EACR;AAAA,EACA,MAAM,IAAI,4BAA4B,KAAK;AAAA;AAIrC,SAAS,sBAAsB,CACrC,MAAyB,QAAQ,KACf;AAAA,EAClB,OAAO,qBAAqB,IAAI,kBAAkB,IAAI,WAAW,SAAS;AAAA;AAG3E,eAAsB,WAAW,CAChC,IAC8B;AAAA,EAC9B,MAAM,MAAM,MAAM,GAChB,WAAW,WAAW,EACtB,OAAO,CAAC,MAAM,WAAW,YAAY,CAAC,EACtC,iBAAiB;AAAA,EACnB,OAAO,MAAM,WAAW,GAAG,IAAI;AAAA;AAQhC,eAAsB,cAAc,CACnC,IACA,OACuB;AAAA,EACvB,MAAM,WAAW,MAAM,YAAY,EAAE;AAAA,EACrC,IAAI,UAAU;AAAA,IACb,IAAI,SAAS,YAAY,MAAM,SAAS;AAAA,MACvC,MAAM,IAAI,6BAA6B,SAAS,SAAS,MAAM,OAAO;AAAA,IACvE;AAAA,IACA,OAAO;AAAA,EACR;AAAA,EACA,IAAI;AAAA,IACH,MAAM,WAAW,MAAM,GACrB,WAAW,WAAW,EACtB,OAAO,EAAE,SAAS,MAAM,QAAQ,CAAC,EACjC,UAAU,CAAC,MAAM,WAAW,YAAY,CAAC,EACzC,wBAAwB;AAAA,IAC1B,OAAO,WAAW,QAAQ;AAAA,IACzB,MAAM;AAAA,IAEP,MAAM,QAAQ,MAAM,YAAY,EAAE;AAAA,IAClC,IAAI,CAAC;AAAA,MAAO,MAAM,IAAI,MAAM,2BAA2B;AAAA,IACvD,IAAI,MAAM,YAAY,MAAM,SAAS;AAAA,MACpC,MAAM,IAAI,6BAA6B,MAAM,SAAS,MAAM,OAAO;AAAA,IACpE;AAAA,IACA,OAAO;AAAA;AAAA;AAIT,SAAS,UAAU,CAAC,KAIJ;AAAA,EACf,OAAO;AAAA,IACN,IAAI,IAAI;AAAA,IACR,SAAS,qBAAqB,IAAI,OAAO;AAAA,IACzC,YAAY,IAAI;AAAA,EACjB;AAAA;",
|
|
8
|
+
"debugId": "2C46A70E1317C50664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -153,7 +153,6 @@ interface SubgraphsTable {
|
|
|
153
153
|
handler_code: string | null;
|
|
154
154
|
source_code: string | null;
|
|
155
155
|
project_id: string | null;
|
|
156
|
-
visibility: Generated<string>;
|
|
157
156
|
/** Paid (wallet-ghost) deploys expire unless renewed or claimed; NULL = no expiry. */
|
|
158
157
|
expires_at: Date | null;
|
|
159
158
|
/** (event type, contract) probe pairs persisted at deploy for weight classification. */
|
|
@@ -783,6 +782,7 @@ interface Database {
|
|
|
783
782
|
coverage_segments: CoverageSegmentsTable;
|
|
784
783
|
stage_failures: StageFailuresTable;
|
|
785
784
|
archive_fetches: ArchiveFetchesTable;
|
|
785
|
+
usage_ledger: UsageLedgerTable;
|
|
786
786
|
waitlists: WaitlistsTable;
|
|
787
787
|
waitlist_signups: WaitlistSignupsTable;
|
|
788
788
|
}
|
|
@@ -827,6 +827,27 @@ interface ArchiveFetchesTable {
|
|
|
827
827
|
via_allowance: Generated<boolean>;
|
|
828
828
|
charged_at: Generated<Date>;
|
|
829
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* One metered ledger for every billable unit (archive partitions, hosted
|
|
832
|
+
* Index/Streams rows, and future hosted-stack meters). Append-only, written
|
|
833
|
+
* only by `meter()`. `idempotency_key` is UNIQUE so a retried submission
|
|
834
|
+
* no-ops instead of double-charging. `debited` is false when the balance
|
|
835
|
+
* (or a spend cap) refused the charge — the row still exists so the gap is
|
|
836
|
+
* visible, never silently dropped. `usd_micros` is negative for a Stripe
|
|
837
|
+
* top-up (`unit: "topup"`).
|
|
838
|
+
*/
|
|
839
|
+
interface UsageLedgerTable {
|
|
840
|
+
id: Generated<string>;
|
|
841
|
+
account_id: string;
|
|
842
|
+
unit: string;
|
|
843
|
+
quantity: string | number | bigint;
|
|
844
|
+
usd_micros: string | number | bigint;
|
|
845
|
+
debited: Generated<boolean>;
|
|
846
|
+
source: string;
|
|
847
|
+
idempotency_key: string;
|
|
848
|
+
occurred_at: Generated<Date>;
|
|
849
|
+
recorded_at: Generated<Date>;
|
|
850
|
+
}
|
|
830
851
|
type WebhookStatus = "active" | "paused" | "error";
|
|
831
852
|
/** Polymorphic webhook mode: `subgraph` reacts to processed table rows;
|
|
832
853
|
* `chain` reacts to raw chain events matched directly off the Index/Streams
|
|
@@ -889,6 +910,10 @@ interface WebhookOutboxTable {
|
|
|
889
910
|
failed_at: Date | null;
|
|
890
911
|
locked_by: string | null;
|
|
891
912
|
locked_until: Date | null;
|
|
913
|
+
/** Set when a reorg marks an in-flight row `dead` instead of retrying it
|
|
914
|
+
* into an orphaned fork. Null otherwise — delivery failures track their
|
|
915
|
+
* history on `webhooks.last_error`, not per-row. */
|
|
916
|
+
last_error: string | null;
|
|
892
917
|
created_at: Generated<Date>;
|
|
893
918
|
}
|
|
894
919
|
interface WebhookDeliveriesTable {
|
|
@@ -990,4 +1015,4 @@ declare function findBrokenLinks(db: Kysely<Database>, opts?: {
|
|
|
990
1015
|
window?: number | null
|
|
991
1016
|
limit?: number
|
|
992
1017
|
}): Promise<BrokenLink[]>;
|
|
993
|
-
export {
|
|
1018
|
+
export { BrokenLink, ChainDataIntegrity, Gap, checkChainDataIntegrity, computeContiguousTip, countMissingBlocks, findBrokenLinks, findGaps };
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
|
|
17
1
|
// src/db/queries/integrity.ts
|
|
18
2
|
import { sql } from "kysely";
|
|
19
3
|
async function findGaps(db, limit) {
|
|
@@ -118,12 +102,12 @@ async function findBrokenLinks(db, opts = {}) {
|
|
|
118
102
|
}));
|
|
119
103
|
}
|
|
120
104
|
export {
|
|
121
|
-
|
|
122
|
-
findBrokenLinks,
|
|
123
|
-
countMissingBlocks,
|
|
105
|
+
checkChainDataIntegrity,
|
|
124
106
|
computeContiguousTip,
|
|
125
|
-
|
|
107
|
+
countMissingBlocks,
|
|
108
|
+
findBrokenLinks,
|
|
109
|
+
findGaps
|
|
126
110
|
};
|
|
127
111
|
|
|
128
|
-
//# debugId=
|
|
112
|
+
//# debugId=1E6E1376EB78163164756E2164756E21
|
|
129
113
|
//# sourceMappingURL=integrity.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { type Kysely, sql } from \"kysely\";\nimport type { Database } from \"../types.ts\";\n\nexport interface Gap {\n\tgapStart: number;\n\tgapEnd: number;\n\tsize: number;\n}\n\nexport async function findGaps(\n\tdb: Kysely<Database>,\n\tlimit?: number,\n): Promise<Gap[]> {\n\tconst limitClause = limit ? sql`LIMIT ${limit}` : sql``;\n\tconst { rows } = await sql<{\n\t\tgap_start: string;\n\t\tgap_end: string;\n\t\tsize: string;\n\t}>`\n SELECT gap_start, gap_end, gap_end - gap_start + 1 AS size\n FROM (\n SELECT height + 1 AS gap_start, next_height - 1 AS gap_end\n FROM (\n SELECT height, LEAD(height) OVER (ORDER BY height) AS next_height\n FROM blocks WHERE canonical = true\n ) sub\n WHERE next_height - height > 1\n ) gaps\n ORDER BY gap_start\n ${limitClause}\n `.execute(db);\n\n\treturn rows.map((r) => ({\n\t\tgapStart: Number(r.gap_start),\n\t\tgapEnd: Number(r.gap_end),\n\t\tsize: Number(r.size),\n\t}));\n}\n\nexport async function countMissingBlocks(\n\tdb: Kysely<Database>,\n): Promise<number> {\n\tconst { rows } = await sql<{ total: string }>`\n SELECT COALESCE(SUM(next_height - height - 1), 0) AS total\n FROM (\n SELECT height, LEAD(height) OVER (ORDER BY height) AS next_height\n FROM blocks WHERE canonical = true\n ) sub\n WHERE next_height - height > 1\n `.execute(db);\n\n\treturn Number(rows[0]?.total ?? 0);\n}\n\nexport interface ChainDataIntegrity {\n\tok: boolean;\n\tmaxHeight: number;\n\tsampleHeight: number | null;\n\tsampleBlocks: number;\n\treason: string | null;\n}\n\n/**\n * Cheap sanity check that the `blocks` table actually holds the history its tip\n * implies. Catches a wrong/empty Postgres volume — e.g. a container recreated\n * against a fresh volume — being silently served: if the tip is high but a\n * window of blocks well below it is missing, the data isn't what the tip claims.\n *\n * Below `checkFloor` we can't distinguish a fresh install from an empty volume,\n * so we report ok — a genuinely new DB legitimately has little history. Two\n * indexed lookups, safe to run on every startup / health poll.\n */\nexport async function checkChainDataIntegrity(\n\tdb: Kysely<Database>,\n\topts?: { checkFloor?: number; lookback?: number },\n): Promise<ChainDataIntegrity> {\n\tconst checkFloor = opts?.checkFloor ?? 1_000_000;\n\tconst lookback = opts?.lookback ?? 500_000;\n\n\tconst { rows: tipRows } = await sql<{ max: string | null }>`\n\t\tSELECT MAX(height) AS max FROM blocks WHERE canonical = true\n\t`.execute(db);\n\tconst maxHeight = Number(tipRows[0]?.max ?? 0);\n\n\tif (maxHeight < checkFloor) {\n\t\treturn {\n\t\t\tok: true,\n\t\t\tmaxHeight,\n\t\t\tsampleHeight: null,\n\t\t\tsampleBlocks: 0,\n\t\t\treason: null,\n\t\t};\n\t}\n\n\tconst sampleHeight = maxHeight - lookback;\n\tconst { rows: sampleRows } = await sql<{ n: string }>`\n\t\tSELECT count(*) AS n FROM blocks\n\t\tWHERE canonical = true AND height >= ${sampleHeight} AND height < ${sampleHeight + 1000}\n\t`.execute(db);\n\tconst sampleBlocks = Number(sampleRows[0]?.n ?? 0);\n\n\treturn {\n\t\tok: sampleBlocks > 0,\n\t\tmaxHeight,\n\t\tsampleHeight,\n\t\tsampleBlocks,\n\t\treason:\n\t\t\tsampleBlocks > 0\n\t\t\t\t? null\n\t\t\t\t: `tip is ${maxHeight} but no canonical blocks near ${sampleHeight} — wrong or empty volume?`,\n\t};\n}\n\nexport async function computeContiguousTip(\n\tdb: Kysely<Database>,\n\tfromHeight: number,\n): Promise<number> {\n\tconst { rows } = await sql<{ tip: string }>`\n SELECT COALESCE(MAX(height), ${fromHeight}) AS tip\n FROM (\n SELECT height, height - ROW_NUMBER() OVER (ORDER BY height) AS grp\n FROM blocks WHERE canonical = true AND height >= ${fromHeight}\n ) sub\n WHERE grp = (\n SELECT height - ROW_NUMBER() OVER (ORDER BY height)\n FROM blocks WHERE canonical = true AND height = ${fromHeight}\n )\n `.execute(db);\n\n\treturn Number(rows[0]?.tip ?? fromHeight);\n}\n\nexport type BrokenLink = {\n\theight: number;\n\thash: string;\n\texpectedParent: string;\n\tstoredParent: string;\n};\n\n/**\n * Canonical heights whose block does not descend from the canonical block\n * below it.\n *\n * `findGaps` asks \"is every height present?\", which a chain sitting on a losing\n * fork answers yes to. On 2026-07-30 a reorg at 8,663,166 was resolved the\n * wrong way: we adopted a block that mainnet orphaned, its true successors were\n * swept non-canonical, and later blocks were inserted canonical on top. Every\n * height existed. Nothing was \"missing\". The chain simply did not join up, and\n * no check asked that question — so we served a broken chain for seventeen\n * hours and only noticed because subgraphs stopped moving.\n *\n * Scoped to a recent window by default: an unlinked height is an active-repair\n * concern, and scanning eight million rows every cycle is not.\n */\nexport async function findBrokenLinks(\n\tdb: Kysely<Database>,\n\t/** Heights below the tip to scan; `null` scans the whole chain. */\n\topts: { window?: number | null; limit?: number } = {},\n): Promise<BrokenLink[]> {\n\tconst windowSize = opts.window === undefined ? 10_000 : opts.window;\n\tconst limitClause = opts.limit ? sql`LIMIT ${opts.limit}` : sql``;\n\tconst windowClause =\n\t\twindowSize === null\n\t\t\t? sql``\n\t\t\t: sql`AND height > (SELECT MAX(height) - ${windowSize} FROM blocks WHERE canonical = true)`;\n\tconst { rows } = await sql<{\n\t\theight: string | number;\n\t\thash: string;\n\t\texpected_parent: string;\n\t\tstored_parent: string;\n\t}>`\n\t\tWITH recent AS (\n\t\t\tSELECT height, hash, parent_hash\n\t\t\tFROM blocks\n\t\t\tWHERE canonical = true\n\t\t\t\t${windowClause}\n\t\t)\n\t\tSELECT b.height, b.hash, p.hash AS expected_parent, b.parent_hash AS stored_parent\n\t\tFROM recent b\n\t\tJOIN recent p ON p.height = b.height - 1\n\t\tWHERE b.parent_hash <> p.hash\n\t\tORDER BY b.height\n\t\t${limitClause}\n\t`.execute(db);\n\treturn rows.map((r) => ({\n\t\theight: Number(r.height),\n\t\thash: r.hash,\n\t\texpectedParent: r.expected_parent,\n\t\tstoredParent: r.stored_parent,\n\t}));\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AAAA;AASA,eAAsB,QAAQ,CAC7B,IACA,OACiB;AAAA,EACjB,MAAM,cAAc,QAAQ,YAAY,UAAU;AAAA,EAClD,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAelB;AAAA,IACF,QAAQ,EAAE;AAAA,EAEb,OAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACvB,UAAU,OAAO,EAAE,SAAS;AAAA,IAC5B,QAAQ,OAAO,EAAE,OAAO;AAAA,IACxB,MAAM,OAAO,EAAE,IAAI;AAAA,EACpB,EAAE;AAAA;AAGH,eAAsB,kBAAkB,CACvC,IACkB;AAAA,EAClB,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOpB,QAAQ,EAAE;AAAA,EAEb,OAAO,OAAO,KAAK,IAAI,SAAS,CAAC;AAAA;AAqBlC,eAAsB,uBAAuB,CAC5C,IACA,MAC8B;AAAA,EAC9B,MAAM,aAAa,MAAM,cAAc;AAAA,EACvC,MAAM,WAAW,MAAM,YAAY;AAAA,EAEnC,QAAQ,MAAM,YAAY,MAAM;AAAA;AAAA,GAE9B,QAAQ,EAAE;AAAA,EACZ,MAAM,YAAY,OAAO,QAAQ,IAAI,OAAO,CAAC;AAAA,EAE7C,IAAI,YAAY,YAAY;AAAA,IAC3B,OAAO;AAAA,MACN,IAAI;AAAA,MACJ;AAAA,MACA,cAAc;AAAA,MACd,cAAc;AAAA,MACd,QAAQ;AAAA,IACT;AAAA,EACD;AAAA,EAEA,MAAM,eAAe,YAAY;AAAA,EACjC,QAAQ,MAAM,eAAe,MAAM;AAAA;AAAA,yCAEK,6BAA6B,eAAe;AAAA,GAClF,QAAQ,EAAE;AAAA,EACZ,MAAM,eAAe,OAAO,WAAW,IAAI,KAAK,CAAC;AAAA,EAEjD,OAAO;AAAA,IACN,IAAI,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA,QACC,eAAe,IACZ,OACA,UAAU,0CAA0C;AAAA,EACzD;AAAA;AAGD,eAAsB,oBAAoB,CACzC,IACA,YACkB;AAAA,EAClB,QAAQ,SAAS,MAAM;AAAA,mCACW;AAAA;AAAA;AAAA,yDAGsB;AAAA;AAAA;AAAA;AAAA,wDAID;AAAA;AAAA,IAEpD,QAAQ,EAAE;AAAA,EAEb,OAAO,OAAO,KAAK,IAAI,OAAO,UAAU;AAAA;AAyBzC,eAAsB,eAAe,CACpC,IAEA,OAAmD,CAAC,GAC5B;AAAA,EACxB,MAAM,aAAa,KAAK,WAAW,YAAY,MAAS,KAAK;AAAA,EAC7D,MAAM,cAAc,KAAK,QAAQ,YAAY,KAAK,UAAU;AAAA,EAC5D,MAAM,eACL,eAAe,OACZ,QACA,yCAAyC;AAAA,EAC7C,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,MAUlB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOF;AAAA,GACD,QAAQ,EAAE;AAAA,EACZ,OAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACvB,QAAQ,OAAO,EAAE,MAAM;AAAA,IACvB,MAAM,EAAE;AAAA,IACR,gBAAgB,EAAE;AAAA,IAClB,cAAc,EAAE;AAAA,EACjB,EAAE;AAAA;",
|
|
8
|
+
"debugId": "1E6E1376EB78163164756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -153,7 +153,6 @@ interface SubgraphsTable {
|
|
|
153
153
|
handler_code: string | null;
|
|
154
154
|
source_code: string | null;
|
|
155
155
|
project_id: string | null;
|
|
156
|
-
visibility: Generated<string>;
|
|
157
156
|
/** Paid (wallet-ghost) deploys expire unless renewed or claimed; NULL = no expiry. */
|
|
158
157
|
expires_at: Date | null;
|
|
159
158
|
/** (event type, contract) probe pairs persisted at deploy for weight classification. */
|
|
@@ -783,6 +782,7 @@ interface Database {
|
|
|
783
782
|
coverage_segments: CoverageSegmentsTable;
|
|
784
783
|
stage_failures: StageFailuresTable;
|
|
785
784
|
archive_fetches: ArchiveFetchesTable;
|
|
785
|
+
usage_ledger: UsageLedgerTable;
|
|
786
786
|
waitlists: WaitlistsTable;
|
|
787
787
|
waitlist_signups: WaitlistSignupsTable;
|
|
788
788
|
}
|
|
@@ -827,6 +827,27 @@ interface ArchiveFetchesTable {
|
|
|
827
827
|
via_allowance: Generated<boolean>;
|
|
828
828
|
charged_at: Generated<Date>;
|
|
829
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* One metered ledger for every billable unit (archive partitions, hosted
|
|
832
|
+
* Index/Streams rows, and future hosted-stack meters). Append-only, written
|
|
833
|
+
* only by `meter()`. `idempotency_key` is UNIQUE so a retried submission
|
|
834
|
+
* no-ops instead of double-charging. `debited` is false when the balance
|
|
835
|
+
* (or a spend cap) refused the charge — the row still exists so the gap is
|
|
836
|
+
* visible, never silently dropped. `usd_micros` is negative for a Stripe
|
|
837
|
+
* top-up (`unit: "topup"`).
|
|
838
|
+
*/
|
|
839
|
+
interface UsageLedgerTable {
|
|
840
|
+
id: Generated<string>;
|
|
841
|
+
account_id: string;
|
|
842
|
+
unit: string;
|
|
843
|
+
quantity: string | number | bigint;
|
|
844
|
+
usd_micros: string | number | bigint;
|
|
845
|
+
debited: Generated<boolean>;
|
|
846
|
+
source: string;
|
|
847
|
+
idempotency_key: string;
|
|
848
|
+
occurred_at: Generated<Date>;
|
|
849
|
+
recorded_at: Generated<Date>;
|
|
850
|
+
}
|
|
830
851
|
type WebhookStatus = "active" | "paused" | "error";
|
|
831
852
|
/** Polymorphic webhook mode: `subgraph` reacts to processed table rows;
|
|
832
853
|
* `chain` reacts to raw chain events matched directly off the Index/Streams
|
|
@@ -889,6 +910,10 @@ interface WebhookOutboxTable {
|
|
|
889
910
|
failed_at: Date | null;
|
|
890
911
|
locked_by: string | null;
|
|
891
912
|
locked_until: Date | null;
|
|
913
|
+
/** Set when a reorg marks an in-flight row `dead` instead of retrying it
|
|
914
|
+
* into an orphaned fork. Null otherwise — delivery failures track their
|
|
915
|
+
* history on `webhooks.last_error`, not per-row. */
|
|
916
|
+
last_error: string | null;
|
|
892
917
|
created_at: Generated<Date>;
|
|
893
918
|
}
|
|
894
919
|
interface WebhookDeliveriesTable {
|
|
@@ -986,4 +1011,4 @@ declare function countSubgraphMissingBlocks(db: Kysely<Database>, subgraphName:
|
|
|
986
1011
|
* Used by the /status endpoint for per-subgraph gap summary.
|
|
987
1012
|
*/
|
|
988
1013
|
declare function getGapSummaryBySubgraph(db: Kysely<Database>): Promise<GapSummary[]>;
|
|
989
|
-
export {
|
|
1014
|
+
export { GapRange, GapSummary, SubgraphGapRow, countSubgraphMissingBlocks, findSubgraphGaps, getGapSummaryBySubgraph, recordGapBatch, resolveGaps };
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
|
|
17
1
|
// src/db/queries/subgraph-gaps.ts
|
|
18
2
|
import { sql } from "kysely";
|
|
19
3
|
async function recordGapBatch(db, subgraphId, subgraphName, gaps) {
|
|
@@ -92,12 +76,12 @@ async function getGapSummaryBySubgraph(db) {
|
|
|
92
76
|
}));
|
|
93
77
|
}
|
|
94
78
|
export {
|
|
95
|
-
|
|
96
|
-
recordGapBatch,
|
|
97
|
-
getGapSummaryBySubgraph,
|
|
79
|
+
countSubgraphMissingBlocks,
|
|
98
80
|
findSubgraphGaps,
|
|
99
|
-
|
|
81
|
+
getGapSummaryBySubgraph,
|
|
82
|
+
recordGapBatch,
|
|
83
|
+
resolveGaps
|
|
100
84
|
};
|
|
101
85
|
|
|
102
|
-
//# debugId=
|
|
86
|
+
//# debugId=3220DE92B2B7A0A764756E2164756E21
|
|
103
87
|
//# sourceMappingURL=subgraph-gaps.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { type Kysely, sql } from \"kysely\";\nimport type { Database } from \"../types.ts\";\n\nexport interface GapRange {\n\tstart: number;\n\tend: number;\n\treason: string;\n}\n\nexport interface SubgraphGapRow {\n\tid: string;\n\tsubgraphName: string;\n\tgapStart: number;\n\tgapEnd: number;\n\tsize: number;\n\treason: string;\n\tdetectedAt: Date;\n\tresolvedAt: Date | null;\n}\n\nexport interface GapSummary {\n\tsubgraphName: string;\n\tgapCount: number;\n\ttotalMissingBlocks: number;\n}\n\n/**\n * Batch-insert gap ranges for a subgraph. Append-only — no merge on write.\n * Callers should coalesce contiguous skipped blocks into ranges before calling.\n */\nexport async function recordGapBatch(\n\tdb: Kysely<Database>,\n\tsubgraphId: string,\n\tsubgraphName: string,\n\tgaps: GapRange[],\n): Promise<void> {\n\tif (gaps.length === 0) return;\n\n\tawait db\n\t\t.insertInto(\"subgraph_gaps\")\n\t\t.values(\n\t\t\tgaps.map((g) => ({\n\t\t\t\tsubgraph_id: subgraphId,\n\t\t\t\tsubgraph_name: subgraphName,\n\t\t\t\tgap_start: g.start,\n\t\t\t\tgap_end: g.end,\n\t\t\t\treason: g.reason,\n\t\t\t})),\n\t\t)\n\t\t.execute();\n}\n\n/**\n * List gaps for a subgraph with computed size.\n */\nexport async function findSubgraphGaps(\n\tdb: Kysely<Database>,\n\tsubgraphName: string,\n\topts?: { limit?: number; offset?: number; unresolvedOnly?: boolean },\n): Promise<{ gaps: SubgraphGapRow[]; total: number }> {\n\tconst limit = opts?.limit ?? 50;\n\tconst offset = opts?.offset ?? 0;\n\tconst unresolvedOnly = opts?.unresolvedOnly ?? true;\n\n\tlet baseQuery = db\n\t\t.selectFrom(\"subgraph_gaps\")\n\t\t.where(\"subgraph_name\", \"=\", subgraphName);\n\n\tif (unresolvedOnly) {\n\t\tbaseQuery = baseQuery.where(\"resolved_at\", \"is\", null);\n\t}\n\n\tconst [rows, countResult] = await Promise.all([\n\t\tbaseQuery\n\t\t\t.select([\n\t\t\t\t\"id\",\n\t\t\t\t\"subgraph_name\",\n\t\t\t\t\"gap_start\",\n\t\t\t\t\"gap_end\",\n\t\t\t\tsql<number>`gap_end - gap_start + 1`.as(\"size\"),\n\t\t\t\t\"reason\",\n\t\t\t\t\"detected_at\",\n\t\t\t\t\"resolved_at\",\n\t\t\t])\n\t\t\t.orderBy(\"gap_start\", \"asc\")\n\t\t\t.limit(limit)\n\t\t\t.offset(offset)\n\t\t\t.execute(),\n\t\tbaseQuery\n\t\t\t.select(sql<number>`count(*)`.as(\"count\"))\n\t\t\t.executeTakeFirstOrThrow(),\n\t]);\n\n\treturn {\n\t\tgaps: rows.map((r) => ({\n\t\t\tid: r.id,\n\t\t\tsubgraphName: r.subgraph_name,\n\t\t\tgapStart: Number(r.gap_start),\n\t\t\tgapEnd: Number(r.gap_end),\n\t\t\tsize: Number(r.size),\n\t\t\treason: r.reason,\n\t\t\tdetectedAt: r.detected_at,\n\t\t\tresolvedAt: r.resolved_at,\n\t\t})),\n\t\ttotal: Number(countResult.count),\n\t};\n}\n\n/**\n * Mark gaps as resolved within a block range. Idempotent.\n * Only resolves gaps fully contained within [fromBlock, toBlock].\n */\nexport async function resolveGaps(\n\tdb: Kysely<Database>,\n\tsubgraphName: string,\n\tfromBlock: number,\n\ttoBlock: number,\n): Promise<number> {\n\tconst result = await db\n\t\t.updateTable(\"subgraph_gaps\")\n\t\t.set({ resolved_at: new Date() })\n\t\t.where(\"subgraph_name\", \"=\", subgraphName)\n\t\t.where(\"resolved_at\", \"is\", null)\n\t\t.where(\"gap_start\", \">=\", fromBlock)\n\t\t.where(\"gap_end\", \"<=\", toBlock)\n\t\t.executeTakeFirst();\n\n\treturn Number(result.numUpdatedRows);\n}\n\n/**\n * Total missing blocks across unresolved gaps for a subgraph.\n */\nexport async function countSubgraphMissingBlocks(\n\tdb: Kysely<Database>,\n\tsubgraphName: string,\n): Promise<number> {\n\tconst { rows } = await sql<{ total: string }>`\n SELECT COALESCE(SUM(gap_end - gap_start + 1), 0) AS total\n FROM subgraph_gaps\n WHERE subgraph_name = ${subgraphName}\n AND resolved_at IS NULL\n `.execute(db);\n\n\treturn Number(rows[0]?.total ?? 0);\n}\n\n/**\n * Aggregate gap counts + missing blocks grouped by subgraph_name.\n * Used by the /status endpoint for per-subgraph gap summary.\n */\nexport async function getGapSummaryBySubgraph(\n\tdb: Kysely<Database>,\n): Promise<GapSummary[]> {\n\tconst { rows } = await sql<{\n\t\tsubgraph_name: string;\n\t\tgap_count: string;\n\t\ttotal_missing: string;\n\t}>`\n SELECT\n subgraph_name,\n COUNT(*) AS gap_count,\n COALESCE(SUM(gap_end - gap_start + 1), 0) AS total_missing\n FROM subgraph_gaps\n WHERE resolved_at IS NULL\n GROUP BY subgraph_name\n `.execute(db);\n\n\treturn rows.map((r) => ({\n\t\tsubgraphName: r.subgraph_name,\n\t\tgapCount: Number(r.gap_count),\n\t\ttotalMissingBlocks: Number(r.total_missing),\n\t}));\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AAAA;AA8BA,eAAsB,cAAc,CACnC,IACA,YACA,cACA,MACgB;AAAA,EAChB,IAAI,KAAK,WAAW;AAAA,IAAG;AAAA,EAEvB,MAAM,GACJ,WAAW,eAAe,EAC1B,OACA,KAAK,IAAI,CAAC,OAAO;AAAA,IAChB,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,EACX,EAAE,CACH,EACC,QAAQ;AAAA;AAMX,eAAsB,gBAAgB,CACrC,IACA,cACA,MACqD;AAAA,EACrD,MAAM,QAAQ,MAAM,SAAS;AAAA,EAC7B,MAAM,SAAS,MAAM,UAAU;AAAA,EAC/B,MAAM,iBAAiB,MAAM,kBAAkB;AAAA,EAE/C,IAAI,YAAY,GACd,WAAW,eAAe,EAC1B,MAAM,iBAAiB,KAAK,YAAY;AAAA,EAE1C,IAAI,gBAAgB;AAAA,IACnB,YAAY,UAAU,MAAM,eAAe,MAAM,IAAI;AAAA,EACtD;AAAA,EAEA,OAAO,MAAM,eAAe,MAAM,QAAQ,IAAI;AAAA,IAC7C,UACE,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,6BAAqC,GAAG,MAAM;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC,EACA,QAAQ,aAAa,KAAK,EAC1B,MAAM,KAAK,EACX,OAAO,MAAM,EACb,QAAQ;AAAA,IACV,UACE,OAAO,cAAsB,GAAG,OAAO,CAAC,EACxC,wBAAwB;AAAA,EAC3B,CAAC;AAAA,EAED,OAAO;AAAA,IACN,MAAM,KAAK,IAAI,CAAC,OAAO;AAAA,MACtB,IAAI,EAAE;AAAA,MACN,cAAc,EAAE;AAAA,MAChB,UAAU,OAAO,EAAE,SAAS;AAAA,MAC5B,QAAQ,OAAO,EAAE,OAAO;AAAA,MACxB,MAAM,OAAO,EAAE,IAAI;AAAA,MACnB,QAAQ,EAAE;AAAA,MACV,YAAY,EAAE;AAAA,MACd,YAAY,EAAE;AAAA,IACf,EAAE;AAAA,IACF,OAAO,OAAO,YAAY,KAAK;AAAA,EAChC;AAAA;AAOD,eAAsB,WAAW,CAChC,IACA,cACA,WACA,SACkB;AAAA,EAClB,MAAM,SAAS,MAAM,GACnB,YAAY,eAAe,EAC3B,IAAI,EAAE,aAAa,IAAI,KAAO,CAAC,EAC/B,MAAM,iBAAiB,KAAK,YAAY,EACxC,MAAM,eAAe,MAAM,IAAI,EAC/B,MAAM,aAAa,MAAM,SAAS,EAClC,MAAM,WAAW,MAAM,OAAO,EAC9B,iBAAiB;AAAA,EAEnB,OAAO,OAAO,OAAO,cAAc;AAAA;AAMpC,eAAsB,0BAA0B,CAC/C,IACA,cACkB;AAAA,EAClB,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA,4BAGI;AAAA;AAAA,IAExB,QAAQ,EAAE;AAAA,EAEb,OAAO,OAAO,KAAK,IAAI,SAAS,CAAC;AAAA;AAOlC,eAAsB,uBAAuB,CAC5C,IACwB;AAAA,EACxB,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAYpB,QAAQ,EAAE;AAAA,EAEb,OAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACvB,cAAc,EAAE;AAAA,IAChB,UAAU,OAAO,EAAE,SAAS;AAAA,IAC5B,oBAAoB,OAAO,EAAE,aAAa;AAAA,EAC3C,EAAE;AAAA;",
|
|
8
|
+
"debugId": "3220DE92B2B7A0A764756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -153,7 +153,6 @@ interface SubgraphsTable {
|
|
|
153
153
|
handler_code: string | null;
|
|
154
154
|
source_code: string | null;
|
|
155
155
|
project_id: string | null;
|
|
156
|
-
visibility: Generated<string>;
|
|
157
156
|
/** Paid (wallet-ghost) deploys expire unless renewed or claimed; NULL = no expiry. */
|
|
158
157
|
expires_at: Date | null;
|
|
159
158
|
/** (event type, contract) probe pairs persisted at deploy for weight classification. */
|
|
@@ -783,6 +782,7 @@ interface Database {
|
|
|
783
782
|
coverage_segments: CoverageSegmentsTable;
|
|
784
783
|
stage_failures: StageFailuresTable;
|
|
785
784
|
archive_fetches: ArchiveFetchesTable;
|
|
785
|
+
usage_ledger: UsageLedgerTable;
|
|
786
786
|
waitlists: WaitlistsTable;
|
|
787
787
|
waitlist_signups: WaitlistSignupsTable;
|
|
788
788
|
}
|
|
@@ -827,6 +827,27 @@ interface ArchiveFetchesTable {
|
|
|
827
827
|
via_allowance: Generated<boolean>;
|
|
828
828
|
charged_at: Generated<Date>;
|
|
829
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* One metered ledger for every billable unit (archive partitions, hosted
|
|
832
|
+
* Index/Streams rows, and future hosted-stack meters). Append-only, written
|
|
833
|
+
* only by `meter()`. `idempotency_key` is UNIQUE so a retried submission
|
|
834
|
+
* no-ops instead of double-charging. `debited` is false when the balance
|
|
835
|
+
* (or a spend cap) refused the charge — the row still exists so the gap is
|
|
836
|
+
* visible, never silently dropped. `usd_micros` is negative for a Stripe
|
|
837
|
+
* top-up (`unit: "topup"`).
|
|
838
|
+
*/
|
|
839
|
+
interface UsageLedgerTable {
|
|
840
|
+
id: Generated<string>;
|
|
841
|
+
account_id: string;
|
|
842
|
+
unit: string;
|
|
843
|
+
quantity: string | number | bigint;
|
|
844
|
+
usd_micros: string | number | bigint;
|
|
845
|
+
debited: Generated<boolean>;
|
|
846
|
+
source: string;
|
|
847
|
+
idempotency_key: string;
|
|
848
|
+
occurred_at: Generated<Date>;
|
|
849
|
+
recorded_at: Generated<Date>;
|
|
850
|
+
}
|
|
830
851
|
type SubgraphOperation = Selectable<SubgraphOperationsTable>;
|
|
831
852
|
type WebhookStatus = "active" | "paused" | "error";
|
|
832
853
|
/** Polymorphic webhook mode: `subgraph` reacts to processed table rows;
|
|
@@ -890,6 +911,10 @@ interface WebhookOutboxTable {
|
|
|
890
911
|
failed_at: Date | null;
|
|
891
912
|
locked_by: string | null;
|
|
892
913
|
locked_until: Date | null;
|
|
914
|
+
/** Set when a reorg marks an in-flight row `dead` instead of retrying it
|
|
915
|
+
* into an orphaned fork. Null otherwise — delivery failures track their
|
|
916
|
+
* history on `webhooks.last_error`, not per-row. */
|
|
917
|
+
last_error: string | null;
|
|
893
918
|
created_at: Generated<Date>;
|
|
894
919
|
}
|
|
895
920
|
interface WebhookDeliveriesTable {
|
|
@@ -986,4 +1011,4 @@ declare function updateOperationProcessedEvents(db: Kysely<Database>, operationI
|
|
|
986
1011
|
* whether THIS caller advanced (false = a racing writer already covered h).
|
|
987
1012
|
*/
|
|
988
1013
|
declare function advanceOperationCursor(db: Kysely<Database>, operationId: string, height: number): Promise<boolean>;
|
|
989
|
-
export {
|
|
1014
|
+
export { advanceOperationCursor, cancelSubgraphOperation, claimSubgraphOperation, completeSubgraphOperation, createSubgraphOperation, failSubgraphOperation, findActiveSubgraphOperation, getOperationQueuePosition, getRecentOperationMedianDuration, getSubgraphOperation, heartbeatSubgraphOperation, isActiveSubgraphOperationConflict, listSubgraphOperations, requestSubgraphOperationCancel, requestSubgraphOperationsCancelForDelete, updateOperationProcessedEvents, waitForSubgraphOperationsClear };
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
|
|
17
1
|
// src/db/queries/subgraph-operations.ts
|
|
18
2
|
import { sql } from "kysely";
|
|
19
3
|
var ACTIVE_STATUSES = ["queued", "running"];
|
|
@@ -222,24 +206,24 @@ async function advanceOperationCursor(db, operationId, height) {
|
|
|
222
206
|
return Number(result.numUpdatedRows ?? 0n) > 0;
|
|
223
207
|
}
|
|
224
208
|
export {
|
|
225
|
-
|
|
226
|
-
updateOperationProcessedEvents,
|
|
227
|
-
requestSubgraphOperationsCancelForDelete,
|
|
228
|
-
requestSubgraphOperationCancel,
|
|
229
|
-
listSubgraphOperations,
|
|
230
|
-
isActiveSubgraphOperationConflict,
|
|
231
|
-
heartbeatSubgraphOperation,
|
|
232
|
-
getSubgraphOperation,
|
|
233
|
-
getRecentOperationMedianDuration,
|
|
234
|
-
getOperationQueuePosition,
|
|
235
|
-
findActiveSubgraphOperation,
|
|
236
|
-
failSubgraphOperation,
|
|
237
|
-
createSubgraphOperation,
|
|
238
|
-
completeSubgraphOperation,
|
|
239
|
-
claimSubgraphOperation,
|
|
209
|
+
advanceOperationCursor,
|
|
240
210
|
cancelSubgraphOperation,
|
|
241
|
-
|
|
211
|
+
claimSubgraphOperation,
|
|
212
|
+
completeSubgraphOperation,
|
|
213
|
+
createSubgraphOperation,
|
|
214
|
+
failSubgraphOperation,
|
|
215
|
+
findActiveSubgraphOperation,
|
|
216
|
+
getOperationQueuePosition,
|
|
217
|
+
getRecentOperationMedianDuration,
|
|
218
|
+
getSubgraphOperation,
|
|
219
|
+
heartbeatSubgraphOperation,
|
|
220
|
+
isActiveSubgraphOperationConflict,
|
|
221
|
+
listSubgraphOperations,
|
|
222
|
+
requestSubgraphOperationCancel,
|
|
223
|
+
requestSubgraphOperationsCancelForDelete,
|
|
224
|
+
updateOperationProcessedEvents,
|
|
225
|
+
waitForSubgraphOperationsClear
|
|
242
226
|
};
|
|
243
227
|
|
|
244
|
-
//# debugId=
|
|
228
|
+
//# debugId=82AB266EA49F95E564756E2164756E21
|
|
245
229
|
//# sourceMappingURL=subgraph-operations.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { type Kysely, sql } from \"kysely\";\nimport type {\n\tDatabase,\n\tSubgraphOperation,\n\tSubgraphOperationKind,\n\tSubgraphOperationStatus,\n} from \"../types.ts\";\n\nconst ACTIVE_STATUSES: SubgraphOperationStatus[] = [\"queued\", \"running\"];\n\nexport function isActiveSubgraphOperationConflict(err: unknown): boolean {\n\tif (!(err instanceof Error)) return false;\n\tconst candidate = err as Error & {\n\t\tcode?: string;\n\t\tconstraint?: string;\n\t\tconstraint_name?: string;\n\t};\n\treturn (\n\t\tcandidate.code === \"23505\" &&\n\t\t(candidate.constraint === \"subgraph_operations_active_unique\" ||\n\t\t\tcandidate.constraint_name === \"subgraph_operations_active_unique\")\n\t);\n}\n\nexport async function createSubgraphOperation(\n\tdb: Kysely<Database>,\n\tdata: {\n\t\tsubgraphId: string;\n\t\tsubgraphName: string;\n\t\taccountId?: string | null;\n\t\tkind: SubgraphOperationKind;\n\t\tfromBlock?: number;\n\t\ttoBlock?: number;\n\t\t/** 'light' | 'heavy' — claim budgets heavy ops. DB default 'heavy'. */\n\t\tweight?: \"light\" | \"heavy\";\n\t\testimatedEvents?: number | null;\n\t},\n): Promise<SubgraphOperation> {\n\t// Requeued backfills inherit the committed prefix of prior attempts over an\n\t// overlapping range — cancel/fail + requeue can't replay applied deltas.\n\tlet seededCursor: string | number | bigint | null = null;\n\tif (\n\t\tdata.kind === \"backfill\" &&\n\t\tdata.fromBlock != null &&\n\t\tdata.toBlock != null\n\t) {\n\t\tconst prior = await db\n\t\t\t.selectFrom(\"subgraph_operations\")\n\t\t\t.select((eb) => eb.fn.max(\"cursor_block\").as(\"max_cursor\"))\n\t\t\t.where(\"subgraph_id\", \"=\", data.subgraphId)\n\t\t\t.where(\"kind\", \"=\", \"backfill\")\n\t\t\t.where(\"status\", \"in\", [\"failed\", \"cancelled\"])\n\t\t\t.where(\"from_block\", \"<=\", data.toBlock)\n\t\t\t.where(\"to_block\", \">=\", data.fromBlock)\n\t\t\t.executeTakeFirst();\n\t\tseededCursor = prior?.max_cursor ?? null;\n\t}\n\treturn await db\n\t\t.insertInto(\"subgraph_operations\")\n\t\t.values({\n\t\t\tsubgraph_id: data.subgraphId,\n\t\t\tsubgraph_name: data.subgraphName,\n\t\t\taccount_id: data.accountId ?? null,\n\t\t\tkind: data.kind,\n\t\t\tfrom_block: data.fromBlock ?? null,\n\t\t\tto_block: data.toBlock ?? null,\n\t\t\t...(data.weight ? { weight: data.weight } : {}),\n\t\t\testimated_events: data.estimatedEvents ?? null,\n\t\t\tcursor_block: seededCursor,\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function findActiveSubgraphOperation(\n\tdb: Kysely<Database>,\n\tsubgraphId: string,\n): Promise<SubgraphOperation | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"subgraph_operations\")\n\t\t\t.selectAll()\n\t\t\t.where(\"subgraph_id\", \"=\", subgraphId)\n\t\t\t.where(\"status\", \"in\", ACTIVE_STATUSES)\n\t\t\t.orderBy(\"created_at\", \"asc\")\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function requestSubgraphOperationCancel(\n\tdb: Kysely<Database>,\n\tsubgraphId: string,\n): Promise<SubgraphOperation | null> {\n\treturn (\n\t\t(await db\n\t\t\t.updateTable(\"subgraph_operations\")\n\t\t\t.set({ cancel_requested: true, updated_at: new Date() })\n\t\t\t.where(\"subgraph_id\", \"=\", subgraphId)\n\t\t\t.where(\"status\", \"in\", ACTIVE_STATUSES)\n\t\t\t.returningAll()\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function requestSubgraphOperationsCancelForDelete(\n\tdb: Kysely<Database>,\n\tsubgraphId: string,\n): Promise<SubgraphOperation[]> {\n\treturn await db\n\t\t.updateTable(\"subgraph_operations\")\n\t\t.set({ cancel_requested: true, updated_at: new Date() })\n\t\t.where(\"subgraph_id\", \"=\", subgraphId)\n\t\t.where(\"status\", \"in\", ACTIVE_STATUSES)\n\t\t.returningAll()\n\t\t.execute();\n}\n\n/**\n * Poll until no active subgraph operations remain for the subgraph or until\n * `timeoutMs` elapses. Returns true if all active operations cleared, false\n * if we timed out. Callers should use this before `DROP SCHEMA` so the active\n * processor has a chance to observe `cancel_requested` and release its row /\n * advisory locks. Without this, the DROP blocks behind the live transaction\n * and the API socket times out before the lock releases.\n */\nexport async function waitForSubgraphOperationsClear(\n\tdb: Kysely<Database>,\n\tsubgraphId: string,\n\topts?: { timeoutMs?: number; pollMs?: number },\n): Promise<boolean> {\n\tconst timeoutMs = opts?.timeoutMs ?? 30_000;\n\tconst pollMs = opts?.pollMs ?? 500;\n\tconst deadline = Date.now() + timeoutMs;\n\twhile (Date.now() < deadline) {\n\t\tconst active = await db\n\t\t\t.selectFrom(\"subgraph_operations\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"subgraph_id\", \"=\", subgraphId)\n\t\t\t.where(\"status\", \"in\", ACTIVE_STATUSES)\n\t\t\t.limit(1)\n\t\t\t.executeTakeFirst();\n\t\tif (!active) return true;\n\t\tawait new Promise((r) => setTimeout(r, pollMs));\n\t}\n\treturn false;\n}\n\n/** Max concurrently-running 'heavy' ops (broad/non-sparse syncs). */\nfunction resolveHeavyOpBudget(): number {\n\tconst parsed = Number.parseInt(\n\t\tprocess.env.SUBGRAPH_HEAVY_OP_BUDGET ?? \"2\",\n\t\t10,\n\t);\n\treturn Number.isFinite(parsed) && parsed > 0 ? parsed : 2;\n}\n\nexport async function claimSubgraphOperation(\n\tdb: Kysely<Database>,\n\tlockedBy: string,\n): Promise<SubgraphOperation | null> {\n\t// Fair queue: accounts with fewer currently-running operations are served first,\n\t// breaking ties by creation time. Prevents one user's long reindex from\n\t// starving other accounts when SUBGRAPH_OPERATION_CONCURRENCY > 1.\n\tconst heavyBudget = resolveHeavyOpBudget();\n\tconst result = await sql<SubgraphOperation>`\n\t\tUPDATE subgraph_operations\n\t\tSET\n\t\t\tstatus = 'running',\n\t\t\tlocked_by = ${lockedBy},\n\t\t\tlocked_until = now() + interval '60 seconds',\n\t\t\tstarted_at = COALESCE(started_at, now()),\n\t\t\tupdated_at = now()\n\t\tWHERE id = (\n\t\t\tSELECT so.id\n\t\t\tFROM subgraph_operations so\n\t\t\tLEFT JOIN (\n\t\t\t\tSELECT account_id, COUNT(*) AS cnt\n\t\t\t\tFROM subgraph_operations\n\t\t\t\tWHERE status = 'running'\n\t\t\t\tGROUP BY account_id\n\t\t\t) rc ON so.account_id = rc.account_id\n\t\t\tWHERE\n\t\t\t\t(\n\t\t\t\t\tso.status = 'queued'\n\t\t\t\t\tOR (\n\t\t\t\t\t\tso.status = 'running'\n\t\t\t\t\t\tAND (so.locked_until IS NULL OR so.locked_until < now())\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t\t-- Heavy budget as an eligibility FILTER (not a post-claim refusal):\n\t\t\t\t-- a budget-blocked heavy op at the head must not starve the light\n\t\t\t\t-- ops behind it. Live-lock condition (locked_until > now()) keeps a\n\t\t\t\t-- STALE heavy op from blocking its own reclaim. Soft across\n\t\t\t\t-- concurrent claimers (can overshoot by one with multiple runners) —\n\t\t\t\t-- acceptable for the single-runner deployment.\n\t\t\t\tAND (\n\t\t\t\t\tso.weight = 'light'\n\t\t\t\t\tOR (\n\t\t\t\t\t\tSELECT COUNT(*)\n\t\t\t\t\t\tFROM subgraph_operations h\n\t\t\t\t\t\tWHERE h.status = 'running'\n\t\t\t\t\t\t\tAND h.weight = 'heavy'\n\t\t\t\t\t\t\tAND h.locked_until > now()\n\t\t\t\t\t\t\tAND h.id != so.id\n\t\t\t\t\t) < ${heavyBudget}\n\t\t\t\t)\n\t\t\tORDER BY\n\t\t\t\tCOALESCE(rc.cnt, 0) ASC,\n\t\t\t\tCASE WHEN so.status = 'queued' THEN 0 ELSE 1 END,\n\t\t\t\tso.created_at ASC\n\t\t\tFOR UPDATE OF so SKIP LOCKED\n\t\t\tLIMIT 1\n\t\t)\n\t\tRETURNING *\n\t`.execute(db);\n\treturn result.rows[0] ?? null;\n}\n\nexport async function heartbeatSubgraphOperation(\n\tdb: Kysely<Database>,\n\toperationId: string,\n\tlockedBy: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraph_operations\")\n\t\t.set({\n\t\t\tlocked_until: sql<Date>`now() + interval '60 seconds'`,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"id\", \"=\", operationId)\n\t\t.where(\"status\", \"=\", \"running\")\n\t\t.where(\"locked_by\", \"=\", lockedBy)\n\t\t.execute();\n}\n\nexport async function getSubgraphOperation(\n\tdb: Kysely<Database>,\n\toperationId: string,\n): Promise<SubgraphOperation | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"subgraph_operations\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", operationId)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\n/** Recent operations for a subgraph, newest first (for the status read API). */\nexport async function listSubgraphOperations(\n\tdb: Kysely<Database>,\n\tsubgraphId: string,\n\tlimit = 20,\n): Promise<SubgraphOperation[]> {\n\treturn db\n\t\t.selectFrom(\"subgraph_operations\")\n\t\t.selectAll()\n\t\t.where(\"subgraph_id\", \"=\", subgraphId)\n\t\t.orderBy(\"created_at\", \"desc\")\n\t\t.limit(limit)\n\t\t.execute();\n}\n\nexport async function completeSubgraphOperation(\n\tdb: Kysely<Database>,\n\toperationId: string,\n\tlockedBy: string,\n\tprocessedBlocks: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraph_operations\")\n\t\t.set({\n\t\t\tstatus: \"completed\",\n\t\t\tfinished_at: new Date(),\n\t\t\tprocessed_blocks: processedBlocks,\n\t\t\tlocked_by: null,\n\t\t\tlocked_until: null,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"id\", \"=\", operationId)\n\t\t.where(\"locked_by\", \"=\", lockedBy)\n\t\t.execute();\n}\n\nexport async function cancelSubgraphOperation(\n\tdb: Kysely<Database>,\n\toperationId: string,\n\tlockedBy: string,\n\tprocessedBlocks: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraph_operations\")\n\t\t.set({\n\t\t\tstatus: \"cancelled\",\n\t\t\tfinished_at: new Date(),\n\t\t\tprocessed_blocks: processedBlocks,\n\t\t\tlocked_by: null,\n\t\t\tlocked_until: null,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"id\", \"=\", operationId)\n\t\t.where(\"locked_by\", \"=\", lockedBy)\n\t\t.execute();\n}\n\nexport async function failSubgraphOperation(\n\tdb: Kysely<Database>,\n\toperationId: string,\n\tlockedBy: string,\n\terror: string,\n\tprocessedBlocks?: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraph_operations\")\n\t\t.set({\n\t\t\tstatus: \"failed\",\n\t\t\tfinished_at: new Date(),\n\t\t\tprocessed_blocks: processedBlocks ?? null,\n\t\t\terror,\n\t\t\tlocked_by: null,\n\t\t\tlocked_until: null,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"id\", \"=\", operationId)\n\t\t.where(\"locked_by\", \"=\", lockedBy)\n\t\t.execute();\n}\n\n/**\n * 1-based position of a queued operation under the claim ordering (fairness →\n * queued-first → FIFO). The heavy-budget admission filter is NOT applied — a\n * heavy op's eligibility depends on runtime budget state — so the position is\n * approximate; render it as \"~N\". Returns null unless queued.\n */\nexport async function getOperationQueuePosition(\n\tdb: Kysely<Database>,\n\toperationId: string,\n): Promise<number | null> {\n\tconst result = await sql<{ rn: string | number }>`\n\t\tWITH candidates AS (\n\t\t\tSELECT\n\t\t\t\tso.id,\n\t\t\t\tROW_NUMBER() OVER (\n\t\t\t\t\tORDER BY\n\t\t\t\t\t\tCOALESCE(rc.cnt, 0) ASC,\n\t\t\t\t\t\tCASE WHEN so.status = 'queued' THEN 0 ELSE 1 END,\n\t\t\t\t\t\tso.created_at ASC\n\t\t\t\t) AS rn\n\t\t\tFROM subgraph_operations so\n\t\t\tLEFT JOIN (\n\t\t\t\tSELECT account_id, COUNT(*) AS cnt\n\t\t\t\tFROM subgraph_operations\n\t\t\t\tWHERE status = 'running'\n\t\t\t\tGROUP BY account_id\n\t\t\t) rc ON so.account_id = rc.account_id\n\t\t\tWHERE so.status = 'queued'\n\t\t)\n\t\tSELECT rn FROM candidates WHERE id = ${operationId}\n\t`.execute(db);\n\tconst rn = result.rows[0]?.rn;\n\treturn rn == null ? null : Number(rn);\n}\n\n/** Median duration (seconds) of the last 20 completed ops of a weight class —\n * the \"est. start\" multiplier for queued positions. Null with no history. */\nexport async function getRecentOperationMedianDuration(\n\tdb: Kysely<Database>,\n\tweight: \"light\" | \"heavy\",\n): Promise<number | null> {\n\tconst result = await sql<{ median: string | number | null }>`\n\t\tSELECT percentile_cont(0.5) WITHIN GROUP (\n\t\t\tORDER BY EXTRACT(EPOCH FROM (finished_at - started_at))\n\t\t) AS median\n\t\tFROM (\n\t\t\tSELECT started_at, finished_at\n\t\t\tFROM subgraph_operations\n\t\t\tWHERE status = 'completed'\n\t\t\t\tAND weight = ${weight}\n\t\t\t\tAND started_at IS NOT NULL\n\t\t\t\tAND finished_at IS NOT NULL\n\t\t\tORDER BY finished_at DESC\n\t\t\tLIMIT 20\n\t\t) recent\n\t`.execute(db);\n\tconst median = result.rows[0]?.median;\n\treturn median == null ? null : Number(median);\n}\n\n/** Progress-flush hook: events processed so far on a running operation. */\nexport async function updateOperationProcessedEvents(\n\tdb: Kysely<Database>,\n\toperationId: string,\n\tprocessedEvents: number,\n): Promise<void> {\n\t// Monotonic: a crash-resumed run restarts its in-memory counter from 0 —\n\t// never let that regress the op's ETA surfaces.\n\tawait db\n\t\t.updateTable(\"subgraph_operations\")\n\t\t.set({\n\t\t\tprocessed_events: sql`GREATEST(COALESCE(processed_events, 0), ${processedEvents})`,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"id\", \"=\", operationId)\n\t\t.execute();\n}\n\n/**\n * Conditionally advance a backfill operation's crash checkpoint. Monotonic by\n * construction: the WHERE clause makes concurrent writers serialize — exactly\n * one advance wins per height, regardless of lease/zombie timing. Returns\n * whether THIS caller advanced (false = a racing writer already covered h).\n */\nexport async function advanceOperationCursor(\n\tdb: Kysely<Database>,\n\toperationId: string,\n\theight: number,\n): Promise<boolean> {\n\tconst result = await db\n\t\t.updateTable(\"subgraph_operations\")\n\t\t.set({ cursor_block: height, updated_at: new Date() })\n\t\t.where(\"id\", \"=\", operationId)\n\t\t.where((eb) =>\n\t\t\teb.or([\n\t\t\t\teb(\"cursor_block\", \"is\", null),\n\t\t\t\teb(\"cursor_block\", \"<\", String(height)),\n\t\t\t]),\n\t\t)\n\t\t.executeTakeFirst();\n\treturn Number(result.numUpdatedRows ?? 0n) > 0;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AAAA;AAQA,IAAM,kBAA6C,CAAC,UAAU,SAAS;AAEhE,SAAS,iCAAiC,CAAC,KAAuB;AAAA,EACxE,IAAI,EAAE,eAAe;AAAA,IAAQ,OAAO;AAAA,EACpC,MAAM,YAAY;AAAA,EAKlB,OACC,UAAU,SAAS,YAClB,UAAU,eAAe,uCACzB,UAAU,oBAAoB;AAAA;AAIjC,eAAsB,uBAAuB,CAC5C,IACA,MAW6B;AAAA,EAG7B,IAAI,eAAgD;AAAA,EACpD,IACC,KAAK,SAAS,cACd,KAAK,aAAa,QAClB,KAAK,WAAW,MACf;AAAA,IACD,MAAM,QAAQ,MAAM,GAClB,WAAW,qBAAqB,EAChC,OAAO,CAAC,OAAO,GAAG,GAAG,IAAI,cAAc,EAAE,GAAG,YAAY,CAAC,EACzD,MAAM,eAAe,KAAK,KAAK,UAAU,EACzC,MAAM,QAAQ,KAAK,UAAU,EAC7B,MAAM,UAAU,MAAM,CAAC,UAAU,WAAW,CAAC,EAC7C,MAAM,cAAc,MAAM,KAAK,OAAO,EACtC,MAAM,YAAY,MAAM,KAAK,SAAS,EACtC,iBAAiB;AAAA,IACnB,eAAe,OAAO,cAAc;AAAA,EACrC;AAAA,EACA,OAAO,MAAM,GACX,WAAW,qBAAqB,EAChC,OAAO;AAAA,IACP,aAAa,KAAK;AAAA,IAClB,eAAe,KAAK;AAAA,IACpB,YAAY,KAAK,aAAa;AAAA,IAC9B,MAAM,KAAK;AAAA,IACX,YAAY,KAAK,aAAa;AAAA,IAC9B,UAAU,KAAK,WAAW;AAAA,OACtB,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,IAC7C,kBAAkB,KAAK,mBAAmB;AAAA,IAC1C,cAAc;AAAA,EACf,CAAC,EACA,aAAa,EACb,wBAAwB;AAAA;AAG3B,eAAsB,2BAA2B,CAChD,IACA,YACoC;AAAA,EACpC,OACE,MAAM,GACL,WAAW,qBAAqB,EAChC,UAAU,EACV,MAAM,eAAe,KAAK,UAAU,EACpC,MAAM,UAAU,MAAM,eAAe,EACrC,QAAQ,cAAc,KAAK,EAC3B,iBAAiB,KAAM;AAAA;AAI3B,eAAsB,8BAA8B,CACnD,IACA,YACoC;AAAA,EACpC,OACE,MAAM,GACL,YAAY,qBAAqB,EACjC,IAAI,EAAE,kBAAkB,MAAM,YAAY,IAAI,KAAO,CAAC,EACtD,MAAM,eAAe,KAAK,UAAU,EACpC,MAAM,UAAU,MAAM,eAAe,EACrC,aAAa,EACb,iBAAiB,KAAM;AAAA;AAI3B,eAAsB,wCAAwC,CAC7D,IACA,YAC+B;AAAA,EAC/B,OAAO,MAAM,GACX,YAAY,qBAAqB,EACjC,IAAI,EAAE,kBAAkB,MAAM,YAAY,IAAI,KAAO,CAAC,EACtD,MAAM,eAAe,KAAK,UAAU,EACpC,MAAM,UAAU,MAAM,eAAe,EACrC,aAAa,EACb,QAAQ;AAAA;AAWX,eAAsB,8BAA8B,CACnD,IACA,YACA,MACmB;AAAA,EACnB,MAAM,YAAY,MAAM,aAAa;AAAA,EACrC,MAAM,SAAS,MAAM,UAAU;AAAA,EAC/B,MAAM,WAAW,KAAK,IAAI,IAAI;AAAA,EAC9B,OAAO,KAAK,IAAI,IAAI,UAAU;AAAA,IAC7B,MAAM,SAAS,MAAM,GACnB,WAAW,qBAAqB,EAChC,OAAO,IAAI,EACX,MAAM,eAAe,KAAK,UAAU,EACpC,MAAM,UAAU,MAAM,eAAe,EACrC,MAAM,CAAC,EACP,iBAAiB;AAAA,IACnB,IAAI,CAAC;AAAA,MAAQ,OAAO;AAAA,IACpB,MAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC;AAAA,EAC/C;AAAA,EACA,OAAO;AAAA;AAIR,SAAS,oBAAoB,GAAW;AAAA,EACvC,MAAM,SAAS,OAAO,SACrB,QAAQ,IAAI,4BAA4B,KACxC,EACD;AAAA,EACA,OAAO,OAAO,SAAS,MAAM,KAAK,SAAS,IAAI,SAAS;AAAA;AAGzD,eAAsB,sBAAsB,CAC3C,IACA,UACoC;AAAA,EAIpC,MAAM,cAAc,qBAAqB;AAAA,EACzC,MAAM,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA,iBAIL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAoCN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAUR,QAAQ,EAAE;AAAA,EACZ,OAAO,OAAO,KAAK,MAAM;AAAA;AAG1B,eAAsB,0BAA0B,CAC/C,IACA,aACA,UACgB;AAAA,EAChB,MAAM,GACJ,YAAY,qBAAqB,EACjC,IAAI;AAAA,IACJ,cAAc;AAAA,IACd,YAAY,IAAI;AAAA,EACjB,CAAC,EACA,MAAM,MAAM,KAAK,WAAW,EAC5B,MAAM,UAAU,KAAK,SAAS,EAC9B,MAAM,aAAa,KAAK,QAAQ,EAChC,QAAQ;AAAA;AAGX,eAAsB,oBAAoB,CACzC,IACA,aACoC;AAAA,EACpC,OACE,MAAM,GACL,WAAW,qBAAqB,EAChC,UAAU,EACV,MAAM,MAAM,KAAK,WAAW,EAC5B,iBAAiB,KAAM;AAAA;AAK3B,eAAsB,sBAAsB,CAC3C,IACA,YACA,QAAQ,IACuB;AAAA,EAC/B,OAAO,GACL,WAAW,qBAAqB,EAChC,UAAU,EACV,MAAM,eAAe,KAAK,UAAU,EACpC,QAAQ,cAAc,MAAM,EAC5B,MAAM,KAAK,EACX,QAAQ;AAAA;AAGX,eAAsB,yBAAyB,CAC9C,IACA,aACA,UACA,iBACgB;AAAA,EAChB,MAAM,GACJ,YAAY,qBAAqB,EACjC,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,aAAa,IAAI;AAAA,IACjB,kBAAkB;AAAA,IAClB,WAAW;AAAA,IACX,cAAc;AAAA,IACd,YAAY,IAAI;AAAA,EACjB,CAAC,EACA,MAAM,MAAM,KAAK,WAAW,EAC5B,MAAM,aAAa,KAAK,QAAQ,EAChC,QAAQ;AAAA;AAGX,eAAsB,uBAAuB,CAC5C,IACA,aACA,UACA,iBACgB;AAAA,EAChB,MAAM,GACJ,YAAY,qBAAqB,EACjC,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,aAAa,IAAI;AAAA,IACjB,kBAAkB;AAAA,IAClB,WAAW;AAAA,IACX,cAAc;AAAA,IACd,YAAY,IAAI;AAAA,EACjB,CAAC,EACA,MAAM,MAAM,KAAK,WAAW,EAC5B,MAAM,aAAa,KAAK,QAAQ,EAChC,QAAQ;AAAA;AAGX,eAAsB,qBAAqB,CAC1C,IACA,aACA,UACA,OACA,iBACgB;AAAA,EAChB,MAAM,GACJ,YAAY,qBAAqB,EACjC,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,aAAa,IAAI;AAAA,IACjB,kBAAkB,mBAAmB;AAAA,IACrC;AAAA,IACA,WAAW;AAAA,IACX,cAAc;AAAA,IACd,YAAY,IAAI;AAAA,EACjB,CAAC,EACA,MAAM,MAAM,KAAK,WAAW,EAC5B,MAAM,aAAa,KAAK,QAAQ,EAChC,QAAQ;AAAA;AASX,eAAsB,yBAAyB,CAC9C,IACA,aACyB;AAAA,EACzB,MAAM,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yCAmBmB;AAAA,GACtC,QAAQ,EAAE;AAAA,EACZ,MAAM,KAAK,OAAO,KAAK,IAAI;AAAA,EAC3B,OAAO,MAAM,OAAO,OAAO,OAAO,EAAE;AAAA;AAKrC,eAAsB,gCAAgC,CACrD,IACA,QACyB;AAAA,EACzB,MAAM,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAQH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAMhB,QAAQ,EAAE;AAAA,EACZ,MAAM,SAAS,OAAO,KAAK,IAAI;AAAA,EAC/B,OAAO,UAAU,OAAO,OAAO,OAAO,MAAM;AAAA;AAI7C,eAAsB,8BAA8B,CACnD,IACA,aACA,iBACgB;AAAA,EAGhB,MAAM,GACJ,YAAY,qBAAqB,EACjC,IAAI;AAAA,IACJ,kBAAkB,8CAA8C;AAAA,IAChE,YAAY,IAAI;AAAA,EACjB,CAAC,EACA,MAAM,MAAM,KAAK,WAAW,EAC5B,QAAQ;AAAA;AASX,eAAsB,sBAAsB,CAC3C,IACA,aACA,QACmB;AAAA,EACnB,MAAM,SAAS,MAAM,GACnB,YAAY,qBAAqB,EACjC,IAAI,EAAE,cAAc,QAAQ,YAAY,IAAI,KAAO,CAAC,EACpD,MAAM,MAAM,KAAK,WAAW,EAC5B,MAAM,CAAC,OACP,GAAG,GAAG;AAAA,IACL,GAAG,gBAAgB,MAAM,IAAI;AAAA,IAC7B,GAAG,gBAAgB,KAAK,OAAO,MAAM,CAAC;AAAA,EACvC,CAAC,CACF,EACC,iBAAiB;AAAA,EACnB,OAAO,OAAO,OAAO,kBAAkB,EAAE,IAAI;AAAA;",
|
|
8
|
+
"debugId": "82AB266EA49F95E564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -153,7 +153,6 @@ interface SubgraphsTable {
|
|
|
153
153
|
handler_code: string | null;
|
|
154
154
|
source_code: string | null;
|
|
155
155
|
project_id: string | null;
|
|
156
|
-
visibility: Generated<string>;
|
|
157
156
|
/** Paid (wallet-ghost) deploys expire unless renewed or claimed; NULL = no expiry. */
|
|
158
157
|
expires_at: Date | null;
|
|
159
158
|
/** (event type, contract) probe pairs persisted at deploy for weight classification. */
|
|
@@ -783,6 +782,7 @@ interface Database {
|
|
|
783
782
|
coverage_segments: CoverageSegmentsTable;
|
|
784
783
|
stage_failures: StageFailuresTable;
|
|
785
784
|
archive_fetches: ArchiveFetchesTable;
|
|
785
|
+
usage_ledger: UsageLedgerTable;
|
|
786
786
|
waitlists: WaitlistsTable;
|
|
787
787
|
waitlist_signups: WaitlistSignupsTable;
|
|
788
788
|
}
|
|
@@ -827,6 +827,27 @@ interface ArchiveFetchesTable {
|
|
|
827
827
|
via_allowance: Generated<boolean>;
|
|
828
828
|
charged_at: Generated<Date>;
|
|
829
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* One metered ledger for every billable unit (archive partitions, hosted
|
|
832
|
+
* Index/Streams rows, and future hosted-stack meters). Append-only, written
|
|
833
|
+
* only by `meter()`. `idempotency_key` is UNIQUE so a retried submission
|
|
834
|
+
* no-ops instead of double-charging. `debited` is false when the balance
|
|
835
|
+
* (or a spend cap) refused the charge — the row still exists so the gap is
|
|
836
|
+
* visible, never silently dropped. `usd_micros` is negative for a Stripe
|
|
837
|
+
* top-up (`unit: "topup"`).
|
|
838
|
+
*/
|
|
839
|
+
interface UsageLedgerTable {
|
|
840
|
+
id: Generated<string>;
|
|
841
|
+
account_id: string;
|
|
842
|
+
unit: string;
|
|
843
|
+
quantity: string | number | bigint;
|
|
844
|
+
usd_micros: string | number | bigint;
|
|
845
|
+
debited: Generated<boolean>;
|
|
846
|
+
source: string;
|
|
847
|
+
idempotency_key: string;
|
|
848
|
+
occurred_at: Generated<Date>;
|
|
849
|
+
recorded_at: Generated<Date>;
|
|
850
|
+
}
|
|
830
851
|
type Subgraph = Selectable<SubgraphsTable>;
|
|
831
852
|
type WebhookStatus = "active" | "paused" | "error";
|
|
832
853
|
/** Polymorphic webhook mode: `subgraph` reacts to processed table rows;
|
|
@@ -890,6 +911,10 @@ interface WebhookOutboxTable {
|
|
|
890
911
|
failed_at: Date | null;
|
|
891
912
|
locked_by: string | null;
|
|
892
913
|
locked_until: Date | null;
|
|
914
|
+
/** Set when a reorg marks an in-flight row `dead` instead of retrying it
|
|
915
|
+
* into an orphaned fork. Null otherwise — delivery failures track their
|
|
916
|
+
* history on `webhooks.last_error`, not per-row. */
|
|
917
|
+
last_error: string | null;
|
|
893
918
|
created_at: Generated<Date>;
|
|
894
919
|
}
|
|
895
920
|
interface WebhookDeliveriesTable {
|
|
@@ -937,21 +962,10 @@ interface WaitlistSignupsTable {
|
|
|
937
962
|
answers: Record<string, unknown>;
|
|
938
963
|
created_at: Generated<Date>;
|
|
939
964
|
}
|
|
940
|
-
/**
|
|
941
|
-
* Convert a subgraph name to its PostgreSQL schema name (legacy form).
|
|
942
|
-
* Pre shared-rip every tenant DB had its own schema namespace so disambiguation
|
|
943
|
-
* was implicit. Kept for oss mode (single-tenant) and legacy-row fallback.
|
|
944
|
-
* Platform-mode deploys use `pgSchemaNameFor(accountId, name)`.
|
|
945
|
-
*/
|
|
965
|
+
/** Convert a subgraph name to its PostgreSQL schema name. */
|
|
946
966
|
declare function pgSchemaName(subgraphName: string): string;
|
|
947
967
|
/** OSS writes empty account_id — name is the local unique key. */
|
|
948
968
|
declare function localSubgraphAccountId(accountId?: string): string;
|
|
949
|
-
/**
|
|
950
|
-
* Account-scoped schema name. Matches migration 0028's rename pattern:
|
|
951
|
-
* subgraph_{first8charsOfAccountId, dashes-as-underscores}_{name}
|
|
952
|
-
* Empty accountId falls back to legacy form (oss mode).
|
|
953
|
-
*/
|
|
954
|
-
declare function pgSchemaNameFor(accountId: string, subgraphName: string): string;
|
|
955
969
|
declare function registerSubgraph(db: Kysely<Database>, data: {
|
|
956
970
|
name: string
|
|
957
971
|
version: string
|
|
@@ -1043,4 +1057,4 @@ declare function recordLiveProgress(db: Kysely<Database>, name: string, lastProc
|
|
|
1043
1057
|
* forward write between the rewind and the fork block's reprocess. Do not
|
|
1044
1058
|
* call this from the live catch-up path. */
|
|
1045
1059
|
declare function rewindLiveProgress(db: Kysely<Database>, name: string, lastProcessedBlock: number): Promise<void>;
|
|
1046
|
-
export {
|
|
1060
|
+
export { deleteSubgraph, getSubgraph, listSubgraphs, localSubgraphAccountId, pgSchemaName, recordLiveError, recordLiveProgress, recordSubgraphProcessed, registerSubgraph, rewindLiveProgress, updateSubgraphExpiry, updateSubgraphHandlerPath, updateSubgraphStatus };
|