tapimo 0.10.2 → 0.11.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 (135) hide show
  1. package/dist/App.d.ts +3412 -1968
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js.map +1 -1
  4. package/dist/Webhooks.d.ts +0 -75
  5. package/dist/Webhooks.d.ts.map +1 -1
  6. package/dist/Webhooks.js +1 -143
  7. package/dist/Webhooks.js.map +1 -1
  8. package/dist/admin/App.d.ts +74 -74
  9. package/dist/admin/apps/api-keys.d.ts +4 -4
  10. package/dist/admin/apps/earn-vaults.d.ts +1 -1
  11. package/dist/admin/apps/organizations.d.ts +3 -3
  12. package/dist/admin/apps/verified-tokens.d.ts +74 -74
  13. package/dist/apps/data/App.d.ts +3618 -2954
  14. package/dist/apps/data/App.d.ts.map +1 -1
  15. package/dist/apps/data/App.js +5 -2
  16. package/dist/apps/data/App.js.map +1 -1
  17. package/dist/apps/data/routes/activities.d.ts +1 -1
  18. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  19. package/dist/apps/data/routes/activities.js +55 -27
  20. package/dist/apps/data/routes/activities.js.map +1 -1
  21. package/dist/apps/data/routes/addresses.d.ts +343 -343
  22. package/dist/apps/data/routes/balances.d.ts +11 -11
  23. package/dist/apps/data/routes/blocks.d.ts +5 -5
  24. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  25. package/dist/apps/data/routes/earn.d.ts +4 -4
  26. package/dist/apps/data/routes/exchanges.d.ts +4 -4
  27. package/dist/apps/data/routes/fee-amm.d.ts +26 -26
  28. package/dist/apps/data/routes/indexer.d.ts +34 -34
  29. package/dist/apps/data/routes/receipts.d.ts +236 -236
  30. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  31. package/dist/apps/data/routes/tokens.d.ts +3 -3
  32. package/dist/apps/data/routes/transactions.d.ts +1 -1
  33. package/dist/apps/data/routes/transfers.d.ts +13 -13
  34. package/dist/apps/data/routes/valuation.d.ts +21 -21
  35. package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
  36. package/dist/apps/data/routes/webhooks.d.ts +1011 -151
  37. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  38. package/dist/apps/data/routes/webhooks.js +124 -737
  39. package/dist/apps/data/routes/webhooks.js.map +1 -1
  40. package/dist/apps/data/routes/zones.d.ts +31 -31
  41. package/dist/apps/funding/routes/deposit-addresses.d.ts +2 -2
  42. package/dist/apps/funding/routes/providers.d.ts +13 -13
  43. package/dist/apps/funding/routes/transfers.d.ts +33 -33
  44. package/dist/apps/management/App.d.ts +793 -16
  45. package/dist/apps/management/App.d.ts.map +1 -1
  46. package/dist/apps/management/routes/api-keys.d.ts +50 -50
  47. package/dist/apps/management/routes/faucet.d.ts +31 -31
  48. package/dist/apps/management/routes/invite-links.d.ts +42 -42
  49. package/dist/apps/management/routes/members.d.ts +79 -79
  50. package/dist/apps/management/routes/projects.d.ts +40 -40
  51. package/dist/apps/management/routes/usage.d.ts +80 -80
  52. package/dist/apps/management/routes/webhooks.d.ts +1031 -59
  53. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  54. package/dist/apps/management/routes/webhooks.js +27 -4
  55. package/dist/apps/management/routes/webhooks.js.map +1 -1
  56. package/dist/db/Db.d.ts.map +1 -1
  57. package/dist/db/Db.js +21 -0
  58. package/dist/db/Db.js.map +1 -1
  59. package/dist/db/Schema.d.ts +7 -1
  60. package/dist/db/Schema.d.ts.map +1 -1
  61. package/dist/db/Schema.js +9 -0
  62. package/dist/db/Schema.js.map +1 -1
  63. package/dist/db/tables/webhookQueueEvents.d.ts +3 -1
  64. package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
  65. package/dist/db/tables/webhookQueueEvents.js +4 -0
  66. package/dist/db/tables/webhookQueueEvents.js.map +1 -1
  67. package/dist/db/tables/webhookSubscriptions.d.ts +33 -69
  68. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  69. package/dist/db/tables/webhookSubscriptions.js +63 -100
  70. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  71. package/dist/internal/MetricSink.d.ts +35 -125
  72. package/dist/internal/MetricSink.d.ts.map +1 -1
  73. package/dist/internal/MetricSink.js +50 -121
  74. package/dist/internal/MetricSink.js.map +1 -1
  75. package/dist/internal/Schema.d.ts +3 -1
  76. package/dist/internal/Schema.d.ts.map +1 -1
  77. package/dist/internal/Schema.js +4 -2
  78. package/dist/internal/Schema.js.map +1 -1
  79. package/dist/internal/WebhookTransfer.d.ts +1 -55
  80. package/dist/internal/WebhookTransfer.d.ts.map +1 -1
  81. package/dist/internal/WebhookTransfer.js +1 -239
  82. package/dist/internal/WebhookTransfer.js.map +1 -1
  83. package/dist/internal/Webhooks.d.ts +83 -44
  84. package/dist/internal/Webhooks.d.ts.map +1 -1
  85. package/dist/internal/Webhooks.js +87 -73
  86. package/dist/internal/Webhooks.js.map +1 -1
  87. package/dist/internal/funding/DepositWebhook.d.ts +25 -0
  88. package/dist/internal/funding/DepositWebhook.d.ts.map +1 -0
  89. package/dist/internal/funding/DepositWebhook.js +74 -0
  90. package/dist/internal/funding/DepositWebhook.js.map +1 -0
  91. package/dist/internal/funding/Reconciliation.d.ts +6 -0
  92. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  93. package/dist/internal/funding/Reconciliation.js +42 -10
  94. package/dist/internal/funding/Reconciliation.js.map +1 -1
  95. package/dist/internal/funding/index.d.ts +1 -0
  96. package/dist/internal/funding/index.d.ts.map +1 -1
  97. package/dist/internal/funding/index.js +1 -0
  98. package/dist/internal/funding/index.js.map +1 -1
  99. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
  100. package/dist/internal/webhookDestinations/slack.js +16 -0
  101. package/dist/internal/webhookDestinations/slack.js.map +1 -1
  102. package/package.json +1 -1
  103. package/src/App.ts +3 -0
  104. package/src/Client.test-d.ts +22 -1
  105. package/src/Webhooks.ts +1 -220
  106. package/src/apps/data/App.ts +8 -2
  107. package/src/apps/data/routes/activities.test.ts +107 -11
  108. package/src/apps/data/routes/activities.ts +64 -32
  109. package/src/apps/data/routes/webhooks.test.ts +179 -1497
  110. package/src/apps/data/routes/webhooks.ts +150 -974
  111. package/src/apps/management/routes/webhooks.test.ts +79 -1
  112. package/src/apps/management/routes/webhooks.ts +40 -4
  113. package/src/db/Db.test.ts +1 -5
  114. package/src/db/Db.ts +23 -0
  115. package/src/db/Schema.ts +13 -0
  116. package/src/db/tables/organizations.test.ts +5 -2
  117. package/src/db/tables/webhookQueueEvents.ts +7 -1
  118. package/src/db/tables/webhookSubscriptions.ts +89 -138
  119. package/src/internal/MetricSink.test.ts +41 -373
  120. package/src/internal/MetricSink.ts +92 -273
  121. package/src/internal/Schema.test.ts +18 -0
  122. package/src/internal/Schema.ts +13 -2
  123. package/src/internal/WebhookTransfer.ts +1 -351
  124. package/src/internal/Webhooks.test.ts +102 -139
  125. package/src/internal/Webhooks.ts +175 -106
  126. package/src/internal/funding/DepositWebhook.test.ts +131 -0
  127. package/src/internal/funding/DepositWebhook.ts +101 -0
  128. package/src/internal/funding/Reconciliation.test.ts +14 -0
  129. package/src/internal/funding/Reconciliation.ts +161 -86
  130. package/src/internal/funding/index.test.ts +1 -0
  131. package/src/internal/funding/index.ts +1 -0
  132. package/src/internal/webhookDestinations/slack.ts +15 -0
  133. package/src/Webhooks.bench.ts +0 -714
  134. package/src/Webhooks.memory.bench.ts +0 -250
  135. package/src/Webhooks.test.ts +0 -146
