@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
@@ -15,7 +15,9 @@ declare const InvalidInput_base: Schema.Class<InvalidInput, Schema.TaggedStruct<
15
15
  readonly path: Schema.$Array<Schema.Union<readonly [Schema.String, Schema.Finite]>>;
16
16
  readonly message: Schema.String;
17
17
  }>>>;
18
- }>, import("effect/Cause").YieldableError>;
18
+ }>, import("effect/Cause").YieldableError & {
19
+ readonly brand: unique symbol;
20
+ }>;
19
21
  /**
20
22
  * The request's input does not decode against the action's input: its message is the schema's
21
23
  * own description of what is wrong, and `issues` the same an issue at a time, for a caller that
@@ -44,7 +46,9 @@ export declare class InvalidInput extends InvalidInput_base {
44
46
  }
45
47
  declare const Unauthenticated_base: Schema.Class<Unauthenticated, Schema.TaggedStruct<"Unauthenticated", {
46
48
  readonly message: Schema.withConstructorDefault<Schema.String>;
47
- }>, import("effect/Cause").YieldableError>;
49
+ }>, import("effect/Cause").YieldableError & {
50
+ readonly brand: unique symbol;
51
+ }>;
48
52
  /**
49
53
  * The caller is not authenticated: authentication refuses the credential, or an implementation's
50
54
  * `authorize` or a handler fails with it.
@@ -62,7 +66,9 @@ export declare class Unauthenticated extends Unauthenticated_base {
62
66
  declare const Forbidden_base: Schema.Class<Forbidden, Schema.TaggedStruct<"Forbidden", {
63
67
  readonly message: Schema.withConstructorDefault<Schema.String>;
64
68
  readonly scopes: Schema.optionalKey<Schema.NonEmptyArray<Schema.String>>;
65
- }>, import("effect/Cause").YieldableError>;
69
+ }>, import("effect/Cause").YieldableError & {
70
+ readonly brand: unique symbol;
71
+ }>;
66
72
  /**
67
73
  * The caller may not run this action: authentication refuses the credential, an
68
74
  * implementation's `authorize` refuses, or a handler fails with it. `scopes`, when given, are every OAuth scope the call needs: a refusal naming
@@ -55,7 +55,7 @@ var InvalidInput = class InvalidInput extends Schema.TaggedError()("InvalidInput
55
55
  * - From another service's client, such as `ActionHttp.client`, one passed on tells a caller whose token is valid to sign in again: map it to an error the action declares, or to a defect.
56
56
  */
57
57
  var Unauthenticated = class extends Schema.TaggedError()("Unauthenticated", { message: defaultedMessage("Authentication is required.") }, { httpApiStatus: 401 }) {};
58
- const scopeToken = /^[\x21\x23-\x5B\x5D-\x7E]+$/;
58
+ const scopeToken = /^[\u0021\u0023-\u005B\u005D-\u007E]+$/;
59
59
  const ScopeToken = Schema.String.check(Schema.isPattern(scopeToken));
