tapimo 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +2296 -1010
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +2 -2
- package/dist/App.js.map +1 -1
- package/dist/Assets.d.ts +15 -0
- package/dist/Assets.d.ts.map +1 -1
- package/dist/Assets.js +7 -0
- package/dist/Assets.js.map +1 -1
- package/dist/admin/App.d.ts +53 -4
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +4 -4
- package/dist/admin/apps/earn-vaults.d.ts +114 -0
- package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.js +59 -6
- package/dist/admin/apps/earn-vaults.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +4 -2
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +9 -9
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +151 -151
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/data/App.d.ts +1699 -749
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +56 -7
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/ZoneSelection.d.ts +62 -0
- package/dist/apps/data/ZoneSelection.d.ts.map +1 -0
- package/dist/apps/data/ZoneSelection.js +44 -0
- package/dist/apps/data/ZoneSelection.js.map +1 -0
- package/dist/apps/data/routes/activities.d.ts +2367 -4
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +368 -87
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +471 -340
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +28 -28
- package/dist/apps/data/routes/balances.js +1 -1
- 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/blocks.js +1 -1
- package/dist/apps/data/routes/blocks.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/earn.d.ts +131 -6
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +157 -25
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +825 -119
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +16 -13
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +9 -11
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +34 -34
- package/dist/apps/data/routes/receipts.d.ts +230 -230
- package/dist/apps/data/routes/receipts.js +1 -1
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.js +24 -10
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
- package/dist/apps/data/routes/tokenlist.js +16 -15
- package/dist/apps/data/routes/tokenlist.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +14 -0
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +34 -15
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.js +1 -1
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +7 -9
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +4 -4
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +2 -2
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +30 -30
- package/dist/apps/funding/App.d.ts +27 -27
- package/dist/apps/funding/routes/chains.d.ts.map +1 -1
- package/dist/apps/funding/routes/chains.js +5 -4
- package/dist/apps/funding/routes/chains.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +16 -16
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +8 -8
- 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/providers.d.ts.map +1 -1
- package/dist/apps/funding/routes/providers.js +3 -2
- package/dist/apps/funding/routes/providers.js.map +1 -1
- package/dist/apps/funding/routes/quotes.d.ts.map +1 -1
- package/dist/apps/funding/routes/quotes.js +2 -2
- package/dist/apps/funding/routes/quotes.js.map +1 -1
- package/dist/apps/management/App.d.ts +627 -293
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +19 -6
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +114 -54
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +76 -30
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +103 -30
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +91 -32
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +33 -33
- package/dist/apps/management/routes/faucet.d.ts.map +1 -1
- package/dist/apps/management/routes/faucet.js +23 -12
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +46 -13
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +79 -29
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts +86 -41
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +100 -41
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +5 -5
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +14 -10
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +93 -72
- package/dist/apps/management/routes/members.d.ts.map +1 -1
- package/dist/apps/management/routes/members.js +37 -18
- package/dist/apps/management/routes/members.js.map +1 -1
- package/dist/apps/management/routes/orgs.d.ts +48 -8
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +48 -19
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +90 -28
- package/dist/apps/management/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js +72 -21
- package/dist/apps/management/routes/projects.js.map +1 -1
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +8 -5
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +78 -78
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +41 -31
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +34 -6
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.js +36 -17
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -1
- package/dist/apps/management/routes/webhooks.d.ts +147 -130
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/management/routes/webhooks.js +80 -35
- package/dist/apps/management/routes/webhooks.js.map +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Adapter.js +12 -0
- package/dist/apps/mpp/internal/Adapter.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 +40 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +23 -1
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +19 -3
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/earnVaults.d.ts +5 -1
- package/dist/db/tables/earnVaults.d.ts.map +1 -1
- package/dist/db/tables/earnVaults.js +5 -0
- package/dist/db/tables/earnVaults.js.map +1 -1
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
- package/dist/db/tables/webhookDeliveries.js +10 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -1
- package/dist/db/tables/webhookQueueCompletions.d.ts +15 -0
- package/dist/db/tables/webhookQueueCompletions.d.ts.map +1 -0
- package/dist/db/tables/webhookQueueCompletions.js +32 -0
- package/dist/db/tables/webhookQueueCompletions.js.map +1 -0
- package/dist/db/tables/webhookQueueEvents.d.ts +14 -20
- package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
- package/dist/db/tables/webhookQueueEvents.js +68 -47
- package/dist/db/tables/webhookQueueEvents.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +16 -5
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +29 -5
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +8 -1
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +13 -3
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.d.ts.map +1 -1
- package/dist/internal/Cache.js +11 -7
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EarnVaults.d.ts +41 -2
- package/dist/internal/EarnVaults.d.ts.map +1 -1
- package/dist/internal/EarnVaults.js +167 -0
- package/dist/internal/EarnVaults.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +26 -22
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +128 -39
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Response.d.ts +13 -8
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +16 -5
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Schema.d.ts +5 -7
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +6 -7
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Store.d.ts +15 -6
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +45 -15
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Tidx.d.ts +2 -0
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +16 -4
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +29 -12
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +37 -20
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/Deposit.d.ts +2 -2
- package/dist/internal/funding/Deposit.d.ts.map +1 -1
- package/dist/internal/funding/Deposit.js +9 -8
- package/dist/internal/funding/Deposit.js.map +1 -1
- package/dist/internal/funding/DepositAddress.d.ts +4 -4
- package/dist/internal/funding/DepositAddress.d.ts.map +1 -1
- package/dist/internal/funding/DepositAddress.js +9 -8
- package/dist/internal/funding/DepositAddress.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +13 -12
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/providers/stargate.d.ts +19 -0
- package/dist/internal/funding/providers/stargate.d.ts.map +1 -0
- package/dist/internal/funding/providers/stargate.js +143 -0
- package/dist/internal/funding/providers/stargate.js.map +1 -0
- package/dist/providers/index.d.ts +7 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +3 -0
- package/dist/providers/index.js.map +1 -1
- package/package.json +2 -2
- package/src/App.test.ts +48 -0
- package/src/App.ts +4 -2
- package/src/Assets.test.ts +18 -0
- package/src/Assets.ts +22 -0
- package/src/Client.test-d.ts +265 -16
- package/src/admin/apps/earn-vaults.test.ts +143 -0
- package/src/admin/apps/earn-vaults.ts +83 -6
- package/src/admin/apps/organizations.ts +6 -2
- package/src/admin/ui/src/routes/api-keys.tsx +36 -0
- package/src/admin/ui.generated.ts +151 -189
- package/src/apps/data/App.test.ts +31 -0
- package/src/apps/data/App.ts +56 -8
- package/src/apps/data/ZoneSelection.test.ts +196 -0
- package/src/apps/data/ZoneSelection.ts +108 -0
- package/src/apps/data/routes/activities.test.ts +328 -1
- package/src/apps/data/routes/activities.ts +498 -93
- package/src/apps/data/routes/balances.ts +1 -1
- package/src/apps/data/routes/blocks.ts +1 -1
- package/src/apps/data/routes/earn.test.ts +183 -23
- package/src/apps/data/routes/earn.ts +225 -29
- package/src/apps/data/routes/earn.zone-access.test.ts +46 -0
- package/src/apps/data/routes/exchanges.test.ts +31 -1
- package/src/apps/data/routes/exchanges.ts +19 -15
- package/src/apps/data/routes/fee-amm.ts +18 -11
- package/src/apps/data/routes/receipts.ts +1 -1
- package/src/apps/data/routes/rpc.test.ts +75 -0
- package/src/apps/data/routes/rpc.ts +23 -9
- package/src/apps/data/routes/tokenlist.ts +21 -18
- package/src/apps/data/routes/tokens.test.ts +29 -0
- package/src/apps/data/routes/tokens.ts +38 -15
- package/src/apps/data/routes/transactions.ts +1 -1
- package/src/apps/data/routes/transfers.test.ts +3 -3
- package/src/apps/data/routes/transfers.ts +12 -9
- package/src/apps/data/routes/webhooks.ts +6 -2
- package/src/apps/data/routes/zones.test.ts +19 -13
- package/src/apps/funding/routes/chains.test.ts +211 -7
- package/src/apps/funding/routes/chains.ts +19 -12
- package/src/apps/funding/routes/deposit-addresses.test.ts +394 -0
- package/src/apps/funding/routes/deposit-addresses.ts +84 -72
- package/src/apps/funding/routes/providers.ts +9 -5
- package/src/apps/funding/routes/quotes.test.ts +46 -0
- package/src/apps/funding/routes/quotes.ts +8 -5
- package/src/apps/funding/routes/transfers.test.ts +35 -32
- package/src/apps/management/Billing.test.ts +26 -0
- package/src/apps/management/Billing.ts +24 -7
- package/src/apps/management/routes/api-keys.test.ts +178 -0
- package/src/apps/management/routes/api-keys.ts +203 -144
- package/src/apps/management/routes/billing.test.ts +209 -0
- package/src/apps/management/routes/billing.ts +254 -177
- package/src/apps/management/routes/faucet.test.ts +39 -0
- package/src/apps/management/routes/faucet.ts +75 -41
- package/src/apps/management/routes/invitations.test.ts +216 -0
- package/src/apps/management/routes/invitations.ts +186 -109
- package/src/apps/management/routes/invite-links.test.ts +263 -1
- package/src/apps/management/routes/invite-links.ts +295 -214
- package/src/apps/management/routes/me.test.ts +49 -0
- package/src/apps/management/routes/me.ts +42 -32
- package/src/apps/management/routes/members.test.ts +124 -1
- package/src/apps/management/routes/members.ts +81 -51
- package/src/apps/management/routes/orgs.test.ts +176 -0
- package/src/apps/management/routes/orgs.ts +101 -59
- package/src/apps/management/routes/projects.test.ts +183 -0
- package/src/apps/management/routes/projects.ts +124 -60
- package/src/apps/management/routes/scopes.test.ts +44 -0
- package/src/apps/management/routes/scopes.ts +37 -28
- package/src/apps/management/routes/usage.test.ts +92 -0
- package/src/apps/management/routes/usage.ts +186 -151
- package/src/apps/management/routes/verified-token-requests.test.ts +114 -0
- package/src/apps/management/routes/verified-token-requests.ts +166 -133
- package/src/apps/management/routes/webhooks.test.ts +234 -0
- package/src/apps/management/routes/webhooks.ts +191 -78
- package/src/apps/mpp/internal/Adapter.ts +13 -1
- package/src/db/Db.test.ts +41 -0
- package/src/db/Db.ts +62 -0
- package/src/db/Schema.ts +28 -3
- package/src/db/tables/earnVaults.test.ts +22 -0
- package/src/db/tables/earnVaults.ts +11 -1
- package/src/db/tables/webhookDeliveries.ts +13 -0
- package/src/db/tables/webhookQueueCompletions.ts +55 -0
- package/src/db/tables/webhookQueueEvents.ts +95 -61
- package/src/db/tables/webhookSubscriptions.ts +45 -6
- package/src/internal/Auth.ts +23 -5
- package/src/internal/Cache.ts +14 -7
- package/src/internal/EarnVaults.capabilities.test.ts +33 -0
- package/src/internal/EarnVaults.test.ts +79 -0
- package/src/internal/EarnVaults.ts +277 -0
- package/src/internal/OpenApi.ts +165 -49
- package/src/internal/Response.test.ts +95 -0
- package/src/internal/Response.ts +40 -2
- package/src/internal/Schema.test.ts +16 -0
- package/src/internal/Schema.ts +7 -7
- package/src/internal/Store.test.ts +186 -0
- package/src/internal/Store.ts +57 -17
- package/src/internal/Tidx.test.ts +19 -0
- package/src/internal/Tidx.ts +16 -4
- package/src/internal/Webhooks.test.ts +122 -8
- package/src/internal/Webhooks.ts +59 -29
- package/src/internal/funding/Deposit.ts +71 -64
- package/src/internal/funding/DepositAddress.ts +50 -43
- package/src/internal/funding/Provider.ts +164 -142
- package/src/internal/funding/providers/index.test.ts +185 -0
- package/src/internal/funding/providers/stargate.ts +184 -0
- package/src/providers/index.test.ts +1 -0
- package/src/providers/index.ts +9 -0
|
@@ -225,6 +225,10 @@ export async function get(
|
|
|
225
225
|
return row ? toDelivery(row) : null
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// Per-statement delete bound: prune runs every minute, so a backlog drains in
|
|
229
|
+
// a few ticks instead of one unbounded DELETE churning the origin.
|
|
230
|
+
const pruneBatchLimit = 50_000
|
|
231
|
+
|
|
228
232
|
/**
|
|
229
233
|
* Deletes deliveries past their retention deadline. Called best-effort from
|
|
230
234
|
* the poller tick.
|
|
@@ -236,7 +240,16 @@ export async function get(
|
|
|
236
240
|
export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
|
|
237
241
|
const result = await db.kysely
|
|
238
242
|
.deleteFrom('webhook_deliveries')
|
|
243
|
+
// Repeated on the outer delete: the row recheck under concurrency re-runs
|
|
244
|
+
// only this predicate, so a row renewed mid-statement survives.
|
|
239
245
|
.where('expiresAt', '<=', now)
|
|
246
|
+
.where('id', 'in', (eb) =>
|
|
247
|
+
eb
|
|
248
|
+
.selectFrom('webhook_deliveries')
|
|
249
|
+
.select('id')
|
|
250
|
+
.where('expiresAt', '<=', now)
|
|
251
|
+
.limit(pruneBatchLimit),
|
|
252
|
+
)
|
|
240
253
|
.executeTakeFirst()
|
|
241
254
|
return Number(result.numDeletedRows)
|
|
242
255
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type * as Webhooks from '../../internal/Webhooks.js'
|
|
2
|
+
import type * as Db from '../Db.js'
|
|
3
|
+
import type * as db_Schema from '../Schema.js'
|
|
4
|
+
|
|
5
|
+
/** Columns of the `webhook_queue_completions` table, derived from `Schema.WebhookQueueCompletion`. */
|
|
6
|
+
export type Table = db_Schema.WebhookQueueCompletion
|
|
7
|
+
|
|
8
|
+
/** A terminal delivery-obligation outcome. */
|
|
9
|
+
export type TerminalStatus = db_Schema.WebhookQueueCompletion['status']
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Reads the unexpired completion for a reference, if any. Rows are written by
|
|
13
|
+
* the queue-event terminal transition and only ever read for dedupe.
|
|
14
|
+
*/
|
|
15
|
+
export async function get(
|
|
16
|
+
db: Db.Db,
|
|
17
|
+
reference: Webhooks.QueueReference,
|
|
18
|
+
now: string,
|
|
19
|
+
): Promise<Table | null> {
|
|
20
|
+
return (
|
|
21
|
+
(await db.kysely
|
|
22
|
+
.selectFrom('webhook_queue_completions')
|
|
23
|
+
.selectAll()
|
|
24
|
+
.where('eventId', '=', reference.eventId)
|
|
25
|
+
.where('subscriptionId', '=', reference.subscriptionId)
|
|
26
|
+
.where('expiresAt', '>', now)
|
|
27
|
+
.executeTakeFirst()) ?? null
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Per-statement delete bound: prune runs every minute, so a backlog drains in
|
|
32
|
+
// a few ticks instead of one unbounded DELETE churning the origin.
|
|
33
|
+
const pruneBatchLimit = 50_000
|
|
34
|
+
|
|
35
|
+
/** Deletes expired completions, bounded per call; expiry is filtered at query time, so pruning is space-only. */
|
|
36
|
+
export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
|
|
37
|
+
const result = await db.kysely
|
|
38
|
+
.deleteFrom('webhook_queue_completions')
|
|
39
|
+
// Repeated on the outer delete: the row recheck under concurrency re-runs
|
|
40
|
+
// only this predicate, so a completion renewed mid-statement survives.
|
|
41
|
+
.where('expiresAt', '<=', now)
|
|
42
|
+
.where(({ eb, refTuple, selectFrom }) =>
|
|
43
|
+
eb(
|
|
44
|
+
refTuple('subscriptionId', 'eventId'),
|
|
45
|
+
'in',
|
|
46
|
+
selectFrom('webhook_queue_completions')
|
|
47
|
+
.select(['subscriptionId', 'eventId'])
|
|
48
|
+
.where('expiresAt', '<=', now)
|
|
49
|
+
.limit(pruneBatchLimit)
|
|
50
|
+
.$asTuple('subscriptionId', 'eventId'),
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
.executeTakeFirst()
|
|
54
|
+
return Number(result.numDeletedRows)
|
|
55
|
+
}
|
|
@@ -2,17 +2,20 @@ import { type ExpressionBuilder, type Generated, type Insertable, type Selectabl
|
|
|
2
2
|
|
|
3
3
|
import type * as Webhooks from '../../internal/Webhooks.js'
|
|
4
4
|
import type * as Db from '../Db.js'
|
|
5
|
+
import * as WebhookQueueCompletions from './webhookQueueCompletions.js'
|
|
5
6
|
import type * as db_Schema from '../Schema.js'
|
|
6
7
|
|
|
7
8
|
/** Columns of the `webhook_queue_events` table, derived from `Schema.WebhookQueueEvent`. */
|
|
8
9
|
export type Table = Omit<
|
|
9
10
|
db_Schema.WebhookQueueEvent,
|
|
10
|
-
'attemptCount' | 'attemptingAt' | 'nextAttemptAt' | 'observedAt' | 'status'
|
|
11
|
+
'attemptCount' | 'attemptingAt' | 'expiresAt' | 'nextAttemptAt' | 'observedAt' | 'status'
|
|
11
12
|
> & {
|
|
12
13
|
/** Delivery attempts claimed so far; database default 0. */
|
|
13
14
|
attemptCount: Generated<number>
|
|
14
15
|
/** ISO timestamp of the current claim; database default null. */
|
|
15
16
|
attemptingAt: Generated<string | null>
|
|
17
|
+
/** Legacy dedupe deadline; unwritten since completions took over. */
|
|
18
|
+
expiresAt: Generated<string | null>
|
|
16
19
|
/** ISO retry due time; database default null (due immediately). */
|
|
17
20
|
nextAttemptAt: Generated<string | null>
|
|
18
21
|
/** ISO head-observation stamp; database default null (replays carry none). */
|
|
@@ -24,12 +27,9 @@ export type Table = Omit<
|
|
|
24
27
|
/** A stored webhook Queue event. */
|
|
25
28
|
export type Record = Selectable<Table>
|
|
26
29
|
|
|
27
|
-
/** A terminal delivery-obligation outcome. */
|
|
28
|
-
export type TerminalStatus = Exclude<db_Schema.WebhookQueueEvent['status'], 'pending'>
|
|
29
|
-
|
|
30
30
|
/**
|
|
31
|
-
* Reads one pending envelope.
|
|
32
|
-
*
|
|
31
|
+
* Reads one pending envelope. The status filter only ever excludes rows
|
|
32
|
+
* completed before the completions split; completions now leave the table.
|
|
33
33
|
*/
|
|
34
34
|
export async function get(
|
|
35
35
|
db: Db.Db,
|
|
@@ -47,42 +47,56 @@ export async function get(
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* Stages envelopes as fresh obligations.
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* dedupe row pruning has not yet reclaimed never blocks a replay.
|
|
50
|
+
* Stages envelopes as fresh obligations. Unexpired completions and any
|
|
51
|
+
* existing row suppress the replay; a replay stages only when neither exists,
|
|
52
|
+
* which a completion past its dedupe window guarantees for completed work.
|
|
54
53
|
*/
|
|
55
54
|
export async function ensure(db: Db.Db, rows: readonly Insertable<Table>[]): Promise<number> {
|
|
56
55
|
if (rows.length === 0) return 0
|
|
56
|
+
const values = sql.join(
|
|
57
|
+
rows.map(
|
|
58
|
+
(row) =>
|
|
59
|
+
sql`(${row.subscriptionId}::text, ${row.eventId}::text, ${JSON.stringify(row.envelope)}::jsonb, ${row.createdAt}::text, ${row.nextAttemptAt ?? null}::text, ${row.observedAt ?? null}::text)`,
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
// The VALUES source is raw because kysely has no table constructor for it,
|
|
63
|
+
// but both real tables stay in the builder so WithSchemaPlugin qualifies
|
|
64
|
+
// them (previews isolate by schema; raw identifiers would escape it). The
|
|
65
|
+
// completion check must ride the insert: a separate read would let a
|
|
66
|
+
// completion commit between the statements and re-stage just-finished work.
|
|
67
|
+
// `column1..6` are Postgres's default VALUES names, in the row order above;
|
|
68
|
+
// the window comparison uses each row's own staging time, not a shared clock.
|
|
69
|
+
// Conflicts only ever hit pending rows (or pre-split terminal rows during
|
|
70
|
+
// the drain), and both suppress the replay; a legitimate post-expiry replay
|
|
71
|
+
// meets no conflict because its completed row was deleted.
|
|
57
72
|
const result = await db.kysely
|
|
58
73
|
.insertInto('webhook_queue_events')
|
|
59
|
-
.
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
.
|
|
63
|
-
.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
.columns(['subscriptionId', 'eventId', 'envelope', 'createdAt', 'nextAttemptAt', 'observedAt'])
|
|
75
|
+
.expression((eb) =>
|
|
76
|
+
eb
|
|
77
|
+
.selectFrom(sql`(VALUES ${values})`.as('v'))
|
|
78
|
+
.select([
|
|
79
|
+
sql`v.column1`.as('subscriptionId'),
|
|
80
|
+
sql`v.column2`.as('eventId'),
|
|
81
|
+
sql`v.column3`.as('envelope'),
|
|
82
|
+
sql`v.column4`.as('createdAt'),
|
|
83
|
+
sql`v.column5`.as('nextAttemptAt'),
|
|
84
|
+
sql`v.column6`.as('observedAt'),
|
|
85
|
+
])
|
|
86
|
+
.where(({ exists, not, selectFrom }) =>
|
|
87
|
+
not(
|
|
88
|
+
exists(
|
|
89
|
+
selectFrom('webhook_queue_completions')
|
|
90
|
+
.select('eventId')
|
|
91
|
+
.where('subscriptionId', '=', sql<string>`v.column1`)
|
|
92
|
+
.where('eventId', '=', sql<string>`v.column2`)
|
|
93
|
+
.where('expiresAt', '>', sql<string>`v.column4`),
|
|
94
|
+
),
|
|
95
|
+
),
|
|
81
96
|
),
|
|
82
97
|
)
|
|
98
|
+
.onConflict((oc) => oc.columns(['subscriptionId', 'eventId']).doNothing())
|
|
83
99
|
.executeTakeFirst()
|
|
84
|
-
// The command tag counts inserted rows plus replaced expired rows; live
|
|
85
|
-
// conflicts fall out of the conditional update and are not counted.
|
|
86
100
|
return Number(result.numInsertedOrUpdatedRows ?? 0n)
|
|
87
101
|
}
|
|
88
102
|
|
|
@@ -114,13 +128,23 @@ export async function claim(
|
|
|
114
128
|
.executeTakeFirst()
|
|
115
129
|
if (claimed) return { record: claimed, type: 'claimed' }
|
|
116
130
|
|
|
131
|
+
// Queue first, completions second: a complete() landing between the reads
|
|
132
|
+
// then still surfaces its completion, so a finished obligation never reads
|
|
133
|
+
// as missing, the one claim outcome alerts treat as a fault.
|
|
117
134
|
const existing = await db.kysely
|
|
118
135
|
.selectFrom('webhook_queue_events')
|
|
119
136
|
.select(['attemptingAt', 'nextAttemptAt', 'status'])
|
|
120
137
|
.where('subscriptionId', '=', reference.subscriptionId)
|
|
121
138
|
.where('eventId', '=', reference.eventId)
|
|
122
139
|
.executeTakeFirst()
|
|
123
|
-
if (!existing)
|
|
140
|
+
if (!existing) {
|
|
141
|
+
// An unexpired completion reads as terminal so claim-outcome metrics keep
|
|
142
|
+
// their meaning; an expired one reads as missing, like a pruned row.
|
|
143
|
+
const completed = await WebhookQueueCompletions.get(db, reference, options.now)
|
|
144
|
+
if (completed) return { status: completed.status, type: 'terminal' }
|
|
145
|
+
return { type: 'missing' }
|
|
146
|
+
}
|
|
147
|
+
// Reachable only for rows completed before the completions split.
|
|
124
148
|
if (existing.status !== 'pending') return { status: existing.status, type: 'terminal' }
|
|
125
149
|
if (existing.attemptingAt !== null && existing.attemptingAt > options.staleBefore)
|
|
126
150
|
return { type: 'attempting' }
|
|
@@ -142,12 +166,12 @@ export declare namespace claim {
|
|
|
142
166
|
| { record: Record; type: 'claimed' }
|
|
143
167
|
| { type: 'missing' }
|
|
144
168
|
| { nextAttemptAt: string | null; type: 'scheduled' }
|
|
145
|
-
| { status: TerminalStatus; type: 'terminal' }
|
|
169
|
+
| { status: WebhookQueueCompletions.TerminalStatus; type: 'terminal' }
|
|
146
170
|
}
|
|
147
171
|
|
|
148
172
|
/**
|
|
149
|
-
* Marks a claimed obligation terminal
|
|
150
|
-
*
|
|
173
|
+
* Marks a claimed obligation terminal: one atomic statement deletes the queue
|
|
174
|
+
* row and writes the insert-only completion that starts the dedupe window.
|
|
151
175
|
* The `attemptingAt` predicate fences stale claimants.
|
|
152
176
|
*/
|
|
153
177
|
export async function complete(
|
|
@@ -156,14 +180,38 @@ export async function complete(
|
|
|
156
180
|
options: complete.Options,
|
|
157
181
|
): Promise<boolean> {
|
|
158
182
|
const result = await db.kysely
|
|
159
|
-
.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
183
|
+
.with('done', (qb) =>
|
|
184
|
+
qb
|
|
185
|
+
.deleteFrom('webhook_queue_events')
|
|
186
|
+
.where('subscriptionId', '=', reference.subscriptionId)
|
|
187
|
+
.where('eventId', '=', reference.eventId)
|
|
188
|
+
.where('status', '=', 'pending')
|
|
189
|
+
.where('attemptingAt', '=', options.claimedAt)
|
|
190
|
+
.returning(['subscriptionId', 'eventId']),
|
|
191
|
+
)
|
|
192
|
+
.insertInto('webhook_queue_completions')
|
|
193
|
+
.columns(['subscriptionId', 'eventId', 'status', 'expiresAt'])
|
|
194
|
+
.expression((eb) =>
|
|
195
|
+
eb
|
|
196
|
+
.selectFrom('done')
|
|
197
|
+
.select((s) => [
|
|
198
|
+
'subscriptionId',
|
|
199
|
+
'eventId',
|
|
200
|
+
s.val(options.status).as('status'),
|
|
201
|
+
s.val(options.expiresAt).as('expiresAt'),
|
|
202
|
+
]),
|
|
203
|
+
)
|
|
204
|
+
// Re-completing a replayed obligation: the latest delivery outcome wins
|
|
205
|
+
// and retention restarts, matching pre-split replay semantics; doNothing
|
|
206
|
+
// would also report zero rows and fail a fence that won.
|
|
207
|
+
.onConflict((oc) =>
|
|
208
|
+
oc.columns(['subscriptionId', 'eventId']).doUpdateSet((eb) => ({
|
|
209
|
+
expiresAt: eb.ref('excluded.expiresAt'),
|
|
210
|
+
status: eb.ref('excluded.status'),
|
|
211
|
+
})),
|
|
212
|
+
)
|
|
165
213
|
.executeTakeFirst()
|
|
166
|
-
return result.
|
|
214
|
+
return result.numInsertedOrUpdatedRows === 1n
|
|
167
215
|
}
|
|
168
216
|
|
|
169
217
|
export declare namespace complete {
|
|
@@ -171,10 +219,10 @@ export declare namespace complete {
|
|
|
171
219
|
type Options = {
|
|
172
220
|
/** Claim marker returned by {@link claim}; fences stale claimants. */
|
|
173
221
|
claimedAt: string
|
|
174
|
-
/**
|
|
222
|
+
/** ISO retention deadline for the dedupe completion. */
|
|
175
223
|
expiresAt: string
|
|
176
224
|
/** Terminal outcome. */
|
|
177
|
-
status: TerminalStatus
|
|
225
|
+
status: WebhookQueueCompletions.TerminalStatus
|
|
178
226
|
}
|
|
179
227
|
}
|
|
180
228
|
|
|
@@ -275,17 +323,3 @@ export declare namespace pending {
|
|
|
275
323
|
oldestDueAt: string | null
|
|
276
324
|
}
|
|
277
325
|
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Deletes terminal dedupe rows past their retention deadline. Pending rows
|
|
281
|
-
* never expire: owed work drains only through terminal transitions
|
|
282
|
-
* (delivery, permanent failure, or the skip after auto-disable).
|
|
283
|
-
*/
|
|
284
|
-
export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
|
|
285
|
-
const result = await db.kysely
|
|
286
|
-
.deleteFrom('webhook_queue_events')
|
|
287
|
-
.where('expiresAt', '<=', now)
|
|
288
|
-
.where('status', '!=', 'pending')
|
|
289
|
-
.executeTakeFirst()
|
|
290
|
-
return Number(result.numDeletedRows)
|
|
291
|
-
}
|
|
@@ -447,37 +447,57 @@ export async function getCursor(db: Db.Db, id: string): Promise<string | null> {
|
|
|
447
447
|
|
|
448
448
|
/**
|
|
449
449
|
* Records a successful delivery: resets the consecutive-failure counter and
|
|
450
|
-
* stamps `lastDeliveryAt`.
|
|
450
|
+
* stamps `lastDeliveryAt`. Skipped when neither field would move, so
|
|
451
|
+
* deliveries stop contending on the row's lock.
|
|
451
452
|
*
|
|
452
453
|
* @param db - The database.
|
|
453
454
|
* @param id - The subscription id (`wh_…`).
|
|
454
|
-
* @param
|
|
455
|
-
* @returns The updated subscription, or `undefined` when
|
|
455
|
+
* @param options - Delivery timestamp and stamp-staleness bound.
|
|
456
|
+
* @returns The updated subscription, or `undefined` when nothing was written.
|
|
456
457
|
*/
|
|
457
458
|
export async function recordSuccess(
|
|
458
459
|
db: Db.Db,
|
|
459
460
|
id: string,
|
|
460
|
-
|
|
461
|
+
options: recordSuccess.Options,
|
|
461
462
|
): Promise<Webhooks.Subscription | undefined> {
|
|
463
|
+
const { now, staleBefore } = options
|
|
462
464
|
const row = await db.kysely
|
|
463
465
|
.updateTable('webhook_subscriptions')
|
|
464
466
|
.set({ failureCount: 0, lastDeliveryAt: now, updatedAt: now })
|
|
465
467
|
.where('id', '=', id)
|
|
468
|
+
.where((eb) =>
|
|
469
|
+
eb.or([
|
|
470
|
+
eb('failureCount', '>', 0),
|
|
471
|
+
eb('lastDeliveryAt', 'is', null),
|
|
472
|
+
eb('lastDeliveryAt', '<', staleBefore),
|
|
473
|
+
]),
|
|
474
|
+
)
|
|
466
475
|
.returningAll()
|
|
467
476
|
.executeTakeFirst()
|
|
468
477
|
return row ? toSubscription(row) : undefined
|
|
469
478
|
}
|
|
470
479
|
|
|
480
|
+
export declare namespace recordSuccess {
|
|
481
|
+
/** Clock bounds for one success record. */
|
|
482
|
+
type Options = {
|
|
483
|
+
/** Delivery timestamp (ISO). */
|
|
484
|
+
now: string
|
|
485
|
+
/** Rewrite the stamp only when it predates this (ISO). */
|
|
486
|
+
staleBefore: string
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
471
490
|
/**
|
|
472
491
|
* Records a failed delivery: atomically increments the consecutive-failure
|
|
473
492
|
* counter and disables the subscription once it reaches `maxFailures` (raw
|
|
474
493
|
* fragments use storage column names — they bypass the camelCase mapping).
|
|
494
|
+
* Skipped once disabled at the cap, when nothing would move.
|
|
475
495
|
*
|
|
476
496
|
* @param db - The database.
|
|
477
497
|
* @param id - The subscription id (`wh_…`).
|
|
478
498
|
* @param maxFailures - Consecutive failures before auto-disabling.
|
|
479
499
|
* @param now - Failure timestamp (ISO).
|
|
480
|
-
* @returns The updated subscription, or `undefined` when
|
|
500
|
+
* @returns The updated subscription, or `undefined` when nothing was written.
|
|
481
501
|
*/
|
|
482
502
|
export async function recordFailure(
|
|
483
503
|
db: Db.Db,
|
|
@@ -493,11 +513,19 @@ export async function recordFailure(
|
|
|
493
513
|
updatedAt: now,
|
|
494
514
|
})
|
|
495
515
|
.where('id', '=', id)
|
|
516
|
+
// In-flight failures race the disable and would queue on the row lock. A
|
|
517
|
+
// re-enabled subscription keeps its capped counter; the status arm lets
|
|
518
|
+
// its next failure re-disable it.
|
|
519
|
+
.where((eb) => eb.or([eb('failureCount', '<', maxFailures), eb('status', '!=', 'disabled')]))
|
|
496
520
|
.returningAll()
|
|
497
521
|
.executeTakeFirst()
|
|
498
522
|
return row ? toSubscription(row) : undefined
|
|
499
523
|
}
|
|
500
524
|
|
|
525
|
+
// Smaller than the ledger-table bounds: each subscription delete cascades
|
|
526
|
+
// into deliveries, queue events, and completions, fanning the statement out.
|
|
527
|
+
const pruneBatchLimit = 1_000
|
|
528
|
+
|
|
501
529
|
/**
|
|
502
530
|
* Deletes expired subscriptions (their delivery rows cascade). Called
|
|
503
531
|
* best-effort on a schedule.
|
|
@@ -507,10 +535,21 @@ export async function recordFailure(
|
|
|
507
535
|
* @returns The number of rows deleted.
|
|
508
536
|
*/
|
|
509
537
|
export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
|
|
538
|
+
// Bounded like the ledger prunes: this delete cascades into deliveries,
|
|
539
|
+
// queue events, and completions, so it fans out the widest of the four.
|
|
510
540
|
const result = await db.kysely
|
|
511
541
|
.deleteFrom('webhook_subscriptions')
|
|
512
|
-
|
|
542
|
+
// Repeated on the outer delete: the row recheck under concurrency re-runs
|
|
543
|
+
// only this predicate, so a subscription renewed mid-statement survives.
|
|
513
544
|
.where('expiresAt', '<=', now)
|
|
545
|
+
.where('id', 'in', (eb) =>
|
|
546
|
+
eb
|
|
547
|
+
.selectFrom('webhook_subscriptions')
|
|
548
|
+
.select('id')
|
|
549
|
+
.where('expiresAt', 'is not', null)
|
|
550
|
+
.where('expiresAt', '<=', now)
|
|
551
|
+
.limit(pruneBatchLimit),
|
|
552
|
+
)
|
|
514
553
|
.executeTakeFirst()
|
|
515
554
|
return Number(result.numDeletedRows)
|
|
516
555
|
}
|
package/src/internal/Auth.ts
CHANGED
|
@@ -587,11 +587,21 @@ export function middleware<
|
|
|
587
587
|
const overrides = options.overrides as
|
|
588
588
|
| Record<string, PolicyOverride | false | undefined>
|
|
589
589
|
| undefined
|
|
590
|
-
const
|
|
590
|
+
const routePolicies = policies(c)
|
|
591
|
+
const configuredOverride = (() => {
|
|
592
|
+
const configured = overrides?.[key]
|
|
593
|
+
if (configured !== undefined) return configured
|
|
594
|
+
const inheritedMethod = routePolicies.find(
|
|
595
|
+
(policy) => policy.inheritOverridesFrom,
|
|
596
|
+
)?.inheritOverridesFrom
|
|
597
|
+
if (inheritedMethod === undefined) return undefined
|
|
598
|
+
const inheritedKey = `${inheritedMethod.toUpperCase()} ${key.slice(key.indexOf(' ') + 1)}`
|
|
599
|
+
return overrides?.[inheritedKey]
|
|
600
|
+
})()
|
|
591
601
|
const routeOverride =
|
|
592
602
|
management && configuredOverride ? { mpp: configuredOverride.mpp } : configuredOverride
|
|
593
603
|
const endpointPolicy = bindMppScope(
|
|
594
|
-
resolvePolicy(base, defaults, [...
|
|
604
|
+
resolvePolicy(base, defaults, [...routePolicies, routeOverride], description),
|
|
595
605
|
scope,
|
|
596
606
|
)
|
|
597
607
|
if (!endpointPolicy) return next()
|
|
@@ -1000,12 +1010,20 @@ export declare namespace install {
|
|
|
1000
1010
|
}
|
|
1001
1011
|
|
|
1002
1012
|
/** Creates route middleware that contributes an auth policy override. */
|
|
1003
|
-
export function policy(options:
|
|
1013
|
+
export function policy(options: policy.Options): MiddlewareHandler {
|
|
1004
1014
|
const middleware = createMiddleware((_, next) => next()) as MiddlewareHandler & PolicyHandler
|
|
1005
1015
|
middleware[policySymbol] = options
|
|
1006
1016
|
return middleware
|
|
1007
1017
|
}
|
|
1008
1018
|
|
|
1019
|
+
export declare namespace policy {
|
|
1020
|
+
/** Route policy plus metadata for compatibility aliases. */
|
|
1021
|
+
type Options = PolicyOverride & {
|
|
1022
|
+
/** HTTP method whose configured override this route inherits when its own is absent. */
|
|
1023
|
+
inheritOverridesFrom?: string | undefined
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1009
1027
|
/** Adds management POSTs for OpenAPI-registered MPP GET resources. */
|
|
1010
1028
|
export function installMppManagementRoutes(
|
|
1011
1029
|
app: Hono<any, any>,
|
|
@@ -1106,7 +1124,7 @@ type OpenApiRoute = {
|
|
|
1106
1124
|
}
|
|
1107
1125
|
|
|
1108
1126
|
type PolicyHandler = {
|
|
1109
|
-
[policySymbol]?:
|
|
1127
|
+
[policySymbol]?: policy.Options | undefined
|
|
1110
1128
|
}
|
|
1111
1129
|
|
|
1112
1130
|
type MppManagementHandler = {
|
|
@@ -1283,7 +1301,7 @@ function policies(c: hono_Context) {
|
|
|
1283
1301
|
return matchedRoutes(c)
|
|
1284
1302
|
.slice(c.req.routeIndex + 1)
|
|
1285
1303
|
.map(policyFromRoute)
|
|
1286
|
-
.filter((
|
|
1304
|
+
.filter((routePolicy): routePolicy is policy.Options => routePolicy !== undefined)
|
|
1287
1305
|
}
|
|
1288
1306
|
|
|
1289
1307
|
function openApiSpec(route: RouterRoute) {
|
package/src/internal/Cache.ts
CHANGED
|
@@ -58,10 +58,8 @@ export function response<environment extends Env>(
|
|
|
58
58
|
): MiddlewareHandler<environment> {
|
|
59
59
|
const defaultCacheControl = options.cacheControl ?? policies.default
|
|
60
60
|
const vary = options.vary ?? ['Accept-Encoding', 'Authorization', 'Tempo-API-Key', 'X-API-Key']
|
|
61
|
-
// hono/cache
|
|
62
|
-
//
|
|
63
|
-
// normalization so the `no-cache` purge below deletes exactly the entry the
|
|
64
|
-
// wrapped middleware reads — drift here would make refreshes silently miss.
|
|
61
|
+
// Mirror hono/cache's vary normalization so the `no-cache` purge below
|
|
62
|
+
// deletes exactly the entry the wrapped middleware reads.
|
|
65
63
|
const varyKeyDirectives = [
|
|
66
64
|
...new Set(vary.map((name) => name.trim().toLowerCase()).filter(Boolean)),
|
|
67
65
|
]
|
|
@@ -95,10 +93,19 @@ export function response<environment extends Env>(
|
|
|
95
93
|
type Caches = { open(name: string): Promise<{ delete(key: string): Promise<unknown> }> }
|
|
96
94
|
const caches = (globalThis as { caches?: Caches }).caches
|
|
97
95
|
if (noCache && !noStore && caches) {
|
|
98
|
-
|
|
96
|
+
// hono/cache 4.13 wraps generated keys in a canonical URL containing the
|
|
97
|
+
// request method and vary values. Keep this in sync with its
|
|
98
|
+
// `createCacheKey` so refreshes remove the stored GET response.
|
|
99
|
+
const key = await options.key(c)
|
|
100
|
+
const url = new URL('/.hono/cache', c.req.url)
|
|
101
|
+
url.searchParams.append('__hono_cache_key', key.split('#', 1)[0] ?? key)
|
|
102
|
+
url.searchParams.append('__hono_cache_method', c.req.method)
|
|
99
103
|
for (const name of varyKeyDirectives)
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
url.searchParams.append(
|
|
105
|
+
'__hono_cache_vary',
|
|
106
|
+
JSON.stringify([name, c.req.raw.headers.get(name) ?? '']),
|
|
107
|
+
)
|
|
108
|
+
await (await caches.open(options.name)).delete(url.href)
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
// Run ETag inside Hono's cache miss path so fresh responses are hashed
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as EarnVaults from './EarnVaults.js'
|
|
2
|
+
|
|
3
|
+
describe('addZoneRouteCapabilities', () => {
|
|
4
|
+
test('adds private routing and router swaps', () => {
|
|
5
|
+
expect(
|
|
6
|
+
EarnVaults.addZoneRouteCapabilities({
|
|
7
|
+
asyncRedeem: false,
|
|
8
|
+
boundedRedeem: true,
|
|
9
|
+
deposit: true,
|
|
10
|
+
exactWithdraw: true,
|
|
11
|
+
inKindDeposit: true,
|
|
12
|
+
privateRouting: false,
|
|
13
|
+
redeem: true,
|
|
14
|
+
routerSwaps: false,
|
|
15
|
+
}),
|
|
16
|
+
).toMatchObject({ privateRouting: true, routerSwaps: true })
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('deriveZoneId', () => {
|
|
21
|
+
test('derives IDs from mainnet and testnet Zone chain schemes', () => {
|
|
22
|
+
expect([
|
|
23
|
+
EarnVaults.deriveZoneId(421_700_006),
|
|
24
|
+
EarnVaults.deriveZoneId(1_424_310_001),
|
|
25
|
+
]).toStrictEqual([6, 1])
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('rejects a source chain ID', () => {
|
|
29
|
+
expect(() => EarnVaults.deriveZoneId(31_318)).toThrowErrorMatchingInlineSnapshot(
|
|
30
|
+
`[EarnVaults.InvalidRegistryError: Chain 31318 does not use a supported Zone chain-ID scheme.]`,
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseAbi } from 'viem'
|
|
2
2
|
import { tempoTestnet } from 'viem/tempo/chains'
|
|
3
3
|
|
|
4
|
+
import * as TestEarn from '../../test/Earn.js'
|
|
4
5
|
import * as Runtime from '../../test/runtime.js'
|
|
5
6
|
import * as TestViem from '../../test/Viem.js'
|
|
6
7
|
import * as Zones from '../apps/Zones.js'
|
|
@@ -43,6 +44,17 @@ describe('schema.ResolveInput', () => {
|
|
|
43
44
|
})
|
|
44
45
|
})
|
|
45
46
|
|
|
47
|
+
describe('schema.ZoneRoute', () => {
|
|
48
|
+
test('normalizes the router address', () => {
|
|
49
|
+
expect(
|
|
50
|
+
EarnVaults.schema.ZoneRoute.parse({
|
|
51
|
+
...TestEarn.zoneRoute,
|
|
52
|
+
earnRouter: TestEarn.zoneRoute.earnRouter.toUpperCase().replace('0X', '0x'),
|
|
53
|
+
}),
|
|
54
|
+
).toStrictEqual(TestEarn.zoneRoute)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
46
58
|
describe('classifyEngineType', () => {
|
|
47
59
|
test('classifies supported capability fingerprints', () => {
|
|
48
60
|
expect([
|
|
@@ -193,3 +205,70 @@ describe('resolve', () => {
|
|
|
193
205
|
60_000,
|
|
194
206
|
)
|
|
195
207
|
})
|
|
208
|
+
|
|
209
|
+
describe('resolveZoneRoutes', () => {
|
|
210
|
+
test.runIf(Runtime.get().mode === 'testnet')(
|
|
211
|
+
'verifies immutable router bindings and derives private tokens',
|
|
212
|
+
async () => {
|
|
213
|
+
const getClient = Viem.createGetClient({
|
|
214
|
+
defaultChainId: tempoTestnet.id,
|
|
215
|
+
rpc: TestViem.rpc,
|
|
216
|
+
zones: [TestEarn.zone],
|
|
217
|
+
})
|
|
218
|
+
const discovery = await EarnVaults.resolve({
|
|
219
|
+
getClient,
|
|
220
|
+
input: {
|
|
221
|
+
chainId: tempoTestnet.id,
|
|
222
|
+
vaultAddress: TestEarn.zoneVaultAddress,
|
|
223
|
+
},
|
|
224
|
+
zones: [TestEarn.zone],
|
|
225
|
+
})
|
|
226
|
+
const routes = await EarnVaults.resolveZoneRoutes({
|
|
227
|
+
discovery,
|
|
228
|
+
getClient,
|
|
229
|
+
routes: [TestEarn.zoneRoute],
|
|
230
|
+
zones: [TestEarn.zone],
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
expect(routes).toHaveLength(1)
|
|
234
|
+
expect(routes[0]).toMatchObject({
|
|
235
|
+
...TestEarn.zoneRoute,
|
|
236
|
+
name: TestEarn.zone.name,
|
|
237
|
+
})
|
|
238
|
+
expect(routes[0]?.inputTokens).toHaveLength(1)
|
|
239
|
+
expect(routes[0]?.outputTokens).toStrictEqual(routes[0]?.inputTokens)
|
|
240
|
+
await expect(
|
|
241
|
+
EarnVaults.resolveZoneRoutes({
|
|
242
|
+
discovery,
|
|
243
|
+
getClient,
|
|
244
|
+
routes: [TestEarn.zoneRoute, TestEarn.zoneRoute],
|
|
245
|
+
zones: [TestEarn.zone],
|
|
246
|
+
}),
|
|
247
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
248
|
+
`[EarnVaults.InvalidRegistryError: Zone 1424310001 is configured more than once.]`,
|
|
249
|
+
)
|
|
250
|
+
await expect(
|
|
251
|
+
EarnVaults.resolveZoneRoutes({
|
|
252
|
+
discovery,
|
|
253
|
+
getClient,
|
|
254
|
+
routes: [
|
|
255
|
+
{ ...TestEarn.zoneRoute, deploymentBlock: TestEarn.zoneRoute.deploymentBlock + 1 },
|
|
256
|
+
],
|
|
257
|
+
zones: [TestEarn.zone],
|
|
258
|
+
}),
|
|
259
|
+
).rejects.toMatchObject({
|
|
260
|
+
result: {
|
|
261
|
+
issues: [
|
|
262
|
+
{
|
|
263
|
+
actual: String(TestEarn.zoneRoute.deploymentBlock + 1),
|
|
264
|
+
code: 'zone-deployment-block-mismatch',
|
|
265
|
+
message: 'Earn router deployment block does not match its code boundary.',
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
status: 'mismatch',
|
|
269
|
+
},
|
|
270
|
+
})
|
|
271
|
+
},
|
|
272
|
+
60_000,
|
|
273
|
+
)
|
|
274
|
+
})
|