tapimo 0.4.1 → 0.4.4
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/README.md +1 -1
- package/dist/ApiKey.d.ts +1 -9
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +2 -3
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +53 -27
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +32 -17
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +1990 -1442
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +23 -9
- package/dist/App.js.map +1 -1
- package/dist/Assets.d.ts +10 -0
- package/dist/Assets.d.ts.map +1 -1
- package/dist/Assets.js +4 -0
- package/dist/Assets.js.map +1 -1
- package/dist/Auth.d.ts +2 -1
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js +2 -1
- package/dist/Auth.js.map +1 -1
- package/dist/Scope.d.ts +40 -15
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +44 -13
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +560 -9
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +38 -6
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +10 -24
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +10 -3
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +833 -0
- package/dist/admin/apps/organizations.d.ts.map +1 -0
- package/dist/admin/apps/organizations.js +733 -0
- package/dist/admin/apps/organizations.js.map +1 -0
- package/dist/admin/apps/scopes.d.ts +8 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +57 -57
- 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 +53 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +10 -0
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +8 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +9 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +14 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +1304 -1304
- package/dist/apps/data/FundingRouteProviders.d.ts +4 -22
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.js +10 -120
- package/dist/apps/data/FundingRouteProviders.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +38 -38
- package/dist/apps/data/routes/addresses.d.ts +24 -24
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/fee-amm.d.ts +20 -20
- package/dist/apps/data/routes/funding.js +7 -7
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/management/App.d.ts +808 -267
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +2 -2
- package/dist/apps/management/Billing.d.ts +3 -0
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +7 -2
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +116 -100
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +41 -30
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +182 -50
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +14 -14
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +93 -20
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +12 -15
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +89 -18
- package/dist/apps/management/routes/members.d.ts.map +1 -1
- package/dist/apps/management/routes/members.js +10 -10
- package/dist/apps/management/routes/members.js.map +1 -1
- package/dist/apps/management/routes/orgs.d.ts +100 -40
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +19 -19
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +122 -33
- package/dist/apps/management/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js +12 -13
- package/dist/apps/management/routes/projects.js.map +1 -1
- package/dist/apps/management/routes/scopes.d.ts +4 -18
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +6 -7
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +63 -19
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +9 -10
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +38 -4
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/relay/App.d.ts +26 -4
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +17 -14
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/cloudflare.d.ts +2 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +4 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +5 -3
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +52 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +13 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +16 -1
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/adminAuditLogs.d.ts +27 -0
- package/dist/db/tables/adminAuditLogs.d.ts.map +1 -0
- package/dist/db/tables/adminAuditLogs.js +19 -0
- package/dist/db/tables/adminAuditLogs.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/memberships.d.ts +32 -4
- package/dist/db/tables/memberships.d.ts.map +1 -1
- package/dist/db/tables/memberships.js +31 -0
- package/dist/db/tables/memberships.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +43 -1
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +103 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.d.ts +25 -0
- package/dist/db/tables/projects.d.ts.map +1 -1
- package/dist/db/tables/projects.js +22 -0
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/webhookDeliveries.d.ts +26 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
- package/dist/db/tables/webhookDeliveries.js +37 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -1
- package/dist/internal/Auth.d.ts +46 -40
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +118 -69
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cursor.d.ts +1 -1
- package/dist/internal/Cursor.d.ts.map +1 -1
- package/dist/internal/Cursor.js +4 -0
- package/dist/internal/Cursor.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +2 -0
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/Errors.d.ts +6 -0
- package/dist/internal/Errors.d.ts.map +1 -0
- package/dist/internal/Errors.js +16 -0
- package/dist/internal/Errors.js.map +1 -0
- package/dist/internal/FundingRoutes.d.ts +15 -43
- package/dist/internal/FundingRoutes.d.ts.map +1 -1
- package/dist/internal/FundingRoutes.js +8 -17
- package/dist/internal/FundingRoutes.js.map +1 -1
- package/dist/internal/Log.d.ts +16 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +5 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/Store.d.ts +61 -16
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +72 -17
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/dist/internal/index.d.ts +27 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +27 -0
- package/dist/internal/index.js.map +1 -0
- package/package.json +8 -3
- package/src/ApiKey.ts +2 -3
- package/src/ApiKeys.ts +95 -17
- package/src/App.test.ts +104 -6
- package/src/App.ts +29 -9
- package/src/Assets.ts +8 -0
- package/src/Auth.test-d.ts +36 -0
- package/src/Auth.ts +20 -1
- package/src/Client.test-d.ts +17 -17
- package/src/Scope.test.ts +34 -0
- package/src/Scope.ts +53 -17
- package/src/admin/App.test.ts +33 -3
- package/src/admin/App.ts +49 -14
- package/src/admin/apps/api-keys.ts +10 -3
- package/src/admin/apps/organizations.test.ts +455 -0
- package/src/admin/apps/organizations.ts +953 -0
- package/src/admin/apps/scopes.test.ts +10 -0
- package/src/admin/ui/plugin.ts +1 -1
- package/src/admin/ui/src/components/MobileProfile.tsx +2 -0
- package/src/admin/ui/src/components/ThemeToggle.tsx +20 -0
- package/src/admin/ui/src/lib/api-keys.ts +36 -19
- package/src/admin/ui/src/lib/config.ts +2 -3
- package/src/admin/ui/src/lib/organizations.test.ts +89 -0
- package/src/admin/ui/src/lib/organizations.ts +262 -0
- package/src/admin/ui/src/lib/query-client.ts +3 -1
- package/src/admin/ui/src/lib/theme.ts +57 -0
- package/src/admin/ui/src/routeTree.gen.ts +47 -4
- package/src/admin/ui/src/routes/__root.tsx +18 -3
- package/src/admin/ui/src/routes/api-keys.tsx +52 -7
- package/src/admin/ui/src/routes/index.tsx +1 -1
- package/src/admin/ui/src/routes/organizations.tsx +158 -0
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +710 -0
- package/src/admin/ui/src/start.ts +6 -0
- package/src/admin/ui.generated.ts +63 -13
- package/src/analytics/Analytics.ts +25 -0
- package/src/analytics/tables/requestEvents.ts +33 -0
- package/src/apps/composition.test.ts +82 -2
- package/src/apps/data/FundingRouteProviders.test.ts +37 -93
- package/src/apps/data/FundingRouteProviders.ts +14 -161
- package/src/apps/data/routes/funding.test.ts +7 -42
- package/src/apps/data/routes/funding.ts +8 -8
- package/src/apps/data/routes/indexer.test.ts +2 -2
- package/src/apps/data/routes/webhooks.test.ts +2 -2
- package/src/apps/management/App.ts +2 -2
- package/src/apps/management/Billing.ts +10 -2
- package/src/apps/management/routes/api-keys.test.ts +137 -1
- package/src/apps/management/routes/api-keys.ts +63 -38
- package/src/apps/management/routes/billing.ts +14 -14
- package/src/apps/management/routes/invitations.test.ts +102 -1
- package/src/apps/management/routes/invitations.ts +13 -15
- package/src/apps/management/routes/members.ts +10 -10
- package/src/apps/management/routes/orgs.test.ts +78 -16
- package/src/apps/management/routes/orgs.ts +21 -20
- package/src/apps/management/routes/projects.ts +12 -13
- package/src/apps/management/routes/scopes.test.ts +16 -0
- package/src/apps/management/routes/scopes.ts +6 -7
- package/src/apps/management/routes/usage.ts +9 -14
- package/src/apps/mcp.test.ts +72 -7
- package/src/apps/mcp.ts +46 -4
- package/src/apps/relay/App.test.ts +23 -18
- package/src/apps/relay/App.ts +35 -14
- package/src/cloudflare.ts +5 -0
- package/src/db/Db.ts +59 -3
- package/src/db/Schema.ts +20 -1
- package/src/db/tables/adminAuditLogs.ts +47 -0
- package/src/db/tables/invitations.ts +1 -1
- package/src/db/tables/memberships.ts +59 -3
- package/src/db/tables/organizations.ts +148 -1
- package/src/db/tables/projects.ts +47 -0
- package/src/db/tables/webhookDeliveries.ts +83 -0
- package/src/internal/Auth.test.ts +571 -23
- package/src/internal/Auth.ts +166 -94
- package/src/internal/Cursor.test.ts +10 -0
- package/src/internal/Cursor.ts +5 -1
- package/src/internal/EdgeCache.test.ts +11 -0
- package/src/internal/EdgeCache.ts +2 -0
- package/src/internal/FundingRoutes.test.ts +8 -8
- package/src/internal/FundingRoutes.ts +25 -29
- package/src/internal/Log.test.ts +41 -3
- package/src/internal/Log.ts +23 -4
- package/src/internal/Store.test-d.ts +15 -0
- package/src/internal/Store.test.ts +153 -2
- package/src/internal/Store.ts +157 -41
- package/src/internal/index.test.ts +34 -0
- package/src/internal/index.ts +26 -0
|
@@ -2,7 +2,7 @@ import * as FundingRoutes from './FundingRoutes.js'
|
|
|
2
2
|
|
|
3
3
|
const baseUsdcRequest = {
|
|
4
4
|
limit: 10,
|
|
5
|
-
provider: '
|
|
5
|
+
provider: 'relay',
|
|
6
6
|
sourceAmount: '1000000',
|
|
7
7
|
sourceAmountUnits: 'baseUnits',
|
|
8
8
|
sourceChain: 'base',
|
|
@@ -15,7 +15,7 @@ describe('listRoutes', () => {
|
|
|
15
15
|
const routes = await FundingRoutes.listRoutes({
|
|
16
16
|
...baseUsdcRequest,
|
|
17
17
|
adapters: {
|
|
18
|
-
|
|
18
|
+
relay: {
|
|
19
19
|
async probeQuote(input, signal) {
|
|
20
20
|
seen = input
|
|
21
21
|
expect(signal.aborted).toBe(false)
|
|
@@ -53,14 +53,14 @@ describe('listRoutes', () => {
|
|
|
53
53
|
},
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
expect(seen?.route.id).toBe('base-usdc-tempo-usdce-
|
|
56
|
+
expect(seen?.route.id).toBe('base-usdc-tempo-usdce-relay')
|
|
57
57
|
expect(seen?.sourceAmount.amount).toBe('1000000')
|
|
58
58
|
expect(seen?.sourceChain.id).toBe('eip155:8453')
|
|
59
59
|
expect(seen?.sourceToken.symbol).toBe('USDC')
|
|
60
60
|
expect(routes).toHaveLength(1)
|
|
61
61
|
expect(routes[0]).toMatchObject({
|
|
62
62
|
errorCode: null,
|
|
63
|
-
id: 'base-usdc-tempo-usdce-
|
|
63
|
+
id: 'base-usdc-tempo-usdce-relay',
|
|
64
64
|
quality: {
|
|
65
65
|
availableVolumeUsd: '1000.00',
|
|
66
66
|
estimatedSeconds: 4,
|
|
@@ -94,7 +94,7 @@ describe('listRoutes', () => {
|
|
|
94
94
|
const routes = await FundingRoutes.listRoutes({
|
|
95
95
|
...baseUsdcRequest,
|
|
96
96
|
adapters: {
|
|
97
|
-
|
|
97
|
+
bridge: {
|
|
98
98
|
async probeQuote() {
|
|
99
99
|
calls += 1
|
|
100
100
|
throw new Error('unexpected call')
|
|
@@ -119,7 +119,7 @@ describe('listRoutes', () => {
|
|
|
119
119
|
const routes = await FundingRoutes.listRoutes({
|
|
120
120
|
...baseUsdcRequest,
|
|
121
121
|
adapters: {
|
|
122
|
-
|
|
122
|
+
relay: {
|
|
123
123
|
async probeQuote() {
|
|
124
124
|
throw new Error('upstream unavailable')
|
|
125
125
|
},
|
|
@@ -145,7 +145,7 @@ describe('listRoutes', () => {
|
|
|
145
145
|
...baseUsdcRequest,
|
|
146
146
|
adapterTimeoutMs: 1,
|
|
147
147
|
adapters: {
|
|
148
|
-
|
|
148
|
+
relay: {
|
|
149
149
|
probeQuote(_input, signal) {
|
|
150
150
|
signal.addEventListener('abort', () => {
|
|
151
151
|
aborted = true
|
|
@@ -174,7 +174,7 @@ describe('listRoutes', () => {
|
|
|
174
174
|
const routes = await FundingRoutes.listRoutes({
|
|
175
175
|
...baseUsdcRequest,
|
|
176
176
|
adapters: {
|
|
177
|
-
|
|
177
|
+
relay: {
|
|
178
178
|
async probeQuote(input) {
|
|
179
179
|
seen = input
|
|
180
180
|
return {
|
|
@@ -18,7 +18,6 @@ const sourceChains = [
|
|
|
18
18
|
kind: 'evm',
|
|
19
19
|
name: 'Ethereum',
|
|
20
20
|
providerIds: {
|
|
21
|
-
across: '1',
|
|
22
21
|
bridge: 'ethereum',
|
|
23
22
|
bungee: '1',
|
|
24
23
|
lifi: '1',
|
|
@@ -75,7 +74,6 @@ const sourceChains = [
|
|
|
75
74
|
kind: 'evm',
|
|
76
75
|
name: 'Base',
|
|
77
76
|
providerIds: {
|
|
78
|
-
across: '8453',
|
|
79
77
|
bridge: 'base',
|
|
80
78
|
bungee: '8453',
|
|
81
79
|
lifi: '8453',
|
|
@@ -116,7 +114,6 @@ const sourceChains = [
|
|
|
116
114
|
kind: 'evm',
|
|
117
115
|
name: 'BNB Smart Chain',
|
|
118
116
|
providerIds: {
|
|
119
|
-
across: '56',
|
|
120
117
|
bungee: '56',
|
|
121
118
|
lifi: '56',
|
|
122
119
|
relay: '56',
|
|
@@ -241,14 +238,6 @@ const destinationTokens = [
|
|
|
241
238
|
|
|
242
239
|
/** Providers exposed by the generic provider catalog. */
|
|
243
240
|
const providers = [
|
|
244
|
-
{
|
|
245
|
-
capabilities: ['fundingRoutes'],
|
|
246
|
-
id: 'across',
|
|
247
|
-
kind: 'intentBridge',
|
|
248
|
-
name: 'Across',
|
|
249
|
-
requiresApiKey: true,
|
|
250
|
-
status: 'inventoryOnly',
|
|
251
|
-
},
|
|
252
241
|
{
|
|
253
242
|
capabilities: ['fundingRoutes'],
|
|
254
243
|
id: 'relay',
|
|
@@ -307,7 +296,9 @@ export type ChainRef = ReturnType<typeof formatChain> | typeof destinationChain
|
|
|
307
296
|
export type TokenAmount = ReturnType<typeof resolveSourceAmount>
|
|
308
297
|
|
|
309
298
|
/** Normalized token reference passed to provider adapters. */
|
|
310
|
-
export type TokenRef =
|
|
299
|
+
export type TokenRef =
|
|
300
|
+
| ReturnType<typeof formatDestinationToken>
|
|
301
|
+
| ReturnType<typeof formatSourceToken>
|
|
311
302
|
|
|
312
303
|
/** A normalized funding route candidate returned by route discovery. */
|
|
313
304
|
export type RouteCandidate = ReturnType<typeof formatRouteCandidate>
|
|
@@ -422,7 +413,6 @@ export type ProviderAdapters = Partial<Record<ProviderId, ProviderAdapter>>
|
|
|
422
413
|
|
|
423
414
|
/** Static provider support rows used until live quote adapters are configured. */
|
|
424
415
|
const inventory: readonly InventoryEntry[] = [
|
|
425
|
-
{ destinationToken: 'usdce', provider: 'across', sourceChains: ['ethereum', 'base', 'bnb'], sourceTokens: ['USDC'] }, // prettier-ignore
|
|
426
416
|
{ destinationToken: 'usdce', provider: 'relay', sourceChains: ['ethereum', 'base', 'bnb'], sourceTokens: ['USDC', 'USDT'] }, // prettier-ignore
|
|
427
417
|
{ destinationToken: 'usdce', provider: 'bridge', sourceChains: ['ethereum', 'base', 'solana'], sourceTokens: ['USDC'] }, // prettier-ignore
|
|
428
418
|
{ destinationToken: 'usdce', provider: 'bridge', sourceChains: ['ethereum', 'solana', 'tron'], sourceTokens: ['USDT'] }, // prettier-ignore
|
|
@@ -472,7 +462,10 @@ export declare namespace listProviders {
|
|
|
472
462
|
/** Parses the comma-separated `include` query parameter. */
|
|
473
463
|
export function parseInclude(input: string | undefined) {
|
|
474
464
|
if (input === undefined || input.trim() === '') return []
|
|
475
|
-
const parsed = input
|
|
465
|
+
const parsed = input
|
|
466
|
+
.split(',')
|
|
467
|
+
.map((part) => part.trim())
|
|
468
|
+
.filter(Boolean)
|
|
476
469
|
for (const include of parsed)
|
|
477
470
|
if (!(includes as readonly string[]).includes(include)) throw new UnsupportedIncludeError()
|
|
478
471
|
return [...new Set(parsed)] as readonly Include[]
|
|
@@ -608,19 +601,22 @@ async function enrichRoute(
|
|
|
608
601
|
if (!adapter) return route
|
|
609
602
|
|
|
610
603
|
try {
|
|
611
|
-
const quote = await
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
604
|
+
const quote = await runWithTimeout(
|
|
605
|
+
(signal) =>
|
|
606
|
+
adapter.probeQuote(
|
|
607
|
+
{
|
|
608
|
+
cache: options.cache,
|
|
609
|
+
destinationChain: route.destinationChain,
|
|
610
|
+
destinationToken: route.destinationToken,
|
|
611
|
+
include: options.include,
|
|
612
|
+
request: options.request,
|
|
613
|
+
route,
|
|
614
|
+
sourceAmount: route.sourceAmount,
|
|
615
|
+
sourceChain: route.sourceChain,
|
|
616
|
+
sourceToken: route.sourceToken,
|
|
617
|
+
},
|
|
618
|
+
signal,
|
|
619
|
+
),
|
|
624
620
|
options.timeoutMs,
|
|
625
621
|
)
|
|
626
622
|
return {
|
|
@@ -770,11 +766,11 @@ function markRouteError(route: RouteCandidate, errorCode: string) {
|
|
|
770
766
|
}
|
|
771
767
|
}
|
|
772
768
|
|
|
773
|
-
function
|
|
769
|
+
function runWithTimeout<result>(fn: (signal: AbortSignal) => Promise<result>, timeoutMs: number) {
|
|
774
770
|
const controller = new AbortController()
|
|
775
771
|
const timeout = setTimeout(() => controller.abort(), timeoutMs)
|
|
776
772
|
return Promise.race([
|
|
777
|
-
|
|
773
|
+
fn(controller.signal),
|
|
778
774
|
new Promise<never>((_, reject) => {
|
|
779
775
|
if (controller.signal.aborted) {
|
|
780
776
|
reject(new ProviderAdapterTimeoutError())
|
package/src/internal/Log.test.ts
CHANGED
|
@@ -65,8 +65,7 @@ describe('middleware', () => {
|
|
|
65
65
|
})
|
|
66
66
|
api.get('/secure', (c) => c.json({ ok: true }, 200))
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
await api.request(`/secure?apiKey=${token}`)
|
|
68
|
+
await api.request('/secure?key=opaque-secret&chainId=mainnet')
|
|
70
69
|
|
|
71
70
|
expect(stable(entries[0]!)).toMatchInlineSnapshot(`
|
|
72
71
|
{
|
|
@@ -80,7 +79,7 @@ describe('middleware', () => {
|
|
|
80
79
|
"orgId": "org_1",
|
|
81
80
|
"type": "api_key",
|
|
82
81
|
},
|
|
83
|
-
"query": "
|
|
82
|
+
"query": "chainId=mainnet",
|
|
84
83
|
"route": "/secure",
|
|
85
84
|
"status": 200,
|
|
86
85
|
}
|
|
@@ -139,6 +138,45 @@ describe('middleware', () => {
|
|
|
139
138
|
`)
|
|
140
139
|
})
|
|
141
140
|
|
|
141
|
+
test('includes redacted provider failure details', async () => {
|
|
142
|
+
const entries: Log.Entry[] = []
|
|
143
|
+
const api = app({ emit: (entry) => void entries.push(entry) })
|
|
144
|
+
api.get('/provider', (c) => {
|
|
145
|
+
c.set('providerFailure', {
|
|
146
|
+
code: 'UNAUTHORIZED',
|
|
147
|
+
failure: 'http',
|
|
148
|
+
id: 'relay',
|
|
149
|
+
operation: 'quote/v2',
|
|
150
|
+
status: 401,
|
|
151
|
+
})
|
|
152
|
+
return Response.error(c, {
|
|
153
|
+
code: 'provider_error',
|
|
154
|
+
message: 'Provider request failed',
|
|
155
|
+
status: 502,
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
await api.request('/provider')
|
|
160
|
+
|
|
161
|
+
expect(stable(entries[0]!)).toMatchInlineSnapshot(`
|
|
162
|
+
{
|
|
163
|
+
"errorCode": "provider_error",
|
|
164
|
+
"level": "error",
|
|
165
|
+
"method": "GET",
|
|
166
|
+
"path": "/provider",
|
|
167
|
+
"provider": {
|
|
168
|
+
"code": "UNAUTHORIZED",
|
|
169
|
+
"failure": "http",
|
|
170
|
+
"id": "relay",
|
|
171
|
+
"operation": "quote/v2",
|
|
172
|
+
"status": 401,
|
|
173
|
+
},
|
|
174
|
+
"route": "/provider",
|
|
175
|
+
"status": 502,
|
|
176
|
+
}
|
|
177
|
+
`)
|
|
178
|
+
})
|
|
179
|
+
|
|
142
180
|
test('logs thrown errors at error level', async () => {
|
|
143
181
|
const entries: Log.Entry[] = []
|
|
144
182
|
const api = app({ emit: (entry) => void entries.push(entry) })
|
package/src/internal/Log.ts
CHANGED
|
@@ -10,6 +10,8 @@ import type * as Timing from './Timing.js'
|
|
|
10
10
|
export type Variables = {
|
|
11
11
|
/** Stable error code recorded by `Response.error` for the request log entry. */
|
|
12
12
|
errorCode?: string | undefined
|
|
13
|
+
/** Redacted provider failure details recorded by an upstream adapter. */
|
|
14
|
+
providerFailure?: Entry['provider'] | undefined
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
/** Hono environment shape used by the request logger. */
|
|
@@ -55,12 +57,27 @@ export type Entry = {
|
|
|
55
57
|
id: string
|
|
56
58
|
/** Owning organization id for API-key callers. */
|
|
57
59
|
orgId?: string | undefined
|
|
58
|
-
/**
|
|
60
|
+
/** Attributed project id for API-key callers. */
|
|
59
61
|
projectId?: string | undefined
|
|
60
62
|
/** Principal kind. */
|
|
61
63
|
type: 'api_key' | 'public' | 'session' | 'super_admin'
|
|
62
64
|
}
|
|
63
65
|
| undefined
|
|
66
|
+
/** Redacted upstream provider failure details, when an adapter request fails. */
|
|
67
|
+
provider?:
|
|
68
|
+
| {
|
|
69
|
+
/** Stable provider error code, when one is safe to retain. */
|
|
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
|
|
64
81
|
/** Raw query string (no leading `?`) with API-key tokens redacted, when present. */
|
|
65
82
|
query?: string | undefined
|
|
66
83
|
/** Rate limit state from the `RateLimit-*` response headers, when enforced. */
|
|
@@ -110,12 +127,15 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
110
127
|
const duration = performance.now() - start
|
|
111
128
|
|
|
112
129
|
const url = new URL(c.req.url)
|
|
130
|
+
url.searchParams.delete('key')
|
|
131
|
+
const query = ApiKey.redact(url.search.slice(1))
|
|
113
132
|
const status = c.res.status
|
|
114
133
|
// Hono cannot resolve variable types through the generic `environment`,
|
|
115
134
|
// so shared-variable reads are cast from the environment contract above
|
|
116
135
|
// (the same pattern EdgeCache uses for its cross-layer reads).
|
|
117
136
|
const principal = c.get('principal' as never) as Auth.Principal | undefined
|
|
118
137
|
const errorCode = c.get('errorCode' as never) as string | undefined
|
|
138
|
+
const provider = c.get('providerFailure' as never) as Entry['provider']
|
|
119
139
|
|
|
120
140
|
// The `request` total metric duplicates `duration` (measured here, one
|
|
121
141
|
// layer further out), so only operation timings are kept.
|
|
@@ -165,9 +185,8 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
165
185
|
type: principal.type,
|
|
166
186
|
},
|
|
167
187
|
}),
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
...(url.search.length > 1 ? { query: ApiKey.redact(url.search.slice(1)) } : {}),
|
|
188
|
+
...(provider === undefined ? {} : { provider }),
|
|
189
|
+
...(query ? { query } : {}),
|
|
171
190
|
...(rateLimit === undefined ? {} : { rateLimit }),
|
|
172
191
|
requestId: c.get('requestId' as never) as string,
|
|
173
192
|
route: c.req.routePath,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Store as MppStore } from 'mppx'
|
|
2
|
+
import { Store } from 'tapimo'
|
|
3
|
+
|
|
4
|
+
declare const namespace: Store.durableObject.Namespace
|
|
5
|
+
|
|
6
|
+
type Items = {
|
|
7
|
+
channel: { spent: bigint }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('durableObject', () => {
|
|
11
|
+
test('matches typed atomic stores', () => {
|
|
12
|
+
expectTypeOf(Store.durableObject(namespace)).toExtend<MppStore.AtomicStore<Items>>()
|
|
13
|
+
expectTypeOf(Store.durableObject<Items>(namespace)).toEqualTypeOf<Store.AtomicState<Items>>()
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Store } from 'tapimo'
|
|
2
|
+
import * as TestStore from '../../test/Store.js'
|
|
2
3
|
|
|
3
4
|
describe('from', () => {
|
|
4
5
|
const ops = {
|
|
@@ -327,16 +328,162 @@ describe('cloudflareKv', () => {
|
|
|
327
328
|
})
|
|
328
329
|
|
|
329
330
|
describe('durableObject', () => {
|
|
330
|
-
|
|
331
|
-
|
|
331
|
+
type TypedItems = {
|
|
332
|
+
channel: { spent: bigint }
|
|
333
|
+
other: bigint
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Derive native RPC operations for in-memory fakes.
|
|
332
337
|
function stubFor(backing: Store.State): Store.durableObject.Stub {
|
|
333
338
|
return {
|
|
334
339
|
...backing,
|
|
340
|
+
deleteIf: (key, expected) => backing.deleteIf!(key, expected),
|
|
335
341
|
increment: (key, options) => Store.increment(backing, key, options),
|
|
336
342
|
swap: (key, expected, next, options) => backing.swap!(key, expected, next, options),
|
|
337
343
|
}
|
|
338
344
|
}
|
|
339
345
|
|
|
346
|
+
test('supports typed values with per-key sharding by default', async () => {
|
|
347
|
+
const { names, namespace } = TestStore.durableObjectNamespace()
|
|
348
|
+
const storeA = Store.durableObject<TypedItems>(namespace)
|
|
349
|
+
const storeB = Store.durableObject<TypedItems>(namespace)
|
|
350
|
+
|
|
351
|
+
await storeA.put('channel', { spent: 42n })
|
|
352
|
+
await storeA.put('other', 7n)
|
|
353
|
+
|
|
354
|
+
expect(await storeB.get('channel')).toMatchInlineSnapshot(`
|
|
355
|
+
{
|
|
356
|
+
"spent": 42n,
|
|
357
|
+
}
|
|
358
|
+
`)
|
|
359
|
+
expect([...new Set(names)]).toMatchInlineSnapshot(`
|
|
360
|
+
[
|
|
361
|
+
"channel",
|
|
362
|
+
"other",
|
|
363
|
+
]
|
|
364
|
+
`)
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
test('keeps typed updates linearizable', async () => {
|
|
368
|
+
const { namespace } = TestStore.durableObjectNamespace()
|
|
369
|
+
const storeA = Store.durableObject<TypedItems>(namespace)
|
|
370
|
+
const storeB = Store.durableObject<TypedItems>(namespace)
|
|
371
|
+
await storeA.put('channel', { spent: 0n })
|
|
372
|
+
|
|
373
|
+
for (let index = 0; index < 20; index++)
|
|
374
|
+
await Promise.all([
|
|
375
|
+
storeA.update('channel', (current) => ({
|
|
376
|
+
op: 'set',
|
|
377
|
+
result: undefined,
|
|
378
|
+
value: { spent: (current?.spent ?? 0n) + 1n },
|
|
379
|
+
})),
|
|
380
|
+
storeB.update('channel', (current) => ({
|
|
381
|
+
op: 'set',
|
|
382
|
+
result: undefined,
|
|
383
|
+
value: { spent: (current?.spent ?? 0n) + 1n },
|
|
384
|
+
})),
|
|
385
|
+
])
|
|
386
|
+
|
|
387
|
+
expect(await storeA.get('channel')).toMatchInlineSnapshot(`
|
|
388
|
+
{
|
|
389
|
+
"spent": 40n,
|
|
390
|
+
}
|
|
391
|
+
`)
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
test('supports no-op and delete update results', async () => {
|
|
395
|
+
const { getState, namespace } = TestStore.durableObjectNamespace()
|
|
396
|
+
const store = Store.durableObject<TypedItems>(namespace)
|
|
397
|
+
await store.put('channel', { spent: 42n })
|
|
398
|
+
|
|
399
|
+
const unchanged = await store.update('channel', (current) => ({
|
|
400
|
+
op: 'noop',
|
|
401
|
+
result: current?.spent,
|
|
402
|
+
}))
|
|
403
|
+
const deleted = await store.update('channel', () => ({ op: 'delete', result: 'deleted' }))
|
|
404
|
+
|
|
405
|
+
expect(unchanged).toMatchInlineSnapshot(`42n`)
|
|
406
|
+
expect(deleted).toMatchInlineSnapshot(`"deleted"`)
|
|
407
|
+
expect(await store.get('channel')).toBeNull()
|
|
408
|
+
expect(await getState('channel').list()).toMatchInlineSnapshot(`
|
|
409
|
+
{
|
|
410
|
+
"keys": [],
|
|
411
|
+
}
|
|
412
|
+
`)
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
test('rejects typed values written with an unknown codec version', async () => {
|
|
416
|
+
const { getState, namespace } = TestStore.durableObjectNamespace()
|
|
417
|
+
await getState('channel').put('channel', 'tempo-api:store:json:v2:{}')
|
|
418
|
+
const store = Store.durableObject<TypedItems>(namespace)
|
|
419
|
+
|
|
420
|
+
await expect(store.get('channel')).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
421
|
+
`[Store.DurableObjectCodecError: Unsupported Durable Object store codec version: v2]`,
|
|
422
|
+
)
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
test('bounds typed update contention', async () => {
|
|
426
|
+
const backing = Store.memory()
|
|
427
|
+
const namespace = {
|
|
428
|
+
getByName() {
|
|
429
|
+
return { ...stubFor(backing), swap: async () => false }
|
|
430
|
+
},
|
|
431
|
+
} satisfies Store.durableObject.Namespace
|
|
432
|
+
const store = Store.durableObject<{ channel: bigint }>(namespace)
|
|
433
|
+
let updates = 0
|
|
434
|
+
|
|
435
|
+
await expect(
|
|
436
|
+
store.update('channel', () => {
|
|
437
|
+
updates += 1
|
|
438
|
+
return { op: 'set', result: undefined, value: 1n }
|
|
439
|
+
}),
|
|
440
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
441
|
+
`[Store.UpdateContentionError: Store update on "channel" lost 8 compare-and-swap attempts.]`,
|
|
442
|
+
)
|
|
443
|
+
expect(updates).toMatchInlineSnapshot(`8`)
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
test('does not retry ambiguous typed update writes', async () => {
|
|
447
|
+
const backing = Store.memory()
|
|
448
|
+
let swaps = 0
|
|
449
|
+
const namespace = {
|
|
450
|
+
getByName() {
|
|
451
|
+
return {
|
|
452
|
+
...stubFor(backing),
|
|
453
|
+
swap() {
|
|
454
|
+
swaps += 1
|
|
455
|
+
throw Object.assign(new Error('Network connection lost.'), { retryable: true })
|
|
456
|
+
},
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
} satisfies Store.durableObject.Namespace
|
|
460
|
+
const store = Store.durableObject<{ channel: bigint }>(namespace)
|
|
461
|
+
|
|
462
|
+
await expect(
|
|
463
|
+
store.update('channel', () => ({ op: 'set', result: undefined, value: 1n })),
|
|
464
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Network connection lost.]`)
|
|
465
|
+
expect(swaps).toMatchInlineSnapshot(`1`)
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
test('round trips strings that use the codec namespace', async () => {
|
|
469
|
+
const { namespace } = TestStore.durableObjectNamespace()
|
|
470
|
+
const store = Store.durableObject(namespace)
|
|
471
|
+
const value = 'tempo-api:store:json:v2:not-a-cell'
|
|
472
|
+
|
|
473
|
+
await store.put('key', value)
|
|
474
|
+
|
|
475
|
+
expect(await store.get<string>('key')).toBe(value)
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
test('rejects unserializable typed values', async () => {
|
|
479
|
+
const { namespace } = TestStore.durableObjectNamespace()
|
|
480
|
+
const store = Store.durableObject(namespace)
|
|
481
|
+
|
|
482
|
+
await expect(store.put('invalid', undefined)).rejects.toThrow(
|
|
483
|
+
'Durable Object store value is not serializable',
|
|
484
|
+
)
|
|
485
|
+
})
|
|
486
|
+
|
|
340
487
|
test('adapts a Durable Object namespace without caching stubs', async () => {
|
|
341
488
|
const calls: string[] = []
|
|
342
489
|
const backing = Store.memory({
|
|
@@ -385,6 +532,7 @@ describe('durableObject', () => {
|
|
|
385
532
|
objects.set(name, backing)
|
|
386
533
|
const stub = {
|
|
387
534
|
delete: (key) => backing.delete(key),
|
|
535
|
+
deleteIf: (key, expected) => backing.deleteIf!(key, expected),
|
|
388
536
|
get: (key) => backing.get(key),
|
|
389
537
|
increment: (key, options) => Store.increment(backing, key, options),
|
|
390
538
|
list: (options) => backing.list(options),
|
|
@@ -541,6 +689,9 @@ describe('durableObject', () => {
|
|
|
541
689
|
],
|
|
542
690
|
}
|
|
543
691
|
`)
|
|
692
|
+
expect(await store.deleteIf!('secret:2', 'wrong')).toBe(false)
|
|
693
|
+
expect(await store.deleteIf!('secret:2', 'beta')).toBe(true)
|
|
694
|
+
expect(await store.get('secret:2')).toBeNull()
|
|
544
695
|
expect(storage.cells.has('secret:1')).toMatchInlineSnapshot(`false`)
|
|
545
696
|
})
|
|
546
697
|
})
|