tapimo 0.10.0 → 0.10.2
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 +6007 -5003
- 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 +1 -1
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +6 -6
- 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 +22 -13
- 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/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 +232 -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/Zones.d.ts +4266 -82
- package/dist/apps/Zones.d.ts.map +1 -1
- package/dist/apps/Zones.js +27 -8
- package/dist/apps/Zones.js.map +1 -1
- package/dist/apps/data/App.d.ts +3467 -2696
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +2 -0
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +2999 -100
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +340 -2
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +514 -0
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +26 -26
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +7 -3
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +4 -4
- package/dist/apps/data/routes/earn.d.ts +75 -75
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +65 -36
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +124 -120
- 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 +24 -24
- package/dist/apps/data/routes/indexer.d.ts +2 -1
- 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 +4 -3
- 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/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 +1 -1
- 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/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/verified-tokens.d.ts +37 -37
- package/dist/apps/data/routes/webhooks.d.ts +71 -1
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +177 -23
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.js +1 -1
- package/dist/apps/data/routes/zones.js.map +1 -1
- package/dist/apps/funding/App.d.ts +322 -89
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +375 -45
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +208 -12
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +32 -32
- 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 +418 -418
- package/dist/apps/management/routes/api-keys.d.ts +50 -50
- package/dist/apps/management/routes/billing.d.ts +72 -72
- 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 +8 -8
- package/dist/apps/management/routes/faucet.js +2 -2
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts +42 -42
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +14 -14
- package/dist/apps/management/routes/projects.d.ts +40 -40
- package/dist/apps/management/routes/usage.d.ts +8 -8
- package/dist/apps/management/routes/webhooks.d.ts +49 -49
- 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/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +90 -0
- 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/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 +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 +2 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +10 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +3 -26
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +6 -37
- 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 +51 -0
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +54 -2
- 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/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 +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/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 +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 +20 -0
- package/dist/internal/WebhookTransfer.d.ts.map +1 -1
- package/dist/internal/WebhookTransfer.js +7 -4
- package/dist/internal/WebhookTransfer.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +141 -0
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +139 -10
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +3 -4
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +59 -8
- package/dist/internal/funding/providers/relay.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/package.json +3 -3
- package/src/ApiKeys.test.ts +20 -0
- package/src/ApiKeys.ts +28 -1
- package/src/App.test.ts +40 -8
- package/src/App.ts +1 -0
- package/src/Client.test-d.ts +34 -1
- package/src/Webhooks.ts +20 -18
- 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 +42 -0
- package/src/analytics/Analytics.ts +232 -0
- package/src/analytics/tables/requestEvents.ts +6 -0
- package/src/apps/Zones.test.ts +20 -3
- package/src/apps/Zones.ts +28 -8
- package/src/apps/composition.test.ts +2 -0
- package/src/apps/data/App.test.ts +2 -2
- package/src/apps/data/App.ts +2 -0
- package/src/apps/data/routes/activities.bench.ts +41 -0
- package/src/apps/data/routes/activities.test.ts +103 -2
- package/src/apps/data/routes/activities.ts +401 -2
- package/src/apps/data/routes/balances.bench.ts +21 -0
- package/src/apps/data/routes/balances.ts +7 -3
- package/src/apps/data/routes/blocks.bench.ts +34 -0
- 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.ts +66 -36
- package/src/apps/data/routes/exchanges.bench.ts +85 -0
- package/src/apps/data/routes/exchanges.test.ts +8 -3
- package/src/apps/data/routes/exchanges.ts +134 -82
- 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.ts +16 -14
- 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.ts +3 -2
- package/src/apps/data/routes/transactions.bench.ts +48 -0
- package/src/apps/data/routes/transfers.bench.ts +28 -0
- package/src/apps/data/routes/transfers.ts +36 -10
- package/src/apps/data/routes/valuation.bench.ts +14 -0
- 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 +53 -6
- package/src/apps/data/routes/webhooks.ts +252 -24
- 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 +1 -1
- package/src/apps/funding/routes/chains.test.ts +1 -1
- package/src/apps/funding/routes/deposit-addresses.test.ts +217 -3
- package/src/apps/funding/routes/deposit-addresses.ts +255 -19
- package/src/apps/funding/routes/providers.test.ts +1 -1
- package/src/apps/funding/routes/quotes.test.ts +1 -1
- package/src/apps/funding/routes/transfers.test.ts +20 -8
- 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/db/Db.ts +117 -0
- package/src/db/Schema.ts +32 -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/webhookQueueEvents.ts +285 -0
- package/src/db/tables/webhookSubscriptions.ts +15 -0
- package/src/internal/Auth.test.ts +61 -98
- package/src/internal/Auth.ts +9 -63
- 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/EarnVaults.test.ts +2 -2
- package/src/internal/Log.test.ts +3 -1
- package/src/internal/Log.ts +14 -0
- package/src/internal/MetricSink.test.ts +108 -2
- package/src/internal/MetricSink.ts +112 -2
- package/src/internal/RateLimit.test.ts +23 -18
- package/src/internal/RateLimit.ts +23 -15
- 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 +5 -4
- package/src/internal/Value.test.ts +25 -0
- package/src/internal/Value.ts +29 -0
- package/src/internal/Viem.test.ts +4 -0
- package/src/internal/Viem.ts +6 -4
- package/src/internal/WebhookTransfer.test.ts +89 -0
- package/src/internal/WebhookTransfer.ts +11 -7
- package/src/internal/Webhooks.test.ts +488 -0
- package/src/internal/Webhooks.ts +291 -10
- package/src/internal/funding/Provider.test.ts +1 -1
- package/src/internal/funding/providers/index.test.ts +73 -3
- package/src/internal/funding/providers/relay.test.ts +1 -5
- package/src/internal/funding/providers/relay.ts +56 -12
- package/src/internal/index.test.ts +1 -0
- package/src/internal/index.ts +1 -0
|
@@ -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
|