@secondlayer/shared 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/crypto/hmac.js +2 -2
- package/dist/src/crypto/hmac.js.map +3 -3
- package/dist/src/db/index.d.ts +15 -1
- package/dist/src/db/index.js +5 -3
- package/dist/src/db/index.js.map +4 -4
- package/dist/src/db/jsonb.js.map +2 -2
- package/dist/src/db/queries/accounts.d.ts +12 -0
- package/dist/src/db/queries/accounts.js.map +2 -2
- package/dist/src/db/queries/integrity.d.ts +12 -0
- package/dist/src/db/queries/integrity.js.map +2 -2
- package/dist/src/db/queries/metrics.d.ts +12 -0
- package/dist/src/db/queries/metrics.js.map +2 -2
- package/dist/src/db/queries/subgraph-gaps.d.ts +305 -0
- package/dist/src/db/queries/subgraph-gaps.js +103 -0
- package/dist/src/db/queries/subgraph-gaps.js.map +10 -0
- package/dist/src/db/queries/subgraphs.d.ts +13 -0
- package/dist/src/db/queries/subgraphs.js +4 -2
- package/dist/src/db/queries/subgraphs.js.map +4 -4
- package/dist/src/db/queries/usage.d.ts +12 -0
- package/dist/src/db/queries/usage.js +13 -3
- package/dist/src/db/queries/usage.js.map +4 -4
- package/dist/src/db/schema.d.ts +15 -1
- package/dist/src/env.js.map +2 -2
- package/dist/src/errors.js.map +2 -2
- package/dist/src/index.d.ts +59 -1
- package/dist/src/index.js +12 -8
- package/dist/src/index.js.map +12 -12
- package/dist/src/lib/plans.js.map +2 -2
- package/dist/src/logger.js.map +3 -3
- package/dist/src/node/archive-client.js +22 -6
- package/dist/src/node/archive-client.js.map +5 -5
- package/dist/src/node/client.js.map +2 -2
- package/dist/src/node/hiro-client.js +47 -11
- package/dist/src/node/hiro-client.js.map +5 -5
- package/dist/src/node/hiro-pg-client.js +131 -26
- package/dist/src/node/hiro-pg-client.js.map +3 -3
- package/dist/src/node/local-client.d.ts +12 -0
- package/dist/src/node/local-client.js.map +2 -2
- package/dist/src/queue/index.js +7 -5
- package/dist/src/queue/index.js.map +5 -5
- package/dist/src/queue/listener.js.map +2 -2
- package/dist/src/queue/recovery.js +5 -3
- package/dist/src/queue/recovery.js.map +5 -5
- package/dist/src/schemas/filters.js +2 -2
- package/dist/src/schemas/filters.js.map +3 -3
- package/dist/src/schemas/index.d.ts +45 -1
- package/dist/src/schemas/index.js +5 -3
- package/dist/src/schemas/index.js.map +5 -5
- package/dist/src/schemas/subgraphs.d.ts +45 -1
- package/dist/src/schemas/subgraphs.js.map +2 -2
- package/migrations/0001_initial.ts +295 -159
- package/migrations/0002_api_keys.ts +44 -28
- package/migrations/0003_tenant_isolation.ts +116 -107
- package/migrations/0004_accounts_and_usage.ts +81 -75
- package/migrations/0005_sessions.ts +33 -33
- package/migrations/0006_tx_index.ts +6 -2
- package/migrations/0007_contracts.ts +38 -24
- package/migrations/0008_drop_contracts.ts +33 -19
- package/migrations/0009_waitlist.ts +12 -12
- package/migrations/0010_waitlist_status.ts +5 -5
- package/migrations/0011_account_insights.ts +52 -52
- package/migrations/0012_view_health_snapshots.ts +21 -21
- package/migrations/0013_view_processing_stats.ts +32 -32
- package/migrations/0014_view_table_snapshots.ts +24 -24
- package/migrations/0015_rename_views_to_subgraphs.ts +137 -75
- package/migrations/0016_rename_webhook_to_endpoint.ts +12 -4
- package/migrations/0017_security_hardening.ts +23 -11
- package/migrations/0018_subgraph_gaps.ts +39 -0
- package/package.json +147 -143
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { Kysely } from "kysely";
|
|
2
|
+
import { Generated } from "kysely";
|
|
3
|
+
interface BlocksTable {
|
|
4
|
+
height: number;
|
|
5
|
+
hash: string;
|
|
6
|
+
parent_hash: string;
|
|
7
|
+
burn_block_height: number;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
canonical: Generated<boolean>;
|
|
10
|
+
created_at: Generated<Date>;
|
|
11
|
+
}
|
|
12
|
+
interface TransactionsTable {
|
|
13
|
+
tx_id: string;
|
|
14
|
+
block_height: number;
|
|
15
|
+
tx_index: Generated<number>;
|
|
16
|
+
type: string;
|
|
17
|
+
sender: string;
|
|
18
|
+
status: string;
|
|
19
|
+
contract_id: string | null;
|
|
20
|
+
function_name: string | null;
|
|
21
|
+
raw_tx: string;
|
|
22
|
+
created_at: Generated<Date>;
|
|
23
|
+
}
|
|
24
|
+
interface EventsTable {
|
|
25
|
+
id: Generated<string>;
|
|
26
|
+
tx_id: string;
|
|
27
|
+
block_height: number;
|
|
28
|
+
event_index: number;
|
|
29
|
+
type: string;
|
|
30
|
+
data: unknown;
|
|
31
|
+
created_at: Generated<Date>;
|
|
32
|
+
}
|
|
33
|
+
interface StreamsTable {
|
|
34
|
+
id: Generated<string>;
|
|
35
|
+
name: string;
|
|
36
|
+
status: Generated<string>;
|
|
37
|
+
filters: unknown;
|
|
38
|
+
options: Generated<unknown>;
|
|
39
|
+
endpoint_url: string;
|
|
40
|
+
signing_secret: string | null;
|
|
41
|
+
api_key_id: string;
|
|
42
|
+
created_at: Generated<Date>;
|
|
43
|
+
updated_at: Generated<Date>;
|
|
44
|
+
}
|
|
45
|
+
interface StreamMetricsTable {
|
|
46
|
+
stream_id: string;
|
|
47
|
+
last_triggered_at: Date | null;
|
|
48
|
+
last_triggered_block: number | null;
|
|
49
|
+
total_deliveries: Generated<number>;
|
|
50
|
+
failed_deliveries: Generated<number>;
|
|
51
|
+
error_message: string | null;
|
|
52
|
+
}
|
|
53
|
+
interface JobsTable {
|
|
54
|
+
id: Generated<string>;
|
|
55
|
+
stream_id: string;
|
|
56
|
+
block_height: number;
|
|
57
|
+
status: Generated<string>;
|
|
58
|
+
attempts: Generated<number>;
|
|
59
|
+
locked_at: Date | null;
|
|
60
|
+
locked_by: string | null;
|
|
61
|
+
error: string | null;
|
|
62
|
+
backfill: Generated<boolean>;
|
|
63
|
+
created_at: Generated<Date>;
|
|
64
|
+
completed_at: Date | null;
|
|
65
|
+
}
|
|
66
|
+
interface IndexProgressTable {
|
|
67
|
+
network: string;
|
|
68
|
+
last_indexed_block: Generated<number>;
|
|
69
|
+
last_contiguous_block: Generated<number>;
|
|
70
|
+
highest_seen_block: Generated<number>;
|
|
71
|
+
updated_at: Generated<Date>;
|
|
72
|
+
}
|
|
73
|
+
interface DeliveriesTable {
|
|
74
|
+
id: Generated<string>;
|
|
75
|
+
stream_id: string;
|
|
76
|
+
job_id: string | null;
|
|
77
|
+
block_height: number;
|
|
78
|
+
status: string;
|
|
79
|
+
status_code: number | null;
|
|
80
|
+
response_time_ms: number | null;
|
|
81
|
+
attempts: Generated<number>;
|
|
82
|
+
error: string | null;
|
|
83
|
+
payload: unknown;
|
|
84
|
+
created_at: Generated<Date>;
|
|
85
|
+
}
|
|
86
|
+
interface SubgraphsTable {
|
|
87
|
+
id: Generated<string>;
|
|
88
|
+
name: string;
|
|
89
|
+
version: Generated<string>;
|
|
90
|
+
status: Generated<string>;
|
|
91
|
+
definition: Record<string, unknown>;
|
|
92
|
+
schema_hash: string;
|
|
93
|
+
handler_path: string;
|
|
94
|
+
schema_name: string | null;
|
|
95
|
+
start_block: Generated<number>;
|
|
96
|
+
last_processed_block: Generated<number>;
|
|
97
|
+
last_error: string | null;
|
|
98
|
+
last_error_at: Date | null;
|
|
99
|
+
total_processed: Generated<number>;
|
|
100
|
+
total_errors: Generated<number>;
|
|
101
|
+
api_key_id: string;
|
|
102
|
+
created_at: Generated<Date>;
|
|
103
|
+
updated_at: Generated<Date>;
|
|
104
|
+
}
|
|
105
|
+
interface SubgraphGapsTable {
|
|
106
|
+
id: Generated<string>;
|
|
107
|
+
subgraph_id: string;
|
|
108
|
+
subgraph_name: string;
|
|
109
|
+
gap_start: number;
|
|
110
|
+
gap_end: number;
|
|
111
|
+
reason: string;
|
|
112
|
+
detected_at: Generated<Date>;
|
|
113
|
+
resolved_at: Date | null;
|
|
114
|
+
}
|
|
115
|
+
interface ApiKeysTable {
|
|
116
|
+
id: Generated<string>;
|
|
117
|
+
key_hash: string;
|
|
118
|
+
key_prefix: string;
|
|
119
|
+
name: string | null;
|
|
120
|
+
status: Generated<string>;
|
|
121
|
+
rate_limit: Generated<number>;
|
|
122
|
+
ip_address: string;
|
|
123
|
+
account_id: string;
|
|
124
|
+
last_used_at: Date | null;
|
|
125
|
+
revoked_at: Date | null;
|
|
126
|
+
created_at: Generated<Date>;
|
|
127
|
+
}
|
|
128
|
+
interface AccountsTable {
|
|
129
|
+
id: Generated<string>;
|
|
130
|
+
email: string;
|
|
131
|
+
plan: Generated<string>;
|
|
132
|
+
created_at: Generated<Date>;
|
|
133
|
+
}
|
|
134
|
+
interface SessionsTable {
|
|
135
|
+
id: Generated<string>;
|
|
136
|
+
token_hash: string;
|
|
137
|
+
token_prefix: string;
|
|
138
|
+
account_id: string;
|
|
139
|
+
ip_address: string;
|
|
140
|
+
expires_at: Generated<Date>;
|
|
141
|
+
revoked_at: Date | null;
|
|
142
|
+
last_used_at: Date | null;
|
|
143
|
+
created_at: Generated<Date>;
|
|
144
|
+
}
|
|
145
|
+
interface MagicLinksTable {
|
|
146
|
+
id: Generated<string>;
|
|
147
|
+
email: string;
|
|
148
|
+
token: string;
|
|
149
|
+
expires_at: Date;
|
|
150
|
+
used_at: Date | null;
|
|
151
|
+
failed_attempts: Generated<number>;
|
|
152
|
+
created_at: Generated<Date>;
|
|
153
|
+
}
|
|
154
|
+
interface UsageDailyTable {
|
|
155
|
+
account_id: string;
|
|
156
|
+
date: string;
|
|
157
|
+
api_requests: Generated<number>;
|
|
158
|
+
deliveries: Generated<number>;
|
|
159
|
+
}
|
|
160
|
+
interface UsageSnapshotsTable {
|
|
161
|
+
id: Generated<string>;
|
|
162
|
+
account_id: string;
|
|
163
|
+
measured_at: Generated<Date>;
|
|
164
|
+
storage_bytes: Generated<number>;
|
|
165
|
+
}
|
|
166
|
+
interface WaitlistTable {
|
|
167
|
+
id: Generated<string>;
|
|
168
|
+
email: string;
|
|
169
|
+
source: Generated<string>;
|
|
170
|
+
status: Generated<string>;
|
|
171
|
+
created_at: Generated<Date>;
|
|
172
|
+
}
|
|
173
|
+
interface AccountInsightsTable {
|
|
174
|
+
id: Generated<string>;
|
|
175
|
+
account_id: string;
|
|
176
|
+
category: string;
|
|
177
|
+
insight_type: string;
|
|
178
|
+
resource_id: string | null;
|
|
179
|
+
severity: string;
|
|
180
|
+
title: string;
|
|
181
|
+
body: string;
|
|
182
|
+
data: unknown;
|
|
183
|
+
dismissed_at: Date | null;
|
|
184
|
+
expires_at: Date | null;
|
|
185
|
+
created_at: Generated<Date>;
|
|
186
|
+
}
|
|
187
|
+
interface AccountAgentRunsTable {
|
|
188
|
+
id: Generated<string>;
|
|
189
|
+
account_id: string;
|
|
190
|
+
started_at: Generated<Date>;
|
|
191
|
+
completed_at: Date | null;
|
|
192
|
+
status: Generated<string>;
|
|
193
|
+
input_tokens: Generated<number>;
|
|
194
|
+
output_tokens: Generated<number>;
|
|
195
|
+
cost_usd: Generated<number>;
|
|
196
|
+
insights_created: Generated<number>;
|
|
197
|
+
error: string | null;
|
|
198
|
+
}
|
|
199
|
+
interface SubgraphProcessingStatsTable {
|
|
200
|
+
id: Generated<string>;
|
|
201
|
+
subgraph_name: string;
|
|
202
|
+
api_key_id: string | null;
|
|
203
|
+
bucket_start: Date | null;
|
|
204
|
+
bucket_end: Date | null;
|
|
205
|
+
blocks_processed: number | null;
|
|
206
|
+
total_time_ms: number | null;
|
|
207
|
+
handler_time_ms: number | null;
|
|
208
|
+
flush_time_ms: number | null;
|
|
209
|
+
max_block_time_ms: number | null;
|
|
210
|
+
max_handler_time_ms: number | null;
|
|
211
|
+
avg_ops_per_block: number | null;
|
|
212
|
+
is_catchup: Generated<boolean>;
|
|
213
|
+
created_at: Generated<Date>;
|
|
214
|
+
}
|
|
215
|
+
interface SubgraphTableSnapshotsTable {
|
|
216
|
+
id: Generated<string>;
|
|
217
|
+
subgraph_name: string;
|
|
218
|
+
api_key_id: string | null;
|
|
219
|
+
table_name: string;
|
|
220
|
+
row_count: number | null;
|
|
221
|
+
created_at: Generated<Date>;
|
|
222
|
+
}
|
|
223
|
+
interface SubgraphHealthSnapshotsTable {
|
|
224
|
+
id: Generated<string>;
|
|
225
|
+
subgraph_id: string;
|
|
226
|
+
total_processed: number;
|
|
227
|
+
total_errors: number;
|
|
228
|
+
last_processed_block: number | null;
|
|
229
|
+
captured_at: Generated<Date>;
|
|
230
|
+
}
|
|
231
|
+
interface Database {
|
|
232
|
+
blocks: BlocksTable;
|
|
233
|
+
transactions: TransactionsTable;
|
|
234
|
+
events: EventsTable;
|
|
235
|
+
streams: StreamsTable;
|
|
236
|
+
stream_metrics: StreamMetricsTable;
|
|
237
|
+
jobs: JobsTable;
|
|
238
|
+
index_progress: IndexProgressTable;
|
|
239
|
+
deliveries: DeliveriesTable;
|
|
240
|
+
subgraphs: SubgraphsTable;
|
|
241
|
+
api_keys: ApiKeysTable;
|
|
242
|
+
accounts: AccountsTable;
|
|
243
|
+
sessions: SessionsTable;
|
|
244
|
+
magic_links: MagicLinksTable;
|
|
245
|
+
usage_daily: UsageDailyTable;
|
|
246
|
+
usage_snapshots: UsageSnapshotsTable;
|
|
247
|
+
waitlist: WaitlistTable;
|
|
248
|
+
account_insights: AccountInsightsTable;
|
|
249
|
+
account_agent_runs: AccountAgentRunsTable;
|
|
250
|
+
subgraph_health_snapshots: SubgraphHealthSnapshotsTable;
|
|
251
|
+
subgraph_processing_stats: SubgraphProcessingStatsTable;
|
|
252
|
+
subgraph_table_snapshots: SubgraphTableSnapshotsTable;
|
|
253
|
+
subgraph_gaps: SubgraphGapsTable;
|
|
254
|
+
}
|
|
255
|
+
interface GapRange {
|
|
256
|
+
start: number;
|
|
257
|
+
end: number;
|
|
258
|
+
reason: string;
|
|
259
|
+
}
|
|
260
|
+
interface SubgraphGapRow {
|
|
261
|
+
id: string;
|
|
262
|
+
subgraphName: string;
|
|
263
|
+
gapStart: number;
|
|
264
|
+
gapEnd: number;
|
|
265
|
+
size: number;
|
|
266
|
+
reason: string;
|
|
267
|
+
detectedAt: Date;
|
|
268
|
+
resolvedAt: Date | null;
|
|
269
|
+
}
|
|
270
|
+
interface GapSummary {
|
|
271
|
+
subgraphName: string;
|
|
272
|
+
gapCount: number;
|
|
273
|
+
totalMissingBlocks: number;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Batch-insert gap ranges for a subgraph. Append-only — no merge on write.
|
|
277
|
+
* Callers should coalesce contiguous skipped blocks into ranges before calling.
|
|
278
|
+
*/
|
|
279
|
+
declare function recordGapBatch(db: Kysely<Database>, subgraphId: string, subgraphName: string, gaps: GapRange[]): Promise<void>;
|
|
280
|
+
/**
|
|
281
|
+
* List gaps for a subgraph with computed size.
|
|
282
|
+
*/
|
|
283
|
+
declare function findSubgraphGaps(db: Kysely<Database>, subgraphName: string, opts?: {
|
|
284
|
+
limit?: number
|
|
285
|
+
offset?: number
|
|
286
|
+
unresolvedOnly?: boolean
|
|
287
|
+
}): Promise<{
|
|
288
|
+
gaps: SubgraphGapRow[]
|
|
289
|
+
total: number
|
|
290
|
+
}>;
|
|
291
|
+
/**
|
|
292
|
+
* Mark gaps as resolved within a block range. Idempotent.
|
|
293
|
+
* Only resolves gaps fully contained within [fromBlock, toBlock].
|
|
294
|
+
*/
|
|
295
|
+
declare function resolveGaps(db: Kysely<Database>, subgraphName: string, fromBlock: number, toBlock: number): Promise<number>;
|
|
296
|
+
/**
|
|
297
|
+
* Total missing blocks across unresolved gaps for a subgraph.
|
|
298
|
+
*/
|
|
299
|
+
declare function countSubgraphMissingBlocks(db: Kysely<Database>, subgraphName: string): Promise<number>;
|
|
300
|
+
/**
|
|
301
|
+
* Aggregate gap counts + missing blocks grouped by subgraph_name.
|
|
302
|
+
* Used by the /status endpoint for per-subgraph gap summary.
|
|
303
|
+
*/
|
|
304
|
+
declare function getGapSummaryBySubgraph(db: Kysely<Database>): Promise<GapSummary[]>;
|
|
305
|
+
export { resolveGaps, recordGapBatch, getGapSummaryBySubgraph, findSubgraphGaps, countSubgraphMissingBlocks, SubgraphGapRow, GapSummary, GapRange };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/db/queries/subgraph-gaps.ts
|
|
18
|
+
import { sql } from "kysely";
|
|
19
|
+
async function recordGapBatch(db, subgraphId, subgraphName, gaps) {
|
|
20
|
+
if (gaps.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
await db.insertInto("subgraph_gaps").values(gaps.map((g) => ({
|
|
23
|
+
subgraph_id: subgraphId,
|
|
24
|
+
subgraph_name: subgraphName,
|
|
25
|
+
gap_start: g.start,
|
|
26
|
+
gap_end: g.end,
|
|
27
|
+
reason: g.reason
|
|
28
|
+
}))).execute();
|
|
29
|
+
}
|
|
30
|
+
async function findSubgraphGaps(db, subgraphName, opts) {
|
|
31
|
+
const limit = opts?.limit ?? 50;
|
|
32
|
+
const offset = opts?.offset ?? 0;
|
|
33
|
+
const unresolvedOnly = opts?.unresolvedOnly ?? true;
|
|
34
|
+
let baseQuery = db.selectFrom("subgraph_gaps").where("subgraph_name", "=", subgraphName);
|
|
35
|
+
if (unresolvedOnly) {
|
|
36
|
+
baseQuery = baseQuery.where("resolved_at", "is", null);
|
|
37
|
+
}
|
|
38
|
+
const [rows, countResult] = await Promise.all([
|
|
39
|
+
baseQuery.select([
|
|
40
|
+
"id",
|
|
41
|
+
"subgraph_name",
|
|
42
|
+
"gap_start",
|
|
43
|
+
"gap_end",
|
|
44
|
+
sql`gap_end - gap_start + 1`.as("size"),
|
|
45
|
+
"reason",
|
|
46
|
+
"detected_at",
|
|
47
|
+
"resolved_at"
|
|
48
|
+
]).orderBy("gap_start", "asc").limit(limit).offset(offset).execute(),
|
|
49
|
+
baseQuery.select(sql`count(*)`.as("count")).executeTakeFirstOrThrow()
|
|
50
|
+
]);
|
|
51
|
+
return {
|
|
52
|
+
gaps: rows.map((r) => ({
|
|
53
|
+
id: r.id,
|
|
54
|
+
subgraphName: r.subgraph_name,
|
|
55
|
+
gapStart: Number(r.gap_start),
|
|
56
|
+
gapEnd: Number(r.gap_end),
|
|
57
|
+
size: Number(r.size),
|
|
58
|
+
reason: r.reason,
|
|
59
|
+
detectedAt: r.detected_at,
|
|
60
|
+
resolvedAt: r.resolved_at
|
|
61
|
+
})),
|
|
62
|
+
total: Number(countResult.count)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function resolveGaps(db, subgraphName, fromBlock, toBlock) {
|
|
66
|
+
const result = await db.updateTable("subgraph_gaps").set({ resolved_at: new Date }).where("subgraph_name", "=", subgraphName).where("resolved_at", "is", null).where("gap_start", ">=", fromBlock).where("gap_end", "<=", toBlock).executeTakeFirst();
|
|
67
|
+
return Number(result.numUpdatedRows);
|
|
68
|
+
}
|
|
69
|
+
async function countSubgraphMissingBlocks(db, subgraphName) {
|
|
70
|
+
const { rows } = await sql`
|
|
71
|
+
SELECT COALESCE(SUM(gap_end - gap_start + 1), 0) AS total
|
|
72
|
+
FROM subgraph_gaps
|
|
73
|
+
WHERE subgraph_name = ${subgraphName}
|
|
74
|
+
AND resolved_at IS NULL
|
|
75
|
+
`.execute(db);
|
|
76
|
+
return Number(rows[0]?.total ?? 0);
|
|
77
|
+
}
|
|
78
|
+
async function getGapSummaryBySubgraph(db) {
|
|
79
|
+
const { rows } = await sql`
|
|
80
|
+
SELECT
|
|
81
|
+
subgraph_name,
|
|
82
|
+
COUNT(*) AS gap_count,
|
|
83
|
+
COALESCE(SUM(gap_end - gap_start + 1), 0) AS total_missing
|
|
84
|
+
FROM subgraph_gaps
|
|
85
|
+
WHERE resolved_at IS NULL
|
|
86
|
+
GROUP BY subgraph_name
|
|
87
|
+
`.execute(db);
|
|
88
|
+
return rows.map((r) => ({
|
|
89
|
+
subgraphName: r.subgraph_name,
|
|
90
|
+
gapCount: Number(r.gap_count),
|
|
91
|
+
totalMissingBlocks: Number(r.total_missing)
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
resolveGaps,
|
|
96
|
+
recordGapBatch,
|
|
97
|
+
getGapSummaryBySubgraph,
|
|
98
|
+
findSubgraphGaps,
|
|
99
|
+
countSubgraphMissingBlocks
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
//# debugId=1045E1B2284F5F1E64756E2164756E21
|
|
103
|
+
//# sourceMappingURL=subgraph-gaps.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/db/queries/subgraph-gaps.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { type Kysely, sql } from \"kysely\";\nimport type { Database } from \"../types.ts\";\n\nexport interface GapRange {\n\tstart: number;\n\tend: number;\n\treason: string;\n}\n\nexport interface SubgraphGapRow {\n\tid: string;\n\tsubgraphName: string;\n\tgapStart: number;\n\tgapEnd: number;\n\tsize: number;\n\treason: string;\n\tdetectedAt: Date;\n\tresolvedAt: Date | null;\n}\n\nexport interface GapSummary {\n\tsubgraphName: string;\n\tgapCount: number;\n\ttotalMissingBlocks: number;\n}\n\n/**\n * Batch-insert gap ranges for a subgraph. Append-only — no merge on write.\n * Callers should coalesce contiguous skipped blocks into ranges before calling.\n */\nexport async function recordGapBatch(\n\tdb: Kysely<Database>,\n\tsubgraphId: string,\n\tsubgraphName: string,\n\tgaps: GapRange[],\n): Promise<void> {\n\tif (gaps.length === 0) return;\n\n\tawait db\n\t\t.insertInto(\"subgraph_gaps\")\n\t\t.values(\n\t\t\tgaps.map((g) => ({\n\t\t\t\tsubgraph_id: subgraphId,\n\t\t\t\tsubgraph_name: subgraphName,\n\t\t\t\tgap_start: g.start,\n\t\t\t\tgap_end: g.end,\n\t\t\t\treason: g.reason,\n\t\t\t})),\n\t\t)\n\t\t.execute();\n}\n\n/**\n * List gaps for a subgraph with computed size.\n */\nexport async function findSubgraphGaps(\n\tdb: Kysely<Database>,\n\tsubgraphName: string,\n\topts?: { limit?: number; offset?: number; unresolvedOnly?: boolean },\n): Promise<{ gaps: SubgraphGapRow[]; total: number }> {\n\tconst limit = opts?.limit ?? 50;\n\tconst offset = opts?.offset ?? 0;\n\tconst unresolvedOnly = opts?.unresolvedOnly ?? true;\n\n\tlet baseQuery = db\n\t\t.selectFrom(\"subgraph_gaps\")\n\t\t.where(\"subgraph_name\", \"=\", subgraphName);\n\n\tif (unresolvedOnly) {\n\t\tbaseQuery = baseQuery.where(\"resolved_at\", \"is\", null);\n\t}\n\n\tconst [rows, countResult] = await Promise.all([\n\t\tbaseQuery\n\t\t\t.select([\n\t\t\t\t\"id\",\n\t\t\t\t\"subgraph_name\",\n\t\t\t\t\"gap_start\",\n\t\t\t\t\"gap_end\",\n\t\t\t\tsql<number>`gap_end - gap_start + 1`.as(\"size\"),\n\t\t\t\t\"reason\",\n\t\t\t\t\"detected_at\",\n\t\t\t\t\"resolved_at\",\n\t\t\t])\n\t\t\t.orderBy(\"gap_start\", \"asc\")\n\t\t\t.limit(limit)\n\t\t\t.offset(offset)\n\t\t\t.execute(),\n\t\tbaseQuery\n\t\t\t.select(sql<number>`count(*)`.as(\"count\"))\n\t\t\t.executeTakeFirstOrThrow(),\n\t]);\n\n\treturn {\n\t\tgaps: rows.map((r) => ({\n\t\t\tid: r.id,\n\t\t\tsubgraphName: r.subgraph_name,\n\t\t\tgapStart: Number(r.gap_start),\n\t\t\tgapEnd: Number(r.gap_end),\n\t\t\tsize: Number(r.size),\n\t\t\treason: r.reason,\n\t\t\tdetectedAt: r.detected_at,\n\t\t\tresolvedAt: r.resolved_at,\n\t\t})),\n\t\ttotal: Number(countResult.count),\n\t};\n}\n\n/**\n * Mark gaps as resolved within a block range. Idempotent.\n * Only resolves gaps fully contained within [fromBlock, toBlock].\n */\nexport async function resolveGaps(\n\tdb: Kysely<Database>,\n\tsubgraphName: string,\n\tfromBlock: number,\n\ttoBlock: number,\n): Promise<number> {\n\tconst result = await db\n\t\t.updateTable(\"subgraph_gaps\")\n\t\t.set({ resolved_at: new Date() })\n\t\t.where(\"subgraph_name\", \"=\", subgraphName)\n\t\t.where(\"resolved_at\", \"is\", null)\n\t\t.where(\"gap_start\", \">=\", fromBlock)\n\t\t.where(\"gap_end\", \"<=\", toBlock)\n\t\t.executeTakeFirst();\n\n\treturn Number(result.numUpdatedRows);\n}\n\n/**\n * Total missing blocks across unresolved gaps for a subgraph.\n */\nexport async function countSubgraphMissingBlocks(\n\tdb: Kysely<Database>,\n\tsubgraphName: string,\n): Promise<number> {\n\tconst { rows } = await sql<{ total: string }>`\n SELECT COALESCE(SUM(gap_end - gap_start + 1), 0) AS total\n FROM subgraph_gaps\n WHERE subgraph_name = ${subgraphName}\n AND resolved_at IS NULL\n `.execute(db);\n\n\treturn Number(rows[0]?.total ?? 0);\n}\n\n/**\n * Aggregate gap counts + missing blocks grouped by subgraph_name.\n * Used by the /status endpoint for per-subgraph gap summary.\n */\nexport async function getGapSummaryBySubgraph(\n\tdb: Kysely<Database>,\n): Promise<GapSummary[]> {\n\tconst { rows } = await sql<{\n\t\tsubgraph_name: string;\n\t\tgap_count: string;\n\t\ttotal_missing: string;\n\t}>`\n SELECT\n subgraph_name,\n COUNT(*) AS gap_count,\n COALESCE(SUM(gap_end - gap_start + 1), 0) AS total_missing\n FROM subgraph_gaps\n WHERE resolved_at IS NULL\n GROUP BY subgraph_name\n `.execute(db);\n\n\treturn rows.map((r) => ({\n\t\tsubgraphName: r.subgraph_name,\n\t\tgapCount: Number(r.gap_count),\n\t\ttotalMissingBlocks: Number(r.total_missing),\n\t}));\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AA8BA,eAAsB,cAAc,CACnC,IACA,YACA,cACA,MACgB;AAAA,EAChB,IAAI,KAAK,WAAW;AAAA,IAAG;AAAA,EAEvB,MAAM,GACJ,WAAW,eAAe,EAC1B,OACA,KAAK,IAAI,CAAC,OAAO;AAAA,IAChB,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,EACX,EAAE,CACH,EACC,QAAQ;AAAA;AAMX,eAAsB,gBAAgB,CACrC,IACA,cACA,MACqD;AAAA,EACrD,MAAM,QAAQ,MAAM,SAAS;AAAA,EAC7B,MAAM,SAAS,MAAM,UAAU;AAAA,EAC/B,MAAM,iBAAiB,MAAM,kBAAkB;AAAA,EAE/C,IAAI,YAAY,GACd,WAAW,eAAe,EAC1B,MAAM,iBAAiB,KAAK,YAAY;AAAA,EAE1C,IAAI,gBAAgB;AAAA,IACnB,YAAY,UAAU,MAAM,eAAe,MAAM,IAAI;AAAA,EACtD;AAAA,EAEA,OAAO,MAAM,eAAe,MAAM,QAAQ,IAAI;AAAA,IAC7C,UACE,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,6BAAqC,GAAG,MAAM;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC,EACA,QAAQ,aAAa,KAAK,EAC1B,MAAM,KAAK,EACX,OAAO,MAAM,EACb,QAAQ;AAAA,IACV,UACE,OAAO,cAAsB,GAAG,OAAO,CAAC,EACxC,wBAAwB;AAAA,EAC3B,CAAC;AAAA,EAED,OAAO;AAAA,IACN,MAAM,KAAK,IAAI,CAAC,OAAO;AAAA,MACtB,IAAI,EAAE;AAAA,MACN,cAAc,EAAE;AAAA,MAChB,UAAU,OAAO,EAAE,SAAS;AAAA,MAC5B,QAAQ,OAAO,EAAE,OAAO;AAAA,MACxB,MAAM,OAAO,EAAE,IAAI;AAAA,MACnB,QAAQ,EAAE;AAAA,MACV,YAAY,EAAE;AAAA,MACd,YAAY,EAAE;AAAA,IACf,EAAE;AAAA,IACF,OAAO,OAAO,YAAY,KAAK;AAAA,EAChC;AAAA;AAOD,eAAsB,WAAW,CAChC,IACA,cACA,WACA,SACkB;AAAA,EAClB,MAAM,SAAS,MAAM,GACnB,YAAY,eAAe,EAC3B,IAAI,EAAE,aAAa,IAAI,KAAO,CAAC,EAC/B,MAAM,iBAAiB,KAAK,YAAY,EACxC,MAAM,eAAe,MAAM,IAAI,EAC/B,MAAM,aAAa,MAAM,SAAS,EAClC,MAAM,WAAW,MAAM,OAAO,EAC9B,iBAAiB;AAAA,EAEnB,OAAO,OAAO,OAAO,cAAc;AAAA;AAMpC,eAAsB,0BAA0B,CAC/C,IACA,cACkB;AAAA,EAClB,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA,4BAGI;AAAA;AAAA,IAExB,QAAQ,EAAE;AAAA,EAEb,OAAO,OAAO,KAAK,IAAI,SAAS,CAAC;AAAA;AAOlC,eAAsB,uBAAuB,CAC5C,IACwB;AAAA,EACxB,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAYpB,QAAQ,EAAE;AAAA,EAEb,OAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACvB,cAAc,EAAE;AAAA,IAChB,UAAU,OAAO,EAAE,SAAS;AAAA,IAC5B,oBAAoB,OAAO,EAAE,aAAa;AAAA,EAC3C,EAAE;AAAA;",
|
|
8
|
+
"debugId": "1045E1B2284F5F1E64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -92,6 +92,7 @@ interface SubgraphsTable {
|
|
|
92
92
|
schema_hash: string;
|
|
93
93
|
handler_path: string;
|
|
94
94
|
schema_name: string | null;
|
|
95
|
+
start_block: Generated<number>;
|
|
95
96
|
last_processed_block: Generated<number>;
|
|
96
97
|
last_error: string | null;
|
|
97
98
|
last_error_at: Date | null;
|
|
@@ -101,6 +102,16 @@ interface SubgraphsTable {
|
|
|
101
102
|
created_at: Generated<Date>;
|
|
102
103
|
updated_at: Generated<Date>;
|
|
103
104
|
}
|
|
105
|
+
interface SubgraphGapsTable {
|
|
106
|
+
id: Generated<string>;
|
|
107
|
+
subgraph_id: string;
|
|
108
|
+
subgraph_name: string;
|
|
109
|
+
gap_start: number;
|
|
110
|
+
gap_end: number;
|
|
111
|
+
reason: string;
|
|
112
|
+
detected_at: Generated<Date>;
|
|
113
|
+
resolved_at: Date | null;
|
|
114
|
+
}
|
|
104
115
|
interface ApiKeysTable {
|
|
105
116
|
id: Generated<string>;
|
|
106
117
|
key_hash: string;
|
|
@@ -239,6 +250,7 @@ interface Database {
|
|
|
239
250
|
subgraph_health_snapshots: SubgraphHealthSnapshotsTable;
|
|
240
251
|
subgraph_processing_stats: SubgraphProcessingStatsTable;
|
|
241
252
|
subgraph_table_snapshots: SubgraphTableSnapshotsTable;
|
|
253
|
+
subgraph_gaps: SubgraphGapsTable;
|
|
242
254
|
}
|
|
243
255
|
type Subgraph = Selectable<SubgraphsTable>;
|
|
244
256
|
/**
|
|
@@ -255,6 +267,7 @@ declare function registerSubgraph(db: Kysely<Database>, data: {
|
|
|
255
267
|
handlerPath: string
|
|
256
268
|
apiKeyId?: string
|
|
257
269
|
schemaName?: string
|
|
270
|
+
startBlock?: number
|
|
258
271
|
}): Promise<Subgraph>;
|
|
259
272
|
declare function getSubgraph(db: Kysely<Database>, name: string, apiKeyId?: string): Promise<Subgraph | null>;
|
|
260
273
|
declare function listSubgraphs(db: Kysely<Database>, apiKeyId?: string): Promise<Subgraph[]>;
|
|
@@ -49,13 +49,15 @@ async function registerSubgraph(db, data) {
|
|
|
49
49
|
schema_hash: data.schemaHash,
|
|
50
50
|
handler_path: data.handlerPath,
|
|
51
51
|
api_key_id: data.apiKeyId,
|
|
52
|
-
schema_name: data.schemaName ?? null
|
|
52
|
+
schema_name: data.schemaName ?? null,
|
|
53
|
+
start_block: data.startBlock ?? 0
|
|
53
54
|
}).onConflict((oc) => oc.columns(["name", "api_key_id"]).doUpdateSet({
|
|
54
55
|
version: data.version,
|
|
55
56
|
definition: jsonb(data.definition),
|
|
56
57
|
schema_hash: data.schemaHash,
|
|
57
58
|
handler_path: data.handlerPath,
|
|
58
59
|
schema_name: data.schemaName ?? null,
|
|
60
|
+
start_block: data.startBlock ?? 0,
|
|
59
61
|
updated_at: new Date
|
|
60
62
|
})).returningAll().executeTakeFirstOrThrow();
|
|
61
63
|
}
|
|
@@ -111,5 +113,5 @@ export {
|
|
|
111
113
|
deleteSubgraph
|
|
112
114
|
};
|
|
113
115
|
|
|
114
|
-
//# debugId=
|
|
116
|
+
//# debugId=3B8125C51D68206164756E2164756E21
|
|
115
117
|
//# sourceMappingURL=subgraphs.js.map
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/db/jsonb.ts", "../src/db/queries/subgraphs.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import {
|
|
6
|
-
"import {
|
|
5
|
+
"import { type RawBuilder, sql } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n */\nexport function jsonb(value: unknown): RawBuilder<unknown> {\n\tconst escaped = JSON.stringify(value).replace(/'/g, \"''\");\n\treturn sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\treturn JSON.parse(value) as T;\n\t\t} catch {\n\t\t\treturn value as T;\n\t\t}\n\t}\n\treturn (value ?? {}) as T;\n}\n",
|
|
6
|
+
"import { type Kysely, sql } from \"kysely\";\nimport { jsonb } from \"../jsonb.ts\";\nimport type { Database, Subgraph } from \"../types.ts\";\n\n/**\n * Convert a subgraph name to its PostgreSQL schema name.\n * With keyPrefix: \"subgraph_{prefix}_{name}\" (tenant-isolated)\n * Without keyPrefix: \"subgraph_{name}\" (backward compat)\n */\nexport function pgSchemaName(subgraphName: string, keyPrefix?: string): string {\n\tconst safeName = subgraphName.replace(/-/g, \"_\");\n\tif (!keyPrefix) {\n\t\treturn `subgraph_${safeName}`;\n\t}\n\tconst safePrefix = keyPrefix.replace(/^sk-sl_/, \"\").replace(/-/g, \"_\");\n\treturn `subgraph_${safePrefix}_${safeName}`;\n}\n\nexport async function registerSubgraph(\n\tdb: Kysely<Database>,\n\tdata: {\n\t\tname: string;\n\t\tversion: string;\n\t\tdefinition: Record<string, unknown>;\n\t\tschemaHash: string;\n\t\thandlerPath: string;\n\t\tapiKeyId?: string;\n\t\tschemaName?: string;\n\t\tstartBlock?: number;\n\t},\n): Promise<Subgraph> {\n\treturn await db\n\t\t.insertInto(\"subgraphs\")\n\t\t.values({\n\t\t\tname: data.name,\n\t\t\tversion: data.version,\n\t\t\tdefinition: jsonb(data.definition) as any,\n\t\t\tschema_hash: data.schemaHash,\n\t\t\thandler_path: data.handlerPath,\n\t\t\tapi_key_id: data.apiKeyId!,\n\t\t\tschema_name: data.schemaName ?? null,\n\t\t\tstart_block: data.startBlock ?? 0,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"name\", \"api_key_id\"]).doUpdateSet({\n\t\t\t\tversion: data.version,\n\t\t\t\tdefinition: jsonb(data.definition) as any,\n\t\t\t\tschema_hash: data.schemaHash,\n\t\t\t\thandler_path: data.handlerPath,\n\t\t\t\tschema_name: data.schemaName ?? null,\n\t\t\t\tstart_block: data.startBlock ?? 0,\n\t\t\t\tupdated_at: new Date(),\n\t\t\t}),\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\tapiKeyId?: string,\n): Promise<Subgraph | null> {\n\tlet query = db.selectFrom(\"subgraphs\").selectAll().where(\"name\", \"=\", name);\n\n\tif (apiKeyId) {\n\t\tquery = query.where(\"api_key_id\", \"=\", apiKeyId);\n\t}\n\n\treturn (await query.executeTakeFirst()) ?? null;\n}\n\nexport async function listSubgraphs(\n\tdb: Kysely<Database>,\n\tapiKeyId?: string,\n): Promise<Subgraph[]> {\n\tlet query = db.selectFrom(\"subgraphs\").selectAll();\n\tif (apiKeyId) {\n\t\tquery = query.where(\"api_key_id\", \"=\", apiKeyId);\n\t}\n\treturn query.execute();\n}\n\nexport async function updateSubgraphStatus(\n\tdb: Kysely<Database>,\n\tname: string,\n\tstatus: string,\n\tlastProcessedBlock?: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\tstatus,\n\t\t\t...(lastProcessedBlock !== undefined\n\t\t\t\t? { last_processed_block: lastProcessedBlock }\n\t\t\t\t: {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function recordSubgraphProcessed(\n\tdb: Kysely<Database>,\n\tname: string,\n\tprocessed: number,\n\terrors: number,\n\tlastError?: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\ttotal_processed: sql`total_processed + ${processed}`,\n\t\t\ttotal_errors: sql`total_errors + ${errors}`,\n\t\t\t...(lastError\n\t\t\t\t? { last_error: lastError, last_error_at: new Date() }\n\t\t\t\t: {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function updateSubgraphHandlerPath(\n\tdb: Kysely<Database>,\n\tname: string,\n\thandlerPath: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({ handler_path: handlerPath, updated_at: new Date() })\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function deleteSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\tapiKeyId?: string,\n): Promise<Subgraph | null> {\n\tconst subgraph = await getSubgraph(db, name, apiKeyId);\n\tif (!subgraph) return null;\n\n\t// Use stored schema_name if available, otherwise compute\n\tconst schemaName = subgraph.schema_name ?? pgSchemaName(name);\n\n\t// Drop the subgraph's schema (CASCADE drops all tables within)\n\tawait sql`DROP SCHEMA IF EXISTS ${sql.raw(`\"${schemaName}\"`)} CASCADE`.execute(\n\t\tdb,\n\t);\n\n\t// Remove from registry\n\tawait db.deleteFrom(\"subgraphs\").where(\"id\", \"=\", subgraph.id).execute();\n\n\treturn subgraph;\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAOO,SAAS,KAAK,CAAC,OAAqC;AAAA,
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "3B8125C51D68206164756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -100,6 +100,7 @@ interface SubgraphsTable {
|
|
|
100
100
|
schema_hash: string;
|
|
101
101
|
handler_path: string;
|
|
102
102
|
schema_name: string | null;
|
|
103
|
+
start_block: Generated<number>;
|
|
103
104
|
last_processed_block: Generated<number>;
|
|
104
105
|
last_error: string | null;
|
|
105
106
|
last_error_at: Date | null;
|
|
@@ -109,6 +110,16 @@ interface SubgraphsTable {
|
|
|
109
110
|
created_at: Generated<Date>;
|
|
110
111
|
updated_at: Generated<Date>;
|
|
111
112
|
}
|
|
113
|
+
interface SubgraphGapsTable {
|
|
114
|
+
id: Generated<string>;
|
|
115
|
+
subgraph_id: string;
|
|
116
|
+
subgraph_name: string;
|
|
117
|
+
gap_start: number;
|
|
118
|
+
gap_end: number;
|
|
119
|
+
reason: string;
|
|
120
|
+
detected_at: Generated<Date>;
|
|
121
|
+
resolved_at: Date | null;
|
|
122
|
+
}
|
|
112
123
|
interface ApiKeysTable {
|
|
113
124
|
id: Generated<string>;
|
|
114
125
|
key_hash: string;
|
|
@@ -247,6 +258,7 @@ interface Database {
|
|
|
247
258
|
subgraph_health_snapshots: SubgraphHealthSnapshotsTable;
|
|
248
259
|
subgraph_processing_stats: SubgraphProcessingStatsTable;
|
|
249
260
|
subgraph_table_snapshots: SubgraphTableSnapshotsTable;
|
|
261
|
+
subgraph_gaps: SubgraphGapsTable;
|
|
250
262
|
}
|
|
251
263
|
/** Increment API request counter for today. Fire-and-forget safe. */
|
|
252
264
|
declare function incrementApiRequests(db: Kysely<Database>, accountId: string): Promise<void>;
|
|
@@ -34,13 +34,23 @@ function getPlanLimits(plan) {
|
|
|
34
34
|
import { sql } from "kysely";
|
|
35
35
|
async function incrementApiRequests(db, accountId) {
|
|
36
36
|
const today = new Date().toISOString().slice(0, 10);
|
|
37
|
-
await db.insertInto("usage_daily").values({
|
|
37
|
+
await db.insertInto("usage_daily").values({
|
|
38
|
+
account_id: accountId,
|
|
39
|
+
date: today,
|
|
40
|
+
api_requests: 1,
|
|
41
|
+
deliveries: 0
|
|
42
|
+
}).onConflict((oc) => oc.columns(["account_id", "date"]).doUpdateSet({
|
|
38
43
|
api_requests: sql`usage_daily.api_requests + 1`
|
|
39
44
|
})).execute();
|
|
40
45
|
}
|
|
41
46
|
async function incrementDeliveries(db, accountId, count = 1) {
|
|
42
47
|
const today = new Date().toISOString().slice(0, 10);
|
|
43
|
-
await db.insertInto("usage_daily").values({
|
|
48
|
+
await db.insertInto("usage_daily").values({
|
|
49
|
+
account_id: accountId,
|
|
50
|
+
date: today,
|
|
51
|
+
api_requests: 0,
|
|
52
|
+
deliveries: count
|
|
53
|
+
}).onConflict((oc) => oc.columns(["account_id", "date"]).doUpdateSet({
|
|
44
54
|
deliveries: sql`usage_daily.deliveries + ${count}`
|
|
45
55
|
})).execute();
|
|
46
56
|
}
|
|
@@ -139,5 +149,5 @@ export {
|
|
|
139
149
|
checkLimits
|
|
140
150
|
};
|
|
141
151
|
|
|
142
|
-
//# debugId=
|
|
152
|
+
//# debugId=C9AA7973D9E596F264756E2164756E21
|
|
143
153
|
//# sourceMappingURL=usage.js.map
|