@gjermundgaraba/effect-actions 0.18.2 → 0.19.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 (144) hide show
  1. package/README.md +20 -47
  2. package/dist/Action.d.mts +96 -47
  3. package/dist/Action.mjs +11 -14
  4. package/dist/ActionCli.d.mts +81 -42
  5. package/dist/ActionCli.mjs +52 -33
  6. package/dist/ActionHttp.d.mts +84 -43
  7. package/dist/ActionHttp.mjs +2 -2
  8. package/dist/ActionMcp.d.mts +54 -27
  9. package/dist/ActionMcp.mjs +5 -5
  10. package/dist/ActionRpc.d.mts +48 -35
  11. package/dist/ActionRpc.mjs +1 -1
  12. package/dist/ActionToolkit.d.mts +32 -13
  13. package/dist/ActionToolkit.mjs +10 -7
  14. package/dist/Authentication.d.mts +92 -40
  15. package/dist/Authentication.mjs +37 -35
  16. package/dist/Testing.d.mts +22 -24
  17. package/dist/authentication/provider.d.mts +1 -4
  18. package/dist/authentication/provider.mjs +1 -1
  19. package/dist/authentication/refusal.mjs +2 -1
  20. package/dist/cli/commands.d.mts +34 -11
  21. package/dist/cli/commands.mjs +47 -22
  22. package/dist/contract/caller.mjs +1 -1
  23. package/dist/contract/declared.mjs +27 -14
  24. package/dist/contract/errors.d.mts +42 -17
  25. package/dist/contract/errors.mjs +42 -17
  26. package/dist/contract/hints.mjs +3 -2
  27. package/dist/contract/implementation.d.mts +5 -8
  28. package/dist/contract/implementation.mjs +29 -17
  29. package/dist/contract/media.d.mts +6 -2
  30. package/dist/contract/media.mjs +6 -2
  31. package/dist/contract/names.mjs +14 -12
  32. package/dist/contract/status.mjs +3 -10
  33. package/dist/http/client.d.mts +15 -4
  34. package/dist/http/client.mjs +1 -1
  35. package/dist/mcp/client.d.mts +21 -8
  36. package/dist/mcp/client.mjs +6 -8
  37. package/docs/Action-failures.md +6 -15
  38. package/docs/Action.md +159 -191
  39. package/docs/ActionCli-failures.md +12 -92
  40. package/docs/ActionCli.md +102 -156
  41. package/docs/ActionHttp-failures.md +14 -76
  42. package/docs/ActionHttp.md +150 -176
  43. package/docs/ActionMcp-failures.md +2 -22
  44. package/docs/ActionMcp.md +79 -170
  45. package/docs/ActionRpc-failures.md +3 -12
  46. package/docs/ActionRpc.md +183 -120
  47. package/docs/ActionToolkit-failures.md +3 -10
  48. package/docs/ActionToolkit.md +112 -109
  49. package/docs/Authentication-advanced.md +167 -108
  50. package/docs/Authentication-failures.md +11 -91
  51. package/docs/Authentication.md +114 -135
  52. package/docs/README.md +45 -30
  53. package/docs/Testing-failures.md +2 -6
  54. package/docs/Testing.md +126 -159
  55. package/docs/setup-failures.md +8 -32
  56. package/docs/setup.md +42 -37
  57. package/examples/README.md +6 -10
  58. package/examples/assistant/README.md +20 -38
  59. package/examples/assistant/src/agent.ts +2 -3
  60. package/examples/assistant/src/cli.ts +4 -9
  61. package/examples/assistant/src/commands.ts +6 -4
  62. package/examples/assistant/src/identity/authorize.ts +3 -3
  63. package/examples/assistant/src/tasks/Tasks.ts +1 -2
  64. package/examples/assistant/src/tasks/handlers.ts +3 -3
  65. package/examples/assistant/src/toolkit.ts +6 -8
  66. package/examples/assistant/test/commands.test.ts +16 -11
  67. package/examples/assistant/test/tasks/tasks.test.ts +0 -2
  68. package/examples/assistant/test/toolkit.test.ts +0 -1
  69. package/examples/kitchen-sink/README.md +30 -52
  70. package/examples/kitchen-sink/src/Actions.ts +2 -3
  71. package/examples/kitchen-sink/src/Api.ts +3 -4
  72. package/examples/kitchen-sink/src/admin/Admin.ts +1 -1
  73. package/examples/kitchen-sink/src/admin/authenticate.ts +3 -3
  74. package/examples/kitchen-sink/src/admin/authorize.ts +1 -2
  75. package/examples/kitchen-sink/src/agent.ts +1 -1
  76. package/examples/kitchen-sink/src/assistant/handlers.ts +1 -6
  77. package/examples/kitchen-sink/src/attachments/Attachments.ts +4 -8
  78. package/examples/kitchen-sink/src/attachments/download.ts +5 -8
  79. package/examples/kitchen-sink/src/attachments/handlers.ts +2 -3
  80. package/examples/kitchen-sink/src/browser.ts +3 -4
  81. package/examples/kitchen-sink/src/cli.ts +6 -7
  82. package/examples/kitchen-sink/src/config.ts +2 -0
  83. package/examples/kitchen-sink/src/identity/Identity.ts +7 -7
  84. package/examples/kitchen-sink/src/identity/authenticate.ts +3 -5
  85. package/examples/kitchen-sink/src/identity/authorize.ts +4 -3
  86. package/examples/kitchen-sink/src/identity/quota.ts +1 -1
  87. package/examples/kitchen-sink/src/issues/Issues.ts +2 -2
  88. package/examples/kitchen-sink/src/issues/assistant.ts +3 -3
  89. package/examples/kitchen-sink/src/issues/guide.ts +2 -3
  90. package/examples/kitchen-sink/src/ops.ts +3 -6
  91. package/examples/kitchen-sink/src/request-policy.ts +3 -3
  92. package/examples/kitchen-sink/src/routes.ts +4 -6
  93. package/examples/kitchen-sink/src/seed.ts +0 -1
  94. package/examples/kitchen-sink/src/server.ts +11 -12
  95. package/examples/kitchen-sink/src/stdio.ts +3 -2
  96. package/examples/kitchen-sink/src/workspaces/tenant.ts +3 -1
  97. package/examples/kitchen-sink/test/admin/admin.test.ts +0 -1
  98. package/examples/kitchen-sink/test/assistant/assistant.test.ts +1 -1
  99. package/examples/kitchen-sink/test/attachments/attachments.test.ts +1 -2
  100. package/examples/kitchen-sink/test/identity/identity.test.ts +0 -1
  101. package/examples/kitchen-sink/test/issues/issues.test.ts +0 -2
  102. package/examples/kitchen-sink/test/request-policy.test.ts +1 -1
  103. package/examples/kitchen-sink/test/routes.test.ts +3 -3
  104. package/examples/live-rpc/README.md +8 -49
  105. package/examples/live-rpc/src/browser.ts +8 -41
  106. package/examples/live-rpc/src/chat/Chat.ts +9 -3
  107. package/examples/live-rpc/src/chat/room.ts +1 -1
  108. package/examples/live-rpc/src/client.ts +2 -3
  109. package/examples/live-rpc/src/page.ts +34 -0
  110. package/examples/live-rpc/src/routes.ts +6 -7
  111. package/examples/live-rpc/src/server.ts +6 -4
  112. package/examples/live-rpc/test/browser.test.ts +6 -5
  113. package/examples/live-rpc/test/routes.test.ts +16 -15
  114. package/examples/live-rpc/test/server.test.ts +1 -1
  115. package/examples/mcp-tools/README.md +22 -45
  116. package/examples/mcp-tools/src/printers/Printers.ts +3 -7
  117. package/examples/mcp-tools/src/routes.ts +0 -7
  118. package/examples/mcp-tools/src/server.ts +1 -1
  119. package/examples/mcp-tools/test/printers/printers.test.ts +2 -3
  120. package/examples/mcp-tools/test/routes.test.ts +2 -3
  121. package/examples/notes-service/README.md +9 -43
  122. package/examples/notes-service/src/cli.ts +0 -3
  123. package/examples/notes-service/src/commands.ts +7 -17
  124. package/examples/notes-service/src/identity/authorize.ts +2 -5
  125. package/examples/notes-service/src/identity/verifier.ts +3 -4
  126. package/examples/notes-service/src/notes/Notes.ts +2 -6
  127. package/examples/notes-service/src/notes/store.ts +5 -1
  128. package/examples/notes-service/src/routes.ts +6 -8
  129. package/examples/notes-service/src/server.ts +4 -3
  130. package/examples/notes-service/src/stdio.ts +4 -3
  131. package/examples/notes-service/test/commands.test.ts +1 -1
  132. package/examples/notes-service/test/notes/as.ts +6 -0
  133. package/examples/notes-service/test/notes/idempotency.test.ts +1 -4
  134. package/examples/notes-service/test/notes/in-process.test.ts +50 -0
  135. package/examples/notes-service/test/notes/notes.test.ts +3 -42
  136. package/examples/notes-service/test/routes.test.ts +2 -2
  137. package/examples/quickstart/README.md +1 -1
  138. package/examples/quickstart/src/client.ts +1 -0
  139. package/examples/quickstart/src/server.ts +2 -4
  140. package/examples/quickstart/test/{greeting/greeting.test.ts → routes.test.ts} +3 -3
  141. package/package.json +1 -1
  142. package/docs/Action-in-process.md +0 -93
  143. package/docs/CONTEXT.md +0 -46
  144. package/docs/guarantees.md +0 -44
