@gjermundgaraba/effect-actions 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/README.md +19 -41
  2. package/dist/Action.d.mts +140 -48
  3. package/dist/Action.mjs +88 -44
  4. package/dist/ActionCli.d.mts +70 -35
  5. package/dist/ActionCli.mjs +53 -34
  6. package/dist/ActionHttp.d.mts +70 -38
  7. package/dist/ActionHttp.mjs +76 -25
  8. package/dist/ActionMcp.d.mts +56 -24
  9. package/dist/ActionMcp.mjs +104 -61
  10. package/dist/ActionRpc.d.mts +222 -57
  11. package/dist/ActionRpc.mjs +301 -49
  12. package/dist/ActionToolkit.d.mts +22 -7
  13. package/dist/ActionToolkit.mjs +37 -14
  14. package/dist/Authentication.d.mts +42 -12
  15. package/dist/Authentication.mjs +38 -32
  16. package/dist/Event.d.mts +370 -0
  17. package/dist/Event.mjs +103 -0
  18. package/dist/Testing.d.mts +1 -3
  19. package/dist/Testing.mjs +2 -3
  20. package/dist/Webhook.d.mts +146 -0
  21. package/dist/Webhook.mjs +72 -0
  22. package/dist/authentication/provider.d.mts +35 -12
  23. package/dist/authentication/provider.mjs +2 -3
  24. package/dist/authentication/refusal.mjs +3 -4
  25. package/dist/cli/commands.d.mts +9 -7
  26. package/dist/cli/commands.mjs +85 -35
  27. package/dist/contract/ast.mjs +5 -3
  28. package/dist/contract/authorizer.d.mts +8 -0
  29. package/dist/contract/authorizer.mjs +20 -0
  30. package/dist/contract/call.d.mts +9 -8
  31. package/dist/contract/call.mjs +49 -8
  32. package/dist/contract/callback.d.mts +7 -0
  33. package/dist/contract/callback.mjs +27 -0
  34. package/dist/contract/caller.d.mts +12 -3
  35. package/dist/contract/caller.mjs +23 -5
  36. package/dist/contract/canonical.mjs +10 -0
  37. package/dist/contract/codec.d.mts +9 -0
  38. package/dist/contract/codec.mjs +9 -0
  39. package/dist/contract/codes.mjs +81 -0
  40. package/dist/contract/declared.d.mts +0 -2
  41. package/dist/contract/declared.mjs +26 -11
  42. package/dist/contract/digest.mjs +6 -0
  43. package/dist/contract/errors.d.mts +9 -5
  44. package/dist/contract/errors.mjs +2 -3
  45. package/dist/contract/feed.d.mts +127 -0
  46. package/dist/contract/feed.mjs +249 -0
  47. package/dist/contract/hints.mjs +1 -2
  48. package/dist/contract/hub.d.mts +94 -0
  49. package/dist/contract/hub.mjs +133 -0
  50. package/dist/contract/idempotency.d.mts +47 -0
  51. package/dist/contract/idempotency.mjs +99 -0
  52. package/dist/contract/implementation.d.mts +73 -51
  53. package/dist/contract/implementation.mjs +94 -60
  54. package/dist/contract/input-check.d.mts +15 -0
  55. package/dist/contract/input-check.mjs +16 -0
  56. package/dist/contract/lease.mjs +28 -0
  57. package/dist/contract/listed.d.mts +4 -0
  58. package/dist/contract/listed.mjs +22 -0
  59. package/dist/contract/log.d.mts +41 -0
  60. package/dist/contract/log.mjs +144 -0
  61. package/dist/contract/media.d.mts +1 -3
  62. package/dist/contract/media.mjs +1 -2
  63. package/dist/contract/names.mjs +15 -6
  64. package/dist/contract/occurrence.d.mts +29 -0
  65. package/dist/contract/occurrence.mjs +45 -0
  66. package/dist/contract/pages.d.mts +13 -0
  67. package/dist/contract/pages.mjs +39 -0
  68. package/dist/contract/progress.d.mts +26 -0
  69. package/dist/contract/progress.mjs +72 -0
  70. package/dist/contract/push.d.mts +20 -0
  71. package/dist/contract/push.mjs +96 -0
  72. package/dist/contract/receiving.d.mts +29 -0
  73. package/dist/contract/receiving.mjs +42 -0
  74. package/dist/contract/recent.mjs +12 -0
  75. package/dist/contract/reported.mjs +16 -0
  76. package/dist/contract/serving.mjs +63 -0
  77. package/dist/contract/status.mjs +15 -7
  78. package/dist/contract/stream.d.mts +34 -0
  79. package/dist/contract/stream.mjs +31 -0
  80. package/dist/contract/subscribe.d.mts +45 -0
  81. package/dist/contract/subscribe.mjs +50 -0
  82. package/dist/contract/transient.mjs +28 -0
  83. package/dist/contract/watch.d.mts +18 -0
  84. package/dist/contract/watch.mjs +105 -0
  85. package/dist/contract/webhooks.d.mts +42 -0
  86. package/dist/contract/webhooks.mjs +53 -0
  87. package/dist/http/client.d.mts +37 -14
  88. package/dist/http/client.mjs +52 -14
  89. package/dist/http/events.d.mts +56 -0
  90. package/dist/http/events.mjs +102 -0
  91. package/dist/http/placed.d.mts +36 -0
  92. package/dist/http/placed.mjs +71 -0
  93. package/dist/http/schema-errors.mjs +2 -3
  94. package/dist/http/streamed.d.mts +21 -0
  95. package/dist/http/streamed.mjs +94 -0
  96. package/dist/http/watch.mjs +50 -0
  97. package/dist/mcp/bridge.mjs +283 -0
  98. package/dist/mcp/client.d.mts +30 -99
  99. package/dist/mcp/client.mjs +60 -134
  100. package/dist/mcp/events.mjs +142 -0
  101. package/dist/mcp/progress.mjs +34 -0
  102. package/dist/mcp/protocol.mjs +40 -15
  103. package/dist/mcp/reply.d.mts +79 -0
  104. package/dist/mcp/reply.mjs +99 -0
  105. package/dist/mcp/request.d.mts +40 -0
  106. package/dist/mcp/request.mjs +51 -0
  107. package/dist/mcp/watch.mjs +115 -0
  108. package/dist/rpc/watch.mjs +39 -0
  109. package/dist/stdio/console.mjs +14 -7
  110. package/dist/testing/memory.d.mts +1 -3
  111. package/dist/testing/memory.mjs +4 -5
  112. package/dist/webhook/address.mjs +47 -0
  113. package/dist/webhook/attempt.mjs +65 -0
  114. package/dist/webhook/receiver.mjs +67 -0
  115. package/dist/webhook/signing.mjs +28 -0
  116. package/dist/webhook/stored.mjs +57 -0
  117. package/dist/webhook/subscriptions.mjs +342 -0
  118. package/docs/Action-failures.md +2 -2
  119. package/docs/Action-streams.md +115 -0
  120. package/docs/Action.md +58 -124
  121. package/docs/ActionCli-failures.md +1 -2
  122. package/docs/ActionCli.md +83 -88
  123. package/docs/ActionHttp-failures.md +3 -3
  124. package/docs/ActionHttp.md +90 -70
  125. package/docs/ActionMcp-failures.md +2 -1
  126. package/docs/ActionMcp.md +120 -92
  127. package/docs/ActionRpc-events.md +126 -0
  128. package/docs/ActionRpc-failures.md +1 -2
  129. package/docs/ActionRpc.md +108 -79
  130. package/docs/ActionToolkit-failures.md +1 -2
  131. package/docs/ActionToolkit.md +33 -90
  132. package/docs/Authentication-advanced.md +57 -61
  133. package/docs/Authentication-failures.md +5 -4
  134. package/docs/Authentication.md +23 -58
  135. package/docs/Event-failures.md +15 -0
  136. package/docs/Event.md +281 -0
  137. package/docs/README.md +51 -82
  138. package/docs/Testing-failures.md +1 -5
  139. package/docs/Testing.md +126 -86
  140. package/docs/Webhook-failures.md +8 -0
  141. package/docs/Webhook.md +319 -0
  142. package/docs/setup-failures.md +2 -3
  143. package/docs/setup.md +32 -53
  144. package/examples/README.md +12 -37
  145. package/examples/assistant/README.md +21 -26
  146. package/examples/assistant/src/agent.ts +66 -24
  147. package/examples/assistant/src/cli.ts +7 -4
  148. package/examples/assistant/src/commands.ts +9 -4
  149. package/examples/assistant/src/identity/Identity.ts +5 -4
  150. package/examples/assistant/src/identity/authorize.ts +13 -5
  151. package/examples/assistant/src/identity/handlers.ts +3 -2
  152. package/examples/assistant/src/identity/permissions.ts +9 -3
  153. package/examples/assistant/src/platform/tasks-file.ts +15 -11
  154. package/examples/assistant/src/scripted.ts +7 -4
  155. package/examples/assistant/src/tasks/Tasks.ts +20 -6
  156. package/examples/assistant/src/tasks/handlers.ts +9 -5
  157. package/examples/assistant/src/tasks/store.ts +38 -11
  158. package/examples/assistant/src/toolkit.ts +11 -7
  159. package/examples/assistant/test/commands.test.ts +53 -27
  160. package/examples/assistant/test/identity/identity.test.ts +27 -11
  161. package/examples/assistant/test/platform/tasks-file.test.ts +39 -23
  162. package/examples/assistant/test/tasks/tasks.test.ts +62 -34
  163. package/examples/assistant/test/toolkit.test.ts +104 -75
  164. package/examples/build-events/README.md +83 -0
  165. package/examples/build-events/src/Api.ts +27 -0
  166. package/examples/build-events/src/Rpc.ts +26 -0
  167. package/examples/build-events/src/builds/Builds.ts +108 -0
  168. package/examples/build-events/src/builds/events.ts +54 -0
  169. package/examples/build-events/src/builds/handlers.ts +58 -0
  170. package/examples/build-events/src/builds/log.ts +30 -0
  171. package/examples/build-events/src/builds/status.ts +28 -0
  172. package/examples/build-events/src/builds/store.ts +130 -0
  173. package/examples/build-events/src/cli.ts +18 -0
  174. package/examples/build-events/src/commands.ts +32 -0
  175. package/examples/build-events/src/deploys/Deploys.ts +44 -0
  176. package/examples/build-events/src/deploys/approvers.ts +55 -0
  177. package/examples/build-events/src/deploys/events.ts +32 -0
  178. package/examples/build-events/src/deploys/handlers.ts +38 -0
  179. package/examples/build-events/src/identity/Identity.ts +29 -0
  180. package/examples/build-events/src/identity/members.ts +7 -0
  181. package/examples/build-events/src/identity/verifier.ts +22 -0
  182. package/examples/build-events/src/notifier/Notices.ts +26 -0
  183. package/examples/build-events/src/notifier/routes.ts +71 -0
  184. package/examples/build-events/src/notifier.ts +48 -0
  185. package/examples/build-events/src/routes.ts +71 -0
  186. package/examples/build-events/src/server.ts +62 -0
  187. package/examples/build-events/test/builds/events.test.ts +68 -0
  188. package/examples/build-events/test/builds/log.test.ts +71 -0
  189. package/examples/build-events/test/builds/progress.test.ts +57 -0
  190. package/examples/build-events/test/builds/status.test.ts +42 -0
  191. package/examples/build-events/test/commands.test.ts +103 -0
  192. package/examples/build-events/test/deploys/events.test.ts +52 -0
  193. package/examples/build-events/test/http.test.ts +95 -0
  194. package/examples/build-events/test/notifier.test.ts +80 -0
  195. package/examples/build-events/test/routes.test.ts +96 -0
  196. package/examples/build-events/test/rpc.test.ts +84 -0
  197. package/examples/build-events/test/support/as.ts +8 -0
  198. package/examples/build-events/test/support/memory.ts +43 -0
  199. package/examples/build-events/test/webhooks.test.ts +143 -0
  200. package/examples/kitchen-sink/README.md +178 -166
  201. package/examples/kitchen-sink/src/Actions.ts +11 -3
  202. package/examples/kitchen-sink/src/Api.ts +12 -6
  203. package/examples/kitchen-sink/src/Rpc.ts +7 -5
  204. package/examples/kitchen-sink/src/admin/Admin.ts +21 -10
  205. package/examples/kitchen-sink/src/admin/authenticate.ts +8 -4
  206. package/examples/kitchen-sink/src/admin/authorize.ts +13 -5
  207. package/examples/kitchen-sink/src/admin/grants.ts +5 -3
  208. package/examples/kitchen-sink/src/admin/handlers.ts +9 -5
  209. package/examples/kitchen-sink/src/agent.ts +14 -6
  210. package/examples/kitchen-sink/src/assistant/Assistant.ts +21 -15
  211. package/examples/kitchen-sink/src/assistant/conversations.ts +14 -9
  212. package/examples/kitchen-sink/src/assistant/demo-model.ts +2 -2
  213. package/examples/kitchen-sink/src/assistant/handlers.ts +42 -22
  214. package/examples/kitchen-sink/src/attachments/Attachments.ts +6 -6
  215. package/examples/kitchen-sink/src/attachments/download.ts +7 -6
  216. package/examples/kitchen-sink/src/attachments/handlers.ts +9 -4
  217. package/examples/kitchen-sink/src/attachments/store.ts +25 -18
  218. package/examples/kitchen-sink/src/browser.ts +10 -4
  219. package/examples/kitchen-sink/src/cli.ts +28 -12
  220. package/examples/kitchen-sink/src/config.ts +1 -1
  221. package/examples/kitchen-sink/src/identity/Identity.ts +24 -10
  222. package/examples/kitchen-sink/src/identity/authenticate.ts +19 -11
  223. package/examples/kitchen-sink/src/identity/authorize.ts +14 -4
  224. package/examples/kitchen-sink/src/identity/directory.ts +16 -7
  225. package/examples/kitchen-sink/src/identity/handlers.ts +1 -0
  226. package/examples/kitchen-sink/src/identity/members.ts +7 -2
  227. package/examples/kitchen-sink/src/identity/quota.ts +23 -20
  228. package/examples/kitchen-sink/src/issues/Issues.ts +91 -15
  229. package/examples/kitchen-sink/src/issues/assistant.ts +4 -2
  230. package/examples/kitchen-sink/src/issues/events.ts +46 -0
  231. package/examples/kitchen-sink/src/issues/export.ts +35 -0
  232. package/examples/kitchen-sink/src/issues/guide.ts +4 -3
  233. package/examples/kitchen-sink/src/issues/handlers.ts +20 -7
  234. package/examples/kitchen-sink/src/issues/store.ts +156 -67
  235. package/examples/kitchen-sink/src/issues/write-limit.ts +44 -18
  236. package/examples/kitchen-sink/src/memory.ts +11 -2
  237. package/examples/kitchen-sink/src/ops.ts +10 -5
  238. package/examples/kitchen-sink/src/request-policy.ts +25 -9
  239. package/examples/kitchen-sink/src/routes.ts +67 -27
  240. package/examples/kitchen-sink/src/sdk.ts +22 -12
  241. package/examples/kitchen-sink/src/seed.ts +15 -9
  242. package/examples/kitchen-sink/src/server.ts +24 -10
  243. package/examples/kitchen-sink/src/stdio.ts +9 -3
  244. package/examples/kitchen-sink/src/workspaces/Workspaces.ts +10 -7
  245. package/examples/kitchen-sink/src/workspaces/handlers.ts +3 -2
  246. package/examples/kitchen-sink/src/workspaces/registry.ts +16 -10
  247. package/examples/kitchen-sink/src/workspaces/tenant.ts +12 -5
  248. package/examples/kitchen-sink/test/admin/admin.test.ts +38 -23
  249. package/examples/kitchen-sink/test/assistant/assistant.test.ts +471 -299
  250. package/examples/kitchen-sink/test/attachments/attachments.test.ts +135 -94
  251. package/examples/kitchen-sink/test/identity/identity.test.ts +110 -82
  252. package/examples/kitchen-sink/test/identity/session.test.ts +65 -43
  253. package/examples/kitchen-sink/test/issues/assistant.test.ts +50 -31
  254. package/examples/kitchen-sink/test/issues/events.test.ts +273 -0
  255. package/examples/kitchen-sink/test/issues/export.test.ts +120 -0
  256. package/examples/kitchen-sink/test/issues/issues.test.ts +105 -75
  257. package/examples/kitchen-sink/test/request-policy.test.ts +130 -33
  258. package/examples/kitchen-sink/test/routes.test.ts +102 -57
  259. package/examples/kitchen-sink/test/support/as.ts +9 -0
  260. package/examples/kitchen-sink/test/support/scripted.ts +6 -5
  261. package/examples/kitchen-sink/test/support/services.ts +17 -0
  262. package/examples/live-rpc/README.md +33 -35
  263. package/examples/live-rpc/src/Rpc.ts +9 -5
  264. package/examples/live-rpc/src/browser.ts +39 -11
  265. package/examples/live-rpc/src/chat/Chat.ts +30 -16
  266. package/examples/live-rpc/src/chat/authorize.ts +2 -1
  267. package/examples/live-rpc/src/chat/events.ts +74 -0
  268. package/examples/live-rpc/src/chat/handlers.ts +13 -5
  269. package/examples/live-rpc/src/chat/room.ts +9 -5
  270. package/examples/live-rpc/src/chat/store.ts +51 -14
  271. package/examples/live-rpc/src/client.ts +22 -5
  272. package/examples/live-rpc/src/identity/Identity.ts +3 -3
  273. package/examples/live-rpc/src/identity/verifier.ts +8 -4
  274. package/examples/live-rpc/src/page.ts +58 -12
  275. package/examples/live-rpc/src/routes.ts +14 -10
  276. package/examples/live-rpc/src/server.ts +15 -5
  277. package/examples/live-rpc/test/browser.test.ts +127 -50
  278. package/examples/live-rpc/test/chat/chat.test.ts +215 -23
  279. package/examples/live-rpc/test/routes.test.ts +130 -46
  280. package/examples/live-rpc/test/server.test.ts +94 -33
  281. package/examples/mcp-tools/README.md +24 -38
  282. package/examples/mcp-tools/src/platform/fake-farm.ts +63 -24
  283. package/examples/mcp-tools/src/platform/print-farm.ts +59 -29
  284. package/examples/mcp-tools/src/printers/Printers.ts +18 -7
  285. package/examples/mcp-tools/src/printers/farm.ts +15 -5
  286. package/examples/mcp-tools/src/printers/handlers.ts +12 -6
  287. package/examples/mcp-tools/src/routes.ts +8 -6
  288. package/examples/mcp-tools/src/server.ts +12 -5
  289. package/examples/mcp-tools/src/stdio.ts +3 -2
  290. package/examples/mcp-tools/test/printers/printers.test.ts +132 -89
  291. package/examples/mcp-tools/test/routes.test.ts +140 -81
  292. package/examples/notes-service/README.md +30 -45
  293. package/examples/notes-service/src/Api.ts +1 -0
  294. package/examples/notes-service/src/browser.ts +15 -14
  295. package/examples/notes-service/src/cli.ts +8 -3
  296. package/examples/notes-service/src/commands.ts +46 -20
  297. package/examples/notes-service/src/identity/Identity.ts +2 -2
  298. package/examples/notes-service/src/identity/authorize.ts +6 -2
  299. package/examples/notes-service/src/identity/handlers.ts +3 -2
  300. package/examples/notes-service/src/identity/users.ts +2 -1
  301. package/examples/notes-service/src/identity/verifier.ts +12 -6
  302. package/examples/notes-service/src/notes/Notes.ts +46 -22
  303. package/examples/notes-service/src/notes/handlers.ts +18 -9
  304. package/examples/notes-service/src/notes/store.ts +70 -43
  305. package/examples/notes-service/src/routes.ts +7 -5
  306. package/examples/notes-service/src/server.ts +12 -5
  307. package/examples/notes-service/src/status/Status.ts +1 -1
  308. package/examples/notes-service/src/status/handlers.ts +5 -3
  309. package/examples/notes-service/src/stdio.ts +7 -3
  310. package/examples/notes-service/src/worker.ts +6 -3
  311. package/examples/notes-service/test/commands.test.ts +164 -91
  312. package/examples/notes-service/test/notes/as.ts +3 -1
  313. package/examples/notes-service/test/notes/idempotency.test.ts +97 -67
  314. package/examples/notes-service/test/notes/in-process.test.ts +26 -15
  315. package/examples/notes-service/test/notes/notes.test.ts +49 -31
  316. package/examples/notes-service/test/routes.test.ts +106 -71
  317. package/examples/notes-service/test/worker.test.ts +21 -13
  318. package/examples/quickstart/README.md +2 -6
  319. package/examples/quickstart/src/Api.ts +1 -0
  320. package/examples/quickstart/src/client.ts +6 -3
  321. package/examples/quickstart/src/greeting/Greeting.ts +1 -1
  322. package/examples/quickstart/src/greeting/handlers.ts +5 -2
  323. package/examples/quickstart/src/routes.ts +3 -2
  324. package/examples/quickstart/src/server.ts +6 -2
  325. package/examples/quickstart/test/routes.test.ts +3 -2
  326. package/package.json +19 -17
