@gjermundgaraba/effect-actions 0.18.2 → 0.20.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 (155) hide show
  1. package/README.md +20 -47
  2. package/dist/Action.d.mts +96 -49
  3. package/dist/Action.mjs +12 -16
  4. package/dist/ActionCli.d.mts +82 -44
  5. package/dist/ActionCli.mjs +54 -35
  6. package/dist/ActionHttp.d.mts +84 -45
  7. package/dist/ActionHttp.mjs +3 -4
  8. package/dist/ActionMcp.d.mts +54 -29
  9. package/dist/ActionMcp.mjs +33 -15
  10. package/dist/ActionRpc.d.mts +48 -37
  11. package/dist/ActionRpc.mjs +3 -4
  12. package/dist/ActionToolkit.d.mts +32 -15
  13. package/dist/ActionToolkit.mjs +11 -9
  14. package/dist/Authentication.d.mts +92 -42
  15. package/dist/Authentication.mjs +38 -37
  16. package/dist/Testing.d.mts +23 -27
  17. package/dist/Testing.mjs +1 -2
  18. package/dist/authentication/provider.d.mts +1 -6
  19. package/dist/authentication/provider.mjs +2 -3
  20. package/dist/authentication/refusal.mjs +3 -3
  21. package/dist/cli/commands.d.mts +34 -13
  22. package/dist/cli/commands.mjs +48 -24
  23. package/dist/contract/ast.mjs +1 -2
  24. package/dist/contract/call.d.mts +1 -3
  25. package/dist/contract/call.mjs +1 -2
  26. package/dist/contract/caller.d.mts +1 -3
  27. package/dist/contract/caller.mjs +2 -3
  28. package/dist/contract/declared.d.mts +0 -2
  29. package/dist/contract/declared.mjs +28 -16
  30. package/dist/contract/errors.d.mts +42 -19
  31. package/dist/contract/errors.mjs +43 -19
  32. package/dist/contract/hints.mjs +4 -4
  33. package/dist/contract/implementation.d.mts +7 -14
  34. package/dist/contract/implementation.mjs +31 -19
  35. package/dist/contract/media.d.mts +7 -5
  36. package/dist/contract/media.mjs +7 -4
  37. package/dist/contract/names.mjs +15 -14
  38. package/dist/contract/status.mjs +4 -12
  39. package/dist/http/client.d.mts +15 -6
  40. package/dist/http/client.mjs +2 -3
  41. package/dist/http/schema-errors.mjs +1 -2
  42. package/dist/mcp/client.d.mts +21 -10
  43. package/dist/mcp/client.mjs +8 -11
  44. package/dist/mcp/protocol.mjs +1 -2
  45. package/dist/stdio/console.mjs +1 -2
  46. package/dist/testing/memory.d.mts +1 -3
  47. package/dist/testing/memory.mjs +1 -2
  48. package/docs/Action-failures.md +6 -15
  49. package/docs/Action.md +159 -191
  50. package/docs/ActionCli-failures.md +12 -92
  51. package/docs/ActionCli.md +102 -156
  52. package/docs/ActionHttp-failures.md +14 -76
  53. package/docs/ActionHttp.md +150 -176
  54. package/docs/ActionMcp-failures.md +2 -22
  55. package/docs/ActionMcp.md +79 -170
  56. package/docs/ActionRpc-failures.md +3 -12
  57. package/docs/ActionRpc.md +183 -120
  58. package/docs/ActionToolkit-failures.md +3 -10
  59. package/docs/ActionToolkit.md +112 -109
  60. package/docs/Authentication-advanced.md +171 -108
  61. package/docs/Authentication-failures.md +11 -91
  62. package/docs/Authentication.md +114 -135
  63. package/docs/README.md +45 -30
  64. package/docs/Testing-failures.md +2 -6
  65. package/docs/Testing.md +126 -159
  66. package/docs/setup-failures.md +8 -32
  67. package/docs/setup.md +42 -38
  68. package/examples/README.md +6 -10
  69. package/examples/assistant/README.md +20 -38
  70. package/examples/assistant/src/agent.ts +2 -3
  71. package/examples/assistant/src/cli.ts +4 -9
  72. package/examples/assistant/src/commands.ts +6 -4
  73. package/examples/assistant/src/identity/authorize.ts +3 -3
  74. package/examples/assistant/src/tasks/Tasks.ts +1 -2
  75. package/examples/assistant/src/tasks/handlers.ts +3 -3
  76. package/examples/assistant/src/toolkit.ts +6 -8
  77. package/examples/assistant/test/commands.test.ts +16 -11
  78. package/examples/assistant/test/tasks/tasks.test.ts +0 -2
  79. package/examples/assistant/test/toolkit.test.ts +0 -1
  80. package/examples/kitchen-sink/README.md +30 -52
  81. package/examples/kitchen-sink/src/Actions.ts +2 -3
  82. package/examples/kitchen-sink/src/Api.ts +3 -4
  83. package/examples/kitchen-sink/src/admin/Admin.ts +1 -1
  84. package/examples/kitchen-sink/src/admin/authenticate.ts +3 -3
  85. package/examples/kitchen-sink/src/admin/authorize.ts +1 -2
  86. package/examples/kitchen-sink/src/agent.ts +1 -1
  87. package/examples/kitchen-sink/src/assistant/handlers.ts +1 -6
  88. package/examples/kitchen-sink/src/attachments/Attachments.ts +4 -8
  89. package/examples/kitchen-sink/src/attachments/download.ts +5 -8
  90. package/examples/kitchen-sink/src/attachments/handlers.ts +2 -3
  91. package/examples/kitchen-sink/src/browser.ts +3 -4
  92. package/examples/kitchen-sink/src/cli.ts +6 -7
  93. package/examples/kitchen-sink/src/config.ts +2 -0
  94. package/examples/kitchen-sink/src/identity/Identity.ts +7 -7
  95. package/examples/kitchen-sink/src/identity/authenticate.ts +3 -5
  96. package/examples/kitchen-sink/src/identity/authorize.ts +4 -3
  97. package/examples/kitchen-sink/src/identity/quota.ts +1 -1
  98. package/examples/kitchen-sink/src/issues/Issues.ts +2 -2
  99. package/examples/kitchen-sink/src/issues/assistant.ts +3 -3
  100. package/examples/kitchen-sink/src/issues/guide.ts +2 -3
  101. package/examples/kitchen-sink/src/ops.ts +3 -6
  102. package/examples/kitchen-sink/src/request-policy.ts +8 -4
  103. package/examples/kitchen-sink/src/routes.ts +4 -6
  104. package/examples/kitchen-sink/src/seed.ts +0 -1
  105. package/examples/kitchen-sink/src/server.ts +11 -12
  106. package/examples/kitchen-sink/src/stdio.ts +3 -2
  107. package/examples/kitchen-sink/src/workspaces/tenant.ts +3 -1
  108. package/examples/kitchen-sink/test/admin/admin.test.ts +0 -1
  109. package/examples/kitchen-sink/test/assistant/assistant.test.ts +1 -1
  110. package/examples/kitchen-sink/test/attachments/attachments.test.ts +1 -2
  111. package/examples/kitchen-sink/test/identity/identity.test.ts +0 -1
  112. package/examples/kitchen-sink/test/issues/issues.test.ts +0 -2
  113. package/examples/kitchen-sink/test/request-policy.test.ts +39 -3
  114. package/examples/kitchen-sink/test/routes.test.ts +3 -3
  115. package/examples/live-rpc/README.md +8 -49
  116. package/examples/live-rpc/src/browser.ts +8 -41
  117. package/examples/live-rpc/src/chat/Chat.ts +9 -3
  118. package/examples/live-rpc/src/chat/room.ts +1 -1
  119. package/examples/live-rpc/src/client.ts +2 -3
  120. package/examples/live-rpc/src/page.ts +34 -0
  121. package/examples/live-rpc/src/routes.ts +6 -7
  122. package/examples/live-rpc/src/server.ts +6 -4
  123. package/examples/live-rpc/test/browser.test.ts +6 -5
  124. package/examples/live-rpc/test/routes.test.ts +16 -15
  125. package/examples/live-rpc/test/server.test.ts +1 -1
  126. package/examples/mcp-tools/README.md +22 -45
  127. package/examples/mcp-tools/src/printers/Printers.ts +3 -7
  128. package/examples/mcp-tools/src/routes.ts +0 -7
  129. package/examples/mcp-tools/src/server.ts +1 -1
  130. package/examples/mcp-tools/test/printers/printers.test.ts +2 -3
  131. package/examples/mcp-tools/test/routes.test.ts +2 -3
  132. package/examples/notes-service/README.md +9 -43
  133. package/examples/notes-service/src/cli.ts +0 -3
  134. package/examples/notes-service/src/commands.ts +7 -17
  135. package/examples/notes-service/src/identity/authorize.ts +2 -5
  136. package/examples/notes-service/src/identity/verifier.ts +3 -4
  137. package/examples/notes-service/src/notes/Notes.ts +2 -6
  138. package/examples/notes-service/src/notes/store.ts +5 -1
  139. package/examples/notes-service/src/routes.ts +6 -8
  140. package/examples/notes-service/src/server.ts +4 -3
  141. package/examples/notes-service/src/stdio.ts +4 -3
  142. package/examples/notes-service/test/commands.test.ts +1 -1
  143. package/examples/notes-service/test/notes/as.ts +6 -0
  144. package/examples/notes-service/test/notes/idempotency.test.ts +1 -4
  145. package/examples/notes-service/test/notes/in-process.test.ts +50 -0
  146. package/examples/notes-service/test/notes/notes.test.ts +3 -42
  147. package/examples/notes-service/test/routes.test.ts +2 -2
  148. package/examples/quickstart/README.md +1 -1
  149. package/examples/quickstart/src/client.ts +1 -0
  150. package/examples/quickstart/src/server.ts +2 -4
  151. package/examples/quickstart/test/{greeting/greeting.test.ts → routes.test.ts} +3 -3
  152. package/package.json +12 -16
  153. package/docs/Action-in-process.md +0 -93
  154. package/docs/CONTEXT.md +0 -46
  155. package/docs/guarantees.md +0 -44
