tapimo 0.10.1 → 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.
- package/dist/App.d.ts +5249 -3572
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js.map +1 -1
- package/dist/Webhooks.d.ts +1 -76
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +2 -144
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +74 -74
- package/dist/admin/apps/earn-vaults.d.ts +1 -1
- package/dist/admin/apps/organizations.d.ts +3 -3
- package/dist/admin/apps/verified-tokens.d.ts +74 -74
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +206 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +4 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +2 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +3618 -2954
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +7 -2
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +1 -1
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +55 -27
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +11 -11
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +2 -6
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +5 -5
- package/dist/apps/data/routes/earn.d.ts +4 -4
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +63 -34
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +8 -4
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +115 -77
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +26 -26
- package/dist/apps/data/routes/receipts.d.ts +240 -239
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +15 -14
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/tokens.d.ts +4 -4
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +4 -3
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +282 -282
- package/dist/apps/data/routes/transfers.d.ts +13 -13
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +25 -7
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +21 -21
- package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
- package/dist/apps/data/routes/webhooks.d.ts +1519 -643
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +134 -740
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +3 -3
- package/dist/apps/funding/App.d.ts +233 -0
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +331 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +207 -11
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +33 -33
- package/dist/apps/management/App.d.ts +793 -16
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +50 -50
- package/dist/apps/management/routes/faucet.d.ts +2 -2
- package/dist/apps/management/routes/invite-links.d.ts +42 -42
- package/dist/apps/management/routes/members.d.ts +2 -2
- package/dist/apps/management/routes/projects.d.ts +40 -40
- package/dist/apps/management/routes/webhooks.d.ts +1031 -59
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/management/routes/webhooks.js +27 -4
- package/dist/apps/management/routes/webhooks.js.map +1 -1
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +53 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +7 -1
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +9 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.d.ts +20 -0
- package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.js +17 -0
- package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
- package/dist/db/tables/fundingDeposits.d.ts +41 -0
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +69 -0
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/db/tables/webhookQueueEvents.d.ts +3 -1
- package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
- package/dist/db/tables/webhookQueueEvents.js +4 -0
- package/dist/db/tables/webhookQueueEvents.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +33 -69
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +63 -100
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -26
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +0 -33
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Credential.d.ts +27 -0
- package/dist/internal/Credential.d.ts.map +1 -0
- package/dist/internal/Credential.js +34 -0
- package/dist/internal/Credential.js.map +1 -0
- package/dist/internal/EarnRates.js +11 -7
- package/dist/internal/EarnRates.js.map +1 -1
- package/dist/internal/Log.d.ts +8 -0
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +4 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +50 -116
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +60 -106
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Schema.d.ts +3 -1
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +4 -2
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Tidx.d.ts +4 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +2 -2
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts +4 -3
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +3 -2
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/WebhookTransfer.d.ts +1 -55
- package/dist/internal/WebhookTransfer.d.ts.map +1 -1
- package/dist/internal/WebhookTransfer.js +1 -239
- package/dist/internal/WebhookTransfer.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +85 -44
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +94 -73
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/DepositWebhook.d.ts +25 -0
- package/dist/internal/funding/DepositWebhook.d.ts.map +1 -0
- package/dist/internal/funding/DepositWebhook.js +74 -0
- package/dist/internal/funding/DepositWebhook.js.map +1 -0
- package/dist/internal/funding/Reconciliation.d.ts +6 -0
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +42 -10
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/index.d.ts +1 -0
- package/dist/internal/funding/index.d.ts.map +1 -1
- package/dist/internal/funding/index.js +1 -0
- package/dist/internal/funding/index.js.map +1 -1
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -0
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
- package/dist/internal/webhookDestinations/slack.js +16 -0
- package/dist/internal/webhookDestinations/slack.js.map +1 -1
- package/package.json +1 -1
- package/src/App.test.ts +30 -0
- package/src/App.ts +3 -0
- package/src/Client.test-d.ts +55 -2
- package/src/Webhooks.ts +2 -221
- package/src/analytics/Analytics.test.ts +34 -0
- package/src/analytics/Analytics.ts +206 -0
- package/src/analytics/tables/requestEvents.ts +6 -0
- package/src/apps/composition.test.ts +1 -0
- package/src/apps/data/App.ts +10 -2
- package/src/apps/data/routes/activities.test.ts +107 -11
- package/src/apps/data/routes/activities.ts +64 -32
- package/src/apps/data/routes/balances.ts +2 -6
- package/src/apps/data/routes/earn.ts +64 -34
- package/src/apps/data/routes/exchanges.test.ts +8 -3
- package/src/apps/data/routes/exchanges.ts +134 -82
- package/src/apps/data/routes/receipts.ts +16 -14
- package/src/apps/data/routes/tokens.ts +3 -2
- package/src/apps/data/routes/transactions.bench.ts +8 -0
- package/src/apps/data/routes/transfers.ts +36 -10
- package/src/apps/data/routes/webhooks.test.ts +179 -1497
- package/src/apps/data/routes/webhooks.ts +172 -975
- package/src/apps/funding/routes/deposit-addresses.test.ts +216 -2
- package/src/apps/funding/routes/deposit-addresses.ts +254 -18
- package/src/apps/funding/routes/transfers.test.ts +17 -2
- package/src/apps/management/routes/webhooks.test.ts +79 -1
- package/src/apps/management/routes/webhooks.ts +40 -4
- package/src/db/Db.test.ts +1 -5
- package/src/db/Db.ts +59 -0
- package/src/db/Schema.ts +13 -0
- package/src/db/tables/fundingDepositAddresses.test.ts +28 -0
- package/src/db/tables/fundingDepositAddresses.ts +39 -0
- package/src/db/tables/fundingDeposits.test.ts +82 -0
- package/src/db/tables/fundingDeposits.ts +130 -0
- package/src/db/tables/organizations.test.ts +5 -2
- package/src/db/tables/webhookQueueEvents.ts +7 -1
- package/src/db/tables/webhookSubscriptions.ts +89 -138
- package/src/internal/Auth.test.ts +0 -48
- package/src/internal/Auth.ts +0 -59
- package/src/internal/Credential.test.ts +51 -0
- package/src/internal/Credential.ts +58 -0
- package/src/internal/EarnRates.ts +13 -7
- package/src/internal/Log.test.ts +3 -1
- package/src/internal/Log.ts +14 -0
- package/src/internal/MetricSink.test.ts +75 -358
- package/src/internal/MetricSink.ts +117 -246
- package/src/internal/Schema.test.ts +18 -0
- package/src/internal/Schema.ts +13 -2
- package/src/internal/Tidx.ts +5 -4
- package/src/internal/Viem.test.ts +4 -0
- package/src/internal/Viem.ts +6 -4
- package/src/internal/WebhookTransfer.ts +1 -351
- package/src/internal/Webhooks.test.ts +111 -139
- package/src/internal/Webhooks.ts +181 -105
- package/src/internal/funding/DepositWebhook.test.ts +131 -0
- package/src/internal/funding/DepositWebhook.ts +101 -0
- package/src/internal/funding/Reconciliation.test.ts +14 -0
- package/src/internal/funding/Reconciliation.ts +161 -86
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/index.test.ts +1 -0
- package/src/internal/index.ts +1 -0
- package/src/internal/webhookDestinations/slack.ts +15 -0
- package/src/Webhooks.bench.ts +0 -714
- package/src/Webhooks.memory.bench.ts +0 -250
- package/src/Webhooks.test.ts +0 -146
|
@@ -10,7 +10,10 @@ import * as Orgs from './orgs.js'
|
|
|
10
10
|
import * as Webhooks from './webhooks.js'
|
|
11
11
|
|
|
12
12
|
const origin = 'http://localhost'
|
|
13
|
-
const webhook = {
|
|
13
|
+
const webhook = {
|
|
14
|
+
applicationEventTypes: ['funding:deposit.updated'],
|
|
15
|
+
supportedChainIds: [4217, 42431],
|
|
16
|
+
} satisfies App.Webhook
|
|
14
17
|
|
|
15
18
|
function json(method: 'PATCH' | 'POST', cookie: string, body: unknown) {
|
|
16
19
|
return {
|
|
@@ -47,6 +50,81 @@ async function organization(app: ReturnType<typeof createApp>, cookie: string) {
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
describe('webhooks', () => {
|
|
53
|
+
test('creates funding deposit subscriptions without reading the RPC head', async () => {
|
|
54
|
+
const db = TestApp.database()
|
|
55
|
+
const app = createApp({ db })
|
|
56
|
+
const cookie = await session(app)
|
|
57
|
+
const org = await organization(app, cookie)
|
|
58
|
+
const response = await app.request(
|
|
59
|
+
`/v1/orgs/${org.id}/webhooks`,
|
|
60
|
+
json('POST', cookie, {
|
|
61
|
+
chainId: 4217,
|
|
62
|
+
destination: { type: 'url', url: 'https://example.com/funding' },
|
|
63
|
+
environment: 'production',
|
|
64
|
+
eventType: 'funding:deposit.updated',
|
|
65
|
+
filters: { status: 'completed' },
|
|
66
|
+
}),
|
|
67
|
+
)
|
|
68
|
+
const created = await TestApp.json(response, Webhooks.schema.createWebhook.Response)
|
|
69
|
+
const stored = await core_Webhooks.getSubscription(
|
|
70
|
+
db,
|
|
71
|
+
{ orgId: org.id, type: 'api_key' },
|
|
72
|
+
created.id,
|
|
73
|
+
)
|
|
74
|
+
const listedResponse = await app.request(`/v1/orgs/${org.id}/webhooks`, {
|
|
75
|
+
headers: { cookie },
|
|
76
|
+
})
|
|
77
|
+
const listed = await TestApp.json(listedResponse, Webhooks.schema.ListResponse)
|
|
78
|
+
|
|
79
|
+
expect(response.status).toBe(200)
|
|
80
|
+
expect(created.environment).toBe('production')
|
|
81
|
+
expect(listed.data[0]?.environment).toBe('production')
|
|
82
|
+
expect(stored?.environment).toBe('production')
|
|
83
|
+
expect(await core_Webhooks.getCursor(db, created.id)).toBeNull()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('preserves a production funding environment on testnet', async () => {
|
|
87
|
+
const db = TestApp.database()
|
|
88
|
+
const app = createApp({ db })
|
|
89
|
+
const cookie = await session(app)
|
|
90
|
+
const org = await organization(app, cookie)
|
|
91
|
+
const response = await app.request(
|
|
92
|
+
`/v1/orgs/${org.id}/webhooks`,
|
|
93
|
+
json('POST', cookie, {
|
|
94
|
+
chainId: 42431,
|
|
95
|
+
destination: { type: 'url', url: 'https://example.com/funding-testnet' },
|
|
96
|
+
environment: 'production',
|
|
97
|
+
eventType: 'funding:deposit.updated',
|
|
98
|
+
}),
|
|
99
|
+
)
|
|
100
|
+
const created = await TestApp.json(response, Webhooks.schema.createWebhook.Response)
|
|
101
|
+
const stored = await core_Webhooks.getSubscription(
|
|
102
|
+
db,
|
|
103
|
+
{ orgId: org.id, type: 'api_key' },
|
|
104
|
+
created.id,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
expect(response.status).toBe(200)
|
|
108
|
+
expect(created.environment).toBe('production')
|
|
109
|
+
expect(stored?.environment).toBe('production')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('requires an explicit environment for funding deposit subscriptions', async () => {
|
|
113
|
+
const app = createApp()
|
|
114
|
+
const cookie = await session(app)
|
|
115
|
+
const org = await organization(app, cookie)
|
|
116
|
+
const response = await app.request(
|
|
117
|
+
`/v1/orgs/${org.id}/webhooks`,
|
|
118
|
+
json('POST', cookie, {
|
|
119
|
+
chainId: 4217,
|
|
120
|
+
destination: { type: 'url', url: 'https://example.com/funding' },
|
|
121
|
+
eventType: 'funding:deposit.updated',
|
|
122
|
+
}),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
expect(response.status).toBe(400)
|
|
126
|
+
})
|
|
127
|
+
|
|
50
128
|
test('creates, lists, pauses, and deletes an organization webhook', async () => {
|
|
51
129
|
const db = TestApp.database()
|
|
52
130
|
const app = createApp({ db })
|
|
@@ -22,13 +22,29 @@ export namespace schema {
|
|
|
22
22
|
|
|
23
23
|
/** Schemas for creating an organization webhook. */
|
|
24
24
|
export namespace createWebhook {
|
|
25
|
+
const Environment = z
|
|
26
|
+
.enum(['production', 'sandbox'])
|
|
27
|
+
.check(
|
|
28
|
+
z.describe('API-key environment whose private funding deposits are delivered.'),
|
|
29
|
+
z.meta({ examples: ['production'] }),
|
|
30
|
+
)
|
|
31
|
+
|
|
25
32
|
/** Organization webhook creation requires an explicit network. */
|
|
26
33
|
export const Body = z
|
|
27
34
|
.intersection(
|
|
28
35
|
webhookSchema.createWebhook.Body,
|
|
29
|
-
z.object({
|
|
36
|
+
z.object({
|
|
37
|
+
chainId: webhookSchema.Subscription.shape.chainId,
|
|
38
|
+
environment: z.optional(Environment),
|
|
39
|
+
}),
|
|
40
|
+
)
|
|
41
|
+
.check(
|
|
42
|
+
z.refine(
|
|
43
|
+
(body) => body.eventType !== 'funding:deposit.updated' || body.environment !== undefined,
|
|
44
|
+
{ error: '`environment` is required for funding deposit subscriptions.' },
|
|
45
|
+
),
|
|
46
|
+
z.describe('Details for creating an organization webhook subscription.'),
|
|
30
47
|
)
|
|
31
|
-
.check(z.describe('Details for creating an organization webhook subscription.'))
|
|
32
48
|
|
|
33
49
|
const secret = webhookSchema.createWebhook.Response.shape.secret
|
|
34
50
|
const urlDestination = z.object({
|
|
@@ -181,6 +197,7 @@ export function webhooks(options: webhooks.Options = {}) {
|
|
|
181
197
|
codes: [
|
|
182
198
|
'body_invalid',
|
|
183
199
|
'chain_id_unsupported',
|
|
200
|
+
'event_type_unsupported',
|
|
184
201
|
'filters_invalid',
|
|
185
202
|
'param_invalid',
|
|
186
203
|
'url_invalid',
|
|
@@ -213,6 +230,15 @@ export function webhooks(options: webhooks.Options = {}) {
|
|
|
213
230
|
if (!options.webhook) return notEnabled(c)
|
|
214
231
|
|
|
215
232
|
const body = c.req.valid('json')
|
|
233
|
+
if (
|
|
234
|
+
body.eventType === 'funding:deposit.updated' &&
|
|
235
|
+
!options.webhook.applicationEventTypes?.includes(body.eventType)
|
|
236
|
+
)
|
|
237
|
+
return Response.error(c, {
|
|
238
|
+
code: 'event_type_unsupported',
|
|
239
|
+
message: 'Webhook event type is not available in this deployment',
|
|
240
|
+
status: 400,
|
|
241
|
+
})
|
|
216
242
|
const chainId = body.chainId
|
|
217
243
|
const supportedChainIds = new Set(options.webhook.supportedChainIds)
|
|
218
244
|
if (!supportedChainIds.has(chainId))
|
|
@@ -225,20 +251,26 @@ export function webhooks(options: webhooks.Options = {}) {
|
|
|
225
251
|
const owner_ = owner(Auth.org(c).id)
|
|
226
252
|
if ((await Webhooks.countSubscriptions(db, owner_)) >= maxPerOwner)
|
|
227
253
|
throw new Webhooks.LimitExceededError(maxPerOwner)
|
|
228
|
-
const startBlockNumber =
|
|
254
|
+
const startBlockNumber =
|
|
255
|
+
body.eventType === 'funding:deposit.updated'
|
|
256
|
+
? undefined
|
|
257
|
+
: Number(await c.get('getClient')(chainId).getBlockNumber())
|
|
229
258
|
const subscription = await Webhooks.createSubscription(
|
|
230
259
|
db,
|
|
231
260
|
{
|
|
232
261
|
chainId,
|
|
233
262
|
...(body.context === undefined ? {} : { context: body.context }),
|
|
234
263
|
destination: body.destination,
|
|
264
|
+
...(body.eventType === 'funding:deposit.updated' && body.environment !== undefined
|
|
265
|
+
? { environment: body.environment }
|
|
266
|
+
: {}),
|
|
235
267
|
eventType: body.eventType,
|
|
236
268
|
filters: body.filters,
|
|
237
269
|
owner: owner_,
|
|
238
270
|
},
|
|
239
271
|
{
|
|
240
272
|
maxPerOwner,
|
|
241
|
-
startBlockNumber,
|
|
273
|
+
...(startBlockNumber === undefined ? {} : { startBlockNumber }),
|
|
242
274
|
},
|
|
243
275
|
)
|
|
244
276
|
return c.json(
|
|
@@ -609,6 +641,10 @@ function publicSubscription(subscription: Webhooks.Subscription) {
|
|
|
609
641
|
...(subscription.context === undefined ? {} : { context: subscription.context }),
|
|
610
642
|
createdAt: subscription.createdAt,
|
|
611
643
|
destination,
|
|
644
|
+
...(subscription.eventType === 'funding:deposit.updated' &&
|
|
645
|
+
subscription.environment !== undefined
|
|
646
|
+
? { environment: subscription.environment }
|
|
647
|
+
: {}),
|
|
612
648
|
eventType: subscription.eventType,
|
|
613
649
|
...(subscription.expiresAt === undefined ? {} : { expiresAt: subscription.expiresAt }),
|
|
614
650
|
failureCount: subscription.failureCount,
|
package/src/db/Db.test.ts
CHANGED
|
@@ -71,11 +71,7 @@ describe('postgres', () => {
|
|
|
71
71
|
await Organizations.create(tx, { id: 'org_qualified', name: 'Qualified' })
|
|
72
72
|
})
|
|
73
73
|
expect((await Organizations.get(db, 'org_qualified'))?.name).toBe('Qualified')
|
|
74
|
-
expect(
|
|
75
|
-
await WebhookSubscriptions.compareAndSetCursors(db, {
|
|
76
|
-
cursors: [{ cursor: 'next', expectedCursor: null, subscriptionId: 'wh_missing' }],
|
|
77
|
-
}),
|
|
78
|
-
).toEqual(new Set())
|
|
74
|
+
expect(await WebhookSubscriptions.getCursor(db, 'wh_missing')).toBeNull()
|
|
79
75
|
await db.close()
|
|
80
76
|
})
|
|
81
77
|
|
package/src/db/Db.ts
CHANGED
|
@@ -1362,6 +1362,65 @@ export const migrations: readonly Migration[] = [
|
|
|
1362
1362
|
sql`DELETE FROM webhook_queue_events WHERE status = 'pending' AND attempting_at IS NULL AND expires_at <= to_char(now() AT TIME ZONE 'utc', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`,
|
|
1363
1363
|
),
|
|
1364
1364
|
},
|
|
1365
|
+
{
|
|
1366
|
+
name: '0123_funding_deposit_addresses_owner_list_index',
|
|
1367
|
+
up: (db) =>
|
|
1368
|
+
db.schema
|
|
1369
|
+
.createIndex('funding_deposit_addresses_owner_list_idx')
|
|
1370
|
+
.ifNotExists()
|
|
1371
|
+
.on('fundingDepositAddresses')
|
|
1372
|
+
.columns(['orgId', 'environment', 'createdAt', 'id']),
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
name: '0124_funding_deposit_addresses_project_list_index',
|
|
1376
|
+
up: (db) =>
|
|
1377
|
+
db.schema
|
|
1378
|
+
.createIndex('funding_deposit_addresses_project_list_idx')
|
|
1379
|
+
.ifNotExists()
|
|
1380
|
+
.on('fundingDepositAddresses')
|
|
1381
|
+
.columns(['orgId', 'environment', 'projectId', 'createdAt', 'id']),
|
|
1382
|
+
},
|
|
1383
|
+
{
|
|
1384
|
+
name: '0125_funding_deposits_owner_list_index',
|
|
1385
|
+
up: (db) =>
|
|
1386
|
+
db.schema
|
|
1387
|
+
.createIndex('funding_deposits_owner_list_idx')
|
|
1388
|
+
.ifNotExists()
|
|
1389
|
+
.on('fundingDeposits')
|
|
1390
|
+
.columns(['orgId', 'environment', 'createdAt', 'id']),
|
|
1391
|
+
},
|
|
1392
|
+
{
|
|
1393
|
+
name: '0126_funding_deposits_project_list_index',
|
|
1394
|
+
up: (db) =>
|
|
1395
|
+
db.schema
|
|
1396
|
+
.createIndex('funding_deposits_project_list_idx')
|
|
1397
|
+
.ifNotExists()
|
|
1398
|
+
.on('fundingDeposits')
|
|
1399
|
+
.columns(['orgId', 'environment', 'projectId', 'createdAt', 'id']),
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
name: '0127_webhook_subscriptions_environment',
|
|
1403
|
+
up: (db) => raw(db, sql`ALTER TABLE webhook_subscriptions ADD COLUMN IF NOT EXISTS environment text`), // prettier-ignore
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
name: '0128_webhook_subscriptions_project',
|
|
1407
|
+
up: (db) => raw(db, sql`ALTER TABLE webhook_subscriptions ADD COLUMN IF NOT EXISTS project_id text`), // prettier-ignore
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
name: '0129_webhook_subscriptions_funding_deposit_index',
|
|
1411
|
+
up: (db) =>
|
|
1412
|
+
db.schema
|
|
1413
|
+
.createIndex('webhook_subscriptions_funding_deposit_idx')
|
|
1414
|
+
.ifNotExists()
|
|
1415
|
+
.on('webhookSubscriptions')
|
|
1416
|
+
.columns(['eventType', 'ownerType', 'ownerId', 'environment', 'projectId', 'chainId'])
|
|
1417
|
+
.where(sql.ref('status'), '=', sql.lit('active')),
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
name: '0130_webhook_queue_events_observed_at',
|
|
1421
|
+
up: (db) =>
|
|
1422
|
+
raw(db, sql`ALTER TABLE webhook_queue_events ADD COLUMN IF NOT EXISTS observed_at text`),
|
|
1423
|
+
},
|
|
1365
1424
|
]
|
|
1366
1425
|
|
|
1367
1426
|
/**
|
package/src/db/Schema.ts
CHANGED
|
@@ -792,6 +792,9 @@ export const WebhookSubscription = z.object({
|
|
|
792
792
|
destination: z
|
|
793
793
|
.custom<Webhooks.Destination>()
|
|
794
794
|
.check(z.describe('Delivery destination (`jsonb`).')),
|
|
795
|
+
environment: z
|
|
796
|
+
.nullable(z.enum(['production', 'sandbox']))
|
|
797
|
+
.check(z.describe('API-key environment for private resource events, or null.')),
|
|
795
798
|
eventType: z
|
|
796
799
|
.custom<Webhooks.EventType>()
|
|
797
800
|
.check(z.describe('Event type the subscription listens to.')),
|
|
@@ -820,6 +823,9 @@ export const WebhookSubscription = z.object({
|
|
|
820
823
|
pollerCursor: z
|
|
821
824
|
.nullable(z.string())
|
|
822
825
|
.check(z.describe('Poller keyset cursor, or null before the first tick.')),
|
|
826
|
+
projectId: z
|
|
827
|
+
.nullable(z.string())
|
|
828
|
+
.check(z.describe('API-key project attribution for private resource events, or null.')),
|
|
823
829
|
secret: z.string().check(z.describe('HMAC signing secret.')),
|
|
824
830
|
status: z.custom<Webhooks.Status>().check(z.describe('Lifecycle status.')),
|
|
825
831
|
updatedAt: z
|
|
@@ -896,6 +902,13 @@ export const WebhookQueueEvent = z.object({
|
|
|
896
902
|
nextAttemptAt: z
|
|
897
903
|
.nullable(z.string())
|
|
898
904
|
.check(z.describe('ISO retry due time, or null when due immediately.')),
|
|
905
|
+
observedAt: z
|
|
906
|
+
.nullable(z.string())
|
|
907
|
+
.check(
|
|
908
|
+
z.describe(
|
|
909
|
+
'ISO timestamp the coordinator observed the head owing this event, or null on replays.',
|
|
910
|
+
),
|
|
911
|
+
),
|
|
899
912
|
status: z
|
|
900
913
|
.enum(['failed', 'pending', 'skipped', 'succeeded'])
|
|
901
914
|
.check(z.describe('Delivery-obligation lifecycle status.')),
|
|
@@ -578,3 +578,31 @@ describe('renewPoll', () => {
|
|
|
578
578
|
`)
|
|
579
579
|
})
|
|
580
580
|
})
|
|
581
|
+
|
|
582
|
+
describe('list', () => {
|
|
583
|
+
test('pages organization addresses and narrows project-attributed owners', async () => {
|
|
584
|
+
const listOwner = { environment: owner.environment, orgId: 'org_address_list' } as const
|
|
585
|
+
const first = await seed({ orgId: listOwner.orgId })
|
|
586
|
+
const project = await seed({ orgId: listOwner.orgId, projectId: 'prj_address_list' })
|
|
587
|
+
const sibling = await seed({ orgId: listOwner.orgId, projectId: 'prj_address_sibling' })
|
|
588
|
+
await seed({ orgId: 'org_address_list_foreign' })
|
|
589
|
+
await seed({ environment: 'sandbox', orgId: listOwner.orgId })
|
|
590
|
+
|
|
591
|
+
const page = await FundingDepositAddresses.list(db, { limit: 2, owner: listOwner })
|
|
592
|
+
expect(page.map((record) => record.id)).toEqual([sibling.id, project.id])
|
|
593
|
+
const rest = await FundingDepositAddresses.list(db, {
|
|
594
|
+
cursor: { createdAt: project.createdAt, id: project.id },
|
|
595
|
+
limit: 2,
|
|
596
|
+
owner: listOwner,
|
|
597
|
+
})
|
|
598
|
+
expect(rest.map((record) => record.id)).toEqual([first.id])
|
|
599
|
+
expect(
|
|
600
|
+
(
|
|
601
|
+
await FundingDepositAddresses.list(db, {
|
|
602
|
+
limit: 10,
|
|
603
|
+
owner: { ...listOwner, projectId: 'prj_address_list' },
|
|
604
|
+
})
|
|
605
|
+
).map((record) => record.id),
|
|
606
|
+
).toEqual([project.id])
|
|
607
|
+
})
|
|
608
|
+
})
|
|
@@ -148,6 +148,45 @@ export function getOwned(db: Db.Db, owner: Owner, id: string): Promise<Record |
|
|
|
148
148
|
return query.executeTakeFirst()
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/** Lists owner-visible funding deposit addresses newest-first. */
|
|
152
|
+
export function list(db: Db.Db, options: list.Options): Promise<Record[]> {
|
|
153
|
+
const { cursor, limit, owner } = options
|
|
154
|
+
let query = db.kysely
|
|
155
|
+
.selectFrom('funding_deposit_addresses')
|
|
156
|
+
.selectAll()
|
|
157
|
+
.where('environment', '=', owner.environment)
|
|
158
|
+
.where('orgId', '=', owner.orgId)
|
|
159
|
+
if (owner.projectId !== undefined) query = query.where('projectId', '=', owner.projectId)
|
|
160
|
+
if (cursor)
|
|
161
|
+
query = query.where((eb) =>
|
|
162
|
+
eb.or([
|
|
163
|
+
eb('createdAt', '<', cursor.createdAt),
|
|
164
|
+
eb.and([eb('createdAt', '=', cursor.createdAt), eb('id', '<', cursor.id)]),
|
|
165
|
+
]),
|
|
166
|
+
)
|
|
167
|
+
return query.orderBy('createdAt', 'desc').orderBy('id', 'desc').limit(limit).execute()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export declare namespace list {
|
|
171
|
+
/** Ownership and paging options for funding deposit addresses. */
|
|
172
|
+
type Options = {
|
|
173
|
+
/** Exclusive lower bound from the previous page. */
|
|
174
|
+
cursor?: Cursor | undefined
|
|
175
|
+
/** Maximum rows to return. */
|
|
176
|
+
limit: number
|
|
177
|
+
/** Ownership filter derived from the authenticated API key. */
|
|
178
|
+
owner: Owner
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Cursor fields for the last address returned by the previous page. */
|
|
182
|
+
type Cursor = {
|
|
183
|
+
/** Deposit address creation time. */
|
|
184
|
+
createdAt: string
|
|
185
|
+
/** Deposit address id, used as a deterministic tie-breaker. */
|
|
186
|
+
id: string
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
151
190
|
/** Gets the reusable address that matches one owner and route identity. */
|
|
152
191
|
export function getMatching(db: Db.Db, match: Match): Promise<Record | undefined> {
|
|
153
192
|
let query = db.kysely
|
|
@@ -498,3 +498,85 @@ describe('toPublic', () => {
|
|
|
498
498
|
})
|
|
499
499
|
})
|
|
500
500
|
})
|
|
501
|
+
|
|
502
|
+
describe('list', () => {
|
|
503
|
+
test('pages organization deposits and narrows project-attributed owners', async () => {
|
|
504
|
+
const listOwner = { environment: owner.environment, orgId: 'org_deposit_list' } as const
|
|
505
|
+
const firstAddress = await seedAddress({ orgId: listOwner.orgId })
|
|
506
|
+
const projectAddress = await seedAddress({
|
|
507
|
+
orgId: listOwner.orgId,
|
|
508
|
+
projectId: 'prj_deposit_list',
|
|
509
|
+
})
|
|
510
|
+
const siblingAddress = await seedAddress({
|
|
511
|
+
orgId: listOwner.orgId,
|
|
512
|
+
projectId: 'prj_deposit_sibling',
|
|
513
|
+
})
|
|
514
|
+
const first = await seedDeposit(firstAddress)
|
|
515
|
+
const project = await seedDeposit(projectAddress)
|
|
516
|
+
const sibling = await seedDeposit(siblingAddress)
|
|
517
|
+
const foreignAddress = await seedAddress({ orgId: 'org_deposit_list_foreign' })
|
|
518
|
+
await seedDeposit(foreignAddress)
|
|
519
|
+
const sandboxAddress = await seedAddress({
|
|
520
|
+
environment: 'sandbox',
|
|
521
|
+
orgId: listOwner.orgId,
|
|
522
|
+
})
|
|
523
|
+
await seedDeposit(sandboxAddress)
|
|
524
|
+
|
|
525
|
+
const page = await FundingDeposits.list(db, { limit: 2, owner: listOwner })
|
|
526
|
+
expect(page.map((record) => record.id)).toEqual([sibling.id, project.id])
|
|
527
|
+
const rest = await FundingDeposits.list(db, {
|
|
528
|
+
cursor: { createdAt: project.createdAt, id: project.id },
|
|
529
|
+
limit: 2,
|
|
530
|
+
owner: listOwner,
|
|
531
|
+
})
|
|
532
|
+
expect(rest.map((record) => record.id)).toEqual([first.id])
|
|
533
|
+
expect(
|
|
534
|
+
(
|
|
535
|
+
await FundingDeposits.list(db, {
|
|
536
|
+
limit: 10,
|
|
537
|
+
owner: { ...listOwner, projectId: 'prj_deposit_list' },
|
|
538
|
+
})
|
|
539
|
+
).map((record) => record.id),
|
|
540
|
+
).toEqual([project.id])
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
test('AND-combines filters and counts through the same ownership scope', async () => {
|
|
544
|
+
const listOwner = { environment: owner.environment, orgId: 'org_deposit_filters' } as const
|
|
545
|
+
const recipient = `0x${'ab'.repeat(20)}`
|
|
546
|
+
const address = await seedAddress({
|
|
547
|
+
orgId: listOwner.orgId,
|
|
548
|
+
snapshot: TestFunding.depositAddressSnapshot({
|
|
549
|
+
provider: { id: 'relay_filters', name: 'Relay' },
|
|
550
|
+
recipient,
|
|
551
|
+
}),
|
|
552
|
+
})
|
|
553
|
+
const match = await seedDeposit(address)
|
|
554
|
+
const unrelatedAddress = await seedAddress({ orgId: listOwner.orgId })
|
|
555
|
+
await seedDeposit(unrelatedAddress)
|
|
556
|
+
const filters = {
|
|
557
|
+
depositAddress: address.address,
|
|
558
|
+
destinationTokenKeys: [address.destinationTokenKey],
|
|
559
|
+
providerId: address.providerId,
|
|
560
|
+
recipient: recipient.toLowerCase(),
|
|
561
|
+
sourceChainIds: [address.sourceChainId],
|
|
562
|
+
sourceTokenKeys: [address.sourceTokenKey],
|
|
563
|
+
status: 'detected',
|
|
564
|
+
} as const
|
|
565
|
+
|
|
566
|
+
expect(
|
|
567
|
+
(await FundingDeposits.list(db, { ...filters, limit: 10, owner: listOwner })).map(
|
|
568
|
+
(record) => record.id,
|
|
569
|
+
),
|
|
570
|
+
).toEqual([match.id])
|
|
571
|
+
expect(await FundingDeposits.count(db, { ...filters, cap: 10, owner: listOwner })).toBe(1)
|
|
572
|
+
expect(await FundingDeposits.count(db, { ...filters, cap: 0, owner: listOwner })).toBe(1)
|
|
573
|
+
expect(
|
|
574
|
+
await FundingDeposits.count(db, {
|
|
575
|
+
...filters,
|
|
576
|
+
destinationTokenKeys: [],
|
|
577
|
+
cap: 10,
|
|
578
|
+
owner: listOwner,
|
|
579
|
+
}),
|
|
580
|
+
).toBe(0)
|
|
581
|
+
})
|
|
582
|
+
})
|
|
@@ -27,6 +27,24 @@ export type Owner = {
|
|
|
27
27
|
projectId?: string | undefined
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** Optional filters shared by funding deposit list and count queries. */
|
|
31
|
+
export type Filters = {
|
|
32
|
+
/** Reusable source-chain address that received the funds. */
|
|
33
|
+
depositAddress?: string | undefined
|
|
34
|
+
/** Canonical destination token keys accepted by the filter. */
|
|
35
|
+
destinationTokenKeys?: readonly string[] | undefined
|
|
36
|
+
/** Funding provider id. */
|
|
37
|
+
providerId?: string | undefined
|
|
38
|
+
/** Tempo account that receives completed deposits. */
|
|
39
|
+
recipient?: string | undefined
|
|
40
|
+
/** Canonical source chain ids accepted by the filter. */
|
|
41
|
+
sourceChainIds?: readonly string[] | undefined
|
|
42
|
+
/** Canonical source token keys accepted by the filter. */
|
|
43
|
+
sourceTokenKeys?: readonly string[] | undefined
|
|
44
|
+
/** Deposit lifecycle status. */
|
|
45
|
+
status?: Record['status'] | undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
30
48
|
/** Inserts one provider-observed source transfer as a funding deposit. */
|
|
31
49
|
export function insert(db: Db.Db, record: Record): Promise<Record> {
|
|
32
50
|
return db.kysely
|
|
@@ -234,6 +252,118 @@ export function getOwned(db: Db.Db, owner: Owner, id: string): Promise<Record |
|
|
|
234
252
|
return query.executeTakeFirst()
|
|
235
253
|
}
|
|
236
254
|
|
|
255
|
+
/** Lists owner-visible funding deposits newest-first. */
|
|
256
|
+
export function list(db: Db.Db, options: list.Options): Promise<Record[]> {
|
|
257
|
+
const { cursor, limit } = options
|
|
258
|
+
let builder = query(db, options).selectAll('funding_deposits')
|
|
259
|
+
if (cursor)
|
|
260
|
+
builder = builder.where((eb) =>
|
|
261
|
+
eb.or([
|
|
262
|
+
eb('funding_deposits.createdAt', '<', cursor.createdAt),
|
|
263
|
+
eb.and([
|
|
264
|
+
eb('funding_deposits.createdAt', '=', cursor.createdAt),
|
|
265
|
+
eb('funding_deposits.id', '<', cursor.id),
|
|
266
|
+
]),
|
|
267
|
+
]),
|
|
268
|
+
)
|
|
269
|
+
return builder
|
|
270
|
+
.orderBy('funding_deposits.createdAt', 'desc')
|
|
271
|
+
.orderBy('funding_deposits.id', 'desc')
|
|
272
|
+
.limit(limit)
|
|
273
|
+
.execute()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export declare namespace list {
|
|
277
|
+
/** Ownership and paging options for funding deposits. */
|
|
278
|
+
type Options = Filters & {
|
|
279
|
+
/** Exclusive lower bound from the previous page. */
|
|
280
|
+
cursor?: listByAddress.Cursor | undefined
|
|
281
|
+
/** Maximum rows to return. */
|
|
282
|
+
limit: number
|
|
283
|
+
/** Ownership filter derived from the authenticated API key. */
|
|
284
|
+
owner: Owner
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Counts owner-visible funding deposits through the list's shared filters. */
|
|
289
|
+
export async function count(db: Db.Db, options: count.Options): Promise<number> {
|
|
290
|
+
const rows = query(db, options)
|
|
291
|
+
.select('funding_deposits.id')
|
|
292
|
+
.limit(options.cap + 1)
|
|
293
|
+
.as('rows')
|
|
294
|
+
const result = await db.kysely
|
|
295
|
+
.selectFrom(rows)
|
|
296
|
+
.select(sql<number>`count(*)::int`.as('count'))
|
|
297
|
+
.executeTakeFirstOrThrow()
|
|
298
|
+
return result.count
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export declare namespace count {
|
|
302
|
+
/** Ownership, filter, and cap options for counting funding deposits. */
|
|
303
|
+
type Options = Filters & {
|
|
304
|
+
/** Maximum exact count before returning the cap plus one. */
|
|
305
|
+
cap: number
|
|
306
|
+
/** Ownership filter derived from the authenticated API key. */
|
|
307
|
+
owner: Owner
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function query(db: Db.Db, options: query.Options) {
|
|
312
|
+
const owner = options.owner
|
|
313
|
+
let builder = db.kysely
|
|
314
|
+
.selectFrom('funding_deposits')
|
|
315
|
+
.innerJoin(
|
|
316
|
+
'funding_deposit_addresses',
|
|
317
|
+
'funding_deposit_addresses.id',
|
|
318
|
+
'funding_deposits.depositAddressId',
|
|
319
|
+
)
|
|
320
|
+
.where('funding_deposit_addresses.environment', '=', owner.environment)
|
|
321
|
+
.where('funding_deposit_addresses.orgId', '=', owner.orgId)
|
|
322
|
+
.where('funding_deposits.environment', '=', owner.environment)
|
|
323
|
+
.where('funding_deposits.orgId', '=', owner.orgId)
|
|
324
|
+
if (owner.projectId !== undefined)
|
|
325
|
+
builder = builder
|
|
326
|
+
.where('funding_deposit_addresses.projectId', '=', owner.projectId)
|
|
327
|
+
.where('funding_deposits.projectId', '=', owner.projectId)
|
|
328
|
+
if (options.depositAddress !== undefined) {
|
|
329
|
+
const address = /^0x[0-9a-fA-F]{40}$/.test(options.depositAddress)
|
|
330
|
+
? options.depositAddress.toLowerCase()
|
|
331
|
+
: options.depositAddress
|
|
332
|
+
builder = builder.where('funding_deposit_addresses.address', '=', address)
|
|
333
|
+
}
|
|
334
|
+
if (options.destinationTokenKeys !== undefined)
|
|
335
|
+
builder = options.destinationTokenKeys.length
|
|
336
|
+
? builder.where(
|
|
337
|
+
'funding_deposit_addresses.destinationTokenKey',
|
|
338
|
+
'in',
|
|
339
|
+
options.destinationTokenKeys,
|
|
340
|
+
)
|
|
341
|
+
: builder.where(sql<boolean>`false`)
|
|
342
|
+
if (options.providerId !== undefined)
|
|
343
|
+
builder = builder.where('funding_deposit_addresses.providerId', '=', options.providerId)
|
|
344
|
+
if (options.recipient !== undefined)
|
|
345
|
+
builder = builder.where(
|
|
346
|
+
sql<boolean>`lower(funding_deposit_addresses.recipient) = ${options.recipient.toLowerCase()}`,
|
|
347
|
+
)
|
|
348
|
+
if (options.sourceChainIds !== undefined)
|
|
349
|
+
builder = options.sourceChainIds.length
|
|
350
|
+
? builder.where('funding_deposit_addresses.sourceChainId', 'in', options.sourceChainIds)
|
|
351
|
+
: builder.where(sql<boolean>`false`)
|
|
352
|
+
if (options.sourceTokenKeys !== undefined)
|
|
353
|
+
builder = options.sourceTokenKeys.length
|
|
354
|
+
? builder.where('funding_deposit_addresses.sourceTokenKey', 'in', options.sourceTokenKeys)
|
|
355
|
+
: builder.where(sql<boolean>`false`)
|
|
356
|
+
if (options.status !== undefined)
|
|
357
|
+
builder = builder.where('funding_deposits.status', '=', options.status)
|
|
358
|
+
return builder
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
declare namespace query {
|
|
362
|
+
type Options = Filters & {
|
|
363
|
+
owner: Owner
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
237
367
|
/** Lists one owner-visible address's deposits newest-first. */
|
|
238
368
|
export function listByAddress(
|
|
239
369
|
db: Db.Db,
|
|
@@ -363,8 +363,11 @@ describe('deleteOrganization', () => {
|
|
|
363
363
|
expect(await Organizations.deleteOrganization(db, 'org_1')).toBe(true)
|
|
364
364
|
|
|
365
365
|
expect(await Webhooks.listSubscriptions(db, deletedOrg)).toEqual([])
|
|
366
|
-
const active = await Webhooks.
|
|
367
|
-
|
|
366
|
+
const active = await Webhooks.listActiveForChain(db, {
|
|
367
|
+
chainId: 4217,
|
|
368
|
+
eventTypes: ['token:transfer'],
|
|
369
|
+
})
|
|
370
|
+
expect(active.map(({ subscription }) => subscription.id).sort()).toEqual(
|
|
368
371
|
[keptApiKey.id, keptMppSubscription.id].sort(),
|
|
369
372
|
)
|
|
370
373
|
await db.close()
|
|
@@ -7,7 +7,7 @@ import type * as db_Schema from '../Schema.js'
|
|
|
7
7
|
/** Columns of the `webhook_queue_events` table, derived from `Schema.WebhookQueueEvent`. */
|
|
8
8
|
export type Table = Omit<
|
|
9
9
|
db_Schema.WebhookQueueEvent,
|
|
10
|
-
'attemptCount' | 'attemptingAt' | 'nextAttemptAt' | 'status'
|
|
10
|
+
'attemptCount' | 'attemptingAt' | 'nextAttemptAt' | 'observedAt' | 'status'
|
|
11
11
|
> & {
|
|
12
12
|
/** Delivery attempts claimed so far; database default 0. */
|
|
13
13
|
attemptCount: Generated<number>
|
|
@@ -15,6 +15,8 @@ export type Table = Omit<
|
|
|
15
15
|
attemptingAt: Generated<string | null>
|
|
16
16
|
/** ISO retry due time; database default null (due immediately). */
|
|
17
17
|
nextAttemptAt: Generated<string | null>
|
|
18
|
+
/** ISO head-observation stamp; database default null (replays carry none). */
|
|
19
|
+
observedAt: Generated<string | null>
|
|
18
20
|
/** Delivery-obligation lifecycle status; database default `pending`. */
|
|
19
21
|
status: Generated<db_Schema.WebhookQueueEvent['status']>
|
|
20
22
|
}
|
|
@@ -65,6 +67,10 @@ export async function ensure(db: Db.Db, rows: readonly Insertable<Table>[]): Pro
|
|
|
65
67
|
envelope: eb.ref('excluded.envelope'),
|
|
66
68
|
expiresAt: eb.ref('excluded.expiresAt'),
|
|
67
69
|
nextAttemptAt: eb.ref('excluded.nextAttemptAt'),
|
|
70
|
+
// Refreshed with the rest: a replacement keeping the replaced row's
|
|
71
|
+
// stamp would measure observation-to-attempt from an observation
|
|
72
|
+
// that belongs to a delivery made days earlier.
|
|
73
|
+
observedAt: eb.ref('excluded.observedAt'),
|
|
68
74
|
status: 'pending',
|
|
69
75
|
}))
|
|
70
76
|
.where((eb) =>
|