@gjermundgaraba/effect-actions 0.20.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 (321) hide show
  1. package/README.md +19 -41
  2. package/dist/Action.d.mts +140 -46
  3. package/dist/Action.mjs +87 -42
  4. package/dist/ActionCli.d.mts +69 -33
  5. package/dist/ActionCli.mjs +51 -32
  6. package/dist/ActionHttp.d.mts +69 -35
  7. package/dist/ActionHttp.mjs +75 -23
  8. package/dist/ActionMcp.d.mts +56 -22
  9. package/dist/ActionMcp.mjs +94 -69
  10. package/dist/ActionRpc.d.mts +222 -55
  11. package/dist/ActionRpc.mjs +299 -46
  12. package/dist/ActionToolkit.d.mts +22 -5
  13. package/dist/ActionToolkit.mjs +36 -12
  14. package/dist/Authentication.d.mts +42 -10
  15. package/dist/Authentication.mjs +37 -30
  16. package/dist/Event.d.mts +370 -0
  17. package/dist/Event.mjs +103 -0
  18. package/dist/Testing.mjs +1 -1
  19. package/dist/Webhook.d.mts +146 -0
  20. package/dist/Webhook.mjs +72 -0
  21. package/dist/authentication/provider.d.mts +35 -10
  22. package/dist/authentication/provider.mjs +1 -1
  23. package/dist/authentication/refusal.mjs +2 -2
  24. package/dist/cli/commands.d.mts +9 -5
  25. package/dist/cli/commands.mjs +84 -33
  26. package/dist/contract/ast.mjs +4 -1
  27. package/dist/contract/authorizer.d.mts +8 -0
  28. package/dist/contract/authorizer.mjs +20 -0
  29. package/dist/contract/call.d.mts +9 -6
  30. package/dist/contract/call.mjs +48 -6
  31. package/dist/contract/callback.d.mts +7 -0
  32. package/dist/contract/callback.mjs +27 -0
  33. package/dist/contract/caller.d.mts +13 -2
  34. package/dist/contract/caller.mjs +22 -3
  35. package/dist/contract/canonical.mjs +10 -0
  36. package/dist/contract/codec.d.mts +9 -0
  37. package/dist/contract/codec.mjs +9 -0
  38. package/dist/contract/codes.mjs +81 -0
  39. package/dist/contract/declared.mjs +25 -9
  40. package/dist/contract/digest.mjs +6 -0
  41. package/dist/contract/errors.d.mts +9 -3
  42. package/dist/contract/errors.mjs +1 -1
  43. package/dist/contract/feed.d.mts +127 -0
  44. package/dist/contract/feed.mjs +249 -0
  45. package/dist/contract/hub.d.mts +94 -0
  46. package/dist/contract/hub.mjs +133 -0
  47. package/dist/contract/idempotency.d.mts +47 -0
  48. package/dist/contract/idempotency.mjs +99 -0
  49. package/dist/contract/implementation.d.mts +73 -47
  50. package/dist/contract/implementation.mjs +92 -58
  51. package/dist/contract/input-check.d.mts +15 -0
  52. package/dist/contract/input-check.mjs +16 -0
  53. package/dist/contract/lease.mjs +28 -0
  54. package/dist/contract/listed.d.mts +4 -0
  55. package/dist/contract/listed.mjs +22 -0
  56. package/dist/contract/log.d.mts +41 -0
  57. package/dist/contract/log.mjs +144 -0
  58. package/dist/contract/names.mjs +14 -4
  59. package/dist/contract/occurrence.d.mts +29 -0
  60. package/dist/contract/occurrence.mjs +45 -0
  61. package/dist/contract/pages.d.mts +13 -0
  62. package/dist/contract/pages.mjs +39 -0
  63. package/dist/contract/progress.d.mts +26 -0
  64. package/dist/contract/progress.mjs +72 -0
  65. package/dist/contract/push.d.mts +20 -0
  66. package/dist/contract/push.mjs +96 -0
  67. package/dist/contract/receiving.d.mts +29 -0
  68. package/dist/contract/receiving.mjs +42 -0
  69. package/dist/contract/recent.mjs +12 -0
  70. package/dist/contract/reported.mjs +16 -0
  71. package/dist/contract/serving.mjs +63 -0
  72. package/dist/contract/status.mjs +14 -5
  73. package/dist/contract/stream.d.mts +34 -0
  74. package/dist/contract/stream.mjs +31 -0
  75. package/dist/contract/subscribe.d.mts +45 -0
  76. package/dist/contract/subscribe.mjs +50 -0
  77. package/dist/contract/transient.mjs +28 -0
  78. package/dist/contract/watch.d.mts +18 -0
  79. package/dist/contract/watch.mjs +105 -0
  80. package/dist/contract/webhooks.d.mts +42 -0
  81. package/dist/contract/webhooks.mjs +53 -0
  82. package/dist/http/client.d.mts +37 -12
  83. package/dist/http/client.mjs +51 -12
  84. package/dist/http/events.d.mts +56 -0
  85. package/dist/http/events.mjs +102 -0
  86. package/dist/http/placed.d.mts +36 -0
  87. package/dist/http/placed.mjs +71 -0
  88. package/dist/http/schema-errors.mjs +1 -1
  89. package/dist/http/streamed.d.mts +21 -0
  90. package/dist/http/streamed.mjs +94 -0
  91. package/dist/http/watch.mjs +50 -0
  92. package/dist/mcp/bridge.mjs +283 -0
  93. package/dist/mcp/client.d.mts +30 -97
  94. package/dist/mcp/client.mjs +59 -132
  95. package/dist/mcp/events.mjs +142 -0
  96. package/dist/mcp/progress.mjs +34 -0
  97. package/dist/mcp/protocol.mjs +39 -13
  98. package/dist/mcp/reply.d.mts +79 -0
  99. package/dist/mcp/reply.mjs +99 -0
  100. package/dist/mcp/request.d.mts +40 -0
  101. package/dist/mcp/request.mjs +51 -0
  102. package/dist/mcp/watch.mjs +115 -0
  103. package/dist/rpc/watch.mjs +39 -0
  104. package/dist/stdio/console.mjs +13 -5
  105. package/dist/testing/memory.d.mts +1 -1
  106. package/dist/testing/memory.mjs +3 -3
  107. package/dist/webhook/address.mjs +47 -0
  108. package/dist/webhook/attempt.mjs +65 -0
  109. package/dist/webhook/receiver.mjs +67 -0
  110. package/dist/webhook/signing.mjs +28 -0
  111. package/dist/webhook/stored.mjs +57 -0
  112. package/dist/webhook/subscriptions.mjs +342 -0
  113. package/docs/Action-failures.md +2 -2
  114. package/docs/Action-streams.md +115 -0
  115. package/docs/Action.md +58 -124
  116. package/docs/ActionCli-failures.md +1 -2
  117. package/docs/ActionCli.md +83 -88
  118. package/docs/ActionHttp-failures.md +3 -3
  119. package/docs/ActionHttp.md +90 -70
  120. package/docs/ActionMcp-failures.md +2 -1
  121. package/docs/ActionMcp.md +120 -92
  122. package/docs/ActionRpc-events.md +126 -0
  123. package/docs/ActionRpc-failures.md +1 -2
  124. package/docs/ActionRpc.md +108 -79
  125. package/docs/ActionToolkit-failures.md +1 -2
  126. package/docs/ActionToolkit.md +33 -90
  127. package/docs/Authentication-advanced.md +52 -60
  128. package/docs/Authentication-failures.md +5 -4
  129. package/docs/Authentication.md +23 -58
  130. package/docs/Event-failures.md +15 -0
  131. package/docs/Event.md +281 -0
  132. package/docs/README.md +51 -82
  133. package/docs/Testing-failures.md +1 -5
  134. package/docs/Testing.md +126 -86
  135. package/docs/Webhook-failures.md +8 -0
  136. package/docs/Webhook.md +319 -0
  137. package/docs/setup-failures.md +2 -3
  138. package/docs/setup.md +32 -52
  139. package/examples/README.md +12 -37
  140. package/examples/assistant/README.md +21 -26
  141. package/examples/assistant/src/agent.ts +66 -24
  142. package/examples/assistant/src/cli.ts +7 -4
  143. package/examples/assistant/src/commands.ts +9 -4
  144. package/examples/assistant/src/identity/Identity.ts +5 -4
  145. package/examples/assistant/src/identity/authorize.ts +13 -5
  146. package/examples/assistant/src/identity/handlers.ts +3 -2
  147. package/examples/assistant/src/identity/permissions.ts +9 -3
  148. package/examples/assistant/src/platform/tasks-file.ts +15 -11
  149. package/examples/assistant/src/scripted.ts +7 -4
  150. package/examples/assistant/src/tasks/Tasks.ts +20 -6
  151. package/examples/assistant/src/tasks/handlers.ts +9 -5
  152. package/examples/assistant/src/tasks/store.ts +38 -11
  153. package/examples/assistant/src/toolkit.ts +11 -7
  154. package/examples/assistant/test/commands.test.ts +53 -27
  155. package/examples/assistant/test/identity/identity.test.ts +27 -11
  156. package/examples/assistant/test/platform/tasks-file.test.ts +39 -23
  157. package/examples/assistant/test/tasks/tasks.test.ts +62 -34
  158. package/examples/assistant/test/toolkit.test.ts +104 -75
  159. package/examples/build-events/README.md +83 -0
  160. package/examples/build-events/src/Api.ts +27 -0
  161. package/examples/build-events/src/Rpc.ts +26 -0
  162. package/examples/build-events/src/builds/Builds.ts +108 -0
  163. package/examples/build-events/src/builds/events.ts +54 -0
  164. package/examples/build-events/src/builds/handlers.ts +58 -0
  165. package/examples/build-events/src/builds/log.ts +30 -0
  166. package/examples/build-events/src/builds/status.ts +28 -0
  167. package/examples/build-events/src/builds/store.ts +130 -0
  168. package/examples/build-events/src/cli.ts +18 -0
  169. package/examples/build-events/src/commands.ts +32 -0
  170. package/examples/build-events/src/deploys/Deploys.ts +44 -0
  171. package/examples/build-events/src/deploys/approvers.ts +55 -0
  172. package/examples/build-events/src/deploys/events.ts +32 -0
  173. package/examples/build-events/src/deploys/handlers.ts +38 -0
  174. package/examples/build-events/src/identity/Identity.ts +29 -0
  175. package/examples/build-events/src/identity/members.ts +7 -0
  176. package/examples/build-events/src/identity/verifier.ts +22 -0
  177. package/examples/build-events/src/notifier/Notices.ts +26 -0
  178. package/examples/build-events/src/notifier/routes.ts +71 -0
  179. package/examples/build-events/src/notifier.ts +48 -0
  180. package/examples/build-events/src/routes.ts +71 -0
  181. package/examples/build-events/src/server.ts +62 -0
  182. package/examples/build-events/test/builds/events.test.ts +68 -0
  183. package/examples/build-events/test/builds/log.test.ts +71 -0
  184. package/examples/build-events/test/builds/progress.test.ts +57 -0
  185. package/examples/build-events/test/builds/status.test.ts +42 -0
  186. package/examples/build-events/test/commands.test.ts +103 -0
  187. package/examples/build-events/test/deploys/events.test.ts +52 -0
  188. package/examples/build-events/test/http.test.ts +95 -0
  189. package/examples/build-events/test/notifier.test.ts +80 -0
  190. package/examples/build-events/test/routes.test.ts +96 -0
  191. package/examples/build-events/test/rpc.test.ts +84 -0
  192. package/examples/build-events/test/support/as.ts +8 -0
  193. package/examples/build-events/test/support/memory.ts +43 -0
  194. package/examples/build-events/test/webhooks.test.ts +143 -0
  195. package/examples/kitchen-sink/README.md +178 -166
  196. package/examples/kitchen-sink/src/Actions.ts +11 -3
  197. package/examples/kitchen-sink/src/Api.ts +12 -6
  198. package/examples/kitchen-sink/src/Rpc.ts +7 -5
  199. package/examples/kitchen-sink/src/admin/Admin.ts +21 -10
  200. package/examples/kitchen-sink/src/admin/authenticate.ts +8 -4
  201. package/examples/kitchen-sink/src/admin/authorize.ts +13 -5
  202. package/examples/kitchen-sink/src/admin/grants.ts +5 -3
  203. package/examples/kitchen-sink/src/admin/handlers.ts +9 -5
  204. package/examples/kitchen-sink/src/agent.ts +14 -6
  205. package/examples/kitchen-sink/src/assistant/Assistant.ts +21 -15
  206. package/examples/kitchen-sink/src/assistant/conversations.ts +14 -9
  207. package/examples/kitchen-sink/src/assistant/demo-model.ts +2 -2
  208. package/examples/kitchen-sink/src/assistant/handlers.ts +42 -22
  209. package/examples/kitchen-sink/src/attachments/Attachments.ts +6 -6
  210. package/examples/kitchen-sink/src/attachments/download.ts +7 -6
  211. package/examples/kitchen-sink/src/attachments/handlers.ts +9 -4
  212. package/examples/kitchen-sink/src/attachments/store.ts +25 -18
  213. package/examples/kitchen-sink/src/browser.ts +10 -4
  214. package/examples/kitchen-sink/src/cli.ts +28 -12
  215. package/examples/kitchen-sink/src/config.ts +1 -1
  216. package/examples/kitchen-sink/src/identity/Identity.ts +24 -10
  217. package/examples/kitchen-sink/src/identity/authenticate.ts +19 -11
  218. package/examples/kitchen-sink/src/identity/authorize.ts +14 -4
  219. package/examples/kitchen-sink/src/identity/directory.ts +16 -7
  220. package/examples/kitchen-sink/src/identity/handlers.ts +1 -0
  221. package/examples/kitchen-sink/src/identity/members.ts +7 -2
  222. package/examples/kitchen-sink/src/identity/quota.ts +23 -20
  223. package/examples/kitchen-sink/src/issues/Issues.ts +91 -15
  224. package/examples/kitchen-sink/src/issues/assistant.ts +4 -2
  225. package/examples/kitchen-sink/src/issues/events.ts +46 -0
  226. package/examples/kitchen-sink/src/issues/export.ts +35 -0
  227. package/examples/kitchen-sink/src/issues/guide.ts +4 -3
  228. package/examples/kitchen-sink/src/issues/handlers.ts +20 -7
  229. package/examples/kitchen-sink/src/issues/store.ts +156 -67
  230. package/examples/kitchen-sink/src/issues/write-limit.ts +44 -18
  231. package/examples/kitchen-sink/src/memory.ts +11 -2
  232. package/examples/kitchen-sink/src/ops.ts +10 -5
  233. package/examples/kitchen-sink/src/request-policy.ts +21 -9
  234. package/examples/kitchen-sink/src/routes.ts +67 -27
  235. package/examples/kitchen-sink/src/sdk.ts +22 -12
  236. package/examples/kitchen-sink/src/seed.ts +15 -9
  237. package/examples/kitchen-sink/src/server.ts +24 -10
  238. package/examples/kitchen-sink/src/stdio.ts +9 -3
  239. package/examples/kitchen-sink/src/workspaces/Workspaces.ts +10 -7
  240. package/examples/kitchen-sink/src/workspaces/handlers.ts +3 -2
  241. package/examples/kitchen-sink/src/workspaces/registry.ts +16 -10
  242. package/examples/kitchen-sink/src/workspaces/tenant.ts +12 -5
  243. package/examples/kitchen-sink/test/admin/admin.test.ts +38 -23
  244. package/examples/kitchen-sink/test/assistant/assistant.test.ts +471 -299
  245. package/examples/kitchen-sink/test/attachments/attachments.test.ts +135 -94
  246. package/examples/kitchen-sink/test/identity/identity.test.ts +110 -82
  247. package/examples/kitchen-sink/test/identity/session.test.ts +65 -43
  248. package/examples/kitchen-sink/test/issues/assistant.test.ts +50 -31
  249. package/examples/kitchen-sink/test/issues/events.test.ts +273 -0
  250. package/examples/kitchen-sink/test/issues/export.test.ts +120 -0
  251. package/examples/kitchen-sink/test/issues/issues.test.ts +105 -75
  252. package/examples/kitchen-sink/test/request-policy.test.ts +108 -47
  253. package/examples/kitchen-sink/test/routes.test.ts +102 -57
  254. package/examples/kitchen-sink/test/support/as.ts +9 -0
  255. package/examples/kitchen-sink/test/support/scripted.ts +6 -5
  256. package/examples/kitchen-sink/test/support/services.ts +17 -0
  257. package/examples/live-rpc/README.md +33 -35
  258. package/examples/live-rpc/src/Rpc.ts +9 -5
  259. package/examples/live-rpc/src/browser.ts +39 -11
  260. package/examples/live-rpc/src/chat/Chat.ts +30 -16
  261. package/examples/live-rpc/src/chat/authorize.ts +2 -1
  262. package/examples/live-rpc/src/chat/events.ts +74 -0
  263. package/examples/live-rpc/src/chat/handlers.ts +13 -5
  264. package/examples/live-rpc/src/chat/room.ts +9 -5
  265. package/examples/live-rpc/src/chat/store.ts +51 -14
  266. package/examples/live-rpc/src/client.ts +22 -5
  267. package/examples/live-rpc/src/identity/Identity.ts +3 -3
  268. package/examples/live-rpc/src/identity/verifier.ts +8 -4
  269. package/examples/live-rpc/src/page.ts +58 -12
  270. package/examples/live-rpc/src/routes.ts +14 -10
  271. package/examples/live-rpc/src/server.ts +15 -5
  272. package/examples/live-rpc/test/browser.test.ts +127 -50
  273. package/examples/live-rpc/test/chat/chat.test.ts +215 -23
  274. package/examples/live-rpc/test/routes.test.ts +130 -46
  275. package/examples/live-rpc/test/server.test.ts +94 -33
  276. package/examples/mcp-tools/README.md +24 -38
  277. package/examples/mcp-tools/src/platform/fake-farm.ts +63 -24
  278. package/examples/mcp-tools/src/platform/print-farm.ts +59 -29
  279. package/examples/mcp-tools/src/printers/Printers.ts +18 -7
  280. package/examples/mcp-tools/src/printers/farm.ts +15 -5
  281. package/examples/mcp-tools/src/printers/handlers.ts +12 -6
  282. package/examples/mcp-tools/src/routes.ts +8 -6
  283. package/examples/mcp-tools/src/server.ts +12 -5
  284. package/examples/mcp-tools/src/stdio.ts +3 -2
  285. package/examples/mcp-tools/test/printers/printers.test.ts +132 -89
  286. package/examples/mcp-tools/test/routes.test.ts +140 -81
  287. package/examples/notes-service/README.md +30 -45
  288. package/examples/notes-service/src/Api.ts +1 -0
  289. package/examples/notes-service/src/browser.ts +15 -14
  290. package/examples/notes-service/src/cli.ts +8 -3
  291. package/examples/notes-service/src/commands.ts +46 -20
  292. package/examples/notes-service/src/identity/Identity.ts +2 -2
  293. package/examples/notes-service/src/identity/authorize.ts +6 -2
  294. package/examples/notes-service/src/identity/handlers.ts +3 -2
  295. package/examples/notes-service/src/identity/users.ts +2 -1
  296. package/examples/notes-service/src/identity/verifier.ts +12 -6
  297. package/examples/notes-service/src/notes/Notes.ts +46 -22
  298. package/examples/notes-service/src/notes/handlers.ts +18 -9
  299. package/examples/notes-service/src/notes/store.ts +70 -43
  300. package/examples/notes-service/src/routes.ts +7 -5
  301. package/examples/notes-service/src/server.ts +12 -5
  302. package/examples/notes-service/src/status/Status.ts +1 -1
  303. package/examples/notes-service/src/status/handlers.ts +5 -3
  304. package/examples/notes-service/src/stdio.ts +7 -3
  305. package/examples/notes-service/src/worker.ts +6 -3
  306. package/examples/notes-service/test/commands.test.ts +164 -91
  307. package/examples/notes-service/test/notes/as.ts +3 -1
  308. package/examples/notes-service/test/notes/idempotency.test.ts +97 -67
  309. package/examples/notes-service/test/notes/in-process.test.ts +26 -15
  310. package/examples/notes-service/test/notes/notes.test.ts +49 -31
  311. package/examples/notes-service/test/routes.test.ts +106 -71
  312. package/examples/notes-service/test/worker.test.ts +21 -13
  313. package/examples/quickstart/README.md +2 -6
  314. package/examples/quickstart/src/Api.ts +1 -0
  315. package/examples/quickstart/src/client.ts +6 -3
  316. package/examples/quickstart/src/greeting/Greeting.ts +1 -1
  317. package/examples/quickstart/src/greeting/handlers.ts +5 -2
  318. package/examples/quickstart/src/routes.ts +3 -2
  319. package/examples/quickstart/src/server.ts +6 -2
  320. package/examples/quickstart/test/routes.test.ts +3 -2
  321. package/package.json +10 -4