@@ -1,10 +1,23 @@
1
- import { Anyone, assertAuthentication } from "./caller.mjs";
1
+ import { Anyone, CallerKeyed } from "./caller.mjs";
2
2
  import { Unauthenticated } from "./errors.mjs";
3
- import { assertDistinct, assertOnce, heldOnce } from "./names.mjs";
4
3
  import { projectedErrors } from "./declared.mjs";
5
- import { Array, Cause, Context, Effect, Exit, Layer, Option, Schema } from "effect";
6
- //#region src/contract/implementation.ts
4
+ import { checkInput } from "./input-check.mjs";
5
+ import { assertOnce, heldOnce } from "./names.mjs";
6
+ import { reportedTo, streamReportedTo } from "./progress.mjs";
7
+ import { elementOf } from "./stream.mjs";
8
+ import { Array, Cause, Context, Effect, Exit, Layer, Option, Predicate, References, Schema, Stream } from "effect";
9
+ // src/contract/implementation.ts
7
10
  const uniqueKey = () => Math.random().toString(36).slice(2);
11
+ const notAStream = /* @__PURE__ */ new Error("A handler answered otherwise than its action's success declares");
12
+ /** The stream of a call of an action answering one, from its `ErasedHandler`'s answer. */
13
+ const elementsOf = (answer) => Stream.unwrap(Effect.filterOrElse(answer, Stream.isStream, () => Effect.die(notAStream)));
14
+ /** The answer of a handler of an action answering one value, a stream refused as a defect. */
15
+ const answered = (answer) => Stream.isStream(answer) ? Effect.die(notAStream) : answer;
16
+ /**
17
+ * `run` as a tool calls it, a tool answering one result: the elements of an action answering a
18
+ * stream collected, in order, into one array once the stream ends.
19
+ */
20
+ const answeringWhole = (action, run) => elementOf(action.success) === void 0 ? run : (input, reporting) => Stream.runCollect(elementsOf(run(input, reporting)));
8
21
  const memoized = (build) => {
9
22
  const key = Context.Service(`effect-actions/Implementation/${uniqueKey()}`);
10
23
  return {
@@ -13,6 +26,48 @@ const memoized = (build) => {
13
26
  };
14
27
  };
15
28
  /**
29
+ * The builders of an implementation of `contracts` as one layer: what its builder made, and its
30
+ * authorizer where a contract is protected, as only those need one.
31
+ */
32
+ const builderLayer = (contracts, built, authorizer) => contracts.some((contract) => contract.caller !== Anyone) ? Layer.merge(built.layer, authorizer.layer) : built.layer;
33
+ /** What a surface throws for an implementation some other copy of this module made. */
34
+ const notMadeHere = (module) => /* @__PURE__ */ new Error(`Not an implementation made by this ${module}.implement: pass the value implement returned, not a copy such as a spread; or effect-actions is installed twice`);
35
+ const asDefects = (reason, rejected) => {
36
+ const trace = Context.makeUnsafe(reason.annotations);
37
+ return [Cause.makeDieReason(reason.error).annotate(trace), Cause.makeDieReason(Cause.squash(rejected)).annotate(trace)];
38
+ };
39
+ const dispatch = (action, handle, before, keyed) => {
40
+ const actionAttributes = {
41
+ "action.name": action.name,
42
+ "action.read_only": action.readOnly
43
+ };
44
+ const authentication = action.caller === Anyone ? Effect.void : Effect.flatMap(Effect.serviceOption(action.caller), (actor) => Option.isSome(actor) ? Effect.void : Effect.fail(new Unauthenticated()));
45
+ const authorization = action.caller === Anyone ? Effect.void : Effect.suspend(() => before(action));
46
+ const validate = Schema.decodeUnknownEffect(Schema.toType(Schema.Union(projectedErrors(action))));
47
+ const declaredOrDefect = Effect.fnUntraced(function* (cause) {
48
+ const reasons = [];
49
+ for (const reason of cause.reasons) if (Cause.isFailReason(reason)) {
50
+ const validated = yield* Effect.exit(validate(reason.error));
51
+ reasons.push(...Exit.isSuccess(validated) ? [reason] : asDefects(reason, validated.cause));
52
+ } else reasons.push(reason);
53
+ return yield* Effect.failCause(Cause.fromReasons(reasons));
54
+ });
55
+ const admitted = (input) => Effect.andThen(authentication, Effect.andThen(checkInput(action, input), authorization));
56
+ const span = { attributes: actionAttributes };
57
+ const trace = { captureStackTrace: false };
58
+ if (elementOf(action.success) !== void 0) {
59
+ const streamed = (answer) => Stream.isStream(answer) ? Stream.scoped(answer) : Stream.die(notAStream);
60
+ return (input, sink) => Effect.succeed(Stream.unwrap(Effect.map(admitted(input), () => streamReportedTo(action.progress, sink, (reporting) => streamed(handle(input, reporting))))).pipe(Stream.catchCause((cause) => Stream.fromEffect(declaredOrDefect(cause))), Stream.updateService(References.CurrentLogAnnotations, (annotations) => ({
61
+ ...annotations,
62
+ ...actionAttributes
63
+ })), Stream.withSpan(action.name, {
64
+ ...span,
65
+ ...trace
66
+ })));
67
+ }
68
+ return (input, sink) => Effect.scoped(admitted(input).pipe(Effect.andThen(Effect.suspend(() => reportedTo(action.progress, sink, (reporting) => answered(handle(input, reporting))))), Effect.provideService(CallerKeyed, keyed), Effect.catchCause(declaredOrDefect))).pipe(Effect.annotateLogs(actionAttributes), Effect.withSpan(action.name, span, trace));
69
+ };
70
+ /**
16
71
  * Actions bound to their handlers and their authorizer: everything one `Action.implement` call
17
72
  * produced, one builder serving every surface. The private fields make this class nominal: a structurally similar object, including one made by
18
73
  * spreading an implementation, is not an implementation. Its type parameters may be written
@@ -22,6 +77,7 @@ const memoized = (build) => {
22
77
  * failures and services of the authorizer's builder.
23
78
  */
24
79
  var Implementation = class Implementation {
80
+ /** The contracts this implementation answers. */
25
81
  actions;
26
82
  #handlers;
27
83
  #authorizer;
@@ -49,28 +105,32 @@ var Implementation = class Implementation {
49
105
  */
50
106
  static layerOf(app) {
51
107
  const source = Implementation.own(app);
52
- return app.actions.some((action) => action.caller !== Anyone) ? Layer.merge(source.#handlers.layer, source.#authorizer.layer) : source.#handlers.layer;
108
+ return builderLayer(app.actions, source.#handlers, source.#authorizer);
53
109
  }
54
110
  /**
55
111
  * The actions of `app` with their handlers, behind its authorizer, from the context
56
112
  * `layerOf(app)` provides.
57
113
  */
58
- static boundOf(app) {
114
+ static boundOf(app, keyed) {
59
115
  const { actions } = app;
60
116
  const source = Implementation.own(app);
61
117
  return Effect.gen(function* () {
62
118
  const bound = yield* source.#handlers.key;
63
119
  const before = actions.some((action) => action.caller !== Anyone) ? yield* source.#authorizer.key : () => Effect.void;
64
- return actions.flatMap((action) => bound.flatMap(([held, handle]) => held === action ? [[action, dispatch(action, handle, before)]] : []));
120
+ return actions.flatMap((action) => bound.flatMap(([held, handle]) => held === action ? [[action, dispatch(action, handle, before, keyed)]] : []));
65
121
  });
66
122
  }
67
123
  /** `app`, if this copy of the module made it; another installed copy's cannot be read. */
68
124
  static own(app) {
69
- if (!(#handlers in app)) throw new Error("Not an implementation made by this Action.implement: pass the value implement returned, not a copy such as a spread; or effect-actions is installed twice");
125
+ if (!(#handlers in app)) throw notMadeHere("Action");
70
126
  return app;
71
127
  }
72
128
  };
73
- const toList = (served) => Array.ensure(served);
129
+ /**
130
+ * The action implementations among what a surface is given; `eventsOf` reads the event
131
+ * implementations, by their tag.
132
+ */
133
+ const toList = (served) => Array.ensure(served).filter((app) => !Predicate.isTagged(app, "EventImplementation"));
74
134
  const notHeld = {
75
135
  byImplementation: {
76
136
  why: "no implementation holds it",
@@ -93,15 +153,22 @@ const assertHeld = ({ why, fix, holder }, listed, held) => {
93
153
  throw new Error(`Listed in actions, but ${why}: ${markingOthers(missing, names)} (${fix}${sameName ? `; one marked (another contract) is a contract of that name ${holder}` : ""})`);
94
154
  }
95
155
  };
96
- const select = (apps, listed) => {
156
+ /**
157
+ * The implementations among `apps` holding what `listed` lists, each sharing its builder to hold
158
+ * those alone, or every one where it lists none: action and event implementations alike, by the
159
+ * contracts each `holds` and how it `share`s them.
160
+ */
161
+ const selectHeld = (apps, listed, holds, share) => {
97
162
  if (listed === void 0) return apps;
98
- assertHeld(notHeld.byImplementation, listed, apps.flatMap((app) => app.actions));
163
+ assertHeld(notHeld.byImplementation, listed, apps.flatMap(holds));
99
164
  return apps.flatMap((app) => {
100
- const own = app.actions.filter((action) => listed.includes(action));
165
+ const held = holds(app);
166
+ const own = held.filter((contract) => listed.includes(contract));
101
167
  if (own.length === 0) return [];
102
- return own.length === app.actions.length ? [app] : [Implementation.share(own, app)];
168
+ return own.length === held.length ? [app] : [share(own, app)];
103
169
  });
104
170
  };
171
+ const select = (apps, listed) => selectHeld(apps, listed, (app) => app.actions, (own, app) => Implementation.share(own, app));
105
172
  const servedBy = (what, bound, apps, listed) => {
106
173
  if (listed !== void 0) assertHeld(notHeld.byBinding, listed, bound);
107
174
  const held = apps.flatMap((app) => app.actions);
@@ -114,59 +181,26 @@ const servedBy = (what, bound, apps, listed) => {
114
181
  assertOnce("served action", actions, heldOnce);
115
182
  return served;
116
183
  };
117
- /**
118
- * What `ActionHttp.layer` and `ActionRpc.layer` serve, with the checks they share;
119
- * `authentication` is the binding's where a served action is protected.
120
- */
121
- const servingOf = (what, binding, served, options) => {
122
- const apps = servedBy(what, binding.actions, toList(served), options.actions);
123
- const actions = apps.flatMap((app) => app.actions);
124
- assertAuthentication(actions, binding.authentication);
125
- assertDistinct("middleware", options.middleware ?? [], (key) => key.key, { fix: "list each middleware once" });
126
- return {
127
- apps,
128
- actions,
129
- authentication: actions.some((action) => action.caller !== Anyone) ? binding.authentication : void 0
130
- };
131
- };
132
- const provideHandlers = (apps) => (layer) => {
133
- const [first, ...rest] = apps.map((app) => Implementation.layerOf(app));
184
+ /** Provide `builders`, implementations' builder layers, to a surface's layer. */
185
+ const provideBuilders = (builders) => (layer) => {
186
+ const [first, ...rest] = builders;
134
187
  return first === void 0 ? layer : Layer.provide(layer, Layer.mergeAll(first, ...rest));
135
188
  };
136
- const builders = (apps) => Layer.empty.pipe(provideHandlers(apps));
137
- const acquire = (apps) => Effect.map(Effect.forEach(apps, (app) => Implementation.boundOf(app)), (bound) => bound.flat());
138
- const built = (apps) => {
139
- const [first, ...rest] = apps.map((app) => Implementation.layerOf(app));
189
+ const provideHandlers = (apps) => provideBuilders(apps.map((app) => Implementation.layerOf(app)));
190
+ const acquire = (apps, keyed) => Effect.map(Effect.forEach(apps, (app) => Implementation.boundOf(app, keyed)), (bound) => bound.flat());
191
+ /**
192
+ * What `acquisition` reads, once `builders` are built into the layer graph around the caller, and
193
+ * held by its scope.
194
+ */
195
+ const builtWith = (builders, acquisition) => {
196
+ const [first, ...rest] = builders;
140
197
  return Effect.gen(function* () {
141
- if (first === void 0) return [];
198
+ if (first === void 0) return yield* acquisition;
142
199
  const current = yield* Effect.serviceOption(Layer.CurrentMemoMap);
143
200
  const memoMap = Option.getOrElse(current, Layer.makeMemoMapUnsafe);
144
201
  const scope = yield* Effect.scope;
145
202
  const context = yield* Layer.buildWithMemoMap(Layer.mergeAll(first, ...rest), memoMap, scope);
146
- return yield* Effect.provideContext(acquire(apps), context);
147
- });
148
- };
149
- const dispatch = (action, handle, before) => {
150
- const actionAttributes = {
151
- "action.name": action.name,
152
- "action.read_only": action.readOnly
153
- };
154
- const authentication = action.caller === Anyone ? Effect.void : Effect.flatMap(Effect.serviceOption(action.caller), (actor) => Option.isSome(actor) ? Effect.void : Effect.fail(new Unauthenticated()));
155
- const authorization = action.caller === Anyone ? Effect.void : Effect.suspend(() => before(action));
156
- const validate = Schema.decodeUnknownEffect(Schema.toType(Schema.Union(projectedErrors(action))));
157
- const asDefects = (reason, rejected) => {
158
- const trace = Context.makeUnsafe(reason.annotations);
159
- return [Cause.makeDieReason(reason.error).annotate(trace), Cause.makeDieReason(Cause.squash(rejected)).annotate(trace)];
160
- };
161
- const declaredOrDefect = Effect.fnUntraced(function* (cause) {
162
- const reasons = [];
163
- for (const reason of cause.reasons) if (Cause.isFailReason(reason)) {
164
- const validated = yield* Effect.exit(validate(reason.error));
165
- reasons.push(...Exit.isSuccess(validated) ? [reason] : asDefects(reason, validated.cause));
166
- } else reasons.push(reason);
167
- return yield* Effect.failCause(Cause.fromReasons(reasons));
203
+ return yield* Effect.provideContext(acquisition, context);
168
204
  });
169
- return (input) => Effect.scoped(authentication.pipe(Effect.andThen(authorization), Effect.andThen(Effect.suspend(() => handle(input))), Effect.catchCause(declaredOrDefect))).pipe(Effect.annotateLogs(actionAttributes), Effect.withSpan(action.name, { attributes: actionAttributes }, { captureStackTrace: false }));
170
205
  };
171
- //#endregion
172
- export { Implementation, acquire, assertHeld, builders, built, memoized, notHeld, provideHandlers, select, servingOf, toList };
206
+ export { Implementation, acquire, answered, answeringWhole, assertHeld, builderLayer, builtWith, elementsOf, markingOthers, memoized, notHeld, notMadeHere, provideBuilders, provideHandlers, select, selectHeld, servedBy, toList, uniqueKey };
@@ -0,0 +1,15 @@
1
+ import { Forbidden } from "./errors.mjs";
2
+ import { Any } from "../Event.mjs";
3
+ import { Any as Any$1 } from "../Action.mjs";
4
+ import { Context, Effect } from "effect";
5
+ /**
6
+ * A check every call and every subscription runs on its decoded input, on every surface, once the
7
+ * surface has verified its caller and before its authorizer: what a host provides, as a Durable
8
+ * Object refuses an input naming another object, to refuse an input it does not serve. Defaults
9
+ * to none.
10
+ *
11
+ * **Gotchas**
12
+ *
13
+ * - Provide it to the layer graph the surfaces are built in, as `Layer.succeed(Action.InputCheck)(check)`: provided per request, it misses a socket's later calls.
14
+ */
15
+ export declare const InputCheck: Context.Reference<(contract: Any$1 | Any, input: Any$1["input"]["Type"]) => Effect.Effect<void, Forbidden>>;
@@ -0,0 +1,16 @@
1
+ import { Context, Effect } from "effect";
2
+ // src/contract/input-check.ts
3
+ /**
4
+ * A check every call and every subscription runs on its decoded input, on every surface, once the
5
+ * surface has verified its caller and before its authorizer: what a host provides, as a Durable
6
+ * Object refuses an input naming another object, to refuse an input it does not serve. Defaults
7
+ * to none.
8
+ *
9
+ * **Gotchas**
10
+ *
11
+ * - Provide it to the layer graph the surfaces are built in, as `Layer.succeed(Action.InputCheck)(check)`: provided per request, it misses a socket's later calls.
12
+ */
13
+ const InputCheck = Context.Reference("effect-actions/Action/InputCheck", { defaultValue: () => () => Effect.void });
14
+ /** `contract`'s `input`, as the provided `InputCheck` judges it. */
15
+ const checkInput = (contract, input) => Effect.flatMap(Effect.service(InputCheck), (check) => check(contract, input));
16
+ export { InputCheck, checkInput };
@@ -0,0 +1,28 @@
1
+ import { Context, Duration, Effect, Equal, Hash, RcMap, Scope } from "effect";
2
+ // src/contract/lease.ts
3
+ /** A poll subscription's key, held with the context of the poll that first acquired it. */
4
+ var Lease = class Lease {
5
+ key;
6
+ input;
7
+ context;
8
+ constructor(key, input, context) {
9
+ this.key = key;
10
+ this.input = input;
11
+ this.context = context;
12
+ }
13
+ [Equal.symbol](that) {
14
+ return that instanceof Lease && that.key === this.key;
15
+ }
16
+ [Hash.symbol]() {
17
+ return Hash.string(this.key);
18
+ }
19
+ };
20
+ const defaultLease = Duration.minutes(5);
21
+ const makeLeases = (subscription, ttl = defaultLease) => Effect.map(RcMap.make({
22
+ lookup: (lease) => Effect.provideContext(subscription(lease.input), Context.omit(Scope.Scope)(lease.context)),
23
+ idleTimeToLive: ttl
24
+ }), (map) => ({
25
+ hold: (key, input) => Effect.flatMap(Effect.context(), (context) => Effect.scoped(RcMap.get(map, new Lease(key, input, context)))),
26
+ release: (key) => RcMap.invalidate(map, new Lease(key, void 0, Context.empty()))
27
+ }));
28
+ export { makeLeases };
@@ -0,0 +1,4 @@
1
+ import "../Event.mjs";
2
+ import "../Action.mjs";
3
+ /** The members of the list `T` that are `U`: `T` itself where every one is, `[]` where none is. */
4
+ export type Only<T extends readonly unknown[], U> = [Exclude<T[number], U>] extends [never] ? Extract<T, readonly U[]> : [Extract<T[number], U>] extends [never] ? readonly [] : ReadonlyArray<Extract<T[number], U>>;
@@ -0,0 +1,22 @@
1
+ import { assertOnce } from "./names.mjs";
2
+ import { isEvent } from "../Event.mjs";
3
+ // src/contract/listed.ts
4
+ /** A list split into its actions and its event types. */
5
+ const split = (contracts) => ({
6
+ actions: contracts.flatMap((contract) => isEvent(contract) ? [] : [contract]),
7
+ events: contracts.filter(isEvent)
8
+ });
9
+ /** A binding's list split into its actions and its event types, each name used once. */
10
+ const listed = (contracts) => {
11
+ assertOnce("action or event", contracts);
12
+ return split(contracts);
13
+ };
14
+ /**
15
+ * A surface's `actions` option split into the actions and the event types it lists, both absent
16
+ * where it is: a surface given the option serves exactly what it lists.
17
+ */
18
+ const selection = (contracts) => contracts === void 0 ? {
19
+ actions: void 0,
20
+ events: void 0
21
+ } : split(contracts);
22
+ export { listed, selection, split };
@@ -0,0 +1,41 @@
1
+ import { Duration, Effect, Stream } from "effect";
2
+ /** One entry of a log: what was published, and the key it was published to, if any. */
3
+ export interface Logged<A> {
4
+ readonly value: A;
5
+ readonly key: string | undefined;
6
+ }
7
+ /** Where a log stands: the epoch its cursors name, and the last sequence number it gave. */
8
+ export interface Position {
9
+ readonly epoch: string;
10
+ readonly last: number;
11
+ }
12
+ /** One entry as a log's storage keeps it: when it was appended, and what was published. */
13
+ export interface Stored<A> {
14
+ readonly at: number;
15
+ readonly logged: Logged<A>;
16
+ }
17
+ /**
18
+ * One entry as a log's storage lists it: as it was kept, or with `logged` `undefined` where the
19
+ * storage could not read it back, such as data an earlier deployment wrote in another shape,
20
+ * which a subscriber receives as a gap at its cursor.
21
+ */
22
+ export interface Listed<A> {
23
+ readonly at: number;
24
+ readonly logged: Logged<A> | undefined;
25
+ }
26
+ /**
27
+ * Where a log keeps its position and its entries, by sequence number, as `Event.makeEvents` is
28
+ * given it: storage alone, the log's cursors, bounds, truncation and expiry being the core's.
29
+ */
30
+ export interface LogStorage<A> {
31
+ /** The position `put` last kept, or `undefined` for a storage never written or emptied. */
32
+ readonly position: Effect.Effect<Position | undefined>;
33
+ /** Keeps `position`, and `entry` with it where given, at once. */
34
+ readonly put: (position: Position, entry?: {
35
+ readonly seq: number;
36
+ readonly stored: Stored<A>;
37
+ }) => Effect.Effect<void>;
38
+ /** The entries kept from sequence number `from` on, at most `limit`, in order. */
39
+ readonly list: (from: number, limit: number) => Effect.Effect<ReadonlyArray<readonly [seq: number, listed: Listed<A>]>>;
40
+ readonly delete: (seqs: readonly number[]) => Effect.Effect<void>;
41
+ }
@@ -0,0 +1,144 @@
1
+ import { uniqueKey } from "./implementation.mjs";
2
+ import { reported } from "./reported.mjs";
3
+ import { Cause, Clock, Duration, Effect, Option, Queue, Semaphore, Stream } from "effect";
4
+ // src/contract/log.ts
5
+ const defaultBounds = {
6
+ maxAge: Duration.minutes(10),
7
+ maxEvents: 1e4
8
+ };
9
+ /** A log storage in memory, for the process that makes it. */
10
+ const memoryStorage = () => {
11
+ let position;
12
+ let newest = 0;
13
+ const entries = /* @__PURE__ */ new Map();
14
+ return {
15
+ position: Effect.sync(() => position),
16
+ put: (next, entry) => Effect.sync(() => {
17
+ position = next;
18
+ if (entry === void 0) return;
19
+ entries.set(entry.seq, entry.stored);
20
+ newest = Math.max(newest, entry.seq);
21
+ }),
22
+ list: (from, limit) => Effect.sync(() => {
23
+ const listed = [];
24
+ const oldest = entries.keys().next().value;
25
+ if (oldest === void 0) return listed;
26
+ for (let seq = Math.max(from, oldest); seq <= newest && listed.length < limit; seq += 1) {
27
+ const stored = entries.get(seq);
28
+ if (stored !== void 0) listed.push([seq, stored]);
29
+ }
30
+ return listed;
31
+ }),
32
+ delete: (seqs) => Effect.sync(() => {
33
+ for (const seq of seqs) entries.delete(seq);
34
+ })
35
+ };
36
+ };
37
+ const separator = ":";
38
+ const cursorAt = (epoch, seq) => `${epoch}${separator}${seq}`;
39
+ const seqIn = (epoch, cursor) => {
40
+ const [named, seq, ...rest] = cursor.split(separator);
41
+ const parsed = Number(seq);
42
+ return named === epoch && rest.length === 0 && Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
43
+ };
44
+ /**
45
+ * A log's entries over `storage`, read by cursor: a cursor is the storage's epoch and the
46
+ * sequence number of the last entry it has seen, so one minted by another epoch, such as a
47
+ * memory log's before a restart or a storage's before it was emptied, reads as truncated, as
48
+ * does one whose next entries were dropped. An epoch is minted where the storage holds none.
49
+ */
50
+ const makeLogStore = (storage, { maxAge = defaultBounds.maxAge, maxEvents = defaultBounds.maxEvents } = {}) => {
51
+ if (!Number.isSafeInteger(maxEvents) || maxEvents < 1) throw new Error(`Invalid log maxEvents: ${maxEvents} (give a positive integer)`);
52
+ const age = Duration.toMillis(Duration.fromInputUnsafe(maxAge));
53
+ const turn = Semaphore.makeUnsafe(1);
54
+ const positioned = Effect.flatMap(storage.position, (found) => {
55
+ if (found !== void 0) return Effect.succeed(found);
56
+ const minted = {
57
+ epoch: uniqueKey(),
58
+ last: 0
59
+ };
60
+ return Effect.as(storage.put(minted), minted);
61
+ });
62
+ const oldestKept = (last, now) => Effect.gen(function* () {
63
+ for (let batch = 1;; batch = 64) {
64
+ const listed = yield* storage.list(0, batch);
65
+ const young = listed.findIndex(([, { at }]) => at >= now - age);
66
+ const dropped = young === -1 ? listed : listed.slice(0, young);
67
+ if (dropped.length > 0) yield* storage.delete(dropped.map(([seq]) => seq));
68
+ const first = listed[dropped.length];
69
+ if (first !== void 0) return Math.max(first[0], last - maxEvents + 1);
70
+ if (listed.length < batch) return last + 1;
71
+ }
72
+ });
73
+ return {
74
+ append: (logged) => turn.withPermit(Effect.gen(function* () {
75
+ const { epoch, last } = yield* positioned;
76
+ const seq = last + 1;
77
+ const at = yield* Clock.currentTimeMillis;
78
+ yield* storage.put({
79
+ epoch,
80
+ last: seq
81
+ }, {
82
+ seq,
83
+ stored: {
84
+ at,
85
+ logged
86
+ }
87
+ });
88
+ if (seq > maxEvents) yield* storage.delete([seq - maxEvents]);
89
+ })),
90
+ read: (cursor, limit) => turn.withPermit(Effect.gen(function* () {
91
+ const { epoch, last } = yield* positioned;
92
+ const oldest = yield* oldestKept(last, yield* Clock.currentTimeMillis);
93
+ const seq = cursor === null ? last : seqIn(epoch, cursor);
94
+ const { after, truncated } = seq === void 0 || seq > last || seq + 1 < oldest ? {
95
+ after: oldest - 1,
96
+ truncated: true
97
+ } : {
98
+ after: seq,
99
+ truncated: false
100
+ };
101
+ const pending = yield* storage.list(after + 1, limit + 1);
102
+ const page = pending.slice(0, limit);
103
+ return {
104
+ entries: page.map(([at, stored]) => ({
105
+ seq: at,
106
+ cursor: cursorAt(epoch, at),
107
+ at: stored.at,
108
+ value: stored.logged
109
+ })),
110
+ after: cursorAt(epoch, after),
111
+ cursor: cursorAt(epoch, page.at(-1)?.[0] ?? after),
112
+ truncated,
113
+ hasMore: pending.length > limit
114
+ };
115
+ }))
116
+ };
117
+ };
118
+ /** A log over `store`, waking its followers as each entry is appended. */
119
+ const makeLog = (store) => {
120
+ const followers = /* @__PURE__ */ new Set();
121
+ const append = (value, key) => Effect.andThen(store.append({
122
+ value,
123
+ key
124
+ }), Effect.sync(() => {
125
+ for (const woken of followers) Queue.offerUnsafe(woken, void 0);
126
+ }));
127
+ const following = Effect.acquireRelease(Effect.tap(Queue.sliding(1), (woken) => Effect.sync(() => followers.add(woken))), (woken) => Effect.sync(() => followers.delete(woken)));
128
+ return {
129
+ append,
130
+ feed: (emit) => emit.pipe(Stream.runForEach(({ value, key }) => append(value, key)), Effect.matchCauseEffect({
131
+ onFailure: (cause) => Cause.hasInterruptsOnly(cause) ? Effect.void : reported(cause),
132
+ onSuccess: () => Effect.logWarning("A log source's emit stream ended: it appends nothing more")
133
+ })),
134
+ read: store.read,
135
+ follow: (cursor, limit) => Stream.unwrap(Effect.map(following, (woken) => Stream.paginate({
136
+ cursor,
137
+ wait: false
138
+ }, (state) => Effect.map(Effect.andThen(state.wait ? Queue.take(woken) : Effect.void, store.read(state.cursor, limit)), (answered) => [[answered], Option.some({
139
+ cursor: answered.cursor,
140
+ wait: !answered.hasMore
141
+ })]))))
142
+ };
143
+ };
144
+ export { makeLog, makeLogStore, memoryStorage };
@@ -1,5 +1,4 @@
1
1
  import { Schema, SchemaAST } from "effect";
2
- //#region src/contract/media.d.ts
3
2
  /**
4
3
  * An image, a media field of a success: `{ data: Uint8Array, mimeType: string }`, `mimeType` such
5
4
  * as `image/png`. A top-level field of a struct success, one, optional, or a required array, or the
@@ -13,5 +12,4 @@ import { Schema, SchemaAST } from "effect";
13
12
  export declare const Image: Schema.Struct<{
14
13
  readonly data: Schema.Uint8Array;
15
14
  readonly mimeType: Schema.String;
16
- }>;
17
- //#endregion
15
+ }>;
@@ -1,5 +1,5 @@
1
1
  import { Schema } from "effect";
2
- //#region src/contract/media.ts
2
+ // src/contract/media.ts
3
3
  const mediaKindAnnotation = "effect-actions/media";
4
4
  const Media = Schema.Struct({
5
5
  data: Schema.Uint8Array,
@@ -18,5 +18,4 @@ const Media = Schema.Struct({
18
18
  const Image = Media.annotate({ [mediaKindAnnotation]: "image" });
19
19
  const isMarked = (ast) => ast.annotations?.[mediaKindAnnotation] === "image";
20
20
  const kindOf = (ast) => isMarked(ast) && ast.encoding === void 0 ? "image" : void 0;
21
- //#endregion
22
21
  export { Image, Media, isMarked, kindOf };
@@ -1,8 +1,16 @@
1
- //#region src/contract/names.ts
1
+ // src/contract/names.ts
2
2
  const namePattern = "[A-Za-z0-9_-]{1,128}";
3
- const validName = new RegExp(`^${namePattern}$`);
3
+ const validName = new RegExp(`^${namePattern}$`, "u");
4
+ const reserved = [["then", "which would make a client a thenable, awaited as a promise"], ["watch", "every client's method for events"]];
5
+ const reservedNames = reserved.map(([name]) => name);
6
+ /** The reserved names as a message lists them: `"then" or "watch"`. */
7
+ const reservedList = reservedNames.map((name) => `"${name}"`).join(" or ");
4
8
  const assertName = (name) => {
5
- if (!validName.test(name) || name === "then") throw new Error(`Invalid action name: ${name} (use ${namePattern}, other than "then")`);
9
+ if (!validName.test(name) || reservedNames.includes(name)) throw new Error(`Invalid action name: ${name} (use ${namePattern}, other than ${reserved.map(([reservedName, why]) => `"${reservedName}", ${why}`).join(", and ")})`);
10
+ };
11
+ const validEventName = new RegExp(`^[a-z0-9_-]+(\\.[a-z0-9_-]+)*$`, "u");
12
+ const assertEventName = (name) => {
13
+ if (name.length > 128 || !validEventName.test(name)) throw new Error(`Invalid event name: ${name} (use lowercase dot-separated segments of [a-z0-9_-], at most 128 characters)`);
6
14
  };
7
15
  const assertDistinct = (what, items, nameOf, { claimantOf = nameOf, labelOf = nameOf, fix }) => {
8
16
  const seen = /* @__PURE__ */ new Map();
@@ -18,10 +26,11 @@ const assertDistinct = (what, items, nameOf, { claimantOf = nameOf, labelOf = na
18
26
  }
19
27
  };
20
28
  const heldOnce = "pass one implementation of it, or rename one";
21
- const assertOnce = (what, actions, fix = "list it once, or rename one") => assertDistinct(what, actions, (action) => action.name, { fix });
29
+ const assertOnce = (what, actions, fix = "list it once, or rename one") => {
30
+ assertDistinct(what, actions, (action) => action.name, { fix });
31
+ };
22
32
  const assertKnown = (what, keys, names, fix) => {
23
33
  const unknown = keys.filter((key) => !names.includes(key));
24
34
  if (unknown.length > 0) throw new Error(`Unknown ${what}: ${unknown.join(", ")} (${fix})`);
25
35
  };
26
- //#endregion
27
- export { assertDistinct, assertKnown, assertName, assertOnce, heldOnce };
36
+ export { assertDistinct, assertEventName, assertKnown, assertName, assertOnce, heldOnce, reservedList, reservedNames };
@@ -0,0 +1,29 @@
1
+ import { Any } from "../Event.mjs";
2
+ import { Effect, Schema } from "effect";
3
+ /**
4
+ * Occurrences were missed: the cursor resumed from was no longer served, or delivery skipped
5
+ * some. `cursor` is where delivery continues.
6
+ */
7
+ export declare const Gap: Schema.TaggedStruct<"Gap", {
8
+ readonly cursor: Schema.NullOr<Schema.String>;
9
+ }>;
10
+ /** A gap in what a subscriber received. */
11
+ export type Gap = typeof Gap.Type;
12
+ /**
13
+ * Where a feed has read up to without an occurrence to show for it, such as a quiet page or an
14
+ * occurrence a subscriber's `match` rejected: a watch keeps it as its cursor and never yields it.
15
+ */
16
+ export declare const Checked: Schema.TaggedStruct<"Checked", {
17
+ readonly cursor: Schema.NullOr<Schema.String>;
18
+ }>;
19
+ export type Checked = typeof Checked.Type;
20
+ declare const fieldsOf: <Name extends Schema.Top, Data extends Schema.Top>(name: Name, data: Data) => {
21
+ eventId: Schema.String;
22
+ name: Name;
23
+ timestamp: Schema.DateTimeUtc;
24
+ data: Data;
25
+ cursor: Schema.NullOr<Schema.String>;
26
+ };
27
+ /** The schema of an occurrence of `E`. */
28
+ export type OccurrenceSchema<E extends Any> = Schema.TaggedStruct<"Occurrence", ReturnType<typeof fieldsOf<Schema.Literal<E["name"]>, E["payload"]>>>;
29
+ export {};
@@ -0,0 +1,45 @@
1
+ import { Effect, Schema, Struct } from "effect";
2
+ // src/contract/occurrence.ts
3
+ /**
4
+ * Occurrences were missed: the cursor resumed from was no longer served, or delivery skipped
5
+ * some. `cursor` is where delivery continues.
6
+ */
7
+ const Gap = Schema.TaggedStruct("Gap", { cursor: Schema.NullOr(Schema.String) });
8
+ /**
9
+ * Where a feed has read up to without an occurrence to show for it, such as a quiet page or an
10
+ * occurrence a subscriber's `match` rejected: a watch keeps it as its cursor and never yields it.
11
+ */
12
+ const Checked = Schema.TaggedStruct("Checked", { cursor: Schema.NullOr(Schema.String) });
13
+ const fieldsOf = (name, data) => ({
14
+ eventId: Schema.String.annotate({ description: "The source's id for it, or one generated: a subscriber deduplicates by it." }),
15
+ name,
16
+ timestamp: Schema.DateTimeUtc,
17
+ data,
18
+ cursor: Schema.NullOr(Schema.String).annotate({ description: "Where to resume after it, null for an event type that cannot be resumed." })
19
+ });
20
+ /** An occurrence of `event`, as a subscriber decodes it. */
21
+ const OccurrenceOf = (event) => Schema.TaggedStruct("Occurrence", fieldsOf(Schema.Literal(event.name), event.payload));
22
+ /** Any occurrence, its data erased, as a feed makes it. */
23
+ const AnyOccurrence = Schema.TaggedStruct("Occurrence", fieldsOf(Schema.String, Schema.Unknown));
24
+ /** What a subscriber of `event` decodes: an occurrence, or a gap. */
25
+ const ReceivedOf = (event) => Schema.Union([OccurrenceOf(event), Gap]);
26
+ const untaggedOf = (event) => OccurrenceOf(event).mapFields((fields) => ({
27
+ ...Struct.omit(fields, ["_tag"]),
28
+ timestamp: Schema.DateTimeUtcFromString,
29
+ data: Schema.toCodecJson(event.payload)
30
+ }));
31
+ /**
32
+ * How a subscriber of `event` decodes an occurrence sent as JSON without its tag, as MCP's
33
+ * notifications and a webhook's body carry one.
34
+ */
35
+ const untaggedOccurrenceOf = (event) => {
36
+ const occurrence = OccurrenceOf(event);
37
+ const decode = Schema.decodeUnknownEffect(untaggedOf(event));
38
+ return (wire) => Effect.map(decode(wire), (fields) => occurrence.make(fields));
39
+ };
40
+ /** How a surface encodes an occurrence of `event` as JSON without its tag, its counterpart. */
41
+ const encodeUntaggedOf = (event) => {
42
+ const encode = Schema.encodeEffect(untaggedOf(event));
43
+ return (occurrence) => Effect.orDie(encode(occurrence));
44
+ };
45
+ export { AnyOccurrence, Checked, Gap, OccurrenceOf, ReceivedOf, encodeUntaggedOf, untaggedOccurrenceOf };
@@ -0,0 +1,13 @@
1
+ import { Checked } from "./occurrence.mjs";
2
+ import { Any, Occurrence, Received } from "../Event.mjs";
3
+ import { Duration, Effect, Stream } from "effect";
4
+ /** One poll's answer, as a surface sends it and a client reads it. */
5
+ export interface Page {
6
+ readonly events: ReadonlyArray<Occurrence<Any>>;
7
+ readonly cursor: string | null;
8
+ readonly truncated: boolean;
9
+ readonly hasMore: boolean;
10
+ readonly nextPoll: Duration.Duration;
11
+ }
12
+ /** What a feed's stream yields: what a subscriber receives, and where it has read up to. */
13
+ export type Fed = Received<Any> | Checked;