@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,116 @@
1
+ import type { Input } from "alchemy/Input";
2
+ import * as Config from "effect/Config";
3
+ import * as Context from "effect/Context";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Layer from "effect/Layer";
6
+ import * as Redacted from "effect/Redacted";
7
+ import type { Providers } from "./Providers.ts";
8
+ import { ShardGroups } from "./ShardGroups.ts";
9
+
10
+ /**
11
+ * The private network runners must join to reach the database and each
12
+ * other. A pool Layer matches on `kind` and refuses a network it cannot
13
+ * join.
14
+ */
15
+ export type ClusterNetwork =
16
+ | {
17
+ /** A Docker network; runner containers join it by name. */
18
+ readonly kind: "docker";
19
+ readonly name: Input<string>;
20
+ }
21
+ | {
22
+ /** An AWS VPC; runners run in its subnets, in the pool's security group. */
23
+ readonly kind: "aws";
24
+ readonly vpcId: Input<string>;
25
+ readonly subnetIds: Input<string>[];
26
+ /** The group every runner joins: it reaches the database and the other runners. */
27
+ readonly securityGroupId: Input<string>;
28
+ }
29
+ | {
30
+ /** A Fly private network; runners' Apps join it by name. */
31
+ readonly kind: "fly";
32
+ readonly network: Input<string>;
33
+ }
34
+ | {
35
+ /** A Hetzner private Network; runners' Servers attach to it. */
36
+ readonly kind: "hetzner";
37
+ readonly networkId: Input<number>;
38
+ /** Its IPv4 range, where each runner finds its own address. */
39
+ readonly ipRange: string;
40
+ readonly location: string;
41
+ }
42
+ | {
43
+ /** A GCP VPC; runners' instances join its subnetwork. */
44
+ readonly kind: "gcp";
45
+ readonly network: Input<string>;
46
+ readonly subnetwork: Input<string>;
47
+ readonly region: string;
48
+ /** The subnetwork's IPv4 range, where each runner finds its own address. */
49
+ readonly cidr: string;
50
+ /** The network tag every runner carries: it reaches the database and the other runners. */
51
+ readonly tag: string;
52
+ }
53
+ | {
54
+ /** A Kubernetes cluster; runners run as a Deployment in its namespace. */
55
+ readonly kind: "kubernetes";
56
+ /** The cluster, as `@alchemy.run/kubernetes` takes it. */
57
+ readonly cluster: unknown;
58
+ readonly namespace: string;
59
+ };
60
+
61
+ /** What every database Layer gives the pool it is provided to. */
62
+ export interface DatabaseShape {
63
+ /**
64
+ * The Postgres connection URL runners use for runner registration,
65
+ * shard locks and persisted messages.
66
+ */
67
+ readonly url: Input<Redacted.Redacted<string>>;
68
+ /** The network runners join to reach it, when it is private. */
69
+ readonly network?: ClusterNetwork;
70
+ /** Every shard group in the cluster, which each pool registers with. */
71
+ readonly shardGroups: ShardGroups;
72
+ }
73
+
74
+ /**
75
+ * The database an Effect Cluster coordinates through: runners register in
76
+ * it, lock their shards in it, and keep persisted messages in it. Each
77
+ * host's `Database` Layer provides it.
78
+ */
79
+ export class Database extends Context.Service<Database, DatabaseShape>()("Cluster.Database") {}
80
+
81
+ export interface DatabaseProps {
82
+ /** A Postgres connection URL. */
83
+ url: Redacted.Redacted<string> | Config.Config<Redacted.Redacted<string>>;
84
+ }
85
+
86
+ /**
87
+ * A Postgres database you already run, as an Effect Cluster `Database`
88
+ * Layer: Neon, PlanetScale, RDS, or any Postgres the pool can reach.
89
+ *
90
+ * ### Using an existing database
91
+ * **Example:** A Postgres URL from config
92
+ * ```typescript
93
+ * export const Database = Cluster.Database.fromUrl({
94
+ * url: Config.Redacted("CLUSTER_DATABASE_URL"),
95
+ * });
96
+ * ```
97
+ *
98
+ * @primitive
99
+ */
100
+ export const fromUrl = (
101
+ props: DatabaseProps,
102
+ ): Layer.Layer<Database, Config.ConfigError, Providers> =>
103
+ Layer.effect(
104
+ Database,
105
+ Effect.gen(function* () {
106
+ if (globalThis.__ALCHEMY_RUNTIME__) {
107
+ // Runners read their database from their own environment at runtime.
108
+ return { url: Redacted.make(""), shardGroups: undefined as never };
109
+ }
110
+ const url = Config.isConfig(props.url) ? yield* props.url : props.url;
111
+ return { url, shardGroups: yield* clusterShardGroups("Database") };
112
+ }),
113
+ );
114
+
115
+ /** The shard groups resource every database Layer declares for its cluster. @internal */
116
+ export const clusterShardGroups = (id: string) => ShardGroups(`${id}ShardGroups`, {});
@@ -0,0 +1,94 @@
1
+ import * as Docker from "@alchemy.run/docker";
2
+ import * as Output from "alchemy/Output";
3
+ import { Random } from "alchemy/Random";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Layer from "effect/Layer";
6
+ import * as Redacted from "effect/Redacted";
7
+ import { clusterShardGroups, Database as ClusterDatabase } from "../Database.ts";
8
+ import type { Providers } from "../Providers.ts";
9
+
10
+ /** The Postgres release `ClusterDocker.Database` runs by default. */
11
+ export const DEFAULT_POSTGRES_IMAGE = "postgres:17-alpine";
12
+
13
+ export interface DatabaseProps {
14
+ /**
15
+ * Postgres image.
16
+ * @default "postgres:17-alpine"
17
+ */
18
+ image?: string;
19
+ /**
20
+ * Publish Postgres on this host port, to inspect it from your machine.
21
+ * @default not published
22
+ */
23
+ port?: number;
24
+ /**
25
+ * The Docker engine to run on: a context name, a `Docker.Context`, or a
26
+ * `Docker.Swarm`.
27
+ * @default the current Docker context
28
+ */
29
+ context?: Docker.Docker.EngineRef;
30
+ }
31
+
32
+ /**
33
+ * The database an Effect Cluster coordinates through, on a Docker swarm, as
34
+ * an `Cluster.Database` Layer: Postgres as a one-replica swarm service
35
+ * with its data on a named volume, on an overlay network the pool's runners join.
36
+ * The Docker engine must be in swarm mode (`docker swarm init`).
37
+ *
38
+ * One Postgres on one disk is meant for development and tests.
39
+ *
40
+ * ### Running a local database
41
+ * **Example:** Postgres for local entities
42
+ * ```typescript
43
+ * export const Database = ClusterDocker.Database("Cluster");
44
+ * ```
45
+ *
46
+ * @primitive
47
+ */
48
+ export const Database = (
49
+ id: string,
50
+ props: DatabaseProps = {},
51
+ ): Layer.Layer<ClusterDatabase, never, Providers | Docker.Providers> =>
52
+ Layer.effect(
53
+ ClusterDatabase,
54
+ Effect.gen(function* () {
55
+ if (globalThis.__ALCHEMY_RUNTIME__) {
56
+ // Runners read their database from their own environment at runtime.
57
+ return { url: Redacted.make(""), shardGroups: undefined as never };
58
+ }
59
+ const context = props.context === undefined ? {} : { context: props.context };
60
+ const password = yield* Random(`${id}Password`, { bytes: 24 }) as unknown as Effect.Effect<
61
+ Random,
62
+ never,
63
+ Providers
64
+ >;
65
+ const network = yield* Docker.Network(`${id}Network`, { driver: "overlay", ...context });
66
+ const volumeContext = Docker.dockerEngineContextName(props.context);
67
+ const data = yield* Docker.Volume(
68
+ `${id}Data`,
69
+ volumeContext === undefined ? {} : { context: volumeContext },
70
+ );
71
+ const alias = "cluster-db";
72
+ const db = yield* Docker.Service(`${id}Postgres`, {
73
+ image: props.image ?? DEFAULT_POSTGRES_IMAGE,
74
+ ...context,
75
+ environment: { POSTGRES_PASSWORD: password.text, POSTGRES_DB: "cluster" },
76
+ ports: props.port === undefined ? [] : [{ external: props.port, internal: 5432 }],
77
+ volumes: [{ hostPath: data.name, containerPath: "/var/lib/postgresql/data" }],
78
+ networks: [{ name: network.name, aliases: [alias] }],
79
+ replicas: 1,
80
+ });
81
+ return {
82
+ // Ordered after the service: runners connect once it exists.
83
+ url: Output.all(db.name, password.text).pipe(
84
+ Output.map(([, text]) =>
85
+ Redacted.make(
86
+ `postgres://postgres:${encodeURIComponent(text.pipe(Redacted.value))}@${alias}:5432/cluster`,
87
+ ),
88
+ ),
89
+ ),
90
+ network: { kind: "docker" as const, name: network.name },
91
+ shardGroups: yield* clusterShardGroups(id),
92
+ };
93
+ }),
94
+ );
@@ -0,0 +1,83 @@
1
+ import * as Docker from "@alchemy.run/docker";
2
+ import * as Output from "alchemy/Output";
3
+ import type { Stack } from "alchemy/Stack";
4
+ import * as Effect from "effect/Effect";
5
+ import type * as Layer from "effect/Layer";
6
+ import type { Database } from "../Database.ts";
7
+ import { GATEWAY_PORT } from "../internal/Gateway.ts";
8
+ import { makePoolLayer, type PoolSettings } from "../internal/PoolHost.ts";
9
+ import type { PoolHost } from "../internal/PoolHostService.ts";
10
+ import type { Providers } from "../Providers.ts";
11
+
12
+ export interface SwarmPoolOptions extends PoolSettings {
13
+ /**
14
+ * Number of runners.
15
+ * @default 1
16
+ */
17
+ replicas?: number;
18
+ /**
19
+ * Publish the gateway on this host port, so code outside the swarm (a
20
+ * test, a script) can call the entities at `http://localhost:<port>`.
21
+ * @default not published
22
+ */
23
+ gatewayPort?: number;
24
+ }
25
+
26
+ /**
27
+ * Run a pool of Effect Cluster runners on a Docker swarm: a swarm service whose
28
+ * tasks host every `Cluster.Entity` given this pool, on the
29
+ * database's overlay network. Runners find each other through the
30
+ * database and talk on the network; the gateway is served on the same
31
+ * network. Requires an `Cluster.Database` on a Docker network —
32
+ * `ClusterDocker.Database`.
33
+ *
34
+ * Every entity given the same pool shares it: declare the pool once
35
+ * and provide them to each class.
36
+ *
37
+ * ### Running entities on Docker
38
+ * **Example:** Two runners on the local swarm
39
+ * ```typescript
40
+ * export const Database = ClusterDocker.Database("Cluster");
41
+ * export const Default = ClusterDocker.SwarmPool("Default", { replicas: 2 }).pipe(
42
+ * Layer.provide(Database),
43
+ * );
44
+ * ```
45
+ *
46
+ * @primitive
47
+ */
48
+ export const SwarmPool = (
49
+ id: string,
50
+ options: SwarmPoolOptions = {},
51
+ ): Layer.Layer<PoolHost, never, Database | Stack | Providers | Docker.Providers> =>
52
+ makePoolLayer({
53
+ label: "ClusterDocker.SwarmPool",
54
+ id,
55
+ settings: options,
56
+ expected: "a Docker network (ClusterDocker.Database)",
57
+ network: (database) => (database.network?.kind === "docker" ? database.network : undefined),
58
+ // Callers on the swarm reach the gateway by the service's name.
59
+ gateway: (name) => Effect.succeed({ url: `http://${name}:${GATEWAY_PORT}` }),
60
+ nodes: ({ network, name, program, environment }) =>
61
+ Effect.gen(function* () {
62
+ // Tagged with the program's hash, so a changed program is a new image.
63
+ const image = yield* Docker.Image(`${id}RunnerImage`, {
64
+ build: { context: program.pipe(Output.map((p) => p.dir)) as unknown as string },
65
+ tag: program.pipe(Output.map((p) => p.hash)) as unknown as string,
66
+ skipPush: true,
67
+ });
68
+ yield* Docker.Service(`${id}Pool`, {
69
+ name,
70
+ image: image.imageRef,
71
+ replicas: options.replicas ?? 1,
72
+ networks: [{ name: network.name }],
73
+ ports:
74
+ options.gatewayPort === undefined
75
+ ? []
76
+ : [{ external: options.gatewayPort, internal: GATEWAY_PORT }],
77
+ // Each runner finds its own address among the service's tasks.
78
+ environment: environment.pipe(
79
+ Output.map((env) => ({ ...env, CLUSTER_PEERS: `tasks.${name}` })),
80
+ ) as unknown as Record<string, string>,
81
+ });
82
+ }),
83
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @alchemy.run/effect-cluster/Docker — run an Effect Cluster's database and
3
+ * runners on a Docker swarm. Requires `@alchemy.run/docker`.
4
+ */
5
+ export * from "./Database.ts";
6
+ export * from "./SwarmPool.ts";