alchemy 2.0.0-beta.2 → 2.0.0-beta.4
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/bin/{alchemy-effect.js → alchemy.js} +171 -171
- package/bin/alchemy.js.map +1 -0
- package/bin/{alchemy-effect.sh → alchemy.sh} +2 -2
- package/lib/cli/index.d.ts.map +1 -1
- package/lib/cli/index.js +167 -167
- package/lib/cli/index.js.map +1 -1
- package/package.json +54 -45
- package/src/AWS/AGENTS.md +10 -10
- package/src/AWS/Assets.ts +1 -1
- package/src/AWS/AuthProvider.ts +392 -0
- package/src/AWS/Credentials.ts +0 -1
- package/src/AWS/DynamoDB/Table.ts +63 -10
- package/src/AWS/EC2/VpcEndpoint.ts +4 -4
- package/src/AWS/EC2/hosted.ts +1 -1
- package/src/AWS/ECS/Task.ts +1 -1
- package/src/AWS/Kinesis/Stream.ts +44 -8
- package/src/AWS/Lambda/Function.ts +218 -10
- package/src/AWS/S3/Bucket.ts +26 -19
- package/src/AWS/S3/BucketNotifications.ts +1 -1
- package/src/AWS/SNS/Topic.ts +47 -10
- package/src/AWS/SQS/Queue.ts +66 -0
- package/src/Auth/AuthProvider.ts +33 -0
- package/src/Auth/Credentials.ts +56 -0
- package/src/Auth/Env.ts +45 -0
- package/src/Auth/Profile.ts +72 -0
- package/src/Auth/index.ts +2 -0
- package/src/Cloudflare/Auth/AuthProvider.ts +670 -0
- package/src/Cloudflare/Auth/OAuthClient.ts +315 -0
- package/src/Cloudflare/Container/Container.ts +122 -0
- package/src/Cloudflare/Container/ContainerApplication.ts +6 -3
- package/src/Cloudflare/Container/ContainerBinding.ts +2 -4
- package/src/Cloudflare/Container/StartContainer.ts +1 -1
- package/src/Cloudflare/D1/D1Database.ts +23 -4
- package/src/Cloudflare/KV/KVNamespace.ts +25 -0
- package/src/Cloudflare/Providers.ts +1 -6
- package/src/Cloudflare/R2/R2Bucket.ts +45 -2
- package/src/Cloudflare/Website/StaticSite.ts +101 -15
- package/src/Cloudflare/Website/Vite.ts +86 -20
- package/src/Cloudflare/Workers/Assets.ts +170 -207
- package/src/Cloudflare/Workers/DurableObjectNamespace.ts +629 -0
- package/src/Cloudflare/Workers/DurableObjectState.ts +63 -0
- package/src/Cloudflare/Workers/DurableObjectStorage.ts +256 -0
- package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +66 -7
- package/src/Cloudflare/Workers/InferEnv.ts +11 -7
- package/src/Cloudflare/Workers/Rpc.ts +13 -2
- package/src/Cloudflare/Workers/ScheduledEvents.ts +185 -0
- package/src/Cloudflare/Workers/WebSocket.ts +1 -1
- package/src/Cloudflare/Workers/Worker.ts +572 -67
- package/src/Cloudflare/Workers/Workflow.ts +53 -11
- package/src/Cloudflare/Workers/index.ts +4 -1
- package/src/Construct.ts +2 -2
- package/src/GitHub/Comment.ts +224 -0
- package/src/GitHub/Secret.ts +257 -0
- package/src/GitHub/Variable.ts +166 -0
- package/src/GitHub/index.ts +3 -0
- package/src/Kubernetes/client.ts +2 -2
- package/src/Output.ts +1 -1
- package/src/Platform.ts +10 -5
- package/src/Provider.ts +1 -1
- package/src/Resource.ts +4 -4
- package/src/Test/Vitest.ts +4 -3
- package/src/Util/Clank.ts +77 -0
- package/src/Util/PlatformServices.ts +21 -0
- package/src/Util/dedent.ts +59 -0
- package/src/Util/index.ts +1 -0
- package/bin/alchemy-effect.js.map +0 -1
- package/src/Cloudflare/Workers/DurableObject.ts +0 -527
- package/src/Daemon/Client.ts +0 -116
- package/src/Daemon/Config.ts +0 -28
- package/src/Daemon/Errors.ts +0 -48
- package/src/Daemon/Lock.ts +0 -162
- package/src/Daemon/ProcessRegistry.ts +0 -284
- package/src/Daemon/RpcSchema.ts +0 -43
- package/src/Daemon/RpcServer.ts +0 -231
- package/src/Daemon/index.ts +0 -27
- package/src/Spawn.ts +0 -23
- /package/bin/{alchemy-effect.ts → alchemy.ts} +0 -0
package/src/Daemon/RpcServer.ts
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import * as NodeSocketServer from "@effect/platform-node/NodeSocketServer";
|
|
2
|
-
import * as Deferred from "effect/Deferred";
|
|
3
|
-
import * as Duration from "effect/Duration";
|
|
4
|
-
import * as Effect from "effect/Effect";
|
|
5
|
-
import * as FileSystem from "effect/FileSystem";
|
|
6
|
-
import * as Layer from "effect/Layer";
|
|
7
|
-
import * as Path from "effect/Path";
|
|
8
|
-
import * as Ref from "effect/Ref";
|
|
9
|
-
import * as Schedule from "effect/Schedule";
|
|
10
|
-
import { RpcSerialization, RpcServer } from "effect/unstable/rpc";
|
|
11
|
-
import { type SQLiteConnection } from "../SQLite/index.ts";
|
|
12
|
-
import { SQLite } from "../SQLite/SQLite.ts";
|
|
13
|
-
import {
|
|
14
|
-
ALCHEMY_DIR,
|
|
15
|
-
DB_FILE,
|
|
16
|
-
ensureAlchemyDir,
|
|
17
|
-
PROCESSES_DIR,
|
|
18
|
-
resolveSocketPath,
|
|
19
|
-
SOCKET_FILE,
|
|
20
|
-
} from "./Config.ts";
|
|
21
|
-
import { ProcessAlreadyExists, ProcessNotFound } from "./Errors.ts";
|
|
22
|
-
import { acquireLock, isProcessAlive } from "./Lock.ts";
|
|
23
|
-
import { makeProcessRegistry } from "./ProcessRegistry.ts";
|
|
24
|
-
import { DaemonRpcs } from "./RpcSchema.ts";
|
|
25
|
-
|
|
26
|
-
// Re-export platform layers needed by the entry point
|
|
27
|
-
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
28
|
-
import { BunSQLite } from "../SQLite/index.ts";
|
|
29
|
-
|
|
30
|
-
export { BunSQLite, NodeServices };
|
|
31
|
-
|
|
32
|
-
const IDLE_TIMEOUT = Duration.seconds(10);
|
|
33
|
-
|
|
34
|
-
export const main = Effect.gen(function* () {
|
|
35
|
-
const shutdownSignal = yield* Deferred.make<void>();
|
|
36
|
-
|
|
37
|
-
yield* Effect.logInfo("Acquiring daemon lock…");
|
|
38
|
-
yield* acquireLock(shutdownSignal);
|
|
39
|
-
|
|
40
|
-
const db = yield* initDb;
|
|
41
|
-
yield* cleanupOrphans(db);
|
|
42
|
-
|
|
43
|
-
yield* cleanStaleSocket;
|
|
44
|
-
|
|
45
|
-
const socketPath = yield* resolveSocketPath;
|
|
46
|
-
yield* Effect.logInfo("Starting daemon socket server…");
|
|
47
|
-
|
|
48
|
-
const socketServerLayer = NodeSocketServer.layer({ path: socketPath });
|
|
49
|
-
|
|
50
|
-
yield* Effect.addFinalizer(() =>
|
|
51
|
-
Effect.gen(function* () {
|
|
52
|
-
const fs = yield* FileSystem.FileSystem;
|
|
53
|
-
yield* fs.remove(socketPath, { force: true }).pipe(Effect.ignore);
|
|
54
|
-
}),
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const watchdog = yield* makeIdleWatchdog(shutdownSignal);
|
|
58
|
-
yield* watchdog.startWatchdog;
|
|
59
|
-
yield* watchdog.recordHeartbeat;
|
|
60
|
-
|
|
61
|
-
const procRegistry = yield* makeProcessRegistry(db);
|
|
62
|
-
|
|
63
|
-
const rpcServerLayer = RpcServer.layer(DaemonRpcs).pipe(
|
|
64
|
-
Layer.provide(RpcServer.layerProtocolSocketServer),
|
|
65
|
-
Layer.provide(RpcSerialization.layerJson),
|
|
66
|
-
Layer.provide(socketServerLayer),
|
|
67
|
-
Layer.provide(makeHandlersLayer(watchdog, procRegistry)),
|
|
68
|
-
Layer.provide(NodeServices.layer),
|
|
69
|
-
Layer.provide(BunSQLite),
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
yield* Layer.launch(rpcServerLayer).pipe(Effect.forkScoped);
|
|
73
|
-
|
|
74
|
-
yield* Effect.logInfo("Daemon ready");
|
|
75
|
-
|
|
76
|
-
yield* watchdog.awaitShutdown;
|
|
77
|
-
}).pipe(Effect.scoped);
|
|
78
|
-
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
// Socket lifecycle
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
|
|
83
|
-
const cleanStaleSocket = Effect.gen(function* () {
|
|
84
|
-
const fs = yield* FileSystem.FileSystem;
|
|
85
|
-
const path = yield* Path.Path;
|
|
86
|
-
const dir = path.resolve(ALCHEMY_DIR);
|
|
87
|
-
const socketPath = path.join(dir, SOCKET_FILE);
|
|
88
|
-
yield* fs.remove(socketPath, { force: true });
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
// SQLite — process metadata persistence
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
|
|
95
|
-
const initDb = Effect.gen(function* () {
|
|
96
|
-
const sqlite = yield* SQLite;
|
|
97
|
-
const path = yield* Path.Path;
|
|
98
|
-
const dir = yield* ensureAlchemyDir;
|
|
99
|
-
const dbPath = path.join(dir, DB_FILE);
|
|
100
|
-
const db = yield* sqlite.open(dbPath);
|
|
101
|
-
|
|
102
|
-
yield* db.exec(`
|
|
103
|
-
CREATE TABLE IF NOT EXISTS processes (
|
|
104
|
-
id TEXT PRIMARY KEY,
|
|
105
|
-
pid INTEGER NOT NULL,
|
|
106
|
-
command TEXT NOT NULL,
|
|
107
|
-
args TEXT NOT NULL,
|
|
108
|
-
cwd TEXT,
|
|
109
|
-
started_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
110
|
-
)
|
|
111
|
-
`);
|
|
112
|
-
|
|
113
|
-
return db;
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
interface ProcessRow {
|
|
117
|
-
id: string;
|
|
118
|
-
pid: number;
|
|
119
|
-
command: string;
|
|
120
|
-
args: string;
|
|
121
|
-
cwd: string | null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
// Boot cleanup — kill orphans from a previous crashed daemon
|
|
126
|
-
// ---------------------------------------------------------------------------
|
|
127
|
-
|
|
128
|
-
const cleanupOrphans = (db: SQLiteConnection) =>
|
|
129
|
-
Effect.gen(function* () {
|
|
130
|
-
const fs = yield* FileSystem.FileSystem;
|
|
131
|
-
const path = yield* Path.Path;
|
|
132
|
-
const dir = yield* ensureAlchemyDir;
|
|
133
|
-
const processesDir = path.join(dir, PROCESSES_DIR);
|
|
134
|
-
|
|
135
|
-
const stmt = yield* db.prepare<ProcessRow>("SELECT id, pid FROM processes");
|
|
136
|
-
const orphans = yield* stmt.all();
|
|
137
|
-
|
|
138
|
-
if (orphans.length === 0) return;
|
|
139
|
-
|
|
140
|
-
yield* Effect.logInfo(`Cleaning up ${orphans.length} orphan process(es)…`);
|
|
141
|
-
|
|
142
|
-
for (const orphan of orphans) {
|
|
143
|
-
yield* isProcessAlive(orphan.pid).pipe(
|
|
144
|
-
Effect.flatMap((alive) =>
|
|
145
|
-
alive
|
|
146
|
-
? Effect.sync(() => {
|
|
147
|
-
try {
|
|
148
|
-
process.kill(orphan.pid, "SIGKILL");
|
|
149
|
-
} catch {}
|
|
150
|
-
})
|
|
151
|
-
: Effect.void,
|
|
152
|
-
),
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
yield* fs
|
|
156
|
-
.remove(path.join(processesDir, orphan.id), {
|
|
157
|
-
recursive: true,
|
|
158
|
-
force: true,
|
|
159
|
-
})
|
|
160
|
-
.pipe(Effect.ignore);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const delStmt = yield* db.prepare("DELETE FROM processes");
|
|
164
|
-
yield* delStmt.run();
|
|
165
|
-
|
|
166
|
-
yield* Effect.logInfo(`Cleaned up ${orphans.length} orphan(s)`);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
// Idle shutdown — heartbeat-based
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
|
|
173
|
-
const makeIdleWatchdog = (shutdownSignal: Deferred.Deferred<void>) =>
|
|
174
|
-
Effect.gen(function* () {
|
|
175
|
-
const lastHeartbeat = yield* Ref.make(Date.now());
|
|
176
|
-
|
|
177
|
-
const recordHeartbeat = Ref.set(lastHeartbeat, Date.now());
|
|
178
|
-
|
|
179
|
-
const startWatchdog = Effect.gen(function* () {
|
|
180
|
-
yield* Effect.gen(function* () {
|
|
181
|
-
yield* Effect.sleep(IDLE_TIMEOUT);
|
|
182
|
-
const last = yield* Ref.get(lastHeartbeat);
|
|
183
|
-
const elapsed = Date.now() - last;
|
|
184
|
-
if (elapsed >= Duration.toMillis(IDLE_TIMEOUT)) {
|
|
185
|
-
yield* Effect.logInfo("No heartbeat received — shutting down daemon");
|
|
186
|
-
yield* Deferred.succeed(shutdownSignal, void 0);
|
|
187
|
-
}
|
|
188
|
-
}).pipe(
|
|
189
|
-
Effect.repeat(Schedule.spaced(IDLE_TIMEOUT)),
|
|
190
|
-
Effect.asVoid,
|
|
191
|
-
Effect.forkChild,
|
|
192
|
-
);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const awaitShutdown = Deferred.await(shutdownSignal);
|
|
196
|
-
|
|
197
|
-
return { recordHeartbeat, startWatchdog, awaitShutdown };
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
// RPC handler layer
|
|
202
|
-
// ---------------------------------------------------------------------------
|
|
203
|
-
|
|
204
|
-
const makeHandlersLayer = (
|
|
205
|
-
watchdog: Effect.Success<ReturnType<typeof makeIdleWatchdog>>,
|
|
206
|
-
procRegistry: Effect.Success<ReturnType<typeof makeProcessRegistry>>,
|
|
207
|
-
) =>
|
|
208
|
-
DaemonRpcs.toLayer({
|
|
209
|
-
heartbeat: () =>
|
|
210
|
-
Effect.gen(function* () {
|
|
211
|
-
yield* Effect.logInfo("Heartbeat received");
|
|
212
|
-
yield* watchdog.recordHeartbeat;
|
|
213
|
-
}),
|
|
214
|
-
spawn: (req) =>
|
|
215
|
-
procRegistry.spawnProcess(req).pipe(
|
|
216
|
-
Effect.catchIf(
|
|
217
|
-
(e): e is Exclude<typeof e, ProcessAlreadyExists> =>
|
|
218
|
-
!(e instanceof ProcessAlreadyExists),
|
|
219
|
-
(e) => Effect.die(e),
|
|
220
|
-
),
|
|
221
|
-
),
|
|
222
|
-
kill: (req) =>
|
|
223
|
-
procRegistry.killProcess(req).pipe(
|
|
224
|
-
Effect.catchIf(
|
|
225
|
-
(e): e is Exclude<typeof e, ProcessNotFound> =>
|
|
226
|
-
!(e instanceof ProcessNotFound),
|
|
227
|
-
(e) => Effect.die(e),
|
|
228
|
-
),
|
|
229
|
-
),
|
|
230
|
-
watch: (req) => procRegistry.watchProcess(req),
|
|
231
|
-
});
|
package/src/Daemon/index.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
ALCHEMY_DIR,
|
|
3
|
-
DB_FILE,
|
|
4
|
-
ensureAlchemyDir,
|
|
5
|
-
LOCK_DIR_NAME,
|
|
6
|
-
PID_FILE_NAME,
|
|
7
|
-
PROCESSES_DIR,
|
|
8
|
-
resolveAlchemyDir,
|
|
9
|
-
resolveSocketPath,
|
|
10
|
-
SOCKET_FILE,
|
|
11
|
-
} from "./Config.ts";
|
|
12
|
-
|
|
13
|
-
export { Daemon, DaemonLive, makeClient } from "./Client.ts";
|
|
14
|
-
export type { DaemonClient } from "./Client.ts";
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
DaemonAlreadyRunning,
|
|
18
|
-
DaemonConnectFailed,
|
|
19
|
-
DaemonSocketNotReady,
|
|
20
|
-
LockCompromised,
|
|
21
|
-
ProcessAlreadyExists,
|
|
22
|
-
ProcessNotFound,
|
|
23
|
-
} from "./Errors.ts";
|
|
24
|
-
|
|
25
|
-
export { DaemonRpcs } from "./RpcSchema.ts";
|
|
26
|
-
|
|
27
|
-
export { main as daemonMain } from "./RpcServer.ts";
|
package/src/Spawn.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
|
-
import { Daemon } from "./Daemon/Client.ts";
|
|
3
|
-
|
|
4
|
-
export const spawn = Effect.fnUntraced(function* (
|
|
5
|
-
id: string,
|
|
6
|
-
command: string,
|
|
7
|
-
args?: string[],
|
|
8
|
-
options?: { cwd?: string; env?: Record<string, string> },
|
|
9
|
-
) {
|
|
10
|
-
const daemon = yield* Daemon;
|
|
11
|
-
const handle = yield* daemon.spawn({
|
|
12
|
-
id,
|
|
13
|
-
command,
|
|
14
|
-
args: args ?? [],
|
|
15
|
-
...options,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
daemon.watch({
|
|
19
|
-
clientId: "my",
|
|
20
|
-
id,
|
|
21
|
-
});
|
|
22
|
-
return handle;
|
|
23
|
-
});
|
|
File without changes
|