@gjermundgaraba/effect-actions 0.19.0 → 0.21.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 (326) hide show
  1. package/README.md +19 -41
  2. package/dist/Action.d.mts +140 -48
  3. package/dist/Action.mjs +88 -44
  4. package/dist/ActionCli.d.mts +70 -35
  5. package/dist/ActionCli.mjs +53 -34
  6. package/dist/ActionHttp.d.mts +70 -38
  7. package/dist/ActionHttp.mjs +76 -25
  8. package/dist/ActionMcp.d.mts +56 -24
  9. package/dist/ActionMcp.mjs +104 -61
  10. package/dist/ActionRpc.d.mts +222 -57
  11. package/dist/ActionRpc.mjs +301 -49
  12. package/dist/ActionToolkit.d.mts +22 -7
  13. package/dist/ActionToolkit.mjs +37 -14
  14. package/dist/Authentication.d.mts +42 -12
  15. package/dist/Authentication.mjs +38 -32
  16. package/dist/Event.d.mts +370 -0
  17. package/dist/Event.mjs +103 -0
  18. package/dist/Testing.d.mts +1 -3
  19. package/dist/Testing.mjs +2 -3
  20. package/dist/Webhook.d.mts +146 -0
  21. package/dist/Webhook.mjs +72 -0
  22. package/dist/authentication/provider.d.mts +35 -12
  23. package/dist/authentication/provider.mjs +2 -3
  24. package/dist/authentication/refusal.mjs +3 -4
  25. package/dist/cli/commands.d.mts +9 -7
  26. package/dist/cli/commands.mjs +85 -35
  27. package/dist/contract/ast.mjs +5 -3
  28. package/dist/contract/authorizer.d.mts +8 -0
  29. package/dist/contract/authorizer.mjs +20 -0
  30. package/dist/contract/call.d.mts +9 -8
  31. package/dist/contract/call.mjs +49 -8
  32. package/dist/contract/callback.d.mts +7 -0
  33. package/dist/contract/callback.mjs +27 -0
  34. package/dist/contract/caller.d.mts +12 -3
  35. package/dist/contract/caller.mjs +23 -5
  36. package/dist/contract/canonical.mjs +10 -0
  37. package/dist/contract/codec.d.mts +9 -0
  38. package/dist/contract/codec.mjs +9 -0
  39. package/dist/contract/codes.mjs +81 -0
  40. package/dist/contract/declared.d.mts +0 -2
  41. package/dist/contract/declared.mjs +26 -11
  42. package/dist/contract/digest.mjs +6 -0
  43. package/dist/contract/errors.d.mts +9 -5
  44. package/dist/contract/errors.mjs +2 -3
  45. package/dist/contract/feed.d.mts +127 -0
  46. package/dist/contract/feed.mjs +249 -0
  47. package/dist/contract/hints.mjs +1 -2
  48. package/dist/contract/hub.d.mts +94 -0
  49. package/dist/contract/hub.mjs +133 -0
  50. package/dist/contract/idempotency.d.mts +47 -0
  51. package/dist/contract/idempotency.mjs +99 -0
  52. package/dist/contract/implementation.d.mts +73 -51
  53. package/dist/contract/implementation.mjs +94 -60
  54. package/dist/contract/input-check.d.mts +15 -0
  55. package/dist/contract/input-check.mjs +16 -0
  56. package/dist/contract/lease.mjs +28 -0
  57. package/dist/contract/listed.d.mts +4 -0
  58. package/dist/contract/listed.mjs +22 -0
  59. package/dist/contract/log.d.mts +41 -0
  60. package/dist/contract/log.mjs +144 -0
  61. package/dist/contract/media.d.mts +1 -3
  62. package/dist/contract/media.mjs +1 -2
  63. package/dist/contract/names.mjs +15 -6
  64. package/dist/contract/occurrence.d.mts +29 -0
  65. package/dist/contract/occurrence.mjs +45 -0
  66. package/dist/contract/pages.d.mts +13 -0
  67. package/dist/contract/pages.mjs +39 -0
  68. package/dist/contract/progress.d.mts +26 -0
  69. package/dist/contract/progress.mjs +72 -0
  70. package/dist/contract/push.d.mts +20 -0
  71. package/dist/contract/push.mjs +96 -0
  72. package/dist/contract/receiving.d.mts +29 -0
  73. package/dist/contract/receiving.mjs +42 -0
  74. package/dist/contract/recent.mjs +12 -0
  75. package/dist/contract/reported.mjs +16 -0
  76. package/dist/contract/serving.mjs +63 -0
  77. package/dist/contract/status.mjs +15 -7
  78. package/dist/contract/stream.d.mts +34 -0
  79. package/dist/contract/stream.mjs +31 -0
  80. package/dist/contract/subscribe.d.mts +45 -0
  81. package/dist/contract/subscribe.mjs +50 -0
  82. package/dist/contract/transient.mjs +28 -0
  83. package/dist/contract/watch.d.mts +18 -0
  84. package/dist/contract/watch.mjs +105 -0
  85. package/dist/contract/webhooks.d.mts +42 -0
  86. package/dist/contract/webhooks.mjs +53 -0
  87. package/dist/http/client.d.mts +37 -14
  88. package/dist/http/client.mjs +52 -14
  89. package/dist/http/events.d.mts +56 -0
  90. package/dist/http/events.mjs +102 -0
  91. package/dist/http/placed.d.mts +36 -0
  92. package/dist/http/placed.mjs +71 -0
  93. package/dist/http/schema-errors.mjs +2 -3
  94. package/dist/http/streamed.d.mts +21 -0
  95. package/dist/http/streamed.mjs +94 -0
  96. package/dist/http/watch.mjs +50 -0
  97. package/dist/mcp/bridge.mjs +283 -0
  98. package/dist/mcp/client.d.mts +30 -99
  99. package/dist/mcp/client.mjs +60 -134
  100. package/dist/mcp/events.mjs +142 -0
  101. package/dist/mcp/progress.mjs +34 -0
  102. package/dist/mcp/protocol.mjs +40 -15
  103. package/dist/mcp/reply.d.mts +79 -0
  104. package/dist/mcp/reply.mjs +99 -0
  105. package/dist/mcp/request.d.mts +40 -0
  106. package/dist/mcp/request.mjs +51 -0
  107. package/dist/mcp/watch.mjs +115 -0
  108. package/dist/rpc/watch.mjs +39 -0
  109. package/dist/stdio/console.mjs +14 -7
  110. package/dist/testing/memory.d.mts +1 -3
  111. package/dist/testing/memory.mjs +4 -5
  112. package/dist/webhook/address.mjs +47 -0
  113. package/dist/webhook/attempt.mjs +65 -0
  114. package/dist/webhook/receiver.mjs +67 -0
  115. package/dist/webhook/signing.mjs +28 -0
  116. package/dist/webhook/stored.mjs +57 -0
  117. package/dist/webhook/subscriptions.mjs +342 -0
  118. package/docs/Action-failures.md +2 -2
  119. package/docs/Action-streams.md +115 -0
  120. package/docs/Action.md +58 -124
  121. package/docs/ActionCli-failures.md +1 -2
  122. package/docs/ActionCli.md +83 -88
  123. package/docs/ActionHttp-failures.md +3 -3
  124. package/docs/ActionHttp.md +90 -70
  125. package/docs/ActionMcp-failures.md +2 -1
  126. package/docs/ActionMcp.md +120 -92
  127. package/docs/ActionRpc-events.md +126 -0
  128. package/docs/ActionRpc-failures.md +1 -2
  129. package/docs/ActionRpc.md +108 -79
  130. package/docs/ActionToolkit-failures.md +1 -2
  131. package/docs/ActionToolkit.md +33 -90
  132. package/docs/Authentication-advanced.md +57 -61
  133. package/docs/Authentication-failures.md +5 -4
  134. package/docs/Authentication.md +23 -58
  135. package/docs/Event-failures.md +15 -0
  136. package/docs/Event.md +281 -0
  137. package/docs/README.md +51 -82
  138. package/docs/Testing-failures.md +1 -5
  139. package/docs/Testing.md +126 -86
  140. package/docs/Webhook-failures.md +8 -0
  141. package/docs/Webhook.md +319 -0
  142. package/docs/setup-failures.md +2 -3
  143. package/docs/setup.md +32 -53
  144. package/examples/README.md +12 -37
  145. package/examples/assistant/README.md +21 -26
  146. package/examples/assistant/src/agent.ts +66 -24
  147. package/examples/assistant/src/cli.ts +7 -4
  148. package/examples/assistant/src/commands.ts +9 -4
  149. package/examples/assistant/src/identity/Identity.ts +5 -4
  150. package/examples/assistant/src/identity/authorize.ts +13 -5
  151. package/examples/assistant/src/identity/handlers.ts +3 -2
  152. package/examples/assistant/src/identity/permissions.ts +9 -3
  153. package/examples/assistant/src/platform/tasks-file.ts +15 -11
  154. package/examples/assistant/src/scripted.ts +7 -4
  155. package/examples/assistant/src/tasks/Tasks.ts +20 -6
  156. package/examples/assistant/src/tasks/handlers.ts +9 -5
  157. package/examples/assistant/src/tasks/store.ts +38 -11
  158. package/examples/assistant/src/toolkit.ts +11 -7
  159. package/examples/assistant/test/commands.test.ts +53 -27
  160. package/examples/assistant/test/identity/identity.test.ts +27 -11
  161. package/examples/assistant/test/platform/tasks-file.test.ts +39 -23
  162. package/examples/assistant/test/tasks/tasks.test.ts +62 -34
  163. package/examples/assistant/test/toolkit.test.ts +104 -75
  164. package/examples/build-events/README.md +83 -0
  165. package/examples/build-events/src/Api.ts +27 -0
  166. package/examples/build-events/src/Rpc.ts +26 -0
  167. package/examples/build-events/src/builds/Builds.ts +108 -0
  168. package/examples/build-events/src/builds/events.ts +54 -0
  169. package/examples/build-events/src/builds/handlers.ts +58 -0
  170. package/examples/build-events/src/builds/log.ts +30 -0
  171. package/examples/build-events/src/builds/status.ts +28 -0
  172. package/examples/build-events/src/builds/store.ts +130 -0
  173. package/examples/build-events/src/cli.ts +18 -0
  174. package/examples/build-events/src/commands.ts +32 -0
  175. package/examples/build-events/src/deploys/Deploys.ts +44 -0
  176. package/examples/build-events/src/deploys/approvers.ts +55 -0
  177. package/examples/build-events/src/deploys/events.ts +32 -0
  178. package/examples/build-events/src/deploys/handlers.ts +38 -0
  179. package/examples/build-events/src/identity/Identity.ts +29 -0
  180. package/examples/build-events/src/identity/members.ts +7 -0
  181. package/examples/build-events/src/identity/verifier.ts +22 -0
  182. package/examples/build-events/src/notifier/Notices.ts +26 -0
  183. package/examples/build-events/src/notifier/routes.ts +71 -0
  184. package/examples/build-events/src/notifier.ts +48 -0
  185. package/examples/build-events/src/routes.ts +71 -0
  186. package/examples/build-events/src/server.ts +62 -0
  187. package/examples/build-events/test/builds/events.test.ts +68 -0
  188. package/examples/build-events/test/builds/log.test.ts +71 -0
  189. package/examples/build-events/test/builds/progress.test.ts +57 -0
  190. package/examples/build-events/test/builds/status.test.ts +42 -0
  191. package/examples/build-events/test/commands.test.ts +103 -0
  192. package/examples/build-events/test/deploys/events.test.ts +52 -0
  193. package/examples/build-events/test/http.test.ts +95 -0
  194. package/examples/build-events/test/notifier.test.ts +80 -0
  195. package/examples/build-events/test/routes.test.ts +96 -0
  196. package/examples/build-events/test/rpc.test.ts +84 -0
  197. package/examples/build-events/test/support/as.ts +8 -0
  198. package/examples/build-events/test/support/memory.ts +43 -0
  199. package/examples/build-events/test/webhooks.test.ts +143 -0
  200. package/examples/kitchen-sink/README.md +178 -166
  201. package/examples/kitchen-sink/src/Actions.ts +11 -3
  202. package/examples/kitchen-sink/src/Api.ts +12 -6
  203. package/examples/kitchen-sink/src/Rpc.ts +7 -5
  204. package/examples/kitchen-sink/src/admin/Admin.ts +21 -10
  205. package/examples/kitchen-sink/src/admin/authenticate.ts +8 -4
  206. package/examples/kitchen-sink/src/admin/authorize.ts +13 -5
  207. package/examples/kitchen-sink/src/admin/grants.ts +5 -3
  208. package/examples/kitchen-sink/src/admin/handlers.ts +9 -5
  209. package/examples/kitchen-sink/src/agent.ts +14 -6
  210. package/examples/kitchen-sink/src/assistant/Assistant.ts +21 -15
  211. package/examples/kitchen-sink/src/assistant/conversations.ts +14 -9
  212. package/examples/kitchen-sink/src/assistant/demo-model.ts +2 -2
  213. package/examples/kitchen-sink/src/assistant/handlers.ts +42 -22
  214. package/examples/kitchen-sink/src/attachments/Attachments.ts +6 -6
  215. package/examples/kitchen-sink/src/attachments/download.ts +7 -6
  216. package/examples/kitchen-sink/src/attachments/handlers.ts +9 -4
  217. package/examples/kitchen-sink/src/attachments/store.ts +25 -18
  218. package/examples/kitchen-sink/src/browser.ts +10 -4
  219. package/examples/kitchen-sink/src/cli.ts +28 -12
  220. package/examples/kitchen-sink/src/config.ts +1 -1
  221. package/examples/kitchen-sink/src/identity/Identity.ts +24 -10
  222. package/examples/kitchen-sink/src/identity/authenticate.ts +19 -11
  223. package/examples/kitchen-sink/src/identity/authorize.ts +14 -4
  224. package/examples/kitchen-sink/src/identity/directory.ts +16 -7
  225. package/examples/kitchen-sink/src/identity/handlers.ts +1 -0
  226. package/examples/kitchen-sink/src/identity/members.ts +7 -2
  227. package/examples/kitchen-sink/src/identity/quota.ts +23 -20
  228. package/examples/kitchen-sink/src/issues/Issues.ts +91 -15
  229. package/examples/kitchen-sink/src/issues/assistant.ts +4 -2
  230. package/examples/kitchen-sink/src/issues/events.ts +46 -0
  231. package/examples/kitchen-sink/src/issues/export.ts +35 -0
  232. package/examples/kitchen-sink/src/issues/guide.ts +4 -3
  233. package/examples/kitchen-sink/src/issues/handlers.ts +20 -7
  234. package/examples/kitchen-sink/src/issues/store.ts +156 -67
  235. package/examples/kitchen-sink/src/issues/write-limit.ts +44 -18
  236. package/examples/kitchen-sink/src/memory.ts +11 -2
  237. package/examples/kitchen-sink/src/ops.ts +10 -5
  238. package/examples/kitchen-sink/src/request-policy.ts +25 -9
  239. package/examples/kitchen-sink/src/routes.ts +67 -27
  240. package/examples/kitchen-sink/src/sdk.ts +22 -12
  241. package/examples/kitchen-sink/src/seed.ts +15 -9
  242. package/examples/kitchen-sink/src/server.ts +24 -10
  243. package/examples/kitchen-sink/src/stdio.ts +9 -3
  244. package/examples/kitchen-sink/src/workspaces/Workspaces.ts +10 -7
  245. package/examples/kitchen-sink/src/workspaces/handlers.ts +3 -2
  246. package/examples/kitchen-sink/src/workspaces/registry.ts +16 -10
  247. package/examples/kitchen-sink/src/workspaces/tenant.ts +12 -5
  248. package/examples/kitchen-sink/test/admin/admin.test.ts +38 -23
  249. package/examples/kitchen-sink/test/assistant/assistant.test.ts +471 -299
  250. package/examples/kitchen-sink/test/attachments/attachments.test.ts +135 -94
  251. package/examples/kitchen-sink/test/identity/identity.test.ts +110 -82
  252. package/examples/kitchen-sink/test/identity/session.test.ts +65 -43
  253. package/examples/kitchen-sink/test/issues/assistant.test.ts +50 -31
  254. package/examples/kitchen-sink/test/issues/events.test.ts +273 -0
  255. package/examples/kitchen-sink/test/issues/export.test.ts +120 -0
  256. package/examples/kitchen-sink/test/issues/issues.test.ts +105 -75
  257. package/examples/kitchen-sink/test/request-policy.test.ts +130 -33
  258. package/examples/kitchen-sink/test/routes.test.ts +102 -57
  259. package/examples/kitchen-sink/test/support/as.ts +9 -0
  260. package/examples/kitchen-sink/test/support/scripted.ts +6 -5
  261. package/examples/kitchen-sink/test/support/services.ts +17 -0
  262. package/examples/live-rpc/README.md +33 -35
  263. package/examples/live-rpc/src/Rpc.ts +9 -5
  264. package/examples/live-rpc/src/browser.ts +39 -11
  265. package/examples/live-rpc/src/chat/Chat.ts +30 -16
  266. package/examples/live-rpc/src/chat/authorize.ts +2 -1
  267. package/examples/live-rpc/src/chat/events.ts +74 -0
  268. package/examples/live-rpc/src/chat/handlers.ts +13 -5
  269. package/examples/live-rpc/src/chat/room.ts +9 -5
  270. package/examples/live-rpc/src/chat/store.ts +51 -14
  271. package/examples/live-rpc/src/client.ts +22 -5
  272. package/examples/live-rpc/src/identity/Identity.ts +3 -3
  273. package/examples/live-rpc/src/identity/verifier.ts +8 -4
  274. package/examples/live-rpc/src/page.ts +58 -12
  275. package/examples/live-rpc/src/routes.ts +14 -10
  276. package/examples/live-rpc/src/server.ts +15 -5
  277. package/examples/live-rpc/test/browser.test.ts +127 -50
  278. package/examples/live-rpc/test/chat/chat.test.ts +215 -23
  279. package/examples/live-rpc/test/routes.test.ts +130 -46
  280. package/examples/live-rpc/test/server.test.ts +94 -33
  281. package/examples/mcp-tools/README.md +24 -38
  282. package/examples/mcp-tools/src/platform/fake-farm.ts +63 -24
  283. package/examples/mcp-tools/src/platform/print-farm.ts +59 -29
  284. package/examples/mcp-tools/src/printers/Printers.ts +18 -7
  285. package/examples/mcp-tools/src/printers/farm.ts +15 -5
  286. package/examples/mcp-tools/src/printers/handlers.ts +12 -6
  287. package/examples/mcp-tools/src/routes.ts +8 -6
  288. package/examples/mcp-tools/src/server.ts +12 -5
  289. package/examples/mcp-tools/src/stdio.ts +3 -2
  290. package/examples/mcp-tools/test/printers/printers.test.ts +132 -89
  291. package/examples/mcp-tools/test/routes.test.ts +140 -81
  292. package/examples/notes-service/README.md +30 -45
  293. package/examples/notes-service/src/Api.ts +1 -0
  294. package/examples/notes-service/src/browser.ts +15 -14
  295. package/examples/notes-service/src/cli.ts +8 -3
  296. package/examples/notes-service/src/commands.ts +46 -20
  297. package/examples/notes-service/src/identity/Identity.ts +2 -2
  298. package/examples/notes-service/src/identity/authorize.ts +6 -2
  299. package/examples/notes-service/src/identity/handlers.ts +3 -2
  300. package/examples/notes-service/src/identity/users.ts +2 -1
  301. package/examples/notes-service/src/identity/verifier.ts +12 -6
  302. package/examples/notes-service/src/notes/Notes.ts +46 -22
  303. package/examples/notes-service/src/notes/handlers.ts +18 -9
  304. package/examples/notes-service/src/notes/store.ts +70 -43
  305. package/examples/notes-service/src/routes.ts +7 -5
  306. package/examples/notes-service/src/server.ts +12 -5
  307. package/examples/notes-service/src/status/Status.ts +1 -1
  308. package/examples/notes-service/src/status/handlers.ts +5 -3
  309. package/examples/notes-service/src/stdio.ts +7 -3
  310. package/examples/notes-service/src/worker.ts +6 -3
  311. package/examples/notes-service/test/commands.test.ts +164 -91
  312. package/examples/notes-service/test/notes/as.ts +3 -1
  313. package/examples/notes-service/test/notes/idempotency.test.ts +97 -67
  314. package/examples/notes-service/test/notes/in-process.test.ts +26 -15
  315. package/examples/notes-service/test/notes/notes.test.ts +49 -31
  316. package/examples/notes-service/test/routes.test.ts +106 -71
  317. package/examples/notes-service/test/worker.test.ts +21 -13
  318. package/examples/quickstart/README.md +2 -6
  319. package/examples/quickstart/src/Api.ts +1 -0
  320. package/examples/quickstart/src/client.ts +6 -3
  321. package/examples/quickstart/src/greeting/Greeting.ts +1 -1
  322. package/examples/quickstart/src/greeting/handlers.ts +5 -2
  323. package/examples/quickstart/src/routes.ts +3 -2
  324. package/examples/quickstart/src/server.ts +6 -2
  325. package/examples/quickstart/test/routes.test.ts +3 -2
  326. package/package.json +19 -17
