@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
package/docs/Event.md ADDED
@@ -0,0 +1,281 @@
1
+ # Event
2
+
3
+ `Event.make` defines an event type: something that happens, which subscribers receive as it happens, each subscription selecting with its own arguments. `Event.implement` binds it to the source its occurrences come from, behind an authorizer. Event implementations go in the same list as action implementations: `Action.client` watches them in process; an MCP endpoint ([ActionMcp.md](ActionMcp.md#serve-events)), HTTP routes ([ActionHttp.md](ActionHttp.md#serve-and-watch-events)) and rpcs ([ActionRpc-events.md](ActionRpc-events.md)) serve them; a server POSTs them to a subscriber's callback by webhook ([Webhook.md](Webhook.md)); and a CLI watches them ([ActionCli.md](ActionCli.md#watch-events)). Events are experimental: their surfaces follow MCP's events extension, SEP-3415, a draft, and may change with it.
4
+
5
+ A message the library throws says its fix; for a compiler diagnostic or Effect's own error, search [Event-failures.md](Event-failures.md). For one symbol, `grep -rn <symbol> node_modules/@gjermundgaraba/effect-actions/dist/`: each declaration's JSDoc carries its rules and gotchas, and this page only what an example cannot show.
6
+
7
+ ## Define an event type
8
+
9
+ One `Event.make` per event type, in the domain's contract file, beside its actions: `input` is what a subscription takes, `payload` what each occurrence carries, and `caller` who may subscribe, as on an action ([Action.md](Action.md#say-who-may-call-an-action)).
10
+
11
+ ```ts example=build-events/src/builds/Builds.ts
12
+ import * as Action from "@gjermundgaraba/effect-actions/Action";
13
+ import * as Event from "@gjermundgaraba/effect-actions/Event";
14
+ import { Schema } from "effect";
15
+
16
+ import { CurrentMember } from "../identity/Identity.js";
17
+
18
+ const Pipeline = Schema.String.annotate({
19
+ description: "A pipeline's name, such as api.",
20
+ });
21
+
22
+ const Status = Schema.Literals(["passed", "failed"]).annotate({
23
+ description: "How the build ended.",
24
+ });
25
+
26
+ export const Build = Schema.Struct({
27
+ id: Schema.String,
28
+ pipeline: Pipeline,
29
+ status: Status,
30
+ });
31
+
32
+ export type Build = typeof Build.Type;
33
+
34
+ // DEMO ONLY: a CI runner reports finished builds; here a member does, so the example can
35
+ // cause the event it watches.
36
+ export const FinishBuild = Action.make("finishBuild", {
37
+ description: "Record that a build of a pipeline finished.",
38
+ input: { pipeline: Pipeline, status: Status },
39
+ success: Build,
40
+ readOnly: false,
41
+ caller: CurrentMember,
42
+ });
43
+
44
+ // A build run here, which reports each step as it starts, before it answers how the build ended.
45
+ // A caller reads the steps only if it asks, with `onProgress`; each surface carries them its
46
+ // own way. They are a snapshot to show: a caller falling behind misses some, never the answer.
47
+ // What a caller must see every time is the answer, or `build.finished`, which this build causes.
48
+ export const StartBuild = Action.make("startBuild", {
49
+ description:
50
+ "Run a build of a pipeline, reporting each step, and record how it ended.",
51
+ input: { pipeline: Pipeline },
52
+ success: Build,
53
+ progress: {
54
+ step: Schema.String.annotate({
55
+ description: "The step that started, such as test.",
56
+ }),
57
+ done: Schema.Finite.annotate({
58
+ description: "How many steps finished before it.",
59
+ }),
60
+ total: Schema.Finite.annotate({
61
+ description: "How many steps the build has.",
62
+ }),
63
+ },
64
+ readOnly: false,
65
+ caller: CurrentMember,
66
+ });
67
+
68
+ // A build this member may not see is not found, as one that never ran: its id tells nothing.
69
+ export class BuildNotFound extends Schema.TaggedError<
70
+ BuildNotFound,
71
+ { readonly brand: unique symbol }
72
+ >()("BuildNotFound", { buildId: Schema.String }, { httpApiStatus: 404 }) {}
73
+
74
+ // An action answering a stream: its handler returns a `Stream` of lines, and every client's
75
+ // call returns one, each line arriving as the server sends it. HTTP, RPC and a command carry a
76
+ // stream; an MCP tool answers one result, the lines collected once the stream ends, so the MCP
77
+ // endpoint in `routes.ts` leaves it out rather than put a whole log in a model's context.
78
+ export const TailLog = Action.make("tailLog", {
79
+ description: "Stream a build's log, line by line.",
80
+ input: {
81
+ buildId: Schema.String.annotate({
82
+ description: "The build's id, such as build-1.",
83
+ }),
84
+ },
85
+ success: Action.Stream({ line: Schema.String }),
86
+ error: BuildNotFound,
87
+ readOnly: true,
88
+ caller: CurrentMember,
89
+ });
90
+
91
+ // An event type: what a subscription takes, what each occurrence carries, and who may
92
+ // subscribe. Like an action's contract, it imports no server code.
93
+ export const BuildFinished = Event.make("build.finished", {
94
+ description: "A build of a pipeline you may see finished.",
95
+ input: {
96
+ pipeline: Schema.optional(Pipeline).annotate({
97
+ description: "Only this pipeline's builds.",
98
+ }),
99
+ status: Schema.optional(Status).annotate({
100
+ description: "Only builds that ended so.",
101
+ }),
102
+ },
103
+ payload: Build,
104
+ caller: CurrentMember,
105
+ });
106
+
107
+ // An event whose occurrence is the whole value: how a pipeline's last build ended, `null` before
108
+ // its first. A subscriber is sent it as it is, then again each time a build of it finishes.
109
+ export const PipelineStatus = Event.make("pipeline.status", {
110
+ description:
111
+ "How a pipeline's last build ended: first as it is, then each time one finishes.",
112
+ input: { pipeline: Pipeline },
113
+ payload: {
114
+ status: Schema.NullOr(Status).annotate({
115
+ description: "How it ended; null before any did.",
116
+ }),
117
+ },
118
+ caller: CurrentMember,
119
+ });
120
+ ```
121
+
122
+ The name's form is in the message `Invalid event name` throws. A contract says nothing about how it is delivered: each surface decides that, as it decides an action's route.
123
+
124
+ ## Read from an upstream you can ask
125
+
126
+ When the upstream can be asked what happened since a position, such as a table with a sequence column, a change feed or a history API, give `check`: one function, from the subscription's input and a cursor to the occurrences since it and the cursor after them. It answers every subscriber, one that polls and one that holds a stream open alike, running as that subscriber.
127
+
128
+ ```ts example=build-events/src/builds/events.ts
129
+ import * as Event from "@gjermundgaraba/effect-actions/Event";
130
+ import { Effect } from "effect";
131
+
132
+ import { CurrentMember } from "../identity/Identity.js";
133
+ import { BuildFinished } from "./Builds.js";
134
+ import { BuildStore } from "./store.js";
135
+
136
+ // A check source: one function answers "what finished since this cursor?" for every
137
+ // subscriber, whether it polls or holds a stream open, on every surface. The builder runs once,
138
+ // at startup; the check runs per subscriber, as that subscriber.
139
+ export const buildEvents = Event.implement(
140
+ BuildFinished,
141
+ Effect.gen(function* () {
142
+ const store = yield* BuildStore;
143
+
144
+ return {
145
+ check: ({ pipeline, status }, cursor) =>
146
+ Effect.gen(function* () {
147
+ const member = yield* CurrentMember;
148
+
149
+ // A first subscription asks with `null`: the store answers the current position, so
150
+ // the subscriber starts from now.
151
+ const page = yield* store.finishedSince(
152
+ member.pipelines,
153
+ cursor,
154
+ 100
155
+ );
156
+
157
+ return {
158
+ cursor: page.cursor,
159
+ hasMore: page.hasMore,
160
+ // A cursor the store cannot resume from: the subscriber is sent a gap first.
161
+ truncated: page.truncated,
162
+ events: page.builds
163
+ .filter(
164
+ (build) => pipeline === undefined || build.pipeline === pipeline
165
+ )
166
+ .filter(
167
+ (build) => status === undefined || build.status === status
168
+ )
169
+ // The store's own id, within its epoch: a subscriber that sees a build twice,
170
+ // after a reconnect, drops the second, and never drops a build of a restarted store
171
+ // as one it saw before.
172
+ .map((build) => ({
173
+ eventId: `${page.epoch}/${build.id}`,
174
+ data: build,
175
+ })),
176
+ };
177
+ }),
178
+ };
179
+ }),
180
+ // Every member may watch; which builds each sees is the store's check, by pipeline.
181
+ { authorize: Event.allowAll }
182
+ );
183
+ ```
184
+
185
+ [build-events/src/builds/store.ts](../examples/build-events/src/builds/store.ts) is the store. What a check answers for a first subscription and a stale cursor is on `Event.Page`; the rest of a check's rules are on `Event.Check`.
186
+
187
+ A subscriber holding a stream open is read again every 5 seconds. Where the application writes the store itself, it wakes them at once: after the write, `Event.Events`' `notify`, as [build-events/src/builds/handlers.ts](../examples/build-events/src/builds/handlers.ts) does after each build finishes. A store another process writes is read at the next poll; when a Durable Object reads it is on the Cloudflare package's `ActionDurableObject.handlers`.
188
+
189
+ - **Check what a subscriber may see where the data is read**, as an action's handler does: `authorize` decides whether it may subscribe at all, and receives the event type, not the subscription's input.
190
+
191
+ ## Send a value whole
192
+
193
+ When an occurrence is the whole value, such as a status or a list small enough to send whole, give `current`: the value as it is, for the subscription's input. A subscriber is sent it first, then again after each `notify` that reaches its key, never each change it missed one by one: the library keeps the revision that is its cursor, so the store keeps none.
194
+
195
+ ```ts example=build-events/src/builds/status.ts
196
+ import * as Event from "@gjermundgaraba/effect-actions/Event";
197
+ import { Effect } from "effect";
198
+
199
+ import { CurrentMember } from "../identity/Identity.js";
200
+ import { PipelineStatus } from "./Builds.js";
201
+ import { BuildStore } from "./store.js";
202
+
203
+ // A current source: the value as it is, which the library sends first and again after each
204
+ // notify reaching the subscriber's key, keeping the revision that tells them apart, so the store
205
+ // keeps no cursor for it. The key is the pipeline: a build of another wakes no one here.
206
+ export const statusEvents = Event.implement(
207
+ PipelineStatus,
208
+ Effect.gen(function* () {
209
+ const store = yield* BuildStore;
210
+
211
+ return {
212
+ current: ({ pipeline }) =>
213
+ Effect.gen(function* () {
214
+ const member = yield* CurrentMember;
215
+
216
+ return { status: yield* store.statusOf(member.pipelines, pipeline) };
217
+ }),
218
+ key: ({ pipeline }) => pipeline,
219
+ };
220
+ }),
221
+ // Every member may watch; which pipelines each sees is the store's check.
222
+ { authorize: Event.allowAll }
223
+ );
224
+ ```
225
+
226
+ [build-events/src/builds/handlers.ts](../examples/build-events/src/builds/handlers.ts) notifies after each build with its pipeline as the key, so a build wakes only that pipeline's subscribers. What a notify without a change sends, and why a value written as it is read is not missed, is on `Event.Current`.
227
+
228
+ ## Keep what nothing else keeps
229
+
230
+ When no store keeps the occurrences, such as a request made and answered at once or an upstream that only pushes, give `log`: the library keeps each occurrence published to it for subscribers resuming from a cursor, and gives each subscriber what its `match` accepts, shaped by its `transform`.
231
+
232
+ ```ts example=build-events/src/deploys/events.ts
233
+ import * as Event from "@gjermundgaraba/effect-actions/Event";
234
+ import { Effect } from "effect";
235
+
236
+ import { CurrentMember } from "../identity/Identity.js";
237
+ import { Approvers } from "./approvers.js";
238
+ import { DeployRequested } from "./Deploys.js";
239
+
240
+ // A log source, for occurrences nothing else keeps: the library keeps each one `requestDeploy`
241
+ // publishes, for 10 minutes and at most 10,000 of them by default, and gives each subscriber
242
+ // what its `match` accepts. A subscriber resuming from a cursor gets what the log still holds:
243
+ // after a restart, or once its occurrences are dropped, a gap first. An upstream you can ask
244
+ // "what since?", such as a table, is a check source.
245
+ export const deployEvents = Event.implement(
246
+ DeployRequested,
247
+ Effect.gen(function* () {
248
+ const approvers = yield* Approvers;
249
+
250
+ return {
251
+ log: {},
252
+ match: ({ environment }, deploy) =>
253
+ environment === undefined || deploy.environment === environment,
254
+ // Set up while each subscription lasts, as the subscriber: a stream's as long as it is
255
+ // read, a poll's until 5 minutes after its last poll. It may run again for one
256
+ // subscription, after a restart, so standing by twice must be harmless.
257
+ subscription: ({ environment }) =>
258
+ Effect.flatMap(CurrentMember, (member) =>
259
+ approvers.standBy(environment, member.id)
260
+ ),
261
+ };
262
+ }),
263
+ { authorize: Event.allowAll }
264
+ );
265
+ ```
266
+
267
+ The action publishes each request with `Event.Events`' `publish`, in [build-events/src/deploys/handlers.ts](../examples/build-events/src/deploys/handlers.ts). An upstream that is a stream, such as a message bus, gives it as `log.emit`, each occurrence with the `key` `publish` would take, which the library consumes from the start of a surface serving its event type: kitchen-sink's [issues/events.ts](../examples/kitchen-sink/src/issues/events.ts). How much the log keeps, and what a cursor from another process or a restart reads as, are on `Event.Log`.
268
+
269
+ - **Prefer `check` wherever the upstream keeps a cursor of its own**: a subscriber then resumes after a restart without a gap.
270
+
271
+ ## Set up a subscription upstream
272
+
273
+ When each subscription needs something set up upstream while it lasts, such as joining the channel it watches, give the source a `subscription`, as `deployEvents` above does: a scoped Effect, whose finalizer tears it down, run as the subscriber. How long it lasts is on the `subscription` option of `Event.Check` and `Event.Log`.
274
+
275
+ ## Announce to the subscribers
276
+
277
+ `Event.Events` is where occurrences are announced as they happen: `notify` for a check source, `publish` for a log source. Domain code yields it as it yields a store, and the entrypoint provides one, `Event.layerMemory`, around both the handlers that announce and the surfaces that serve, as [build-events/src/server.ts](../examples/build-events/src/server.ts) does; a host keeping logs elsewhere builds its own with `Event.makeEvents`, as the Cloudflare package's `ActionDurableObject.make` does. A source's `key`, such as the room a subscriber follows, lets an announcement given that key reach that room's subscribers alone, as [live-rpc/src/chat/events.ts](../examples/live-rpc/src/chat/events.ts) keys its messages by room. The rules are on `Event.Events`.
278
+
279
+ ## Watch in process
280
+
281
+ `Action.client` given event implementations has `watch(event, input, options)`: a `Stream` of `Event.Received`, the occurrences and a `Gap` wherever some were missed, owing per subscription what an action's call owes. [Testing.md](Testing.md#test-events) watches one in a test.
package/docs/README.md CHANGED
@@ -1,103 +1,72 @@
1
1
  # effect-actions
2
2
 
3
- `@gjermundgaraba/effect-actions` defines an Effect action once, as a contract with one
4
- implementation, and serves it over HTTP, MCP and Effect RPC, gives it to a model as a native
5
- Toolkit, and runs it from a CLI. Every surface is Effect's own server or client, running the
6
- same schemas, handler and authorization.
3
+ `@gjermundgaraba/effect-actions` defines an Effect action once, as a contract with one implementation, and serves it over HTTP, MCP and Effect RPC, gives it to a model as a native Toolkit, and runs it from a CLI. Every surface is Effect's own server or client, running the same schemas, handler and authorization.
7
4
 
8
5
  ## How to read these pages
9
6
 
10
7
  **Arriving with an error?** A message the library throws says its fix; for any other, step 5.
11
8
 
12
- 1. **Open one page per tool call, with your file-reading tool.** Pages are kept small to reduce
13
- truncation. Several in one call, however many commands it runs, can be cut by its output
14
- limit, with no mark or with one that understates what is lost. If an output says it was
15
- truncated, read that page again alone.
16
- 2. **Start from the example closest to what you are building** ([Examples](#examples)). Its
17
- README has a layout tree, and all but quickstart's a "Where to look" table naming the file
18
- for each thing. Copy its layout and idioms: the examples are the reference for how code is
19
- written, and its comments carry the traps it shows. An example shows one way, not the rules
20
- or the alternatives: read steps 3 and 4 even when it fits.
9
+ 1. **Open one page per tool call, with your file-reading tool.** Pages are kept small to reduce truncation. Several in one call, however many commands it runs, can be cut by its output limit, with no mark or with one that understates what is lost. If an output says it was truncated, read that page again alone.
10
+ 2. **Start from the example closest to what you are building** ([Examples](#examples)). Its README has a layout tree, and all but quickstart's a "Where to look" table naming the file for each thing. Copy its layout and idioms: the examples are the reference for how code is written, and its comments carry the traps it shows. An example shows one way, not the rules or the alternatives: read steps 3 and 4 even when it fits.
21
11
  3. **Read [Action.md](Action.md) completely.** Every surface builds on it.
22
- 4. **Read the page of each surface you serve** ([Pages](#pages)), and the example files its task
23
- sections link.
24
- 5. **For a compiler diagnostic or Effect's own error, search the `*-failures.md` pages** for a
25
- fragment of the message without quotes, your own code's first compile error included:
26
- `grep -rnF "<fragment>" node_modules/@gjermundgaraba/effect-actions/docs/*-failures.md`. Each
27
- module page has one beside it, such as [Action-failures.md](Action-failures.md).
28
-
29
- A service others call is [notes-service](../examples/notes-service/) plus
30
- [Action.md](Action.md), [ActionHttp.md](ActionHttp.md), [ActionMcp.md](ActionMcp.md),
31
- [Authentication.md](Authentication.md) and [Testing.md](Testing.md).
32
-
33
- Where things are, in a project that installs the package: these pages in
34
- `node_modules/@gjermundgaraba/effect-actions/docs/`, the examples in `examples/` beside them, and
35
- the declarations with their JSDoc, the reference for signatures, under `dist/`, all at
36
- the installed version. Install first ([setup.md](setup.md#install)).
12
+ 4. **Read the page of each surface you serve** ([Pages](#pages)), and the example files its task sections link.
13
+ 5. **For a compiler diagnostic or Effect's own error, search the `*-failures.md` pages** for a fragment of the message without quotes, your own code's first compile error included: `grep -rnF "<fragment>" node_modules/@gjermundgaraba/effect-actions/docs/*-failures.md`. Each module page has one beside it, such as [Action-failures.md](Action-failures.md).
14
+
15
+ A service others call is [notes-service](../examples/notes-service/) plus [Action.md](Action.md), [ActionHttp.md](ActionHttp.md), [ActionMcp.md](ActionMcp.md), [Authentication.md](Authentication.md) and [Testing.md](Testing.md).
16
+
17
+ Where things are, in a project that installs the package: these pages in `node_modules/@gjermundgaraba/effect-actions/docs/`, the examples in `examples/` beside them, and the declarations with their JSDoc, the reference for signatures, under `dist/`, all at the installed version. Install first ([setup.md](setup.md#install)).
37
18
 
38
19
  ## Pages
39
20
 
40
- | Read | When you need to |
41
- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
- | [setup.md](setup.md) | install, write `tsconfig.json`, import the modules, write the minimal program, lay out an application, bundle a browser client |
43
- | [Action.md](Action.md) | define a contract and who may call it, implement it, authorize calls, fail with declared and built-in errors, call actions from a job, share one build across servers |
44
- | [ActionHttp.md](ActionHttp.md) | bind actions to JSON routes, serve them, limit request bodies, split a binding across layers and add middleware, publish OpenAPI, call them from Effect or a browser |
45
- | [ActionMcp.md](ActionMcp.md) | serve MCP tools over HTTP or stdio, describe them to a model, return images, protect them, allow browser clients, add prompts and resources, send notifications |
46
- | [ActionRpc.md](ActionRpc.md) | bind actions to Effect rpcs, serve them on a protocol, call them, send request context with a call |
47
- | [ActionToolkit.md](ActionToolkit.md) | give a model actions as a native Effect AI `Toolkit`, name the caller, ask for approval |
48
- | [ActionCli.md](ActionCli.md) | run actions as local commands, or call a server from commands; shape flags, input and output; name the caller; add a command of your own |
49
- | [Authentication.md](Authentication.md) | declare how callers sign in, verify credentials, publish OAuth discovery, step up for a scope |
50
- | [Authentication-advanced.md](Authentication-advanced.md) | use an API key or a cookie, read the request in a verifier, protect a route of your own, run a request policy before authentication |
51
- | [Testing.md](Testing.md) | test an implementation in process, and routes, tools and rpcs in memory, as a signed-in caller; assert on errors |
21
+ | Read | When you need to |
22
+ | --- | --- |
23
+ | [setup.md](setup.md) | install, write `tsconfig.json`, import the modules, write the minimal program, lay out an application, bundle a browser client |
24
+ | [Action.md](Action.md) | define a contract and who may call it, implement it, authorize calls, fail with declared and built-in errors, answer a retried call once, call actions from a job, share one build across servers |
25
+ | [Action-streams.md](Action-streams.md) | report progress while an action runs, answer a stream of elements |
26
+ | [Event.md](Event.md) | define an event type, read it from an upstream you can ask or one that pushes, set up a subscription upstream, watch events in process |
27
+ | [ActionHttp.md](ActionHttp.md) | bind actions to JSON routes, serve them, limit request bodies, split a binding across layers and add middleware, publish OpenAPI, call them from Effect or a browser, serve and watch events |
28
+ | [ActionMcp.md](ActionMcp.md) | serve MCP tools over HTTP or stdio, describe them to a model, return images, protect them, serve events, allow browser clients, add prompts and resources, send notifications |
29
+ | [ActionRpc.md](ActionRpc.md) | bind actions to Effect rpcs, serve them on a protocol, call them, add native rpcs, send request context with a call |
30
+ | [ActionRpc-events.md](ActionRpc-events.md) | serve and watch events over rpcs, follow what a request names, such as a room |
31
+ | [ActionToolkit.md](ActionToolkit.md) | give a model actions as a native Effect AI `Toolkit`, name the caller, ask for approval |
32
+ | [ActionCli.md](ActionCli.md) | run actions as local commands, or call a server from commands; watch events; shape flags, input and output; name the caller; add a command of your own |
33
+ | [Webhook.md](Webhook.md) | deliver events to a subscriber's callback by webhook, signed, and watch by webhook |
34
+ | [Authentication.md](Authentication.md) | declare how callers sign in, verify credentials, publish OAuth discovery, step up for a scope |
35
+ | [Authentication-advanced.md](Authentication-advanced.md) | use an API key or a cookie, read the request in a verifier, protect a route of your own, run a request policy before authentication |
36
+ | [Testing.md](Testing.md) | test an implementation in process, and routes, tools and rpcs in memory, as a signed-in caller; test events and webhooks; assert on errors |
37
+ | `@gjermundgaraba/effect-actions-cloudflare`'s `docs/README.md` | serve actions, events and Effect rpcs from Cloudflare Durable Objects: install that package; its pages and example are in its own `node_modules` folder |
52
38
 
53
39
  ## Examples
54
40
 
55
- | Example | What it is | Start from it when |
56
- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
57
- | [quickstart](../examples/quickstart/) | One action, served as a JSON route and an MCP tool, called with its typed client. | You want the smallest thing that runs. |
58
- | [notes-service](../examples/notes-service/) | A service with bearer authentication: a JSON API and MCP tools from one implementation, a CLI, a browser client. | You are building a service others call. Most applications are this. |
59
- | [mcp-tools](../examples/mcp-tools/) | An MCP server whose tools wrap an upstream HTTP API, over stdio and over HTTP, with an image result. | You are putting an existing API in front of a model. |
60
- | [assistant](../examples/assistant/) | No server: the same implementations as a model's tools, with approval of writes, and as local commands. | A model calls your code in process, or you are building a local CLI. |
61
- | [live-rpc](../examples/live-rpc/) | Effect RPC over a WebSocket with a browser-shaped client, each message carrying its own token. | A client keeps a connection open, or the application speaks Effect RPC. |
62
- | [kitchen-sink](../examples/kitchen-sink/) | The reference: a multi-tenant issue tracker using every module and nearly every option, with an index. | You need a working use of one specific feature. |
41
+ | Example | What it is | Start from it when |
42
+ | --- | --- | --- |
43
+ | [quickstart](../examples/quickstart/) | One action, served as a JSON route and an MCP tool, called with its typed client. | You want the smallest thing that runs. |
44
+ | [notes-service](../examples/notes-service/) | A service with bearer authentication: a JSON API and MCP tools from one implementation, a CLI, a browser client. | You are building a service others call. Most applications are this. |
45
+ | [mcp-tools](../examples/mcp-tools/) | An MCP server whose tools wrap an upstream HTTP API, over stdio and over HTTP, with an image result. | You are putting an existing API in front of a model. |
46
+ | [assistant](../examples/assistant/) | No server: the same implementations as a model's tools, with approval of writes, and as local commands. | A model calls your code in process, or you are building a local CLI. |
47
+ | [live-rpc](../examples/live-rpc/) | Effect RPC over a WebSocket with a browser-shaped client, each message carrying its own token. | A client keeps a connection open, or the application speaks Effect RPC. |
48
+ | [build-events](../examples/build-events/) | A CI service whose members watch events over MCP, HTTP, RPC and a CLI: one read by cursor, one pushed upstream. | Something happens that subscribers should hear about. |
49
+ | [kitchen-sink](../examples/kitchen-sink/) | The reference: a multi-tenant issue tracker using every module and nearly every option, with an index. | You need a working use of one specific feature. |
63
50
 
64
- The index in [kitchen-sink](../examples/kitchen-sink/README.md#index) maps every export and
65
- option it uses to a file.
51
+ The index in [kitchen-sink](../examples/kitchen-sink/README.md#index) maps every export and option it uses to a file.
66
52
 
67
53
  ## Choose a surface
68
54
 
69
- | Callers | Use |
70
- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
71
- | Speak JSON over HTTP | `ActionHttp`; clients use `ActionHttp.client`, or `ActionHttp.fetchClient` outside an Effect |
72
- | Are Effect programs, such as a page over one WebSocket | `ActionRpc`; clients use `ActionRpc.client` |
73
- | Are MCP clients | `ActionMcp.layerHttp` for a hosted endpoint, `ActionMcp.runStdio` for a subprocess, which runs the handlers itself |
74
- | Are a model in the same process | `ActionToolkit` |
75
- | Are people or scripts in a terminal | `ActionCli`: `make` runs handlers locally, `remote` calls a server through its HTTP binding |
76
- | Are your own code in the same process, such as a job or a test | `Action.client` |
77
-
78
- Surfaces combine: every one that runs handlers takes the same implementations, one or a list,
79
- and enforces each contract's `caller` and its implementation's `authorize`
80
- ([Action.md](Action.md#authorize-calls)). What a call answers on each surface, a success, bad
81
- input, a declared error, a refusal or a defect, is on `ActionHttp.layer`, `ActionMcp.layerHttp`
82
- and `runStdio`, `ActionRpc.layer`, `ActionToolkit.make`, `ActionCli.command`, each typed client,
83
- and each built-in error: grep the symbol.
55
+ | Callers | Use |
56
+ | --- | --- |
57
+ | Speak JSON over HTTP | `ActionHttp`; clients use `ActionHttp.client`, or `ActionHttp.fetchClient` outside an Effect |
58
+ | Are Effect programs, such as a page over one WebSocket | `ActionRpc`; clients use `ActionRpc.client` |
59
+ | Are MCP clients | `ActionMcp.layerHttp` for a hosted endpoint, `ActionMcp.runStdio` for a subprocess, which runs the handlers itself |
60
+ | Are a model in the same process | `ActionToolkit` |
61
+ | Are people or scripts in a terminal | `ActionCli`: `make` runs handlers locally, `remote` calls a server through its HTTP binding |
62
+ | Are your own code in the same process, such as a job or a test | `Action.client` |
63
+
64
+ Surfaces combine: every one that runs handlers takes the same implementations, one or a list, and enforces each contract's `caller` and its implementation's `authorize` ([Action.md](Action.md#authorize-calls)). What a call answers on each surface, a success, bad input, a declared error, a refusal or a defect, is on `ActionHttp.layer`, `ActionMcp.layerHttp` and `runStdio`, `ActionRpc.layer`, `ActionToolkit.make`, `ActionCli.command`, each typed client, and each built-in error: grep the symbol.
84
65
 
85
66
  ## What the library leaves to you
86
67
 
87
- - **Verify credentials or issue them.** The application writes the verifier
88
- ([Authentication.md](Authentication.md#verify-credentials)), and login, consent and token
89
- issuance are its own. The library defines no scopes: a `Forbidden` names the scopes a call
90
- needs, and the library answers with the OAuth challenge
91
- ([Authentication.md](Authentication.md#step-up-for-a-scope)); which scheme speaks OAuth is on
92
- `Authentication.Options`'s `security`.
93
- - **Decide who may do what.** It runs the `authorize` an implementation states and writes no rule
94
- of its own ([Action.md](Action.md#authorize-calls)).
95
- - **Filter tool discovery by caller.** What `tools/list` shows a signed-out caller, and the
96
- endpoint split that hides protected tools, are on `ActionMcp.LayerHttpOptions`'s
97
- `authentication`.
98
- - **Limit requests.** A rate limit is the handler's, failing with an error its action declares, or
99
- middleware's ([Action.md](Action.md#authorize-calls)); the request body limit is the host's
100
- ([ActionHttp.md](ActionHttp.md#serve-the-routes)).
101
- - **Allow browsers across origins.** CORS for JSON routes is the host's, with `HttpRouter.cors`;
102
- an MCP endpoint refuses a browser `Origin` it does not list
103
- ([ActionMcp.md](ActionMcp.md#allow-browser-clients)).
68
+ - **Verify credentials or issue them.** The application writes the verifier ([Authentication.md](Authentication.md#verify-credentials)), and login, consent and token issuance are its own. The library defines no scopes: a `Forbidden` names the scopes a call needs, and the library answers with the OAuth challenge ([Authentication.md](Authentication.md#step-up-for-a-scope)); which scheme speaks OAuth is on `Authentication.Options`'s `security`.
69
+ - **Decide who may do what.** It runs the `authorize` an implementation states and writes no rule of its own ([Action.md](Action.md#authorize-calls)).
70
+ - **Filter tool discovery by caller.** What `tools/list` shows a signed-out caller, and the endpoint split that hides protected tools, are on `ActionMcp.LayerHttpOptions`'s `authentication`.
71
+ - **Limit requests.** A rate limit is the handler's, failing with an error its action declares, or middleware's ([Action.md](Action.md#authorize-calls)); the request body limit is the host's ([ActionHttp.md](ActionHttp.md#serve-the-routes)).
72
+ - **Allow browsers across origins.** CORS for JSON routes is the host's, with `HttpRouter.cors`; an MCP endpoint refuses a browser `Origin` it does not list ([ActionMcp.md](ActionMcp.md#allow-browser-clients)).
@@ -1,10 +1,6 @@
1
1
  # Testing failure modes
2
2
 
3
- What you see when something is wrong, and the fix. Search for a fragment of the message.
4
- A missing service's error can start
5
- `… with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined'`: search a line
6
- beneath it, `is missing the following properties from type 'Scope'`, instead.
7
- The tasks are in [Testing.md](Testing.md).
3
+ What you see when something is wrong, and the fix. Search for a fragment of the message. A missing service's error can start `… with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined'`: search a line beneath it, `is missing the following properties from type 'Scope'`, instead. The tasks are in [Testing.md](Testing.md).
8
4
 
9
5
  - A route answers an empty 500, which a client method reports as `HttpClientError: Decode error (500 …)`: a defect, or a result that does not encode. `Testing.layer(routes)` logs its cause on the process console, under `it.effect` too: read the test's output, or collect it with a `Logger` provided around the layer.
10
6
  - `Type 'Provider<CurrentUser, "notes-service.Login">' is missing the following properties from type 'Scope'` at `it.effect`, or `… is not assignable to type 'never'` at `Effect.runPromise`: the routes serve a protected action without a provider of its descriptor. Provide the real one or a test verifier to the routes; a caller provided around `Testing.layer` does not satisfy them.