@alchemy.run/effect-cluster 0.0.0 → 2.0.0-beta.87

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.
Files changed (233) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +101 -4
  3. package/lib/AWS/Database.d.ts +54 -0
  4. package/lib/AWS/Database.d.ts.map +1 -0
  5. package/lib/AWS/Database.js +96 -0
  6. package/lib/AWS/Database.js.map +1 -0
  7. package/lib/AWS/EcsPool.d.ts +56 -0
  8. package/lib/AWS/EcsPool.d.ts.map +1 -0
  9. package/lib/AWS/EcsPool.js +87 -0
  10. package/lib/AWS/EcsPool.js.map +1 -0
  11. package/lib/AWS/index.d.ts +7 -0
  12. package/lib/AWS/index.d.ts.map +1 -0
  13. package/lib/AWS/index.js +7 -0
  14. package/lib/AWS/index.js.map +1 -0
  15. package/lib/AWS/internal/Gateway.d.ts +23 -0
  16. package/lib/AWS/internal/Gateway.d.ts.map +1 -0
  17. package/lib/AWS/internal/Gateway.js +70 -0
  18. package/lib/AWS/internal/Gateway.js.map +1 -0
  19. package/lib/Cloudflare/DurableObjectPool.d.ts +42 -0
  20. package/lib/Cloudflare/DurableObjectPool.d.ts.map +1 -0
  21. package/lib/Cloudflare/DurableObjectPool.js +136 -0
  22. package/lib/Cloudflare/DurableObjectPool.js.map +1 -0
  23. package/lib/Cloudflare/PoolWorker.d.ts +186 -0
  24. package/lib/Cloudflare/PoolWorker.d.ts.map +1 -0
  25. package/lib/Cloudflare/PoolWorker.js +75 -0
  26. package/lib/Cloudflare/PoolWorker.js.map +1 -0
  27. package/lib/Cloudflare/index.d.ts +7 -0
  28. package/lib/Cloudflare/index.d.ts.map +1 -0
  29. package/lib/Cloudflare/index.js +7 -0
  30. package/lib/Cloudflare/index.js.map +1 -0
  31. package/lib/Cloudflare/internal/Objects.d.ts +38 -0
  32. package/lib/Cloudflare/internal/Objects.d.ts.map +1 -0
  33. package/lib/Cloudflare/internal/Objects.js +102 -0
  34. package/lib/Cloudflare/internal/Objects.js.map +1 -0
  35. package/lib/Cron.d.ts +143 -0
  36. package/lib/Cron.d.ts.map +1 -0
  37. package/lib/Cron.js +83 -0
  38. package/lib/Cron.js.map +1 -0
  39. package/lib/Database.d.ts +95 -0
  40. package/lib/Database.d.ts.map +1 -0
  41. package/lib/Database.js +38 -0
  42. package/lib/Database.js.map +1 -0
  43. package/lib/Docker/Database.d.ts +42 -0
  44. package/lib/Docker/Database.d.ts.map +1 -0
  45. package/lib/Docker/Database.js +53 -0
  46. package/lib/Docker/Database.js.map +1 -0
  47. package/lib/Docker/SwarmPool.d.ts +44 -0
  48. package/lib/Docker/SwarmPool.d.ts.map +1 -0
  49. package/lib/Docker/SwarmPool.js +56 -0
  50. package/lib/Docker/SwarmPool.js.map +1 -0
  51. package/lib/Docker/index.d.ts +7 -0
  52. package/lib/Docker/index.d.ts.map +1 -0
  53. package/lib/Docker/index.js +7 -0
  54. package/lib/Docker/index.js.map +1 -0
  55. package/lib/Entity.d.ts +348 -0
  56. package/lib/Entity.d.ts.map +1 -0
  57. package/lib/Entity.js +308 -0
  58. package/lib/Entity.js.map +1 -0
  59. package/lib/Fly/Database.d.ts +50 -0
  60. package/lib/Fly/Database.d.ts.map +1 -0
  61. package/lib/Fly/Database.js +56 -0
  62. package/lib/Fly/Database.js.map +1 -0
  63. package/lib/Fly/MachinePool.d.ts +39 -0
  64. package/lib/Fly/MachinePool.d.ts.map +1 -0
  65. package/lib/Fly/MachinePool.js +51 -0
  66. package/lib/Fly/MachinePool.js.map +1 -0
  67. package/lib/Fly/index.d.ts +7 -0
  68. package/lib/Fly/index.d.ts.map +1 -0
  69. package/lib/Fly/index.js +7 -0
  70. package/lib/Fly/index.js.map +1 -0
  71. package/lib/GCP/ComputeEnginePool.d.ts +41 -0
  72. package/lib/GCP/ComputeEnginePool.d.ts.map +1 -0
  73. package/lib/GCP/ComputeEnginePool.js +172 -0
  74. package/lib/GCP/ComputeEnginePool.js.map +1 -0
  75. package/lib/GCP/Database.d.ts +48 -0
  76. package/lib/GCP/Database.d.ts.map +1 -0
  77. package/lib/GCP/Database.js +115 -0
  78. package/lib/GCP/Database.js.map +1 -0
  79. package/lib/GCP/index.d.ts +7 -0
  80. package/lib/GCP/index.d.ts.map +1 -0
  81. package/lib/GCP/index.js +7 -0
  82. package/lib/GCP/index.js.map +1 -0
  83. package/lib/Hetzner/Database.d.ts +48 -0
  84. package/lib/Hetzner/Database.d.ts.map +1 -0
  85. package/lib/Hetzner/Database.js +91 -0
  86. package/lib/Hetzner/Database.js.map +1 -0
  87. package/lib/Hetzner/ServerPool.d.ts +35 -0
  88. package/lib/Hetzner/ServerPool.d.ts.map +1 -0
  89. package/lib/Hetzner/ServerPool.js +98 -0
  90. package/lib/Hetzner/ServerPool.js.map +1 -0
  91. package/lib/Hetzner/index.d.ts +7 -0
  92. package/lib/Hetzner/index.d.ts.map +1 -0
  93. package/lib/Hetzner/index.js +7 -0
  94. package/lib/Hetzner/index.js.map +1 -0
  95. package/lib/Host.d.ts +60 -0
  96. package/lib/Host.d.ts.map +1 -0
  97. package/lib/Host.js +31 -0
  98. package/lib/Host.js.map +1 -0
  99. package/lib/Kubernetes/Database.d.ts +40 -0
  100. package/lib/Kubernetes/Database.d.ts.map +1 -0
  101. package/lib/Kubernetes/Database.js +56 -0
  102. package/lib/Kubernetes/Database.js.map +1 -0
  103. package/lib/Kubernetes/DeploymentPool.d.ts +54 -0
  104. package/lib/Kubernetes/DeploymentPool.d.ts.map +1 -0
  105. package/lib/Kubernetes/DeploymentPool.js +49 -0
  106. package/lib/Kubernetes/DeploymentPool.js.map +1 -0
  107. package/lib/Kubernetes/index.d.ts +7 -0
  108. package/lib/Kubernetes/index.d.ts.map +1 -0
  109. package/lib/Kubernetes/index.js +7 -0
  110. package/lib/Kubernetes/index.js.map +1 -0
  111. package/lib/Pool.d.ts +66 -0
  112. package/lib/Pool.d.ts.map +1 -0
  113. package/lib/Pool.js +47 -0
  114. package/lib/Pool.js.map +1 -0
  115. package/lib/Providers.d.ts +19 -0
  116. package/lib/Providers.d.ts.map +1 -0
  117. package/lib/Providers.js +31 -0
  118. package/lib/Providers.js.map +1 -0
  119. package/lib/ShardGroups.d.ts +24 -0
  120. package/lib/ShardGroups.d.ts.map +1 -0
  121. package/lib/ShardGroups.js +19 -0
  122. package/lib/ShardGroups.js.map +1 -0
  123. package/lib/Singleton.d.ts +110 -0
  124. package/lib/Singleton.d.ts.map +1 -0
  125. package/lib/Singleton.js +60 -0
  126. package/lib/Singleton.js.map +1 -0
  127. package/lib/Workflow.d.ts +251 -0
  128. package/lib/Workflow.d.ts.map +1 -0
  129. package/lib/Workflow.js +288 -0
  130. package/lib/Workflow.js.map +1 -0
  131. package/lib/index.d.ts +12 -0
  132. package/lib/index.d.ts.map +1 -0
  133. package/lib/index.js +12 -0
  134. package/lib/index.js.map +1 -0
  135. package/lib/internal/Bootstrap.d.ts +28 -0
  136. package/lib/internal/Bootstrap.d.ts.map +1 -0
  137. package/lib/internal/Bootstrap.js +257 -0
  138. package/lib/internal/Bootstrap.js.map +1 -0
  139. package/lib/internal/EntityRuntime.d.ts +40 -0
  140. package/lib/internal/EntityRuntime.d.ts.map +1 -0
  141. package/lib/internal/EntityRuntime.js +698 -0
  142. package/lib/internal/EntityRuntime.js.map +1 -0
  143. package/lib/internal/Gateway.d.ts +82 -0
  144. package/lib/internal/Gateway.d.ts.map +1 -0
  145. package/lib/internal/Gateway.js +219 -0
  146. package/lib/internal/Gateway.js.map +1 -0
  147. package/lib/internal/HostedPlatform.d.ts +36 -0
  148. package/lib/internal/HostedPlatform.d.ts.map +1 -0
  149. package/lib/internal/HostedPlatform.js +36 -0
  150. package/lib/internal/HostedPlatform.js.map +1 -0
  151. package/lib/internal/HostedRuntime.d.ts +58 -0
  152. package/lib/internal/HostedRuntime.d.ts.map +1 -0
  153. package/lib/internal/HostedRuntime.js +72 -0
  154. package/lib/internal/HostedRuntime.js.map +1 -0
  155. package/lib/internal/Naming.d.ts +12 -0
  156. package/lib/internal/Naming.d.ts.map +1 -0
  157. package/lib/internal/Naming.js +40 -0
  158. package/lib/internal/Naming.js.map +1 -0
  159. package/lib/internal/ObjectSharding.d.ts +141 -0
  160. package/lib/internal/ObjectSharding.d.ts.map +1 -0
  161. package/lib/internal/ObjectSharding.js +337 -0
  162. package/lib/internal/ObjectSharding.js.map +1 -0
  163. package/lib/internal/PoolHost.d.ts +80 -0
  164. package/lib/internal/PoolHost.d.ts.map +1 -0
  165. package/lib/internal/PoolHost.js +203 -0
  166. package/lib/internal/PoolHost.js.map +1 -0
  167. package/lib/internal/PoolHostService.d.ts +25 -0
  168. package/lib/internal/PoolHostService.d.ts.map +1 -0
  169. package/lib/internal/PoolHostService.js +10 -0
  170. package/lib/internal/PoolHostService.js.map +1 -0
  171. package/lib/internal/Wire.d.ts +60 -0
  172. package/lib/internal/Wire.d.ts.map +1 -0
  173. package/lib/internal/Wire.js +36 -0
  174. package/lib/internal/Wire.js.map +1 -0
  175. package/lib/internal/WorkflowGateway.d.ts +36 -0
  176. package/lib/internal/WorkflowGateway.d.ts.map +1 -0
  177. package/lib/internal/WorkflowGateway.js +124 -0
  178. package/lib/internal/WorkflowGateway.js.map +1 -0
  179. package/lib/internal/external-runner.d.ts +2 -0
  180. package/lib/internal/external-runner.d.ts.map +1 -0
  181. package/lib/internal/external-runner.js +7 -0
  182. package/lib/internal/external-runner.js.map +1 -0
  183. package/lib/internal/runner.d.ts +2 -0
  184. package/lib/internal/runner.d.ts.map +1 -0
  185. package/lib/internal/runner.js +5 -0
  186. package/lib/internal/runner.js.map +1 -0
  187. package/package.json +128 -3
  188. package/src/AWS/Database.ts +139 -0
  189. package/src/AWS/EcsPool.ts +118 -0
  190. package/src/AWS/index.ts +6 -0
  191. package/src/AWS/internal/Gateway.ts +82 -0
  192. package/src/Cloudflare/DurableObjectPool.ts +198 -0
  193. package/src/Cloudflare/PoolWorker.ts +124 -0
  194. package/src/Cloudflare/index.ts +6 -0
  195. package/src/Cloudflare/internal/Objects.ts +160 -0
  196. package/src/Cron.ts +202 -0
  197. package/src/Database.ts +116 -0
  198. package/src/Docker/Database.ts +94 -0
  199. package/src/Docker/SwarmPool.ts +83 -0
  200. package/src/Docker/index.ts +6 -0
  201. package/src/Entity.ts +621 -0
  202. package/src/Fly/Database.ts +102 -0
  203. package/src/Fly/MachinePool.ts +72 -0
  204. package/src/Fly/index.ts +6 -0
  205. package/src/GCP/ComputeEnginePool.ts +203 -0
  206. package/src/GCP/Database.ts +168 -0
  207. package/src/GCP/index.ts +6 -0
  208. package/src/Hetzner/Database.ts +142 -0
  209. package/src/Hetzner/ServerPool.ts +116 -0
  210. package/src/Hetzner/index.ts +6 -0
  211. package/src/Host.ts +68 -0
  212. package/src/Kubernetes/Database.ts +89 -0
  213. package/src/Kubernetes/DeploymentPool.ts +77 -0
  214. package/src/Kubernetes/index.ts +6 -0
  215. package/src/Pool.ts +104 -0
  216. package/src/Providers.ts +49 -0
  217. package/src/ShardGroups.ts +41 -0
  218. package/src/Singleton.ts +153 -0
  219. package/src/Workflow.ts +556 -0
  220. package/src/index.ts +11 -0
  221. package/src/internal/Bootstrap.ts +368 -0
  222. package/src/internal/EntityRuntime.ts +996 -0
  223. package/src/internal/Gateway.ts +367 -0
  224. package/src/internal/HostedPlatform.ts +83 -0
  225. package/src/internal/HostedRuntime.ts +118 -0
  226. package/src/internal/Naming.ts +44 -0
  227. package/src/internal/ObjectSharding.ts +578 -0
  228. package/src/internal/PoolHost.ts +330 -0
  229. package/src/internal/PoolHostService.ts +25 -0
  230. package/src/internal/Wire.ts +83 -0
  231. package/src/internal/WorkflowGateway.ts +188 -0
  232. package/src/internal/external-runner.ts +10 -0
  233. package/src/internal/runner.ts +5 -0
@@ -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";