tapimo 0.9.0 → 0.10.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 (198) hide show
  1. package/dist/App.d.ts +4887 -5074
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/Webhooks.d.ts +21 -20
  4. package/dist/Webhooks.d.ts.map +1 -1
  5. package/dist/Webhooks.js +66 -23
  6. package/dist/Webhooks.js.map +1 -1
  7. package/dist/admin/apps/verified-tokens.d.ts +168 -168
  8. package/dist/apps/Zones.d.ts +34 -22
  9. package/dist/apps/Zones.d.ts.map +1 -1
  10. package/dist/apps/Zones.js +9 -8
  11. package/dist/apps/Zones.js.map +1 -1
  12. package/dist/apps/data/App.d.ts +5198 -5099
  13. package/dist/apps/data/App.d.ts.map +1 -1
  14. package/dist/apps/data/FxOracle.d.ts +4 -5
  15. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  16. package/dist/apps/data/FxOracle.js +19 -4
  17. package/dist/apps/data/FxOracle.js.map +1 -1
  18. package/dist/apps/data/routes/activities.d.ts +270 -270
  19. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  20. package/dist/apps/data/routes/activities.js +32 -27
  21. package/dist/apps/data/routes/activities.js.map +1 -1
  22. package/dist/apps/data/routes/addresses.d.ts +52 -52
  23. package/dist/apps/data/routes/balances.d.ts +36 -36
  24. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  25. package/dist/apps/data/routes/balances.js +135 -81
  26. package/dist/apps/data/routes/balances.js.map +1 -1
  27. package/dist/apps/data/routes/blocks.d.ts +125 -125
  28. package/dist/apps/data/routes/coingecko.d.ts +54 -54
  29. package/dist/apps/data/routes/earn.d.ts +512 -58
  30. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  31. package/dist/apps/data/routes/earn.js +368 -44
  32. package/dist/apps/data/routes/earn.js.map +1 -1
  33. package/dist/apps/data/routes/exchanges.d.ts +120 -120
  34. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  35. package/dist/apps/data/routes/exchanges.js +114 -118
  36. package/dist/apps/data/routes/exchanges.js.map +1 -1
  37. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  38. package/dist/apps/data/routes/receipts.js +68 -47
  39. package/dist/apps/data/routes/receipts.js.map +1 -1
  40. package/dist/apps/data/routes/rpc.d.ts +31 -31
  41. package/dist/apps/data/routes/tokens.d.ts +461 -459
  42. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  43. package/dist/apps/data/routes/transactions.d.ts +59 -51
  44. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  45. package/dist/apps/data/routes/transactions.js +25 -23
  46. package/dist/apps/data/routes/transactions.js.map +1 -1
  47. package/dist/apps/data/routes/transfers.d.ts +108 -86
  48. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  49. package/dist/apps/data/routes/transfers.js +618 -105
  50. package/dist/apps/data/routes/transfers.js.map +1 -1
  51. package/dist/apps/data/routes/valuation.d.ts +21 -21
  52. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  53. package/dist/apps/data/routes/valuation.js +2 -2
  54. package/dist/apps/data/routes/valuation.js.map +1 -1
  55. package/dist/apps/data/routes/verified-tokens.d.ts +37 -37
  56. package/dist/apps/data/routes/webhooks.d.ts +102 -505
  57. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  58. package/dist/apps/data/routes/webhooks.js +484 -342
  59. package/dist/apps/data/routes/webhooks.js.map +1 -1
  60. package/dist/apps/data/routes/zones.d.ts +12 -12
  61. package/dist/apps/data/routes/zones.d.ts.map +1 -1
  62. package/dist/apps/data/routes/zones.js +3 -1
  63. package/dist/apps/data/routes/zones.js.map +1 -1
  64. package/dist/apps/funding/App.d.ts +96 -96
  65. package/dist/apps/funding/routes/deposit-addresses.d.ts +52 -52
  66. package/dist/apps/funding/routes/quotes.d.ts +89 -89
  67. package/dist/apps/funding/routes/transfers.d.ts +4 -4
  68. package/dist/apps/management/App.d.ts +429 -727
  69. package/dist/apps/management/App.d.ts.map +1 -1
  70. package/dist/apps/management/routes/api-keys.d.ts +24 -24
  71. package/dist/apps/management/routes/billing.d.ts +72 -72
  72. package/dist/apps/management/routes/faucet.d.ts +6 -6
  73. package/dist/apps/management/routes/invitations.d.ts +134 -134
  74. package/dist/apps/management/routes/invite-links.d.ts +38 -38
  75. package/dist/apps/management/routes/me.d.ts +6 -6
  76. package/dist/apps/management/routes/members.d.ts +30 -30
  77. package/dist/apps/management/routes/orgs.d.ts +98 -98
  78. package/dist/apps/management/routes/projects.d.ts +20 -20
  79. package/dist/apps/management/routes/usage.d.ts +24 -24
  80. package/dist/apps/management/routes/verified-token-requests.d.ts +83 -83
  81. package/dist/apps/management/routes/webhooks.d.ts +158 -573
  82. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  83. package/dist/apps/mpp/App.d.ts +56 -44
  84. package/dist/apps/mpp/App.d.ts.map +1 -1
  85. package/dist/apps/mpp/Mpp.d.ts +3 -0
  86. package/dist/apps/mpp/Mpp.d.ts.map +1 -1
  87. package/dist/apps/mpp/Mpp.js +3 -0
  88. package/dist/apps/mpp/Mpp.js.map +1 -1
  89. package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
  90. package/dist/apps/mpp/internal/Adapter.js +9 -2
  91. package/dist/apps/mpp/internal/Adapter.js.map +1 -1
  92. package/dist/db/Db.d.ts +2 -2
  93. package/dist/db/Db.d.ts.map +1 -1
  94. package/dist/db/Db.js +17 -15
  95. package/dist/db/Db.js.map +1 -1
  96. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  97. package/dist/db/tables/sponsoredTransactions.js +4 -1
  98. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  99. package/dist/db/tables/webhookSubscriptions.d.ts +56 -3
  100. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  101. package/dist/db/tables/webhookSubscriptions.js +76 -3
  102. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  103. package/dist/internal/MetricSink.d.ts +112 -3
  104. package/dist/internal/MetricSink.d.ts.map +1 -1
  105. package/dist/internal/MetricSink.js +116 -3
  106. package/dist/internal/MetricSink.js.map +1 -1
  107. package/dist/internal/Schema.js +1 -1
  108. package/dist/internal/Schema.js.map +1 -1
  109. package/dist/internal/Tidx.d.ts +1 -3
  110. package/dist/internal/Tidx.d.ts.map +1 -1
  111. package/dist/internal/Tidx.js +1 -3
  112. package/dist/internal/Tidx.js.map +1 -1
  113. package/dist/internal/Viem.d.ts.map +1 -1
  114. package/dist/internal/Viem.js +2 -1
  115. package/dist/internal/Viem.js.map +1 -1
  116. package/dist/internal/WebhookDestination.d.ts +2 -0
  117. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  118. package/dist/internal/WebhookDestination.js +11 -0
  119. package/dist/internal/WebhookDestination.js.map +1 -1
  120. package/dist/internal/WebhookTransfer.d.ts +55 -0
  121. package/dist/internal/WebhookTransfer.d.ts.map +1 -0
  122. package/dist/internal/WebhookTransfer.js +296 -0
  123. package/dist/internal/WebhookTransfer.js.map +1 -0
  124. package/dist/internal/Webhooks.d.ts +37 -0
  125. package/dist/internal/Webhooks.d.ts.map +1 -1
  126. package/dist/internal/Webhooks.js +87 -13
  127. package/dist/internal/Webhooks.js.map +1 -1
  128. package/dist/internal/funding/Provider.d.ts +4 -0
  129. package/dist/internal/funding/Provider.d.ts.map +1 -1
  130. package/dist/internal/funding/Provider.js +11 -7
  131. package/dist/internal/funding/Provider.js.map +1 -1
  132. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  133. package/dist/internal/funding/Reconciliation.js +128 -0
  134. package/dist/internal/funding/Reconciliation.js.map +1 -1
  135. package/dist/internal/funding/providers/relay.d.ts +3 -0
  136. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  137. package/dist/internal/funding/providers/relay.js +12 -1
  138. package/dist/internal/funding/providers/relay.js.map +1 -1
  139. package/package.json +3 -3
  140. package/src/Client.test-d.ts +60 -5
  141. package/src/Webhooks.bench.ts +714 -0
  142. package/src/Webhooks.memory.bench.ts +250 -0
  143. package/src/Webhooks.test.ts +77 -6
  144. package/src/Webhooks.ts +83 -29
  145. package/src/apps/Zones.test.ts +5 -4
  146. package/src/apps/Zones.ts +9 -8
  147. package/src/apps/data/FxOracle.test.ts +18 -0
  148. package/src/apps/data/FxOracle.ts +28 -5
  149. package/src/apps/data/routes/activities.test.ts +19 -0
  150. package/src/apps/data/routes/activities.ts +34 -30
  151. package/src/apps/data/routes/balances.test.ts +119 -10
  152. package/src/apps/data/routes/balances.ts +154 -95
  153. package/src/apps/data/routes/blocks.test.ts +12 -6
  154. package/src/apps/data/routes/earn.test.ts +197 -0
  155. package/src/apps/data/routes/earn.ts +486 -44
  156. package/src/apps/data/routes/exchanges.test.ts +7 -6
  157. package/src/apps/data/routes/exchanges.ts +124 -117
  158. package/src/apps/data/routes/receipts.test.ts +24 -7
  159. package/src/apps/data/routes/receipts.ts +67 -50
  160. package/src/apps/data/routes/tokens.test.ts +51 -0
  161. package/src/apps/data/routes/transactions.test.ts +42 -0
  162. package/src/apps/data/routes/transactions.ts +44 -39
  163. package/src/apps/data/routes/transfers.test.ts +402 -24
  164. package/src/apps/data/routes/transfers.ts +708 -101
  165. package/src/apps/data/routes/valuation.test.ts +26 -0
  166. package/src/apps/data/routes/valuation.ts +6 -2
  167. package/src/apps/data/routes/webhooks.test.ts +414 -130
  168. package/src/apps/data/routes/webhooks.ts +603 -419
  169. package/src/apps/data/routes/zones.ts +6 -1
  170. package/src/apps/funding/routes/quotes.test.ts +1 -1
  171. package/src/apps/mpp/Mpp.test.ts +13 -1
  172. package/src/apps/mpp/Mpp.ts +8 -0
  173. package/src/apps/mpp/internal/Adapter.ts +10 -2
  174. package/src/db/Db.test.ts +40 -6
  175. package/src/db/Db.ts +33 -19
  176. package/src/db/tables/sponsoredTransactions.ts +9 -2
  177. package/src/db/tables/webhookSubscriptions.ts +116 -4
  178. package/src/internal/EarnVaults.test.ts +2 -2
  179. package/src/internal/MetricSink.test.ts +518 -0
  180. package/src/internal/MetricSink.ts +266 -9
  181. package/src/internal/Schema.ts +1 -1
  182. package/src/internal/Tidx.test.ts +4 -4
  183. package/src/internal/Tidx.ts +1 -3
  184. package/src/internal/Viem.test.ts +8 -0
  185. package/src/internal/Viem.ts +2 -1
  186. package/src/internal/WebhookDestination.test.ts +20 -0
  187. package/src/internal/WebhookDestination.ts +10 -0
  188. package/src/internal/WebhookTransfer.ts +426 -0
  189. package/src/internal/Webhooks.test.ts +137 -0
  190. package/src/internal/Webhooks.ts +131 -17
  191. package/src/internal/funding/Provider.test.ts +47 -7
  192. package/src/internal/funding/Provider.ts +12 -7
  193. package/src/internal/funding/Reconciliation.local.test.ts +3 -1
  194. package/src/internal/funding/Reconciliation.test.ts +1240 -0
  195. package/src/internal/funding/Reconciliation.ts +136 -0
  196. package/src/internal/funding/SourceObservation.test.ts +1 -0
  197. package/src/internal/funding/providers/relay.test.ts +1 -4
  198. package/src/internal/funding/providers/relay.ts +16 -1
