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,48 +0,0 @@
1
- import * as Data from "effect/Data";
2
- import * as Schema from "effect/Schema";
3
-
4
- // Schema-based errors used in the RPC contract (shared between client and server)
5
-
6
- export class ProcessAlreadyExists extends Schema.TaggedClass<ProcessAlreadyExists>()(
7
- "ProcessAlreadyExists",
8
- { id: Schema.String },
9
- ) {}
10
-
11
- export class ProcessNotFound extends Schema.TaggedClass<ProcessNotFound>()(
12
- "ProcessNotFound",
13
- { id: Schema.String },
14
- ) {}
15
-
16
- // Data-based errors used internally by the daemon runtime
17
-
18
- export class DaemonAlreadyRunning extends Data.TaggedError(
19
- "DaemonAlreadyRunning",
20
- )<{
21
- readonly pid: number | undefined;
22
- }> {
23
- get message() {
24
- return this.pid !== undefined
25
- ? `Daemon already running (pid ${this.pid})`
26
- : "Daemon already running";
27
- }
28
- }
29
-
30
- export class LockCompromised extends Data.TaggedError("LockCompromised")<{
31
- readonly lockDir: string;
32
- }> {}
33
-
34
- export class DaemonSocketNotReady extends Data.TaggedError(
35
- "DaemonSocketNotReady",
36
- ) {
37
- get message() {
38
- return "Daemon socket is not ready yet";
39
- }
40
- }
41
-
42
- export class DaemonConnectFailed extends Data.TaggedError(
43
- "DaemonConnectFailed",
44
- ) {
45
- get message() {
46
- return "Failed to connect to daemon";
47
- }
48
- }
@@ -1,162 +0,0 @@
1
- import * as Deferred from "effect/Deferred";
2
- import * as Duration from "effect/Duration";
3
- import * as Effect from "effect/Effect";
4
- import * as Fiber from "effect/Fiber";
5
- import * as FileSystem from "effect/FileSystem";
6
- import * as Option from "effect/Option";
7
- import * as Path from "effect/Path";
8
- import * as Ref from "effect/Ref";
9
- import * as Schedule from "effect/Schedule";
10
- import { ensureAlchemyDir, LOCK_DIR_NAME, PID_FILE_NAME } from "./Config.ts";
11
- import { DaemonAlreadyRunning } from "./Errors.ts";
12
-
13
- const STALE_THRESHOLD = Duration.seconds(10);
14
- const LOCK_UPDATE_INTERVAL = Duration.seconds(4);
15
-
16
- export const acquireLock = (shutdownSignal: Deferred.Deferred<void>) =>
17
- Effect.gen(function* () {
18
- const fs = yield* FileSystem.FileSystem;
19
- const path = yield* Path.Path;
20
- const dir = yield* ensureAlchemyDir;
21
- const lockDir = path.join(dir, LOCK_DIR_NAME);
22
- const pidFile = path.join(lockDir, PID_FILE_NAME);
23
-
24
- const tryMkdir = Effect.gen(function* () {
25
- yield* fs.makeDirectory(lockDir);
26
- yield* fs.writeFileString(pidFile, String(process.pid));
27
- });
28
-
29
- yield* tryMkdir.pipe(
30
- Effect.catchTag("PlatformError", (err) => {
31
- if (err.reason._tag !== "AlreadyExists") return Effect.fail(err);
32
-
33
- return Effect.gen(function* () {
34
- const stale = yield* isLockStale(pidFile);
35
- if (!stale) {
36
- const pid = yield* readLockPid(lockDir);
37
- return yield* new DaemonAlreadyRunning({ pid });
38
- }
39
-
40
- const pid = yield* readLockPid(lockDir);
41
- if (pid !== undefined) {
42
- const alive = yield* isProcessAlive(pid);
43
- if (alive) {
44
- return yield* new DaemonAlreadyRunning({ pid });
45
- }
46
- }
47
-
48
- yield* Effect.logWarning(
49
- `Removing stale lock (previous pid: ${pid ?? "unknown"})`,
50
- );
51
- yield* fs.remove(lockDir, { recursive: true, force: true });
52
-
53
- yield* tryMkdir.pipe(
54
- Effect.catchTag("PlatformError", (retryErr) =>
55
- retryErr.reason._tag !== "AlreadyExists"
56
- ? Effect.fail<DaemonAlreadyRunning | typeof retryErr>(retryErr)
57
- : Effect.gen(function* () {
58
- const winnerPid = yield* readLockPid(lockDir);
59
- return yield* new DaemonAlreadyRunning({ pid: winnerPid });
60
- }),
61
- ),
62
- );
63
- });
64
- }),
65
- );
66
-
67
- const updaterFiber = yield* startLockUpdater(lockDir, shutdownSignal);
68
-
69
- yield* Effect.addFinalizer(() =>
70
- Effect.gen(function* () {
71
- yield* Fiber.interrupt(updaterFiber).pipe(Effect.ignore);
72
- yield* fs
73
- .remove(lockDir, { recursive: true, force: true })
74
- .pipe(Effect.ignore);
75
- }),
76
- );
77
- });
78
-
79
- export const isProcessAlive = (pid: number): Effect.Effect<boolean> =>
80
- Effect.sync(() => {
81
- try {
82
- process.kill(pid, 0);
83
- return true;
84
- } catch {
85
- return false;
86
- }
87
- });
88
-
89
- const readLockPid = (lockDir: string) =>
90
- Effect.gen(function* () {
91
- const fs = yield* FileSystem.FileSystem;
92
- const path = yield* Path.Path;
93
- const pidFile = path.join(lockDir, PID_FILE_NAME);
94
- const contents = yield* fs
95
- .readFileString(pidFile)
96
- .pipe(Effect.catchTag("PlatformError", () => Effect.succeed("")));
97
- const pid = parseInt(contents.trim(), 10);
98
- return isNaN(pid) ? undefined : pid;
99
- });
100
-
101
- const isLockStale = (pidFile: string) =>
102
- Effect.gen(function* () {
103
- const fs = yield* FileSystem.FileSystem;
104
- const info = yield* fs
105
- .stat(pidFile)
106
- .pipe(Effect.catchTag("PlatformError", () => Effect.succeed(undefined)));
107
- if (info === undefined) return true;
108
- const mtime = Option.getOrElse(info.mtime, () => new Date(0));
109
- const age = Date.now() - mtime.getTime();
110
- return age > Duration.toMillis(STALE_THRESHOLD);
111
- });
112
-
113
- const startLockUpdater = (
114
- lockDir: string,
115
- shutdownSignal: Deferred.Deferred<void>,
116
- ) =>
117
- Effect.gen(function* () {
118
- const fs = yield* FileSystem.FileSystem;
119
- const path = yield* Path.Path;
120
- const pidFile = path.join(lockDir, PID_FILE_NAME);
121
- const lastMtime = yield* Ref.make<number>(Date.now());
122
-
123
- const touch = Effect.gen(function* () {
124
- const info = yield* fs
125
- .stat(pidFile)
126
- .pipe(
127
- Effect.catchTag("PlatformError", () => Effect.succeed(undefined)),
128
- );
129
-
130
- if (info === undefined) {
131
- yield* Effect.logError("Lock compromised: pid file disappeared");
132
- yield* Deferred.succeed(shutdownSignal, void 0);
133
- return;
134
- }
135
-
136
- const currentMtime = Option.getOrElse(info.mtime, () => new Date(0));
137
- const expected = yield* Ref.get(lastMtime);
138
-
139
- if (Math.abs(currentMtime.getTime() - expected) > 1000) {
140
- yield* Effect.logError(
141
- `Lock compromised: mtime changed externally (expected ${expected}, got ${currentMtime.getTime()})`,
142
- );
143
- yield* Deferred.succeed(shutdownSignal, void 0);
144
- return;
145
- }
146
-
147
- const now = new Date();
148
- yield* fs.utimes(pidFile, now, now).pipe(
149
- Effect.catchTag("PlatformError", (err) => {
150
- Effect.logWarning(`Failed to update lock mtime: ${err.message}`);
151
- return Effect.void;
152
- }),
153
- );
154
- yield* Ref.set(lastMtime, now.getTime());
155
- });
156
-
157
- return yield* touch.pipe(
158
- Effect.repeat(Schedule.spaced(LOCK_UPDATE_INTERVAL)),
159
- Effect.asVoid,
160
- Effect.forkChild,
161
- );
162
- });
@@ -1,284 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as FileSystem from "effect/FileSystem";
3
- import * as HashMap from "effect/HashMap";
4
- import * as Option from "effect/Option";
5
- import * as Path from "effect/Path";
6
- import * as PubSub from "effect/PubSub";
7
- import * as Ref from "effect/Ref";
8
- import * as Scope from "effect/Scope";
9
- import * as Stream from "effect/Stream";
10
- import * as ChildProcess from "effect/unstable/process/ChildProcess";
11
- import {
12
- ChildProcessSpawner,
13
- type ChildProcessHandle,
14
- } from "effect/unstable/process/ChildProcessSpawner";
15
- import { type SQLiteConnection } from "../SQLite/index.ts";
16
- import { ensureAlchemyDir, PROCESSES_DIR } from "./Config.ts";
17
- import { ProcessAlreadyExists, ProcessNotFound } from "./Errors.ts";
18
-
19
- interface OutputMessage {
20
- readonly fd: "stdout" | "stderr";
21
- readonly text: string;
22
- }
23
-
24
- interface ManagedProcess {
25
- readonly id: string;
26
- readonly handle: ChildProcessHandle;
27
- readonly pubsub: PubSub.PubSub<OutputMessage>;
28
- readonly stdoutLogPath: string;
29
- readonly stderrLogPath: string;
30
- readonly cursors: Ref.Ref<
31
- HashMap.HashMap<string, { stdoutOffset: number; stderrOffset: number }>
32
- >;
33
- }
34
-
35
- type Registry = Ref.Ref<HashMap.HashMap<string, ManagedProcess>>;
36
-
37
- export const makeProcessRegistry = (db: SQLiteConnection) =>
38
- Effect.gen(function* () {
39
- const registry: Registry = yield* Ref.make(
40
- HashMap.empty<string, ManagedProcess>(),
41
- );
42
-
43
- const fs = yield* FileSystem.FileSystem;
44
- const pathSvc = yield* Path.Path;
45
- const spawner = yield* ChildProcessSpawner;
46
- const alchemyDir = yield* ensureAlchemyDir;
47
- const processesDir = pathSvc.join(alchemyDir, PROCESSES_DIR);
48
- yield* fs.makeDirectory(processesDir, { recursive: true });
49
-
50
- const spawnProcess = (req: {
51
- id: string;
52
- command: string;
53
- args: ReadonlyArray<string>;
54
- cwd?: string;
55
- env?: Record<string, string>;
56
- }) =>
57
- Effect.gen(function* () {
58
- const current = yield* Ref.get(registry);
59
- const existing = HashMap.get(current, req.id);
60
-
61
- if (Option.isSome(existing)) {
62
- const alive = yield* existing.value.handle.isRunning;
63
- if (alive) {
64
- return yield* Effect.fail(new ProcessAlreadyExists({ id: req.id }));
65
- }
66
- yield* Ref.update(registry, HashMap.remove(req.id));
67
- }
68
-
69
- const logDir = pathSvc.join(processesDir, req.id);
70
- yield* fs.makeDirectory(logDir, { recursive: true });
71
- const stdoutLogPath = pathSvc.join(logDir, "stdout.log");
72
- const stderrLogPath = pathSvc.join(logDir, "stderr.log");
73
-
74
- yield* fs
75
- .writeFile(stdoutLogPath, new Uint8Array(0))
76
- .pipe(Effect.ignore);
77
- yield* fs
78
- .writeFile(stderrLogPath, new Uint8Array(0))
79
- .pipe(Effect.ignore);
80
-
81
- const cmd = ChildProcess.make(req.command, req.args as string[], {
82
- cwd: req.cwd,
83
- env: req.env,
84
- });
85
- const childScope = yield* Scope.make();
86
- const handle = yield* Scope.provide(spawner.spawn(cmd), childScope);
87
-
88
- const insertStmt = yield* db.prepare(
89
- "INSERT OR REPLACE INTO processes (id, pid, command, args, cwd) VALUES (?, ?, ?, ?, ?)",
90
- );
91
- yield* insertStmt.run(
92
- req.id,
93
- handle.pid,
94
- req.command,
95
- JSON.stringify(req.args),
96
- req.cwd ?? null,
97
- );
98
-
99
- const pubsub = yield* PubSub.unbounded<OutputMessage>();
100
- const cursors = yield* Ref.make(
101
- HashMap.empty<
102
- string,
103
- { stdoutOffset: number; stderrOffset: number }
104
- >(),
105
- );
106
-
107
- const collectStream = (
108
- stream: Stream.Stream<Uint8Array, any>,
109
- fd: "stdout" | "stderr",
110
- logPath: string,
111
- ) =>
112
- Effect.scoped(
113
- Effect.gen(function* () {
114
- const logFile = yield* fs.open(logPath, { flag: "a" });
115
-
116
- yield* stream.pipe(
117
- Stream.runForEach((chunk) =>
118
- Effect.gen(function* () {
119
- yield* logFile.writeAll(chunk);
120
- const text = new TextDecoder().decode(chunk);
121
- yield* PubSub.publish(pubsub, { fd, text });
122
- }),
123
- ),
124
- Effect.ignore,
125
- );
126
- }),
127
- );
128
-
129
- yield* collectStream(handle.stdout, "stdout", stdoutLogPath).pipe(
130
- Effect.forkDetach,
131
- );
132
- yield* collectStream(handle.stderr, "stderr", stderrLogPath).pipe(
133
- Effect.forkDetach,
134
- );
135
-
136
- const managed: ManagedProcess = {
137
- id: req.id,
138
- handle,
139
- pubsub,
140
- stdoutLogPath,
141
- stderrLogPath,
142
- cursors,
143
- };
144
-
145
- yield* Ref.update(registry, HashMap.set(req.id, managed));
146
-
147
- return { pid: handle.pid };
148
- });
149
-
150
- const killProcess = (req: { id: string; killSignal?: string }) =>
151
- Effect.gen(function* () {
152
- const current = yield* Ref.get(registry);
153
- const existing = HashMap.get(current, req.id);
154
-
155
- if (Option.isNone(existing)) {
156
- return yield* Effect.fail(new ProcessNotFound({ id: req.id }));
157
- }
158
-
159
- const proc = existing.value;
160
- yield* proc.handle
161
- .kill(
162
- req.killSignal
163
- ? { killSignal: req.killSignal as ChildProcess.Signal }
164
- : undefined,
165
- )
166
- .pipe(Effect.ignore);
167
- yield* proc.handle.exitCode.pipe(Effect.ignore);
168
-
169
- const delStmt = yield* db.prepare("DELETE FROM processes WHERE id = ?");
170
- yield* delStmt.run(req.id);
171
-
172
- yield* Ref.update(registry, HashMap.remove(req.id));
173
- });
174
-
175
- const watchProcess = (req: { id: string; clientId: string }) =>
176
- Stream.unwrap(
177
- Effect.gen(function* () {
178
- const current = yield* Ref.get(registry);
179
- const existing = HashMap.get(current, req.id);
180
-
181
- if (Option.isNone(existing)) {
182
- return yield* Effect.fail(new ProcessNotFound({ id: req.id }));
183
- }
184
-
185
- const proc = existing.value;
186
-
187
- const allCursors = yield* Ref.get(proc.cursors);
188
- const cursor = Option.getOrElse(
189
- HashMap.get(allCursors, req.clientId),
190
- () => ({ stdoutOffset: 0, stderrOffset: 0 }),
191
- );
192
-
193
- const replayFile = (
194
- logPath: string,
195
- fd: "stdout" | "stderr",
196
- offset: number,
197
- ) =>
198
- Effect.gen(function* () {
199
- const content = yield* fs
200
- .readFile(logPath)
201
- .pipe(
202
- Effect.catchTag("PlatformError", () =>
203
- Effect.succeed(new Uint8Array(0)),
204
- ),
205
- );
206
- if (offset >= content.byteLength)
207
- return { messages: [] as OutputMessage[], newOffset: offset };
208
- const chunk = content.slice(offset);
209
- const text = new TextDecoder().decode(chunk);
210
- return {
211
- messages: [{ fd, text } as OutputMessage],
212
- newOffset: content.byteLength,
213
- };
214
- });
215
-
216
- const stdoutReplay = yield* replayFile(
217
- proc.stdoutLogPath,
218
- "stdout",
219
- cursor.stdoutOffset,
220
- );
221
- const stderrReplay = yield* replayFile(
222
- proc.stderrLogPath,
223
- "stderr",
224
- cursor.stderrOffset,
225
- );
226
-
227
- const replayMessages = [
228
- ...stdoutReplay.messages,
229
- ...stderrReplay.messages,
230
- ].filter((m) => m.text.length > 0);
231
-
232
- const cursorUpdate = Stream.fromEffect(
233
- Effect.as(
234
- Ref.update(
235
- proc.cursors,
236
- HashMap.set(req.clientId, {
237
- stdoutOffset: stdoutReplay.newOffset,
238
- stderrOffset: stderrReplay.newOffset,
239
- }),
240
- ),
241
- undefined as never,
242
- ),
243
- ).pipe(Stream.filter((_): _ is never => false));
244
-
245
- const replayStream = Stream.concat(
246
- Stream.fromIterable(replayMessages),
247
- cursorUpdate,
248
- );
249
-
250
- const isRunning = yield* proc.handle.isRunning.pipe(
251
- Effect.catchTag("PlatformError", () => Effect.succeed(false)),
252
- );
253
-
254
- if (!isRunning) {
255
- return replayStream;
256
- }
257
-
258
- const liveStream = Stream.fromPubSub(proc.pubsub).pipe(
259
- Stream.tap((msg) =>
260
- Ref.update(proc.cursors, (cursorsMap) => {
261
- const cur = Option.getOrElse(
262
- HashMap.get(cursorsMap, req.clientId),
263
- () => ({
264
- stdoutOffset: cursor.stdoutOffset,
265
- stderrOffset: cursor.stderrOffset,
266
- }),
267
- );
268
- const byteLen = new TextEncoder().encode(msg.text).byteLength;
269
- return HashMap.set(cursorsMap, req.clientId, {
270
- stdoutOffset:
271
- cur.stdoutOffset + (msg.fd === "stdout" ? byteLen : 0),
272
- stderrOffset:
273
- cur.stderrOffset + (msg.fd === "stderr" ? byteLen : 0),
274
- });
275
- }),
276
- ),
277
- );
278
-
279
- return Stream.concat(replayStream, liveStream);
280
- }),
281
- );
282
-
283
- return { registry, spawnProcess, killProcess, watchProcess };
284
- });
@@ -1,43 +0,0 @@
1
- import * as Schema from "effect/Schema";
2
- import { Rpc, RpcGroup } from "effect/unstable/rpc";
3
- import { ProcessAlreadyExists, ProcessNotFound } from "./Errors.ts";
4
-
5
- const heartbeat = Rpc.make("heartbeat", {
6
- success: Schema.Void,
7
- });
8
-
9
- const spawn = Rpc.make("spawn", {
10
- payload: {
11
- id: Schema.String,
12
- command: Schema.String,
13
- args: Schema.Array(Schema.String),
14
- cwd: Schema.optionalKey(Schema.String),
15
- env: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
16
- },
17
- error: ProcessAlreadyExists,
18
- success: Schema.Struct({ pid: Schema.Number }),
19
- });
20
-
21
- const kill = Rpc.make("kill", {
22
- payload: {
23
- id: Schema.String,
24
- killSignal: Schema.optionalKey(Schema.String),
25
- },
26
- error: ProcessNotFound,
27
- success: Schema.Void,
28
- });
29
-
30
- const watch = Rpc.make("watch", {
31
- payload: {
32
- id: Schema.String,
33
- clientId: Schema.String,
34
- },
35
- success: Schema.Struct({
36
- fd: Schema.Literals(["stdout", "stderr"]),
37
- text: Schema.String,
38
- }),
39
- error: ProcessNotFound,
40
- stream: true,
41
- });
42
-
43
- export class DaemonRpcs extends RpcGroup.make(heartbeat, spawn, kill, watch) {}