@fuzdev/fuz_app 0.30.0 → 0.32.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 (222) hide show
  1. package/dist/actions/CLAUDE.md +630 -0
  2. package/dist/actions/action_rpc.d.ts +29 -0
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +42 -6
  5. package/dist/actions/action_types.d.ts +2 -2
  6. package/dist/actions/cancel.d.ts +12 -13
  7. package/dist/actions/cancel.d.ts.map +1 -1
  8. package/dist/actions/cancel.js +10 -13
  9. package/dist/actions/heartbeat.d.ts +8 -13
  10. package/dist/actions/heartbeat.d.ts.map +1 -1
  11. package/dist/actions/heartbeat.js +5 -8
  12. package/dist/actions/register_action_ws.d.ts +3 -3
  13. package/dist/actions/register_action_ws.js +2 -2
  14. package/dist/actions/register_ws_endpoint.d.ts +4 -4
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +3 -3
  17. package/dist/actions/rpc_client.d.ts +29 -0
  18. package/dist/actions/rpc_client.d.ts.map +1 -1
  19. package/dist/actions/rpc_client.js +31 -0
  20. package/dist/actions/socket.svelte.d.ts +16 -16
  21. package/dist/actions/socket.svelte.d.ts.map +1 -1
  22. package/dist/actions/socket.svelte.js +15 -15
  23. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  24. package/dist/auth/CLAUDE.md +945 -0
  25. package/dist/auth/account_action_specs.d.ts +216 -0
  26. package/dist/auth/account_action_specs.d.ts.map +1 -0
  27. package/dist/auth/account_action_specs.js +159 -0
  28. package/dist/auth/account_actions.d.ts +51 -0
  29. package/dist/auth/account_actions.d.ts.map +1 -0
  30. package/dist/auth/account_actions.js +119 -0
  31. package/dist/auth/account_queries.d.ts +6 -2
  32. package/dist/auth/account_queries.d.ts.map +1 -1
  33. package/dist/auth/account_queries.js +40 -4
  34. package/dist/auth/account_routes.d.ts +94 -16
  35. package/dist/auth/account_routes.d.ts.map +1 -1
  36. package/dist/auth/account_routes.js +108 -180
  37. package/dist/auth/account_schema.d.ts +85 -30
  38. package/dist/auth/account_schema.d.ts.map +1 -1
  39. package/dist/auth/account_schema.js +40 -8
  40. package/dist/auth/admin_action_specs.d.ts +674 -0
  41. package/dist/auth/admin_action_specs.d.ts.map +1 -0
  42. package/dist/auth/admin_action_specs.js +287 -0
  43. package/dist/auth/admin_actions.d.ts +69 -0
  44. package/dist/auth/admin_actions.d.ts.map +1 -0
  45. package/dist/auth/admin_actions.js +256 -0
  46. package/dist/auth/admin_rpc_actions.d.ts +49 -0
  47. package/dist/auth/admin_rpc_actions.d.ts.map +1 -0
  48. package/dist/auth/admin_rpc_actions.js +32 -0
  49. package/dist/auth/api_token.d.ts +10 -0
  50. package/dist/auth/api_token.d.ts.map +1 -1
  51. package/dist/auth/api_token.js +9 -0
  52. package/dist/auth/api_token_queries.d.ts +3 -3
  53. package/dist/auth/api_token_queries.js +3 -3
  54. package/dist/auth/app_settings_schema.d.ts +4 -3
  55. package/dist/auth/app_settings_schema.d.ts.map +1 -1
  56. package/dist/auth/app_settings_schema.js +2 -1
  57. package/dist/auth/audit_log_routes.d.ts +14 -6
  58. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  59. package/dist/auth/audit_log_routes.js +22 -79
  60. package/dist/auth/audit_log_schema.d.ts +100 -29
  61. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  62. package/dist/auth/audit_log_schema.js +83 -11
  63. package/dist/auth/bootstrap_routes.d.ts +14 -0
  64. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  65. package/dist/auth/bootstrap_routes.js +10 -3
  66. package/dist/auth/cleanup.d.ts +63 -0
  67. package/dist/auth/cleanup.d.ts.map +1 -0
  68. package/dist/auth/cleanup.js +80 -0
  69. package/dist/auth/invite_schema.d.ts +11 -10
  70. package/dist/auth/invite_schema.d.ts.map +1 -1
  71. package/dist/auth/invite_schema.js +4 -3
  72. package/dist/auth/migrations.d.ts +6 -0
  73. package/dist/auth/migrations.d.ts.map +1 -1
  74. package/dist/auth/migrations.js +28 -0
  75. package/dist/auth/permit_offer_action_specs.d.ts +364 -0
  76. package/dist/auth/permit_offer_action_specs.d.ts.map +1 -0
  77. package/dist/auth/permit_offer_action_specs.js +216 -0
  78. package/dist/auth/permit_offer_actions.d.ts +96 -0
  79. package/dist/auth/permit_offer_actions.d.ts.map +1 -0
  80. package/dist/auth/permit_offer_actions.js +428 -0
  81. package/dist/auth/permit_offer_notifications.d.ts +361 -0
  82. package/dist/auth/permit_offer_notifications.d.ts.map +1 -0
  83. package/dist/auth/permit_offer_notifications.js +179 -0
  84. package/dist/auth/permit_offer_queries.d.ts +165 -0
  85. package/dist/auth/permit_offer_queries.d.ts.map +1 -0
  86. package/dist/auth/permit_offer_queries.js +390 -0
  87. package/dist/auth/permit_offer_schema.d.ts +103 -0
  88. package/dist/auth/permit_offer_schema.d.ts.map +1 -0
  89. package/dist/auth/permit_offer_schema.js +142 -0
  90. package/dist/auth/permit_queries.d.ts +77 -14
  91. package/dist/auth/permit_queries.d.ts.map +1 -1
  92. package/dist/auth/permit_queries.js +119 -24
  93. package/dist/auth/session_queries.d.ts +4 -2
  94. package/dist/auth/session_queries.d.ts.map +1 -1
  95. package/dist/auth/session_queries.js +4 -2
  96. package/dist/auth/signup_routes.d.ts +13 -0
  97. package/dist/auth/signup_routes.d.ts.map +1 -1
  98. package/dist/auth/signup_routes.js +14 -7
  99. package/dist/http/CLAUDE.md +584 -0
  100. package/dist/http/pending_effects.d.ts +29 -0
  101. package/dist/http/pending_effects.d.ts.map +1 -0
  102. package/dist/http/pending_effects.js +31 -0
  103. package/dist/http/route_spec.d.ts.map +1 -1
  104. package/dist/http/route_spec.js +4 -3
  105. package/dist/rate_limiter.d.ts +30 -0
  106. package/dist/rate_limiter.d.ts.map +1 -1
  107. package/dist/rate_limiter.js +25 -2
  108. package/dist/realtime/sse_auth_guard.d.ts +2 -0
  109. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  110. package/dist/realtime/sse_auth_guard.js +5 -3
  111. package/dist/server/app_server.d.ts +13 -2
  112. package/dist/server/app_server.d.ts.map +1 -1
  113. package/dist/server/app_server.js +12 -1
  114. package/dist/testing/CLAUDE.md +668 -1
  115. package/dist/testing/admin_integration.d.ts +10 -7
  116. package/dist/testing/admin_integration.d.ts.map +1 -1
  117. package/dist/testing/admin_integration.js +382 -482
  118. package/dist/testing/app_server.d.ts +7 -6
  119. package/dist/testing/app_server.d.ts.map +1 -1
  120. package/dist/testing/attack_surface.d.ts +9 -3
  121. package/dist/testing/attack_surface.d.ts.map +1 -1
  122. package/dist/testing/attack_surface.js +4 -4
  123. package/dist/testing/audit_completeness.d.ts +11 -0
  124. package/dist/testing/audit_completeness.d.ts.map +1 -1
  125. package/dist/testing/audit_completeness.js +169 -134
  126. package/dist/testing/auth_apps.d.ts.map +1 -1
  127. package/dist/testing/auth_apps.js +4 -33
  128. package/dist/testing/db.d.ts +1 -1
  129. package/dist/testing/db.d.ts.map +1 -1
  130. package/dist/testing/db.js +2 -0
  131. package/dist/testing/entities.d.ts +35 -13
  132. package/dist/testing/entities.d.ts.map +1 -1
  133. package/dist/testing/entities.js +17 -0
  134. package/dist/testing/integration.d.ts +10 -0
  135. package/dist/testing/integration.d.ts.map +1 -1
  136. package/dist/testing/integration.js +352 -340
  137. package/dist/testing/integration_helpers.d.ts +16 -5
  138. package/dist/testing/integration_helpers.d.ts.map +1 -1
  139. package/dist/testing/integration_helpers.js +24 -4
  140. package/dist/testing/rate_limiting.d.ts +7 -0
  141. package/dist/testing/rate_limiting.d.ts.map +1 -1
  142. package/dist/testing/rate_limiting.js +41 -10
  143. package/dist/testing/rpc_helpers.d.ts +153 -1
  144. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  145. package/dist/testing/rpc_helpers.js +184 -8
  146. package/dist/testing/sse_round_trip.d.ts +8 -0
  147. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  148. package/dist/testing/sse_round_trip.js +10 -3
  149. package/dist/testing/standard.d.ts +9 -1
  150. package/dist/testing/standard.d.ts.map +1 -1
  151. package/dist/testing/standard.js +6 -2
  152. package/dist/testing/stubs.d.ts +10 -2
  153. package/dist/testing/stubs.d.ts.map +1 -1
  154. package/dist/testing/stubs.js +17 -2
  155. package/dist/testing/surface_invariants.d.ts +7 -3
  156. package/dist/testing/surface_invariants.d.ts.map +1 -1
  157. package/dist/testing/surface_invariants.js +5 -4
  158. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  159. package/dist/testing/ws_round_trip.js +9 -38
  160. package/dist/ui/AccountSessions.svelte +8 -4
  161. package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
  162. package/dist/ui/AdminAccounts.svelte +61 -33
  163. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  164. package/dist/ui/AdminAuditLog.svelte +3 -2
  165. package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -1
  166. package/dist/ui/AdminInvites.svelte +3 -2
  167. package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
  168. package/dist/ui/AdminOverview.svelte +14 -9
  169. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  170. package/dist/ui/AdminPermitHistory.svelte +3 -2
  171. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
  172. package/dist/ui/AdminSessions.svelte +29 -25
  173. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
  174. package/dist/ui/CLAUDE.md +363 -0
  175. package/dist/ui/OpenSignupToggle.svelte +6 -3
  176. package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
  177. package/dist/ui/PermitOfferForm.svelte +141 -0
  178. package/dist/ui/PermitOfferForm.svelte.d.ts +14 -0
  179. package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -0
  180. package/dist/ui/PermitOfferHistory.svelte +109 -0
  181. package/dist/ui/PermitOfferHistory.svelte.d.ts +11 -0
  182. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -0
  183. package/dist/ui/PermitOfferInbox.svelte +121 -0
  184. package/dist/ui/PermitOfferInbox.svelte.d.ts +12 -0
  185. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -0
  186. package/dist/ui/account_sessions_state.svelte.d.ts +53 -3
  187. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  188. package/dist/ui/account_sessions_state.svelte.js +39 -16
  189. package/dist/ui/admin_accounts_state.svelte.d.ts +118 -2
  190. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  191. package/dist/ui/admin_accounts_state.svelte.js +99 -23
  192. package/dist/ui/admin_invites_state.svelte.d.ts +47 -1
  193. package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
  194. package/dist/ui/admin_invites_state.svelte.js +38 -26
  195. package/dist/ui/admin_rpc_adapters.d.ts +94 -0
  196. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -0
  197. package/dist/ui/admin_rpc_adapters.js +100 -0
  198. package/dist/ui/admin_sessions_state.svelte.d.ts +26 -0
  199. package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
  200. package/dist/ui/admin_sessions_state.svelte.js +35 -21
  201. package/dist/ui/app_settings_state.svelte.d.ts +39 -0
  202. package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
  203. package/dist/ui/app_settings_state.svelte.js +34 -18
  204. package/dist/ui/audit_log_state.svelte.d.ts +40 -3
  205. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  206. package/dist/ui/audit_log_state.svelte.js +36 -42
  207. package/dist/ui/auth_state.svelte.d.ts +4 -3
  208. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  209. package/dist/ui/auth_state.svelte.js +4 -1
  210. package/dist/ui/permit_offers_state.svelte.d.ts +125 -0
  211. package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -0
  212. package/dist/ui/permit_offers_state.svelte.js +197 -0
  213. package/package.json +3 -3
  214. package/dist/auth/admin_routes.d.ts +0 -29
  215. package/dist/auth/admin_routes.d.ts.map +0 -1
  216. package/dist/auth/admin_routes.js +0 -226
  217. package/dist/auth/app_settings_routes.d.ts +0 -27
  218. package/dist/auth/app_settings_routes.d.ts.map +0 -1
  219. package/dist/auth/app_settings_routes.js +0 -66
  220. package/dist/auth/invite_routes.d.ts +0 -18
  221. package/dist/auth/invite_routes.d.ts.map +0 -1
  222. package/dist/auth/invite_routes.js +0 -129
