@pithy-sh/core 0.13.0 → 0.14.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 (354) hide show
  1. package/README.md +54 -0
  2. package/dist/audit/auditEvent.js +1 -1
  3. package/dist/audit/chain.d.ts +289 -0
  4. package/dist/audit/chain.d.ts.map +1 -0
  5. package/dist/audit/chain.js +400 -0
  6. package/dist/audit/citations.d.ts +52 -0
  7. package/dist/audit/citations.d.ts.map +1 -0
  8. package/dist/audit/citations.js +13 -0
  9. package/dist/capability/bindings.d.ts +22 -0
  10. package/dist/capability/bindings.d.ts.map +1 -1
  11. package/dist/capability/bindings.js +32 -2
  12. package/dist/capability/capability.d.ts +123 -16
  13. package/dist/capability/capability.d.ts.map +1 -1
  14. package/dist/capability/capability.js +17 -2
  15. package/dist/capability/erasure.d.ts +75 -0
  16. package/dist/capability/erasure.d.ts.map +1 -0
  17. package/dist/capability/erasure.js +2 -0
  18. package/dist/capability/manifest.d.ts +5 -0
  19. package/dist/capability/manifest.d.ts.map +1 -1
  20. package/dist/capability/personColumns.d.ts +28 -0
  21. package/dist/capability/personColumns.d.ts.map +1 -0
  22. package/dist/capability/personColumns.js +66 -0
  23. package/dist/capability/personRows.d.ts +62 -0
  24. package/dist/capability/personRows.d.ts.map +1 -0
  25. package/dist/capability/personRows.js +59 -0
  26. package/dist/capability/purge.d.ts +86 -19
  27. package/dist/capability/purge.d.ts.map +1 -1
  28. package/dist/capability/purge.js +83 -5
  29. package/dist/capability/settings.d.ts +10 -0
  30. package/dist/capability/settings.d.ts.map +1 -1
  31. package/dist/capability/vectorIndex.d.ts +36 -0
  32. package/dist/capability/vectorIndex.d.ts.map +1 -0
  33. package/dist/capability/vectorIndex.js +25 -0
  34. package/dist/cli/commandCatalog.generated.d.ts +433 -3
  35. package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
  36. package/dist/cli/commandCatalog.generated.js +449 -2
  37. package/dist/cli/commandFacts.d.ts +8 -5
  38. package/dist/cli/commandFacts.d.ts.map +1 -1
  39. package/dist/cli/commandFacts.js +12 -3
  40. package/dist/consent/purpose.d.ts +70 -0
  41. package/dist/consent/purpose.d.ts.map +1 -0
  42. package/dist/consent/purpose.js +97 -0
  43. package/dist/consent/reader.d.ts +99 -0
  44. package/dist/consent/reader.d.ts.map +1 -0
  45. package/dist/consent/reader.js +33 -0
  46. package/dist/controlPlane/audit/actions.d.ts +15 -4
  47. package/dist/controlPlane/audit/actions.d.ts.map +1 -1
  48. package/dist/controlPlane/audit/actions.js +16 -5
  49. package/dist/controlPlane/capability.d.ts +10 -0
  50. package/dist/controlPlane/capability.d.ts.map +1 -1
  51. package/dist/controlPlane/capability.js +56 -8
  52. package/dist/controlPlane/context.d.ts +4 -0
  53. package/dist/controlPlane/context.d.ts.map +1 -1
  54. package/dist/controlPlane/context.js +3 -2
  55. package/dist/controlPlane/data/connection.d.ts +21 -2
  56. package/dist/controlPlane/data/connection.d.ts.map +1 -1
  57. package/dist/controlPlane/data/connection.js +13 -4
  58. package/dist/controlPlane/data/connections.d.ts +94 -0
  59. package/dist/controlPlane/data/connections.d.ts.map +1 -0
  60. package/dist/controlPlane/data/connections.js +151 -0
  61. package/dist/controlPlane/data/hold.d.ts +46 -0
  62. package/dist/controlPlane/data/hold.d.ts.map +1 -0
  63. package/dist/controlPlane/data/hold.js +36 -0
  64. package/dist/controlPlane/data/oidcTrust.d.ts +122 -0
  65. package/dist/controlPlane/data/oidcTrust.d.ts.map +1 -0
  66. package/dist/controlPlane/data/oidcTrust.js +173 -0
  67. package/dist/controlPlane/data/tables.d.ts +4 -0
  68. package/dist/controlPlane/data/tables.d.ts.map +1 -1
  69. package/dist/controlPlane/data/tables.js +9 -4
  70. package/dist/controlPlane/data/trustFirstCall.d.ts +16 -0
  71. package/dist/controlPlane/data/trustFirstCall.d.ts.map +1 -0
  72. package/dist/controlPlane/data/trustFirstCall.js +25 -0
  73. package/dist/controlPlane/discovery/adminRoute.d.ts +165 -2
  74. package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -1
  75. package/dist/controlPlane/discovery/adminRoute.js +32 -8
  76. package/dist/controlPlane/discovery/routeInput.d.ts +23 -0
  77. package/dist/controlPlane/discovery/routeInput.d.ts.map +1 -0
  78. package/dist/controlPlane/discovery/routeInput.js +80 -0
  79. package/dist/controlPlane/discovery/toolName.d.ts +21 -0
  80. package/dist/controlPlane/discovery/toolName.d.ts.map +1 -0
  81. package/dist/controlPlane/discovery/toolName.js +25 -0
  82. package/dist/controlPlane/erasure/attest.d.ts +78 -0
  83. package/dist/controlPlane/erasure/attest.d.ts.map +1 -0
  84. package/dist/controlPlane/erasure/attest.js +164 -0
  85. package/dist/controlPlane/erasure/read.d.ts +39 -0
  86. package/dist/controlPlane/erasure/read.d.ts.map +1 -0
  87. package/dist/controlPlane/erasure/read.js +140 -0
  88. package/dist/controlPlane/error/errors.d.ts +61 -2
  89. package/dist/controlPlane/error/errors.d.ts.map +1 -1
  90. package/dist/controlPlane/error/errors.js +13 -0
  91. package/dist/controlPlane/evidence/derive.d.ts +124 -0
  92. package/dist/controlPlane/evidence/derive.d.ts.map +1 -0
  93. package/dist/controlPlane/evidence/derive.js +343 -0
  94. package/dist/controlPlane/evidence/mapping.d.ts +85 -0
  95. package/dist/controlPlane/evidence/mapping.d.ts.map +1 -0
  96. package/dist/controlPlane/evidence/mapping.js +114 -0
  97. package/dist/controlPlane/evidence/pack.d.ts +46 -0
  98. package/dist/controlPlane/evidence/pack.d.ts.map +1 -0
  99. package/dist/controlPlane/evidence/pack.js +124 -0
  100. package/dist/controlPlane/http/connections.d.ts +96 -0
  101. package/dist/controlPlane/http/connections.d.ts.map +1 -0
  102. package/dist/controlPlane/http/connections.js +109 -0
  103. package/dist/controlPlane/http/guard.d.ts +23 -2
  104. package/dist/controlPlane/http/guard.d.ts.map +1 -1
  105. package/dist/controlPlane/http/guard.js +69 -12
  106. package/dist/controlPlane/http/handlers.d.ts +212 -10
  107. package/dist/controlPlane/http/handlers.d.ts.map +1 -1
  108. package/dist/controlPlane/http/handlers.js +117 -26
  109. package/dist/controlPlane/http/holds.d.ts +57 -0
  110. package/dist/controlPlane/http/holds.d.ts.map +1 -0
  111. package/dist/controlPlane/http/holds.js +98 -0
  112. package/dist/controlPlane/http/presence.d.ts +27 -0
  113. package/dist/controlPlane/http/presence.d.ts.map +1 -0
  114. package/dist/controlPlane/http/presence.js +67 -0
  115. package/dist/controlPlane/http/processing.d.ts +73 -0
  116. package/dist/controlPlane/http/processing.d.ts.map +1 -0
  117. package/dist/controlPlane/http/processing.js +17 -0
  118. package/dist/controlPlane/http/responses.d.ts +927 -3
  119. package/dist/controlPlane/http/responses.d.ts.map +1 -1
  120. package/dist/controlPlane/http/responses.js +283 -7
  121. package/dist/controlPlane/http/routes.d.ts +12 -2
  122. package/dist/controlPlane/http/routes.d.ts.map +1 -1
  123. package/dist/controlPlane/http/routes.js +182 -5
  124. package/dist/controlPlane/http/schemas.d.ts +63 -0
  125. package/dist/controlPlane/http/schemas.d.ts.map +1 -1
  126. package/dist/controlPlane/http/schemas.js +39 -1
  127. package/dist/controlPlane/http/verify.d.ts.map +1 -1
  128. package/dist/controlPlane/http/verify.js +13 -1
  129. package/dist/controlPlane/http/verifyTrust.d.ts +76 -0
  130. package/dist/controlPlane/http/verifyTrust.d.ts.map +1 -0
  131. package/dist/controlPlane/http/verifyTrust.js +75 -0
  132. package/dist/controlPlane/migrations/0004_connection_trusts.d.ts +15 -0
  133. package/dist/controlPlane/migrations/0004_connection_trusts.d.ts.map +1 -0
  134. package/dist/controlPlane/migrations/0004_connection_trusts.js +23 -0
  135. package/dist/controlPlane/migrations/0005_holds.d.ts +12 -0
  136. package/dist/controlPlane/migrations/0005_holds.d.ts.map +1 -0
  137. package/dist/controlPlane/migrations/0005_holds.js +26 -0
  138. package/dist/controlPlane/processing/derive.d.ts +77 -0
  139. package/dist/controlPlane/processing/derive.d.ts.map +1 -0
  140. package/dist/controlPlane/processing/derive.js +206 -0
  141. package/dist/controlPlane/processing/read.d.ts +10 -0
  142. package/dist/controlPlane/processing/read.d.ts.map +1 -0
  143. package/dist/controlPlane/processing/read.js +89 -0
  144. package/dist/controlPlane/scope/kind.d.ts +46 -0
  145. package/dist/controlPlane/scope/kind.d.ts.map +1 -0
  146. package/dist/controlPlane/scope/kind.js +52 -0
  147. package/dist/controlPlane/scope/scope.d.ts +58 -3
  148. package/dist/controlPlane/scope/scope.d.ts.map +1 -1
  149. package/dist/controlPlane/scope/scope.js +59 -4
  150. package/dist/controlPlane/token/jws.js +1 -1
  151. package/dist/controlPlane/token/mint.js +1 -1
  152. package/dist/controlPlane/wire.d.ts +23 -0
  153. package/dist/controlPlane/wire.d.ts.map +1 -1
  154. package/dist/controlPlane/wire.js +24 -1
  155. package/dist/createBackend.d.ts +13 -0
  156. package/dist/createBackend.d.ts.map +1 -1
  157. package/dist/createBackend.js +11 -3
  158. package/dist/createEntrypoint.d.ts +7 -0
  159. package/dist/createEntrypoint.d.ts.map +1 -1
  160. package/dist/createEntrypoint.js +4 -0
  161. package/dist/error/http.d.ts +168 -0
  162. package/dist/error/http.d.ts.map +1 -1
  163. package/dist/error/payload.d.ts +336 -0
  164. package/dist/error/payload.d.ts.map +1 -1
  165. package/dist/error/payload.js +112 -0
  166. package/dist/error/pithyError.d.ts +22 -0
  167. package/dist/error/pithyError.d.ts.map +1 -1
  168. package/dist/error/pithyError.js +31 -1
  169. package/dist/http/routeSpec.d.ts +46 -1
  170. package/dist/http/routeSpec.d.ts.map +1 -1
  171. package/dist/http/routeSpec.js +39 -5
  172. package/dist/http/verification.d.ts.map +1 -1
  173. package/dist/http/verification.js +1 -1
  174. package/dist/i18n/translator.d.ts +2 -1
  175. package/dist/i18n/translator.d.ts.map +1 -1
  176. package/dist/index.d.ts +1 -1
  177. package/dist/index.d.ts.map +1 -1
  178. package/dist/index.js +3 -3
  179. package/dist/logger/record.d.ts +98 -0
  180. package/dist/logger/record.d.ts.map +1 -1
  181. package/dist/mcp/annotations.d.ts +20 -0
  182. package/dist/mcp/annotations.d.ts.map +1 -0
  183. package/dist/mcp/annotations.js +23 -0
  184. package/dist/mcp/tools.d.ts +75 -0
  185. package/dist/mcp/tools.d.ts.map +1 -0
  186. package/dist/mcp/tools.js +118 -0
  187. package/dist/openapi/document.d.ts.map +1 -1
  188. package/dist/openapi/document.js +8 -26
  189. package/dist/openapi/operations.d.ts +35 -0
  190. package/dist/openapi/operations.d.ts.map +1 -0
  191. package/dist/openapi/operations.js +37 -0
  192. package/dist/presence/channel.d.ts +11 -0
  193. package/dist/presence/channel.d.ts.map +1 -0
  194. package/dist/presence/channel.js +65 -0
  195. package/dist/presence/declaration.d.ts +154 -0
  196. package/dist/presence/declaration.d.ts.map +1 -0
  197. package/dist/presence/declaration.js +164 -0
  198. package/dist/presence/durableObject.d.ts +61 -0
  199. package/dist/presence/durableObject.d.ts.map +1 -0
  200. package/dist/presence/durableObject.js +257 -0
  201. package/dist/presence/frames.d.ts +166 -0
  202. package/dist/presence/frames.d.ts.map +1 -0
  203. package/dist/presence/frames.js +90 -0
  204. package/dist/presence/http/guard.d.ts +11 -0
  205. package/dist/presence/http/guard.d.ts.map +1 -0
  206. package/dist/presence/http/guard.js +20 -0
  207. package/dist/presence/http/responses.d.ts +31 -0
  208. package/dist/presence/http/responses.d.ts.map +1 -0
  209. package/dist/presence/http/responses.js +34 -0
  210. package/dist/presence/http/routes.d.ts +24 -0
  211. package/dist/presence/http/routes.d.ts.map +1 -0
  212. package/dist/presence/http/routes.js +127 -0
  213. package/dist/presence/http/schemas.d.ts +29 -0
  214. package/dist/presence/http/schemas.d.ts.map +1 -0
  215. package/dist/presence/http/schemas.js +28 -0
  216. package/dist/presence/names.d.ts +23 -0
  217. package/dist/presence/names.d.ts.map +1 -0
  218. package/dist/presence/names.js +22 -0
  219. package/dist/presence/room.d.ts +95 -0
  220. package/dist/presence/room.d.ts.map +1 -0
  221. package/dist/presence/room.js +105 -0
  222. package/dist/presence/ticket.d.ts +55 -0
  223. package/dist/presence/ticket.d.ts.map +1 -0
  224. package/dist/presence/ticket.js +109 -0
  225. package/dist/privacy/classify.js +1 -1
  226. package/dist/privacy/encrypted.js +1 -1
  227. package/dist/processing/activity.d.ts +134 -0
  228. package/dist/processing/activity.d.ts.map +1 -0
  229. package/dist/processing/activity.js +161 -0
  230. package/dist/processing/composed.d.ts +18 -0
  231. package/dist/processing/composed.d.ts.map +1 -0
  232. package/dist/processing/composed.js +28 -0
  233. package/dist/processing/vocabulary.d.ts +26 -0
  234. package/dist/processing/vocabulary.d.ts.map +1 -0
  235. package/dist/processing/vocabulary.js +35 -0
  236. package/dist/queue/consumer.d.ts +59 -0
  237. package/dist/queue/consumer.d.ts.map +1 -0
  238. package/dist/queue/consumer.js +46 -0
  239. package/dist/retention/composed.d.ts +37 -0
  240. package/dist/retention/composed.d.ts.map +1 -0
  241. package/dist/retention/composed.js +66 -0
  242. package/dist/retention/holds.d.ts +61 -0
  243. package/dist/retention/holds.d.ts.map +1 -0
  244. package/dist/retention/holds.js +107 -0
  245. package/dist/retention/holdsPurge.d.ts +22 -0
  246. package/dist/retention/holdsPurge.d.ts.map +1 -0
  247. package/dist/retention/holdsPurge.js +50 -0
  248. package/dist/retention/policy.d.ts +132 -0
  249. package/dist/retention/policy.d.ts.map +1 -0
  250. package/dist/retention/policy.js +197 -0
  251. package/dist/session/rotated.d.ts +34 -0
  252. package/dist/session/rotated.d.ts.map +1 -0
  253. package/dist/session/rotated.js +25 -0
  254. package/dist/test-utils/connectionFixtures.d.ts +13 -0
  255. package/dist/test-utils/connectionFixtures.d.ts.map +1 -0
  256. package/dist/test-utils/connectionFixtures.js +58 -0
  257. package/dist/test-utils/interleave.d.ts +12 -0
  258. package/dist/test-utils/interleave.d.ts.map +1 -0
  259. package/dist/test-utils/interleave.js +26 -0
  260. package/dist/testWorker.d.ts +8 -0
  261. package/dist/testWorker.d.ts.map +1 -0
  262. package/dist/testWorker.js +9 -0
  263. package/dist/version.generated.d.ts +1 -1
  264. package/dist/version.generated.js +1 -1
  265. package/dist/workflow/dispatchRoute.js +1 -1
  266. package/package.json +1 -1
  267. package/src/audit/auditEvent.ts +1 -1
  268. package/src/audit/chain.ts +551 -0
  269. package/src/audit/citations.ts +63 -0
  270. package/src/capability/bindings.ts +61 -0
  271. package/src/capability/capability.ts +156 -19
  272. package/src/capability/erasure.ts +82 -0
  273. package/src/capability/personColumns.ts +83 -0
  274. package/src/capability/personRows.ts +125 -0
  275. package/src/capability/purge.ts +158 -21
  276. package/src/capability/settings.ts +7 -0
  277. package/src/capability/vectorIndex.ts +60 -0
  278. package/src/cli/commandCatalog.generated.ts +482 -2
  279. package/src/cli/commandFacts.ts +12 -3
  280. package/src/cloudflare-test.d.ts +3 -1
  281. package/src/consent/purpose.ts +160 -0
  282. package/src/consent/reader.ts +134 -0
  283. package/src/controlPlane/audit/actions.ts +15 -4
  284. package/src/controlPlane/capability.ts +99 -20
  285. package/src/controlPlane/context.ts +10 -2
  286. package/src/controlPlane/data/connection.ts +16 -2
  287. package/src/controlPlane/data/connections.ts +231 -0
  288. package/src/controlPlane/data/hold.ts +52 -0
  289. package/src/controlPlane/data/oidcTrust.ts +249 -0
  290. package/src/controlPlane/data/tables.ts +7 -0
  291. package/src/controlPlane/data/trustFirstCall.ts +37 -0
  292. package/src/controlPlane/discovery/adminRoute.ts +71 -9
  293. package/src/controlPlane/discovery/routeInput.ts +86 -0
  294. package/src/controlPlane/discovery/toolName.ts +23 -0
  295. package/src/controlPlane/erasure/attest.ts +205 -0
  296. package/src/controlPlane/erasure/read.ts +169 -0
  297. package/src/controlPlane/error/errors.ts +69 -2
  298. package/src/controlPlane/evidence/derive.ts +426 -0
  299. package/src/controlPlane/evidence/mapping.ts +118 -0
  300. package/src/controlPlane/evidence/pack.ts +190 -0
  301. package/src/controlPlane/http/connections.ts +129 -0
  302. package/src/controlPlane/http/guard.ts +118 -9
  303. package/src/controlPlane/http/handlers.ts +172 -40
  304. package/src/controlPlane/http/holds.ts +108 -0
  305. package/src/controlPlane/http/presence.ts +71 -0
  306. package/src/controlPlane/http/processing.ts +19 -0
  307. package/src/controlPlane/http/responses.ts +590 -7
  308. package/src/controlPlane/http/routes.ts +302 -3
  309. package/src/controlPlane/http/schemas.ts +126 -0
  310. package/src/controlPlane/http/verify.ts +19 -2
  311. package/src/controlPlane/http/verifyTrust.ts +176 -0
  312. package/src/controlPlane/migrations/0004_connection_trusts.ts +30 -0
  313. package/src/controlPlane/migrations/0005_holds.ts +40 -0
  314. package/src/controlPlane/processing/derive.ts +359 -0
  315. package/src/controlPlane/processing/read.ts +102 -0
  316. package/src/controlPlane/scope/kind.ts +63 -0
  317. package/src/controlPlane/scope/scope.ts +65 -3
  318. package/src/controlPlane/wire.ts +26 -0
  319. package/src/createBackend.ts +31 -6
  320. package/src/createEntrypoint.ts +17 -0
  321. package/src/error/payload.ts +234 -0
  322. package/src/error/pithyError.ts +42 -0
  323. package/src/http/routeSpec.ts +75 -5
  324. package/src/http/verification.ts +3 -1
  325. package/src/i18n/translator.ts +2 -1
  326. package/src/index.ts +2 -0
  327. package/src/mcp/annotations.ts +38 -0
  328. package/src/mcp/tools.ts +220 -0
  329. package/src/openapi/document.ts +8 -28
  330. package/src/openapi/operations.ts +62 -0
  331. package/src/presence/channel.ts +46 -0
  332. package/src/presence/declaration.ts +283 -0
  333. package/src/presence/durableObject.ts +287 -0
  334. package/src/presence/frames.ts +153 -0
  335. package/src/presence/http/guard.ts +23 -0
  336. package/src/presence/http/responses.ts +63 -0
  337. package/src/presence/http/routes.ts +165 -0
  338. package/src/presence/http/schemas.ts +39 -0
  339. package/src/presence/names.ts +45 -0
  340. package/src/presence/room.ts +145 -0
  341. package/src/presence/ticket.ts +145 -0
  342. package/src/processing/activity.ts +289 -0
  343. package/src/processing/composed.ts +33 -0
  344. package/src/processing/vocabulary.ts +43 -0
  345. package/src/queue/consumer.ts +104 -0
  346. package/src/retention/composed.ts +75 -0
  347. package/src/retention/holds.ts +175 -0
  348. package/src/retention/holdsPurge.ts +50 -0
  349. package/src/retention/policy.ts +305 -0
  350. package/src/session/rotated.ts +53 -0
  351. package/src/test-utils/connectionFixtures.ts +58 -0
  352. package/src/test-utils/interleave.ts +31 -0
  353. package/src/testWorker.ts +13 -0
  354. package/src/version.generated.ts +1 -1
