@gjermundgaraba/effect-actions 0.11.0 → 0.12.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 (215) hide show
  1. package/README.md +101 -40
  2. package/dist/Action.d.mts +29 -117
  3. package/dist/Action.mjs +14 -53
  4. package/dist/ActionCli.d.mts +60 -28
  5. package/dist/ActionCli.mjs +54 -48
  6. package/dist/ActionHttp.d.mts +18 -67
  7. package/dist/ActionHttp.mjs +20 -70
  8. package/dist/ActionMcp.d.mts +23 -29
  9. package/dist/ActionMcp.mjs +56 -114
  10. package/dist/ActionRpc.d.mts +147 -0
  11. package/dist/ActionRpc.mjs +113 -0
  12. package/dist/ActionToolkit.d.mts +5 -8
  13. package/dist/ActionToolkit.mjs +3 -3
  14. package/dist/Authentication.d.mts +15 -15
  15. package/dist/Authentication.mjs +42 -96
  16. package/dist/Testing.d.mts +28 -23
  17. package/dist/Testing.mjs +27 -44
  18. package/dist/{internal/authentication.d.mts → authentication/provider.d.mts} +41 -9
  19. package/dist/authentication/provider.mjs +12 -0
  20. package/dist/authentication/refusal.mjs +36 -0
  21. package/dist/{internal/cli.d.mts → cli/commands.d.mts} +16 -12
  22. package/dist/cli/commands.mjs +302 -0
  23. package/dist/{internal → contract}/call.d.mts +2 -2
  24. package/dist/contract/call.mjs +10 -0
  25. package/dist/{internal → contract}/errors.d.mts +13 -10
  26. package/dist/{internal → contract}/errors.mjs +9 -14
  27. package/dist/{internal → contract}/implementation.d.mts +37 -21
  28. package/dist/{internal → contract}/implementation.mjs +18 -49
  29. package/dist/contract/media.d.mts +13 -0
  30. package/dist/contract/media.mjs +18 -0
  31. package/dist/contract/rules.d.mts +25 -0
  32. package/dist/{internal/actions.mjs → contract/rules.mjs} +6 -42
  33. package/dist/{internal → http}/client.d.mts +7 -9
  34. package/dist/http/client.mjs +17 -0
  35. package/dist/http/declared.mjs +20 -0
  36. package/dist/http/schema-errors.mjs +9 -0
  37. package/dist/mcp/protocol.d.mts +17 -0
  38. package/dist/{internal/mcp.mjs → mcp/protocol.mjs} +29 -62
  39. package/dist/stdio/console.mjs +64 -0
  40. package/dist/{internal → testing}/memory.d.mts +1 -1
  41. package/dist/{internal → testing}/memory.mjs +5 -17
  42. package/dist/toolkit/tools.mjs +29 -0
  43. package/docs/Action-failures.md +47 -0
  44. package/docs/Action-in-process.md +93 -0
  45. package/docs/Action.md +206 -426
  46. package/docs/ActionCli-failures.md +102 -0
  47. package/docs/ActionCli.md +206 -293
  48. package/docs/ActionHttp-failures.md +83 -0
  49. package/docs/ActionHttp.md +202 -282
  50. package/docs/ActionMcp-failures.md +31 -0
  51. package/docs/ActionMcp.md +222 -271
  52. package/docs/ActionRpc-failures.md +21 -0
  53. package/docs/ActionRpc.md +178 -0
  54. package/docs/ActionToolkit-failures.md +15 -0
  55. package/docs/ActionToolkit.md +100 -119
  56. package/docs/Authentication-advanced.md +139 -0
  57. package/docs/Authentication-failures.md +99 -0
  58. package/docs/Authentication.md +142 -392
  59. package/docs/CONTEXT.md +39 -24
  60. package/docs/README.md +71 -35
  61. package/docs/Testing-failures.md +20 -0
  62. package/docs/Testing.md +186 -151
  63. package/docs/guarantees.md +35 -96
  64. package/docs/setup-failures.md +34 -0
  65. package/docs/setup.md +87 -91
  66. package/examples/README.md +68 -0
  67. package/examples/assistant/README.md +92 -0
  68. package/examples/assistant/src/agent.ts +92 -0
  69. package/examples/assistant/src/cli.ts +34 -0
  70. package/examples/assistant/src/commands.ts +25 -0
  71. package/examples/assistant/src/identity/Identity.ts +33 -0
  72. package/examples/assistant/src/identity/authorize.ts +24 -0
  73. package/examples/assistant/src/identity/handlers.ts +10 -0
  74. package/examples/assistant/src/identity/members.ts +9 -0
  75. package/examples/assistant/src/identity/permissions.ts +23 -0
  76. package/examples/assistant/src/scripted.ts +23 -0
  77. package/examples/assistant/src/tasks/Tasks.ts +63 -0
  78. package/examples/assistant/src/tasks/handlers.ts +25 -0
  79. package/examples/assistant/src/tasks/store.ts +96 -0
  80. package/examples/assistant/src/toolkit.ts +30 -0
  81. package/examples/assistant/test/commands.test.ts +57 -0
  82. package/examples/assistant/test/identity/identity.test.ts +43 -0
  83. package/examples/assistant/test/tasks/store.test.ts +48 -0
  84. package/examples/assistant/test/tasks/tasks.test.ts +75 -0
  85. package/examples/assistant/test/toolkit.test.ts +121 -0
  86. package/examples/kitchen-sink/README.md +317 -0
  87. package/examples/kitchen-sink/src/Actions.ts +19 -0
  88. package/examples/kitchen-sink/src/Api.ts +24 -0
  89. package/examples/kitchen-sink/src/Rpc.ts +12 -0
  90. package/examples/kitchen-sink/src/admin/Admin.ts +59 -0
  91. package/examples/kitchen-sink/src/admin/authenticate.ts +18 -0
  92. package/examples/kitchen-sink/src/admin/authorize.ts +27 -0
  93. package/examples/kitchen-sink/src/admin/grants.ts +22 -0
  94. package/examples/kitchen-sink/src/admin/handlers.ts +21 -0
  95. package/examples/kitchen-sink/src/admin/keys.ts +8 -0
  96. package/examples/kitchen-sink/src/agent.ts +47 -0
  97. package/examples/kitchen-sink/src/assistant/Assistant.ts +82 -0
  98. package/examples/kitchen-sink/src/assistant/conversations.ts +71 -0
  99. package/examples/kitchen-sink/src/assistant/demo-model.ts +33 -0
  100. package/examples/kitchen-sink/src/assistant/handlers.ts +146 -0
  101. package/examples/kitchen-sink/src/attachments/Attachments.ts +48 -0
  102. package/examples/kitchen-sink/src/attachments/download.ts +49 -0
  103. package/examples/kitchen-sink/src/attachments/handlers.ts +38 -0
  104. package/examples/kitchen-sink/src/attachments/store.ts +58 -0
  105. package/examples/kitchen-sink/src/browser.ts +33 -0
  106. package/examples/kitchen-sink/src/cli.ts +85 -0
  107. package/examples/kitchen-sink/src/config.ts +18 -0
  108. package/examples/kitchen-sink/src/identity/Identity.ts +61 -0
  109. package/examples/kitchen-sink/src/identity/authenticate.ts +87 -0
  110. package/examples/kitchen-sink/src/identity/authorize.ts +16 -0
  111. package/examples/kitchen-sink/src/identity/directory.ts +52 -0
  112. package/examples/kitchen-sink/src/identity/handlers.ts +9 -0
  113. package/examples/kitchen-sink/src/identity/members.ts +13 -0
  114. package/examples/kitchen-sink/src/identity/quota.ts +62 -0
  115. package/examples/kitchen-sink/src/issues/Issues.ts +109 -0
  116. package/examples/kitchen-sink/src/issues/assistant.ts +23 -0
  117. package/examples/kitchen-sink/src/issues/guide.ts +37 -0
  118. package/examples/kitchen-sink/src/issues/handlers.ts +37 -0
  119. package/examples/kitchen-sink/src/issues/store.ts +96 -0
  120. package/examples/kitchen-sink/src/issues/write-limit.ts +41 -0
  121. package/examples/kitchen-sink/src/memory.ts +30 -0
  122. package/examples/kitchen-sink/src/ops.ts +42 -0
  123. package/examples/kitchen-sink/src/request-policy.ts +63 -0
  124. package/examples/kitchen-sink/src/routes.ts +120 -0
  125. package/examples/kitchen-sink/src/sdk.ts +46 -0
  126. package/examples/kitchen-sink/src/seed.ts +65 -0
  127. package/examples/kitchen-sink/src/server.ts +62 -0
  128. package/examples/kitchen-sink/src/stdio.ts +34 -0
  129. package/examples/kitchen-sink/src/workspaces/Workspaces.ts +28 -0
  130. package/examples/kitchen-sink/src/workspaces/handlers.ts +23 -0
  131. package/examples/kitchen-sink/src/workspaces/registry.ts +46 -0
  132. package/examples/kitchen-sink/src/workspaces/tenant.ts +17 -0
  133. package/examples/kitchen-sink/test/admin/admin.test.ts +67 -0
  134. package/examples/kitchen-sink/test/assistant/assistant.test.ts +435 -0
  135. package/examples/kitchen-sink/test/attachments/attachments.test.ts +161 -0
  136. package/examples/kitchen-sink/test/identity/identity.test.ts +137 -0
  137. package/examples/kitchen-sink/test/identity/session.test.ts +110 -0
  138. package/examples/kitchen-sink/test/issues/assistant.test.ts +75 -0
  139. package/examples/kitchen-sink/test/issues/issues.test.ts +135 -0
  140. package/examples/kitchen-sink/test/request-policy.test.ts +125 -0
  141. package/examples/kitchen-sink/test/routes.test.ts +123 -0
  142. package/examples/kitchen-sink/test/support/scripted.ts +40 -0
  143. package/examples/live-rpc/README.md +107 -0
  144. package/examples/live-rpc/src/Rpc.ts +8 -0
  145. package/examples/live-rpc/src/browser.ts +98 -0
  146. package/examples/live-rpc/src/chat/Chat.ts +75 -0
  147. package/examples/live-rpc/src/chat/authorize.ts +16 -0
  148. package/examples/live-rpc/src/chat/handlers.ts +50 -0
  149. package/examples/live-rpc/src/chat/room.ts +23 -0
  150. package/examples/live-rpc/src/chat/store.ts +54 -0
  151. package/examples/live-rpc/src/client.ts +37 -0
  152. package/examples/live-rpc/src/identity/Identity.ts +19 -0
  153. package/examples/live-rpc/src/identity/members.ts +8 -0
  154. package/examples/live-rpc/src/identity/verifier.ts +23 -0
  155. package/examples/live-rpc/src/routes.ts +22 -0
  156. package/examples/live-rpc/src/server.ts +43 -0
  157. package/examples/live-rpc/test/browser.test.ts +105 -0
  158. package/examples/live-rpc/test/chat/chat.test.ts +69 -0
  159. package/examples/live-rpc/test/routes.test.ts +99 -0
  160. package/examples/live-rpc/test/server.test.ts +75 -0
  161. package/examples/mcp-tools/README.md +136 -0
  162. package/examples/mcp-tools/src/instructions.ts +12 -0
  163. package/examples/mcp-tools/src/printers/Printers.ts +119 -0
  164. package/examples/mcp-tools/src/printers/fake.ts +115 -0
  165. package/examples/mcp-tools/src/printers/farm.ts +136 -0
  166. package/examples/mcp-tools/src/printers/handlers.ts +29 -0
  167. package/examples/mcp-tools/src/routes.ts +52 -0
  168. package/examples/mcp-tools/src/server.ts +30 -0
  169. package/examples/mcp-tools/src/stdio.ts +26 -0
  170. package/examples/mcp-tools/test/printers/printers.test.ts +189 -0
  171. package/examples/mcp-tools/test/routes.test.ts +202 -0
  172. package/examples/notes-service/README.md +108 -0
  173. package/examples/notes-service/src/Api.ts +13 -0
  174. package/examples/notes-service/src/browser.ts +41 -0
  175. package/examples/notes-service/src/cli.ts +22 -0
  176. package/examples/notes-service/src/commands.ts +89 -0
  177. package/examples/notes-service/src/identity/Identity.ts +31 -0
  178. package/examples/notes-service/src/identity/authorize.ts +18 -0
  179. package/examples/notes-service/src/identity/handlers.ts +10 -0
  180. package/examples/notes-service/src/identity/users.ts +9 -0
  181. package/examples/notes-service/src/identity/verifier.ts +36 -0
  182. package/examples/notes-service/src/notes/Notes.ts +91 -0
  183. package/examples/notes-service/src/notes/handlers.ts +33 -0
  184. package/examples/notes-service/src/notes/store.ts +73 -0
  185. package/examples/notes-service/src/routes.ts +45 -0
  186. package/examples/notes-service/src/server.ts +26 -0
  187. package/examples/notes-service/src/status/Status.ts +10 -0
  188. package/examples/notes-service/src/status/handlers.ts +14 -0
  189. package/examples/notes-service/src/stdio.ts +25 -0
  190. package/examples/notes-service/test/commands.test.ts +134 -0
  191. package/examples/notes-service/test/notes/notes.test.ts +103 -0
  192. package/examples/notes-service/test/routes.test.ts +84 -0
  193. package/examples/quickstart/README.md +31 -0
  194. package/examples/quickstart/src/Api.ts +5 -0
  195. package/examples/quickstart/src/client.ts +13 -0
  196. package/examples/quickstart/src/greeting/Greeting.ts +12 -0
  197. package/examples/quickstart/src/greeting/handlers.ts +6 -0
  198. package/examples/quickstart/src/routes.ts +11 -0
  199. package/examples/quickstart/src/server.ts +16 -0
  200. package/examples/quickstart/test/greeting/greeting.test.ts +18 -0
  201. package/package.json +23 -5
  202. package/dist/internal/actions.d.mts +0 -9
  203. package/dist/internal/authentication.mjs +0 -11
  204. package/dist/internal/call.mjs +0 -12
  205. package/dist/internal/cli.mjs +0 -328
  206. package/dist/internal/client.mjs +0 -25
  207. package/dist/internal/console.mjs +0 -77
  208. package/dist/internal/declared.d.mts +0 -13
  209. package/dist/internal/declared.mjs +0 -34
  210. package/dist/internal/mcp.d.mts +0 -14
  211. package/dist/internal/media.d.mts +0 -8
  212. package/dist/internal/media.mjs +0 -26
  213. package/dist/internal/refusal.mjs +0 -67
  214. package/dist/internal/schema-errors.mjs +0 -20
  215. package/dist/internal/tools.mjs +0 -44