@@ -11,6 +11,7 @@
11
11
  *
12
12
  * @module
13
13
  */
14
+ import { z } from 'zod';
14
15
  import type { Logger } from '@fuzdev/fuz_util/log.js';
15
16
  import type { RequestResponseActionSpec } from './action_spec.js';
16
17
  import { type RouteSpec } from '../http/route_spec.js';
@@ -35,6 +36,15 @@ export interface ActionContext {
35
36
  background_db: Db;
36
37
  /** Fire-and-forget side effects — push here for post-response flushing. */
37
38
  pending_effects: Array<Promise<void>>;
39
+ /**
40
+ * Resolved client IP from the trusted-proxy middleware — `'unknown'` if the
41
+ * middleware wasn't in the stack (e.g. WS dispatch) or couldn't resolve.
42
+ * Thread into `audit_log_fire_and_forget` as `ip: ctx.client_ip` for every
43
+ * user-initiated action so RPC audit rows match the REST convention. Pass
44
+ * `null` only for rows written outside a request (e.g. the
45
+ * `permit_offer_expire` cleanup sweep in `auth/cleanup.ts`).
46
+ */
47
+ client_ip: string;
38
48
  /** Logger instance. */
39
49
  log: Logger;
40
50
  /**
@@ -71,6 +81,25 @@ export interface RpcAction {
71
81
  spec: RequestResponseActionSpec;
72
82
  handler: ActionHandler;
73
83
  }
84
+ /**
85
+ * Pair a spec with a handler while preserving per-method input/output types.
86
+ *
87
+ * Constructing `{spec, handler}` literals widens `handler` to
88
+ * `ActionHandler<any, any>`, so spec/handler drift (renamed Zod schema,
89
+ * output field removal, input shape change) slips past the typechecker.
90
+ * `rpc_action(spec, handler)` binds the handler signature to
91
+ * `(input: z.infer<spec.input>, ctx) => z.infer<spec.output>` via the
92
+ * generic spec parameter — drift surfaces at the call site.
93
+ *
94
+ * Fits fuz_app's factory-closure pattern (handlers close over
95
+ * `grantable_roles`, `app_settings` ref, `notification_sender`, etc.).
96
+ * zzz uses a different shape — a codegen-keyed `Record<Method, Handler>`
97
+ * map typed via generated `ActionInputs`/`ActionOutputs` — which works when
98
+ * handlers are pure (no closure state) and specs are codegen-enumerated.
99
+ * fuz_app's admin + permit-offer actions have neither, so per-pair typing
100
+ * at the registration site is the right fit.
101
+ */
102
+ export declare const rpc_action: <TSpec extends RequestResponseActionSpec>(spec: TSpec, handler: ActionHandler<z.infer<TSpec["input"]>, z.infer<TSpec["output"]>>) => RpcAction;
74
103
  /** Options for `create_rpc_endpoint`. */
75
104
  export interface CreateRpcEndpointOptions {
76
105
  /** Mount path for the endpoint (e.g., `/api/rpc`). */
@@ -1 +1 @@
1
- {"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAoB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAgC,KAAK,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAE9F,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,OAAO,EAGN,KAAK,gBAAgB,EAGrB,MAAM,oBAAoB,CAAC;AAO5B;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC7B,+DAA+D;IAC/D,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,iDAAiD;IACjD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,8DAA8D;IAC9D,EAAE,EAAE,EAAE,CAAC;IACP,oFAAoF;IACpF,aAAa,EAAE,EAAE,CAAC;IAClB,2EAA2E;IAC3E,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,MAAM,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,CACxD,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,aAAa,CAAC;CACvB;AAED,yCAAyC;AACzC,MAAM,WAAW,wBAAwB;IACxC,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;CACZ;AAkDD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,wBAAwB,KAAG,KAAK,CAAC,SAAS,CAqPtF,CAAC"}
1
+ {"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAoB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAExE,OAAO,EAAgC,KAAK,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAE9F,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,OAAO,EAGN,KAAK,gBAAgB,EAGrB,MAAM,oBAAoB,CAAC;AAW5B;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC7B,+DAA+D;IAC/D,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,iDAAiD;IACjD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,8DAA8D;IAC9D,EAAE,EAAE,EAAE,CAAC;IACP,oFAAoF;IACpF,aAAa,EAAE,EAAE,CAAC;IAClB,2EAA2E;IAC3E,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,MAAM,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,CACxD,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,aAAa,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,SAAS,yBAAyB,EACjE,MAAM,KAAK,EACX,SAAS,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KACvE,SAGD,CAAC;AAEH,yCAAyC;AACzC,MAAM,WAAW,wBAAwB;IACxC,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;CACZ;AA4DD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,wBAAwB,KAAG,KAAK,CAAC,SAAS,CAwPtF,CAAC"}
@@ -14,11 +14,35 @@
14
14
  import { z } from 'zod';
15
15
  import { DEV } from 'esm-env';
16
16
  import {} from '../http/route_spec.js';
17
+ import { get_client_ip } from '../http/proxy.js';
17
18
  import { get_request_context, has_role } from '../auth/request_context.js';
18
19
  import { CREDENTIAL_TYPE_KEY } from '../hono_context.js';
19
20
  import { is_null_schema } from '../http/schema_helpers.js';
20
21
  import { JSONRPC_VERSION, JsonrpcRequest, } from '../http/jsonrpc.js';
21
22
  import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, JSONRPC_ERROR_CODES, } from '../http/jsonrpc_errors.js';
23
+ import { ERROR_INSUFFICIENT_PERMISSIONS, ERROR_KEEPER_REQUIRES_DAEMON_TOKEN, } from '../http/error_schemas.js';
24
+ /**
25
+ * Pair a spec with a handler while preserving per-method input/output types.
26
+ *
27
+ * Constructing `{spec, handler}` literals widens `handler` to
28
+ * `ActionHandler<any, any>`, so spec/handler drift (renamed Zod schema,
29
+ * output field removal, input shape change) slips past the typechecker.
30
+ * `rpc_action(spec, handler)` binds the handler signature to
31
+ * `(input: z.infer<spec.input>, ctx) => z.infer<spec.output>` via the
32
+ * generic spec parameter — drift surfaces at the call site.
33
+ *
34
+ * Fits fuz_app's factory-closure pattern (handlers close over
35
+ * `grantable_roles`, `app_settings` ref, `notification_sender`, etc.).
36
+ * zzz uses a different shape — a codegen-keyed `Record<Method, Handler>`
37
+ * map typed via generated `ActionInputs`/`ActionOutputs` — which works when
38
+ * handlers are pure (no closure state) and specs are codegen-enumerated.
39
+ * fuz_app's admin + permit-offer actions have neither, so per-pair typing
40
+ * at the registration site is the right fit.
41
+ */
42
+ export const rpc_action = (spec, handler) => ({
43
+ spec,
44
+ handler: handler,
45
+ });
22
46
  /**
23
47
  * Format a JSON-RPC error response.
24
48
  *
@@ -49,15 +73,25 @@ const check_action_auth = (auth, request_context, credential_type) => {
49
73
  if (auth === 'keeper') {
50
74
  // keeper requires daemon_token credential type AND the keeper role.
51
75
  // API tokens and session cookies cannot access keeper actions even
52
- // if the account has the keeper permit.
76
+ // if the account has the keeper permit. Attach the credential type
77
+ // under `data` so clients can distinguish "wrong credential shape"
78
+ // from "missing keeper role" — mirrors REST 403 semantics.
53
79
  if (credential_type !== 'daemon_token' || !has_role(request_context, 'keeper')) {
54
- return jsonrpc_error_messages.forbidden();
80
+ return jsonrpc_error_messages.forbidden('forbidden', {
81
+ reason: ERROR_KEEPER_REQUIRES_DAEMON_TOKEN,
82
+ credential_type,
83
+ });
55
84
  }
56
85
  return null;
57
86
  }
58
- // role check
87
+ // role check — attach `required_role` under `data.required_role` so
88
+ // clients can render targeted copy (matches the former REST `PermissionError`
89
+ // shape that exposed `required_role` as a top-level field).
59
90
  if (!has_role(request_context, auth.role)) {
60
- return jsonrpc_error_messages.forbidden(`requires role: ${auth.role}`);
91
+ return jsonrpc_error_messages.forbidden(`requires role: ${auth.role}`, {
92
+ reason: ERROR_INSUFFICIENT_PERMISSIONS,
93
+ required_role: auth.role,
94
+ });
61
95
  }
62
96
  return null;
63
97
  };
@@ -145,6 +179,7 @@ export const create_rpc_endpoint = (options) => {
145
179
  }
146
180
  };
147
181
  const signal = c.req.raw.signal;
182
+ const client_ip = get_client_ip(c);
148
183
  const execute = async (db) => {
149
184
  const action_context = {
150
185
  auth: request_context,
@@ -152,16 +187,17 @@ export const create_rpc_endpoint = (options) => {
152
187
  db,
153
188
  background_db: route.background_db,
154
189
  pending_effects: route.pending_effects,
190
+ client_ip,
155
191
  log,
156
192
  notify,
157
193
  signal,
158
194
  };
159
195
  const output = await action.handler(parse_result.data, action_context);
160
- // DEV-only output validation
196
+ // DEV-only output validation — logs an error on mismatch, does not throw.
161
197
  if (DEV) {
162
198
  const output_result = action.spec.output.safeParse(output);
163
199
  if (!output_result.success) {
164
- log.warn(`RPC output schema mismatch: ${method_name}`, output_result.error.issues);
200
+ log.error(`RPC output schema mismatch: ${method_name}`, output_result.error.issues);
165
201
  }
166
202
  }
167
203
  return c.json({ jsonrpc: JSONRPC_VERSION, id, result: output });
@@ -54,8 +54,8 @@ export interface BaseHandlerContext {
54
54
  export type WsActionHandler<TCtx extends BaseHandlerContext = BaseHandlerContext> = (input: unknown, ctx: TCtx) => unknown;
55
55
  /**
56
56
  * A spec paired with its optional handler — the composable unit passed to
57
- * {@link register_action_ws} and {@link create_rpc_client}. The server uses
58
- * both fields; the client reads only {@link spec} (the {@link handler} is
57
+ * `register_action_ws` and `create_rpc_client`. The server uses
58
+ * both fields; the client reads only `spec` (the `handler` is
59
59
  * ignored, harmless). Shared fuz_app primitives (e.g. `heartbeat_action`)
60
60
  * export a complete tuple so consumers spread them into both sides'
61
61
  * `actions` array without inventing per-repo ping plumbing.
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Semantics: the client sends `{jsonrpc, method: 'cancel', params:
6
6
  * {request_id}}` to abort an in-flight request on the same socket.
7
- * {@link register_action_ws} intercepts this notification and aborts the
7
+ * `register_action_ws` intercepts this notification and aborts the
8
8
  * matching pending request's `ctx.signal`. Unknown ids are no-ops by design —
9
9
  * races between response arrival and cancel delivery are safe without extra
10
10
  * coordination.
@@ -12,8 +12,8 @@
12
12
  * The handler field is an empty stub: cancel semantics are dispatcher-owned
13
13
  * (the dispatcher has the `{request_id → AbortController}` map, not the
14
14
  * handler). The handler exists for symmetry with other composable primitives
15
- * like {@link heartbeat_action}; the dispatcher never calls it. Consumers
16
- * spread {@link cancel_action} into their server's `actions` array so
15
+ * like `heartbeat_action`; the dispatcher never calls it. Consumers
16
+ * spread `cancel_action` into their server's `actions` array so
17
17
  * `spec_by_method` knows about it (enabling input validation on incoming
18
18
  * cancels) and so `create_rpc_client` codegen produces `app.api.cancel()`
19
19
  * when desired — though `FrontendWebsocketClient.request({signal})` sends
@@ -29,11 +29,9 @@
29
29
  */
30
30
  import { z } from 'zod';
31
31
  import type { Action } from './action_types.js';
32
- /** Method name on the wire — shared across every fuz_app consumer. */
33
- export declare const CANCEL_METHOD = "cancel";
34
32
  /**
35
- * Params for a {@link CANCEL_METHOD} notification. `request_id` is the id of
36
- * the pending request to abort. Must match the id of a request sent on the
33
+ * Params for the `cancel` notification. `request_id` is the id of the
34
+ * pending request to abort. Must match the id of a request sent on the
37
35
  * same socket; cancels from other sockets (or for unknown ids) are ignored.
38
36
  */
39
37
  export declare const CancelNotificationParams: z.ZodObject<{
@@ -50,19 +48,20 @@ export type CancelNotificationParams = z.infer<typeof CancelNotificationParams>;
50
48
  */
51
49
  export declare const cancel_action_spec: {
52
50
  method: string;
53
- initiator: "both" | "frontend" | "backend";
54
- input: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
55
- description: string;
56
51
  kind: "remote_notification";
52
+ initiator: "frontend";
57
53
  auth: null;
58
54
  side_effects: true;
55
+ input: z.ZodObject<{
56
+ request_id: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
57
+ }, z.core.$strict>;
59
58
  output: z.ZodVoid;
60
59
  async: true;
61
- streams?: string | undefined;
60
+ description: string;
62
61
  };
63
62
  /**
64
- * Placeholder handler — cancel semantics are owned by {@link register_action_ws},
65
- * not invoked per-handler. Exported for symmetry with the {@link Action}
63
+ * Placeholder handler — cancel semantics are owned by `register_action_ws`,
64
+ * not invoked per-handler. Exported for symmetry with the `Action`
66
65
  * tuple shape; the dispatcher short-circuits cancel notifications before any
67
66
  * handler lookup happens.
68
67
  */
@@ -1 +1 @@
1
- {"version":3,"file":"cancel.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/cancel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAE9C,sEAAsE;AACtE,eAAO,MAAM,aAAa,WAAW,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;kBAEnC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;CAW7B,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAAO,IAAU,CAAC;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,MAG3B,CAAC"}
1
+ {"version":3,"file":"cancel.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/cancel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;kBAEnC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;CAWS,CAAC;AAEzC;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAAO,IAAU,CAAC;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,MAG3B,CAAC"}
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Semantics: the client sends `{jsonrpc, method: 'cancel', params:
6
6
  * {request_id}}` to abort an in-flight request on the same socket.
7
- * {@link register_action_ws} intercepts this notification and aborts the
7
+ * `register_action_ws` intercepts this notification and aborts the
8
8
  * matching pending request's `ctx.signal`. Unknown ids are no-ops by design —
9
9
  * races between response arrival and cancel delivery are safe without extra
10
10
  * coordination.
@@ -12,8 +12,8 @@
12
12
  * The handler field is an empty stub: cancel semantics are dispatcher-owned
13
13
  * (the dispatcher has the `{request_id → AbortController}` map, not the
14
14
  * handler). The handler exists for symmetry with other composable primitives
15
- * like {@link heartbeat_action}; the dispatcher never calls it. Consumers
16
- * spread {@link cancel_action} into their server's `actions` array so
15
+ * like `heartbeat_action`; the dispatcher never calls it. Consumers
16
+ * spread `cancel_action` into their server's `actions` array so
17
17
  * `spec_by_method` knows about it (enabling input validation on incoming
18
18
  * cancels) and so `create_rpc_client` codegen produces `app.api.cancel()`
19
19
  * when desired — though `FrontendWebsocketClient.request({signal})` sends
@@ -29,12 +29,9 @@
29
29
  */
30
30
  import { z } from 'zod';
31
31
  import { JsonrpcRequestId } from '../http/jsonrpc.js';
32
- import { RemoteNotificationActionSpec } from './action_spec.js';
33
- /** Method name on the wire — shared across every fuz_app consumer. */
34
- export const CANCEL_METHOD = 'cancel';
35
32
  /**
36
- * Params for a {@link CANCEL_METHOD} notification. `request_id` is the id of
37
- * the pending request to abort. Must match the id of a request sent on the
33
+ * Params for the `cancel` notification. `request_id` is the id of the
34
+ * pending request to abort. Must match the id of a request sent on the
38
35
  * same socket; cancels from other sockets (or for unknown ids) are ignored.
39
36
  */
40
37
  export const CancelNotificationParams = z.strictObject({
@@ -48,8 +45,8 @@ export const CancelNotificationParams = z.strictObject({
48
45
  * ownership naturally: a different socket's cancel for the same id misses
49
46
  * in its own map.
50
47
  */
51
- export const cancel_action_spec = RemoteNotificationActionSpec.parse({
52
- method: CANCEL_METHOD,
48
+ export const cancel_action_spec = {
49
+ method: 'cancel',
53
50
  kind: 'remote_notification',
54
51
  initiator: 'frontend',
55
52
  auth: null,
@@ -58,10 +55,10 @@ export const cancel_action_spec = RemoteNotificationActionSpec.parse({
58
55
  output: z.void(),
59
56
  async: true,
60
57
  description: 'Client-initiated cancellation of an in-flight request by id. Dispatcher-handled: aborts the ctx.signal of the matching pending request on the same socket. Unknown or completed ids no-op.',
61
- });
58
+ };
62
59
  /**
63
- * Placeholder handler — cancel semantics are owned by {@link register_action_ws},
64
- * not invoked per-handler. Exported for symmetry with the {@link Action}
60
+ * Placeholder handler — cancel semantics are owned by `register_action_ws`,
61
+ * not invoked per-handler. Exported for symmetry with the `Action`
65
62
  * tuple shape; the dispatcher short-circuits cancel notifications before any
66
63
  * handler lookup happens.
67
64
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shared heartbeat action — the first composable fuz_app primitive carrying
3
3
  * both a spec and a handler in one tuple. Consumers spread
4
- * {@link heartbeat_action} into both the server's and the client's `actions`
4
+ * `heartbeat_action` into both the server's and the client's `actions`
5
5
  * array so disconnect detection works identically across every repo without
6
6
  * per-consumer ping plumbing.
7
7
  *
@@ -12,15 +12,13 @@
12
12
  * alive without any handler-level state.
13
13
  *
14
14
  * Nullary input/output today. `{client_ts, server_ts}` fields can be added
15
- * later if clock-skew telemetry ever matters — the {@link Action} container
15
+ * later if clock-skew telemetry ever matters — the `Action` container
16
16
  * is open for additions without churning consumer call sites.
17
17
  *
18
18
  * @module
19
19
  */
20
20
  import { z } from 'zod';
21
21
  import type { Action } from './action_types.js';
22
- /** Method name on the wire — shared across every fuz_app consumer. */
23
- export declare const HEARTBEAT_METHOD = "heartbeat";
24
22
  /**
25
23
  * `ActionSpec` for the shared heartbeat. `authenticated` auth — upgrade-time
26
24
  * auth has already admitted the socket; heartbeats don't need role gating.
@@ -28,17 +26,14 @@ export declare const HEARTBEAT_METHOD = "heartbeat";
28
26
  */
29
27
  export declare const heartbeat_action_spec: {
30
28
  method: string;
31
- initiator: "both" | "frontend" | "backend";
32
- side_effects: boolean;
33
- input: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
34
- output: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
35
- description: string;
36
29
  kind: "request_response";
37
- auth: "authenticated" | "keeper" | "public" | {
38
- role: string;
39
- };
30
+ initiator: "frontend";
31
+ auth: "authenticated";
32
+ side_effects: false;
33
+ input: z.ZodObject<{}, z.core.$strict>;
34
+ output: z.ZodObject<{}, z.core.$strict>;
40
35
  async: true;
41
- streams?: string | undefined;
36
+ description: string;
42
37
  };
43
38
  /** Handler — nullary echo. Stateless, suitable for high-frequency pings. */
44
39
  export declare const heartbeat_handler: () => Record<string, never>;
@@ -1 +1 @@
1
- {"version":3,"file":"heartbeat.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/heartbeat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAE9C,sEAAsE;AACtE,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;CAUhC,CAAC;AAEH,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,QAAO,MAAM,CAAC,MAAM,EAAE,KAAK,CAAS,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAG9B,CAAC"}
1
+ {"version":3,"file":"heartbeat.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/heartbeat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;CAUG,CAAC;AAEtC,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,QAAO,MAAM,CAAC,MAAM,EAAE,KAAK,CAAS,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAG9B,CAAC"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shared heartbeat action — the first composable fuz_app primitive carrying
3
3
  * both a spec and a handler in one tuple. Consumers spread
4
- * {@link heartbeat_action} into both the server's and the client's `actions`
4
+ * `heartbeat_action` into both the server's and the client's `actions`
5
5
  * array so disconnect detection works identically across every repo without
6
6
  * per-consumer ping plumbing.
7
7
  *
@@ -12,22 +12,19 @@
12
12
  * alive without any handler-level state.
13
13
  *
14
14
  * Nullary input/output today. `{client_ts, server_ts}` fields can be added
15
- * later if clock-skew telemetry ever matters — the {@link Action} container
15
+ * later if clock-skew telemetry ever matters — the `Action` container
16
16
  * is open for additions without churning consumer call sites.
17
17
  *
18
18
  * @module
19
19
  */
20
20
  import { z } from 'zod';
21
- import { RequestResponseActionSpec } from './action_spec.js';
22
- /** Method name on the wire — shared across every fuz_app consumer. */
23
- export const HEARTBEAT_METHOD = 'heartbeat';
24
21
  /**
25
22
  * `ActionSpec` for the shared heartbeat. `authenticated` auth — upgrade-time
26
23
  * auth has already admitted the socket; heartbeats don't need role gating.
27
24
  * `side_effects: false` keeps it orthogonal to state changes.
28
25
  */
29
- export const heartbeat_action_spec = RequestResponseActionSpec.parse({
30
- method: HEARTBEAT_METHOD,
26
+ export const heartbeat_action_spec = {
27
+ method: 'heartbeat',
31
28
  kind: 'request_response',
32
29
  initiator: 'frontend',
33
30
  auth: 'authenticated',
@@ -36,7 +33,7 @@ export const heartbeat_action_spec = RequestResponseActionSpec.parse({
36
33
  output: z.strictObject({}),
37
34
  async: true,
38
35
  description: 'Shared activity ping — keeps the socket alive and exercises the dispatch path.',
39
- });
36
+ };
40
37
  /** Handler — nullary echo. Stateless, suitable for high-frequency pings. */
41
38
  export const heartbeat_handler = () => ({});
42
39
  /**
@@ -79,8 +79,8 @@ export interface SocketCloseContext {
79
79
  export interface ServerHeartbeatOptions {
80
80
  /**
81
81
  * Receive-silence (ms) past which the server closes the socket with
82
- * {@link WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT}. Any incoming message resets
83
- * the counter — chatty clients never trip it. First {@link timeout}
82
+ * `WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT`. Any incoming message resets
83
+ * the counter — chatty clients never trip it. First `timeout`
84
84
  * window after socket open is exempt (cold-start grace).
85
85
  */
86
86
  timeout?: number;
@@ -97,7 +97,7 @@ export interface RegisterActionWsOptions<TCtx extends BaseHandlerContext> {
97
97
  * The actions registered on this endpoint — each carries a spec (drives
98
98
  * method lookup, per-action auth, input/output validation) and an
99
99
  * optional handler (omit for client-only specs like inbound
100
- * notifications). Include the shared {@link heartbeat_action} here to
100
+ * notifications). Include the shared `heartbeat_action` here to
101
101
  * complete the disconnect-detection pairing with the frontend client.
102
102
  */
103
103
  actions: ReadonlyArray<Action<TCtx>>;
@@ -39,7 +39,7 @@ import { jsonrpc_error_messages, ThrownJsonrpcError } from '../http/jsonrpc_erro
39
39
  import { create_jsonrpc_error_response, create_jsonrpc_error_response_from_thrown, create_jsonrpc_notification, to_jsonrpc_message_id, to_jsonrpc_params, is_jsonrpc_request, } from '../http/jsonrpc_helpers.js';
40
40
  import { CREDENTIAL_TYPE_KEY, AUTH_API_TOKEN_ID_KEY } from '../hono_context.js';
41
41
  import {} from './action_types.js';
42
- import { CANCEL_METHOD, CancelNotificationParams } from './cancel.js';
42
+ import { cancel_action_spec, CancelNotificationParams } from './cancel.js';
43
43
  import { WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT } from './transports.js';
44
44
  import { BackendWebsocketTransport } from './transports_ws_backend.js';
45
45
  /** Default inactivity window before the server closes a silent socket. */
@@ -206,7 +206,7 @@ export const register_action_ws = (options) => {
206
206
  // are not a feature yet).
207
207
  if (!is_jsonrpc_request(json)) {
208
208
  if (typeof json === 'object' && json !== null && 'method' in json && !('id' in json)) {
209
- if (json.method === CANCEL_METHOD) {
209
+ if (json.method === cancel_action_spec.method) {
210
210
  const parsed = CancelNotificationParams.safeParse(json.params);
211
211
  if (!parsed.success) {
212
212
  log.debug('cancel: invalid params, ignoring', parsed.error.issues);
@@ -10,7 +10,7 @@
10
10
  * 3. Optional `require_role(required_role)` — for endpoints gated to a
11
11
  * specific role.
12
12
  *
13
- * Then delegates to {@link register_action_ws} for per-message JSON-RPC
13
+ * Then delegates to `register_action_ws` for per-message JSON-RPC
14
14
  * dispatch.
15
15
  *
16
16
  * @module
@@ -18,7 +18,7 @@
18
18
  import type { RoleName } from '../auth/role_schema.js';
19
19
  import { type RegisterActionWsOptions, type RegisterActionWsResult } from './register_action_ws.js';
20
20
  import type { BaseHandlerContext } from './action_types.js';
21
- /** Options for {@link register_ws_endpoint}. */
21
+ /** Options for `register_ws_endpoint`. */
22
22
  export interface RegisterWsEndpointOptions<TCtx extends BaseHandlerContext> extends RegisterActionWsOptions<TCtx> {
23
23
  /**
24
24
  * Origin allowlist regexes — typically parsed from the `ALLOWED_ORIGINS`
@@ -38,8 +38,8 @@ export interface RegisterWsEndpointOptions<TCtx extends BaseHandlerContext> exte
38
38
  * Mount a WebSocket endpoint with the standard upgrade stack (origin check
39
39
  * + auth + optional role) and JSON-RPC dispatch.
40
40
  *
41
- * Returns the {@link BackendWebsocketTransport} (supplied or freshly
42
- * created), same as {@link register_action_ws} — retain it to wire
41
+ * Returns the `BackendWebsocketTransport` (supplied or freshly
42
+ * created), same as `register_action_ws` — retain it to wire
43
43
  * `create_ws_auth_guard` on `on_audit_event` or to broadcast.
44
44
  */
45
45
  export declare const register_ws_endpoint: <TCtx extends BaseHandlerContext>(options: RegisterWsEndpointOptions<TCtx>) => RegisterActionWsResult;
@@ -1 +1 @@
1
- {"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAE1D,gDAAgD;AAChD,MAAM,WAAW,yBAAyB,CACzC,IAAI,SAAS,kBAAkB,CAC9B,SAAQ,uBAAuB,CAAC,IAAI,CAAC;IACtC;;;;OAIG;IACH,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,QAAQ,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,IAAI,SAAS,kBAAkB,EACnE,SAAS,yBAAyB,CAAC,IAAI,CAAC,KACtC,sBAUF,CAAC"}
1
+ {"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAE1D,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB,CACzC,IAAI,SAAS,kBAAkB,CAC9B,SAAQ,uBAAuB,CAAC,IAAI,CAAC;IACtC;;;;OAIG;IACH,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,QAAQ,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,IAAI,SAAS,kBAAkB,EACnE,SAAS,yBAAyB,CAAC,IAAI,CAAC,KACtC,sBAUF,CAAC"}
@@ -10,7 +10,7 @@
10
10
  * 3. Optional `require_role(required_role)` — for endpoints gated to a
11
11
  * specific role.
12
12
  *
13
- * Then delegates to {@link register_action_ws} for per-message JSON-RPC
13
+ * Then delegates to `register_action_ws` for per-message JSON-RPC
14
14
  * dispatch.
15
15
  *
16
16
  * @module
@@ -23,8 +23,8 @@ import { register_action_ws, } from './register_action_ws.js';
23
23
  * Mount a WebSocket endpoint with the standard upgrade stack (origin check
24
24
  * + auth + optional role) and JSON-RPC dispatch.
25
25
  *
26
- * Returns the {@link BackendWebsocketTransport} (supplied or freshly
27
- * created), same as {@link register_action_ws} — retain it to wire
26
+ * Returns the `BackendWebsocketTransport` (supplied or freshly
27
+ * created), same as `register_action_ws` — retain it to wire
28
28
  * `create_ws_auth_guard` on `on_audit_event` or to broadcast.
29
29
  */
30
30
  export const register_ws_endpoint = (options) => {
@@ -65,4 +65,33 @@ export declare const create_rpc_client: (options: CreateRpcClientOptions) => Rec
65
65
  */
66
66
  export interface RpcClientCallOptions extends ActionPeerSendOptions {
67
67
  }
68
+ /**
69
+ * `method, input -> unwrapped output` signature for adapter wiring.
70
+ *
71
+ * The typed `create_rpc_client` Proxy returns `Result<T, JsonrpcErrorObject>`
72
+ * on every call. UI adapters (e.g. `admin_rpc_adapters.ts`) want a
73
+ * throw-on-error shape so form components can match on `error.data.reason`
74
+ * via catch blocks. `create_throwing_rpc_call` bridges the two.
75
+ */
76
+ export type ThrowingRpcCall = <TOutput = unknown>(method: string, input?: unknown) => Promise<TOutput>;
77
+ /**
78
+ * Wrap a typed RPC client so every call returns its unwrapped value or throws.
79
+ *
80
+ * On `{ok: false}`, throws an `Error` with the JSON-RPC error object's
81
+ * `{code, message, data}` spread onto it — so catch blocks that inspect
82
+ * `err.data?.reason` continue to work. On unknown method, throws a clear
83
+ * "rpc method not found" error instead of the cryptic `undefined is not a
84
+ * function` that would otherwise surface.
85
+ *
86
+ * Invariant upheld by `create_rpc_client`: every `{ok: false}` return
87
+ * carries a well-formed `JsonrpcErrorObject` with `code` + `message`.
88
+ * Callers must still use optional chaining on `err.data` because the
89
+ * JSON-RPC `data` field is spec-level optional — a handler that throws
90
+ * `jsonrpc_errors.forbidden()` without a `data` argument produces
91
+ * `err.data === undefined`.
92
+ *
93
+ * @param api - typed RPC client from `create_rpc_client` (or any Proxy-like
94
+ * object mapping method names to `(input) => Promise<Result<T, error>>`)
95
+ */
96
+ export declare const create_throwing_rpc_call: (api: Record<string, ((input?: any) => Promise<any>) | undefined>) => ThrowingRpcCall;
68
97
  //# sourceMappingURL=rpc_client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rpc_client.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/rpc_client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAOpE,OAAO,KAAK,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,kBAAkB,CAAC;AACxE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAGnD;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;AAM/E,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACtC,aAAa,EAAE,CAAC,IAAI,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,oBAAoB,CAAA;KAAC,KAC5E;QACA,sBAAsB,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;KAC5C,GACD,SAAS,CAAC;CACb;AAED,uCAAuC;AACvC,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,sBAAsB,CAAC;IACpC,kEAAkE;IAClE,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;CAC1C;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAC7B,SAAS,sBAAsB,KAC7B,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,CAgB7C,CAAC;AA2DF;;;;;GAKG;AACH,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;CAAG"}
1
+ {"version":3,"file":"rpc_client.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/rpc_client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAOpE,OAAO,KAAK,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,kBAAkB,CAAC;AACxE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAGnD;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;AAM/E,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACtC,aAAa,EAAE,CAAC,IAAI,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,oBAAoB,CAAA;KAAC,KAC5E;QACA,sBAAsB,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;KAC5C,GACD,SAAS,CAAC;CACb;AAED,uCAAuC;AACvC,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,sBAAsB,CAAC;IACpC,kEAAkE;IAClE,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;CAC1C;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAC7B,SAAS,sBAAsB,KAC7B,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,CAgB7C,CAAC;AA2DF;;;;;GAKG;AACH,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;CAAG;AAgItE;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,GAAG,OAAO,EAC/C,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,OAAO,KACX,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,wBAAwB,GACpC,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,KAC9D,eAUF,CAAC"}
@@ -168,3 +168,34 @@ const create_remote_notification_method = (peer, environment, spec, actions, tra
168
168
  return extract_action_result(event);
169
169
  };
170
170
  };
171
+ /**
172
+ * Wrap a typed RPC client so every call returns its unwrapped value or throws.
173
+ *
174
+ * On `{ok: false}`, throws an `Error` with the JSON-RPC error object's
175
+ * `{code, message, data}` spread onto it — so catch blocks that inspect
176
+ * `err.data?.reason` continue to work. On unknown method, throws a clear
177
+ * "rpc method not found" error instead of the cryptic `undefined is not a
178
+ * function` that would otherwise surface.
179
+ *
180
+ * Invariant upheld by `create_rpc_client`: every `{ok: false}` return
181
+ * carries a well-formed `JsonrpcErrorObject` with `code` + `message`.
182
+ * Callers must still use optional chaining on `err.data` because the
183
+ * JSON-RPC `data` field is spec-level optional — a handler that throws
184
+ * `jsonrpc_errors.forbidden()` without a `data` argument produces
185
+ * `err.data === undefined`.
186
+ *
187
+ * @param api - typed RPC client from `create_rpc_client` (or any Proxy-like
188
+ * object mapping method names to `(input) => Promise<Result<T, error>>`)
189
+ */
190
+ export const create_throwing_rpc_call = (api) => {
191
+ return async (method, input) => {
192
+ const fn = api[method];
193
+ if (!fn)
194
+ throw new Error(`rpc method not found: ${method}`);
195
+ const result = await fn(input);
196
+ if (!result.ok) {
197
+ throw Object.assign(new Error(result.error?.message ?? 'rpc error'), result.error);
198
+ }
199
+ return result.value;
200
+ };
201
+ };