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.
Files changed (263) hide show
  1. package/README.md +1 -1
  2. package/dist/ApiKey.d.ts +1 -9
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +2 -3
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +53 -27
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +32 -17
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +1990 -1442
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +23 -9
  13. package/dist/App.js.map +1 -1
  14. package/dist/Assets.d.ts +10 -0
  15. package/dist/Assets.d.ts.map +1 -1
  16. package/dist/Assets.js +4 -0
  17. package/dist/Assets.js.map +1 -1
  18. package/dist/Auth.d.ts +2 -1
  19. package/dist/Auth.d.ts.map +1 -1
  20. package/dist/Auth.js +2 -1
  21. package/dist/Auth.js.map +1 -1
  22. package/dist/Scope.d.ts +40 -15
  23. package/dist/Scope.d.ts.map +1 -1
  24. package/dist/Scope.js +44 -13
  25. package/dist/Scope.js.map +1 -1
  26. package/dist/admin/App.d.ts +560 -9
  27. package/dist/admin/App.d.ts.map +1 -1
  28. package/dist/admin/App.js +38 -6
  29. package/dist/admin/App.js.map +1 -1
  30. package/dist/admin/apps/api-keys.d.ts +10 -24
  31. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  32. package/dist/admin/apps/api-keys.js +10 -3
  33. package/dist/admin/apps/api-keys.js.map +1 -1
  34. package/dist/admin/apps/organizations.d.ts +833 -0
  35. package/dist/admin/apps/organizations.d.ts.map +1 -0
  36. package/dist/admin/apps/organizations.js +733 -0
  37. package/dist/admin/apps/organizations.js.map +1 -0
  38. package/dist/admin/apps/scopes.d.ts +8 -0
  39. package/dist/admin/apps/scopes.d.ts.map +1 -1
  40. package/dist/admin/apps/verified-tokens.d.ts +57 -57
  41. package/dist/admin/ui.generated.d.ts +1 -1
  42. package/dist/admin/ui.generated.d.ts.map +1 -1
  43. package/dist/admin/ui.generated.js +53 -13
  44. package/dist/admin/ui.generated.js.map +1 -1
  45. package/dist/analytics/Analytics.d.ts +10 -0
  46. package/dist/analytics/Analytics.d.ts.map +1 -1
  47. package/dist/analytics/Analytics.js +8 -0
  48. package/dist/analytics/Analytics.js.map +1 -1
  49. package/dist/analytics/tables/requestEvents.d.ts +9 -0
  50. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  51. package/dist/analytics/tables/requestEvents.js +14 -0
  52. package/dist/analytics/tables/requestEvents.js.map +1 -1
  53. package/dist/apps/data/App.d.ts +1304 -1304
  54. package/dist/apps/data/FundingRouteProviders.d.ts +4 -22
  55. package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
  56. package/dist/apps/data/FundingRouteProviders.js +10 -120
  57. package/dist/apps/data/FundingRouteProviders.js.map +1 -1
  58. package/dist/apps/data/routes/activities.d.ts +38 -38
  59. package/dist/apps/data/routes/addresses.d.ts +24 -24
  60. package/dist/apps/data/routes/blocks.d.ts +39 -39
  61. package/dist/apps/data/routes/fee-amm.d.ts +20 -20
  62. package/dist/apps/data/routes/funding.js +7 -7
  63. package/dist/apps/data/routes/funding.js.map +1 -1
  64. package/dist/apps/data/routes/indexer.d.ts +5 -5
  65. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  66. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  67. package/dist/apps/management/App.d.ts +808 -267
  68. package/dist/apps/management/App.d.ts.map +1 -1
  69. package/dist/apps/management/App.js +2 -2
  70. package/dist/apps/management/Billing.d.ts +3 -0
  71. package/dist/apps/management/Billing.d.ts.map +1 -1
  72. package/dist/apps/management/Billing.js +7 -2
  73. package/dist/apps/management/Billing.js.map +1 -1
  74. package/dist/apps/management/routes/api-keys.d.ts +116 -100
  75. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  76. package/dist/apps/management/routes/api-keys.js +41 -30
  77. package/dist/apps/management/routes/api-keys.js.map +1 -1
  78. package/dist/apps/management/routes/billing.d.ts +182 -50
  79. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  80. package/dist/apps/management/routes/billing.js +14 -14
  81. package/dist/apps/management/routes/billing.js.map +1 -1
  82. package/dist/apps/management/routes/invitations.d.ts +93 -20
  83. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  84. package/dist/apps/management/routes/invitations.js +12 -15
  85. package/dist/apps/management/routes/invitations.js.map +1 -1
  86. package/dist/apps/management/routes/members.d.ts +89 -18
  87. package/dist/apps/management/routes/members.d.ts.map +1 -1
  88. package/dist/apps/management/routes/members.js +10 -10
  89. package/dist/apps/management/routes/members.js.map +1 -1
  90. package/dist/apps/management/routes/orgs.d.ts +100 -40
  91. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  92. package/dist/apps/management/routes/orgs.js +19 -19
  93. package/dist/apps/management/routes/orgs.js.map +1 -1
  94. package/dist/apps/management/routes/projects.d.ts +122 -33
  95. package/dist/apps/management/routes/projects.d.ts.map +1 -1
  96. package/dist/apps/management/routes/projects.js +12 -13
  97. package/dist/apps/management/routes/projects.js.map +1 -1
  98. package/dist/apps/management/routes/scopes.d.ts +4 -18
  99. package/dist/apps/management/routes/scopes.d.ts.map +1 -1
  100. package/dist/apps/management/routes/scopes.js +6 -7
  101. package/dist/apps/management/routes/scopes.js.map +1 -1
  102. package/dist/apps/management/routes/usage.d.ts +63 -19
  103. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  104. package/dist/apps/management/routes/usage.js +9 -10
  105. package/dist/apps/management/routes/usage.js.map +1 -1
  106. package/dist/apps/mcp.d.ts.map +1 -1
  107. package/dist/apps/mcp.js +38 -4
  108. package/dist/apps/mcp.js.map +1 -1
  109. package/dist/apps/relay/App.d.ts +26 -4
  110. package/dist/apps/relay/App.d.ts.map +1 -1
  111. package/dist/apps/relay/App.js +17 -14
  112. package/dist/apps/relay/App.js.map +1 -1
  113. package/dist/cloudflare.d.ts +2 -0
  114. package/dist/cloudflare.d.ts.map +1 -1
  115. package/dist/cloudflare.js +4 -0
  116. package/dist/cloudflare.js.map +1 -1
  117. package/dist/db/Db.d.ts +5 -3
  118. package/dist/db/Db.d.ts.map +1 -1
  119. package/dist/db/Db.js +52 -0
  120. package/dist/db/Db.js.map +1 -1
  121. package/dist/db/Schema.d.ts +13 -0
  122. package/dist/db/Schema.d.ts.map +1 -1
  123. package/dist/db/Schema.js +16 -1
  124. package/dist/db/Schema.js.map +1 -1
  125. package/dist/db/tables/adminAuditLogs.d.ts +27 -0
  126. package/dist/db/tables/adminAuditLogs.d.ts.map +1 -0
  127. package/dist/db/tables/adminAuditLogs.js +19 -0
  128. package/dist/db/tables/adminAuditLogs.js.map +1 -0
  129. package/dist/db/tables/invitations.d.ts +1 -1
  130. package/dist/db/tables/invitations.d.ts.map +1 -1
  131. package/dist/db/tables/memberships.d.ts +32 -4
  132. package/dist/db/tables/memberships.d.ts.map +1 -1
  133. package/dist/db/tables/memberships.js +31 -0
  134. package/dist/db/tables/memberships.js.map +1 -1
  135. package/dist/db/tables/organizations.d.ts +43 -1
  136. package/dist/db/tables/organizations.d.ts.map +1 -1
  137. package/dist/db/tables/organizations.js +103 -0
  138. package/dist/db/tables/organizations.js.map +1 -1
  139. package/dist/db/tables/projects.d.ts +25 -0
  140. package/dist/db/tables/projects.d.ts.map +1 -1
  141. package/dist/db/tables/projects.js +22 -0
  142. package/dist/db/tables/projects.js.map +1 -1
  143. package/dist/db/tables/webhookDeliveries.d.ts +26 -0
  144. package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
  145. package/dist/db/tables/webhookDeliveries.js +37 -0
  146. package/dist/db/tables/webhookDeliveries.js.map +1 -1
  147. package/dist/internal/Auth.d.ts +46 -40
  148. package/dist/internal/Auth.d.ts.map +1 -1
  149. package/dist/internal/Auth.js +118 -69
  150. package/dist/internal/Auth.js.map +1 -1
  151. package/dist/internal/Cursor.d.ts +1 -1
  152. package/dist/internal/Cursor.d.ts.map +1 -1
  153. package/dist/internal/Cursor.js +4 -0
  154. package/dist/internal/Cursor.js.map +1 -1
  155. package/dist/internal/EdgeCache.d.ts.map +1 -1
  156. package/dist/internal/EdgeCache.js +2 -0
  157. package/dist/internal/EdgeCache.js.map +1 -1
  158. package/dist/internal/Errors.d.ts +6 -0
  159. package/dist/internal/Errors.d.ts.map +1 -0
  160. package/dist/internal/Errors.js +16 -0
  161. package/dist/internal/Errors.js.map +1 -0
  162. package/dist/internal/FundingRoutes.d.ts +15 -43
  163. package/dist/internal/FundingRoutes.d.ts.map +1 -1
  164. package/dist/internal/FundingRoutes.js +8 -17
  165. package/dist/internal/FundingRoutes.js.map +1 -1
  166. package/dist/internal/Log.d.ts +16 -1
  167. package/dist/internal/Log.d.ts.map +1 -1
  168. package/dist/internal/Log.js +5 -3
  169. package/dist/internal/Log.js.map +1 -1
  170. package/dist/internal/Store.d.ts +61 -16
  171. package/dist/internal/Store.d.ts.map +1 -1
  172. package/dist/internal/Store.js +72 -17
  173. package/dist/internal/Store.js.map +1 -1
  174. package/dist/internal/Viem.d.ts +1 -1
  175. package/dist/internal/index.d.ts +27 -0
  176. package/dist/internal/index.d.ts.map +1 -0
  177. package/dist/internal/index.js +27 -0
  178. package/dist/internal/index.js.map +1 -0
  179. package/package.json +8 -3
  180. package/src/ApiKey.ts +2 -3
  181. package/src/ApiKeys.ts +95 -17
  182. package/src/App.test.ts +104 -6
  183. package/src/App.ts +29 -9
  184. package/src/Assets.ts +8 -0
  185. package/src/Auth.test-d.ts +36 -0
  186. package/src/Auth.ts +20 -1
  187. package/src/Client.test-d.ts +17 -17
  188. package/src/Scope.test.ts +34 -0
  189. package/src/Scope.ts +53 -17
  190. package/src/admin/App.test.ts +33 -3
  191. package/src/admin/App.ts +49 -14
  192. package/src/admin/apps/api-keys.ts +10 -3
  193. package/src/admin/apps/organizations.test.ts +455 -0
  194. package/src/admin/apps/organizations.ts +953 -0
  195. package/src/admin/apps/scopes.test.ts +10 -0
  196. package/src/admin/ui/plugin.ts +1 -1
  197. package/src/admin/ui/src/components/MobileProfile.tsx +2 -0
  198. package/src/admin/ui/src/components/ThemeToggle.tsx +20 -0
  199. package/src/admin/ui/src/lib/api-keys.ts +36 -19
  200. package/src/admin/ui/src/lib/config.ts +2 -3
  201. package/src/admin/ui/src/lib/organizations.test.ts +89 -0
  202. package/src/admin/ui/src/lib/organizations.ts +262 -0
  203. package/src/admin/ui/src/lib/query-client.ts +3 -1
  204. package/src/admin/ui/src/lib/theme.ts +57 -0
  205. package/src/admin/ui/src/routeTree.gen.ts +47 -4
  206. package/src/admin/ui/src/routes/__root.tsx +18 -3
  207. package/src/admin/ui/src/routes/api-keys.tsx +52 -7
  208. package/src/admin/ui/src/routes/index.tsx +1 -1
  209. package/src/admin/ui/src/routes/organizations.tsx +158 -0
  210. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +710 -0
  211. package/src/admin/ui/src/start.ts +6 -0
  212. package/src/admin/ui.generated.ts +63 -13
  213. package/src/analytics/Analytics.ts +25 -0
  214. package/src/analytics/tables/requestEvents.ts +33 -0
  215. package/src/apps/composition.test.ts +82 -2
  216. package/src/apps/data/FundingRouteProviders.test.ts +37 -93
  217. package/src/apps/data/FundingRouteProviders.ts +14 -161
  218. package/src/apps/data/routes/funding.test.ts +7 -42
  219. package/src/apps/data/routes/funding.ts +8 -8
  220. package/src/apps/data/routes/indexer.test.ts +2 -2
  221. package/src/apps/data/routes/webhooks.test.ts +2 -2
  222. package/src/apps/management/App.ts +2 -2
  223. package/src/apps/management/Billing.ts +10 -2
  224. package/src/apps/management/routes/api-keys.test.ts +137 -1
  225. package/src/apps/management/routes/api-keys.ts +63 -38
  226. package/src/apps/management/routes/billing.ts +14 -14
  227. package/src/apps/management/routes/invitations.test.ts +102 -1
  228. package/src/apps/management/routes/invitations.ts +13 -15
  229. package/src/apps/management/routes/members.ts +10 -10
  230. package/src/apps/management/routes/orgs.test.ts +78 -16
  231. package/src/apps/management/routes/orgs.ts +21 -20
  232. package/src/apps/management/routes/projects.ts +12 -13
  233. package/src/apps/management/routes/scopes.test.ts +16 -0
  234. package/src/apps/management/routes/scopes.ts +6 -7
  235. package/src/apps/management/routes/usage.ts +9 -14
  236. package/src/apps/mcp.test.ts +72 -7
  237. package/src/apps/mcp.ts +46 -4
  238. package/src/apps/relay/App.test.ts +23 -18
  239. package/src/apps/relay/App.ts +35 -14
  240. package/src/cloudflare.ts +5 -0
  241. package/src/db/Db.ts +59 -3
  242. package/src/db/Schema.ts +20 -1
  243. package/src/db/tables/adminAuditLogs.ts +47 -0
  244. package/src/db/tables/invitations.ts +1 -1
  245. package/src/db/tables/memberships.ts +59 -3
  246. package/src/db/tables/organizations.ts +148 -1
  247. package/src/db/tables/projects.ts +47 -0
  248. package/src/db/tables/webhookDeliveries.ts +83 -0
  249. package/src/internal/Auth.test.ts +571 -23
  250. package/src/internal/Auth.ts +166 -94
  251. package/src/internal/Cursor.test.ts +10 -0
  252. package/src/internal/Cursor.ts +5 -1
  253. package/src/internal/EdgeCache.test.ts +11 -0
  254. package/src/internal/EdgeCache.ts +2 -0
  255. package/src/internal/FundingRoutes.test.ts +8 -8
  256. package/src/internal/FundingRoutes.ts +25 -29
  257. package/src/internal/Log.test.ts +41 -3
  258. package/src/internal/Log.ts +23 -4
  259. package/src/internal/Store.test-d.ts +15 -0
  260. package/src/internal/Store.test.ts +153 -2
  261. package/src/internal/Store.ts +157 -41
  262. package/src/internal/index.test.ts +34 -0
  263. 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: 'across',
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
- across: {
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-across')
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-across',
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
- relay: {
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
- across: {
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
- across: {
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
- across: {
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 = ReturnType<typeof formatDestinationToken> | ReturnType<typeof formatSourceToken>
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.split(',').map((part) => part.trim()).filter(Boolean)
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 quoteWithTimeout(
612
- adapter,
613
- {
614
- cache: options.cache,
615
- destinationChain: route.destinationChain,
616
- destinationToken: route.destinationToken,
617
- include: options.include,
618
- request: options.request,
619
- route,
620
- sourceAmount: route.sourceAmount,
621
- sourceChain: route.sourceChain,
622
- sourceToken: route.sourceToken,
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 quoteWithTimeout(adapter: ProviderAdapter, input: QuoteProbeInput, timeoutMs: number) {
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
- adapter.probeQuote(input, controller.signal),
773
+ fn(controller.signal),
778
774
  new Promise<never>((_, reject) => {
779
775
  if (controller.signal.aborted) {
780
776
  reject(new ProviderAdapterTimeoutError())
@@ -65,8 +65,7 @@ describe('middleware', () => {
65
65
  })
66
66
  api.get('/secure', (c) => c.json({ ok: true }, 200))
67
67
 
68
- const token = `tempo:sk:${'a'.repeat(48)}`
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": "apiKey=tempo:sk:…",
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) })
@@ -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
- /** Owning project id for project-attributed API-key callers. */
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
- // Keys belong in headers, but a confused client may pass one in the
169
- // query string; redact defensively before it reaches a log store.
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
- // The stub contract requires native `increment` and `swap` (our DO class
331
- // implements them); derive them for the in-memory fakes.
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
  })