@secondlayer/subgraphs 6.6.1 → 7.0.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.
@@ -11,18 +11,21 @@ import { listen, targetListenerUrl } from "@secondlayer/shared/queue/listener";
11
11
  import {
12
12
  webhookTimeoutMsCeiling
13
13
  } from "@secondlayer/shared/schemas/webhooks";
14
- import { sql as sql2 } from "kysely";
14
+ import { sql } from "kysely";
15
15
 
16
16
  // src/runtime/formats/index.ts
17
17
  import { signSecondlayerWebhook } from "@secondlayer/shared/crypto/secondlayer-webhook";
18
18
  import { logger } from "@secondlayer/shared/logger";
19
19
 
20
20
  // src/runtime/formats/cloudevents.ts
21
+ function chainOf(payload) {
22
+ return payload?.chain === "bitcoin" ? "bitcoin" : "stacks";
23
+ }
21
24
  function buildCloudEvents(outboxRow, _sub) {
22
25
  const event = {
23
26
  specversion: "1.0",
24
27
  type: outboxRow.event_type,
25
- source: `secondlayer:${outboxRow.subgraph_name}`,
28
+ source: `secondlayer:${chainOf(outboxRow.payload)}`,
26
29
  id: outboxRow.id,
27
30
  time: new Date(outboxRow.created_at).toISOString(),
28
31
  datacontenttype: "application/json",
@@ -191,178 +194,6 @@ function recordDelivery() {
191
194
  pending += 1;
192
195
  }
193
196
 
194
- // src/runtime/webhook-state.ts
195
- import { sql } from "kysely";
196
-
197
- // src/runtime/emitter-matcher.ts
198
- function isPrimitive(v) {
199
- const t = typeof v;
200
- return t === "string" || t === "number" || t === "boolean";
201
- }
202
- function coerceBigInt(v) {
203
- if (typeof v === "bigint")
204
- return v;
205
- if (typeof v === "number") {
206
- if (!Number.isFinite(v))
207
- return null;
208
- if (!Number.isInteger(v))
209
- return null;
210
- return BigInt(v);
211
- }
212
- if (typeof v === "string" && /^-?\d+$/.test(v)) {
213
- try {
214
- return BigInt(v);
215
- } catch {
216
- return null;
217
- }
218
- }
219
- return null;
220
- }
221
- function coerceFloat(v) {
222
- if (typeof v === "number" && Number.isFinite(v))
223
- return v;
224
- if (typeof v === "bigint")
225
- return Number(v);
226
- if (typeof v === "string" && v !== "" && !Number.isNaN(Number(v))) {
227
- return Number(v);
228
- }
229
- return null;
230
- }
231
- function compareNumeric(a, b) {
232
- const ba = coerceBigInt(a);
233
- const bb = coerceBigInt(b);
234
- if (ba !== null && bb !== null) {
235
- if (ba === bb)
236
- return 0;
237
- return ba > bb ? 1 : -1;
238
- }
239
- const fa = coerceFloat(a);
240
- const fb = coerceFloat(b);
241
- if (fa === null || fb === null)
242
- return null;
243
- if (fa === fb)
244
- return 0;
245
- return fa > fb ? 1 : -1;
246
- }
247
- function matchClause(rowValue, clause) {
248
- const rowIsPrimitive = isPrimitive(rowValue) || typeof rowValue === "bigint";
249
- if (isPrimitive(clause)) {
250
- if (!rowIsPrimitive)
251
- return false;
252
- if (typeof clause === "number" || typeof rowValue === "number" || typeof rowValue === "bigint") {
253
- const cmp = compareNumeric(rowValue, clause);
254
- if (cmp !== null)
255
- return cmp === 0;
256
- }
257
- return rowValue === clause || String(rowValue) === String(clause);
258
- }
259
- if (clause === null || typeof clause !== "object" || Array.isArray(clause)) {
260
- return false;
261
- }
262
- const keys = Object.keys(clause);
263
- if (keys.length !== 1)
264
- return false;
265
- const op = keys[0];
266
- const c = clause;
267
- switch (op) {
268
- case "eq":
269
- return matchClause(rowValue, c.eq);
270
- case "neq":
271
- return !matchClause(rowValue, c.neq);
272
- case "gt":
273
- case "gte":
274
- case "lt":
275
- case "lte": {
276
- if (!rowIsPrimitive)
277
- return false;
278
- const cmp = compareNumeric(rowValue, c[op]);
279
- if (cmp === null)
280
- return false;
281
- if (op === "gt")
282
- return cmp > 0;
283
- if (op === "gte")
284
- return cmp >= 0;
285
- if (op === "lt")
286
- return cmp < 0;
287
- return cmp <= 0;
288
- }
289
- case "in": {
290
- const list = c.in;
291
- if (!Array.isArray(list))
292
- return false;
293
- if (!rowIsPrimitive)
294
- return false;
295
- return list.some((item) => isPrimitive(item) && matchClause(rowValue, item));
296
- }
297
- default:
298
- return false;
299
- }
300
- }
301
- function matchesFilter(filter, row) {
302
- if (!filter || Object.keys(filter).length === 0)
303
- return true;
304
- for (const [col, clause] of Object.entries(filter)) {
305
- if (!matchClause(row[col], clause))
306
- return false;
307
- }
308
- return true;
309
- }
310
- function key(subgraphName, tableName) {
311
- return `${subgraphName}\x00${tableName}`;
312
- }
313
-
314
- class WebhookMatcher {
315
- byKey = new Map;
316
- byId = new Map;
317
- setAll(subs) {
318
- this.byKey.clear();
319
- this.byId.clear();
320
- for (const sub of subs) {
321
- if (sub.status !== "active")
322
- continue;
323
- if (sub.kind !== "subgraph" || !sub.subgraph_name || !sub.table_name)
324
- continue;
325
- this.byId.set(sub.id, sub);
326
- const k = key(sub.subgraph_name, sub.table_name);
327
- const arr = this.byKey.get(k);
328
- if (arr)
329
- arr.push(sub);
330
- else
331
- this.byKey.set(k, [sub]);
332
- }
333
- }
334
- match(subgraphName, tableName, row) {
335
- const bucket = this.byKey.get(key(subgraphName, tableName));
336
- if (!bucket)
337
- return [];
338
- const hits = [];
339
- for (const sub of bucket) {
340
- if (matchesFilter(sub.filter, row))
341
- hits.push(sub);
342
- }
343
- return hits;
344
- }
345
- has(subgraphName, tableName) {
346
- return this.byKey.has(key(subgraphName, tableName));
347
- }
348
- size() {
349
- return this.byId.size;
350
- }
351
- get(id) {
352
- return this.byId.get(id);
353
- }
354
- }
355
-
356
- // src/runtime/webhook-state.ts
357
- var matcher = new WebhookMatcher;
358
- async function refreshMatcher(db) {
359
- const rows = await sql`
360
- SELECT * FROM webhooks WHERE status = 'active'
361
- `.execute(db);
362
- matcher.setAll(rows.rows);
363
- return matcher.size();
364
- }
365
-
366
197
  // src/runtime/emitter.ts
367
198
  var BATCH_SIZE = 50;
368
199
  var LIVE_SHARE = 0.9;
@@ -598,12 +429,10 @@ function buildTestOutboxRow(sub) {
598
429
  id: randomUUID(),
599
430
  webhook_id: sub.id,
600
431
  kind: sub.kind,
601
- subgraph_name: sub.subgraph_name ?? null,
602
- table_name: sub.table_name ?? null,
603
432
  block_height: 0,
604
433
  tx_id: null,
605
434
  row_pk: null,
606
- event_type: sub.kind === "chain" ? "chain.test.apply" : `${sub.subgraph_name ?? "subgraph"}.${sub.table_name ?? "test"}.created`,
435
+ event_type: "chain.test.apply",
607
436
  payload: {
608
437
  test: true,
609
438
  message: "Secondlayer test delivery",
@@ -694,7 +523,7 @@ async function settleFailed(db, outboxRow, sub, errText) {
694
523
  });
695
524
  return;
696
525
  }
697
- const incResult = await sql2`
526
+ const incResult = await sql`
698
527
  UPDATE webhooks
699
528
  SET circuit_failures = circuit_failures + 1,
700
529
  last_delivery_at = NOW(),
@@ -756,23 +585,23 @@ async function claimAndDispatchOnce(db, state, emitterId, trigger) {
756
585
  const liveLimit = Math.max(1, Math.round(BATCH_SIZE * LIVE_SHARE));
757
586
  const replayLimit = BATCH_SIZE - liveLimit;
758
587
  const { combined, liveCount, replayCount } = await db.transaction().execute(async (tx) => {
759
- const live = await sql2`
588
+ const live = await sql`
760
589
  SELECT * FROM webhook_outbox
761
590
  WHERE status = 'pending'
762
591
  AND next_attempt_at <= NOW()
763
592
  AND is_replay = FALSE
764
593
  ORDER BY next_attempt_at ASC
765
594
  FOR UPDATE SKIP LOCKED
766
- LIMIT ${sql2.lit(liveLimit)}
595
+ LIMIT ${sql.lit(liveLimit)}
767
596
  `.execute(tx);
768
- const replay = await sql2`
597
+ const replay = await sql`
769
598
  SELECT * FROM webhook_outbox
770
599
  WHERE status = 'pending'
771
600
  AND next_attempt_at <= NOW()
772
601
  AND is_replay = TRUE
773
602
  ORDER BY next_attempt_at ASC
774
603
  FOR UPDATE SKIP LOCKED
775
- LIMIT ${sql2.lit(replayLimit)}
604
+ LIMIT ${sql.lit(replayLimit)}
776
605
  `.execute(tx);
777
606
  const rows = [...live.rows, ...replay.rows];
778
607
  if (rows.length === 0) {
@@ -891,15 +720,15 @@ async function runOne(db, state, sq, row) {
891
720
  }
892
721
  }
893
722
  async function runRetention(db) {
894
- await sql2`
723
+ await sql`
895
724
  DELETE FROM webhook_outbox
896
725
  WHERE status = 'delivered' AND delivered_at < NOW() - interval '7 days'
897
726
  `.execute(db);
898
- await sql2`
727
+ await sql`
899
728
  DELETE FROM webhook_deliveries
900
729
  WHERE dispatched_at < NOW() - interval '30 days'
901
730
  `.execute(db);
902
- await sql2`
731
+ await sql`
903
732
  DELETE FROM webhook_outbox
904
733
  WHERE status = 'dead' AND failed_at < NOW() - interval '90 days'
905
734
  `.execute(db);
@@ -918,27 +747,6 @@ async function startEmitter(opts) {
918
747
  const pollIntervalMs = opts?.pollIntervalMs ?? 120000;
919
748
  const retentionIntervalMs = opts?.retentionIntervalMs ?? 60 * 60000;
920
749
  logger3.info("[emitter] started", { id: emitterId });
921
- const MATCHER_BOOT_ATTEMPTS = 5;
922
- let lastErr = null;
923
- for (let i = 0;i < MATCHER_BOOT_ATTEMPTS; i++) {
924
- try {
925
- await refreshMatcher(db);
926
- lastErr = null;
927
- break;
928
- } catch (err) {
929
- lastErr = err;
930
- const delayMs = 500 * 2 ** i;
931
- logger3.warn("[emitter] matcher refresh failed, retrying", {
932
- attempt: i + 1,
933
- delayMs,
934
- error: err instanceof Error ? err.message : String(err)
935
- });
936
- await new Promise((r) => setTimeout(r, delayMs));
937
- }
938
- }
939
- if (lastErr) {
940
- throw new Error(`[emitter] matcher refresh failed ${MATCHER_BOOT_ATTEMPTS}×; aborting boot: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
941
- }
942
750
  const listenUrl = targetListenerUrl();
943
751
  const stopNew = await listen("webhooks:new_outbox", () => {
944
752
  if (!state.running)
@@ -950,9 +758,6 @@ async function startEmitter(opts) {
950
758
  const stopChanged = await listen("webhooks:changed", () => {
951
759
  if (!state.running)
952
760
  return;
953
- refreshMatcher(db).catch((err) => logger3.error("[emitter] matcher refresh failed", {
954
- error: err instanceof Error ? err.message : String(err)
955
- }));
956
761
  claimAndDrain(db, state, emitterId, "changed").catch((err) => logger3.error("[emitter] claim failed", {
957
762
  error: err instanceof Error ? err.message : String(err)
958
763
  }));
@@ -1018,5 +823,5 @@ export {
1018
823
  startEmitter
1019
824
  };
1020
825
 
1021
- //# debugId=F345D8FE7F2FF6CC64756E2164756E21
826
+ //# debugId=CAA46708A06EDDEB64756E2164756E21
1022
827
  //# sourceMappingURL=emitter.js.map