@gjermundgaraba/effect-actions 0.10.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 (214) hide show
  1. package/README.md +103 -43
  2. package/dist/Action.d.mts +31 -118
  3. package/dist/Action.mjs +15 -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 +25 -31
  9. package/dist/ActionMcp.mjs +140 -74
  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 +32 -25
  17. package/dist/Testing.mjs +45 -30
  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/mcp/protocol.mjs +149 -0
  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 -424
  46. package/docs/ActionCli-failures.md +102 -0
  47. package/docs/ActionCli.md +206 -292
  48. package/docs/ActionHttp-failures.md +83 -0
  49. package/docs/ActionHttp.md +202 -281
  50. package/docs/ActionMcp-failures.md +31 -0
  51. package/docs/ActionMcp.md +229 -193
  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 -117
  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 -23
  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 -95
  64. package/docs/setup-failures.md +34 -0
  65. package/docs/setup.md +88 -87
  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 +32 -14
  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 -11
  211. package/dist/internal/mcp.mjs +0 -52
  212. package/dist/internal/refusal.mjs +0 -67
  213. package/dist/internal/schema-errors.mjs +0 -20
  214. package/dist/internal/tools.mjs +0 -44
@@ -1,10 +1,10 @@
1
- import { ActionOf, BuildError, BuildServices, Holding, Known, Member, Protected, SelectedOf, Selection, Served, ServedRequest } from "./internal/implementation.mjs";
1
+ import { ActionOf, BuildError, BuildServices, Holding, Known, Member, Protected, SelectedOf, Selection, Served, ServedRequest } from "./contract/implementation.mjs";
2
2
  import { Action_d_exports, Any } from "./Action.mjs";
3
- import { Any as Any$1, Matching, ProviderOf, RemoteRequest, Required } from "./internal/authentication.mjs";
3
+ import { Any as Any$1, Matching, ProviderOf, RemoteRequest, Required, ServerBuildServices } from "./authentication/provider.mjs";
4
4
  import { Cause, Effect, Layer, Stdio } from "effect";
5
- import { HttpRouter } from "effect/http";
6
5
  import { McpSchema, McpServer } from "effect/ai";
7
- //#region src/ActionMcp.d.ts
6
+ import { HttpRouter } from "effect/http";
7
+ //#region src/mcp/ActionMcp.d.ts
8
8
  /**
9
9
  * An MCP server, over HTTP or stdio: every native `McpServer.layerStdio` option except
10
10
  * `protocols`, the server information, `instructions` and `extensions`, the actions `A` that
@@ -24,58 +24,52 @@ export interface Options<E = never, R = never, A extends Any = Any> extends Omit
24
24
  readonly features?: Layer.Layer<never, E, R> | undefined;
25
25
  }
26
26
  /**
27
- * One Streamable HTTP MCP endpoint: `Options`, and every native `McpServer.layerHttp` option
28
- * except `protocols`, `allowedOrigins` among them, with `path` defaulting to `/mcp`.
27
+ * One Streamable HTTP MCP endpoint, speaking MCP 2026-07-28, 2025-11-25 or 2025-06-18, as the
28
+ * client negotiates: `Options`, and every native `McpServer.layerHttp` option, `allowedOrigins`
29
+ * among them, except `protocols` and `allowSessionTermination`, which are fixed, with `path`
30
+ * defaulting to `/mcp`. A client of a 2025 revision opens a session with `initialize`, which
31
+ * lasts until that client ends it with a DELETE, answered 204, or the process stops.
29
32
  */
