@alchemy.run/effect-cluster 0.0.0 → 2.0.0-beta.86
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/LICENSE +201 -0
- package/README.md +101 -4
- package/lib/AWS/Database.d.ts +54 -0
- package/lib/AWS/Database.d.ts.map +1 -0
- package/lib/AWS/Database.js +96 -0
- package/lib/AWS/Database.js.map +1 -0
- package/lib/AWS/EcsPool.d.ts +56 -0
- package/lib/AWS/EcsPool.d.ts.map +1 -0
- package/lib/AWS/EcsPool.js +87 -0
- package/lib/AWS/EcsPool.js.map +1 -0
- package/lib/AWS/index.d.ts +7 -0
- package/lib/AWS/index.d.ts.map +1 -0
- package/lib/AWS/index.js +7 -0
- package/lib/AWS/index.js.map +1 -0
- package/lib/AWS/internal/Gateway.d.ts +23 -0
- package/lib/AWS/internal/Gateway.d.ts.map +1 -0
- package/lib/AWS/internal/Gateway.js +70 -0
- package/lib/AWS/internal/Gateway.js.map +1 -0
- package/lib/Cloudflare/DurableObjectPool.d.ts +42 -0
- package/lib/Cloudflare/DurableObjectPool.d.ts.map +1 -0
- package/lib/Cloudflare/DurableObjectPool.js +136 -0
- package/lib/Cloudflare/DurableObjectPool.js.map +1 -0
- package/lib/Cloudflare/PoolWorker.d.ts +186 -0
- package/lib/Cloudflare/PoolWorker.d.ts.map +1 -0
- package/lib/Cloudflare/PoolWorker.js +75 -0
- package/lib/Cloudflare/PoolWorker.js.map +1 -0
- package/lib/Cloudflare/index.d.ts +7 -0
- package/lib/Cloudflare/index.d.ts.map +1 -0
- package/lib/Cloudflare/index.js +7 -0
- package/lib/Cloudflare/index.js.map +1 -0
- package/lib/Cloudflare/internal/Objects.d.ts +38 -0
- package/lib/Cloudflare/internal/Objects.d.ts.map +1 -0
- package/lib/Cloudflare/internal/Objects.js +102 -0
- package/lib/Cloudflare/internal/Objects.js.map +1 -0
- package/lib/Cron.d.ts +143 -0
- package/lib/Cron.d.ts.map +1 -0
- package/lib/Cron.js +83 -0
- package/lib/Cron.js.map +1 -0
- package/lib/Database.d.ts +95 -0
- package/lib/Database.d.ts.map +1 -0
- package/lib/Database.js +38 -0
- package/lib/Database.js.map +1 -0
- package/lib/Docker/Database.d.ts +42 -0
- package/lib/Docker/Database.d.ts.map +1 -0
- package/lib/Docker/Database.js +53 -0
- package/lib/Docker/Database.js.map +1 -0
- package/lib/Docker/SwarmPool.d.ts +44 -0
- package/lib/Docker/SwarmPool.d.ts.map +1 -0
- package/lib/Docker/SwarmPool.js +56 -0
- package/lib/Docker/SwarmPool.js.map +1 -0
- package/lib/Docker/index.d.ts +7 -0
- package/lib/Docker/index.d.ts.map +1 -0
- package/lib/Docker/index.js +7 -0
- package/lib/Docker/index.js.map +1 -0
- package/lib/Entity.d.ts +348 -0
- package/lib/Entity.d.ts.map +1 -0
- package/lib/Entity.js +308 -0
- package/lib/Entity.js.map +1 -0
- package/lib/Fly/Database.d.ts +50 -0
- package/lib/Fly/Database.d.ts.map +1 -0
- package/lib/Fly/Database.js +56 -0
- package/lib/Fly/Database.js.map +1 -0
- package/lib/Fly/MachinePool.d.ts +39 -0
- package/lib/Fly/MachinePool.d.ts.map +1 -0
- package/lib/Fly/MachinePool.js +51 -0
- package/lib/Fly/MachinePool.js.map +1 -0
- package/lib/Fly/index.d.ts +7 -0
- package/lib/Fly/index.d.ts.map +1 -0
- package/lib/Fly/index.js +7 -0
- package/lib/Fly/index.js.map +1 -0
- package/lib/GCP/ComputeEnginePool.d.ts +41 -0
- package/lib/GCP/ComputeEnginePool.d.ts.map +1 -0
- package/lib/GCP/ComputeEnginePool.js +172 -0
- package/lib/GCP/ComputeEnginePool.js.map +1 -0
- package/lib/GCP/Database.d.ts +48 -0
- package/lib/GCP/Database.d.ts.map +1 -0
- package/lib/GCP/Database.js +115 -0
- package/lib/GCP/Database.js.map +1 -0
- package/lib/GCP/index.d.ts +7 -0
- package/lib/GCP/index.d.ts.map +1 -0
- package/lib/GCP/index.js +7 -0
- package/lib/GCP/index.js.map +1 -0
- package/lib/Hetzner/Database.d.ts +48 -0
- package/lib/Hetzner/Database.d.ts.map +1 -0
- package/lib/Hetzner/Database.js +91 -0
- package/lib/Hetzner/Database.js.map +1 -0
- package/lib/Hetzner/ServerPool.d.ts +35 -0
- package/lib/Hetzner/ServerPool.d.ts.map +1 -0
- package/lib/Hetzner/ServerPool.js +98 -0
- package/lib/Hetzner/ServerPool.js.map +1 -0
- package/lib/Hetzner/index.d.ts +7 -0
- package/lib/Hetzner/index.d.ts.map +1 -0
- package/lib/Hetzner/index.js +7 -0
- package/lib/Hetzner/index.js.map +1 -0
- package/lib/Host.d.ts +60 -0
- package/lib/Host.d.ts.map +1 -0
- package/lib/Host.js +31 -0
- package/lib/Host.js.map +1 -0
- package/lib/Kubernetes/Database.d.ts +40 -0
- package/lib/Kubernetes/Database.d.ts.map +1 -0
- package/lib/Kubernetes/Database.js +56 -0
- package/lib/Kubernetes/Database.js.map +1 -0
- package/lib/Kubernetes/DeploymentPool.d.ts +54 -0
- package/lib/Kubernetes/DeploymentPool.d.ts.map +1 -0
- package/lib/Kubernetes/DeploymentPool.js +49 -0
- package/lib/Kubernetes/DeploymentPool.js.map +1 -0
- package/lib/Kubernetes/index.d.ts +7 -0
- package/lib/Kubernetes/index.d.ts.map +1 -0
- package/lib/Kubernetes/index.js +7 -0
- package/lib/Kubernetes/index.js.map +1 -0
- package/lib/Pool.d.ts +66 -0
- package/lib/Pool.d.ts.map +1 -0
- package/lib/Pool.js +47 -0
- package/lib/Pool.js.map +1 -0
- package/lib/Providers.d.ts +19 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +31 -0
- package/lib/Providers.js.map +1 -0
- package/lib/ShardGroups.d.ts +24 -0
- package/lib/ShardGroups.d.ts.map +1 -0
- package/lib/ShardGroups.js +19 -0
- package/lib/ShardGroups.js.map +1 -0
- package/lib/Singleton.d.ts +110 -0
- package/lib/Singleton.d.ts.map +1 -0
- package/lib/Singleton.js +60 -0
- package/lib/Singleton.js.map +1 -0
- package/lib/Workflow.d.ts +251 -0
- package/lib/Workflow.d.ts.map +1 -0
- package/lib/Workflow.js +288 -0
- package/lib/Workflow.js.map +1 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +12 -0
- package/lib/index.js.map +1 -0
- package/lib/internal/Bootstrap.d.ts +28 -0
- package/lib/internal/Bootstrap.d.ts.map +1 -0
- package/lib/internal/Bootstrap.js +257 -0
- package/lib/internal/Bootstrap.js.map +1 -0
- package/lib/internal/EntityRuntime.d.ts +40 -0
- package/lib/internal/EntityRuntime.d.ts.map +1 -0
- package/lib/internal/EntityRuntime.js +698 -0
- package/lib/internal/EntityRuntime.js.map +1 -0
- package/lib/internal/Gateway.d.ts +82 -0
- package/lib/internal/Gateway.d.ts.map +1 -0
- package/lib/internal/Gateway.js +218 -0
- package/lib/internal/Gateway.js.map +1 -0
- package/lib/internal/HostedPlatform.d.ts +36 -0
- package/lib/internal/HostedPlatform.d.ts.map +1 -0
- package/lib/internal/HostedPlatform.js +36 -0
- package/lib/internal/HostedPlatform.js.map +1 -0
- package/lib/internal/HostedRuntime.d.ts +58 -0
- package/lib/internal/HostedRuntime.d.ts.map +1 -0
- package/lib/internal/HostedRuntime.js +72 -0
- package/lib/internal/HostedRuntime.js.map +1 -0
- package/lib/internal/Naming.d.ts +12 -0
- package/lib/internal/Naming.d.ts.map +1 -0
- package/lib/internal/Naming.js +40 -0
- package/lib/internal/Naming.js.map +1 -0
- package/lib/internal/ObjectSharding.d.ts +141 -0
- package/lib/internal/ObjectSharding.d.ts.map +1 -0
- package/lib/internal/ObjectSharding.js +337 -0
- package/lib/internal/ObjectSharding.js.map +1 -0
- package/lib/internal/PoolHost.d.ts +80 -0
- package/lib/internal/PoolHost.d.ts.map +1 -0
- package/lib/internal/PoolHost.js +203 -0
- package/lib/internal/PoolHost.js.map +1 -0
- package/lib/internal/PoolHostService.d.ts +25 -0
- package/lib/internal/PoolHostService.d.ts.map +1 -0
- package/lib/internal/PoolHostService.js +10 -0
- package/lib/internal/PoolHostService.js.map +1 -0
- package/lib/internal/Wire.d.ts +60 -0
- package/lib/internal/Wire.d.ts.map +1 -0
- package/lib/internal/Wire.js +36 -0
- package/lib/internal/Wire.js.map +1 -0
- package/lib/internal/WorkflowGateway.d.ts +36 -0
- package/lib/internal/WorkflowGateway.d.ts.map +1 -0
- package/lib/internal/WorkflowGateway.js +124 -0
- package/lib/internal/WorkflowGateway.js.map +1 -0
- package/lib/internal/external-runner.d.ts +2 -0
- package/lib/internal/external-runner.d.ts.map +1 -0
- package/lib/internal/external-runner.js +7 -0
- package/lib/internal/external-runner.js.map +1 -0
- package/lib/internal/runner.d.ts +2 -0
- package/lib/internal/runner.d.ts.map +1 -0
- package/lib/internal/runner.js +5 -0
- package/lib/internal/runner.js.map +1 -0
- package/package.json +128 -3
- package/src/AWS/Database.ts +139 -0
- package/src/AWS/EcsPool.ts +118 -0
- package/src/AWS/index.ts +6 -0
- package/src/AWS/internal/Gateway.ts +82 -0
- package/src/Cloudflare/DurableObjectPool.ts +198 -0
- package/src/Cloudflare/PoolWorker.ts +124 -0
- package/src/Cloudflare/index.ts +6 -0
- package/src/Cloudflare/internal/Objects.ts +160 -0
- package/src/Cron.ts +202 -0
- package/src/Database.ts +116 -0
- package/src/Docker/Database.ts +94 -0
- package/src/Docker/SwarmPool.ts +83 -0
- package/src/Docker/index.ts +6 -0
- package/src/Entity.ts +621 -0
- package/src/Fly/Database.ts +102 -0
- package/src/Fly/MachinePool.ts +72 -0
- package/src/Fly/index.ts +6 -0
- package/src/GCP/ComputeEnginePool.ts +203 -0
- package/src/GCP/Database.ts +168 -0
- package/src/GCP/index.ts +6 -0
- package/src/Hetzner/Database.ts +142 -0
- package/src/Hetzner/ServerPool.ts +116 -0
- package/src/Hetzner/index.ts +6 -0
- package/src/Host.ts +68 -0
- package/src/Kubernetes/Database.ts +89 -0
- package/src/Kubernetes/DeploymentPool.ts +77 -0
- package/src/Kubernetes/index.ts +6 -0
- package/src/Pool.ts +104 -0
- package/src/Providers.ts +49 -0
- package/src/ShardGroups.ts +41 -0
- package/src/Singleton.ts +153 -0
- package/src/Workflow.ts +556 -0
- package/src/index.ts +11 -0
- package/src/internal/Bootstrap.ts +368 -0
- package/src/internal/EntityRuntime.ts +996 -0
- package/src/internal/Gateway.ts +366 -0
- package/src/internal/HostedPlatform.ts +83 -0
- package/src/internal/HostedRuntime.ts +118 -0
- package/src/internal/Naming.ts +44 -0
- package/src/internal/ObjectSharding.ts +578 -0
- package/src/internal/PoolHost.ts +330 -0
- package/src/internal/PoolHostService.ts +25 -0
- package/src/internal/Wire.ts +83 -0
- package/src/internal/WorkflowGateway.ts +188 -0
- package/src/internal/external-runner.ts +10 -0
- package/src/internal/runner.ts +5 -0
package/src/Workflow.ts
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
import type { InputProps } from "alchemy/Input";
|
|
2
|
+
import type { Named } from "alchemy/Named";
|
|
3
|
+
import * as Output from "alchemy/Output";
|
|
4
|
+
import {
|
|
5
|
+
Platform,
|
|
6
|
+
type PlatformIdentity,
|
|
7
|
+
type PlatformProps,
|
|
8
|
+
type PlatformServices,
|
|
9
|
+
} from "alchemy/Platform";
|
|
10
|
+
import * as Provider from "alchemy/Provider";
|
|
11
|
+
import type { Resource } from "alchemy/Resource";
|
|
12
|
+
import { RuntimeContext, sanitizeKey } from "alchemy/RuntimeContext";
|
|
13
|
+
import type { Self } from "alchemy/Self";
|
|
14
|
+
import {
|
|
15
|
+
type Execute,
|
|
16
|
+
type Implemented,
|
|
17
|
+
implemented,
|
|
18
|
+
makeRun,
|
|
19
|
+
type WorkflowClient,
|
|
20
|
+
} from "alchemy/Workflows";
|
|
21
|
+
import { ClusterSchema } from "effect/cluster";
|
|
22
|
+
import type * as ConfigError from "effect/Config";
|
|
23
|
+
import * as Context from "effect/Context";
|
|
24
|
+
import * as Data from "effect/Data";
|
|
25
|
+
import * as Effect from "effect/Effect";
|
|
26
|
+
import * as Effectable from "effect/Effectable";
|
|
27
|
+
import * as Layer from "effect/Layer";
|
|
28
|
+
import * as Option from "effect/Option";
|
|
29
|
+
import * as Redacted from "effect/Redacted";
|
|
30
|
+
import type * as Schema from "effect/Schema";
|
|
31
|
+
import {
|
|
32
|
+
Workflow as EffectWorkflow,
|
|
33
|
+
WorkflowEngine as EffectWorkflowEngine,
|
|
34
|
+
} from "effect/workflow";
|
|
35
|
+
import { type EntityBindingContract, PlatformDeclaration, shardGroupKey } from "./Entity.ts";
|
|
36
|
+
import { bindRoute, type GatewayTarget } from "./internal/Gateway.ts";
|
|
37
|
+
import { type HostedRuntimeContext, makeHostedRuntimeContext } from "./internal/HostedRuntime.ts";
|
|
38
|
+
import { makeRemoteEngine } from "./internal/WorkflowGateway.ts";
|
|
39
|
+
import type { Providers } from "./Providers.ts";
|
|
40
|
+
|
|
41
|
+
export const WorkflowTypeId = "Cluster.Workflow";
|
|
42
|
+
export type WorkflowTypeId = typeof WorkflowTypeId;
|
|
43
|
+
|
|
44
|
+
export interface WorkflowProps extends PlatformProps {
|
|
45
|
+
/**
|
|
46
|
+
* The module whose default export is this workflow: the class, or the
|
|
47
|
+
* Layer its `.make(...)` returned. Usually `import.meta.url`.
|
|
48
|
+
*/
|
|
49
|
+
main: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface WorkflowAttributes {
|
|
53
|
+
/** The workflow's name. */
|
|
54
|
+
workflow: string;
|
|
55
|
+
/** The shard group of the pool that hosts it. */
|
|
56
|
+
pool: string | undefined;
|
|
57
|
+
/** The gateway URL callers start and read runs through. */
|
|
58
|
+
gateway: string | undefined;
|
|
59
|
+
/** The token callers present to the gateway. */
|
|
60
|
+
token: Redacted.Redacted<string> | undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ClusterWorkflow extends Resource<
|
|
64
|
+
WorkflowTypeId,
|
|
65
|
+
WorkflowProps,
|
|
66
|
+
WorkflowAttributes,
|
|
67
|
+
EntityBindingContract,
|
|
68
|
+
Providers
|
|
69
|
+
> {}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A workflow as a runner runs it: the workflow, the handler its
|
|
73
|
+
* Construction phase returned, and the services that phase ran with.
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
export interface HostedWorkflow {
|
|
77
|
+
readonly kind: "workflow";
|
|
78
|
+
readonly workflow: EffectWorkflow.Any;
|
|
79
|
+
readonly execute: (payload: any, executionId: string) => Effect.Effect<any, any, any>;
|
|
80
|
+
readonly context: Context.Context<never>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type WorkflowRuntimeContext = HostedRuntimeContext<HostedWorkflow>;
|
|
84
|
+
|
|
85
|
+
/** Services the Construction phase may use without the caller providing them. */
|
|
86
|
+
type WorkflowServices = ClusterWorkflow | Providers | RuntimeContext | Self | Self<ClusterWorkflow>;
|
|
87
|
+
|
|
88
|
+
/** The workflow behind each declared resource id, for the runtime context. */
|
|
89
|
+
const workflows = new Map<string, EffectWorkflow.Any>();
|
|
90
|
+
|
|
91
|
+
/** Every `Cluster.Workflow` declared, by workflow name, for the engine. */
|
|
92
|
+
const declarations = new Map<string, Effect.Effect<ClusterWorkflow, never, any>>();
|
|
93
|
+
|
|
94
|
+
const platform = Platform(WorkflowTypeId, {
|
|
95
|
+
createRuntimeContext: makeHostedRuntimeContext<HostedWorkflow>(
|
|
96
|
+
WorkflowTypeId,
|
|
97
|
+
(id, execute, context) => ({
|
|
98
|
+
kind: "workflow",
|
|
99
|
+
workflow: workflows.get(id)!,
|
|
100
|
+
execute: execute as HostedWorkflow["execute"],
|
|
101
|
+
context,
|
|
102
|
+
}),
|
|
103
|
+
),
|
|
104
|
+
}) as unknown as Platform<ClusterWorkflow, WorkflowServices, unknown, WorkflowRuntimeContext>;
|
|
105
|
+
|
|
106
|
+
/** The resource type behind every workflow, which the providers register. @internal */
|
|
107
|
+
export const WorkflowResource = platform;
|
|
108
|
+
|
|
109
|
+
/** The resource id of a workflow: kept apart from an entity of the same name. */
|
|
110
|
+
const resourceId = (name: string) => `${name}Workflow`;
|
|
111
|
+
|
|
112
|
+
type AnyWorkflow = EffectWorkflow.Workflow<string, any, any, any>;
|
|
113
|
+
|
|
114
|
+
type PayloadOf<W> = W extends EffectWorkflow.Workflow<any, infer P, any, any> ? P : never;
|
|
115
|
+
type SuccessOf<W> = W extends EffectWorkflow.Workflow<any, any, infer S, any> ? S : never;
|
|
116
|
+
type ErrorOf<W> = W extends EffectWorkflow.Workflow<any, any, any, infer E> ? E : never;
|
|
117
|
+
|
|
118
|
+
/** A workflow's handler, typed by its declaration. */
|
|
119
|
+
export type WorkflowHandler<W> = Execute<
|
|
120
|
+
PayloadOf<W> & EffectWorkflow.AnyStructSchema,
|
|
121
|
+
SuccessOf<W> & Schema.Top,
|
|
122
|
+
ErrorOf<W> & Schema.Top,
|
|
123
|
+
any
|
|
124
|
+
>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* A declared workflow: the tag callers `yield*` for its client, carrying
|
|
128
|
+
* Effect's workflow surface (`.workflow`, `.execute`, `.withCompensation`),
|
|
129
|
+
* implemented by `.make(props, impl)`.
|
|
130
|
+
*/
|
|
131
|
+
export type WorkflowTag<Self, W extends AnyWorkflow> = Effect.Effect<
|
|
132
|
+
WorkflowClient<W>,
|
|
133
|
+
never,
|
|
134
|
+
Providers
|
|
135
|
+
> &
|
|
136
|
+
Named<W["_tag"]> &
|
|
137
|
+
PlatformIdentity<W["_tag"]> & {
|
|
138
|
+
/** The Effect workflow behind the class, for Effect's own APIs. */
|
|
139
|
+
readonly workflow: W;
|
|
140
|
+
/** Run it as a child of the current workflow, as Effect's `Workflow.execute`. */
|
|
141
|
+
readonly execute: W["execute"];
|
|
142
|
+
/** Undo a step when the workflow fails later, as Effect's `withCompensation`. */
|
|
143
|
+
readonly withCompensation: W["withCompensation"];
|
|
144
|
+
/** Type the workflow's handler against its payload, success and error. */
|
|
145
|
+
of<Handler extends WorkflowHandler<W>>(handler: Handler): Handler;
|
|
146
|
+
/**
|
|
147
|
+
* The workflow's implementation, as a Layer: an Effect that resolves
|
|
148
|
+
* bindings once per runner and returns the handler. Provide a pool
|
|
149
|
+
* Layer to `impl` to deploy it; provide the returned Layer to the Stack.
|
|
150
|
+
*/
|
|
151
|
+
make<InitReq = never>(
|
|
152
|
+
props: InputProps<WorkflowProps>,
|
|
153
|
+
impl: Effect.Effect<WorkflowHandler<W>, ConfigError.ConfigError, InitReq>,
|
|
154
|
+
): Layer.Layer<
|
|
155
|
+
Self | Implemented<W["_tag"]>,
|
|
156
|
+
never,
|
|
157
|
+
Providers | Exclude<InitReq, WorkflowServices | PlatformServices>
|
|
158
|
+
>;
|
|
159
|
+
new (_: never): WorkflowClient<W>;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A workflow declared with its implementation: the class callers `yield*`,
|
|
164
|
+
* which also deploys it.
|
|
165
|
+
*/
|
|
166
|
+
export type WorkflowInline<W extends AnyWorkflow, Req> = Effect.Effect<
|
|
167
|
+
WorkflowClient<W>,
|
|
168
|
+
never,
|
|
169
|
+
Req
|
|
170
|
+
> &
|
|
171
|
+
Named<W["_tag"]> &
|
|
172
|
+
PlatformIdentity<W["_tag"]> & {
|
|
173
|
+
/** The Effect workflow behind the class, for Effect's own APIs. */
|
|
174
|
+
readonly workflow: W;
|
|
175
|
+
/** Run it as a child of the current workflow, as Effect's `Workflow.execute`. */
|
|
176
|
+
readonly execute: W["execute"];
|
|
177
|
+
/** Undo a step when the workflow fails later, as Effect's `withCompensation`. */
|
|
178
|
+
readonly withCompensation: W["withCompensation"];
|
|
179
|
+
/** Type the workflow's handler against its payload, success and error. */
|
|
180
|
+
of<Handler extends WorkflowHandler<W>>(handler: Handler): Handler;
|
|
181
|
+
new (_: never): WorkflowClient<W>;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
type WorkflowOf<
|
|
185
|
+
Name extends string,
|
|
186
|
+
Payload extends Schema.Struct.Fields | EffectWorkflow.AnyStructSchema,
|
|
187
|
+
Success extends Schema.Top,
|
|
188
|
+
Error extends Schema.Top,
|
|
189
|
+
> = EffectWorkflow.Workflow<
|
|
190
|
+
Name,
|
|
191
|
+
Payload extends Schema.Struct.Fields ? Schema.Struct<Payload> : Payload,
|
|
192
|
+
Success,
|
|
193
|
+
Error
|
|
194
|
+
>;
|
|
195
|
+
|
|
196
|
+
/** The class surface of {@link Workflow}. */
|
|
197
|
+
export interface WorkflowClass {
|
|
198
|
+
// `Self` names the class (`extends Cluster.Workflow<PlaceOrder>()`).
|
|
199
|
+
<Self>(): {
|
|
200
|
+
/**
|
|
201
|
+
* Declared like Effect's `Workflow.make`, with its implementation: an
|
|
202
|
+
* Effect that resolves bindings once per runner and returns the
|
|
203
|
+
* handler. Provide a pool Layer to `impl` to deploy it.
|
|
204
|
+
*/
|
|
205
|
+
<
|
|
206
|
+
const Name extends string,
|
|
207
|
+
Payload extends Schema.Struct.Fields | EffectWorkflow.AnyStructSchema,
|
|
208
|
+
Success extends Schema.Top = Schema.Void,
|
|
209
|
+
Error extends Schema.Top = Schema.Never,
|
|
210
|
+
InitReq = never,
|
|
211
|
+
>(
|
|
212
|
+
name: Name,
|
|
213
|
+
options: Parameters<typeof EffectWorkflow.make<Name, Payload, Success, Error>>[1],
|
|
214
|
+
props: InputProps<WorkflowProps>,
|
|
215
|
+
impl: Effect.Effect<
|
|
216
|
+
WorkflowHandler<WorkflowOf<Name, Payload, Success, Error>>,
|
|
217
|
+
ConfigError.ConfigError,
|
|
218
|
+
InitReq
|
|
219
|
+
>,
|
|
220
|
+
): WorkflowInline<
|
|
221
|
+
WorkflowOf<Name, Payload, Success, Error>,
|
|
222
|
+
Providers | Exclude<InitReq, WorkflowServices | PlatformServices>
|
|
223
|
+
>;
|
|
224
|
+
/** Declared like Effect's `Workflow.make`, implemented by `.make(props, impl)`. */
|
|
225
|
+
<
|
|
226
|
+
const Name extends string,
|
|
227
|
+
Payload extends Schema.Struct.Fields | EffectWorkflow.AnyStructSchema,
|
|
228
|
+
Success extends Schema.Top = Schema.Void,
|
|
229
|
+
Error extends Schema.Top = Schema.Never,
|
|
230
|
+
>(
|
|
231
|
+
name: Name,
|
|
232
|
+
options: Parameters<typeof EffectWorkflow.make<Name, Payload, Success, Error>>[1],
|
|
233
|
+
): WorkflowTag<Self, WorkflowOf<Name, Payload, Success, Error>>;
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* A durable **Workflow** on an Effect Cluster: each run is a workflow
|
|
239
|
+
* entity, so its activities are persisted, `DurableClock.sleep` is a
|
|
240
|
+
* delayed message, and a run continues on another runner if its runner
|
|
241
|
+
* dies.
|
|
242
|
+
*
|
|
243
|
+
* Declare it like Effect's `Workflow.make`, from a name and its schemas,
|
|
244
|
+
* with its implementation: an Effect that resolves bindings once per runner
|
|
245
|
+
* and returns the handler. Providing a pool Layer to the implementation is
|
|
246
|
+
* what deploys it. Or declare it bare and implement it with
|
|
247
|
+
* `.make(props, impl)`, as a Layer: callers import the bare declaration
|
|
248
|
+
* without bundling the implementation or its pool.
|
|
249
|
+
*
|
|
250
|
+
* `yield* PlaceOrder` from any runtime returns its client: `start` a run,
|
|
251
|
+
* then read, signal or interrupt it.
|
|
252
|
+
*
|
|
253
|
+
* ### Declaring a workflow
|
|
254
|
+
* **Example:** A workflow, with its implementation
|
|
255
|
+
* ```typescript
|
|
256
|
+
* // src/PlaceOrder.ts
|
|
257
|
+
* export default class PlaceOrder extends Cluster.Workflow<PlaceOrder>()(
|
|
258
|
+
* "PlaceOrder",
|
|
259
|
+
* {
|
|
260
|
+
* payload: { orderId: Schema.String },
|
|
261
|
+
* success: Schema.String,
|
|
262
|
+
* idempotencyKey: ({ orderId }) => orderId,
|
|
263
|
+
* },
|
|
264
|
+
* { main: import.meta.url },
|
|
265
|
+
* Effect.gen(function* () {
|
|
266
|
+
* const counters = yield* Counter;
|
|
267
|
+
* return Effect.fn(function* ({ orderId }) {
|
|
268
|
+
* const n = yield* Activity.make({
|
|
269
|
+
* name: "count",
|
|
270
|
+
* success: Schema.Int,
|
|
271
|
+
* execute: Effect.flatMap(counters.getByName("orders"), (counter) =>
|
|
272
|
+
* counter.Increment({ amount: 1 }),
|
|
273
|
+
* ),
|
|
274
|
+
* });
|
|
275
|
+
* return `order ${orderId} is #${n}`;
|
|
276
|
+
* });
|
|
277
|
+
* }).pipe(Effect.provide(Default)),
|
|
278
|
+
* ) {}
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* ### Keeping its code out of its callers
|
|
282
|
+
* **Example:** A bare declaration, which callers import
|
|
283
|
+
* ```typescript
|
|
284
|
+
* // src/PlaceOrder.ts
|
|
285
|
+
* export class PlaceOrder extends Cluster.Workflow<PlaceOrder>()("PlaceOrder", {
|
|
286
|
+
* payload: { orderId: Schema.String },
|
|
287
|
+
* success: Schema.String,
|
|
288
|
+
* idempotencyKey: ({ orderId }) => orderId,
|
|
289
|
+
* }) {}
|
|
290
|
+
* ```
|
|
291
|
+
*
|
|
292
|
+
* **Example:** Its implementation, as a Layer, deployed to a pool
|
|
293
|
+
* ```typescript
|
|
294
|
+
* // src/PlaceOrderLive.ts
|
|
295
|
+
* export default PlaceOrder.make(
|
|
296
|
+
* { main: import.meta.url },
|
|
297
|
+
* Effect.gen(function* () {
|
|
298
|
+
* const counters = yield* Counter;
|
|
299
|
+
* return PlaceOrder.of(
|
|
300
|
+
* Effect.fn(function* ({ orderId }) {
|
|
301
|
+
* const n = yield* Activity.make({
|
|
302
|
+
* name: "count",
|
|
303
|
+
* success: Schema.Int,
|
|
304
|
+
* execute: Effect.flatMap(counters.getByName("orders"), (counter) =>
|
|
305
|
+
* counter.Increment({ amount: 1 }),
|
|
306
|
+
* ),
|
|
307
|
+
* });
|
|
308
|
+
* return `order ${orderId} is #${n}`;
|
|
309
|
+
* }),
|
|
310
|
+
* );
|
|
311
|
+
* }).pipe(Effect.provide(Default)),
|
|
312
|
+
* );
|
|
313
|
+
* ```
|
|
314
|
+
*
|
|
315
|
+
* ### Running a workflow
|
|
316
|
+
* **Example:** Start a run from any runtime
|
|
317
|
+
* ```typescript
|
|
318
|
+
* const placeOrder = yield* PlaceOrder;
|
|
319
|
+
* const run = yield* placeOrder.start({ orderId: "o-1" });
|
|
320
|
+
* const status = yield* run.status;
|
|
321
|
+
* ```
|
|
322
|
+
*
|
|
323
|
+
* **Example:** A child workflow, inside another workflow
|
|
324
|
+
* ```typescript
|
|
325
|
+
* const receipt = yield* PlaceOrder.execute({ orderId: "o-1" });
|
|
326
|
+
* ```
|
|
327
|
+
*
|
|
328
|
+
* @resource
|
|
329
|
+
*/
|
|
330
|
+
export const Workflow: WorkflowClass = (() =>
|
|
331
|
+
(
|
|
332
|
+
name: string,
|
|
333
|
+
options: Parameters<typeof EffectWorkflow.make>[1],
|
|
334
|
+
props?: InputProps<WorkflowProps>,
|
|
335
|
+
impl?: unknown,
|
|
336
|
+
) => {
|
|
337
|
+
const workflow = EffectWorkflow.make(name, options) as AnyWorkflow;
|
|
338
|
+
const id = resourceId(name);
|
|
339
|
+
placeInShardGroup(workflow, id);
|
|
340
|
+
workflows.set(id, workflow);
|
|
341
|
+
const members = {
|
|
342
|
+
LogicalId: name,
|
|
343
|
+
workflow,
|
|
344
|
+
execute: workflow.execute.bind(workflow),
|
|
345
|
+
withCompensation: workflow.withCompensation,
|
|
346
|
+
of: (handler: unknown) => handler,
|
|
347
|
+
};
|
|
348
|
+
// With an implementation, the class deploys it, and a runner builds it.
|
|
349
|
+
if (impl !== undefined) {
|
|
350
|
+
const declaration = (platform as any)()(id, props, impl);
|
|
351
|
+
declarations.set(name, declaration);
|
|
352
|
+
return Object.assign(
|
|
353
|
+
class {},
|
|
354
|
+
members,
|
|
355
|
+
{ [PlatformDeclaration]: declaration },
|
|
356
|
+
Effectable.Prototype({
|
|
357
|
+
label: `${WorkflowTypeId}<${name}>`,
|
|
358
|
+
evaluate: () => clientOf(workflow, declaration),
|
|
359
|
+
}),
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
const tag = (platform as any)()(id);
|
|
363
|
+
declarations.set(name, tag);
|
|
364
|
+
return Object.assign(
|
|
365
|
+
class {},
|
|
366
|
+
members,
|
|
367
|
+
{
|
|
368
|
+
make: (props: InputProps<WorkflowProps>, impl: unknown) => {
|
|
369
|
+
const live = tag.make(props, impl) as Layer.Layer<never, never, any>;
|
|
370
|
+
// A runner builds the Platform declaration; the Stack, both.
|
|
371
|
+
return Object.assign(Layer.merge(live, implemented([name])), {
|
|
372
|
+
[PlatformDeclaration]: live,
|
|
373
|
+
});
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
Effectable.Prototype({
|
|
377
|
+
label: `${WorkflowTypeId}<${name}>`,
|
|
378
|
+
evaluate: () => clientOf(workflow, tag),
|
|
379
|
+
}),
|
|
380
|
+
);
|
|
381
|
+
}) as unknown as WorkflowClass;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Bind the gateway of the pool that hosts `name` into the current
|
|
385
|
+
* runtime: from its resource while deploying, from the environment at
|
|
386
|
+
* runtime. Undefined outside any runtime.
|
|
387
|
+
*/
|
|
388
|
+
const routeOf = (
|
|
389
|
+
name: string,
|
|
390
|
+
declaration: Effect.Effect<ClusterWorkflow, never, any> | undefined,
|
|
391
|
+
) =>
|
|
392
|
+
Effect.gen(function* () {
|
|
393
|
+
const ctx = Option.getOrUndefined(yield* Effect.serviceOption(RuntimeContext));
|
|
394
|
+
if (ctx === undefined) return undefined;
|
|
395
|
+
if (!globalThis.__ALCHEMY_RUNTIME__ && declaration === undefined) {
|
|
396
|
+
return yield* Effect.die(new WorkflowNotHosted({ workflow: name }));
|
|
397
|
+
}
|
|
398
|
+
// Built on a memo map of its own, as an entity's client is.
|
|
399
|
+
const resource = globalThis.__ALCHEMY_RUNTIME__
|
|
400
|
+
? undefined
|
|
401
|
+
: yield* declaration!.pipe(
|
|
402
|
+
Effect.provideServiceEffect(Layer.CurrentMemoMap, Layer.makeMemoMap),
|
|
403
|
+
);
|
|
404
|
+
const bind = <T>(key: string, value: unknown) =>
|
|
405
|
+
ctx
|
|
406
|
+
.set(sanitizeKey(key), Output.isOutput(value) ? value : Output.literal(value))
|
|
407
|
+
.pipe(Effect.flatMap((bound) => ctx.get<T>(bound)));
|
|
408
|
+
const url = yield* bind<string>(`CLUSTER_WORKFLOW_${name}_GATEWAY`, resource?.gateway);
|
|
409
|
+
const token = yield* bind<Redacted.Redacted<string> | string>(
|
|
410
|
+
`CLUSTER_WORKFLOW_${name}_TOKEN`,
|
|
411
|
+
resource?.token,
|
|
412
|
+
);
|
|
413
|
+
// A Worker calling a gateway Worker reaches it through a service binding.
|
|
414
|
+
const service = sanitizeKey(`CLUSTER_WORKFLOW_${name}_SERVICE`);
|
|
415
|
+
if (!globalThis.__ALCHEMY_RUNTIME__) yield* bindRoute(resourceId(name), service);
|
|
416
|
+
return {
|
|
417
|
+
url: url ?? "",
|
|
418
|
+
token: token === undefined ? "" : Redacted.isRedacted(token) ? Redacted.value(token) : token,
|
|
419
|
+
service,
|
|
420
|
+
} satisfies GatewayTarget;
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const unrouted = (name: string, inRuntime: boolean) =>
|
|
424
|
+
Effect.die(
|
|
425
|
+
inRuntime
|
|
426
|
+
? new WorkflowNotHosted({ workflow: name })
|
|
427
|
+
: new WorkflowEngineOutsideRuntime({
|
|
428
|
+
message: `Cluster.Workflow '${name}' can only be started inside a runtime.`,
|
|
429
|
+
}),
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
/** `yield* PlaceOrder`: its client, over the gateway of the pool that hosts it. */
|
|
433
|
+
const clientOf = (workflow: AnyWorkflow, declaration: Effect.Effect<ClusterWorkflow, never, any>) =>
|
|
434
|
+
Effect.gen(function* () {
|
|
435
|
+
const target = yield* routeOf(workflow._tag, declaration);
|
|
436
|
+
const engine = makeRemoteEngine(() =>
|
|
437
|
+
target === undefined ? unrouted(workflow._tag, false) : Effect.succeed(target),
|
|
438
|
+
);
|
|
439
|
+
const run = (executionId: string) => makeRun(engine, workflow, executionId);
|
|
440
|
+
return {
|
|
441
|
+
start: (payload) =>
|
|
442
|
+
workflow
|
|
443
|
+
.execute(payload, { discard: true })
|
|
444
|
+
.pipe(
|
|
445
|
+
Effect.provideService(EffectWorkflowEngine.WorkflowEngine, engine),
|
|
446
|
+
Effect.map(run),
|
|
447
|
+
),
|
|
448
|
+
get: run,
|
|
449
|
+
} as WorkflowClient<AnyWorkflow>;
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Place a workflow in its pool's shard group, read when a run is routed:
|
|
454
|
+
* the pool that hosts it has the group in their environment, so a child
|
|
455
|
+
* run started on any of them reaches them.
|
|
456
|
+
*/
|
|
457
|
+
const placeInShardGroup = (workflow: EffectWorkflow.Any, id: string) => {
|
|
458
|
+
const key = shardGroupKey(id);
|
|
459
|
+
const self = workflow as unknown as { annotations: Context.Context<never> };
|
|
460
|
+
self.annotations = Context.add(
|
|
461
|
+
self.annotations,
|
|
462
|
+
ClusterSchema.ShardGroup,
|
|
463
|
+
() =>
|
|
464
|
+
// A synchronous lookup at routing time; workerd has no `process`.
|
|
465
|
+
(globalThis as { process?: { env: Record<string, string | undefined> } }).process?.env[key] ??
|
|
466
|
+
"default",
|
|
467
|
+
);
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
/** A workflow started outside any runtime. */
|
|
471
|
+
export class WorkflowEngineOutsideRuntime extends Data.TaggedError(
|
|
472
|
+
"Cluster.WorkflowEngineOutsideRuntime",
|
|
473
|
+
)<{
|
|
474
|
+
readonly message: string;
|
|
475
|
+
}> {}
|
|
476
|
+
|
|
477
|
+
/** No `Cluster.Workflow` in the Stack hosts the workflow. */
|
|
478
|
+
export class WorkflowNotHosted extends Data.TaggedError("Cluster.WorkflowNotHosted")<{
|
|
479
|
+
readonly workflow: string;
|
|
480
|
+
}> {}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Run `Cluster.Workflow`s through Alchemy's engine-neutral
|
|
484
|
+
* `Workflows.RunWorkflow`: a `WorkflowEngine` that reaches each workflow
|
|
485
|
+
* through the gateway of the pool that hosts it. Use it for code that
|
|
486
|
+
* runs the same workflow on any engine; otherwise `yield*` the workflow.
|
|
487
|
+
*
|
|
488
|
+
* ### Running workflows on any engine
|
|
489
|
+
* **Example:** `Workflows.RunWorkflow` on the cluster
|
|
490
|
+
* ```typescript
|
|
491
|
+
* Effect.gen(function* () {
|
|
492
|
+
* const placeOrder = yield* Workflows.RunWorkflow(PlaceOrder.workflow);
|
|
493
|
+
* return {
|
|
494
|
+
* fetch: Effect.gen(function* () {
|
|
495
|
+
* const run = yield* placeOrder.start({ orderId: "o-1" });
|
|
496
|
+
* return yield* HttpServerResponse.json(yield* run.status);
|
|
497
|
+
* }),
|
|
498
|
+
* };
|
|
499
|
+
* }).pipe(Effect.provide(Cluster.WorkflowEngine(PlaceOrder)));
|
|
500
|
+
* ```
|
|
501
|
+
*
|
|
502
|
+
* @layer
|
|
503
|
+
* @provides WorkflowEngine
|
|
504
|
+
*/
|
|
505
|
+
export const WorkflowEngine = <
|
|
506
|
+
const Workflows extends ReadonlyArray<{ readonly workflow: EffectWorkflow.Any }>,
|
|
507
|
+
>(
|
|
508
|
+
...runs: Workflows
|
|
509
|
+
): Layer.Layer<
|
|
510
|
+
EffectWorkflowEngine.WorkflowEngine | Implemented<Workflows[number]["workflow"]["_tag"]>,
|
|
511
|
+
never,
|
|
512
|
+
Providers
|
|
513
|
+
> =>
|
|
514
|
+
Layer.merge(
|
|
515
|
+
Layer.effect(
|
|
516
|
+
EffectWorkflowEngine.WorkflowEngine,
|
|
517
|
+
Effect.gen(function* () {
|
|
518
|
+
const inRuntime = Option.isSome(yield* Effect.serviceOption(RuntimeContext));
|
|
519
|
+
const routes = new Map<string, GatewayTarget>();
|
|
520
|
+
for (const { workflow } of runs) {
|
|
521
|
+
const target = yield* routeOf(workflow._tag, declarations.get(workflow._tag));
|
|
522
|
+
if (target !== undefined) routes.set(workflow._tag, target);
|
|
523
|
+
}
|
|
524
|
+
return makeRemoteEngine((name) => {
|
|
525
|
+
const target = routes.get(name);
|
|
526
|
+
return target === undefined ? unrouted(name, inRuntime) : Effect.succeed(target);
|
|
527
|
+
});
|
|
528
|
+
}),
|
|
529
|
+
),
|
|
530
|
+
implemented(runs.map(({ workflow }) => workflow._tag)),
|
|
531
|
+
) as Layer.Layer<
|
|
532
|
+
EffectWorkflowEngine.WorkflowEngine | Implemented<Workflows[number]["workflow"]["_tag"]>,
|
|
533
|
+
never,
|
|
534
|
+
Providers
|
|
535
|
+
>;
|
|
536
|
+
|
|
537
|
+
export const WorkflowProvider = () =>
|
|
538
|
+
Provider.succeed(platform, {
|
|
539
|
+
stables: ["workflow"],
|
|
540
|
+
reconcile: ({ id, bindings }) =>
|
|
541
|
+
Effect.sync(() => {
|
|
542
|
+
const attrs: WorkflowAttributes = {
|
|
543
|
+
workflow: id.replace(/Workflow$/, ""),
|
|
544
|
+
pool: undefined,
|
|
545
|
+
gateway: undefined,
|
|
546
|
+
token: undefined,
|
|
547
|
+
};
|
|
548
|
+
for (const { data } of bindings) {
|
|
549
|
+
attrs.pool = data.pool ?? attrs.pool;
|
|
550
|
+
attrs.gateway = data.gateway ?? attrs.gateway;
|
|
551
|
+
attrs.token = data.token ?? attrs.token;
|
|
552
|
+
}
|
|
553
|
+
return attrs;
|
|
554
|
+
}),
|
|
555
|
+
delete: () => Effect.void,
|
|
556
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @alchemy.run/effect-cluster — Effect Cluster entities, with the database
|
|
3
|
+
* they coordinate through and the pool that hosts them on any cloud.
|
|
4
|
+
*/
|
|
5
|
+
export * as Database from "./Database.ts";
|
|
6
|
+
export * from "./Entity.ts";
|
|
7
|
+
export * from "./Providers.ts";
|
|
8
|
+
export * from "./Pool.ts";
|
|
9
|
+
export * from "./Workflow.ts";
|
|
10
|
+
export * from "./Singleton.ts";
|
|
11
|
+
export * from "./Cron.ts";
|