@gjermundgaraba/effect-actions 0.8.0 → 0.10.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 +57 -50
- package/dist/Action.d.mts +285 -79
- package/dist/Action.mjs +124 -23
- package/dist/ActionCli.d.mts +102 -31
- package/dist/ActionCli.mjs +114 -16
- package/dist/ActionHttp.d.mts +170 -51
- package/dist/ActionHttp.mjs +130 -79
- package/dist/ActionMcp.d.mts +71 -21
- package/dist/ActionMcp.mjs +108 -81
- package/dist/ActionToolkit.d.mts +56 -27
- package/dist/ActionToolkit.mjs +16 -5
- package/dist/Authentication.d.mts +129 -33
- package/dist/Authentication.mjs +231 -38
- package/dist/Testing.d.mts +82 -57
- package/dist/Testing.mjs +89 -79
- package/dist/internal/actions.d.mts +5 -25
- package/dist/internal/actions.mjs +75 -27
- package/dist/internal/authentication.d.mts +102 -0
- package/dist/internal/authentication.mjs +11 -0
- package/dist/internal/call.d.mts +12 -0
- package/dist/internal/call.mjs +12 -0
- package/dist/internal/cli.d.mts +36 -22
- package/dist/internal/cli.mjs +311 -33
- package/dist/internal/client.d.mts +43 -0
- package/dist/internal/client.mjs +25 -0
- package/dist/internal/console.mjs +77 -0
- package/dist/internal/declared.d.mts +13 -0
- package/dist/internal/declared.mjs +34 -0
- package/dist/internal/errors.d.mts +77 -0
- package/dist/internal/errors.mjs +79 -0
- package/dist/internal/implementation.d.mts +171 -41
- package/dist/internal/implementation.mjs +147 -29
- package/dist/internal/mcp.d.mts +11 -0
- package/dist/internal/mcp.mjs +52 -0
- package/dist/internal/memory.d.mts +6 -0
- package/dist/internal/memory.mjs +38 -0
- package/dist/internal/refusal.mjs +67 -0
- package/dist/internal/schema-errors.mjs +20 -0
- package/dist/internal/tools.mjs +27 -57
- package/docs/Action.md +401 -73
- package/docs/ActionCli.md +280 -51
- package/docs/ActionHttp.md +266 -82
- package/docs/ActionMcp.md +149 -104
- package/docs/ActionToolkit.md +97 -35
- package/docs/Authentication.md +369 -62
- package/docs/CONTEXT.md +23 -36
- package/docs/README.md +29 -73
- package/docs/Testing.md +154 -77
- package/docs/guarantees.md +81 -43
- package/docs/setup.md +90 -27
- package/package.json +9 -17
- package/dist/ActionCatalog.d.mts +0 -30
- package/dist/ActionCatalog.mjs +0 -39
- package/dist/ActionCliClient.d.mts +0 -48
- package/dist/ActionCliClient.mjs +0 -43
- package/dist/ActionGroup.d.mts +0 -44
- package/dist/ActionGroup.mjs +0 -35
- package/dist/ActionHttpClient.d.mts +0 -33
- package/dist/ActionHttpClient.mjs +0 -16
- package/dist/TestingClient.d.mts +0 -17
- package/dist/TestingClient.mjs +0 -22
- package/dist/internal/fetchClient.d.mts +0 -6
- package/dist/internal/fetchClient.mjs +0 -8
- package/dist/internal/tools.d.mts +0 -22
- package/docs/ActionCatalog.md +0 -54
- package/docs/ActionCliClient.md +0 -63
- package/docs/ActionGroup.md +0 -128
- package/docs/ActionHttpClient.md +0 -80
|
@@ -1,29 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import "../Action.mjs";
|
|
2
|
+
import { SchemaAST } from "effect";
|
|
3
3
|
//#region src/internal/actions.d.ts
|
|
4
|
-
/** One side of a schema-error policy: the error it answers with, and how to make it. */
|
|
5
|
-
export interface SchemaErrorAnswer<E extends Codec> {
|
|
6
|
-
/** A declared error schema; its `httpApiStatus` is the HTTP status of the answer. */
|
|
7
|
-
readonly schema: E;
|
|
8
|
-
readonly make: (failure: HttpApiError.HttpApiSchemaError) => NoInfer<E["Type"]>;
|
|
9
|
-
}
|
|
10
4
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
5
|
+
* The caller of a public action: anyone, signed in or not. Registered globally, so two copies
|
|
6
|
+
* of the package agree on it, as they agree on a name.
|
|
13
7
|
*/
|
|
14
|
-
export
|
|
15
|
-
/** The request did not decode: its payload, or its params, headers or query. */
|
|
16
|
-
readonly invalid: SchemaErrorAnswer<Invalid>;
|
|
17
|
-
/** The handler's result did not encode: its body or its response headers. */
|
|
18
|
-
readonly internal: SchemaErrorAnswer<Internal>;
|
|
19
|
-
}
|
|
20
|
-
/** The contract half of a group: what adapters and clients need, without `implement`. */
|
|
21
|
-
export interface Actions<Name extends string = string, A extends ReadonlyArray<Any> = ReadonlyArray<Any>, PolicyError extends Codec = Codec> {
|
|
22
|
-
readonly name: Name;
|
|
23
|
-
readonly actions: A;
|
|
24
|
-
/** How HTTP answers failed decoding or encoding; native behavior without one. MCP is unaffected. */
|
|
25
|
-
readonly schemaError: SchemaErrorPolicy<PolicyError, PolicyError> | undefined;
|
|
26
|
-
}
|
|
27
|
-
/** The errors a group's policy may answer with, by lookup rather than a conditional type. */
|
|
28
|
-
export type PolicyError<G extends Actions> = NonNullable<G["schemaError"]>[keyof SchemaErrorPolicy]["schema"];
|
|
8
|
+
export declare const Anyone: unique symbol;
|
|
29
9
|
//#endregion
|
|
@@ -1,41 +1,89 @@
|
|
|
1
|
+
import { builtIns, statuses } from "./errors.mjs";
|
|
2
|
+
import { Predicate, Schema, SchemaAST } from "effect";
|
|
1
3
|
//#region src/internal/actions.ts
|
|
2
|
-
/** Each distinct error a policy may answer with, invalid first. */
|
|
3
|
-
const policyErrors = (policy) => [.../* @__PURE__ */ new Set([policy.invalid.schema, policy.internal.schema])];
|
|
4
4
|
/**
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
* decoding the request before it.
|
|
5
|
+
* The list an `error` option stands for: one schema, a list as given, or none, as
|
|
6
|
+
* `HttpApiEndpoint` takes it.
|
|
8
7
|
*/
|
|
9
|
-
const
|
|
10
|
-
/** The policy's answer to one native failure: `internal` for the response side, else `invalid`. */
|
|
11
|
-
const answerSchemaError = (policy, failure) => responseKinds.has(failure.kind) ? policy.internal.make(failure) : policy.invalid.make(failure);
|
|
8
|
+
const errorList = (error) => error === void 0 ? [] : Schema.isSchema(error) ? [error] : error;
|
|
12
9
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* What a projection of an action declares: the built-in errors, the action's own, and any
|
|
11
|
+
* its surface adds, such as a binding's. The built-ins come first, since a failure encodes
|
|
12
|
+
* and decodes with the first schema that accepts it, and they are classes, which accept
|
|
13
|
+
* only their own instances: a loose schema of the action's never captures one. A schema
|
|
14
|
+
* listed twice is not repeated.
|
|
16
15
|
*/
|
|
17
|
-
const projectedErrors = (action, surface) => [.../* @__PURE__ */ new Set([
|
|
18
|
-
|
|
16
|
+
const projectedErrors = (action, surface = []) => [.../* @__PURE__ */ new Set([
|
|
17
|
+
...builtIns,
|
|
18
|
+
...action.error,
|
|
19
|
+
...surface
|
|
20
|
+
])];
|
|
21
|
+
/**
|
|
22
|
+
* The caller of a public action: anyone, signed in or not. Registered globally, so two copies
|
|
23
|
+
* of the package agree on it, as they agree on a name.
|
|
24
|
+
*/
|
|
25
|
+
const Anyone = Symbol.for("@gjermundgaraba/effect-actions/Anyone");
|
|
26
|
+
const validName = /^[A-Za-z0-9_-]{1,128}$/;
|
|
19
27
|
/** Names become path segments, OpenAPI identifiers and client method keys. */
|
|
20
28
|
const assertName = (what, name) => {
|
|
21
29
|
if (!validName.test(name) || name === "then") throw new Error(`Invalid ${what}: ${name}`);
|
|
22
30
|
};
|
|
23
31
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
32
|
+
* Names are checked by whoever owns them: an implementation, a binding, a CLI, or the MCP
|
|
33
|
+
* tools. `claimantOf` says who claims a name, so a clash between two claimants names both.
|
|
26
34
|
*/
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
seen.
|
|
35
|
+
const assertDistinct = (what, items, nameOf, claimantOf = nameOf) => {
|
|
36
|
+
const seen = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const item of items) {
|
|
38
|
+
const name = nameOf(item);
|
|
39
|
+
const claimant = claimantOf(item);
|
|
40
|
+
const other = seen.get(name);
|
|
41
|
+
if (other !== void 0) {
|
|
42
|
+
const by = other === claimant ? "" : `, claimed by ${other} and ${claimant}`;
|
|
43
|
+
throw new Error(`Duplicate ${what}: ${name}${by}`);
|
|
44
|
+
}
|
|
45
|
+
seen.set(name, claimant);
|
|
38
46
|
}
|
|
39
47
|
};
|
|
48
|
+
/** Refuse an action name `actions` hold twice. */
|
|
49
|
+
const assertOnce = (what, actions) => assertDistinct(what, actions, (action) => action.name);
|
|
50
|
+
/**
|
|
51
|
+
* Refuse a key of options keyed by action name, such as handlers, tools or commands, that no
|
|
52
|
+
* action of `names` has, so a stale option cannot outlive its action.
|
|
53
|
+
*/
|
|
54
|
+
const assertKnown = (what, keys, names) => {
|
|
55
|
+
const unknown = keys.filter((key) => !names.includes(key));
|
|
56
|
+
if (unknown.length > 0) throw new Error(`Unknown ${what}: ${unknown.join(", ")}`);
|
|
57
|
+
};
|
|
58
|
+
/** `ast` with any suspension at its top resolved, as a recursive schema's is. */
|
|
59
|
+
const unsuspended = (ast) => SchemaAST.isSuspend(ast) ? unsuspended(ast.thunk()) : ast;
|
|
60
|
+
/** The members of a union, nested and suspended ones included, or the one type otherwise. */
|
|
61
|
+
const members = (ast) => {
|
|
62
|
+
const resolved = unsuspended(ast);
|
|
63
|
+
return SchemaAST.isUnion(resolved) ? resolved.types.flatMap(members) : [resolved];
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* The values a literal or an enum accepts, or `undefined` for any other type, which has no
|
|
67
|
+
* fixed set.
|
|
68
|
+
*/
|
|
69
|
+
const literalValues = (ast) => SchemaAST.isLiteral(ast) ? [ast.literal] : SchemaAST.isEnum(ast) ? ast.enums.map(([, value]) => value) : [void 0];
|
|
70
|
+
/**
|
|
71
|
+
* The `_tag`s a schema's encoding carries: each member's of a union, every value of a union of
|
|
72
|
+
* literals or of an enum, and a suspended schema's, as a recursive error is written.
|
|
73
|
+
*/
|
|
74
|
+
const tagsOf = (ast) => members(SchemaAST.toEncoded(ast)).flatMap((member) => {
|
|
75
|
+
const tag = SchemaAST.isObjects(member) ? member.propertySignatures.find((property) => property.name === "_tag")?.type : void 0;
|
|
76
|
+
return tag === void 0 ? [] : members(tag).flatMap(literalValues).filter(Predicate.isString);
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* Refuse an error that encodes with a built-in error's `_tag`, the built-in itself included:
|
|
80
|
+
* every endpoint and tool declares the built-in errors already, a client decoding the answer
|
|
81
|
+
* could not tell a look-alike from them, and a look-alike of a refusal would step up. Checked
|
|
82
|
+
* where the action or the binding is made, so every surface and client of it may rely on it.
|
|
83
|
+
*/
|
|
84
|
+
const assertOwnTags = (what, errors) => {
|
|
85
|
+
const tag = errors.flatMap((error) => tagsOf(error.ast)).find((tag) => Object.hasOwn(statuses, tag));
|
|
86
|
+
if (tag !== void 0) throw new Error(`${what}: error _tag "${tag}" is built in, and declared on every surface`);
|
|
87
|
+
};
|
|
40
88
|
//#endregion
|
|
41
|
-
export {
|
|
89
|
+
export { Anyone, assertDistinct, assertKnown, assertName, assertOnce, assertOwnTags, errorList, literalValues, members, projectedErrors, unsuspended };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Anyone } from "./actions.mjs";
|
|
2
|
+
import { Refusal } from "./errors.mjs";
|
|
3
|
+
import { Protected, ServedRequest, Serving } from "./implementation.mjs";
|
|
4
|
+
import { Action_d_exports, Any as Any$1, Errors } from "../Action.mjs";
|
|
5
|
+
import { Context, Effect } from "effect";
|
|
6
|
+
import { HttpRouter, HttpServerResponse } from "effect/http";
|
|
7
|
+
import { HttpApiMiddleware, HttpApiSecurity } from "effect/http-api";
|
|
8
|
+
//#region src/internal/authentication.d.ts
|
|
9
|
+
/** One native security scheme: Bearer unless a descriptor names another. */
|
|
10
|
+
export type Security = HttpApiSecurity.HttpApiSecurity;
|
|
11
|
+
/** What a native scheme decodes: a `Redacted` token or key, or Basic credentials. */
|
|
12
|
+
export type Credential = HttpApiSecurity.HttpApiSecurity.Type<Security>;
|
|
13
|
+
/** The provider of a descriptor's verifier: a type of its own, apart from the actor. */
|
|
14
|
+
export interface Provider<I, Name extends string = string> {
|
|
15
|
+
readonly "~effect-actions/Authentication": I;
|
|
16
|
+
readonly "~effect-actions/Authentication/Name": Name;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The native security middleware a descriptor's protected endpoints carry, which provides
|
|
20
|
+
* the identity: an identifier of its own, which no `Authentication.layer` provides, so only
|
|
21
|
+
* `ActionHttp.layer` satisfies it, from the provider.
|
|
22
|
+
*/
|
|
23
|
+
export interface SecurityMiddleware<I, Name extends string = string> {
|
|
24
|
+
readonly "~effect-actions/Authentication/Security": Name;
|
|
25
|
+
readonly "~effect/http-api/HttpApiMiddleware": {
|
|
26
|
+
readonly provides: I;
|
|
27
|
+
readonly requires: never;
|
|
28
|
+
readonly error: never;
|
|
29
|
+
readonly clientError: never;
|
|
30
|
+
readonly requiredForClient: false;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
type Response = Effect.Effect<HttpServerResponse.HttpServerResponse, unknown, unknown>;
|
|
34
|
+
interface Runtime$1 {
|
|
35
|
+
/** The descriptor the provider was built from, the one a binding naming it must hold. */
|
|
36
|
+
readonly descriptor: unknown;
|
|
37
|
+
/** The native security middleware's implementation, keyed by its scheme. */
|
|
38
|
+
readonly middleware: Readonly<Record<string, (route: Response, options: {
|
|
39
|
+
readonly credential: Credential;
|
|
40
|
+
}) => Response>>;
|
|
41
|
+
/**
|
|
42
|
+
* A protected HTTP route as it leaves the layer's middleware, a step-up refusal it fails with
|
|
43
|
+
* answered with its challenge under Bearer; as it is under another scheme.
|
|
44
|
+
*/
|
|
45
|
+
readonly stepUp: HttpApiMiddleware.HttpApiMiddleware<never, never, never>;
|
|
46
|
+
readonly http: <E, R>(route: Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>, optional: boolean) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R | HttpRouter.Provided>;
|
|
47
|
+
/**
|
|
48
|
+
* A route of the host's own: the request is decoded here, the route gets the identity
|
|
49
|
+
* itself, and a refusal it fails with is answered as an action route's.
|
|
50
|
+
*/
|
|
51
|
+
readonly route: <E, R>(route: Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>) => Effect.Effect<HttpServerResponse.HttpServerResponse, Exclude<E, Refusal>, HttpRouter.Provided>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* What `Authentication.make` declares: its name, the identity it authenticates, its scheme,
|
|
55
|
+
* and what its verifier may fail with besides a refusal. The `~` keys are the library's own
|
|
56
|
+
* wiring, which no host composes.
|
|
57
|
+
*/
|
|
58
|
+
export interface Descriptor<I, A, S extends Security, Name extends string = string, E extends Errors = readonly []> {
|
|
59
|
+
readonly name: Name;
|
|
60
|
+
readonly identity: Context.Key<I, A>;
|
|
61
|
+
readonly security: S;
|
|
62
|
+
/**
|
|
63
|
+
* What its verifier may fail with besides a refusal, as a list: every protected endpoint
|
|
64
|
+
* declares it, and every remote surface sends it with its status.
|
|
65
|
+
*/
|
|
66
|
+
readonly error: E;
|
|
67
|
+
readonly "~provider": Context.Key<Provider<I, Name>, Runtime$1>;
|
|
68
|
+
readonly "~middleware": Context.Key<SecurityMiddleware<I, Name>, unknown>;
|
|
69
|
+
/** An MCP request's verified identity, which a protected tool's call promotes. */
|
|
70
|
+
readonly "~verified": Context.Key<never, unknown>;
|
|
71
|
+
}
|
|
72
|
+
export type Any = Descriptor<unknown, unknown, Security, string, Errors>;
|
|
73
|
+
export type Identity<A extends Any$1> = [A] extends [never] ? never : A["caller"] extends Context.Key<infer I, unknown> ? I : never;
|
|
74
|
+
/**
|
|
75
|
+
* The identity a descriptor for `A` must authenticate: `unknown` for an erased action, whose
|
|
76
|
+
* key is known only at run time, where `assertAuthentication` checks it.
|
|
77
|
+
*/
|
|
78
|
+
type Covered<A extends Any$1> = Exclude<A["caller"], typeof Anyone> extends Context.Key<infer I, unknown> ? I : never;
|
|
79
|
+
export type Required<A extends Any$1> = [Protected<A>] extends [never] ? {
|
|
80
|
+
readonly authentication?: Any;
|
|
81
|
+
} : {
|
|
82
|
+
readonly authentication: Descriptor<Covered<Protected<A>>, unknown, Security, string, Errors>;
|
|
83
|
+
};
|
|
84
|
+
export type ProviderOf<D> = D extends Descriptor<infer I, unknown, Security, infer Name, Errors> ? Provider<I, Name> : never;
|
|
85
|
+
/** All protected actions on a surface use the descriptor's single identity key. */
|
|
86
|
+
export type Matching<A extends Any$1, D> = unknown extends Covered<Protected<A>> ? unknown : [Exclude<Covered<Protected<A>>, D extends Descriptor<infer I, unknown, Security, string, Errors> ? I : never>] extends [never] ? unknown : {
|
|
87
|
+
readonly "Authentication descriptor does not cover every protected action": never;
|
|
88
|
+
};
|
|
89
|
+
/** Keep public requirements separate, without rescanning all handlers for every action. */
|
|
90
|
+
export type RemoteRequest<App, A extends Any$1> = ServedRequest<App, Extract<A, {
|
|
91
|
+
readonly caller: typeof Anyone;
|
|
92
|
+
}>> | Exclude<ServedRequest<App, Protected<A>>, Identity<Protected<A>>>;
|
|
93
|
+
/**
|
|
94
|
+
* What a call of `A` under the descriptor `D` may fail with from its verifier: the errors `D`
|
|
95
|
+
* declares, for a protected action; none for a public one, which nothing verifies.
|
|
96
|
+
*/
|
|
97
|
+
export type VerifierError<A extends Any$1, D> = A["caller"] extends typeof Anyone ? never : D extends {
|
|
98
|
+
readonly error: infer E extends Errors;
|
|
99
|
+
} ? E[number] : never;
|
|
100
|
+
export type ServedProvider<App, A extends Any$1, D> = [Protected<Serving<App, A>>] extends [never] ? never : ProviderOf<D>;
|
|
101
|
+
//#endregion
|
|
102
|
+
export { Runtime$1 as Runtime };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Unauthenticated } from "./errors.mjs";
|
|
2
|
+
import { Anyone } from "./actions.mjs";
|
|
3
|
+
import { Effect, Option } from "effect";
|
|
4
|
+
//#region src/internal/authentication.ts
|
|
5
|
+
const assertAuthentication = (actions, auth) => {
|
|
6
|
+
for (const action of actions) if (action.caller !== Anyone && (auth === void 0 || action.caller.key !== auth.identity.key)) throw new Error(`Protected action '${action.name}' requires its matching authentication descriptor`);
|
|
7
|
+
};
|
|
8
|
+
/** Remote promotion happens at tool entry, before shared dispatch, never in local calls. */
|
|
9
|
+
const promote = (auth, run) => Effect.flatMap(Effect.serviceOption(auth["~verified"]), (actor) => Option.isSome(actor) ? Effect.provideService(run, auth.identity, actor.value) : Effect.fail(new Unauthenticated()));
|
|
10
|
+
//#endregion
|
|
11
|
+
export { assertAuthentication, promote };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "./implementation.mjs";
|
|
2
|
+
import { Action_d_exports, Any } from "../Action.mjs";
|
|
3
|
+
import { Effect, Schema } from "effect";
|
|
4
|
+
//#region src/internal/call.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A call of `A` answering `R`. It may leave its input out when `{}` is a valid encoded input,
|
|
7
|
+
* such as for an action declared without `input` or one whose every field has a decoding
|
|
8
|
+
* default, and then sends the input `{}` decodes to. Every client shares this rule:
|
|
9
|
+
* `ActionHttp.client`, `Testing.mcpClient` and `Action.client`.
|
|
10
|
+
*/
|
|
11
|
+
export type Call<A extends Any, R> = {} extends A["input"]["Encoded"] ? (...input: [] | [input: A["input"]["Type"]]) => R : (input: A["input"]["Type"]) => R;
|
|
12
|
+
//#endregion
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
//#region src/internal/call.ts
|
|
3
|
+
/**
|
|
4
|
+
* The input a method of `action` sends for the arguments it was called with: a given
|
|
5
|
+
* argument as given, and none as the input `{}` decodes to, as a server decodes the `{}` it
|
|
6
|
+
* receives. `Call` allows none only when `{}` is a valid encoded input, which is what this
|
|
7
|
+
* decodes; decoding makes it one of the input's own values, such as an instance of an input
|
|
8
|
+
* class, or fills its defaults.
|
|
9
|
+
*/
|
|
10
|
+
const inputOf = (action, args) => args.length === 0 ? Schema.decodeUnknownEffect(Schema.toCodecJson(action.input))({}) : Effect.succeed(args[0]);
|
|
11
|
+
//#endregion
|
|
12
|
+
export { inputOf };
|
package/dist/internal/cli.d.mts
CHANGED
|
@@ -1,29 +1,43 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import "./errors.mjs";
|
|
2
|
+
import { Action_d_exports, Any } from "../Action.mjs";
|
|
3
|
+
import { Effect, Runtime, Types } from "effect";
|
|
4
|
+
import { CliError, Command } from "effect/cli";
|
|
4
5
|
//#region src/internal/cli.d.ts
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The fields of `A`'s input that may be positional: the named top-level fields of a struct
|
|
8
|
+
* or class input, as encoded. None for any other input, a record or a union included.
|
|
9
|
+
*/
|
|
10
|
+
export type Field<A extends Any> = A["input"]["Encoded"] extends (infer E) ? true extends Types.IsUnion<E> ? never : E extends ReadonlyArray<unknown> ? never : E extends object ? string extends keyof E ? never : Extract<keyof E, string> : never : never;
|
|
11
|
+
/** How one command is named, takes its input and prints its result. */
|
|
12
|
+
export interface Options<A extends Any> {
|
|
13
|
+
/** Override the command name. The action name, in kebab case, is used by default. */
|
|
8
14
|
readonly name?: string;
|
|
9
15
|
/** Human output. Adds a `--json` flag to the command that selects JSON instead. */
|
|
10
|
-
readonly render?: (output:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
readonly
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
readonly
|
|
21
|
-
/** Maps native parsed parameters to the action's canonical JSON input. */
|
|
22
|
-
readonly input: (parsed: Command.Command.Config.InferValue<Parameters>) => Schema.Json;
|
|
16
|
+
readonly render?: (output: A["success"]["Type"]) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Input fields taken as positional arguments instead of flags, in this order. An
|
|
19
|
+
* optional field is an optional argument, so it follows every required one.
|
|
20
|
+
*/
|
|
21
|
+
readonly positional?: ReadonlyArray<Field<A>>;
|
|
22
|
+
/**
|
|
23
|
+
* A short name per input field's flag, such as `{ limit: "n" }` for `-n`, given as
|
|
24
|
+
* `Param.withAlias` takes it.
|
|
25
|
+
*/
|
|
26
|
+
readonly aliases?: { readonly [K in Field<A>]?: string; };
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
29
|
+
* What a command fails with when its action fails: Effect CLI's own error for a handler's
|
|
30
|
+
* failure, which `Command.run` prints on stderr and marks reported, so `runMain` does not
|
|
31
|
+
* print it again. Its `cause` is the action's failure. The process exits with the cause's
|
|
32
|
+
* `Runtime.errorExitCode`, 1 by default.
|
|
27
33
|
*/
|
|
28
|
-
export
|
|
34
|
+
export declare class UserError<out E = unknown> extends CliError.UserError {
|
|
35
|
+
readonly cause: E;
|
|
36
|
+
/**
|
|
37
|
+
* The action's failure, as Effect names an error's nested one, so the native
|
|
38
|
+
* `Effect.catchReason("UserError", "UserNotFound", f)` and `catchReasons` match it by tag.
|
|
39
|
+
*/
|
|
40
|
+
get reason(): E;
|
|
41
|
+
get [Runtime.errorExitCode](): number;
|
|
42
|
+
}
|
|
29
43
|
//#endregion
|