@@ -1,12 +1,12 @@
1
+ import { Codec } from "./contract/codec.mjs";
1
2
  import { Refusal } from "./contract/errors.mjs";
2
3
  import { Known, OptionalUnless } from "./contract/implementation.mjs";
3
4
  import { Errors } from "./Action.mjs";
4
- import { Any, Descriptor, Provider, Security } from "./authentication/provider.mjs";
5
5
  import { ErrorsOf } from "./contract/declared.mjs";
6
- import { Array, Context, Effect, Layer, Option, Redacted, Scope } from "effect";
6
+ import { Any, Descriptor, Provider, Security } from "./authentication/provider.mjs";
7
+ import { Array, Context, Effect, Layer, Option, Redacted, Schema, Scope } from "effect";
7
8
  import { HttpRouter, HttpServerResponse } from "effect/http";
8
9
  import { HttpApiSecurity } from "effect/http-api";
9
- //#region src/authentication/Authentication.d.ts
10
10
  /**
11
11
  * The bearer token of an `Authorization` header, `authorization`, or none, read as Effect's
12
12
  * `HttpApiSecurity.bearer` reads it for routes and MCP: the scheme matched case-insensitively,
@@ -14,16 +14,18 @@ import { HttpApiSecurity } from "effect/http-api";
14
14
  * which holds the header and no request; a route of the host's own is authenticated by
15
15
  * `protect`.
16
16
  */
