@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.
- package/README.md +20 -47
- package/dist/Action.d.mts +96 -47
- package/dist/Action.mjs +11 -14
- package/dist/ActionCli.d.mts +81 -42
- package/dist/ActionCli.mjs +52 -33
- package/dist/ActionHttp.d.mts +84 -43
- package/dist/ActionHttp.mjs +2 -2
- package/dist/ActionMcp.d.mts +54 -27
- package/dist/ActionMcp.mjs +5 -5
- package/dist/ActionRpc.d.mts +48 -35
- package/dist/ActionRpc.mjs +1 -1
- package/dist/ActionToolkit.d.mts +32 -13
- package/dist/ActionToolkit.mjs +10 -7
- package/dist/Authentication.d.mts +92 -40
- package/dist/Authentication.mjs +37 -35
- package/dist/Testing.d.mts +22 -24
- package/dist/authentication/provider.d.mts +1 -4
- package/dist/authentication/provider.mjs +1 -1
- package/dist/authentication/refusal.mjs +2 -1
- package/dist/cli/commands.d.mts +34 -11
- package/dist/cli/commands.mjs +47 -22
- package/dist/contract/caller.mjs +1 -1
- package/dist/contract/declared.mjs +27 -14
- package/dist/contract/errors.d.mts +42 -17
- package/dist/contract/errors.mjs +42 -17
- package/dist/contract/hints.mjs +3 -2
- package/dist/contract/implementation.d.mts +5 -8
- package/dist/contract/implementation.mjs +29 -17
- package/dist/contract/media.d.mts +6 -2
- package/dist/contract/media.mjs +6 -2
- package/dist/contract/names.mjs +14 -12
- package/dist/contract/status.mjs +3 -10
- package/dist/http/client.d.mts +15 -4
- package/dist/http/client.mjs +1 -1
- package/dist/mcp/client.d.mts +21 -8
- package/dist/mcp/client.mjs +6 -8
- package/docs/Action-failures.md +6 -15
- package/docs/Action.md +159 -191
- package/docs/ActionCli-failures.md +12 -92
- package/docs/ActionCli.md +102 -156
- package/docs/ActionHttp-failures.md +14 -76
- package/docs/ActionHttp.md +150 -176
- package/docs/ActionMcp-failures.md +2 -22
- package/docs/ActionMcp.md +79 -170
- package/docs/ActionRpc-failures.md +3 -12
- package/docs/ActionRpc.md +183 -120
- package/docs/ActionToolkit-failures.md +3 -10
- package/docs/ActionToolkit.md +112 -109
- package/docs/Authentication-advanced.md +167 -108
- package/docs/Authentication-failures.md +11 -91
- package/docs/Authentication.md +114 -135
- package/docs/README.md +45 -30
- package/docs/Testing-failures.md +2 -6
- package/docs/Testing.md +126 -159
- package/docs/setup-failures.md +8 -32
- package/docs/setup.md +42 -37
- package/examples/README.md +6 -10
- package/examples/assistant/README.md +20 -38
- package/examples/assistant/src/agent.ts +2 -3
- package/examples/assistant/src/cli.ts +4 -9
- package/examples/assistant/src/commands.ts +6 -4
- package/examples/assistant/src/identity/authorize.ts +3 -3
- package/examples/assistant/src/tasks/Tasks.ts +1 -2
- package/examples/assistant/src/tasks/handlers.ts +3 -3
- package/examples/assistant/src/toolkit.ts +6 -8
- package/examples/assistant/test/commands.test.ts +16 -11
- package/examples/assistant/test/tasks/tasks.test.ts +0 -2
- package/examples/assistant/test/toolkit.test.ts +0 -1
- package/examples/kitchen-sink/README.md +30 -52
- package/examples/kitchen-sink/src/Actions.ts +2 -3
- package/examples/kitchen-sink/src/Api.ts +3 -4
- package/examples/kitchen-sink/src/admin/Admin.ts +1 -1
- package/examples/kitchen-sink/src/admin/authenticate.ts +3 -3
- package/examples/kitchen-sink/src/admin/authorize.ts +1 -2
- package/examples/kitchen-sink/src/agent.ts +1 -1
- package/examples/kitchen-sink/src/assistant/handlers.ts +1 -6
- package/examples/kitchen-sink/src/attachments/Attachments.ts +4 -8
- package/examples/kitchen-sink/src/attachments/download.ts +5 -8
- package/examples/kitchen-sink/src/attachments/handlers.ts +2 -3
- package/examples/kitchen-sink/src/browser.ts +3 -4
- package/examples/kitchen-sink/src/cli.ts +6 -7
- package/examples/kitchen-sink/src/config.ts +2 -0
- package/examples/kitchen-sink/src/identity/Identity.ts +7 -7
- package/examples/kitchen-sink/src/identity/authenticate.ts +3 -5
- package/examples/kitchen-sink/src/identity/authorize.ts +4 -3
- package/examples/kitchen-sink/src/identity/quota.ts +1 -1
- package/examples/kitchen-sink/src/issues/Issues.ts +2 -2
- package/examples/kitchen-sink/src/issues/assistant.ts +3 -3
- package/examples/kitchen-sink/src/issues/guide.ts +2 -3
- package/examples/kitchen-sink/src/ops.ts +3 -6
- package/examples/kitchen-sink/src/request-policy.ts +3 -3
- package/examples/kitchen-sink/src/routes.ts +4 -6
- package/examples/kitchen-sink/src/seed.ts +0 -1
- package/examples/kitchen-sink/src/server.ts +11 -12
- package/examples/kitchen-sink/src/stdio.ts +3 -2
- package/examples/kitchen-sink/src/workspaces/tenant.ts +3 -1
- package/examples/kitchen-sink/test/admin/admin.test.ts +0 -1
- package/examples/kitchen-sink/test/assistant/assistant.test.ts +1 -1
- package/examples/kitchen-sink/test/attachments/attachments.test.ts +1 -2
- package/examples/kitchen-sink/test/identity/identity.test.ts +0 -1
- package/examples/kitchen-sink/test/issues/issues.test.ts +0 -2
- package/examples/kitchen-sink/test/request-policy.test.ts +1 -1
- package/examples/kitchen-sink/test/routes.test.ts +3 -3
- package/examples/live-rpc/README.md +8 -49
- package/examples/live-rpc/src/browser.ts +8 -41
- package/examples/live-rpc/src/chat/Chat.ts +9 -3
- package/examples/live-rpc/src/chat/room.ts +1 -1
- package/examples/live-rpc/src/client.ts +2 -3
- package/examples/live-rpc/src/page.ts +34 -0
- package/examples/live-rpc/src/routes.ts +6 -7
- package/examples/live-rpc/src/server.ts +6 -4
- package/examples/live-rpc/test/browser.test.ts +6 -5
- package/examples/live-rpc/test/routes.test.ts +16 -15
- package/examples/live-rpc/test/server.test.ts +1 -1
- package/examples/mcp-tools/README.md +22 -45
- package/examples/mcp-tools/src/printers/Printers.ts +3 -7
- package/examples/mcp-tools/src/routes.ts +0 -7
- package/examples/mcp-tools/src/server.ts +1 -1
- package/examples/mcp-tools/test/printers/printers.test.ts +2 -3
- package/examples/mcp-tools/test/routes.test.ts +2 -3
- package/examples/notes-service/README.md +9 -43
- package/examples/notes-service/src/cli.ts +0 -3
- package/examples/notes-service/src/commands.ts +7 -17
- package/examples/notes-service/src/identity/authorize.ts +2 -5
- package/examples/notes-service/src/identity/verifier.ts +3 -4
- package/examples/notes-service/src/notes/Notes.ts +2 -6
- package/examples/notes-service/src/notes/store.ts +5 -1
- package/examples/notes-service/src/routes.ts +6 -8
- package/examples/notes-service/src/server.ts +4 -3
- package/examples/notes-service/src/stdio.ts +4 -3
- package/examples/notes-service/test/commands.test.ts +1 -1
- package/examples/notes-service/test/notes/as.ts +6 -0
- package/examples/notes-service/test/notes/idempotency.test.ts +1 -4
- package/examples/notes-service/test/notes/in-process.test.ts +50 -0
- package/examples/notes-service/test/notes/notes.test.ts +3 -42
- package/examples/notes-service/test/routes.test.ts +2 -2
- package/examples/quickstart/README.md +1 -1
- package/examples/quickstart/src/client.ts +1 -0
- package/examples/quickstart/src/server.ts +2 -4
- package/examples/quickstart/test/{greeting/greeting.test.ts → routes.test.ts} +3 -3
- package/package.json +1 -1
- package/docs/Action-in-process.md +0 -93
- package/docs/CONTEXT.md +0 -46
- package/docs/guarantees.md +0 -44
package/dist/contract/names.mjs
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
//#region src/contract/names.ts
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
|
|
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
|
|
14
|
-
|
|
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,
|
|
17
|
+
seen.set(name, item);
|
|
17
18
|
}
|
|
18
19
|
};
|
|
19
|
-
const
|
|
20
|
-
const
|
|
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 };
|
package/dist/contract/status.mjs
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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 };
|
package/dist/http/client.d.mts
CHANGED
|
@@ -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>
|
|
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))
|
|
19
|
-
*
|
|
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.
|
|
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>>;
|
package/dist/http/client.mjs
CHANGED
|
@@ -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
|
});
|
package/dist/mcp/client.d.mts
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
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>;
|
package/dist/mcp/client.mjs
CHANGED
|
@@ -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
|
|
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,
|
package/docs/Action-failures.md
CHANGED
|
@@ -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 & ...'
|
|
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'
|
|
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
|
|
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({ ... })`.
|