@secondlayer/shared 0.2.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 (76) hide show
  1. package/README.md +19 -0
  2. package/dist/src/crypto/hmac.d.ts +26 -0
  3. package/dist/src/crypto/hmac.js +75 -0
  4. package/dist/src/crypto/hmac.js.map +10 -0
  5. package/dist/src/db/index.d.ts +227 -0
  6. package/dist/src/db/index.js +75 -0
  7. package/dist/src/db/index.js.map +11 -0
  8. package/dist/src/db/jsonb.d.ts +13 -0
  9. package/dist/src/db/jsonb.js +35 -0
  10. package/dist/src/db/jsonb.js.map +10 -0
  11. package/dist/src/db/queries/accounts.d.ts +179 -0
  12. package/dist/src/db/queries/accounts.js +39 -0
  13. package/dist/src/db/queries/accounts.js.map +10 -0
  14. package/dist/src/db/queries/integrity.d.ts +178 -0
  15. package/dist/src/db/queries/integrity.js +68 -0
  16. package/dist/src/db/queries/integrity.js.map +10 -0
  17. package/dist/src/db/queries/metrics.d.ts +179 -0
  18. package/dist/src/db/queries/metrics.js +51 -0
  19. package/dist/src/db/queries/metrics.js.map +10 -0
  20. package/dist/src/db/queries/usage.d.ts +205 -0
  21. package/dist/src/db/queries/usage.js +117 -0
  22. package/dist/src/db/queries/usage.js.map +11 -0
  23. package/dist/src/db/queries/views.d.ts +191 -0
  24. package/dist/src/db/queries/views.js +111 -0
  25. package/dist/src/db/queries/views.js.map +11 -0
  26. package/dist/src/db/schema.d.ts +207 -0
  27. package/dist/src/db/schema.js +3 -0
  28. package/dist/src/db/schema.js.map +9 -0
  29. package/dist/src/env.d.ts +7 -0
  30. package/dist/src/env.js +60 -0
  31. package/dist/src/env.js.map +10 -0
  32. package/dist/src/errors.d.ts +51 -0
  33. package/dist/src/errors.js +103 -0
  34. package/dist/src/errors.js.map +10 -0
  35. package/dist/src/index.d.ts +464 -0
  36. package/dist/src/index.js +642 -0
  37. package/dist/src/index.js.map +19 -0
  38. package/dist/src/lib/plans.d.ts +10 -0
  39. package/dist/src/lib/plans.js +34 -0
  40. package/dist/src/lib/plans.js.map +10 -0
  41. package/dist/src/logger.d.ts +2 -0
  42. package/dist/src/logger.js +130 -0
  43. package/dist/src/logger.js.map +11 -0
  44. package/dist/src/node/client.d.ts +35 -0
  45. package/dist/src/node/client.js +56 -0
  46. package/dist/src/node/client.js.map +10 -0
  47. package/dist/src/node/hiro-client.d.ts +186 -0
  48. package/dist/src/node/hiro-client.js +410 -0
  49. package/dist/src/node/hiro-client.js.map +12 -0
  50. package/dist/src/queue/index.d.ts +50 -0
  51. package/dist/src/queue/index.js +176 -0
  52. package/dist/src/queue/index.js.map +12 -0
  53. package/dist/src/queue/listener.d.ts +20 -0
  54. package/dist/src/queue/listener.js +63 -0
  55. package/dist/src/queue/listener.js.map +10 -0
  56. package/dist/src/queue/recovery.d.ts +14 -0
  57. package/dist/src/queue/recovery.js +100 -0
  58. package/dist/src/queue/recovery.js.map +12 -0
  59. package/dist/src/schemas/filters.d.ts +30 -0
  60. package/dist/src/schemas/filters.js +133 -0
  61. package/dist/src/schemas/filters.js.map +10 -0
  62. package/dist/src/schemas/index.d.ts +109 -0
  63. package/dist/src/schemas/index.js +228 -0
  64. package/dist/src/schemas/index.js.map +12 -0
  65. package/dist/src/schemas/views.d.ts +51 -0
  66. package/dist/src/schemas/views.js +29 -0
  67. package/dist/src/schemas/views.js.map +10 -0
  68. package/dist/src/types.d.ts +102 -0
  69. package/dist/src/types.js +3 -0
  70. package/dist/src/types.js.map +9 -0
  71. package/migrations/0001_initial.ts +182 -0
  72. package/migrations/0002_api_keys.ts +38 -0
  73. package/migrations/0003_tenant_isolation.ts +114 -0
  74. package/migrations/0004_accounts_and_usage.ts +90 -0
  75. package/migrations/0005_sessions.ts +42 -0
  76. package/package.json +128 -0