@@ -1,25 +1,27 @@
1
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}`);
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
26
  //#endregion
25
- export { assertDistinct, assertKnown, assertName, assertOnce };
27
+ export { assertDistinct, assertKnown, assertName, assertOnce, heldOnce };
@@ -1,18 +1,11 @@
1
- import { unsuspended } from "./ast.mjs";
2
- import { Schema, SchemaAST } from "effect";
1
+ import { SchemaAST } from "effect";
3
2
  //#region 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
10
  //#endregion
18
11
  export { declared, httpApiStatusOf };
@@ -12,11 +12,22 @@ import { HttpApi, HttpApiClient } from "effect/http-api";
12
12
  * which may return a client failing with `E` or requiring `R` of its own, as
13
13
  * `HttpApiClient.makeWith` takes one.
14
14
  */
15
- export interface Options<E = HttpClientError.HttpClientError, R = never> extends Pick<NonNullable<Parameters<typeof HttpApiClient.make>[1]>, "baseUrl"> {
15
+ export interface Options<E = HttpClientError.HttpClientError, R = never> {
16
+ /**
17
+ * What the routes resolve against: `https://api.example.com`. Omitted: relative routes, which
18
+ * a page resolves against its own origin.
19
+ *
20
+ * **Gotchas**
21
+ *
22
+ * - 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`.
23
+ */
24
+ readonly baseUrl?: NonNullable<Parameters<typeof HttpApiClient.make>[1]>["baseUrl"];
16
25
  /**
17
26
  * 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.
27
+ * `HttpClient.mapRequest(HttpClientRequest.bearerToken(token))`, and a credential read or
28
+ * refreshed as each request is sent `(client) => HttpClient.mapRequestEffect(client, authorize)`.
29
+ * A method also fails with what the returned client fails with beyond an `HttpClientError`, as
30
+ * it is, and requires what it requires.
20
31
  */
21
32
  readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient.With<E, R>) | undefined;
22
33
  }
@@ -25,7 +36,7 @@ type BindingError = Errors[number];
25
36
  * What one call of `A` fails with: a declared error value (the action's own, one `E` of the
26
37
  * binding's, or a built-in `InvalidInput`, `Unauthenticated` or `Forbidden`), a native
27
38
  * `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.
