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.
Files changed (77) hide show
  1. package/bin/{alchemy-effect.js → alchemy.js} +171 -171
  2. package/bin/alchemy.js.map +1 -0
  3. package/bin/{alchemy-effect.sh → alchemy.sh} +2 -2
  4. package/lib/cli/index.d.ts.map +1 -1
  5. package/lib/cli/index.js +167 -167
  6. package/lib/cli/index.js.map +1 -1
  7. package/package.json +54 -45
  8. package/src/AWS/AGENTS.md +10 -10
  9. package/src/AWS/Assets.ts +1 -1
  10. package/src/AWS/AuthProvider.ts +392 -0
  11. package/src/AWS/Credentials.ts +0 -1
  12. package/src/AWS/DynamoDB/Table.ts +63 -10
  13. package/src/AWS/EC2/VpcEndpoint.ts +4 -4
  14. package/src/AWS/EC2/hosted.ts +1 -1
  15. package/src/AWS/ECS/Task.ts +1 -1
  16. package/src/AWS/Kinesis/Stream.ts +44 -8
  17. package/src/AWS/Lambda/Function.ts +218 -10
  18. package/src/AWS/S3/Bucket.ts +26 -19
  19. package/src/AWS/S3/BucketNotifications.ts +1 -1
  20. package/src/AWS/SNS/Topic.ts +47 -10
  21. package/src/AWS/SQS/Queue.ts +66 -0
  22. package/src/Auth/AuthProvider.ts +33 -0
  23. package/src/Auth/Credentials.ts +56 -0
  24. package/src/Auth/Env.ts +45 -0
  25. package/src/Auth/Profile.ts +72 -0
  26. package/src/Auth/index.ts +2 -0
  27. package/src/Cloudflare/Auth/AuthProvider.ts +670 -0
  28. package/src/Cloudflare/Auth/OAuthClient.ts +315 -0
  29. package/src/Cloudflare/Container/Container.ts +122 -0
  30. package/src/Cloudflare/Container/ContainerApplication.ts +6 -3
  31. package/src/Cloudflare/Container/ContainerBinding.ts +2 -4
  32. package/src/Cloudflare/Container/StartContainer.ts +1 -1
  33. package/src/Cloudflare/D1/D1Database.ts +23 -4
  34. package/src/Cloudflare/KV/KVNamespace.ts +25 -0
  35. package/src/Cloudflare/Providers.ts +1 -6
  36. package/src/Cloudflare/R2/R2Bucket.ts +45 -2
  37. package/src/Cloudflare/Website/StaticSite.ts +101 -15
  38. package/src/Cloudflare/Website/Vite.ts +86 -20
  39. package/src/Cloudflare/Workers/Assets.ts +170 -207
  40. package/src/Cloudflare/Workers/DurableObjectNamespace.ts +629 -0
  41. package/src/Cloudflare/Workers/DurableObjectState.ts +63 -0
  42. package/src/Cloudflare/Workers/DurableObjectStorage.ts +256 -0
  43. package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +66 -7
  44. package/src/Cloudflare/Workers/InferEnv.ts +11 -7
  45. package/src/Cloudflare/Workers/Rpc.ts +13 -2
  46. package/src/Cloudflare/Workers/ScheduledEvents.ts +185 -0
  47. package/src/Cloudflare/Workers/WebSocket.ts +1 -1
  48. package/src/Cloudflare/Workers/Worker.ts +572 -67
  49. package/src/Cloudflare/Workers/Workflow.ts +53 -11
  50. package/src/Cloudflare/Workers/index.ts +4 -1
  51. package/src/Construct.ts +2 -2
  52. package/src/GitHub/Comment.ts +224 -0
  53. package/src/GitHub/Secret.ts +257 -0
  54. package/src/GitHub/Variable.ts +166 -0
  55. package/src/GitHub/index.ts +3 -0
  56. package/src/Kubernetes/client.ts +2 -2
  57. package/src/Output.ts +1 -1
  58. package/src/Platform.ts +10 -5
  59. package/src/Provider.ts +1 -1
  60. package/src/Resource.ts +4 -4
  61. package/src/Test/Vitest.ts +4 -3
  62. package/src/Util/Clank.ts +77 -0
  63. package/src/Util/PlatformServices.ts +21 -0
  64. package/src/Util/dedent.ts +59 -0
  65. package/src/Util/index.ts +1 -0
  66. package/bin/alchemy-effect.js.map +0 -1
  67. package/src/Cloudflare/Workers/DurableObject.ts +0 -527
  68. package/src/Daemon/Client.ts +0 -116
  69. package/src/Daemon/Config.ts +0 -28
  70. package/src/Daemon/Errors.ts +0 -48
  71. package/src/Daemon/Lock.ts +0 -162
  72. package/src/Daemon/ProcessRegistry.ts +0 -284
  73. package/src/Daemon/RpcSchema.ts +0 -43
  74. package/src/Daemon/RpcServer.ts +0 -231
  75. package/src/Daemon/index.ts +0 -27
  76. package/src/Spawn.ts +0 -23
  77. /package/bin/{alchemy-effect.ts → alchemy.ts} +0 -0
@@ -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
- });
@@ -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