@secondlayer/shared 0.7.0 → 0.8.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/crypto/hmac.js +2 -2
- package/dist/src/crypto/hmac.js.map +3 -3
- package/dist/src/db/index.d.ts +18 -3
- package/dist/src/db/index.js +6 -2
- 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 +16 -3
- package/dist/src/db/queries/accounts.js +6 -3
- package/dist/src/db/queries/accounts.js.map +3 -3
- package/dist/src/db/queries/integrity.d.ts +15 -2
- package/dist/src/db/queries/integrity.js.map +2 -2
- package/dist/src/db/queries/metrics.d.ts +15 -2
- 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 +15 -2
- package/dist/src/db/queries/subgraphs.js +2 -2
- package/dist/src/db/queries/subgraphs.js.map +4 -4
- package/dist/src/db/queries/usage.d.ts +23 -3
- package/dist/src/db/queries/usage.js +35 -3
- package/dist/src/db/queries/usage.js.map +4 -4
- package/dist/src/db/schema.d.ts +18 -3
- package/dist/src/env.js.map +2 -2
- package/dist/src/errors.d.ts +17 -4
- package/dist/src/errors.js +14 -2
- package/dist/src/errors.js.map +3 -3
- package/dist/src/index.d.ts +78 -6
- package/dist/src/index.js +26 -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 +15 -2
- package/dist/src/node/local-client.js.map +2 -2
- package/dist/src/queue/index.js +8 -4
- package/dist/src/queue/index.js.map +5 -5
- package/dist/src/queue/listener.js.map +2 -2
- package/dist/src/queue/recovery.js +6 -2
- 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/dist/src/types.d.ts +1 -1
- 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 +32 -0
- 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
|
+
}
|
|
@@ -38,7 +38,7 @@ interface StreamsTable {
|
|
|
38
38
|
options: Generated<unknown>;
|
|
39
39
|
endpoint_url: string;
|
|
40
40
|
signing_secret: string | null;
|
|
41
|
-
api_key_id: string
|
|
41
|
+
api_key_id: string;
|
|
42
42
|
created_at: Generated<Date>;
|
|
43
43
|
updated_at: Generated<Date>;
|
|
44
44
|
}
|
|
@@ -92,15 +92,26 @@ 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;
|
|
98
99
|
total_processed: Generated<number>;
|
|
99
100
|
total_errors: Generated<number>;
|
|
100
|
-
api_key_id: string
|
|
101
|
+
api_key_id: string;
|
|
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;
|
|
@@ -137,6 +148,7 @@ interface MagicLinksTable {
|
|
|
137
148
|
token: string;
|
|
138
149
|
expires_at: Date;
|
|
139
150
|
used_at: Date | null;
|
|
151
|
+
failed_attempts: Generated<number>;
|
|
140
152
|
created_at: Generated<Date>;
|
|
141
153
|
}
|
|
142
154
|
interface UsageDailyTable {
|
|
@@ -238,6 +250,7 @@ interface Database {
|
|
|
238
250
|
subgraph_health_snapshots: SubgraphHealthSnapshotsTable;
|
|
239
251
|
subgraph_processing_stats: SubgraphProcessingStatsTable;
|
|
240
252
|
subgraph_table_snapshots: SubgraphTableSnapshotsTable;
|
|
253
|
+
subgraph_gaps: SubgraphGapsTable;
|
|
241
254
|
}
|
|
242
255
|
type Subgraph = Selectable<SubgraphsTable>;
|
|
243
256
|
/**
|
|
@@ -48,7 +48,7 @@ async function registerSubgraph(db, data) {
|
|
|
48
48
|
definition: jsonb(data.definition),
|
|
49
49
|
schema_hash: data.schemaHash,
|
|
50
50
|
handler_path: data.handlerPath,
|
|
51
|
-
api_key_id: data.apiKeyId
|
|
51
|
+
api_key_id: data.apiKeyId,
|
|
52
52
|
schema_name: data.schemaName ?? null
|
|
53
53
|
}).onConflict((oc) => oc.columns(["name", "api_key_id"]).doUpdateSet({
|
|
54
54
|
version: data.version,
|
|
@@ -111,5 +111,5 @@ export {
|
|
|
111
111
|
deleteSubgraph
|
|
112
112
|
};
|
|
113
113
|
|
|
114
|
-
//# debugId=
|
|
114
|
+
//# debugId=CA1F8ED652ED1B5C64756E2164756E21
|
|
115
115
|
//# 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},\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})\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\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": "CA1F8ED652ED1B5C64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -46,7 +46,7 @@ interface StreamsTable {
|
|
|
46
46
|
options: Generated<unknown>;
|
|
47
47
|
endpoint_url: string;
|
|
48
48
|
signing_secret: string | null;
|
|
49
|
-
api_key_id: string
|
|
49
|
+
api_key_id: string;
|
|
50
50
|
created_at: Generated<Date>;
|
|
51
51
|
updated_at: Generated<Date>;
|
|
52
52
|
}
|
|
@@ -100,15 +100,26 @@ 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;
|
|
106
107
|
total_processed: Generated<number>;
|
|
107
108
|
total_errors: Generated<number>;
|
|
108
|
-
api_key_id: string
|
|
109
|
+
api_key_id: string;
|
|
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;
|
|
@@ -145,6 +156,7 @@ interface MagicLinksTable {
|
|
|
145
156
|
token: string;
|
|
146
157
|
expires_at: Date;
|
|
147
158
|
used_at: Date | null;
|
|
159
|
+
failed_attempts: Generated<number>;
|
|
148
160
|
created_at: Generated<Date>;
|
|
149
161
|
}
|
|
150
162
|
interface UsageDailyTable {
|
|
@@ -246,6 +258,7 @@ interface Database {
|
|
|
246
258
|
subgraph_health_snapshots: SubgraphHealthSnapshotsTable;
|
|
247
259
|
subgraph_processing_stats: SubgraphProcessingStatsTable;
|
|
248
260
|
subgraph_table_snapshots: SubgraphTableSnapshotsTable;
|
|
261
|
+
subgraph_gaps: SubgraphGapsTable;
|
|
249
262
|
}
|
|
250
263
|
/** Increment API request counter for today. Fire-and-forget safe. */
|
|
251
264
|
declare function incrementApiRequests(db: Kysely<Database>, accountId: string): Promise<void>;
|
|
@@ -258,6 +271,13 @@ interface UsageSummary {
|
|
|
258
271
|
}
|
|
259
272
|
/** Get current usage for an account. */
|
|
260
273
|
declare function getUsage(db: Kysely<Database>, accountId: string): Promise<UsageSummary>;
|
|
274
|
+
interface DailyUsage {
|
|
275
|
+
date: string;
|
|
276
|
+
apiRequests: number;
|
|
277
|
+
deliveries: number;
|
|
278
|
+
}
|
|
279
|
+
/** Get last 7 days of daily usage, filling missing days with 0. */
|
|
280
|
+
declare function getDailyUsage(db: Kysely<Database>, accountId: string): Promise<DailyUsage[]>;
|
|
261
281
|
interface LimitCheck {
|
|
262
282
|
allowed: boolean;
|
|
263
283
|
limits: ReturnType<typeof getPlanLimits>;
|
|
@@ -274,4 +294,4 @@ declare function checkLimits(db: Kysely<Database>, accountId: string, plan: stri
|
|
|
274
294
|
* for each tenant's subgraph schemas.
|
|
275
295
|
*/
|
|
276
296
|
declare function measureStorage(db: Kysely<Database>): Promise<void>;
|
|
277
|
-
export { measureStorage, incrementDeliveries, incrementApiRequests, getUsage, checkLimits, UsageSummary, LimitCheck };
|
|
297
|
+
export { measureStorage, incrementDeliveries, incrementApiRequests, getUsage, getDailyUsage, checkLimits, UsageSummary, LimitCheck, DailyUsage };
|