@secondlayer/shared 7.11.1 → 8.0.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/dist/src/archive/range-digest.d.ts +0 -169
- package/dist/src/archive/semantic-digest-builder.d.ts +0 -169
- package/dist/src/coverage/index.d.ts +0 -169
- package/dist/src/crypto/secrets.js +22 -3
- package/dist/src/crypto/secrets.js.map +3 -3
- package/dist/src/db/index.d.ts +1 -211
- package/dist/src/db/index.js +22 -16
- package/dist/src/db/index.js.map +4 -4
- package/dist/src/db/queries/chain-reorgs.d.ts +0 -169
- package/dist/src/db/queries/chain-reorgs.js +22 -16
- package/dist/src/db/queries/chain-reorgs.js.map +4 -4
- package/dist/src/db/queries/contracts.d.ts +0 -169
- package/dist/src/db/queries/instance.d.ts +0 -169
- package/dist/src/db/queries/integrity.d.ts +0 -169
- package/dist/src/db/queries/subgraph-gaps.d.ts +0 -169
- package/dist/src/db/queries/subgraph-operations.d.ts +1 -170
- package/dist/src/db/queries/subgraph-operations.js +1 -15
- package/dist/src/db/queries/subgraph-operations.js.map +3 -3
- package/dist/src/db/queries/subgraphs.d.ts +1 -191
- package/dist/src/db/queries/subgraphs.js +30 -1068
- package/dist/src/db/queries/subgraphs.js.map +5 -12
- package/dist/src/db/queries/subscriptions.d.ts +0 -169
- package/dist/src/db/queries/subscriptions.js +22 -3
- package/dist/src/db/queries/subscriptions.js.map +3 -3
- package/dist/src/db/schema.d.ts +1 -198
- package/dist/src/errors.d.ts +1 -22
- package/dist/src/errors.js +2 -4
- package/dist/src/errors.js.map +3 -3
- package/dist/src/index.d.ts +5 -256
- package/dist/src/index.js +23 -20
- package/dist/src/index.js.map +6 -6
- package/dist/src/mode.d.ts +16 -2
- package/dist/src/mode.js +25 -4
- package/dist/src/mode.js.map +3 -3
- package/dist/src/node/local-client.d.ts +0 -169
- package/dist/src/runtime/index.js +26 -3
- package/dist/src/runtime/index.js.map +3 -3
- package/dist/src/schemas/index.d.ts +5 -25
- package/dist/src/schemas/index.js +1 -2
- package/dist/src/schemas/index.js.map +3 -3
- package/dist/src/schemas/subgraphs.d.ts +5 -25
- package/dist/src/schemas/subgraphs.js +1 -2
- package/dist/src/schemas/subgraphs.js.map +3 -3
- package/dist/src/streams-rows/index.d.ts +399 -0
- package/dist/src/streams-rows/index.js +336 -0
- package/dist/src/streams-rows/index.js.map +17 -0
- package/dist/src/subgraphs/spec.d.ts +0 -9
- package/migrations/0118_drop_retired_control_tables.ts +53 -0
- package/migrations/0119_drop_accounts_plan.ts +29 -0
- package/package.json +5 -1
|
@@ -223,11 +223,9 @@ interface AccountsTable {
|
|
|
223
223
|
ghost: Generated<boolean>;
|
|
224
224
|
/** Stacks principal owning a wallet-ghost account (x402-paid deploys). */
|
|
225
225
|
wallet_principal: string | null;
|
|
226
|
-
plan: Generated<string>;
|
|
227
226
|
display_name: string | null;
|
|
228
227
|
bio: string | null;
|
|
229
228
|
avatar_url: string | null;
|
|
230
|
-
slug: string | null;
|
|
231
229
|
stripe_customer_id: string | null;
|
|
232
230
|
/** Opt-out toggle for the subgraph reindex-completion email. Defaults true. */
|
|
233
231
|
notify_reindex_complete: Generated<boolean>;
|
|
@@ -244,19 +242,6 @@ interface SessionsTable {
|
|
|
244
242
|
last_used_at: Date | null;
|
|
245
243
|
created_at: Generated<Date>;
|
|
246
244
|
}
|
|
247
|
-
/**
|
|
248
|
-
* One-time claim tokens for ghost accounts. The raw token only ever appears in
|
|
249
|
-
* the claim URL returned at mint time; we store its sha256. A used token marks
|
|
250
|
-
* the account as claimed (or merged into an existing account).
|
|
251
|
-
*/
|
|
252
|
-
interface ClaimTokensTable {
|
|
253
|
-
id: Generated<string>;
|
|
254
|
-
account_id: string;
|
|
255
|
-
token_hash: string;
|
|
256
|
-
created_at: Generated<Date>;
|
|
257
|
-
expires_at: Date;
|
|
258
|
-
used_at: Date | null;
|
|
259
|
-
}
|
|
260
245
|
interface MagicLinksTable {
|
|
261
246
|
id: Generated<string>;
|
|
262
247
|
email: string;
|
|
@@ -267,47 +252,6 @@ interface MagicLinksTable {
|
|
|
267
252
|
failed_attempts: Generated<number>;
|
|
268
253
|
created_at: Generated<Date>;
|
|
269
254
|
}
|
|
270
|
-
interface UsageDailyTable {
|
|
271
|
-
account_id: string;
|
|
272
|
-
tenant_id: string | null;
|
|
273
|
-
date: string;
|
|
274
|
-
api_requests: Generated<number>;
|
|
275
|
-
deliveries: Generated<number>;
|
|
276
|
-
streams_events_returned: Generated<number>;
|
|
277
|
-
index_decoded_events_returned: Generated<number>;
|
|
278
|
-
}
|
|
279
|
-
interface UsageSnapshotsTable {
|
|
280
|
-
id: Generated<string>;
|
|
281
|
-
account_id: string;
|
|
282
|
-
measured_at: Generated<Date>;
|
|
283
|
-
storage_bytes: Generated<number>;
|
|
284
|
-
}
|
|
285
|
-
interface AccountInsightsTable {
|
|
286
|
-
id: Generated<string>;
|
|
287
|
-
account_id: string;
|
|
288
|
-
category: string;
|
|
289
|
-
insight_type: string;
|
|
290
|
-
resource_id: string | null;
|
|
291
|
-
severity: string;
|
|
292
|
-
title: string;
|
|
293
|
-
body: string;
|
|
294
|
-
data: unknown;
|
|
295
|
-
dismissed_at: Date | null;
|
|
296
|
-
expires_at: Date | null;
|
|
297
|
-
created_at: Generated<Date>;
|
|
298
|
-
}
|
|
299
|
-
interface AccountAgentRunsTable {
|
|
300
|
-
id: Generated<string>;
|
|
301
|
-
account_id: string;
|
|
302
|
-
started_at: Generated<Date>;
|
|
303
|
-
completed_at: Date | null;
|
|
304
|
-
status: Generated<string>;
|
|
305
|
-
input_tokens: Generated<number>;
|
|
306
|
-
output_tokens: Generated<number>;
|
|
307
|
-
cost_usd: Generated<number>;
|
|
308
|
-
insights_created: Generated<number>;
|
|
309
|
-
error: string | null;
|
|
310
|
-
}
|
|
311
255
|
interface SubgraphProcessingStatsTable {
|
|
312
256
|
id: Generated<string>;
|
|
313
257
|
subgraph_name: string;
|
|
@@ -340,41 +284,6 @@ interface SubgraphHealthSnapshotsTable {
|
|
|
340
284
|
last_processed_block: number | null;
|
|
341
285
|
captured_at: Generated<Date>;
|
|
342
286
|
}
|
|
343
|
-
interface SubgraphUsageDailyTable {
|
|
344
|
-
subgraph_id: string;
|
|
345
|
-
date: string;
|
|
346
|
-
query_count: Generated<number>;
|
|
347
|
-
}
|
|
348
|
-
interface ProjectsTable {
|
|
349
|
-
id: Generated<string>;
|
|
350
|
-
name: string;
|
|
351
|
-
slug: string;
|
|
352
|
-
account_id: string;
|
|
353
|
-
settings: Generated<Record<string, unknown>>;
|
|
354
|
-
network: Generated<string>;
|
|
355
|
-
node_rpc: string | null;
|
|
356
|
-
created_at: Generated<Date>;
|
|
357
|
-
updated_at: Generated<Date>;
|
|
358
|
-
}
|
|
359
|
-
interface TeamMembersTable {
|
|
360
|
-
id: Generated<string>;
|
|
361
|
-
project_id: string;
|
|
362
|
-
account_id: string;
|
|
363
|
-
role: Generated<string>;
|
|
364
|
-
invited_by: string | null;
|
|
365
|
-
created_at: Generated<Date>;
|
|
366
|
-
}
|
|
367
|
-
interface TeamInvitationsTable {
|
|
368
|
-
id: Generated<string>;
|
|
369
|
-
project_id: string;
|
|
370
|
-
email: string;
|
|
371
|
-
role: Generated<string>;
|
|
372
|
-
token: string;
|
|
373
|
-
invited_by: string | null;
|
|
374
|
-
expires_at: Date;
|
|
375
|
-
accepted_at: Date | null;
|
|
376
|
-
created_at: Generated<Date>;
|
|
377
|
-
}
|
|
378
287
|
interface ProcessedStripeEventsTable {
|
|
379
288
|
event_id: string;
|
|
380
289
|
event_type: string;
|
|
@@ -827,27 +736,14 @@ interface Database {
|
|
|
827
736
|
accounts: AccountsTable;
|
|
828
737
|
sessions: SessionsTable;
|
|
829
738
|
magic_links: MagicLinksTable;
|
|
830
|
-
claim_tokens: ClaimTokensTable;
|
|
831
|
-
usage_daily: UsageDailyTable;
|
|
832
|
-
usage_snapshots: UsageSnapshotsTable;
|
|
833
|
-
account_insights: AccountInsightsTable;
|
|
834
|
-
account_agent_runs: AccountAgentRunsTable;
|
|
835
739
|
subgraph_health_snapshots: SubgraphHealthSnapshotsTable;
|
|
836
740
|
subgraph_processing_stats: SubgraphProcessingStatsTable;
|
|
837
741
|
subgraph_table_snapshots: SubgraphTableSnapshotsTable;
|
|
838
742
|
subgraph_gaps: SubgraphGapsTable;
|
|
839
743
|
subgraph_operations: SubgraphOperationsTable;
|
|
840
|
-
subgraph_usage_daily: SubgraphUsageDailyTable;
|
|
841
|
-
projects: ProjectsTable;
|
|
842
|
-
team_members: TeamMembersTable;
|
|
843
|
-
team_invitations: TeamInvitationsTable;
|
|
844
744
|
processed_stripe_events: ProcessedStripeEventsTable;
|
|
845
|
-
tenants: TenantsTable;
|
|
846
|
-
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
847
|
-
tenant_compute_addons: TenantComputeAddonsTable;
|
|
848
745
|
account_spend_caps: AccountSpendCapsTable;
|
|
849
746
|
account_credits: AccountCreditsTable;
|
|
850
|
-
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
851
747
|
subscriptions: SubscriptionsTable;
|
|
852
748
|
subscription_outbox: SubscriptionOutboxTable;
|
|
853
749
|
subscription_deliveries: SubscriptionDeliveriesTable;
|
|
@@ -922,57 +818,6 @@ interface X402PaymentsTable {
|
|
|
922
818
|
* reconciler's heal path can never double-credit them. */
|
|
923
819
|
credited_at: Generated<Date> | null;
|
|
924
820
|
}
|
|
925
|
-
type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
|
|
926
|
-
interface TenantsTable {
|
|
927
|
-
id: Generated<string>;
|
|
928
|
-
account_id: string;
|
|
929
|
-
slug: string;
|
|
930
|
-
status: ColumnType<TenantStatus, TenantStatus | undefined, TenantStatus>;
|
|
931
|
-
plan: string;
|
|
932
|
-
cpus: ColumnType<number, number | string, number | string>;
|
|
933
|
-
memory_mb: number;
|
|
934
|
-
storage_limit_mb: number;
|
|
935
|
-
storage_used_mb: number | null;
|
|
936
|
-
pg_container_id: string | null;
|
|
937
|
-
api_container_id: string | null;
|
|
938
|
-
processor_container_id: string | null;
|
|
939
|
-
target_database_url_enc: Buffer;
|
|
940
|
-
tenant_jwt_secret_enc: Buffer;
|
|
941
|
-
anon_key_enc: Buffer;
|
|
942
|
-
service_key_enc: Buffer;
|
|
943
|
-
api_url_internal: string;
|
|
944
|
-
api_url_public: string;
|
|
945
|
-
suspended_at: Date | null;
|
|
946
|
-
last_health_check_at: Date | null;
|
|
947
|
-
last_active_at: Generated<Date>;
|
|
948
|
-
service_gen: Generated<number>;
|
|
949
|
-
anon_gen: Generated<number>;
|
|
950
|
-
project_id: string | null;
|
|
951
|
-
created_at: Generated<Date>;
|
|
952
|
-
updated_at: Generated<Date>;
|
|
953
|
-
}
|
|
954
|
-
interface TenantUsageMonthlyTable {
|
|
955
|
-
id: Generated<string>;
|
|
956
|
-
tenant_id: string;
|
|
957
|
-
period_month: Date;
|
|
958
|
-
storage_peak_mb: Generated<number>;
|
|
959
|
-
storage_avg_mb: Generated<number>;
|
|
960
|
-
storage_last_mb: Generated<number>;
|
|
961
|
-
measurements: Generated<number>;
|
|
962
|
-
first_at: Generated<Date>;
|
|
963
|
-
last_at: Generated<Date>;
|
|
964
|
-
}
|
|
965
|
-
interface TenantComputeAddonsTable {
|
|
966
|
-
id: Generated<string>;
|
|
967
|
-
tenant_id: string;
|
|
968
|
-
memory_mb_delta: Generated<number>;
|
|
969
|
-
cpu_delta: Generated<number | string>;
|
|
970
|
-
storage_mb_delta: Generated<number>;
|
|
971
|
-
effective_from: Generated<Date>;
|
|
972
|
-
effective_until: Date | null;
|
|
973
|
-
stripe_subscription_item_id: string | null;
|
|
974
|
-
created_at: Generated<Date>;
|
|
975
|
-
}
|
|
976
821
|
interface AccountSpendCapsTable {
|
|
977
822
|
account_id: string;
|
|
978
823
|
monthly_cap_cents: number | null;
|
|
@@ -996,20 +841,6 @@ interface AccountCreditsTable {
|
|
|
996
841
|
refill_last_at: Date | null;
|
|
997
842
|
updated_at: Generated<Date>;
|
|
998
843
|
}
|
|
999
|
-
type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
|
|
1000
|
-
type ProvisioningAuditStatus = "ok" | "error";
|
|
1001
|
-
interface ProvisioningAuditLogTable {
|
|
1002
|
-
id: Generated<string>;
|
|
1003
|
-
tenant_id: string | null;
|
|
1004
|
-
tenant_slug: string | null;
|
|
1005
|
-
account_id: string | null;
|
|
1006
|
-
actor: string;
|
|
1007
|
-
event: ProvisioningAuditEvent;
|
|
1008
|
-
status: ProvisioningAuditStatus;
|
|
1009
|
-
detail: unknown | null;
|
|
1010
|
-
error: string | null;
|
|
1011
|
-
created_at: Generated<Date>;
|
|
1012
|
-
}
|
|
1013
844
|
type SubgraphOperation = Selectable<SubgraphOperationsTable>;
|
|
1014
845
|
type SubscriptionStatus = "active" | "paused" | "error";
|
|
1015
846
|
/** Polymorphic subscription mode: `subgraph` reacts to processed table rows;
|
|
@@ -1134,7 +965,7 @@ declare function listSubgraphOperations(db: Kysely<Database>, subgraphId: string
|
|
|
1134
965
|
declare function completeSubgraphOperation(db: Kysely<Database>, operationId: string, lockedBy: string, processedBlocks: number): Promise<void>;
|
|
1135
966
|
declare function cancelSubgraphOperation(db: Kysely<Database>, operationId: string, lockedBy: string, processedBlocks: number): Promise<void>;
|
|
1136
967
|
declare function failSubgraphOperation(db: Kysely<Database>, operationId: string, lockedBy: string, error: string, processedBlocks?: number): Promise<void>;
|
|
1137
|
-
/**\\n* 1-based position of a queued operation under the claim ordering (fairness →\\n*
|
|
968
|
+
/**\\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*/
|
|
1138
969
|
declare function getOperationQueuePosition(db: Kysely<Database>, operationId: string): Promise<number | null>;
|
|
1139
970
|
/** Median duration (seconds) of the last 20 completed ops of a weight class —
|
|
1140
971
|
* the "est. start" multiplier for queued positions. Null with no history. */
|
|
@@ -85,7 +85,6 @@ async function claimSubgraphOperation(db, lockedBy) {
|
|
|
85
85
|
WHERE status = 'running'
|
|
86
86
|
GROUP BY account_id
|
|
87
87
|
) rc ON so.account_id = rc.account_id
|
|
88
|
-
LEFT JOIN accounts a ON a.id::text = so.account_id
|
|
89
88
|
WHERE
|
|
90
89
|
(
|
|
91
90
|
so.status = 'queued'
|
|
@@ -113,12 +112,6 @@ async function claimSubgraphOperation(db, lockedBy) {
|
|
|
113
112
|
)
|
|
114
113
|
ORDER BY
|
|
115
114
|
COALESCE(rc.cnt, 0) ASC,
|
|
116
|
-
CASE COALESCE(a.plan, 'none')
|
|
117
|
-
WHEN 'enterprise' THEN 0
|
|
118
|
-
WHEN 'scale' THEN 1
|
|
119
|
-
WHEN 'launch' THEN 2
|
|
120
|
-
ELSE 3
|
|
121
|
-
END,
|
|
122
115
|
CASE WHEN so.status = 'queued' THEN 0 ELSE 1 END,
|
|
123
116
|
so.created_at ASC
|
|
124
117
|
FOR UPDATE OF so SKIP LOCKED
|
|
@@ -179,12 +172,6 @@ async function getOperationQueuePosition(db, operationId) {
|
|
|
179
172
|
ROW_NUMBER() OVER (
|
|
180
173
|
ORDER BY
|
|
181
174
|
COALESCE(rc.cnt, 0) ASC,
|
|
182
|
-
CASE COALESCE(a.plan, 'none')
|
|
183
|
-
WHEN 'enterprise' THEN 0
|
|
184
|
-
WHEN 'scale' THEN 1
|
|
185
|
-
WHEN 'launch' THEN 2
|
|
186
|
-
ELSE 3
|
|
187
|
-
END,
|
|
188
175
|
CASE WHEN so.status = 'queued' THEN 0 ELSE 1 END,
|
|
189
176
|
so.created_at ASC
|
|
190
177
|
) AS rn
|
|
@@ -195,7 +182,6 @@ async function getOperationQueuePosition(db, operationId) {
|
|
|
195
182
|
WHERE status = 'running'
|
|
196
183
|
GROUP BY account_id
|
|
197
184
|
) rc ON so.account_id = rc.account_id
|
|
198
|
-
LEFT JOIN accounts a ON a.id::text = so.account_id
|
|
199
185
|
WHERE so.status = 'queued'
|
|
200
186
|
)
|
|
201
187
|
SELECT rn FROM candidates WHERE id = ${operationId}
|
|
@@ -255,5 +241,5 @@ export {
|
|
|
255
241
|
advanceOperationCursor
|
|
256
242
|
};
|
|
257
243
|
|
|
258
|
-
//# debugId=
|
|
244
|
+
//# debugId=774B93C0E098748E64756E2164756E21
|
|
259
245
|
//# sourceMappingURL=subgraph-operations.js.map
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/db/queries/subgraph-operations.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
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\tLEFT JOIN accounts a ON a.id::text = so.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 COALESCE(a.plan, 'none')\n\t\t\t\t\tWHEN 'enterprise' THEN 0\n\t\t\t\t\tWHEN 'scale' THEN 1\n\t\t\t\t\tWHEN 'launch' THEN 2\n\t\t\t\t\tELSE 3\n\t\t\t\tEND,\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 * plan rank → queued-first → FIFO). The heavy-budget admission filter is NOT\n * applied — a heavy op's eligibility depends on runtime budget state — so the\n * position is 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 COALESCE(a.plan, 'none')\n\t\t\t\t\t\t\tWHEN 'enterprise' THEN 0\n\t\t\t\t\t\t\tWHEN 'scale' THEN 1\n\t\t\t\t\t\t\tWHEN 'launch' THEN 2\n\t\t\t\t\t\t\tELSE 3\n\t\t\t\t\t\tEND,\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\tLEFT JOIN accounts a ON a.id::text = so.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"
|
|
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": "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
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "774B93C0E098748E64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Kysely } from "kysely";
|
|
2
|
-
import postgres from "postgres";
|
|
3
2
|
import { ColumnType, Generated, Selectable } from "kysely";
|
|
4
3
|
/**
|
|
5
4
|
* Coverage-kernel constraints. SQL CHECKs in migration 0116 must stay
|
|
@@ -224,11 +223,9 @@ interface AccountsTable {
|
|
|
224
223
|
ghost: Generated<boolean>;
|
|
225
224
|
/** Stacks principal owning a wallet-ghost account (x402-paid deploys). */
|
|
226
225
|
wallet_principal: string | null;
|
|
227
|
-
plan: Generated<string>;
|
|
228
226
|
display_name: string | null;
|
|
229
227
|
bio: string | null;
|
|
230
228
|
avatar_url: string | null;
|
|
231
|
-
slug: string | null;
|
|
232
229
|
stripe_customer_id: string | null;
|
|
233
230
|
/** Opt-out toggle for the subgraph reindex-completion email. Defaults true. */
|
|
234
231
|
notify_reindex_complete: Generated<boolean>;
|
|
@@ -245,19 +242,6 @@ interface SessionsTable {
|
|
|
245
242
|
last_used_at: Date | null;
|
|
246
243
|
created_at: Generated<Date>;
|
|
247
244
|
}
|
|
248
|
-
/**
|
|
249
|
-
* One-time claim tokens for ghost accounts. The raw token only ever appears in
|
|
250
|
-
* the claim URL returned at mint time; we store its sha256. A used token marks
|
|
251
|
-
* the account as claimed (or merged into an existing account).
|
|
252
|
-
*/
|
|
253
|
-
interface ClaimTokensTable {
|
|
254
|
-
id: Generated<string>;
|
|
255
|
-
account_id: string;
|
|
256
|
-
token_hash: string;
|
|
257
|
-
created_at: Generated<Date>;
|
|
258
|
-
expires_at: Date;
|
|
259
|
-
used_at: Date | null;
|
|
260
|
-
}
|
|
261
245
|
interface MagicLinksTable {
|
|
262
246
|
id: Generated<string>;
|
|
263
247
|
email: string;
|
|
@@ -268,47 +252,6 @@ interface MagicLinksTable {
|
|
|
268
252
|
failed_attempts: Generated<number>;
|
|
269
253
|
created_at: Generated<Date>;
|
|
270
254
|
}
|
|
271
|
-
interface UsageDailyTable {
|
|
272
|
-
account_id: string;
|
|
273
|
-
tenant_id: string | null;
|
|
274
|
-
date: string;
|
|
275
|
-
api_requests: Generated<number>;
|
|
276
|
-
deliveries: Generated<number>;
|
|
277
|
-
streams_events_returned: Generated<number>;
|
|
278
|
-
index_decoded_events_returned: Generated<number>;
|
|
279
|
-
}
|
|
280
|
-
interface UsageSnapshotsTable {
|
|
281
|
-
id: Generated<string>;
|
|
282
|
-
account_id: string;
|
|
283
|
-
measured_at: Generated<Date>;
|
|
284
|
-
storage_bytes: Generated<number>;
|
|
285
|
-
}
|
|
286
|
-
interface AccountInsightsTable {
|
|
287
|
-
id: Generated<string>;
|
|
288
|
-
account_id: string;
|
|
289
|
-
category: string;
|
|
290
|
-
insight_type: string;
|
|
291
|
-
resource_id: string | null;
|
|
292
|
-
severity: string;
|
|
293
|
-
title: string;
|
|
294
|
-
body: string;
|
|
295
|
-
data: unknown;
|
|
296
|
-
dismissed_at: Date | null;
|
|
297
|
-
expires_at: Date | null;
|
|
298
|
-
created_at: Generated<Date>;
|
|
299
|
-
}
|
|
300
|
-
interface AccountAgentRunsTable {
|
|
301
|
-
id: Generated<string>;
|
|
302
|
-
account_id: string;
|
|
303
|
-
started_at: Generated<Date>;
|
|
304
|
-
completed_at: Date | null;
|
|
305
|
-
status: Generated<string>;
|
|
306
|
-
input_tokens: Generated<number>;
|
|
307
|
-
output_tokens: Generated<number>;
|
|
308
|
-
cost_usd: Generated<number>;
|
|
309
|
-
insights_created: Generated<number>;
|
|
310
|
-
error: string | null;
|
|
311
|
-
}
|
|
312
255
|
interface SubgraphProcessingStatsTable {
|
|
313
256
|
id: Generated<string>;
|
|
314
257
|
subgraph_name: string;
|
|
@@ -341,41 +284,6 @@ interface SubgraphHealthSnapshotsTable {
|
|
|
341
284
|
last_processed_block: number | null;
|
|
342
285
|
captured_at: Generated<Date>;
|
|
343
286
|
}
|
|
344
|
-
interface SubgraphUsageDailyTable {
|
|
345
|
-
subgraph_id: string;
|
|
346
|
-
date: string;
|
|
347
|
-
query_count: Generated<number>;
|
|
348
|
-
}
|
|
349
|
-
interface ProjectsTable {
|
|
350
|
-
id: Generated<string>;
|
|
351
|
-
name: string;
|
|
352
|
-
slug: string;
|
|
353
|
-
account_id: string;
|
|
354
|
-
settings: Generated<Record<string, unknown>>;
|
|
355
|
-
network: Generated<string>;
|
|
356
|
-
node_rpc: string | null;
|
|
357
|
-
created_at: Generated<Date>;
|
|
358
|
-
updated_at: Generated<Date>;
|
|
359
|
-
}
|
|
360
|
-
interface TeamMembersTable {
|
|
361
|
-
id: Generated<string>;
|
|
362
|
-
project_id: string;
|
|
363
|
-
account_id: string;
|
|
364
|
-
role: Generated<string>;
|
|
365
|
-
invited_by: string | null;
|
|
366
|
-
created_at: Generated<Date>;
|
|
367
|
-
}
|
|
368
|
-
interface TeamInvitationsTable {
|
|
369
|
-
id: Generated<string>;
|
|
370
|
-
project_id: string;
|
|
371
|
-
email: string;
|
|
372
|
-
role: Generated<string>;
|
|
373
|
-
token: string;
|
|
374
|
-
invited_by: string | null;
|
|
375
|
-
expires_at: Date;
|
|
376
|
-
accepted_at: Date | null;
|
|
377
|
-
created_at: Generated<Date>;
|
|
378
|
-
}
|
|
379
287
|
interface ProcessedStripeEventsTable {
|
|
380
288
|
event_id: string;
|
|
381
289
|
event_type: string;
|
|
@@ -828,27 +736,14 @@ interface Database {
|
|
|
828
736
|
accounts: AccountsTable;
|
|
829
737
|
sessions: SessionsTable;
|
|
830
738
|
magic_links: MagicLinksTable;
|
|
831
|
-
claim_tokens: ClaimTokensTable;
|
|
832
|
-
usage_daily: UsageDailyTable;
|
|
833
|
-
usage_snapshots: UsageSnapshotsTable;
|
|
834
|
-
account_insights: AccountInsightsTable;
|
|
835
|
-
account_agent_runs: AccountAgentRunsTable;
|
|
836
739
|
subgraph_health_snapshots: SubgraphHealthSnapshotsTable;
|
|
837
740
|
subgraph_processing_stats: SubgraphProcessingStatsTable;
|
|
838
741
|
subgraph_table_snapshots: SubgraphTableSnapshotsTable;
|
|
839
742
|
subgraph_gaps: SubgraphGapsTable;
|
|
840
743
|
subgraph_operations: SubgraphOperationsTable;
|
|
841
|
-
subgraph_usage_daily: SubgraphUsageDailyTable;
|
|
842
|
-
projects: ProjectsTable;
|
|
843
|
-
team_members: TeamMembersTable;
|
|
844
|
-
team_invitations: TeamInvitationsTable;
|
|
845
744
|
processed_stripe_events: ProcessedStripeEventsTable;
|
|
846
|
-
tenants: TenantsTable;
|
|
847
|
-
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
848
|
-
tenant_compute_addons: TenantComputeAddonsTable;
|
|
849
745
|
account_spend_caps: AccountSpendCapsTable;
|
|
850
746
|
account_credits: AccountCreditsTable;
|
|
851
|
-
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
852
747
|
subscriptions: SubscriptionsTable;
|
|
853
748
|
subscription_outbox: SubscriptionOutboxTable;
|
|
854
749
|
subscription_deliveries: SubscriptionDeliveriesTable;
|
|
@@ -923,57 +818,6 @@ interface X402PaymentsTable {
|
|
|
923
818
|
* reconciler's heal path can never double-credit them. */
|
|
924
819
|
credited_at: Generated<Date> | null;
|
|
925
820
|
}
|
|
926
|
-
type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
|
|
927
|
-
interface TenantsTable {
|
|
928
|
-
id: Generated<string>;
|
|
929
|
-
account_id: string;
|
|
930
|
-
slug: string;
|
|
931
|
-
status: ColumnType<TenantStatus, TenantStatus | undefined, TenantStatus>;
|
|
932
|
-
plan: string;
|
|
933
|
-
cpus: ColumnType<number, number | string, number | string>;
|
|
934
|
-
memory_mb: number;
|
|
935
|
-
storage_limit_mb: number;
|
|
936
|
-
storage_used_mb: number | null;
|
|
937
|
-
pg_container_id: string | null;
|
|
938
|
-
api_container_id: string | null;
|
|
939
|
-
processor_container_id: string | null;
|
|
940
|
-
target_database_url_enc: Buffer;
|
|
941
|
-
tenant_jwt_secret_enc: Buffer;
|
|
942
|
-
anon_key_enc: Buffer;
|
|
943
|
-
service_key_enc: Buffer;
|
|
944
|
-
api_url_internal: string;
|
|
945
|
-
api_url_public: string;
|
|
946
|
-
suspended_at: Date | null;
|
|
947
|
-
last_health_check_at: Date | null;
|
|
948
|
-
last_active_at: Generated<Date>;
|
|
949
|
-
service_gen: Generated<number>;
|
|
950
|
-
anon_gen: Generated<number>;
|
|
951
|
-
project_id: string | null;
|
|
952
|
-
created_at: Generated<Date>;
|
|
953
|
-
updated_at: Generated<Date>;
|
|
954
|
-
}
|
|
955
|
-
interface TenantUsageMonthlyTable {
|
|
956
|
-
id: Generated<string>;
|
|
957
|
-
tenant_id: string;
|
|
958
|
-
period_month: Date;
|
|
959
|
-
storage_peak_mb: Generated<number>;
|
|
960
|
-
storage_avg_mb: Generated<number>;
|
|
961
|
-
storage_last_mb: Generated<number>;
|
|
962
|
-
measurements: Generated<number>;
|
|
963
|
-
first_at: Generated<Date>;
|
|
964
|
-
last_at: Generated<Date>;
|
|
965
|
-
}
|
|
966
|
-
interface TenantComputeAddonsTable {
|
|
967
|
-
id: Generated<string>;
|
|
968
|
-
tenant_id: string;
|
|
969
|
-
memory_mb_delta: Generated<number>;
|
|
970
|
-
cpu_delta: Generated<number | string>;
|
|
971
|
-
storage_mb_delta: Generated<number>;
|
|
972
|
-
effective_from: Generated<Date>;
|
|
973
|
-
effective_until: Date | null;
|
|
974
|
-
stripe_subscription_item_id: string | null;
|
|
975
|
-
created_at: Generated<Date>;
|
|
976
|
-
}
|
|
977
821
|
interface AccountSpendCapsTable {
|
|
978
822
|
account_id: string;
|
|
979
823
|
monthly_cap_cents: number | null;
|
|
@@ -997,20 +841,6 @@ interface AccountCreditsTable {
|
|
|
997
841
|
refill_last_at: Date | null;
|
|
998
842
|
updated_at: Generated<Date>;
|
|
999
843
|
}
|
|
1000
|
-
type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
|
|
1001
|
-
type ProvisioningAuditStatus = "ok" | "error";
|
|
1002
|
-
interface ProvisioningAuditLogTable {
|
|
1003
|
-
id: Generated<string>;
|
|
1004
|
-
tenant_id: string | null;
|
|
1005
|
-
tenant_slug: string | null;
|
|
1006
|
-
account_id: string | null;
|
|
1007
|
-
actor: string;
|
|
1008
|
-
event: ProvisioningAuditEvent;
|
|
1009
|
-
status: ProvisioningAuditStatus;
|
|
1010
|
-
detail: unknown | null;
|
|
1011
|
-
error: string | null;
|
|
1012
|
-
created_at: Generated<Date>;
|
|
1013
|
-
}
|
|
1014
844
|
type Subgraph = Selectable<SubgraphsTable>;
|
|
1015
845
|
type SubscriptionStatus = "active" | "paused" | "error";
|
|
1016
846
|
/** Polymorphic subscription mode: `subgraph` reacts to processed table rows;
|
|
@@ -1101,24 +931,6 @@ interface TriggerEvaluatorStateTable {
|
|
|
1101
931
|
updated_at: Generated<Date>;
|
|
1102
932
|
}
|
|
1103
933
|
/**
|
|
1104
|
-
* BYO data plane helpers. A subgraph's user-owned Postgres connection string is
|
|
1105
|
-
* stored encrypted at rest in `database_url_enc` (AES-GCM envelope). Plaintext
|
|
1106
|
-
* only exists transiently — at deploy (to encrypt) and at pool construction (to
|
|
1107
|
-
* connect). Never serialize it into API responses.
|
|
1108
|
-
*/
|
|
1109
|
-
declare function encryptDatabaseUrl(url: string): Buffer;
|
|
1110
|
-
/** Decrypt a subgraph's BYO connection string, or null when managed. */
|
|
1111
|
-
declare function subgraphDatabaseUrl(subgraph: Subgraph): string | null;
|
|
1112
|
-
/** True when the subgraph writes/serves from a user-owned DB. */
|
|
1113
|
-
declare function isByoSubgraph(subgraph: Subgraph): boolean;
|
|
1114
|
-
/**
|
|
1115
|
-
* Resolve the Kysely instance a subgraph's data plane lives on: the user's DB
|
|
1116
|
-
* when BYO, else the managed target DB. Pools are cached by URL in db/index.ts.
|
|
1117
|
-
*/
|
|
1118
|
-
declare function resolveSubgraphDb(subgraph: Subgraph): Kysely<Database>;
|
|
1119
|
-
/** Raw postgres.js client for a subgraph's data plane (DDL / serving queries). */
|
|
1120
|
-
declare function resolveSubgraphRawClient(subgraph: Subgraph): ReturnType<typeof postgres>;
|
|
1121
|
-
/**
|
|
1122
934
|
* Convert a subgraph name to its PostgreSQL schema name (legacy form).
|
|
1123
935
|
* Pre shared-rip every tenant DB had its own schema namespace so disambiguation
|
|
1124
936
|
* was implicit. Kept for oss mode (single-tenant) and legacy-row fallback.
|
|
@@ -1145,8 +957,6 @@ declare function registerSubgraph(db: Kysely<Database>, data: {
|
|
|
1145
957
|
startBlock?: number
|
|
1146
958
|
handlerCode?: string
|
|
1147
959
|
sourceCode?: string
|
|
1148
|
-
/** BYO data plane: encrypted user-DB connection string, or null = managed. */
|
|
1149
|
-
databaseUrlEnc?: Buffer | null
|
|
1150
960
|
}): Promise<Subgraph>;
|
|
1151
961
|
declare function getSubgraph(db: Kysely<Database>, name: string, accountId?: string): Promise<Subgraph | null>;
|
|
1152
962
|
declare function listSubgraphs(db: Kysely<Database>, accountId?: string): Promise<Subgraph[]>;
|
|
@@ -1233,4 +1043,4 @@ declare function recordLiveProgress(db: Kysely<Database>, name: string, lastProc
|
|
|
1233
1043
|
* forward write between the rewind and the fork block's reprocess. Do not
|
|
1234
1044
|
* call this from the live catch-up path. */
|
|
1235
1045
|
declare function rewindLiveProgress(db: Kysely<Database>, name: string, lastProcessedBlock: number): Promise<void>;
|
|
1236
|
-
export { updateSubgraphVisibility, updateSubgraphStatus, updateSubgraphHandlerPath, updateSubgraphExpiry,
|
|
1046
|
+
export { updateSubgraphVisibility, updateSubgraphStatus, updateSubgraphHandlerPath, updateSubgraphExpiry, rewindLiveProgress, registerSubgraph, recordSubgraphProcessed, recordLiveProgress, recordLiveError, pgSchemaNameFor, pgSchemaName, localSubgraphAccountId, listSubgraphs, getSubgraph, findPublicSubgraphByName, deleteSubgraph };
|