@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.
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 +218 -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 +366 -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,98 @@
1
+ import * as Hetzner from "@alchemy.run/hetzner";
2
+ import * as Output from "alchemy/Output";
3
+ import * as Effect from "effect/Effect";
4
+ import { GATEWAY_PORT } from "../internal/Gateway.js";
5
+ import { makePoolLayer } from "../internal/PoolHost.js";
6
+ /** The label the gateway's Load Balancer finds the pool's Servers by. */
7
+ const POOL_LABEL = "cluster-pool";
8
+ /**
9
+ * Run a pool of Effect Cluster runners on Hetzner Cloud: Servers on the
10
+ * database's private Network, each running every class given these
11
+ * runners as a `Hetzner.Service`, behind a Load Balancer that serves their
12
+ * gateway to callers anywhere. Runners talk to each other and the database
13
+ * over the Network; their firewall only lets SSH in, which deploys them.
14
+ * Requires a database on a Hetzner Network — `ClusterHetzner.Database`.
15
+ *
16
+ * ### Running on Hetzner
17
+ * **Example:** Two runners
18
+ * ```typescript
19
+ * export const Database = ClusterHetzner.Database("Cluster");
20
+ * export const Default = ClusterHetzner.ServerPool("Default", { replicas: 2 }).pipe(
21
+ * Layer.provide(Database),
22
+ * );
23
+ * ```
24
+ *
25
+ * @primitive
26
+ */
27
+ export const ServerPool = (id, options = {}) => makePoolLayer({
28
+ label: "ClusterHetzner.ServerPool",
29
+ id,
30
+ settings: options,
31
+ expected: "a Hetzner Network (ClusterHetzner.Database)",
32
+ network: (database) => (database.network?.kind === "hetzner" ? database.network : undefined),
33
+ // The Load Balancer finds the Servers by label, so it needs none of them.
34
+ gateway: (name, network) => Effect.gen(function* () {
35
+ const balancer = yield* Hetzner.LoadBalancer(`${id}Gateway`, {
36
+ loadBalancerType: "lb11",
37
+ location: network.location,
38
+ // A stub of the database's Network, by id.
39
+ networks: [{ networkId: network.networkId }],
40
+ services: [
41
+ {
42
+ protocol: "tcp",
43
+ listenPort: 80,
44
+ destinationPort: GATEWAY_PORT,
45
+ healthCheck: {
46
+ protocol: "http",
47
+ port: GATEWAY_PORT,
48
+ interval: 10,
49
+ timeout: 5,
50
+ retries: 3,
51
+ http: { path: "/health" },
52
+ },
53
+ },
54
+ ],
55
+ targets: [
56
+ { type: "label_selector", selector: `${POOL_LABEL}=${name}`, usePrivateIp: true },
57
+ ],
58
+ });
59
+ return { url: Output.interpolate `http://${balancer.ipv4}` };
60
+ }),
61
+ nodes: ({ network, name, program, environment }) => Effect.gen(function* () {
62
+ const firewall = yield* Hetzner.Firewall(`${id}Firewall`, {
63
+ rules: [
64
+ {
65
+ description: "SSH, which deploys the pool",
66
+ direction: "in",
67
+ protocol: "tcp",
68
+ port: "22",
69
+ sourceIps: ["0.0.0.0/0", "::/0"],
70
+ },
71
+ ],
72
+ });
73
+ for (let i = 0; i < (options.replicas ?? 2); i++) {
74
+ const server = yield* Hetzner.Server(`${id}Runner${i}`, {
75
+ serverType: options.serverType ?? "cx23",
76
+ image: "ubuntu-24.04",
77
+ location: network.location,
78
+ // IPv4 installs Node and deploys over SSH; IPv6 would only spend an address.
79
+ enableIpv6: false,
80
+ networks: [{ networkId: network.networkId }],
81
+ firewalls: [firewall],
82
+ labels: { [POOL_LABEL]: name },
83
+ });
84
+ yield* Hetzner.Service(`${id}Runner${i}Service`, {
85
+ server,
86
+ main: program.pipe(Output.map((p) => `${p.dir}/runner.mjs`)),
87
+ port: GATEWAY_PORT,
88
+ env: environment.pipe(
89
+ // Each runner finds its own address in the Network's range.
90
+ Output.map((env) => ({
91
+ ...env,
92
+ CLUSTER_RUNNER_NETWORK: network.ipRange,
93
+ }))),
94
+ });
95
+ }
96
+ }),
97
+ });
98
+ //# sourceMappingURL=ServerPool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServerPool.js","sources":["../../src/Hetzner/ServerPool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @alchemy.run/effect-cluster/Hetzner — run an Effect Cluster's database and
3
+ * runners on Hetzner Cloud. Requires `@alchemy.run/hetzner`.
4
+ */
5
+ export * from "./Database.ts";
6
+ export * from "./ServerPool.ts";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sources":["../../src/Hetzner/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @alchemy.run/effect-cluster/Hetzner — run an Effect Cluster's database and
3
+ * runners on Hetzner Cloud. Requires `@alchemy.run/hetzner`.
4
+ */
5
+ export * from "./Database.js";
6
+ export * from "./ServerPool.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/Hetzner/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
package/lib/Host.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * What a platform built on Effect Cluster entities, such as
3
+ * `@alchemy.run/domain/EffectCluster`, needs from the runner it is built on
4
+ * and from the gateway its callers reach entities through.
5
+ *
6
+ * @module
7
+ */
8
+ import * as Context from "effect/Context";
9
+ import type * as Effect from "effect/Effect";
10
+ import type * as Layer from "effect/Layer";
11
+ import type { HostedEntity } from "./Entity.ts";
12
+ export { type GatewayTarget, makeNamespace } from "./internal/Gateway.ts";
13
+ export { HasNoPool } from "./internal/HostedRuntime.ts";
14
+ declare const RunnerHost_base: Context.ServiceClass<RunnerHost, "Cluster.RunnerHost", {
15
+ /** Host an entity on this runner, launching `layer` in the cluster beside it. */
16
+ readonly host: (entity: HostedEntity, layer: Layer.Layer<never, never, any>) => Effect.Effect<void>;
17
+ }>;
18
+ /**
19
+ * The runner a Layer is being built on. A runner's bootstrap provides it
20
+ * while it builds its entrypoints, so a Layer that hosts an entity itself
21
+ * (a Domain platform's, built by the model's Layer rather than exported as
22
+ * a class) hands the runner that entity, and the Layer to launch with it
23
+ * inside the cluster (with Sharding and the workflow engine). Anywhere
24
+ * else, the service is absent.
25
+ */
26
+ export declare class RunnerHost extends RunnerHost_base {
27
+ }
28
+ /** Options for {@link InstanceStorage}'s `list`. Keys come back in order. */
29
+ export interface InstanceStorageListOptions {
30
+ /** Only keys that start with this prefix. */
31
+ readonly prefix?: string;
32
+ /** Only keys at or after this key. */
33
+ readonly start?: string;
34
+ /** Only keys after this key. */
35
+ readonly startAfter?: string;
36
+ /** Only keys before this key. */
37
+ readonly end?: string;
38
+ /** At most this many entries. */
39
+ readonly limit?: number;
40
+ }
41
+ declare const InstanceStorage_base: Context.ServiceClass<InstanceStorage, "Cluster.InstanceStorage", {
42
+ get<T>(key: string): Effect.Effect<T | undefined>;
43
+ put<T>(key: string, value: T): Effect.Effect<void>;
44
+ put<T>(entries: Record<string, T>): Effect.Effect<void>;
45
+ delete(key: string): Effect.Effect<boolean>;
46
+ delete(keys: ReadonlyArray<string>): Effect.Effect<number>;
47
+ list<T>(options?: InstanceStorageListOptions): Effect.Effect<Map<string, T>>;
48
+ transaction<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>;
49
+ }>;
50
+ /**
51
+ * The key-value storage of the object an entity instance runs on, on pools
52
+ * that give every instance an object of its own
53
+ * (`ClusterCloudflare.DurableObjectPool`: the instance's Durable Object).
54
+ * Values are structured-cloneable; writes inside `transaction` commit
55
+ * together, or not at all. The pool provides it to every instance it runs;
56
+ * on pools that share one database, the service is absent.
57
+ */
58
+ export declare class InstanceStorage extends InstanceStorage_base {
59
+ }
60
+ //# sourceMappingURL=Host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Host.d.ts","sources":["../src/Host.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,KAAK,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;;IAapD,iFAAiF;mBAClE,CACb,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,KAClC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;;AAf5B;;;;;;;GAOG;AACH,qBAAa,UAAW,SAAQ,eASP;CAAG;AAE5B,6EAA6E;AAC7E,MAAM,WAAW,0BAA0B;IACzC,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,gCAAgC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,iCAAiC;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;;QAaO,CAAC,OAAO,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;QAC7C,CAAC,OAAO,MAAM,SAAS,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QAC9C,CAAC,WAAW,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;gBAC3C,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;iBAC9B,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;SACrD,CAAC,YAAY,0BAA0B,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;gBAChE,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;;AAjBhF;;;;;;;GAOG;AACH,qBAAa,eAAgB,SAAQ,oBAWP;CAAG"}
package/lib/Host.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * What a platform built on Effect Cluster entities, such as
3
+ * `@alchemy.run/domain/EffectCluster`, needs from the runner it is built on
4
+ * and from the gateway its callers reach entities through.
5
+ *
6
+ * @module
7
+ */
8
+ import * as Context from "effect/Context";
9
+ export { makeNamespace } from "./internal/Gateway.js";
10
+ export { HasNoPool } from "./internal/HostedRuntime.js";
11
+ /**
12
+ * The runner a Layer is being built on. A runner's bootstrap provides it
13
+ * while it builds its entrypoints, so a Layer that hosts an entity itself
14
+ * (a Domain platform's, built by the model's Layer rather than exported as
15
+ * a class) hands the runner that entity, and the Layer to launch with it
16
+ * inside the cluster (with Sharding and the workflow engine). Anywhere
17
+ * else, the service is absent.
18
+ */
19
+ export class RunnerHost extends Context.Service()("Cluster.RunnerHost") {
20
+ }
21
+ /**
22
+ * The key-value storage of the object an entity instance runs on, on pools
23
+ * that give every instance an object of its own
24
+ * (`ClusterCloudflare.DurableObjectPool`: the instance's Durable Object).
25
+ * Values are structured-cloneable; writes inside `transaction` commit
26
+ * together, or not at all. The pool provides it to every instance it runs;
27
+ * on pools that share one database, the service is absent.
28
+ */
29
+ export class InstanceStorage extends Context.Service()("Cluster.InstanceStorage") {
30
+ }
31
+ //# sourceMappingURL=Host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Host.js","sources":["../src/Host.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAK1C,OAAO,EAAsB,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAExD;;;;;;;GAOG;AACH,MAAM,OAAO,UAAW,SAAQ,OAAO,CAAC,OAAO,EAS5C,CAAC,oBAAoB,CAAC;CAAG;AAgB5B;;;;;;;GAOG;AACH,MAAM,OAAO,eAAgB,SAAQ,OAAO,CAAC,OAAO,EAWjD,CAAC,yBAAyB,CAAC;CAAG"}
@@ -0,0 +1,40 @@
1
+ import * as Kubernetes from "@alchemy.run/kubernetes";
2
+ import type { Input } from "alchemy/Input";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import { Database as ClusterDatabase } from "../Database.ts";
6
+ export interface DatabaseProps {
7
+ /** The cluster the database and runners run on. */
8
+ cluster: Kubernetes.ClusterLike | Effect.Effect<Input<Kubernetes.ClusterLike>, never, any>;
9
+ /**
10
+ * Kubernetes namespace of the database and runners.
11
+ * @default "default"
12
+ */
13
+ namespace?: string;
14
+ /**
15
+ * Postgres image.
16
+ * @default "postgres:17-alpine"
17
+ */
18
+ image?: string;
19
+ }
20
+ /**
21
+ * The database an Effect Cluster coordinates through, on Kubernetes, as a
22
+ * `Cluster.Database` Layer: Postgres as a Deployment behind a ClusterIP
23
+ * Service, on the cluster the runners run on. Nothing outside the cluster
24
+ * reaches it.
25
+ *
26
+ * Its data lives in the pod, so it is meant for development and tests; for
27
+ * a managed Postgres (RDS, Cloud SQL, Neon), pass its URL to
28
+ * `Cluster.Database.fromUrl` and give the pool its cluster.
29
+ *
30
+ * ### Running the database on Kubernetes
31
+ * **Example:** Postgres on a local kind cluster
32
+ * ```typescript
33
+ * export const Kind = Kubernetes.LocalCluster("Kind", { name: "cluster" });
34
+ * export const Database = ClusterKubernetes.Database("Cluster", { cluster: Kind });
35
+ * ```
36
+ *
37
+ * @primitive
38
+ */
39
+ export declare const Database: (id: string, props: DatabaseProps) => Layer.Layer<ClusterDatabase, never, any>;
40
+ //# sourceMappingURL=Database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Database.d.ts","sources":["../../src/Kubernetes/Database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAG3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAsB,QAAQ,IAAI,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIjF,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,OAAO,EAAE,UAAU,CAAC,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IAC3F;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,QAAQ,OAAQ,MAAM,SAAS,aAAa,6CA2CtD,CAAC"}
@@ -0,0 +1,56 @@
1
+ import * as Kubernetes from "@alchemy.run/kubernetes";
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.js";
8
+ import { stackDnsLabel } from "../internal/Naming.js";
9
+ /**
10
+ * The database an Effect Cluster coordinates through, on Kubernetes, as a
11
+ * `Cluster.Database` Layer: Postgres as a Deployment behind a ClusterIP
12
+ * Service, on the cluster the runners run on. Nothing outside the cluster
13
+ * reaches it.
14
+ *
15
+ * Its data lives in the pod, so it is meant for development and tests; for
16
+ * a managed Postgres (RDS, Cloud SQL, Neon), pass its URL to
17
+ * `Cluster.Database.fromUrl` and give the pool its cluster.
18
+ *
19
+ * ### Running the database on Kubernetes
20
+ * **Example:** Postgres on a local kind cluster
21
+ * ```typescript
22
+ * export const Kind = Kubernetes.LocalCluster("Kind", { name: "cluster" });
23
+ * export const Database = ClusterKubernetes.Database("Cluster", { cluster: Kind });
24
+ * ```
25
+ *
26
+ * @primitive
27
+ */
28
+ export const Database = (id, props) => Layer.effect(ClusterDatabase, Effect.gen(function* () {
29
+ if (globalThis.__ALCHEMY_RUNTIME__) {
30
+ // Runners read their database from their own environment at runtime.
31
+ return { url: Redacted.make(""), shardGroups: undefined };
32
+ }
33
+ const namespace = props.namespace ?? "default";
34
+ const cluster = Effect.isEffect(props.cluster) ? yield* props.cluster : props.cluster;
35
+ const name = yield* stackDnsLabel(`${id}-db`, 50);
36
+ const password = yield* Random(`${id}Password`, { bytes: 24 });
37
+ const db = yield* Kubernetes.Deployment(`${id}Postgres`, {
38
+ cluster,
39
+ name,
40
+ namespace,
41
+ image: props.image ?? "postgres:17-alpine",
42
+ port: 5432,
43
+ serviceType: "ClusterIP",
44
+ env: password.text.pipe(Output.map((text) => ({
45
+ POSTGRES_PASSWORD: Redacted.value(text),
46
+ POSTGRES_DB: "cluster",
47
+ }))),
48
+ });
49
+ return {
50
+ // Ordered after the Deployment: runners connect once it exists.
51
+ url: Output.all(db.deploymentName, password.text).pipe(Output.map(([, text]) => Redacted.make(`postgres://postgres:${encodeURIComponent(text.pipe(Redacted.value))}@${name}.${namespace}.svc.cluster.local:5432/cluster`))),
52
+ network: { kind: "kubernetes", cluster, namespace },
53
+ shardGroups: yield* clusterShardGroups(id),
54
+ };
55
+ }));
56
+ //# sourceMappingURL=Database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Database.js","sources":["../../src/Kubernetes/Database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,MAAM,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,QAAQ,IAAI,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAkBtD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EAAU,EAAE,KAAoB,EAAE,EAAE,CAC3D,KAAK,CAAC,MAAM,CACV,eAAe,EACf,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,IAAI,UAAU,CAAC,mBAAmB,EAAE,CAAC;QACnC,qEAAqE;QACrE,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,WAAW,EAAE,SAAkB,EAAE,CAAC;IACrE,CAAC;IACD,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,SAAS,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;IACtF,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,aAAa,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAI5D,CAAC;IACF,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE,UAAU,EAAE;QACvD,OAAO;QACP,IAAI;QACJ,SAAS;QACT,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,oBAAoB;QAC1C,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,WAAW;QACxB,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CACrB,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACpB,iBAAiB,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;YACvC,WAAW,EAAE,SAAS;SACvB,CAAC,CAAC,CACiC;KACvC,CAAC,CAAC;IACH,OAAO;QACL,gEAAgE;QAChE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,cAAc,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CACpD,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CACtB,QAAQ,CAAC,IAAI,CACX,uBAAuB,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,IAAI,SAAS,iCAAiC,CAC3H,CACF,CACF;QACD,OAAO,EAAE,EAAE,IAAI,EAAE,YAAqB,EAAE,OAAO,EAAE,SAAS,EAAE;QAC5D,WAAW,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,EAAE,CAAC;KAC3C,CAAC;AACJ,CAAC,CAAC,CACH,CAAC"}
@@ -0,0 +1,54 @@
1
+ import * as Kubernetes from "@alchemy.run/kubernetes";
2
+ import { type PoolSettings } from "../internal/PoolHost.ts";
3
+ export interface DeploymentPoolOptions extends PoolSettings {
4
+ /**
5
+ * Number of runners.
6
+ * @default 2
7
+ */
8
+ replicas?: number;
9
+ /**
10
+ * How the gateway's Service is exposed. `ClusterIP` serves callers in the
11
+ * cluster; `LoadBalancer` serves callers anywhere, where the cluster
12
+ * provisions load balancers.
13
+ * @default "ClusterIP"
14
+ */
15
+ serviceType?: "ClusterIP" | "NodePort" | "LoadBalancer";
16
+ /** Container CPU/memory requests and limits. */
17
+ resources?: {
18
+ requests?: {
19
+ cpu?: string;
20
+ memory?: string;
21
+ };
22
+ limits?: {
23
+ cpu?: string;
24
+ memory?: string;
25
+ };
26
+ };
27
+ /**
28
+ * CPU architecture of the runner image, built on the deploying machine.
29
+ * @default the cluster's
30
+ */
31
+ architecture?: "amd64" | "arm64";
32
+ }
33
+ /**
34
+ * Run a pool of Effect Cluster runners on Kubernetes: a Deployment whose pods
35
+ * host every class given this pool, in the database's cluster and
36
+ * namespace. Pods reach each other at their own addresses; the gateway is
37
+ * a Service of the Deployment, at
38
+ * `http://{name}.{namespace}.svc.cluster.local:34500` for callers in the
39
+ * cluster. Requires a database on a Kubernetes cluster —
40
+ * `ClusterKubernetes.Database`.
41
+ *
42
+ * ### Running on Kubernetes
43
+ * **Example:** Two runners
44
+ * ```typescript
45
+ * export const Database = ClusterKubernetes.Database("Cluster", { cluster: Kind });
46
+ * export const Default = ClusterKubernetes.DeploymentPool("Default", { replicas: 2 }).pipe(
47
+ * Layer.provide(Database),
48
+ * );
49
+ * ```
50
+ *
51
+ * @primitive
52
+ */
53
+ export declare const DeploymentPool: (id: string, options?: DeploymentPoolOptions) => import("effect/Layer").Layer<import("../internal/PoolHostService.ts").PoolHost, never, import("../Database.ts").Database | import("../Providers.ts").Providers | Kubernetes.Providers | import("alchemy").Stack>;
54
+ //# sourceMappingURL=DeploymentPool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeploymentPool.d.ts","sources":["../../src/Kubernetes/DeploymentPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,yBAAyB,CAAC;AAItD,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE3E,MAAM,WAAW,qBAAsB,SAAQ,YAAY;IACzD;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC;IACxD,gDAAgD;IAChD,SAAS,CAAC,EAAE;QACV,QAAQ,CAAC,EAAE;YAAE,GAAG,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC7C,MAAM,CAAC,EAAE;YAAE,GAAG,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAC5C,CAAC;IACF;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,cAAc,OAAQ,MAAM,YAAW,qBAAqB,qNAyBrE,CAAC"}
@@ -0,0 +1,49 @@
1
+ import * as Kubernetes from "@alchemy.run/kubernetes";
2
+ import * as Output from "alchemy/Output";
3
+ import * as Effect from "effect/Effect";
4
+ import { GATEWAY_PORT } from "../internal/Gateway.js";
5
+ import { makePoolLayer } from "../internal/PoolHost.js";
6
+ /**
7
+ * Run a pool of Effect Cluster runners on Kubernetes: a Deployment whose pods
8
+ * host every class given this pool, in the database's cluster and
9
+ * namespace. Pods reach each other at their own addresses; the gateway is
10
+ * a Service of the Deployment, at
11
+ * `http://{name}.{namespace}.svc.cluster.local:34500` for callers in the
12
+ * cluster. Requires a database on a Kubernetes cluster —
13
+ * `ClusterKubernetes.Database`.
14
+ *
15
+ * ### Running on Kubernetes
16
+ * **Example:** Two runners
17
+ * ```typescript
18
+ * export const Database = ClusterKubernetes.Database("Cluster", { cluster: Kind });
19
+ * export const Default = ClusterKubernetes.DeploymentPool("Default", { replicas: 2 }).pipe(
20
+ * Layer.provide(Database),
21
+ * );
22
+ * ```
23
+ *
24
+ * @primitive
25
+ */
26
+ export const DeploymentPool = (id, options = {}) => makePoolLayer({
27
+ label: "ClusterKubernetes.DeploymentPool",
28
+ id,
29
+ settings: options,
30
+ expected: "a Kubernetes cluster (ClusterKubernetes.Database)",
31
+ network: (database) => (database.network?.kind === "kubernetes" ? database.network : undefined),
32
+ // The Deployment's Service is named after the pool.
33
+ gateway: (name, network) => Effect.succeed({
34
+ url: `http://${name}.${network.namespace}.svc.cluster.local:${GATEWAY_PORT}`,
35
+ }),
36
+ nodes: ({ network, name, program, environment }) => Kubernetes.Deployment(`${id}Pool`, {
37
+ cluster: network.cluster,
38
+ name,
39
+ namespace: network.namespace,
40
+ context: program.pipe(Output.map((p) => p.dir)),
41
+ replicas: options.replicas ?? 2,
42
+ port: GATEWAY_PORT,
43
+ serviceType: options.serviceType ?? "ClusterIP",
44
+ ...(options.resources === undefined ? {} : { resources: options.resources }),
45
+ ...(options.architecture === undefined ? {} : { architecture: options.architecture }),
46
+ env: environment,
47
+ }).pipe(Effect.asVoid),
48
+ });
49
+ //# sourceMappingURL=DeploymentPool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeploymentPool.js","sources":["../../src/Kubernetes/DeploymentPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,MAAM,MAAM,gBAAgB,CAAC;AACzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAqB,MAAM,yBAAyB,CAAC;AA2B3E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAAU,EAAE,OAAO,GAA0B,EAAE,EAAE,EAAE,CAChF,aAAa,CAAC;IACZ,KAAK,EAAE,kCAAkC;IACzC,EAAE;IACF,QAAQ,EAAE,OAAO;IACjB,QAAQ,EAAE,mDAAmD;IAC7D,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/F,oDAAoD;IACpD,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CACzB,MAAM,CAAC,OAAO,CAAC;QACb,GAAG,EAAE,UAAU,IAAI,IAAI,OAAO,CAAC,SAAS,sBAAsB,YAAY,EAAE;KAC7E,CAAC;IACJ,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,CACjD,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE;QACjC,OAAO,EAAE,OAAO,CAAC,OAAiC;QAClD,IAAI;QACJ,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAsB;QACpE,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,CAAC;QAC/B,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,WAAW;QAC/C,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;QAC5E,GAAG,CAAC,OAAO,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC;QACrF,GAAG,EAAE,WAAgD;KACtD,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;CACzB,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @alchemy.run/effect-cluster/Kubernetes — run an Effect Cluster's database and
3
+ * runners on Kubernetes. Requires `@alchemy.run/kubernetes`.
4
+ */
5
+ export * from "./Database.ts";
6
+ export * from "./DeploymentPool.ts";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sources":["../../src/Kubernetes/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @alchemy.run/effect-cluster/Kubernetes — run an Effect Cluster's database and
3
+ * runners on Kubernetes. Requires `@alchemy.run/kubernetes`.
4
+ */
5
+ export * from "./Database.js";
6
+ export * from "./DeploymentPool.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/Kubernetes/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC"}
package/lib/Pool.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ import * as Provider from "alchemy/Provider";
2
+ import { Resource } from "alchemy/Resource";
3
+ import type * as Layer from "effect/Layer";
4
+ import type { PoolHost } from "./internal/PoolHostService.ts";
5
+ import type { Providers } from "./Providers.ts";
6
+ /**
7
+ * A pool Layer, as every host's pool returns it: provide it to an
8
+ * implementation to deploy it to that pool.
9
+ */
10
+ export type PoolLayer<R = never> = Layer.Layer<PoolHost, never, R>;
11
+ /** An entity class a pool is built with. */
12
+ export interface PoolEntity {
13
+ /** The entity type: the class's logical id. */
14
+ name: string;
15
+ /** The module that exports the class. */
16
+ module: string;
17
+ /** The export of {@link module} that holds it. */
18
+ handler: string;
19
+ /**
20
+ * An existing Effect Cluster program that hosts the entity, run as it is:
21
+ * an external `Cluster.Entity`. It owns every runner in its set.
22
+ */
23
+ program?: string;
24
+ }
25
+ /** Data the entities a pool hosts bind onto it. */
26
+ export interface PoolBindingContract {
27
+ entities?: PoolEntity[];
28
+ env?: Record<string, unknown>;
29
+ }
30
+ export interface PoolProps {
31
+ /** The pool's name, used in its service names. */
32
+ name: string;
33
+ }
34
+ export interface PoolAttributes {
35
+ /** The pool's name. */
36
+ name: string;
37
+ /** The entity classes the pool hosts, sorted by type. */
38
+ entities: PoolEntity[];
39
+ /** Environment values the hosted classes need, merged. */
40
+ env: Record<string, unknown>;
41
+ }
42
+ export interface Pool extends Resource<"Cluster.Pool", PoolProps, PoolAttributes, PoolBindingContract, Providers> {
43
+ }
44
+ /**
45
+ * The set of entity classes one pool hosts, collected from the
46
+ * entities that were given it. Pool Layers (`ClusterDocker.SwarmPool`,
47
+ * …) declare one and build their program from it; it owns nothing in the
48
+ * cloud.
49
+ *
50
+ * @internal
51
+ */
52
+ export declare const Pool: import("alchemy").ResourceClass<Pool>;
53
+ declare const PoolProgramConflict_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
54
+ readonly _tag: "Cluster.PoolProgramConflict";
55
+ } & Readonly<A>;
56
+ /**
57
+ * A pool was given an external `Cluster.Entity` and something
58
+ * else hosted another way: an external program owns every runner it runs on.
59
+ */
60
+ export declare class PoolProgramConflict extends PoolProgramConflict_base<{
61
+ readonly message: string;
62
+ }> {
63
+ }
64
+ export declare const PoolProvider: () => Layer.Layer<Provider.Provider<Pool>, never, never>;
65
+ export {};
66
+ //# sourceMappingURL=Pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pool.d.ts","sources":["../src/Pool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;GAGG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAEnE,4CAA4C;AAC5C,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,0DAA0D;IAC1D,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,IAAK,SAAQ,QAAQ,CACpC,cAAc,EACd,SAAS,EACT,cAAc,EACd,mBAAmB,EACnB,SAAS,CACV;CAAG;AAEJ;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI,uCAAiC,CAAC;;;;AAEnD;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,yBAAgD;IACvF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;AAEL,eAAO,MAAM,YAAY,0DA4BrB,CAAC"}
package/lib/Pool.js ADDED
@@ -0,0 +1,47 @@
1
+ import * as Provider from "alchemy/Provider";
2
+ import { Resource } from "alchemy/Resource";
3
+ import * as Data from "effect/Data";
4
+ import * as Effect from "effect/Effect";
5
+ /**
6
+ * The set of entity classes one pool hosts, collected from the
7
+ * entities that were given it. Pool Layers (`ClusterDocker.SwarmPool`,
8
+ * …) declare one and build their program from it; it owns nothing in the
9
+ * cloud.
10
+ *
11
+ * @internal
12
+ */
13
+ export const Pool = Resource("Cluster.Pool");
14
+ /**
15
+ * A pool was given an external `Cluster.Entity` and something
16
+ * else hosted another way: an external program owns every runner it runs on.
17
+ */
18
+ export class PoolProgramConflict extends Data.TaggedError("Cluster.PoolProgramConflict") {
19
+ }
20
+ export const PoolProvider = () => Provider.succeed(Pool, {
21
+ stables: ["name"],
22
+ reconcile: ({ id, news, bindings }) => Effect.gen(function* () {
23
+ const entities = new Map();
24
+ const env = {};
25
+ for (const { data } of bindings) {
26
+ for (const entity of data.entities ?? [])
27
+ entities.set(entity.name, entity);
28
+ Object.assign(env, data.env);
29
+ }
30
+ const all = [...entities.values()];
31
+ const programs = new Set(all.flatMap((entity) => (entity.program ? [entity.program] : [])));
32
+ if (programs.size > 1 || (programs.size === 1 && all.some((entity) => !entity.program))) {
33
+ return yield* new PoolProgramConflict({
34
+ message: `Runners '${id}' were given ${all.map((entity) => `'${entity.name}'`).join(", ")}, ` +
35
+ `but an external program runs on every runner it is given: give each program ` +
36
+ `a pool of its own, and host Alchemy entities in another pool.`,
37
+ });
38
+ }
39
+ return {
40
+ name: news.name,
41
+ entities: [...entities.values()].sort((a, b) => a.name.localeCompare(b.name)),
42
+ env,
43
+ };
44
+ }),
45
+ delete: () => Effect.void,
46
+ });
47
+ //# sourceMappingURL=Pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pool.js","sources":["../src/Pool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAsDxC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,QAAQ,CAAO,cAAc,CAAC,CAAC;AAEnD;;;GAGG;AACH,MAAM,OAAO,mBAAoB,SAAQ,IAAI,CAAC,WAAW,CAAC,6BAA6B,CAErF;CAAG;AAEL,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,EAAE,CAC/B,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE;IACrB,OAAO,EAAE,CAAC,MAAM,CAAC;IACjB,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CACpC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC/C,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,QAAQ,EAAE,CAAC;YAChC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE;gBAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC5E,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;QACD,MAAM,GAAG,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACnC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5F,IAAI,QAAQ,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YACxF,OAAO,KAAK,CAAC,CAAC,IAAI,mBAAmB,CAAC;gBACpC,OAAO,EACL,YAAY,EAAE,gBAAgB,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;oBACpF,8EAA8E;oBAC9E,+DAA+D;aAClE,CAAC,CAAC;QACL,CAAC;QACD,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC7E,GAAG;SACJ,CAAC;IACJ,CAAC,CAAC;IACJ,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI;CAC1B,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ import * as Provider from "alchemy/Provider";
2
+ import * as Layer from "effect/Layer";
3
+ declare const Providers_base: Provider.ProviderCollection<Providers, "Cluster">;
4
+ /** Service tag bundling every Effect Cluster provider. */
5
+ export declare class Providers extends Providers_base {
6
+ }
7
+ export type ProviderRequirements = Layer.Services<ReturnType<typeof providers>>;
8
+ /**
9
+ * The Effect Cluster providers Layer. Add it next to the providers of the
10
+ * cloud your database and runners run on.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * Alchemy.Stack("App", { providers: Layer.mergeAll(Docker.providers(), Cluster.providers()) }, ...)
15
+ * ```
16
+ */
17
+ export declare const providers: () => Layer.Layer<Providers, never, never>;
18
+ export {};
19
+ //# sourceMappingURL=Providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Providers.d.ts","sources":["../src/Providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;;AAQtC,0DAA0D;AAC1D,qBAAa,SAAU,SAAQ,cAAmD;CAAG;AAErF,MAAM,MAAM,oBAAoB,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC;AAEhF;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,4CAwBnB,CAAC"}