17
- export declare const bearerTokenOf: (authorization: string | null | undefined) => Option.Option<Redacted.Redacted<string>>;
17
+ export declare const bearerTokenOf: (authorization: string | null | undefined) => Option.Option<Redacted.Redacted>;
18
18
  /** An OAuth protected resource (RFC 9728), as `layer` publishes it. */
19
19
  export interface ProtectedResource {
20
20
  /**
21
- * Exact OAuth resource identifier: the public URL of the endpoint clients connect to, such as
22
- * `${url}/mcp`. Discovery is served at `/.well-known/oauth-protected-resource` followed by its
23
- * path and query.
21
+ * OAuth resource identifier: the public URL of the endpoint clients connect to, such as
22
+ * `${url}/mcp`, or its origin, such as `https://api.example.com`, which MCP allows to name one
23
+ * resource for every endpoint it serves. Discovery is served at
24
+ * `/.well-known/oauth-protected-resource` followed by its path and query.
24
25
  *
25
26
  * **Gotchas**
26
27
  *
28
+ * - A client applying RFC 9728 §3.3 strictly finds no authorization server for an origin, since metadata reached from a challenge must name the URL it requested; the official MCP client accepts it, and the endpoint's URL works with both.
27
29
  * - Discovery answers 404, and an MCP client finds no authorization server, when a request's path and query differ from this URL's, or when the provider is provided to no served layer.
28
30
  * - That it and `authorizationServers` are HTTPS, or loopback HTTP in development, is the deployment's to ensure: nothing checks it.
29
31
  */
@@ -34,7 +36,7 @@ export interface ProtectedResource {
34
36
  * Every scope the resource accepts, each an OAuth scope token, which a client requests when a
35
37
  * 401 names none.
36
38
  */
37
- readonly scopesSupported?: ReadonlyArray<string>;
39
+ readonly scopesSupported?: readonly string[];
38
40
  /**
39
41
  * The scopes every 401 names, each an OAuth scope token: what a client requests when it
40
42
  * authenticates, rather than every scope supported.
@@ -79,8 +81,8 @@ type DeclaredOf<D> = D extends {
79
81
  * naming scopes. `HttpServerResponse.toWeb` gives it as a web `Response`.
80
82
  */
81
83
  export declare const refusalResponse: <D extends Any = never>(error: Refusal | DeclaredOf<D>, options?: RefusalResponseOptions<D>) => HttpServerResponse.HttpServerResponse;
82
- /** What `make` takes besides the name and the identity. */
83
- export interface Options {
84
+ /** What `make` takes besides the name and the identity `A`. */
85
+ export interface Options<A = unknown> {
84
86
  /**
85
87
  * The one native scheme a caller proves the identity by, Bearer when left out: an API key in a
86
88
  * cookie, a header or the query, such as `HttpApiSecurity.apiKey({ in: "cookie", key: "session" })`,
@@ -101,7 +103,35 @@ export interface Options {
101
103
  * - A failure that is not the caller's, such as an issuer the verifier cannot reach, belongs here with `{ httpApiStatus: 503 }`: an `Unauthenticated` would have the caller sign in again.
102
104
  */
103
105
  readonly error?: Errors | Errors[number];
106
+ /**
107
+ * The stable id of an identity, such as its user id: what tells subscribers apart where a
108
+ * surface keeps a subscription between requests, such as an MCP poll subscription's lease.
109
+ *
110
+ * **Gotchas**
111
+ *
112
+ * - A surface serving a protected event type whose source has a `subscription` hook refuses to start without it, its message naming this option.
113
+ * - Two identities with one principal share those subscriptions, so give each its own: a tenant's id alone merges its users.
114
+ * - Without it no surface offers a protected event by webhook, which a subscription keys by its principal: the listing names no webhook, and a webhook subscription fails with `Event.SubscriptionError`, reason `Unsupported`.
115
+ * - Without it every caller shares one idempotency key space: another caller sending a key with the same input is given the first call's answer, as `Action.Options.idempotencyKey` says.
116
+ */
117
+ readonly principal?: ((identity: A) => string) | undefined;
118
+ /**
119
+ * The identity's schema, its `Type` the identity: how a surface keeps an identity past a
120
+ * restart, as `Webhook.layer` with `persist` keeps each webhook subscriber's, its JSON in the
121
+ * store.
122
+ *
123
+ * **Gotchas**
124
+ *
125
+ * - `Webhook.layer` with `persist` refuses to serve a protected event by webhook without it, its message naming this option.
126
+ */
127
+ readonly schema?: Codec | undefined;
104
128
  }
129
+ type PrincipalOf<A> = NoInfer<Pick<Options<A>, "principal">>;
130
+ type SchemaOf<O, A> = O extends {
131
+ readonly schema: infer S extends Schema.Top;
132
+ } ? [S["Type"], A] extends [A, S["Type"]] ? unknown : {
133
+ readonly schema: "a schema whose Type is the identity's";
134
+ } : unknown;
105
135
  type SecurityOf<O> = O extends {
106
136
  readonly security: infer S extends Security;
107
137
  } ? S : "security" extends keyof O ? NonNullable<O["security" & keyof O]> | HttpApiSecurity.Http : HttpApiSecurity.Http;
@@ -112,7 +142,7 @@ type SecurityOf<O> = O extends {
112
142
  * one name only for one declaration, whose values a server and a client may hold as `layer` says.
113
143
  * Guide: `docs/Authentication.md` in this package; in an application: `examples/notes-service/src/identity/`.
114
144
  */
115
- export declare const make: <const Name extends string, I, A, const O extends Options = {}>(name: Name, identity: Context.Key<I, A>, ...options: OptionalUnless<O, O & NoInfer<Known<O, Options>>>) => Descriptor<I, A, SecurityOf<O>, Name, ErrorsOf<O>>;
145
+ export declare const make: <const Name extends string, I, A, const O extends Options<never> = {}>(name: Name, identity: Context.Key<I, A>, ...options: OptionalUnless<O, O & NoInfer<Known<O, Options>> & PrincipalOf<A> & SchemaOf<O, A>>) => Descriptor<I, A, SecurityOf<O>, Name, ErrorsOf<O>>;
116
146
  /**
117
147
  * A descriptor's verifier: the credential its scheme decodes, as Effect's own decoder gives
118
148
  * it, to the identity, or a refusal, or an error the descriptor declares, `E`. Its services
@@ -155,6 +185,7 @@ type Resource = ProtectedResource | Effect.Effect<ProtectedResource | undefined,
155
185
  * **Gotchas**
156
186
  *
157
187
  * - Call it once per descriptor, and provide that value to every layer serving a protected action: nothing else satisfies the provider they require, neither the identity nor another descriptor's provider, which type-checks under the same name and identity, then dies when the layer builds; only a client may hold another descriptor of the name, such as one of a cookie name the server decides at startup.
188
+ * - Over `ActionRpc` a verifier reading `HttpServerRequest` reads one holding the message's headers alone, a socket's upgrade headers before its own, at `/` with no query or body, on every protocol: one judging the URL or query sees none of the client's.
158
189
  */
159
190
  export declare function layer<const Name extends string, I, A, const S extends Security, E extends Errors, R, EX = never, RX = never, P extends Resource = undefined>(authentication: Descriptor<I, A, S, Name, E>, verify: Verify<NoInfer<A>, NoInfer<S>, R, NoInfer<E[number]["Type"]>> | Effect.Effect<Verify<NoInfer<A>, NoInfer<S>, R, NoInfer<E[number]["Type"]>>, EX, RX>, options?: {
160
191
  /** The OAuth protected resource to publish: its rules are on `LayerOptions.protectedResource`. */
@@ -190,5 +221,4 @@ export type Protection<I, Name extends string> = HttpRouter.Middleware<{
190
221
  * - An erased descriptor, `Any`, provides no service the types can name: its identity is `unknown`, which would discharge every request service the route owes.
191
222
  */
192
223
  export declare const protect: <I, A, S extends Security, Name extends string, E extends Errors>(authentication: Descriptor<I, A, S, Name, E>) => Protection<I, Name>;
193
- //#endregion
194
224
  export type { Any, Descriptor, Provider };
@@ -2,11 +2,11 @@ import { Unauthenticated, builtIns, scopeToken } from "./contract/errors.mjs";
2
2
  import { assertDistinctTags, errorList } from "./contract/declared.mjs";
3
3
  import { providerOf } from "./authentication/provider.mjs";
4
4
  import { answer, answerStepUp, bearer, challenge, declaredResponse, isRefusal, isStepUp, plain } from "./authentication/refusal.mjs";
5
- import { Context, Effect, Layer, Match, Option, Predicate, Redacted } from "effect";
5
+ import { Context, Effect, Function, Layer, Match, Option, Predicate, Redacted } from "effect";
6
6
  import { HttpEffect, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/http";
7
7
  import { HttpApiBuilder, HttpApiMiddleware, HttpApiSecurity } from "effect/http-api";
8
- //#region src/authentication/Authentication.ts
9
- const bearerAuthorizationHeader = /^[ \t]*Bearer +(.*[^ \t])[ \t]*$/i;
8
+ // src/authentication/Authentication.ts
9
+ const bearerAuthorizationHeader = /^[ \t]*Bearer +(?<token>.*[^ \t])[ \t]*$/iu;
10
10
  /**
11
11
  * The bearer token of an `Authorization` header, `authorization`, or none, read as Effect's
12
12
  * `HttpApiSecurity.bearer` reads it for routes and MCP: the scheme matched case-insensitively,
@@ -14,7 +14,7 @@ const bearerAuthorizationHeader = /^[ \t]*Bearer +(.*[^ \t])[ \t]*$/i;
14
14
  * which holds the header and no request; a route of the host's own is authenticated by
15
15
  * `protect`.
16
16
  */
17
- const bearerTokenOf = (authorization) => Option.map(Option.fromNullishOr(bearerAuthorizationHeader.exec(authorization ?? "")?.[1]), (token) => Redacted.make(token));
17
+ const bearerTokenOf = (authorization) => Option.map(Option.fromNullishOr(bearerAuthorizationHeader.exec(authorization ?? "")?.groups?.["token"]), (token) => Redacted.make(token));
18
18
  const metadataUrlOf = (options) => {
19
19
  const resource = new URL(options.resource);
20
20
  const url = new URL(resource);
@@ -43,10 +43,13 @@ const assertPublishableResource = (oauth, options) => {
43
43
  if (resource === null) throw new Error(`A protected resource is an absolute URL: ${JSON.stringify(options.resource)} is not one; give the public URL of the endpoint clients connect to, such as https://api.example.com/mcp, read from configuration`);
44
44
  if (resource.href.includes("#")) throw new Error(`A protected resource has no fragment: ${options.resource}; drop the "#", which no request URL carries, so its discovery would never answer`);
45
45
  };
46
- const withNoStoreAndChallenge = (response, challenge) => HttpServerResponse.setHeaders(response, {
46
+ const withNoStoreAndChallenge = (response, wwwAuthenticate) => HttpServerResponse.setHeaders(response, {
47
47
  ...response.headers["cache-control"] === void 0 ? { "cache-control": "no-store" } : {},
48
- ...challenge !== void 0 && response.status === 401 && response.headers["www-authenticate"] === void 0 ? { "www-authenticate": challenge } : {}
48
+ ...wwwAuthenticate !== void 0 && response.status === 401 && response.headers["www-authenticate"] === void 0 ? { "www-authenticate": wwwAuthenticate } : {}
49
49
  });
50
+ const isBearer = (security) => Predicate.isTagged(security, "Http") && security.scheme.toLowerCase() === "bearer";
51
+ const openApiComponentKey = /^[\w.-]+$/u;
52
+ const nonBearerChallengeOf = (security, name) => Match.value(security).pipe(Match.tag("Http", ({ scheme }) => challenge(scheme, [])), Match.tag("Basic", () => challenge("Basic", [["realm", name]])), Match.tag("ApiKey", Function.constUndefined), Match.exhaustive);
50
53
  /**
51
54
  * The response `layer` answers a refusal with, for a caller the router never routes, such as a
52
55
  * Node `upgrade` handler admitting a socket, which holds a header and no request: read its token
@@ -60,15 +63,29 @@ const refusalResponse = (error, options) => {
60
63
  const oauth = authentication === void 0 || isBearer(authentication.security);
61
64
  assertPublishableResource(oauth, options?.protectedResource);
62
65
  const named = namedOf(options?.protectedResource);
63
- const challenge = authentication !== void 0 && !oauth ? nonBearerChallengeOf(authentication.security, authentication.name) : bearerChallengeOf(named, presentsBearerToken(options?.authorization));
66
+ const wwwAuthenticate = authentication !== void 0 && !oauth ? nonBearerChallengeOf(authentication.security, authentication.name) : bearerChallengeOf(named, presentsBearerToken(options?.authorization));
64
67
  if (!isRefusal(error)) {
65
68
  const response = declaredResponse(authentication?.error ?? [], error);
66
69
  if (response === void 0) throw new Error("Not a refusal, nor an error the authentication declares: pass the descriptor declaring it as `authentication`, or answer the error yourself");
67
- return withNoStoreAndChallenge(response, challenge);
70
+ return withNoStoreAndChallenge(response, wwwAuthenticate);
68
71
  }
69
- return withNoStoreAndChallenge(oauth ? answer(error, named.metadataUrl) : plain(error), challenge);
72
+ return withNoStoreAndChallenge(oauth ? answer(error, named.metadataUrl) : plain(error), wwwAuthenticate);
73
+ };
74
+ const mayResolveToDiscoveryPath = /\.well-known|[\t\n\r]/u;
75
+ const corsPreflight = (request) => {
76
+ const requested = request.headers["access-control-request-headers"];
77
+ return HttpServerResponse.empty({
78
+ status: 204,
79
+ headers: {
80
+ "access-control-allow-origin": "*",
81
+ "access-control-allow-methods": "GET, HEAD, OPTIONS",
82
+ ...requested === void 0 ? {} : {
83
+ "access-control-allow-headers": requested,
84
+ vary: "Access-Control-Request-Headers"
85
+ }
86
+ }
87
+ });
70
88
  };
71
- const mayResolveToDiscoveryPath = /\.well-known|[\t\n\r]/;
72
89
  const discoveryMiddleware = (options) => {
73
90
  const discoveryUrl = metadataUrlOf(options);
74
91
  const target = discoveryUrl.href.slice(discoveryUrl.origin.length);
@@ -79,20 +96,6 @@ const discoveryMiddleware = (options) => {
79
96
  scopes_supported: options.scopesSupported,
80
97
  resource_name: options.resourceName
81
98
  }, { headers: { "access-control-allow-origin": "*" } });
82
- const corsPreflight = (request) => {
83
- const requested = request.headers["access-control-request-headers"];
84
- return HttpServerResponse.empty({
85
- status: 204,
86
- headers: {
87
- "access-control-allow-origin": "*",
88
- "access-control-allow-methods": "GET, HEAD, OPTIONS",
89
- ...requested === void 0 ? {} : {
90
- "access-control-allow-headers": requested,
91
- vary: "Access-Control-Request-Headers"
92
- }
93
- }
94
- });
95
- };
96
99
  return (next) => Effect.flatMap(HttpServerRequest.HttpServerRequest, (request) => {
97
100
  const targeted = mayResolveToDiscoveryPath.test(request.url) && URL.canParse(request.url, discoveryUrl.origin) && new URL(request.url, discoveryUrl.origin).href.slice(discoveryUrl.origin.length) === target;
98
101
  if (targeted && (request.method === "GET" || request.method === "HEAD")) return Effect.succeed(metadata);
@@ -109,9 +112,6 @@ const resourceAnswersOf = (options) => {
109
112
  published: options === void 0 ? void 0 : discoveryMiddleware(options)
110
113
  };
111
114
  };
112
- const isBearer = (security) => Predicate.isTagged(security, "Http") && security.scheme.toLowerCase() === "bearer";
113
- const openApiComponentKey = /^[\w.-]+$/;
114
- const nonBearerChallengeOf = (security, name) => Match.value(security).pipe(Match.tag("Http", ({ scheme }) => challenge(scheme, [])), Match.tag("Basic", () => challenge("Basic", [["realm", name]])), Match.tag("ApiKey", () => void 0), Match.exhaustive);
115
115
  /**
116
116
  * Browser-safe declaration shared by clients and remote surfaces: the `identity` it
117
117
  * authenticates, and the scheme a caller proves it by, `Options.security`. The literal name
@@ -122,6 +122,8 @@ const nonBearerChallengeOf = (security, name) => Match.value(security).pipe(Matc
122
122
  const make = (name, identity, ...options) => {
123
123
  const security = options[0]?.security ?? HttpApiSecurity.bearer;
124
124
  const error = errorList(options[0]?.error);
125
+ const principal = options[0]?.principal;
126
+ const schema = options[0]?.schema;
125
127
  if (!openApiComponentKey.test(name)) throw new Error(`Invalid authentication name: ${JSON.stringify(name)}, not an OpenAPI key: use only letters, digits, "_", "." and "-"`);
126
128
  assertDistinctTags(`Authentication "${name}"`, [...builtIns, ...error]);
127
129
  if (!Predicate.isTagged(security, "Http") && !Predicate.isTagged(security, "ApiKey") && !Predicate.isTagged(security, "Basic")) throw new Error("Authentication takes one native HttpApiSecurity scheme as `security`: omit it for Bearer, or give one scheme, such as `HttpApiSecurity.basic`, never a record of them");
@@ -130,6 +132,8 @@ const make = (name, identity, ...options) => {
130
132
  identity,
131
133
  security,
132
134
  error,
135
+ ...principal === void 0 ? {} : { principal },
136
+ ...schema === void 0 ? {} : { schema },
133
137
  "~provider": Context.Service(`effect-actions/Authentication/Provider/${name}`),
134
138
  "~middleware": HttpApiMiddleware.Service()(`effect-actions/Authentication/Security/${name}`, { security: { [name]: security } }),
135
139
  "~verified": Context.Service(`effect-actions/Authentication/Verified/${name}`)
@@ -139,7 +143,7 @@ const presented = (credential) => Redacted.isRedacted(credential) ? Redacted.val
139
143
  function layer(authentication, verify, options = {}) {
140
144
  const { security } = authentication;
141
145
  const oauth = isBearer(security);
142
- const protectedResource = options.protectedResource;
146
+ const { protectedResource } = options;
143
147
  assertPublishableResource(oauth, protectedResource);
144
148
  const resource = Effect.isEffect(protectedResource) ? Effect.map(protectedResource, (built) => {
145
149
  assertPublishableResource(oauth, built);
@@ -153,17 +157,20 @@ function layer(authentication, verify, options = {}) {
153
157
  if (!Predicate.isFunction(verifier)) return yield* Effect.die(/* @__PURE__ */ new Error("Missing verify: pass a verify function, or an Effect building one"));
154
158
  const authenticate = (credential) => presented(credential) ? verifier(credential) : Effect.fail(missing);
155
159
  const noStoreOnFailure = HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setHeader(response, "cache-control", "no-store")));
156
- const challengeOf = (credential) => !oauth ? nonBearerChallengeOf(security, authentication.name) : presented(credential) ? invalid : anonymous;
160
+ const challengeOf = (credential) => {
161
+ if (!oauth) return nonBearerChallengeOf(security, authentication.name);
162
+ return presented(credential) ? invalid : anonymous;
163
+ };
157
164
  const refuse = (error) => isRefusal(error) ? Effect.succeed(oauth ? answer(error, metadataUrl) : plain(error)) : Effect.suspend(() => {
158
165
  const response = declaredResponse(authentication.error, error);
159
166
  return response === void 0 ? Effect.die(error) : Effect.succeed(response);
160
167
  });
161
168
  const verified = (credential, route, slot) => {
162
- const challenge = challengeOf(credential);
169
+ const wwwAuthenticate = challengeOf(credential);
163
170
  return Effect.matchEffect(authenticate(credential), {
164
171
  onFailure: refuse,
165
172
  onSuccess: (actor) => Effect.provideService(route, slot, actor)
166
- }).pipe(Effect.onError(() => noStoreOnFailure), HttpEffect.withPreResponseHandler((_request, response) => Effect.succeed(withNoStoreAndChallenge(response, challenge))));
173
+ }).pipe(Effect.onError(() => noStoreOnFailure), HttpEffect.withPreResponseHandler((_request, response) => Effect.succeed(withNoStoreAndChallenge(response, wwwAuthenticate))));
167
174
  };
168
175
  return {
169
176
  descriptor: authentication,
@@ -191,5 +198,4 @@ function layer(authentication, verify, options = {}) {
191
198
  * - An erased descriptor, `Any`, provides no service the types can name: its identity is `unknown`, which would discharge every request service the route owes.
192
199
  */
193
200
  const protect = (authentication) => HttpRouter.middleware()(Effect.map(providerOf(authentication), (runtime) => (route) => runtime.route(route)));
194
- //#endregion
195
201
  export { bearerTokenOf, layer, make, protect, refusalResponse };