tapimo 0.9.0 → 0.10.1
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/ApiKey.d.ts +2 -1
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKeys.d.ts +4 -2
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +26 -1
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +4292 -3708
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +1 -0
- package/dist/App.js.map +1 -1
- package/dist/Webhooks.d.ts +21 -20
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +66 -23
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +91 -84
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +26 -17
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +6 -0
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +4 -2
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +168 -168
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +69 -69
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +26 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/apps/Zones.d.ts +4280 -84
- package/dist/apps/Zones.d.ts.map +1 -1
- package/dist/apps/Zones.js +30 -10
- package/dist/apps/Zones.js.map +1 -1
- package/dist/apps/data/App.d.ts +5799 -4929
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +4 -5
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +19 -4
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +3391 -492
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +372 -29
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +853 -339
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +10 -10
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +145 -83
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +123 -123
- package/dist/apps/data/routes/coingecko.d.ts +169 -169
- package/dist/apps/data/routes/earn.d.ts +455 -1
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +369 -45
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +114 -118
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +24 -24
- package/dist/apps/data/routes/indexer.d.ts +35 -34
- package/dist/apps/data/routes/indexer.d.ts.map +1 -1
- package/dist/apps/data/routes/indexer.js +3 -2
- package/dist/apps/data/routes/indexer.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +68 -47
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/rpc.d.ts.map +1 -1
- package/dist/apps/data/routes/rpc.js +5 -1
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/tokens.d.ts +461 -459
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +313 -305
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +25 -23
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +108 -86
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +618 -105
- 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/valuation.d.ts.map +1 -1
- package/dist/apps/data/routes/valuation.js +2 -2
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +519 -868
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +642 -353
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +31 -31
- package/dist/apps/data/routes/zones.d.ts.map +1 -1
- package/dist/apps/data/routes/zones.js +4 -2
- package/dist/apps/data/routes/zones.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/providers.d.ts +13 -13
- package/dist/apps/funding/routes/quotes.d.ts +89 -89
- package/dist/apps/funding/routes/transfers.d.ts +36 -36
- package/dist/apps/funding/routes/transfers.js +1 -1
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- package/dist/apps/management/App.d.ts +11 -309
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +74 -74
- package/dist/apps/management/routes/billing.js +1 -1
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +35 -35
- package/dist/apps/management/routes/faucet.js +2 -2
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +134 -134
- package/dist/apps/management/routes/invite-links.d.ts +80 -80
- package/dist/apps/management/routes/members.d.ts +69 -69
- package/dist/apps/management/routes/orgs.d.ts +98 -98
- package/dist/apps/management/routes/projects.d.ts +60 -60
- package/dist/apps/management/routes/usage.d.ts +72 -72
- package/dist/apps/management/routes/verified-token-requests.d.ts +83 -83
- package/dist/apps/management/routes/webhooks.d.ts +203 -618
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +4 -1
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/mpp/App.d.ts +56 -44
- package/dist/apps/mpp/App.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.d.ts +3 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.js +3 -0
- package/dist/apps/mpp/Mpp.js.map +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Adapter.js +9 -2
- package/dist/apps/mpp/internal/Adapter.js.map +1 -1
- package/dist/db/Db.d.ts +4 -2
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +75 -15
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +19 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +24 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +4 -1
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/webhookQueueEvents.d.ts +122 -0
- package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -0
- package/dist/db/tables/webhookQueueEvents.js +160 -0
- package/dist/db/tables/webhookQueueEvents.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +58 -3
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +86 -3
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +3 -0
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +6 -4
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +139 -3
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +144 -4
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/RateLimit.d.ts +4 -6
- package/dist/internal/RateLimit.d.ts.map +1 -1
- package/dist/internal/RateLimit.js +15 -14
- package/dist/internal/RateLimit.js.map +1 -1
- package/dist/internal/Schema.js +1 -1
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Store.d.ts +2 -1
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +57 -18
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Tidx.d.ts +1 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +1 -3
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Value.d.ts +2 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +25 -0
- package/dist/internal/Value.js.map +1 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +2 -1
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/WebhookDestination.d.ts +2 -0
- package/dist/internal/WebhookDestination.d.ts.map +1 -1
- package/dist/internal/WebhookDestination.js +11 -0
- package/dist/internal/WebhookDestination.js.map +1 -1
- package/dist/internal/WebhookTransfer.d.ts +75 -0
- package/dist/internal/WebhookTransfer.d.ts.map +1 -0
- package/dist/internal/WebhookTransfer.js +299 -0
- package/dist/internal/WebhookTransfer.js.map +1 -0
- package/dist/internal/Webhooks.d.ts +176 -0
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +216 -20
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +4 -0
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +11 -7
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +128 -0
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +6 -4
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +71 -9
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +4 -4
- package/src/ApiKeys.test.ts +20 -0
- package/src/ApiKeys.ts +28 -1
- package/src/App.test.ts +10 -8
- package/src/App.ts +1 -0
- package/src/Client.test-d.ts +61 -5
- package/src/Webhooks.bench.ts +714 -0
- package/src/Webhooks.memory.bench.ts +250 -0
- package/src/Webhooks.test.ts +77 -6
- package/src/Webhooks.ts +85 -29
- package/src/admin/apps/api-keys.test.ts +9 -0
- package/src/admin/apps/api-keys.ts +15 -0
- package/src/admin/ui/src/lib/query-client.ts +3 -2
- package/src/admin/ui/src/routes/api-keys.tsx +31 -9
- package/src/admin/ui.generated.ts +69 -69
- package/src/analytics/Analytics.test.ts +8 -0
- package/src/analytics/Analytics.ts +26 -0
- package/src/apps/Zones.test.ts +23 -5
- package/src/apps/Zones.ts +31 -10
- package/src/apps/composition.test.ts +1 -0
- package/src/apps/data/App.test.ts +2 -2
- package/src/apps/data/FxOracle.test.ts +18 -0
- package/src/apps/data/FxOracle.ts +28 -5
- package/src/apps/data/routes/activities.bench.ts +41 -0
- package/src/apps/data/routes/activities.test.ts +122 -2
- package/src/apps/data/routes/activities.ts +435 -32
- package/src/apps/data/routes/balances.bench.ts +21 -0
- package/src/apps/data/routes/balances.test.ts +119 -10
- package/src/apps/data/routes/balances.ts +164 -97
- package/src/apps/data/routes/blocks.bench.ts +34 -0
- package/src/apps/data/routes/blocks.test.ts +12 -6
- package/src/apps/data/routes/coingecko.bench.ts +53 -0
- package/src/apps/data/routes/earn.bench.ts +71 -0
- package/src/apps/data/routes/earn.test.ts +197 -0
- package/src/apps/data/routes/earn.ts +487 -45
- package/src/apps/data/routes/exchanges.bench.ts +85 -0
- package/src/apps/data/routes/exchanges.test.ts +7 -6
- package/src/apps/data/routes/exchanges.ts +124 -117
- package/src/apps/data/routes/fee-amm.bench.ts +13 -0
- package/src/apps/data/routes/indexer.bench.ts +8 -0
- package/src/apps/data/routes/indexer.test.ts +2 -1
- package/src/apps/data/routes/indexer.ts +3 -2
- package/src/apps/data/routes/receipts.bench.ts +39 -0
- package/src/apps/data/routes/receipts.test.ts +24 -7
- package/src/apps/data/routes/receipts.ts +67 -50
- package/src/apps/data/routes/rpc.bench.ts +14 -0
- package/src/apps/data/routes/rpc.test.ts +27 -0
- package/src/apps/data/routes/rpc.ts +5 -1
- package/src/apps/data/routes/tokenlist.bench.ts +3 -0
- package/src/apps/data/routes/tokens.bench.ts +76 -0
- package/src/apps/data/routes/tokens.test.ts +51 -0
- package/src/apps/data/routes/transactions.bench.ts +40 -0
- package/src/apps/data/routes/transactions.test.ts +42 -0
- package/src/apps/data/routes/transactions.ts +44 -39
- package/src/apps/data/routes/transfers.bench.ts +28 -0
- package/src/apps/data/routes/transfers.test.ts +402 -24
- package/src/apps/data/routes/transfers.ts +708 -101
- package/src/apps/data/routes/valuation.bench.ts +14 -0
- package/src/apps/data/routes/valuation.test.ts +26 -0
- package/src/apps/data/routes/valuation.ts +6 -2
- package/src/apps/data/routes/verified-tokens.bench.ts +8 -0
- package/src/apps/data/routes/webhooks.bench.ts +26 -0
- package/src/apps/data/routes/webhooks.scan.test.ts +326 -0
- package/src/apps/data/routes/webhooks.test.ts +466 -135
- package/src/apps/data/routes/webhooks.ts +820 -429
- package/src/apps/data/routes/zones.bench.ts +32 -0
- package/src/apps/data/routes/zones.test.ts +1 -1
- package/src/apps/data/routes/zones.ts +7 -2
- package/src/apps/funding/routes/chains.test.ts +1 -1
- package/src/apps/funding/routes/deposit-addresses.test.ts +1 -1
- package/src/apps/funding/routes/deposit-addresses.ts +1 -1
- package/src/apps/funding/routes/providers.test.ts +1 -1
- package/src/apps/funding/routes/quotes.test.ts +2 -2
- package/src/apps/funding/routes/transfers.test.ts +3 -6
- package/src/apps/funding/routes/transfers.ts +1 -1
- package/src/apps/management/routes/billing.ts +1 -1
- package/src/apps/management/routes/faucet.ts +2 -2
- package/src/apps/management/routes/me.test.ts +31 -0
- package/src/apps/mcp.ts +4 -1
- package/src/apps/mpp/Mpp.test.ts +13 -1
- package/src/apps/mpp/Mpp.ts +8 -0
- package/src/apps/mpp/internal/Adapter.ts +10 -2
- package/src/db/Db.test.ts +40 -6
- package/src/db/Db.ts +114 -19
- package/src/db/Schema.ts +32 -0
- package/src/db/tables/sponsoredTransactions.ts +9 -2
- package/src/db/tables/webhookQueueEvents.ts +285 -0
- package/src/db/tables/webhookSubscriptions.ts +131 -4
- package/src/internal/Auth.test.ts +61 -50
- package/src/internal/Auth.ts +9 -4
- package/src/internal/EarnVaults.test.ts +2 -2
- package/src/internal/MetricSink.test.ts +575 -0
- package/src/internal/MetricSink.ts +325 -10
- package/src/internal/RateLimit.test.ts +23 -18
- package/src/internal/RateLimit.ts +23 -15
- package/src/internal/Schema.ts +1 -1
- package/src/internal/Store.test.ts +99 -13
- package/src/internal/Store.ts +52 -17
- package/src/internal/Tidx.test.ts +4 -4
- package/src/internal/Tidx.ts +1 -3
- package/src/internal/Value.test.ts +25 -0
- package/src/internal/Value.ts +29 -0
- package/src/internal/Viem.test.ts +8 -0
- package/src/internal/Viem.ts +2 -1
- package/src/internal/WebhookDestination.test.ts +20 -0
- package/src/internal/WebhookDestination.ts +10 -0
- package/src/internal/WebhookTransfer.test.ts +89 -0
- package/src/internal/WebhookTransfer.ts +430 -0
- package/src/internal/Webhooks.test.ts +616 -0
- package/src/internal/Webhooks.ts +412 -24
- package/src/internal/funding/Provider.test.ts +48 -8
- package/src/internal/funding/Provider.ts +12 -7
- package/src/internal/funding/Reconciliation.local.test.ts +3 -1
- package/src/internal/funding/Reconciliation.test.ts +1240 -0
- package/src/internal/funding/Reconciliation.ts +136 -0
- package/src/internal/funding/SourceObservation.test.ts +1 -0
- package/src/internal/funding/providers/index.test.ts +73 -3
- package/src/internal/funding/providers/relay.test.ts +2 -9
- package/src/internal/funding/providers/relay.ts +72 -13
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { type ExpressionBuilder, type Generated, type Insertable, type Selectable, sql } from 'kysely' // prettier-ignore
|
|
2
|
+
|
|
3
|
+
import type * as Webhooks from '../../internal/Webhooks.js'
|
|
4
|
+
import type * as Db from '../Db.js'
|
|
5
|
+
import type * as db_Schema from '../Schema.js'
|
|
6
|
+
|
|
7
|
+
/** Columns of the `webhook_queue_events` table, derived from `Schema.WebhookQueueEvent`. */
|
|
8
|
+
export type Table = Omit<
|
|
9
|
+
db_Schema.WebhookQueueEvent,
|
|
10
|
+
'attemptCount' | 'attemptingAt' | 'nextAttemptAt' | 'status'
|
|
11
|
+
> & {
|
|
12
|
+
/** Delivery attempts claimed so far; database default 0. */
|
|
13
|
+
attemptCount: Generated<number>
|
|
14
|
+
/** ISO timestamp of the current claim; database default null. */
|
|
15
|
+
attemptingAt: Generated<string | null>
|
|
16
|
+
/** ISO retry due time; database default null (due immediately). */
|
|
17
|
+
nextAttemptAt: Generated<string | null>
|
|
18
|
+
/** Delivery-obligation lifecycle status; database default `pending`. */
|
|
19
|
+
status: Generated<db_Schema.WebhookQueueEvent['status']>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A stored webhook Queue event. */
|
|
23
|
+
export type Record = Selectable<Table>
|
|
24
|
+
|
|
25
|
+
/** A terminal delivery-obligation outcome. */
|
|
26
|
+
export type TerminalStatus = Exclude<db_Schema.WebhookQueueEvent['status'], 'pending'>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reads one pending envelope. Terminal rows read as absent so no consumer
|
|
30
|
+
* can redeliver a completed obligation or disturb its dedupe row.
|
|
31
|
+
*/
|
|
32
|
+
export async function get(
|
|
33
|
+
db: Db.Db,
|
|
34
|
+
reference: Webhooks.QueueReference,
|
|
35
|
+
): Promise<Webhooks.QueueEvent | null> {
|
|
36
|
+
return (
|
|
37
|
+
(await db.kysely
|
|
38
|
+
.selectFrom('webhook_queue_events')
|
|
39
|
+
.selectAll()
|
|
40
|
+
.where('eventId', '=', reference.eventId)
|
|
41
|
+
.where('status', '=', 'pending')
|
|
42
|
+
.where('subscriptionId', '=', reference.subscriptionId)
|
|
43
|
+
.executeTakeFirst()) ?? null
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Stages envelopes as fresh obligations. Pending conflicts keep their first
|
|
49
|
+
* envelope and backoff position; terminal conflicts inside the dedupe window
|
|
50
|
+
* suppress the replay; terminal conflicts past it are replaced, so an expired
|
|
51
|
+
* dedupe row pruning has not yet reclaimed never blocks a replay.
|
|
52
|
+
*/
|
|
53
|
+
export async function ensure(db: Db.Db, rows: readonly Insertable<Table>[]): Promise<number> {
|
|
54
|
+
if (rows.length === 0) return 0
|
|
55
|
+
const result = await db.kysely
|
|
56
|
+
.insertInto('webhook_queue_events')
|
|
57
|
+
.values([...rows])
|
|
58
|
+
.onConflict((oc) =>
|
|
59
|
+
oc
|
|
60
|
+
.columns(['subscriptionId', 'eventId'])
|
|
61
|
+
.doUpdateSet((eb) => ({
|
|
62
|
+
attemptCount: 0,
|
|
63
|
+
attemptingAt: null,
|
|
64
|
+
createdAt: eb.ref('excluded.createdAt'),
|
|
65
|
+
envelope: eb.ref('excluded.envelope'),
|
|
66
|
+
expiresAt: eb.ref('excluded.expiresAt'),
|
|
67
|
+
nextAttemptAt: eb.ref('excluded.nextAttemptAt'),
|
|
68
|
+
status: 'pending',
|
|
69
|
+
}))
|
|
70
|
+
.where((eb) =>
|
|
71
|
+
eb.and([
|
|
72
|
+
eb('webhook_queue_events.status', '!=', 'pending'),
|
|
73
|
+
eb('webhook_queue_events.expiresAt', '<=', eb.ref('excluded.createdAt')),
|
|
74
|
+
]),
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
.executeTakeFirst()
|
|
78
|
+
// The command tag counts inserted rows plus replaced expired rows; live
|
|
79
|
+
// conflicts fall out of the conditional update and are not counted.
|
|
80
|
+
return Number(result.numInsertedOrUpdatedRows ?? 0n)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Due, unclaimed pending work: shared by {@link claim} and {@link due}. */
|
|
84
|
+
function whereClaimable(
|
|
85
|
+
eb: ExpressionBuilder<Db.Database, 'webhook_queue_events'>,
|
|
86
|
+
options: { now: string; staleBefore: string },
|
|
87
|
+
) {
|
|
88
|
+
return eb.and([
|
|
89
|
+
eb('status', '=', 'pending'),
|
|
90
|
+
eb.or([eb('nextAttemptAt', 'is', null), eb('nextAttemptAt', '<=', options.now)]),
|
|
91
|
+
eb.or([eb('attemptingAt', 'is', null), eb('attemptingAt', '<=', options.staleBefore)]),
|
|
92
|
+
])
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Claims one due pending obligation with a single atomic update. */
|
|
96
|
+
export async function claim(
|
|
97
|
+
db: Db.Db,
|
|
98
|
+
reference: Webhooks.QueueReference,
|
|
99
|
+
options: claim.Options,
|
|
100
|
+
): Promise<claim.Result> {
|
|
101
|
+
const claimed = await db.kysely
|
|
102
|
+
.updateTable('webhook_queue_events')
|
|
103
|
+
.set((eb) => ({ attemptCount: eb('attemptCount', '+', 1), attemptingAt: options.now }))
|
|
104
|
+
.where('subscriptionId', '=', reference.subscriptionId)
|
|
105
|
+
.where('eventId', '=', reference.eventId)
|
|
106
|
+
.where((eb) => whereClaimable(eb, options))
|
|
107
|
+
.returningAll()
|
|
108
|
+
.executeTakeFirst()
|
|
109
|
+
if (claimed) return { record: claimed, type: 'claimed' }
|
|
110
|
+
|
|
111
|
+
const existing = await db.kysely
|
|
112
|
+
.selectFrom('webhook_queue_events')
|
|
113
|
+
.select(['attemptingAt', 'nextAttemptAt', 'status'])
|
|
114
|
+
.where('subscriptionId', '=', reference.subscriptionId)
|
|
115
|
+
.where('eventId', '=', reference.eventId)
|
|
116
|
+
.executeTakeFirst()
|
|
117
|
+
if (!existing) return { type: 'missing' }
|
|
118
|
+
if (existing.status !== 'pending') return { status: existing.status, type: 'terminal' }
|
|
119
|
+
if (existing.attemptingAt !== null && existing.attemptingAt > options.staleBefore)
|
|
120
|
+
return { type: 'attempting' }
|
|
121
|
+
return { nextAttemptAt: existing.nextAttemptAt, type: 'scheduled' }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export declare namespace claim {
|
|
125
|
+
/** Claim clock bounds. */
|
|
126
|
+
type Options = {
|
|
127
|
+
/** Current ISO timestamp; becomes the claim marker. */
|
|
128
|
+
now: string
|
|
129
|
+
/** ISO cutoff below which an existing claim counts as stale. */
|
|
130
|
+
staleBefore: string
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Atomic claim outcome. */
|
|
134
|
+
type Result =
|
|
135
|
+
| { type: 'attempting' }
|
|
136
|
+
| { record: Record; type: 'claimed' }
|
|
137
|
+
| { type: 'missing' }
|
|
138
|
+
| { nextAttemptAt: string | null; type: 'scheduled' }
|
|
139
|
+
| { status: TerminalStatus; type: 'terminal' }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Marks a claimed obligation terminal, starting its dedupe retention window;
|
|
144
|
+
* pending rows never expire, so this stamp is the only expiry that matters.
|
|
145
|
+
* The `attemptingAt` predicate fences stale claimants.
|
|
146
|
+
*/
|
|
147
|
+
export async function complete(
|
|
148
|
+
db: Db.Db,
|
|
149
|
+
reference: Webhooks.QueueReference,
|
|
150
|
+
options: complete.Options,
|
|
151
|
+
): Promise<boolean> {
|
|
152
|
+
const result = await db.kysely
|
|
153
|
+
.updateTable('webhook_queue_events')
|
|
154
|
+
.set({ attemptingAt: null, expiresAt: options.expiresAt, status: options.status })
|
|
155
|
+
.where('subscriptionId', '=', reference.subscriptionId)
|
|
156
|
+
.where('eventId', '=', reference.eventId)
|
|
157
|
+
.where('status', '=', 'pending')
|
|
158
|
+
.where('attemptingAt', '=', options.claimedAt)
|
|
159
|
+
.executeTakeFirst()
|
|
160
|
+
return result.numUpdatedRows === 1n
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export declare namespace complete {
|
|
164
|
+
/** Fenced terminal transition. */
|
|
165
|
+
type Options = {
|
|
166
|
+
/** Claim marker returned by {@link claim}; fences stale claimants. */
|
|
167
|
+
claimedAt: string
|
|
168
|
+
/** Replacement ISO retention deadline for the terminal dedupe row. */
|
|
169
|
+
expiresAt: string
|
|
170
|
+
/** Terminal outcome. */
|
|
171
|
+
status: TerminalStatus
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Releases a claim and schedules the next attempt; fenced like {@link complete}. */
|
|
176
|
+
export async function schedule(
|
|
177
|
+
db: Db.Db,
|
|
178
|
+
reference: Webhooks.QueueReference,
|
|
179
|
+
options: schedule.Options,
|
|
180
|
+
): Promise<boolean> {
|
|
181
|
+
const result = await db.kysely
|
|
182
|
+
.updateTable('webhook_queue_events')
|
|
183
|
+
.set({ attemptingAt: null, nextAttemptAt: options.nextAttemptAt })
|
|
184
|
+
.where('subscriptionId', '=', reference.subscriptionId)
|
|
185
|
+
.where('eventId', '=', reference.eventId)
|
|
186
|
+
.where('status', '=', 'pending')
|
|
187
|
+
.where('attemptingAt', '=', options.claimedAt)
|
|
188
|
+
.executeTakeFirst()
|
|
189
|
+
return result.numUpdatedRows === 1n
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export declare namespace schedule {
|
|
193
|
+
/** Fenced retry scheduling. */
|
|
194
|
+
type Options = {
|
|
195
|
+
/** Claim marker returned by {@link claim}; fences stale claimants. */
|
|
196
|
+
claimedAt: string
|
|
197
|
+
/** ISO due time of the next attempt. */
|
|
198
|
+
nextAttemptAt: string
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Lists due pending references for the sweeper, most overdue first. */
|
|
203
|
+
export async function due(db: Db.Db, options: due.Options): Promise<Webhooks.QueueReference[]> {
|
|
204
|
+
return (
|
|
205
|
+
db.kysely
|
|
206
|
+
.selectFrom('webhook_queue_events')
|
|
207
|
+
.select(['eventId', 'subscriptionId'])
|
|
208
|
+
.where((eb) => whereClaimable(eb, options))
|
|
209
|
+
// Matches the pending partial index order so the scan needs no sort.
|
|
210
|
+
.orderBy(sql`next_attempt_at nulls first`)
|
|
211
|
+
.limit(options.limit)
|
|
212
|
+
.execute()
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export declare namespace due {
|
|
217
|
+
/** Sweep bounds. */
|
|
218
|
+
type Options = {
|
|
219
|
+
/** Maximum references to return. */
|
|
220
|
+
limit: number
|
|
221
|
+
/** Current ISO timestamp. */
|
|
222
|
+
now: string
|
|
223
|
+
/** ISO cutoff below which an existing claim counts as stale. */
|
|
224
|
+
staleBefore: string
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Returns the pending-obligation snapshot, split by due-and-unclaimed work. */
|
|
229
|
+
export async function pending(db: Db.Db, options: pending.Options): Promise<pending.Result> {
|
|
230
|
+
const row = await db.kysely
|
|
231
|
+
.selectFrom('webhook_queue_events')
|
|
232
|
+
.select((eb) => [
|
|
233
|
+
eb.fn.countAll<number>().as('count'),
|
|
234
|
+
eb.fn.min('createdAt').as('oldestAt'),
|
|
235
|
+
eb.fn
|
|
236
|
+
.countAll<number>()
|
|
237
|
+
.filterWhere((fb) => whereClaimable(fb, options))
|
|
238
|
+
.as('dueCount'),
|
|
239
|
+
eb.fn
|
|
240
|
+
// Null next attempts (migration-backfilled rows) are due immediately;
|
|
241
|
+
// coalesce to staging time so they age instead of vanishing from MIN.
|
|
242
|
+
.min(eb.fn.coalesce('nextAttemptAt', 'createdAt'))
|
|
243
|
+
.filterWhere((fb) => whereClaimable(fb, options))
|
|
244
|
+
.as('oldestDueAt'),
|
|
245
|
+
])
|
|
246
|
+
.where('status', '=', 'pending')
|
|
247
|
+
.executeTakeFirstOrThrow()
|
|
248
|
+
return {
|
|
249
|
+
count: Number(row.count),
|
|
250
|
+
dueCount: Number(row.dueCount),
|
|
251
|
+
oldestAt: row.oldestAt,
|
|
252
|
+
oldestDueAt: row.oldestDueAt,
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export declare namespace pending {
|
|
257
|
+
/** Snapshot clock bounds; the read-only subset of {@link claim.Options}. */
|
|
258
|
+
type Options = Pick<claim.Options, 'now' | 'staleBefore'>
|
|
259
|
+
|
|
260
|
+
/** Pending-work snapshot. */
|
|
261
|
+
type Result = {
|
|
262
|
+
/** Pending obligations, including in-flight claims and scheduled retries. */
|
|
263
|
+
count: number
|
|
264
|
+
/** Obligations due now with no live claim; the sweeper's backlog. */
|
|
265
|
+
dueCount: number
|
|
266
|
+
/** Oldest pending staging timestamp, or null when none. */
|
|
267
|
+
oldestAt: string | null
|
|
268
|
+
/** Oldest due-and-unclaimed retry time, or null when none; drives the stall alert. */
|
|
269
|
+
oldestDueAt: string | null
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Deletes terminal dedupe rows past their retention deadline. Pending rows
|
|
275
|
+
* never expire: owed work drains only through terminal transitions
|
|
276
|
+
* (delivery, permanent failure, or the skip after auto-disable).
|
|
277
|
+
*/
|
|
278
|
+
export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
|
|
279
|
+
const result = await db.kysely
|
|
280
|
+
.deleteFrom('webhook_queue_events')
|
|
281
|
+
.where('expiresAt', '<=', now)
|
|
282
|
+
.where('status', '!=', 'pending')
|
|
283
|
+
.executeTakeFirst()
|
|
284
|
+
return Number(result.numDeletedRows)
|
|
285
|
+
}
|
|
@@ -13,6 +13,21 @@ export type Table = db_Schema.WebhookSubscription
|
|
|
13
13
|
/** A stored webhook-subscription row. */
|
|
14
14
|
export type Record = Selectable<Table>
|
|
15
15
|
|
|
16
|
+
/** Reads one live subscription by id for an internal Queue delivery. */
|
|
17
|
+
export async function getById(
|
|
18
|
+
db: Db.Db,
|
|
19
|
+
id: string,
|
|
20
|
+
now: string,
|
|
21
|
+
): Promise<Webhooks.Subscription | null> {
|
|
22
|
+
const row = await db.kysely
|
|
23
|
+
.selectFrom('webhook_subscriptions')
|
|
24
|
+
.selectAll()
|
|
25
|
+
.where('id', '=', id)
|
|
26
|
+
.where(live(now))
|
|
27
|
+
.executeTakeFirst()
|
|
28
|
+
return row ? toSubscription(row) : null
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
/** Maps an owner union to its stored `ownerId` column value. */
|
|
17
32
|
export function ownerId(owner: Webhooks.Owner): string {
|
|
18
33
|
return owner.type === 'api_key' ? owner.orgId : owner.payer
|
|
@@ -273,20 +288,21 @@ export async function remove(
|
|
|
273
288
|
|
|
274
289
|
/**
|
|
275
290
|
* Lists live `active` subscriptions for a `(chainId, eventType)` stream — the
|
|
276
|
-
* poller's per-stream lookup, served by the partial status index.
|
|
291
|
+
* poller's per-stream lookup, served by the partial status index. Each row
|
|
292
|
+
* carries its poller cursor so the poller avoids a per-subscription read.
|
|
277
293
|
*
|
|
278
294
|
* @param db - The database.
|
|
279
295
|
* @param chainId - Chain the stream belongs to.
|
|
280
296
|
* @param eventType - Event type of the stream.
|
|
281
297
|
* @param now - ISO timestamp expiry is evaluated against.
|
|
282
|
-
* @returns
|
|
298
|
+
* @returns Cursor-and-subscription pairs, in id (chronological) order.
|
|
283
299
|
*/
|
|
284
300
|
export async function listActive(
|
|
285
301
|
db: Db.Db,
|
|
286
302
|
chainId: number,
|
|
287
303
|
eventType: Webhooks.EventType,
|
|
288
304
|
now: string,
|
|
289
|
-
): Promise<Webhooks.Subscription[]> {
|
|
305
|
+
): Promise<{ cursor: string | null; subscription: Webhooks.Subscription }[]> {
|
|
290
306
|
const rows = await db.kysely
|
|
291
307
|
.selectFrom('webhook_subscriptions')
|
|
292
308
|
.selectAll()
|
|
@@ -296,7 +312,69 @@ export async function listActive(
|
|
|
296
312
|
.where(live(now))
|
|
297
313
|
.orderBy('id')
|
|
298
314
|
.execute()
|
|
299
|
-
return rows.map(toSubscription)
|
|
315
|
+
return rows.map((row) => ({ cursor: row.pollerCursor, subscription: toSubscription(row) }))
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Lists every live `active` subscription on a chain in one read. The poller
|
|
320
|
+
* groups the rows by event type so empty streams cost no extra database reads.
|
|
321
|
+
*
|
|
322
|
+
* @param db - The database.
|
|
323
|
+
* @param options - Chain, configured event types, and expiry time.
|
|
324
|
+
* @returns Cursor-and-subscription pairs ordered by event type, then id.
|
|
325
|
+
*/
|
|
326
|
+
export async function listActiveForChain(
|
|
327
|
+
db: Db.Db,
|
|
328
|
+
options: listActiveForChain.Options,
|
|
329
|
+
): Promise<{ cursor: string | null; subscription: Webhooks.Subscription }[]> {
|
|
330
|
+
if (options.eventTypes.length === 0) return []
|
|
331
|
+
const rows = await db.kysely
|
|
332
|
+
.selectFrom('webhook_subscriptions')
|
|
333
|
+
.selectAll()
|
|
334
|
+
.where('chainId', '=', options.chainId)
|
|
335
|
+
.where('eventType', 'in', options.eventTypes)
|
|
336
|
+
.where('status', '=', 'active')
|
|
337
|
+
.where(live(options.now))
|
|
338
|
+
.orderBy('eventType')
|
|
339
|
+
.orderBy('id')
|
|
340
|
+
.execute()
|
|
341
|
+
return rows.map((row) => ({ cursor: row.pollerCursor, subscription: toSubscription(row) }))
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export declare namespace listActiveForChain {
|
|
345
|
+
/** Filters for the poller's chain-level subscription read. */
|
|
346
|
+
type Options = {
|
|
347
|
+
/** Chain whose subscriptions to read. */
|
|
348
|
+
chainId: number
|
|
349
|
+
/** Event types configured for this poller tick. */
|
|
350
|
+
eventTypes: readonly Webhooks.EventType[]
|
|
351
|
+
/** ISO timestamp expiry is evaluated against. */
|
|
352
|
+
now: string
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Reads the poller cursors of every live `active` subscription on a chain,
|
|
358
|
+
* across event types. Feeds the cron's cursor-freshness fallback gate.
|
|
359
|
+
*
|
|
360
|
+
* @param db - The database.
|
|
361
|
+
* @param chainId - Chain whose cursors to read.
|
|
362
|
+
* @param now - ISO timestamp expiry is evaluated against.
|
|
363
|
+
* @returns The non-null cursors, unordered.
|
|
364
|
+
*/
|
|
365
|
+
export async function listActiveCursors(
|
|
366
|
+
db: Db.Db,
|
|
367
|
+
chainId: number,
|
|
368
|
+
now: string,
|
|
369
|
+
): Promise<string[]> {
|
|
370
|
+
const rows = await db.kysely
|
|
371
|
+
.selectFrom('webhook_subscriptions')
|
|
372
|
+
.select('pollerCursor')
|
|
373
|
+
.where('chainId', '=', chainId)
|
|
374
|
+
.where('status', '=', 'active')
|
|
375
|
+
.where(live(now))
|
|
376
|
+
.execute()
|
|
377
|
+
return rows.flatMap((row) => (row.pollerCursor === null ? [] : [row.pollerCursor]))
|
|
300
378
|
}
|
|
301
379
|
|
|
302
380
|
/**
|
|
@@ -367,6 +445,55 @@ export declare namespace compareAndSetCursor {
|
|
|
367
445
|
}
|
|
368
446
|
}
|
|
369
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Writes multiple poller cursors when each stored value still matches the
|
|
450
|
+
* caller's observed value.
|
|
451
|
+
*
|
|
452
|
+
* @param db - The database.
|
|
453
|
+
* @param options - Candidate cursor writes.
|
|
454
|
+
* @returns Subscription ids whose cursors were updated.
|
|
455
|
+
*/
|
|
456
|
+
export async function compareAndSetCursors(
|
|
457
|
+
db: Db.Db,
|
|
458
|
+
options: compareAndSetCursors.Options,
|
|
459
|
+
): Promise<Set<string>> {
|
|
460
|
+
if (options.cursors.length === 0) return new Set()
|
|
461
|
+
const values = JSON.stringify(options.cursors)
|
|
462
|
+
type Candidate = {
|
|
463
|
+
cursor: string
|
|
464
|
+
expectedCursor: string | null
|
|
465
|
+
subscriptionId: string
|
|
466
|
+
}
|
|
467
|
+
const result = await db.kysely
|
|
468
|
+
.with(
|
|
469
|
+
'candidates',
|
|
470
|
+
() => sql<Candidate>`(
|
|
471
|
+
SELECT
|
|
472
|
+
candidate."subscriptionId" AS subscription_id,
|
|
473
|
+
candidate."expectedCursor" AS expected_cursor,
|
|
474
|
+
candidate.cursor
|
|
475
|
+
FROM jsonb_to_recordset(${values}::jsonb)
|
|
476
|
+
AS candidate("subscriptionId" text, "expectedCursor" text, cursor text)
|
|
477
|
+
)`,
|
|
478
|
+
)
|
|
479
|
+
.updateTable('webhook_subscriptions as subscription')
|
|
480
|
+
.from('candidates as candidate')
|
|
481
|
+
.set((eb) => ({ pollerCursor: eb.ref('candidate.cursor') }))
|
|
482
|
+
.whereRef('subscription.id', '=', 'candidate.subscriptionId')
|
|
483
|
+
.whereRef('subscription.pollerCursor', 'is not distinct from', 'candidate.expectedCursor')
|
|
484
|
+
.returning('subscription.id as subscriptionId')
|
|
485
|
+
.execute()
|
|
486
|
+
return new Set(result.map((row) => row.subscriptionId))
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export declare namespace compareAndSetCursors {
|
|
490
|
+
/** Options for {@link compareAndSetCursors}. */
|
|
491
|
+
type Options = {
|
|
492
|
+
/** Candidate cursor writes. */
|
|
493
|
+
cursors: readonly compareAndSetCursor.Options[]
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
370
497
|
/**
|
|
371
498
|
* Records a successful delivery: resets the consecutive-failure counter and
|
|
372
499
|
* stamps `lastDeliveryAt`.
|