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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Metrics } from '../Metrics.js'
|
|
2
2
|
import type * as Webhooks from '../Webhooks.js'
|
|
3
3
|
import type * as Log from './Log.js'
|
|
4
|
+
import * as WebhookDestination from './WebhookDestination.js'
|
|
4
5
|
|
|
5
6
|
/** Webhook event type, including the synthetic `ping` carried on test envelopes. */
|
|
6
7
|
export type EventType = Webhooks.Envelope['type']
|
|
@@ -8,6 +9,12 @@ export type EventType = Webhooks.Envelope['type']
|
|
|
8
9
|
/** Where a delivery attempt originated. */
|
|
9
10
|
export type Trigger = 'manual_retry' | 'ping' | 'queue'
|
|
10
11
|
|
|
12
|
+
/** Bounded poller stage names used by `webhook:poll`. */
|
|
13
|
+
export type PollStage = 'cursor_write' | 'enrich' | 'rpc_head' | 'scan' | 'subscription_read'
|
|
14
|
+
|
|
15
|
+
/** Bounded Durable Object or transport failure classes used by `webhook:poll-failure`. */
|
|
16
|
+
export type PollFailure = 'other' | 'overloaded' | 'remote' | 'retryable'
|
|
17
|
+
|
|
11
18
|
/** Context for one poller tick that the {@link Webhooks.createPoller.Result} omits. */
|
|
12
19
|
export type TickMeta = {
|
|
13
20
|
/** Chain polled this tick; used when the tick errored before a result existed. */
|
|
@@ -20,11 +27,127 @@ export type TickMeta = {
|
|
|
20
27
|
|
|
21
28
|
/**
|
|
22
29
|
* A webhook metric event, recorded by the {@link webhooks} sink. The poller
|
|
23
|
-
* emits
|
|
24
|
-
* `webhook:delivery`.
|
|
30
|
+
* emits head recovery, poll, tick, and enqueue measurements; the delivery path
|
|
31
|
+
* emits `webhook:delivery`.
|
|
25
32
|
*/
|
|
26
33
|
export type WebhookEvent =
|
|
27
34
|
| {
|
|
35
|
+
/** Tempo chain whose poll cycle completed. */
|
|
36
|
+
chainId: number
|
|
37
|
+
/** Whether the poll cycle completed without scan or dispatch failures. */
|
|
38
|
+
outcome: 'error' | 'ok'
|
|
39
|
+
/** Requested boundary movement since the preceding successful poll, in blocks. */
|
|
40
|
+
requestedBoundaryAdvanceBlocks?: number | undefined
|
|
41
|
+
/** Execution path that acquired this poll claim. */
|
|
42
|
+
trigger: 'alarm' | 'cron' | 'direct' | 'queue'
|
|
43
|
+
/** Identifies a transport-neutral poll-cycle measurement. */
|
|
44
|
+
type: 'webhook:poll-cycle'
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
/** Tempo chain whose durable progress was read. */
|
|
48
|
+
chainId: number
|
|
49
|
+
/** Blocks between the desired and fully drained boundaries. */
|
|
50
|
+
lagBlocks: number
|
|
51
|
+
/** Identifies durable coordinator progress. */
|
|
52
|
+
type: 'webhook:head-progress'
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
/** Tempo chain whose listener was checked. */
|
|
56
|
+
chainId: number
|
|
57
|
+
/** Heads refused in a row by the monotonic guard; zero on a delivering listener. */
|
|
58
|
+
skippedHeads: number
|
|
59
|
+
/** Identifies heads received and discarded rather than heads not received. */
|
|
60
|
+
type: 'webhook:head-skipped'
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
chainId: number
|
|
64
|
+
/** Blocks the probed chain head leads the listener's newest observation. */
|
|
65
|
+
lagBlocks: number
|
|
66
|
+
/** Identifies the out-of-band listener head probe. */
|
|
67
|
+
type: 'webhook:listener-head-lag'
|
|
68
|
+
}
|
|
69
|
+
| {
|
|
70
|
+
/** Tempo chain whose recovery poke was dequeued. */
|
|
71
|
+
chainId: number
|
|
72
|
+
/** When the recovery poke was dequeued, as epoch milliseconds. */
|
|
73
|
+
dequeuedAt: number
|
|
74
|
+
/** When the recovery poke was enqueued, as epoch milliseconds. */
|
|
75
|
+
enqueuedAt: number
|
|
76
|
+
/** Recovery source carried through the Queue handoff. */
|
|
77
|
+
trigger: 'alarm' | 'queue'
|
|
78
|
+
/** Identifies recovery Queue latency, not observed head volume. */
|
|
79
|
+
type: 'webhook:head-recovery'
|
|
80
|
+
}
|
|
81
|
+
| {
|
|
82
|
+
/** Tempo chain whose poll claim was acquired. */
|
|
83
|
+
chainId: number
|
|
84
|
+
/** Delay from observing the desired boundary to acquiring the claim. */
|
|
85
|
+
delayMs?: number | undefined
|
|
86
|
+
/** Execution path that acquired the claim. */
|
|
87
|
+
trigger: 'alarm' | 'cron' | 'direct' | 'queue'
|
|
88
|
+
/** Identifies a poll beginning, before scan work runs. */
|
|
89
|
+
type: 'webhook:poll-start'
|
|
90
|
+
}
|
|
91
|
+
| {
|
|
92
|
+
/** Tempo chain whose poll failed. */
|
|
93
|
+
chainId: number
|
|
94
|
+
/** Bounded class of the thrown failure. */
|
|
95
|
+
failure: PollFailure
|
|
96
|
+
/** Operation boundary that rejected the poll. */
|
|
97
|
+
stage: 'claim' | 'continuation_enqueue' | 'finish' | 'scan'
|
|
98
|
+
/** Execution path that failed before recovery. */
|
|
99
|
+
trigger: 'alarm' | 'cron' | 'direct' | 'queue'
|
|
100
|
+
/** Identifies a transport-neutral poll failure. */
|
|
101
|
+
type: 'webhook:poll-failure'
|
|
102
|
+
}
|
|
103
|
+
| {
|
|
104
|
+
chainId: number
|
|
105
|
+
durationMs: number
|
|
106
|
+
eventType?: EventType | undefined
|
|
107
|
+
outcome: 'error' | 'ok'
|
|
108
|
+
stage: PollStage
|
|
109
|
+
type: 'webhook:poll'
|
|
110
|
+
}
|
|
111
|
+
| {
|
|
112
|
+
/** Tempo chain whose eligible subscription count was read. */
|
|
113
|
+
chainId: number
|
|
114
|
+
/** Active, unexpired subscriptions selected for the poll. */
|
|
115
|
+
count: number
|
|
116
|
+
/** Identifies a pre-scan subscription eligibility measurement. */
|
|
117
|
+
type: 'webhook:poll-subscriptions'
|
|
118
|
+
}
|
|
119
|
+
| {
|
|
120
|
+
/** Chain scanned by this poll batch. */
|
|
121
|
+
chainId: number
|
|
122
|
+
/** Shared upper bound for concurrent upstream scans. */
|
|
123
|
+
concurrency: number
|
|
124
|
+
/** Scan duration, present only after completion. */
|
|
125
|
+
durationMs?: number | undefined
|
|
126
|
+
/** Subscription event type scanned by this batch. */
|
|
127
|
+
eventType: EventType
|
|
128
|
+
/** Subscriptions whose scan rejected, present only after completion. */
|
|
129
|
+
failedSubscriptions?: number | undefined
|
|
130
|
+
/** Matching rows retained by completed pages, present only after completion. */
|
|
131
|
+
matchedItems?: number | undefined
|
|
132
|
+
/** Largest block window requested by one subscription. */
|
|
133
|
+
maxWindowBlocks: number
|
|
134
|
+
/** Whether the checkpoint precedes allocation or follows scan completion. */
|
|
135
|
+
phase: 'complete' | 'start'
|
|
136
|
+
/** Outcome of the completed RPC-backed scan. */
|
|
137
|
+
outcome?: 'empty' | 'error' | 'missing' | 'ok' | undefined
|
|
138
|
+
/** JSON-RPC method used by this event type's scanner. */
|
|
139
|
+
rpcMethod: 'eth_getBlockByNumber' | 'eth_getLogs'
|
|
140
|
+
/** Subscriptions retained in this scan batch. */
|
|
141
|
+
subscriptionCount: number
|
|
142
|
+
/** Maximum subscriptions retained by this event-type batch. */
|
|
143
|
+
subscriptionLimit: number
|
|
144
|
+
/** Sum of subscription block windows in this scan batch. */
|
|
145
|
+
totalWindowBlocks: number
|
|
146
|
+
/** Identifies a memory-attribution scan boundary. */
|
|
147
|
+
type: 'webhook:poll-scan'
|
|
148
|
+
}
|
|
149
|
+
| {
|
|
150
|
+
cursorLagBlocks?: number | undefined
|
|
28
151
|
meta: TickMeta
|
|
29
152
|
result: Webhooks.createPoller.Result | undefined
|
|
30
153
|
type: 'webhook:tick'
|
|
@@ -37,9 +160,37 @@ export type WebhookEvent =
|
|
|
37
160
|
type: 'webhook:enqueue'
|
|
38
161
|
}
|
|
39
162
|
| {
|
|
163
|
+
/** Atomic ledger claim outcome. */
|
|
164
|
+
outcome: 'attempting' | 'claimed' | 'missing' | 'scheduled' | 'terminal'
|
|
165
|
+
/** Identifies a delivery-obligation claim. */
|
|
166
|
+
type: 'webhook:delivery-job-claim'
|
|
167
|
+
}
|
|
168
|
+
| {
|
|
169
|
+
/** Obligations represented by this outcome. */
|
|
170
|
+
count: number
|
|
171
|
+
/** Whether staging created or found the obligation. */
|
|
172
|
+
outcome: 'created' | 'existing'
|
|
173
|
+
/** Identifies obligation staging ahead of Queue handoff. */
|
|
174
|
+
type: 'webhook:delivery-job-ensure'
|
|
175
|
+
}
|
|
176
|
+
| {
|
|
177
|
+
/** Delivery attempt ordinal: the ledger claim count on the queue path. */
|
|
178
|
+
attempt: number
|
|
40
179
|
envelope: Webhooks.Envelope
|
|
180
|
+
/** Cloudflare Queue delivery attempt, absent outside the Queue path. */
|
|
181
|
+
queueAttempt?: number | undefined
|
|
41
182
|
result: Webhooks.Result
|
|
42
183
|
subscription: Webhooks.Subscription
|
|
184
|
+
timings?: {
|
|
185
|
+
deliveryLogMs?: number | undefined
|
|
186
|
+
envelopeToQueueMs?: number | undefined
|
|
187
|
+
endToEndMs?: number | undefined
|
|
188
|
+
eventToEnvelopeMs?: number | undefined
|
|
189
|
+
processingMs?: number | undefined
|
|
190
|
+
queueWaitMs?: number | undefined
|
|
191
|
+
stateWriteMs?: number | undefined
|
|
192
|
+
subscriptionReadMs?: number | undefined
|
|
193
|
+
}
|
|
43
194
|
trigger: Trigger
|
|
44
195
|
type: 'webhook:delivery'
|
|
45
196
|
}
|
|
@@ -195,7 +346,7 @@ type StatusClass = '2xx' | '4xx' | '5xx' | 'network' | 'timeout' | 'unknown' | '
|
|
|
195
346
|
/**
|
|
196
347
|
* Builds a webhook delivery-reliability sink over `backend`. Tags stay
|
|
197
348
|
* low-cardinality (`chain_id`, `event_type`, `destination_type`, `status_class`,
|
|
198
|
-
* `attempt_bucket`, `outcome`) — never subscription/org/event ids, which belong
|
|
349
|
+
* `attempt_bucket`, `failure`, `queue_attempt_bucket`, `outcome`, `stage`) — never subscription/org/event ids, which belong
|
|
199
350
|
* in logs or analytics, not StatsD-style series.
|
|
200
351
|
*/
|
|
201
352
|
export function webhooks(backend: Metrics): WebhookSink {
|
|
@@ -205,6 +356,111 @@ export function webhooks(backend: Metrics): WebhookSink {
|
|
|
205
356
|
},
|
|
206
357
|
record(event) {
|
|
207
358
|
switch (event.type) {
|
|
359
|
+
case 'webhook:head-recovery': {
|
|
360
|
+
const tags = { chain_id: String(event.chainId), trigger: event.trigger }
|
|
361
|
+
backend.count('webhook_head_recovery_queue_count', 1, tags)
|
|
362
|
+
backend.histogram(
|
|
363
|
+
'webhook_head_recovery_queue_wait_ms',
|
|
364
|
+
Math.max(0, event.dequeuedAt - event.enqueuedAt),
|
|
365
|
+
tags,
|
|
366
|
+
)
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
case 'webhook:head-progress':
|
|
370
|
+
backend.gauge('webhook_head_progress_lag_blocks', event.lagBlocks, {
|
|
371
|
+
chain_id: String(event.chainId),
|
|
372
|
+
})
|
|
373
|
+
return
|
|
374
|
+
case 'webhook:head-skipped':
|
|
375
|
+
backend.gauge('webhook_head_skipped_count', event.skippedHeads, {
|
|
376
|
+
chain_id: String(event.chainId),
|
|
377
|
+
})
|
|
378
|
+
return
|
|
379
|
+
case 'webhook:listener-head-lag':
|
|
380
|
+
backend.gauge('webhook_listener_head_lag_blocks', event.lagBlocks, {
|
|
381
|
+
chain_id: String(event.chainId),
|
|
382
|
+
})
|
|
383
|
+
return
|
|
384
|
+
case 'webhook:poll-failure':
|
|
385
|
+
backend.count('webhook_poll_failure_count', 1, {
|
|
386
|
+
chain_id: String(event.chainId),
|
|
387
|
+
failure: event.failure,
|
|
388
|
+
stage: event.stage,
|
|
389
|
+
trigger: event.trigger,
|
|
390
|
+
})
|
|
391
|
+
return
|
|
392
|
+
case 'webhook:poll': {
|
|
393
|
+
const tags = {
|
|
394
|
+
chain_id: String(event.chainId),
|
|
395
|
+
...(event.eventType === undefined ? {} : { event_type: event.eventType }),
|
|
396
|
+
outcome: event.outcome,
|
|
397
|
+
stage: event.stage,
|
|
398
|
+
}
|
|
399
|
+
backend.histogram('webhook_poller_stage_duration_ms', event.durationMs, tags)
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
case 'webhook:poll-scan': {
|
|
403
|
+
const tags = {
|
|
404
|
+
chain_id: String(event.chainId),
|
|
405
|
+
event_type: event.eventType,
|
|
406
|
+
phase: event.phase,
|
|
407
|
+
}
|
|
408
|
+
backend.count('webhook_poller_scan_checkpoint_count', 1, tags)
|
|
409
|
+
backend.histogram('webhook_poller_scan_concurrency', event.concurrency, tags)
|
|
410
|
+
backend.histogram('webhook_poller_scan_subscription_count', event.subscriptionCount, tags)
|
|
411
|
+
backend.histogram('webhook_poller_scan_subscription_limit', event.subscriptionLimit, tags)
|
|
412
|
+
backend.histogram('webhook_poller_scan_max_window_blocks', event.maxWindowBlocks, tags)
|
|
413
|
+
backend.histogram(
|
|
414
|
+
'webhook_poller_scan_total_window_blocks',
|
|
415
|
+
event.totalWindowBlocks,
|
|
416
|
+
tags,
|
|
417
|
+
)
|
|
418
|
+
if (event.durationMs !== undefined)
|
|
419
|
+
backend.histogram('webhook_poller_scan_duration_ms', event.durationMs, tags)
|
|
420
|
+
if (event.failedSubscriptions !== undefined)
|
|
421
|
+
backend.count(
|
|
422
|
+
'webhook_poller_scan_failed_subscription_count',
|
|
423
|
+
event.failedSubscriptions,
|
|
424
|
+
tags,
|
|
425
|
+
)
|
|
426
|
+
if (event.matchedItems !== undefined)
|
|
427
|
+
backend.count('webhook_poller_scan_matched_item_count', event.matchedItems, tags)
|
|
428
|
+
if (event.outcome !== undefined)
|
|
429
|
+
backend.count('webhook_poller_rpc_scan_count', 1, {
|
|
430
|
+
chain_id: String(event.chainId),
|
|
431
|
+
event_type: event.eventType,
|
|
432
|
+
outcome: event.outcome,
|
|
433
|
+
rpc_method: event.rpcMethod,
|
|
434
|
+
})
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
case 'webhook:poll-subscriptions':
|
|
438
|
+
backend.gauge('webhook_poller_active_subscription_count', event.count, {
|
|
439
|
+
chain_id: String(event.chainId),
|
|
440
|
+
})
|
|
441
|
+
return
|
|
442
|
+
case 'webhook:poll-cycle': {
|
|
443
|
+
const tags = {
|
|
444
|
+
chain_id: String(event.chainId),
|
|
445
|
+
outcome: event.outcome,
|
|
446
|
+
trigger: event.trigger,
|
|
447
|
+
}
|
|
448
|
+
backend.count('webhook_poll_cycle_count', 1, tags)
|
|
449
|
+
if (event.requestedBoundaryAdvanceBlocks !== undefined)
|
|
450
|
+
backend.histogram(
|
|
451
|
+
'webhook_poll_requested_boundary_advance_blocks',
|
|
452
|
+
event.requestedBoundaryAdvanceBlocks,
|
|
453
|
+
tags,
|
|
454
|
+
)
|
|
455
|
+
return
|
|
456
|
+
}
|
|
457
|
+
case 'webhook:poll-start': {
|
|
458
|
+
const tags = { chain_id: String(event.chainId), trigger: event.trigger }
|
|
459
|
+
backend.count('webhook_poll_start_count', 1, tags)
|
|
460
|
+
if (event.delayMs !== undefined)
|
|
461
|
+
backend.histogram('webhook_poll_start_delay_ms', event.delayMs, tags)
|
|
462
|
+
return
|
|
463
|
+
}
|
|
208
464
|
case 'webhook:tick': {
|
|
209
465
|
const { meta, result } = event
|
|
210
466
|
const chain_id = String(result?.chainId ?? meta.chainId ?? 'unknown')
|
|
@@ -213,6 +469,10 @@ export function webhooks(backend: Metrics): WebhookSink {
|
|
|
213
469
|
chain_id,
|
|
214
470
|
outcome: meta.outcome,
|
|
215
471
|
})
|
|
472
|
+
if (event.cursorLagBlocks !== undefined)
|
|
473
|
+
backend.gauge('webhook_subscription_cursor_lag_blocks', event.cursorLagBlocks, {
|
|
474
|
+
chain_id,
|
|
475
|
+
})
|
|
216
476
|
if (!result) return
|
|
217
477
|
backend.histogram('webhook_poller_subscription_count', result.subscriptions, { chain_id })
|
|
218
478
|
backend.count('webhook_poller_scanned_count', result.scanned, { chain_id })
|
|
@@ -237,8 +497,29 @@ export function webhooks(backend: Metrics): WebhookSink {
|
|
|
237
497
|
})
|
|
238
498
|
return
|
|
239
499
|
}
|
|
500
|
+
case 'webhook:delivery-job-claim': {
|
|
501
|
+
backend.count('webhook_delivery_job_claim_count', 1, { outcome: event.outcome })
|
|
502
|
+
return
|
|
503
|
+
}
|
|
504
|
+
case 'webhook:delivery-job-ensure': {
|
|
505
|
+
backend.count('webhook_delivery_job_ensure_count', event.count, {
|
|
506
|
+
outcome: event.outcome,
|
|
507
|
+
})
|
|
508
|
+
return
|
|
509
|
+
}
|
|
240
510
|
case 'webhook:delivery': {
|
|
241
|
-
const { envelope, result, subscription } = event
|
|
511
|
+
const { envelope, result, subscription, timings } = event
|
|
512
|
+
const queueTags =
|
|
513
|
+
event.queueAttempt === undefined
|
|
514
|
+
? {}
|
|
515
|
+
: {
|
|
516
|
+
queue_attempt_bucket: attemptBucket(event.queueAttempt),
|
|
517
|
+
// Retryable results ack the message and schedule a ledger
|
|
518
|
+
// retry; the Queue transport never redelivers them.
|
|
519
|
+
queue_disposition: WebhookDestination.isRetryable(result)
|
|
520
|
+
? 'scheduled_retry'
|
|
521
|
+
: 'acked',
|
|
522
|
+
}
|
|
242
523
|
const tags = {
|
|
243
524
|
chain_id: String(envelope.chainId),
|
|
244
525
|
destination_type: subscription.destination.type satisfies DestinationType,
|
|
@@ -247,8 +528,9 @@ export function webhooks(backend: Metrics): WebhookSink {
|
|
|
247
528
|
const outcome = result.ok ? 'succeeded' : 'failed'
|
|
248
529
|
backend.count('webhook_delivery_attempt_count', 1, {
|
|
249
530
|
...tags,
|
|
250
|
-
attempt_bucket: attemptBucket(
|
|
531
|
+
attempt_bucket: attemptBucket(event.attempt),
|
|
251
532
|
outcome,
|
|
533
|
+
...queueTags,
|
|
252
534
|
status_class: statusClass(result),
|
|
253
535
|
trigger: event.trigger,
|
|
254
536
|
})
|
|
@@ -257,11 +539,44 @@ export function webhooks(backend: Metrics): WebhookSink {
|
|
|
257
539
|
...tags,
|
|
258
540
|
outcome,
|
|
259
541
|
})
|
|
260
|
-
|
|
261
|
-
'
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
542
|
+
if (timings?.deliveryLogMs !== undefined)
|
|
543
|
+
backend.histogram('webhook_delivery_log_write_ms', timings.deliveryLogMs, tags)
|
|
544
|
+
if (timings?.envelopeToQueueMs !== undefined)
|
|
545
|
+
backend.histogram(
|
|
546
|
+
'webhook_delivery_envelope_to_queue_ms',
|
|
547
|
+
timings.envelopeToQueueMs,
|
|
548
|
+
tags,
|
|
549
|
+
)
|
|
550
|
+
if (timings?.endToEndMs !== undefined)
|
|
551
|
+
backend.histogram('webhook_delivery_end_to_end_ms', timings.endToEndMs, {
|
|
552
|
+
...tags,
|
|
553
|
+
// Unlimited-horizon ledger retries emit hour-plus samples;
|
|
554
|
+
// the bucket keeps first-attempt percentiles interpretable.
|
|
555
|
+
attempt_bucket: attemptBucket(event.attempt),
|
|
556
|
+
outcome,
|
|
557
|
+
})
|
|
558
|
+
if (timings?.eventToEnvelopeMs !== undefined)
|
|
559
|
+
backend.histogram(
|
|
560
|
+
'webhook_delivery_event_to_envelope_ms',
|
|
561
|
+
timings.eventToEnvelopeMs,
|
|
562
|
+
tags,
|
|
563
|
+
)
|
|
564
|
+
if (timings?.processingMs !== undefined)
|
|
565
|
+
backend.histogram('webhook_delivery_processing_ms', timings.processingMs, tags)
|
|
566
|
+
if (timings?.queueWaitMs !== undefined)
|
|
567
|
+
backend.histogram('webhook_delivery_queue_wait_ms', timings.queueWaitMs, {
|
|
568
|
+
...tags,
|
|
569
|
+
outcome,
|
|
570
|
+
...queueTags,
|
|
571
|
+
})
|
|
572
|
+
if (timings?.stateWriteMs !== undefined)
|
|
573
|
+
backend.histogram('webhook_delivery_state_write_ms', timings.stateWriteMs, tags)
|
|
574
|
+
if (timings?.subscriptionReadMs !== undefined)
|
|
575
|
+
backend.histogram(
|
|
576
|
+
'webhook_delivery_subscription_read_ms',
|
|
577
|
+
timings.subscriptionReadMs,
|
|
578
|
+
tags,
|
|
579
|
+
)
|
|
265
580
|
return
|
|
266
581
|
}
|
|
267
582
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { RateLimit, Store } from 'tapimo'
|
|
2
2
|
|
|
3
3
|
describe('memory', () => {
|
|
4
|
-
test('tracks fixed-
|
|
4
|
+
test('tracks fixed-minute quota in a store', async () => {
|
|
5
5
|
const store = RateLimit.memory({
|
|
6
6
|
now: () => new Date('2026-05-25T00:00:30.000Z'),
|
|
7
7
|
})
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
8
|
+
const limit = { limit: 2, period: 'minute' } as const
|
|
9
|
+
const first = await store.consume({ key: 'public:anonymous', limit })
|
|
10
|
+
const second = await store.consume({ key: 'public:anonymous', limit })
|
|
11
|
+
const third = await store.consume({ key: 'public:anonymous', limit })
|
|
11
12
|
|
|
12
13
|
expect(first).toMatchInlineSnapshot(`
|
|
13
14
|
{
|
|
@@ -35,25 +36,26 @@ describe('memory', () => {
|
|
|
35
36
|
`)
|
|
36
37
|
})
|
|
37
38
|
|
|
38
|
-
test('restarts the count when the next
|
|
39
|
-
// The window reset rides on counter expiry
|
|
40
|
-
//
|
|
39
|
+
test('restarts the count when the next second opens', async () => {
|
|
40
|
+
// The window reset rides on counter expiry, so the store clock and the
|
|
41
|
+
// consume clock must agree.
|
|
41
42
|
vi.useFakeTimers()
|
|
42
43
|
try {
|
|
43
|
-
vi.setSystemTime(new Date('2026-05-25T00:00:30.
|
|
44
|
+
vi.setSystemTime(new Date('2026-05-25T00:00:30.250Z'))
|
|
44
45
|
const store = RateLimit.memory()
|
|
45
|
-
|
|
46
|
-
await store.consume({ key: 'public:anonymous', limit
|
|
46
|
+
const limit = { limit: 2, period: 'second' } as const
|
|
47
|
+
await store.consume({ key: 'public:anonymous', limit })
|
|
48
|
+
await store.consume({ key: 'public:anonymous', limit })
|
|
47
49
|
|
|
48
|
-
vi.setSystemTime(new Date('2026-05-25T00:
|
|
49
|
-
const next = await store.consume({ key: 'public:anonymous', limit
|
|
50
|
+
vi.setSystemTime(new Date('2026-05-25T00:00:31.001Z'))
|
|
51
|
+
const next = await store.consume({ key: 'public:anonymous', limit })
|
|
50
52
|
|
|
51
53
|
expect(next).toMatchInlineSnapshot(`
|
|
52
54
|
{
|
|
53
55
|
"allowed": true,
|
|
54
56
|
"limit": 2,
|
|
55
57
|
"remaining": 1,
|
|
56
|
-
"reset":
|
|
58
|
+
"reset": 1779667232,
|
|
57
59
|
}
|
|
58
60
|
`)
|
|
59
61
|
} finally {
|
|
@@ -72,19 +74,22 @@ describe('memory', () => {
|
|
|
72
74
|
},
|
|
73
75
|
})
|
|
74
76
|
const store = RateLimit.memory({
|
|
75
|
-
//
|
|
76
|
-
now: () => new Date('2026-05-25T00:00:30.
|
|
77
|
+
// 250ms into the second, so 750ms of the window remains.
|
|
78
|
+
now: () => new Date('2026-05-25T00:00:30.250Z'),
|
|
77
79
|
store: recording,
|
|
78
80
|
})
|
|
79
81
|
|
|
80
|
-
await store.consume({
|
|
82
|
+
await store.consume({
|
|
83
|
+
key: 'public:anonymous',
|
|
84
|
+
limit: { limit: 2, period: 'second' },
|
|
85
|
+
})
|
|
81
86
|
|
|
82
87
|
expect(increments).toMatchInlineSnapshot(`
|
|
83
88
|
[
|
|
84
89
|
{
|
|
85
|
-
"key": "ratelimit:public:anonymous",
|
|
90
|
+
"key": "ratelimit:second:1779667230:public:anonymous",
|
|
86
91
|
"options": {
|
|
87
|
-
"ttl":
|
|
92
|
+
"ttl": 750,
|
|
88
93
|
},
|
|
89
94
|
},
|
|
90
95
|
]
|
|
@@ -6,9 +6,15 @@ import * as Store from './Store.js'
|
|
|
6
6
|
export namespace schema {
|
|
7
7
|
/** A rate-limit policy; the source of truth for {@link Limit}. */
|
|
8
8
|
export const Limit = z.object({
|
|
9
|
-
|
|
9
|
+
limit: z
|
|
10
10
|
.number()
|
|
11
|
-
.check(z.int(), z.nonnegative(), z.describe('Number of requests allowed per
|
|
11
|
+
.check(z.int(), z.nonnegative(), z.describe('Number of requests allowed per period.')),
|
|
12
|
+
period: z
|
|
13
|
+
.union([z.literal('minute'), z.literal('second')])
|
|
14
|
+
.check(
|
|
15
|
+
z.describe('Fixed window used to enforce the request limit.'),
|
|
16
|
+
z.meta({ examples: ['minute'] }),
|
|
17
|
+
),
|
|
12
18
|
})
|
|
13
19
|
}
|
|
14
20
|
|
|
@@ -49,11 +55,8 @@ export namespace RateLimitStore {
|
|
|
49
55
|
* Each consume is a single `Store.increment`, so a backend with a native
|
|
50
56
|
* counter (e.g. a Durable Object) is one atomic round trip; backends without
|
|
51
57
|
* one fall back to get + put, acceptable for dev/in-process stores only.
|
|
52
|
-
* The storage key
|
|
53
|
-
*
|
|
54
|
-
* current minute, so the next window's first hit restarts at 1 and idle keys
|
|
55
|
-
* hold no storage. The sub-minute TTL rules out Cloudflare KV as a backend,
|
|
56
|
-
* which is unsuitable for enforced counters anyway.
|
|
58
|
+
* The storage key includes the fixed-window bucket, so backend latency cannot
|
|
59
|
+
* carry a previous count into a later window.
|
|
57
60
|
*/
|
|
58
61
|
export function memory(options: memory.Options = {}): RateLimitStore {
|
|
59
62
|
const store = options.store ?? Store.memory()
|
|
@@ -62,19 +65,24 @@ export function memory(options: memory.Options = {}): RateLimitStore {
|
|
|
62
65
|
return {
|
|
63
66
|
async consume(options) {
|
|
64
67
|
const time = now().getTime()
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
// window
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
const window = options.limit.period === 'second' ? 1_000 : 60_000
|
|
69
|
+
const bucket = Math.floor(time / window)
|
|
70
|
+
// The bucket isolates window counts if the backing store applies the TTL
|
|
71
|
+
// after the next window starts.
|
|
72
|
+
const count = await Store.increment(
|
|
73
|
+
store,
|
|
74
|
+
`ratelimit:${options.limit.period}:${bucket}:${options.key}`,
|
|
75
|
+
{
|
|
76
|
+
ttl: (bucket + 1) * window - time,
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
const limit = options.limit.limit
|
|
72
80
|
|
|
73
81
|
return {
|
|
74
82
|
allowed: count <= limit,
|
|
75
83
|
limit,
|
|
76
84
|
remaining: Math.max(limit - count, 0),
|
|
77
|
-
reset: Math.ceil(((bucket + 1) *
|
|
85
|
+
reset: Math.ceil(((bucket + 1) * window) / 1_000),
|
|
78
86
|
}
|
|
79
87
|
},
|
|
80
88
|
}
|
package/src/internal/Schema.ts
CHANGED
|
@@ -356,7 +356,7 @@ export const Denomination = z
|
|
|
356
356
|
.check(
|
|
357
357
|
z.describe(
|
|
358
358
|
'Currency to denominate values in (case-insensitive, e.g. `AUD`). ' +
|
|
359
|
-
'Must be priced by the configured FX oracle.
|
|
359
|
+
'Must be priced by the configured FX oracle.',
|
|
360
360
|
),
|
|
361
361
|
z.meta({ examples: ['AUD'] }),
|
|
362
362
|
)
|