@@ -1,9 +1,10 @@
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
10
  /**
@@ -13,16 +14,18 @@ import { HttpApiSecurity } from "effect/http-api";
13
14
  * which holds the header and no request; a route of the host's own is authenticated by
14
15
  * `protect`.
15
16
  */
16
- 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>;
17
18
  /** An OAuth protected resource (RFC 9728), as `layer` publishes it. */
18
19
  export interface ProtectedResource {
19
20
  /**
20
- * Exact OAuth resource identifier: the public URL of the endpoint clients connect to, such as
21
- * `${url}/mcp`. Discovery is served at `/.well-known/oauth-protected-resource` followed by its
22
- * 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.
23
25
  *
24
26
  * **Gotchas**
25
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.
26
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.
27
30
  * - That it and `authorizationServers` are HTTPS, or loopback HTTP in development, is the deployment's to ensure: nothing checks it.
28
31
  */
@@ -33,7 +36,7 @@ export interface ProtectedResource {
33
36
  * Every scope the resource accepts, each an OAuth scope token, which a client requests when a
34
37
  * 401 names none.
35
38
  */
36
- readonly scopesSupported?: ReadonlyArray<string>;
39
+ readonly scopesSupported?: readonly string[];
37
40
  /**
38
41
  * The scopes every 401 names, each an OAuth scope token: what a client requests when it
39
42
  * authenticates, rather than every scope supported.
@@ -78,8 +81,8 @@ type DeclaredOf<D> = D extends {
78
81
  * naming scopes. `HttpServerResponse.toWeb` gives it as a web `Response`.
79
82
  */
80
83
  export declare const refusalResponse: <D extends Any = never>(error: Refusal | DeclaredOf<D>, options?: RefusalResponseOptions<D>) => HttpServerResponse.HttpServerResponse;
81
- /** What `make` takes besides the name and the identity. */
82
- export interface Options {
84
+ /** What `make` takes besides the name and the identity `A`. */
85
+ export interface Options<A = unknown> {
83
86
  /**
84
87
  * The one native scheme a caller proves the identity by, Bearer when left out: an API key in a
85
88
  * cookie, a header or the query, such as `HttpApiSecurity.apiKey({ in: "cookie", key: "session" })`,
@@ -100,7 +103,35 @@ export interface Options {
100
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.
101
104
  */
102
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;
103
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;
104
135
  type SecurityOf<O> = O extends {
105
136
  readonly security: infer S extends Security;
106
137
  } ? S : "security" extends keyof O ? NonNullable<O["security" & keyof O]> | HttpApiSecurity.Http : HttpApiSecurity.Http;
@@ -111,7 +142,7 @@ type SecurityOf<O> = O extends {
111
142
  * one name only for one declaration, whose values a server and a client may hold as `layer` says.
112
143
  * Guide: `docs/Authentication.md` in this package; in an application: `examples/notes-service/src/identity/`.
113
144
  */
114
- 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>>;
115
146
  /**
116
147
  * A descriptor's verifier: the credential its scheme decodes, as Effect's own decoder gives
117
148
  * it, to the identity, or a refusal, or an error the descriptor declares, `E`. Its services
@@ -154,6 +185,7 @@ type Resource = ProtectedResource | Effect.Effect<ProtectedResource | undefined,
154
185
  * **Gotchas**
155
186
  *
156
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.
157
189
  */
158
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?: {
159
191
  /** The OAuth protected resource to publish: its rules are on `LayerOptions.protectedResource`. */
@@ -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
8
  // src/authentication/Authentication.ts
9
- const bearerAuthorizationHeader = /^[ \t]*Bearer +(.*[^ \t])[ \t]*$/i;
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,
@@ -0,0 +1,370 @@
1
+ import { Codec, CodecOf, Fields, NoInput } from "./contract/codec.mjs";
2
+ import { Refusal } from "./contract/errors.mjs";
3
+ import { Gap, OccurrenceSchema } from "./contract/occurrence.mjs";
4
+ import { AnyEventImplementation, EventImplementation } from "./contract/feed.mjs";
5
+ import { Listed, LogStorage } from "./contract/log.mjs";
6
+ import { Events, KeyOptions, MakeEventsOptions, RevisionStorage, layerMemory, makeEvents } from "./contract/hub.mjs";
7
+ import { Protected } from "./contract/implementation.mjs";
8
+ import { Authorizer, Deferred as Deferred$1 } from "./contract/authorizer.mjs";
9
+ import { Options as Options$1 } from "./Action.mjs";
10
+ import { DateTime, Duration, Effect, Schema, Scope, Stream } from "effect";
11
+ declare const TypeId = "~@gjermundgaraba/effect-actions/Event";
12
+ /** What `make` needs to define an event type. */
13
+ export interface Options {
14
+ /**
15
+ * Who may subscribe: `Action.Anyone`, or the identity service a subscriber must have, as an
16
+ * action's `caller` says; the rules of an identity are on `Action.Options.caller`.
17
+ */
18
+ readonly caller: Options$1["caller"];
19
+ /** What happens when the event fires; a model and a CLI user read it beside the event's name. */
20
+ readonly description: string;
21
+ /**
22
+ * The subscription's arguments, a schema or struct fields, as an action's `input` is: they may
23
+ * filter the occurrences, shape them, or configure the subscription. Omit for an event without
24
+ * arguments. Its rules are `Action.Options.input`'s.
25
+ */
26
+ readonly input?: Codec | Fields | undefined;
27
+ /**
28
+ * What each occurrence carries, a schema or struct fields: an object, since MCP sends it as an
29
+ * occurrence's `data`.
30
+ */
31
+ readonly payload: Codec | Fields;
32
+ }
33
+ type OnlyOptions<O> = { readonly [K in Exclude<O extends unknown ? keyof O : never, keyof Options>]: never; };
34
+ type Rules<O> = Options extends O ? unknown : OnlyOptions<O>;
35
+ type InputOf<O> = O extends {
36
+ readonly input?: infer I;
37
+ } ? CodecOf<Extract<Exclude<I, undefined>, Codec | Fields>> | ([undefined] extends [I] ? typeof NoInput : never) : typeof NoInput;
38
+ type PayloadOf<O> = O extends {
39
+ readonly payload: infer P extends Codec | Fields;
40
+ } ? CodecOf<P> : never;
41
+ /**
42
+ * A pure contract for something that happens, bound to its source by `implement`: subscribers
43
+ * receive its occurrences, each carrying a `payload`, filtered and shaped by the subscription's
44
+ * `input`. Its `name`, `input` and `payload` are public: reuse them, as in
45
+ * `Schema.Array(NoteCreated.payload)`.
46
+ */
47
+ export interface Event<Name extends string, Input extends Codec, Payload extends Codec, Caller extends Options["caller"] = Options["caller"]> {
48
+ readonly [TypeId]: typeof TypeId;
49
+ readonly caller: Caller;
50
+ readonly name: Name;
51
+ readonly description: string;
52
+ /** The subscription's arguments as a schema, a `Schema.Struct` where fields were given. */
53
+ readonly input: Input;
54
+ /** What each occurrence carries, as a schema. */
55
+ readonly payload: Payload;
56
+ }
57
+ /** Any event type, with its schemas erased. */
58
+ export type Any = Event<string, Codec, Codec>;
59
+ /** Whether `u` is an event type `make` defined: a binding's list tells events from actions by it. */
60
+ export declare const isEvent: (u: unknown) => u is Any;
61
+ /**
62
+ * Define an event type: what a subscription takes, what each occurrence carries, and who may
63
+ * subscribe. A surface serving it names it by `name`.
64
+ * Guide: `docs/Event.md` in this package; in an application: `examples/build-events/src/builds/`.
65
+ *
66
+ * **Gotchas**
67
+ *
68
+ * - A name is lowercase segments joined by dots, `note.created`: its form is in the message `Invalid event name` throws; it is unique among the events each surface serves, apart from action names.
69
+ * - Surfaces match an implementation to a contract by object identity, never by name, as for actions.
70
+ */
71
+ export declare function make<const Name extends string, const O extends Options>(name: Name, options: O & NoInfer<Rules<O>>): Event<Name, InputOf<O>, PayloadOf<O>, O["caller"]>;
72
+ /**
73
+ * One occurrence as a source produces it: its `data`, and optionally the upstream's own id, which
74
+ * subscribers deduplicate by, and when it happened. Without them, a log source's occurrence
75
+ * takes its cursor as its id and the time it was appended, the same on every read; a check's
76
+ * takes a generated id and the time it is read.
77
+ */
78
+ export interface Emitted<E extends Any> {
79
+ readonly data: E["payload"]["Type"];
80
+ /**
81
+ * A stable id: give the upstream's own, such as a message or delivery id, so a retry or a replay repeats it.
82
+ *
83
+ * **Gotchas**
84
+ *
85
+ * - An id given again for another occurrence is dropped as a duplicate by a `watch` and by `Webhook.layerReceiver`, each keeping its last 1,000: an id from a counter that restarts with the process repeats, so leave it out rather than give one.
86
+ */
87
+ readonly eventId?: string | undefined;
88
+ readonly timestamp?: DateTime.Utc | undefined;
89
+ }
90
+ /**
91
+ * What a check answers for a cursor: the occurrences since it, and the cursor after them.
92
+ *
93
+ * **Gotchas**
94
+ *
95
+ * - A cursor the upstream no longer serves, such as one older than its retention, is answered with `truncated: true` and a fresh cursor: the subscriber then knows it missed occurrences.
96
+ * - A `nextPoll` under a second is read as a second, and one over 5 minutes, `Duration.infinity` included, as 5 minutes, by the server's loop and by a polling client alike.
97
+ */
98
+ export interface Page<E extends Any> {
99
+ readonly events: ReadonlyArray<Emitted<E>>;
100
+ readonly cursor: string;
101
+ readonly truncated?: boolean | undefined;
102
+ /** More occurrences are ready: the subscriber asks again without waiting. */
103
+ readonly hasMore?: boolean | undefined;
104
+ /** How long a subscriber waits before it asks again; defaults to 5 seconds. */
105
+ readonly nextPoll?: Duration.Input | undefined;
106
+ }
107
+ interface Hooked<E extends Any, R> {
108
+ /**
109
+ * What a subscription needs set up upstream while it lasts, such as joining a channel: a scoped
110
+ * Effect, whose finalizer tears it down, run as the subscriber. A stream's lasts as long as the
111
+ * stream, a poll subscription's until `lease` after its last poll, or, watched in process, until
112
+ * the watch ends, and a webhook subscription's until it is unsubscribed or lapses, set up again
113
+ * as `persist` restores it.
114
+ *
115
+ * **Gotchas**
116
+ *
117
+ * - Write it so it may run twice for one subscription: a lease does not outlive the process, so a poll after a restart sets it up again.
118
+ * - A poll subscription's runs in its first poll's context, that request included, held until its lease ends, where a webhook subscription's runs without the request: read what it needs from the request in middleware, which provides it as a service.
119
+ */
120
+ readonly subscription?: ((input: E["input"]["Type"]) => Effect.Effect<void, never, R | Scope.Scope>) | undefined;
121
+ /** How long a poll subscription's `subscription` lasts after its last poll; defaults to 5 minutes. */
122
+ readonly lease?: Duration.Input | undefined;
123
+ /**
124
+ * The key a subscriber of `input` is reached by, run as the subscriber, such as the room it
125
+ * follows: `Events.notify` and `Events.publish` given a key reach the subscribers of that key
126
+ * alone, and those it answers `undefined` for, who follow every key. Without it, every
127
+ * announcement reaches every subscriber.
128
+ *
129
+ * **Gotchas**
130
+ *
131
+ * - An occurrence a log's `emit` gives without a `key` reaches every subscriber, whatever key it follows: give each one its key.
132
+ */
133
+ readonly key?: ((input: E["input"]["Type"]) => string | undefined | Effect.Effect<string | undefined, never, R>) | undefined;
134
+ }
135
+ /**
136
+ * An event type read from an upstream that can be asked what happened since a cursor, such as a
137
+ * change feed or a history API: one function answers every subscriber, whatever the surface.
138
+ *
139
+ * **Gotchas**
140
+ *
141
+ * - An occurrence without `eventId` takes a fresh id at each read, and a page's occurrences before its last are read again from the cursor asked after a reconnect mid-page: give the upstream's ids, or a subscriber receives them again under ids no `watch` drops.
142
+ */
143
+ export interface Check<E extends Any, R = never> extends Hooked<E, R> {
144
+ /**
145
+ * What happened since `cursor`, as the subscriber's `input` selects it, run as the subscriber.
146
+ * A `null` cursor is a subscriber with no position: answer nothing, so it starts after the last
147
+ * occurrence. An event whose occurrence is the whole value, such as a status, is a `Current`.
148
+ */
149
+ readonly check: (input: E["input"]["Type"], cursor: string | null) => Effect.Effect<Page<E>, Refusal, R>;
150
+ readonly log?: never;
151
+ readonly current?: never;
152
+ }
153
+ /**
154
+ * An event type whose occurrence is the whole value, such as a status or a list small enough to
155
+ * send whole: a subscriber receives the value as it is first, then again after each
156
+ * `Events.notify` that reaches it, never a change it missed one by one. The library keeps a
157
+ * revision per key, which each such notify moves, as the cursor and each occurrence's id.
158
+ *
159
+ * **Gotchas**
160
+ *
161
+ * - A notify without a change sends the value again, under a new id: notify after a write that changed it.
162
+ * - The revision is read before `current`, so a value written between them is sent again at the next read, never missed: write the value, then notify.
163
+ */
164
+ export interface Current<E extends Any, R = never> extends Hooked<E, R> {
165
+ /** The value as it is, as the subscriber's `input` selects it, run as the subscriber. */
166
+ readonly current: (input: E["input"]["Type"]) => Effect.Effect<E["payload"]["Type"], Refusal, R>;
167
+ readonly check?: never;
168
+ readonly log?: never;
169
+ }
170
+ /**
171
+ * An event type whose occurrences the library keeps as they are announced, for an upstream with
172
+ * no history of its own: `Events.publish` appends each, and `log.emit`, where given, a stream of
173
+ * them. Each subscriber receives the occurrences its `match` accepts, shaped by its `transform`.
174
+ *
175
+ * **Gotchas**
176
+ *
177
+ * - A cursor is only good where the log that minted it lives, for as long as it keeps its occurrences: after a restart, or once they are dropped, a subscriber resuming from it receives `truncated` and a fresh cursor.
178
+ * - A subscriber reading slower than occurrences arrive falls behind the log like a resumed one: it receives `truncated`, never a backlog held for it alone.
179
+ * - An `emit` stream is consumed from startup, in the server's scope, by one build at a time, until it ends, even past its build's release: a failure ends it and is reported, an end is logged, and the next held build's stream, if any, takes over; the log then holds what was published.
180
+ */
181
+ export interface Log<E extends Any, R = never> extends Hooked<E, R> {
182
+ /** What the log keeps, and the stream feeding it besides `Events.publish`; `{}` for none. */
183
+ readonly log: LogOptions<E>;
184
+ /** Whether a subscriber of `input` receives an occurrence carrying `data`; defaults to every one. */
185
+ readonly match?: ((input: E["input"]["Type"], data: E["payload"]["Type"]) => boolean | Effect.Effect<boolean, never, R>) | undefined;
186
+ /** The data a subscriber of `input` receives in place of `data`, such as one with fields removed. */
187
+ readonly transform?: ((input: E["input"]["Type"], data: E["payload"]["Type"]) => E["payload"]["Type"] | Effect.Effect<E["payload"]["Type"], never, R>) | undefined;
188
+ readonly check?: never;
189
+ readonly current?: never;
190
+ }
191
+ /** What a log source keeps, and what feeds it besides `Events.publish`. */
192
+ export interface LogOptions<E extends Any> {
193
+ /**
194
+ * A stream of occurrences, such as a message bus's, appended as they arrive from startup, each
195
+ * reaching the subscribers of its `key`, as `Events.publish` given one does.
196
+ */
197
+ readonly emit?: Stream.Stream<Emitted<E> & KeyOptions> | undefined;
198
+ /**
199
+ * How long an occurrence is kept for subscribers resuming from a cursor; defaults to 10 minutes.
200
+ * The first build attached to its log sets it, and later builds share that log.
201
+ */
202
+ readonly maxAge?: Duration.Input | undefined;
203
+ /**
204
+ * How many occurrences are kept at most, of every key together; defaults to 10,000.
205
+ *
206
+ * **Gotchas**
207
+ *
208
+ * - A busy key drops a quiet one's occurrences too: a subscriber of the quiet key resuming after them receives a gap, though none of its own was dropped.
209
+ */
210
+ readonly maxEvents?: number | undefined;
211
+ }
212
+ /** Where an event type's occurrences come from: exactly one of `check`, `current` or `log`. */
213
+ export type Source<E extends Any, R = never> = Check<E, R> | Current<E, R> | Log<E, R>;
214
+ /**
215
+ * One occurrence as a subscriber receives it: `eventId`, the source's id or one generated, which
216
+ * a subscriber deduplicates by; `cursor`, where to resume after it, `null` for an event type that
217
+ * cannot be resumed.
218
+ */
219
+ export type Occurrence<E extends Any> = OccurrenceSchema<E>["Type"];
220
+ /** What a subscriber receives: occurrences, and gaps where it missed some. */
221
+ export type Received<E extends Any> = Occurrence<E> | Gap;
222
+ declare const NotServed_base: Schema.Class<NotServed, Schema.TaggedStruct<"NotServed", {}>, import("effect/Cause").YieldableError & {
223
+ readonly brand: unique symbol;
224
+ }>;
225
+ /** The server serves no event type of that name, or no longer does. */
226
+ export declare class NotServed extends NotServed_base {}
227
+ declare const Unsupported_base: Schema.Class<Unsupported, Schema.TaggedStruct<"Unsupported", {
228
+ readonly feature: Schema.String;
229
+ readonly value: Schema.optional<Schema.String>;
230
+ }>, import("effect/Cause").YieldableError & {
231
+ readonly brand: unique symbol;
232
+ }>;
233
+ /**
234
+ * The server serves the event type, but not as asked: `feature` names what it does not offer,
235
+ * such as `deliveryMode`, and `value` which one, such as `push`.
236
+ */
237
+ export declare class Unsupported extends Unsupported_base {}
238
+ /**
239
+ * Why a webhook's callback endpoint failed, in SEP-3415's categories: it refused the connection
240
+ * or was unreachable, timed out, failed TLS, answered a status from 300 to 499 (`http_4xx`, as
241
+ * the categories name no 3xx) or a 5xx, or did not echo the verification challenge.
242
+ */
243
+ export declare const CallbackCategory: Schema.Literals<readonly ["connection_refused", "timeout", "tls_error", "http_4xx", "http_5xx", "challenge_failed"]>;
244
+ export type CallbackCategory = typeof CallbackCategory.Type;
245
+ declare const CallbackFailed_base: Schema.Class<CallbackFailed, Schema.TaggedStruct<"CallbackFailed", {
246
+ readonly category: Schema.Literals<readonly ["connection_refused", "timeout", "tls_error", "http_4xx", "http_5xx", "challenge_failed"]>;
247
+ }>, import("effect/Cause").YieldableError & {
248
+ readonly brand: unique symbol;
249
+ }>;
250
+ /** A webhook subscription's callback endpoint failed its verification, or was unreachable. */
251
+ export declare class CallbackFailed extends CallbackFailed_base {}
252
+ declare const NotSubscribed_base: Schema.Class<NotSubscribed, Schema.TaggedStruct<"NotSubscribed", {}>, import("effect/Cause").YieldableError & {
253
+ readonly brand: unique symbol;
254
+ }>;
255
+ /** No webhook subscription matches the one an unsubscribe names. */
256
+ export declare class NotSubscribed extends NotSubscribed_base {}
257
+ declare const Reason: Schema.Union<readonly [typeof NotServed, typeof Unsupported, typeof CallbackFailed, typeof NotSubscribed]>;
258
+ declare const SubscriptionError_base: Schema.Class<SubscriptionError, Schema.TaggedStruct<"SubscriptionError", {
259
+ readonly event: Schema.String;
260
+ readonly reason: Schema.Union<readonly [typeof NotServed, typeof Unsupported, typeof CallbackFailed, typeof NotSubscribed]>;
261
+ readonly message: Schema.String;
262
+ }>, import("effect/Cause").YieldableError & {
263
+ readonly brand: unique symbol;
264
+ }>;
265
+ /**
266
+ * A subscription that cannot start or go on: `reason` says why, so a subscriber branches on it
267
+ * with `Effect.catchReason("SubscriptionError", "NotServed", ...)`; `message` names the event and
268
+ * the reason. A refusal is `Action.Unauthenticated` or `Action.Forbidden`, and input that does not
269
+ * decode `Action.InvalidInput`, as for an action.
270
+ */
271
+ export declare class SubscriptionError extends SubscriptionError_base {
272
+ /** The error for `event` failing with `reason`, its message built from both. */
273
+ static of(event: string, reason: (typeof Reason)["Type"]): SubscriptionError;
274
+ }
275
+ /**
276
+ * Where a watch keeps its cursor: what it kept under a key, and keeping a new one; a failure `E`
277
+ * of either ends the watch with it.
278
+ */
279
+ export interface CursorStore<E = never> {
280
+ readonly get: (key: string) => Effect.Effect<string | undefined, E>;
281
+ readonly set: (key: string, cursor: string) => Effect.Effect<void, E>;
282
+ }
283
+ declare const WebhookDelivery_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
284
+ readonly _tag: "WebhookDelivery";
285
+ } & import("effect/Pipeable").Pipeable;
286
+ /**
287
+ * A watch delivered by webhook, which `Webhook.delivery` makes: the server POSTs each occurrence
288
+ * to the route `Webhook.layerReceiver` serves in the watching process, reached under `baseUrl`,
289
+ * for as long as the watch is read, each grant asking for `ttl`.
290
+ */
291
+ export declare class WebhookDelivery extends WebhookDelivery_base<{
292
+ /**
293
+ * The https URL the server reaches the watching process at: its `layerReceiver` route's path
294
+ * is appended to it, `https://notifier.example.com` with `/hooks/builds` delivering to
295
+ * `https://notifier.example.com/hooks/builds`.
296
+ */
297
+ readonly baseUrl: string;
298
+ /** The lifetime each subscribe asks for; omitted, the server's longest. */
299
+ readonly ttl?: Duration.Input | undefined;
300
+ }> {}
301
+ /**
302
+ * How a client watches an event type; `E` is what its `resume` store fails with.
303
+ *
304
+ * **Gotchas**
305
+ *
306
+ * - Each run of a watch's stream starts from these options, a retry or a repeat included: to go on where a failed run stopped, give `resume`, which every run reads.
307
+ * - `resume` keeps an occurrence's cursor once the next is pulled, so a consumer stopped while handling one receives it again: deduplicate by `eventId` where that matters.
308
+ */
309
+ export interface WatchOptions<E = never> {
310
+ /** Where to start: a cursor an occurrence or a gap carried; defaults to now. */
311
+ readonly cursor?: string | null | undefined;
312
+ /**
313
+ * `"stream"` holds one subscription open, `"poll"` asks again and again, and a
314
+ * `Webhook.delivery` has the server POST each occurrence to a receiver this process serves,
315
+ * which the watch then requires; left out, a watch streams where its surface can and polls
316
+ * where the server serves the event by poll alone.
317
+ */
318
+ readonly mode?: "stream" | "poll" | WebhookDelivery | undefined;
319
+ /**
320
+ * Where to keep the cursor between watches, such as across restarts: it starts from the cursor
321
+ * kept under `key`, when no `cursor` is given, and keeps each new one there. Effect's
322
+ * `KeyValueStore` is one: `{ store: yield* KeyValueStore.KeyValueStore, key: "builds" }`.
323
+ */
324
+ readonly resume?: {
325
+ readonly store: CursorStore<E>;
326
+ readonly key: string;
327
+ } | undefined;
328
+ }
329
+ /**
330
+ * Authorization for protected event types: it refuses, and fails with nothing else. It runs for
331
+ * every subscription of a protected event type, before its source.
332
+ */
333
+ export type Authorize<E extends Any, R = never> = (event: E) => Effect.Effect<void, Refusal, R>;
334
+ /** Every authenticated subscriber may subscribe to a protected event type. */
335
+ export declare const allowAll: Authorize<Any>;
336
+ type Target = Any | readonly Any[];
337
+ type EventsOf<T extends Target> = T extends readonly Any[] ? T[number] : Extract<T, Any>;
338
+ type TakesNoAuthorize<T extends Target> = [Protected<EventsOf<T>>] extends [never] ? unknown : "A protected event takes { authorize }: your rule, or Event.allowAll";
339
+ type NamesOf<T extends Target> = EventsOf<T>["name"];
340
+ type SourcesOf<T extends Target, R, RequestServices> = T extends Any ? Check<T, R> | Current<T, R> | Log<T, R> : { readonly [K in keyof RequestServices]: K extends NamesOf<T> ? Check<Extract<EventsOf<T>, {
341
+ readonly name: K;
342
+ }>, RequestServices[K]> | Current<Extract<EventsOf<T>, {
343
+ readonly name: K;
344
+ }>, RequestServices[K]> | Log<Extract<EventsOf<T>, {
345
+ readonly name: K;
346
+ }>, RequestServices[K]> : never; };
347
+ type Implemented<T extends Target, RequestServices, R, EX, RX, RA, EAX, RAX> = EventImplementation<EventsOf<T>, { readonly [K in NamesOf<T>]: Exclude<T extends readonly Any[] ? RequestServices[K & keyof RequestServices] : R, Scope.Scope>; }, Deferred$1<EX>, Deferred$1<Exclude<RX, Scope.Scope>> | Events, Exclude<RA, Scope.Scope>, Deferred$1<EAX>, Deferred$1<Exclude<RAX, Scope.Scope>>>;
348
+ /**
349
+ * Bind sources to event types, behind authorization: one event type and its source, or a list
350
+ * and a record of sources keyed by event name, given directly or built by an Effect whose
351
+ * services are startup requirements, while what a source yields per subscriber is a request-time
352
+ * requirement. `authorize` is required when the list holds a protected event type. Every surface
353
+ * serving events takes the implementation it returns, beside action implementations in one list.
354
+ *
355
+ * **Gotchas**
356
+ *
357
+ * - The builder runs once per layer graph, however many surfaces serve it, as the first event type a surface serves attaches; a log source's `emit` starts being consumed once its event type is served or watched, and an event type no surface serves starts nothing.
358
+ * - `check`, `current`, `match`, `transform`, `key` and `subscription` run as the subscriber: what they yield, such as the caller, is resolved per subscription.
359
+ * - A source holds exactly one of `check`, `current` or `log`, checked as a surface serving its event type builds, which fails otherwise: a source of an event type no surface serves is not checked.
360
+ * - Serving or watching an event type needs `Event.Events`, which its source attaches to as it builds: provide `Event.layerMemory` once, around the handlers announcing and the surfaces serving.
361
+ * - An occurrence whose data breaks the event's payload is reported, and every surface sends a `Gap` in its place, a poll's page `truncated`: the rest of its page is still delivered.
362
+ * - A read failing with no refusal is reported once, and the subscription reads on from where it reached: a stream a server holds, a webhook's and `Action.client`'s `watch` after 5 seconds, a client's poll, which a server answers with an error, after its reconnect backoff, from 1 second doubling to 30; a refusal ends the subscription.
363
+ */
364
+ export declare function implement<const T extends Target, RequestServices extends (T extends readonly Any[] ? { readonly [K in NamesOf<T>]: unknown; } : never), R = never, EX = never, RX = never, RA = never, EAX = never, RAX = never>(events: T, sources: SourcesOf<T, R, RequestServices> | Effect.Effect<SourcesOf<T, R, RequestServices>, EX, RX>, options: [Protected<EventsOf<T>>] extends [never] ? {
365
+ readonly authorize?: "A public-only target takes no authorize";
366
+ } | undefined : {
367
+ readonly authorize: Authorizer<Protected<EventsOf<T>>, RA, EAX, RAX>;
368
+ }): Implemented<T, RequestServices, R, EX, RX, RA, EAX, RAX>;
369
+ export declare function implement<const T extends Target, RequestServices extends (T extends readonly Any[] ? { readonly [K in NamesOf<T>]: unknown; } : never), R = never, EX = never, RX = never>(events: T & TakesNoAuthorize<T>, sources: SourcesOf<T, R, RequestServices> | Effect.Effect<SourcesOf<T, R, RequestServices>, EX, RX>): Implemented<T, RequestServices, R, EX, RX, never, never, never>;
370
+ export { type AnyEventImplementation as AnyImplementation, Events, Gap, type EventImplementation as Implementation, type KeyOptions, type Listed, type LogStorage, type MakeEventsOptions, type RevisionStorage, layerMemory, makeEvents };