package/docs/CONTEXT.md CHANGED
@@ -1,29 +1,44 @@
1
1
  # Context
2
2
 
3
- Vocabulary for `@gjermundgaraba/effect-actions`. The docs, the code, and the tests use these
4
- words with these meanings. Module names and Effect's own terms (`HttpApi`, `Tool`, `Toolkit`,
5
- `McpServer`, `Command`) mean what their modules say.
3
+ Vocabulary for `@gjermundgaraba/effect-actions`. The docs, the examples, the code and the tests
4
+ use these words with these meanings. Module names and Effect's own terms (`HttpApi`, `Tool`,
5
+ `Toolkit`, `McpServer`, `Rpc`, `RpcGroup`, `RpcServer`, `Command`) mean what their modules say.
6
+
7
+ Read this page alone, in one tool call: pages read together are cut. Tasks are routed from
8
+ [README.md](README.md), and each module's failures from its `-failures.md` page.
6
9
 
7
10
  ## Terms
8
11
 
9
- - **Action**: one contract. A name, a description, an input schema, a success schema, declared error schemas, `readOnly`, `caller`, and its `mcp` options: how its MCP tool presents itself. Holds no behavior. Made by `Action.make`.
10
- - **Read-only**: `readOnly`, a boolean required on every action and kept as a literal. States whether the action leaves its resource unchanged. Read by an implementation's `authorize`, and the only source of a tool's read-only hint. It enforces nothing itself.
11
- - **Caller**: who may call an action, `caller`, required on every contract: `Action.Anyone`, the exported value making it a **public action**, which anyone may call, signed in or not, or the identity service a caller must have, such as `caller: CurrentActor`, a **protected action**. Every surface enforces it: a remote one through authentication, a local one by requiring the host to provide that identity on every call.
12
- - **Identity**: the service a protected action declares, `Context.Service` only, so a contract importing it stays browser-safe. One per action, and one per remote surface; a tenant or other request context is an ordinary request-time requirement. A **trusted identity** is one only a host supplies on a local surface, such as an operator's on an admin CLI, typed apart from what a verifier returns, so no token names it.
13
- - **Handler**: a function from decoded input to an Effect of decoded success, failing only with the action's declared errors and the built-in ones. The services it yields, and an authorizer's per call, are **request-time requirements**, supplied per call: over HTTP by authentication and router middleware around the surface; on a local surface by the host.
14
- - **Implementation**: actions bound to their handlers and, where any is protected, their authorizer, one per `Action.implement` call. Nominal. Every surface that runs handlers takes an implementation or a list; its [selection rules](guarantees.md#names) determine which actions are exposed, each keeping its implementation's handlers, builder and authorizer.
15
- - **Builder**: the Effect passed to `implement` instead of a plain handler or record. It runs once per layer graph, however many surfaces serve its implementation, and once per invocation of a local CLI command: [guarantees.md](guarantees.md#dependency-lifetimes). The services it yields are **build-time requirements**, or startup services, resolved when its layer is built. An authorizer may be built the same way, the Effect returning it instead ([Action.md](Action.md#built-authorization)); so is a verifier, by `Authentication.layer`.
16
- - **Layer graph**: the layers Effect builds together, sharing one memo map, so each is built once however many layers use it. `HttpRouter.serve` and `Testing.layer` build their routes in a graph of their own, which reuses what the graph around them has already built, and `Layer.fresh` builds its layer in a new one. `HttpRouter.provideRequest` also builds the layer it provides in a graph of its own, which reuses what the routes' graph has already built. Builders and your own services follow it alike: [dependency lifetimes](guarantees.md#dependency-lifetimes).
17
- - **Host**: the application that serves the surfaces: it builds their layers, provides their services and middleware, and runs the server, subprocess or CLI.
18
- - **Surface**: where callers reach actions: HTTP routes, an MCP endpoint, a Toolkit, a CLI command, a client in process. Each serves the implementations passed to it and validates only the names it serves. A **remote surface** has callers it authenticates (`ActionHttp.layer`, `ActionMcp.layerHttp`). A **local surface** has none (`ActionToolkit`, a local `ActionCli` command, `ActionMcp.runStdio`, `Action.client`): nothing authenticates, and the host provides the identity.
19
- - **Binding**: the value `ActionHttp.make` returns, `{ actions, error, prefix, authentication, api }`, typed `ActionHttp.Binding`. Plain data shared by the server and every client. It decides what HTTP serves: `ActionHttp.layer` serves its actions among the implementations given. No other surface has one.
20
- - **Client**: one method per action, `client.<action>(input)`, taking its input and answering its decoded success, or failing with its declared errors and the built-in ones as decoded values. `ActionHttp.client` calls a binding's routes and `Testing.mcpClient` an endpoint's tools; `Action.client` calls implementations in process, through the dispatch every surface runs, encoding then decoding the input, the success and the failure as a wire carries them: a local surface for code.
21
- - **Authorizer**: `authorize`, an Effectful function of the selected action, which each implementation of protected actions states, `Action.implement(actions, handlers, { authorize })`, and every surface serving them runs. It runs only for protected actions, after authentication and input decoding, before the handler, and succeeds or fails with a refusal: [guarantees.md](guarantees.md#authorization). `Action.allowAll` lets every authenticated caller call. An implementation of public actions alone states none.
22
- - **Authentication descriptor**: what `Authentication.make` returns: a named, browser-safe declaration of how a remote caller proves an identity, by one native scheme, Bearer unless it names another, and what its verifier may fail with besides a refusal. A binding and an MCP endpoint serving protected actions name it, `authentication: Login`. Its name identifies the one provider that satisfies it.
23
- - **Authentication provider**: what `Authentication.layer` returns for a descriptor: the server-only verifier, built once per layer graph, and the discovery of an OAuth protected resource. Every HTTP or MCP layer serving protected actions requires it, and it authenticates their requests before decoding them. Action-level authorization is the authorizer's; record-level checks belong in handler data access ([authorization](guarantees.md#authorization)).
24
- - **Layer middleware**: native `HttpApiMiddleware` given to `ActionHttp.layer(binding, apps, { middleware })`, run around every route the layer serves, inside authentication and outside decoding. HTTP only.
25
- - **Mixed endpoint**: an MCP endpoint serving public and protected tools on one URL. Discovery and public tools' calls answer anyone; every other request authenticates before the endpoint decodes it ([ActionMcp.md](ActionMcp.md#rules)).
26
- - **Declared error**: an error schema listed in an action's `error`. Its handler may fail with it, a limit such as a rate limit included. HTTP sends its JSON encoding with its `httpApiStatus`, 422 without one; MCP returns the same JSON as an `isError` result; the Toolkit returns it as a failure result; a CLI command prints the same JSON on stderr, failing with Effect CLI's `UserError`, whose `cause` it is.
27
- - **Built-in errors**: `Action.InvalidInput` (400), `Action.Unauthenticated` (401) and `Action.Forbidden` (403), tagged errors with body `{ _tag, message }` and a default `message`; an `InvalidInput` also lists `issues`, empty when it names none, each an `Action.Issue`: a `path` into the input and a `message`, and a `Forbidden` the OAuth `scopes` a call lacks. The library produces them, and every surface declares them: [guarantees.md](guarantees.md#wire-behavior).
28
- - **Refusal**: `Action.Refusal`, `Unauthenticated | Forbidden`, a schema and a type, as `Action.BuiltIn` is of every built-in error. What authentication or an authorizer refuses a caller with instead of letting a handler run, and all an authorizer may fail with. A **step-up refusal** is one an OAuth client acts on, `Unauthenticated` or a `Forbidden` naming `scopes`. Its HTTP status, challenge, and the exception for an MCP response that has already started follow the [authorization rules](guarantees.md#authorization).
29
- - **Media field**: a field of an action's success whose schema is a media kind, `Action.Image`, `{ data: Uint8Array, mimeType: string }`: a top-level field of a struct success, optional or an array, or the whole success or an array of it. An MCP tool **lifts** it out of its result into a content block of its own, an image block, beside the rest of the success; every other surface sends it as JSON, the bytes in base64 ([ActionMcp.md](ActionMcp.md#media)).
12
+ ### Contracts and calls
13
+
14
+ - **Action**: one contract: a name, a description, input, success and declared error schemas, `readOnly`, `caller`, and its `mcp` options. Holds no behavior. Made by `Action.make`.
15
+ - **Read-only**: `readOnly`, required on every action: whether it leaves its resource unchanged. It enforces nothing itself.
16
+ - **Caller**: who may call an action, `caller`. `Action.Anyone` makes a **public action**; an identity service, such as `caller: CurrentUser`, makes a **protected action**.
17
+ - **Identity**: the service a protected action names as its caller. A **trusted identity** is one only a host supplies, on a local surface, which no token names.
18
+ - **Handler**: a function from an action's decoded input to an Effect of its decoded success. The services it yields per call, and an authorizer's, are **request-time requirements**.
19
+ - **Implementation**: what `Action.implement` returns: actions bound to their handlers and, where any is protected, their authorizer. Every surface that runs handlers takes one, or a list.
20
+ - **Builder**: an Effect given to `Action.implement` in place of handlers, which returns them, once per layer graph. The services it yields are **build-time requirements**, or **startup services** ([Action.md](Action.md#implement-actions)).
21
+ - **Layer graph**: the layers Effect builds together, each built once ([Action-in-process.md](Action-in-process.md#share-one-build-across-servers)).
22
+ - **Host**: the application that serves the surfaces: it builds their layers, provides their services and runs the server, subprocess or CLI.
23
+ - **Surface**: where callers reach actions. A **remote surface** authenticates its callers (`ActionHttp.layer`, `ActionMcp.layerHttp`, `ActionRpc.layer`). A **local surface** has no one to authenticate (`ActionToolkit`, a local `ActionCli` command, `ActionMcp.runStdio`, `Action.client`): the host provides the identity.
24
+ - **Binding**: what `ActionHttp.make` or `ActionRpc.make` returns: the actions its surface serves, as browser-safe data the server and every client share.
25
+ - **Client**: one method per action, `client.<action>(input)`: `ActionHttp.client`, `ActionRpc.client`, `Testing.mcpClient` and, in process, `Action.client`.
26
+ - **Authorizer**: `authorize`, a function of the called action that an implementation of protected actions states. It succeeds or fails with a refusal ([Action.md](Action.md#authorize-calls)).
27
+ - **Authentication descriptor**: what `Authentication.make` returns: a named, browser-safe declaration of how a remote caller proves an identity. A binding or an MCP endpoint serving protected actions names it.
28
+ - **Authentication provider**: what `Authentication.layer` returns for a descriptor, holding its **verifier**: the server-only function from a credential to the identity.
29
+ - **Layer middleware**: native `HttpApiMiddleware` or `RpcMiddleware` given to `ActionHttp.layer` or `ActionRpc.layer` as `middleware`, run around every route or rpc the layer serves.
30
+ - **Mixed endpoint**: an MCP endpoint serving public and protected tools on one URL ([ActionMcp.md](ActionMcp.md#protect-tools)).
31
+ - **Declared error**: an error schema listed in an action's `error`, which its handler may fail with ([guarantees.md](guarantees.md#wire-behavior)).
32
+ - **Built-in errors**: `Action.InvalidInput`, `Action.Unauthenticated` and `Action.Forbidden`, together `Action.BuiltIn`: errors the library produces and every surface declares ([Action.md](Action.md#fail-with-declared-and-built-in-errors)).
33
+ - **Refusal**: `Action.Refusal`, `Unauthenticated | Forbidden`: what authentication or an authorizer refuses a caller with. A **step-up refusal** is one an OAuth client acts on: an `Unauthenticated`, or a `Forbidden` naming `scopes` ([Authentication.md](Authentication.md#publish-oauth-discovery)).
34
+ - **Media field**: a field of an action's success whose schema is a media kind, `Action.Image`, which an MCP tool **lifts** into a content block of its own ([ActionMcp.md](ActionMcp.md#return-images-and-other-media)).
35
+
36
+ ### Layout of an application
37
+
38
+ The parts of the layout every example uses: [examples/README.md](../examples/README.md#how-they-are-laid-out).
39
+
40
+ - **Domain**: one subject of the application, such as notes or identity, and the folder holding everything about it. A folder is never a mechanism, such as `contracts/` or `handlers/`.
41
+ - **Contract file**: the PascalCase file of a domain, such as `notes/Notes.ts`: its schemas, errors and actions, and the identity and descriptor it declares. It imports no server code.
42
+ - **Routes**: `routes.ts`, the host's surfaces as one Layer. A route, singular, is one HTTP route.
43
+ - **Entrypoint**: a file that runs the application one way, such as `server.ts`, `stdio.ts` or `cli.ts`: the **composition root**, the one place that chooses the services, the platform and, on a local surface, the caller.
44
+ - **Demo shortcut**: what an example does in place of what a deployment needs, marked `DEMO ONLY` in a comment.
package/docs/README.md CHANGED
@@ -1,47 +1,83 @@
1
- # effect-actions reference
1
+ # effect-actions
2
2
 
3
- Reference for `@gjermundgaraba/effect-actions`, written for coding agents. Each public-module
4
- card has the same sections: **API** (inventory and options), **Canonical** (the one right way to write
5
- it), **Rules** (must and never), **Failure modes** (what you see when it is wrong, and the fix).
6
- Routing, setup, vocabulary, and shared guarantees use the structure their role needs.
7
- Read the card for a module before writing code that uses it. Exported TypeScript declarations are the exact signature reference. Vocabulary is defined in
8
- [CONTEXT.md](CONTEXT.md); the pages use those terms exactly.
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.
9
7
 
10
- Package facts that apply everywhere:
8
+ ## How to read these pages
11
9
 
12
- - Every module is a subpath import: `import * as Action from "@gjermundgaraba/effect-actions/Action"`. There is no package root.
13
- - The `effect` peer admits the patches of the Effect release the package is tested against, and no later minor until a release is tested against it ([setup.md](setup.md#install)).
14
- - Action contracts are pure values. Defining contracts and implementations runs neither handlers nor builder Effects. Builder services are acquired when a layer is built, an `Action.client` is acquired, or a local CLI command runs; see [dependency lifetimes](guarantees.md#dependency-lifetimes).
10
+ **Arriving with an error?** Search the `*-failures.md` pages for a fragment of its message
11
+ first (step 5), before the examples and the pages.
12
+
13
+ 1. **Open one page per tool call, with your file-reading tool.** A page fits one read. Several
14
+ in one call, however many commands it runs, are cut by its output limit, and what is cut goes
15
+ unnoticed.
16
+ 2. **Start from the example closest to what you are building** ([Examples](#examples)). Its
17
+ README has a layout tree and a "Where to look" table naming the file for each thing. Copy its
18
+ layout and idioms: the examples are the reference for how code is written.
19
+ 3. **Read [Action.md](Action.md) completely.** Every surface builds on it.
20
+ 4. **Read the page of each surface you serve** ([Pages](#pages)), and the example files its task
21
+ sections link.
22
+ 5. **When something fails, search the `*-failures.md` pages** for a fragment of the message. Each
23
+ module page has one beside it, such as [Action-failures.md](Action-failures.md).
24
+
25
+ A service others call is [notes-service](../examples/notes-service/) plus
26
+ [Action.md](Action.md), [ActionHttp.md](ActionHttp.md), [ActionMcp.md](ActionMcp.md),
27
+ [Authentication.md](Authentication.md) and [Testing.md](Testing.md).
28
+
29
+ Where things are, in a project that installs the package: these pages in
30
+ `node_modules/@gjermundgaraba/effect-actions/docs/`, the examples in `examples/` beside them, and
31
+ the declarations with their JSDoc, the reference for signatures, in `dist/<Module>.d.mts`, all at
32
+ the installed version. Install first ([setup.md](setup.md#install)): it needs TypeScript 7 and
33
+ one Effect release. Every module is a subpath import, `@gjermundgaraba/effect-actions/Action`;
34
+ there is no package root.
15
35
 
16
36
  ## Pages
17
37
 
18
- | Read | When you need to |
19
- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
20
- | [setup.md](setup.md) | install, run the minimal program, pick entry points, bundle a browser client, check package boundaries |
21
- | [Action.md](Action.md) | define a contract and who may call it, bind its handler and authorization, use built-in errors, call or test implementations in process (`Action.client`) |
22
- | [ActionHttp.md](ActionHttp.md) | serve JSON POST routes, answer bad input, publish OpenAPI, call the API with one Effect method per action |
23
- | [ActionMcp.md](ActionMcp.md) | serve MCP tools over Streamable HTTP or stdio |
24
- | [ActionToolkit.md](ActionToolkit.md) | use actions as a native Effect AI `Toolkit` without a server |
25
- | [ActionCli.md](ActionCli.md) | run handlers in-process, or call the HTTP API, from a command with derived flags |
26
- | [Authentication.md](Authentication.md) | declare how a remote caller proves its identity, verify it on HTTP and MCP surfaces, refuse with 401/403, publish RFC 9728 discovery |
27
- | [Testing.md](Testing.md) | call routes and tools in memory through `HttpClient`, or test an implementation in process |
28
- | [guarantees.md](guarantees.md) | rules every surface shares: builder lifetimes, authorization, wire formats, names, spans, the package's scope |
29
- | [CONTEXT.md](CONTEXT.md) | look up a term the pages use |
38
+ | Read | When you need to |
39
+ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
40
+ | [setup.md](setup.md) | install, import the modules, write the minimal program, lay out an application, bundle a browser client |
41
+ | [Action.md](Action.md) | define a contract and who may call it, implement it, authorize calls, fail with declared and built-in errors |
42
+ | [Action-in-process.md](Action-in-process.md) | call actions from your own code, such as a job; share one build of the implementations across servers and jobs |
43
+ | [ActionHttp.md](ActionHttp.md) | bind actions to JSON routes, serve them, limit request bodies, publish OpenAPI, call them from Effect or a browser |
44
+ | [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 |
45
+ | [ActionRpc.md](ActionRpc.md) | bind actions to Effect rpcs, serve them on a protocol, call them, send request context with a call |
46
+ | [ActionToolkit.md](ActionToolkit.md) | give a model actions as a native Effect AI `Toolkit`, name the caller, ask for approval |
47
+ | [ActionCli.md](ActionCli.md) | run actions as local commands, or call a server from commands; shape flags, input and output; add a command of your own |
48
+ | [Authentication.md](Authentication.md) | declare how callers sign in, verify credentials, publish OAuth discovery, step up for a scope |
49
+ | [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 |
50
+ | [Testing.md](Testing.md) | test an implementation in process, and routes, tools and rpcs in memory, as a signed-in caller; assert on errors |
51
+ | [guarantees.md](guarantees.md) | see the status and body of one case, such as bad input, a declared error or a refusal, on each surface; what the library leaves to you |
52
+ | [CONTEXT.md](CONTEXT.md) | look up a term the pages use |
30
53
 
31
- ## Choose a surface
54
+ ## Examples
32
55
 
33
- - Callers speak JSON over HTTP: `ActionHttp`. Clients use `ActionHttp.client(Http)`.
34
- - Callers are MCP clients: `ActionMcp.layerHttp` for a hosted endpoint, `ActionMcp.runStdio` for a subprocess.
35
- - Callers are an Effect AI program in the same process: `ActionToolkit`.
36
- - Callers are humans or scripts in a terminal: `ActionCli`, `make` and `command` from implementations to run handlers locally, or `remote` and `remoteCommand` from the HTTP binding to call a server.
37
- - Callers are your own code in the same process, such as a test, a job or a command of your own: `Action.client`, with the methods of `ActionHttp.client`.
56
+ | Example | What it is | Start from it when |
57
+ | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
58
+ | [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. |
59
+ | [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. |
60
+ | [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. |
61
+ | [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. |
62
+ | [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. |
63
+ | [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. |
38
64
 
39
- Every surface that runs handlers takes the same implementations (`Action.implement(...)`), one or a list, with options last. A contract states who may call it, `caller`, and an implementation of protected actions carries its own `authorize`, which every surface runs; surfaces take only their transport's options. A remote surface serving protected actions names their authentication descriptor and requires its verifier, `Authentication.layer`; on a local surface the host provides the identity.
65
+ Each example's README says where to look for what. The index in
66
+ [kitchen-sink](../examples/kitchen-sink/README.md#index) maps every export and option it uses to
67
+ a file. Every token, user and store in the examples is a demo, marked `DEMO ONLY` where it must
68
+ change before a deployment.
40
69
 
41
- For first-time setup, follow the [minimal program](setup.md#minimal-program).
70
+ ## Choose a surface
42
71
 
43
- ## Runnable examples
72
+ | Callers | Use |
73
+ | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
74
+ | Speak JSON over HTTP | `ActionHttp`; clients use `ActionHttp.client`, or `ActionHttp.fetchClient` outside an Effect |
75
+ | Are Effect programs, such as a page over one WebSocket | `ActionRpc`; clients use `ActionRpc.client` |
76
+ | Are MCP clients | `ActionMcp.layerHttp` for a hosted endpoint, `ActionMcp.runStdio` for a subprocess, which runs the handlers itself |
77
+ | Are a model in the same process | `ActionToolkit` |
78
+ | Are people or scripts in a terminal | `ActionCli`: `make` runs handlers locally, `remote` calls a server through its HTTP binding |
79
+ | Are your own code in the same process, such as a job or a test | `Action.client` |
44
80
 
45
- Repository directory `examples/`: the minimal program, an authenticated application served on
46
- every surface, and one file per other surface, each listed in its
47
- [README](https://github.com/gjermundgaraba/effect-actions/tree/main/examples#readme).
81
+ Surfaces combine: every one that runs handlers takes the same implementations, one or a list,
82
+ and enforces each contract's `caller` and its implementation's `authorize`
83
+ ([Action.md](Action.md#authorize-calls)).
@@ -0,0 +1,20 @@
1
+ # Testing failure modes
2
+
3
+ What you see when something is wrong, and the fix. Search for a fragment of the message.
4
+ The tasks are in [Testing.md](Testing.md).
5
+
6
+ - A route answers an empty 500: a defect, or a result that does not encode. `Testing.layer` logs the cause as an error, and nothing for a request that succeeds or is refused: read the test's output, or collect it with a `Logger` or an `ErrorReporter` provided around the layer.
7
+ - A client method fails with `Action.Unauthenticated`: routes under `Testing.layer` keep their real authentication. Give the client a credential in `transformClient` ([Test as a signed-in caller](Testing.md#test-as-a-signed-in-caller)).
8
+ - A protected route answers 401 where the test expects 415 or 400: authentication runs before the body is read. Send a valid credential.
9
+ - `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.
10
+ - `Type 'CurrentUser' is missing the following properties from type 'Scope'` at `it.effect`, or `… is not assignable to type 'never'` at `Effect.runPromise`: the test still owes the identity. An `Action.client` call of a protected action takes its caller per call: provide it around the call. Or a public action's handler reads the identity, which no route provides to it: make the action protected. Or, where the caller is provided already, a builder yields it: move that `yield*` into the handler ([Action-failures.md](Action-failures.md#where-an-implementation-is-served)).
11
+ - `Type '<Service>' is missing the following properties from type 'Scope'` at every `it.effect`, for a service that is no identity, such as a store, a limiter or a `LanguageModel`: a builder now yields it, and the routes require it. Provide its layer to `Testing.layer(routes)`, as the entrypoint provides it to the server.
12
+ - `Type 'unknown' is not assignable to type 'Scope'` at `it.effect`, or `Argument of type 'Effect<…>' is not assignable` at an `Effect.flip` or `Effect.exit`, around a loop over an array of calls to different actions: their successes and errors differ, so the array has no one element type. Annotate it, `ReadonlyArray<Effect.Effect<unknown, unknown>>`, with the caller as a third parameter for `Action.client` calls of protected actions, or assert each call on its own.
13
+ - An rpc call never answers under `Testing.layer`, and nothing is logged: the layer holds the rpc layer twice, under two protocols, and built it for the other one, a WebSocket. Give the rpc layer its WebSocket in the entrypoint, so the routes a test serves hold it once, under the test's protocol ([Serve the rpcs](ActionRpc.md#serve-the-rpcs)).
14
+ - A test passes whatever message an error carries: `toEqual` never compares a tagged error's `message`. Assert it separately ([Assert on errors](Testing.md#assert-on-errors)).
15
+ - The test reads a service the handlers never changed: it was provided inside the routes and again to the test, two instances. Provide it once, around `Testing.layer`, with `Layer.provideMerge`.
16
+ - `MCP tools/call "<name>" answered 404`: the endpoint is not at `/mcp`, the default. Pass `mcpClient` its `url`.
17
+ - `MCP tools/call "<name>" returned an error: Invalid parameters for tool ...`: the endpoint serves another contract under that name. Pass `mcpClient` the served contract value.
18
+ - A builder or handler fails with `NotFound: FileSystem.<method> (<path>)` for a file that exists, or a file route answers 500: with no `FileSystem` provided around `Testing.layer`, the routes read a no-op one. Provide `NodeFileSystem.layer` or `NodeServices.layer`. In a command test, the nearer of two provided is read: provide a fake `FileSystem` on the command, inside `NodeServices.layer`.
19
+ - `Argument of type 'Layer<…, FileSystem | Generator | HttpPlatform | HttpRouter | Path>' is not assignable` at `HttpRouter.toWebHandler(routes)`: a web handler's routes need the platform services `Testing.layer(routes)` supplies itself. Provide `HttpServer.layerServices` to them.
20
+ - `UrlError` from `HttpClientRequest.toWebResult` or `toWeb` of an `mcpRequest`, or an `HttpClientError` whose reason is `InvalidUrlError` from a client method or a sent `mcpRequest`: the `url` is relative, such as the default `/mcp`, which only the `HttpClient` of `Testing.layer` resolves. Pass an absolute one.