tapimo 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/dist/App.d.ts +13987 -5755
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js.map +1 -1
  4. package/dist/admin/App.d.ts +910 -412
  5. package/dist/admin/App.d.ts.map +1 -1
  6. package/dist/admin/App.js +4 -0
  7. package/dist/admin/App.js.map +1 -1
  8. package/dist/admin/apps/early-access.d.ts +148 -0
  9. package/dist/admin/apps/early-access.d.ts.map +1 -0
  10. package/dist/admin/apps/early-access.js +133 -0
  11. package/dist/admin/apps/early-access.js.map +1 -0
  12. package/dist/admin/apps/verified-tokens.d.ts +643 -118
  13. package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
  14. package/dist/admin/apps/verified-tokens.js +214 -1
  15. package/dist/admin/apps/verified-tokens.js.map +1 -1
  16. package/dist/admin/ui.generated.d.ts +1 -1
  17. package/dist/admin/ui.generated.d.ts.map +1 -1
  18. package/dist/admin/ui.generated.js +45 -33
  19. package/dist/admin/ui.generated.js.map +1 -1
  20. package/dist/apps/data/App.d.ts +3733 -2428
  21. package/dist/apps/data/App.d.ts.map +1 -1
  22. package/dist/apps/data/App.js +11 -6
  23. package/dist/apps/data/App.js.map +1 -1
  24. package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
  25. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
  26. package/dist/apps/data/FundingQuoteProviders.js +193 -2
  27. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  28. package/dist/apps/data/FxOracle.d.ts +71 -0
  29. package/dist/apps/data/FxOracle.d.ts.map +1 -0
  30. package/dist/apps/data/FxOracle.js +97 -0
  31. package/dist/apps/data/FxOracle.js.map +1 -0
  32. package/dist/apps/data/index.d.ts +1 -0
  33. package/dist/apps/data/index.d.ts.map +1 -1
  34. package/dist/apps/data/index.js +1 -0
  35. package/dist/apps/data/index.js.map +1 -1
  36. package/dist/apps/data/routes/activities.d.ts +3575 -1358
  37. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  38. package/dist/apps/data/routes/activities.js +230 -88
  39. package/dist/apps/data/routes/activities.js.map +1 -1
  40. package/dist/apps/data/routes/addresses.d.ts +963 -342
  41. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  42. package/dist/apps/data/routes/addresses.js +11 -9
  43. package/dist/apps/data/routes/addresses.js.map +1 -1
  44. package/dist/apps/data/routes/balances.d.ts +108 -57
  45. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  46. package/dist/apps/data/routes/balances.js +88 -45
  47. package/dist/apps/data/routes/balances.js.map +1 -1
  48. package/dist/apps/data/routes/coingecko.d.ts +169 -169
  49. package/dist/apps/data/routes/exchanges.d.ts +952 -201
  50. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  51. package/dist/apps/data/routes/exchanges.js +518 -76
  52. package/dist/apps/data/routes/exchanges.js.map +1 -1
  53. package/dist/apps/data/routes/fee-amm.d.ts +24 -24
  54. package/dist/apps/data/routes/funding.d.ts +8 -2
  55. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  56. package/dist/apps/data/routes/funding.js +32 -6
  57. package/dist/apps/data/routes/funding.js.map +1 -1
  58. package/dist/apps/data/routes/indexer.d.ts +5 -5
  59. package/dist/apps/data/routes/receipts.d.ts +392 -315
  60. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  61. package/dist/apps/data/routes/receipts.js +152 -77
  62. package/dist/apps/data/routes/receipts.js.map +1 -1
  63. package/dist/apps/data/routes/tokens.d.ts +166 -173
  64. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  65. package/dist/apps/data/routes/tokens.js +14 -12
  66. package/dist/apps/data/routes/tokens.js.map +1 -1
  67. package/dist/apps/data/routes/transactions.d.ts +277 -536
  68. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  69. package/dist/apps/data/routes/transactions.js +96 -77
  70. package/dist/apps/data/routes/transactions.js.map +1 -1
  71. package/dist/apps/data/routes/transfers.d.ts +82 -17
  72. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  73. package/dist/apps/data/routes/transfers.js +70 -13
  74. package/dist/apps/data/routes/transfers.js.map +1 -1
  75. package/dist/apps/data/routes/valuation.d.ts +307 -0
  76. package/dist/apps/data/routes/valuation.d.ts.map +1 -0
  77. package/dist/apps/data/routes/valuation.js +292 -0
  78. package/dist/apps/data/routes/valuation.js.map +1 -0
  79. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  80. package/dist/apps/data/routes/verified-tokens.js +7 -5
  81. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  82. package/dist/apps/data/routes/webhooks.d.ts +459 -479
  83. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  84. package/dist/apps/data/routes/webhooks.js +124 -25
  85. package/dist/apps/data/routes/webhooks.js.map +1 -1
  86. package/dist/apps/index.d.ts +1 -1
  87. package/dist/apps/index.d.ts.map +1 -1
  88. package/dist/apps/index.js +1 -1
  89. package/dist/apps/index.js.map +1 -1
  90. package/dist/apps/management/App.d.ts +8606 -1681
  91. package/dist/apps/management/App.d.ts.map +1 -1
  92. package/dist/apps/management/App.js +12 -1
  93. package/dist/apps/management/App.js.map +1 -1
  94. package/dist/apps/management/routes/api-keys.d.ts +23 -0
  95. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  96. package/dist/apps/management/routes/api-keys.js +5 -3
  97. package/dist/apps/management/routes/api-keys.js.map +1 -1
  98. package/dist/apps/management/routes/billing.d.ts +88 -48
  99. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  100. package/dist/apps/management/routes/billing.js +10 -2
  101. package/dist/apps/management/routes/billing.js.map +1 -1
  102. package/dist/apps/management/routes/faucet.d.ts +218 -0
  103. package/dist/apps/management/routes/faucet.d.ts.map +1 -0
  104. package/dist/apps/management/routes/faucet.js +139 -0
  105. package/dist/apps/management/routes/faucet.js.map +1 -0
  106. package/dist/apps/management/routes/invitations.d.ts +121 -121
  107. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  108. package/dist/apps/management/routes/invitations.js +3 -0
  109. package/dist/apps/management/routes/invitations.js.map +1 -1
  110. package/dist/apps/management/routes/invite-links.d.ts +197 -168
  111. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  112. package/dist/apps/management/routes/invite-links.js +26 -1
  113. package/dist/apps/management/routes/invite-links.js.map +1 -1
  114. package/dist/apps/management/routes/me.d.ts +2 -0
  115. package/dist/apps/management/routes/me.d.ts.map +1 -1
  116. package/dist/apps/management/routes/me.js +4 -0
  117. package/dist/apps/management/routes/me.js.map +1 -1
  118. package/dist/apps/management/routes/members.d.ts +49 -49
  119. package/dist/apps/management/routes/orgs.d.ts +91 -91
  120. package/dist/apps/management/routes/projects.d.ts +115 -115
  121. package/dist/apps/management/routes/usage.d.ts +35 -32
  122. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  123. package/dist/apps/management/routes/usage.js +4 -0
  124. package/dist/apps/management/routes/usage.js.map +1 -1
  125. package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
  126. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
  127. package/dist/apps/management/routes/verified-token-requests.js +271 -0
  128. package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
  129. package/dist/apps/management/routes/webhooks.d.ts +6977 -0
  130. package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
  131. package/dist/apps/management/routes/webhooks.js +573 -0
  132. package/dist/apps/management/routes/webhooks.js.map +1 -0
  133. package/dist/apps/mcp.d.ts +9 -2
  134. package/dist/apps/mcp.d.ts.map +1 -1
  135. package/dist/apps/mcp.js +9 -4
  136. package/dist/apps/mcp.js.map +1 -1
  137. package/dist/cli/Cli.d.ts.map +1 -1
  138. package/dist/cli/Cli.js +7 -2
  139. package/dist/cli/Cli.js.map +1 -1
  140. package/dist/db/Db.d.ts +4 -0
  141. package/dist/db/Db.d.ts.map +1 -1
  142. package/dist/db/Db.js +83 -0
  143. package/dist/db/Db.js.map +1 -1
  144. package/dist/db/Schema.d.ts +41 -0
  145. package/dist/db/Schema.d.ts.map +1 -1
  146. package/dist/db/Schema.js +45 -0
  147. package/dist/db/Schema.js.map +1 -1
  148. package/dist/db/tables/earlyAccess.d.ts +50 -0
  149. package/dist/db/tables/earlyAccess.d.ts.map +1 -0
  150. package/dist/db/tables/earlyAccess.js +63 -0
  151. package/dist/db/tables/earlyAccess.js.map +1 -0
  152. package/dist/db/tables/invitations.d.ts +12 -1
  153. package/dist/db/tables/invitations.d.ts.map +1 -1
  154. package/dist/db/tables/invitations.js +23 -0
  155. package/dist/db/tables/invitations.js.map +1 -1
  156. package/dist/db/tables/inviteLinks.d.ts +10 -2
  157. package/dist/db/tables/inviteLinks.d.ts.map +1 -1
  158. package/dist/db/tables/inviteLinks.js +10 -1
  159. package/dist/db/tables/inviteLinks.js.map +1 -1
  160. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  161. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  162. package/dist/db/tables/sponsoredTransactions.js +2 -0
  163. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  164. package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
  165. package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
  166. package/dist/db/tables/verifiedTokenRequests.js +120 -0
  167. package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
  168. package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
  169. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  170. package/dist/db/tables/webhookSubscriptions.js +16 -0
  171. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  172. package/dist/internal/Auth.d.ts +43 -2
  173. package/dist/internal/Auth.d.ts.map +1 -1
  174. package/dist/internal/Auth.js +60 -45
  175. package/dist/internal/Auth.js.map +1 -1
  176. package/dist/internal/FundingQuotes.d.ts +69 -15
  177. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  178. package/dist/internal/FundingQuotes.js +74 -25
  179. package/dist/internal/FundingQuotes.js.map +1 -1
  180. package/dist/internal/Log.d.ts +19 -1
  181. package/dist/internal/Log.d.ts.map +1 -1
  182. package/dist/internal/Log.js +2 -0
  183. package/dist/internal/Log.js.map +1 -1
  184. package/dist/internal/MetricSink.d.ts.map +1 -1
  185. package/dist/internal/MetricSink.js +9 -0
  186. package/dist/internal/MetricSink.js.map +1 -1
  187. package/dist/internal/OpenApi.d.ts +2 -2
  188. package/dist/internal/OpenApi.d.ts.map +1 -1
  189. package/dist/internal/OpenApi.js +3 -0
  190. package/dist/internal/OpenApi.js.map +1 -1
  191. package/dist/internal/Schema.d.ts +14 -5
  192. package/dist/internal/Schema.d.ts.map +1 -1
  193. package/dist/internal/Schema.js +23 -5
  194. package/dist/internal/Schema.js.map +1 -1
  195. package/dist/internal/Value.d.ts +18 -0
  196. package/dist/internal/Value.d.ts.map +1 -1
  197. package/dist/internal/Value.js +11 -1
  198. package/dist/internal/Value.js.map +1 -1
  199. package/dist/internal/VerifiedTokens.d.ts.map +1 -1
  200. package/dist/internal/VerifiedTokens.js +10 -1
  201. package/dist/internal/VerifiedTokens.js.map +1 -1
  202. package/dist/internal/Viem.d.ts +1 -1
  203. package/dist/internal/Webhooks.d.ts +24 -3
  204. package/dist/internal/Webhooks.d.ts.map +1 -1
  205. package/dist/internal/Webhooks.js +34 -8
  206. package/dist/internal/Webhooks.js.map +1 -1
  207. package/package.json +2 -2
  208. package/src/App.ts +9 -1
  209. package/src/Client.test-d.ts +638 -6
  210. package/src/admin/App.ts +4 -0
  211. package/src/admin/apps/early-access.test.ts +125 -0
  212. package/src/admin/apps/early-access.ts +175 -0
  213. package/src/admin/apps/verified-tokens.test.ts +201 -0
  214. package/src/admin/apps/verified-tokens.ts +532 -286
  215. package/src/admin/ui/plugin.ts +2 -1
  216. package/src/admin/ui/src/lib/early-access.ts +63 -0
  217. package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
  218. package/src/admin/ui/src/routeTree.gen.ts +33 -2
  219. package/src/admin/ui/src/routes/__root.tsx +2 -1
  220. package/src/admin/ui/src/routes/early-access.tsx +157 -0
  221. package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
  222. package/src/admin/ui.generated.ts +48 -33
  223. package/src/apps/data/App.ts +22 -14
  224. package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
  225. package/src/apps/data/FundingQuoteProviders.ts +257 -4
  226. package/src/apps/data/FxOracle.test.ts +118 -0
  227. package/src/apps/data/FxOracle.ts +153 -0
  228. package/src/apps/data/index.ts +1 -0
  229. package/src/apps/data/routes/activities.test.ts +173 -3
  230. package/src/apps/data/routes/activities.ts +403 -132
  231. package/src/apps/data/routes/addresses.ts +19 -9
  232. package/src/apps/data/routes/balances.test.ts +144 -11
  233. package/src/apps/data/routes/balances.ts +140 -51
  234. package/src/apps/data/routes/exchanges.test.ts +540 -9
  235. package/src/apps/data/routes/exchanges.ts +690 -103
  236. package/src/apps/data/routes/funding.test.ts +84 -0
  237. package/src/apps/data/routes/funding.ts +38 -6
  238. package/src/apps/data/routes/receipts.test.ts +125 -23
  239. package/src/apps/data/routes/receipts.ts +205 -100
  240. package/src/apps/data/routes/tokens.test.ts +43 -0
  241. package/src/apps/data/routes/tokens.ts +27 -12
  242. package/src/apps/data/routes/transactions.test.ts +72 -19
  243. package/src/apps/data/routes/transactions.ts +162 -92
  244. package/src/apps/data/routes/transfers.test.ts +146 -4
  245. package/src/apps/data/routes/transfers.ts +91 -13
  246. package/src/apps/data/routes/valuation.test.ts +422 -0
  247. package/src/apps/data/routes/valuation.ts +423 -0
  248. package/src/apps/data/routes/verified-tokens.ts +14 -2
  249. package/src/apps/data/routes/webhooks.test.ts +56 -8
  250. package/src/apps/data/routes/webhooks.ts +144 -27
  251. package/src/apps/index.ts +1 -1
  252. package/src/apps/management/App.ts +13 -0
  253. package/src/apps/management/routes/api-keys.test.ts +87 -0
  254. package/src/apps/management/routes/api-keys.ts +4 -3
  255. package/src/apps/management/routes/billing.test.ts +112 -1
  256. package/src/apps/management/routes/billing.ts +7 -2
  257. package/src/apps/management/routes/faucet.test.ts +79 -0
  258. package/src/apps/management/routes/faucet.ts +185 -0
  259. package/src/apps/management/routes/invitations.test.ts +172 -0
  260. package/src/apps/management/routes/invitations.ts +3 -0
  261. package/src/apps/management/routes/invite-links.test.ts +117 -0
  262. package/src/apps/management/routes/invite-links.ts +43 -1
  263. package/src/apps/management/routes/me.test.ts +140 -88
  264. package/src/apps/management/routes/me.ts +6 -0
  265. package/src/apps/management/routes/usage.test.ts +6 -0
  266. package/src/apps/management/routes/usage.ts +7 -0
  267. package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
  268. package/src/apps/management/routes/verified-token-requests.ts +397 -0
  269. package/src/apps/management/routes/webhooks.test.ts +426 -0
  270. package/src/apps/management/routes/webhooks.ts +672 -0
  271. package/src/apps/mcp.test.ts +13 -0
  272. package/src/apps/mcp.ts +18 -5
  273. package/src/cli/Cli.test.ts +36 -0
  274. package/src/cli/Cli.ts +7 -2
  275. package/src/db/Db.test.ts +19 -0
  276. package/src/db/Db.ts +95 -0
  277. package/src/db/Schema.ts +53 -0
  278. package/src/db/tables/earlyAccess.ts +86 -0
  279. package/src/db/tables/invitations.ts +26 -1
  280. package/src/db/tables/inviteLinks.ts +22 -3
  281. package/src/db/tables/sponsoredTransactions.ts +4 -0
  282. package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
  283. package/src/db/tables/verifiedTokenRequests.ts +182 -0
  284. package/src/db/tables/webhookSubscriptions.ts +31 -0
  285. package/src/internal/Auth.ts +73 -45
  286. package/src/internal/FundingQuotes.test.ts +143 -1
  287. package/src/internal/FundingQuotes.ts +95 -28
  288. package/src/internal/Log.test-d.ts +21 -0
  289. package/src/internal/Log.test.ts +50 -0
  290. package/src/internal/Log.ts +24 -1
  291. package/src/internal/MetricSink.test.ts +160 -0
  292. package/src/internal/MetricSink.ts +10 -0
  293. package/src/internal/OpenApi.ts +3 -0
  294. package/src/internal/Schema.ts +50 -5
  295. package/src/internal/Value.test.ts +20 -0
  296. package/src/internal/Value.ts +24 -1
  297. package/src/internal/VerifiedTokens.ts +9 -1
  298. package/src/internal/Webhooks.test.ts +140 -0
  299. package/src/internal/Webhooks.ts +53 -9
