@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
@@ -56,7 +56,7 @@ function verifySignatureHeader(payload, header, secret, toleranceSeconds = 300)
56
56
  if (!timestamp || !signature) {
57
57
  return false;
58
58
  }
59
- const ts = parseInt(timestamp, 10);
59
+ const ts = Number.parseInt(timestamp, 10);
60
60
  if (isNaN(ts)) {
61
61
  return false;
62
62
  }
@@ -75,5 +75,5 @@ export {
75
75
  createSignatureHeader
76
76
  };
77
77
 
78
- //# debugId=A51CF8494ADEA11C64756E2164756E21
78
+ //# debugId=DCA3A03C2D7DBC2364756E2164756E21
79
79
  //# sourceMappingURL=hmac.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/crypto/hmac.ts"],
4
4
  "sourcesContent": [
5
- "import { createHmac, randomBytes } from \"crypto\";\n\n/**\n * Generate a random secret for delivery signing\n * Returns 32 bytes as a 64-character hex string\n */\nexport function generateSecret(): string {\n return randomBytes(32).toString(\"hex\");\n}\n\n/**\n * Sign a payload with HMAC-SHA256\n * Returns the signature as a hex string\n */\nexport function signPayload(payload: string, secret: string): string {\n const hmac = createHmac(\"sha256\", secret);\n hmac.update(payload);\n return hmac.digest(\"hex\");\n}\n\n/**\n * Verify an HMAC signature\n * Uses constant-time comparison to prevent timing attacks\n */\nexport function verifySignature(\n payload: string,\n signature: string,\n secret: string\n): boolean {\n const expectedSignature = signPayload(payload, secret);\n\n // Constant-time comparison\n if (signature.length !== expectedSignature.length) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < signature.length; i++) {\n result |= signature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);\n }\n\n return result === 0;\n}\n\n/**\n * Create a Stripe-style signature header\n * Format: t=timestamp,v1=signature\n */\nexport function createSignatureHeader(\n payload: string,\n secret: string,\n timestamp?: number\n): string {\n const ts = timestamp ?? Math.floor(Date.now() / 1000);\n const signedPayload = `${ts}.${payload}`;\n const signature = signPayload(signedPayload, secret);\n\n return `t=${ts},v1=${signature}`;\n}\n\n/**\n * Parse and verify a Stripe-style signature header\n * Returns true if valid, false otherwise\n */\nexport function verifySignatureHeader(\n payload: string,\n header: string,\n secret: string,\n toleranceSeconds = 300 // 5 minutes\n): boolean {\n // Parse header\n const parts = header.split(\",\");\n const timestamp = parts\n .find((p) => p.startsWith(\"t=\"))\n ?.slice(2);\n const signature = parts\n .find((p) => p.startsWith(\"v1=\"))\n ?.slice(3);\n\n if (!timestamp || !signature) {\n return false;\n }\n\n const ts = parseInt(timestamp, 10);\n if (isNaN(ts)) {\n return false;\n }\n\n // Check timestamp is within tolerance\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - ts) > toleranceSeconds) {\n return false;\n }\n\n // Verify signature\n const signedPayload = `${ts}.${payload}`;\n return verifySignature(signedPayload, signature, secret);\n}\n"
5
+ "import { createHmac, randomBytes } from \"crypto\";\n\n/**\n * Generate a random secret for delivery signing\n * Returns 32 bytes as a 64-character hex string\n */\nexport function generateSecret(): string {\n\treturn randomBytes(32).toString(\"hex\");\n}\n\n/**\n * Sign a payload with HMAC-SHA256\n * Returns the signature as a hex string\n */\nexport function signPayload(payload: string, secret: string): string {\n\tconst hmac = createHmac(\"sha256\", secret);\n\thmac.update(payload);\n\treturn hmac.digest(\"hex\");\n}\n\n/**\n * Verify an HMAC signature\n * Uses constant-time comparison to prevent timing attacks\n */\nexport function verifySignature(\n\tpayload: string,\n\tsignature: string,\n\tsecret: string,\n): boolean {\n\tconst expectedSignature = signPayload(payload, secret);\n\n\t// Constant-time comparison\n\tif (signature.length !== expectedSignature.length) {\n\t\treturn false;\n\t}\n\n\tlet result = 0;\n\tfor (let i = 0; i < signature.length; i++) {\n\t\tresult |= signature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);\n\t}\n\n\treturn result === 0;\n}\n\n/**\n * Create a Stripe-style signature header\n * Format: t=timestamp,v1=signature\n */\nexport function createSignatureHeader(\n\tpayload: string,\n\tsecret: string,\n\ttimestamp?: number,\n): string {\n\tconst ts = timestamp ?? Math.floor(Date.now() / 1000);\n\tconst signedPayload = `${ts}.${payload}`;\n\tconst signature = signPayload(signedPayload, secret);\n\n\treturn `t=${ts},v1=${signature}`;\n}\n\n/**\n * Parse and verify a Stripe-style signature header\n * Returns true if valid, false otherwise\n */\nexport function verifySignatureHeader(\n\tpayload: string,\n\theader: string,\n\tsecret: string,\n\ttoleranceSeconds = 300, // 5 minutes\n): boolean {\n\t// Parse header\n\tconst parts = header.split(\",\");\n\tconst timestamp = parts.find((p) => p.startsWith(\"t=\"))?.slice(2);\n\tconst signature = parts.find((p) => p.startsWith(\"v1=\"))?.slice(3);\n\n\tif (!timestamp || !signature) {\n\t\treturn false;\n\t}\n\n\tconst ts = Number.parseInt(timestamp, 10);\n\tif (isNaN(ts)) {\n\t\treturn false;\n\t}\n\n\t// Check timestamp is within tolerance\n\tconst now = Math.floor(Date.now() / 1000);\n\tif (Math.abs(now - ts) > toleranceSeconds) {\n\t\treturn false;\n\t}\n\n\t// Verify signature\n\tconst signedPayload = `${ts}.${payload}`;\n\treturn verifySignature(signedPayload, signature, secret);\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAMO,SAAS,cAAc,GAAW;AAAA,EACvC,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAAA;AAOhC,SAAS,WAAW,CAAC,SAAiB,QAAwB;AAAA,EACnE,MAAM,OAAO,WAAW,UAAU,MAAM;AAAA,EACxC,KAAK,OAAO,OAAO;AAAA,EACnB,OAAO,KAAK,OAAO,KAAK;AAAA;AAOnB,SAAS,eAAe,CAC7B,SACA,WACA,QACS;AAAA,EACT,MAAM,oBAAoB,YAAY,SAAS,MAAM;AAAA,EAGrD,IAAI,UAAU,WAAW,kBAAkB,QAAQ;AAAA,IACjD,OAAO;AAAA,EACT;AAAA,EAEA,IAAI,SAAS;AAAA,EACb,SAAS,IAAI,EAAG,IAAI,UAAU,QAAQ,KAAK;AAAA,IACzC,UAAU,UAAU,WAAW,CAAC,IAAI,kBAAkB,WAAW,CAAC;AAAA,EACpE;AAAA,EAEA,OAAO,WAAW;AAAA;AAOb,SAAS,qBAAqB,CACnC,SACA,QACA,WACQ;AAAA,EACR,MAAM,KAAK,aAAa,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI;AAAA,EACpD,MAAM,gBAAgB,GAAG,MAAM;AAAA,EAC/B,MAAM,YAAY,YAAY,eAAe,MAAM;AAAA,EAEnD,OAAO,KAAK,SAAS;AAAA;AAOhB,SAAS,qBAAqB,CACnC,SACA,QACA,QACA,mBAAmB,KACV;AAAA,EAET,MAAM,QAAQ,OAAO,MAAM,GAAG;AAAA,EAC9B,MAAM,YAAY,MACf,KAAK,CAAC,MAAM,EAAE,WAAW,IAAI,CAAC,GAC7B,MAAM,CAAC;AAAA,EACX,MAAM,YAAY,MACf,KAAK,CAAC,MAAM,EAAE,WAAW,KAAK,CAAC,GAC9B,MAAM,CAAC;AAAA,EAEX,IAAI,CAAC,aAAa,CAAC,WAAW;AAAA,IAC5B,OAAO;AAAA,EACT;AAAA,EAEA,MAAM,KAAK,SAAS,WAAW,EAAE;AAAA,EACjC,IAAI,MAAM,EAAE,GAAG;AAAA,IACb,OAAO;AAAA,EACT;AAAA,EAGA,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI;AAAA,EACxC,IAAI,KAAK,IAAI,MAAM,EAAE,IAAI,kBAAkB;AAAA,IACzC,OAAO;AAAA,EACT;AAAA,EAGA,MAAM,gBAAgB,GAAG,MAAM;AAAA,EAC/B,OAAO,gBAAgB,eAAe,WAAW,MAAM;AAAA;",
8
- "debugId": "A51CF8494ADEA11C64756E2164756E21",
7
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAMO,SAAS,cAAc,GAAW;AAAA,EACxC,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAAA;AAO/B,SAAS,WAAW,CAAC,SAAiB,QAAwB;AAAA,EACpE,MAAM,OAAO,WAAW,UAAU,MAAM;AAAA,EACxC,KAAK,OAAO,OAAO;AAAA,EACnB,OAAO,KAAK,OAAO,KAAK;AAAA;AAOlB,SAAS,eAAe,CAC9B,SACA,WACA,QACU;AAAA,EACV,MAAM,oBAAoB,YAAY,SAAS,MAAM;AAAA,EAGrD,IAAI,UAAU,WAAW,kBAAkB,QAAQ;AAAA,IAClD,OAAO;AAAA,EACR;AAAA,EAEA,IAAI,SAAS;AAAA,EACb,SAAS,IAAI,EAAG,IAAI,UAAU,QAAQ,KAAK;AAAA,IAC1C,UAAU,UAAU,WAAW,CAAC,IAAI,kBAAkB,WAAW,CAAC;AAAA,EACnE;AAAA,EAEA,OAAO,WAAW;AAAA;AAOZ,SAAS,qBAAqB,CACpC,SACA,QACA,WACS;AAAA,EACT,MAAM,KAAK,aAAa,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI;AAAA,EACpD,MAAM,gBAAgB,GAAG,MAAM;AAAA,EAC/B,MAAM,YAAY,YAAY,eAAe,MAAM;AAAA,EAEnD,OAAO,KAAK,SAAS;AAAA;AAOf,SAAS,qBAAqB,CACpC,SACA,QACA,QACA,mBAAmB,KACT;AAAA,EAEV,MAAM,QAAQ,OAAO,MAAM,GAAG;AAAA,EAC9B,MAAM,YAAY,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,IAAI,CAAC,GAAG,MAAM,CAAC;AAAA,EAChE,MAAM,YAAY,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,KAAK,CAAC,GAAG,MAAM,CAAC;AAAA,EAEjE,IAAI,CAAC,aAAa,CAAC,WAAW;AAAA,IAC7B,OAAO;AAAA,EACR;AAAA,EAEA,MAAM,KAAK,OAAO,SAAS,WAAW,EAAE;AAAA,EACxC,IAAI,MAAM,EAAE,GAAG;AAAA,IACd,OAAO;AAAA,EACR;AAAA,EAGA,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI;AAAA,EACxC,IAAI,KAAK,IAAI,MAAM,EAAE,IAAI,kBAAkB;AAAA,IAC1C,OAAO;AAAA,EACR;AAAA,EAGA,MAAM,gBAAgB,GAAG,MAAM;AAAA,EAC/B,OAAO,gBAAgB,eAAe,WAAW,MAAM;AAAA;",
8
+ "debugId": "DCA3A03C2D7DBC2364756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -52,7 +52,7 @@ interface StreamsTable {
52
52
  options: Generated<unknown>;
53
53
  endpoint_url: string;
54
54
  signing_secret: string | null;
55
- api_key_id: string | null;
55
+ api_key_id: string;
56
56
  created_at: Generated<Date>;
57
57
  updated_at: Generated<Date>;
58
58
  }