30
- export interface LayerHttpOptions<E = never, R = never, A extends Any = Any> extends Options<E, R, A>, Omit<Parameters<typeof McpServer.layerHttp>[0], "protocols" | "path"> {
33
+ export interface LayerHttpOptions<E = never, R = never, A extends Any = Any> extends Options<E, R, A>, Omit<Parameters<typeof McpServer.layerHttp>[0], "protocols" | "path" | "allowSessionTermination"> {
31
34
  /** The endpoint's route; defaults to `/mcp`. */
32
35
  readonly path?: HttpRouter.PathInput;
33
36
  readonly authentication?: Any$1;
34
37
  }
35
- /**
36
- * The native server supplies its own request context to every tool call, and the registry
37
- * it registers the tools on, the endpoint's own; over HTTP the router its own, such as the
38
- * request.
39
- */
40
38
  type ToolRequestContext<R> = Exclude<R, McpSchema.McpRequestContext | McpServer.McpServer>;
41
39
  type HttpToolRequestContext<R> = Exclude<ToolRequestContext<R>, HttpRouter.Provided>;
42
- /**
43
- * What the native features owe, `R`, beside the registry, which the endpoint provides them,
44
- * so one registering through `McpServer.registerResource` owes it no host.
45
- */
46
40
  type Features<R> = Exclude<R, McpServer.McpServer>;
47
- /**
48
- * Where `D`, the descriptor given, is inferred, so the provider owed is that descriptor's.
49
- * Without one, tools `A` all public take none, even past a `D` given explicitly; protected
50
- * ones state theirs in `RequiredAuthentication` alone, as an absent `authentication` and the
51
- * one they require would intersect to `never`, reading as every option being wrong.
52
- */
53
41
  type Inferring<D, A extends Any> = [D] extends [undefined] ? [Protected<A>] extends [never] ? {
54
42
  readonly authentication?: undefined;
55
43
  } : unknown : {
56
44
  readonly authentication?: D;
57
45
  };
58
46
  /**
59
- * Serve MCP tools over one Streamable HTTP endpoint, speaking MCP 2026-07-28 only.
47
+ * Serve MCP tools over one Streamable HTTP endpoint, speaking MCP 2026-07-28, 2025-11-25 or
48
+ * 2025-06-18, as the client negotiates. A 2026-07-28 request stands alone; a client of a 2025
49
+ * revision opens a session with `initialize`, which holds what it sent there and no identity,
50
+ * and lasts until that client ends it with a DELETE, answered 204, or the process stops.
60
51
  *
61
52
  * Protected tools need the `authentication` descriptor their contracts name, and the
62
53
  * provider `Authentication.layer` builds from it. On an endpoint of protected tools only,
63
54
  * every request authenticates before it is decoded. On a mixed endpoint, anyone may discover
64
- * the server, list its tools, cancel, and call a public tool; everything else authenticates
65
- * first, so a protected tool's description is listed to anyone but run only for its caller.
66
- * A call refused with `Unauthenticated`, or with `Forbidden` naming scopes, is answered with
67
- * its HTTP status and challenge, 401 or 403, as MCP authorization requires; any other
68
- * failure is a tool result. In a tool call, what middleware provides per request wins over
55
+ * the server, list its tools, cancel, and call a public tool in a 2026-07-28 request;
56
+ * everything else authenticates first, a session's every request included, so a protected
57
+ * tool's description is listed to anyone but run only for its caller.
58
+ * On a Bearer endpoint, a protected tool's call refused with `Unauthenticated`, or with
59
+ * `Forbidden` naming scopes, is answered with its HTTP status and challenge, 401 or 403, as MCP
60
+ * authorization requires, unless its response has started; any other failure is a tool result. Claude Code connects with
61
+ * `claude mcp add --transport http <name> <url>`. In a tool call, what middleware provides per request wins over
69
62
  * what the endpoint was built with; native `features` read only what they were built with,
70
63
  * never a request's. Never provide an identity at startup: a tool call takes its identity
71
64
  * only from the request it authenticated.
65
+ * Guide: `docs/ActionMcp.md` in this package; in an application: `examples/mcp-tools/`.
72
66
  */
73
- export declare function layerHttp<const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>>> = {}, E = never, R = never, const D extends Any$1 | undefined = undefined>(implementations: Apps, options: LayerHttpOptions<E, R, ActionOf<Member<Apps>>> & O & NoInfer<Known<O, LayerHttpOptions<E, R, Any>>> & NoInfer<Required<SelectedOf<O, Apps>>> & Matching<NoInfer<SelectedOf<O, Apps>>, NoInfer<D>> & Inferring<D, NoInfer<SelectedOf<O, Apps>>>): Layer.Layer<never, BuildError<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>> | Cause.IllegalArgumentError | E, BuildServices<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>> | HttpRouter.HttpRouter | HttpRouter.Request.From<"Requires", HttpToolRequestContext<RemoteRequest<Member<Apps>, SelectedOf<O, Apps>>>> | ProviderOf<D> | Features<R>>;
67
+ export declare function layerHttp<const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>>> = {}, E = never, R = never, const D extends Any$1 | undefined = undefined>(implementations: Apps, options: LayerHttpOptions<E, R, ActionOf<Member<Apps>>> & O & NoInfer<Known<O, LayerHttpOptions<E, R, Any>>> & NoInfer<Required<SelectedOf<O, Apps>>> & Matching<NoInfer<SelectedOf<O, Apps>>, NoInfer<D>> & Inferring<D, NoInfer<SelectedOf<O, Apps>>>): Layer.Layer<never, BuildError<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>> | Cause.IllegalArgumentError | E, ServerBuildServices<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>> | HttpRouter.HttpRouter | HttpRouter.Request.From<"Requires", HttpToolRequestContext<RemoteRequest<Member<Apps>, SelectedOf<O, Apps>>>> | ProviderOf<D> | Features<R>>;
74
68
  /**
75
69
  * Serve MCP tools through newline-delimited JSON-RPC on standard I/O, speaking MCP
76
70
  * 2026-07-28, 2025-11-25 or 2025-06-18, as the host negotiates: the whole
77
- * program of an MCP subprocess, which succeeds once the host closes its side and the calls in
78
- * flight, which that interrupts, have stopped. A signal interrupts it.
71
+ * program of an MCP subprocess, which succeeds once the host closes its side and every call
72
+ * in flight has been answered. A signal interrupts it.
79
73
  *
80
74
  * Effect logs go to stderr, since stdout carries the protocol. The host supplies the
81
75
  * `Stdio` service and the identity. Arguments are tool input only and never establish
@@ -1,105 +1,170 @@
1
- import { Anyone } from "./internal/actions.mjs";
2
- import { provideHandlers, select, toList } from "./internal/implementation.mjs";
3
- import { assertAuthentication, promote } from "./internal/authentication.mjs";
4
- import { httpProtocol } from "./internal/mcp.mjs";
5
- import { recordStepUp } from "./internal/refusal.mjs";
6
- import { logToStderr } from "./internal/console.mjs";
7
- import { bindTools } from "./internal/tools.mjs";
8
- import { Cause, Context, Effect, Exit, Fiber, JsonPointer, Layer, Predicate, Schema } from "effect";
1
+ import { Anyone } from "./contract/rules.mjs";
2
+ import { provideHandlers, select, toList } from "./contract/implementation.mjs";
3
+ import { bindTools } from "./toolkit/tools.mjs";
4
+ import { fieldKey, httpProtocol, isMisplaced, liftOf, liftedFrom, protocols } from "./mcp/protocol.mjs";
5
+ import { assertAuthentication, promote, providerOf } from "./authentication/provider.mjs";
6
+ import { recordStepUp } from "./authentication/refusal.mjs";
7
+ import { withStderrConsole } from "./stdio/console.mjs";
8
+ import { Cause, Context, Effect, ErrorReporter, Exit, Fiber, JsonPointer, Layer, Option, Predicate, References, Result, Schema, Stream } from "effect";
9
+ import { AiError, McpSchema, McpServer, Tool, Toolkit } from "effect/ai";
9
10
  import { HttpRouter, HttpServerRequest } from "effect/http";
10
- import { McpProtocol, McpSchema, McpServer, Tool } from "effect/ai";
11
- //#region src/ActionMcp.ts
12
- /**
13
- * The revisions served over stdio: 2026-07-28 and the stateful revisions hosts open stdio
14
- * with, newest first; a host asking for another is offered 2025-11-25. A success is sent as it
15
- * is on each. 2026-07-28 carries any success as `structuredContent`; the adapters of 2025-11-25
16
- * and 2025-06-18 carry only an object there, and list only an object-rooted `outputSchema`. A
17
- * success they do not structure is text alone, a string as itself.
18
- */
19
- const stdioProtocols = [
20
- McpProtocol.v2026_07_28,
21
- McpProtocol.v2025_11_25,
22
- McpProtocol.v2025_06_18
23
- ];
24
- /**
25
- * MCP has a JSON-only wire contract. Success is the encoded success as structured content,
26
- * and its JSON as text; declared failures are returned as JSON text. The native server
27
- * refuses undeclared arguments and publishes closed input schemas.
28
- */
29
- const projection = {
11
+ //#region src/mcp/ActionMcp.ts
12
+ const strictToolProjection = {
30
13
  label: "MCP tool",
31
14
  tool: (tool) => tool.annotate(Tool.Strict, true)
32
15
  };
33
- /** Whether a JSON Schema is one the native server takes for a tool's arguments. */
34
- const isToolJson = Schema.is(McpSchema.ToolJson);
35
- /**
36
- * The JSON Schema root of `schema` as the native server lists it for a tool, a `$ref` at its
37
- * root resolved.
38
- */
39
- const rootOf = (schema) => {
40
- const document = Schema.toJsonSchemaDocument(Schema.toCodecJson(schema));
16
+ const isToolArgumentsSchema = Schema.is(McpSchema.ToolJson);
17
+ const nativeToolJsonSchema = (schema, strict) => {
18
+ const document = Schema.toJsonSchemaDocument(schema, { onExcessProperty: strict ? "error" : "ignore" });
41
19
  const [scope, key] = Predicate.isString(document.schema.$ref) ? JsonPointer.parseUriFragment(document.schema.$ref) ?? [] : [];
42
- return scope === "$defs" && key !== void 0 ? document.definitions[key] : document.schema;
20
+ const root = scope === "$defs" && key !== void 0 ? document.definitions[key] : document.schema;
21
+ return root === void 0 || Object.keys(document.definitions).length === 0 ? root : {
22
+ ...root,
23
+ $defs: document.definitions
24
+ };
43
25
  };
44
- /**
45
- * Refuse, when the server is made, the actions of `apps` whose input is not one object, as the
46
- * root of a tool's arguments must be, naming every one, reading the JSON Schema the native
47
- * server reads: not a union, an array, a scalar, nor `Schema.Struct({})`, which takes any value
48
- * but `null`, where the native server dies when the layer builds, suggesting
49
- * `Tool.EmptyParams`.
50
- */
51
- const assertShapes = (apps) => {
52
- const inputs = apps.flatMap((app) => app.actions).filter((action) => !isToolJson(rootOf(action.input))).map(({ name }) => name);
26
+ const assertToolShapes = (lifted) => {
27
+ const inputs = lifted.map(([action]) => action).filter((action) => !isToolArgumentsSchema(nativeToolJsonSchema(Schema.toCodecJson(action.input), true))).map(({ name }) => name);
53
28
  if (inputs.length > 0) throw new Error(`MCP tool input must be one object with keys, such as a struct: ${inputs.join(", ")}`);
29
+ const misplaced = lifted.filter(([action, lift]) => isMisplaced(action, lift)).map(([{ name }]) => name);
30
+ if (misplaced.length > 0) throw new Error(`MCP media must be the success or an array of it, or a top-level field of a struct success, one, optional or a required array: ${misplaced.join(", ")}`);
54
31
  };
32
+ const nativeInternalErrorText = "Tool execution failed due to an internal server error.";
33
+ const textErrorResult = (text) => new McpSchema.CallToolResult({
34
+ isError: true,
35
+ content: [{
36
+ type: "text",
37
+ text
38
+ }]
39
+ });
40
+ const nativeJsonTextOf = (encoded) => encoded === void 0 ? [] : [{
41
+ type: "text",
42
+ text: JSON.stringify(encoded)
43
+ }];
44
+ const contentBlockOf = ({ kind, field, value }) => ({
45
+ type: kind,
46
+ data: value.data,
47
+ mimeType: value.mimeType,
48
+ ...field === void 0 ? {} : { _meta: { [fieldKey]: field } }
49
+ });
50
+ const parameterValidationErrorOf = (error) => AiError.isAiError(error) && Predicate.isTagged(error.reason, "ToolParameterValidationError") ? error.reason : void 0;
51
+ const omitRequestServices = Context.omit(McpSchema.McpRequestContext, McpSchema.McpServerClient, HttpServerRequest.HttpServerRequest, References.CurrentLogLevel);
52
+ const registerToolsLiftingMedia = Effect.fnUntraced(function* (toolkit, lifts) {
53
+ const registry = yield* McpServer.McpServer;
54
+ const built = yield* toolkit.pipe(Effect.updateContext((context) => Context.merge(context, Context.mergeAll(...Object.values(toolkit.tools).flatMap((tool) => {
55
+ const key = Context.Service(tool.id);
56
+ return Option.toArray(Context.getOption(context, key)).map((handler) => Context.make(key, {
57
+ ...handler,
58
+ context: omitRequestServices(handler.context)
59
+ }));
60
+ })))));
61
+ const retainedServices = omitRequestServices(yield* Effect.context());
62
+ const propagateInterruptOrReportInternal = (cause) => {
63
+ const failure = Cause.findFail(cause);
64
+ return Result.isFailure(failure) && !Cause.hasDies(cause) ? Effect.failCause(failure.failure) : Effect.logError(cause).pipe(Effect.andThen(Effect.provideContext(ErrorReporter.report(cause), retainedServices)), Effect.as(textErrorResult(nativeInternalErrorText)));
65
+ };
66
+ const invalidParamsOrInternal = (cause) => {
67
+ const failure = Cause.findFail(cause);
68
+ if (Result.isSuccess(failure)) {
69
+ const { error } = failure.success;
70
+ const invalid = Context.get(Cause.reasonAnnotations(failure.success), Toolkit.FailureOrigin) === "parameters" ? parameterValidationErrorOf(error) : void 0;
71
+ if (invalid !== void 0) return Effect.fail(new McpSchema.InvalidParams({ message: invalid.message }));
72
+ }
73
+ return propagateInterruptOrReportInternal(cause);
74
+ };
75
+ const registrations = yield* Effect.forEach(Object.entries(built.tools), ([name, tool]) => Effect.gen(function* () {
76
+ const strict = Tool.getStrictMode(tool) === true;
77
+ const decodeOptions = {
78
+ onExcessProperty: strict ? "error" : "ignore",
79
+ errors: "all"
80
+ };
81
+ const lift = lifts.get(name);
82
+ const media = lift === void 0 || lift.fields.length === 0 ? void 0 : lift;
83
+ const structured = media === void 0 ? tool.successSchema : media.rest === void 0 ? void 0 : Schema.toCodecJson(media.rest);
84
+ const outputSchema = structured === void 0 ? void 0 : yield* Schema.decodeUnknownEffect(McpSchema.ToolOutputJson)(nativeToolJsonSchema(structured, false)).pipe(Effect.orDie);
85
+ const inputSchema = yield* Schema.decodeUnknownEffect(McpSchema.ToolJson)(nativeToolJsonSchema(tool.parametersSchema, strict)).pipe(Effect.orDie);
86
+ const liftedFieldNames = new Set(media?.fields.map(({ name }) => name));
87
+ const resultWithLiftedMedia = ({ encodedResult, result }) => {
88
+ if (media === void 0) return new McpSchema.CallToolResult({
89
+ isError: false,
90
+ structuredContent: encodedResult,
91
+ content: nativeJsonTextOf(encodedResult)
92
+ });
93
+ const blocks = liftedFrom(media, result).map(contentBlockOf);
94
+ if (structured === void 0) return new McpSchema.CallToolResult({
95
+ isError: false,
96
+ content: blocks
97
+ });
98
+ const encodedRestOfStruct = Predicate.isObject(encodedResult) ? Object.fromEntries(Object.entries(encodedResult).filter(([key]) => !liftedFieldNames.has(key))) : encodedResult;
99
+ return new McpSchema.CallToolResult({
100
+ isError: false,
101
+ structuredContent: encodedRestOfStruct,
102
+ content: [...nativeJsonTextOf(encodedRestOfStruct), ...blocks]
103
+ });
104
+ };
105
+ return {
106
+ tool: new McpSchema.Tool({
107
+ name,
108
+ description: Tool.getDescription(tool),
109
+ inputSchema,
110
+ outputSchema,
111
+ annotations: {
112
+ ...Context.getOption(tool.annotations, Tool.Title).pipe(Option.map((title) => ({ title })), Option.getOrUndefined),
113
+ readOnlyHint: Context.get(tool.annotations, Tool.Readonly),
114
+ destructiveHint: Context.get(tool.annotations, Tool.Destructive),
115
+ idempotentHint: Context.get(tool.annotations, Tool.Idempotent),
116
+ openWorldHint: Context.get(tool.annotations, Tool.OpenWorld)
117
+ },
118
+ _meta: Context.getOrUndefined(tool.annotations, Tool.Meta)
119
+ }),
120
+ annotations: tool.annotations,
121
+ handle: (payload) => built.handle(name, payload ?? {}, void 0, decodeOptions).pipe(Stream.unwrap, Stream.runLast, Effect.flatMap(Effect.fromOption), Effect.flatMap((result) => !result.isFailure ? Effect.succeed(resultWithLiftedMedia(result)) : result.failureOrigin === "handler" ? Effect.succeed(new McpSchema.CallToolResult({
122
+ isError: true,
123
+ content: nativeJsonTextOf(result.encodedResult)
124
+ })) : Effect.failCause(Cause.annotate(Cause.fail(result.result), Context.make(Toolkit.FailureOrigin, result.failureOrigin ?? "result")))), Effect.catchCause(invalidParamsOrInternal), Effect.provideContext(retainedServices))
125
+ };
126
+ }));
127
+ for (const registration of registrations) yield* registry.addTool(registration);
128
+ });
55
129
  const server = (apps, transport, features = Layer.empty, authentication) => {
56
- const binding = bindTools(apps, authentication === void 0 ? projection : {
57
- ...projection,
130
+ const binding = bindTools(apps, authentication === void 0 ? strictToolProjection : {
131
+ ...strictToolProjection,
58
132
  handler: (run, action) => (input) => action.caller === Anyone ? run(input) : recordStepUp(promote(authentication, run(input)))
59
133
  });
60
- assertShapes(apps);
61
- const names = new Set(apps.flatMap((app) => app.actions.map(({ name }) => name)));
134
+ const lifted = apps.flatMap((app) => app.actions.map((action) => [action, liftOf(action.success)]));
135
+ assertToolShapes(lifted);
136
+ const lifts = new Map(lifted.map(([{ name }, lift]) => [name, lift]));
62
137
  const register = Effect.gen(function* () {
63
138
  const registry = yield* McpServer.McpServer;
64
- const claimed = registry.tools.flatMap(({ tool }) => names.has(tool.name) ? [tool.name] : []);
65
- if (claimed.length > 0) return yield* Effect.die(/* @__PURE__ */ new Error(`Duplicate MCP tool: ${claimed.join(", ")}, claimed by an action and a native feature`));
139
+ const namesClaimedByFeatures = registry.tools.flatMap(({ tool }) => lifts.has(tool.name) ? [tool.name] : []);
140
+ if (namesClaimedByFeatures.length > 0) return yield* Effect.die(/* @__PURE__ */ new Error(`Duplicate MCP tool: ${namesClaimedByFeatures.join(", ")}, claimed by an action and a native feature`));
66
141
  const handlers = yield* Layer.build(binding.layer);
67
- yield* McpServer.registerToolkit(binding.toolkit).pipe(Effect.setContext(Context.add(handlers, McpServer.McpServer, registry)));
142
+ yield* registerToolsLiftingMedia(binding.toolkit, lifts).pipe(Effect.setContext(Context.add(handlers, McpServer.McpServer, registry)));
68
143
  });
69
144
  return Layer.effectDiscard(register).pipe(Layer.provide(features), Layer.provide(transport), Layer.fresh, provideHandlers(apps));
70
145
  };
71
- /**
72
- * What an anonymous caller of a mixed endpoint may send besides a public tool's call:
73
- * discovering the server and its tools, which the contracts describe, and cancelling.
74
- * Everything else, a native feature's listing, completion or subscription among them, and
75
- * any method a later revision adds, authenticates.
76
- */
77
- const anonymous = /* @__PURE__ */ new Set([
146
+ const anonymousMethods = /* @__PURE__ */ new Set([
78
147
  "server/discover",
79
148
  "tools/list",
80
149
  "notifications/cancelled"
81
150
  ]);
82
- /**
83
- * Whether a request to an endpoint serving `actions` must authenticate before the native
84
- * server reads its body. An endpoint whose every tool is protected authenticates every
85
- * request, so a host signs in when it connects. Otherwise the request's routing headers
86
- * decide, which the native server refuses when they disagree with the body: discovery and a
87
- * call of a public tool authenticate only a caller presenting credentials, and everything
88
- * else authenticates, so native features are protected too. A request whose headers name
89
- * no public tool as it is authenticates: it fails closed.
90
- */
91
151
  const requiresAuthentication = (actions) => {
92
152
  const open = new Set(actions.filter(({ caller }) => caller === Anyone).map(({ name }) => name));
93
153
  return (headers) => {
94
154
  if (open.size === 0) return true;
155
+ if (headers["mcp-protocol-version"] !== httpProtocol.protocolVersion) return true;
95
156
  const method = headers["mcp-method"];
96
157
  if (method === void 0) return true;
97
- if (anonymous.has(method)) return false;
158
+ if (anonymousMethods.has(method)) return false;
98
159
  if (method !== "tools/call") return true;
99
160
  const name = headers["mcp-name"];
100
161
  return name === void 0 || !open.has(name);
101
162
  };
102
163
  };
164
+ const namesItsMethod = (request) => Effect.match(request.json, {
165
+ onFailure: () => true,
166
+ onSuccess: (body) => !Predicate.hasProperty(body, "method") || body.method === request.headers["mcp-method"]
167
+ });
103
168
  function layerHttp(apps, { actions, features, path, authentication, ...options }) {
104
169
  const selected = select(toList(apps), actions);
105
170
  const served = selected.flatMap((app) => app.actions);
@@ -107,19 +172,20 @@ function layerHttp(apps, { actions, features, path, authentication, ...options }
107
172
  const transport = server(selected, McpServer.layerHttp({
108
173
  ...options,
109
174
  path: path ?? "/mcp",
110
- protocols: [httpProtocol]
175
+ protocols,
176
+ allowSessionTermination: true
111
177
  }), features, authentication);
112
178
  if (authentication === void 0) return transport;
113
179
  const required = requiresAuthentication(served);
114
- const middleware = HttpRouter.middleware(Effect.map(authentication["~provider"], (runtime) => (route) => Effect.flatMap(HttpServerRequest.HttpServerRequest, (request) => runtime.http(route, !required(request.headers)))));
180
+ const middleware = HttpRouter.middleware(Effect.map(providerOf(authentication), (runtime) => (route) => Effect.flatMap(HttpServerRequest.HttpServerRequest, (request) => request.method !== "POST" || required(request.headers) ? runtime.http(route, false) : Effect.flatMap(namesItsMethod(request), (named) => runtime.http(route, named)))));
115
181
  return transport.pipe(Layer.provide(middleware.layer));
116
182
  }
117
183
  function runStdio(apps, { actions, features, ...options }) {
118
184
  const transport = server(select(toList(apps), actions), McpServer.layerStdio({
119
185
  ...options,
120
- protocols: stdioProtocols
186
+ protocols
121
187
  }), features);
122
- return Layer.launch(transport).pipe(logToStderr, Effect.forkChild, Effect.flatMap(Fiber.await), Effect.flatMap((exit) => Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause) ? Effect.failCause(exit.cause) : Effect.void));
188
+ return Layer.launch(transport).pipe(withStderrConsole, Effect.forkChild, Effect.flatMap(Fiber.await), Effect.flatMap((exit) => Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause) ? Effect.failCause(exit.cause) : Effect.void));
123
189
  }
124
190
  //#endregion
125
191
  export { layerHttp, runStdio };
@@ -0,0 +1,147 @@
1
+ import { Anyone, Certain, ErrorsOf } from "./contract/rules.mjs";
2
+ import { BuiltIn, BuiltIns } from "./contract/errors.mjs";
3
+ import { BuildError, Holding, Known, Member, MiddlewareOf, Protected, Selected, Served, Serving } from "./contract/implementation.mjs";
4
+ import { Call } from "./contract/call.mjs";
5
+ import { Action_d_exports, Any as Any$1, Errors } from "./Action.mjs";
6
+ import { Any as Any$2, DescriptorOf, Identity, Matching, PublicOnly, RemoteRequest, Required, ServedProvider, ServerBuildServices, VerifierError } from "./authentication/provider.mjs";
7
+ import { Context, Effect, Layer, Schema, Scope, Types } from "effect";
8
+ import { HttpRouter } from "effect/http";
9
+ import { Rpc, RpcClient, RpcClientError, RpcGroup, RpcMiddleware, RpcServer } from "effect/rpc";
10
+ //#region src/rpc/ActionRpc.d.ts
11
+ type BindingError = Errors[number];
12
+ type ErrorOf<A extends Any$1, E extends Errors, D> = Schema.Union<ReadonlyArray<A["error"][number] | E[number] | VerifierError<A, D> | BuiltIns>>;
13
+ type RpcOf<A extends Any$1, E extends Errors, D> = A extends Any$1 ? Rpc.Rpc<A["name"], A["input"], A["success"], ErrorOf<A, E, D>> : never;
14
+ /**
15
+ * An RPC binding: actions, the errors every rpc declares, and the descriptor authenticating
16
+ * its protected ones. Plain data, so a copy of it, or one made by another installed copy of
17
+ * this package, serves the same; `layer` serves it and `client` calls it.
18
+ */
19
+ export interface Binding<Actions extends ReadonlyArray<Any$1>, E extends Errors = [], D extends Any$2 | undefined = undefined> {
20
+ readonly authentication: D;
21
+ /** The exact actions bound to this binding. */
22
+ readonly actions: Actions;
23
+ /** The errors every rpc declares besides its action's own, as a list. */
24
+ readonly error: E;
25
+ /**
26
+ * The native group of one rpc per action, named after it, as clients see it:
27
+ * `RpcClient.make(Rpc.group)` is the native client.
28
+ */
29
+ readonly group: RpcGroup.RpcGroup<RpcOf<Actions[number], E, D>>;
30
+ }
31
+ /**
32
+ * Any RPC binding. Its group is any native group here, since native groups are invariant in
33
+ * their rpcs; `actions` carries the types.
34
+ */
35
+ export interface Any {
36
+ readonly authentication?: Any$2 | undefined;
37
+ readonly actions: ReadonlyArray<Any$1>;
38
+ readonly error: Errors;
39
+ readonly group: RpcGroup.Any;
40
+ }
41
+ /** Contract-level configuration shared by servers and clients. */
42
+ export interface Options<E extends Errors = Errors> {
43
+ /** Layer middleware's errors, which every rpc declares: one schema, or a list. */
44
+ readonly error?: E | E[number];
45
+ readonly authentication?: Any$2;
46
+ }
47
+ /**
48
+ * Bind actions once, for servers and clients alike, each as an rpc named after it, taking
49
+ * its action's input and answering its success. Every rpc declares its action's errors, the
50
+ * binding's `error`, and the built-in `InvalidInput`, `Unauthenticated` and `Forbidden`, so
51
+ * clients decode each as a typed failure; a protected one also declares what its descriptor's
52
+ * verifier fails with. Browser-safe, as an HTTP binding is.
53
+ * Guide: `docs/ActionRpc.md` in this package; in an application: `examples/live-rpc/`.
54
+ */
55
+ export declare function make<const Actions extends ReadonlyArray<Any$1 & {
56
+ readonly caller: typeof Anyone;
57
+ }>>(actions: Actions): Binding<Actions, [], undefined>;
58
+ export declare function make<const Actions extends ReadonlyArray<Any$1>, const O extends Options = {}>(actions: Actions, options: O & Required<Actions[number]> & Matching<Actions[number], NoInfer<DescriptorOf<O>>> & NoInfer<Known<O, Options>>): Binding<Actions, ErrorsOf<O>, DescriptorOf<O>>;
59
+ export declare function make<const Actions extends ReadonlyArray<Any$1>>(actions: Actions & PublicOnly<Actions>): Binding<Actions, [], undefined>;
60
+ type Middleware = ReadonlyArray<RpcMiddleware.AnyService>;
61
+ type IdOf<K> = K extends Context.Key<infer I extends RpcMiddleware.AnyId, unknown> ? I : never;
62
+ type Through<M extends Middleware, R> = number extends M["length"] ? R | RpcMiddleware.Requires<IdOf<M[number]>> : M extends readonly [infer K, ...infer Rest extends Middleware] ? Through<Rest, true extends Types.IsUnion<IdOf<K>> ? R | RpcMiddleware.Requires<IdOf<K>> : RpcMiddleware.ApplyServices<IdOf<K>, R>> : R;
63
+ type ForClient<K> = K extends {
64
+ readonly requiredForClient: infer F;
65
+ } ? true extends F ? K : never : never;
66
+ type ServerOnly<M extends Middleware, E extends Errors> = [Exclude<RpcMiddleware.Error<IdOf<M[number]>>, Extract<Certain<E>, BindingError>["Type"] | BuiltIn> | ForClient<M[number]>] extends [never] ? unknown : {
67
+ readonly "Layer middleware fails only with the binding's errors and needs no client": never;
68
+ };
69
+ type LayerRequest<App, A extends Any$1, M extends Middleware> = Exclude<Through<M, RemoteRequest<App, A>>, [Extract<Serving<App, A>, {
70
+ readonly caller: typeof Anyone;
71
+ }>] extends [never] ? Identity<Protected<Serving<App, A>>> : never>;
72
+ type RpcLayer<B extends Any, Apps extends Served, M extends Middleware, A extends Any$1 = B["actions"][number]> = Layer.Layer<never, BuildError<Holding<Member<Apps>, A>, A>, ServerBuildServices<Holding<Member<Apps>, A>, A> | RpcServer.Protocol | HttpRouter.Request.From<"Requires", LayerRequest<Member<Apps>, A, M>> | ServedProvider<Member<Apps>, A, B extends {
73
+ readonly authentication: infer D;
74
+ } ? D : never> | IdOf<M[number]>>;
75
+ /** `ActionRpc.layer`'s options. */
76
+ export interface LayerOptions<M extends Middleware = [], A extends Any$1 = never> {
77
+ /**
78
+ * The actions it serves, among the binding's actions the implementations hold: `[GetUser]`.
79
+ * Defaults to every one of them. A layer whose middleware requires the identity lists
80
+ * protected actions only.
81
+ */
82
+ readonly actions?: ReadonlyArray<A> | undefined;
83
+ /**
84
+ * Native rpc middleware every served rpc runs, the first innermost: inside the
85
+ * authentication of a protected rpc, outside its decoding. Server-only: the binding's group,
86
+ * which clients use, carries none. A middleware requiring the identity needs a layer
87
+ * serving only protected actions.
88
+ */
89
+ readonly middleware?: M;
90
+ }
91
+ /**
92
+ * Serve the binding's actions among `implementations` with Effect's own `RpcServer`, or those
93
+ * its `actions` lists, as `ActionHttp.layer` selects them. The host provides the protocol,
94
+ * such as `RpcServer.layerProtocolWebsocket` or `layerProtocolHttp` and its JSON serialization:
95
+ * the layer refuses to build under a protocol that does not speak JSON. A protected rpc
96
+ * authenticates each message from its headers with the binding's descriptor, outermost; then
97
+ * the layer's `middleware` runs, then the input is decoded, a failure answering `InvalidInput`,
98
+ * then the implementation's `authorize` and the handler, as on every surface.
99
+ *
100
+ * The server serves its own copy of the binding's group, whose payloads, successes and errors
101
+ * are the actions' JSON and which carries no native middleware: the protocol decodes a payload before
102
+ * any middleware runs, and answers one that does not decode with a defect, so each handler
103
+ * runs the authentication and the middleware itself, then decodes the input. A defect is
104
+ * logged with its cause, reported to the call's `ErrorReporter`s, and answered as its
105
+ * request's generic failure, never failing the connection's other calls; a declared failure is
106
+ * the caller's answer, and not reported.
107
+ *
108
+ * The layer fails as the builders do, needs at startup what they need, and per request what
109
+ * each implementation's authorization and the handlers of the actions it serves need, as
110
+ * router request markers, until its `middleware` provides them: over a router-mounted
111
+ * protocol, the route's request context does too. The identity of a protected action is
112
+ * provided by its authentication alone.
113
+ */
114
+ export declare function layer<const B extends Any, const Apps extends Served>(binding: B, implementations: Apps): RpcLayer<B, Apps, []>;
115
+ export declare function layer<const B extends Any, const Apps extends Served, const O extends LayerOptions<Middleware, B["actions"][number]>>(binding: B, implementations: Apps, options: O & ServerOnly<MiddlewareOf<O, Middleware>, B["error"]> & NoInfer<Known<O, LayerOptions<Middleware>>>): RpcLayer<B, Apps, MiddlewareOf<O, Middleware>, Selected<O, B["actions"][number]>>;
116
+ /**
117
+ * What one call of an action `A` fails with, beside the binding's errors `E`: its declared and
118
+ * built-in errors, and Effect's own `RpcClientError`. Input that does not encode is
119
+ * `InvalidInput`, and nothing is sent; an answer that does not decode is a defect, as the
120
+ * native client makes it.
121
+ */
122
+ export type MethodError<A extends Any$1, E extends BindingError = never> = A["error"][number]["Type"] | E["Type"] | BuiltIns["Type"] | RpcClientError.RpcClientError;
123
+ type Method<A extends Any$1, E extends BindingError> = Call<A, Effect.Effect<A["success"]["Type"], MethodError<A, E>>>;
124
+ /**
125
+ * Every action of the binding `B`, as `client.<action>(input)`: a protected one may also fail
126
+ * with what its descriptor's verifier declares.
127
+ */
128
+ export type Client<B extends Any> = { readonly [A in B["actions"][number] as A["name"]]: Method<A, B["error"][number] | VerifierError<A, B["authentication"]>>; };
129
+ /**
130
+ * The native `RpcClient.make` options `client` takes: `spanPrefix`, `spanAttributes`,
131
+ * `generateRequestId` and `disableTracing`. `flatten` changes the client's shape, which the
132
+ * method types cannot follow.
133
+ */
134
+ export type ClientOptions = Omit<NonNullable<Parameters<typeof RpcClient.make>[1]>, "flatten">;
135
+ /**
136
+ * Effect's native `RpcClient` for a binding, one method per action taking the action's input
137
+ * directly: `client.greet({ name })`, as `ActionHttp.client`'s. The argument may be omitted
138
+ * when `{}` is a valid input. Requires the native client `Protocol` and a `Scope`, as
139
+ * `RpcClient.make` does; headers set with `RpcClient.withHeaders` around a call go with its
140
+ * message. The native client itself stays available: `RpcClient.make(Rpc.group)`.
141
+ *
142
+ * A call fails with a declared error value (the action's own, the binding's, a built-in
143
+ * `InvalidInput`, `Unauthenticated` or `Forbidden`, or on a protected rpc its descriptor's),
144
+ * or with Effect's own `RpcClientError` when the server could not be reached.
145
+ */
146
+ export declare function client<const B extends Any>(binding: B, options?: ClientOptions): Effect.Effect<Client<B>, never, RpcClient.Protocol | Scope.Scope>;
147
+ //#endregion
@@ -0,0 +1,113 @@
1
+ import { InvalidInput } from "./contract/errors.mjs";
2
+ import { Anyone, assertDistinct, assertOnce, assertOwnTags, errorList, projectedErrors } from "./contract/rules.mjs";
3
+ import { acquire, provideHandlers, servedBy, toList } from "./contract/implementation.mjs";
4
+ import { checked } from "./contract/call.mjs";
5
+ import { assertAuthentication, providerOf, withVerifierErrors } from "./authentication/provider.mjs";
6
+ import { Cause, Effect, ErrorReporter, Layer, Predicate, Result, Schema, SchemaTransformation } from "effect";
7
+ import { HttpClientRequest, HttpServerRequest } from "effect/http";
8
+ import { HttpApiBuilder } from "effect/http-api";
9
+ import { Rpc, RpcClient, RpcGroup, RpcSerialization, RpcServer } from "effect/rpc";
10
+ //#region src/rpc/ActionRpc.ts
11
+ const declaredErrorsOf = (action, errors, authentication) => projectedErrors(action, withVerifierErrors(action, errors, authentication?.error));
12
+ function make(actions, options = {}) {
13
+ assertOnce("action", actions);
14
+ const errors = errorList(options.error);
15
+ assertOwnTags("ActionRpc binding", errors);
16
+ assertAuthentication(actions, options.authentication);
17
+ return {
18
+ actions,
19
+ error: errors,
20
+ group: RpcGroup.make(...actions.map((action) => Rpc.make(action.name, {
21
+ payload: action.input,
22
+ success: action.success,
23
+ error: Schema.Union(declaredErrorsOf(action, errors, options.authentication))
24
+ }))),
25
+ authentication: options.authentication
26
+ };
27
+ }
28
+ const credentialFromRpcHeaders = (security, headers) => HttpApiBuilder.securityDecode(security).pipe(Effect.provideService(HttpServerRequest.HttpServerRequest, HttpServerRequest.fromClientRequest(HttpClientRequest.get("/", { headers }))), Effect.provideService(HttpServerRequest.ParsedSearchParams, {}));
29
+ const authenticationStepOf = (authentication) => Effect.map(providerOf(authentication), (provider) => (effect, { headers }) => credentialFromRpcHeaders(authentication.security, headers).pipe(Effect.flatMap(provider.authenticate), Effect.flatMap((actor) => Effect.provideService(effect, authentication.identity, actor))));
30
+ var InternalError = class extends Error {
31
+ [ErrorReporter.ignore] = true;
32
+ constructor() {
33
+ super("Internal server error");
34
+ }
35
+ };
36
+ var DeclaredFailure = class {
37
+ json;
38
+ [ErrorReporter.ignore] = true;
39
+ constructor(json) {
40
+ this.json = json;
41
+ }
42
+ toString() {
43
+ return Predicate.isString(this.json) ? this.json : JSON.stringify(this.json);
44
+ }
45
+ };
46
+ const DeclaredFromJson = Schema.Json.pipe(Schema.decodeTo(Schema.instanceOf(DeclaredFailure), SchemaTransformation.transform({
47
+ decode: (json) => new DeclaredFailure(json),
48
+ encode: (failure) => failure.json
49
+ })));
50
+ const callBoundaryOf = (error) => {
51
+ const encode = Schema.encodeUnknownEffect(Schema.toCodecJson(error));
52
+ const internal = (cause) => Effect.logError(cause).pipe(Effect.andThen(ErrorReporter.report(cause)), Effect.andThen(Effect.die(new InternalError())));
53
+ return (effect) => Effect.catchCause(effect, (cause) => {
54
+ if (Cause.hasDies(cause)) return internal(cause);
55
+ const failure = Cause.findFail(cause);
56
+ return Result.isSuccess(failure) ? encode(failure.success.error).pipe(Effect.matchCauseEffect({
57
+ onFailure: (unencoded) => internal(Cause.combine(cause, unencoded)),
58
+ onSuccess: (encoded) => Effect.fail(new DeclaredFailure(encoded))
59
+ })) : Effect.failCause(cause);
60
+ });
61
+ };
62
+ const handlerOf = (action, run, steps, error) => {
63
+ const decodeStrictly = Schema.decodeUnknownEffect(Schema.toCodecJson(action.input), {
64
+ errors: "all",
65
+ onExcessProperty: "error"
66
+ });
67
+ const encode = Schema.encodeUnknownEffect(Schema.toCodecJson(action.success));
68
+ const boundary = callBoundaryOf(error);
69
+ return (payload, options) => {
70
+ const call = Effect.suspend(() => decodeStrictly(payload)).pipe(Effect.mapError(InvalidInput.fromSchemaError), Effect.flatMap(run), Effect.flatMap(encode));
71
+ const wrapped = steps.reduce((inner, step) => Effect.suspend(() => step(inner, {
72
+ ...options,
73
+ payload
74
+ })), call);
75
+ return boundary(wrapped);
76
+ };
77
+ };
78
+ function layer(binding, served, options = {}) {
79
+ const apps = servedBy("RPC binding", binding.actions, toList(served), options.actions);
80
+ const actions = apps.flatMap((app) => app.actions);
81
+ assertAuthentication(actions, binding.authentication);
82
+ const middleware = options.middleware ?? [];
83
+ assertDistinct("middleware", middleware, (key) => key.key);
84
+ const auth = actions.some((action) => action.caller !== Anyone) ? binding.authentication : void 0;
85
+ const jsonGroup = RpcGroup.make(...actions.map((action) => Rpc.make(action.name, {
86
+ payload: Schema.Json,
87
+ success: Schema.Json,
88
+ error: DeclaredFromJson
89
+ })));
90
+ const handlers = Layer.unwrap(Effect.gen(function* () {
91
+ const { codecFor } = yield* RpcServer.Protocol;
92
+ if (codecFor !== RpcSerialization.json.codecFor) return yield* Effect.die(/* @__PURE__ */ new Error("ActionRpc serves a protocol speaking JSON: RpcSerialization.layerJson, layerNdjson, layerJsonRpc or layerNdJsonRpc, not another codec, such as schema-binary's"));
93
+ const listedMiddleware = yield* Effect.forEach(middleware, (key) => Effect.service(key));
94
+ const authenticate = auth === void 0 ? void 0 : yield* authenticationStepOf(auth);
95
+ const bound = yield* acquire(apps);
96
+ const byName = Object.fromEntries(bound.map(([action, run]) => [action.name, handlerOf(action, run, action.caller === Anyone || authenticate === void 0 ? listedMiddleware : [...listedMiddleware, authenticate], Schema.Union(declaredErrorsOf(action, binding.error, binding.authentication)))]));
97
+ return jsonGroup.toLayer(byName);
98
+ })).pipe(provideHandlers(apps));
99
+ return RpcServer.layer(jsonGroup, { disableFatalDefects: true }).pipe(Layer.provide(handlers));
100
+ }
101
+ function client(binding, options) {
102
+ const group = binding.group;
103
+ return Effect.map(RpcClient.make(group, options), (client) => {
104
+ const native = client;
105
+ return Object.fromEntries(binding.actions.map((action) => {
106
+ const method = native[action.name];
107
+ if (method === void 0) throw new Error(`No client method for ${action.name}`);
108
+ return [action.name, checked(action, (payload) => method(payload))];
109
+ }));
110
+ });
111
+ }
112
+ //#endregion
113
+ export { client, layer, make };