@secondlayer/shared 0.7.1 → 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 (68) 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 +15 -1
  4. package/dist/src/db/index.js +5 -3
  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 +12 -0
  8. package/dist/src/db/queries/accounts.js.map +2 -2
  9. package/dist/src/db/queries/integrity.d.ts +12 -0
  10. package/dist/src/db/queries/integrity.js.map +2 -2
  11. package/dist/src/db/queries/metrics.d.ts +12 -0
  12. package/dist/src/db/queries/metrics.js.map +2 -2
  13. package/dist/src/db/queries/subgraph-gaps.d.ts +305 -0
  14. package/dist/src/db/queries/subgraph-gaps.js +103 -0
  15. package/dist/src/db/queries/subgraph-gaps.js.map +10 -0
  16. package/dist/src/db/queries/subgraphs.d.ts +12 -0
  17. package/dist/src/db/queries/subgraphs.js.map +3 -3
  18. package/dist/src/db/queries/usage.d.ts +12 -0
  19. package/dist/src/db/queries/usage.js +13 -3
  20. package/dist/src/db/queries/usage.js.map +4 -4
  21. package/dist/src/db/schema.d.ts +15 -1
  22. package/dist/src/env.js.map +2 -2
  23. package/dist/src/errors.js.map +2 -2
  24. package/dist/src/index.d.ts +59 -1
  25. package/dist/src/index.js +12 -8
  26. package/dist/src/index.js.map +12 -12
  27. package/dist/src/lib/plans.js.map +2 -2
  28. package/dist/src/logger.js.map +3 -3
  29. package/dist/src/node/archive-client.js +22 -6
  30. package/dist/src/node/archive-client.js.map +5 -5
  31. package/dist/src/node/client.js.map +2 -2
  32. package/dist/src/node/hiro-client.js +47 -11
  33. package/dist/src/node/hiro-client.js.map +5 -5
  34. package/dist/src/node/hiro-pg-client.js +131 -26
  35. package/dist/src/node/hiro-pg-client.js.map +3 -3
  36. package/dist/src/node/local-client.d.ts +12 -0
  37. package/dist/src/node/local-client.js.map +2 -2
  38. package/dist/src/queue/index.js +7 -5
  39. package/dist/src/queue/index.js.map +5 -5
  40. package/dist/src/queue/listener.js.map +2 -2
  41. package/dist/src/queue/recovery.js +5 -3
  42. package/dist/src/queue/recovery.js.map +5 -5
  43. package/dist/src/schemas/filters.js +2 -2
  44. package/dist/src/schemas/filters.js.map +3 -3
  45. package/dist/src/schemas/index.d.ts +45 -1
  46. package/dist/src/schemas/index.js +5 -3
  47. package/dist/src/schemas/index.js.map +5 -5
  48. package/dist/src/schemas/subgraphs.d.ts +45 -1
  49. package/dist/src/schemas/subgraphs.js.map +2 -2
  50. package/migrations/0001_initial.ts +295 -159
  51. package/migrations/0002_api_keys.ts +44 -28
  52. package/migrations/0003_tenant_isolation.ts +116 -107
  53. package/migrations/0004_accounts_and_usage.ts +81 -75
  54. package/migrations/0005_sessions.ts +33 -33
  55. package/migrations/0006_tx_index.ts +6 -2
  56. package/migrations/0007_contracts.ts +38 -24
  57. package/migrations/0008_drop_contracts.ts +33 -19
  58. package/migrations/0009_waitlist.ts +12 -12
  59. package/migrations/0010_waitlist_status.ts +5 -5
  60. package/migrations/0011_account_insights.ts +52 -52
  61. package/migrations/0012_view_health_snapshots.ts +21 -21
  62. package/migrations/0013_view_processing_stats.ts +32 -32
  63. package/migrations/0014_view_table_snapshots.ts +24 -24
  64. package/migrations/0015_rename_views_to_subgraphs.ts +137 -75
  65. package/migrations/0016_rename_webhook_to_endpoint.ts +12 -4
  66. package/migrations/0017_security_hardening.ts +23 -11
  67. package/migrations/0018_subgraph_gaps.ts +39 -0
  68. 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
  /**
@@ -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!,\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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",
8
+ "mappings": "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",
9
9
  "debugId": "CA1F8ED652ED1B5C64756E2164756E21",
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({ account_id: accountId, date: today, api_requests: 1, deliveries: 0 }).onConflict((oc) => oc.columns(["account_id", "date"]).doUpdateSet({
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({ account_id: accountId, date: today, api_requests: 0, deliveries: count }).onConflict((oc) => oc.columns(["account_id", "date"]).doUpdateSet({
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=DDD1942F27B4B83564756E2164756E21
152
+ //# debugId=C9AA7973D9E596F264756E2164756E21
143
153
  //# sourceMappingURL=usage.js.map