@@ -1,250 +0,0 @@
1
- /** @module-tag localnet */
2
- /**
3
- * Measures retained database-factory memory and dense transfer-scan allocations.
4
- * Set `WEBHOOK_MEMORY_SNAPSHOT_DIR` to write V8 heap snapshots for retained objects.
5
- */
6
- import * as fs from 'node:fs'
7
- import * as path from 'node:path'
8
- import * as v8 from 'node:v8'
9
- import { nanoid } from 'nanoid'
10
- import { sendTransactionSync } from 'viem/actions'
11
- import { Actions, Addresses } from 'viem/tempo'
12
- import { Webhooks } from 'tapimo'
13
- import { bench } from 'vite-plus/test'
14
-
15
- import * as Db from './db/Db.js'
16
- import * as Cursor from './internal/Cursor.js'
17
- import * as Runtime from '../test/runtime.js'
18
- import * as TestApp from '../test/App.js'
19
- import * as Tempo from '../test/Tempo.js'
20
- import * as TestViem from '../test/Viem.js'
21
-
22
- const batchSize = 200
23
- const endOfBlockIndex = 2_147_483_647
24
- const factoryTicks = positiveInteger('WEBHOOK_MEMORY_FACTORY_TICKS', 25)
25
- const mebibyte = 1024 * 1024
26
- const runtime = Runtime.get()
27
- const transferCallsPerTransaction = 32
28
- const transferCount = positiveInteger('WEBHOOK_MEMORY_TRANSFER_COUNT', 3_600)
29
-
30
- type Memory = {
31
- arrayBuffers: number
32
- external: number
33
- heapUsed: number
34
- rss: number
35
- }
36
-
37
- type Report = {
38
- after: Memory
39
- afterOperation: Memory
40
- afterOperationDelta: Memory
41
- before: Memory
42
- delta: Memory
43
- details?: Record<string, number> | undefined
44
- label: string
45
- }
46
-
47
- const reports: Report[] = []
48
-
49
- beforeAll(() => {
50
- if (runtime.mode !== 'localnet')
51
- throw new Error('Run webhook memory benchmarks with TEMPO_ENV=localnet.')
52
- if (!globalThis.gc)
53
- throw new Error('Run webhook memory benchmarks with NODE_OPTIONS=--expose-gc.')
54
- })
55
-
56
- afterAll(() => {
57
- process.stdout.write(`WEBHOOK_MEMORY_REPORT ${JSON.stringify(reports)}\n`)
58
- })
59
-
60
- describe('webhook poller memory', () => {
61
- bench(
62
- `factory-created databases / ${factoryTicks} ticks`,
63
- async () => {
64
- reports.push(await measureFactoryRetention())
65
- },
66
- { iterations: 1, time: 0, warmupIterations: 0, warmupTime: 0 },
67
- )
68
-
69
- bench(
70
- `transfer firehose / ${transferCount} generated transfers`,
71
- async () => {
72
- reports.push(await measureTransferFirehose())
73
- },
74
- { iterations: 1, time: 0, warmupIterations: 0, warmupTime: 0 },
75
- )
76
- })
77
-
78
- async function measureFactoryRetention(): Promise<Report> {
79
- const databases: Db.Db[] = []
80
- const schema = `memory_${nanoid()}`
81
- const database = Db.postgres({ connectionString: Runtime.postgresUrl, schema })
82
- await database.migrate()
83
- await Webhooks.createSubscription(database, {
84
- chainId: runtime.chainId,
85
- destination: { type: 'url', url: 'https://hooks.example.com/bench/memory/factory' },
86
- eventType: 'block:created',
87
- owner: { orgId: 'org_benchmark', type: 'api_key' },
88
- })
89
- const headBlockNumber = Number(await TestViem.getClient().getBlockNumber())
90
- const poller = Webhooks.createPoller({
91
- db: () => {
92
- const database = Db.postgres({ connectionString: Runtime.postgresUrl, schema })
93
- databases.push(database)
94
- return database
95
- },
96
- defaultChainId: runtime.chainId,
97
- dispatchBatch: async () => ({ ok: true }),
98
- rpc: TestApp.rpc,
99
- })
100
-
101
- try {
102
- const report = await measure('factory-retained', async () => {
103
- for (let index = 0; index < factoryTicks; index++)
104
- await poller.tick({
105
- confirmations: 0,
106
- eventTypes: ['block:created'],
107
- headBlockNumber,
108
- maxBlocks: 1,
109
- })
110
- })
111
- writeHeapSnapshot('factory-retained')
112
- return { ...report, details: { databases: databases.length, ticks: factoryTicks } }
113
- } finally {
114
- await Promise.allSettled(databases.map((database) => database.close()))
115
- await database.close()
116
- }
117
- }
118
-
119
- async function measureTransferFirehose(): Promise<Report> {
120
- const database = TestApp.database()
121
- const client = Tempo.getClient({ account: Tempo.accounts[0] })
122
- const startBlockNumber = Number(await TestViem.getClient().getBlockNumber())
123
-
124
- try {
125
- for (let offset = 0; offset < transferCount; offset += transferCallsPerTransaction) {
126
- const length = Math.min(transferCallsPerTransaction, transferCount - offset)
127
- await sendTransactionSync(client, {
128
- account: Tempo.accounts[0],
129
- calls: Array.from({ length }, () =>
130
- Actions.token.transfer.call(client, {
131
- amount: 1n,
132
- to: Tempo.accounts[1].address,
133
- token: Addresses.pathUsd,
134
- }),
135
- ),
136
- })
137
- }
138
-
139
- const headBlockNumber = Number(await TestViem.getClient().getBlockNumber())
140
- const subscription = await Webhooks.createSubscription(database, {
141
- chainId: runtime.chainId,
142
- destination: { type: 'url', url: 'https://hooks.example.com/bench/memory/firehose' },
143
- eventType: 'token:transfer',
144
- filters: {},
145
- owner: { orgId: 'org_benchmark', type: 'api_key' },
146
- })
147
- await Webhooks.setCursor(
148
- database,
149
- subscription.id,
150
- Cursor.encode([startBlockNumber, endOfBlockIndex]),
151
- )
152
-
153
- let dispatched = 0
154
- let serializedBytes = 0
155
- const poller = Webhooks.createPoller({
156
- db: database,
157
- defaultChainId: runtime.chainId,
158
- dispatchBatch: async (dispatchables) => {
159
- dispatched += dispatchables.length
160
- for (const body of dispatchables)
161
- serializedBytes += new TextEncoder().encode(JSON.stringify({ body })).byteLength
162
- return { ok: true }
163
- },
164
- rpc: TestApp.rpc,
165
- })
166
-
167
- const report = await measure('transfer-firehose', async () => {
168
- const result = await poller.tick({
169
- batchSize,
170
- confirmations: 0,
171
- eventTypes: ['token:transfer'],
172
- headBlockNumber,
173
- maxBlocks: 500,
174
- })
175
- if (result.dispatched === 0 || result.dispatched !== dispatched)
176
- throw new Error(`Unexpected dispatch count: ${result.dispatched} / ${dispatched}.`)
177
- if (result.dispatched > batchSize)
178
- throw new Error(`Dispatch count ${result.dispatched} exceeded batch size ${batchSize}.`)
179
- if (serializedBytes === 0) throw new Error('Expected serialized webhook queue messages.')
180
- })
181
- writeHeapSnapshot('transfer-firehose-retained')
182
- return {
183
- ...report,
184
- details: { dispatched, serializedBytes, transferCount },
185
- }
186
- } finally {
187
- await database.close()
188
- }
189
- }
190
-
191
- async function measure(label: string, fn: () => Promise<void>): Promise<Report> {
192
- collectGarbage()
193
- const before = memoryUsage()
194
- await fn()
195
- const afterOperation = memoryUsage()
196
- collectGarbage()
197
- const after = memoryUsage()
198
- return {
199
- after,
200
- afterOperation,
201
- afterOperationDelta: subtract(afterOperation, before),
202
- before,
203
- delta: subtract(after, before),
204
- label,
205
- }
206
- }
207
-
208
- function collectGarbage(): void {
209
- globalThis.gc?.()
210
- globalThis.gc?.()
211
- }
212
-
213
- function memoryUsage(): Memory {
214
- const usage = process.memoryUsage()
215
- return {
216
- arrayBuffers: mebibytes(usage.arrayBuffers),
217
- external: mebibytes(usage.external),
218
- heapUsed: mebibytes(usage.heapUsed),
219
- rss: mebibytes(usage.rss),
220
- }
221
- }
222
-
223
- function mebibytes(bytes: number): number {
224
- return Math.round((bytes / mebibyte) * 100) / 100
225
- }
226
-
227
- function positiveInteger(name: string, fallback: number): number {
228
- const input = process.env[name]
229
- if (!input) return fallback
230
- const value = Number(input)
231
- if (!Number.isSafeInteger(value) || value < 1)
232
- throw new Error(`${name} must be a positive integer.`)
233
- return value
234
- }
235
-
236
- function subtract(after: Memory, before: Memory): Memory {
237
- return {
238
- arrayBuffers: after.arrayBuffers - before.arrayBuffers,
239
- external: after.external - before.external,
240
- heapUsed: after.heapUsed - before.heapUsed,
241
- rss: after.rss - before.rss,
242
- }
243
- }
244
-
245
- function writeHeapSnapshot(label: string): void {
246
- const directory = process.env.WEBHOOK_MEMORY_SNAPSHOT_DIR
247
- if (!directory) return
248
- fs.mkdirSync(directory, { recursive: true })
249
- v8.writeHeapSnapshot(path.join(directory, `${label}.heapsnapshot`))
250
- }
@@ -1,146 +0,0 @@
1
- import { Metrics, Webhooks } from 'tapimo'
2
- import * as TestApp from '../test/App.js'
3
-
4
- describe('createPoller', () => {
5
- /** Decodes the opaque `(block, index)` keyset cursor the poller stores. */
6
- function decodeCursor(token: string | null): [number, number] | null {
7
- if (!token) return null
8
- const json = atob(token.replaceAll('-', '+').replaceAll('_', '/'))
9
- return JSON.parse(json) as [number, number]
10
- }
11
-
12
- test('binds a database and runs a tick at an injected head', async () => {
13
- const db = TestApp.database()
14
- const subscription = await Webhooks.createSubscription(db, {
15
- chainId: 42431,
16
- destination: { type: 'url', url: 'https://hooks.example.com/x' },
17
- eventType: 'token:transfer',
18
- owner: { orgId: 'org_test', type: 'api_key' },
19
- })
20
-
21
- const poller = Webhooks.createPoller({
22
- db,
23
- defaultChainId: 42431,
24
- })
25
-
26
- // A brand-new subscription anchors at the confirmed head on its first tick
27
- // (no backfill), so this exercises the createPoller → poll wiring (db
28
- // binding, default chain id, injected head, and result shape) without an
29
- // upstream request. Detailed scanner behavior lives in the route test.
30
- const result = await poller.tick({ headBlockNumber: 200 })
31
-
32
- expect(result).toMatchInlineSnapshot(`
33
- {
34
- "chainId": 42431,
35
- "dispatched": 0,
36
- "failed": 0,
37
- "safeBlock": 199,
38
- "scanned": 0,
39
- "subscriptions": 1,
40
- }
41
- `)
42
- expect(decodeCursor(await Webhooks.getCursor(db, subscription.id))).toMatchInlineSnapshot(`
43
- [
44
- 199,
45
- 2147483647,
46
- ]
47
- `)
48
- })
49
-
50
- test('records active subscriptions before scanning', async () => {
51
- const db = TestApp.database()
52
- await Webhooks.createSubscription(db, {
53
- chainId: 42431,
54
- destination: { type: 'url', url: 'https://hooks.example.com/x' },
55
- eventType: 'block:created',
56
- owner: { orgId: 'org_test', type: 'api_key' },
57
- })
58
- const gauges: { name: string; tags: Metrics.Tags; value: number }[] = []
59
- const poller = Webhooks.createPoller({
60
- db,
61
- defaultChainId: 42431,
62
- metrics: Metrics.from({
63
- count() {},
64
- flush() {},
65
- gauge(name, value, tags = {}) {
66
- gauges.push({ name, tags, value })
67
- },
68
- histogram() {},
69
- }),
70
- })
71
-
72
- await poller.tick({ eventTypes: ['block:created'], headBlockNumber: 200 })
73
-
74
- expect(gauges).toMatchInlineSnapshot(`
75
- [
76
- {
77
- "name": "webhook_poller_active_subscription_count",
78
- "tags": {
79
- "chain_id": "42431",
80
- },
81
- "value": 1,
82
- },
83
- {
84
- "name": "webhook_subscription_cursor_lag_blocks",
85
- "tags": {
86
- "chain_id": "42431",
87
- },
88
- "value": 0,
89
- },
90
- {
91
- "name": "webhook_poller_safe_block",
92
- "tags": {
93
- "chain_id": "42431",
94
- },
95
- "value": 199,
96
- },
97
- ]
98
- `)
99
- })
100
-
101
- test('clamps a creation checkpoint to genesis', async () => {
102
- const db = TestApp.database()
103
- const subscription = await Webhooks.createSubscription(
104
- db,
105
- {
106
- chainId: 42431,
107
- destination: { type: 'url', url: 'https://hooks.example.com/x' },
108
- eventType: 'block:created',
109
- owner: { orgId: 'org_test', type: 'api_key' },
110
- },
111
- { startBlockNumber: 1 },
112
- )
113
- const poller = Webhooks.createPoller({
114
- db,
115
- defaultChainId: 42431,
116
- })
117
-
118
- await poller.tick({
119
- eventTypes: ['block:created'],
120
- headBlockNumber: 1,
121
- })
122
-
123
- expect(decodeCursor(await Webhooks.getCursor(db, subscription.id))).toEqual([0, 2_147_483_647])
124
- })
125
-
126
- test('keeps a factory-created database open after each tick', async () => {
127
- const db = TestApp.database()
128
- await Webhooks.createSubscription(db, {
129
- chainId: 42431,
130
- destination: { type: 'url', url: 'https://hooks.example.com/x' },
131
- eventType: 'block:created',
132
- owner: { orgId: 'org_test', type: 'api_key' },
133
- })
134
- const poller = Webhooks.createPoller({
135
- db: () => db,
136
- defaultChainId: 42431,
137
- })
138
-
139
- await poller.tick({
140
- eventTypes: ['block:created'],
141
- headBlockNumber: 1,
142
- })
143
-
144
- await expect(Webhooks.maxCursorBlock(db, 42431)).resolves.toBe(0)
145
- })
146
- })