@@ -106,15 +106,26 @@ interface SubgraphsTable {
106
106
  schema_hash: string;
107
107
  handler_path: string;
108
108
  schema_name: string | null;
109
+ start_block: Generated<number>;
109
110
  last_processed_block: Generated<number>;
110
111
  last_error: string | null;
111
112
  last_error_at: Date | null;
112
113
  total_processed: Generated<number>;
113
114
  total_errors: Generated<number>;
114
- api_key_id: string | null;
115
+ api_key_id: string;
115
116
  created_at: Generated<Date>;
116
117
  updated_at: Generated<Date>;
117
118
  }
119
+ interface SubgraphGapsTable {
120
+ id: Generated<string>;
121
+ subgraph_id: string;
122
+ subgraph_name: string;
123
+ gap_start: number;
124
+ gap_end: number;
125
+ reason: string;
126
+ detected_at: Generated<Date>;
127
+ resolved_at: Date | null;
128
+ }
118
129
  interface ApiKeysTable {
119
130
  id: Generated<string>;
120
131
  key_hash: string;
@@ -151,6 +162,7 @@ interface MagicLinksTable {
151
162
  token: string;
152
163
  expires_at: Date;
153
164
  used_at: Date | null;
165
+ failed_attempts: Generated<number>;
154
166
  created_at: Generated<Date>;
155
167
  }
156
168
  interface UsageDailyTable {
@@ -252,6 +264,7 @@ interface Database {
252
264
  subgraph_health_snapshots: SubgraphHealthSnapshotsTable;
253
265
  subgraph_processing_stats: SubgraphProcessingStatsTable;
254
266
  subgraph_table_snapshots: SubgraphTableSnapshotsTable;
267
+ subgraph_gaps: SubgraphGapsTable;
255
268
  }
256
269
  type Block = Selectable<BlocksTable>;
257
270
  type InsertBlock = Insertable<BlocksTable>;
@@ -297,10 +310,12 @@ type AccountAgentRun = Selectable<AccountAgentRunsTable>;
297
310
  type InsertAccountAgentRun = Insertable<AccountAgentRunsTable>;
298
311
  type SubgraphHealthSnapshot = Selectable<SubgraphHealthSnapshotsTable>;
299
312
  type InsertSubgraphHealthSnapshot = Insertable<SubgraphHealthSnapshotsTable>;
313
+ type SubgraphGap = Selectable<SubgraphGapsTable>;
314
+ type InsertSubgraphGap = Insertable<SubgraphGapsTable>;
300
315
  import { sql } from "kysely";
301
316
  declare function getDb(connectionString?: string): Kysely<Database>;
302
317
  /** Raw postgres.js client for dynamic schema DDL (CREATE SCHEMA, DROP, etc.) */
303
318
  declare function getRawClient(): ReturnType<typeof postgres>;
304
319
  /** Close the DB connection pool. Call in CLI commands to allow process exit. */
305
320
  declare function closeDb(): Promise<void>;
306
- export { sql, parseJsonb, jsonb, getRawClient, getDb, closeDb, WaitlistTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateTransaction, UpdateSubgraph, UpdateStreamRow, UpdateStreamMetrics, UpdateJob, UpdateIndexProgress, UpdateEvent, UpdateDelivery, UpdateBlock, UpdateApiKey, TransactionsTable, Transaction, SubgraphsTable, SubgraphTableSnapshotsTable, SubgraphProcessingStatsTable, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, Subgraph, StreamsTable, StreamMetricsTable, StreamMetrics, Stream, SessionsTable, Session, MagicLinksTable, MagicLink, JobsTable, Job, InsertTransaction, InsertSubgraphHealthSnapshot, InsertSubgraph, InsertStreamMetrics, InsertStream, InsertSession, InsertMagicLink, InsertJob, InsertIndexProgress, InsertEvent, InsertDelivery, InsertBlock, InsertApiKey, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, EventsTable, Event, Delivery, DeliveriesTable, Database, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
321
+ export { sql, parseJsonb, jsonb, getRawClient, getDb, closeDb, WaitlistTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateTransaction, UpdateSubgraph, UpdateStreamRow, UpdateStreamMetrics, UpdateJob, UpdateIndexProgress, UpdateEvent, UpdateDelivery, UpdateBlock, UpdateApiKey, TransactionsTable, Transaction, SubgraphsTable, SubgraphTableSnapshotsTable, SubgraphProcessingStatsTable, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGap, Subgraph, StreamsTable, StreamMetricsTable, StreamMetrics, Stream, SessionsTable, Session, MagicLinksTable, MagicLink, JobsTable, Job, InsertTransaction, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertStreamMetrics, InsertStream, InsertSession, InsertMagicLink, InsertJob, InsertIndexProgress, InsertEvent, InsertDelivery, InsertBlock, InsertApiKey, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, EventsTable, Event, Delivery, DeliveriesTable, Database, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
@@ -42,8 +42,12 @@ function getDb(connectionString) {
42
42
  if (!db) {
43
43
  const url = connectionString || process.env.DATABASE_URL || "postgres://postgres:postgres@localhost:5432/streams_dev";
44
44
  const isLocal = url.includes("localhost") || url.includes("127.0.0.1") || url.includes("@postgres:");
45
+ const poolMax = Number.parseInt(process.env.DATABASE_POOL_MAX ?? "20", 10);
45
46
  rawClient = postgres(url, {
46
- ssl: isLocal ? undefined : { rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0" }
47
+ max: poolMax,
48
+ ssl: isLocal ? undefined : {
49
+ rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0"
50
+ }
47
51
  });
48
52
  db = new Kysely({
49
53
  dialect: new PostgresJSDialect({ postgres: rawClient })
@@ -75,5 +79,5 @@ export {
75
79
  closeDb
76
80
  };
77
81
 
78
- //# debugId=0350F4EABDF4D64C64756E2164756E21
82
+ //# debugId=AB57A20678E6DFB964756E2164756E21
79
83
  //# sourceMappingURL=index.js.map
@@ -2,10 +2,10 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/jsonb.ts", "../src/db/index.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 { Kysely } from \"kysely\";\nimport { PostgresJSDialect } from \"kysely-postgres-js\";\nimport postgres from \"postgres\";\nimport type { Database } from \"./types.ts\";\n\nlet db: Kysely<Database> | null = null;\nlet rawClient: ReturnType<typeof postgres> | null = null;\n\nexport function getDb(connectionString?: string): Kysely<Database> {\n if (!db) {\n const url = connectionString || process.env.DATABASE_URL || \"postgres://postgres:postgres@localhost:5432/streams_dev\";\n\n // Always use SSL for remote databases, just disable cert verification if needed\n const isLocal = url.includes(\"localhost\") || url.includes(\"127.0.0.1\") || url.includes(\"@postgres:\");\n rawClient = postgres(url, {\n ssl: isLocal ? undefined : { rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== \"0\" },\n });\n db = new Kysely<Database>({\n dialect: new PostgresJSDialect({ postgres: rawClient }),\n });\n }\n return db;\n}\n\n/** Raw postgres.js client for dynamic schema DDL (CREATE SCHEMA, DROP, etc.) */\nexport function getRawClient(): ReturnType<typeof postgres> {\n if (!rawClient) getDb();\n return rawClient!;\n}\n\n/** Close the DB connection pool. Call in CLI commands to allow process exit. */\nexport async function closeDb(): Promise<void> {\n if (db) {\n await db.destroy();\n db = null;\n }\n if (rawClient) {\n await rawClient.end();\n rawClient = null;\n }\n}\n\nimport { sql } from \"kysely\";\nexport { sql };\nexport * from \"./types.ts\";\nexport { jsonb, parseJsonb } from \"./jsonb.ts\";\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 { Kysely } from \"kysely\";\nimport { PostgresJSDialect } from \"kysely-postgres-js\";\nimport postgres from \"postgres\";\nimport type { Database } from \"./types.ts\";\n\nlet db: Kysely<Database> | null = null;\nlet rawClient: ReturnType<typeof postgres> | null = null;\n\nexport function getDb(connectionString?: string): Kysely<Database> {\n\tif (!db) {\n\t\tconst url =\n\t\t\tconnectionString ||\n\t\t\tprocess.env.DATABASE_URL ||\n\t\t\t\"postgres://postgres:postgres@localhost:5432/streams_dev\";\n\n\t\t// Always use SSL for remote databases, just disable cert verification if needed\n\t\tconst isLocal =\n\t\t\turl.includes(\"localhost\") ||\n\t\t\turl.includes(\"127.0.0.1\") ||\n\t\t\turl.includes(\"@postgres:\");\n\t\tconst poolMax = Number.parseInt(process.env.DATABASE_POOL_MAX ?? \"20\", 10);\n\t\trawClient = postgres(url, {\n\t\t\tmax: poolMax,\n\t\t\tssl: isLocal\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\trejectUnauthorized:\n\t\t\t\t\t\t\tprocess.env.NODE_TLS_REJECT_UNAUTHORIZED !== \"0\",\n\t\t\t\t\t},\n\t\t});\n\t\tdb = new Kysely<Database>({\n\t\t\tdialect: new PostgresJSDialect({ postgres: rawClient }),\n\t\t});\n\t}\n\treturn db;\n}\n\n/** Raw postgres.js client for dynamic schema DDL (CREATE SCHEMA, DROP, etc.) */\nexport function getRawClient(): ReturnType<typeof postgres> {\n\tif (!rawClient) getDb();\n\treturn rawClient!;\n}\n\n/** Close the DB connection pool. Call in CLI commands to allow process exit. */\nexport async function closeDb(): Promise<void> {\n\tif (db) {\n\t\tawait db.destroy();\n\t\tdb = null;\n\t}\n\tif (rawClient) {\n\t\tawait rawClient.end();\n\t\trawClient = null;\n\t}\n}\n\nimport { sql } from \"kysely\";\nexport { sql };\nexport * from \"./types.ts\";\nexport { jsonb, parseJsonb } from \"./jsonb.ts\";\n"
7
7
  ],
8
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAOO,SAAS,KAAK,CAAC,OAAqC;AAAA,EACzD,MAAM,UAAU,KAAK,UAAU,KAAK,EAAE,QAAQ,MAAM,IAAI;AAAA,EACxD,OAAO,MAAM,IAAI,IAAI,IAAI,iBAAiB;AAAA;AAQrC,SAAS,UAAuB,CAAC,OAAmB;AAAA,EACzD,IAAI,OAAO,UAAU,UAAU;AAAA,IAC7B,IAAI;AAAA,MACF,OAAO,KAAK,MAAM,KAAK;AAAA,MACvB,MAAM;AAAA,MACN,OAAO;AAAA;AAAA,EAEX;AAAA,EACA,OAAQ,SAAS,CAAC;AAAA;;;ACzBpB;AACA;AACA;AAwCA,gBAAS;AArCT,IAAI,KAA8B;AAClC,IAAI,YAAgD;AAE7C,SAAS,KAAK,CAAC,kBAA6C;AAAA,EACjE,IAAI,CAAC,IAAI;AAAA,IACP,MAAM,MAAM,oBAAoB,QAAQ,IAAI,gBAAgB;AAAA,IAG5D,MAAM,UAAU,IAAI,SAAS,WAAW,KAAK,IAAI,SAAS,WAAW,KAAK,IAAI,SAAS,YAAY;AAAA,IACnG,YAAY,SAAS,KAAK;AAAA,MACxB,KAAK,UAAU,YAAY,EAAE,oBAAoB,QAAQ,IAAI,iCAAiC,IAAI;AAAA,IACpG,CAAC;AAAA,IACD,KAAK,IAAI,OAAiB;AAAA,MACxB,SAAS,IAAI,kBAAkB,EAAE,UAAU,UAAU,CAAC;AAAA,IACxD,CAAC;AAAA,EACH;AAAA,EACA,OAAO;AAAA;AAIF,SAAS,YAAY,GAAgC;AAAA,EAC1D,IAAI,CAAC;AAAA,IAAW,MAAM;AAAA,EACtB,OAAO;AAAA;AAIT,eAAsB,OAAO,GAAkB;AAAA,EAC7C,IAAI,IAAI;AAAA,IACN,MAAM,GAAG,QAAQ;AAAA,IACjB,KAAK;AAAA,EACP;AAAA,EACA,IAAI,WAAW;AAAA,IACb,MAAM,UAAU,IAAI;AAAA,IACpB,YAAY;AAAA,EACd;AAAA;",
9
- "debugId": "0350F4EABDF4D64C64756E2164756E21",
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;AACA;AACA;AAqDA,gBAAS;AAlDT,IAAI,KAA8B;AAClC,IAAI,YAAgD;AAE7C,SAAS,KAAK,CAAC,kBAA6C;AAAA,EAClE,IAAI,CAAC,IAAI;AAAA,IACR,MAAM,MACL,oBACA,QAAQ,IAAI,gBACZ;AAAA,IAGD,MAAM,UACL,IAAI,SAAS,WAAW,KACxB,IAAI,SAAS,WAAW,KACxB,IAAI,SAAS,YAAY;AAAA,IAC1B,MAAM,UAAU,OAAO,SAAS,QAAQ,IAAI,qBAAqB,MAAM,EAAE;AAAA,IACzE,YAAY,SAAS,KAAK;AAAA,MACzB,KAAK;AAAA,MACL,KAAK,UACF,YACA;AAAA,QACA,oBACC,QAAQ,IAAI,iCAAiC;AAAA,MAC/C;AAAA,IACH,CAAC;AAAA,IACD,KAAK,IAAI,OAAiB;AAAA,MACzB,SAAS,IAAI,kBAAkB,EAAE,UAAU,UAAU,CAAC;AAAA,IACvD,CAAC;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAID,SAAS,YAAY,GAAgC;AAAA,EAC3D,IAAI,CAAC;AAAA,IAAW,MAAM;AAAA,EACtB,OAAO;AAAA;AAIR,eAAsB,OAAO,GAAkB;AAAA,EAC9C,IAAI,IAAI;AAAA,IACP,MAAM,GAAG,QAAQ;AAAA,IACjB,KAAK;AAAA,EACN;AAAA,EACA,IAAI,WAAW;AAAA,IACd,MAAM,UAAU,IAAI;AAAA,IACpB,YAAY;AAAA,EACb;AAAA;",
9
+ "debugId": "AB57A20678E6DFB964756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/jsonb.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"
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
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAOO,SAAS,KAAK,CAAC,OAAqC;AAAA,EACzD,MAAM,UAAU,KAAK,UAAU,KAAK,EAAE,QAAQ,MAAM,IAAI;AAAA,EACxD,OAAO,MAAM,IAAI,IAAI,IAAI,iBAAiB;AAAA;AAQrC,SAAS,UAAuB,CAAC,OAAmB;AAAA,EACzD,IAAI,OAAO,UAAU,UAAU;AAAA,IAC7B,IAAI;AAAA,MACF,OAAO,KAAK,MAAM,KAAK;AAAA,MACvB,MAAM;AAAA,MACN,OAAO;AAAA;AAAA,EAEX;AAAA,EACA,OAAQ,SAAS,CAAC;AAAA;",
7
+ "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;",
8
8
  "debugId": "A6577D9BF54F45FE64756E2164756E21",
9
9
  "names": []
10
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 Account = Selectable<AccountsTable>;
243
256
  declare function upsertAccount(db: Kysely<Database>, email: string): Promise<Account>;
@@ -246,7 +259,7 @@ declare function isEmailAllowed(db: Kysely<Database>, email: string): Promise<bo
246
259
  declare function createMagicLink(db: Kysely<Database>, email: string, token: string, expiresInMs?: number): Promise<void>;
247
260
  /**
248
261
  * Verify a magic link token. Returns the email if valid, null otherwise.
249
- * Marks the token as used atomically.
262
+ * Marks the token as used atomically. Rejects after 5 failed attempts.
250
263
  */
251
264
  declare function verifyMagicLink(db: Kysely<Database>, token: string): Promise<string | null>;
252
265
  export { verifyMagicLink, upsertAccount, isEmailAllowed, getAccountById, createMagicLink };
@@ -39,8 +39,11 @@ async function createMagicLink(db, email, token, expiresInMs = 15 * 60 * 1000) {
39
39
  }).execute();
40
40
  }
41
41
  async function verifyMagicLink(db, token) {
42
- const result = await db.updateTable("magic_links").set({ used_at: new Date }).where("token", "=", token).where("used_at", "is", null).where("expires_at", ">", new Date).returning("email").executeTakeFirst();
43
- return result?.email ?? null;
42
+ const result = await db.updateTable("magic_links").set({ used_at: new Date }).where("token", "=", token).where("used_at", "is", null).where("expires_at", ">", new Date).where("failed_attempts", "<", 5).returning("email").executeTakeFirst();
43
+ if (result?.email)
44
+ return result.email;
45
+ await db.updateTable("magic_links").set({ failed_attempts: sql`failed_attempts + 1` }).where("token", "=", token).where("used_at", "is", null).where("expires_at", ">", new Date).execute();
46
+ return null;
44
47
  }
45
48
  export {
46
49
  verifyMagicLink,
@@ -50,5 +53,5 @@ export {
50
53
  createMagicLink
51
54
  };
52
55
 
53
- //# debugId=F1197D197EF2B0A364756E2164756E21
56
+ //# debugId=20A55F621EB14D5064756E2164756E21
54
57
  //# sourceMappingURL=accounts.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/queries/accounts.ts"],
4
4
  "sourcesContent": [
5
- "import { sql, type Kysely } from \"kysely\";\nimport type { Database, Account } from \"../types.ts\";\n\nexport async function upsertAccount(\n db: Kysely<Database>,\n email: string,\n): Promise<Account> {\n return await db\n .insertInto(\"accounts\")\n .values({ email })\n .onConflict((oc) =>\n oc.column(\"email\").doUpdateSet({ email }), // no-op update to return existing\n )\n .returningAll()\n .executeTakeFirstOrThrow();\n}\n\nexport async function getAccountById(\n db: Kysely<Database>,\n id: string,\n): Promise<Account | null> {\n return (\n (await db\n .selectFrom(\"accounts\")\n .selectAll()\n .where(\"id\", \"=\", id)\n .executeTakeFirst()) ?? null\n );\n}\n\nexport async function isEmailAllowed(\n db: Kysely<Database>,\n email: string,\n): Promise<boolean> {\n const result = await sql<{ found: number }>`\n SELECT 1 AS found FROM accounts WHERE email = ${email}\n UNION ALL\n SELECT 1 AS found FROM waitlist WHERE email = ${email} AND status = 'approved'\n LIMIT 1\n `.execute(db);\n\n return result.rows.length > 0;\n}\n\nexport async function createMagicLink(\n db: Kysely<Database>,\n email: string,\n token: string,\n expiresInMs: number = 15 * 60 * 1000,\n): Promise<void> {\n await db\n .insertInto(\"magic_links\")\n .values({\n email,\n token,\n expires_at: new Date(Date.now() + expiresInMs),\n })\n .execute();\n}\n\n/**\n * Verify a magic link token. Returns the email if valid, null otherwise.\n * Marks the token as used atomically.\n */\nexport async function verifyMagicLink(\n db: Kysely<Database>,\n token: string,\n): Promise<string | null> {\n const result = await db\n .updateTable(\"magic_links\")\n .set({ used_at: new Date() })\n .where(\"token\", \"=\", token)\n .where(\"used_at\", \"is\", null)\n .where(\"expires_at\", \">\", new Date())\n .returning(\"email\")\n .executeTakeFirst();\n\n return result?.email ?? null;\n}\n"
5
+ "import { type Kysely, sql } from \"kysely\";\nimport type { Account, Database } from \"../types.ts\";\n\nexport async function upsertAccount(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<Account> {\n\treturn await db\n\t\t.insertInto(\"accounts\")\n\t\t.values({ email })\n\t\t.onConflict(\n\t\t\t(oc) => oc.column(\"email\").doUpdateSet({ email }), // no-op update to return existing\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getAccountById(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<Account | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"accounts\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function isEmailAllowed(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<boolean> {\n\tconst result = await sql<{ found: number }>`\n SELECT 1 AS found FROM accounts WHERE email = ${email}\n UNION ALL\n SELECT 1 AS found FROM waitlist WHERE email = ${email} AND status = 'approved'\n LIMIT 1\n `.execute(db);\n\n\treturn result.rows.length > 0;\n}\n\nexport async function createMagicLink(\n\tdb: Kysely<Database>,\n\temail: string,\n\ttoken: string,\n\texpiresInMs: number = 15 * 60 * 1000,\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"magic_links\")\n\t\t.values({\n\t\t\temail,\n\t\t\ttoken,\n\t\t\texpires_at: new Date(Date.now() + expiresInMs),\n\t\t})\n\t\t.execute();\n}\n\n/**\n * Verify a magic link token. Returns the email if valid, null otherwise.\n * Marks the token as used atomically. Rejects after 5 failed attempts.\n */\nexport async function verifyMagicLink(\n\tdb: Kysely<Database>,\n\ttoken: string,\n): Promise<string | null> {\n\tconst result = await db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ used_at: new Date() })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.where(\"failed_attempts\", \"<\", 5)\n\t\t.returning(\"email\")\n\t\t.executeTakeFirst();\n\n\tif (result?.email) return result.email;\n\n\t// Increment failed attempts if token exists but didn't verify\n\tawait db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ failed_attempts: sql`failed_attempts + 1` })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.execute();\n\n\treturn null;\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAGA,eAAsB,aAAa,CACjC,IACA,OACkB;AAAA,EAClB,OAAO,MAAM,GACV,WAAW,UAAU,EACrB,OAAO,EAAE,MAAM,CAAC,EAChB,WAAW,CAAC,OACX,GAAG,OAAO,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CAC1C,EACC,aAAa,EACb,wBAAwB;AAAA;AAG7B,eAAsB,cAAc,CAClC,IACA,IACyB;AAAA,EACzB,OACG,MAAM,GACJ,WAAW,UAAU,EACrB,UAAU,EACV,MAAM,MAAM,KAAK,EAAE,EACnB,iBAAiB,KAAM;AAAA;AAI9B,eAAsB,cAAc,CAClC,IACA,OACkB;AAAA,EAClB,MAAM,SAAS,MAAM;AAAA,oDAC6B;AAAA;AAAA,oDAEA;AAAA;AAAA,IAEhD,QAAQ,EAAE;AAAA,EAEZ,OAAO,OAAO,KAAK,SAAS;AAAA;AAG9B,eAAsB,eAAe,CACnC,IACA,OACA,OACA,cAAsB,KAAK,KAAK,MACjB;AAAA,EACf,MAAM,GACH,WAAW,aAAa,EACxB,OAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,WAAW;AAAA,EAC/C,CAAC,EACA,QAAQ;AAAA;AAOb,eAAsB,eAAe,CACnC,IACA,OACwB;AAAA,EACxB,MAAM,SAAS,MAAM,GAClB,YAAY,aAAa,EACzB,IAAI,EAAE,SAAS,IAAI,KAAO,CAAC,EAC3B,MAAM,SAAS,KAAK,KAAK,EACzB,MAAM,WAAW,MAAM,IAAI,EAC3B,MAAM,cAAc,KAAK,IAAI,IAAM,EACnC,UAAU,OAAO,EACjB,iBAAiB;AAAA,EAEpB,OAAO,QAAQ,SAAS;AAAA;",
8
- "debugId": "F1197D197EF2B0A364756E2164756E21",
7
+ "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAGA,eAAsB,aAAa,CAClC,IACA,OACmB;AAAA,EACnB,OAAO,MAAM,GACX,WAAW,UAAU,EACrB,OAAO,EAAE,MAAM,CAAC,EAChB,WACA,CAAC,OAAO,GAAG,OAAO,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CACjD,EACC,aAAa,EACb,wBAAwB;AAAA;AAG3B,eAAsB,cAAc,CACnC,IACA,IAC0B;AAAA,EAC1B,OACE,MAAM,GACL,WAAW,UAAU,EACrB,UAAU,EACV,MAAM,MAAM,KAAK,EAAE,EACnB,iBAAiB,KAAM;AAAA;AAI3B,eAAsB,cAAc,CACnC,IACA,OACmB;AAAA,EACnB,MAAM,SAAS,MAAM;AAAA,oDAC8B;AAAA;AAAA,oDAEA;AAAA;AAAA,IAEhD,QAAQ,EAAE;AAAA,EAEb,OAAO,OAAO,KAAK,SAAS;AAAA;AAG7B,eAAsB,eAAe,CACpC,IACA,OACA,OACA,cAAsB,KAAK,KAAK,MAChB;AAAA,EAChB,MAAM,GACJ,WAAW,aAAa,EACxB,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,WAAW;AAAA,EAC9C,CAAC,EACA,QAAQ;AAAA;AAOX,eAAsB,eAAe,CACpC,IACA,OACyB;AAAA,EACzB,MAAM,SAAS,MAAM,GACnB,YAAY,aAAa,EACzB,IAAI,EAAE,SAAS,IAAI,KAAO,CAAC,EAC3B,MAAM,SAAS,KAAK,KAAK,EACzB,MAAM,WAAW,MAAM,IAAI,EAC3B,MAAM,cAAc,KAAK,IAAI,IAAM,EACnC,MAAM,mBAAmB,KAAK,CAAC,EAC/B,UAAU,OAAO,EACjB,iBAAiB;AAAA,EAEnB,IAAI,QAAQ;AAAA,IAAO,OAAO,OAAO;AAAA,EAGjC,MAAM,GACJ,YAAY,aAAa,EACzB,IAAI,EAAE,iBAAiB,yBAAyB,CAAC,EACjD,MAAM,SAAS,KAAK,KAAK,EACzB,MAAM,WAAW,MAAM,IAAI,EAC3B,MAAM,cAAc,KAAK,IAAI,IAAM,EACnC,QAAQ;AAAA,EAEV,OAAO;AAAA;",
8
+ "debugId": "20A55F621EB14D5064756E2164756E21",
9
9
  "names": []
10
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
  interface Gap {
243
256
  gapStart: number;
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/queries/integrity.ts"],
4
4
  "sourcesContent": [
5
- "import { sql, type Kysely } from \"kysely\";\nimport type { Database } from \"../types.ts\";\n\nexport interface Gap {\n gapStart: number;\n gapEnd: number;\n size: number;\n}\n\nexport async function findGaps(db: Kysely<Database>, limit?: number): Promise<Gap[]> {\n const limitClause = limit ? sql`LIMIT ${limit}` : sql``;\n const { rows } = await sql<{ gap_start: string; gap_end: string; size: string }>`\n SELECT gap_start, gap_end, gap_end - gap_start + 1 AS size\n FROM (\n SELECT height + 1 AS gap_start, next_height - 1 AS gap_end\n FROM (\n SELECT height, LEAD(height) OVER (ORDER BY height) AS next_height\n FROM blocks WHERE canonical = true\n ) sub\n WHERE next_height - height > 1\n ) gaps\n ORDER BY gap_start\n ${limitClause}\n `.execute(db);\n\n return rows.map((r) => ({\n gapStart: Number(r.gap_start),\n gapEnd: Number(r.gap_end),\n size: Number(r.size),\n }));\n}\n\nexport async function countMissingBlocks(db: Kysely<Database>): Promise<number> {\n const { rows } = await sql<{ total: string }>`\n SELECT COALESCE(SUM(next_height - height - 1), 0) AS total\n FROM (\n SELECT height, LEAD(height) OVER (ORDER BY height) AS next_height\n FROM blocks WHERE canonical = true\n ) sub\n WHERE next_height - height > 1\n `.execute(db);\n\n return Number(rows[0]?.total ?? 0);\n}\n\nexport async function computeContiguousTip(db: Kysely<Database>, fromHeight: number): Promise<number> {\n const { rows } = await sql<{ tip: string }>`\n SELECT COALESCE(MAX(height), ${fromHeight}) AS tip\n FROM (\n SELECT height, height - ROW_NUMBER() OVER (ORDER BY height) AS grp\n FROM blocks WHERE canonical = true AND height >= ${fromHeight}\n ) sub\n WHERE grp = (\n SELECT height - ROW_NUMBER() OVER (ORDER BY height)\n FROM blocks WHERE canonical = true AND height = ${fromHeight}\n )\n `.execute(db);\n\n return Number(rows[0]?.tip ?? fromHeight);\n}\n"
5
+ "import { type Kysely, sql } from \"kysely\";\nimport type { Database } from \"../types.ts\";\n\nexport interface Gap {\n\tgapStart: number;\n\tgapEnd: number;\n\tsize: number;\n}\n\nexport async function findGaps(\n\tdb: Kysely<Database>,\n\tlimit?: number,\n): Promise<Gap[]> {\n\tconst limitClause = limit ? sql`LIMIT ${limit}` : sql``;\n\tconst { rows } = await sql<{\n\t\tgap_start: string;\n\t\tgap_end: string;\n\t\tsize: string;\n\t}>`\n SELECT gap_start, gap_end, gap_end - gap_start + 1 AS size\n FROM (\n SELECT height + 1 AS gap_start, next_height - 1 AS gap_end\n FROM (\n SELECT height, LEAD(height) OVER (ORDER BY height) AS next_height\n FROM blocks WHERE canonical = true\n ) sub\n WHERE next_height - height > 1\n ) gaps\n ORDER BY gap_start\n ${limitClause}\n `.execute(db);\n\n\treturn rows.map((r) => ({\n\t\tgapStart: Number(r.gap_start),\n\t\tgapEnd: Number(r.gap_end),\n\t\tsize: Number(r.size),\n\t}));\n}\n\nexport async function countMissingBlocks(\n\tdb: Kysely<Database>,\n): Promise<number> {\n\tconst { rows } = await sql<{ total: string }>`\n SELECT COALESCE(SUM(next_height - height - 1), 0) AS total\n FROM (\n SELECT height, LEAD(height) OVER (ORDER BY height) AS next_height\n FROM blocks WHERE canonical = true\n ) sub\n WHERE next_height - height > 1\n `.execute(db);\n\n\treturn Number(rows[0]?.total ?? 0);\n}\n\nexport async function computeContiguousTip(\n\tdb: Kysely<Database>,\n\tfromHeight: number,\n): Promise<number> {\n\tconst { rows } = await sql<{ tip: string }>`\n SELECT COALESCE(MAX(height), ${fromHeight}) AS tip\n FROM (\n SELECT height, height - ROW_NUMBER() OVER (ORDER BY height) AS grp\n FROM blocks WHERE canonical = true AND height >= ${fromHeight}\n ) sub\n WHERE grp = (\n SELECT height - ROW_NUMBER() OVER (ORDER BY height)\n FROM blocks WHERE canonical = true AND height = ${fromHeight}\n )\n `.execute(db);\n\n\treturn Number(rows[0]?.tip ?? fromHeight);\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AASA,eAAsB,QAAQ,CAAC,IAAsB,OAAgC;AAAA,EACnF,MAAM,cAAc,QAAQ,YAAY,UAAU;AAAA,EAClD,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWnB;AAAA,IACF,QAAQ,EAAE;AAAA,EAEZ,OAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,UAAU,OAAO,EAAE,SAAS;AAAA,IAC5B,QAAQ,OAAO,EAAE,OAAO;AAAA,IACxB,MAAM,OAAO,EAAE,IAAI;AAAA,EACrB,EAAE;AAAA;AAGJ,eAAsB,kBAAkB,CAAC,IAAuC;AAAA,EAC9E,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOrB,QAAQ,EAAE;AAAA,EAEZ,OAAO,OAAO,KAAK,IAAI,SAAS,CAAC;AAAA;AAGnC,eAAsB,oBAAoB,CAAC,IAAsB,YAAqC;AAAA,EACpG,QAAQ,SAAS,MAAM;AAAA,mCACU;AAAA;AAAA;AAAA,yDAGsB;AAAA;AAAA;AAAA;AAAA,wDAID;AAAA;AAAA,IAEpD,QAAQ,EAAE;AAAA,EAEZ,OAAO,OAAO,KAAK,IAAI,OAAO,UAAU;AAAA;",
7
+ "mappings": ";;;;;;;;;;;;;;;;;AAAA;AASA,eAAsB,QAAQ,CAC7B,IACA,OACiB;AAAA,EACjB,MAAM,cAAc,QAAQ,YAAY,UAAU;AAAA,EAClD,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAelB;AAAA,IACF,QAAQ,EAAE;AAAA,EAEb,OAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACvB,UAAU,OAAO,EAAE,SAAS;AAAA,IAC5B,QAAQ,OAAO,EAAE,OAAO;AAAA,IACxB,MAAM,OAAO,EAAE,IAAI;AAAA,EACpB,EAAE;AAAA;AAGH,eAAsB,kBAAkB,CACvC,IACkB;AAAA,EAClB,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOpB,QAAQ,EAAE;AAAA,EAEb,OAAO,OAAO,KAAK,IAAI,SAAS,CAAC;AAAA;AAGlC,eAAsB,oBAAoB,CACzC,IACA,YACkB;AAAA,EAClB,QAAQ,SAAS,MAAM;AAAA,mCACW;AAAA;AAAA;AAAA,yDAGsB;AAAA;AAAA;AAAA;AAAA,wDAID;AAAA;AAAA,IAEpD,QAAQ,EAAE;AAAA,EAEb,OAAO,OAAO,KAAK,IAAI,OAAO,UAAU;AAAA;",
8
8
  "debugId": "C8E32B40B73BEACD64756E2164756E21",
9
9
  "names": []
10
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 StreamMetrics = Selectable<StreamMetricsTable>;
243
256
  declare function getStreamMetrics(db: Kysely<Database>, streamId: string): Promise<StreamMetrics | null>;
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/queries/metrics.ts"],
4
4
  "sourcesContent": [
5
- "import { sql, type Kysely } from \"kysely\";\nimport type { Database, StreamMetrics } from \"../types.ts\";\n\nexport async function getStreamMetrics(db: Kysely<Database>, streamId: string): Promise<StreamMetrics | null> {\n return (\n await db\n .selectFrom(\"stream_metrics\")\n .selectAll()\n .where(\"stream_id\", \"=\", streamId)\n .executeTakeFirst()\n ) ?? null;\n}\n\nexport async function updateStreamMetrics(\n db: Kysely<Database>,\n streamId: string,\n updates: Partial<{\n lastTriggeredAt: Date;\n lastTriggeredBlock: number;\n totalDeliveries: number;\n failedDeliveries: number;\n errorMessage: string | null;\n }>,\n): Promise<void> {\n await db\n .insertInto(\"stream_metrics\")\n .values({\n stream_id: streamId,\n last_triggered_at: updates.lastTriggeredAt ?? null,\n last_triggered_block: updates.lastTriggeredBlock ?? null,\n total_deliveries: updates.totalDeliveries ?? 0,\n failed_deliveries: updates.failedDeliveries ?? 0,\n error_message: updates.errorMessage ?? null,\n })\n .onConflict((oc) =>\n oc.column(\"stream_id\").doUpdateSet({\n ...(updates.lastTriggeredAt !== undefined ? { last_triggered_at: updates.lastTriggeredAt } : {}),\n ...(updates.lastTriggeredBlock !== undefined ? { last_triggered_block: updates.lastTriggeredBlock } : {}),\n ...(updates.totalDeliveries !== undefined ? { total_deliveries: updates.totalDeliveries } : {}),\n ...(updates.failedDeliveries !== undefined ? { failed_deliveries: updates.failedDeliveries } : {}),\n ...(updates.errorMessage !== undefined ? { error_message: updates.errorMessage } : {}),\n }),\n )\n .execute();\n}\n\nexport async function incrementDeliveryCount(\n db: Kysely<Database>,\n streamId: string,\n failed: boolean,\n): Promise<void> {\n await db\n .insertInto(\"stream_metrics\")\n .values({\n stream_id: streamId,\n total_deliveries: 1,\n failed_deliveries: failed ? 1 : 0,\n })\n .onConflict((oc) =>\n oc.column(\"stream_id\").doUpdateSet({\n total_deliveries: sql`stream_metrics.total_deliveries + 1`,\n ...(failed ? { failed_deliveries: sql`stream_metrics.failed_deliveries + 1` } : {}),\n }),\n )\n .execute();\n}\n"
5
+ "import { type Kysely, sql } from \"kysely\";\nimport type { Database, StreamMetrics } from \"../types.ts\";\n\nexport async function getStreamMetrics(\n\tdb: Kysely<Database>,\n\tstreamId: string,\n): Promise<StreamMetrics | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"stream_metrics\")\n\t\t\t.selectAll()\n\t\t\t.where(\"stream_id\", \"=\", streamId)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function updateStreamMetrics(\n\tdb: Kysely<Database>,\n\tstreamId: string,\n\tupdates: Partial<{\n\t\tlastTriggeredAt: Date;\n\t\tlastTriggeredBlock: number;\n\t\ttotalDeliveries: number;\n\t\tfailedDeliveries: number;\n\t\terrorMessage: string | null;\n\t}>,\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"stream_metrics\")\n\t\t.values({\n\t\t\tstream_id: streamId,\n\t\t\tlast_triggered_at: updates.lastTriggeredAt ?? null,\n\t\t\tlast_triggered_block: updates.lastTriggeredBlock ?? null,\n\t\t\ttotal_deliveries: updates.totalDeliveries ?? 0,\n\t\t\tfailed_deliveries: updates.failedDeliveries ?? 0,\n\t\t\terror_message: updates.errorMessage ?? null,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.column(\"stream_id\").doUpdateSet({\n\t\t\t\t...(updates.lastTriggeredAt !== undefined\n\t\t\t\t\t? { last_triggered_at: updates.lastTriggeredAt }\n\t\t\t\t\t: {}),\n\t\t\t\t...(updates.lastTriggeredBlock !== undefined\n\t\t\t\t\t? { last_triggered_block: updates.lastTriggeredBlock }\n\t\t\t\t\t: {}),\n\t\t\t\t...(updates.totalDeliveries !== undefined\n\t\t\t\t\t? { total_deliveries: updates.totalDeliveries }\n\t\t\t\t\t: {}),\n\t\t\t\t...(updates.failedDeliveries !== undefined\n\t\t\t\t\t? { failed_deliveries: updates.failedDeliveries }\n\t\t\t\t\t: {}),\n\t\t\t\t...(updates.errorMessage !== undefined\n\t\t\t\t\t? { error_message: updates.errorMessage }\n\t\t\t\t\t: {}),\n\t\t\t}),\n\t\t)\n\t\t.execute();\n}\n\nexport async function incrementDeliveryCount(\n\tdb: Kysely<Database>,\n\tstreamId: string,\n\tfailed: boolean,\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"stream_metrics\")\n\t\t.values({\n\t\t\tstream_id: streamId,\n\t\t\ttotal_deliveries: 1,\n\t\t\tfailed_deliveries: failed ? 1 : 0,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.column(\"stream_id\").doUpdateSet({\n\t\t\t\ttotal_deliveries: sql`stream_metrics.total_deliveries + 1`,\n\t\t\t\t...(failed\n\t\t\t\t\t? { failed_deliveries: sql`stream_metrics.failed_deliveries + 1` }\n\t\t\t\t\t: {}),\n\t\t\t}),\n\t\t)\n\t\t.execute();\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAGA,eAAsB,gBAAgB,CAAC,IAAsB,UAAiD;AAAA,EAC5G,OACE,MAAM,GACH,WAAW,gBAAgB,EAC3B,UAAU,EACV,MAAM,aAAa,KAAK,QAAQ,EAChC,iBAAiB,KACjB;AAAA;AAGP,eAAsB,mBAAmB,CACvC,IACA,UACA,SAOe;AAAA,EACf,MAAM,GACH,WAAW,gBAAgB,EAC3B,OAAO;AAAA,IACN,WAAW;AAAA,IACX,mBAAmB,QAAQ,mBAAmB;AAAA,IAC9C,sBAAsB,QAAQ,sBAAsB;AAAA,IACpD,kBAAkB,QAAQ,mBAAmB;AAAA,IAC7C,mBAAmB,QAAQ,oBAAoB;AAAA,IAC/C,eAAe,QAAQ,gBAAgB;AAAA,EACzC,CAAC,EACA,WAAW,CAAC,OACX,GAAG,OAAO,WAAW,EAAE,YAAY;AAAA,OAC7B,QAAQ,oBAAoB,YAAY,EAAE,mBAAmB,QAAQ,gBAAgB,IAAI,CAAC;AAAA,OAC1F,QAAQ,uBAAuB,YAAY,EAAE,sBAAsB,QAAQ,mBAAmB,IAAI,CAAC;AAAA,OACnG,QAAQ,oBAAoB,YAAY,EAAE,kBAAkB,QAAQ,gBAAgB,IAAI,CAAC;AAAA,OACzF,QAAQ,qBAAqB,YAAY,EAAE,mBAAmB,QAAQ,iBAAiB,IAAI,CAAC;AAAA,OAC5F,QAAQ,iBAAiB,YAAY,EAAE,eAAe,QAAQ,aAAa,IAAI,CAAC;AAAA,EACtF,CAAC,CACH,EACC,QAAQ;AAAA;AAGb,eAAsB,sBAAsB,CAC1C,IACA,UACA,QACe;AAAA,EACf,MAAM,GACH,WAAW,gBAAgB,EAC3B,OAAO;AAAA,IACN,WAAW;AAAA,IACX,kBAAkB;AAAA,IAClB,mBAAmB,SAAS,IAAI;AAAA,EAClC,CAAC,EACA,WAAW,CAAC,OACX,GAAG,OAAO,WAAW,EAAE,YAAY;AAAA,IACjC,kBAAkB;AAAA,OACd,SAAS,EAAE,mBAAmB,0CAA0C,IAAI,CAAC;AAAA,EACnF,CAAC,CACH,EACC,QAAQ;AAAA;",
7
+ "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAGA,eAAsB,gBAAgB,CACrC,IACA,UACgC;AAAA,EAChC,OACE,MAAM,GACL,WAAW,gBAAgB,EAC3B,UAAU,EACV,MAAM,aAAa,KAAK,QAAQ,EAChC,iBAAiB,KAAM;AAAA;AAI3B,eAAsB,mBAAmB,CACxC,IACA,UACA,SAOgB;AAAA,EAChB,MAAM,GACJ,WAAW,gBAAgB,EAC3B,OAAO;AAAA,IACP,WAAW;AAAA,IACX,mBAAmB,QAAQ,mBAAmB;AAAA,IAC9C,sBAAsB,QAAQ,sBAAsB;AAAA,IACpD,kBAAkB,QAAQ,mBAAmB;AAAA,IAC7C,mBAAmB,QAAQ,oBAAoB;AAAA,IAC/C,eAAe,QAAQ,gBAAgB;AAAA,EACxC,CAAC,EACA,WAAW,CAAC,OACZ,GAAG,OAAO,WAAW,EAAE,YAAY;AAAA,OAC9B,QAAQ,oBAAoB,YAC7B,EAAE,mBAAmB,QAAQ,gBAAgB,IAC7C,CAAC;AAAA,OACA,QAAQ,uBAAuB,YAChC,EAAE,sBAAsB,QAAQ,mBAAmB,IACnD,CAAC;AAAA,OACA,QAAQ,oBAAoB,YAC7B,EAAE,kBAAkB,QAAQ,gBAAgB,IAC5C,CAAC;AAAA,OACA,QAAQ,qBAAqB,YAC9B,EAAE,mBAAmB,QAAQ,iBAAiB,IAC9C,CAAC;AAAA,OACA,QAAQ,iBAAiB,YAC1B,EAAE,eAAe,QAAQ,aAAa,IACtC,CAAC;AAAA,EACL,CAAC,CACF,EACC,QAAQ;AAAA;AAGX,eAAsB,sBAAsB,CAC3C,IACA,UACA,QACgB;AAAA,EAChB,MAAM,GACJ,WAAW,gBAAgB,EAC3B,OAAO;AAAA,IACP,WAAW;AAAA,IACX,kBAAkB;AAAA,IAClB,mBAAmB,SAAS,IAAI;AAAA,EACjC,CAAC,EACA,WAAW,CAAC,OACZ,GAAG,OAAO,WAAW,EAAE,YAAY;AAAA,IAClC,kBAAkB;AAAA,OACd,SACD,EAAE,mBAAmB,0CAA0C,IAC/D,CAAC;AAAA,EACL,CAAC,CACF,EACC,QAAQ;AAAA;",
8
8
  "debugId": "B4106C2E1F1C5A3F64756E2164756E21",
9
9
  "names": []
10
10
  }