tapimo 0.5.0 → 0.5.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/App.d.ts +4854 -4829
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +20 -2
- package/dist/App.js.map +1 -1
- package/dist/Assets.js +3 -3
- package/dist/Assets.js.map +1 -1
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +1 -2
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.d.ts +1 -1
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +5 -10
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +6 -34
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +3 -3
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +4 -1
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/scopes.d.ts +6 -34
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/scopes.js +2 -2
- package/dist/admin/apps/scopes.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +2 -2
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +33 -33
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/data/App.d.ts +1398 -1405
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +25 -38
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts +108 -3
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +409 -2
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +245 -245
- package/dist/apps/data/routes/blocks.d.ts +123 -123
- package/dist/apps/data/routes/funding.d.ts +236 -146
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +23 -12
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts.map +1 -1
- package/dist/apps/data/routes/indexer.js +9 -2
- package/dist/apps/data/routes/indexer.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokenlist.d.ts +36 -36
- package/dist/apps/data/routes/transactions.d.ts +281 -281
- package/dist/apps/data/routes/transfers.d.ts +66 -73
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +21 -27
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +86 -86
- package/dist/apps/data/routes/webhooks.d.ts +35 -35
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +176 -42
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/management/App.d.ts +520 -504
- 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 +5 -4
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +120 -120
- package/dist/apps/management/routes/billing.d.ts +211 -211
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +1 -1
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +72 -72
- package/dist/apps/management/routes/invite-links.d.ts +159 -159
- package/dist/apps/management/routes/me.d.ts +17 -17
- package/dist/apps/management/routes/members.d.ts +80 -80
- package/dist/apps/management/routes/orgs.d.ts +59 -43
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +18 -1
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +109 -109
- package/dist/apps/management/routes/scopes.d.ts +11 -11
- package/dist/apps/management/routes/usage.d.ts +79 -79
- package/dist/apps/mcp.js +2 -2
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/relay/App.d.ts +2 -2
- package/dist/apps/relay/App.js +2 -2
- package/dist/apps/relay/Sponsorships.d.ts +3 -0
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
- package/dist/apps/relay/Sponsorships.js +15 -1
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +4 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.js +1 -1
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +6 -0
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +26 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/requestUsage.d.ts +2 -11
- package/dist/db/tables/requestUsage.d.ts.map +1 -1
- package/dist/db/tables/requestUsage.js +1 -20
- package/dist/db/tables/requestUsage.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +2 -1
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +52 -10
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/internal/Auth.d.ts +4 -0
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +15 -8
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.d.ts.map +1 -1
- package/dist/internal/Cache.js +9 -1
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +12 -3
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +176 -56
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +100 -5
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +18 -12
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +2 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/RateLimit.d.ts +2 -0
- package/dist/internal/RateLimit.d.ts.map +1 -1
- package/dist/internal/RateLimit.js +4 -4
- package/dist/internal/RateLimit.js.map +1 -1
- package/dist/internal/Tidx.d.ts +9 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +13 -7
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts +14 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +19 -1
- package/dist/internal/Viem.js.map +1 -1
- package/package.json +7 -2
- package/src/ApiKeys.test.ts +2 -0
- package/src/App.test.ts +15 -4
- package/src/App.ts +31 -5
- package/src/Assets.ts +3 -3
- package/src/Client.test-d.ts +30 -3
- package/src/Scope.ts +4 -2
- package/src/Webhooks.ts +5 -10
- package/src/admin/apps/api-keys.test.ts +27 -0
- package/src/admin/apps/api-keys.ts +8 -1
- package/src/admin/apps/scopes.test.ts +7 -2
- package/src/admin/apps/scopes.ts +2 -2
- package/src/admin/ui/src/components/Dialog.tsx +8 -2
- package/src/admin/ui/src/lib/scopes.test.ts +19 -0
- package/src/admin/ui/src/lib/scopes.ts +12 -0
- package/src/admin/ui/src/routes/api-keys.tsx +128 -27
- package/src/admin/ui.generated.ts +33 -33
- package/src/apps/composition.test.ts +77 -0
- package/src/apps/data/App.ts +25 -45
- package/src/apps/data/FundingQuoteProviders.test.ts +615 -5
- package/src/apps/data/FundingQuoteProviders.ts +570 -4
- package/src/apps/data/routes/funding.test.ts +181 -7
- package/src/apps/data/routes/funding.ts +27 -12
- package/src/apps/data/routes/indexer.test.ts +82 -20
- package/src/apps/data/routes/indexer.ts +8 -2
- package/src/apps/data/routes/tokens.test.ts +54 -0
- package/src/apps/data/routes/transfers.test.ts +73 -1
- package/src/apps/data/routes/transfers.ts +24 -30
- package/src/apps/data/routes/webhooks.test.ts +203 -0
- package/src/apps/data/routes/webhooks.ts +191 -54
- package/src/apps/management/Billing.test.ts +52 -3
- package/src/apps/management/Billing.ts +5 -4
- package/src/apps/management/routes/billing.ts +6 -1
- package/src/apps/management/routes/orgs.ts +19 -1
- package/src/apps/mcp.test.ts +18 -0
- package/src/apps/mcp.ts +2 -2
- package/src/apps/relay/App.test.ts +6 -5
- package/src/apps/relay/App.ts +2 -2
- package/src/apps/relay/Sponsorships.test.ts +2 -2
- package/src/apps/relay/Sponsorships.ts +24 -1
- package/src/db/Db.test.ts +5 -5
- package/src/db/Db.ts +4 -0
- package/src/db/Schema.ts +1 -1
- package/src/db/tables/billingSettings.test.ts +2 -2
- package/src/db/tables/organizations.test.ts +32 -2
- package/src/db/tables/organizations.ts +28 -0
- package/src/db/tables/requestUsage.ts +3 -23
- package/src/db/tables/sponsoredTransactions.test.ts +60 -2
- package/src/db/tables/sponsoredTransactions.ts +67 -21
- package/src/db/tables/stripeCustomers.test.ts +2 -2
- package/src/internal/Auth.test.ts +63 -10
- package/src/internal/Auth.ts +21 -9
- package/src/internal/Cache.test.ts +19 -0
- package/src/internal/Cache.ts +13 -1
- package/src/internal/EdgeCache.test.ts +40 -0
- package/src/internal/EdgeCache.ts +11 -3
- package/src/internal/FundingQuotes.test.ts +130 -3
- package/src/internal/FundingQuotes.ts +113 -6
- package/src/internal/Log.ts +21 -14
- package/src/internal/RateLimit.test.ts +12 -0
- package/src/internal/RateLimit.ts +4 -4
- package/src/internal/Tidx.test.ts +48 -0
- package/src/internal/Tidx.ts +21 -7
- package/src/internal/Viem.test.ts +46 -0
- package/src/internal/Viem.ts +34 -1
package/src/internal/Auth.ts
CHANGED
|
@@ -58,6 +58,8 @@ export type Context = {
|
|
|
58
58
|
* framework default.
|
|
59
59
|
*/
|
|
60
60
|
publicRateLimit?: RateLimit.Limit | undefined
|
|
61
|
+
/** Resolves a trusted client IP for public quota identity. */
|
|
62
|
+
publicClientIp?: ((request: Request) => string | undefined) | undefined
|
|
61
63
|
/** Rate-limit store used by API handlers. */
|
|
62
64
|
rateLimit?: RateLimit.Store | undefined
|
|
63
65
|
/** Session lane resolution (the session surface's capability), or undefined when sessions are unmounted. */
|
|
@@ -523,6 +525,9 @@ export function middleware<
|
|
|
523
525
|
// API-key-only routes (no per-route `policy.public`) still fall back to the
|
|
524
526
|
// deployment's configured public quota.
|
|
525
527
|
publicRateLimit: defaults.public.rateLimit,
|
|
528
|
+
...(options.public && options.public.clientIp
|
|
529
|
+
? { publicClientIp: options.public.clientIp }
|
|
530
|
+
: {}),
|
|
526
531
|
...(options.mpp === false
|
|
527
532
|
? {}
|
|
528
533
|
: {
|
|
@@ -608,6 +613,8 @@ export declare namespace middleware {
|
|
|
608
613
|
/** Default public access policy. Pass false to disable anonymous access. */
|
|
609
614
|
public?:
|
|
610
615
|
| {
|
|
616
|
+
/** Resolves the client IP from trusted runtime or proxy metadata. Defaults to `cf-connecting-ip`. */
|
|
617
|
+
clientIp?: ((request: Request) => string | undefined) | undefined
|
|
611
618
|
/** Anonymous public quota for protected routes. */
|
|
612
619
|
rateLimit?: RateLimit.Limit | undefined
|
|
613
620
|
}
|
|
@@ -1406,6 +1413,16 @@ export function require<environment extends Environment = Environment>(
|
|
|
1406
1413
|
const hasQueryApiKey = new URL(c.req.url).searchParams.has('key')
|
|
1407
1414
|
// Auth owns `key`; strict route schemas must not validate it.
|
|
1408
1415
|
stripApiKeyQuery(c)
|
|
1416
|
+
// Reject unknown chains before MPP builds a chain-bound challenge. Route
|
|
1417
|
+
// groups publish their supported chains into the shared app context.
|
|
1418
|
+
const chainId = explicitChainId(c)
|
|
1419
|
+
const supportedChainIds = (c.var as Record<string, unknown>)['supportedChainIds']
|
|
1420
|
+
if (
|
|
1421
|
+
chainId !== undefined &&
|
|
1422
|
+
supportedChainIds instanceof Set &&
|
|
1423
|
+
!supportedChainIds.has(chainId)
|
|
1424
|
+
)
|
|
1425
|
+
return Response.unsupportedChainId(c, chainId, supportedChainIds)
|
|
1409
1426
|
const proceed = async () => {
|
|
1410
1427
|
await next()
|
|
1411
1428
|
// Query credential responses must not enter shared caches.
|
|
@@ -1557,7 +1574,7 @@ export function require<environment extends Environment = Environment>(
|
|
|
1557
1574
|
}
|
|
1558
1575
|
}
|
|
1559
1576
|
|
|
1560
|
-
const publicPrincipal = principalFromPublic(c)
|
|
1577
|
+
const publicPrincipal = principalFromPublic(c, auth)
|
|
1561
1578
|
if (hasPayment) {
|
|
1562
1579
|
c.set('principal', publicPrincipal)
|
|
1563
1580
|
return requirePayment(c, proceed, {
|
|
@@ -1891,15 +1908,10 @@ function principalFromApiKey(apiKey: ApiKey.ApiKey): Principal {
|
|
|
1891
1908
|
}
|
|
1892
1909
|
}
|
|
1893
1910
|
|
|
1894
|
-
function principalFromPublic(c: hono_Context): Principal {
|
|
1895
|
-
const
|
|
1911
|
+
function principalFromPublic(c: hono_Context, auth: Context): Principal {
|
|
1912
|
+
const ip = auth.publicClientIp ? auth.publicClientIp(c.req.raw) : c.req.header('cf-connecting-ip')
|
|
1896
1913
|
return {
|
|
1897
|
-
id:
|
|
1898
|
-
c.req.header('cf-connecting-ip') ??
|
|
1899
|
-
c.req.header('x-real-ip') ??
|
|
1900
|
-
forwarded ??
|
|
1901
|
-
c.req.header('x-test-client-ip') ??
|
|
1902
|
-
'anonymous',
|
|
1914
|
+
id: ip ? RateLimit.normalizeIp(ip) : 'anonymous',
|
|
1903
1915
|
type: 'public',
|
|
1904
1916
|
}
|
|
1905
1917
|
}
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { Hono } from 'hono'
|
|
2
|
+
import * as z from 'zod/mini'
|
|
2
3
|
import * as Cache from './Cache.js'
|
|
3
4
|
|
|
5
|
+
describe('urlKey', () => {
|
|
6
|
+
test('serializes parsed arrays as repeated query parameters', async () => {
|
|
7
|
+
const schema = z.object({ include: z.array(z.string()) })
|
|
8
|
+
const app = new Hono<{ Variables: { chainId: number } }>()
|
|
9
|
+
app.use('*', async (c, next) => {
|
|
10
|
+
c.set('chainId', 4217)
|
|
11
|
+
await next()
|
|
12
|
+
})
|
|
13
|
+
app.get('/', (c) => c.text(Cache.urlKey(c, schema)))
|
|
14
|
+
|
|
15
|
+
const left = await (await app.request('/?include=a%2Cb&include=c')).text()
|
|
16
|
+
const right = await (await app.request('/?include=a&include=b%2Cc')).text()
|
|
17
|
+
|
|
18
|
+
expect(left).toMatchInlineSnapshot(`"http://localhost/?chainId=4217&include=a%2Cb&include=c"`)
|
|
19
|
+
expect(right).toMatchInlineSnapshot(`"http://localhost/?chainId=4217&include=a&include=b%2Cc"`)
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
4
23
|
describe('response', () => {
|
|
5
24
|
test('preserves rate-limit headers without caching them', async () => {
|
|
6
25
|
await withCaches(async () => {
|
package/src/internal/Cache.ts
CHANGED
|
@@ -192,11 +192,23 @@ export function urlKey<
|
|
|
192
192
|
schema extends z.ZodMiniType,
|
|
193
193
|
>(c: Context<environment>, schema: schema): string {
|
|
194
194
|
const url = new URL(c.req.url)
|
|
195
|
-
const query = schema.parse(
|
|
195
|
+
const query = schema.parse(
|
|
196
|
+
Object.fromEntries(
|
|
197
|
+
Object.entries(c.req.queries()).map(([key, values]) => [
|
|
198
|
+
key,
|
|
199
|
+
values.length === 1 ? values[0] : values,
|
|
200
|
+
]),
|
|
201
|
+
),
|
|
202
|
+
) as Record<string, unknown>
|
|
196
203
|
url.search = ''
|
|
197
204
|
if (query['chainId'] === undefined) url.searchParams.set('chainId', String(c.get('chainId')))
|
|
198
205
|
for (const [key, value] of Object.entries(query)) {
|
|
199
206
|
if (value === undefined) continue
|
|
207
|
+
if (Array.isArray(value)) {
|
|
208
|
+
for (const item of value)
|
|
209
|
+
url.searchParams.append(key, String(item as number | string | boolean))
|
|
210
|
+
continue
|
|
211
|
+
}
|
|
200
212
|
url.searchParams.set(key, String(value as number | string | boolean))
|
|
201
213
|
}
|
|
202
214
|
return url.toString()
|
|
@@ -130,6 +130,33 @@ describe('middleware', () => {
|
|
|
130
130
|
expect(store.size).toMatchInlineSnapshot(`0`)
|
|
131
131
|
})
|
|
132
132
|
|
|
133
|
+
test('strips payment headers from private responses before sharing them', async () => {
|
|
134
|
+
const { app, store, downstreamCalls } = createApp()
|
|
135
|
+
|
|
136
|
+
const miss = await app.request('/private')
|
|
137
|
+
const hit = await app.request('/private')
|
|
138
|
+
|
|
139
|
+
expect(downstreamCalls()).toMatchInlineSnapshot(`1`)
|
|
140
|
+
expect(store.size).toMatchInlineSnapshot(`1`)
|
|
141
|
+
expect(miss.headers.get('payment-receipt')).toMatchInlineSnapshot(`"payer-specific"`)
|
|
142
|
+
expect(miss.headers.get('payment-session-snapshot')).toMatchInlineSnapshot(`"session-specific"`)
|
|
143
|
+
expect(hit.headers.get('payment-receipt')).toMatchInlineSnapshot(`null`)
|
|
144
|
+
expect(hit.headers.get('payment-session-snapshot')).toMatchInlineSnapshot(`null`)
|
|
145
|
+
expect(hit.headers.get('cache-control')).toMatchInlineSnapshot(
|
|
146
|
+
`"public, max-age=30, s-maxage=30"`,
|
|
147
|
+
)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('does not cache responses marked `no-store`', async () => {
|
|
151
|
+
const { app, store, downstreamCalls } = createApp()
|
|
152
|
+
|
|
153
|
+
await app.request('/no-store')
|
|
154
|
+
await app.request('/no-store')
|
|
155
|
+
|
|
156
|
+
expect(downstreamCalls()).toMatchInlineSnapshot(`2`)
|
|
157
|
+
expect(store.size).toMatchInlineSnapshot(`0`)
|
|
158
|
+
})
|
|
159
|
+
|
|
133
160
|
test('does not cache routes that did not opt in', async () => {
|
|
134
161
|
const { app, store, downstreamCalls } = createApp()
|
|
135
162
|
|
|
@@ -271,6 +298,19 @@ function createApp(
|
|
|
271
298
|
c.set('edgeCache', { maxAge: 30, staleWhileRevalidate: 300 })
|
|
272
299
|
return c.json({ ok: false }, 500)
|
|
273
300
|
})
|
|
301
|
+
app.get('/private', cacheable, (c) => {
|
|
302
|
+
c.set('edgeCache', { maxAge: 30 })
|
|
303
|
+
c.header('Cache-Control', 'private, max-age=30')
|
|
304
|
+
c.header('Payment-Receipt', 'payer-specific')
|
|
305
|
+
c.header('Payment-Session-Snapshot', 'session-specific')
|
|
306
|
+
return c.json({ ok: true })
|
|
307
|
+
})
|
|
308
|
+
app.get('/no-store', cacheable, (c) => {
|
|
309
|
+
c.set('edgeCache', { maxAge: 30 })
|
|
310
|
+
c.header('Cache-Control', 'no-store')
|
|
311
|
+
c.header('Payment-Receipt', 'payer-specific')
|
|
312
|
+
return c.json({ ok: true })
|
|
313
|
+
})
|
|
274
314
|
app.get('/binary', cacheable, (c) => {
|
|
275
315
|
c.set('edgeCache', { maxAge: 30, staleWhileRevalidate: 300 })
|
|
276
316
|
// Bytes that are not valid UTF-8 (PNG signature) — corrupted if stored via
|
|
@@ -33,12 +33,16 @@ const volatileHeaders = [
|
|
|
33
33
|
'cf-cache-status',
|
|
34
34
|
'content-encoding',
|
|
35
35
|
'content-length',
|
|
36
|
-
'
|
|
37
|
-
'
|
|
36
|
+
'payment-receipt',
|
|
37
|
+
'payment-session-snapshot',
|
|
38
38
|
'ratelimit-limit',
|
|
39
39
|
'ratelimit-remaining',
|
|
40
40
|
'ratelimit-reset',
|
|
41
|
+
'ratelimit-scope',
|
|
42
|
+
'server-timing',
|
|
43
|
+
'set-cookie',
|
|
41
44
|
'tempo-request-id',
|
|
45
|
+
'www-authenticate',
|
|
42
46
|
]
|
|
43
47
|
|
|
44
48
|
/**
|
|
@@ -115,6 +119,8 @@ export function middleware<environment extends Env>(
|
|
|
115
119
|
if (c.res.status < 200 || c.res.status >= 300) return
|
|
116
120
|
if (c.res.headers.has('set-cookie')) return
|
|
117
121
|
if (c.res.headers.get('content-type')?.includes('text/event-stream')) return
|
|
122
|
+
const responseDirectives = (c.res.headers.get('cache-control') ?? '').toLowerCase()
|
|
123
|
+
if (responseDirectives.includes('no-cache') || responseDirectives.includes('no-store')) return
|
|
118
124
|
|
|
119
125
|
// Store a public, sanitized copy; the client still receives the original
|
|
120
126
|
// (private, metered) response for this miss. Clone synchronously — the
|
|
@@ -247,7 +253,9 @@ function isTextual(contentType: string | null): boolean {
|
|
|
247
253
|
function cacheKey(requestUrl: string) {
|
|
248
254
|
const url = new URL(requestUrl)
|
|
249
255
|
url.searchParams.sort()
|
|
250
|
-
|
|
256
|
+
// Version the key so entries written before a sanitizer change cannot
|
|
257
|
+
// survive the deployment that changes the shared-cache boundary.
|
|
258
|
+
return `edge:v2:${url.toString()}`
|
|
251
259
|
}
|
|
252
260
|
|
|
253
261
|
function getWaitUntil(c: { executionCtx?: { waitUntil(promise: Promise<unknown>): void } }) {
|
|
@@ -83,9 +83,11 @@ describe('listQuotes', () => {
|
|
|
83
83
|
},
|
|
84
84
|
})
|
|
85
85
|
|
|
86
|
-
await
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
const cause = await quotes.catch((error: unknown) => error)
|
|
87
|
+
expect(cause).toMatchObject({
|
|
88
|
+
failures: [{ cause: expect.any(Error), provider: 'relay' }],
|
|
89
|
+
name: 'FundingQuotes.ProviderQuoteError',
|
|
90
|
+
})
|
|
89
91
|
})
|
|
90
92
|
|
|
91
93
|
test('rejects successful provider responses without output amounts', async () => {
|
|
@@ -145,6 +147,32 @@ describe('listQuotes', () => {
|
|
|
145
147
|
|
|
146
148
|
expect(seen?.cache.bypass).toBe(true)
|
|
147
149
|
})
|
|
150
|
+
|
|
151
|
+
test('returns successful quotes when another provider fails', async () => {
|
|
152
|
+
const quotes = await FundingQuotes.listQuotes({
|
|
153
|
+
...baseUsdcRequest,
|
|
154
|
+
adapters: {
|
|
155
|
+
across: {
|
|
156
|
+
async probeQuote() {
|
|
157
|
+
throw new Error('upstream unavailable')
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
relay: {
|
|
161
|
+
async probeQuote() {
|
|
162
|
+
return {
|
|
163
|
+
outputAmount: '990000',
|
|
164
|
+
sampledAt: '2026-01-01T00:00:00Z',
|
|
165
|
+
status: 'available',
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
provider: undefined,
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
expect(quotes).toHaveLength(1)
|
|
174
|
+
expect(quotes[0]?.provider.id).toBe('relay')
|
|
175
|
+
})
|
|
148
176
|
})
|
|
149
177
|
|
|
150
178
|
describe('listChains', () => {
|
|
@@ -153,6 +181,84 @@ describe('listChains', () => {
|
|
|
153
181
|
'eip155:1',
|
|
154
182
|
'eip155:8453',
|
|
155
183
|
'eip155:56',
|
|
184
|
+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
|
|
185
|
+
'tron:0x2b6653dc',
|
|
186
|
+
])
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('uses chain-specific token keys for non-EVM inventory', () => {
|
|
190
|
+
const chains = FundingQuotes.listChains()
|
|
191
|
+
expect(chains.find((chain) => chain.kind === 'solana')?.tokens[0]?.tokenKey).toBe(
|
|
192
|
+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
193
|
+
)
|
|
194
|
+
expect(chains.find((chain) => chain.kind === 'tron')?.tokens[0]?.tokenKey).toBe(
|
|
195
|
+
'tron:0x2b6653dc/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
|
|
196
|
+
)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
describe('listQuoteCandidates', () => {
|
|
201
|
+
test('round-trips the advertised Solana chain and token ids', () => {
|
|
202
|
+
const [candidate] = FundingQuotes.listQuoteCandidates({
|
|
203
|
+
provider: 'rhino',
|
|
204
|
+
sourceAmount: '1000000',
|
|
205
|
+
sourceAmountUnits: 'baseUnits',
|
|
206
|
+
sourceChain: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
|
|
207
|
+
sourceToken:
|
|
208
|
+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
expect(candidate?.id).toBe('solana-usdc-tempo-usdce-rhino')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test('keeps non-EVM token identifiers case-sensitive', () => {
|
|
215
|
+
expect(
|
|
216
|
+
FundingQuotes.listQuoteCandidates({
|
|
217
|
+
provider: 'rhino',
|
|
218
|
+
sourceAmount: '1000000',
|
|
219
|
+
sourceAmountUnits: 'baseUnits',
|
|
220
|
+
sourceChain: 'solana',
|
|
221
|
+
sourceToken: 'epjfwdd5aufqssqem2qn1xzybapc8g4wegkzwytdt1v',
|
|
222
|
+
}),
|
|
223
|
+
).toEqual([])
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test('resolves the complete Tron token key', () => {
|
|
227
|
+
const [candidate] = FundingQuotes.listQuoteCandidates({
|
|
228
|
+
destinationToken: 'USDT0',
|
|
229
|
+
provider: 'rhino',
|
|
230
|
+
sourceAmount: '1000000',
|
|
231
|
+
sourceAmountUnits: 'baseUnits',
|
|
232
|
+
sourceChain: 'tron:0x2b6653dc',
|
|
233
|
+
sourceToken: 'tron:0x2b6653dc/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
expect(candidate?.id).toBe('tron-usdt-tempo-usdt0-rhino')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test('selects proven Squid stablecoin corridors', () => {
|
|
240
|
+
const candidates = [
|
|
241
|
+
FundingQuotes.listQuoteCandidates({
|
|
242
|
+
destinationToken: 'USDC.e',
|
|
243
|
+
provider: 'squid',
|
|
244
|
+
sourceAmount: '1000000',
|
|
245
|
+
sourceAmountUnits: 'baseUnits',
|
|
246
|
+
sourceChain: 'base',
|
|
247
|
+
sourceToken: 'USDC',
|
|
248
|
+
})[0],
|
|
249
|
+
FundingQuotes.listQuoteCandidates({
|
|
250
|
+
destinationToken: 'USDT0',
|
|
251
|
+
provider: 'squid',
|
|
252
|
+
sourceAmount: '1000000',
|
|
253
|
+
sourceAmountUnits: 'baseUnits',
|
|
254
|
+
sourceChain: 'ethereum',
|
|
255
|
+
sourceToken: 'USDT',
|
|
256
|
+
})[0],
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
expect(candidates.map((candidate) => candidate?.id)).toEqual([
|
|
260
|
+
'base-usdc-tempo-usdce-squid',
|
|
261
|
+
'ethereum-usdt-tempo-usdt0-squid',
|
|
156
262
|
])
|
|
157
263
|
})
|
|
158
264
|
})
|
|
@@ -161,6 +267,13 @@ describe('listProviders', () => {
|
|
|
161
267
|
test('reports deployment configuration', () => {
|
|
162
268
|
expect(FundingQuotes.listProviders()).toMatchInlineSnapshot(`
|
|
163
269
|
[
|
|
270
|
+
{
|
|
271
|
+
"id": "across",
|
|
272
|
+
"kind": "intentBridge",
|
|
273
|
+
"name": "Across",
|
|
274
|
+
"requiresApiKey": true,
|
|
275
|
+
"status": "unconfigured",
|
|
276
|
+
},
|
|
164
277
|
{
|
|
165
278
|
"id": "relay",
|
|
166
279
|
"kind": "solverNetwork",
|
|
@@ -168,6 +281,20 @@ describe('listProviders', () => {
|
|
|
168
281
|
"requiresApiKey": false,
|
|
169
282
|
"status": "unconfigured",
|
|
170
283
|
},
|
|
284
|
+
{
|
|
285
|
+
"id": "rhino",
|
|
286
|
+
"kind": "bridge",
|
|
287
|
+
"name": "Rhino",
|
|
288
|
+
"requiresApiKey": false,
|
|
289
|
+
"status": "unconfigured",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"id": "squid",
|
|
293
|
+
"kind": "solverNetwork",
|
|
294
|
+
"name": "Squid",
|
|
295
|
+
"requiresApiKey": true,
|
|
296
|
+
"status": "unconfigured",
|
|
297
|
+
},
|
|
171
298
|
]
|
|
172
299
|
`)
|
|
173
300
|
})
|
|
@@ -18,6 +18,7 @@ const sourceChains = [
|
|
|
18
18
|
kind: 'evm',
|
|
19
19
|
name: 'Ethereum',
|
|
20
20
|
providerIds: {
|
|
21
|
+
across: '1',
|
|
21
22
|
relay: '1',
|
|
22
23
|
},
|
|
23
24
|
slug: 'ethereum',
|
|
@@ -71,7 +72,9 @@ const sourceChains = [
|
|
|
71
72
|
kind: 'evm',
|
|
72
73
|
name: 'Base',
|
|
73
74
|
providerIds: {
|
|
75
|
+
across: '8453',
|
|
74
76
|
relay: '8453',
|
|
77
|
+
rhino: 'BASE',
|
|
75
78
|
},
|
|
76
79
|
slug: 'base',
|
|
77
80
|
tokens: [
|
|
@@ -108,6 +111,7 @@ const sourceChains = [
|
|
|
108
111
|
kind: 'evm',
|
|
109
112
|
name: 'BNB Smart Chain',
|
|
110
113
|
providerIds: {
|
|
114
|
+
across: '56',
|
|
111
115
|
relay: '56',
|
|
112
116
|
},
|
|
113
117
|
slug: 'bnb',
|
|
@@ -130,6 +134,48 @@ const sourceChains = [
|
|
|
130
134
|
},
|
|
131
135
|
],
|
|
132
136
|
},
|
|
137
|
+
{
|
|
138
|
+
addressFormat: 'base58',
|
|
139
|
+
aliases: ['solana', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'],
|
|
140
|
+
id: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
|
|
141
|
+
kind: 'solana',
|
|
142
|
+
name: 'Solana',
|
|
143
|
+
providerIds: {
|
|
144
|
+
rhino: 'SOLANA',
|
|
145
|
+
},
|
|
146
|
+
slug: 'solana',
|
|
147
|
+
tokens: [
|
|
148
|
+
{
|
|
149
|
+
address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
150
|
+
coingeckoId: 'usd-coin',
|
|
151
|
+
decimals: 6,
|
|
152
|
+
name: 'USD Coin',
|
|
153
|
+
standard: 'SPL',
|
|
154
|
+
symbol: 'USDC',
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
addressFormat: 'base58check',
|
|
160
|
+
aliases: ['tron', 'tron:0x2b6653dc'],
|
|
161
|
+
id: 'tron:0x2b6653dc',
|
|
162
|
+
kind: 'tron',
|
|
163
|
+
name: 'Tron',
|
|
164
|
+
providerIds: {
|
|
165
|
+
rhino: 'TRON',
|
|
166
|
+
},
|
|
167
|
+
slug: 'tron',
|
|
168
|
+
tokens: [
|
|
169
|
+
{
|
|
170
|
+
address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
|
|
171
|
+
coingeckoId: 'tether',
|
|
172
|
+
decimals: 6,
|
|
173
|
+
name: 'Tether USD',
|
|
174
|
+
standard: 'TRC-20',
|
|
175
|
+
symbol: 'USDT',
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
},
|
|
133
179
|
] as const
|
|
134
180
|
|
|
135
181
|
/** Tempo destination tokens supported by funding quote providers. */
|
|
@@ -178,6 +224,13 @@ const destinationTokens = [
|
|
|
178
224
|
|
|
179
225
|
/** Providers supported by live funding quotes. */
|
|
180
226
|
const providers = [
|
|
227
|
+
{
|
|
228
|
+
id: 'across',
|
|
229
|
+
kind: 'intentBridge',
|
|
230
|
+
name: 'Across',
|
|
231
|
+
requiresApiKey: true,
|
|
232
|
+
status: 'unconfigured',
|
|
233
|
+
},
|
|
181
234
|
{
|
|
182
235
|
id: 'relay',
|
|
183
236
|
kind: 'solverNetwork',
|
|
@@ -185,6 +238,20 @@ const providers = [
|
|
|
185
238
|
requiresApiKey: false,
|
|
186
239
|
status: 'unconfigured',
|
|
187
240
|
},
|
|
241
|
+
{
|
|
242
|
+
id: 'rhino',
|
|
243
|
+
kind: 'bridge',
|
|
244
|
+
name: 'Rhino',
|
|
245
|
+
requiresApiKey: false,
|
|
246
|
+
status: 'unconfigured',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: 'squid',
|
|
250
|
+
kind: 'solverNetwork',
|
|
251
|
+
name: 'Squid',
|
|
252
|
+
requiresApiKey: true,
|
|
253
|
+
status: 'unconfigured',
|
|
254
|
+
},
|
|
188
255
|
] as const
|
|
189
256
|
|
|
190
257
|
type TokenInput = (typeof sourceChains)[number]['tokens'][number]
|
|
@@ -262,6 +329,8 @@ export type QuoteProbeInput = {
|
|
|
262
329
|
|
|
263
330
|
/** Quote probe data returned by a provider adapter. */
|
|
264
331
|
export type QuoteProbeResult = {
|
|
332
|
+
/** Timestamp after which the provider quote is no longer valid. */
|
|
333
|
+
expiresAt?: string | undefined
|
|
265
334
|
/** Slippage-adjusted minimum destination amount in base units. */
|
|
266
335
|
minimumOutputAmount?: string | undefined
|
|
267
336
|
/** Expected destination amount in base units. */
|
|
@@ -294,9 +363,22 @@ export type ProviderAdapter = {
|
|
|
294
363
|
/** Provider adapter registry, keyed by provider id. */
|
|
295
364
|
export type ProviderAdapters = Partial<Record<ProviderId, ProviderAdapter>>
|
|
296
365
|
|
|
366
|
+
/** A provider adapter failure retained for bounded request diagnostics. */
|
|
367
|
+
export type ProviderFailure = {
|
|
368
|
+
/** Error thrown by the provider boundary. */
|
|
369
|
+
cause: unknown
|
|
370
|
+
/** Provider that failed. */
|
|
371
|
+
provider: ProviderId
|
|
372
|
+
}
|
|
373
|
+
|
|
297
374
|
/** Curated provider support rows used to select live quote probes. */
|
|
298
375
|
const inventory: readonly InventoryEntry[] = [
|
|
376
|
+
{ destinationToken: 'usdce', provider: 'across', sourceChains: ['ethereum', 'base', 'bnb'], sourceTokens: ['USDC'] }, // prettier-ignore
|
|
299
377
|
{ destinationToken: 'usdce', provider: 'relay', sourceChains: ['ethereum', 'base', 'bnb'], sourceTokens: ['USDC', 'USDT'] }, // prettier-ignore
|
|
378
|
+
{ destinationToken: 'usdce', provider: 'rhino', sourceChains: ['base', 'solana'], sourceTokens: ['USDC'] }, // prettier-ignore
|
|
379
|
+
{ destinationToken: 'usdce', provider: 'squid', sourceChains: ['ethereum', 'base', 'bnb'], sourceTokens: ['USDC'] }, // prettier-ignore
|
|
380
|
+
{ destinationToken: 'usdt0', provider: 'rhino', sourceChains: ['tron'], sourceTokens: ['USDT'] }, // prettier-ignore
|
|
381
|
+
{ destinationToken: 'usdt0', provider: 'squid', sourceChains: ['ethereum', 'base', 'bnb'], sourceTokens: ['USDT'] }, // prettier-ignore
|
|
300
382
|
]
|
|
301
383
|
|
|
302
384
|
type InventoryEntry = {
|
|
@@ -367,8 +449,12 @@ export async function listQuotes(options: listQuotes.Options) {
|
|
|
367
449
|
const quotes = results.flatMap((result) =>
|
|
368
450
|
result.status === 'fulfilled' && result.value ? [result.value] : [],
|
|
369
451
|
)
|
|
370
|
-
|
|
371
|
-
|
|
452
|
+
const failures = results.flatMap((result, index) =>
|
|
453
|
+
result.status === 'rejected'
|
|
454
|
+
? [{ cause: result.reason, provider: candidates[index]!.provider.id }]
|
|
455
|
+
: [],
|
|
456
|
+
)
|
|
457
|
+
if (quotes.length === 0 && failures.length > 0) throw new ProviderQuoteError(failures)
|
|
372
458
|
return quotes
|
|
373
459
|
}
|
|
374
460
|
|
|
@@ -439,8 +525,13 @@ export declare namespace listQuotes {
|
|
|
439
525
|
/** Source amount unit accepted by the funding quote request. */
|
|
440
526
|
export type SourceAmountUnits = 'baseUnits' | 'decimal'
|
|
441
527
|
|
|
442
|
-
function tokenKey(chain: { id: string }, token: TokenInput | DestinationTokenInput) {
|
|
443
|
-
|
|
528
|
+
function tokenKey(chain: { id: string; kind: string }, token: TokenInput | DestinationTokenInput) {
|
|
529
|
+
const namespace = (() => {
|
|
530
|
+
if (chain.kind === 'solana') return 'token'
|
|
531
|
+
if (chain.kind === 'tron') return 'trc20'
|
|
532
|
+
return 'erc20'
|
|
533
|
+
})()
|
|
534
|
+
return `${chain.id}/${namespace}:${token.address}`
|
|
444
535
|
}
|
|
445
536
|
|
|
446
537
|
function decimalFromBaseUnits(baseUnits: string, decimals: number) {
|
|
@@ -485,7 +576,7 @@ async function quoteCandidate(
|
|
|
485
576
|
quote.outputAmount === undefined ||
|
|
486
577
|
quote.outputAmount === null
|
|
487
578
|
)
|
|
488
|
-
throw new
|
|
579
|
+
throw new InvalidProviderQuoteError()
|
|
489
580
|
return formatFundingQuote(candidate, {
|
|
490
581
|
...quote,
|
|
491
582
|
outputAmount: quote.outputAmount,
|
|
@@ -595,6 +686,7 @@ function formatFundingQuote(candidate: QuoteCandidate, quote: SuccessfulQuotePro
|
|
|
595
686
|
tier: quote.status === 'thin' ? ('thin' as const) : ('liquid' as const),
|
|
596
687
|
},
|
|
597
688
|
quote: {
|
|
689
|
+
...(quote.expiresAt !== undefined ? { expiresAt: quote.expiresAt } : {}),
|
|
598
690
|
inputAmount: candidate.sourceAmount.amount,
|
|
599
691
|
...(quote.minimumOutputAmount !== undefined
|
|
600
692
|
? { minimumOutputAmount: quote.minimumOutputAmount }
|
|
@@ -685,10 +777,13 @@ function matchesTokenIdentifier(
|
|
|
685
777
|
}
|
|
686
778
|
|
|
687
779
|
function resolveSourceChain(input: string) {
|
|
780
|
+
const trimmed = input.trim()
|
|
688
781
|
const normalized = normalizeInput(input)
|
|
689
782
|
const chain = sourceChains.find(
|
|
690
783
|
(candidate) =>
|
|
691
|
-
candidate.id ===
|
|
784
|
+
candidate.id === trimmed ||
|
|
785
|
+
candidate.slug === normalized ||
|
|
786
|
+
(candidate.kind === 'evm' && (candidate.aliases as readonly string[]).includes(normalized)),
|
|
692
787
|
)
|
|
693
788
|
if (!chain) throw new UnsupportedSourceChainError()
|
|
694
789
|
return chain
|
|
@@ -722,6 +817,14 @@ export class ProviderAdapterTimeoutError extends Error {
|
|
|
722
817
|
/** Error thrown when every matching provider fails to produce a quote. */
|
|
723
818
|
export class ProviderQuoteError extends Error {
|
|
724
819
|
override name = 'FundingQuotes.ProviderQuoteError'
|
|
820
|
+
|
|
821
|
+
/** Provider failures that prevented a successful quote. */
|
|
822
|
+
failures: readonly ProviderFailure[]
|
|
823
|
+
|
|
824
|
+
constructor(failures: readonly ProviderFailure[]) {
|
|
825
|
+
super()
|
|
826
|
+
this.failures = failures
|
|
827
|
+
}
|
|
725
828
|
}
|
|
726
829
|
|
|
727
830
|
/** Error thrown when the caller filters to an unknown provider. */
|
|
@@ -733,3 +836,7 @@ export class UnsupportedProviderError extends Error {
|
|
|
733
836
|
export class UnsupportedSourceChainError extends Error {
|
|
734
837
|
override name = 'FundingQuotes.UnsupportedSourceChainError'
|
|
735
838
|
}
|
|
839
|
+
|
|
840
|
+
class InvalidProviderQuoteError extends Error {
|
|
841
|
+
override name = 'FundingQuotes.InvalidProviderQuoteError'
|
|
842
|
+
}
|
package/src/internal/Log.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type Variables = {
|
|
|
12
12
|
errorCode?: string | undefined
|
|
13
13
|
/** Redacted provider failure details recorded by an upstream adapter. */
|
|
14
14
|
providerFailure?: Entry['provider'] | undefined
|
|
15
|
+
/** Redacted provider failures when multiple upstream adapters fail. */
|
|
16
|
+
providerFailures?: Entry['providerFailures'] | undefined
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/** Hono environment shape used by the request logger. */
|
|
@@ -28,6 +30,20 @@ export type Environment = {
|
|
|
28
30
|
/** Log level, derived from the response status. */
|
|
29
31
|
export type Level = 'error' | 'info' | 'warn'
|
|
30
32
|
|
|
33
|
+
/** Redacted upstream provider failure details. */
|
|
34
|
+
export type ProviderFailure = {
|
|
35
|
+
/** Stable provider error code, when one is safe to retain. */
|
|
36
|
+
code?: string | undefined
|
|
37
|
+
/** Failure boundary that rejected the provider request. */
|
|
38
|
+
failure: 'http' | 'network' | 'payload' | 'timeout' | 'unknown'
|
|
39
|
+
/** Provider identifier. */
|
|
40
|
+
id: string
|
|
41
|
+
/** Provider operation identifier. */
|
|
42
|
+
operation: string
|
|
43
|
+
/** Upstream HTTP status, when a response was received. */
|
|
44
|
+
status?: number | undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
/** One canonical request log entry. */
|
|
32
48
|
export type Entry = {
|
|
33
49
|
/** `'hit'` when the edge response cache served the request without reaching the origin handlers. */
|
|
@@ -64,20 +80,9 @@ export type Entry = {
|
|
|
64
80
|
}
|
|
65
81
|
| undefined
|
|
66
82
|
/** Redacted upstream provider failure details, when an adapter request fails. */
|
|
67
|
-
provider?:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
code?: string | undefined
|
|
71
|
-
/** Failure boundary that rejected the provider request. */
|
|
72
|
-
failure: 'http' | 'network' | 'payload' | 'unknown'
|
|
73
|
-
/** Provider identifier. */
|
|
74
|
-
id: string
|
|
75
|
-
/** Provider operation identifier. */
|
|
76
|
-
operation: string
|
|
77
|
-
/** Upstream HTTP status, when a response was received. */
|
|
78
|
-
status?: number | undefined
|
|
79
|
-
}
|
|
80
|
-
| undefined
|
|
83
|
+
provider?: ProviderFailure | undefined
|
|
84
|
+
/** Redacted failures for every provider when multiple adapters fail. */
|
|
85
|
+
providerFailures?: readonly ProviderFailure[] | undefined
|
|
81
86
|
/** Raw query string (no leading `?`) with API-key tokens redacted, when present. */
|
|
82
87
|
query?: string | undefined
|
|
83
88
|
/** Rate limit state from the `RateLimit-*` response headers, when enforced. */
|
|
@@ -136,6 +141,7 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
136
141
|
const principal = c.get('principal' as never) as Auth.Principal | undefined
|
|
137
142
|
const errorCode = c.get('errorCode' as never) as string | undefined
|
|
138
143
|
const provider = c.get('providerFailure' as never) as Entry['provider']
|
|
144
|
+
const providerFailures = c.get('providerFailures' as never) as Entry['providerFailures']
|
|
139
145
|
|
|
140
146
|
// The `request` total metric duplicates `duration` (measured here, one
|
|
141
147
|
// layer further out), so only operation timings are kept.
|
|
@@ -186,6 +192,7 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
186
192
|
},
|
|
187
193
|
}),
|
|
188
194
|
...(provider === undefined ? {} : { provider }),
|
|
195
|
+
...(providerFailures === undefined ? {} : { providerFailures }),
|
|
189
196
|
...(query ? { query } : {}),
|
|
190
197
|
...(rateLimit === undefined ? {} : { rateLimit }),
|
|
191
198
|
requestId: c.get('requestId' as never) as string,
|