@gjermundgaraba/effect-actions 0.19.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 -48
- package/dist/Action.mjs +88 -44
- package/dist/ActionCli.d.mts +70 -35
- package/dist/ActionCli.mjs +53 -34
- package/dist/ActionHttp.d.mts +70 -38
- package/dist/ActionHttp.mjs +76 -25
- package/dist/ActionMcp.d.mts +56 -24
- package/dist/ActionMcp.mjs +104 -61
- package/dist/ActionRpc.d.mts +222 -57
- package/dist/ActionRpc.mjs +301 -49
- package/dist/ActionToolkit.d.mts +22 -7
- package/dist/ActionToolkit.mjs +37 -14
- package/dist/Authentication.d.mts +42 -12
- package/dist/Authentication.mjs +38 -32
- package/dist/Event.d.mts +370 -0
- package/dist/Event.mjs +103 -0
- package/dist/Testing.d.mts +1 -3
- package/dist/Testing.mjs +2 -3
- package/dist/Webhook.d.mts +146 -0
- package/dist/Webhook.mjs +72 -0
- package/dist/authentication/provider.d.mts +35 -12
- package/dist/authentication/provider.mjs +2 -3
- package/dist/authentication/refusal.mjs +3 -4
- package/dist/cli/commands.d.mts +9 -7
- package/dist/cli/commands.mjs +85 -35
- package/dist/contract/ast.mjs +5 -3
- package/dist/contract/authorizer.d.mts +8 -0
- package/dist/contract/authorizer.mjs +20 -0
- package/dist/contract/call.d.mts +9 -8
- package/dist/contract/call.mjs +49 -8
- package/dist/contract/callback.d.mts +7 -0
- package/dist/contract/callback.mjs +27 -0
- package/dist/contract/caller.d.mts +12 -3
- package/dist/contract/caller.mjs +23 -5
- 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.d.mts +0 -2
- package/dist/contract/declared.mjs +26 -11
- package/dist/contract/digest.mjs +6 -0
- package/dist/contract/errors.d.mts +9 -5
- package/dist/contract/errors.mjs +2 -3
- package/dist/contract/feed.d.mts +127 -0
- package/dist/contract/feed.mjs +249 -0
- package/dist/contract/hints.mjs +1 -2
- 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 -51
- package/dist/contract/implementation.mjs +94 -60
- 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/media.d.mts +1 -3
- package/dist/contract/media.mjs +1 -2
- package/dist/contract/names.mjs +15 -6
- 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 +15 -7
- 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 -14
- package/dist/http/client.mjs +52 -14
- 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 +2 -3
- 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 -99
- package/dist/mcp/client.mjs +60 -134
- package/dist/mcp/events.mjs +142 -0
- package/dist/mcp/progress.mjs +34 -0
- package/dist/mcp/protocol.mjs +40 -15
- 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 +14 -7
- package/dist/testing/memory.d.mts +1 -3
- package/dist/testing/memory.mjs +4 -5
- 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 +57 -61
- 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 -53
- 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 +25 -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 +130 -33
- 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 +19 -17
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { makeLog, makeLogStore, memoryStorage } from "./log.mjs";
|
|
2
|
+
import { Context, Effect, Layer, Queue, Stream } from "effect";
|
|
3
|
+
// src/contract/hub.ts
|
|
4
|
+
/** Revisions kept in memory, starting again, in a new epoch, with each call. */
|
|
5
|
+
const memoryRevisions = () => {
|
|
6
|
+
const counts = /* @__PURE__ */ new Map();
|
|
7
|
+
return {
|
|
8
|
+
epoch: globalThis.crypto.randomUUID(),
|
|
9
|
+
get: (name) => Effect.sync(() => counts.get(name) ?? 0),
|
|
10
|
+
bump: (names) => Effect.sync(() => {
|
|
11
|
+
for (const name of names) counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
12
|
+
})
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
const everyKey = (event) => event.name;
|
|
16
|
+
const noKey = (event) => `${event.name}\u0000`;
|
|
17
|
+
const ofKey = (event, key) => `${event.name}\u0000=${key}`;
|
|
18
|
+
/**
|
|
19
|
+
* Where an event's occurrences are announced as they happen: `notify` wakes the subscribers of a
|
|
20
|
+
* check source to read now, and `publish` appends an occurrence to a log source, both reaching
|
|
21
|
+
* the subscribers of one `key` or every one. Domain code yields it as it yields a store, and an
|
|
22
|
+
* entrypoint provides one implementation: `Event.layerMemory`, kept in a process's memory.
|
|
23
|
+
* Every surface serving events and `Action.client` watching them need it.
|
|
24
|
+
*
|
|
25
|
+
* **Gotchas**
|
|
26
|
+
*
|
|
27
|
+
* - The handlers announcing and the surfaces serving must share one, as they share a store: built separately, as a test building its handlers under a `provide` of their own does, each has its own, and an announcement reaches no subscriber of the other.
|
|
28
|
+
* - An event no implementation attached here has no subscribers here: `publish` does nothing for it, and `notify` only moves its revision, so a current source attached later answers the change.
|
|
29
|
+
* - `notify` of a log source and `publish` to a check source die naming the other: a check source reads your store, so write there and notify; a log source keeps what you publish.
|
|
30
|
+
* - Implementations attach only to one `layerMemory` or `makeEvents` made: built over another, such as `Events.of` in a test, they die, naming both.
|
|
31
|
+
*/
|
|
32
|
+
var Events = class extends Context.Service()("effect-actions/Event/Events") {};
|
|
33
|
+
const attached = /* @__PURE__ */ new WeakMap();
|
|
34
|
+
/** How sources attach to `events`, the `Events` `makeEvents` made; one made otherwise dies. */
|
|
35
|
+
const attachmentsOf = (events) => Effect.suspend(() => {
|
|
36
|
+
const attachments = attached.get(events);
|
|
37
|
+
return attachments === void 0 ? Effect.die(/* @__PURE__ */ new Error("This Event.Events serves no event implementation: provide Event.layerMemory, or one Event.makeEvents builds")) : Effect.succeed(attachments);
|
|
38
|
+
});
|
|
39
|
+
/** Whether an announcement to `target` reaches a subscriber of `key`: either absent means every one. */
|
|
40
|
+
const reaches = (target, key) => target === void 0 || key === void 0 || target === key;
|
|
41
|
+
const holding = (set, value) => Effect.acquireRelease(Effect.sync(() => {
|
|
42
|
+
set.add(value);
|
|
43
|
+
return value;
|
|
44
|
+
}), () => Effect.sync(() => set.delete(value)));
|
|
45
|
+
/**
|
|
46
|
+
* An `Events` for as long as the scope lasts, over the log storage `log` makes: what a host keeping
|
|
47
|
+
* logs elsewhere, such as a Durable Object's storage, provides as `Events`, its subscribers' wakes
|
|
48
|
+
* in memory and its logs where `log` keeps them.
|
|
49
|
+
*
|
|
50
|
+
* **Gotchas**
|
|
51
|
+
*
|
|
52
|
+
* - Make it once per host, around the handlers that announce and the surfaces that serve, as `layerMemory` is built.
|
|
53
|
+
* - A log's storage is made once per event for the life of the scope, its position read on every append and read: a storage emptied behind its back, as `storage.deleteAll` empties one, starts a new epoch, and a cursor from before reads a gap.
|
|
54
|
+
*/
|
|
55
|
+
const makeEvents = (options) => Effect.gen(function* () {
|
|
56
|
+
const polls = options.polls ?? true;
|
|
57
|
+
const emits = options.emits ?? true;
|
|
58
|
+
const revisions = options.revisions ?? memoryRevisions();
|
|
59
|
+
const scope = yield* Effect.scope;
|
|
60
|
+
const checks = /* @__PURE__ */ new Map();
|
|
61
|
+
const logs = /* @__PURE__ */ new Map();
|
|
62
|
+
const checkOf = (event) => {
|
|
63
|
+
const found = checks.get(event) ?? {
|
|
64
|
+
attached: /* @__PURE__ */ new Set(),
|
|
65
|
+
waiters: /* @__PURE__ */ new Set()
|
|
66
|
+
};
|
|
67
|
+
checks.set(event, found);
|
|
68
|
+
return found;
|
|
69
|
+
};
|
|
70
|
+
const isCheck = (event) => (checks.get(event)?.attached.size ?? 0) > 0;
|
|
71
|
+
const feed = (kept) => Effect.suspend(() => {
|
|
72
|
+
const [next] = kept.feeding ? [] : kept.waiting;
|
|
73
|
+
if (next === void 0) return Effect.void;
|
|
74
|
+
kept.waiting.delete(next);
|
|
75
|
+
kept.feeding = true;
|
|
76
|
+
const fed = Effect.andThen(kept.log.feed(Stream.map(next.emit, ({ key, ...value }) => ({
|
|
77
|
+
value,
|
|
78
|
+
key
|
|
79
|
+
}))), Effect.suspend(() => {
|
|
80
|
+
kept.feeding = false;
|
|
81
|
+
return feed(kept);
|
|
82
|
+
}));
|
|
83
|
+
return Effect.asVoid(Effect.forkIn(fed, scope));
|
|
84
|
+
});
|
|
85
|
+
const fedBy = (kept, emit) => Effect.tap(holding(kept.waiting, { emit }), () => feed(kept));
|
|
86
|
+
const events = Events.of({
|
|
87
|
+
notify: (event, notifyOptions) => Effect.suspend(() => {
|
|
88
|
+
if (!isCheck(event) && logs.has(event)) return Effect.die(/* @__PURE__ */ new Error(`Event "${event.name}" has a log source, which keeps what is published: give each occurrence to publish (Event.Events.publish(event, { data }))`));
|
|
89
|
+
const key = notifyOptions?.key;
|
|
90
|
+
return Effect.andThen(checks.get(event)?.current === false ? Effect.void : revisions.bump([everyKey(event), key === void 0 ? noKey(event) : ofKey(event, key)]), Effect.sync(() => {
|
|
91
|
+
for (const waiter of checkOf(event).waiters) if (reaches(key, waiter.key)) Queue.offerUnsafe(waiter.woken, void 0);
|
|
92
|
+
}));
|
|
93
|
+
}),
|
|
94
|
+
publish: (event, emitted, publishOptions) => Effect.suspend(() => {
|
|
95
|
+
const kept = logs.get(event);
|
|
96
|
+
if (kept !== void 0) return kept.log.append(emitted, publishOptions?.key);
|
|
97
|
+
return isCheck(event) ? Effect.die(/* @__PURE__ */ new Error(`Event "${event.name}" has a check source, which reads your store: write the occurrence there, then wake its subscribers (Event.Events.notify(event))`)) : Effect.void;
|
|
98
|
+
})
|
|
99
|
+
});
|
|
100
|
+
attached.set(events, {
|
|
101
|
+
check: (event, current) => Effect.map(holding(Object.assign(checkOf(event), { current }).attached, {}), () => ({
|
|
102
|
+
polls,
|
|
103
|
+
wakes: (key) => Effect.flatMap(Queue.sliding(1), (woken) => Effect.as(holding(checkOf(event).waiters, {
|
|
104
|
+
key,
|
|
105
|
+
woken
|
|
106
|
+
}), Queue.take(woken))),
|
|
107
|
+
revision: (key) => key === void 0 ? Effect.map(revisions.get(everyKey(event)), (count) => `${revisions.epoch}:${count}`) : Effect.map(Effect.all([revisions.get(noKey(event)), revisions.get(ofKey(event, key))]), ([unkeyed, keyed]) => `${revisions.epoch}:${unkeyed + keyed}:${key}`)
|
|
108
|
+
})),
|
|
109
|
+
log: (event, bounds, emit) => Effect.suspend(() => {
|
|
110
|
+
if (emit !== void 0 && !emits) return Effect.die(/* @__PURE__ */ new Error(`Event "${event.name}" has a log source fed by emit, which this Event.Events does not consume, as its host does not last between requests (publish each occurrence with Event.Events.publish as the host receives it, such as in a call or a route)`));
|
|
111
|
+
const kept = logs.get(event) ?? {
|
|
112
|
+
log: makeLog(makeLogStore(options.log(event), bounds)),
|
|
113
|
+
waiting: /* @__PURE__ */ new Set(),
|
|
114
|
+
feeding: false
|
|
115
|
+
};
|
|
116
|
+
logs.set(event, kept);
|
|
117
|
+
return Effect.as(emit === void 0 ? Effect.void : fedBy(kept, emit), kept.log);
|
|
118
|
+
})
|
|
119
|
+
});
|
|
120
|
+
return events;
|
|
121
|
+
});
|
|
122
|
+
/**
|
|
123
|
+
* An `Events` kept in the memory of the process that builds it: for a server holding its
|
|
124
|
+
* subscribers' connections itself, such as Node's, and for tests.
|
|
125
|
+
*
|
|
126
|
+
* **Gotchas**
|
|
127
|
+
*
|
|
128
|
+
* - Build it once, around the handlers that announce and the surfaces that serve, as a store is: each build is its own.
|
|
129
|
+
* - A log lives as long as this build, through rebuilds of the implementations attached to it: a restart forgets it, and a subscriber resuming from one of its cursors receives a gap.
|
|
130
|
+
* - Several builds of one log source share its log, and its `emit` feeds it from one build at a time, so nothing is appended twice.
|
|
131
|
+
*/
|
|
132
|
+
const layerMemory = Layer.effect(Events)(makeEvents({ log: () => memoryStorage() }));
|
|
133
|
+
export { Events, attachmentsOf, layerMemory, makeEvents, reaches };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import "./implementation.mjs";
|
|
2
|
+
import "../Action.mjs";
|
|
3
|
+
import { Context, Effect, Layer, Schema } from "effect";
|
|
4
|
+
declare const Kept: Schema.Struct<{
|
|
5
|
+
readonly input: Schema.String;
|
|
6
|
+
readonly outcome: Schema.String;
|
|
7
|
+
readonly at: Schema.Finite;
|
|
8
|
+
}>;
|
|
9
|
+
type Kept = typeof Kept.Type;
|
|
10
|
+
declare const Idempotency_base: Context.ServiceClass<Idempotency, "effect-actions/Action/Idempotency", {
|
|
11
|
+
/** What was kept under `id`, or `undefined`; one kept longer than 24 hours may be either. */
|
|
12
|
+
readonly get: (id: string) => Effect.Effect<Kept | undefined>;
|
|
13
|
+
/** Keeps `kept` under `id`, replacing what was. */
|
|
14
|
+
readonly set: (id: string, kept: Kept) => Effect.Effect<void>;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Where the answers of calls given an idempotency key are kept, by the action's name and a
|
|
18
|
+
* SHA-256 digest in hex of the key and its caller's principal, `<action>/<digest>`, for 24 hours
|
|
19
|
+
* at least: what an implementation of an action naming `idempotencyKey` requires at startup.
|
|
20
|
+
* `layerIdempotency` keeps them in memory; a Durable Object's `make` and `handlers` provide one
|
|
21
|
+
* over its storage; another store, shared by several servers, implements `get` and `set`, keeping
|
|
22
|
+
* `Idempotency.Kept` at least `Idempotency.keptFor` milliseconds.
|
|
23
|
+
*
|
|
24
|
+
* **Gotchas**
|
|
25
|
+
*
|
|
26
|
+
* - Provide one per server, around the surfaces: an implementation built in another layer graph keeps its own.
|
|
27
|
+
* - A server waits for a repeat's first call itself, in memory: two servers sharing a store run a call each, the later answer kept.
|
|
28
|
+
*/
|
|
29
|
+
export declare class Idempotency extends Idempotency_base {
|
|
30
|
+
/**
|
|
31
|
+
* What is kept of a call answered under its key: the digest of its input, its outcome as JSON,
|
|
32
|
+
* and when it was kept, in milliseconds since the epoch.
|
|
33
|
+
*/
|
|
34
|
+
static readonly Kept: Schema.Struct<{
|
|
35
|
+
readonly input: Schema.String;
|
|
36
|
+
readonly outcome: Schema.String;
|
|
37
|
+
readonly at: Schema.Finite;
|
|
38
|
+
}>;
|
|
39
|
+
/** How long, in milliseconds, a kept answer is kept at least: 24 hours. */
|
|
40
|
+
static readonly keptFor: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `Idempotency` in the server's memory, each answer kept 24 hours after it was kept: lost when
|
|
44
|
+
* the server stops, and kept apart from every other server's.
|
|
45
|
+
*/
|
|
46
|
+
export declare const layerIdempotency: Layer.Layer<Idempotency>;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { CallerKeyed, principalOf } from "./caller.mjs";
|
|
2
|
+
import { InvalidInput } from "./errors.mjs";
|
|
3
|
+
import { declaredStatus, isTransientStatus } from "./status.mjs";
|
|
4
|
+
import { projectedErrors } from "./declared.mjs";
|
|
5
|
+
import { answered } from "./implementation.mjs";
|
|
6
|
+
import { canonicalInput } from "./canonical.mjs";
|
|
7
|
+
import { sha256Hex } from "./digest.mjs";
|
|
8
|
+
import { Cause, Clock, Context, Deferred, Effect, Exit, Layer, Option, Predicate, Schema } from "effect";
|
|
9
|
+
// src/contract/idempotency.ts
|
|
10
|
+
const Kept = Schema.Struct({
|
|
11
|
+
input: Schema.String,
|
|
12
|
+
outcome: Schema.String,
|
|
13
|
+
at: Schema.Finite
|
|
14
|
+
});
|
|
15
|
+
const keptFor = 864e5;
|
|
16
|
+
/**
|
|
17
|
+
* Where the answers of calls given an idempotency key are kept, by the action's name and a
|
|
18
|
+
* SHA-256 digest in hex of the key and its caller's principal, `<action>/<digest>`, for 24 hours
|
|
19
|
+
* at least: what an implementation of an action naming `idempotencyKey` requires at startup.
|
|
20
|
+
* `layerIdempotency` keeps them in memory; a Durable Object's `make` and `handlers` provide one
|
|
21
|
+
* over its storage; another store, shared by several servers, implements `get` and `set`, keeping
|
|
22
|
+
* `Idempotency.Kept` at least `Idempotency.keptFor` milliseconds.
|
|
23
|
+
*
|
|
24
|
+
* **Gotchas**
|
|
25
|
+
*
|
|
26
|
+
* - Provide one per server, around the surfaces: an implementation built in another layer graph keeps its own.
|
|
27
|
+
* - A server waits for a repeat's first call itself, in memory: two servers sharing a store run a call each, the later answer kept.
|
|
28
|
+
*/
|
|
29
|
+
var Idempotency = class extends Context.Service()("effect-actions/Action/Idempotency") {
|
|
30
|
+
/**
|
|
31
|
+
* What is kept of a call answered under its key: the digest of its input, its outcome as JSON,
|
|
32
|
+
* and when it was kept, in milliseconds since the epoch.
|
|
33
|
+
*/
|
|
34
|
+
static Kept = Kept;
|
|
35
|
+
/** How long, in milliseconds, a kept answer is kept at least: 24 hours. */
|
|
36
|
+
static keptFor = keptFor;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* `Idempotency` in the server's memory, each answer kept 24 hours after it was kept: lost when
|
|
40
|
+
* the server stops, and kept apart from every other server's.
|
|
41
|
+
*/
|
|
42
|
+
const layerIdempotency = Layer.sync(Idempotency)(() => {
|
|
43
|
+
const held = /* @__PURE__ */ new Map();
|
|
44
|
+
return Idempotency.of({
|
|
45
|
+
get: (id) => Effect.sync(() => held.get(id)),
|
|
46
|
+
set: (id, kept) => Effect.sync(() => {
|
|
47
|
+
held.delete(id);
|
|
48
|
+
held.set(id, kept);
|
|
49
|
+
for (const [oldest, { at }] of held) {
|
|
50
|
+
if (at > kept.at - keptFor) break;
|
|
51
|
+
held.delete(oldest);
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* `handle`, answering a call given `action`'s idempotency key as the first call with it and the
|
|
58
|
+
* same input was answered: from `store`, or, while that call runs, once it has.
|
|
59
|
+
*/
|
|
60
|
+
const keyed = (action, store, handle) => {
|
|
61
|
+
const field = action.idempotencyKey;
|
|
62
|
+
if (field === void 0) return handle;
|
|
63
|
+
const Outcome = Schema.fromJsonString(Schema.toCodecJson(Schema.Exit(action.success, Schema.Union(projectedErrors(action)), Schema.Never)));
|
|
64
|
+
const encodeOutcome = Schema.encodeUnknownEffect(Outcome);
|
|
65
|
+
const decodeOutcome = Schema.decodeUnknownOption(Outcome);
|
|
66
|
+
const isTransientFailure = Schema.is(Schema.Union(projectedErrors(action).filter((error) => isTransientStatus(declaredStatus(error)))));
|
|
67
|
+
const running = /* @__PURE__ */ new Map();
|
|
68
|
+
const run = (id, key, digest, answer) => Effect.gen(function* () {
|
|
69
|
+
const first = running.get(id);
|
|
70
|
+
if (first !== void 0) {
|
|
71
|
+
yield* Deferred.await(first).pipe(Effect.withSpan("Idempotency.wait", { attributes: { "action.name": action.name } }));
|
|
72
|
+
return yield* run(id, key, digest, answer);
|
|
73
|
+
}
|
|
74
|
+
const claim = Deferred.makeUnsafe();
|
|
75
|
+
running.set(id, claim);
|
|
76
|
+
const release = Effect.andThen(Effect.sync(() => running.delete(id)), Deferred.succeed(claim, void 0));
|
|
77
|
+
return yield* Effect.gen(function* () {
|
|
78
|
+
const kept = yield* store.get(id);
|
|
79
|
+
if (kept !== void 0 && kept.input !== digest) return yield* new InvalidInput({ message: `The idempotency key ${key} of ${action.name} was given with another input (send a new key for another call)` });
|
|
80
|
+
const replayed = kept === void 0 ? Option.none() : decodeOutcome(kept.outcome);
|
|
81
|
+
if (Option.isSome(replayed)) return yield* replayed.value;
|
|
82
|
+
const exit = yield* Effect.exit(answer);
|
|
83
|
+
const json = Exit.isSuccess(exit) || exit.cause.reasons.every((reason) => Cause.isFailReason(reason) && !isTransientFailure(reason.error)) ? yield* Effect.option(encodeOutcome(exit)) : Option.none();
|
|
84
|
+
if (Option.isSome(json)) yield* store.set(id, {
|
|
85
|
+
input: digest,
|
|
86
|
+
outcome: json.value,
|
|
87
|
+
at: yield* Clock.currentTimeMillis
|
|
88
|
+
});
|
|
89
|
+
return yield* exit;
|
|
90
|
+
}).pipe(Effect.ensuring(release));
|
|
91
|
+
});
|
|
92
|
+
return (input, reporting) => {
|
|
93
|
+
const key = Predicate.hasProperty(input, field) ? input[field] : void 0;
|
|
94
|
+
if (!Predicate.isString(key)) return handle(input, reporting);
|
|
95
|
+
const answer = Effect.suspend(() => answered(handle(input, reporting)));
|
|
96
|
+
return Effect.flatMap(Effect.all([Effect.service(CallerKeyed).pipe(Effect.flatMap((named) => principalOf(action, named)), Effect.flatMap((principal) => sha256Hex(JSON.stringify([principal ?? null, key])))), Effect.flatMap(canonicalInput(action.input, input), sha256Hex)]), ([id, digest]) => run(`${action.name}/${id}`, key, digest, answer));
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
export { Idempotency, keyed, layerIdempotency };
|
|
@@ -1,28 +1,41 @@
|
|
|
1
|
-
import { Anyone } from "./caller.mjs";
|
|
1
|
+
import { Anyone, Keyed } from "./caller.mjs";
|
|
2
2
|
import { Refusal } from "./errors.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import { AnyEventImplementation, EventOf } from "./feed.mjs";
|
|
4
|
+
import { Any } from "../Event.mjs";
|
|
5
|
+
import { Reporting } from "./progress.mjs";
|
|
6
|
+
import { Any as Any$1 } from "../Action.mjs";
|
|
7
|
+
import { Context, Effect, Layer, Scope, Stream } from "effect";
|
|
6
8
|
/** Decoded values at the dispatch boundary every surface shares. */
|
|
7
|
-
export type ErasedValue = Any["input"]["Type"];
|
|
9
|
+
export type ErasedValue = Any$1["input"]["Type"];
|
|
8
10
|
/**
|
|
9
|
-
* A handler, erased
|
|
10
|
-
*
|
|
11
|
-
* handlers are not widened at
|
|
11
|
+
* A handler as written, erased: its answer, or the stream of an action answering one. Method
|
|
12
|
+
* extraction makes the argument bivariant: a surface selects the action before it invokes a
|
|
13
|
+
* handler, so its decoded input is the matching handler input, and handlers are not widened at
|
|
14
|
+
* their public API.
|
|
12
15
|
*/
|
|
13
|
-
export type
|
|
14
|
-
handle(input: ErasedValue): Effect.Effect<ErasedValue, ErasedValue, R>;
|
|
16
|
+
export type WrittenHandler<R> = {
|
|
17
|
+
handle(input: ErasedValue, reporting?: Reporting): Effect.Effect<ErasedValue, ErasedValue, R> | Stream.Stream<ErasedValue, ErasedValue, R>;
|
|
15
18
|
}["handle"];
|
|
19
|
+
/**
|
|
20
|
+
* A handler behind its action's authentication, authorization and declared errors, as a surface
|
|
21
|
+
* calls it. For an action answering a stream it succeeds at once with the call's stream, which
|
|
22
|
+
* `elementsOf` reads, the refusals included. `reporting` is where its progress goes, ignored for
|
|
23
|
+
* an action declaring no `progress`, so a surface may hand one to every call.
|
|
24
|
+
*/
|
|
25
|
+
export type ErasedHandler<R> = (input: ErasedValue, reporting?: Reporting) => Effect.Effect<ErasedValue, ErasedValue, R>;
|
|
16
26
|
/**
|
|
17
27
|
* Authorization for protected contracts: it refuses, and fails with nothing else. It runs only for
|
|
18
28
|
* protected actions, after authentication and before the handler.
|
|
19
29
|
*/
|
|
20
|
-
export type Authorize<A extends Any, R = never> = (action: A) => Effect.Effect<void, Refusal, R>;
|
|
21
|
-
/** An
|
|
22
|
-
export type ErasedAuthorize = (
|
|
23
|
-
/** Each action of an implementation with its handler, as
|
|
24
|
-
export type Bound = ReadonlyArray<readonly [Any, ErasedHandler<unknown>]>;
|
|
25
|
-
|
|
30
|
+
export type Authorize<A extends Any$1, R = never> = (action: A) => Effect.Effect<void, Refusal, R>;
|
|
31
|
+
/** An authorization rule over contracts `C`, erased: it refuses, or lets the call run. */
|
|
32
|
+
export type ErasedAuthorize<C = Any$1> = (contract: C) => Effect.Effect<void, unknown, unknown>;
|
|
33
|
+
/** Each action of an implementation with its handler, as `dispatch` makes it. */
|
|
34
|
+
export type Bound = ReadonlyArray<readonly [Any$1, ErasedHandler<unknown>]>;
|
|
35
|
+
/** Each action of an implementation with its handler as written, as its builder made them. */
|
|
36
|
+
export type Written = ReadonlyArray<readonly [Any$1, WrittenHandler<unknown>]>;
|
|
37
|
+
/** A build shared by every layer an implementation reaches, through the one key it is provided under. */
|
|
38
|
+
export interface Memoized<S, E, R> {
|
|
26
39
|
readonly key: Context.Key<S, S>;
|
|
27
40
|
readonly layer: Layer.Layer<S, E, R>;
|
|
28
41
|
}
|
|
@@ -35,12 +48,10 @@ interface Memoized<S, E, R> {
|
|
|
35
48
|
* its handlers' builder, `RA` what its authorizer reads per request, and `EAX` and `RAX` the
|
|
36
49
|
* failures and services of the authorizer's builder.
|
|
37
50
|
*/
|
|
38
|
-
export declare class Implementation<A extends Any, RequestServices extends {
|
|
51
|
+
export declare class Implementation<A extends Any$1, RequestServices extends {
|
|
39
52
|
readonly [name: string]: unknown;
|
|
40
53
|
}, EX, RX, RA = never, EAX = never, RAX = never> {
|
|
41
54
|
#private;
|
|
42
|
-
/** The contracts this implementation answers. */
|
|
43
|
-
readonly actions: ReadonlyArray<A>;
|
|
44
55
|
/** Type-only: each action's handler's request-time requirements. */
|
|
45
56
|
readonly "~request": RequestServices;
|
|
46
57
|
/** Type-only: what building its handlers fails with. */
|
|
@@ -53,15 +64,15 @@ export declare class Implementation<A extends Any, RequestServices extends {
|
|
|
53
64
|
readonly "~authorizeBuildError": EAX;
|
|
54
65
|
/** Type-only: what building its authorizer needs. */
|
|
55
66
|
readonly "~authorizeBuildServices": RAX;
|
|
56
|
-
constructor(
|
|
57
67
|
/** The contracts this implementation answers. */
|
|
58
|
-
actions:
|
|
68
|
+
readonly actions: readonly A[];
|
|
69
|
+
constructor(actions: readonly A[],
|
|
59
70
|
/** Builds each action paired with its handler: its own builder, or the one it shares. */
|
|
60
|
-
handlers: Memoized<
|
|
71
|
+
handlers: Memoized<Written, EX, RX>,
|
|
61
72
|
/** Builds its authorizer. */
|
|
62
73
|
authorizer: Memoized<ErasedAuthorize, EAX, RAX>);
|
|
63
74
|
/** Select contracts while retaining their handlers, authorization and shared builder. */
|
|
64
|
-
static share(actions:
|
|
75
|
+
static share(actions: readonly Any$1[], app: AnyImplementation): AnyImplementation;
|
|
65
76
|
/**
|
|
66
77
|
* The key of what runs a call of `app`'s actions: its handlers' memoized layer's, which each
|
|
67
78
|
* `implement` makes, so it is unique to the implementation. Every selection retains it.
|
|
@@ -77,7 +88,7 @@ export declare class Implementation<A extends Any, RequestServices extends {
|
|
|
77
88
|
* The actions of `app` with their handlers, behind its authorizer, from the context
|
|
78
89
|
* `layerOf(app)` provides.
|
|
79
90
|
*/
|
|
80
|
-
static boundOf(app: AnyImplementation): Effect.Effect<Bound, never, unknown>;
|
|
91
|
+
static boundOf(app: AnyImplementation, keyed: Keyed | undefined): Effect.Effect<Bound, never, unknown>;
|
|
81
92
|
/** `app`, if this copy of the module made it; another installed copy's cannot be read. */
|
|
82
93
|
private static own;
|
|
83
94
|
}
|
|
@@ -88,43 +99,55 @@ export declare class Implementation<A extends Any, RequestServices extends {
|
|
|
88
99
|
* admits every implementation, while a value of this type owes `unknown`, which no surface can be
|
|
89
100
|
* given.
|
|
90
101
|
*/
|
|
91
|
-
export type AnyImplementation<A extends Any = Any> = Implementation<A, {
|
|
102
|
+
export type AnyImplementation<A extends Any$1 = Any$1> = Implementation<A, {
|
|
92
103
|
readonly [name: string]: unknown;
|
|
93
104
|
}, unknown, unknown, unknown, unknown, unknown>;
|
|
94
|
-
/** What a surface serves: one implementation, or a list of them. */
|
|
95
|
-
export type Served = AnyImplementation | ReadonlyArray<AnyImplementation>;
|
|
105
|
+
/** What a surface serves: one implementation, of actions or of events, or a list of them. */
|
|
106
|
+
export type Served = AnyImplementation | AnyEventImplementation | ReadonlyArray<AnyImplementation | AnyEventImplementation>;
|
|
107
|
+
/** What a surface serving no events takes: one action implementation, or a list of them. */
|
|
108
|
+
export type ServedActions = AnyImplementation | readonly AnyImplementation[];
|
|
96
109
|
/** The implementations `S` stands for. */
|
|
97
110
|
export type Member<S> = S extends ReadonlyArray<infer App> ? App : S;
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Options that may list actions and event types of `C`, the shape every selecting surface's
|
|
113
|
+
* options share.
|
|
114
|
+
*/
|
|
115
|
+
export interface Selection<C extends Any$1 | Any> {
|
|
116
|
+
readonly actions?: readonly C[] | undefined;
|
|
101
117
|
}
|
|
102
118
|
/**
|
|
103
119
|
* The actions options `O` select, of `All`: those its `actions` lists when it always lists
|
|
104
|
-
* them, and every one otherwise. Options whose `actions`
|
|
105
|
-
* a union with options lacking it, select every action at run
|
|
120
|
+
* them, none where it lists event types alone, and every one otherwise. Options whose `actions`
|
|
121
|
+
* may be absent, being optional or a union with options lacking it, select every action at run
|
|
122
|
+
* time, so they narrow nothing.
|
|
106
123
|
*/
|
|
107
|
-
export type Selected<O, All extends Any> = O extends {
|
|
108
|
-
readonly actions: ReadonlyArray<infer
|
|
109
|
-
} ?
|
|
124
|
+
export type Selected<O, All extends Any$1> = O extends {
|
|
125
|
+
readonly actions: ReadonlyArray<infer C>;
|
|
126
|
+
} ? Extract<C, Any$1> : All;
|
|
127
|
+
/** The event types options `O` select, of `All`, as `Selected` selects actions. */
|
|
128
|
+
export type SelectedEvents<O, All extends Any> = O extends {
|
|
129
|
+
readonly actions: ReadonlyArray<infer C>;
|
|
130
|
+
} ? Extract<C, Any> : All;
|
|
110
131
|
/**
|
|
111
132
|
* The middleware options `O` install, each a member of `L`: its `middleware` when always given,
|
|
112
133
|
* an array of unknown length when it may be absent, which installs none or all of them, and
|
|
113
134
|
* none otherwise.
|
|
114
135
|
*/
|
|
115
|
-
export type MiddlewareOf<O, L extends
|
|
136
|
+
export type MiddlewareOf<O, L extends readonly unknown[]> = O extends {
|
|
116
137
|
readonly middleware: infer M extends L;
|
|
117
138
|
} ? M : O extends {
|
|
118
139
|
readonly middleware?: infer M extends L | undefined;
|
|
119
140
|
} ? "middleware" extends keyof O ? ReadonlyArray<NonNullable<M>[number]> : [] : [];
|
|
120
141
|
/** The actions options `O` select of the implementations `Apps` stand for. */
|
|
121
142
|
export type SelectedOf<O, Apps extends Served> = Selected<O, ActionOf<Member<Apps>>>;
|
|
143
|
+
/** The event types options `O` select of the implementations `Apps` stand for. */
|
|
144
|
+
export type SelectedEventsOf<O, Apps extends Served> = SelectedEvents<O, EventOf<Member<Apps>>>;
|
|
122
145
|
/**
|
|
123
|
-
* The actions a client of options `O` offers
|
|
146
|
+
* The actions or event types a client of options `O` offers, of `All`: those `actions` may
|
|
124
147
|
* list, wherever some member of `O` may list them, as each is present whether the list is
|
|
125
148
|
* given or not; every one where no member lists any.
|
|
126
149
|
*/
|
|
127
|
-
export type Offered<O, All extends Any> = [Listing<O>] extends [never] ? All : Extract<All, Listing<O> extends ReadonlyArray<infer
|
|
150
|
+
export type Offered<O, All extends Any$1 | Any> = [Listing<O>] extends [never] ? All : Extract<All, Listing<O> extends ReadonlyArray<infer C> ? C : never>;
|
|
128
151
|
type Listing<O> = O extends unknown ? NonNullable<O["actions" & keyof O]> : never;
|
|
129
152
|
/**
|
|
130
153
|
* No key of any member of `O` beyond those of `Keys`, so a misspelled option is refused
|
|
@@ -142,7 +165,7 @@ export type OptionalUnless<O, Given> = {} extends O ? [options?: Given] : [optio
|
|
|
142
165
|
export type ActionOf<App> = App extends {
|
|
143
166
|
readonly actions: ReadonlyArray<infer A>;
|
|
144
167
|
} ? A : never;
|
|
145
|
-
type Matches<A extends Any, Listed extends Any> = Listed extends Any ? [A] extends [Listed] ? true : [Listed] extends [A] ? true : never : never;
|
|
168
|
+
type Matches<A extends Any$1, Listed extends Any$1> = Listed extends Any$1 ? [A] extends [Listed] ? true : [Listed] extends [A] ? true : never : never;
|
|
146
169
|
/**
|
|
147
170
|
* The actions of `App` a surface given the actions `Listed` may serve, as `select` selects
|
|
148
171
|
* them by identity: an action whose type either is assignable to, or is assigned by, one
|
|
@@ -150,34 +173,34 @@ type Matches<A extends Any, Listed extends Any> = Listed extends Any ? [A] exten
|
|
|
150
173
|
* actions, an erased one included, still counts, and another contract of a listed name, which
|
|
151
174
|
* the surface leaves out beside an action it serves, does not.
|
|
152
175
|
*/
|
|
153
|
-
export type Serving<App, Listed extends Any> = ActionOf<App> extends (infer A extends Any) ? A extends unknown ? [A] extends [Listed] ? A : string extends A["name"] ? A : [Extract<A["name"], Listed["name"]>] extends [never] ? never : [Matches<A, Listed>] extends [never] ? never : A : never : never;
|
|
176
|
+
export type Serving<App, Listed extends Any$1> = ActionOf<App> extends (infer A extends Any$1) ? A extends unknown ? [A] extends [Listed] ? A : string extends A["name"] ? A : [Extract<A["name"], Listed["name"]>] extends [never] ? never : [Matches<A, Listed>] extends [never] ? never : A : never : never;
|
|
154
177
|
/**
|
|
155
178
|
* What each implementation among `App` owes per request where a surface serving the actions
|
|
156
179
|
* `Listed` serves it: its authorizer's services, and the handlers' of the actions it serves.
|
|
157
180
|
*/
|
|
158
|
-
export type ServedRequest<App, Listed extends Any> = App extends unknown ? RequestOf<App, Serving<App, Listed>> : never;
|
|
181
|
+
export type ServedRequest<App, Listed extends Any$1> = App extends unknown ? RequestOf<App, Serving<App, Listed>> : never;
|
|
159
182
|
/** The implementations among `App` holding an action of `Listed`: those a surface builds. */
|
|
160
|
-
export type Holding<App, Listed extends Any> = App extends unknown ? [Serving<App, Listed>] extends [never] ? never : App : never;
|
|
161
|
-
/** The protected members of a contract union. */
|
|
162
|
-
export type Protected<A extends Any
|
|
183
|
+
export type Holding<App, Listed extends Any$1> = App extends unknown ? [Serving<App, Listed>] extends [never] ? never : App : never;
|
|
184
|
+
/** The protected members of a contract union, of actions or of events. */
|
|
185
|
+
export type Protected<A extends Pick<Any$1, "caller">> = A extends unknown ? A["caller"] extends typeof Anyone ? never : A : never;
|
|
163
186
|
type ServiceOf<K> = K extends Context.Key<infer I, unknown> ? I : never;
|
|
164
|
-
type AuthenticationOf<A extends Any> = A["caller"] extends typeof Anyone ? never : ServiceOf<A["caller"]>;
|
|
187
|
+
type AuthenticationOf<A extends Any$1> = A["caller"] extends typeof Anyone ? never : ServiceOf<A["caller"]>;
|
|
165
188
|
/**
|
|
166
189
|
* Per-request requirements of `App`'s authorization and handler for each `A` it
|
|
167
190
|
* implements: those of every name `A`'s may be, so a union or an erased name owes each it may
|
|
168
191
|
* stand for.
|
|
169
192
|
*/
|
|
170
|
-
export type RequestOf<App, A extends Any> = App extends {
|
|
193
|
+
export type RequestOf<App, A extends Any$1> = App extends {
|
|
171
194
|
readonly "~request": infer R;
|
|
172
195
|
readonly "~authorizeRequest": infer RA;
|
|
173
|
-
} ? A extends Any ? [A["name"] & keyof R] extends [never] ? never : R[A["name"] & keyof R] | (A["caller"] extends typeof Anyone ? never : RA) | AuthenticationOf<A> : never : never;
|
|
174
|
-
type OwnActions<App> = Extract<ActionOf<App>, Any>;
|
|
196
|
+
} ? A extends Any$1 ? [A["name"] & keyof R] extends [never] ? never : R[A["name"] & keyof R] | (A["caller"] extends typeof Anyone ? never : RA) | AuthenticationOf<A> : never : never;
|
|
197
|
+
type OwnActions<App> = Extract<ActionOf<App>, Any$1>;
|
|
175
198
|
/**
|
|
176
199
|
* What a surface serving the actions `Listed` of the implementations `App` fails with at startup:
|
|
177
200
|
* their builders' and built authorizers' failures, an authorizer's only if a selected protected
|
|
178
201
|
* action needs it.
|
|
179
202
|
*/
|
|
180
|
-
export type BuildError<App, Listed extends Any = OwnActions<App>> = App extends {
|
|
203
|
+
export type BuildError<App, Listed extends Any$1 = OwnActions<App>> = App extends {
|
|
181
204
|
readonly "~buildError": infer EX;
|
|
182
205
|
readonly "~authorizeBuildError": infer EAX;
|
|
183
206
|
} ? EX | ([Protected<Serving<App, Listed>>] extends [never] ? never : EAX) : never;
|
|
@@ -185,9 +208,8 @@ export type BuildError<App, Listed extends Any = OwnActions<App>> = App extends
|
|
|
185
208
|
* What a surface serving the actions `Listed` of the implementations `App` reads at startup: their
|
|
186
209
|
* builders' and built authorizers' services.
|
|
187
210
|
*/
|
|
188
|
-
export type BuildServices<App, Listed extends Any = OwnActions<App>> = App extends {
|
|
211
|
+
export type BuildServices<App, Listed extends Any$1 = OwnActions<App>> = App extends {
|
|
189
212
|
readonly "~buildServices": infer RX;
|
|
190
213
|
readonly "~authorizeBuildServices": infer RAX;
|
|
191
214
|
} ? RX | ([Protected<Serving<App, Listed>>] extends [never] ? never : RAX) : never;
|
|
192
|
-
//#endregion
|
|
193
215
|
export {};
|