@@ -1,4 +1,5 @@
1
1
  import * as Metrics from '../Metrics.js'
2
+ import type * as Webhooks from '../Webhooks.js'
2
3
  import type * as Log from './Log.js'
3
4
  import * as MetricSink from './MetricSink.js'
4
5
 
@@ -453,3 +454,520 @@ describe('requests', () => {
453
454
  expect(JSON.stringify(mpp)).not.toMatch(/request_private|credential|payload|signature/)
454
455
  })
455
456
  })
457
+
458
+ describe('webhooks', () => {
459
+ test('records recovery, poll-cycle, poll-stage, and failed-tick telemetry with bounded tags', () => {
460
+ const events: MetricEvent[] = []
461
+ const metrics = Metrics.from({
462
+ count(name, value, tags) {
463
+ events.push({ name, tags, type: 'count', value })
464
+ },
465
+ flush() {},
466
+ gauge(name, value, tags) {
467
+ events.push({ name, tags, type: 'gauge', value })
468
+ },
469
+ histogram(name, value, tags) {
470
+ events.push({ name, tags, type: 'histogram', value })
471
+ },
472
+ })
473
+ const sink = MetricSink.webhooks(metrics)
474
+
475
+ sink.record({
476
+ chainId: 4217,
477
+ dequeuedAt: 1_300,
478
+ enqueuedAt: 1_100,
479
+ trigger: 'alarm',
480
+ type: 'webhook:head-recovery',
481
+ })
482
+ sink.record({
483
+ chainId: 4217,
484
+ lagBlocks: 2,
485
+ type: 'webhook:head-progress',
486
+ })
487
+ sink.record({
488
+ chainId: 4217,
489
+ delayMs: 200,
490
+ trigger: 'direct',
491
+ type: 'webhook:poll-start',
492
+ })
493
+ sink.record({
494
+ chainId: 4217,
495
+ outcome: 'ok',
496
+ requestedBoundaryAdvanceBlocks: 10,
497
+ trigger: 'direct',
498
+ type: 'webhook:poll-cycle',
499
+ })
500
+ sink.record({
501
+ chainId: 4217,
502
+ failure: 'remote',
503
+ stage: 'finish',
504
+ trigger: 'direct',
505
+ type: 'webhook:poll-failure',
506
+ })
507
+ sink.record({
508
+ chainId: 4217,
509
+ durationMs: 42,
510
+ eventType: 'token:transfer',
511
+ outcome: 'ok',
512
+ stage: 'scan',
513
+ type: 'webhook:poll',
514
+ })
515
+ sink.record({
516
+ cursorLagBlocks: 7,
517
+ meta: { chainId: 4217, durationMs: 50, outcome: 'error' },
518
+ result: undefined,
519
+ type: 'webhook:tick',
520
+ })
521
+
522
+ expect(events).toMatchInlineSnapshot(`
523
+ [
524
+ {
525
+ "name": "webhook_head_recovery_queue_count",
526
+ "tags": {
527
+ "chain_id": "4217",
528
+ "trigger": "alarm",
529
+ },
530
+ "type": "count",
531
+ "value": 1,
532
+ },
533
+ {
534
+ "name": "webhook_head_recovery_queue_wait_ms",
535
+ "tags": {
536
+ "chain_id": "4217",
537
+ "trigger": "alarm",
538
+ },
539
+ "type": "histogram",
540
+ "value": 200,
541
+ },
542
+ {
543
+ "name": "webhook_head_progress_lag_blocks",
544
+ "tags": {
545
+ "chain_id": "4217",
546
+ },
547
+ "type": "gauge",
548
+ "value": 2,
549
+ },
550
+ {
551
+ "name": "webhook_poll_start_count",
552
+ "tags": {
553
+ "chain_id": "4217",
554
+ "trigger": "direct",
555
+ },
556
+ "type": "count",
557
+ "value": 1,
558
+ },
559
+ {
560
+ "name": "webhook_poll_start_delay_ms",
561
+ "tags": {
562
+ "chain_id": "4217",
563
+ "trigger": "direct",
564
+ },
565
+ "type": "histogram",
566
+ "value": 200,
567
+ },
568
+ {
569
+ "name": "webhook_poll_cycle_count",
570
+ "tags": {
571
+ "chain_id": "4217",
572
+ "outcome": "ok",
573
+ "trigger": "direct",
574
+ },
575
+ "type": "count",
576
+ "value": 1,
577
+ },
578
+ {
579
+ "name": "webhook_poll_requested_boundary_advance_blocks",
580
+ "tags": {
581
+ "chain_id": "4217",
582
+ "outcome": "ok",
583
+ "trigger": "direct",
584
+ },
585
+ "type": "histogram",
586
+ "value": 10,
587
+ },
588
+ {
589
+ "name": "webhook_poll_failure_count",
590
+ "tags": {
591
+ "chain_id": "4217",
592
+ "failure": "remote",
593
+ "stage": "finish",
594
+ "trigger": "direct",
595
+ },
596
+ "type": "count",
597
+ "value": 1,
598
+ },
599
+ {
600
+ "name": "webhook_poller_stage_duration_ms",
601
+ "tags": {
602
+ "chain_id": "4217",
603
+ "event_type": "token:transfer",
604
+ "outcome": "ok",
605
+ "stage": "scan",
606
+ },
607
+ "type": "histogram",
608
+ "value": 42,
609
+ },
610
+ {
611
+ "name": "webhook_poller_tick_count",
612
+ "tags": {
613
+ "chain_id": "4217",
614
+ "outcome": "error",
615
+ },
616
+ "type": "count",
617
+ "value": 1,
618
+ },
619
+ {
620
+ "name": "webhook_poller_tick_duration_ms",
621
+ "tags": {
622
+ "chain_id": "4217",
623
+ "outcome": "error",
624
+ },
625
+ "type": "histogram",
626
+ "value": 50,
627
+ },
628
+ {
629
+ "name": "webhook_subscription_cursor_lag_blocks",
630
+ "tags": {
631
+ "chain_id": "4217",
632
+ },
633
+ "type": "gauge",
634
+ "value": 7,
635
+ },
636
+ ]
637
+ `)
638
+ })
639
+
640
+ test('records scan boundaries with bounded workload dimensions', () => {
641
+ const events: MetricEvent[] = []
642
+ const metrics = Metrics.from({
643
+ count(name, value, tags) {
644
+ events.push({ name, tags, type: 'count', value })
645
+ },
646
+ flush() {},
647
+ gauge() {},
648
+ histogram(name, value, tags) {
649
+ events.push({ name, tags, type: 'histogram', value })
650
+ },
651
+ })
652
+ const sink = MetricSink.webhooks(metrics)
653
+
654
+ sink.record({
655
+ chainId: 4217,
656
+ concurrency: 8,
657
+ eventType: 'log:emitted',
658
+ maxWindowBlocks: 1_000,
659
+ phase: 'start',
660
+ rpcMethod: 'eth_getLogs',
661
+ subscriptionCount: 8,
662
+ subscriptionLimit: 256,
663
+ totalWindowBlocks: 8_000,
664
+ type: 'webhook:poll-scan',
665
+ })
666
+ sink.record({
667
+ chainId: 4217,
668
+ concurrency: 8,
669
+ durationMs: 250,
670
+ eventType: 'log:emitted',
671
+ failedSubscriptions: 1,
672
+ matchedItems: 700,
673
+ maxWindowBlocks: 1_000,
674
+ outcome: 'error',
675
+ phase: 'complete',
676
+ rpcMethod: 'eth_getLogs',
677
+ subscriptionCount: 8,
678
+ subscriptionLimit: 256,
679
+ totalWindowBlocks: 8_000,
680
+ type: 'webhook:poll-scan',
681
+ })
682
+
683
+ expect(events.map(({ name, tags, value }) => ({ name, tags, value }))).toMatchInlineSnapshot(`
684
+ [
685
+ {
686
+ "name": "webhook_poller_scan_checkpoint_count",
687
+ "tags": {
688
+ "chain_id": "4217",
689
+ "event_type": "log:emitted",
690
+ "phase": "start",
691
+ },
692
+ "value": 1,
693
+ },
694
+ {
695
+ "name": "webhook_poller_scan_concurrency",
696
+ "tags": {
697
+ "chain_id": "4217",
698
+ "event_type": "log:emitted",
699
+ "phase": "start",
700
+ },
701
+ "value": 8,
702
+ },
703
+ {
704
+ "name": "webhook_poller_scan_subscription_count",
705
+ "tags": {
706
+ "chain_id": "4217",
707
+ "event_type": "log:emitted",
708
+ "phase": "start",
709
+ },
710
+ "value": 8,
711
+ },
712
+ {
713
+ "name": "webhook_poller_scan_subscription_limit",
714
+ "tags": {
715
+ "chain_id": "4217",
716
+ "event_type": "log:emitted",
717
+ "phase": "start",
718
+ },
719
+ "value": 256,
720
+ },
721
+ {
722
+ "name": "webhook_poller_scan_max_window_blocks",
723
+ "tags": {
724
+ "chain_id": "4217",
725
+ "event_type": "log:emitted",
726
+ "phase": "start",
727
+ },
728
+ "value": 1000,
729
+ },
730
+ {
731
+ "name": "webhook_poller_scan_total_window_blocks",
732
+ "tags": {
733
+ "chain_id": "4217",
734
+ "event_type": "log:emitted",
735
+ "phase": "start",
736
+ },
737
+ "value": 8000,
738
+ },
739
+ {
740
+ "name": "webhook_poller_scan_checkpoint_count",
741
+ "tags": {
742
+ "chain_id": "4217",
743
+ "event_type": "log:emitted",
744
+ "phase": "complete",
745
+ },
746
+ "value": 1,
747
+ },
748
+ {
749
+ "name": "webhook_poller_scan_concurrency",
750
+ "tags": {
751
+ "chain_id": "4217",
752
+ "event_type": "log:emitted",
753
+ "phase": "complete",
754
+ },
755
+ "value": 8,
756
+ },
757
+ {
758
+ "name": "webhook_poller_scan_subscription_count",
759
+ "tags": {
760
+ "chain_id": "4217",
761
+ "event_type": "log:emitted",
762
+ "phase": "complete",
763
+ },
764
+ "value": 8,
765
+ },
766
+ {
767
+ "name": "webhook_poller_scan_subscription_limit",
768
+ "tags": {
769
+ "chain_id": "4217",
770
+ "event_type": "log:emitted",
771
+ "phase": "complete",
772
+ },
773
+ "value": 256,
774
+ },
775
+ {
776
+ "name": "webhook_poller_scan_max_window_blocks",
777
+ "tags": {
778
+ "chain_id": "4217",
779
+ "event_type": "log:emitted",
780
+ "phase": "complete",
781
+ },
782
+ "value": 1000,
783
+ },
784
+ {
785
+ "name": "webhook_poller_scan_total_window_blocks",
786
+ "tags": {
787
+ "chain_id": "4217",
788
+ "event_type": "log:emitted",
789
+ "phase": "complete",
790
+ },
791
+ "value": 8000,
792
+ },
793
+ {
794
+ "name": "webhook_poller_scan_duration_ms",
795
+ "tags": {
796
+ "chain_id": "4217",
797
+ "event_type": "log:emitted",
798
+ "phase": "complete",
799
+ },
800
+ "value": 250,
801
+ },
802
+ {
803
+ "name": "webhook_poller_scan_failed_subscription_count",
804
+ "tags": {
805
+ "chain_id": "4217",
806
+ "event_type": "log:emitted",
807
+ "phase": "complete",
808
+ },
809
+ "value": 1,
810
+ },
811
+ {
812
+ "name": "webhook_poller_scan_matched_item_count",
813
+ "tags": {
814
+ "chain_id": "4217",
815
+ "event_type": "log:emitted",
816
+ "phase": "complete",
817
+ },
818
+ "value": 700,
819
+ },
820
+ {
821
+ "name": "webhook_poller_rpc_scan_count",
822
+ "tags": {
823
+ "chain_id": "4217",
824
+ "event_type": "log:emitted",
825
+ "outcome": "error",
826
+ "rpc_method": "eth_getLogs",
827
+ },
828
+ "value": 1,
829
+ },
830
+ ]
831
+ `)
832
+ })
833
+
834
+ test('records delivery stages without subscription identity', () => {
835
+ const events: MetricEvent[] = []
836
+ const metrics = Metrics.from({
837
+ count(name, value, tags) {
838
+ events.push({ name, tags, type: 'count', value })
839
+ },
840
+ flush() {},
841
+ gauge() {},
842
+ histogram(name, value, tags) {
843
+ events.push({ name, tags, type: 'histogram', value })
844
+ },
845
+ })
846
+ const sink = MetricSink.webhooks(metrics)
847
+ const subscription = {
848
+ chainId: 4217,
849
+ createdAt: '2026-01-01T00:00:00.000Z',
850
+ destination: { type: 'url', url: 'https://hooks.example.com/endpoint' },
851
+ eventType: 'token:transfer',
852
+ failureCount: 0,
853
+ filters: {},
854
+ id: 'wh_private',
855
+ owner: { orgId: 'org_private', type: 'api_key' },
856
+ secret: 'whsec_private',
857
+ status: 'active',
858
+ updatedAt: '2026-01-01T00:00:00.000Z',
859
+ } satisfies Webhooks.Subscription
860
+ const envelope = {
861
+ chainId: 4217,
862
+ createdAt: '2026-01-01T00:00:00.000Z',
863
+ data: { ping: true },
864
+ id: 'evt_private',
865
+ subscriptionId: subscription.id,
866
+ type: 'ping',
867
+ } satisfies Webhooks.Envelope
868
+
869
+ sink.record({
870
+ envelope,
871
+ queueAttempt: 2,
872
+ result: { durationMs: 25, ok: true, status: 204 },
873
+ subscription,
874
+ timings: {
875
+ deliveryLogMs: 5,
876
+ envelopeToQueueMs: 3,
877
+ endToEndMs: 250,
878
+ eventToEnvelopeMs: 80,
879
+ processingMs: 67,
880
+ queueWaitMs: 100,
881
+ stateWriteMs: 10,
882
+ subscriptionReadMs: 4,
883
+ },
884
+ trigger: 'queue',
885
+ type: 'webhook:delivery',
886
+ })
887
+
888
+ expect(events.map(({ name, value }) => ({ name, value }))).toMatchInlineSnapshot(`
889
+ [
890
+ {
891
+ "name": "webhook_delivery_attempt_count",
892
+ "value": 1,
893
+ },
894
+ {
895
+ "name": "webhook_delivery_duration_ms",
896
+ "value": 25,
897
+ },
898
+ {
899
+ "name": "webhook_delivery_log_write_ms",
900
+ "value": 5,
901
+ },
902
+ {
903
+ "name": "webhook_delivery_envelope_to_queue_ms",
904
+ "value": 3,
905
+ },
906
+ {
907
+ "name": "webhook_delivery_end_to_end_ms",
908
+ "value": 250,
909
+ },
910
+ {
911
+ "name": "webhook_delivery_event_to_envelope_ms",
912
+ "value": 80,
913
+ },
914
+ {
915
+ "name": "webhook_delivery_processing_ms",
916
+ "value": 67,
917
+ },
918
+ {
919
+ "name": "webhook_delivery_queue_wait_ms",
920
+ "value": 100,
921
+ },
922
+ {
923
+ "name": "webhook_delivery_state_write_ms",
924
+ "value": 10,
925
+ },
926
+ {
927
+ "name": "webhook_delivery_subscription_read_ms",
928
+ "value": 4,
929
+ },
930
+ ]
931
+ `)
932
+ expect(events.find((event) => event.name === 'webhook_delivery_attempt_count')?.tags)
933
+ .toMatchInlineSnapshot(`
934
+ {
935
+ "attempt_bucket": "1",
936
+ "chain_id": "4217",
937
+ "destination_type": "url",
938
+ "event_type": "ping",
939
+ "outcome": "succeeded",
940
+ "queue_attempt_bucket": "2",
941
+ "queue_disposition": "acked",
942
+ "status_class": "2xx",
943
+ "trigger": "queue",
944
+ }
945
+ `)
946
+ expect(events.find((event) => event.name === 'webhook_delivery_queue_wait_ms')?.tags)
947
+ .toMatchInlineSnapshot(`
948
+ {
949
+ "chain_id": "4217",
950
+ "destination_type": "url",
951
+ "event_type": "ping",
952
+ "outcome": "succeeded",
953
+ "queue_attempt_bucket": "2",
954
+ "queue_disposition": "acked",
955
+ }
956
+ `)
957
+ expect(JSON.stringify(events)).not.toMatch(/evt_private|org_private|wh_private|whsec_private/)
958
+
959
+ sink.record({
960
+ envelope,
961
+ queueAttempt: 3,
962
+ result: { error: 'upstream unavailable', ok: false, status: 503 },
963
+ subscription,
964
+ trigger: 'queue',
965
+ type: 'webhook:delivery',
966
+ })
967
+
968
+ expect(events.at(-1)?.tags).toMatchObject({
969
+ queue_attempt_bucket: '3-5',
970
+ queue_disposition: 'retried',
971
+ })
972
+ })
973
+ })