@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.
Files changed (73) hide show
  1. package/dist/src/crypto/hmac.js +2 -2
  2. package/dist/src/crypto/hmac.js.map +3 -3
  3. package/dist/src/db/index.d.ts +18 -3
  4. package/dist/src/db/index.js +6 -2
  5. package/dist/src/db/index.js.map +4 -4
  6. package/dist/src/db/jsonb.js.map +2 -2
  7. package/dist/src/db/queries/accounts.d.ts +16 -3
  8. package/dist/src/db/queries/accounts.js +6 -3
  9. package/dist/src/db/queries/accounts.js.map +3 -3
  10. package/dist/src/db/queries/integrity.d.ts +15 -2
  11. package/dist/src/db/queries/integrity.js.map +2 -2
  12. package/dist/src/db/queries/metrics.d.ts +15 -2
  13. package/dist/src/db/queries/metrics.js.map +2 -2
  14. package/dist/src/db/queries/subgraph-gaps.d.ts +305 -0
  15. package/dist/src/db/queries/subgraph-gaps.js +103 -0
  16. package/dist/src/db/queries/subgraph-gaps.js.map +10 -0
  17. package/dist/src/db/queries/subgraphs.d.ts +15 -2
  18. package/dist/src/db/queries/subgraphs.js +2 -2
  19. package/dist/src/db/queries/subgraphs.js.map +4 -4
  20. package/dist/src/db/queries/usage.d.ts +23 -3
  21. package/dist/src/db/queries/usage.js +35 -3
  22. package/dist/src/db/queries/usage.js.map +4 -4
  23. package/dist/src/db/schema.d.ts +18 -3
  24. package/dist/src/env.js.map +2 -2
  25. package/dist/src/errors.d.ts +17 -4
  26. package/dist/src/errors.js +14 -2
  27. package/dist/src/errors.js.map +3 -3
  28. package/dist/src/index.d.ts +78 -6
  29. package/dist/src/index.js +26 -8
  30. package/dist/src/index.js.map +12 -12
  31. package/dist/src/lib/plans.js.map +2 -2
  32. package/dist/src/logger.js.map +3 -3
  33. package/dist/src/node/archive-client.js +22 -6
  34. package/dist/src/node/archive-client.js.map +5 -5
  35. package/dist/src/node/client.js.map +2 -2
  36. package/dist/src/node/hiro-client.js +47 -11
  37. package/dist/src/node/hiro-client.js.map +5 -5
  38. package/dist/src/node/hiro-pg-client.js +131 -26
  39. package/dist/src/node/hiro-pg-client.js.map +3 -3
  40. package/dist/src/node/local-client.d.ts +15 -2
  41. package/dist/src/node/local-client.js.map +2 -2
  42. package/dist/src/queue/index.js +8 -4
  43. package/dist/src/queue/index.js.map +5 -5
  44. package/dist/src/queue/listener.js.map +2 -2
  45. package/dist/src/queue/recovery.js +6 -2
  46. package/dist/src/queue/recovery.js.map +5 -5
  47. package/dist/src/schemas/filters.js +2 -2
  48. package/dist/src/schemas/filters.js.map +3 -3
  49. package/dist/src/schemas/index.d.ts +45 -1
  50. package/dist/src/schemas/index.js +5 -3
  51. package/dist/src/schemas/index.js.map +5 -5
  52. package/dist/src/schemas/subgraphs.d.ts +45 -1
  53. package/dist/src/schemas/subgraphs.js.map +2 -2
  54. package/dist/src/types.d.ts +1 -1
  55. package/migrations/0001_initial.ts +295 -159
  56. package/migrations/0002_api_keys.ts +44 -28
  57. package/migrations/0003_tenant_isolation.ts +116 -107
  58. package/migrations/0004_accounts_and_usage.ts +81 -75
  59. package/migrations/0005_sessions.ts +33 -33
  60. package/migrations/0006_tx_index.ts +6 -2
  61. package/migrations/0007_contracts.ts +38 -24
  62. package/migrations/0008_drop_contracts.ts +33 -19
  63. package/migrations/0009_waitlist.ts +12 -12
  64. package/migrations/0010_waitlist_status.ts +5 -5
  65. package/migrations/0011_account_insights.ts +52 -52
  66. package/migrations/0012_view_health_snapshots.ts +21 -21
  67. package/migrations/0013_view_processing_stats.ts +32 -32
  68. package/migrations/0014_view_table_snapshots.ts +24 -24
  69. package/migrations/0015_rename_views_to_subgraphs.ts +137 -75
  70. package/migrations/0016_rename_webhook_to_endpoint.ts +12 -4
  71. package/migrations/0017_security_hardening.ts +32 -0
  72. package/migrations/0018_subgraph_gaps.ts +39 -0
  73. 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 | null;
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 | null;
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 ?? null,
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=A3A252C7C09E8C5564756E2164756E21
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 { sql, type RawBuilder } 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 const escaped = JSON.stringify(value).replace(/'/g, \"''\");\n return 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 if (typeof value === \"string\") {\n try {\n return JSON.parse(value) as T;\n } catch {\n return value as T;\n }\n }\n return (value ?? {}) as T;\n}\n",
6
- "import { sql, type Kysely } 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 const safeName = subgraphName.replace(/-/g, \"_\");\n if (!keyPrefix) {\n return `subgraph_${safeName}`;\n }\n const safePrefix = keyPrefix.replace(/^sk-sl_/, \"\").replace(/-/g, \"_\");\n return `subgraph_${safePrefix}_${safeName}`;\n}\n\nexport async function registerSubgraph(\n db: Kysely<Database>,\n data: {\n name: string;\n version: string;\n definition: Record<string, unknown>;\n schemaHash: string;\n handlerPath: string;\n apiKeyId?: string;\n schemaName?: string;\n },\n): Promise<Subgraph> {\n return await db\n .insertInto(\"subgraphs\")\n .values({\n name: data.name,\n version: data.version,\n definition: jsonb(data.definition) as any,\n schema_hash: data.schemaHash,\n handler_path: data.handlerPath,\n api_key_id: data.apiKeyId ?? null,\n schema_name: data.schemaName ?? null,\n })\n .onConflict((oc) =>\n oc.columns([\"name\", \"api_key_id\"]).doUpdateSet({\n version: data.version,\n definition: jsonb(data.definition) as any,\n schema_hash: data.schemaHash,\n handler_path: data.handlerPath,\n schema_name: data.schemaName ?? null,\n updated_at: new Date(),\n }),\n )\n .returningAll()\n .executeTakeFirstOrThrow();\n}\n\nexport async function getSubgraph(db: Kysely<Database>, name: string, apiKeyId?: string): Promise<Subgraph | null> {\n let query = db\n .selectFrom(\"subgraphs\")\n .selectAll()\n .where(\"name\", \"=\", name);\n\n if (apiKeyId) {\n query = query.where(\"api_key_id\", \"=\", apiKeyId);\n }\n\n return (await query.executeTakeFirst()) ?? null;\n}\n\nexport async function listSubgraphs(db: Kysely<Database>, apiKeyId?: string): Promise<Subgraph[]> {\n let query = db.selectFrom(\"subgraphs\").selectAll();\n if (apiKeyId) {\n query = query.where(\"api_key_id\", \"=\", apiKeyId);\n }\n return query.execute();\n}\n\nexport async function updateSubgraphStatus(\n db: Kysely<Database>,\n name: string,\n status: string,\n lastProcessedBlock?: number,\n): Promise<void> {\n await db\n .updateTable(\"subgraphs\")\n .set({\n status,\n ...(lastProcessedBlock !== undefined ? { last_processed_block: lastProcessedBlock } : {}),\n updated_at: new Date(),\n })\n .where(\"name\", \"=\", name)\n .execute();\n}\n\nexport async function recordSubgraphProcessed(\n db: Kysely<Database>,\n name: string,\n processed: number,\n errors: number,\n lastError?: string,\n): Promise<void> {\n await db\n .updateTable(\"subgraphs\")\n .set({\n total_processed: sql`total_processed + ${processed}`,\n total_errors: sql`total_errors + ${errors}`,\n ...(lastError\n ? { last_error: lastError, last_error_at: new Date() }\n : {}),\n updated_at: new Date(),\n })\n .where(\"name\", \"=\", name)\n .execute();\n}\n\nexport async function updateSubgraphHandlerPath(\n db: Kysely<Database>,\n name: string,\n handlerPath: string,\n): Promise<void> {\n await db\n .updateTable(\"subgraphs\")\n .set({ handler_path: handlerPath, updated_at: new Date() })\n .where(\"name\", \"=\", name)\n .execute();\n}\n\nexport async function deleteSubgraph(db: Kysely<Database>, name: string, apiKeyId?: string): Promise<Subgraph | null> {\n const subgraph = await getSubgraph(db, name, apiKeyId);\n if (!subgraph) return null;\n\n // Use stored schema_name if available, otherwise compute\n const schemaName = subgraph.schema_name ?? pgSchemaName(name);\n\n // Drop the subgraph's schema (CASCADE drops all tables within)\n await sql`DROP SCHEMA IF EXISTS ${sql.raw(`\"${schemaName}\"`)} CASCADE`.execute(db);\n\n // Remove from registry\n await db.deleteFrom(\"subgraphs\").where(\"id\", \"=\", subgraph.id).execute();\n\n return subgraph;\n}\n"
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": "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",
9
- "debugId": "A3A252C7C09E8C5564756E2164756E21",
8
+ "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAOO,SAAS,KAAK,CAAC,OAAqC;AAAA,EAC1D,MAAM,UAAU,KAAK,UAAU,KAAK,EAAE,QAAQ,MAAM,IAAI;AAAA,EACxD,OAAO,MAAM,IAAI,IAAI,IAAI,iBAAiB;AAAA;AAQpC,SAAS,UAAuB,CAAC,OAAmB;AAAA,EAC1D,IAAI,OAAO,UAAU,UAAU;AAAA,IAC9B,IAAI;AAAA,MACH,OAAO,KAAK,MAAM,KAAK;AAAA,MACtB,MAAM;AAAA,MACP,OAAO;AAAA;AAAA,EAET;AAAA,EACA,OAAQ,SAAS,CAAC;AAAA;;;ACzBnB,gBAAsB;AASf,SAAS,YAAY,CAAC,cAAsB,WAA4B;AAAA,EAC9E,MAAM,WAAW,aAAa,QAAQ,MAAM,GAAG;AAAA,EAC/C,IAAI,CAAC,WAAW;AAAA,IACf,OAAO,YAAY;AAAA,EACpB;AAAA,EACA,MAAM,aAAa,UAAU,QAAQ,WAAW,EAAE,EAAE,QAAQ,MAAM,GAAG;AAAA,EACrE,OAAO,YAAY,cAAc;AAAA;AAGlC,eAAsB,gBAAgB,CACrC,IACA,MASoB;AAAA,EACpB,OAAO,MAAM,GACX,WAAW,WAAW,EACtB,OAAO;AAAA,IACP,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,YAAY,MAAM,KAAK,UAAU;AAAA,IACjC,aAAa,KAAK;AAAA,IAClB,cAAc,KAAK;AAAA,IACnB,YAAY,KAAK;AAAA,IACjB,aAAa,KAAK,cAAc;AAAA,EACjC,CAAC,EACA,WAAW,CAAC,OACZ,GAAG,QAAQ,CAAC,QAAQ,YAAY,CAAC,EAAE,YAAY;AAAA,IAC9C,SAAS,KAAK;AAAA,IACd,YAAY,MAAM,KAAK,UAAU;AAAA,IACjC,aAAa,KAAK;AAAA,IAClB,cAAc,KAAK;AAAA,IACnB,aAAa,KAAK,cAAc;AAAA,IAChC,YAAY,IAAI;AAAA,EACjB,CAAC,CACF,EACC,aAAa,EACb,wBAAwB;AAAA;AAG3B,eAAsB,WAAW,CAChC,IACA,MACA,UAC2B;AAAA,EAC3B,IAAI,QAAQ,GAAG,WAAW,WAAW,EAAE,UAAU,EAAE,MAAM,QAAQ,KAAK,IAAI;AAAA,EAE1E,IAAI,UAAU;AAAA,IACb,QAAQ,MAAM,MAAM,cAAc,KAAK,QAAQ;AAAA,EAChD;AAAA,EAEA,OAAQ,MAAM,MAAM,iBAAiB,KAAM;AAAA;AAG5C,eAAsB,aAAa,CAClC,IACA,UACsB;AAAA,EACtB,IAAI,QAAQ,GAAG,WAAW,WAAW,EAAE,UAAU;AAAA,EACjD,IAAI,UAAU;AAAA,IACb,QAAQ,MAAM,MAAM,cAAc,KAAK,QAAQ;AAAA,EAChD;AAAA,EACA,OAAO,MAAM,QAAQ;AAAA;AAGtB,eAAsB,oBAAoB,CACzC,IACA,MACA,QACA,oBACgB;AAAA,EAChB,MAAM,GACJ,YAAY,WAAW,EACvB,IAAI;AAAA,IACJ;AAAA,OACI,uBAAuB,YACxB,EAAE,sBAAsB,mBAAmB,IAC3C,CAAC;AAAA,IACJ,YAAY,IAAI;AAAA,EACjB,CAAC,EACA,MAAM,QAAQ,KAAK,IAAI,EACvB,QAAQ;AAAA;AAGX,eAAsB,uBAAuB,CAC5C,IACA,MACA,WACA,QACA,WACgB;AAAA,EAChB,MAAM,GACJ,YAAY,WAAW,EACvB,IAAI;AAAA,IACJ,iBAAiB,yBAAwB;AAAA,IACzC,cAAc,sBAAqB;AAAA,OAC/B,YACD,EAAE,YAAY,WAAW,eAAe,IAAI,KAAO,IACnD,CAAC;AAAA,IACJ,YAAY,IAAI;AAAA,EACjB,CAAC,EACA,MAAM,QAAQ,KAAK,IAAI,EACvB,QAAQ;AAAA;AAGX,eAAsB,yBAAyB,CAC9C,IACA,MACA,aACgB;AAAA,EAChB,MAAM,GACJ,YAAY,WAAW,EACvB,IAAI,EAAE,cAAc,aAAa,YAAY,IAAI,KAAO,CAAC,EACzD,MAAM,QAAQ,KAAK,IAAI,EACvB,QAAQ;AAAA;AAGX,eAAsB,cAAc,CACnC,IACA,MACA,UAC2B;AAAA,EAC3B,MAAM,WAAW,MAAM,YAAY,IAAI,MAAM,QAAQ;AAAA,EACrD,IAAI,CAAC;AAAA,IAAU,OAAO;AAAA,EAGtB,MAAM,aAAa,SAAS,eAAe,aAAa,IAAI;AAAA,EAG5D,MAAM,6BAA4B,KAAI,IAAI,IAAI,aAAa,YAAY,QACtE,EACD;AAAA,EAGA,MAAM,GAAG,WAAW,WAAW,EAAE,MAAM,MAAM,KAAK,SAAS,EAAE,EAAE,QAAQ;AAAA,EAEvE,OAAO;AAAA;",
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 | null;
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 | null;
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 };