@@ -0,0 +1,166 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ import type { SocketVersions } from "../http/subprotocol";
5
+ /**
6
+ * **Every frame on a presence room socket, as the Zod schema it is sent and parsed with.**
7
+ *
8
+ * Pure: zod and core's leaf modules. A browser imports it to type the socket, and the AsyncAPI document
9
+ * reads the same schemas through the declaring capability's `sockets`, so the document and the Durable
10
+ * Object cannot disagree.
11
+ *
12
+ * **How a client reads a frame, stated once** (`docs/SOCKETS.md`). A field added to a frame later is
13
+ * optional. A client ignores a `type` and a key it does not know. A change no client could ignore is a new
14
+ * subprotocol, and the Worker keeps serving the one before it.
15
+ *
16
+ * **The keepalive is plain text**, the one exception to JSON: a client sends `ping`, the Worker answers
17
+ * `pong`. The only JSON frame a client sends is `set_activity`.
18
+ */
19
+ /** The subprotocols a room serves. There is no unversioned room: a ticket can only arrive in the offer. */
20
+ export declare const PRESENCE_ROOM_VERSIONS: SocketVersions;
21
+ /** Which door a participant came in by. */
22
+ export declare const PresenceOrigin: z.ZodEnum<{
23
+ "control-plane": "control-plane";
24
+ session: "session";
25
+ }>;
26
+ export type PresenceOrigin = z.infer<typeof PresenceOrigin>;
27
+ /** Who a participant is, as the minting door states it. */
28
+ export declare const PresenceParticipantRef: z.ZodString;
29
+ /** The longest participant name, in UTF-16 code units — what zod's `.max` counts. */
30
+ export declare const PRESENCE_PARTICIPANT_NAME_MAX = 80;
31
+ /** How the others see a participant. */
32
+ export declare const PresenceParticipantName: z.ZodString;
33
+ /** One participant in a room. */
34
+ export declare const PresenceParticipant: z.ZodObject<{
35
+ id: z.ZodString;
36
+ ref: z.ZodString;
37
+ name: z.ZodString;
38
+ origin: z.ZodEnum<{
39
+ "control-plane": "control-plane";
40
+ session: "session";
41
+ }>;
42
+ activity: z.ZodString;
43
+ joinedAt: z.ZodISODateTime;
44
+ }, z.core.$strip>;
45
+ export type PresenceParticipant = z.infer<typeof PresenceParticipant>;
46
+ /** The first frame on every room socket. */
47
+ export declare const PresenceRoomInitFrame: z.ZodObject<{
48
+ type: z.ZodLiteral<"init">;
49
+ self: z.ZodString;
50
+ participants: z.ZodArray<z.ZodObject<{
51
+ id: z.ZodString;
52
+ ref: z.ZodString;
53
+ name: z.ZodString;
54
+ origin: z.ZodEnum<{
55
+ "control-plane": "control-plane";
56
+ session: "session";
57
+ }>;
58
+ activity: z.ZodString;
59
+ joinedAt: z.ZodISODateTime;
60
+ }, z.core.$strip>>;
61
+ }, z.core.$strip>;
62
+ export type PresenceRoomInitFrame = z.infer<typeof PresenceRoomInitFrame>;
63
+ /** Someone joined. */
64
+ export declare const PresenceJoinedFrame: z.ZodObject<{
65
+ type: z.ZodLiteral<"joined">;
66
+ participant: z.ZodObject<{
67
+ id: z.ZodString;
68
+ ref: z.ZodString;
69
+ name: z.ZodString;
70
+ origin: z.ZodEnum<{
71
+ "control-plane": "control-plane";
72
+ session: "session";
73
+ }>;
74
+ activity: z.ZodString;
75
+ joinedAt: z.ZodISODateTime;
76
+ }, z.core.$strip>;
77
+ }, z.core.$strip>;
78
+ export type PresenceJoinedFrame = z.infer<typeof PresenceJoinedFrame>;
79
+ /** Someone left, or their socket dropped. */
80
+ export declare const PresenceLeftFrame: z.ZodObject<{
81
+ type: z.ZodLiteral<"left">;
82
+ participantId: z.ZodString;
83
+ }, z.core.$strip>;
84
+ export type PresenceLeftFrame = z.infer<typeof PresenceLeftFrame>;
85
+ /** Someone changed what they are doing. */
86
+ export declare const PresenceActivityFrame: z.ZodObject<{
87
+ type: z.ZodLiteral<"activity">;
88
+ participantId: z.ZodString;
89
+ activity: z.ZodString;
90
+ }, z.core.$strip>;
91
+ export type PresenceActivityFrame = z.infer<typeof PresenceActivityFrame>;
92
+ /** A frame the room could not accept. */
93
+ export declare const PresenceRoomErrorFrame: z.ZodObject<{
94
+ type: z.ZodLiteral<"error">;
95
+ error: z.ZodObject<{
96
+ code: z.ZodString;
97
+ status: z.ZodNumber;
98
+ message: z.ZodString;
99
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
100
+ issues: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
101
+ }, z.core.$strip>;
102
+ }, z.core.$strip>;
103
+ export type PresenceRoomErrorFrame = z.infer<typeof PresenceRoomErrorFrame>;
104
+ /** Every JSON frame the Worker sends on a room socket. `pong` is the plain-text exception. */
105
+ export declare const PresenceRoomServerFrame: z.ZodDiscriminatedUnion<[z.ZodObject<{
106
+ type: z.ZodLiteral<"init">;
107
+ self: z.ZodString;
108
+ participants: z.ZodArray<z.ZodObject<{
109
+ id: z.ZodString;
110
+ ref: z.ZodString;
111
+ name: z.ZodString;
112
+ origin: z.ZodEnum<{
113
+ "control-plane": "control-plane";
114
+ session: "session";
115
+ }>;
116
+ activity: z.ZodString;
117
+ joinedAt: z.ZodISODateTime;
118
+ }, z.core.$strip>>;
119
+ }, z.core.$strip>, z.ZodObject<{
120
+ type: z.ZodLiteral<"joined">;
121
+ participant: z.ZodObject<{
122
+ id: z.ZodString;
123
+ ref: z.ZodString;
124
+ name: z.ZodString;
125
+ origin: z.ZodEnum<{
126
+ "control-plane": "control-plane";
127
+ session: "session";
128
+ }>;
129
+ activity: z.ZodString;
130
+ joinedAt: z.ZodISODateTime;
131
+ }, z.core.$strip>;
132
+ }, z.core.$strip>, z.ZodObject<{
133
+ type: z.ZodLiteral<"left">;
134
+ participantId: z.ZodString;
135
+ }, z.core.$strip>, z.ZodObject<{
136
+ type: z.ZodLiteral<"activity">;
137
+ participantId: z.ZodString;
138
+ activity: z.ZodString;
139
+ }, z.core.$strip>, z.ZodObject<{
140
+ type: z.ZodLiteral<"error">;
141
+ error: z.ZodObject<{
142
+ code: z.ZodString;
143
+ status: z.ZodNumber;
144
+ message: z.ZodString;
145
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
146
+ issues: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
147
+ }, z.core.$strip>;
148
+ }, z.core.$strip>], "type">;
149
+ export type PresenceRoomServerFrame = z.infer<typeof PresenceRoomServerFrame>;
150
+ /** Announce a different activity. */
151
+ export declare const PresenceSetActivityFrame: z.ZodObject<{
152
+ type: z.ZodLiteral<"set_activity">;
153
+ activity: z.ZodString;
154
+ }, z.core.$strip>;
155
+ export type PresenceSetActivityFrame = z.infer<typeof PresenceSetActivityFrame>;
156
+ /** Every JSON frame a client may send on a room socket. `ping` is the plain-text exception. */
157
+ export declare const PresenceRoomClientFrame: z.ZodDiscriminatedUnion<[z.ZodObject<{
158
+ type: z.ZodLiteral<"set_activity">;
159
+ activity: z.ZodString;
160
+ }, z.core.$strip>], "type">;
161
+ export type PresenceRoomClientFrame = z.infer<typeof PresenceRoomClientFrame>;
162
+ /** The keepalive a client sends, as plain text. */
163
+ export declare const PresenceRoomPing: z.ZodLiteral<"ping">;
164
+ /** The Worker's answer to `ping`, as plain text. */
165
+ export declare const PresenceRoomPong: z.ZodLiteral<"pong">;
166
+ //# sourceMappingURL=frames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frames.d.ts","sourceRoot":"","sources":["../../src/presence/frames.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D;;;;;;;;;;;;;GAaG;AAEH,2GAA2G;AAC3G,eAAO,MAAM,sBAAsB,EAAE,cAAiE,CAAC;AAEvG,2CAA2C;AAC3C,eAAO,MAAM,cAAc;;;EAIxB,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,aAMhC,CAAC;AAEJ,qFAAqF;AACrF,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,wCAAwC;AACxC,eAAO,MAAM,uBAAuB,aAK6E,CAAC;AAElH,iCAAiC;AACjC,eAAO,MAAM,mBAAmB;;;;;;;;;;iBAYmC,CAAC;AACpE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,4CAA4C;AAC5C,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;iBAQuC,CAAC;AAC1E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE1E,sBAAsB;AACtB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;iBAKmB,CAAC;AACpD,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,6CAA6C;AAC7C,eAAO,MAAM,iBAAiB;;;iBAK4C,CAAC;AAC3E,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,2CAA2C;AAC3C,eAAO,MAAM,qBAAqB;;;;iBAMgC,CAAC;AACnE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE1E,yCAAyC;AACzC,eAAO,MAAM,sBAAsB;;;;;;;;;iBAOhC,CAAC;AACJ,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,8FAA8F;AAC9F,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAQmC,CAAC;AACxE,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E,qCAAqC;AACrC,eAAO,MAAM,wBAAwB;;;iBAKsB,CAAC;AAC5D,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF,+FAA+F;AAC/F,eAAO,MAAM,uBAAuB;;;2BAEiC,CAAC;AACtE,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E,mDAAmD;AACnD,eAAO,MAAM,gBAAgB,sBAAyE,CAAC;AAEvG,oDAAoD;AACpD,eAAO,MAAM,gBAAgB,sBAAiE,CAAC"}
@@ -0,0 +1,90 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { ErrorBody } from "../error/wire.js";
4
+ import { PresenceActivity } from "./names.js";
5
+ import { z } from "zod";
6
+ //#region src/presence/frames.ts
7
+ /**
8
+ * **Every frame on a presence room socket, as the Zod schema it is sent and parsed with.**
9
+ *
10
+ * Pure: zod and core's leaf modules. A browser imports it to type the socket, and the AsyncAPI document
11
+ * reads the same schemas through the declaring capability's `sockets`, so the document and the Durable
12
+ * Object cannot disagree.
13
+ *
14
+ * **How a client reads a frame, stated once** (`docs/SOCKETS.md`). A field added to a frame later is
15
+ * optional. A client ignores a `type` and a key it does not know. A change no client could ignore is a new
16
+ * subprotocol, and the Worker keeps serving the one before it.
17
+ *
18
+ * **The keepalive is plain text**, the one exception to JSON: a client sends `ping`, the Worker answers
19
+ * `pong`. The only JSON frame a client sends is `set_activity`.
20
+ */
21
+ /** The subprotocols a room serves. There is no unversioned room: a ticket can only arrive in the offer. */
22
+ const PRESENCE_ROOM_VERSIONS = {
23
+ current: "pithy.presence.v1",
24
+ previous: null
25
+ };
26
+ /** Which door a participant came in by. */
27
+ const PresenceOrigin = z.enum(["control-plane", "session"]).describe("Which door the participant came in by: `control-plane`, a management client such as the dashboard, or `session`, a signed-in user of the app.");
28
+ /** Who a participant is, as the minting door states it. */
29
+ const PresenceParticipantRef = z.string().min(1).max(128).describe("Who the participant is. A management client's own opaque reference, which it chose; or the signed-in user's id, which the Worker chose.");
30
+ /** The longest participant name, in UTF-16 code units — what zod's `.max` counts. */
31
+ const PRESENCE_PARTICIPANT_NAME_MAX = 80;
32
+ /** How the others see a participant. */
33
+ const PresenceParticipantName = z.string().trim().min(1).max(80).describe("How the other participants see this one. A label, never an identity — `ref` and `origin` are that.");
34
+ /** One participant in a room. */
35
+ const PresenceParticipant = z.object({
36
+ id: z.string().uuid().describe("This participant's id in the room — one per socket, so one person in two tabs is two participants."),
37
+ ref: PresenceParticipantRef,
38
+ name: PresenceParticipantName,
39
+ origin: PresenceOrigin,
40
+ activity: PresenceActivity.describe("What they are doing now, one of the room kind's declared activities."),
41
+ joinedAt: z.iso.datetime().describe("When this socket joined, ISO-8601.")
42
+ }).describe("One participant in a presence room: one open socket.");
43
+ /** The first frame on every room socket. */
44
+ const PresenceRoomInitFrame = z.object({
45
+ type: z.literal("init").describe("Always `init`."),
46
+ self: z.string().uuid().describe("This socket's own participant id, so a client can find itself in the list."),
47
+ participants: z.array(PresenceParticipant).describe("Everyone in the room now, this socket included, in the order they joined.")
48
+ }).describe("Who is in the room, sent once as soon as the socket opens.");
49
+ /** Someone joined. */
50
+ const PresenceJoinedFrame = z.object({
51
+ type: z.literal("joined").describe("Always `joined`."),
52
+ participant: PresenceParticipant.describe("Who joined.")
53
+ }).describe("Another participant joined the room.");
54
+ /** Someone left, or their socket dropped. */
55
+ const PresenceLeftFrame = z.object({
56
+ type: z.literal("left").describe("Always `left`."),
57
+ participantId: z.string().uuid().describe("The participant who left.")
58
+ }).describe("Another participant left the room, or their socket dropped.");
59
+ /** Someone changed what they are doing. */
60
+ const PresenceActivityFrame = z.object({
61
+ type: z.literal("activity").describe("Always `activity`."),
62
+ participantId: z.string().uuid().describe("The participant whose activity changed."),
63
+ activity: PresenceActivity.describe("What they are doing now.")
64
+ }).describe("Another participant announced a different activity.");
65
+ /** A frame the room could not accept. */
66
+ const PresenceRoomErrorFrame = z.object({
67
+ type: z.literal("error").describe("Always `error`."),
68
+ error: ErrorBody.shape.error
69
+ }).describe("The room could not accept a frame — not one it reads, an activity the room kind does not declare, or past the size limit. The error is the one an HTTP response would carry.");
70
+ /** Every JSON frame the Worker sends on a room socket. `pong` is the plain-text exception. */
71
+ const PresenceRoomServerFrame = z.discriminatedUnion("type", [
72
+ PresenceRoomInitFrame,
73
+ PresenceJoinedFrame,
74
+ PresenceLeftFrame,
75
+ PresenceActivityFrame,
76
+ PresenceRoomErrorFrame
77
+ ]).describe("A JSON frame the Worker sends on a presence room socket.");
78
+ /** Announce a different activity. */
79
+ const PresenceSetActivityFrame = z.object({
80
+ type: z.literal("set_activity").describe("Always `set_activity`."),
81
+ activity: PresenceActivity.describe("What this participant is doing now. One of the room kind's declared values.")
82
+ }).describe("Announce what this participant is doing now.");
83
+ /** Every JSON frame a client may send on a room socket. `ping` is the plain-text exception. */
84
+ const PresenceRoomClientFrame = z.discriminatedUnion("type", [PresenceSetActivityFrame]).describe("A JSON frame a client sends on a presence room socket.");
85
+ /** The keepalive a client sends, as plain text. */
86
+ const PresenceRoomPing = z.literal("ping").describe("The plain-text keepalive a client sends.");
87
+ /** The Worker's answer to `ping`, as plain text. */
88
+ const PresenceRoomPong = z.literal("pong").describe("The plain-text answer to `ping`.");
89
+ //#endregion
90
+ export { PRESENCE_PARTICIPANT_NAME_MAX, PRESENCE_ROOM_VERSIONS, PresenceActivityFrame, PresenceJoinedFrame, PresenceLeftFrame, PresenceOrigin, PresenceParticipant, PresenceParticipantName, PresenceParticipantRef, PresenceRoomClientFrame, PresenceRoomErrorFrame, PresenceRoomInitFrame, PresenceRoomPing, PresenceRoomPong, PresenceRoomServerFrame, PresenceSetActivityFrame };
@@ -0,0 +1,11 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { MiddlewareHandler } from "hono";
4
+ import type { PithyHonoEnv } from "../../capability/capability";
5
+ /**
6
+ * Require a signed-in user of the app, over the `AuthContext` seam. Kept here rather than imported from
7
+ * `@pithy-sh/auth`, which core may not depend on — and without auth composed `c.var.auth` is null, so the
8
+ * session ticket route denies rather than stands open.
9
+ */
10
+ export declare function requireAuth(): MiddlewareHandler<PithyHonoEnv>;
11
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../../src/presence/http/guard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,iBAAiB,CAAC,YAAY,CAAC,CAU7D"}
@@ -0,0 +1,20 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { UnauthorizedError } from "../../error/pithyError.js";
4
+ //#region src/presence/http/guard.ts
5
+ /**
6
+ * Require a signed-in user of the app, over the `AuthContext` seam. Kept here rather than imported from
7
+ * `@pithy-sh/auth`, which core may not depend on — and without auth composed `c.var.auth` is null, so the
8
+ * session ticket route denies rather than stands open.
9
+ */
10
+ function requireAuth() {
11
+ return async (c, next) => {
12
+ if (!c.var.auth) throw new UnauthorizedError({
13
+ message: "Authentication required.",
14
+ action: "Sign in and retry with a valid session or bearer token."
15
+ });
16
+ await next();
17
+ };
18
+ }
19
+ //#endregion
20
+ export { requireAuth };
@@ -0,0 +1,31 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ /**
5
+ * A minted presence ticket and where to spend it. Both ticket routes answer it.
6
+ *
7
+ * Read across a version boundary like every response schema, so a field added here later is `.optional()`.
8
+ */
9
+ export declare const PresenceTicket: z.ZodObject<{
10
+ ticket: z.ZodString;
11
+ expiresAt: z.ZodISODateTime;
12
+ kind: z.ZodString;
13
+ recordId: z.ZodString;
14
+ socketPath: z.ZodString;
15
+ protocol: z.ZodLiteral<string>;
16
+ }, z.core.$strip>;
17
+ export type PresenceTicket = z.infer<typeof PresenceTicket>;
18
+ /**
19
+ * One room kind, as `GET /control-plane/manifest` reports it under the capability that declares it. Built
20
+ * from the capability's resolved `basePath`, so a client never assumes where a socket is.
21
+ */
22
+ export declare const PresenceRoomDescriptor: z.ZodObject<{
23
+ kind: z.ZodString;
24
+ scope: z.ZodString;
25
+ activities: z.ZodArray<z.ZodString>;
26
+ maxParticipants: z.ZodNumber;
27
+ sessionTicketPath: z.ZodNullable<z.ZodString>;
28
+ socketPath: z.ZodString;
29
+ }, z.core.$strip>;
30
+ export type PresenceRoomDescriptor = z.infer<typeof PresenceRoomDescriptor>;
31
+ //# sourceMappingURL=responses.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../../src/presence/http/responses.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;;iBAmBgF,CAAC;AAC5G,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;;;iBAsB4D,CAAC;AAChG,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
@@ -0,0 +1,34 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { ControlPlaneScope } from "../../controlPlane/scope/scope.js";
4
+ import { PresenceActivity, PresenceRecordId, PresenceRoomKindName } from "../names.js";
5
+ import { PRESENCE_ROOM_VERSIONS } from "../frames.js";
6
+ import { z } from "zod";
7
+ //#region src/presence/http/responses.ts
8
+ /**
9
+ * A minted presence ticket and where to spend it. Both ticket routes answer it.
10
+ *
11
+ * Read across a version boundary like every response schema, so a field added here later is `.optional()`.
12
+ */
13
+ const PresenceTicket = z.object({
14
+ ticket: z.string().min(1).describe("The ticket. Offer it in `Sec-WebSocket-Protocol` as `pithy.ticket.<ticket>`, beside `protocol`. Single use."),
15
+ expiresAt: z.iso.datetime().describe("When the ticket stops being honored, ISO-8601 — 60 seconds after minting."),
16
+ kind: PresenceRoomKindName.describe("The room kind the ticket admits to."),
17
+ recordId: PresenceRecordId.describe("The record the room is about."),
18
+ socketPath: z.string().min(1).describe("The path to open the socket at, on this Worker — this room's, already filled in."),
19
+ protocol: z.literal(PRESENCE_ROOM_VERSIONS.current).describe("The subprotocol to offer beside the ticket. The Worker answers with it.")
20
+ }).describe("A single-use, 60-second ticket into one presence room, and where to open the socket with it.");
21
+ /**
22
+ * One room kind, as `GET /control-plane/manifest` reports it under the capability that declares it. Built
23
+ * from the capability's resolved `basePath`, so a client never assumes where a socket is.
24
+ */
25
+ const PresenceRoomDescriptor = z.object({
26
+ kind: PresenceRoomKindName.describe("The room kind — `support.thread`."),
27
+ scope: ControlPlaneScope.describe("The scope that reads the record, and so joins its room. A management client mints its ticket with exactly this scope."),
28
+ activities: z.array(PresenceActivity).min(1).describe("What a participant may announce. The first is what everyone starts as."),
29
+ maxParticipants: z.number().int().positive().describe("How many participants the room admits at once."),
30
+ sessionTicketPath: z.string().nullable().describe("Where a signed-in user of the app mints a ticket for this kind, or null when the kind admits management clients only."),
31
+ socketPath: z.string().min(1).describe("The socket route, with `:kind` and `:recordId` to fill in — the path as the Worker mounted it.")
32
+ }).describe("One presence room kind this capability serves, and how to join one of its rooms.");
33
+ //#endregion
34
+ export { PresenceRoomDescriptor, PresenceTicket };
@@ -0,0 +1,24 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { Context, Hono } from "hono";
4
+ import type { PithyHonoEnv } from "../../capability/capability";
5
+ import { type PresenceDeclaration, type PresenceRoomKind } from "../declaration";
6
+ import { type PresenceTicketRequest } from "../ticket";
7
+ import { type PresenceRoomDescriptor, PresenceTicket } from "./responses";
8
+ /**
9
+ * A capability's presence routes, mounted by `createBackend` under the `basePath` it declared:
10
+ *
11
+ * POST {base}/presence/tickets → a ticket for a signed-in user (bearer | session)
12
+ * GET {base}/presence/rooms/:kind/:recordId → the room's socket (a ticket)
13
+ *
14
+ * The control-plane door is the seam's own, `POST /control-plane/presence/tickets`, because one management
15
+ * client reaches every capability's rooms through it; both doors answer the same {@link PresenceTicket}.
16
+ */
17
+ /** Find a declared room kind, or the 404 an undeclared one is. */
18
+ export declare function roomKind(presence: PresenceDeclaration, kind: string): PresenceRoomKind;
19
+ /** Mint the ticket a door answers with. */
20
+ export declare function issuePresenceTicket(c: Context<PithyHonoEnv>, presence: PresenceDeclaration, request: PresenceTicketRequest): Promise<PresenceTicket>;
21
+ /** The room kinds a declaration serves, as the manifest reports them. */
22
+ export declare function describePresenceRooms(presence: PresenceDeclaration): PresenceRoomDescriptor[];
23
+ export declare function registerPresenceRoutes(app: Hono<PithyHonoEnv>, presence: PresenceDeclaration): void;
24
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/presence/http/routes.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAOhE,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EAOtB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAsB,KAAK,qBAAqB,EAAyC,MAAM,WAAW,CAAC;AAElH,OAAO,EAAE,KAAK,sBAAsB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG1E;;;;;;;;GAQG;AAEH,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAItF;AAED,2CAA2C;AAC3C,wBAAsB,mBAAmB,CACvC,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,EACxB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,cAAc,CAAC,CAWzB;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,sBAAsB,EAAE,CAS7F;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CA2EnG"}
@@ -0,0 +1,127 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { ForbiddenError, InternalError, NotFoundError, PresenceTicketInvalidError } from "../../error/pithyError.js";
4
+ import { remedy } from "../../cli/command.js";
5
+ import { PRESENCE_ROOMS_BINDING, participantCap, presenceRoomPath, presenceSocketPath, presenceTicketPath, resolveTicketKeys, sessionParticipantName } from "../declaration.js";
6
+ import { PresenceRoomParams, PresenceSessionTicketRequest } from "./schemas.js";
7
+ import { describeRoute } from "../../http/routeSpec.js";
8
+ import { requireSameOrigin } from "../../http/sameOrigin.js";
9
+ import { SUBPROTOCOL_HEADER } from "../../http/subprotocol.js";
10
+ import { validate } from "../../http/validation.js";
11
+ import { PRESENCE_ADMISSION_HEADER } from "../room.js";
12
+ import { mintPresenceTicket, ticketFromOffer, verifyPresenceTicket } from "../ticket.js";
13
+ import { requireAuth } from "./guard.js";
14
+ import { PresenceTicket } from "./responses.js";
15
+ //#region src/presence/http/routes.ts
16
+ /**
17
+ * A capability's presence routes, mounted by `createBackend` under the `basePath` it declared:
18
+ *
19
+ * POST {base}/presence/tickets → a ticket for a signed-in user (bearer | session)
20
+ * GET {base}/presence/rooms/:kind/:recordId → the room's socket (a ticket)
21
+ *
22
+ * The control-plane door is the seam's own, `POST /control-plane/presence/tickets`, because one management
23
+ * client reaches every capability's rooms through it; both doors answer the same {@link PresenceTicket}.
24
+ */
25
+ /** Find a declared room kind, or the 404 an undeclared one is. */
26
+ function roomKind(presence, kind) {
27
+ const room = presence.rooms.find((candidate) => candidate.kind === kind);
28
+ if (!room) throw new NotFoundError({
29
+ message: "That room kind does not exist.",
30
+ params: { kind }
31
+ });
32
+ return room;
33
+ }
34
+ /** Mint the ticket a door answers with. */
35
+ async function issuePresenceTicket(c, presence, request) {
36
+ const keys = await resolveTicketKeys(presence.ticketKey, c.env);
37
+ const { ticket, claims } = await mintPresenceTicket(request, keys);
38
+ return {
39
+ ticket,
40
+ expiresAt: (/* @__PURE__ */ new Date(claims.exp * 1e3)).toISOString(),
41
+ kind: claims.kind,
42
+ recordId: claims.recordId,
43
+ socketPath: presenceRoomPath(presence.basePath, claims.kind, claims.recordId),
44
+ protocol: "pithy.presence.v1"
45
+ };
46
+ }
47
+ /** The room kinds a declaration serves, as the manifest reports them. */
48
+ function describePresenceRooms(presence) {
49
+ return presence.rooms.map((room) => ({
50
+ kind: room.kind,
51
+ scope: room.scope,
52
+ activities: [...room.activities],
53
+ maxParticipants: participantCap(room),
54
+ sessionTicketPath: room.session ? presenceTicketPath(presence.basePath) : null,
55
+ socketPath: presenceSocketPath(presence.basePath)
56
+ }));
57
+ }
58
+ function registerPresenceRoutes(app, presence) {
59
+ app.post(presenceTicketPath(presence.basePath), describeRoute({
60
+ summary: "Mint a presence ticket for the signed-in user.",
61
+ description: "Admits the caller to one room of a kind this capability declares, when the capability's own read permission admits them to the record. Single use, for 60 seconds. Open the socket at `socketPath`, offering `protocol` and `pithy.ticket.<ticket>` in `Sec-WebSocket-Protocol`.",
62
+ verification: ["bearer", "session"],
63
+ responses: { 201: PresenceTicket }
64
+ }), requireAuth(), requireSameOrigin(), validate("json", PresenceSessionTicketRequest), async (c) => {
65
+ const body = c.req.valid("json");
66
+ const room = roomKind(presence, body.kind);
67
+ const auth = c.var.auth;
68
+ if (!auth) throw new InternalError({ detail: "presence session ticket reached without auth" });
69
+ const grant = room.session ? await room.session(c, body.recordId) : null;
70
+ if (!grant) throw new ForbiddenError({
71
+ message: "You cannot join that room.",
72
+ detail: room.session ? `session gate refused ${body.kind}/${body.recordId}` : `${body.kind} admits management clients only`
73
+ });
74
+ const ticket = await issuePresenceTicket(c, presence, {
75
+ kind: room.kind,
76
+ recordId: body.recordId,
77
+ ref: auth.userId,
78
+ name: sessionParticipantName(grant.name, auth.userId),
79
+ origin: "session"
80
+ });
81
+ return c.json(ticket, 201);
82
+ });
83
+ app.get(presenceSocketPath(presence.basePath), describeRoute({
84
+ summary: "Open a presence room's socket.",
85
+ description: "Send `Upgrade: websocket` and offer `pithy.presence.v1` and `pithy.ticket.<ticket>` in `Sec-WebSocket-Protocol`; the ticket is the credential, minted by either ticket route for this room. The socket receives who is in the room, then joins, leaves and activity changes as they happen. The frames are in the AsyncAPI contract.",
86
+ verification: "public",
87
+ responses: { 101: { description: "Switches to a WebSocket. The room pushes presence frames as JSON text." } }
88
+ }), validate("param", PresenceRoomParams), async (c) => {
89
+ const { kind, recordId } = c.req.valid("param");
90
+ const room = roomKind(presence, kind);
91
+ const ticket = ticketFromOffer(c.req.header(SUBPROTOCOL_HEADER));
92
+ if (ticket === void 0) throw new PresenceTicketInvalidError({ detail: "no single ticket in the offer" });
93
+ const keys = await resolveTicketKeys(presence.ticketKey, c.env);
94
+ const claims = await verifyPresenceTicket(ticket, {
95
+ kind,
96
+ recordId
97
+ }, keys);
98
+ const admission = {
99
+ kind,
100
+ recordId,
101
+ ref: claims.ref,
102
+ name: claims.name,
103
+ origin: claims.origin,
104
+ ticket: {
105
+ id: claims.id,
106
+ exp: claims.exp
107
+ },
108
+ activities: [...room.activities],
109
+ maxParticipants: participantCap(room)
110
+ };
111
+ const request = new Request(c.req.url, c.req.raw);
112
+ request.headers.set(PRESENCE_ADMISSION_HEADER, JSON.stringify(admission));
113
+ return roomStub(c, kind, recordId).fetch(request);
114
+ });
115
+ }
116
+ /** The one room for this record. */
117
+ function roomStub(c, kind, recordId) {
118
+ const ns = c.env[PRESENCE_ROOMS_BINDING];
119
+ if (!ns) throw new InternalError({
120
+ message: "Presence is not available right now.",
121
+ action: `Bind the ${PRESENCE_ROOMS_BINDING} Durable Object. ${remedy("upgrade", {}, { runtime: true }).say((command) => `${command} writes it.`)}`,
122
+ detail: `no ${PRESENCE_ROOMS_BINDING} binding`
123
+ });
124
+ return ns.get(ns.idFromName(`${kind}:${recordId}`));
125
+ }
126
+ //#endregion
127
+ export { describePresenceRooms, issuePresenceTicket, registerPresenceRoutes, roomKind };
@@ -0,0 +1,29 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ /** `POST {base}/presence/tickets` — a signed-in user asks to join a room. The participant is that user. */
5
+ export declare const PresenceSessionTicketRequest: z.ZodObject<{
6
+ kind: z.ZodString;
7
+ recordId: z.ZodString;
8
+ }, z.core.$strip>;
9
+ export type PresenceSessionTicketRequest = z.infer<typeof PresenceSessionTicketRequest>;
10
+ /**
11
+ * `POST /control-plane/presence/tickets` — a management client asks to join a room, naming its participant.
12
+ * The body is signed with the call, which is what makes the participant the client's own claim.
13
+ */
14
+ export declare const ControlPlanePresenceTicketRequest: z.ZodObject<{
15
+ kind: z.ZodString;
16
+ recordId: z.ZodString;
17
+ participant: z.ZodObject<{
18
+ ref: z.ZodString;
19
+ name: z.ZodString;
20
+ }, z.core.$strip>;
21
+ }, z.core.$strip>;
22
+ export type ControlPlanePresenceTicketRequest = z.infer<typeof ControlPlanePresenceTicketRequest>;
23
+ /** The socket route's path parameters. */
24
+ export declare const PresenceRoomParams: z.ZodObject<{
25
+ kind: z.ZodString;
26
+ recordId: z.ZodString;
27
+ }, z.core.$strip>;
28
+ export type PresenceRoomParams = z.infer<typeof PresenceRoomParams>;
29
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/presence/http/schemas.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,2GAA2G;AAC3G,eAAO,MAAM,4BAA4B;;;iBAE0D,CAAC;AACpG,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,iCAAiC;;;;;;;iBAU2C,CAAC;AAC1F,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAElG,0CAA0C;AAC1C,eAAO,MAAM,kBAAkB;;;iBAAwF,CAAC;AACxH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
@@ -0,0 +1,28 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { PresenceRecordId, PresenceRoomKindName } from "../names.js";
4
+ import { PresenceParticipantName, PresenceParticipantRef } from "../frames.js";
5
+ import { z } from "zod";
6
+ //#region src/presence/http/schemas.ts
7
+ /** The room a ticket is for. */
8
+ const room = {
9
+ kind: PresenceRoomKindName.describe("The room kind, as the manifest lists it — `support.thread`."),
10
+ recordId: PresenceRecordId.describe("The record the room is about.")
11
+ };
12
+ /** `POST {base}/presence/tickets` — a signed-in user asks to join a room. The participant is that user. */
13
+ const PresenceSessionTicketRequest = z.object(room).describe("The room a signed-in user asks a presence ticket for. The participant is the caller.");
14
+ /**
15
+ * `POST /control-plane/presence/tickets` — a management client asks to join a room, naming its participant.
16
+ * The body is signed with the call, which is what makes the participant the client's own claim.
17
+ */
18
+ const ControlPlanePresenceTicketRequest = z.object({
19
+ ...room,
20
+ participant: z.object({
21
+ ref: PresenceParticipantRef,
22
+ name: PresenceParticipantName
23
+ }).describe("Who joins: the client's own opaque reference for them, and the name the others see.")
24
+ }).describe("The room a management client asks a presence ticket for, and who joins it.");
25
+ /** The socket route's path parameters. */
26
+ const PresenceRoomParams = z.object(room).describe("The room a presence socket joins: its kind and its record.");
27
+ //#endregion
28
+ export { ControlPlanePresenceTicketRequest, PresenceRoomParams, PresenceSessionTicketRequest };
@@ -0,0 +1,23 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ /**
5
+ * The three names a presence room is made of, as Zod schemas and nothing else.
6
+ *
7
+ * A leaf on purpose: the manifest's room descriptor, the ticket responses and the frames all read these, and
8
+ * a management client holds those in a browser. So this module imports zod and no other thing — never the
9
+ * declaration, which names the Hono context a session gate runs in.
10
+ */
11
+ /** A room kind's name: the declaring capability's name, a dot, and a noun — `support.thread`. */
12
+ export declare const PresenceRoomKindName: z.ZodString;
13
+ export type PresenceRoomKindName = z.infer<typeof PresenceRoomKindName>;
14
+ /** One activity a participant may announce — `viewing`, `replying`. A closed vocabulary per room kind, never free text. */
15
+ export declare const PresenceActivity: z.ZodString;
16
+ export type PresenceActivity = z.infer<typeof PresenceActivity>;
17
+ /**
18
+ * The record a room is about — the id the declaring capability's own routes take. Constrained to what fits a
19
+ * path segment and a Durable Object name without escaping.
20
+ */
21
+ export declare const PresenceRecordId: z.ZodString;
22
+ export type PresenceRecordId = z.infer<typeof PresenceRecordId>;
23
+ //# sourceMappingURL=names.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../src/presence/names.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AAEH,iGAAiG;AACjG,eAAO,MAAM,oBAAoB,aAS9B,CAAC;AACJ,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,2HAA2H;AAC3H,eAAO,MAAM,gBAAgB,aAIsF,CAAC;AACpH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;GAGG;AACH,eAAO,MAAM,gBAAgB,aAKyE,CAAC;AACvG,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ //#region src/presence/names.ts
5
+ /**
6
+ * The three names a presence room is made of, as Zod schemas and nothing else.
7
+ *
8
+ * A leaf on purpose: the manifest's room descriptor, the ticket responses and the frames all read these, and
9
+ * a management client holds those in a browser. So this module imports zod and no other thing — never the
10
+ * declaration, which names the Hono context a session gate runs in.
11
+ */
12
+ /** A room kind's name: the declaring capability's name, a dot, and a noun — `support.thread`. */
13
+ const PresenceRoomKindName = z.string().regex(/^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9_]*)+$/, "A room kind is the capability's name, a dot, and a lowercase noun — `support.thread`.").max(64).describe("A room kind: the declaring capability's name, a dot, and a noun — `support.thread`. The first segment is always the capability that declares it, so two capabilities cannot declare the same kind.");
14
+ /** One activity a participant may announce — `viewing`, `replying`. A closed vocabulary per room kind, never free text. */
15
+ const PresenceActivity = z.string().regex(/^[a-z][a-z0-9_]*$/, "An activity is one lowercase word — `viewing`, `replying`.").max(32).describe("What a participant is doing in the room — one of the values its room kind declares, never free text.");
16
+ /**
17
+ * The record a room is about — the id the declaring capability's own routes take. Constrained to what fits a
18
+ * path segment and a Durable Object name without escaping.
19
+ */
20
+ const PresenceRecordId = z.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/, "A record id is letters, digits, `.`, `_`, `:` and `-`.").describe("The record the room is about — the id the declaring capability's own routes use for it.");
21
+ //#endregion
22
+ export { PresenceActivity, PresenceRecordId, PresenceRoomKindName };