@@ -0,0 +1,410 @@
1
+ import { createRequire } from "node:module";
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true,
8
+ configurable: true,
9
+ set: (newValue) => all[name] = () => newValue
10
+ });
11
+ };
12
+
13
+ // src/env.ts
14
+ import { z } from "zod";
15
+ var networksSchema = z.string().transform((val) => {
16
+ const networks = val.split(",").map((n) => n.trim()).filter(Boolean);
17
+ const valid = ["mainnet", "testnet"];
18
+ for (const n of networks) {
19
+ if (!valid.includes(n)) {
20
+ throw new Error(`Invalid network: ${n}. Must be one of: ${valid.join(", ")}`);
21
+ }
22
+ }
23
+ return networks;
24
+ });
25
+ var envSchema = z.object({
26
+ DATABASE_URL: z.preprocess((val) => typeof val === "string" && val.length === 0 ? undefined : val, z.string().url().optional()),
27
+ NETWORK: z.enum(["mainnet", "testnet"]).optional(),
28
+ NETWORKS: networksSchema.optional(),
29
+ LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
30
+ NODE_ENV: z.enum(["development", "production", "test"]).default("development")
31
+ });
32
+ var cachedEnv = null;
33
+ function getEnv() {
34
+ if (cachedEnv) {
35
+ return cachedEnv;
36
+ }
37
+ const result = envSchema.safeParse(process.env);
38
+ if (!result.success) {
39
+ console.error("❌ Invalid environment configuration:");
40
+ console.error(result.error.format());
41
+ throw new Error("Invalid environment configuration");
42
+ }
43
+ let enabledNetworks;
44
+ if (result.data.NETWORKS && result.data.NETWORKS.length > 0) {
45
+ enabledNetworks = result.data.NETWORKS;
46
+ } else if (result.data.NETWORK) {
47
+ enabledNetworks = [result.data.NETWORK];
48
+ } else {
49
+ enabledNetworks = ["mainnet"];
50
+ }
51
+ cachedEnv = { ...result.data, enabledNetworks };
52
+ return cachedEnv;
53
+ }
54
+ // src/logger.ts
55
+ var LOG_LEVELS = {
56
+ debug: 0,
57
+ info: 1,
58
+ warn: 2,
59
+ error: 3
60
+ };
61
+
62
+ class Logger {
63
+ _level;
64
+ _isProduction;
65
+ _initialized = false;
66
+ init() {
67
+ if (this._initialized)
68
+ return;
69
+ this._initialized = true;
70
+ try {
71
+ const env = getEnv();
72
+ this._level = env.LOG_LEVEL;
73
+ this._isProduction = env.NODE_ENV === "production";
74
+ } catch {
75
+ this._level = "info";
76
+ this._isProduction = false;
77
+ }
78
+ }
79
+ get level() {
80
+ this.init();
81
+ return this._level;
82
+ }
83
+ get isProduction() {
84
+ this.init();
85
+ return this._isProduction;
86
+ }
87
+ shouldLog(level) {
88
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
89
+ }
90
+ formatMessage(level, message, meta) {
91
+ const timestamp = new Date().toISOString();
92
+ if (this.isProduction) {
93
+ return JSON.stringify({
94
+ timestamp,
95
+ level,
96
+ message,
97
+ ...meta
98
+ });
99
+ }
100
+ const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
101
+ return `[${timestamp}] ${level.toUpperCase()}: ${message}${metaStr}`;
102
+ }
103
+ debug(message, meta) {
104
+ if (this.shouldLog("debug")) {
105
+ console.debug(this.formatMessage("debug", message, meta));
106
+ }
107
+ }
108
+ info(message, meta) {
109
+ if (this.shouldLog("info")) {
110
+ console.info(this.formatMessage("info", message, meta));
111
+ }
112
+ }
113
+ warn(message, meta) {
114
+ if (this.shouldLog("warn")) {
115
+ console.warn(this.formatMessage("warn", message, meta));
116
+ }
117
+ }
118
+ error(message, meta) {
119
+ if (this.shouldLog("error")) {
120
+ console.error(this.formatMessage("error", message, meta));
121
+ }
122
+ }
123
+ }
124
+ var logger = new Logger;
125
+
126
+ // src/node/hiro-client.ts
127
+ var DEFAULT_HIRO_API_URL = "https://api.mainnet.hiro.so";
128
+
129
+ class HiroClient {
130
+ apiUrl;
131
+ apiKey;
132
+ maxRetries;
133
+ constructor(apiUrl, maxRetries = 5) {
134
+ this.apiUrl = apiUrl || process.env.HIRO_API_URL || DEFAULT_HIRO_API_URL;
135
+ this.apiKey = process.env.HIRO_API_KEY;
136
+ this.maxRetries = maxRetries;
137
+ }
138
+ get headers() {
139
+ return this.apiKey ? { "x-hiro-api-key": this.apiKey } : {};
140
+ }
141
+ async fetchWithRetry(url, timeoutMs = 15000) {
142
+ for (let attempt = 0;attempt < this.maxRetries; attempt++) {
143
+ const res = await fetch(url, { headers: this.headers, signal: AbortSignal.timeout(timeoutMs) });
144
+ if (res.ok || res.status === 404)
145
+ return res;
146
+ if (res.status === 429 || res.status >= 500) {
147
+ const delay = Math.min(1000 * Math.pow(2, attempt), 1e4);
148
+ logger.info("Rate limited, retrying", { url: url.split("/").slice(-2).join("/"), attempt, delay });
149
+ await new Promise((r) => setTimeout(r, delay));
150
+ continue;
151
+ }
152
+ return res;
153
+ }
154
+ return fetch(url, { headers: this.headers, signal: AbortSignal.timeout(timeoutMs) });
155
+ }
156
+ async getBlockForIndexer(height) {
157
+ const block = await this.fetchBlock(height);
158
+ if (!block)
159
+ return null;
160
+ const hiroTxs = await this.fetchBlockTransactions(height);
161
+ const txPayloads = [];
162
+ const eventPayloads = [];
163
+ for (const hiroTx of hiroTxs) {
164
+ txPayloads.push({
165
+ txid: hiroTx.tx_id,
166
+ raw_tx: "0x00",
167
+ status: mapTxStatus(hiroTx.tx_status),
168
+ tx_index: hiroTx.tx_index ?? 0,
169
+ tx_type: mapTxType(hiroTx.tx_type),
170
+ sender_address: hiroTx.sender_address
171
+ });
172
+ if (hiroTx.event_count > 0) {
173
+ try {
174
+ const events = await this.fetchAllEvents(hiroTx.tx_id);
175
+ eventPayloads.push(...events);
176
+ } catch (err) {
177
+ logger.warn("Failed to fetch events for backfill", { txId: hiroTx.tx_id, error: String(err) });
178
+ }
179
+ }
180
+ }
181
+ return {
182
+ block_hash: block.hash,
183
+ block_height: block.height,
184
+ index_block_hash: block.index_block_hash,
185
+ parent_block_hash: block.parent_block_hash,
186
+ parent_index_block_hash: block.parent_index_block_hash,
187
+ burn_block_hash: block.burn_block_hash,
188
+ burn_block_height: block.burn_block_height,
189
+ burn_block_timestamp: block.burn_block_time,
190
+ miner_txid: block.miner_txid,
191
+ timestamp: block.block_time,
192
+ transactions: txPayloads,
193
+ events: eventPayloads
194
+ };
195
+ }
196
+ async fetchBlock(height) {
197
+ const res = await this.fetchWithRetry(`${this.apiUrl}/extended/v2/blocks/${height}`);
198
+ if (res.status === 404)
199
+ return null;
200
+ if (!res.ok)
201
+ throw new Error(`Hiro API block/${height} returned ${res.status}`);
202
+ return res.json();
203
+ }
204
+ async fetchBlockTransactions(height) {
205
+ const txs = [];
206
+ let offset = 0;
207
+ const limit = 50;
208
+ while (true) {
209
+ const res = await this.fetchWithRetry(`${this.apiUrl}/extended/v2/blocks/${height}/transactions?limit=${limit}&offset=${offset}`);
210
+ if (!res.ok)
211
+ throw new Error(`Hiro API block/${height}/transactions returned ${res.status}`);
212
+ const data = await res.json();
213
+ txs.push(...data.results);
214
+ if (txs.length >= data.total || data.results.length < limit)
215
+ break;
216
+ offset += limit;
217
+ }
218
+ return txs;
219
+ }
220
+ async fetchAllEvents(txId) {
221
+ const events = [];
222
+ let offset = 0;
223
+ const limit = 50;
224
+ while (true) {
225
+ const res = await this.fetchWithRetry(`${this.apiUrl}/extended/v1/tx/events?tx_id=${txId}&limit=${limit}&offset=${offset}`);
226
+ if (!res.ok) {
227
+ logger.warn("Failed to fetch events from Hiro", { txId, status: res.status });
228
+ break;
229
+ }
230
+ const data = await res.json();
231
+ for (const hEvent of data.events) {
232
+ const converted = convertHiroEvent(hEvent);
233
+ if (converted)
234
+ events.push(converted);
235
+ }
236
+ if (data.events.length < limit)
237
+ break;
238
+ offset += limit;
239
+ }
240
+ return events;
241
+ }
242
+ async isHealthy() {
243
+ try {
244
+ const res = await fetch(`${this.apiUrl}/extended/v1/status`, {
245
+ headers: this.headers,
246
+ signal: AbortSignal.timeout(5000)
247
+ });
248
+ return res.ok;
249
+ } catch {
250
+ return false;
251
+ }
252
+ }
253
+ getApiUrl() {
254
+ return this.apiUrl;
255
+ }
256
+ }
257
+ function mapTxStatus(status) {
258
+ switch (status) {
259
+ case "success":
260
+ return "success";
261
+ case "abort_by_response":
262
+ case "abort_by_post_condition":
263
+ return status;
264
+ default:
265
+ return "success";
266
+ }
267
+ }
268
+ function mapTxType(type) {
269
+ switch (type) {
270
+ case "token_transfer":
271
+ return "token_transfer";
272
+ case "contract_call":
273
+ return "contract_call";
274
+ case "smart_contract":
275
+ return "smart_contract";
276
+ case "coinbase":
277
+ return "coinbase";
278
+ case "tenure_change":
279
+ return "tenure_change";
280
+ case "poison_microblock":
281
+ return "poison_microblock";
282
+ default:
283
+ return type;
284
+ }
285
+ }
286
+ function convertHiroEvent(hEvent) {
287
+ const base = {
288
+ txid: hEvent.tx_id,
289
+ event_index: hEvent.event_index,
290
+ committed: true
291
+ };
292
+ if (hEvent.event_type === "stx_asset" && hEvent.asset) {
293
+ const a = hEvent.asset;
294
+ switch (a.asset_event_type) {
295
+ case "transfer":
296
+ return {
297
+ ...base,
298
+ type: "stx_transfer_event",
299
+ stx_transfer_event: {
300
+ sender: a.sender,
301
+ recipient: a.recipient,
302
+ amount: a.amount,
303
+ memo: a.memo
304
+ }
305
+ };
306
+ case "mint":
307
+ return {
308
+ ...base,
309
+ type: "stx_mint_event",
310
+ stx_mint_event: { recipient: a.recipient, amount: a.amount }
311
+ };
312
+ case "burn":
313
+ return {
314
+ ...base,
315
+ type: "stx_burn_event",
316
+ stx_burn_event: { sender: a.sender, amount: a.amount }
317
+ };
318
+ case "lock":
319
+ return {
320
+ ...base,
321
+ type: "stx_lock_event",
322
+ stx_lock_event: {
323
+ locked_amount: a.amount,
324
+ unlock_height: "0",
325
+ locked_address: a.sender
326
+ }
327
+ };
328
+ }
329
+ }
330
+ if (hEvent.event_type === "fungible_token_asset" && hEvent.asset) {
331
+ const a = hEvent.asset;
332
+ const assetId = a.asset_id || "";
333
+ switch (a.asset_event_type) {
334
+ case "transfer":
335
+ return {
336
+ ...base,
337
+ type: "ft_transfer_event",
338
+ ft_transfer_event: {
339
+ asset_identifier: assetId,
340
+ sender: a.sender,
341
+ recipient: a.recipient,
342
+ amount: a.amount
343
+ }
344
+ };
345
+ case "mint":
346
+ return {
347
+ ...base,
348
+ type: "ft_mint_event",
349
+ ft_mint_event: { asset_identifier: assetId, recipient: a.recipient, amount: a.amount }
350
+ };
351
+ case "burn":
352
+ return {
353
+ ...base,
354
+ type: "ft_burn_event",
355
+ ft_burn_event: { asset_identifier: assetId, sender: a.sender, amount: a.amount }
356
+ };
357
+ }
358
+ }
359
+ if (hEvent.event_type === "non_fungible_token_asset" && hEvent.asset) {
360
+ const a = hEvent.asset;
361
+ const assetId = a.asset_id || "";
362
+ switch (a.asset_event_type) {
363
+ case "transfer":
364
+ return {
365
+ ...base,
366
+ type: "nft_transfer_event",
367
+ nft_transfer_event: {
368
+ asset_identifier: assetId,
369
+ sender: a.sender,
370
+ recipient: a.recipient,
371
+ value: a.value
372
+ }
373
+ };
374
+ case "mint":
375
+ return {
376
+ ...base,
377
+ type: "nft_mint_event",
378
+ nft_mint_event: { asset_identifier: assetId, recipient: a.recipient, value: a.value }
379
+ };
380
+ case "burn":
381
+ return {
382
+ ...base,
383
+ type: "nft_burn_event",
384
+ nft_burn_event: { asset_identifier: assetId, sender: a.sender, value: a.value }
385
+ };
386
+ }
387
+ }
388
+ if (hEvent.event_type === "smart_contract_log" && hEvent.contract_log) {
389
+ return {
390
+ ...base,
391
+ type: "smart_contract_event",
392
+ smart_contract_event: {
393
+ contract_identifier: hEvent.contract_log.contract_id,
394
+ topic: hEvent.contract_log.topic,
395
+ value: hEvent.contract_log.value
396
+ }
397
+ };
398
+ }
399
+ logger.debug("Unknown Hiro event type, skipping", {
400
+ eventType: hEvent.event_type,
401
+ txId: hEvent.tx_id
402
+ });
403
+ return null;
404
+ }
405
+ export {
406
+ HiroClient
407
+ };
408
+
409
+ //# debugId=ECD1D20F327C2DE164756E2164756E21
410
+ //# sourceMappingURL=hiro-client.js.map
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/env.ts", "../src/logger.ts", "../src/node/hiro-client.ts"],
4
+ "sourcesContent": [
5
+ "import { z } from \"zod\";\n\n// Parse comma-separated networks\nconst networksSchema = z.string().transform((val) => {\n const networks = val.split(\",\").map((n) => n.trim()).filter(Boolean);\n const valid = [\"mainnet\", \"testnet\"];\n for (const n of networks) {\n if (!valid.includes(n)) {\n throw new Error(`Invalid network: ${n}. Must be one of: ${valid.join(\", \")}`);\n }\n }\n return networks as (\"mainnet\" | \"testnet\")[];\n});\n\nconst envSchema = z.object({\n // DATABASE_URL is optional - consumers must provide their own\n DATABASE_URL: z.preprocess(\n (val) => (typeof val === \"string\" && val.length === 0) ? undefined : val,\n z.string().url().optional(),\n ),\n // Single network (deprecated, for backwards compatibility)\n NETWORK: z.enum([\"mainnet\", \"testnet\"]).optional(),\n // Multiple networks (comma-separated)\n NETWORKS: networksSchema.optional(),\n LOG_LEVEL: z.enum([\"debug\", \"info\", \"warn\", \"error\"]).default(\"info\"),\n NODE_ENV: z.enum([\"development\", \"production\", \"test\"]).default(\"development\"),\n});\n\nexport type Env = z.infer<typeof envSchema> & {\n enabledNetworks: (\"mainnet\" | \"testnet\")[];\n};\n\nlet cachedEnv: Env | null = null;\n\nexport function getEnv(): Env {\n if (cachedEnv) {\n return cachedEnv;\n }\n\n const result = envSchema.safeParse(process.env);\n\n if (!result.success) {\n console.error(\"❌ Invalid environment configuration:\");\n console.error(result.error.format());\n throw new Error(\"Invalid environment configuration\");\n }\n\n // Compute enabled networks from NETWORKS or NETWORK\n let enabledNetworks: (\"mainnet\" | \"testnet\")[];\n if (result.data.NETWORKS && result.data.NETWORKS.length > 0) {\n enabledNetworks = result.data.NETWORKS;\n } else if (result.data.NETWORK) {\n enabledNetworks = [result.data.NETWORK];\n } else {\n enabledNetworks = [\"mainnet\"]; // Default\n }\n\n cachedEnv = { ...result.data, enabledNetworks };\n return cachedEnv;\n}\n\n// Export for testing\nexport { envSchema };\n",
6
+ "import { getEnv } from \"./env.ts\";\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nclass Logger {\n private _level?: LogLevel;\n private _isProduction?: boolean;\n private _initialized = false;\n\n private init() {\n if (this._initialized) return;\n this._initialized = true;\n try {\n const env = getEnv();\n this._level = env.LOG_LEVEL;\n this._isProduction = env.NODE_ENV === \"production\";\n } catch {\n // Fallback when env is unavailable (e.g. tests without DATABASE_URL)\n this._level = \"info\";\n this._isProduction = false;\n }\n }\n\n private get level(): LogLevel {\n this.init();\n return this._level!;\n }\n\n private get isProduction(): boolean {\n this.init();\n return this._isProduction!;\n }\n\n private shouldLog(level: LogLevel): boolean {\n return LOG_LEVELS[level] >= LOG_LEVELS[this.level];\n }\n\n private formatMessage(level: LogLevel, message: string, meta?: Record<string, any>) {\n const timestamp = new Date().toISOString();\n\n if (this.isProduction) {\n // JSON output for production\n return JSON.stringify({\n timestamp,\n level,\n message,\n ...meta,\n });\n }\n\n // Human-readable output for development\n const metaStr = meta ? ` ${JSON.stringify(meta)}` : \"\";\n return `[${timestamp}] ${level.toUpperCase()}: ${message}${metaStr}`;\n }\n\n debug(message: string, meta?: Record<string, any>) {\n if (this.shouldLog(\"debug\")) {\n console.debug(this.formatMessage(\"debug\", message, meta));\n }\n }\n\n info(message: string, meta?: Record<string, any>) {\n if (this.shouldLog(\"info\")) {\n console.info(this.formatMessage(\"info\", message, meta));\n }\n }\n\n warn(message: string, meta?: Record<string, any>) {\n if (this.shouldLog(\"warn\")) {\n console.warn(this.formatMessage(\"warn\", message, meta));\n }\n }\n\n error(message: string, meta?: Record<string, any>) {\n if (this.shouldLog(\"error\")) {\n console.error(this.formatMessage(\"error\", message, meta));\n }\n }\n}\n\n// Export singleton instance\nexport const logger = new Logger();\n",
7
+ "/**\n * Hiro public API client for backfilling historical block data.\n *\n * The stacks-node RPC `/v2/blocks/{height}` only returns block headers + tx IDs,\n * not full transaction data or events. The Hiro API serves complete block data\n * that we can transform into the NewBlockPayload format our indexer expects.\n */\n\nimport { logger } from \"../logger.ts\";\n\nconst DEFAULT_HIRO_API_URL = \"https://api.mainnet.hiro.so\";\n\n/** v2 /extended/v2/blocks/{height} response */\nexport interface HiroBlockResponse {\n canonical: boolean;\n height: number;\n hash: string;\n block_time: number;\n block_time_iso: string;\n index_block_hash: string;\n parent_block_hash: string;\n parent_index_block_hash: string;\n burn_block_hash: string;\n burn_block_height: number;\n burn_block_time: number;\n miner_txid: string;\n tx_count: number;\n}\n\n/** v2 /extended/v2/blocks/{height}/transactions response */\nexport interface HiroBlockTxsResponse {\n limit: number;\n offset: number;\n total: number;\n results: HiroTxResponse[];\n}\n\nexport interface HiroTxResponse {\n tx_id: string;\n tx_type: string;\n tx_status: string;\n sender_address: string;\n fee_rate: string;\n nonce: number;\n block_hash: string;\n block_height: number;\n burn_block_height: number;\n tx_index: number;\n event_count: number;\n token_transfer?: {\n recipient_address: string;\n amount: string;\n memo: string;\n };\n contract_call?: {\n contract_id: string;\n function_name: string;\n function_args: unknown[];\n };\n smart_contract?: {\n contract_id: string;\n source_code: string;\n };\n}\n\nexport interface HiroEvent {\n event_index: number;\n event_type: string; // \"stx_asset\" | \"fungible_token_asset\" | \"non_fungible_token_asset\" | \"smart_contract_log\"\n tx_id: string;\n asset?: {\n asset_event_type: string; // \"transfer\" | \"mint\" | \"burn\"\n sender?: string;\n recipient?: string;\n amount?: string;\n memo?: string;\n asset_id?: string;\n value?: unknown;\n };\n contract_log?: {\n contract_id: string;\n topic: string;\n value: unknown;\n };\n}\n\nexport interface HiroEventsResponse {\n limit: number;\n offset: number;\n events: HiroEvent[];\n}\n\n/** Shape our indexer expects at POST /new_block */\nexport interface NewBlockPayload {\n block_hash: string;\n block_height: number;\n index_block_hash: string;\n parent_block_hash: string;\n parent_index_block_hash: string;\n burn_block_hash: string;\n burn_block_height: number;\n burn_block_timestamp: number;\n miner_txid: string;\n timestamp: number;\n transactions: TransactionPayload[];\n events: TransactionEventPayload[];\n}\n\ninterface TransactionPayload {\n txid: string;\n raw_tx: string;\n status: string;\n tx_index: number;\n tx_type?: string;\n sender_address?: string;\n}\n\ninterface TransactionEventPayload {\n txid: string;\n event_index: number;\n committed: boolean;\n type: string;\n stx_transfer_event?: { sender: string; recipient: string; amount: string; memo?: string };\n stx_mint_event?: { recipient: string; amount: string };\n stx_burn_event?: { sender: string; amount: string };\n stx_lock_event?: { locked_amount: string; unlock_height: string; locked_address: string };\n ft_transfer_event?: { asset_identifier: string; sender: string; recipient: string; amount: string };\n ft_mint_event?: { asset_identifier: string; recipient: string; amount: string };\n ft_burn_event?: { asset_identifier: string; sender: string; amount: string };\n nft_transfer_event?: { asset_identifier: string; sender: string; recipient: string; value: unknown };\n nft_mint_event?: { asset_identifier: string; recipient: string; value: unknown };\n nft_burn_event?: { asset_identifier: string; sender: string; value: unknown };\n smart_contract_event?: { contract_identifier: string; topic: string; value: unknown };\n}\n\nexport class HiroClient {\n private apiUrl: string;\n private apiKey: string | undefined;\n private maxRetries: number;\n\n constructor(apiUrl?: string, maxRetries = 5) {\n this.apiUrl = apiUrl || process.env.HIRO_API_URL || DEFAULT_HIRO_API_URL;\n this.apiKey = process.env.HIRO_API_KEY;\n this.maxRetries = maxRetries;\n }\n\n private get headers(): Record<string, string> {\n return this.apiKey ? { \"x-hiro-api-key\": this.apiKey } : {};\n }\n\n /** Fetch with retry on 429/5xx using exponential backoff */\n private async fetchWithRetry(url: string, timeoutMs = 15_000): Promise<Response> {\n for (let attempt = 0; attempt < this.maxRetries; attempt++) {\n const res = await fetch(url, { headers: this.headers, signal: AbortSignal.timeout(timeoutMs) });\n\n if (res.ok || res.status === 404) return res;\n\n if (res.status === 429 || res.status >= 500) {\n const delay = Math.min(1000 * Math.pow(2, attempt), 10_000);\n logger.info(\"Rate limited, retrying\", { url: url.split(\"/\").slice(-2).join(\"/\"), attempt, delay });\n await new Promise((r) => setTimeout(r, delay));\n continue;\n }\n\n // 4xx (not 404/429) — don't retry\n return res;\n }\n\n // Final attempt\n return fetch(url, { headers: this.headers, signal: AbortSignal.timeout(timeoutMs) });\n }\n\n /**\n * Fetch a complete block by height, including all transactions and events,\n * transformed into the NewBlockPayload format our indexer expects.\n *\n * Uses 3-step approach:\n * 1. GET /extended/v2/blocks/{height} — block metadata\n * 2. GET /extended/v2/blocks/{height}/transactions — all txs (paginated)\n * 3. GET /extended/v1/tx/events?tx_id={txId} — events per tx (only for txs with events)\n */\n async getBlockForIndexer(height: number): Promise<NewBlockPayload | null> {\n // 1. Fetch block metadata\n const block = await this.fetchBlock(height);\n if (!block) return null;\n\n // 2. Fetch all transactions via v2 block/transactions endpoint\n const hiroTxs = await this.fetchBlockTransactions(height);\n\n const txPayloads: TransactionPayload[] = [];\n const eventPayloads: TransactionEventPayload[] = [];\n\n for (const hiroTx of hiroTxs) {\n txPayloads.push({\n txid: hiroTx.tx_id,\n raw_tx: \"0x00\",\n status: mapTxStatus(hiroTx.tx_status),\n tx_index: hiroTx.tx_index ?? 0,\n tx_type: mapTxType(hiroTx.tx_type),\n sender_address: hiroTx.sender_address,\n });\n\n // 3. Fetch events only for txs that have them\n if (hiroTx.event_count > 0) {\n try {\n const events = await this.fetchAllEvents(hiroTx.tx_id);\n eventPayloads.push(...events);\n } catch (err) {\n logger.warn(\"Failed to fetch events for backfill\", { txId: hiroTx.tx_id, error: String(err) });\n }\n }\n }\n\n return {\n block_hash: block.hash,\n block_height: block.height,\n index_block_hash: block.index_block_hash,\n parent_block_hash: block.parent_block_hash,\n parent_index_block_hash: block.parent_index_block_hash,\n burn_block_hash: block.burn_block_hash,\n burn_block_height: block.burn_block_height,\n burn_block_timestamp: block.burn_block_time,\n miner_txid: block.miner_txid,\n timestamp: block.block_time,\n transactions: txPayloads,\n events: eventPayloads,\n };\n }\n\n /** v2 block metadata */\n private async fetchBlock(height: number): Promise<HiroBlockResponse | null> {\n const res = await this.fetchWithRetry(`${this.apiUrl}/extended/v2/blocks/${height}`);\n if (res.status === 404) return null;\n if (!res.ok) throw new Error(`Hiro API block/${height} returned ${res.status}`);\n return res.json() as Promise<HiroBlockResponse>;\n }\n\n /** v2 block transactions (paginated) */\n private async fetchBlockTransactions(height: number): Promise<HiroTxResponse[]> {\n const txs: HiroTxResponse[] = [];\n let offset = 0;\n const limit = 50;\n\n while (true) {\n const res = await this.fetchWithRetry(\n `${this.apiUrl}/extended/v2/blocks/${height}/transactions?limit=${limit}&offset=${offset}`\n );\n if (!res.ok) throw new Error(`Hiro API block/${height}/transactions returned ${res.status}`);\n\n const data = (await res.json()) as HiroBlockTxsResponse;\n txs.push(...data.results);\n\n if (txs.length >= data.total || data.results.length < limit) break;\n offset += limit;\n }\n\n return txs;\n }\n\n private async fetchAllEvents(txId: string): Promise<TransactionEventPayload[]> {\n const events: TransactionEventPayload[] = [];\n let offset = 0;\n const limit = 50;\n\n while (true) {\n const res = await this.fetchWithRetry(\n `${this.apiUrl}/extended/v1/tx/events?tx_id=${txId}&limit=${limit}&offset=${offset}`\n );\n if (!res.ok) {\n logger.warn(\"Failed to fetch events from Hiro\", { txId, status: res.status });\n break;\n }\n\n const data = (await res.json()) as HiroEventsResponse;\n for (const hEvent of data.events) {\n const converted = convertHiroEvent(hEvent);\n if (converted) events.push(converted);\n }\n\n if (data.events.length < limit) break;\n offset += limit;\n }\n\n return events;\n }\n\n async isHealthy(): Promise<boolean> {\n try {\n const res = await fetch(`${this.apiUrl}/extended/v1/status`, {\n headers: this.headers,\n signal: AbortSignal.timeout(5_000),\n });\n return res.ok;\n } catch {\n return false;\n }\n }\n\n getApiUrl(): string {\n return this.apiUrl;\n }\n}\n\n/** Map Hiro tx_status to our indexer's expected format */\nfunction mapTxStatus(status: string): string {\n switch (status) {\n case \"success\":\n return \"success\";\n case \"abort_by_response\":\n case \"abort_by_post_condition\":\n return status;\n default:\n return \"success\";\n }\n}\n\n/** Map Hiro tx_type to node event tx_type */\nfunction mapTxType(type: string): string {\n switch (type) {\n case \"token_transfer\":\n return \"token_transfer\";\n case \"contract_call\":\n return \"contract_call\";\n case \"smart_contract\":\n return \"smart_contract\";\n case \"coinbase\":\n return \"coinbase\";\n case \"tenure_change\":\n return \"tenure_change\";\n case \"poison_microblock\":\n return \"poison_microblock\";\n default:\n return type;\n }\n}\n\n/**\n * Convert a Hiro API event to our indexer's TransactionEvent format.\n *\n * Hiro uses:\n * event_type: \"stx_asset\" | \"fungible_token_asset\" | \"non_fungible_token_asset\" | \"smart_contract_log\"\n * asset.asset_event_type: \"transfer\" | \"mint\" | \"burn\" | \"lock\"\n *\n * Our indexer expects:\n * type: \"stx_transfer_event\" | \"stx_mint_event\" | \"ft_transfer_event\" | \"smart_contract_event\" | ...\n */\nfunction convertHiroEvent(hEvent: HiroEvent): TransactionEventPayload | null {\n const base = {\n txid: hEvent.tx_id,\n event_index: hEvent.event_index,\n committed: true,\n };\n\n if (hEvent.event_type === \"stx_asset\" && hEvent.asset) {\n const a = hEvent.asset;\n switch (a.asset_event_type) {\n case \"transfer\":\n return {\n ...base,\n type: \"stx_transfer_event\",\n stx_transfer_event: {\n sender: a.sender!,\n recipient: a.recipient!,\n amount: a.amount!,\n memo: a.memo,\n },\n };\n case \"mint\":\n return {\n ...base,\n type: \"stx_mint_event\",\n stx_mint_event: { recipient: a.recipient!, amount: a.amount! },\n };\n case \"burn\":\n return {\n ...base,\n type: \"stx_burn_event\",\n stx_burn_event: { sender: a.sender!, amount: a.amount! },\n };\n case \"lock\":\n return {\n ...base,\n type: \"stx_lock_event\",\n stx_lock_event: {\n locked_amount: a.amount!,\n unlock_height: \"0\",\n locked_address: a.sender!,\n },\n };\n }\n }\n\n if (hEvent.event_type === \"fungible_token_asset\" && hEvent.asset) {\n const a = hEvent.asset;\n const assetId = a.asset_id || \"\";\n switch (a.asset_event_type) {\n case \"transfer\":\n return {\n ...base,\n type: \"ft_transfer_event\",\n ft_transfer_event: {\n asset_identifier: assetId,\n sender: a.sender!,\n recipient: a.recipient!,\n amount: a.amount!,\n },\n };\n case \"mint\":\n return {\n ...base,\n type: \"ft_mint_event\",\n ft_mint_event: { asset_identifier: assetId, recipient: a.recipient!, amount: a.amount! },\n };\n case \"burn\":\n return {\n ...base,\n type: \"ft_burn_event\",\n ft_burn_event: { asset_identifier: assetId, sender: a.sender!, amount: a.amount! },\n };\n }\n }\n\n if (hEvent.event_type === \"non_fungible_token_asset\" && hEvent.asset) {\n const a = hEvent.asset;\n const assetId = a.asset_id || \"\";\n switch (a.asset_event_type) {\n case \"transfer\":\n return {\n ...base,\n type: \"nft_transfer_event\",\n nft_transfer_event: {\n asset_identifier: assetId,\n sender: a.sender!,\n recipient: a.recipient!,\n value: a.value,\n },\n };\n case \"mint\":\n return {\n ...base,\n type: \"nft_mint_event\",\n nft_mint_event: { asset_identifier: assetId, recipient: a.recipient!, value: a.value },\n };\n case \"burn\":\n return {\n ...base,\n type: \"nft_burn_event\",\n nft_burn_event: { asset_identifier: assetId, sender: a.sender!, value: a.value },\n };\n }\n }\n\n if (hEvent.event_type === \"smart_contract_log\" && hEvent.contract_log) {\n return {\n ...base,\n type: \"smart_contract_event\",\n smart_contract_event: {\n contract_identifier: hEvent.contract_log.contract_id,\n topic: hEvent.contract_log.topic,\n value: hEvent.contract_log.value,\n },\n };\n }\n\n logger.debug(\"Unknown Hiro event type, skipping\", {\n eventType: hEvent.event_type,\n txId: hEvent.tx_id,\n });\n return null;\n}\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "ECD1D20F327C2DE164756E2164756E21",
11
+ "names": []
12
+ }
@@ -0,0 +1,50 @@
1
+ import { Generated, Selectable } from "kysely";
2
+ interface JobsTable {
3
+ id: Generated<string>;
4
+ stream_id: string;
5
+ block_height: number;
6
+ status: Generated<string>;
7
+ attempts: Generated<number>;
8
+ locked_at: Date | null;
9
+ locked_by: string | null;
10
+ error: string | null;
11
+ backfill: Generated<boolean>;
12
+ created_at: Generated<Date>;
13
+ completed_at: Date | null;
14
+ }
15
+ type Job = Selectable<JobsTable>;
16
+ interface QueueStats {
17
+ pending: number;
18
+ processing: number;
19
+ completed: number;
20
+ failed: number;
21
+ total: number;
22
+ }
23
+ declare const WORKER_ID: unknown;
24
+ /**
25
+ * Enqueue a new job for stream evaluation
26
+ */
27
+ declare function enqueue(streamId: string, blockHeight: number, backfill?: boolean): Promise<string>;
28
+ /**
29
+ * Claim a pending job using SKIP LOCKED to prevent concurrent access
30
+ * Returns null if no jobs available
31
+ */
32
+ declare function claim(): Promise<Job | null>;
33
+ /**
34
+ * Mark a job as completed
35
+ */
36
+ declare function complete(jobId: string): Promise<void>;
37
+ /**
38
+ * Mark a job as failed
39
+ * Re-queues if under max attempts, otherwise marks as permanently failed
40
+ */
41
+ declare function fail(jobId: string, error: string, maxAttempts?: number): Promise<void>;
42
+ /**
43
+ * Get queue statistics
44
+ */
45
+ declare function stats(): Promise<QueueStats>;
46
+ /**
47
+ * Get worker ID for this process
48
+ */
49
+ declare function getWorkerId(): string;
50
+ export { stats, getWorkerId, fail, enqueue, complete, claim, WORKER_ID, QueueStats };