@@ -0,0 +1,672 @@
1
+ import { type Context, Hono } from 'hono'
2
+ import * as z from 'zod/mini'
3
+
4
+ import type * as App from '../../../App.js'
5
+ import * as Db from '../../../db/Db.js'
6
+ import * as Auth from '../../../internal/Auth.js'
7
+ import * as OpenApi from '../../../internal/OpenApi.js'
8
+ import * as Response from '../../../internal/Response.js'
9
+ import * as Webhooks from '../../../internal/Webhooks.js'
10
+ import { schema as webhookSchema } from '../../data/routes/webhooks.js'
11
+ import type { Environment } from '../App.js'
12
+
13
+ const defaultMaxPerOwner = 100
14
+ const redactedBetterstackToken = '[redacted]'
15
+ const redactedSlackUrl = 'https://hooks.slack.com/…'
16
+
17
+ /** OpenAPI schemas owned by console webhook management. */
18
+ export namespace schema {
19
+ /** Public webhook subscription representation. */
20
+ export const Subscription = webhookSchema.Subscription
21
+
22
+ /** Schemas for creating an organization webhook. */
23
+ export namespace createWebhook {
24
+ /** Organization webhook creation requires an explicit network. */
25
+ export const Body = z
26
+ .intersection(
27
+ webhookSchema.createWebhook.Body,
28
+ z.object({ chainId: webhookSchema.Subscription.shape.chainId }),
29
+ )
30
+ .check(z.describe('Details for creating an organization webhook subscription.'))
31
+
32
+ const secret = webhookSchema.createWebhook.Response.shape.secret
33
+ const urlDestination = z.object({
34
+ type: z.literal('url'),
35
+ url: z
36
+ .url()
37
+ .check(
38
+ z.describe('Your HTTPS endpoint where Tempo sends signed event POSTs.'),
39
+ z.meta({ examples: ['https://example.com/webhooks'] }),
40
+ ),
41
+ })
42
+ const slackDestination = z.object({
43
+ type: z.literal('slack'),
44
+ url: z.literal(redactedSlackUrl).check(z.describe('Redacted Slack webhook URL.')),
45
+ })
46
+ const betterstackDestination = z.object({
47
+ token: z
48
+ .literal(redactedBetterstackToken)
49
+ .check(z.describe('Redacted Better Stack source token.')),
50
+ type: z.literal('betterstack'),
51
+ url: z
52
+ .url()
53
+ .check(
54
+ z.describe('Your Better Stack source ingest host URL.'),
55
+ z.meta({ examples: ['https://s1234567.eu-nbg-2.betterstackdata.com'] }),
56
+ ),
57
+ })
58
+
59
+ /** Created webhook with transport credentials redacted. */
60
+ export const Response = z
61
+ .union([
62
+ z.object({ ...Subscription.shape, destination: urlDestination, secret }),
63
+ z.object({ ...Subscription.shape, destination: slackDestination }),
64
+ z.object({ ...Subscription.shape, destination: betterstackDestination }),
65
+ ])
66
+ .check(
67
+ z.describe(
68
+ 'The new webhook subscription. HTTPS endpoints include their one-time signing secret.',
69
+ ),
70
+ )
71
+ }
72
+
73
+ /** Organization webhook collection parameters. */
74
+ export const Params = z
75
+ .object({
76
+ orgId: z
77
+ .string()
78
+ .check(
79
+ z.describe('Organization id (`org_…`).'),
80
+ z.meta({ examples: ['org_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
81
+ ),
82
+ })
83
+ .check(z.describe("Path parameters for an organization's webhooks."))
84
+
85
+ /** Parameters addressing one organization webhook. */
86
+ export const WebhookParams = z
87
+ .object({
88
+ id: z
89
+ .string()
90
+ .check(
91
+ z.describe('Webhook subscription id (`wh_…`).'),
92
+ z.meta({ examples: ['wh_001718668800000_2ZPE2gvateYEQ0dQslgvkhjx'] }),
93
+ ),
94
+ orgId: Params.shape.orgId,
95
+ })
96
+ .check(z.describe("Path parameters for one organization's webhook."))
97
+
98
+ /** Schemas for listing an organization webhook's delivery attempts. */
99
+ export namespace listWebhookDeliveries {
100
+ export const Params = WebhookParams
101
+ export const Query = webhookSchema.listWebhookDeliveries.Query
102
+ export const Response = webhookSchema.listWebhookDeliveries.Response
103
+ }
104
+
105
+ const DeliveryParams = z
106
+ .object({
107
+ deliveryId: webhookSchema.retryWebhookDelivery.Params.shape.deliveryId,
108
+ id: WebhookParams.shape.id,
109
+ orgId: WebhookParams.shape.orgId,
110
+ })
111
+ .check(z.describe("Path parameters for one organization's webhook delivery."))
112
+
113
+ /** Schemas for loading one organization webhook delivery. */
114
+ export namespace getWebhookDelivery {
115
+ export const Params = DeliveryParams
116
+ export const Response = z
117
+ .object({
118
+ ...webhookSchema.Delivery.shape,
119
+ envelope: webhookSchema.Envelope,
120
+ })
121
+ .check(z.describe('A webhook delivery attempt with the exact JSON payload Tempo sent.'))
122
+ }
123
+
124
+ /** Schemas for replaying one organization webhook delivery. */
125
+ export namespace retryWebhookDelivery {
126
+ export const Params = DeliveryParams
127
+ export const Response = webhookSchema.retryWebhookDelivery.Response
128
+ }
129
+
130
+ /** Non-paginated organization webhook list. */
131
+ export const ListResponse = z
132
+ .object({
133
+ data: z.array(Subscription).check(z.describe("The organization's webhooks, newest first.")),
134
+ })
135
+ .check(z.describe("An organization's webhook subscriptions."))
136
+
137
+ /** Lifecycle state editable from the console. */
138
+ export const UpdateBody = z
139
+ .object({ status: webhookSchema.Status })
140
+ .check(z.describe('New delivery state for a webhook subscription.'))
141
+
142
+ /** Webhook deletion confirmation. */
143
+ export const DeleteResponse = z
144
+ .object({
145
+ id: z
146
+ .string()
147
+ .check(
148
+ z.describe('Deleted webhook subscription id.'),
149
+ z.meta({ examples: ['wh_001718668800000_2ZPE2gvateYEQ0dQslgvkhjx'] }),
150
+ ),
151
+ })
152
+ .check(z.describe('Confirmation that a webhook subscription was deleted.'))
153
+ }
154
+
155
+ const bodyValidation = {
156
+ code: 'body_invalid',
157
+ message: 'Check the request body and try again.',
158
+ } as const
159
+ const paramValidation = {
160
+ code: 'param_invalid',
161
+ message: 'Check the path parameters and try again.',
162
+ } as const
163
+
164
+ /** Mounts session-authenticated organization webhook management operations. */
165
+ export function webhooks(options: webhooks.Options = {}) {
166
+ const hidden = !options.webhook
167
+ return new Hono<Environment>()
168
+ .post(
169
+ '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/webhooks',
170
+ Auth.policy({ session: true }),
171
+ Auth.ensureOrg({ role: 'admin' }),
172
+ OpenApi.validate('param', schema.Params, paramValidation),
173
+ OpenApi.validate('json', schema.createWebhook.Body, bodyValidation),
174
+ OpenApi.describeRoute({
175
+ hide: hidden,
176
+ operationId: 'createOrgWebhook',
177
+ responses: OpenApi.responses({
178
+ errors: {
179
+ 400: {
180
+ codes: [
181
+ 'body_invalid',
182
+ 'chain_id_unsupported',
183
+ 'filters_invalid',
184
+ 'param_invalid',
185
+ 'url_invalid',
186
+ ],
187
+ description: 'The request or destination is invalid, or the chain is not polled.',
188
+ },
189
+ 403: {
190
+ codes: ['forbidden', 'limit_exceeded'],
191
+ description: 'Admin access is required and the organization must be under its limit.',
192
+ },
193
+ 404: {
194
+ codes: ['organization_not_found', 'webhooks_not_enabled'],
195
+ description: 'No accessible organization or webhook capability was found.',
196
+ },
197
+ },
198
+ success: {
199
+ description: 'Created webhook. HTTPS endpoints include a one-time signing secret.',
200
+ schema: schema.createWebhook.Response,
201
+ },
202
+ }),
203
+ summary: 'Create webhook',
204
+ tags: ['Webhooks'],
205
+ }),
206
+ async (c) => {
207
+ if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
208
+ if (Auth.narrowScope) return Auth.ensureOrgError(c)
209
+ if (Auth.narrowOrgRole) return Auth.ensureOrgRoleError(c)
210
+ if (OpenApi.narrowValidation) return OpenApi.validationError(c, paramValidation)
211
+ if (OpenApi.narrowValidation) return OpenApi.validationError(c, bodyValidation)
212
+ if (!options.webhook) return notEnabled(c)
213
+
214
+ const body = c.req.valid('json')
215
+ const chainId = body.chainId
216
+ const supportedChainIds = new Set(options.webhook.supportedChainIds)
217
+ if (!supportedChainIds.has(chainId))
218
+ return Response.unsupportedChainId(c, chainId, supportedChainIds)
219
+ try {
220
+ const subscription = await Webhooks.createSubscription(
221
+ Db.get(c.get('db')),
222
+ {
223
+ chainId,
224
+ ...(body.context === undefined ? {} : { context: body.context }),
225
+ destination: body.destination,
226
+ eventType: body.eventType,
227
+ filters: body.filters,
228
+ owner: owner(Auth.org(c).id),
229
+ },
230
+ { maxPerOwner: options.webhook.maxPerOwner ?? defaultMaxPerOwner },
231
+ )
232
+ return c.json(
233
+ Response.validated(schema.createWebhook.Response, created(subscription)),
234
+ 200,
235
+ )
236
+ } catch (cause) {
237
+ return mutationError(c, cause)
238
+ }
239
+ },
240
+ )
241
+ .get(
242
+ '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/webhooks',
243
+ Auth.policy({ session: true }),
244
+ Auth.ensureOrg(),
245
+ OpenApi.validate('param', schema.Params, paramValidation),
246
+ OpenApi.describeRoute({
247
+ hide: hidden,
248
+ operationId: 'listOrgWebhooks',
249
+ responses: OpenApi.responses({
250
+ errors: {
251
+ 400: { codes: ['param_invalid'], description: 'The path parameters are invalid.' },
252
+ 404: {
253
+ codes: ['organization_not_found', 'webhooks_not_enabled'],
254
+ description: 'No accessible organization or webhook capability was found.',
255
+ },
256
+ },
257
+ success: { description: 'Organization webhooks.', schema: schema.ListResponse },
258
+ }),
259
+ summary: 'List webhooks',
260
+ tags: ['Webhooks'],
261
+ }),
262
+ async (c) => {
263
+ if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
264
+ if (Auth.narrowScope) return Auth.ensureOrgError(c)
265
+ if (OpenApi.narrowValidation) return OpenApi.validationError(c, paramValidation)
266
+ if (!options.webhook) return notEnabled(c)
267
+ try {
268
+ const records = await Webhooks.listSubscriptions(
269
+ Db.get(c.get('db')),
270
+ owner(Auth.org(c).id),
271
+ )
272
+ return c.json(
273
+ Response.validated(schema.ListResponse, { data: records.map(publicSubscription) }),
274
+ 200,
275
+ )
276
+ } catch (cause) {
277
+ return Response.upstream(c, cause)
278
+ }
279
+ },
280
+ )
281
+ .get(
282
+ '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/webhooks/:id{wh_[A-Za-z0-9_-]+}/deliveries',
283
+ Auth.policy({ session: true }),
284
+ Auth.ensureOrg(),
285
+ OpenApi.validate('param', schema.listWebhookDeliveries.Params, paramValidation),
286
+ OpenApi.validate('query', schema.listWebhookDeliveries.Query, {
287
+ code: 'query_invalid',
288
+ message: 'Check the query parameters and try again.',
289
+ }),
290
+ OpenApi.describeRoute({
291
+ hide: hidden,
292
+ operationId: 'listOrgWebhookDeliveries',
293
+ responses: OpenApi.responses({
294
+ errors: {
295
+ 400: {
296
+ codes: ['param_invalid', 'query_invalid'],
297
+ description: 'The path or query parameters are invalid.',
298
+ },
299
+ 404: {
300
+ codes: ['organization_not_found', 'webhook_not_found', 'webhooks_not_enabled'],
301
+ description: 'No accessible organization, webhook, or webhook capability was found.',
302
+ },
303
+ },
304
+ success: {
305
+ description: 'A newest-first page of webhook delivery attempts.',
306
+ schema: schema.listWebhookDeliveries.Response,
307
+ },
308
+ }),
309
+ summary: 'List webhook deliveries',
310
+ tags: ['Webhooks'],
311
+ }),
312
+ async (c) => {
313
+ if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
314
+ if (Auth.narrowScope) return Auth.ensureOrgError(c)
315
+ if (OpenApi.narrowValidation) return OpenApi.validationError(c, paramValidation)
316
+ if (OpenApi.narrowValidation)
317
+ return OpenApi.validationError(c, {
318
+ code: 'query_invalid',
319
+ message: 'Check the query parameters and try again.',
320
+ })
321
+ if (!options.webhook) return notEnabled(c)
322
+
323
+ const db = Db.get(c.get('db'))
324
+ const { id } = c.req.valid('param')
325
+ const { cursor, include, limit, page } = c.req.valid('query')
326
+ try {
327
+ const subscription = await Webhooks.getSubscription(db, owner(Auth.org(c).id), id)
328
+ if (!subscription) return notFound(c)
329
+
330
+ const countPromise = include.includes('totalCount')
331
+ ? Webhooks.countDeliveries(db, id).catch(() => undefined)
332
+ : undefined
333
+ const rows = await Webhooks.listDeliveries(db, id, {
334
+ cursor,
335
+ limit: limit + 1,
336
+ offset: page !== undefined && page > 1 ? (page - 1) * limit : undefined,
337
+ })
338
+ const hasMore = rows.length > limit
339
+ const data = hasMore ? rows.slice(0, limit) : rows
340
+ const nextCursor = hasMore ? (data[data.length - 1]?.id ?? null) : null
341
+ const totalCount = countPromise ? await countPromise : undefined
342
+
343
+ return c.json(
344
+ Response.validated(schema.listWebhookDeliveries.Response, {
345
+ data: data.map((delivery) => publicDelivery(delivery, subscription)),
346
+ ...(totalCount === undefined
347
+ ? {}
348
+ : { meta: { totalCountCapped: false, totalCount } }),
349
+ nextCursor,
350
+ }),
351
+ 200,
352
+ )
353
+ } catch (cause) {
354
+ return Response.upstream(c, cause)
355
+ }
356
+ },
357
+ )
358
+ .get(
359
+ '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/webhooks/:id{wh_[A-Za-z0-9_-]+}/deliveries/:deliveryId',
360
+ Auth.policy({ session: true }),
361
+ Auth.ensureOrg(),
362
+ OpenApi.validate('param', schema.getWebhookDelivery.Params, paramValidation),
363
+ OpenApi.describeRoute({
364
+ hide: hidden,
365
+ operationId: 'getOrgWebhookDelivery',
366
+ responses: OpenApi.responses({
367
+ errors: {
368
+ 400: { codes: ['param_invalid'], description: 'The path parameters are invalid.' },
369
+ 404: {
370
+ codes: [
371
+ 'delivery_not_found',
372
+ 'organization_not_found',
373
+ 'webhook_not_found',
374
+ 'webhooks_not_enabled',
375
+ ],
376
+ description:
377
+ 'No accessible organization, webhook, delivery, or webhook capability was found.',
378
+ },
379
+ },
380
+ success: {
381
+ description: 'The delivery attempt and exact JSON payload Tempo sent.',
382
+ schema: schema.getWebhookDelivery.Response,
383
+ },
384
+ }),
385
+ summary: 'Get webhook delivery',
386
+ tags: ['Webhooks'],
387
+ }),
388
+ async (c) => {
389
+ if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
390
+ if (Auth.narrowScope) return Auth.ensureOrgError(c)
391
+ if (OpenApi.narrowValidation) return OpenApi.validationError(c, paramValidation)
392
+ if (!options.webhook) return notEnabled(c)
393
+
394
+ const db = Db.get(c.get('db'))
395
+ const { deliveryId, id } = c.req.valid('param')
396
+ try {
397
+ const subscription = await Webhooks.getSubscription(db, owner(Auth.org(c).id), id)
398
+ if (!subscription) return notFound(c)
399
+ const delivery = await Webhooks.getDelivery(db, id, deliveryId)
400
+ if (!delivery) return deliveryNotFound(c)
401
+
402
+ return c.json(
403
+ Response.validated(schema.getWebhookDelivery.Response, {
404
+ ...publicDelivery(delivery, subscription),
405
+ envelope: delivery.envelope,
406
+ }),
407
+ 200,
408
+ )
409
+ } catch (cause) {
410
+ return Response.upstream(c, cause)
411
+ }
412
+ },
413
+ )
414
+ .post(
415
+ '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/webhooks/:id{wh_[A-Za-z0-9_-]+}/deliveries/:deliveryId/retry',
416
+ Auth.policy({ session: true }),
417
+ Auth.ensureOrg({ role: 'admin' }),
418
+ OpenApi.validate('param', schema.retryWebhookDelivery.Params, paramValidation),
419
+ OpenApi.describeRoute({
420
+ hide: hidden,
421
+ operationId: 'retryOrgWebhookDelivery',
422
+ responses: OpenApi.responses({
423
+ errors: {
424
+ 400: { codes: ['param_invalid'], description: 'The path parameters are invalid.' },
425
+ 403: { codes: ['forbidden'], description: 'Organization admin access is required.' },
426
+ 404: {
427
+ codes: [
428
+ 'delivery_not_found',
429
+ 'organization_not_found',
430
+ 'webhook_not_found',
431
+ 'webhooks_not_enabled',
432
+ ],
433
+ description:
434
+ 'No accessible organization, webhook, delivery, or webhook capability was found.',
435
+ },
436
+ },
437
+ success: {
438
+ description: 'Result of replaying the webhook delivery.',
439
+ schema: schema.retryWebhookDelivery.Response,
440
+ },
441
+ }),
442
+ summary: 'Retry webhook delivery',
443
+ tags: ['Webhooks'],
444
+ }),
445
+ async (c) => {
446
+ if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
447
+ if (Auth.narrowScope) return Auth.ensureOrgError(c)
448
+ if (Auth.narrowOrgRole) return Auth.ensureOrgRoleError(c)
449
+ if (OpenApi.narrowValidation) return OpenApi.validationError(c, paramValidation)
450
+ if (!options.webhook) return notEnabled(c)
451
+
452
+ const db = Db.get(c.get('db'))
453
+ const { deliveryId, id } = c.req.valid('param')
454
+ try {
455
+ const subscription = await Webhooks.getSubscription(db, owner(Auth.org(c).id), id)
456
+ if (!subscription) return notFound(c)
457
+ const delivery = await Webhooks.getDelivery(db, id, deliveryId)
458
+ if (!delivery) return deliveryNotFound(c)
459
+
460
+ const result = await Webhooks.deliverAndRecord(db, subscription, delivery.envelope, {
461
+ trigger: 'manual_retry',
462
+ })
463
+ return c.json(
464
+ Response.validated(schema.retryWebhookDelivery.Response, {
465
+ delivered: result.ok,
466
+ eventId: delivery.eventId,
467
+ ...(result.error === undefined ? {} : { error: result.error }),
468
+ ...(result.durationMs === undefined ? {} : { responseMs: result.durationMs }),
469
+ ...(result.status === undefined ? {} : { responseStatus: result.status }),
470
+ }),
471
+ 200,
472
+ )
473
+ } catch (cause) {
474
+ return Response.upstream(c, cause)
475
+ }
476
+ },
477
+ )
478
+ .patch(
479
+ '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/webhooks/:id{wh_[A-Za-z0-9_-]+}',
480
+ Auth.policy({ session: true }),
481
+ Auth.ensureOrg({ role: 'admin' }),
482
+ OpenApi.validate('param', schema.WebhookParams, paramValidation),
483
+ OpenApi.validate('json', schema.UpdateBody, bodyValidation),
484
+ OpenApi.describeRoute({
485
+ hide: hidden,
486
+ operationId: 'updateOrgWebhook',
487
+ responses: OpenApi.responses({
488
+ errors: {
489
+ 400: {
490
+ codes: ['body_invalid', 'param_invalid'],
491
+ description: 'The path parameters or request body are invalid.',
492
+ },
493
+ 403: { codes: ['forbidden'], description: 'Organization admin access is required.' },
494
+ 404: {
495
+ codes: ['organization_not_found', 'webhook_not_found', 'webhooks_not_enabled'],
496
+ description: 'No accessible organization, webhook, or webhook capability was found.',
497
+ },
498
+ },
499
+ success: {
500
+ description: 'Updated webhook.',
501
+ schema: webhookSchema.Subscription,
502
+ },
503
+ }),
504
+ summary: 'Update webhook',
505
+ tags: ['Webhooks'],
506
+ }),
507
+ async (c) => {
508
+ if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
509
+ if (Auth.narrowScope) return Auth.ensureOrgError(c)
510
+ if (Auth.narrowOrgRole) return Auth.ensureOrgRoleError(c)
511
+ if (OpenApi.narrowValidation) return OpenApi.validationError(c, paramValidation)
512
+ if (OpenApi.narrowValidation) return OpenApi.validationError(c, bodyValidation)
513
+ if (!options.webhook) return notEnabled(c)
514
+ try {
515
+ const subscription = await Webhooks.updateSubscription(
516
+ Db.get(c.get('db')),
517
+ owner(Auth.org(c).id),
518
+ c.req.valid('param').id,
519
+ c.req.valid('json'),
520
+ )
521
+ if (!subscription) return notFound(c)
522
+ return c.json(
523
+ Response.validated(webhookSchema.Subscription, publicSubscription(subscription)),
524
+ 200,
525
+ )
526
+ } catch (cause) {
527
+ return Response.upstream(c, cause)
528
+ }
529
+ },
530
+ )
531
+ .delete(
532
+ '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/webhooks/:id{wh_[A-Za-z0-9_-]+}',
533
+ Auth.policy({ session: true }),
534
+ Auth.ensureOrg({ role: 'admin' }),
535
+ OpenApi.validate('param', schema.WebhookParams, paramValidation),
536
+ OpenApi.describeRoute({
537
+ hide: hidden,
538
+ operationId: 'deleteOrgWebhook',
539
+ responses: OpenApi.responses({
540
+ errors: {
541
+ 400: { codes: ['param_invalid'], description: 'The path parameters are invalid.' },
542
+ 403: { codes: ['forbidden'], description: 'Organization admin access is required.' },
543
+ 404: {
544
+ codes: ['organization_not_found', 'webhook_not_found', 'webhooks_not_enabled'],
545
+ description: 'No accessible organization, webhook, or webhook capability was found.',
546
+ },
547
+ },
548
+ success: { description: 'Deleted webhook.', schema: schema.DeleteResponse },
549
+ }),
550
+ summary: 'Delete webhook',
551
+ tags: ['Webhooks'],
552
+ }),
553
+ async (c) => {
554
+ if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
555
+ if (Auth.narrowScope) return Auth.ensureOrgError(c)
556
+ if (Auth.narrowOrgRole) return Auth.ensureOrgRoleError(c)
557
+ if (OpenApi.narrowValidation) return OpenApi.validationError(c, paramValidation)
558
+ if (!options.webhook) return notEnabled(c)
559
+ try {
560
+ const id = c.req.valid('param').id
561
+ const removed = await Webhooks.deleteSubscription(
562
+ Db.get(c.get('db')),
563
+ owner(Auth.org(c).id),
564
+ id,
565
+ )
566
+ if (!removed) return notFound(c)
567
+ return c.json(Response.validated(schema.DeleteResponse, { id }), 200)
568
+ } catch (cause) {
569
+ return Response.upstream(c, cause)
570
+ }
571
+ },
572
+ )
573
+ }
574
+
575
+ export declare namespace webhooks {
576
+ /** Options for organization webhook management routes. */
577
+ type Options = {
578
+ /** Webhook capability shared with the data routes and host poller. */
579
+ webhook?: App.Webhook | undefined
580
+ }
581
+ }
582
+
583
+ function owner(orgId: string): Webhooks.Owner {
584
+ return { orgId, type: 'api_key' }
585
+ }
586
+
587
+ function publicSubscription(subscription: Webhooks.Subscription) {
588
+ const destination = (() => {
589
+ if (subscription.destination.type === 'slack')
590
+ return { ...subscription.destination, url: redactedSlackUrl }
591
+ if (subscription.destination.type === 'betterstack')
592
+ return { ...subscription.destination, token: redactedBetterstackToken }
593
+ return { ...subscription.destination, url: redactUrl(subscription.destination.url) }
594
+ })()
595
+ return {
596
+ chainId: subscription.chainId,
597
+ ...(subscription.context === undefined ? {} : { context: subscription.context }),
598
+ createdAt: subscription.createdAt,
599
+ destination,
600
+ eventType: subscription.eventType,
601
+ ...(subscription.expiresAt === undefined ? {} : { expiresAt: subscription.expiresAt }),
602
+ failureCount: subscription.failureCount,
603
+ filters: subscription.filters,
604
+ id: subscription.id,
605
+ ...(subscription.lastDeliveryAt === undefined
606
+ ? {}
607
+ : { lastDeliveryAt: subscription.lastDeliveryAt }),
608
+ status: subscription.status,
609
+ updatedAt: subscription.updatedAt,
610
+ }
611
+ }
612
+
613
+ function created(subscription: Webhooks.Subscription) {
614
+ const record = publicSubscription(subscription)
615
+ if (subscription.destination.type !== 'url') return record
616
+ return {
617
+ ...record,
618
+ destination: subscription.destination,
619
+ secret: subscription.secret,
620
+ }
621
+ }
622
+
623
+ function publicDelivery(delivery: Webhooks.Delivery, subscription: Webhooks.Subscription) {
624
+ const { envelope: _, ...record } = delivery
625
+ return {
626
+ ...record,
627
+ requestUrl: publicSubscription(subscription).destination.url,
628
+ }
629
+ }
630
+
631
+ function redactUrl(input: string) {
632
+ return `${new URL(input).origin}/…`
633
+ }
634
+
635
+ function mutationError(c: Context<Environment>, cause: unknown) {
636
+ if (cause instanceof Webhooks.InvalidUrlError)
637
+ return Response.error(c, { code: 'url_invalid', message: cause.message, status: 400 })
638
+ if (cause instanceof Webhooks.InvalidFilterError)
639
+ return Response.error(c, {
640
+ code: 'filters_invalid',
641
+ details: cause.details,
642
+ message: cause.message,
643
+ status: 400,
644
+ })
645
+ if (cause instanceof Webhooks.LimitExceededError)
646
+ return Response.error(c, { code: 'limit_exceeded', message: cause.message, status: 403 })
647
+ return Response.upstream(c, cause)
648
+ }
649
+
650
+ function notFound(c: Context<Environment>) {
651
+ return Response.error(c, {
652
+ code: 'webhook_not_found',
653
+ message: 'Webhook not found',
654
+ status: 404,
655
+ })
656
+ }
657
+
658
+ function deliveryNotFound(c: Context<Environment>) {
659
+ return Response.error(c, {
660
+ code: 'delivery_not_found',
661
+ message: 'Delivery not found',
662
+ status: 404,
663
+ })
664
+ }
665
+
666
+ function notEnabled(c: Context<Environment>) {
667
+ return Response.error(c, {
668
+ code: 'webhooks_not_enabled',
669
+ message: 'Webhooks are not enabled for this Tempo API deployment.',
670
+ status: 404,
671
+ })
672
+ }
@@ -136,6 +136,19 @@ function resultJson(result: Awaited<ReturnType<typeof toolsCall>>) {
136
136
  return result.structuredContent ?? JSON.parse(result.content[0]!.text)
137
137
  }
138
138
 
139
+ test('identifies as Tempo MCP', async () => {
140
+ const { host } = createHost()
141
+
142
+ expect((await initialize(host)).result).toMatchObject({
143
+ instructions: expect.stringContaining('Tempo MCP provides access'),
144
+ serverInfo: {
145
+ name: 'tempo',
146
+ title: 'Tempo MCP',
147
+ version: '0.0.0',
148
+ },
149
+ })
150
+ })
151
+
139
152
  test('scopes tools to the data domain', async () => {
140
153
  const { app, host } = createHost({ docs: docsStub() })
141
154