39
+ * the success does not decode.
29
40
  */
30
41
  export type MethodError<A extends Any, E extends BindingError = never> = A["error"][number]["Type"] | E["Type"] | BuiltIns["Type"] | HttpClientError.HttpClientError | Schema.SchemaError;
31
42
  type Method<A extends Any, E extends BindingError, CE, CR> = Call<A, Effect.Effect<A["success"]["Type"], MethodError<A, E> | CE, CR>>;
@@ -10,7 +10,7 @@ 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
  });
@@ -23,7 +23,8 @@ export interface Params {
23
23
  export interface ClientOptions<D extends Any$1 | undefined = Any$1 | undefined, E = HttpClientError.HttpClientError, R = never> {
24
24
  /**
25
25
  * The endpoint, resolved by the `HttpClient`: relative under `Testing.layer`. Defaults to `/mcp`,
26
- * the default `layerHttp` path.
26
+ * the default `layerHttp` path, so a call to an endpoint on another `path` fails with an
27
+ * `McpCallError` that `answered 404`.
27
28
  */
28
29
  readonly url?: string;
29
30
  /**
@@ -34,7 +35,8 @@ export interface ClientOptions<D extends Any$1 | undefined = Any$1 | undefined,
34
35
  /**
35
36
  * The endpoint's descriptor, as `layerHttp` takes it: an error its verifier declares, the
36
37
  * endpoint's HTTP answer for a protected tool, is then that error's decoded value, as over
37
- * `ActionHttp.client`.
38
+ * `ActionHttp.client`; without it, the call fails with an `McpCallError` whose `reason` is a
39
+ * `StatusError`.
38
40
  */
39
41
  readonly authentication?: D;
40
42
  }
@@ -46,6 +48,10 @@ export interface RequestOptions {
46
48
  * one.
47
49
  */
48
50
  readonly url?: string;
51
+ /**
52
+ * Headers sent beside the routing ones `request` fills in, such as `Authorization`; a routing
53
+ * header wins over a given one of its name.
54
+ */
49
55
  readonly headers?: Headers.Input;
50
56
  }
51
57
  declare const StatusError_base: Schema.Class<StatusError, Schema.TaggedStruct<"StatusError", {
@@ -59,8 +65,7 @@ declare const ProtocolError_base: Schema.Class<ProtocolError, Schema.TaggedStruc
59
65
  readonly message: Schema.String;
60
66
  }>, import("effect/Cause").YieldableError>;
61
67
  /**
62
- * The endpoint answered with a JSON-RPC error, such as the native server's for invalid arguments
63
- * or an unknown tool.
68
+ * The endpoint answered with a JSON-RPC error, such as the native server's for an unknown tool.
64
69
  */
65
70
  declare class ProtocolError extends ProtocolError_base {}
66
71
  declare const NoReply_base: Schema.Class<NoReply, Schema.TaggedStruct<"NoReply", {
@@ -89,6 +94,10 @@ declare const McpCallError_base: Schema.Class<McpCallError, Schema.TaggedStruct<
89
94
  * `Effect.catchReason("McpCallError", "StatusError", ...)`. `message` names the tool and the
90
95
  * reason. Structured content that does not decode as the success is a `SchemaError` instead,
91
96
  * and a request that fails an `HttpClientError`.
97
+ *
98
+ * **Gotchas**
99
+ *
100
+ * - `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
101
  */
93
102
  export declare class McpCallError extends McpCallError_base {}
94
103
  type CallError<A extends Any, D> = A["error"][number]["Type"] | VerifierError<A, D>["Type"] | BuiltIn | Schema.SchemaError | HttpClientError.HttpClientError | McpCallError;
@@ -103,10 +112,14 @@ export type Client<Actions extends ReadonlyArray<Any>, D = undefined, E = never,
103
112
  * encoded with the action's schema, and the success decoded from its structured content, each
104
113
  * media field from the blocks whose `_meta` names it, and a success that is media, or whose
105
114
  * 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.
115
+ * or a refusal, is its decoded value, and so is a refusal the endpoint's authentication answers
116
+ * with: a test telling a 401 or 403 from a tool result asserts the status of a request `request`
117
+ * builds. The argument may be omitted when `{}` is a valid input. Requires the native
118
+ * `HttpClient`, such as the one `Testing.layer` provides.
119
+ *
120
+ * **Gotchas**
121
+ *
122
+ * - It drops undeclared input fields as it encodes, and fails with `InvalidInput`, sending nothing, for an input that does not encode.
110
123
  */
111
124
  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
125
  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>;
@@ -9,12 +9,7 @@ import { McpSchema } from "effect/ai";
9
9
  import { HttpClient, HttpClientRequest } from "effect/http";
10
10
  import { Sse } from "effect/encoding";
11
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
- */
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
15
  const name = method === "resources/read" ? params.uri : params.name;
@@ -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,
@@ -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({ ... })`.