60
60
  /**
61
61
  * The caller may not run this action: authentication refuses the credential, an
@@ -0,0 +1,127 @@
1
+ import { Anyone } from "./caller.mjs";
2
+ import { Fed, Page } from "./pages.mjs";
3
+ import { Any } from "../Event.mjs";
4
+ import { ErasedAuthorize, ErasedValue, Memoized, Protected } from "./implementation.mjs";
5
+ import { Context, Duration, Effect, Layer, Stream } from "effect";
6
+ /**
7
+ * An implemented event type, behind its authorizer, as a surface serves it: a poll, whose
8
+ * subscription `subscriber` names, such as its principal, and a stream, which lasts as long as it
9
+ * is read. Both authenticate and authorize the subscriber first, and run as it.
10
+ */
11
+ export interface Feed {
12
+ readonly event: Any;
13
+ /**
14
+ * Releases the `subscription` hook `subscriber`'s polls of `input` hold: present exactly where
15
+ * its source has a hook, held per subscriber.
16
+ */
17
+ readonly release?: ((input: ErasedValue, subscriber: string) => Effect.Effect<void>) | undefined;
18
+ /**
19
+ * Authenticates the subscriber, checks `input` by `Action.InputCheck` and authorizes it, as
20
+ * `poll` and `stream` do first.
21
+ */
22
+ readonly admit: (input: ErasedValue) => Effect.Effect<void, unknown, unknown>;
23
+ readonly poll: (input: ErasedValue, cursor: string | null, options: PollOptions) => Effect.Effect<Page, unknown, unknown>;
24
+ /**
25
+ * Admits the subscriber, then reads the feed from a cursor, as often as a subscription
26
+ * reopens it.
27
+ */
28
+ readonly stream: (input: ErasedValue) => Effect.Effect<(cursor: string | null) => Stream.Stream<Fed, unknown, unknown>, unknown, unknown>;
29
+ }
30
+ /** Who polls, and how many occurrences at most a log source's page holds. */
31
+ export interface PollOptions {
32
+ readonly subscriber?: string | undefined;
33
+ /** Capped at the page a log source reads anyway; a check's page is answered whole. */
34
+ readonly maxEvents?: number | undefined;
35
+ }
36
+ /** What reads one event type's source, before its subscriber is admitted. */
37
+ export interface Run extends Omit<Feed, "event" | "stream" | "admit"> {
38
+ readonly stream: (input: ErasedValue, cursor: string | null) => Stream.Stream<Fed, unknown, unknown>;
39
+ }
40
+ /**
41
+ * Each event of an implementation with what runs its source, each built on its own over the
42
+ * implementation's one builder, so a selection runs only the sources of the events it holds.
43
+ */
44
+ export type Running<E = unknown, R = unknown> = ReadonlyArray<readonly [Any, Memoized<Run, E, R>]>;
45
+ /**
46
+ * Event types bound to their sources and their authorizer: everything one `Event.implement` call
47
+ * produced. Its type parameters may be written out, as a package emitting declarations prints
48
+ * them: `E` its event types, `R` what its sources read per subscription, by event name, `EX` and `RX` the
49
+ * failures and services of its builder, `RA` what its authorizer reads per subscription, and
50
+ * `EAX` and `RAX` the failures and services of the authorizer's builder.
51
+ */
52
+ export declare class EventImplementation<E extends Any, R, EX, RX, RA = never, EAX = never, RAX = never> {
53
+ #private;
54
+ /** Tells an event implementation from an action implementation among what a surface is given. */
55
+ readonly _tag = "EventImplementation";
56
+ /** Type-only: what its sources read per subscription, by event name. */
57
+ readonly "~eventRequest": R;
58
+ /** Type-only: what building its sources fails with. */
59
+ readonly "~eventBuildError": EX;
60
+ /** Type-only: what building its sources needs. */
61
+ readonly "~eventBuildServices": RX;
62
+ /** Type-only: what its authorizer reads per subscription. */
63
+ readonly "~eventAuthorizeRequest": RA;
64
+ /** Type-only: what building its authorizer fails with. */
65
+ readonly "~eventAuthorizeBuildError": EAX;
66
+ /** Type-only: what building its authorizer needs. */
67
+ readonly "~eventAuthorizeBuildServices": RAX;
68
+ /** The event types this implementation answers. */
69
+ readonly events: readonly E[];
70
+ constructor(events: readonly E[], running: Running<EX, RX>, authorizer: Memoized<ErasedAuthorize<Any>, EAX, RAX>);
71
+ /** Select event types while retaining their sources, authorization and shared builder. */
72
+ static share(events: readonly Any[], app: AnyEventImplementation): AnyEventImplementation;
73
+ /** The builders of `app`, its sources' and its authorizer's, as one layer. */
74
+ static layerOf(app: AnyEventImplementation): Layer.Layer<never, unknown, unknown>;
75
+ /** The event types of `app` with what runs them, behind its authorizer. */
76
+ static feedsOf(app: AnyEventImplementation): Effect.Effect<readonly Feed[], never, unknown>;
77
+ /** What runs each event type `app` holds. */
78
+ private static held;
79
+ private static own;
80
+ }
81
+ /** Any event implementation, with its events and channels erased. */
82
+ export type AnyEventImplementation<E extends Any = Any> = EventImplementation<E, unknown, unknown, unknown, unknown, unknown, unknown>;
83
+ /** The event types of the implementations `App`. */
84
+ export type EventOf<App> = App extends {
85
+ readonly events: ReadonlyArray<infer E extends Any>;
86
+ } ? E : never;
87
+ type IdentityOf<K> = K extends Context.Key<infer I, unknown> ? I : never;
88
+ type RequestOf<R, Name> = Name extends keyof R ? R[Name] : never;
89
+ /**
90
+ * What a subscription of `E` owes per subscriber where the implementations `App` serve it: its
91
+ * sources' services, and for a protected event its authorizer's and the subscriber's identity.
92
+ */
93
+ export type EventRequestOf<App, E extends Any> = App extends {
94
+ readonly events: ReadonlyArray<infer Held>;
95
+ readonly "~eventRequest": infer R;
96
+ readonly "~eventAuthorizeRequest": infer RA;
97
+ } ? E extends Held ? RequestOf<R, E["name"]> | (E["caller"] extends typeof Anyone ? never : RA | IdentityOf<E["caller"]>) : never : never;
98
+ /**
99
+ * The event types of `App` a surface given the event types `Listed` serves, as `selectEvents`
100
+ * selects them by identity: one whose type either is assignable to, or is assigned by, one
101
+ * listed, so an implementation or a list typed wider than its event types still counts.
102
+ */
103
+ export type ServingEvents<App, Listed extends Any> = EventOf<App> extends (infer E extends Any) ? E extends unknown ? [E] extends [Listed] ? E : [Extract<Listed, E>] extends [never] ? never : E : never : never;
104
+ /** The event implementations among `App` holding an event type of `Listed`: those a surface builds. */
105
+ export type HoldingEvents<App, Listed extends Any> = App extends unknown ? [ServingEvents<App, Listed>] extends [never] ? never : App : never;
106
+ type ServesProtected<App, Listed extends Any> = [Protected<ServingEvents<App, Listed>>] extends [never] ? false : true;
107
+ /**
108
+ * What a remote surface's requests owe the event implementations `App` per subscription where it
109
+ * serves their event types of `Listed`: their sources' services, and their authorizer's where a
110
+ * served one is protected; the identity is the endpoint's own to provide.
111
+ */
112
+ export type EventServedRequest<App, Listed extends Any = EventOf<App>> = App extends {
113
+ readonly "~eventRequest": infer R;
114
+ readonly "~eventAuthorizeRequest": infer RA;
115
+ } ? [ServingEvents<App, Listed>] extends [never] ? never : EndpointRequest<R, ServingEvents<App, Listed>> | (ServesProtected<App, Listed> extends true ? Exclude<RA, IdentityOf<Protected<ServingEvents<App, Listed>>["caller"]>> : never) : never;
116
+ type EndpointRequest<R, E extends Any> = E extends unknown ? E["caller"] extends typeof Anyone ? RequestOf<R, E["name"]> : Exclude<RequestOf<R, E["name"]>, IdentityOf<E["caller"]>> : never;
117
+ /** What building the event implementations `App` fails with, where their event types of `Listed` are served. */
118
+ export type EventBuildError<App, Listed extends Any = EventOf<App>> = App extends {
119
+ readonly "~eventBuildError": infer EX;
120
+ readonly "~eventAuthorizeBuildError": infer EAX;
121
+ } ? [ServingEvents<App, Listed>] extends [never] ? never : EX | (ServesProtected<App, Listed> extends true ? EAX : never) : never;
122
+ /** What building the event implementations `App` reads at startup, where their event types of `Listed` are served. */
123
+ export type EventBuildServices<App, Listed extends Any = EventOf<App>> = App extends {
124
+ readonly "~eventBuildServices": infer RX;
125
+ readonly "~eventAuthorizeBuildServices": infer RAX;
126
+ } ? [ServingEvents<App, Listed>] extends [never] ? never : RX | (ServesProtected<App, Listed> extends true ? RAX : never) : never;
127
+ export {};
@@ -0,0 +1,249 @@
1
+ import { Anyone } from "./caller.mjs";
2
+ import { Unauthenticated } from "./errors.mjs";
3
+ import { checkInput } from "./input-check.mjs";
4
+ import { assertKnown } from "./names.mjs";
5
+ import { builderLayer, memoized, notMadeHere, selectHeld } from "./implementation.mjs";
6
+ import { jsonRoundTrip } from "./call.mjs";
7
+ import { canonicalInput } from "./canonical.mjs";
8
+ import { reported } from "./reported.mjs";
9
+ import { Events, attachmentsOf, reaches } from "./hub.mjs";
10
+ import { makeLeases } from "./lease.mjs";
11
+ import { AnyOccurrence, Gap } from "./occurrence.mjs";
12
+ import { fedOf, pagedFrom } from "./pages.mjs";
13
+ import { Array, DateTime, Duration, Effect, Layer, Option, Predicate, Stream } from "effect";
14
+ // src/contract/feed.ts
15
+ const defaultNextPoll = Duration.seconds(5);
16
+ const longestNextPoll = Duration.minutes(5);
17
+ const logPageSize = 100;
18
+ const pageLimit = (maxEvents) => Math.min(Math.max(Math.floor(maxEvents), 1), logPageSize);
19
+ const occurrencesOf = (event) => {
20
+ const sent = jsonRoundTrip(event.payload);
21
+ return (emitted, cursor) => Effect.matchCauseEffect(sent(emitted.data), {
22
+ onFailure: (cause) => Effect.as(Effect.annotateLogs(reported(cause), "event", event.name), Gap.make({ cursor })),
23
+ onSuccess: ({ value }) => Effect.map(DateTime.now, (now) => AnyOccurrence.make({
24
+ eventId: emitted.eventId ?? globalThis.crypto.randomUUID(),
25
+ name: event.name,
26
+ timestamp: emitted.timestamp ?? now,
27
+ data: value,
28
+ cursor
29
+ }))
30
+ });
31
+ };
32
+ const isOccurrence = (fed) => Predicate.isTagged(fed, "Occurrence");
33
+ /** A poll's page of `items`: their occurrences, `truncated` too where a gap stands for one. */
34
+ const pageOf = (items, { truncated, ...page }) => {
35
+ const events = items.filter(isOccurrence);
36
+ return {
37
+ ...page,
38
+ events,
39
+ truncated: truncated || events.length < items.length
40
+ };
41
+ };
42
+ const lifted = (value) => Effect.isEffect(value) ? value : Effect.succeed(value);
43
+ /** The key a subscriber of `input` is reached by, as the source's `key` answers it. */
44
+ const keyOf = (source, input) => source.key === void 0 ? Effect.undefined : lifted(source.key(input));
45
+ const leaseKey = (event, subscriber, input) => Effect.map(canonicalInput(event.input, input), (encoded) => `${subscriber ?? ""}\u0000${encoded}`);
46
+ const answeredPage = ({ answered, items }) => pageOf(items, {
47
+ cursor: answered.cursor,
48
+ truncated: answered.truncated === true,
49
+ hasMore: answered.hasMore === true,
50
+ nextPoll: Duration.min(Duration.fromInputUnsafe(answered.nextPoll ?? defaultNextPoll), longestNextPoll)
51
+ });
52
+ const checkRun = (event, source, { wakes, polls }) => {
53
+ const occurrenceOf = occurrencesOf(event);
54
+ const read = (input, cursor) => Effect.flatMap(source.check(input, cursor), (answered) => Effect.map(Effect.forEach(answered.events, (emitted, index) => occurrenceOf(emitted, index === answered.events.length - 1 ? answered.cursor : cursor)), (items) => ({
55
+ answered,
56
+ items
57
+ })));
58
+ return {
59
+ poll: (input, cursor) => Effect.map(read(input, cursor), answeredPage),
60
+ stream: (input, cursor) => Stream.unwrap(Effect.map(Effect.flatMap(keyOf(source, input), wakes), (woken) => pagedFrom((at) => Effect.map(read(input, at), (delivered) => ({
61
+ ...answeredPage(delivered),
62
+ fed: fedOf({
63
+ items: delivered.items,
64
+ truncated: delivered.answered.truncated === true,
65
+ from: at,
66
+ to: delivered.answered.cursor
67
+ })
68
+ })), cursor, woken, polls)))
69
+ };
70
+ };
71
+ const currentRun = (event, source, checking) => checkRun(event, {
72
+ key: source.key,
73
+ check: (input, cursor) => Effect.gen(function* () {
74
+ const at = yield* checking.revision(yield* keyOf(source, input));
75
+ if (cursor === at) return {
76
+ cursor: at,
77
+ events: []
78
+ };
79
+ return {
80
+ cursor: at,
81
+ events: [{
82
+ eventId: at,
83
+ data: yield* source.current(input)
84
+ }]
85
+ };
86
+ })
87
+ }, checking);
88
+ const logRun = (event, source, log) => {
89
+ const occurrenceOf = occurrencesOf(event);
90
+ const delivered = (input, key, { cursor, at, value: logged }) => Effect.gen(function* () {
91
+ if (logged === void 0) {
92
+ yield* Effect.annotateLogs(Effect.logWarning(`An entry of the log of ${event.name} could not be read: sent as a gap`), "event", event.name);
93
+ return Gap.make({ cursor });
94
+ }
95
+ const emitted = logged.value;
96
+ if (!reaches(logged.key, key)) return;
97
+ if (source.match !== void 0 && !(yield* lifted(source.match(input, emitted.data)))) return;
98
+ const data = source.transform === void 0 ? emitted.data : yield* lifted(source.transform(input, emitted.data));
99
+ return yield* occurrenceOf({
100
+ data,
101
+ eventId: emitted.eventId ?? cursor,
102
+ timestamp: emitted.timestamp ?? DateTime.makeUnsafe(at)
103
+ }, cursor);
104
+ });
105
+ const received = (input, key, read) => Effect.map(Effect.forEach(read.entries, (entry) => delivered(input, key, entry)), (items) => items.filter(Predicate.isNotUndefined));
106
+ return {
107
+ poll: (input, cursor, { maxEvents = logPageSize }) => Effect.flatMap(Effect.all([keyOf(source, input), log.read(cursor, pageLimit(maxEvents))]), ([key, read]) => Effect.map(received(input, key, read), (items) => pageOf(items, {
108
+ cursor: read.cursor,
109
+ truncated: read.truncated,
110
+ hasMore: read.hasMore,
111
+ nextPoll: defaultNextPoll
112
+ }))),
113
+ stream: (input, cursor) => Stream.unwrap(Effect.map(keyOf(source, input), (key) => log.follow(cursor, logPageSize).pipe(Stream.mapEffect((read) => Effect.map(received(input, key, read), (items) => fedOf({
114
+ items,
115
+ truncated: read.truncated,
116
+ from: read.after,
117
+ to: read.cursor
118
+ }))), Stream.flattenIterable)))
119
+ };
120
+ };
121
+ /** `run` with the source's `subscription` held while each subscription lasts. */
122
+ const hooked = (event, run, subscription, leases) => ({
123
+ ...run,
124
+ poll: (input, cursor, options) => Effect.flatMap(leaseKey(event, options.subscriber, input), (key) => Effect.andThen(leases.hold(key, input), () => run.poll(input, cursor, options))),
125
+ release: (input, subscriber) => Effect.flatMap(leaseKey(event, subscriber, input), leases.release),
126
+ stream: (input, cursor) => Stream.unwrap(Effect.map(subscription(input), () => run.stream(input, cursor)))
127
+ });
128
+ const isCheck = (source) => Predicate.isFunction(source.check);
129
+ const isCurrent = (source) => Predicate.isFunction(source.current);
130
+ const runOf = (event, source) => Effect.gen(function* () {
131
+ const attach = yield* Effect.flatMap(Events, attachmentsOf);
132
+ let run;
133
+ if (isCheck(source)) run = checkRun(event, source, yield* attach.check(event, false));
134
+ else if (isCurrent(source)) run = currentRun(event, source, yield* attach.check(event, true));
135
+ else run = logRun(event, source, yield* attach.log(event, source.log, source.log.emit));
136
+ if (source.subscription === void 0) return run;
137
+ return hooked(event, run, source.subscription, yield* makeLeases(source.subscription, source.lease));
138
+ });
139
+ const isSource = (source) => {
140
+ const log = Predicate.hasProperty(source, "log") ? source.log : void 0;
141
+ const logged = Predicate.isObject(log) && (!Predicate.hasProperty(log, "emit") || log.emit === void 0 || Stream.isStream(log.emit));
142
+ return [
143
+ Predicate.isFunction(Predicate.hasProperty(source, "check") ? source.check : void 0),
144
+ Predicate.isFunction(Predicate.hasProperty(source, "current") ? source.current : void 0),
145
+ logged
146
+ ].filter(Boolean).length === 1;
147
+ };
148
+ const assertSource = (event, source) => {
149
+ if (!isSource(source)) throw new Error(`Event "${event.name}": its source needs exactly one of check, current or log (give check or current a function, or log its options, {} or { emit: stream })`);
150
+ return source;
151
+ };
152
+ /**
153
+ * The runtime of `events`' sources: one per event, from the source given for one event alone, or
154
+ * from a record keyed by event name, the builder run once for all of them.
155
+ */
156
+ const sourcesOf = (events, single, sources) => {
157
+ const names = events.map((event) => event.name);
158
+ const checked = (built) => {
159
+ const keyed = single ? Object.fromEntries(names.map((name) => [name, built])) : Object.fromEntries(Predicate.isObject(built) ? Object.entries(built) : []);
160
+ assertKnown("sources", Object.keys(keyed), names, "remove it, or list its event");
161
+ const missing = names.filter((name) => !Object.hasOwn(keyed, name));
162
+ if (missing.length > 0) throw new Error(`Missing sources: ${missing.join(", ")} (give the source of one event, or a record with an own source per listed event)`);
163
+ return keyed;
164
+ };
165
+ const built = memoized(Effect.isEffect(sources) ? Effect.map(sources, checked) : Effect.sync(() => checked(sources)));
166
+ return events.map((event) => {
167
+ const run = memoized(Effect.flatMap(Effect.service(built.key), (keyed) => runOf(event, assertSource(event, keyed[event.name]))));
168
+ return [event, {
169
+ key: run.key,
170
+ layer: Layer.provide(run.layer, built.layer)
171
+ }];
172
+ });
173
+ };
174
+ const guarded = (event, run, before) => {
175
+ const attributes = { "event.name": event.name };
176
+ const admit = (input) => event.caller === Anyone ? checkInput(event, input) : Effect.flatMap(Effect.serviceOption(event.caller), (subscriber) => Option.isSome(subscriber) ? Effect.andThen(checkInput(event, input), Effect.suspend(() => before(event))) : Effect.fail(new Unauthenticated()));
177
+ return {
178
+ event,
179
+ release: run.release,
180
+ admit,
181
+ poll: (input, cursor, options) => Effect.andThen(admit(input), () => run.poll(input, cursor, options)).pipe(Effect.annotateLogs(attributes), Effect.withSpan(event.name, { attributes }, { captureStackTrace: false })),
182
+ stream: (input) => Effect.as(admit(input), (cursor) => run.stream(input, cursor).pipe(Stream.withSpan(event.name, { attributes })))
183
+ };
184
+ };
185
+ /**
186
+ * Event types bound to their sources and their authorizer: everything one `Event.implement` call
187
+ * produced. Its type parameters may be written out, as a package emitting declarations prints
188
+ * them: `E` its event types, `R` what its sources read per subscription, by event name, `EX` and `RX` the
189
+ * failures and services of its builder, `RA` what its authorizer reads per subscription, and
190
+ * `EAX` and `RAX` the failures and services of the authorizer's builder.
191
+ */
192
+ var EventImplementation = class EventImplementation {
193
+ /** Tells an event implementation from an action implementation among what a surface is given. */
194
+ _tag = "EventImplementation";
195
+ /** The event types this implementation answers. */
196
+ events;
197
+ #running;
198
+ #authorizer;
199
+ constructor(events, running, authorizer) {
200
+ this.events = events;
201
+ this.#running = running;
202
+ this.#authorizer = authorizer;
203
+ }
204
+ /** Select event types while retaining their sources, authorization and shared builder. */
205
+ static share(events, app) {
206
+ const source = EventImplementation.own(app);
207
+ return new EventImplementation(events, source.#running, source.#authorizer);
208
+ }
209
+ /** The builders of `app`, its sources' and its authorizer's, as one layer. */
210
+ static layerOf(app) {
211
+ const source = EventImplementation.own(app);
212
+ const runs = Layer.mergeAll(Layer.empty, ...EventImplementation.held(app).map(([, run]) => run.layer));
213
+ return builderLayer(app.events, { layer: runs }, source.#authorizer);
214
+ }
215
+ /** The event types of `app` with what runs them, behind its authorizer. */
216
+ static feedsOf(app) {
217
+ const source = EventImplementation.own(app);
218
+ return Effect.gen(function* () {
219
+ const before = app.events.some((event) => event.caller !== Anyone) ? yield* source.#authorizer.key : () => Effect.void;
220
+ return yield* Effect.forEach(EventImplementation.held(app), ([event, run]) => Effect.map(Effect.service(run.key), (ran) => guarded(event, ran, before)));
221
+ });
222
+ }
223
+ /** What runs each event type `app` holds. */
224
+ static held(app) {
225
+ return EventImplementation.own(app).#running.filter(([event]) => app.events.includes(event));
226
+ }
227
+ static own(app) {
228
+ if (!(#running in app)) throw notMadeHere("Event");
229
+ return app;
230
+ }
231
+ };
232
+ /**
233
+ * Why `surface` cannot serve `feeds` unless its authentication is `keyed` by a principal: a
234
+ * protected event type whose `subscription` hook is held per subscriber between polls, which a
235
+ * principal names.
236
+ */
237
+ const unkeyedHook = (surface, feeds, keyed) => keyed ? void 0 : Array.findFirst(feeds, ({ event, release }) => release !== void 0 && event.caller !== Anyone).pipe(Option.map(({ event }) => `${surface} event "${event.name}" is protected and has a subscription hook, so its authentication needs principal to tell poll subscribers apart (give Authentication.make a principal, such as (user) => user.id)`), Option.getOrUndefined);
238
+ /** The event implementations among what a surface is given. */
239
+ const eventsOf = (served) => Array.ensure(served).filter((app) => Predicate.isTagged(app, "EventImplementation"));
240
+ /**
241
+ * The event implementations among `apps` holding an event type of `listed`, each holding those
242
+ * alone, refusing one listed that none holds; every one where `listed` is absent.
243
+ */
244
+ const selectEvents = (apps, listed) => selectHeld(apps, listed, (app) => app.events, (own, app) => EventImplementation.share(own, app));
245
+ /** The builder layers of event implementations, as a surface provides them. */
246
+ const feedBuilders = (events) => events.map((app) => EventImplementation.layerOf(app));
247
+ /** Every feed of event implementations, from the context their builders provide. */
248
+ const acquireFeeds = (events) => Effect.map(Effect.forEach(events, (app) => EventImplementation.feedsOf(app)), (feeds) => feeds.flat());
249
+ export { EventImplementation, acquireFeeds, defaultNextPoll, eventsOf, feedBuilders, selectEvents, sourcesOf, unkeyedHook };
@@ -0,0 +1,94 @@
1
+ import { LogStorage } from "./log.mjs";
2
+ import { Any, Emitted } from "../Event.mjs";
3
+ import { Context, Effect, Layer, Scope, Stream } from "effect";
4
+ /** What `notify` and `publish` take: the key whose subscribers they reach. */
5
+ export interface KeyOptions {
6
+ /**
7
+ * Reach only the subscribers whose source's `key` answers this, such as one room's; without
8
+ * it, every subscriber of the event. A subscriber of a source without `key` is reached either way.
9
+ */
10
+ readonly key?: string | undefined;
11
+ }
12
+ type ErasedEmitted = Emitted<Any>;
13
+ /**
14
+ * Where an `Events` keeps its current sources' revisions: counters by name, which only count up,
15
+ * in an `epoch` telling them from counters that started again.
16
+ */
17
+ export interface RevisionStorage {
18
+ /** Changes whenever the counters start again, so a cursor from before is behind every revision. */
19
+ readonly epoch: string;
20
+ readonly get: (name: string) => Effect.Effect<number>;
21
+ /** Adds one to each counter `names` names. */
22
+ readonly bump: (names: readonly string[]) => Effect.Effect<void>;
23
+ }
24
+ declare const Events_base: Context.ServiceClass<Events, "effect-actions/Event/Events", {
25
+ /** Wakes the subscribers of `event`'s check source to read it now, rather than at their next poll. */
26
+ readonly notify: (event: Any, options?: KeyOptions) => Effect.Effect<void>;
27
+ /** Appends `emitted` to `event`'s log, delivering it to the log source's subscribers now. */
28
+ readonly publish: <E extends Any>(event: E, emitted: Emitted<E>, options?: KeyOptions) => Effect.Effect<void>;
29
+ }>;
30
+ /**
31
+ * Where an event's occurrences are announced as they happen: `notify` wakes the subscribers of a
32
+ * check source to read now, and `publish` appends an occurrence to a log source, both reaching
33
+ * the subscribers of one `key` or every one. Domain code yields it as it yields a store, and an
34
+ * entrypoint provides one implementation: `Event.layerMemory`, kept in a process's memory.
35
+ * Every surface serving events and `Action.client` watching them need it.
36
+ *
37
+ * **Gotchas**
38
+ *
39
+ * - The handlers announcing and the surfaces serving must share one, as they share a store: built separately, as a test building its handlers under a `provide` of their own does, each has its own, and an announcement reaches no subscriber of the other.
40
+ * - An event no implementation attached here has no subscribers here: `publish` does nothing for it, and `notify` only moves its revision, so a current source attached later answers the change.
41
+ * - `notify` of a log source and `publish` to a check source die naming the other: a check source reads your store, so write there and notify; a log source keeps what you publish.
42
+ * - Implementations attach only to one `layerMemory` or `makeEvents` made: built over another, such as `Events.of` in a test, they die, naming both.
43
+ */
44
+ export declare class Events extends Events_base {}
45
+ /** `makeEvents`' options: where it keeps logs, and whether check sources are read on a timer. */
46
+ export interface MakeEventsOptions {
47
+ /**
48
+ * The storage of `event`'s log, made at its first attachment: the core keeps the log's cursors,
49
+ * bounds and expiry over it.
50
+ */
51
+ readonly log: (event: Any) => LogStorage<ErasedEmitted>;
52
+ /**
53
+ * Whether a check source's subscribers also read it again on a timer, each page's `nextPoll`,
54
+ * besides when notified; defaults to `true`. `false` where a pending timer would keep the host
55
+ * awake, as it keeps a Durable Object from hibernating: its sources are read when notified, and
56
+ * 5 seconds after a read that fails.
57
+ */
58
+ readonly polls?: boolean | undefined;
59
+ /**
60
+ * Whether a log source's `emit` is consumed; defaults to `true`. `false` where the host does not
61
+ * last, as a Durable Object is evicted while idle and misses what a stream sends meanwhile: a
62
+ * log source fed by `emit` dies as it attaches, naming `publish`.
63
+ */
64
+ readonly emits?: boolean | undefined;
65
+ /**
66
+ * Where current sources' revisions are kept; defaults to memory, a new epoch with each
67
+ * `makeEvents`. A host keeping its logs in storage keeps them there too, so a subscriber's
68
+ * cursor outlives the host.
69
+ */
70
+ readonly revisions?: RevisionStorage | undefined;
71
+ }
72
+ /**
73
+ * An `Events` for as long as the scope lasts, over the log storage `log` makes: what a host keeping
74
+ * logs elsewhere, such as a Durable Object's storage, provides as `Events`, its subscribers' wakes
75
+ * in memory and its logs where `log` keeps them.
76
+ *
77
+ * **Gotchas**
78
+ *
79
+ * - Make it once per host, around the handlers that announce and the surfaces that serve, as `layerMemory` is built.
80
+ * - A log's storage is made once per event for the life of the scope, its position read on every append and read: a storage emptied behind its back, as `storage.deleteAll` empties one, starts a new epoch, and a cursor from before reads a gap.
81
+ */
82
+ export declare const makeEvents: (options: MakeEventsOptions) => Effect.Effect<Events["Service"], never, Scope.Scope>;
83
+ /**
84
+ * An `Events` kept in the memory of the process that builds it: for a server holding its
85
+ * subscribers' connections itself, such as Node's, and for tests.
86
+ *
87
+ * **Gotchas**
88
+ *
89
+ * - Build it once, around the handlers that announce and the surfaces that serve, as a store is: each build is its own.
90
+ * - A log lives as long as this build, through rebuilds of the implementations attached to it: a restart forgets it, and a subscriber resuming from one of its cursors receives a gap.
91
+ * - Several builds of one log source share its log, and its `emit` feeds it from one build at a time, so nothing is appended twice.
92
+ */
93
+ export declare const layerMemory: Layer.Layer<Events>;
94
+ export {};
@@ -0,0 +1,133 @@
1
+ import { makeLog, makeLogStore, memoryStorage } from "./log.mjs";
2
+ import { Context, Effect, Layer, Queue, Stream } from "effect";
3
+ // src/contract/hub.ts
4
+ /** Revisions kept in memory, starting again, in a new epoch, with each call. */
5
+ const memoryRevisions = () => {
6
+ const counts = /* @__PURE__ */ new Map();
7
+ return {
8
+ epoch: globalThis.crypto.randomUUID(),
9
+ get: (name) => Effect.sync(() => counts.get(name) ?? 0),
10
+ bump: (names) => Effect.sync(() => {
11
+ for (const name of names) counts.set(name, (counts.get(name) ?? 0) + 1);
12
+ })
13
+ };
14
+ };
15
+ const everyKey = (event) => event.name;
16
+ const noKey = (event) => `${event.name}\u0000`;
17
+ const ofKey = (event, key) => `${event.name}\u0000=${key}`;
18
+ /**
19
+ * Where an event's occurrences are announced as they happen: `notify` wakes the subscribers of a
20
+ * check source to read now, and `publish` appends an occurrence to a log source, both reaching
21
+ * the subscribers of one `key` or every one. Domain code yields it as it yields a store, and an
22
+ * entrypoint provides one implementation: `Event.layerMemory`, kept in a process's memory.
23
+ * Every surface serving events and `Action.client` watching them need it.
24
+ *
25
+ * **Gotchas**
26
+ *
27
+ * - The handlers announcing and the surfaces serving must share one, as they share a store: built separately, as a test building its handlers under a `provide` of their own does, each has its own, and an announcement reaches no subscriber of the other.
28
+ * - An event no implementation attached here has no subscribers here: `publish` does nothing for it, and `notify` only moves its revision, so a current source attached later answers the change.
29
+ * - `notify` of a log source and `publish` to a check source die naming the other: a check source reads your store, so write there and notify; a log source keeps what you publish.
30
+ * - Implementations attach only to one `layerMemory` or `makeEvents` made: built over another, such as `Events.of` in a test, they die, naming both.
31
+ */
32
+ var Events = class extends Context.Service()("effect-actions/Event/Events") {};
33
+ const attached = /* @__PURE__ */ new WeakMap();
34
+ /** How sources attach to `events`, the `Events` `makeEvents` made; one made otherwise dies. */
35
+ const attachmentsOf = (events) => Effect.suspend(() => {
36
+ const attachments = attached.get(events);
37
+ return attachments === void 0 ? Effect.die(/* @__PURE__ */ new Error("This Event.Events serves no event implementation: provide Event.layerMemory, or one Event.makeEvents builds")) : Effect.succeed(attachments);
38
+ });
39
+ /** Whether an announcement to `target` reaches a subscriber of `key`: either absent means every one. */
40
+ const reaches = (target, key) => target === void 0 || key === void 0 || target === key;
41
+ const holding = (set, value) => Effect.acquireRelease(Effect.sync(() => {
42
+ set.add(value);
43
+ return value;
44
+ }), () => Effect.sync(() => set.delete(value)));
45
+ /**
46
+ * An `Events` for as long as the scope lasts, over the log storage `log` makes: what a host keeping
47
+ * logs elsewhere, such as a Durable Object's storage, provides as `Events`, its subscribers' wakes
48
+ * in memory and its logs where `log` keeps them.
49
+ *
50
+ * **Gotchas**
51
+ *
52
+ * - Make it once per host, around the handlers that announce and the surfaces that serve, as `layerMemory` is built.
53
+ * - A log's storage is made once per event for the life of the scope, its position read on every append and read: a storage emptied behind its back, as `storage.deleteAll` empties one, starts a new epoch, and a cursor from before reads a gap.
54
+ */
55
+ const makeEvents = (options) => Effect.gen(function* () {
56
+ const polls = options.polls ?? true;
57
+ const emits = options.emits ?? true;
58
+ const revisions = options.revisions ?? memoryRevisions();
59
+ const scope = yield* Effect.scope;
60
+ const checks = /* @__PURE__ */ new Map();
61
+ const logs = /* @__PURE__ */ new Map();
62
+ const checkOf = (event) => {
63
+ const found = checks.get(event) ?? {
64
+ attached: /* @__PURE__ */ new Set(),
65
+ waiters: /* @__PURE__ */ new Set()
66
+ };
67
+ checks.set(event, found);
68
+ return found;
69
+ };
70
+ const isCheck = (event) => (checks.get(event)?.attached.size ?? 0) > 0;
71
+ const feed = (kept) => Effect.suspend(() => {
72
+ const [next] = kept.feeding ? [] : kept.waiting;
73
+ if (next === void 0) return Effect.void;
74
+ kept.waiting.delete(next);
75
+ kept.feeding = true;
76
+ const fed = Effect.andThen(kept.log.feed(Stream.map(next.emit, ({ key, ...value }) => ({
77
+ value,
78
+ key
79
+ }))), Effect.suspend(() => {
80
+ kept.feeding = false;
81
+ return feed(kept);
82
+ }));
83
+ return Effect.asVoid(Effect.forkIn(fed, scope));
84
+ });
85
+ const fedBy = (kept, emit) => Effect.tap(holding(kept.waiting, { emit }), () => feed(kept));
86
+ const events = Events.of({
87
+ notify: (event, notifyOptions) => Effect.suspend(() => {
88
+ if (!isCheck(event) && logs.has(event)) return Effect.die(/* @__PURE__ */ new Error(`Event "${event.name}" has a log source, which keeps what is published: give each occurrence to publish (Event.Events.publish(event, { data }))`));
89
+ const key = notifyOptions?.key;
90
+ return Effect.andThen(checks.get(event)?.current === false ? Effect.void : revisions.bump([everyKey(event), key === void 0 ? noKey(event) : ofKey(event, key)]), Effect.sync(() => {
91
+ for (const waiter of checkOf(event).waiters) if (reaches(key, waiter.key)) Queue.offerUnsafe(waiter.woken, void 0);
92
+ }));
93
+ }),
94
+ publish: (event, emitted, publishOptions) => Effect.suspend(() => {
95
+ const kept = logs.get(event);
96
+ if (kept !== void 0) return kept.log.append(emitted, publishOptions?.key);
97
+ return isCheck(event) ? Effect.die(/* @__PURE__ */ new Error(`Event "${event.name}" has a check source, which reads your store: write the occurrence there, then wake its subscribers (Event.Events.notify(event))`)) : Effect.void;
98
+ })
99
+ });
100
+ attached.set(events, {
101
+ check: (event, current) => Effect.map(holding(Object.assign(checkOf(event), { current }).attached, {}), () => ({
102
+ polls,
103
+ wakes: (key) => Effect.flatMap(Queue.sliding(1), (woken) => Effect.as(holding(checkOf(event).waiters, {
104
+ key,
105
+ woken
106
+ }), Queue.take(woken))),
107
+ revision: (key) => key === void 0 ? Effect.map(revisions.get(everyKey(event)), (count) => `${revisions.epoch}:${count}`) : Effect.map(Effect.all([revisions.get(noKey(event)), revisions.get(ofKey(event, key))]), ([unkeyed, keyed]) => `${revisions.epoch}:${unkeyed + keyed}:${key}`)
108
+ })),
109
+ log: (event, bounds, emit) => Effect.suspend(() => {
110
+ if (emit !== void 0 && !emits) return Effect.die(/* @__PURE__ */ new Error(`Event "${event.name}" has a log source fed by emit, which this Event.Events does not consume, as its host does not last between requests (publish each occurrence with Event.Events.publish as the host receives it, such as in a call or a route)`));
111
+ const kept = logs.get(event) ?? {
112
+ log: makeLog(makeLogStore(options.log(event), bounds)),
113
+ waiting: /* @__PURE__ */ new Set(),
114
+ feeding: false
115
+ };
116
+ logs.set(event, kept);
117
+ return Effect.as(emit === void 0 ? Effect.void : fedBy(kept, emit), kept.log);
118
+ })
119
+ });
120
+ return events;
121
+ });
122
+ /**
123
+ * An `Events` kept in the memory of the process that builds it: for a server holding its
124
+ * subscribers' connections itself, such as Node's, and for tests.
125
+ *
126
+ * **Gotchas**
127
+ *
128
+ * - Build it once, around the handlers that announce and the surfaces that serve, as a store is: each build is its own.
129
+ * - A log lives as long as this build, through rebuilds of the implementations attached to it: a restart forgets it, and a subscriber resuming from one of its cursors receives a gap.
130
+ * - Several builds of one log source share its log, and its `emit` feeds it from one build at a time, so nothing is appended twice.
131
+ */
132
+ const layerMemory = Layer.effect(Events)(makeEvents({ log: () => memoryStorage() }));
133
+ export { Events, attachmentsOf, layerMemory, makeEvents, reaches };