@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.
- package/README.md +20 -47
- package/dist/Action.d.mts +96 -49
- package/dist/Action.mjs +12 -16
- package/dist/ActionCli.d.mts +82 -44
- package/dist/ActionCli.mjs +54 -35
- package/dist/ActionHttp.d.mts +84 -45
- package/dist/ActionHttp.mjs +3 -4
- package/dist/ActionMcp.d.mts +54 -29
- package/dist/ActionMcp.mjs +33 -15
- package/dist/ActionRpc.d.mts +48 -37
- package/dist/ActionRpc.mjs +3 -4
- package/dist/ActionToolkit.d.mts +32 -15
- package/dist/ActionToolkit.mjs +11 -9
- package/dist/Authentication.d.mts +92 -42
- package/dist/Authentication.mjs +38 -37
- package/dist/Testing.d.mts +23 -27
- package/dist/Testing.mjs +1 -2
- package/dist/authentication/provider.d.mts +1 -6
- package/dist/authentication/provider.mjs +2 -3
- package/dist/authentication/refusal.mjs +3 -3
- package/dist/cli/commands.d.mts +34 -13
- package/dist/cli/commands.mjs +48 -24
- package/dist/contract/ast.mjs +1 -2
- package/dist/contract/call.d.mts +1 -3
- package/dist/contract/call.mjs +1 -2
- package/dist/contract/caller.d.mts +1 -3
- package/dist/contract/caller.mjs +2 -3
- package/dist/contract/declared.d.mts +0 -2
- package/dist/contract/declared.mjs +28 -16
- package/dist/contract/errors.d.mts +42 -19
- package/dist/contract/errors.mjs +43 -19
- package/dist/contract/hints.mjs +4 -4
- package/dist/contract/implementation.d.mts +7 -14
- package/dist/contract/implementation.mjs +31 -19
- package/dist/contract/media.d.mts +7 -5
- package/dist/contract/media.mjs +7 -4
- package/dist/contract/names.mjs +15 -14
- package/dist/contract/status.mjs +4 -12
- package/dist/http/client.d.mts +15 -6
- package/dist/http/client.mjs +2 -3
- package/dist/http/schema-errors.mjs +1 -2
- package/dist/mcp/client.d.mts +21 -10
- package/dist/mcp/client.mjs +8 -11
- package/dist/mcp/protocol.mjs +1 -2
- package/dist/stdio/console.mjs +1 -2
- package/dist/testing/memory.d.mts +1 -3
- package/dist/testing/memory.mjs +1 -2
- 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 +171 -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 -38
- 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 +8 -4
- 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 +39 -3
- 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 +12 -16
- package/docs/Action-in-process.md +0 -93
- package/docs/CONTEXT.md +0 -46
- 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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
+
}>;
|
package/dist/contract/media.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
|
-
|
|
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
|
|
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 };
|
package/dist/contract/names.mjs
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
|
|
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
|
|
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
|
-
|
|
25
|
-
export { assertDistinct, assertKnown, assertName, assertOnce };
|
|
26
|
+
export { assertDistinct, assertKnown, assertName, assertOnce, heldOnce };
|
package/dist/contract/status.mjs
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
-
|
|
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 };
|
package/dist/http/client.d.mts
CHANGED
|
@@ -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>
|
|
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))
|
|
19
|
-
*
|
|
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.
|
|
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 {};
|
package/dist/http/client.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 };
|
package/dist/mcp/client.d.mts
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
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 {};
|
package/dist/mcp/client.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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 };
|
package/dist/mcp/protocol.mjs
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/dist/stdio/console.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Clock, Console, Effect, Function, Predicate } from "effect";
|
|
2
|
-
|
|
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>;
|
package/dist/testing/memory.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Layer, Predicate, Stream } from "effect";
|
|
2
2
|
import { FetchHttpClient, HttpBody, HttpClient, HttpClientRequest } from "effect/http";
|
|
3
|
-
|
|
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 };
|
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({ ... })`.
|