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
@@ -19,6 +19,8 @@ describe('scopes', () => {
19
19
  [
20
20
  "data:read",
21
21
  "indexer:query",
22
+ "management:read",
23
+ "management:write",
22
24
  "rpc-relay:read",
23
25
  "rpc-relay:sponsor",
24
26
  "webhooks:read",
@@ -26,4 +28,18 @@ describe('scopes', () => {
26
28
  ]
27
29
  `)
28
30
  })
31
+
32
+ test('includes host scopes configured on the app', async () => {
33
+ const custom = {
34
+ description: 'Read project accounts.',
35
+ scope: 'accounts:read',
36
+ selfServe: true,
37
+ } as const satisfies Scope.Entry
38
+ const app = TestApp.create({ scopes: [custom] })
39
+ const response = await app.request('/v1/scopes')
40
+ const body = await TestApp.json(response, Scopes.schema.getScopes.Response)
41
+
42
+ expect(response.status).toBe(200)
43
+ expect(body.data).toEqual([...Scope.catalog, custom])
44
+ })
29
45
  })
@@ -6,7 +6,6 @@ import * as Auth from '../../../internal/Auth.js'
6
6
  import * as Cache from '../../../internal/Cache.js'
7
7
  import * as OpenApi from '../../../internal/OpenApi.js'
8
8
  import * as Response from '../../../internal/Response.js'
9
- import * as Scope from '../../../Scope.js'
10
9
 
11
10
  /** Zod schemas owned by the scope-catalog resource. */
12
11
  export namespace schema {
@@ -22,7 +21,7 @@ export namespace schema {
22
21
  z.meta({ examples: ['Read webhook subscriptions.'] }),
23
22
  ),
24
23
  scope: z
25
- .enum(Scope.catalog.map((entry) => entry.scope))
24
+ .string()
26
25
  .check(
27
26
  z.describe('Scope identifier used in API-key grants and route policies.'),
28
27
  z.meta({ examples: ['data:read'] }),
@@ -30,7 +29,7 @@ export namespace schema {
30
29
  selfServe: z
31
30
  .boolean()
32
31
  .check(
33
- z.describe('Whether a session may self-mint this scope (else super-admin-only).'),
32
+ z.describe('Whether an eligible session may self-mint this scope.'),
34
33
  z.meta({ examples: [true] }),
35
34
  ),
36
35
  })
@@ -47,13 +46,13 @@ export namespace schema {
47
46
 
48
47
  /**
49
48
  * Mounts the `/scopes` resource: the issuable API-key scope vocabulary, served
50
- * from {@link Scope.catalog}. Public catalog data: anonymous callers are served
49
+ * from the configured catalog. Public catalog data: anonymous callers are served
51
50
  * within the public quota; the session lane lets signed-in consoles skip it.
52
51
  */
53
52
  export function scopes() {
54
53
  return new Hono<App.Environment>().get(
55
54
  '/v1/scopes',
56
- Auth.policy({ public: true, session: true }),
55
+ Auth.policy({ apiKey: { scopes: [] }, public: true, session: true }),
57
56
  OpenApi.describeRoute({
58
57
  description: 'See which scopes can be granted to API keys.',
59
58
  operationId: 'getScopes',
@@ -65,7 +64,7 @@ export function scopes() {
65
64
  }),
66
65
  Cache.response<App.Environment>({
67
66
  cacheControl: Cache.policies.stable,
68
- name: 'tempo-api:scopes:v1',
67
+ name: 'tempo-api:scopes:v2',
69
68
  key: (c) => {
70
69
  const url = new URL(c.req.url)
71
70
  url.search = ''
@@ -76,7 +75,7 @@ export function scopes() {
76
75
  if (Auth.narrowAccess) return Auth.accessError(c)
77
76
  return c.json(
78
77
  Response.validated(schema.getScopes.Response, {
79
- data: Scope.catalog.map((entry) => ({ ...entry })),
78
+ data: c.get('scopeCatalog').map((entry) => ({ ...entry })),
80
79
  }),
81
80
  200,
82
81
  )
@@ -285,16 +285,12 @@ export namespace schema {
285
285
  }
286
286
  }
287
287
 
288
- /**
289
- * Mounts the usage surface: project request usage (read from a host-supplied
290
- * analytics capability) and an organization's sponsorship volume and fees
291
- * over time. Session lane; reads for members.
292
- */
288
+ /** Mounts organization request and sponsorship usage. Reads require membership or a scoped API key. */
293
289
  export function usage() {
294
290
  return new Hono<Environment>()
295
291
  .get(
296
292
  '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/usage/requests',
297
- Auth.policy({ session: true }),
293
+ Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
298
294
  Auth.ensureOrg(),
299
295
  OpenApi.validate('param', schema.getRequestUsage.Params, {
300
296
  code: 'param_invalid',
@@ -327,7 +323,7 @@ export function usage() {
327
323
  tags: ['Usage'],
328
324
  }),
329
325
  async (c) => {
330
- if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
326
+ if (Auth.narrowAccess) return Auth.accessError(c)
331
327
  if (Auth.narrowScope) return Auth.ensureOrgError(c)
332
328
  if (OpenApi.narrowValidation)
333
329
  return OpenApi.validationError(c, {
@@ -398,7 +394,7 @@ export function usage() {
398
394
  )
399
395
  .get(
400
396
  '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/usage/sponsorships',
401
- Auth.policy({ session: true }),
397
+ Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
402
398
  Auth.ensureOrg(),
403
399
  OpenApi.validate('param', schema.getSponsorshipUsage.Params, {
404
400
  code: 'param_invalid',
@@ -423,7 +419,7 @@ export function usage() {
423
419
  tags: ['Usage'],
424
420
  }),
425
421
  async (c) => {
426
- if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
422
+ if (Auth.narrowAccess) return Auth.accessError(c)
427
423
  if (Auth.narrowScope) return Auth.ensureOrgError(c)
428
424
  if (OpenApi.narrowValidation)
429
425
  return OpenApi.validationError(c, {
@@ -498,11 +494,10 @@ async function legacyKeyIds(
498
494
  ): Promise<readonly string[]> {
499
495
  const kv = c.get('kv')
500
496
  if (!kv) return []
501
- const records = await ApiKeys.listByOrg(
502
- kv.store,
503
- Auth.org(c).id,
504
- options.projectId === undefined ? {} : { projectId: options.projectId },
505
- )
497
+ const records = await ApiKeys.listByOrg(kv.store, Auth.org(c).id, {
498
+ ...(options.projectId === undefined ? {} : { projectId: options.projectId }),
499
+ scopeCatalog: c.get('scopeCatalog'),
500
+ })
506
501
  return records
507
502
  .filter(
508
503
  (record) => options.environment === undefined || record.environment === options.environment,
@@ -91,6 +91,46 @@ async function toolsCall(
91
91
  }
92
92
  }
93
93
 
94
+ /** Searches every page of the progressive tool catalog. */
95
+ async function toolsSearch(host: Host) {
96
+ const tools: { annotations?: { readOnlyHint?: boolean }; name: string }[] = []
97
+ let offset = 0
98
+ while (true) {
99
+ const result = resultJson(await toolsCall(host, 'search_tools', { limit: 20, offset })) as {
100
+ nextOffset?: number
101
+ tools: typeof tools
102
+ }
103
+ tools.push(...result.tools)
104
+ if (result.nextOffset === undefined) return tools
105
+ offset = result.nextOffset
106
+ }
107
+ }
108
+
109
+ /** Loads the complete schema and metadata for one catalog tool. */
110
+ async function toolDetails(host: Host, name: string) {
111
+ return resultJson(await toolsCall(host, 'get_tool_details', { name })) as {
112
+ annotations?: { readOnlyHint?: boolean }
113
+ inputSchema: { properties?: Record<string, { description?: string }> }
114
+ name: string
115
+ }
116
+ }
117
+
118
+ /** Inspects and executes a generated tool through its matching read or write gate. */
119
+ async function generatedToolCall(
120
+ host: Host,
121
+ name: string,
122
+ args: Record<string, unknown>,
123
+ headers: Record<string, string> = {},
124
+ ) {
125
+ const details = await toolDetails(host, name)
126
+ return toolsCall(
127
+ host,
128
+ details.annotations?.readOnlyHint === true ? 'call_read_tool' : 'call_write_tool',
129
+ { arguments: args, name },
130
+ headers,
131
+ )
132
+ }
133
+
94
134
  /** Parses a tool result: structured content when present, else the JSON text item. */
95
135
  function resultJson(result: Awaited<ReturnType<typeof toolsCall>>) {
96
136
  return result.structuredContent ?? JSON.parse(result.content[0]!.text)
@@ -108,7 +148,13 @@ test('scopes tools to the data domain', async () => {
108
148
  expect(Object.keys(spec.paths).some((path) => path.startsWith('/v1/orgs'))).toBe(true)
109
149
 
110
150
  await initialize(host)
111
- const tools = await toolsList(host)
151
+ expect(await toolsList(host)).toEqual([
152
+ 'search_tools',
153
+ 'get_tool_details',
154
+ 'call_read_tool',
155
+ 'call_write_tool',
156
+ ])
157
+ const tools = (await toolsSearch(host)).map((tool) => tool.name)
112
158
 
113
159
  expect(tools).toContain('v1_tokens_get')
114
160
  expect(tools).toContain('docs_search')
@@ -121,6 +167,25 @@ test('scopes tools to the data domain', async () => {
121
167
  expect(tools.some((name) => name.startsWith('v1_me'))).toBe(false)
122
168
  })
123
169
 
170
+ test('keeps tool input schemas compact', async () => {
171
+ const { host } = createHost()
172
+ await initialize(host)
173
+
174
+ const tool = await toolDetails(host, 'v1_tokens_get')
175
+ const properties = tool.inputSchema.properties ?? {}
176
+
177
+ // Credential inputs stay off the schema; auth rides the forwarded header.
178
+ expect(properties['authorization']).toBeUndefined()
179
+ expect(properties['tempo-api-key']).toBeUndefined()
180
+ // Shared parameters carry the terse tool descriptions.
181
+ expect(properties['limit']?.description).toBe('Items per page.')
182
+ expect(properties['cursor']?.description).toBe(
183
+ 'Keyset cursor from a previous response `nextCursor`; omit for the first page.',
184
+ )
185
+ // `compact` strips examples from generated schemas.
186
+ expect(JSON.stringify(tool.inputSchema)).not.toContain('"examples"')
187
+ })
188
+
124
189
  test('executes generated tools against the composed app', async () => {
125
190
  // Verified tokens are database-backed, so seed the default chain.
126
191
  const db = TestApp.database()
@@ -128,7 +193,7 @@ test('executes generated tools against the composed app', async () => {
128
193
  const { host } = createHost({ app: TestApp.create({ db }) })
129
194
 
130
195
  await initialize(host)
131
- const result = await toolsCall(host, 'v1_tokens_get', { verified: true })
196
+ const result = await generatedToolCall(host, 'v1_tokens_get', { verified: true })
132
197
 
133
198
  expect(result.isError).toBeFalsy()
134
199
  const body = resultJson(result) as { data: { verified: boolean }[]; nextCursor: unknown }
@@ -140,10 +205,10 @@ test('forwards the caller Authorization header to authenticated tools', async ()
140
205
  const { host } = createHost({ app: TestApp.create({ auth: { keys: [reader] } }) })
141
206
  await initialize(host)
142
207
 
143
- const denied = await toolsCall(host, 'v1_webhooks_get', {})
208
+ const denied = await generatedToolCall(host, 'v1_webhooks_get', {})
144
209
  expect(denied.isError).toBe(true)
145
210
 
146
- const allowed = await toolsCall(
211
+ const allowed = await generatedToolCall(
147
212
  host,
148
213
  'v1_webhooks_get',
149
214
  {},
@@ -157,7 +222,7 @@ test('proxies docs tool calls to the remote MCP source', async () => {
157
222
  const { host } = createHost({ docs: docsStub() })
158
223
  await initialize(host)
159
224
 
160
- const result = await toolsCall(host, 'docs_search', { query: 'fees' })
225
+ const result = await generatedToolCall(host, 'docs_search', { query: 'fees' })
161
226
 
162
227
  expect(result.isError).toBeFalsy()
163
228
  expect(resultJson(result)).toMatchObject({ hits: ['fees'] })
@@ -176,7 +241,7 @@ test('documents POST /mcp and keeps it open under auth enforcement', async () =>
176
241
  expect(operation?.operationId).toBe('mcpRequest')
177
242
  expect(operation?.tags).toEqual(['MCP'])
178
243
  // Anonymous and any-valid-key access; no required scopes.
179
- expect(operation?.security).toEqual([{ apiKey: [] }, { bearerAuth: [] }, {}])
244
+ expect(operation?.security).toEqual([{ apiKey: [] }, { queryApiKey: [] }, { bearerAuth: [] }, {}])
180
245
 
181
246
  // Surfaces under `Data API` in the reference, but the tool filter excludes the `MCP` tag.
182
247
  const groups = Object.fromEntries(spec['x-tagGroups'].map((group) => [group.name, group.tags]))
@@ -186,7 +251,7 @@ test('documents POST /mcp and keeps it open under auth enforcement', async () =>
186
251
  // Documenting the route makes it auth-enforced; the public lane keeps it
187
252
  // anonymously reachable, and the endpoint never surfaces itself as a tool.
188
253
  await initialize(app)
189
- const tools = await toolsList(app)
254
+ const tools = (await toolsSearch(app)).map((tool) => tool.name)
190
255
  expect(tools.length).toBeGreaterThan(0)
191
256
  expect(tools.some((name) => name.startsWith('mcp'))).toBe(false)
192
257
 
package/src/apps/mcp.ts CHANGED
@@ -15,7 +15,7 @@ const excludedTags = ['CoinGecko', 'MCP']
15
15
 
16
16
  /** Usage guidance surfaced to MCP clients on initialize. */
17
17
  const instructions =
18
- "Tools mirror the Tempo API's data domain. Authenticated tools accept a Tempo API key: send it as an `Authorization: Bearer` header on the HTTP request, or pass the `authorization` tool input per call. List endpoints paginate via `cursor`/`nextCursor`. `docs_*` tools search the Tempo documentation."
18
+ "Tools mirror the Tempo API's data domain. Authenticated tools accept a Tempo API key sent as an `Authorization: Bearer` header on the HTTP request. List endpoints paginate via `cursor`/`nextCursor`. `docs_*` tools search the Tempo documentation."
19
19
 
20
20
  /** Zod schemas owned by the MCP endpoint's OpenAPI description. */
21
21
  export namespace schema {
@@ -97,7 +97,15 @@ export function mcp(options: mcp.Options) {
97
97
  },
98
98
  mcp: { instructions },
99
99
  openapi: scope(document),
100
- openapiConfig: { forwardHeaders: ['authorization'], mode: 'namespace' },
100
+ // `compact` + `security: false` keep tool schemas lean: no examples,
101
+ // oversized regexes, or per-tool credential inputs (auth rides the
102
+ // forwarded `Authorization` header).
103
+ openapiConfig: {
104
+ compact: true,
105
+ forwardHeaders: ['authorization'],
106
+ mode: 'namespace',
107
+ security: false,
108
+ },
101
109
  version,
102
110
  })
103
111
  if (docs !== false)
@@ -139,7 +147,9 @@ export function mcp(options: mcp.Options) {
139
147
  example: {
140
148
  jsonrpc: '2.0',
141
149
  id: 1,
142
- result: { tools: [{ name: 'v1_tokens_get', description: 'List tokens' }] },
150
+ result: {
151
+ tools: [{ name: 'search_tools', description: 'Search available tools' }],
152
+ },
143
153
  },
144
154
  schema: OpenApi.resolver(schema.Response),
145
155
  },
@@ -217,11 +227,43 @@ function scope(document: Document): Openapi.OpenAPISpec {
217
227
  return tags_operation?.some((tag) => tags.has(tag)) ?? false
218
228
  })
219
229
  if (entries.some(([method]) => methods.includes(method)))
220
- paths[path] = Object.fromEntries(entries)
230
+ paths[path] = Object.fromEntries(
231
+ entries.map(([method, operation]) =>
232
+ methods.includes(method) ? [method, condense(operation)] : [method, operation],
233
+ ),
234
+ )
221
235
  }
222
236
  return { ...document, paths }
223
237
  }
224
238
 
239
+ /** Terse tool descriptions for parameters repeated across most operations; the public document keeps the full prose. */
240
+ const parameterDescriptions: Record<string, string> = {
241
+ chainId: 'Chain: `mainnet` (default), `testnet`, or a numeric chain id.',
242
+ cursor: 'Keyset cursor from a previous response `nextCursor`; omit for the first page.',
243
+ include: 'Comma-separated related resources to include.',
244
+ limit: 'Items per page.',
245
+ order: 'Sort order: `asc` or `desc` (default).',
246
+ page: '1-indexed page number; mutually exclusive with `cursor`.',
247
+ }
248
+
249
+ /** Swaps shared query-parameter prose for the terse tool descriptions. */
250
+ function condense(operation: unknown) {
251
+ const { parameters } = operation as {
252
+ parameters?: readonly { in?: string; name?: string }[] | undefined
253
+ }
254
+ if (!parameters) return operation
255
+ return {
256
+ ...(operation as object),
257
+ parameters: parameters.map((parameter) => {
258
+ const description =
259
+ parameter.in === 'query' && parameter.name
260
+ ? parameterDescriptions[parameter.name]
261
+ : undefined
262
+ return description ? { ...parameter, description } : parameter
263
+ }),
264
+ }
265
+ }
266
+
225
267
  export declare namespace mcp {
226
268
  /** Options for the MCP route group. */
227
269
  type Options = {
@@ -29,14 +29,14 @@ const senderAccount = Tempo.accounts[9]!
29
29
  /** Super admin secret configured on the end-to-end billing app. */
30
30
  const superAdminSecret = 'tempo:sk:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1'
31
31
 
32
- /** Keys covering the sponsorship policy matrix; all hold the base relay scope. */
32
+ /** Keys covering the sponsorship policy matrix. */
33
33
  const keys = [
34
34
  {
35
35
  id: 'key_sponsor',
36
36
  orgId: 'org_1',
37
37
  projectId: 'prj_1',
38
38
  environment: 'sandbox',
39
- scopes: ['rpc-relay:read', 'rpc-relay:sponsor'],
39
+ scopes: ['rpc-relay:sponsor'],
40
40
  token: 'secret_sponsor',
41
41
  },
42
42
  {
@@ -44,7 +44,7 @@ const keys = [
44
44
  orgId: 'org_1',
45
45
  projectId: 'prj_1',
46
46
  environment: 'production',
47
- scopes: ['rpc-relay:read', 'rpc-relay:sponsor'],
47
+ scopes: ['rpc-relay:sponsor'],
48
48
  token: 'secret_sponsor_prod',
49
49
  },
50
50
  {
@@ -75,7 +75,7 @@ const keys = [
75
75
  id: 'key_unattributed',
76
76
  orgId: 'org_1',
77
77
  environment: 'sandbox',
78
- scopes: ['rpc-relay:read', 'rpc-relay:sponsor'],
78
+ scopes: ['rpc-relay:sponsor'],
79
79
  token: 'secret_unattributed',
80
80
  },
81
81
  ] as const satisfies readonly TestApp.kvStore.Key[]
@@ -169,7 +169,7 @@ describe.skipIf(runtime.mode !== 'localnet')('behavior: sponsorship', () => {
169
169
  expect(row?.orgId).toMatchInlineSnapshot(`"org_2"`)
170
170
  })
171
171
 
172
- test('behavior: keys without the sponsor scope are refused', async () => {
172
+ test('behavior: read-only keys cannot access the sponsor route', async () => {
173
173
  const db = TestApp.database()
174
174
  const app = TestApp.create({
175
175
  auth: { keys },
@@ -177,14 +177,15 @@ describe.skipIf(runtime.mode !== 'localnet')('behavior: sponsorship', () => {
177
177
  relay: { feePayer: { account: feePayerAccount } },
178
178
  })
179
179
 
180
- const body = await relayRequest(app, {
181
- method: 'eth_signRawTransaction',
182
- params: [await signSponsorable()],
183
- token: 'secret_unscoped',
184
- })
185
- // "Sponsorship rejected." surfaces as invalid params (-32602).
186
- expect(body.result).toBeUndefined()
187
- expect(body.error?.code).toBe(-32602)
180
+ const response = await app.fetch(
181
+ new Request('http://tempo-api.test/rpc/sponsor', {
182
+ body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
183
+ headers: { 'content-type': 'application/json', 'tempo-api-key': 'secret_unscoped' },
184
+ method: 'POST',
185
+ }),
186
+ )
187
+ expect(response.status).toBe(403)
188
+ expect(await response.json()).toMatchObject({ error: { code: 'api_key_forbidden' } })
188
189
  expect(await SponsoredTransactions.listPending(db)).toHaveLength(0)
189
190
  })
190
191
 
@@ -288,12 +289,16 @@ describe.skipIf(runtime.mode !== 'localnet')('behavior: sponsorship', () => {
288
289
  nonce: '0x0',
289
290
  },
290
291
  ],
291
- token: 'secret_unscoped',
292
+ token: 'secret_sponsor',
292
293
  })
293
294
  const result = body.result as unknown as
294
- | { capabilities?: { sponsored?: boolean }; tx?: { feePayerSignature?: string | null } }
295
+ | {
296
+ capabilities?: { balanceDiffs?: unknown; sponsored?: boolean }
297
+ tx?: { feePayerSignature?: string | null }
298
+ }
295
299
  | undefined
296
300
  expect(result?.tx?.feePayerSignature ?? null).toBeNull()
301
+ expect(result?.capabilities?.balanceDiffs).toBeUndefined()
297
302
  expect(result?.capabilities?.sponsored).toBe(false)
298
303
  expect(await SponsoredTransactions.listPending(db)).toHaveLength(0)
299
304
  })
@@ -629,7 +634,7 @@ describe.skipIf(runtime.mode !== 'localnet')('behavior: sponsorship (viem)', ()
629
634
  chain: Tempo.chain,
630
635
  transport: withRelay(
631
636
  Relay.http(),
632
- http(`${server.url}/rpc/relay`, {
637
+ http(`${server.url}/rpc/sponsor`, {
633
638
  fetchOptions: { headers: { 'tempo-api-key': 'secret_sponsor' } },
634
639
  }),
635
640
  policy ? { policy } : {},
@@ -785,13 +790,13 @@ async function signSponsorable(
785
790
  })
786
791
  }
787
792
 
788
- /** Posts one JSON-RPC request to the composed app's relay mount. */
793
+ /** Posts one JSON-RPC request to the composed app's sponsor mount. */
789
794
  async function relayRequest(
790
795
  app: ReturnType<typeof TestApp.create>,
791
796
  options: { method: string; params: readonly unknown[]; token?: string | undefined },
792
797
  ) {
793
798
  const response = await app.fetch(
794
- new Request('http://tempo-api.test/rpc/relay', {
799
+ new Request('http://tempo-api.test/rpc/sponsor', {
795
800
  body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: options.method, params: options.params }), // prettier-ignore
796
801
  headers: {
797
802
  'content-type': 'application/json',
@@ -18,16 +18,17 @@ import * as Viem from '../../internal/Viem.js'
18
18
  import * as Scope from '../../Scope.js'
19
19
 
20
20
  /**
21
- * The relay route group: the wallet relay at `/rpc/relay`. Mount
22
- * after `data()`, whose published chain clients and supported-chain set the
23
- * middleware reads from request context.
21
+ * The relay route group exposes wallet features at `/rpc/relay` and fee
22
+ * sponsorship at `/rpc/sponsor`. Mount it after `data()`, which publishes
23
+ * chain clients and supported chains.
24
24
  *
25
25
  * ```ts
26
26
  * App.create(options).route('/', data()).route('/', relay())
27
27
  * ```
28
28
  */
29
29
  export function relay(options: relay.Options = {}) {
30
- const handler = middleware(options)
30
+ const handler_relay = middleware({ ...options, features: 'all', path: 'rpc/relay' })
31
+ const handler_sponsor = middleware({ ...options, path: 'rpc/sponsor' })
31
32
  return new Hono<App.Environment>()
32
33
  .use(
33
34
  '/rpc/relay',
@@ -39,8 +40,20 @@ export function relay(options: relay.Options = {}) {
39
40
  Auth.policy({ apiKey: { scopes: ['rpc-relay:read'] }, mpp: false, public: false }),
40
41
  OpenApi.describeRoute({ hide: true, summary: 'Relay RPC' }),
41
42
  )
42
- .all('/rpc/relay', handler)
43
- .all('/rpc/relay/:chainId', handler)
43
+ .all('/rpc/relay', handler_relay)
44
+ .all('/rpc/relay/:chainId', handler_relay)
45
+ .use(
46
+ '/rpc/sponsor',
47
+ Auth.policy({ apiKey: { scopes: ['rpc-relay:sponsor'] }, mpp: false, public: false }),
48
+ OpenApi.describeRoute({ hide: true, summary: 'Sponsor RPC' }),
49
+ )
50
+ .use(
51
+ '/rpc/sponsor/*',
52
+ Auth.policy({ apiKey: { scopes: ['rpc-relay:sponsor'] }, mpp: false, public: false }),
53
+ OpenApi.describeRoute({ hide: true, summary: 'Sponsor RPC' }),
54
+ )
55
+ .all('/rpc/sponsor', handler_sponsor)
56
+ .all('/rpc/sponsor/:chainId', handler_sponsor)
44
57
  }
45
58
 
46
59
  export declare namespace relay {
@@ -58,11 +71,9 @@ export declare namespace relay {
58
71
  }
59
72
  }
60
73
 
61
- // The wallet relay middleware: proxies wallet JSON-RPC (fills, sponsored
62
- // sends) with wallet-aware enrichment. Built per request from context rather
63
- // than closed over group locals; relay traffic is low-QPS and the viem
64
- // clients are memoized upstream, so per-request handler construction is cheap.
65
- function middleware(options: relay.Options = {}) {
74
+ // Builds request-aware RPC middleware. Viem clients are memoized upstream, so
75
+ // constructing `Handler.relay` per request stays cheap.
76
+ function middleware(options: middleware.Options) {
66
77
  // Platform-wide per-transaction cap; org settings tighten it, never loosen.
67
78
  const txFeeLimit = options.feePayer
68
79
  ? Billing.toBaseUnits(options.feePayer.txFeeLimit ?? '1.00')
@@ -215,12 +226,12 @@ function middleware(options: relay.Options = {}) {
215
226
 
216
227
  // Dispatching a standalone Hono via `fetch(c.req.raw)` bypasses Hono's
217
228
  // mount-path stripping, so its internal routes register at the request's
218
- // full external path (`basePath` + `/rpc/relay`).
229
+ // full external path (`basePath` + the RPC mount).
219
230
  const handler = Handler.relay({
220
231
  cache: c.get('store'),
221
- features: 'all',
232
+ ...(options.features ? { features: options.features } : {}),
222
233
  getClient: (chainId) => c.get('getClient')(chainId as Viem.ChainId | undefined),
223
- path: Path.join(c.get('basePath'), 'rpc/relay'),
234
+ path: Path.join(c.get('basePath'), options.path),
224
235
  ...(feePayer ? { feePayer } : {}),
225
236
  ...(verifiedTokens
226
237
  ? {
@@ -234,3 +245,13 @@ function middleware(options: relay.Options = {}) {
234
245
  return handler.fetch(c.req.raw)
235
246
  })
236
247
  }
248
+
249
+ declare namespace middleware {
250
+ /** Internal relay mount configuration. */
251
+ type Options = relay.Options & {
252
+ /** Optional wallet feature bundle. */
253
+ features?: Handler.relay.Options['features']
254
+ /** RPC mount path. */
255
+ path: 'rpc/relay' | 'rpc/sponsor'
256
+ }
257
+ }
package/src/cloudflare.ts CHANGED
@@ -26,6 +26,11 @@ export class DurableObject extends CloudflareDurableObject {
26
26
  await this.#store.delete(key)
27
27
  }
28
28
 
29
+ /** Deletes a key only when its value matches `expected`. */
30
+ async deleteIf(key: string, expected: null | string) {
31
+ return this.#store.deleteIf!(key, expected)
32
+ }
33
+
29
34
  /** Gets a string value. Returns `null` for missing or expired keys. */
30
35
  async get(key: string) {
31
36
  return this.#store.get(key)
package/src/db/Db.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  } from 'kysely'
11
11
  import * as pg from 'pg'
12
12
 
13
+ import type * as AdminAuditLogs from './tables/adminAuditLogs.js'
13
14
  import type * as BillingSettings from './tables/billingSettings.js'
14
15
  import type * as Invitations from './tables/invitations.js'
15
16
  import type * as Memberships from './tables/memberships.js'
@@ -28,6 +29,10 @@ import type * as WebhookSubscriptions from './tables/webhookSubscriptions.js'
28
29
  * records, queries, and {@link migrations} DDL to snake_case storage columns.
29
30
  */
30
31
  export type Database = {
32
+ admin_audit_logs: AdminAuditLogs.Table
33
+ billing_settings: BillingSettings.Table
34
+ invitations: Invitations.Table
35
+ memberships: Memberships.Table
31
36
  migrations: {
32
37
  appliedAt: string
33
38
  checksum: string
@@ -35,9 +40,6 @@ export type Database = {
35
40
  /** SQL as applied; null on rows written before the column existed. */
36
41
  sql: string | null
37
42
  }
38
- billing_settings: BillingSettings.Table
39
- invitations: Invitations.Table
40
- memberships: Memberships.Table
41
43
  organizations: Organizations.Table
42
44
  projects: Projects.Table
43
45
  request_usage_buckets: RequestUsage.BucketTable
@@ -520,6 +522,60 @@ export const migrations: readonly Migration[] = [
520
522
  // Partial: the reporter scans only unreported (pending) rows.
521
523
  up: (db) => raw(db, sql`CREATE INDEX IF NOT EXISTS request_usage_meter_events_pending_idx ON request_usage_meter_events (first_attempted_at, identifier) WHERE state = 'pending'`), // prettier-ignore
522
524
  },
525
+ {
526
+ // Supports the admin organization's newest-first keyset pagination.
527
+ name: '0045_organizations_admin_list_index',
528
+ up: (db) => raw(db, sql`CREATE INDEX IF NOT EXISTS organizations_admin_list_idx ON organizations (created_at DESC, id DESC)`), // prettier-ignore
529
+ },
530
+ {
531
+ // Prefix search keeps organization-name lookup index-backed.
532
+ name: '0046_organizations_name_prefix_index',
533
+ up: (db) => raw(db, sql`CREATE INDEX IF NOT EXISTS organizations_name_prefix_idx ON organizations (lower(name) text_pattern_ops)`), // prettier-ignore
534
+ },
535
+ {
536
+ // Prefix search keeps member-email lookup index-backed.
537
+ name: '0047_users_email_prefix_index',
538
+ up: (db) => raw(db, sql`CREATE INDEX IF NOT EXISTS users_email_prefix_idx ON users (lower(email) text_pattern_ops) WHERE email IS NOT NULL`), // prettier-ignore
539
+ },
540
+ {
541
+ // Exact owner lookup for organizations created by a user.
542
+ name: '0048_organizations_user_index',
543
+ up: (db) => raw(db, sql`CREATE INDEX IF NOT EXISTS organizations_user_idx ON organizations (user_id) WHERE user_id IS NOT NULL`), // prettier-ignore
544
+ },
545
+ {
546
+ // Supports oldest-first admin membership keyset pagination.
547
+ name: '0049_memberships_admin_list_index',
548
+ up: (db) => raw(db, sql`CREATE INDEX IF NOT EXISTS memberships_admin_list_idx ON memberships (org_id, created_at, user_id)`), // prettier-ignore
549
+ },
550
+ {
551
+ // Supports newest-first admin project keyset pagination.
552
+ name: '0050_projects_admin_list_index',
553
+ up: (db) => raw(db, sql`CREATE INDEX IF NOT EXISTS projects_admin_list_idx ON projects (org_id, created_at DESC, id DESC)`), // prettier-ignore
554
+ },
555
+ {
556
+ name: '0051_admin_audit_logs',
557
+ up: (db) =>
558
+ db.schema
559
+ .createTable('adminAuditLogs')
560
+ .ifNotExists()
561
+ .addColumn('id', 'text', (c) => c.primaryKey())
562
+ .addColumn('actor', 'text', (c) => c.notNull())
563
+ .addColumn('method', 'text', (c) => c.notNull())
564
+ .addColumn('path', 'text', (c) => c.notNull())
565
+ .addColumn('query', 'text')
566
+ .addColumn('requestId', 'text', (c) => c.notNull())
567
+ .addColumn('status', 'integer', (c) => c.notNull())
568
+ .addColumn('createdAt', 'text', (c) => c.notNull()),
569
+ },
570
+ {
571
+ name: '0052_admin_audit_logs_created_index',
572
+ up: (db) =>
573
+ db.schema
574
+ .createIndex('admin_audit_logs_created_idx')
575
+ .ifNotExists()
576
+ .on('adminAuditLogs')
577
+ .columns(['createdAt', 'id']),
578
+ },
523
579
  ]
524
580
 
525
581
  /**