@@ -1,9 +1,9 @@
1
1
  import { Anyone, assertAuthentication } from "./caller.mjs";
2
2
  import { Unauthenticated } from "./errors.mjs";
3
- import { assertDistinct, assertOnce } from "./names.mjs";
3
+ import { assertDistinct, assertOnce, heldOnce } from "./names.mjs";
4
4
  import { projectedErrors } from "./declared.mjs";
5
5
  import { Array, Cause, Context, Effect, Exit, Layer, Option, Schema } from "effect";
6
- //#region src/contract/implementation.ts
6
+ // src/contract/implementation.ts
7
7
  const uniqueKey = () => Math.random().toString(36).slice(2);
8
8
  const memoized = (build) => {
9
9
  const key = Context.Service(`effect-actions/Implementation/${uniqueKey()}`);
@@ -14,16 +14,15 @@ const memoized = (build) => {
14
14
  };
15
15
  /**
16
16
  * Actions bound to their handlers and their authorizer: everything one `Action.implement` call
17
- * produced, one builder serving every surface.
18
- * The private fields make this class nominal: a structurally similar object, including one made by
17
+ * produced, one builder serving every surface. The private fields make this class nominal: a structurally similar object, including one made by
19
18
  * spreading an implementation, is not an implementation. Its type parameters may be written
20
19
  * out, as a package emitting declarations prints them: `A` its actions, `RequestServices` each
21
- * action name's handler's per-request requirements, `EX` and `RX` the failures and services of
20
+ * action name's handler's request-time requirements, `EX` and `RX` the failures and services of
22
21
  * its handlers' builder, `RA` what its authorizer reads per request, and `EAX` and `RAX` the
23
- * failures and services of the authorizer's builder. Public-only selections do not acquire
24
- * authorization. A plain authorization callback builds nothing.
22
+ * failures and services of the authorizer's builder.
25
23
  */
26
24
  var Implementation = class Implementation {
25
+ /** The contracts this implementation answers. */
27
26
  actions;
28
27
  #handlers;
29
28
  #authorizer;
@@ -47,8 +46,7 @@ var Implementation = class Implementation {
47
46
  /**
48
47
  * The builders of `app`, its handlers' and its authorizer's, as a layer. Effect memoizes each
49
48
  * by reference within one layer graph, so every surface serving `app` there, and every
50
- * implementation sharing its builder, shares one run. Static, so it stays off the
51
- * public instance type.
49
+ * implementation sharing its builder, shares one run.
52
50
  */
53
51
  static layerOf(app) {
54
52
  const source = Implementation.own(app);
@@ -69,21 +67,36 @@ var Implementation = class Implementation {
69
67
  }
70
68
  /** `app`, if this copy of the module made it; another installed copy's cannot be read. */
71
69
  static own(app) {
72
- if (!(#handlers in app)) throw new Error("Not an implementation made by this Action.implement: is effect-actions installed twice?");
70
+ if (!(#handlers in app)) throw new Error("Not an implementation made by this Action.implement: pass the value implement returned, not a copy such as a spread; or effect-actions is installed twice");
73
71
  return app;
74
72
  }
75
73
  };
76
74
  const toList = (served) => Array.ensure(served);
77
- const assertHeld = (why, listed, held) => {
75
+ const notHeld = {
76
+ byImplementation: {
77
+ why: "no implementation holds it",
78
+ fix: "pass the implementation holding it, or list the contract it implements",
79
+ holder: "they do not hold: list the one they hold"
80
+ },
81
+ byBinding: {
82
+ why: "the binding does not hold it",
83
+ fix: "add it to the binding's actions",
84
+ holder: "it does not hold: list the one it holds"
85
+ }
86
+ };
87
+ /** The actions' names, joined; one whose name another listed contract shares is marked `(another contract)`. */
88
+ const markingOthers = (actions, names) => actions.map(({ name }) => names.has(name) ? `${name} (another contract)` : name).join(", ");
89
+ const assertHeld = ({ why, fix, holder }, listed, held) => {
78
90
  const missing = listed.filter((action) => !held.includes(action));
79
91
  if (missing.length > 0) {
80
92
  const names = new Set(held.map(({ name }) => name));
81
- throw new Error(`Listed in actions, but ${why}: ${missing.map(({ name }) => names.has(name) ? `${name} (another contract)` : name).join(", ")}`);
93
+ const sameName = missing.some(({ name }) => names.has(name));
94
+ throw new Error(`Listed in actions, but ${why}: ${markingOthers(missing, names)} (${fix}${sameName ? `; one marked (another contract) is a contract of that name ${holder}` : ""})`);
82
95
  }
83
96
  };
84
97
  const select = (apps, listed) => {
85
98
  if (listed === void 0) return apps;
86
- assertHeld("no implementation holds it", listed, apps.flatMap((app) => app.actions));
99
+ assertHeld(notHeld.byImplementation, listed, apps.flatMap((app) => app.actions));
87
100
  return apps.flatMap((app) => {
88
101
  const own = app.actions.filter((action) => listed.includes(action));
89
102
  if (own.length === 0) return [];
@@ -91,15 +104,15 @@ const select = (apps, listed) => {
91
104
  });
92
105
  };
93
106
  const servedBy = (what, bound, apps, listed) => {
94
- if (listed !== void 0) assertHeld("the binding does not hold it", listed, bound);
107
+ if (listed !== void 0) assertHeld(notHeld.byBinding, listed, bound);
95
108
  const held = apps.flatMap((app) => app.actions);
96
109
  const served = select(apps, listed ?? bound.filter((action) => held.includes(action)));
97
110
  const actions = served.flatMap((app) => app.actions);
98
111
  if (actions.length === 0 && listed === void 0 && apps.length > 0) {
99
112
  const names = new Set(bound.map(({ name }) => name));
100
- throw new Error(`No action of these implementations is in this ${what}: ${held.map(({ name }) => names.has(name) ? `${name} (another contract)` : name).join(", ") || "none"}`);
113
+ throw new Error(`No action of these implementations is in this ${what}: ${markingOthers(held, names) || "none"} (pass an implementation of a bound action, or bind one of these)`);
101
114
  }
102
- assertOnce("served action", actions);
115
+ assertOnce("served action", actions, heldOnce);
103
116
  return served;
104
117
  };
105
118
  /**
@@ -110,7 +123,7 @@ const servingOf = (what, binding, served, options) => {
110
123
  const apps = servedBy(what, binding.actions, toList(served), options.actions);
111
124
  const actions = apps.flatMap((app) => app.actions);
112
125
  assertAuthentication(actions, binding.authentication);
113
- assertDistinct("middleware", options.middleware ?? [], (key) => key.key);
126
+ assertDistinct("middleware", options.middleware ?? [], (key) => key.key, { fix: "list each middleware once" });
114
127
  return {
115
128
  apps,
116
129
  actions,
@@ -156,5 +169,4 @@ const dispatch = (action, handle, before) => {
156
169
  });
157
170
  return (input) => Effect.scoped(authentication.pipe(Effect.andThen(authorization), Effect.andThen(Effect.suspend(() => handle(input))), Effect.catchCause(declaredOrDefect))).pipe(Effect.annotateLogs(actionAttributes), Effect.withSpan(action.name, { attributes: actionAttributes }, { captureStackTrace: false }));
158
171
  };
159
- //#endregion
160
- export { Implementation, acquire, assertHeld, builders, built, memoized, provideHandlers, select, servingOf, toList };
172
+ export { Implementation, acquire, assertHeld, builders, built, memoized, notHeld, provideHandlers, select, servingOf, toList };
@@ -1,13 +1,15 @@
1
1
  import { Schema, SchemaAST } from "effect";
2
- //#region src/contract/media.d.ts
3
2
  /**
4
3
  * An image, a media field of a success: `{ data: Uint8Array, mimeType: string }`, `mimeType` such
5
- * as `image/png`. A top-level field of a struct success, optional or an array, or the whole
6
- * success, one or an array: an MCP tool lifts it into an image block of its own, and every other
4
+ * as `image/png`. A top-level field of a struct success, one, optional, or a required array, or the
5
+ * whole success, one or an array: an MCP tool lifts it into an image block of its own, and every other
7
6
  * surface sends it as JSON, the bytes in base64. `ActionMcp` refuses it anywhere else.
7
+ *
8
+ * **Gotchas**
9
+ *
10
+ * - A media field's own `description` reaches no model: describe the image in the action's `description`.
8
11
  */
9
12
  export declare const Image: Schema.Struct<{
10
13
  readonly data: Schema.Uint8Array;
11
14
  readonly mimeType: Schema.String;
12
- }>;
13
- //#endregion
15
+ }>;
@@ -1,5 +1,5 @@
1
1
  import { Schema } from "effect";
2
- //#region src/contract/media.ts
2
+ // src/contract/media.ts
3
3
  const mediaKindAnnotation = "effect-actions/media";
4
4
  const Media = Schema.Struct({
5
5
  data: Schema.Uint8Array,
@@ -7,12 +7,15 @@ const Media = Schema.Struct({
7
7
  });
8
8
  /**
9
9
  * An image, a media field of a success: `{ data: Uint8Array, mimeType: string }`, `mimeType` such
10
- * as `image/png`. A top-level field of a struct success, optional or an array, or the whole
11
- * success, one or an array: an MCP tool lifts it into an image block of its own, and every other
10
+ * as `image/png`. A top-level field of a struct success, one, optional, or a required array, or the
11
+ * whole success, one or an array: an MCP tool lifts it into an image block of its own, and every other
12
12
  * surface sends it as JSON, the bytes in base64. `ActionMcp` refuses it anywhere else.
13
+ *
14
+ * **Gotchas**
15
+ *
16
+ * - A media field's own `description` reaches no model: describe the image in the action's `description`.
13
17
  */
14
18
  const Image = Media.annotate({ [mediaKindAnnotation]: "image" });
15
19
  const isMarked = (ast) => ast.annotations?.[mediaKindAnnotation] === "image";
16
20
  const kindOf = (ast) => isMarked(ast) && ast.encoding === void 0 ? "image" : void 0;
17
- //#endregion
18
21
  export { Image, Media, isMarked, kindOf };
@@ -1,25 +1,26 @@
1
- //#region src/contract/names.ts
2
- const validName = /^[A-Za-z0-9_-]{1,128}$/;
3
- const assertName = (what, name) => {
4
- if (!validName.test(name) || name === "then") throw new Error(`Invalid ${what}: ${name}`);
1
+ // src/contract/names.ts
2
+ const namePattern = "[A-Za-z0-9_-]{1,128}";
3
+ const validName = new RegExp(`^${namePattern}$`);
4
+ const assertName = (name) => {
5
+ if (!validName.test(name) || name === "then") throw new Error(`Invalid action name: ${name} (use ${namePattern}, other than "then")`);
5
6
  };
6
- const assertDistinct = (what, items, nameOf, claimantOf = nameOf) => {
7
+ const assertDistinct = (what, items, nameOf, { claimantOf = nameOf, labelOf = nameOf, fix }) => {
7
8
  const seen = /* @__PURE__ */ new Map();
8
9
  for (const item of items) {
9
10
  const name = nameOf(item);
10
- const claimant = claimantOf(item);
11
11
  const other = seen.get(name);
12
12
  if (other !== void 0) {
13
- const by = other === claimant ? "" : `, claimed by ${other} and ${claimant}`;
14
- throw new Error(`Duplicate ${what}: ${name}${by}`);
13
+ const claimants = [claimantOf(other), claimantOf(item)];
14
+ const by = claimants[0] === claimants[1] ? "" : `, claimed by ${claimants.join(" and ")}`;
15
+ throw new Error(`Duplicate ${what}: ${labelOf(other)}${by} (${fix})`);
15
16
  }
16
- seen.set(name, claimant);
17
+ seen.set(name, item);
17
18
  }
18
19
  };
19
- const assertOnce = (what, actions) => assertDistinct(what, actions, (action) => action.name);
20
- const assertKnown = (what, keys, names) => {
20
+ const heldOnce = "pass one implementation of it, or rename one";
21
+ const assertOnce = (what, actions, fix = "list it once, or rename one") => assertDistinct(what, actions, (action) => action.name, { fix });
22
+ const assertKnown = (what, keys, names, fix) => {
21
23
  const unknown = keys.filter((key) => !names.includes(key));
22
- if (unknown.length > 0) throw new Error(`Unknown ${what}: ${unknown.join(", ")}`);
24
+ if (unknown.length > 0) throw new Error(`Unknown ${what}: ${unknown.join(", ")} (${fix})`);
23
25
  };
24
- //#endregion
25
- export { assertDistinct, assertKnown, assertName, assertOnce };
26
+ export { assertDistinct, assertKnown, assertName, assertOnce, heldOnce };
@@ -1,18 +1,10 @@
1
- import { unsuspended } from "./ast.mjs";
2
- import { Schema, SchemaAST } from "effect";
3
- //#region src/contract/status.ts
1
+ import { SchemaAST } from "effect";
2
+ // src/contract/status.ts
4
3
  const httpApiStatusOf = SchemaAST.resolveAt("httpApiStatus");
5
4
  const statusThroughSuspensions = (ast) => httpApiStatusOf(ast) ?? (SchemaAST.isSuspend(ast) ? statusThroughSuspensions(ast.thunk()) : void 0);
6
- /**
7
- * `error` as an endpoint declares it: each member of a union without a status of its own apart,
8
- * each with its status, 422 without one.
9
- */
5
+ /** One error schema, from `errorSchemas`, as an endpoint declares it: with its status, 422 without one. */
10
6
  const declared = (error) => {
11
7
  const status = statusThroughSuspensions(error.ast);
12
- if (status !== void 0) return [httpApiStatusOf(error.ast) === void 0 ? error.annotate({ httpApiStatus: status }) : error];
13
- const resolved = unsuspended(error.ast);
14
- if (SchemaAST.isUnion(resolved) && resolved.checks === void 0 && resolved.encoding === void 0) return resolved.types.flatMap((member) => declared(Schema.make(member)));
15
- return [error.annotate({ httpApiStatus: 422 })];
8
+ return status === void 0 ? error.annotate({ httpApiStatus: 422 }) : httpApiStatusOf(error.ast) === void 0 ? error.annotate({ httpApiStatus: status }) : error;
16
9
  };
17
- //#endregion
18
10
  export { declared, httpApiStatusOf };
@@ -5,18 +5,28 @@ import { Any as Any$1, VerifierError } from "../authentication/provider.mjs";
5
5
  import { Effect, Schema } from "effect";
6
6
  import { HttpClient, HttpClientError } from "effect/http";
7
7
  import { HttpApi, HttpApiClient } from "effect/http-api";
8
- //#region src/http/client.d.ts
9
8
  /**
10
9
  * The native `HttpApiClient.make` options except `transformResponse`, which may change a call's
11
10
  * success, failure or services the method types cannot follow: `baseUrl`, and `transformClient`,
12
11
  * which may return a client failing with `E` or requiring `R` of its own, as
13
12
  * `HttpApiClient.makeWith` takes one.
14
13
  */
15
- export interface Options<E = HttpClientError.HttpClientError, R = never> extends Pick<NonNullable<Parameters<typeof HttpApiClient.make>[1]>, "baseUrl"> {
14
+ export interface Options<E = HttpClientError.HttpClientError, R = never> {
15
+ /**
16
+ * What the routes resolve against: `https://api.example.com`. Omitted: relative routes, which
17
+ * a page resolves against its own origin.
18
+ *
19
+ * **Gotchas**
20
+ *
21
+ * - Outside a browser, set it: a relative route has no page to resolve against, and the call fails with an `HttpClientError` whose `reason._tag` is `InvalidUrlError`.
22
+ */
23
+ readonly baseUrl?: NonNullable<Parameters<typeof HttpApiClient.make>[1]>["baseUrl"];
16
24
  /**
17
25
  * Wraps the native `HttpClient` the calls send with: a bearer token is
18
- * `HttpClient.mapRequest(HttpClientRequest.bearerToken(token))`. A method also fails with what
19
- * the returned client fails with beyond an `HttpClientError`, and requires what it requires.
26
+ * `HttpClient.mapRequest(HttpClientRequest.bearerToken(token))`, and a credential read or
27
+ * refreshed as each request is sent `(client) => HttpClient.mapRequestEffect(client, authorize)`.
28
+ * A method also fails with what the returned client fails with beyond an `HttpClientError`, as
29
+ * it is, and requires what it requires.
20
30
  */
21
31
  readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient.With<E, R>) | undefined;
22
32
  }
@@ -25,7 +35,7 @@ type BindingError = Errors[number];
25
35
  * What one call of `A` fails with: a declared error value (the action's own, one `E` of the
26
36
  * binding's, or a built-in `InvalidInput`, `Unauthenticated` or `Forbidden`), a native
27
37
  * `HttpClientError` for a failed request or an undeclared answer, or a `SchemaError` when
28
- * the success does not decode. Input that does not encode is `InvalidInput`, and nothing is sent.
38
+ * the success does not decode.
29
39
  */
30
40
  export type MethodError<A extends Any, E extends BindingError = never> = A["error"][number]["Type"] | E["Type"] | BuiltIns["Type"] | HttpClientError.HttpClientError | Schema.SchemaError;
31
41
  type Method<A extends Any, E extends BindingError, CE, CR> = Call<A, Effect.Effect<A["success"]["Type"], MethodError<A, E> | CE, CR>>;
@@ -47,5 +57,4 @@ export interface AnyHttp {
47
57
  * what its `transformClient` adds.
48
58
  */
49
59
  export type Client<H extends AnyHttp, E = never, R = never> = { readonly [A in H["actions"][number] as A["name"]]: Method<A, H["error"][number] | VerifierError<A, H["authentication"]>, E, R>; };
50
- //#endregion
51
60
  export {};
@@ -2,7 +2,7 @@ import { checked } from "../contract/call.mjs";
2
2
  import { Effect } from "effect";
3
3
  import { HttpClient } from "effect/http";
4
4
  import { HttpApiClient } from "effect/http-api";
5
- //#region src/http/client.ts
5
+ // src/http/client.ts
6
6
  const methodsWith = (http, httpClient, baseUrl) => Effect.map(HttpApiClient.makeWith(http.api, {
7
7
  baseUrl,
8
8
  httpClient
@@ -10,10 +10,9 @@ const methodsWith = (http, httpClient, baseUrl) => Effect.map(HttpApiClient.make
10
10
  const native = client;
11
11
  return Object.fromEntries(http.actions.map((action) => {
12
12
  const method = native[action.name];
13
- if (method === void 0) throw new Error(`No client method for ${action.name}`);
13
+ if (method === void 0) throw new Error(`No client method for ${action.name} (use the binding \`ActionHttp.make\` returned, whose group holds this action)`);
14
14
  return [action.name, checked(action, (payload) => method({ payload }))];
15
15
  }));
16
16
  });
17
17
  const methods = (http, { baseUrl, transformClient } = {}) => Effect.flatMap(HttpClient.HttpClient, (native) => transformClient === void 0 ? methodsWith(http, native, baseUrl) : methodsWith(http, transformClient(native), baseUrl));
18
- //#endregion
19
18
  export { methods };
@@ -1,9 +1,8 @@
1
1
  import { InvalidInput } from "../contract/errors.mjs";
2
2
  import { Effect } from "effect";
3
3
  import { HttpApiMiddleware } from "effect/http-api";
4
- //#region src/http/schema-errors.ts
4
+ // src/http/schema-errors.ts
5
5
  const responseEncodingKinds = /* @__PURE__ */ new Set(["Body", "ResponseHeaders"]);
6
6
  var SchemaErrors = class extends HttpApiMiddleware.Service()("effect-actions/http/SchemaErrors", { error: [InvalidInput] }) {};
7
7
  const schemaErrors = () => HttpApiMiddleware.layerSchemaErrorTransform(SchemaErrors, (failure) => Effect.fail(responseEncodingKinds.has(failure.kind) ? failure : InvalidInput.fromSchemaError(failure.cause)));
8
- //#endregion
9
8
  export { SchemaErrors, schemaErrors };
@@ -4,7 +4,6 @@ import { Any } from "../Action.mjs";
4
4
  import { Any as Any$1, VerifierError } from "../authentication/provider.mjs";
5
5
  import { Effect, Schema } from "effect";
6
6
  import { Headers, HttpClient, HttpClientError, HttpClientRequest } from "effect/http";
7
- //#region src/mcp/client.d.ts
8
7
  /**
9
8
  * An MCP request's parameters, as `request` takes them: JSON, with any `_meta`, leaving
10
9
  * out an `undefined` one, as a caller may. A helper passing on whole params types them as this,
@@ -23,7 +22,8 @@ export interface Params {
23
22
  export interface ClientOptions<D extends Any$1 | undefined = Any$1 | undefined, E = HttpClientError.HttpClientError, R = never> {
24
23
  /**
25
24
  * The endpoint, resolved by the `HttpClient`: relative under `Testing.layer`. Defaults to `/mcp`,
26
- * the default `layerHttp` path.
25
+ * the default `layerHttp` path, so a call to an endpoint on another `path` fails with an
26
+ * `McpCallError` that `answered 404`.
27
27
  */
28
28
  readonly url?: string;
29
29
  /**
@@ -34,7 +34,8 @@ export interface ClientOptions<D extends Any$1 | undefined = Any$1 | undefined,
34
34
  /**
35
35
  * The endpoint's descriptor, as `layerHttp` takes it: an error its verifier declares, the
36
36
  * endpoint's HTTP answer for a protected tool, is then that error's decoded value, as over
37
- * `ActionHttp.client`.
37
+ * `ActionHttp.client`; without it, the call fails with an `McpCallError` whose `reason` is a
38
+ * `StatusError`.
38
39
  */
39
40
  readonly authentication?: D;
40
41
  }
@@ -46,6 +47,10 @@ export interface RequestOptions {
46
47
  * one.
47
48
  */
48
49
  readonly url?: string;
50
+ /**
51
+ * Headers sent beside the routing ones `request` fills in, such as `Authorization`; a routing
52
+ * header wins over a given one of its name.
53
+ */
49
54
  readonly headers?: Headers.Input;
50
55
  }
51
56
  declare const StatusError_base: Schema.Class<StatusError, Schema.TaggedStruct<"StatusError", {
@@ -59,8 +64,7 @@ declare const ProtocolError_base: Schema.Class<ProtocolError, Schema.TaggedStruc
59
64
  readonly message: Schema.String;
60
65
  }>, import("effect/Cause").YieldableError>;
61
66
  /**
62
- * The endpoint answered with a JSON-RPC error, such as the native server's for invalid arguments
63
- * or an unknown tool.
67
+ * The endpoint answered with a JSON-RPC error, such as the native server's for an unknown tool.
64
68
  */
65
69
  declare class ProtocolError extends ProtocolError_base {}
66
70
  declare const NoReply_base: Schema.Class<NoReply, Schema.TaggedStruct<"NoReply", {
@@ -89,6 +93,10 @@ declare const McpCallError_base: Schema.Class<McpCallError, Schema.TaggedStruct<
89
93
  * `Effect.catchReason("McpCallError", "StatusError", ...)`. `message` names the tool and the
90
94
  * reason. Structured content that does not decode as the success is a `SchemaError` instead,
91
95
  * and a request that fails an `HttpClientError`.
96
+ *
97
+ * **Gotchas**
98
+ *
99
+ * - `returned an error: Invalid parameters for tool …`, an `UndeclaredError`, from a call its types allowed: the endpoint serves another contract under that name; give `client` the served contract value.
92
100
  */
93
101
  export declare class McpCallError extends McpCallError_base {}
94
102
  type CallError<A extends Any, D> = A["error"][number]["Type"] | VerifierError<A, D>["Type"] | BuiltIn | Schema.SchemaError | HttpClientError.HttpClientError | McpCallError;
@@ -103,10 +111,14 @@ export type Client<Actions extends ReadonlyArray<Any>, D = undefined, E = never,
103
111
  * encoded with the action's schema, and the success decoded from its structured content, each
104
112
  * media field from the blocks whose `_meta` names it, and a success that is media, or whose
105
113
  * every field is, from its blocks alone. A declared error the tool returns, the action's own
106
- * or a refusal, is its decoded value, and so is a refusal the endpoint's authentication
107
- * answers with, or an error its verifier declares, given the `authentication`: a test telling a 401 or 403 from a tool result asserts the status of a request
108
- * `request` builds. The argument may be omitted when `{}` is a valid input. Requires the
109
- * native `HttpClient`, such as the one `Testing.layer` provides.
114
+ * or a refusal, is its decoded value, and so is a refusal the endpoint's authentication answers
115
+ * with: a test telling a 401 or 403 from a tool result asserts the status of a request `request`
116
+ * builds. The argument may be omitted when `{}` is a valid input. Requires the native
117
+ * `HttpClient`, such as the one `Testing.layer` provides.
118
+ *
119
+ * **Gotchas**
120
+ *
121
+ * - It drops undeclared input fields as it encodes, and fails with `InvalidInput`, sending nothing, for an input that does not encode.
110
122
  */
111
123
  export declare function client<const Actions extends ReadonlyArray<Any>, D extends Any$1 | undefined = undefined>(actions: Actions, options?: ClientOptions<D>): Effect.Effect<Client<Actions, D>, never, HttpClient.HttpClient>;
112
124
  export declare function client<const Actions extends ReadonlyArray<Any>, E, R, D extends Any$1 | undefined = undefined>(actions: Actions, options: ClientOptions<D, E, R>): Effect.Effect<Client<Actions, D, E, R>, never, HttpClient.HttpClient>;
@@ -123,5 +135,4 @@ export declare function client<const Actions extends ReadonlyArray<Any>, E, R, D
123
135
  * a call its types would not send.
124
136
  */
125
137
  export declare const request: (method: string, params?: Params, { headers, url }?: RequestOptions) => HttpClientRequest.HttpClientRequest;
126
- //#endregion
127
138
  export {};
@@ -8,16 +8,11 @@ import { Effect, Match, Option, Predicate, Schema } from "effect";
8
8
  import { McpSchema } from "effect/ai";
9
9
  import { HttpClient, HttpClientRequest } from "effect/http";
10
10
  import { Sse } from "effect/encoding";
11
- //#region src/mcp/client.ts
12
- /**
13
- * One stateless MCP request of `method` with `params`, as `layerHttp` serves it: the headers
14
- * routing it, which repeat what its body says, and its JSON-RPC body. The client metadata goes in
15
- * `_meta`, under any given, such as a `progressToken`; the protocol version is always the
16
- * request's own.
17
- */
11
+ // src/mcp/client.ts
12
+ /** The routing headers and JSON-RPC body of the request `request` builds. */
18
13
  const statelessRequest = (method, params) => {
19
14
  const { protocolVersion } = httpProtocol;
20
- const name = method === "resources/read" ? params.uri : params.name;
15
+ const name = method === "resources/read" ? params["uri"] : params["name"];
21
16
  return {
22
17
  headers: {
23
18
  accept: "application/json, text/event-stream",
@@ -50,8 +45,7 @@ var StatusError = class extends Schema.TaggedError()("StatusError", {
50
45
  body: Schema.String
51
46
  }) {};
52
47
  /**
53
- * The endpoint answered with a JSON-RPC error, such as the native server's for invalid arguments
54
- * or an unknown tool.
48
+ * The endpoint answered with a JSON-RPC error, such as the native server's for an unknown tool.
55
49
  */
56
50
  var ProtocolError = class extends Schema.TaggedError()("ProtocolError", {
57
51
  code: Schema.Finite,
@@ -83,6 +77,10 @@ const sentenceOf = Match.typeTags()({
83
77
  * `Effect.catchReason("McpCallError", "StatusError", ...)`. `message` names the tool and the
84
78
  * reason. Structured content that does not decode as the success is a `SchemaError` instead,
85
79
  * and a request that fails an `HttpClientError`.
80
+ *
81
+ * **Gotchas**
82
+ *
83
+ * - `returned an error: Invalid parameters for tool …`, an `UndeclaredError`, from a call its types allowed: the endpoint serves another contract under that name; give `client` the served contract value.
86
84
  */
87
85
  var McpCallError = class extends Schema.TaggedError()("McpCallError", {
88
86
  tool: Schema.String,
@@ -161,5 +159,4 @@ const request = (method, params = {}, { headers = {}, url = defaultPath } = {})
161
159
  const { headers: routingHeaders, body } = statelessRequest(method, params);
162
160
  return HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.setHeaders(routingHeaders), HttpClientRequest.bodyJsonUnsafe(body));
163
161
  };
164
- //#endregion
165
162
  export { McpCallError, client, request, statelessRequest };
@@ -3,7 +3,7 @@ import { Media, isMarked, kindOf } from "../contract/media.mjs";
3
3
  import { Predicate, Schema, SchemaAST } from "effect";
4
4
  import { McpProtocol } from "effect/ai";
5
5
  import { Base64 } from "effect/encoding";
6
- //#region src/mcp/protocol.ts
6
+ // src/mcp/protocol.ts
7
7
  const httpProtocol = McpProtocol.v2026_07_28;
8
8
  const protocols = [
9
9
  McpProtocol.v2026_07_28,
@@ -133,5 +133,4 @@ const unliftedFrom = (lift, result) => {
133
133
  return field.name === void 0 || value === void 0 ? [] : [[field.name, value]];
134
134
  })]);
135
135
  };
136
- //#endregion
137
136
  export { defaultPath, fieldKey, httpProtocol, isMisplaced, liftOf, liftedFrom, protocols, unliftedFrom };
@@ -1,5 +1,5 @@
1
1
  import { Clock, Console, Effect, Function, Predicate } from "effect";
2
- //#region src/stdio/console.ts
2
+ // src/stdio/console.ts
3
3
  const stderrConsole = (console, clock) => {
4
4
  const counts = /* @__PURE__ */ new Map();
5
5
  const timers = /* @__PURE__ */ new Map();
@@ -60,5 +60,4 @@ const stderrConsole = (console, clock) => {
60
60
  };
61
61
  };
62
62
  const withStderrConsole = (effect) => Effect.provideServiceEffect(effect, Console.Console, Effect.zipWith(Effect.service(Console.Console), Effect.service(Clock.Clock), stderrConsole));
63
- //#endregion
64
63
  export { withStderrConsole };
@@ -1,6 +1,4 @@
1
1
  import { Layer } from "effect";
2
2
  import { HttpClient, HttpRouter, HttpServer } from "effect/http";
3
- //#region src/testing/memory.d.ts
4
3
  /** What in-memory routes may leave to the host: the router, the platform, nothing per request. */
5
- export type Served = HttpRouter.HttpRouter | HttpRouter.Request<"Requires", never> | HttpRouter.Request<"GlobalRequires", never> | HttpRouter.Request<"Error", any> | HttpRouter.Request<"GlobalError", any> | Layer.Success<typeof HttpServer.layerServices>;
6
- //#endregion
4
+ export type Served = HttpRouter.HttpRouter | HttpRouter.Request<"Requires", never> | HttpRouter.Request<"GlobalRequires", never> | HttpRouter.Request<"Error", any> | HttpRouter.Request<"GlobalError", any> | Layer.Success<typeof HttpServer.layerServices>;
@@ -1,6 +1,6 @@
1
1
  import { Effect, Layer, Predicate, Stream } from "effect";
2
2
  import { FetchHttpClient, HttpBody, HttpClient, HttpClientRequest } from "effect/http";
3
- //#region src/testing/memory.ts
3
+ // src/testing/memory.ts
4
4
  const relativeUrlOrigin = "http://localhost/";
5
5
  const withWebStreamBodyInCurrentContext = (request) => {
6
6
  const body = request.body;
@@ -22,5 +22,4 @@ const clientOf = (handler) => {
22
22
  return Effect.flatMap(withWebStreamBodyInCurrentContext(sent), (ready) => native.postprocess(Effect.succeed(ready)).pipe(Effect.provideService(FetchHttpClient.Fetch, fetch)));
23
23
  }), native.preprocess))).pipe(Layer.provide(Layer.fresh(FetchHttpClient.layer)));
24
24
  };
25
- //#endregion
26
25
  export { clientOf };
@@ -6,42 +6,33 @@ The tasks are in [Action.md](Action.md).
6
6
  ## At `make`
7
7
 
8
8
  - `Argument of type 'Struct<…>' is not assignable to parameter of type 'Fields'` at `Schema.Struct(X.input)`, or `Objects property names must be strings` or `Invalid value used as weak map key` thrown: an action's `input` is a schema already, a `Schema.Struct` where fields were given. Use it as it is: `Schema.Array(X.input)`.
9
- - `Invalid action name: <name>` thrown: an action's name matches `[A-Za-z0-9_-]{1,128}` and is not `then`. Rename the action.
10
9
  - `'readOnlyHint' does not exist in type 'Mcp'`, or `Type 'true' is not assignable to type 'never'` on `readOnlyHint` beside another hint: a tool's read-only hint is its action's `readOnly`. Set `readOnly` instead.
11
10
  - `'<key>' does not exist in type 'Options'`, or `in type 'Mcp'`, or `Type '...' is not assignable to type 'never'` on the key: `make` takes no such option or hint. The key may be misspelled, such as `destructive` for `destructiveHint`.
12
- - `Type 'H' is not assignable to type 'H & ...'`, or `… is not assignable to type 'ServiceCaller<…>'`, or `… is not assignable to type '[Extract<ErrorMembers<E>, …>] extends …'`, at `make` in a helper whose type parameter stands for `mcp`, `caller` or `error`, as in `<const H extends Action.Mcp>(mcp: H)`: `make` refuses options a type parameter stands for. Type the parameter concretely, `Action.Mcp` or `typeof CurrentUser`, or spread a constant object into the options.
13
- - A type error on `error`, `... is not assignable to type 'readonly never[]'` or naming the remaining errors, or `Action "<name>": error _tag "Forbidden" is built in, and declared on every surface` thrown: an action's `error` lists a built-in error, or one of your own that encodes with the `_tag` `InvalidInput`, `Unauthenticated` or `Forbidden`. Drop the built-in error, which a handler may fail with anyway; rename your own.
11
+ - `Type 'H' is not assignable to type 'H & ...'` at `make` in a helper whose type parameter stands for `mcp`, as in `<const H extends Action.Mcp>(mcp: H)`: `make` cannot check the hints of a type parameter. Type the parameter `Action.Mcp`, or spread a constant object into the options.
14
12
  - `ReferenceError` thrown where a contract is defined: `make` reads every declared error's `_tag` when it is called, so an entry of `error` that is a `Schema.suspend` whose thunk reads a `const` declared further down fails at once. Declare the error before the action.
15
13
 
16
14
  ## At `implement`
17
15
 
18
- - `... is not assignable to type '"A protected action takes { authorize }: your rule, or Action.allowAll"'`, or `Protected actions require authorize, or Action.allowAll` thrown: an implementation holding a protected action states `authorize`. Pass `{ authorize }`, or `{ authorize: Action.allowAll }`.
19
- - `Missing authorize: pass an authorization function, or Action.allowAll` thrown, or a layer build dying with it: `authorize` is neither a function nor an Effect building one, or that Effect built something else.
20
16
  - `Type 'undefined' is not assignable to type 'Authorizer<...>'`: `authorize` is never `undefined`. Write `enabled ? authorize : Action.allowAll`.
21
17
  - `Parameter 'action' implicitly has an 'any' type`, or an `Effect.fn` authorizer whose `action` is `any`: an authorizer defined apart from `implement` annotates its parameter, `(action: Action.Any)`.
22
18
  - `Type 'X' is not assignable to type 'Forbidden | Unauthenticated'`, ending `Property '"~declared in the action's error, or built in"' is missing in type 'X'`: an authorizer fails only with `Action.Unauthenticated` or `Action.Forbidden`, and this one fails with `X`. A limit, or another failure callers should see, is the handler's: declare `X` in the action's `error` and fail with it there.
23
- - `Argument of type ... is not assignable to parameter of type ...`, or `Type 'Effect<...>' is not assignable to type 'Effect<...>'` at a handler of a record written in place, ending `Property '"~declared in the action's error, or built in"' is missing in type 'X'`, under `Type 'X' is not assignable to type 'Forbidden | InvalidInput | Unauthenticated'` or a longer union: a handler fails only with the errors its action declares and the library's built-in ones, and this one fails with `X`, which may be Effect's `HttpApiError.Forbidden` or a `Forbidden` of your own. Add `X` to the action's `error`, or handle it. A builder's error is reported at the builder and names one handler, in `The types returned by '<handler>(...)'`: the next shows once that one is fixed.
19
+ - `Argument of type ... is not assignable to parameter of type ...`, or `Type 'Effect<...>' is not assignable to type 'Effect<...>'` at a handler of a record written in place, ending `Property '"~declared in the action's error, or built in"' is missing in type 'X'`: the handler fails with `X`, which its action does not declare. Add `X` to the action's `error`, or handle it.
20
+ - `Type 'X' is not assignable to type 'Forbidden | InvalidInput | Unauthenticated'`, or a longer union, under a handler's error: a handler fails only with the errors its action declares and the library's built-in ones, and `X` may be Effect's `HttpApiError.Forbidden` or a `Forbidden` of your own. Add `X` to the action's `error`, or handle it.
21
+ - `The types returned by '<handler>(...)'` at a builder: that handler fails with an error its action does not declare, and the error names one handler at a time; add the error to the action's `error` or handle it, and the next handler's shows.
24
22
  - `Binding element '<field>' implicitly has an 'any' type` (TS7031), or `Parameter 'input' implicitly has an 'any' type`, at every handler of a builder: the builder is wrapped, as in `Effect.gen(…).pipe(Effect.provide(layer))`, and its record is no longer typed from the contracts. Give `implement` the builder itself, and provide the layer to the surface's layer or to the server.
25
- - `Missing handlers: <names>` or `Unknown handlers: <keys>` thrown, also when a builder's layer builds: a record of handlers has one own function for each action of its `implement` call, and no other key. Add a missing handler to the record itself, not to a prototype; remove an unknown one, or add its action to the list.
26
23
  - `'<name>' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer` (TS7022), or, with the types bypassed, an acquisition that never completes: a builder acquires an `Action.client` of its own implementation, directly or through another builder. Call the services both use instead.
27
24
 
28
25
  ## Where an implementation is served
29
26
 
30
27
  - `Type 'CurrentUser' is not assignable to type 'never'`, or `Request<"Requires", CurrentUser>` still owed, for a layer serving only public actions: no surface provides a public action an identity, and a public action's handler reads one. Make the action protected.
31
- - `Type '{ readonly "A builder yields the caller, which a server has per request: yield it in a handler or an authorizer": { readonly caller: CurrentUser; readonly builderOf: "createNote" | ...; }; }' is not assignable to type 'never'` where a server is launched, or `is missing the following properties from type 'Scope'` at each `it.effect` that runs the routes, one error for each, all of one cause, the builder of the `implement` call whose actions `builderOf` names, or a service shared across requests that should be resolved per request: what a builder, or the Effect building an authorizer, yields is resolved once, at startup, and a server has the caller per request alone. Move the `yield*` into the handler or the authorizer.
28
+ - `Type '{ readonly "A builder yields the caller, which a server has per request: yield it in a handler or an authorizer": { readonly caller: CurrentUser; readonly builderOf: "createNote" | ...; }; }' is not assignable to type 'never'` where a server is launched: the builder of the `implement` call whose actions `builderOf` names, or the Effect building its authorizer, yields the caller, which a builder would resolve once at startup. Move the `yield*` into the handler or the authorizer.
29
+ - `is missing the following properties from type 'Scope'` at each `it.effect` that runs the routes: a builder, or the Effect building an authorizer, yields the caller or another per-request service, which a builder would resolve once at startup. Move the `yield*` into the handler or the authorizer.
32
30
  - `HttpRouter.Request<"Requires", X>` still owed, or `Type 'X' is not assignable to type 'never'` at `runMain`, although `X`, a store the authorizer reads, is provided at startup: what a plain authorizer yields is a request-time requirement, and this one yields `X` on every call. Make `authorize` an Effect that yields `X` once and returns the function ([Authorize calls](Action.md#authorize-calls)).
33
31
  - `unknown` among a surface's requirements, and a type error where it is served or run, such as `Argument of type 'Layer<never, unknown, unknown>' is not assignable` at `HttpRouter.toWebHandler` or `Type 'unknown' is not assignable to type 'never'`: the implementations are typed `Action.AnyImplementation`, as a helper's parameter or a list's annotation. Make the helper generic over them, `<const Apps extends Action.AnyImplementation | ReadonlyArray<Action.AnyImplementation>>(apps: Apps)`, or drop the annotation.
34
32
  - `Request<"Requires", unknown>` where an implementation is served, or `unknown` among the services a call or a run still requires: TypeScript inferred no requirements for one handler of a record, as a data-first call whose type takes a parameter from no argument causes, `Effect.catchTag(effect, tag, f)` for one. Write that handler's Effect `effect.pipe(Effect.catchTag(tag, f))`, or as a generator.
35
- - A builder runs twice, or a job, an agent or a second server reads other state than the routes, such as an empty in-memory store: inside one `HttpRouter.serve` or one `Testing.layer` a builder runs once, however many surfaces serve it, but each builds its routes in a layer graph of its own, which shares nothing it builds with a layer merged beside it. A job beside the server shares its build only when the job's side builds first, so it can pass in a test and build twice when the job starts later. Put the job or agent inside the layer the server serves, or provide `Action.layer(implementations)` and the services they share above both ([Share one build across servers](Action-in-process.md#share-one-build-across-servers)).
36
- - Two surfaces in one layer graph, each given startup services of its own, both answer with one surface's: a builder runs once per layer graph, with the services of its first build. Provide the services once; where the surfaces must differ, wrap each in `Layer.fresh`, providing what they share, authentication included, outside it, or give each an implementation of its own.
37
- - `Not an implementation made by this Action.implement: is effect-actions installed twice?` thrown by a surface, `client` or `layer`: another installed copy of the package made the implementation, or the value is a copy of one, such as a spread. Install one copy of the package, and pass the value `implement` returned.
38
33
 
39
34
  ## At `client` and its calls
40
35
 
41
- - `Listed in actions, but no implementation holds it: <names>` thrown by `Action.client`: an action listed in `actions` is none the given implementations hold. Pass the implementation holding it, or list the contract value it implemented.
42
- - `Duplicate action: <name>` thrown: a list given to `implement` or `byName` names one action twice, or two implementations given to `Action.client` implement an action of that name. Two contracts sharing a name are told apart by `actions`, listing one; two implementations of one contract are not: pass one.
43
36
  - `Type 'CurrentUser' is not assignable to type 'never'` where a program calling an `Action.client` runs, or `Type 'CurrentUser' is missing the following properties from type 'Scope'` at `it.effect`: a call to a protected action reads the caller from its own context, never from the one the client was acquired in. Provide the caller around the call.
44
37
  - `Type 'Scope' is not assignable to type 'never'` where a program runs, or `Service not found: effect/Scope` in plain JavaScript: acquiring an `Action.client` builds, as a layer does. Acquire it in a builder or a layer, or run the program in `Effect.scoped`.
45
- - A builder runs on every request, or a route owes `HttpRouter.Request<"Requires", X>` for `X`, a service its builder yields: acquiring an `Action.client` runs the builders, and a handler acquires one on every request. Acquire the client in the builder, and call it in the handler.
46
38
  - An `Action.client` call dies with a `SchemaError`: the handler's success, or what it fails with, does not pass through its codec. The cause holds the failure beside the `SchemaError`, as `Cause.pretty` shows: before it for a failure the action's errors reject, undeclared or breaking a check, after it for one they accept that does not encode. Return what the success schema accepts, and fail with an error the action declares.
47
- - `InvalidInput` with `Expected <Class>` from an `Action.client` call given a plain object: an input that is a class encodes only its instances. Pass `new Class({ ... })`.