@alchemy.run/effect-cluster 0.0.0-stage → 2.0.0-beta.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +105 -2
- package/lib/AWS/Database.d.ts +54 -0
- package/lib/AWS/Database.d.ts.map +1 -0
- package/lib/AWS/Database.js +96 -0
- package/lib/AWS/Database.js.map +1 -0
- package/lib/AWS/EcsPool.d.ts +56 -0
- package/lib/AWS/EcsPool.d.ts.map +1 -0
- package/lib/AWS/EcsPool.js +87 -0
- package/lib/AWS/EcsPool.js.map +1 -0
- package/lib/AWS/index.d.ts +7 -0
- package/lib/AWS/index.d.ts.map +1 -0
- package/lib/AWS/index.js +7 -0
- package/lib/AWS/index.js.map +1 -0
- package/lib/AWS/internal/Gateway.d.ts +23 -0
- package/lib/AWS/internal/Gateway.d.ts.map +1 -0
- package/lib/AWS/internal/Gateway.js +70 -0
- package/lib/AWS/internal/Gateway.js.map +1 -0
- package/lib/Cloudflare/DurableObjectPool.d.ts +42 -0
- package/lib/Cloudflare/DurableObjectPool.d.ts.map +1 -0
- package/lib/Cloudflare/DurableObjectPool.js +136 -0
- package/lib/Cloudflare/DurableObjectPool.js.map +1 -0
- package/lib/Cloudflare/PoolWorker.d.ts +186 -0
- package/lib/Cloudflare/PoolWorker.d.ts.map +1 -0
- package/lib/Cloudflare/PoolWorker.js +75 -0
- package/lib/Cloudflare/PoolWorker.js.map +1 -0
- package/lib/Cloudflare/index.d.ts +7 -0
- package/lib/Cloudflare/index.d.ts.map +1 -0
- package/lib/Cloudflare/index.js +7 -0
- package/lib/Cloudflare/index.js.map +1 -0
- package/lib/Cloudflare/internal/Objects.d.ts +38 -0
- package/lib/Cloudflare/internal/Objects.d.ts.map +1 -0
- package/lib/Cloudflare/internal/Objects.js +102 -0
- package/lib/Cloudflare/internal/Objects.js.map +1 -0
- package/lib/Cron.d.ts +143 -0
- package/lib/Cron.d.ts.map +1 -0
- package/lib/Cron.js +83 -0
- package/lib/Cron.js.map +1 -0
- package/lib/Database.d.ts +95 -0
- package/lib/Database.d.ts.map +1 -0
- package/lib/Database.js +38 -0
- package/lib/Database.js.map +1 -0
- package/lib/Docker/Database.d.ts +42 -0
- package/lib/Docker/Database.d.ts.map +1 -0
- package/lib/Docker/Database.js +53 -0
- package/lib/Docker/Database.js.map +1 -0
- package/lib/Docker/SwarmPool.d.ts +44 -0
- package/lib/Docker/SwarmPool.d.ts.map +1 -0
- package/lib/Docker/SwarmPool.js +56 -0
- package/lib/Docker/SwarmPool.js.map +1 -0
- package/lib/Docker/index.d.ts +7 -0
- package/lib/Docker/index.d.ts.map +1 -0
- package/lib/Docker/index.js +7 -0
- package/lib/Docker/index.js.map +1 -0
- package/lib/Entity.d.ts +348 -0
- package/lib/Entity.d.ts.map +1 -0
- package/lib/Entity.js +308 -0
- package/lib/Entity.js.map +1 -0
- package/lib/Fly/Database.d.ts +50 -0
- package/lib/Fly/Database.d.ts.map +1 -0
- package/lib/Fly/Database.js +56 -0
- package/lib/Fly/Database.js.map +1 -0
- package/lib/Fly/MachinePool.d.ts +39 -0
- package/lib/Fly/MachinePool.d.ts.map +1 -0
- package/lib/Fly/MachinePool.js +51 -0
- package/lib/Fly/MachinePool.js.map +1 -0
- package/lib/Fly/index.d.ts +7 -0
- package/lib/Fly/index.d.ts.map +1 -0
- package/lib/Fly/index.js +7 -0
- package/lib/Fly/index.js.map +1 -0
- package/lib/GCP/ComputeEnginePool.d.ts +41 -0
- package/lib/GCP/ComputeEnginePool.d.ts.map +1 -0
- package/lib/GCP/ComputeEnginePool.js +172 -0
- package/lib/GCP/ComputeEnginePool.js.map +1 -0
- package/lib/GCP/Database.d.ts +48 -0
- package/lib/GCP/Database.d.ts.map +1 -0
- package/lib/GCP/Database.js +115 -0
- package/lib/GCP/Database.js.map +1 -0
- package/lib/GCP/index.d.ts +7 -0
- package/lib/GCP/index.d.ts.map +1 -0
- package/lib/GCP/index.js +7 -0
- package/lib/GCP/index.js.map +1 -0
- package/lib/Hetzner/Database.d.ts +48 -0
- package/lib/Hetzner/Database.d.ts.map +1 -0
- package/lib/Hetzner/Database.js +91 -0
- package/lib/Hetzner/Database.js.map +1 -0
- package/lib/Hetzner/ServerPool.d.ts +35 -0
- package/lib/Hetzner/ServerPool.d.ts.map +1 -0
- package/lib/Hetzner/ServerPool.js +98 -0
- package/lib/Hetzner/ServerPool.js.map +1 -0
- package/lib/Hetzner/index.d.ts +7 -0
- package/lib/Hetzner/index.d.ts.map +1 -0
- package/lib/Hetzner/index.js +7 -0
- package/lib/Hetzner/index.js.map +1 -0
- package/lib/Host.d.ts +60 -0
- package/lib/Host.d.ts.map +1 -0
- package/lib/Host.js +31 -0
- package/lib/Host.js.map +1 -0
- package/lib/Kubernetes/Database.d.ts +40 -0
- package/lib/Kubernetes/Database.d.ts.map +1 -0
- package/lib/Kubernetes/Database.js +56 -0
- package/lib/Kubernetes/Database.js.map +1 -0
- package/lib/Kubernetes/DeploymentPool.d.ts +54 -0
- package/lib/Kubernetes/DeploymentPool.d.ts.map +1 -0
- package/lib/Kubernetes/DeploymentPool.js +49 -0
- package/lib/Kubernetes/DeploymentPool.js.map +1 -0
- package/lib/Kubernetes/index.d.ts +7 -0
- package/lib/Kubernetes/index.d.ts.map +1 -0
- package/lib/Kubernetes/index.js +7 -0
- package/lib/Kubernetes/index.js.map +1 -0
- package/lib/Pool.d.ts +66 -0
- package/lib/Pool.d.ts.map +1 -0
- package/lib/Pool.js +47 -0
- package/lib/Pool.js.map +1 -0
- package/lib/Providers.d.ts +19 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +31 -0
- package/lib/Providers.js.map +1 -0
- package/lib/ShardGroups.d.ts +24 -0
- package/lib/ShardGroups.d.ts.map +1 -0
- package/lib/ShardGroups.js +19 -0
- package/lib/ShardGroups.js.map +1 -0
- package/lib/Singleton.d.ts +110 -0
- package/lib/Singleton.d.ts.map +1 -0
- package/lib/Singleton.js +60 -0
- package/lib/Singleton.js.map +1 -0
- package/lib/Workflow.d.ts +251 -0
- package/lib/Workflow.d.ts.map +1 -0
- package/lib/Workflow.js +288 -0
- package/lib/Workflow.js.map +1 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +12 -0
- package/lib/index.js.map +1 -0
- package/lib/internal/Bootstrap.d.ts +28 -0
- package/lib/internal/Bootstrap.d.ts.map +1 -0
- package/lib/internal/Bootstrap.js +257 -0
- package/lib/internal/Bootstrap.js.map +1 -0
- package/lib/internal/EntityRuntime.d.ts +40 -0
- package/lib/internal/EntityRuntime.d.ts.map +1 -0
- package/lib/internal/EntityRuntime.js +698 -0
- package/lib/internal/EntityRuntime.js.map +1 -0
- package/lib/internal/Gateway.d.ts +82 -0
- package/lib/internal/Gateway.d.ts.map +1 -0
- package/lib/internal/Gateway.js +218 -0
- package/lib/internal/Gateway.js.map +1 -0
- package/lib/internal/HostedPlatform.d.ts +36 -0
- package/lib/internal/HostedPlatform.d.ts.map +1 -0
- package/lib/internal/HostedPlatform.js +36 -0
- package/lib/internal/HostedPlatform.js.map +1 -0
- package/lib/internal/HostedRuntime.d.ts +58 -0
- package/lib/internal/HostedRuntime.d.ts.map +1 -0
- package/lib/internal/HostedRuntime.js +72 -0
- package/lib/internal/HostedRuntime.js.map +1 -0
- package/lib/internal/Naming.d.ts +12 -0
- package/lib/internal/Naming.d.ts.map +1 -0
- package/lib/internal/Naming.js +40 -0
- package/lib/internal/Naming.js.map +1 -0
- package/lib/internal/ObjectSharding.d.ts +141 -0
- package/lib/internal/ObjectSharding.d.ts.map +1 -0
- package/lib/internal/ObjectSharding.js +337 -0
- package/lib/internal/ObjectSharding.js.map +1 -0
- package/lib/internal/PoolHost.d.ts +80 -0
- package/lib/internal/PoolHost.d.ts.map +1 -0
- package/lib/internal/PoolHost.js +203 -0
- package/lib/internal/PoolHost.js.map +1 -0
- package/lib/internal/PoolHostService.d.ts +25 -0
- package/lib/internal/PoolHostService.d.ts.map +1 -0
- package/lib/internal/PoolHostService.js +10 -0
- package/lib/internal/PoolHostService.js.map +1 -0
- package/lib/internal/Wire.d.ts +60 -0
- package/lib/internal/Wire.d.ts.map +1 -0
- package/lib/internal/Wire.js +36 -0
- package/lib/internal/Wire.js.map +1 -0
- package/lib/internal/WorkflowGateway.d.ts +36 -0
- package/lib/internal/WorkflowGateway.d.ts.map +1 -0
- package/lib/internal/WorkflowGateway.js +124 -0
- package/lib/internal/WorkflowGateway.js.map +1 -0
- package/lib/internal/external-runner.d.ts +2 -0
- package/lib/internal/external-runner.d.ts.map +1 -0
- package/lib/internal/external-runner.js +7 -0
- package/lib/internal/external-runner.js.map +1 -0
- package/lib/internal/runner.d.ts +2 -0
- package/lib/internal/runner.d.ts.map +1 -0
- package/lib/internal/runner.js +5 -0
- package/lib/internal/runner.js.map +1 -0
- package/package.json +143 -3
- package/src/AWS/Database.ts +139 -0
- package/src/AWS/EcsPool.ts +118 -0
- package/src/AWS/index.ts +6 -0
- package/src/AWS/internal/Gateway.ts +82 -0
- package/src/Cloudflare/DurableObjectPool.ts +198 -0
- package/src/Cloudflare/PoolWorker.ts +124 -0
- package/src/Cloudflare/index.ts +6 -0
- package/src/Cloudflare/internal/Objects.ts +160 -0
- package/src/Cron.ts +202 -0
- package/src/Database.ts +116 -0
- package/src/Docker/Database.ts +94 -0
- package/src/Docker/SwarmPool.ts +83 -0
- package/src/Docker/index.ts +6 -0
- package/src/Entity.ts +621 -0
- package/src/Fly/Database.ts +102 -0
- package/src/Fly/MachinePool.ts +72 -0
- package/src/Fly/index.ts +6 -0
- package/src/GCP/ComputeEnginePool.ts +203 -0
- package/src/GCP/Database.ts +168 -0
- package/src/GCP/index.ts +6 -0
- package/src/Hetzner/Database.ts +142 -0
- package/src/Hetzner/ServerPool.ts +116 -0
- package/src/Hetzner/index.ts +6 -0
- package/src/Host.ts +68 -0
- package/src/Kubernetes/Database.ts +89 -0
- package/src/Kubernetes/DeploymentPool.ts +77 -0
- package/src/Kubernetes/index.ts +6 -0
- package/src/Pool.ts +104 -0
- package/src/Providers.ts +49 -0
- package/src/ShardGroups.ts +41 -0
- package/src/Singleton.ts +153 -0
- package/src/Workflow.ts +556 -0
- package/src/index.ts +11 -0
- package/src/internal/Bootstrap.ts +368 -0
- package/src/internal/EntityRuntime.ts +996 -0
- package/src/internal/Gateway.ts +366 -0
- package/src/internal/HostedPlatform.ts +83 -0
- package/src/internal/HostedRuntime.ts +118 -0
- package/src/internal/Naming.ts +44 -0
- package/src/internal/ObjectSharding.ts +578 -0
- package/src/internal/PoolHost.ts +330 -0
- package/src/internal/PoolHostService.ts +25 -0
- package/src/internal/Wire.ts +83 -0
- package/src/internal/WorkflowGateway.ts +188 -0
- package/src/internal/external-runner.ts +10 -0
- package/src/internal/runner.ts +5 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Durable Object classes a `ClusterCloudflare.DurableObjectPool` Worker
|
|
3
|
+
* exports. Every entity type shares one class, `ClusterEntity`, whose objects
|
|
4
|
+
* are named by `(type, id)`; every workflow shares `ClusterWorkflow`, one
|
|
5
|
+
* object per execution; every singleton and cron shares `ClusterScheduled`,
|
|
6
|
+
* one object per name. What each object runs is host-neutral (see
|
|
7
|
+
* `internal/ObjectSharding.ts`); this module gives it workerd's storage.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
import * as Cloudflare from "@alchemy.run/cloudflare/Workers";
|
|
12
|
+
import { makeDurableObjectEngine } from "alchemy/Workers/DurableObjectEngine";
|
|
13
|
+
import * as Context from "effect/Context";
|
|
14
|
+
import * as Effect from "effect/Effect";
|
|
15
|
+
import * as Predicate from "effect/Predicate";
|
|
16
|
+
import { InstanceStorage } from "../../Host.js";
|
|
17
|
+
import { makeEntityObject, makeScheduledObject, NotHosted, registry, workflowClassOf, workflowObjectName, } from "../../internal/ObjectSharding.js";
|
|
18
|
+
/** Cloudflare, as a host of the cluster's objects. */
|
|
19
|
+
export const host = {
|
|
20
|
+
group: "cloudflare",
|
|
21
|
+
// workerd marks a transient failure `retryable`, and an `overloaded`
|
|
22
|
+
// object wants less traffic, not more.
|
|
23
|
+
isRetryable: (error) => Predicate.hasProperty(error, "cause") &&
|
|
24
|
+
Predicate.hasProperty(error.cause, "retryable") &&
|
|
25
|
+
error.cause.retryable === true &&
|
|
26
|
+
!(Predicate.hasProperty(error.cause, "overloaded") && error.cause.overloaded === true),
|
|
27
|
+
};
|
|
28
|
+
/** The most bytes workerd's SQLite stores in one value. */
|
|
29
|
+
const MAX_ENCODED_BYTES = 2 * 1024 * 1024;
|
|
30
|
+
const objectName = (state) => state.id.name ?? "";
|
|
31
|
+
/**
|
|
32
|
+
* The object's storage, called directly: a statement is one synchronous
|
|
33
|
+
* call, so the mailbox costs no event-loop turns.
|
|
34
|
+
*/
|
|
35
|
+
const storageOf = (state) => {
|
|
36
|
+
const storage = state.raw.storage;
|
|
37
|
+
return {
|
|
38
|
+
exec: (query, ...bindings) => Effect.sync(() => storage.sql.exec(query, ...bindings).toArray()),
|
|
39
|
+
// Every statement is synchronous, so the transaction is too.
|
|
40
|
+
transaction: (effect) => Effect.sync(() => storage.transactionSync(() => Effect.runSync(effect))),
|
|
41
|
+
getAlarm: Effect.promise(() => storage.getAlarm()),
|
|
42
|
+
setAlarm: (at) => Effect.promise(() => storage.setAlarm(at)),
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
/** Every entity instance: one object per `(type, id)`. */
|
|
46
|
+
export const EntityObject = Cloudflare.DurableObject()("ClusterEntity", Effect.gen(function* () {
|
|
47
|
+
const state = yield* Cloudflare.DurableObjectState;
|
|
48
|
+
return Effect.gen(function* () {
|
|
49
|
+
const runtime = yield* Effect.context();
|
|
50
|
+
return yield* makeEntityObject({
|
|
51
|
+
name: objectName(state),
|
|
52
|
+
host,
|
|
53
|
+
storage: storageOf(state),
|
|
54
|
+
// The object's own key-value storage, for platforms built on
|
|
55
|
+
// entities (a Domain model's) to keep each instance's data in.
|
|
56
|
+
runtime: Context.add(runtime, InstanceStorage, state.storage),
|
|
57
|
+
maxEncodedBytes: MAX_ENCODED_BYTES,
|
|
58
|
+
waitUntil: (effect) => state.waitUntil(effect).pipe(Effect.provideContext(runtime)),
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}));
|
|
62
|
+
/** Every singleton and cron: one object per name. */
|
|
63
|
+
export const ScheduledObject = Cloudflare.DurableObject()("ClusterScheduled", Effect.gen(function* () {
|
|
64
|
+
const state = yield* Cloudflare.DurableObjectState;
|
|
65
|
+
return Effect.gen(function* () {
|
|
66
|
+
const runtime = yield* Effect.context();
|
|
67
|
+
return yield* makeScheduledObject({
|
|
68
|
+
name: objectName(state),
|
|
69
|
+
storage: storageOf(state),
|
|
70
|
+
runtime,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}));
|
|
74
|
+
/**
|
|
75
|
+
* Every workflow execution: one object per `(workflow, execution)`, running
|
|
76
|
+
* Alchemy's Durable Object workflow engine. The engine declares a class per
|
|
77
|
+
* workflow; here each of those is a view onto this one class, so the Worker
|
|
78
|
+
* exports the same classes whatever workflows it hosts.
|
|
79
|
+
*/
|
|
80
|
+
export const WorkflowObject = Cloudflare.DurableObject()("ClusterWorkflow", Effect.gen(function* () {
|
|
81
|
+
const state = yield* Cloudflare.DurableObjectState;
|
|
82
|
+
return Effect.gen(function* () {
|
|
83
|
+
const className = workflowClassOf(objectName(state));
|
|
84
|
+
const impl = registry.workflows.get(className);
|
|
85
|
+
if (impl === undefined) {
|
|
86
|
+
return yield* Effect.die(new NotHosted({ message: `This Worker hosts no Cluster.Workflow '${className}'.` }));
|
|
87
|
+
}
|
|
88
|
+
const object = yield* impl.pipe(Effect.provideService(Cloudflare.DurableObjectState, state));
|
|
89
|
+
return (yield* object.pipe(Effect.provideService(Cloudflare.DurableObjectState, state)));
|
|
90
|
+
});
|
|
91
|
+
}));
|
|
92
|
+
/** The workflow engine, its executions on {@link WorkflowObject}. */
|
|
93
|
+
export const workflowEngine = (objects) => makeDurableObjectEngine({
|
|
94
|
+
declare: (className, impl) => Effect.sync(() => {
|
|
95
|
+
registry.workflows.set(className, impl);
|
|
96
|
+
return {
|
|
97
|
+
getByName: (executionId) => objects().getByName(workflowObjectName(className, executionId)),
|
|
98
|
+
};
|
|
99
|
+
}),
|
|
100
|
+
state: Cloudflare.DurableObjectState,
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=Objects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Objects.js","sources":["../../../src/Cloudflare/internal/Objects.ts"],"names":[],"mappings":"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"}
|
package/lib/Cron.d.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { InputProps } from "alchemy/Input";
|
|
2
|
+
import type { Named } from "alchemy/Named";
|
|
3
|
+
import type { PlatformIdentity, PlatformServices } from "alchemy/Platform";
|
|
4
|
+
import type { RuntimeContext } from "alchemy/RuntimeContext";
|
|
5
|
+
import type { Self } from "alchemy/Self";
|
|
6
|
+
import type { ClusterCron } from "effect/cluster";
|
|
7
|
+
import type * as ConfigError from "effect/Config";
|
|
8
|
+
import type * as Context from "effect/Context";
|
|
9
|
+
import * as Cron_ from "effect/Cron";
|
|
10
|
+
import type * as Duration from "effect/Duration";
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import type * as Layer from "effect/Layer";
|
|
13
|
+
import type { Scope } from "effect/Scope";
|
|
14
|
+
import { type HostedProps, type HostedResource } from "./internal/HostedPlatform.ts";
|
|
15
|
+
import type { Providers } from "./Providers.ts";
|
|
16
|
+
export declare const CronTypeId = "Cluster.Cron";
|
|
17
|
+
export type CronTypeId = typeof CronTypeId;
|
|
18
|
+
/** When a cron runs: its Layer's props, like Effect's `ClusterCron.make` options. */
|
|
19
|
+
export interface CronProps extends HostedProps {
|
|
20
|
+
/**
|
|
21
|
+
* The schedule, as a cron expression with an optional leading seconds
|
|
22
|
+
* field: `"0 3 * * *"`, `"*\/10 * * * * *"`.
|
|
23
|
+
*/
|
|
24
|
+
cron: string;
|
|
25
|
+
/** The time zone the expression is read in. @default UTC */
|
|
26
|
+
timeZone?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Compute each run from the previous run's scheduled time rather than
|
|
29
|
+
* from when it finished.
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
calculateNextRunFromPrevious?: boolean;
|
|
33
|
+
/** Skip a run that is due longer ago than this, as after an outage. */
|
|
34
|
+
skipIfOlderThan?: Duration.Input;
|
|
35
|
+
}
|
|
36
|
+
export interface ClusterCronResource extends HostedResource<CronTypeId, CronProps> {
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A cron as a runner runs it: its schedule, the job its Construction phase
|
|
40
|
+
* returned, and the services that phase ran with.
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
export interface HostedCron {
|
|
44
|
+
readonly kind: "cron";
|
|
45
|
+
readonly name: string;
|
|
46
|
+
readonly cron: Cron_.Cron;
|
|
47
|
+
readonly options: Pick<CronProps, "calculateNextRunFromPrevious" | "skipIfOlderThan">;
|
|
48
|
+
readonly run: Effect.Effect<void, unknown, Scope | ClusterCron.CurrentScheduledTime>;
|
|
49
|
+
readonly context: Context.Context<never>;
|
|
50
|
+
}
|
|
51
|
+
declare const InvalidCron_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 & {
|
|
52
|
+
readonly _tag: "Cluster.InvalidCron";
|
|
53
|
+
} & Readonly<A>;
|
|
54
|
+
/** A cron expression that doesn't parse. */
|
|
55
|
+
export declare class InvalidCron extends InvalidCron_base<{
|
|
56
|
+
readonly message: string;
|
|
57
|
+
}> {
|
|
58
|
+
}
|
|
59
|
+
type CronServices = ClusterCronResource | Providers | RuntimeContext | Self | Self<ClusterCronResource>;
|
|
60
|
+
/** The resource type behind every cron, which the providers register. @internal */
|
|
61
|
+
export declare const CronResource: import("alchemy").Platform<HostedResource<"Cluster.Cron", CronProps>, any, unknown, import("./internal/HostedRuntime.ts").HostedRuntimeContext<HostedCron>, {}, CronProps>;
|
|
62
|
+
export declare const CronProvider: () => Layer.Layer<import("alchemy").Provider<HostedResource<"Cluster.Cron", CronProps>>, never, never>;
|
|
63
|
+
/** A declared cron, implemented by `.make(props, impl)`. */
|
|
64
|
+
export type CronTag<Self, Name extends string> = Named<Name> & PlatformIdentity<Name> & {
|
|
65
|
+
/**
|
|
66
|
+
* The cron's implementation, as a Layer: its schedule, and an Effect
|
|
67
|
+
* that resolves bindings once per runner and returns the job to run.
|
|
68
|
+
* Provide a pool Layer to `impl` to deploy it; provide the returned
|
|
69
|
+
* Layer to the Stack.
|
|
70
|
+
*/
|
|
71
|
+
make<InitReq = never>(props: InputProps<CronProps>, impl: Effect.Effect<Effect.Effect<void, unknown, Scope | ClusterCron.CurrentScheduledTime>, ConfigError.ConfigError, InitReq>): Layer.Layer<Self, never, Providers | Exclude<InitReq, CronServices | PlatformServices>>;
|
|
72
|
+
new (_: never): {};
|
|
73
|
+
};
|
|
74
|
+
/** A cron declared with its implementation: the class is what deploys it. */
|
|
75
|
+
export type CronInline<Name extends string, Req> = Effect.Effect<ClusterCronResource, never, Req> & Named<Name> & PlatformIdentity<Name> & {
|
|
76
|
+
new (_: never): {};
|
|
77
|
+
};
|
|
78
|
+
/** The class surface of {@link Cron}. */
|
|
79
|
+
export interface CronClass {
|
|
80
|
+
<Self>(): {
|
|
81
|
+
/**
|
|
82
|
+
* Declared with its implementation: an Effect that resolves bindings
|
|
83
|
+
* once per runner and returns the job. Provide a pool Layer to
|
|
84
|
+
* `impl` to deploy it; `yield*` the class in the Stack.
|
|
85
|
+
*/
|
|
86
|
+
<const Name extends string, InitReq = never>(name: Name, props: InputProps<CronProps>, impl: Effect.Effect<Effect.Effect<void, unknown, Scope | ClusterCron.CurrentScheduledTime>, ConfigError.ConfigError, InitReq>): CronInline<Name, Providers | Exclude<InitReq, CronServices | PlatformServices>>;
|
|
87
|
+
/** A bare declaration, implemented by `.make(props, impl)`. */
|
|
88
|
+
<const Name extends string>(name: Name): CronTag<Self, Name>;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A **Cron** on an Effect Cluster: a job that runs on a schedule, once per
|
|
93
|
+
* scheduled time across the cluster, on whichever runner owns it. Each run
|
|
94
|
+
* is a persisted message, so a run due while its runner restarts still
|
|
95
|
+
* happens.
|
|
96
|
+
*
|
|
97
|
+
* Declare it by name, with its schedule, as Effect's `ClusterCron.make`
|
|
98
|
+
* takes it, and its implementation: an Effect that resolves bindings once
|
|
99
|
+
* per runner and returns the job. The job can read the time it was
|
|
100
|
+
* scheduled for with `ClusterCron.CurrentScheduledTime`. Providing a pool
|
|
101
|
+
* Layer to the implementation is what deploys it. Or declare it bare and
|
|
102
|
+
* implement it with `.make(props, impl)`, as a Layer.
|
|
103
|
+
*
|
|
104
|
+
* ### Declaring a cron
|
|
105
|
+
* **Example:** A nightly job
|
|
106
|
+
* ```typescript
|
|
107
|
+
* // src/Nightly.ts
|
|
108
|
+
* export default class Nightly extends Cluster.Cron<Nightly>()(
|
|
109
|
+
* "Nightly",
|
|
110
|
+
* { main: import.meta.url, cron: "0 3 * * *", skipIfOlderThan: "1 hour" },
|
|
111
|
+
* Effect.gen(function* () {
|
|
112
|
+
* const counters = yield* Counter;
|
|
113
|
+
* return Effect.gen(function* () {
|
|
114
|
+
* const counter = yield* counters.getByName("nightly");
|
|
115
|
+
* yield* counter.Increment({ amount: 1 });
|
|
116
|
+
* }).pipe(Effect.orDie);
|
|
117
|
+
* }).pipe(Effect.provide(Default)),
|
|
118
|
+
* ) {}
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* **Example:** Declared bare, and implemented as a Layer
|
|
122
|
+
* ```typescript
|
|
123
|
+
* // src/Nightly.ts
|
|
124
|
+
* export class Nightly extends Cluster.Cron<Nightly>()("Nightly") {}
|
|
125
|
+
*
|
|
126
|
+
* // src/NightlyLive.ts
|
|
127
|
+
* export default Nightly.make(
|
|
128
|
+
* { main: import.meta.url, cron: "0 3 * * *", skipIfOlderThan: "1 hour" },
|
|
129
|
+
* Effect.gen(function* () {
|
|
130
|
+
* const counters = yield* Counter;
|
|
131
|
+
* return Effect.gen(function* () {
|
|
132
|
+
* const counter = yield* counters.getByName("nightly");
|
|
133
|
+
* yield* counter.Increment({ amount: 1 });
|
|
134
|
+
* }).pipe(Effect.orDie);
|
|
135
|
+
* }).pipe(Effect.provide(Default)),
|
|
136
|
+
* );
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* @resource
|
|
140
|
+
*/
|
|
141
|
+
export declare const Cron: CronClass;
|
|
142
|
+
export {};
|
|
143
|
+
//# sourceMappingURL=Cron.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Cron.d.ts","sources":["../src/Cron.ts"],"names":[],"mappings":"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"}
|
package/lib/Cron.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as Cron_ from "effect/Cron";
|
|
2
|
+
import * as Data from "effect/Data";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Result from "effect/Result";
|
|
5
|
+
import { makeHostedPlatform, } from "./internal/HostedPlatform.js";
|
|
6
|
+
export const CronTypeId = "Cluster.Cron";
|
|
7
|
+
/** A cron expression that doesn't parse. */
|
|
8
|
+
export class InvalidCron extends Data.TaggedError("Cluster.InvalidCron") {
|
|
9
|
+
}
|
|
10
|
+
const { platform, provider, declare } = makeHostedPlatform(CronTypeId, (id, run, context, props) => ({
|
|
11
|
+
kind: "cron",
|
|
12
|
+
name: id,
|
|
13
|
+
// Checked while deploying, so it parses.
|
|
14
|
+
cron: Cron_.parse(props.cron, props.timeZone).success,
|
|
15
|
+
options: {
|
|
16
|
+
calculateNextRunFromPrevious: props.calculateNextRunFromPrevious,
|
|
17
|
+
skipIfOlderThan: props.skipIfOlderThan,
|
|
18
|
+
},
|
|
19
|
+
run: run,
|
|
20
|
+
context,
|
|
21
|
+
}),
|
|
22
|
+
// A schedule that doesn't parse fails the deploy, not the runner.
|
|
23
|
+
(id, _context, props) => {
|
|
24
|
+
const cron = Cron_.parse(props.cron, props.timeZone);
|
|
25
|
+
return Result.isSuccess(cron)
|
|
26
|
+
? Effect.void
|
|
27
|
+
: Effect.die(new InvalidCron({ message: `Cluster.Cron '${id}': ${cron.failure.message}` }));
|
|
28
|
+
});
|
|
29
|
+
/** The resource type behind every cron, which the providers register. @internal */
|
|
30
|
+
export const CronResource = platform;
|
|
31
|
+
export const CronProvider = provider;
|
|
32
|
+
/**
|
|
33
|
+
* A **Cron** on an Effect Cluster: a job that runs on a schedule, once per
|
|
34
|
+
* scheduled time across the cluster, on whichever runner owns it. Each run
|
|
35
|
+
* is a persisted message, so a run due while its runner restarts still
|
|
36
|
+
* happens.
|
|
37
|
+
*
|
|
38
|
+
* Declare it by name, with its schedule, as Effect's `ClusterCron.make`
|
|
39
|
+
* takes it, and its implementation: an Effect that resolves bindings once
|
|
40
|
+
* per runner and returns the job. The job can read the time it was
|
|
41
|
+
* scheduled for with `ClusterCron.CurrentScheduledTime`. Providing a pool
|
|
42
|
+
* Layer to the implementation is what deploys it. Or declare it bare and
|
|
43
|
+
* implement it with `.make(props, impl)`, as a Layer.
|
|
44
|
+
*
|
|
45
|
+
* ### Declaring a cron
|
|
46
|
+
* **Example:** A nightly job
|
|
47
|
+
* ```typescript
|
|
48
|
+
* // src/Nightly.ts
|
|
49
|
+
* export default class Nightly extends Cluster.Cron<Nightly>()(
|
|
50
|
+
* "Nightly",
|
|
51
|
+
* { main: import.meta.url, cron: "0 3 * * *", skipIfOlderThan: "1 hour" },
|
|
52
|
+
* Effect.gen(function* () {
|
|
53
|
+
* const counters = yield* Counter;
|
|
54
|
+
* return Effect.gen(function* () {
|
|
55
|
+
* const counter = yield* counters.getByName("nightly");
|
|
56
|
+
* yield* counter.Increment({ amount: 1 });
|
|
57
|
+
* }).pipe(Effect.orDie);
|
|
58
|
+
* }).pipe(Effect.provide(Default)),
|
|
59
|
+
* ) {}
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* **Example:** Declared bare, and implemented as a Layer
|
|
63
|
+
* ```typescript
|
|
64
|
+
* // src/Nightly.ts
|
|
65
|
+
* export class Nightly extends Cluster.Cron<Nightly>()("Nightly") {}
|
|
66
|
+
*
|
|
67
|
+
* // src/NightlyLive.ts
|
|
68
|
+
* export default Nightly.make(
|
|
69
|
+
* { main: import.meta.url, cron: "0 3 * * *", skipIfOlderThan: "1 hour" },
|
|
70
|
+
* Effect.gen(function* () {
|
|
71
|
+
* const counters = yield* Counter;
|
|
72
|
+
* return Effect.gen(function* () {
|
|
73
|
+
* const counter = yield* counters.getByName("nightly");
|
|
74
|
+
* yield* counter.Increment({ amount: 1 });
|
|
75
|
+
* }).pipe(Effect.orDie);
|
|
76
|
+
* }).pipe(Effect.provide(Default)),
|
|
77
|
+
* );
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @resource
|
|
81
|
+
*/
|
|
82
|
+
export const Cron = (() => (name, props, impl) => declare(name, {}, props, impl));
|
|
83
|
+
//# sourceMappingURL=Cron.js.map
|
package/lib/Cron.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Cron.js","sources":["../src/Cron.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,KAAK,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AAEpC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,EAGL,kBAAkB,GACnB,MAAM,8BAA8B,CAAC;AAGtC,MAAM,CAAC,MAAM,UAAU,GAAG,cAAc,CAAC;AAsCzC,4CAA4C;AAC5C,MAAM,OAAO,WAAY,SAAQ,IAAI,CAAC,WAAW,CAAC,qBAAqB,CAErE;CAAG;AASL,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,kBAAkB,CACxD,UAAU,EACV,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IAC5B,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,EAAE;IACR,yCAAyC;IACzC,IAAI,EAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAuC,CAAC,OAAO;IAC5F,OAAO,EAAE;QACP,4BAA4B,EAAE,KAAK,CAAC,4BAA4B;QAChE,eAAe,EAAE,KAAK,CAAC,eAAe;KACvC;IACD,GAAG,EAAE,GAAwB;IAC7B,OAAO;CACR,CAAC;AACF,kEAAkE;AAClE,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IACtB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;QAC3B,CAAC,CAAC,MAAM,CAAC,IAAI;QACb,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAChG,CAAC,CACF,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC;AACrC,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC;AAoDrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,MAAM,CAAC,MAAM,IAAI,GAAc,CAAC,GAAG,EAAE,CACnC,CAAC,IAAY,EAAE,KAA6B,EAAE,IAAc,EAAE,EAAE,CAC9D,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,CAAyB,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
* The private network runners must join to reach the database and each
|
|
11
|
+
* other. A pool Layer matches on `kind` and refuses a network it cannot
|
|
12
|
+
* join.
|
|
13
|
+
*/
|
|
14
|
+
export type ClusterNetwork = {
|
|
15
|
+
/** A Docker network; runner containers join it by name. */
|
|
16
|
+
readonly kind: "docker";
|
|
17
|
+
readonly name: Input<string>;
|
|
18
|
+
} | {
|
|
19
|
+
/** An AWS VPC; runners run in its subnets, in the pool's security group. */
|
|
20
|
+
readonly kind: "aws";
|
|
21
|
+
readonly vpcId: Input<string>;
|
|
22
|
+
readonly subnetIds: Input<string>[];
|
|
23
|
+
/** The group every runner joins: it reaches the database and the other runners. */
|
|
24
|
+
readonly securityGroupId: Input<string>;
|
|
25
|
+
} | {
|
|
26
|
+
/** A Fly private network; runners' Apps join it by name. */
|
|
27
|
+
readonly kind: "fly";
|
|
28
|
+
readonly network: Input<string>;
|
|
29
|
+
} | {
|
|
30
|
+
/** A Hetzner private Network; runners' Servers attach to it. */
|
|
31
|
+
readonly kind: "hetzner";
|
|
32
|
+
readonly networkId: Input<number>;
|
|
33
|
+
/** Its IPv4 range, where each runner finds its own address. */
|
|
34
|
+
readonly ipRange: string;
|
|
35
|
+
readonly location: string;
|
|
36
|
+
} | {
|
|
37
|
+
/** A GCP VPC; runners' instances join its subnetwork. */
|
|
38
|
+
readonly kind: "gcp";
|
|
39
|
+
readonly network: Input<string>;
|
|
40
|
+
readonly subnetwork: Input<string>;
|
|
41
|
+
readonly region: string;
|
|
42
|
+
/** The subnetwork's IPv4 range, where each runner finds its own address. */
|
|
43
|
+
readonly cidr: string;
|
|
44
|
+
/** The network tag every runner carries: it reaches the database and the other runners. */
|
|
45
|
+
readonly tag: string;
|
|
46
|
+
} | {
|
|
47
|
+
/** A Kubernetes cluster; runners run as a Deployment in its namespace. */
|
|
48
|
+
readonly kind: "kubernetes";
|
|
49
|
+
/** The cluster, as `@alchemy.run/kubernetes` takes it. */
|
|
50
|
+
readonly cluster: unknown;
|
|
51
|
+
readonly namespace: string;
|
|
52
|
+
};
|
|
53
|
+
/** What every database Layer gives the pool it is provided to. */
|
|
54
|
+
export interface DatabaseShape {
|
|
55
|
+
/**
|
|
56
|
+
* The Postgres connection URL runners use for runner registration,
|
|
57
|
+
* shard locks and persisted messages.
|
|
58
|
+
*/
|
|
59
|
+
readonly url: Input<Redacted.Redacted<string>>;
|
|
60
|
+
/** The network runners join to reach it, when it is private. */
|
|
61
|
+
readonly network?: ClusterNetwork;
|
|
62
|
+
/** Every shard group in the cluster, which each pool registers with. */
|
|
63
|
+
readonly shardGroups: ShardGroups;
|
|
64
|
+
}
|
|
65
|
+
declare const Database_base: Context.ServiceClass<Database, "Cluster.Database", DatabaseShape>;
|
|
66
|
+
/**
|
|
67
|
+
* The database an Effect Cluster coordinates through: runners register in
|
|
68
|
+
* it, lock their shards in it, and keep persisted messages in it. Each
|
|
69
|
+
* host's `Database` Layer provides it.
|
|
70
|
+
*/
|
|
71
|
+
export declare class Database extends Database_base {
|
|
72
|
+
}
|
|
73
|
+
export interface DatabaseProps {
|
|
74
|
+
/** A Postgres connection URL. */
|
|
75
|
+
url: Redacted.Redacted<string> | Config.Config<Redacted.Redacted<string>>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A Postgres database you already run, as an Effect Cluster `Database`
|
|
79
|
+
* Layer: Neon, PlanetScale, RDS, or any Postgres the pool can reach.
|
|
80
|
+
*
|
|
81
|
+
* ### Using an existing database
|
|
82
|
+
* **Example:** A Postgres URL from config
|
|
83
|
+
* ```typescript
|
|
84
|
+
* export const Database = Cluster.Database.fromUrl({
|
|
85
|
+
* url: Config.Redacted("CLUSTER_DATABASE_URL"),
|
|
86
|
+
* });
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @primitive
|
|
90
|
+
*/
|
|
91
|
+
export declare const fromUrl: (props: DatabaseProps) => Layer.Layer<Database, Config.ConfigError, Providers>;
|
|
92
|
+
/** The shard groups resource every database Layer declares for its cluster. @internal */
|
|
93
|
+
export declare const clusterShardGroups: (id: string) => Effect.Effect<ShardGroups, never, Providers>;
|
|
94
|
+
export {};
|
|
95
|
+
//# sourceMappingURL=Database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Database.d.ts","sources":["../src/Database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB;IACE,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC9B,GACD;IACE,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;IACpC,mFAAmF;IACnF,QAAQ,CAAC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACzC,GACD;IACE,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACjC,GACD;IACE,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAClC,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,GACD;IACE,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2FAA2F;IAC3F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,GACD;IACE,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEN,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/C,gEAAgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;;AAED;;;;GAIG;AACH,qBAAa,QAAS,SAAQ,aAA8D;CAAG;AAE/F,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;CAC3E;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,OAAO,UACX,aAAa,KACnB,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,SAAS,CAWnD,CAAC;AAEJ,yFAAyF;AACzF,eAAO,MAAM,kBAAkB,OAAQ,MAAM,iDAAwC,CAAC"}
|
package/lib/Database.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as Config from "effect/Config";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
import * as Redacted from "effect/Redacted";
|
|
6
|
+
import { ShardGroups } from "./ShardGroups.js";
|
|
7
|
+
/**
|
|
8
|
+
* The database an Effect Cluster coordinates through: runners register in
|
|
9
|
+
* it, lock their shards in it, and keep persisted messages in it. Each
|
|
10
|
+
* host's `Database` Layer provides it.
|
|
11
|
+
*/
|
|
12
|
+
export class Database extends Context.Service()("Cluster.Database") {
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A Postgres database you already run, as an Effect Cluster `Database`
|
|
16
|
+
* Layer: Neon, PlanetScale, RDS, or any Postgres the pool can reach.
|
|
17
|
+
*
|
|
18
|
+
* ### Using an existing database
|
|
19
|
+
* **Example:** A Postgres URL from config
|
|
20
|
+
* ```typescript
|
|
21
|
+
* export const Database = Cluster.Database.fromUrl({
|
|
22
|
+
* url: Config.Redacted("CLUSTER_DATABASE_URL"),
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @primitive
|
|
27
|
+
*/
|
|
28
|
+
export const fromUrl = (props) => Layer.effect(Database, 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 url = Config.isConfig(props.url) ? yield* props.url : props.url;
|
|
34
|
+
return { url, shardGroups: yield* clusterShardGroups("Database") };
|
|
35
|
+
}));
|
|
36
|
+
/** The shard groups resource every database Layer declares for its cluster. @internal */
|
|
37
|
+
export const clusterShardGroups = (id) => ShardGroups(`${id}ShardGroups`, {});
|
|
38
|
+
//# sourceMappingURL=Database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Database.js","sources":["../src/Database.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAkE/C;;;;GAIG;AACH,MAAM,OAAO,QAAS,SAAQ,OAAO,CAAC,OAAO,EAA2B,CAAC,kBAAkB,CAAC;CAAG;AAO/F;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CACrB,KAAoB,EACkC,EAAE,CACxD,KAAK,CAAC,MAAM,CACV,QAAQ,EACR,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,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;IACtE,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,UAAU,CAAC,EAAE,CAAC;AACrE,CAAC,CAAC,CACH,CAAC;AAEJ,yFAAyF;AACzF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as Docker from "@alchemy.run/docker";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
import { Database as ClusterDatabase } from "../Database.ts";
|
|
4
|
+
import type { Providers } from "../Providers.ts";
|
|
5
|
+
/** The Postgres release `ClusterDocker.Database` runs by default. */
|
|
6
|
+
export declare const DEFAULT_POSTGRES_IMAGE = "postgres:17-alpine";
|
|
7
|
+
export interface DatabaseProps {
|
|
8
|
+
/**
|
|
9
|
+
* Postgres image.
|
|
10
|
+
* @default "postgres:17-alpine"
|
|
11
|
+
*/
|
|
12
|
+
image?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Publish Postgres on this host port, to inspect it from your machine.
|
|
15
|
+
* @default not published
|
|
16
|
+
*/
|
|
17
|
+
port?: number;
|
|
18
|
+
/**
|
|
19
|
+
* The Docker engine to run on: a context name, a `Docker.Context`, or a
|
|
20
|
+
* `Docker.Swarm`.
|
|
21
|
+
* @default the current Docker context
|
|
22
|
+
*/
|
|
23
|
+
context?: Docker.Docker.EngineRef;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The database an Effect Cluster coordinates through, on a Docker swarm, as
|
|
27
|
+
* an `Cluster.Database` Layer: Postgres as a one-replica swarm service
|
|
28
|
+
* with its data on a named volume, on an overlay network the pool's runners join.
|
|
29
|
+
* The Docker engine must be in swarm mode (`docker swarm init`).
|
|
30
|
+
*
|
|
31
|
+
* One Postgres on one disk is meant for development and tests.
|
|
32
|
+
*
|
|
33
|
+
* ### Running a local database
|
|
34
|
+
* **Example:** Postgres for local entities
|
|
35
|
+
* ```typescript
|
|
36
|
+
* export const Database = ClusterDocker.Database("Cluster");
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @primitive
|
|
40
|
+
*/
|
|
41
|
+
export declare const Database: (id: string, props?: DatabaseProps) => Layer.Layer<ClusterDatabase, never, Providers | Docker.Providers>;
|
|
42
|
+
//# sourceMappingURL=Database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Database.d.ts","sources":["../../src/Docker/Database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,qBAAqB,CAAC;AAI9C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAsB,QAAQ,IAAI,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,qEAAqE;AACrE,eAAO,MAAM,sBAAsB,uBAAuB,CAAC;AAE3D,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;CACnC;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,QAAQ,OACf,MAAM,UACH,aAAa,KACnB,KAAK,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,SAAS,CA2ChE,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
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.js";
|
|
8
|
+
/** The Postgres release `ClusterDocker.Database` runs by default. */
|
|
9
|
+
export const DEFAULT_POSTGRES_IMAGE = "postgres:17-alpine";
|
|
10
|
+
/**
|
|
11
|
+
* The database an Effect Cluster coordinates through, on a Docker swarm, as
|
|
12
|
+
* an `Cluster.Database` Layer: Postgres as a one-replica swarm service
|
|
13
|
+
* with its data on a named volume, on an overlay network the pool's runners join.
|
|
14
|
+
* The Docker engine must be in swarm mode (`docker swarm init`).
|
|
15
|
+
*
|
|
16
|
+
* One Postgres on one disk is meant for development and tests.
|
|
17
|
+
*
|
|
18
|
+
* ### Running a local database
|
|
19
|
+
* **Example:** Postgres for local entities
|
|
20
|
+
* ```typescript
|
|
21
|
+
* export const Database = ClusterDocker.Database("Cluster");
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @primitive
|
|
25
|
+
*/
|
|
26
|
+
export const Database = (id, props = {}) => Layer.effect(ClusterDatabase, Effect.gen(function* () {
|
|
27
|
+
if (globalThis.__ALCHEMY_RUNTIME__) {
|
|
28
|
+
// Runners read their database from their own environment at runtime.
|
|
29
|
+
return { url: Redacted.make(""), shardGroups: undefined };
|
|
30
|
+
}
|
|
31
|
+
const context = props.context === undefined ? {} : { context: props.context };
|
|
32
|
+
const password = yield* Random(`${id}Password`, { bytes: 24 });
|
|
33
|
+
const network = yield* Docker.Network(`${id}Network`, { driver: "overlay", ...context });
|
|
34
|
+
const volumeContext = Docker.dockerEngineContextName(props.context);
|
|
35
|
+
const data = yield* Docker.Volume(`${id}Data`, volumeContext === undefined ? {} : { context: volumeContext });
|
|
36
|
+
const alias = "cluster-db";
|
|
37
|
+
const db = yield* Docker.Service(`${id}Postgres`, {
|
|
38
|
+
image: props.image ?? DEFAULT_POSTGRES_IMAGE,
|
|
39
|
+
...context,
|
|
40
|
+
environment: { POSTGRES_PASSWORD: password.text, POSTGRES_DB: "cluster" },
|
|
41
|
+
ports: props.port === undefined ? [] : [{ external: props.port, internal: 5432 }],
|
|
42
|
+
volumes: [{ hostPath: data.name, containerPath: "/var/lib/postgresql/data" }],
|
|
43
|
+
networks: [{ name: network.name, aliases: [alias] }],
|
|
44
|
+
replicas: 1,
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
// Ordered after the service: runners connect once it exists.
|
|
48
|
+
url: Output.all(db.name, password.text).pipe(Output.map(([, text]) => Redacted.make(`postgres://postgres:${encodeURIComponent(text.pipe(Redacted.value))}@${alias}:5432/cluster`))),
|
|
49
|
+
network: { kind: "docker", name: network.name },
|
|
50
|
+
shardGroups: yield* clusterShardGroups(id),
|
|
51
|
+
};
|
|
52
|
+
}));
|
|
53
|
+
//# sourceMappingURL=Database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Database.js","sources":["../../src/Docker/Database.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as Docker from "@alchemy.run/docker";
|
|
2
|
+
import type { Stack } from "alchemy/Stack";
|
|
3
|
+
import type * as Layer from "effect/Layer";
|
|
4
|
+
import type { Database } from "../Database.ts";
|
|
5
|
+
import { type PoolSettings } from "../internal/PoolHost.ts";
|
|
6
|
+
import type { PoolHost } from "../internal/PoolHostService.ts";
|
|
7
|
+
import type { Providers } from "../Providers.ts";
|
|
8
|
+
export interface SwarmPoolOptions extends PoolSettings {
|
|
9
|
+
/**
|
|
10
|
+
* Number of runners.
|
|
11
|
+
* @default 1
|
|
12
|
+
*/
|
|
13
|
+
replicas?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Publish the gateway on this host port, so code outside the swarm (a
|
|
16
|
+
* test, a script) can call the entities at `http://localhost:<port>`.
|
|
17
|
+
* @default not published
|
|
18
|
+
*/
|
|
19
|
+
gatewayPort?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run a pool of Effect Cluster runners on a Docker swarm: a swarm service whose
|
|
23
|
+
* tasks host every `Cluster.Entity` given this pool, on the
|
|
24
|
+
* database's overlay network. Runners find each other through the
|
|
25
|
+
* database and talk on the network; the gateway is served on the same
|
|
26
|
+
* network. Requires an `Cluster.Database` on a Docker network —
|
|
27
|
+
* `ClusterDocker.Database`.
|
|
28
|
+
*
|
|
29
|
+
* Every entity given the same pool shares it: declare the pool once
|
|
30
|
+
* and provide them to each class.
|
|
31
|
+
*
|
|
32
|
+
* ### Running entities on Docker
|
|
33
|
+
* **Example:** Two runners on the local swarm
|
|
34
|
+
* ```typescript
|
|
35
|
+
* export const Database = ClusterDocker.Database("Cluster");
|
|
36
|
+
* export const Default = ClusterDocker.SwarmPool("Default", { replicas: 2 }).pipe(
|
|
37
|
+
* Layer.provide(Database),
|
|
38
|
+
* );
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @primitive
|
|
42
|
+
*/
|
|
43
|
+
export declare const SwarmPool: (id: string, options?: SwarmPoolOptions) => Layer.Layer<PoolHost, never, Database | Stack | Providers | Docker.Providers>;
|
|
44
|
+
//# sourceMappingURL=SwarmPool.d.ts.map
|