@gjermundgaraba/effect-actions 0.20.0 → 0.21.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 +19 -41
- package/dist/Action.d.mts +140 -46
- package/dist/Action.mjs +87 -42
- package/dist/ActionCli.d.mts +69 -33
- package/dist/ActionCli.mjs +51 -32
- package/dist/ActionHttp.d.mts +69 -35
- package/dist/ActionHttp.mjs +75 -23
- package/dist/ActionMcp.d.mts +56 -22
- package/dist/ActionMcp.mjs +94 -69
- package/dist/ActionRpc.d.mts +222 -55
- package/dist/ActionRpc.mjs +299 -46
- package/dist/ActionToolkit.d.mts +22 -5
- package/dist/ActionToolkit.mjs +36 -12
- package/dist/Authentication.d.mts +42 -10
- package/dist/Authentication.mjs +37 -30
- package/dist/Event.d.mts +370 -0
- package/dist/Event.mjs +103 -0
- package/dist/Testing.mjs +1 -1
- package/dist/Webhook.d.mts +146 -0
- package/dist/Webhook.mjs +72 -0
- package/dist/authentication/provider.d.mts +35 -10
- package/dist/authentication/provider.mjs +1 -1
- package/dist/authentication/refusal.mjs +2 -2
- package/dist/cli/commands.d.mts +9 -5
- package/dist/cli/commands.mjs +84 -33
- package/dist/contract/ast.mjs +4 -1
- package/dist/contract/authorizer.d.mts +8 -0
- package/dist/contract/authorizer.mjs +20 -0
- package/dist/contract/call.d.mts +9 -6
- package/dist/contract/call.mjs +48 -6
- package/dist/contract/callback.d.mts +7 -0
- package/dist/contract/callback.mjs +27 -0
- package/dist/contract/caller.d.mts +13 -2
- package/dist/contract/caller.mjs +22 -3
- package/dist/contract/canonical.mjs +10 -0
- package/dist/contract/codec.d.mts +9 -0
- package/dist/contract/codec.mjs +9 -0
- package/dist/contract/codes.mjs +81 -0
- package/dist/contract/declared.mjs +25 -9
- package/dist/contract/digest.mjs +6 -0
- package/dist/contract/errors.d.mts +9 -3
- package/dist/contract/errors.mjs +1 -1
- package/dist/contract/feed.d.mts +127 -0
- package/dist/contract/feed.mjs +249 -0
- package/dist/contract/hub.d.mts +94 -0
- package/dist/contract/hub.mjs +133 -0
- package/dist/contract/idempotency.d.mts +47 -0
- package/dist/contract/idempotency.mjs +99 -0
- package/dist/contract/implementation.d.mts +73 -47
- package/dist/contract/implementation.mjs +92 -58
- package/dist/contract/input-check.d.mts +15 -0
- package/dist/contract/input-check.mjs +16 -0
- package/dist/contract/lease.mjs +28 -0
- package/dist/contract/listed.d.mts +4 -0
- package/dist/contract/listed.mjs +22 -0
- package/dist/contract/log.d.mts +41 -0
- package/dist/contract/log.mjs +144 -0
- package/dist/contract/names.mjs +14 -4
- package/dist/contract/occurrence.d.mts +29 -0
- package/dist/contract/occurrence.mjs +45 -0
- package/dist/contract/pages.d.mts +13 -0
- package/dist/contract/pages.mjs +39 -0
- package/dist/contract/progress.d.mts +26 -0
- package/dist/contract/progress.mjs +72 -0
- package/dist/contract/push.d.mts +20 -0
- package/dist/contract/push.mjs +96 -0
- package/dist/contract/receiving.d.mts +29 -0
- package/dist/contract/receiving.mjs +42 -0
- package/dist/contract/recent.mjs +12 -0
- package/dist/contract/reported.mjs +16 -0
- package/dist/contract/serving.mjs +63 -0
- package/dist/contract/status.mjs +14 -5
- package/dist/contract/stream.d.mts +34 -0
- package/dist/contract/stream.mjs +31 -0
- package/dist/contract/subscribe.d.mts +45 -0
- package/dist/contract/subscribe.mjs +50 -0
- package/dist/contract/transient.mjs +28 -0
- package/dist/contract/watch.d.mts +18 -0
- package/dist/contract/watch.mjs +105 -0
- package/dist/contract/webhooks.d.mts +42 -0
- package/dist/contract/webhooks.mjs +53 -0
- package/dist/http/client.d.mts +37 -12
- package/dist/http/client.mjs +51 -12
- package/dist/http/events.d.mts +56 -0
- package/dist/http/events.mjs +102 -0
- package/dist/http/placed.d.mts +36 -0
- package/dist/http/placed.mjs +71 -0
- package/dist/http/schema-errors.mjs +1 -1
- package/dist/http/streamed.d.mts +21 -0
- package/dist/http/streamed.mjs +94 -0
- package/dist/http/watch.mjs +50 -0
- package/dist/mcp/bridge.mjs +283 -0
- package/dist/mcp/client.d.mts +30 -97
- package/dist/mcp/client.mjs +59 -132
- package/dist/mcp/events.mjs +142 -0
- package/dist/mcp/progress.mjs +34 -0
- package/dist/mcp/protocol.mjs +39 -13
- package/dist/mcp/reply.d.mts +79 -0
- package/dist/mcp/reply.mjs +99 -0
- package/dist/mcp/request.d.mts +40 -0
- package/dist/mcp/request.mjs +51 -0
- package/dist/mcp/watch.mjs +115 -0
- package/dist/rpc/watch.mjs +39 -0
- package/dist/stdio/console.mjs +13 -5
- package/dist/testing/memory.d.mts +1 -1
- package/dist/testing/memory.mjs +3 -3
- package/dist/webhook/address.mjs +47 -0
- package/dist/webhook/attempt.mjs +65 -0
- package/dist/webhook/receiver.mjs +67 -0
- package/dist/webhook/signing.mjs +28 -0
- package/dist/webhook/stored.mjs +57 -0
- package/dist/webhook/subscriptions.mjs +342 -0
- package/docs/Action-failures.md +2 -2
- package/docs/Action-streams.md +115 -0
- package/docs/Action.md +58 -124
- package/docs/ActionCli-failures.md +1 -2
- package/docs/ActionCli.md +83 -88
- package/docs/ActionHttp-failures.md +3 -3
- package/docs/ActionHttp.md +90 -70
- package/docs/ActionMcp-failures.md +2 -1
- package/docs/ActionMcp.md +120 -92
- package/docs/ActionRpc-events.md +126 -0
- package/docs/ActionRpc-failures.md +1 -2
- package/docs/ActionRpc.md +108 -79
- package/docs/ActionToolkit-failures.md +1 -2
- package/docs/ActionToolkit.md +33 -90
- package/docs/Authentication-advanced.md +52 -60
- package/docs/Authentication-failures.md +5 -4
- package/docs/Authentication.md +23 -58
- package/docs/Event-failures.md +15 -0
- package/docs/Event.md +281 -0
- package/docs/README.md +51 -82
- package/docs/Testing-failures.md +1 -5
- package/docs/Testing.md +126 -86
- package/docs/Webhook-failures.md +8 -0
- package/docs/Webhook.md +319 -0
- package/docs/setup-failures.md +2 -3
- package/docs/setup.md +32 -52
- package/examples/README.md +12 -37
- package/examples/assistant/README.md +21 -26
- package/examples/assistant/src/agent.ts +66 -24
- package/examples/assistant/src/cli.ts +7 -4
- package/examples/assistant/src/commands.ts +9 -4
- package/examples/assistant/src/identity/Identity.ts +5 -4
- package/examples/assistant/src/identity/authorize.ts +13 -5
- package/examples/assistant/src/identity/handlers.ts +3 -2
- package/examples/assistant/src/identity/permissions.ts +9 -3
- package/examples/assistant/src/platform/tasks-file.ts +15 -11
- package/examples/assistant/src/scripted.ts +7 -4
- package/examples/assistant/src/tasks/Tasks.ts +20 -6
- package/examples/assistant/src/tasks/handlers.ts +9 -5
- package/examples/assistant/src/tasks/store.ts +38 -11
- package/examples/assistant/src/toolkit.ts +11 -7
- package/examples/assistant/test/commands.test.ts +53 -27
- package/examples/assistant/test/identity/identity.test.ts +27 -11
- package/examples/assistant/test/platform/tasks-file.test.ts +39 -23
- package/examples/assistant/test/tasks/tasks.test.ts +62 -34
- package/examples/assistant/test/toolkit.test.ts +104 -75
- package/examples/build-events/README.md +83 -0
- package/examples/build-events/src/Api.ts +27 -0
- package/examples/build-events/src/Rpc.ts +26 -0
- package/examples/build-events/src/builds/Builds.ts +108 -0
- package/examples/build-events/src/builds/events.ts +54 -0
- package/examples/build-events/src/builds/handlers.ts +58 -0
- package/examples/build-events/src/builds/log.ts +30 -0
- package/examples/build-events/src/builds/status.ts +28 -0
- package/examples/build-events/src/builds/store.ts +130 -0
- package/examples/build-events/src/cli.ts +18 -0
- package/examples/build-events/src/commands.ts +32 -0
- package/examples/build-events/src/deploys/Deploys.ts +44 -0
- package/examples/build-events/src/deploys/approvers.ts +55 -0
- package/examples/build-events/src/deploys/events.ts +32 -0
- package/examples/build-events/src/deploys/handlers.ts +38 -0
- package/examples/build-events/src/identity/Identity.ts +29 -0
- package/examples/build-events/src/identity/members.ts +7 -0
- package/examples/build-events/src/identity/verifier.ts +22 -0
- package/examples/build-events/src/notifier/Notices.ts +26 -0
- package/examples/build-events/src/notifier/routes.ts +71 -0
- package/examples/build-events/src/notifier.ts +48 -0
- package/examples/build-events/src/routes.ts +71 -0
- package/examples/build-events/src/server.ts +62 -0
- package/examples/build-events/test/builds/events.test.ts +68 -0
- package/examples/build-events/test/builds/log.test.ts +71 -0
- package/examples/build-events/test/builds/progress.test.ts +57 -0
- package/examples/build-events/test/builds/status.test.ts +42 -0
- package/examples/build-events/test/commands.test.ts +103 -0
- package/examples/build-events/test/deploys/events.test.ts +52 -0
- package/examples/build-events/test/http.test.ts +95 -0
- package/examples/build-events/test/notifier.test.ts +80 -0
- package/examples/build-events/test/routes.test.ts +96 -0
- package/examples/build-events/test/rpc.test.ts +84 -0
- package/examples/build-events/test/support/as.ts +8 -0
- package/examples/build-events/test/support/memory.ts +43 -0
- package/examples/build-events/test/webhooks.test.ts +143 -0
- package/examples/kitchen-sink/README.md +178 -166
- package/examples/kitchen-sink/src/Actions.ts +11 -3
- package/examples/kitchen-sink/src/Api.ts +12 -6
- package/examples/kitchen-sink/src/Rpc.ts +7 -5
- package/examples/kitchen-sink/src/admin/Admin.ts +21 -10
- package/examples/kitchen-sink/src/admin/authenticate.ts +8 -4
- package/examples/kitchen-sink/src/admin/authorize.ts +13 -5
- package/examples/kitchen-sink/src/admin/grants.ts +5 -3
- package/examples/kitchen-sink/src/admin/handlers.ts +9 -5
- package/examples/kitchen-sink/src/agent.ts +14 -6
- package/examples/kitchen-sink/src/assistant/Assistant.ts +21 -15
- package/examples/kitchen-sink/src/assistant/conversations.ts +14 -9
- package/examples/kitchen-sink/src/assistant/demo-model.ts +2 -2
- package/examples/kitchen-sink/src/assistant/handlers.ts +42 -22
- package/examples/kitchen-sink/src/attachments/Attachments.ts +6 -6
- package/examples/kitchen-sink/src/attachments/download.ts +7 -6
- package/examples/kitchen-sink/src/attachments/handlers.ts +9 -4
- package/examples/kitchen-sink/src/attachments/store.ts +25 -18
- package/examples/kitchen-sink/src/browser.ts +10 -4
- package/examples/kitchen-sink/src/cli.ts +28 -12
- package/examples/kitchen-sink/src/config.ts +1 -1
- package/examples/kitchen-sink/src/identity/Identity.ts +24 -10
- package/examples/kitchen-sink/src/identity/authenticate.ts +19 -11
- package/examples/kitchen-sink/src/identity/authorize.ts +14 -4
- package/examples/kitchen-sink/src/identity/directory.ts +16 -7
- package/examples/kitchen-sink/src/identity/handlers.ts +1 -0
- package/examples/kitchen-sink/src/identity/members.ts +7 -2
- package/examples/kitchen-sink/src/identity/quota.ts +23 -20
- package/examples/kitchen-sink/src/issues/Issues.ts +91 -15
- package/examples/kitchen-sink/src/issues/assistant.ts +4 -2
- package/examples/kitchen-sink/src/issues/events.ts +46 -0
- package/examples/kitchen-sink/src/issues/export.ts +35 -0
- package/examples/kitchen-sink/src/issues/guide.ts +4 -3
- package/examples/kitchen-sink/src/issues/handlers.ts +20 -7
- package/examples/kitchen-sink/src/issues/store.ts +156 -67
- package/examples/kitchen-sink/src/issues/write-limit.ts +44 -18
- package/examples/kitchen-sink/src/memory.ts +11 -2
- package/examples/kitchen-sink/src/ops.ts +10 -5
- package/examples/kitchen-sink/src/request-policy.ts +21 -9
- package/examples/kitchen-sink/src/routes.ts +67 -27
- package/examples/kitchen-sink/src/sdk.ts +22 -12
- package/examples/kitchen-sink/src/seed.ts +15 -9
- package/examples/kitchen-sink/src/server.ts +24 -10
- package/examples/kitchen-sink/src/stdio.ts +9 -3
- package/examples/kitchen-sink/src/workspaces/Workspaces.ts +10 -7
- package/examples/kitchen-sink/src/workspaces/handlers.ts +3 -2
- package/examples/kitchen-sink/src/workspaces/registry.ts +16 -10
- package/examples/kitchen-sink/src/workspaces/tenant.ts +12 -5
- package/examples/kitchen-sink/test/admin/admin.test.ts +38 -23
- package/examples/kitchen-sink/test/assistant/assistant.test.ts +471 -299
- package/examples/kitchen-sink/test/attachments/attachments.test.ts +135 -94
- package/examples/kitchen-sink/test/identity/identity.test.ts +110 -82
- package/examples/kitchen-sink/test/identity/session.test.ts +65 -43
- package/examples/kitchen-sink/test/issues/assistant.test.ts +50 -31
- package/examples/kitchen-sink/test/issues/events.test.ts +273 -0
- package/examples/kitchen-sink/test/issues/export.test.ts +120 -0
- package/examples/kitchen-sink/test/issues/issues.test.ts +105 -75
- package/examples/kitchen-sink/test/request-policy.test.ts +108 -47
- package/examples/kitchen-sink/test/routes.test.ts +102 -57
- package/examples/kitchen-sink/test/support/as.ts +9 -0
- package/examples/kitchen-sink/test/support/scripted.ts +6 -5
- package/examples/kitchen-sink/test/support/services.ts +17 -0
- package/examples/live-rpc/README.md +33 -35
- package/examples/live-rpc/src/Rpc.ts +9 -5
- package/examples/live-rpc/src/browser.ts +39 -11
- package/examples/live-rpc/src/chat/Chat.ts +30 -16
- package/examples/live-rpc/src/chat/authorize.ts +2 -1
- package/examples/live-rpc/src/chat/events.ts +74 -0
- package/examples/live-rpc/src/chat/handlers.ts +13 -5
- package/examples/live-rpc/src/chat/room.ts +9 -5
- package/examples/live-rpc/src/chat/store.ts +51 -14
- package/examples/live-rpc/src/client.ts +22 -5
- package/examples/live-rpc/src/identity/Identity.ts +3 -3
- package/examples/live-rpc/src/identity/verifier.ts +8 -4
- package/examples/live-rpc/src/page.ts +58 -12
- package/examples/live-rpc/src/routes.ts +14 -10
- package/examples/live-rpc/src/server.ts +15 -5
- package/examples/live-rpc/test/browser.test.ts +127 -50
- package/examples/live-rpc/test/chat/chat.test.ts +215 -23
- package/examples/live-rpc/test/routes.test.ts +130 -46
- package/examples/live-rpc/test/server.test.ts +94 -33
- package/examples/mcp-tools/README.md +24 -38
- package/examples/mcp-tools/src/platform/fake-farm.ts +63 -24
- package/examples/mcp-tools/src/platform/print-farm.ts +59 -29
- package/examples/mcp-tools/src/printers/Printers.ts +18 -7
- package/examples/mcp-tools/src/printers/farm.ts +15 -5
- package/examples/mcp-tools/src/printers/handlers.ts +12 -6
- package/examples/mcp-tools/src/routes.ts +8 -6
- package/examples/mcp-tools/src/server.ts +12 -5
- package/examples/mcp-tools/src/stdio.ts +3 -2
- package/examples/mcp-tools/test/printers/printers.test.ts +132 -89
- package/examples/mcp-tools/test/routes.test.ts +140 -81
- package/examples/notes-service/README.md +30 -45
- package/examples/notes-service/src/Api.ts +1 -0
- package/examples/notes-service/src/browser.ts +15 -14
- package/examples/notes-service/src/cli.ts +8 -3
- package/examples/notes-service/src/commands.ts +46 -20
- package/examples/notes-service/src/identity/Identity.ts +2 -2
- package/examples/notes-service/src/identity/authorize.ts +6 -2
- package/examples/notes-service/src/identity/handlers.ts +3 -2
- package/examples/notes-service/src/identity/users.ts +2 -1
- package/examples/notes-service/src/identity/verifier.ts +12 -6
- package/examples/notes-service/src/notes/Notes.ts +46 -22
- package/examples/notes-service/src/notes/handlers.ts +18 -9
- package/examples/notes-service/src/notes/store.ts +70 -43
- package/examples/notes-service/src/routes.ts +7 -5
- package/examples/notes-service/src/server.ts +12 -5
- package/examples/notes-service/src/status/Status.ts +1 -1
- package/examples/notes-service/src/status/handlers.ts +5 -3
- package/examples/notes-service/src/stdio.ts +7 -3
- package/examples/notes-service/src/worker.ts +6 -3
- package/examples/notes-service/test/commands.test.ts +164 -91
- package/examples/notes-service/test/notes/as.ts +3 -1
- package/examples/notes-service/test/notes/idempotency.test.ts +97 -67
- package/examples/notes-service/test/notes/in-process.test.ts +26 -15
- package/examples/notes-service/test/notes/notes.test.ts +49 -31
- package/examples/notes-service/test/routes.test.ts +106 -71
- package/examples/notes-service/test/worker.test.ts +21 -13
- package/examples/quickstart/README.md +2 -6
- package/examples/quickstart/src/Api.ts +1 -0
- package/examples/quickstart/src/client.ts +6 -3
- package/examples/quickstart/src/greeting/Greeting.ts +1 -1
- package/examples/quickstart/src/greeting/handlers.ts +5 -2
- package/examples/quickstart/src/routes.ts +3 -2
- package/examples/quickstart/src/server.ts +6 -2
- package/examples/quickstart/test/routes.test.ts +3 -2
- package/package.json +10 -4
package/dist/Action.mjs
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
|
-
import { Anyone } from "./contract/caller.mjs";
|
|
1
|
+
import { Anyone, assertCaller } from "./contract/caller.mjs";
|
|
2
2
|
import { BuiltIn, Forbidden, InvalidInput, Refusal, Unauthenticated } from "./contract/errors.mjs";
|
|
3
|
-
import { assertKnown, assertName, assertOnce, heldOnce } from "./contract/names.mjs";
|
|
4
3
|
import { assertDistinctTags, errorList, projectedErrors } from "./contract/declared.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { InputCheck } from "./contract/input-check.mjs";
|
|
5
|
+
import { assertKnown, assertName, assertOnce, heldOnce } from "./contract/names.mjs";
|
|
6
|
+
import { progressSink } from "./contract/progress.mjs";
|
|
7
|
+
import { codecOf } from "./contract/codec.mjs";
|
|
8
|
+
import { Stream, elementOf } from "./contract/stream.mjs";
|
|
9
|
+
import { Implementation, acquire, builtWith, elementsOf, memoized, provideBuilders, toList, uniqueKey } from "./contract/implementation.mjs";
|
|
10
|
+
import { authorizerOf } from "./contract/authorizer.mjs";
|
|
11
|
+
import { inputOf, jsonRoundTrip, onProgressOf, watchInput } from "./contract/call.mjs";
|
|
12
|
+
import { acquireFeeds, eventsOf, feedBuilders } from "./contract/feed.mjs";
|
|
13
|
+
import { NotServed, SubscriptionError } from "./Event.mjs";
|
|
14
|
+
import { polledOn, queuedPush, readOn } from "./contract/push.mjs";
|
|
15
|
+
import { selectFrom } from "./contract/serving.mjs";
|
|
16
|
+
import { watching } from "./contract/watch.mjs";
|
|
7
17
|
import { Image } from "./contract/media.mjs";
|
|
8
|
-
import {
|
|
18
|
+
import { Idempotency, keyed, layerIdempotency } from "./contract/idempotency.mjs";
|
|
19
|
+
import { Array as Array$1, Cause, Effect, Function, Layer, Predicate, Schema, Stream as Stream$1 } from "effect";
|
|
9
20
|
// src/contract/Action.ts
|
|
10
|
-
const NoInput = Schema.Record(Schema.String, Schema.Never);
|
|
11
|
-
const codecOf = (schema) => Schema.isSchema(schema) ? schema : Reflect.ownKeys(schema).length === 0 ? NoInput : Schema.Struct(schema);
|
|
12
21
|
function make(name, options) {
|
|
13
22
|
assertName(name);
|
|
14
23
|
const { caller, readOnly } = options;
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
if (readOnly !== true && readOnly !== false) throw new Error(`Invalid readOnly: ${String(readOnly)} (set true or false)`);
|
|
24
|
+
assertCaller(caller);
|
|
25
|
+
if (!Predicate.isBoolean(readOnly)) throw new Error(`Invalid readOnly: ${String(readOnly)} (set true or false)`);
|
|
18
26
|
const action = {
|
|
19
27
|
caller,
|
|
20
28
|
name,
|
|
@@ -22,72 +30,109 @@ function make(name, options) {
|
|
|
22
30
|
input: codecOf(options.input ?? {}),
|
|
23
31
|
success: options.success === void 0 ? Schema.Void : codecOf(options.success),
|
|
24
32
|
error: errorList(options.error),
|
|
33
|
+
progress: options.progress === void 0 ? void 0 : codecOf(options.progress),
|
|
25
34
|
readOnly,
|
|
26
|
-
mcp: { ...options.mcp }
|
|
35
|
+
mcp: { ...options.mcp },
|
|
36
|
+
idempotencyKey: options.idempotencyKey
|
|
27
37
|
};
|
|
38
|
+
if (action.idempotencyKey !== void 0 && elementOf(action.success) !== void 0) throw new Error(`Action "${name}" answers a stream, so it takes no idempotencyKey (a kept answer is whole: answer a value, or remove the key)`);
|
|
28
39
|
assertDistinctTags(`Action "${action.name}"`, projectedErrors(action));
|
|
29
40
|
return action;
|
|
30
41
|
}
|
|
42
|
+
const appendErrors = (action, more) => {
|
|
43
|
+
const error = [...action.error, ...more];
|
|
44
|
+
const appended = {
|
|
45
|
+
...action,
|
|
46
|
+
error
|
|
47
|
+
};
|
|
48
|
+
assertDistinctTags(`Action "${action.name}"`, projectedErrors(appended));
|
|
49
|
+
return appended;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* `action` with the errors `more` declared after its own, as if `make` had been given them in
|
|
53
|
+
* `error`: its handler may fail with them, and every surface answers and every client decodes
|
|
54
|
+
* them. For errors a module adds to the actions it serves, such as a proxy's or a store's,
|
|
55
|
+
* whose contracts are written elsewhere: `pipe(action, Action.withErrors([Unavailable]))`, or
|
|
56
|
+
* `Action.withErrors(action, [Unavailable])`.
|
|
57
|
+
*
|
|
58
|
+
* **Gotchas**
|
|
59
|
+
*
|
|
60
|
+
* - It returns a new contract with the same name: implement, bind and list the value it returns, since surfaces match a contract by identity; the original and the copy in one surface are one name held twice.
|
|
61
|
+
* - A `_tag` that `more` shares with the action's own errors or a built-in one throws as `make` throws for one `error` list: reuse the one schema value, which is declared once.
|
|
62
|
+
*/
|
|
63
|
+
const withErrors = Function.dual(2, appendErrors);
|
|
31
64
|
function byName(actions) {
|
|
32
65
|
assertOnce("action", actions);
|
|
33
66
|
return Object.fromEntries(actions.map((action) => [action.name, action]));
|
|
34
67
|
}
|
|
35
68
|
/** Every authenticated caller may call a protected action. */
|
|
36
69
|
const allowAll = () => Effect.void;
|
|
37
|
-
const assertAuthorizer = (before) => {
|
|
38
|
-
if (!Predicate.isFunction(before)) throw new Error("Missing authorize: not a function (pass your rule, Action.allowAll, or an Effect returning one)");
|
|
39
|
-
return before;
|
|
40
|
-
};
|
|
41
|
-
const assertAuthorization = (authorize) => {
|
|
42
|
-
if (authorize === void 0) throw new Error("Protected actions require authorize, or Action.allowAll");
|
|
43
|
-
return authorize;
|
|
44
|
-
};
|
|
45
|
-
const assertNoAuthorization = (authorize) => {
|
|
46
|
-
if (authorize !== void 0) throw new Error("A public-only target takes no authorize (remove it, or list a protected action)");
|
|
47
|
-
return allowAll;
|
|
48
|
-
};
|
|
49
|
-
const authorizerOf = (before) => memoized(Effect.isEffect(before) ? Effect.map(before, assertAuthorizer) : Effect.succeed(assertAuthorizer(before)));
|
|
50
70
|
function implement(actions, handlers, options) {
|
|
51
71
|
const listed = Array$1.ensure(actions);
|
|
52
72
|
const names = listed.map((action) => action.name);
|
|
53
73
|
assertOnce("action", listed);
|
|
54
|
-
const authorizer = authorizerOf(
|
|
74
|
+
const authorizer = authorizerOf({
|
|
75
|
+
singular: "action",
|
|
76
|
+
module: "Action"
|
|
77
|
+
}, listed.some((action) => action.caller !== Anyone), options?.authorize);
|
|
55
78
|
const pairWithActions = (built) => {
|
|
56
|
-
const
|
|
57
|
-
assertKnown("handlers", Object.keys(
|
|
79
|
+
const handlersByName = Predicate.isFunction(built) ? Object.fromEntries(Array.isArray(actions) ? [] : listed.map(({ name }) => [name, built])) : built;
|
|
80
|
+
assertKnown("handlers", Object.keys(handlersByName), names, "remove it, or list its action");
|
|
58
81
|
const bound = listed.flatMap((action) => {
|
|
59
|
-
const handle = Object.hasOwn(
|
|
82
|
+
const handle = Object.hasOwn(handlersByName, action.name) ? handlersByName[action.name] : void 0;
|
|
60
83
|
return Predicate.isFunction(handle) ? [[action, handle]] : [];
|
|
61
84
|
});
|
|
62
85
|
const missing = names.filter((name) => !bound.some(([action]) => action.name === name));
|
|
63
86
|
if (missing.length > 0) throw new Error(`Missing handlers: ${missing.join(", ")} (return the handler for one action, or a record with an own function per listed action)`);
|
|
64
87
|
return bound;
|
|
65
88
|
};
|
|
66
|
-
const
|
|
89
|
+
const paired = Effect.isEffect(handlers) ? Effect.map(handlers, pairWithActions) : Effect.succeed(pairWithActions(handlers));
|
|
90
|
+
const recorded = listed.some((action) => action.idempotencyKey !== void 0) ? Effect.flatMap(paired, (written) => Effect.map(Effect.service(Idempotency), (store) => written.map(([action, handle]) => [action, keyed(action, store, handle)]))) : paired;
|
|
67
91
|
return new Implementation(listed, memoized(recorded), authorizer);
|
|
68
92
|
}
|
|
69
93
|
function layer(implementations) {
|
|
70
|
-
return
|
|
94
|
+
return Layer.empty.pipe(provideBuilders([...toList(implementations).map((app) => Implementation.layerOf(app)), ...feedBuilders(eventsOf(implementations))]));
|
|
71
95
|
}
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const decode = Schema.decodeUnknownEffect(codec, { errors: "all" });
|
|
76
|
-
return (value) => Effect.flatMap(encode(value), (encoded) => decode(encoded));
|
|
96
|
+
const received = (schema) => {
|
|
97
|
+
const sent = jsonRoundTrip(schema);
|
|
98
|
+
return (value) => Effect.map(sent(value), (roundTripped) => roundTripped.value);
|
|
77
99
|
};
|
|
78
100
|
const mapFailuresInPlace = (cause, failure) => Effect.map(Effect.forEach(cause.reasons, (reason) => Cause.isFailReason(reason) ? Effect.match(failure(reason.error), {
|
|
79
101
|
onFailure: (refused) => Cause.combine(Cause.die(refused), Cause.die(reason.error)),
|
|
80
102
|
onSuccess: (decoded) => Cause.fail(Predicate.isError(reason.error) && Predicate.isError(decoded) ? Object.assign(decoded, { stack: reason.error.stack }) : decoded)
|
|
81
103
|
}).pipe(Effect.map((mapped) => Cause.annotate(mapped, Cause.reasonAnnotations(reason)))) : Effect.succeed(Cause.fromReasons([reason]))), (causes) => Cause.fromReasons(causes.flatMap(({ reasons }) => reasons)));
|
|
82
104
|
const methodOf = (action, run) => {
|
|
83
|
-
const input =
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
105
|
+
const input = received(action.input);
|
|
106
|
+
const element = elementOf(action.success);
|
|
107
|
+
const success = received(element ?? action.success);
|
|
108
|
+
const failure = received(Schema.Union(projectedErrors(action)));
|
|
109
|
+
const declared = (cause) => Effect.flatMap(mapFailuresInPlace(cause, failure), Effect.failCause);
|
|
110
|
+
const called = (args) => inputOf(action, args).pipe(Effect.flatMap(input), Effect.mapError(InvalidInput.fromSchemaError), Effect.map((value) => run(value, progressSink(onProgressOf(args)))));
|
|
111
|
+
if (element !== void 0) return (...args) => Stream$1.unwrap(Effect.map(called(args), elementsOf)).pipe(Stream$1.catchCause((cause) => Stream$1.fromEffect(declared(cause))), Stream$1.mapEffect((value) => Effect.orDie(success(value))));
|
|
112
|
+
return (...args) => called(args).pipe(Effect.flatMap((answer) => Effect.catchCause(answer, declared)), Effect.flatMap((value) => Effect.orDie(success(value))));
|
|
113
|
+
};
|
|
114
|
+
const watcherOf = (feeds) => (event, input, options) => {
|
|
115
|
+
const feed = feeds.find((held) => held.event === event);
|
|
116
|
+
if (feed === void 0) return Stream$1.fail(SubscriptionError.of(event.name, new NotServed()));
|
|
117
|
+
return Stream$1.unwrap(Effect.map(watchInput(event, input), ({ value }) => {
|
|
118
|
+
const subscriber = uniqueKey();
|
|
119
|
+
return watching({
|
|
120
|
+
name: event.name,
|
|
121
|
+
poll: polledOn("In process", event.name)((cursor) => feed.poll(value, cursor, { subscriber })),
|
|
122
|
+
stream: (cursor) => Stream$1.unwrap(Effect.flatMap(feed.stream(value), (open) => Effect.map(queuedPush(open, cursor, Effect.succeed, {
|
|
123
|
+
inOrder: true,
|
|
124
|
+
capacity: 1
|
|
125
|
+
}), (queue) => Stream$1.fromQueue(queue)))).pipe(readOn("In process", event.name))
|
|
126
|
+
}, options).pipe(Stream$1.ensuring(feed.release?.(value, subscriber) ?? Effect.void));
|
|
127
|
+
}));
|
|
87
128
|
};
|
|
88
129
|
function client(served, options) {
|
|
89
|
-
const apps =
|
|
130
|
+
const { apps, events } = selectFrom(served, options?.actions);
|
|
90
131
|
assertOnce("action", apps.flatMap((app) => app.actions), heldOnce);
|
|
91
|
-
|
|
132
|
+
assertOnce("event", events.flatMap((app) => app.events), heldOnce);
|
|
133
|
+
return Effect.map(builtWith([...apps.map((app) => Implementation.layerOf(app)), ...feedBuilders(events)], Effect.all([acquire(apps, void 0), acquireFeeds(events)])), ([bound, feeds]) => ({
|
|
134
|
+
...Object.fromEntries(bound.map(([action, run]) => [action.name, methodOf(action, run)])),
|
|
135
|
+
...feeds.length === 0 ? {} : { watch: watcherOf(feeds) }
|
|
136
|
+
}));
|
|
92
137
|
}
|
|
93
|
-
export { Anyone, BuiltIn, Forbidden, Image, InvalidInput, Refusal, Unauthenticated, allowAll, byName, client, implement, layer, make };
|
|
138
|
+
export { Anyone, BuiltIn, Forbidden, Idempotency, Image, InputCheck, InvalidInput, Refusal, Stream, Unauthenticated, allowAll, byName, client, implement, layer, layerIdempotency, make, withErrors };
|
package/dist/ActionCli.d.mts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { BuiltIn } from "./contract/errors.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { Any } from "./
|
|
2
|
+
import { EventBuildError, EventBuildServices, EventOf, EventRequestOf, ServingEvents } from "./contract/feed.mjs";
|
|
3
|
+
import { Any, SubscriptionError } from "./Event.mjs";
|
|
4
|
+
import { ActionOf, BuildError, BuildServices, Holding, Known, Member, RequestOf, Selected, SelectedEvents, SelectedEventsOf, SelectedOf, Selection, Served, ServedActions } from "./contract/implementation.mjs";
|
|
5
|
+
import { Receiving } from "./contract/receiving.mjs";
|
|
6
|
+
import { WatchArgs } from "./contract/watch.mjs";
|
|
7
|
+
import { Any as Any$1 } from "./Action.mjs";
|
|
4
8
|
import { Declaring } from "./contract/declared.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import { AnyHttp, Client } from "./http/client.mjs";
|
|
9
|
+
import { AnyHttp, Client, WatchError } from "./http/client.mjs";
|
|
7
10
|
import "./ActionHttp.mjs";
|
|
8
|
-
import {
|
|
11
|
+
import { Options, UserError } from "./cli/commands.mjs";
|
|
12
|
+
import { Effect, Stream } from "effect";
|
|
9
13
|
import { HttpClient } from "effect/http";
|
|
10
14
|
import { CliError, Command } from "effect/cli";
|
|
11
15
|
interface AnyBinding extends Declaring {
|
|
12
|
-
readonly actions:
|
|
16
|
+
readonly actions: readonly Any$1[];
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
15
19
|
* Make a command of your own fail as a derived command does. Give it what the command calls, the
|
|
@@ -25,24 +29,29 @@ interface AnyBinding extends Declaring {
|
|
|
25
29
|
* - A failure left unmapped, one call's included, is reported as a crash, `ERROR (#n)` with a stack trace: pipe the whole Effect through it, once.
|
|
26
30
|
* - To add context, such as how far a batch got, print it before this in the pipe, `Effect.tapError(() => Console.error(…))`: a `UserError` wrapped around the result puts its JSON mid-sentence and hides the action's failure from `catchReason`.
|
|
27
31
|
*/
|
|
28
|
-
export declare const toUserError: (calls: AnyBinding |
|
|
32
|
+
export declare const toUserError: (calls: AnyBinding | readonly Any$1[]) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, UserError<Exclude<E, CliError.CliError>> | Exclude<E, Exclude<E, CliError.CliError>>, R>;
|
|
29
33
|
/**
|
|
30
34
|
* An aggregate command of the actions `A`: its name, the actions that are its subcommands,
|
|
31
35
|
* and their options.
|
|
32
36
|
*/
|
|
33
|
-
interface Options$1<A extends Any = Any> {
|
|
37
|
+
interface Options$1<A extends Any$1 | Any = Any$1 | Any> {
|
|
34
38
|
/** The aggregate command's name. */
|
|
35
39
|
readonly name: string;
|
|
36
40
|
/**
|
|
37
|
-
* The actions that are subcommands
|
|
38
|
-
* `[GetUser, RenameUser]
|
|
41
|
+
* The actions that are subcommands and the event types that have a `watch`, among the
|
|
42
|
+
* binding's or the implementations': `[GetUser, RenameUser, UserRenamed]`, exactly those.
|
|
43
|
+
* Defaults to every one of them.
|
|
44
|
+
*
|
|
45
|
+
* **Gotchas**
|
|
46
|
+
*
|
|
47
|
+
* - A list naming actions alone adds no `watch`: list the event types beside them.
|
|
39
48
|
*/
|
|
40
|
-
readonly actions?:
|
|
49
|
+
readonly actions?: readonly A[] | undefined;
|
|
41
50
|
/**
|
|
42
51
|
* The options of each subcommand, keyed by its action's name, as `command` takes them:
|
|
43
52
|
* `{ readFile: { positional: ["path"], render } }`.
|
|
44
53
|
*/
|
|
45
|
-
readonly commands?: { readonly [K in A as K["name"]]?: Options<K>; };
|
|
54
|
+
readonly commands?: { readonly [K in Extract<A, Any$1> as K["name"]]?: Options<K>; };
|
|
46
55
|
/**
|
|
47
56
|
* Commands of your own beside the derived ones, as `Command.withSubcommands` takes them:
|
|
48
57
|
* `[importLinks]`, each made with Effect CLI's `Command.make` and printing its output itself,
|
|
@@ -58,16 +67,29 @@ interface Options$1<A extends Any = Any> {
|
|
|
58
67
|
*/
|
|
59
68
|
readonly subcommands?: Subcommands | undefined;
|
|
60
69
|
}
|
|
61
|
-
type Subcommands =
|
|
70
|
+
type Subcommands = readonly Command.Command.SubcommandEntry[];
|
|
62
71
|
type OwnCommand<Own extends Subcommands> = Subcommands extends Own ? never : Own[number] extends (infer Entry) ? Entry extends Command.Command<infer _N, infer _I, infer _C, infer _E, infer _R> ? Entry : Entry extends Command.Command.SubcommandGroup<infer Commands> ? Commands[number] : never : never;
|
|
63
72
|
type CommandsOf<O> = O extends unknown ? "commands" extends keyof O ? NonNullable<O["commands" & keyof O]> : never : never;
|
|
64
73
|
type KeysOf<C> = C extends unknown ? keyof C : never;
|
|
65
|
-
type CommandOf<C, K> = C extends unknown ?
|
|
66
|
-
type KnownOptions<O, Keys, A extends Any> = Known<O, Keys> & ([CommandsOf<O>] extends [never] ? unknown : {
|
|
67
|
-
readonly commands?: Known<CommandsOf<O>, { readonly [K in A["name"]]: Options<A>; }> & { readonly [K in KeysOf<CommandsOf<O>>]?: Known<CommandOf<CommandsOf<O>, K>, Options<Any>>; };
|
|
74
|
+
type CommandOf<C, K> = C extends unknown ? K extends keyof C ? C[K] : never : never;
|
|
75
|
+
type KnownOptions<O, Keys, A extends Any$1> = Known<O, Keys> & ([CommandsOf<O>] extends [never] ? unknown : {
|
|
76
|
+
readonly commands?: Known<CommandsOf<O>, { readonly [K in A["name"]]: Options<A>; }> & { readonly [K in KeysOf<CommandsOf<O>>]?: Known<CommandOf<CommandsOf<O>, K>, Options<Any$1>>; };
|
|
68
77
|
});
|
|
69
|
-
type Owning<App, A extends Any> = App extends unknown ? A extends ActionOf<App> ? App : never : never;
|
|
70
|
-
type Local<App, A extends Any> = Effect.Effect<A["success"]["Type"], A["error"][number]["Type"] | BuildError<App, A> | BuiltIn, RequestOf<App, A> | BuildServices<App, A>>;
|
|
78
|
+
type Owning<App, A extends Any$1> = App extends unknown ? A extends ActionOf<App> ? App : never : never;
|
|
79
|
+
type Local<App, A extends Any$1> = Effect.Effect<A["success"]["Type"], A["error"][number]["Type"] | BuildError<App, A> | BuiltIn, RequestOf<App, A> | BuildServices<App, A>>;
|
|
80
|
+
type LocalCall<Apps extends Served, O> = [SelectedOf<O, Apps>] extends [never] ? never : Local<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>>;
|
|
81
|
+
type LocalWatch<App, Listed extends Any> = [ServingEvents<App, Listed>] extends [never] ? never : Effect.Effect<void, SubscriptionError | BuiltIn | EventBuildError<App, Listed>, EventRequestOf<App, ServingEvents<App, Listed>> | EventBuildServices<App, Listed>>;
|
|
82
|
+
type Watched<M, E extends Any> = M extends {
|
|
83
|
+
readonly watch: (event: E, ...args: WatchArgs<E, never>) => Stream.Stream<unknown, infer WE, infer WR>;
|
|
84
|
+
} ? {
|
|
85
|
+
readonly error: WE;
|
|
86
|
+
readonly services: Exclude<WR, Receiving>;
|
|
87
|
+
} : {
|
|
88
|
+
readonly error: never;
|
|
89
|
+
readonly services: never;
|
|
90
|
+
};
|
|
91
|
+
type WatchFailure<H extends AnyHttp, C, E extends Any> = [E] extends [never] ? never : C extends Effect.Effect<infer M, infer CE, unknown> ? CE | Watched<M, E>["error"] : WatchError<E, H>;
|
|
92
|
+
type WatchServices<C, E extends Any> = [E] extends [never] ? never : C extends Effect.Effect<infer M, unknown, infer CR> ? CR | Watched<M, E>["services"] : HttpClient.HttpClient;
|
|
71
93
|
/**
|
|
72
94
|
* Project one implemented action into a native Effect CLI command that runs its handler in
|
|
73
95
|
* process, behind its implementation's authorization. It is named after the action in kebab
|
|
@@ -79,6 +101,8 @@ type Local<App, A extends Any> = Effect.Effect<A["success"]["Type"], A["error"][
|
|
|
79
101
|
* **Gotchas**
|
|
80
102
|
*
|
|
81
103
|
* - A success is its JSON on stdout, indented by two spaces, or `render`'s text; nothing for an action without `success`. Its Effect logs and `Console` output go to stderr; other writes as `logToStderr` says.
|
|
104
|
+
* - Each value an action declaring `progress` reports is one line of compact JSON on stderr, as it arrives, so stdout holds the answer alone; `remoteCommand` asks the server for them too.
|
|
105
|
+
* - An action answering a stream prints each element on stdout as it arrives, one line of compact JSON, or `render`'s text of it; a declared failure ends the command after the elements before it.
|
|
82
106
|
* - A declared error is its JSON on stderr, under Effect CLI's `ERROR` heading, the command failing with a `UserError` whose `cause` is the error, exit 1 or its `Runtime.errorExitCode`; a refusal as `Action.Unauthenticated` and `Action.Forbidden` say; input that does not decode, in `--input` or a flag's JSON value, is `InvalidInput`'s JSON there, while a missing flag or a rejected choice is Effect CLI's own parse error.
|
|
83
107
|
* - Effect CLI styles what it prints, a failure's JSON included, on a terminal and, from Effect 4.0.6, wherever `FORCE_COLOR` forces colour: for plain text there, set `NO_COLOR` or provide `TerminalCapabilities.TerminalCapabilities` with `canColor: false`.
|
|
84
108
|
* - A failure no schema encodes, a builder's or an accepted error that does not encode, is a `UserError` printed as its tag or name and its message, then its causes, never its other fields; a defect, an undeclared failure, or a success that does not encode is the command's defect, which `runMain` reports as `logToStderr` says.
|
|
@@ -86,14 +110,15 @@ type Local<App, A extends Any> = Effect.Effect<A["success"]["Type"], A["error"][
|
|
|
86
110
|
* - Each run builds the action's implementation, its builder and an authorizer an Effect builds, and releases them after the call, even where the host already built them; implementations passed beside it are not built.
|
|
87
111
|
* - A field named like a global flag (`help`, `version`, `log-level`) is no clash: its flag shadows the global one on this command.
|
|
88
112
|
*/
|
|
89
|
-
export declare
|
|
113
|
+
export declare const command: <const Apps extends ServedActions, A extends ActionOf<Member<Apps>>, const O = {}>(implementations: Apps, action: A, options?: O & NoInfer<Options<A>> & NoInfer<Known<O, Options<Any$1>>>) => Command.Command<string, never, {}, Failing<Effect.Error<Local<Owning<Member<Apps>, A>, A>>>, Effect.Services<Local<Owning<Member<Apps>, A>, A>>>;
|
|
90
114
|
/**
|
|
91
115
|
* Project one action of an HTTP binding into a native Effect CLI command, derived as `command`
|
|
92
116
|
* derives it, that calls the action through `client`'s method and fails as the method does, or
|
|
93
117
|
* as the Effect building the client does. It runs nothing locally and names no caller: the
|
|
94
118
|
* server verifies the credentials its client sends.
|
|
95
119
|
*/
|
|
96
|
-
export declare function remoteCommand<const H extends AnyHttp, A extends H["actions"][number], const O = {}, C extends ClientOf<H, A> | undefined = undefined>(binding: H, action: A, options?: O & NoInfer<Options<A>> & NoInfer<Known<O, RemoteKeys<Options<Any>>>> & Calling<C>): Command.Command<string, never, {}, Failing<CallFailure<H, C, A>>, CallServices<C, A>>;
|
|
120
|
+
export declare function remoteCommand<const H extends AnyHttp, A extends H["actions"][number], const O = {}, C extends ClientOf<H, A> | undefined = undefined>(binding: H, action: A, options?: O & NoInfer<Options<A>> & NoInfer<Known<O, RemoteKeys<Options<Any$1>>>> & Calling<C>): Command.Command<string, never, {}, Failing<CallFailure<H, C, A>>, CallServices<C, A>>;
|
|
121
|
+
/** The `client` option of `remote` and `remoteCommand`, declared once for both. */
|
|
97
122
|
export interface Calling<C> {
|
|
98
123
|
/**
|
|
99
124
|
* The connection: an Effect of the binding's client, such as
|
|
@@ -112,41 +137,52 @@ export interface Calling<C> {
|
|
|
112
137
|
*/
|
|
113
138
|
readonly client?: C;
|
|
114
139
|
}
|
|
115
|
-
type ClientOf<H extends AnyHttp, A extends Any> = Effect.Effect<Pick<Client<H, unknown, unknown>, A["name"]>, unknown, unknown>;
|
|
116
|
-
type Needed<O, L extends
|
|
140
|
+
type ClientOf<H extends AnyHttp, A extends Any$1, E extends Any = never> = Effect.Effect<Pick<Client<H, unknown, unknown>, A["name"] | ([E] extends [never] ? never : "watch")>, unknown, unknown>;
|
|
141
|
+
type Needed<O, L extends readonly unknown[], All extends Any$1> = "actions" extends (O extends unknown ? keyof O : never) ? Selected<O, All> : Extract<L[number], All>;
|
|
142
|
+
type NeededEvents<O, L extends readonly unknown[], All extends Any> = "actions" extends (O extends unknown ? keyof O : never) ? SelectedEvents<O, All> : Extract<L[number], All>;
|
|
117
143
|
type RemoteKeys<Keys> = Keys & {
|
|
118
144
|
readonly client?: unknown;
|
|
119
145
|
};
|
|
120
|
-
type Failing<F> = UserError<Exclude<F, CliError.CliError>> | Extract<F, CliError.CliError>;
|
|
121
|
-
type Called<M, A extends Any> = A extends unknown ? A["name"] extends keyof M ? M[A["name"]] extends ((...input: never) => infer Call) ? Call : never : never : never;
|
|
122
|
-
type
|
|
123
|
-
type
|
|
146
|
+
type Failing<F> = [F] extends [never] ? never : UserError<Exclude<F, CliError.CliError>> | Extract<F, CliError.CliError>;
|
|
147
|
+
type Called<M, A extends Any$1> = A extends unknown ? A["name"] extends keyof M ? M[A["name"]] extends ((...input: never) => infer Call) ? Call : never : never : never;
|
|
148
|
+
type CallError<Call> = Effect.Error<Call> | Stream.Error<Call>;
|
|
149
|
+
type CallRequirements<Call> = Effect.Services<Call> | Stream.Services<Call>;
|
|
150
|
+
type CallFailure<H extends AnyHttp, C, A extends Any$1> = C extends Effect.Effect<infer M, infer CE, unknown> ? CE | CallError<Called<M, A>> : CallError<Called<Client<H>, A>>;
|
|
151
|
+
type CallServices<C, A extends Any$1> = C extends Effect.Effect<infer M, unknown, infer CR> ? CR | CallRequirements<Called<M, A>> : HttpClient.HttpClient;
|
|
124
152
|
/**
|
|
125
153
|
* Project the implemented actions, every one or those `actions` selects, as subcommands of one
|
|
126
154
|
* aggregate command, each derived as `command` derives it and run in process. `commands` gives a
|
|
127
155
|
* subcommand the options `command` takes, by action name; `subcommands` adds commands of your
|
|
128
|
-
* own. `remote` calls a server instead.
|
|
156
|
+
* own. `remote` calls a server instead. Each event type among the implementations, or each
|
|
157
|
+
* `actions` lists, adds `watch <event>`, a flag per input field and `--cursor`, printing each
|
|
158
|
+
* occurrence, or a `Gap`, as one line of JSON, its `cursor` included, until it is stopped. A test
|
|
159
|
+
* runs it in memory, with no process: `Command.runWith(command, { version })(args)`.
|
|
129
160
|
* Guide: `docs/ActionCli.md` in this package; in an application: `examples/assistant/src/commands.ts`.
|
|
130
161
|
*
|
|
131
162
|
* **Gotchas**
|
|
132
163
|
*
|
|
133
164
|
* - Services provided on it with `Command.provide` are also built when it runs with no subcommand, before it shows its help.
|
|
165
|
+
* - An event input field named `cursor` throws when `make` or `remote` is called, `--cursor` claiming its flag: rename the field.
|
|
166
|
+
* - A watch's refusal, when it starts or later, or `Event.SubscriptionError`, ends the command as its `UserError`, exit 1, after the lines it printed.
|
|
134
167
|
*/
|
|
135
|
-
export declare
|
|
168
|
+
export declare const make: <const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>> | EventOf<Member<Apps>>> = {}, const Own extends Subcommands = []>(implementations: Apps, options: O & NoInfer<Options$1<ActionOf<Member<Apps>> | EventOf<Member<Apps>>>> & NoInfer<KnownOptions<O, Options$1, ActionOf<Member<Apps>>>> & {
|
|
136
169
|
readonly subcommands?: Own | undefined;
|
|
137
|
-
})
|
|
170
|
+
}) => Command.Command<string, {}, {}, Failing<Effect.Error<LocalCall<Apps, O> | LocalWatch<Member<Apps>, SelectedEventsOf<O, Apps>>>> | Command.Error<OwnCommand<Own>>, Effect.Services<LocalCall<Apps, O> | LocalWatch<Member<Apps>, SelectedEventsOf<O, Apps>>> | Command.Services<OwnCommand<Own>>>;
|
|
138
171
|
/**
|
|
139
172
|
* Project the actions of an HTTP binding, every one or those `actions` selects, as subcommands of
|
|
140
173
|
* one aggregate command, derived as `make` derives them, each calling its action through
|
|
141
174
|
* `client`'s method, as `remoteCommand` does. `commands` gives one the options `command` takes;
|
|
142
175
|
* `subcommands` adds commands of your own, which call through the same client by yielding the
|
|
143
|
-
* Effect given as `client`.
|
|
144
|
-
*
|
|
176
|
+
* Effect given as `client`. Each event type of the binding, or each `actions` lists, adds
|
|
177
|
+
* `watch <event>`, as `make` adds it, watching through `client`'s `watch`, which reconnects as
|
|
178
|
+
* `ActionHttp.client`'s does.
|
|
179
|
+
* In an application: `examples/notes-service/src/commands.ts`, and for events,
|
|
180
|
+
* `examples/build-events/src/commands.ts`.
|
|
145
181
|
*/
|
|
146
|
-
export declare function remote<const H extends AnyHttp, const O extends Selection<H["actions"][number]> = {}, const Own extends Subcommands = [], const L extends ReadonlyArray<H["actions"][number]> = H["actions"], C extends ClientOf<H, Needed<O, L, H["actions"][number]>> | undefined = undefined>(binding: H, options: O & NoInfer<Options$1<H["actions"][number]>> & NoInfer<KnownOptions<O, RemoteKeys<Options$1
|
|
182
|
+
export declare function remote<const H extends AnyHttp, const O extends Selection<H["actions"][number] | H["events"][number]> = {}, const Own extends Subcommands = [], const L extends ReadonlyArray<H["actions"][number] | H["events"][number]> = [...H["actions"], ...H["events"]], C extends ClientOf<H, Needed<O, L, H["actions"][number]>, NeededEvents<O, L, H["events"][number]>> | undefined = undefined>(binding: H, options: O & NoInfer<Options$1<H["actions"][number] | H["events"][number]>> & NoInfer<KnownOptions<O, RemoteKeys<Options$1>, H["actions"][number]>> & Calling<C> & {
|
|
147
183
|
readonly actions?: L | undefined;
|
|
148
184
|
readonly subcommands?: Own | undefined;
|
|
149
|
-
}): Command.Command<string, {}, {}, Failing<CallFailure<H, C, Selected<O, H["actions"][number]>>> | Command.Error<OwnCommand<Own>>, CallServices<C, Selected<O, H["actions"][number]>> | Command.Services<OwnCommand<Own>>>;
|
|
185
|
+
}): Command.Command<string, {}, {}, Failing<CallFailure<H, C, Selected<O, H["actions"][number]>> | WatchFailure<H, C, SelectedEvents<O, H["events"][number]>>> | Command.Error<OwnCommand<Own>>, CallServices<C, Selected<O, H["actions"][number]>> | WatchServices<C, SelectedEvents<O, H["events"][number]>> | Command.Services<OwnCommand<Own>>>;
|
|
150
186
|
/**
|
|
151
187
|
* A program whose stdout carries only its results, such as a CLI feeding scripts or an MCP
|
|
152
188
|
* subprocess, applied last, before `NodeRuntime.runMain`: the default logger of every layer
|
package/dist/ActionCli.mjs
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { bindingErrors, eventErrors, projectedErrors } from "./contract/declared.mjs";
|
|
1
2
|
import { assertDistinct, assertKnown } from "./contract/names.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { progressSink } from "./contract/progress.mjs";
|
|
4
|
+
import { elementOf } from "./contract/stream.mjs";
|
|
5
|
+
import { Implementation, acquire, assertHeld, elementsOf, notHeld, toList } from "./contract/implementation.mjs";
|
|
6
|
+
import { selection } from "./contract/listed.mjs";
|
|
7
|
+
import { selectFrom } from "./contract/serving.mjs";
|
|
8
|
+
import { client } from "./Action.mjs";
|
|
9
|
+
import { client as client$1 } from "./ActionHttp.mjs";
|
|
10
|
+
import { command as command$1, failingAsDerived, watchCommand } from "./cli/commands.mjs";
|
|
11
|
+
import { Cause, Effect, Logger, Runtime, Stream } from "effect";
|
|
7
12
|
import { Command } from "effect/cli";
|
|
8
13
|
// src/cli/ActionCli.ts
|
|
9
14
|
/**
|
|
@@ -21,12 +26,14 @@ import { Command } from "effect/cli";
|
|
|
21
26
|
* - To add context, such as how far a batch got, print it before this in the pipe, `Effect.tapError(() => Console.error(…))`: a `UserError` wrapped around the result puts its JSON mid-sentence and hides the action's failure from `catchReason`.
|
|
22
27
|
*/
|
|
23
28
|
const toUserError = (calls) => "actions" in calls ? failingAsDerived(calls.actions.flatMap((action) => bindingErrors(action, calls))) : failingAsDerived(calls.flatMap((action) => projectedErrors(action)));
|
|
24
|
-
const runWithBuildOfItsOwn = (app, action, input) => {
|
|
29
|
+
const runWithBuildOfItsOwn = (app, action, input, onProgress) => {
|
|
25
30
|
const thisActionOnly = Implementation.share([action], app);
|
|
26
|
-
|
|
31
|
+
const call = Effect.flatMap(acquire([thisActionOnly], void 0), (bound) => {
|
|
27
32
|
const [, run] = bound.find(([candidate]) => candidate === action) ?? [];
|
|
28
|
-
return run === void 0 ? Effect.die(/* @__PURE__ */ new Error(`No handler for ${action.name}`)) : run(input);
|
|
29
|
-
})
|
|
33
|
+
return run === void 0 ? Effect.die(/* @__PURE__ */ new Error(`No handler for ${action.name}`)) : run(input, progressSink(onProgress));
|
|
34
|
+
});
|
|
35
|
+
const builder = Implementation.layerOf(thisActionOnly);
|
|
36
|
+
return elementOf(action.success) === void 0 ? call.pipe(Effect.provide(builder, { local: true })) : Effect.succeed(elementsOf(call).pipe(Stream.provide(builder, { local: true })));
|
|
30
37
|
};
|
|
31
38
|
const theImplementationOf = (apps, action) => {
|
|
32
39
|
const [app, ...others] = apps.filter((candidate) => candidate.actions.includes(action));
|
|
@@ -36,32 +43,38 @@ const theImplementationOf = (apps, action) => {
|
|
|
36
43
|
};
|
|
37
44
|
const inProcess = (apps, action, options) => {
|
|
38
45
|
const app = theImplementationOf(apps, action);
|
|
39
|
-
return command$1(action, (input) => runWithBuildOfItsOwn(app, action, input), options, projectedErrors(action));
|
|
46
|
+
return command$1(action, (input, onProgress) => runWithBuildOfItsOwn(app, action, input, onProgress), options, projectedErrors(action));
|
|
40
47
|
};
|
|
41
|
-
const overHttp = (binding, action, options, client
|
|
48
|
+
const overHttp = (binding, action, options, client) => {
|
|
42
49
|
if (!binding.actions.includes(action)) throw new Error(`Action "${action.name}" is not in this HTTP binding (pass it to the binding's ActionHttp.make, and select with the contract value it received)`);
|
|
43
|
-
return command$1(action, (input) => Effect.flatMap(client
|
|
50
|
+
return command$1(action, (input, onProgress) => Effect.flatMap(client ?? client$1(binding), (methods) => {
|
|
44
51
|
const method = methods[action.name];
|
|
45
52
|
if (method === void 0) throw new Error(`No client method for ${action.name} (give \`client\` a method for this action)`);
|
|
46
|
-
|
|
53
|
+
const answer = method(input, { onProgress });
|
|
54
|
+
return Stream.isStream(answer) ? Effect.succeed(answer) : answer;
|
|
47
55
|
}), options, bindingErrors(action, binding));
|
|
48
56
|
};
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
/** The command watching `event` through the `watch` of each client `client` makes as it runs. */
|
|
58
|
+
const watched = (event, client, errors) => watchCommand(event, (input, cursor) => Stream.unwrap(Effect.map(client, (methods) => methods.watch(event, input, { cursor }))), errors);
|
|
59
|
+
const aggregateCommand = (name, actions, commands, own, project, watches) => {
|
|
60
|
+
const derived = [...actions.map((action) => ({
|
|
51
61
|
claimant: `action ${action.name}`,
|
|
52
62
|
command: project(action, Object.hasOwn(commands, action.name) ? commands[action.name] : void 0)
|
|
53
|
-
}))
|
|
54
|
-
|
|
63
|
+
})), ...watches.length === 0 ? [] : [{
|
|
64
|
+
claimant: "the event types",
|
|
65
|
+
command: Command.make("watch").pipe(Command.withDescription("Watch an event type, each occurrence a line of JSON"), Command.withSubcommands(watches))
|
|
66
|
+
}]];
|
|
67
|
+
assertDistinct("command", [...derived.map(({ claimant, command: derivedCommand }) => ({
|
|
55
68
|
claimant,
|
|
56
|
-
name:
|
|
57
|
-
})), ...own.flatMap((entry) => Command.isCommand(entry) ? [entry] : entry.commands).map((
|
|
69
|
+
name: derivedCommand.name
|
|
70
|
+
})), ...own.flatMap((entry) => Command.isCommand(entry) ? [entry] : entry.commands).map((subcommand) => ({
|
|
58
71
|
claimant: "subcommands",
|
|
59
|
-
name:
|
|
72
|
+
name: subcommand.name
|
|
60
73
|
}))], ({ name: claimed }) => claimed, {
|
|
61
74
|
claimantOf: ({ claimant }) => claimant,
|
|
62
75
|
fix: "rename one of your subcommands, or give an action's command another name in commands"
|
|
63
76
|
});
|
|
64
|
-
return Command.make(name).pipe(Command.withSubcommands([...derived.map(({ command }) =>
|
|
77
|
+
return Command.make(name).pipe(Command.withSubcommands([...derived.map(({ command: derivedCommand }) => derivedCommand), ...own]));
|
|
65
78
|
};
|
|
66
79
|
const checkedCommandsOf = (commands, actions) => {
|
|
67
80
|
const given = commands ?? {};
|
|
@@ -79,6 +92,8 @@ const checkedCommandsOf = (commands, actions) => {
|
|
|
79
92
|
* **Gotchas**
|
|
80
93
|
*
|
|
81
94
|
* - A success is its JSON on stdout, indented by two spaces, or `render`'s text; nothing for an action without `success`. Its Effect logs and `Console` output go to stderr; other writes as `logToStderr` says.
|
|
95
|
+
* - Each value an action declaring `progress` reports is one line of compact JSON on stderr, as it arrives, so stdout holds the answer alone; `remoteCommand` asks the server for them too.
|
|
96
|
+
* - An action answering a stream prints each element on stdout as it arrives, one line of compact JSON, or `render`'s text of it; a declared failure ends the command after the elements before it.
|
|
82
97
|
* - A declared error is its JSON on stderr, under Effect CLI's `ERROR` heading, the command failing with a `UserError` whose `cause` is the error, exit 1 or its `Runtime.errorExitCode`; a refusal as `Action.Unauthenticated` and `Action.Forbidden` say; input that does not decode, in `--input` or a flag's JSON value, is `InvalidInput`'s JSON there, while a missing flag or a rejected choice is Effect CLI's own parse error.
|
|
83
98
|
* - Effect CLI styles what it prints, a failure's JSON included, on a terminal and, from Effect 4.0.6, wherever `FORCE_COLOR` forces colour: for plain text there, set `NO_COLOR` or provide `TerminalCapabilities.TerminalCapabilities` with `canColor: false`.
|
|
84
99
|
* - A failure no schema encodes, a builder's or an accepted error that does not encode, is a `UserError` printed as its tag or name and its message, then its causes, never its other fields; a defect, an undeclared failure, or a success that does not encode is the command's defect, which `runMain` reports as `logToStderr` says.
|
|
@@ -86,9 +101,7 @@ const checkedCommandsOf = (commands, actions) => {
|
|
|
86
101
|
* - Each run builds the action's implementation, its builder and an authorizer an Effect builds, and releases them after the call, even where the host already built them; implementations passed beside it are not built.
|
|
87
102
|
* - A field named like a global flag (`help`, `version`, `log-level`) is no clash: its flag shadows the global one on this command.
|
|
88
103
|
*/
|
|
89
|
-
|
|
90
|
-
return inProcess(toList(implementations), action, options);
|
|
91
|
-
}
|
|
104
|
+
const command = (implementations, action, options) => inProcess(toList(implementations), action, options);
|
|
92
105
|
function remoteCommand(binding, action, options) {
|
|
93
106
|
const { client, ...syntax } = options ?? {};
|
|
94
107
|
return overHttp(binding, action, options === void 0 ? void 0 : syntax, client);
|
|
@@ -97,24 +110,30 @@ function remoteCommand(binding, action, options) {
|
|
|
97
110
|
* Project the implemented actions, every one or those `actions` selects, as subcommands of one
|
|
98
111
|
* aggregate command, each derived as `command` derives it and run in process. `commands` gives a
|
|
99
112
|
* subcommand the options `command` takes, by action name; `subcommands` adds commands of your
|
|
100
|
-
* own. `remote` calls a server instead.
|
|
113
|
+
* own. `remote` calls a server instead. Each event type among the implementations, or each
|
|
114
|
+
* `actions` lists, adds `watch <event>`, a flag per input field and `--cursor`, printing each
|
|
115
|
+
* occurrence, or a `Gap`, as one line of JSON, its `cursor` included, until it is stopped. A test
|
|
116
|
+
* runs it in memory, with no process: `Command.runWith(command, { version })(args)`.
|
|
101
117
|
* Guide: `docs/ActionCli.md` in this package; in an application: `examples/assistant/src/commands.ts`.
|
|
102
118
|
*
|
|
103
119
|
* **Gotchas**
|
|
104
120
|
*
|
|
105
121
|
* - Services provided on it with `Command.provide` are also built when it runs with no subcommand, before it shows its help.
|
|
122
|
+
* - An event input field named `cursor` throws when `make` or `remote` is called, `--cursor` claiming its flag: rename the field.
|
|
123
|
+
* - A watch's refusal, when it starts or later, or `Event.SubscriptionError`, ends the command as its `UserError`, exit 1, after the lines it printed.
|
|
106
124
|
*/
|
|
107
|
-
|
|
125
|
+
const make = (implementations, options) => {
|
|
108
126
|
const apps = toList(implementations);
|
|
127
|
+
const { apps: served, events } = selectFrom(implementations, options.actions);
|
|
109
128
|
const commands = checkedCommandsOf(options.commands, apps.flatMap((app) => app.actions));
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
129
|
+
return aggregateCommand(options.name, served.flatMap((app) => app.actions), commands, options.subcommands ?? [], (action, syntax) => inProcess(served, action, syntax), events.flatMap((app) => app.events.map((event) => watched(event, client(app), eventErrors(event, { error: [] })))));
|
|
130
|
+
};
|
|
113
131
|
function remote(binding, options) {
|
|
114
|
-
const listed = options.actions;
|
|
115
|
-
if (listed !== void 0) assertHeld(notHeld.byBinding, listed, binding.actions);
|
|
132
|
+
const listed = selection(options.actions);
|
|
133
|
+
if (listed.actions !== void 0) assertHeld(notHeld.byBinding, listed.actions, binding.actions);
|
|
134
|
+
if (listed.events !== void 0) assertHeld(notHeld.byBinding, listed.events, binding.events);
|
|
116
135
|
const commands = checkedCommandsOf(options.commands, binding.actions);
|
|
117
|
-
return aggregateCommand(options.name, binding.actions.filter((action) => listed?.includes(action) ?? true), commands, options.subcommands ?? [], (action, syntax) => overHttp(binding, action, syntax, options.client));
|
|
136
|
+
return aggregateCommand(options.name, binding.actions.filter((action) => listed.actions?.includes(action) ?? true), commands, options.subcommands ?? [], (action, syntax) => overHttp(binding, action, syntax, options.client), binding.events.filter((event) => listed.events?.includes(event) ?? true).map((event) => watched(event, options.client ?? client$1(binding), eventErrors(event, binding))));
|
|
118
137
|
}
|
|
119
138
|
/**
|
|
120
139
|
* A program whose stdout carries only its results, such as a CLI feeding scripts or an MCP
|