@secondlayer/shared 6.30.0 → 6.31.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/db/index.d.ts +9 -26
- package/dist/src/db/index.js +1 -3
- package/dist/src/db/index.js.map +3 -3
- package/dist/src/db/queries/chain-reorgs.d.ts +8 -18
- package/dist/src/db/queries/chain-reorgs.js +1 -3
- package/dist/src/db/queries/chain-reorgs.js.map +3 -3
- package/dist/src/db/queries/contracts.d.ts +8 -18
- package/dist/src/db/queries/integrity.d.ts +8 -18
- package/dist/src/db/queries/subgraph-gaps.d.ts +8 -18
- package/dist/src/db/queries/subgraph-operations.d.ts +19 -19
- package/dist/src/db/queries/subgraph-operations.js +96 -6
- package/dist/src/db/queries/subgraph-operations.js.map +3 -3
- package/dist/src/db/queries/subgraphs.d.ts +8 -18
- package/dist/src/db/queries/subgraphs.js +1 -3
- package/dist/src/db/queries/subgraphs.js.map +3 -3
- package/dist/src/db/queries/subscriptions.d.ts +8 -18
- package/dist/src/db/schema.d.ts +9 -24
- package/dist/src/index.d.ts +25 -28
- package/dist/src/index.js +1 -3
- package/dist/src/index.js.map +4 -4
- package/dist/src/node/local-client.d.ts +8 -18
- package/dist/src/schemas/index.d.ts +16 -2
- package/dist/src/schemas/index.js.map +1 -1
- package/dist/src/schemas/subgraphs.d.ts +16 -2
- package/dist/src/schemas/subgraphs.js.map +1 -1
- package/dist/src/subgraphs/spec.d.ts +14 -1
- package/migrations/0097_drop_chat_sessions.ts +48 -0
- package/migrations/0098_operation_weights.ts +52 -0
- package/package.json +1 -1
|
@@ -116,6 +116,8 @@ interface SubgraphsTable {
|
|
|
116
116
|
visibility: Generated<string>;
|
|
117
117
|
/** Paid (wallet-ghost) deploys expire unless renewed or claimed; NULL = no expiry. */
|
|
118
118
|
expires_at: Date | null;
|
|
119
|
+
/** (event type, contract) probe pairs persisted at deploy for weight classification. */
|
|
120
|
+
sparse_probe_targets: unknown | null;
|
|
119
121
|
database_url_enc: ColumnType<Buffer | null, Buffer | null | undefined, Buffer | null>;
|
|
120
122
|
created_at: Generated<Date>;
|
|
121
123
|
updated_at: Generated<Date>;
|
|
@@ -162,6 +164,12 @@ interface SubgraphOperationsTable {
|
|
|
162
164
|
error: string | null;
|
|
163
165
|
created_at: Generated<Date>;
|
|
164
166
|
updated_at: Generated<Date>;
|
|
167
|
+
/** 'light' (contract-scoped sparse) | 'heavy' (broad). Claim budgets heavy. */
|
|
168
|
+
weight: Generated<string>;
|
|
169
|
+
/** Candidate-event denominator computed at enqueue (sparse ops only). */
|
|
170
|
+
estimated_events: string | number | bigint | null;
|
|
171
|
+
/** Events processed so far — written by the progress flush. */
|
|
172
|
+
processed_events: string | number | bigint | null;
|
|
165
173
|
}
|
|
166
174
|
interface ApiKeysTable {
|
|
167
175
|
id: Generated<string>;
|
|
@@ -336,22 +344,6 @@ interface TeamInvitationsTable {
|
|
|
336
344
|
accepted_at: Date | null;
|
|
337
345
|
created_at: Generated<Date>;
|
|
338
346
|
}
|
|
339
|
-
interface ChatSessionsTable {
|
|
340
|
-
id: Generated<string>;
|
|
341
|
-
account_id: string;
|
|
342
|
-
title: string | null;
|
|
343
|
-
summary: unknown | null;
|
|
344
|
-
created_at: Generated<Date>;
|
|
345
|
-
updated_at: Generated<Date>;
|
|
346
|
-
}
|
|
347
|
-
interface ChatMessagesTable {
|
|
348
|
-
id: Generated<string>;
|
|
349
|
-
chat_session_id: string;
|
|
350
|
-
role: string;
|
|
351
|
-
parts: unknown;
|
|
352
|
-
metadata: unknown | null;
|
|
353
|
-
created_at: Generated<Date>;
|
|
354
|
-
}
|
|
355
347
|
interface ProcessedStripeEventsTable {
|
|
356
348
|
event_id: string;
|
|
357
349
|
event_type: string;
|
|
@@ -649,8 +641,6 @@ interface Database {
|
|
|
649
641
|
projects: ProjectsTable;
|
|
650
642
|
team_members: TeamMembersTable;
|
|
651
643
|
team_invitations: TeamInvitationsTable;
|
|
652
|
-
chat_sessions: ChatSessionsTable;
|
|
653
|
-
chat_messages: ChatMessagesTable;
|
|
654
644
|
processed_stripe_events: ProcessedStripeEventsTable;
|
|
655
645
|
tenants: TenantsTable;
|
|
656
646
|
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
@@ -116,6 +116,8 @@ interface SubgraphsTable {
|
|
|
116
116
|
visibility: Generated<string>;
|
|
117
117
|
/** Paid (wallet-ghost) deploys expire unless renewed or claimed; NULL = no expiry. */
|
|
118
118
|
expires_at: Date | null;
|
|
119
|
+
/** (event type, contract) probe pairs persisted at deploy for weight classification. */
|
|
120
|
+
sparse_probe_targets: unknown | null;
|
|
119
121
|
database_url_enc: ColumnType<Buffer | null, Buffer | null | undefined, Buffer | null>;
|
|
120
122
|
created_at: Generated<Date>;
|
|
121
123
|
updated_at: Generated<Date>;
|
|
@@ -162,6 +164,12 @@ interface SubgraphOperationsTable {
|
|
|
162
164
|
error: string | null;
|
|
163
165
|
created_at: Generated<Date>;
|
|
164
166
|
updated_at: Generated<Date>;
|
|
167
|
+
/** 'light' (contract-scoped sparse) | 'heavy' (broad). Claim budgets heavy. */
|
|
168
|
+
weight: Generated<string>;
|
|
169
|
+
/** Candidate-event denominator computed at enqueue (sparse ops only). */
|
|
170
|
+
estimated_events: string | number | bigint | null;
|
|
171
|
+
/** Events processed so far — written by the progress flush. */
|
|
172
|
+
processed_events: string | number | bigint | null;
|
|
165
173
|
}
|
|
166
174
|
interface ApiKeysTable {
|
|
167
175
|
id: Generated<string>;
|
|
@@ -336,22 +344,6 @@ interface TeamInvitationsTable {
|
|
|
336
344
|
accepted_at: Date | null;
|
|
337
345
|
created_at: Generated<Date>;
|
|
338
346
|
}
|
|
339
|
-
interface ChatSessionsTable {
|
|
340
|
-
id: Generated<string>;
|
|
341
|
-
account_id: string;
|
|
342
|
-
title: string | null;
|
|
343
|
-
summary: unknown | null;
|
|
344
|
-
created_at: Generated<Date>;
|
|
345
|
-
updated_at: Generated<Date>;
|
|
346
|
-
}
|
|
347
|
-
interface ChatMessagesTable {
|
|
348
|
-
id: Generated<string>;
|
|
349
|
-
chat_session_id: string;
|
|
350
|
-
role: string;
|
|
351
|
-
parts: unknown;
|
|
352
|
-
metadata: unknown | null;
|
|
353
|
-
created_at: Generated<Date>;
|
|
354
|
-
}
|
|
355
347
|
interface ProcessedStripeEventsTable {
|
|
356
348
|
event_id: string;
|
|
357
349
|
event_type: string;
|
|
@@ -649,8 +641,6 @@ interface Database {
|
|
|
649
641
|
projects: ProjectsTable;
|
|
650
642
|
team_members: TeamMembersTable;
|
|
651
643
|
team_invitations: TeamInvitationsTable;
|
|
652
|
-
chat_sessions: ChatSessionsTable;
|
|
653
|
-
chat_messages: ChatMessagesTable;
|
|
654
644
|
processed_stripe_events: ProcessedStripeEventsTable;
|
|
655
645
|
tenants: TenantsTable;
|
|
656
646
|
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
@@ -116,6 +116,8 @@ interface SubgraphsTable {
|
|
|
116
116
|
visibility: Generated<string>;
|
|
117
117
|
/** Paid (wallet-ghost) deploys expire unless renewed or claimed; NULL = no expiry. */
|
|
118
118
|
expires_at: Date | null;
|
|
119
|
+
/** (event type, contract) probe pairs persisted at deploy for weight classification. */
|
|
120
|
+
sparse_probe_targets: unknown | null;
|
|
119
121
|
database_url_enc: ColumnType<Buffer | null, Buffer | null | undefined, Buffer | null>;
|
|
120
122
|
created_at: Generated<Date>;
|
|
121
123
|
updated_at: Generated<Date>;
|
|
@@ -162,6 +164,12 @@ interface SubgraphOperationsTable {
|
|
|
162
164
|
error: string | null;
|
|
163
165
|
created_at: Generated<Date>;
|
|
164
166
|
updated_at: Generated<Date>;
|
|
167
|
+
/** 'light' (contract-scoped sparse) | 'heavy' (broad). Claim budgets heavy. */
|
|
168
|
+
weight: Generated<string>;
|
|
169
|
+
/** Candidate-event denominator computed at enqueue (sparse ops only). */
|
|
170
|
+
estimated_events: string | number | bigint | null;
|
|
171
|
+
/** Events processed so far — written by the progress flush. */
|
|
172
|
+
processed_events: string | number | bigint | null;
|
|
165
173
|
}
|
|
166
174
|
interface ApiKeysTable {
|
|
167
175
|
id: Generated<string>;
|
|
@@ -336,22 +344,6 @@ interface TeamInvitationsTable {
|
|
|
336
344
|
accepted_at: Date | null;
|
|
337
345
|
created_at: Generated<Date>;
|
|
338
346
|
}
|
|
339
|
-
interface ChatSessionsTable {
|
|
340
|
-
id: Generated<string>;
|
|
341
|
-
account_id: string;
|
|
342
|
-
title: string | null;
|
|
343
|
-
summary: unknown | null;
|
|
344
|
-
created_at: Generated<Date>;
|
|
345
|
-
updated_at: Generated<Date>;
|
|
346
|
-
}
|
|
347
|
-
interface ChatMessagesTable {
|
|
348
|
-
id: Generated<string>;
|
|
349
|
-
chat_session_id: string;
|
|
350
|
-
role: string;
|
|
351
|
-
parts: unknown;
|
|
352
|
-
metadata: unknown | null;
|
|
353
|
-
created_at: Generated<Date>;
|
|
354
|
-
}
|
|
355
347
|
interface ProcessedStripeEventsTable {
|
|
356
348
|
event_id: string;
|
|
357
349
|
event_type: string;
|
|
@@ -649,8 +641,6 @@ interface Database {
|
|
|
649
641
|
projects: ProjectsTable;
|
|
650
642
|
team_members: TeamMembersTable;
|
|
651
643
|
team_invitations: TeamInvitationsTable;
|
|
652
|
-
chat_sessions: ChatSessionsTable;
|
|
653
|
-
chat_messages: ChatMessagesTable;
|
|
654
644
|
processed_stripe_events: ProcessedStripeEventsTable;
|
|
655
645
|
tenants: TenantsTable;
|
|
656
646
|
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
@@ -116,6 +116,8 @@ interface SubgraphsTable {
|
|
|
116
116
|
visibility: Generated<string>;
|
|
117
117
|
/** Paid (wallet-ghost) deploys expire unless renewed or claimed; NULL = no expiry. */
|
|
118
118
|
expires_at: Date | null;
|
|
119
|
+
/** (event type, contract) probe pairs persisted at deploy for weight classification. */
|
|
120
|
+
sparse_probe_targets: unknown | null;
|
|
119
121
|
database_url_enc: ColumnType<Buffer | null, Buffer | null | undefined, Buffer | null>;
|
|
120
122
|
created_at: Generated<Date>;
|
|
121
123
|
updated_at: Generated<Date>;
|
|
@@ -162,6 +164,12 @@ interface SubgraphOperationsTable {
|
|
|
162
164
|
error: string | null;
|
|
163
165
|
created_at: Generated<Date>;
|
|
164
166
|
updated_at: Generated<Date>;
|
|
167
|
+
/** 'light' (contract-scoped sparse) | 'heavy' (broad). Claim budgets heavy. */
|
|
168
|
+
weight: Generated<string>;
|
|
169
|
+
/** Candidate-event denominator computed at enqueue (sparse ops only). */
|
|
170
|
+
estimated_events: string | number | bigint | null;
|
|
171
|
+
/** Events processed so far — written by the progress flush. */
|
|
172
|
+
processed_events: string | number | bigint | null;
|
|
165
173
|
}
|
|
166
174
|
interface ApiKeysTable {
|
|
167
175
|
id: Generated<string>;
|
|
@@ -336,22 +344,6 @@ interface TeamInvitationsTable {
|
|
|
336
344
|
accepted_at: Date | null;
|
|
337
345
|
created_at: Generated<Date>;
|
|
338
346
|
}
|
|
339
|
-
interface ChatSessionsTable {
|
|
340
|
-
id: Generated<string>;
|
|
341
|
-
account_id: string;
|
|
342
|
-
title: string | null;
|
|
343
|
-
summary: unknown | null;
|
|
344
|
-
created_at: Generated<Date>;
|
|
345
|
-
updated_at: Generated<Date>;
|
|
346
|
-
}
|
|
347
|
-
interface ChatMessagesTable {
|
|
348
|
-
id: Generated<string>;
|
|
349
|
-
chat_session_id: string;
|
|
350
|
-
role: string;
|
|
351
|
-
parts: unknown;
|
|
352
|
-
metadata: unknown | null;
|
|
353
|
-
created_at: Generated<Date>;
|
|
354
|
-
}
|
|
355
347
|
interface ProcessedStripeEventsTable {
|
|
356
348
|
event_id: string;
|
|
357
349
|
event_type: string;
|
|
@@ -649,8 +641,6 @@ interface Database {
|
|
|
649
641
|
projects: ProjectsTable;
|
|
650
642
|
team_members: TeamMembersTable;
|
|
651
643
|
team_invitations: TeamInvitationsTable;
|
|
652
|
-
chat_sessions: ChatSessionsTable;
|
|
653
|
-
chat_messages: ChatMessagesTable;
|
|
654
644
|
processed_stripe_events: ProcessedStripeEventsTable;
|
|
655
645
|
tenants: TenantsTable;
|
|
656
646
|
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
@@ -882,6 +872,9 @@ declare function createSubgraphOperation(db: Kysely<Database>, data: {
|
|
|
882
872
|
kind: SubgraphOperationKind
|
|
883
873
|
fromBlock?: number
|
|
884
874
|
toBlock?: number
|
|
875
|
+
/** 'light' | 'heavy' — claim budgets heavy ops. DB default 'heavy'. */
|
|
876
|
+
weight?: "light" | "heavy"
|
|
877
|
+
estimatedEvents?: number | null
|
|
885
878
|
}): Promise<SubgraphOperation>;
|
|
886
879
|
declare function findActiveSubgraphOperation(db: Kysely<Database>, subgraphId: string): Promise<SubgraphOperation | null>;
|
|
887
880
|
declare function requestSubgraphOperationCancel(db: Kysely<Database>, subgraphId: string): Promise<SubgraphOperation | null>;
|
|
@@ -906,4 +899,11 @@ declare function listSubgraphOperations(db: Kysely<Database>, subgraphId: string
|
|
|
906
899
|
declare function completeSubgraphOperation(db: Kysely<Database>, operationId: string, lockedBy: string, processedBlocks: number): Promise<void>;
|
|
907
900
|
declare function cancelSubgraphOperation(db: Kysely<Database>, operationId: string, lockedBy: string, processedBlocks: number): Promise<void>;
|
|
908
901
|
declare function failSubgraphOperation(db: Kysely<Database>, operationId: string, lockedBy: string, error: string, processedBlocks?: number): Promise<void>;
|
|
909
|
-
|
|
902
|
+
/**\\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*/
|
|
903
|
+
declare function getOperationQueuePosition(db: Kysely<Database>, operationId: string): Promise<number | null>;
|
|
904
|
+
/** Median duration (seconds) of the last 20 completed ops of a weight class —
|
|
905
|
+
* the "est. start" multiplier for queued positions. Null with no history. */
|
|
906
|
+
declare function getRecentOperationMedianDuration(db: Kysely<Database>, weight: "light" | "heavy"): Promise<number | null>;
|
|
907
|
+
/** Progress-flush hook: events processed so far on a running operation. */
|
|
908
|
+
declare function updateOperationProcessedEvents(db: Kysely<Database>, operationId: string, processedEvents: number): Promise<void>;
|
|
909
|
+
export { waitForSubgraphOperationsClear, updateOperationProcessedEvents, requestSubgraphOperationsCancelForDelete, requestSubgraphOperationCancel, listSubgraphOperations, isActiveSubgraphOperationConflict, heartbeatSubgraphOperation, getSubgraphOperation, getRecentOperationMedianDuration, getOperationQueuePosition, findActiveSubgraphOperation, failSubgraphOperation, createSubgraphOperation, completeSubgraphOperation, claimSubgraphOperation, cancelSubgraphOperation };
|
|
@@ -30,7 +30,9 @@ async function createSubgraphOperation(db, data) {
|
|
|
30
30
|
account_id: data.accountId ?? null,
|
|
31
31
|
kind: data.kind,
|
|
32
32
|
from_block: data.fromBlock ?? null,
|
|
33
|
-
to_block: data.toBlock ?? null
|
|
33
|
+
to_block: data.toBlock ?? null,
|
|
34
|
+
...data.weight ? { weight: data.weight } : {},
|
|
35
|
+
estimated_events: data.estimatedEvents ?? null
|
|
34
36
|
}).returningAll().executeTakeFirstOrThrow();
|
|
35
37
|
}
|
|
36
38
|
async function findActiveSubgraphOperation(db, subgraphId) {
|
|
@@ -54,7 +56,12 @@ async function waitForSubgraphOperationsClear(db, subgraphId, opts) {
|
|
|
54
56
|
}
|
|
55
57
|
return false;
|
|
56
58
|
}
|
|
59
|
+
function resolveHeavyOpBudget() {
|
|
60
|
+
const parsed = Number.parseInt(process.env.SUBGRAPH_HEAVY_OP_BUDGET ?? "2", 10);
|
|
61
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 2;
|
|
62
|
+
}
|
|
57
63
|
async function claimSubgraphOperation(db, lockedBy) {
|
|
64
|
+
const heavyBudget = resolveHeavyOpBudget();
|
|
58
65
|
const result = await sql`
|
|
59
66
|
UPDATE subgraph_operations
|
|
60
67
|
SET
|
|
@@ -72,14 +79,40 @@ async function claimSubgraphOperation(db, lockedBy) {
|
|
|
72
79
|
WHERE status = 'running'
|
|
73
80
|
GROUP BY account_id
|
|
74
81
|
) rc ON so.account_id = rc.account_id
|
|
82
|
+
LEFT JOIN accounts a ON a.id::text = so.account_id
|
|
75
83
|
WHERE
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
(
|
|
85
|
+
so.status = 'queued'
|
|
86
|
+
OR (
|
|
87
|
+
so.status = 'running'
|
|
88
|
+
AND (so.locked_until IS NULL OR so.locked_until < now())
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
-- Heavy budget as an eligibility FILTER (not a post-claim refusal):
|
|
92
|
+
-- a budget-blocked heavy op at the head must not starve the light
|
|
93
|
+
-- ops behind it. Live-lock condition (locked_until > now()) keeps a
|
|
94
|
+
-- STALE heavy op from blocking its own reclaim. Soft across
|
|
95
|
+
-- concurrent claimers (can overshoot by one with multiple runners) —
|
|
96
|
+
-- acceptable for the single-runner deployment.
|
|
97
|
+
AND (
|
|
98
|
+
so.weight = 'light'
|
|
99
|
+
OR (
|
|
100
|
+
SELECT COUNT(*)
|
|
101
|
+
FROM subgraph_operations h
|
|
102
|
+
WHERE h.status = 'running'
|
|
103
|
+
AND h.weight = 'heavy'
|
|
104
|
+
AND h.locked_until > now()
|
|
105
|
+
AND h.id != so.id
|
|
106
|
+
) < ${heavyBudget}
|
|
80
107
|
)
|
|
81
108
|
ORDER BY
|
|
82
109
|
COALESCE(rc.cnt, 0) ASC,
|
|
110
|
+
CASE COALESCE(a.plan, 'none')
|
|
111
|
+
WHEN 'enterprise' THEN 0
|
|
112
|
+
WHEN 'scale' THEN 1
|
|
113
|
+
WHEN 'launch' THEN 2
|
|
114
|
+
ELSE 3
|
|
115
|
+
END,
|
|
83
116
|
CASE WHEN so.status = 'queued' THEN 0 ELSE 1 END,
|
|
84
117
|
so.created_at ASC
|
|
85
118
|
FOR UPDATE OF so SKIP LOCKED
|
|
@@ -132,14 +165,71 @@ async function failSubgraphOperation(db, operationId, lockedBy, error, processed
|
|
|
132
165
|
updated_at: new Date
|
|
133
166
|
}).where("id", "=", operationId).where("locked_by", "=", lockedBy).execute();
|
|
134
167
|
}
|
|
168
|
+
async function getOperationQueuePosition(db, operationId) {
|
|
169
|
+
const result = await sql`
|
|
170
|
+
WITH candidates AS (
|
|
171
|
+
SELECT
|
|
172
|
+
so.id,
|
|
173
|
+
ROW_NUMBER() OVER (
|
|
174
|
+
ORDER BY
|
|
175
|
+
COALESCE(rc.cnt, 0) ASC,
|
|
176
|
+
CASE COALESCE(a.plan, 'none')
|
|
177
|
+
WHEN 'enterprise' THEN 0
|
|
178
|
+
WHEN 'scale' THEN 1
|
|
179
|
+
WHEN 'launch' THEN 2
|
|
180
|
+
ELSE 3
|
|
181
|
+
END,
|
|
182
|
+
CASE WHEN so.status = 'queued' THEN 0 ELSE 1 END,
|
|
183
|
+
so.created_at ASC
|
|
184
|
+
) AS rn
|
|
185
|
+
FROM subgraph_operations so
|
|
186
|
+
LEFT JOIN (
|
|
187
|
+
SELECT account_id, COUNT(*) AS cnt
|
|
188
|
+
FROM subgraph_operations
|
|
189
|
+
WHERE status = 'running'
|
|
190
|
+
GROUP BY account_id
|
|
191
|
+
) rc ON so.account_id = rc.account_id
|
|
192
|
+
LEFT JOIN accounts a ON a.id::text = so.account_id
|
|
193
|
+
WHERE so.status = 'queued'
|
|
194
|
+
)
|
|
195
|
+
SELECT rn FROM candidates WHERE id = ${operationId}
|
|
196
|
+
`.execute(db);
|
|
197
|
+
const rn = result.rows[0]?.rn;
|
|
198
|
+
return rn == null ? null : Number(rn);
|
|
199
|
+
}
|
|
200
|
+
async function getRecentOperationMedianDuration(db, weight) {
|
|
201
|
+
const result = await sql`
|
|
202
|
+
SELECT percentile_cont(0.5) WITHIN GROUP (
|
|
203
|
+
ORDER BY EXTRACT(EPOCH FROM (finished_at - started_at))
|
|
204
|
+
) AS median
|
|
205
|
+
FROM (
|
|
206
|
+
SELECT started_at, finished_at
|
|
207
|
+
FROM subgraph_operations
|
|
208
|
+
WHERE status = 'completed'
|
|
209
|
+
AND weight = ${weight}
|
|
210
|
+
AND started_at IS NOT NULL
|
|
211
|
+
AND finished_at IS NOT NULL
|
|
212
|
+
ORDER BY finished_at DESC
|
|
213
|
+
LIMIT 20
|
|
214
|
+
) recent
|
|
215
|
+
`.execute(db);
|
|
216
|
+
const median = result.rows[0]?.median;
|
|
217
|
+
return median == null ? null : Number(median);
|
|
218
|
+
}
|
|
219
|
+
async function updateOperationProcessedEvents(db, operationId, processedEvents) {
|
|
220
|
+
await db.updateTable("subgraph_operations").set({ processed_events: processedEvents, updated_at: new Date }).where("id", "=", operationId).execute();
|
|
221
|
+
}
|
|
135
222
|
export {
|
|
136
223
|
waitForSubgraphOperationsClear,
|
|
224
|
+
updateOperationProcessedEvents,
|
|
137
225
|
requestSubgraphOperationsCancelForDelete,
|
|
138
226
|
requestSubgraphOperationCancel,
|
|
139
227
|
listSubgraphOperations,
|
|
140
228
|
isActiveSubgraphOperationConflict,
|
|
141
229
|
heartbeatSubgraphOperation,
|
|
142
230
|
getSubgraphOperation,
|
|
231
|
+
getRecentOperationMedianDuration,
|
|
232
|
+
getOperationQueuePosition,
|
|
143
233
|
findActiveSubgraphOperation,
|
|
144
234
|
failSubgraphOperation,
|
|
145
235
|
createSubgraphOperation,
|
|
@@ -148,5 +238,5 @@ export {
|
|
|
148
238
|
cancelSubgraphOperation
|
|
149
239
|
};
|
|
150
240
|
|
|
151
|
-
//# debugId=
|
|
241
|
+
//# debugId=07A2F028289EB86864756E2164756E21
|
|
152
242
|
//# 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},\n): Promise<SubgraphOperation> {\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})\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\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 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\tso.status = 'queued'\n\t\t\t\tOR (\n\t\t\t\t\tso.status = 'running'\n\t\t\t\t\tAND (so.locked_until IS NULL OR so.locked_until < now())\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"
|
|
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\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})\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\tawait db\n\t\t.updateTable(\"subgraph_operations\")\n\t\t.set({ processed_events: processedEvents, updated_at: new Date() })\n\t\t.where(\"id\", \"=\", operationId)\n\t\t.execute();\n}\n"
|
|
6
6
|
],
|
|
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,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "07A2F028289EB86864756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -117,6 +117,8 @@ interface SubgraphsTable {
|
|
|
117
117
|
visibility: Generated<string>;
|
|
118
118
|
/** Paid (wallet-ghost) deploys expire unless renewed or claimed; NULL = no expiry. */
|
|
119
119
|
expires_at: Date | null;
|
|
120
|
+
/** (event type, contract) probe pairs persisted at deploy for weight classification. */
|
|
121
|
+
sparse_probe_targets: unknown | null;
|
|
120
122
|
database_url_enc: ColumnType<Buffer | null, Buffer | null | undefined, Buffer | null>;
|
|
121
123
|
created_at: Generated<Date>;
|
|
122
124
|
updated_at: Generated<Date>;
|
|
@@ -163,6 +165,12 @@ interface SubgraphOperationsTable {
|
|
|
163
165
|
error: string | null;
|
|
164
166
|
created_at: Generated<Date>;
|
|
165
167
|
updated_at: Generated<Date>;
|
|
168
|
+
/** 'light' (contract-scoped sparse) | 'heavy' (broad). Claim budgets heavy. */
|
|
169
|
+
weight: Generated<string>;
|
|
170
|
+
/** Candidate-event denominator computed at enqueue (sparse ops only). */
|
|
171
|
+
estimated_events: string | number | bigint | null;
|
|
172
|
+
/** Events processed so far — written by the progress flush. */
|
|
173
|
+
processed_events: string | number | bigint | null;
|
|
166
174
|
}
|
|
167
175
|
interface ApiKeysTable {
|
|
168
176
|
id: Generated<string>;
|
|
@@ -337,22 +345,6 @@ interface TeamInvitationsTable {
|
|
|
337
345
|
accepted_at: Date | null;
|
|
338
346
|
created_at: Generated<Date>;
|
|
339
347
|
}
|
|
340
|
-
interface ChatSessionsTable {
|
|
341
|
-
id: Generated<string>;
|
|
342
|
-
account_id: string;
|
|
343
|
-
title: string | null;
|
|
344
|
-
summary: unknown | null;
|
|
345
|
-
created_at: Generated<Date>;
|
|
346
|
-
updated_at: Generated<Date>;
|
|
347
|
-
}
|
|
348
|
-
interface ChatMessagesTable {
|
|
349
|
-
id: Generated<string>;
|
|
350
|
-
chat_session_id: string;
|
|
351
|
-
role: string;
|
|
352
|
-
parts: unknown;
|
|
353
|
-
metadata: unknown | null;
|
|
354
|
-
created_at: Generated<Date>;
|
|
355
|
-
}
|
|
356
348
|
interface ProcessedStripeEventsTable {
|
|
357
349
|
event_id: string;
|
|
358
350
|
event_type: string;
|
|
@@ -650,8 +642,6 @@ interface Database {
|
|
|
650
642
|
projects: ProjectsTable;
|
|
651
643
|
team_members: TeamMembersTable;
|
|
652
644
|
team_invitations: TeamInvitationsTable;
|
|
653
|
-
chat_sessions: ChatSessionsTable;
|
|
654
|
-
chat_messages: ChatMessagesTable;
|
|
655
645
|
processed_stripe_events: ProcessedStripeEventsTable;
|
|
656
646
|
tenants: TenantsTable;
|
|
657
647
|
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
@@ -687,8 +687,6 @@ var TABLE_TO_DB = {
|
|
|
687
687
|
projects: "target",
|
|
688
688
|
team_members: "target",
|
|
689
689
|
team_invitations: "target",
|
|
690
|
-
chat_sessions: "target",
|
|
691
|
-
chat_messages: "target",
|
|
692
690
|
subscriptions: "target",
|
|
693
691
|
subscription_outbox: "target",
|
|
694
692
|
subscription_deliveries: "target",
|
|
@@ -1131,5 +1129,5 @@ export {
|
|
|
1131
1129
|
deleteSubgraph
|
|
1132
1130
|
};
|
|
1133
1131
|
|
|
1134
|
-
//# debugId=
|
|
1132
|
+
//# debugId=6E70C885DAD9B44864756E2164756E21
|
|
1135
1133
|
//# sourceMappingURL=subgraphs.js.map
|