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
|
@@ -1,527 +0,0 @@
|
|
|
1
|
-
import type * as cf from "@cloudflare/workers-types";
|
|
2
|
-
import * as Context from "effect/Context";
|
|
3
|
-
import * as Effect from "effect/Effect";
|
|
4
|
-
import * as Option from "effect/Option";
|
|
5
|
-
import type { HttpServerError } from "effect/unstable/http/HttpServerError";
|
|
6
|
-
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
|
|
7
|
-
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
|
|
8
|
-
import type { HttpEffect } from "../../Http.ts";
|
|
9
|
-
import * as Output from "../../Output.ts";
|
|
10
|
-
import type { PlatformServices } from "../../Platform.ts";
|
|
11
|
-
import { effectClass, taggedFunction } from "../../Util/effect.ts";
|
|
12
|
-
import { makeRpcStub } from "./Rpc.ts";
|
|
13
|
-
import { fromWebSocket, type DurableWebSocket } from "./WebSocket.ts";
|
|
14
|
-
import { Worker, WorkerEnvironment, type WorkerServices } from "./Worker.ts";
|
|
15
|
-
|
|
16
|
-
export interface DurableObjectExport {
|
|
17
|
-
readonly kind: "durableObject";
|
|
18
|
-
readonly make: (
|
|
19
|
-
state: cf.DurableObjectState,
|
|
20
|
-
env: any,
|
|
21
|
-
) => Effect.Effect<Record<string, unknown>>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const isDurableObjectExport = (
|
|
25
|
-
value: unknown,
|
|
26
|
-
): value is DurableObjectExport =>
|
|
27
|
-
typeof value === "object" &&
|
|
28
|
-
value !== null &&
|
|
29
|
-
"kind" in value &&
|
|
30
|
-
(value as any).kind === "durableObject";
|
|
31
|
-
|
|
32
|
-
export type DurableObjectId = cf.DurableObjectId;
|
|
33
|
-
export type DurableObjectJurisdiction = cf.DurableObjectJurisdiction;
|
|
34
|
-
export type DurableObjectNamespaceGetDurableObjectOptions =
|
|
35
|
-
cf.DurableObjectNamespaceGetDurableObjectOptions;
|
|
36
|
-
|
|
37
|
-
export type AlarmInvocationInfo = cf.AlarmInvocationInfo;
|
|
38
|
-
|
|
39
|
-
type TypeId = "Cloudflare.DurableObjectNamespace";
|
|
40
|
-
const TypeId = "Cloudflare.DurableObjectNamespace";
|
|
41
|
-
|
|
42
|
-
export interface DurableObjectNamespace<Shape = unknown> {
|
|
43
|
-
Type: TypeId;
|
|
44
|
-
name: string;
|
|
45
|
-
namespaceId: Output.Output<string>;
|
|
46
|
-
getByName: (name: string) => DurableObjectStub<Shape>;
|
|
47
|
-
newUniqueId: () => DurableObjectId;
|
|
48
|
-
idFromName: (name: string) => DurableObjectId;
|
|
49
|
-
idFromString: (id: string) => DurableObjectId;
|
|
50
|
-
get: (
|
|
51
|
-
id: DurableObjectId,
|
|
52
|
-
options?: DurableObjectNamespaceGetDurableObjectOptions,
|
|
53
|
-
) => DurableObjectStub<Shape>;
|
|
54
|
-
jurisdiction: (
|
|
55
|
-
jurisdiction: DurableObjectJurisdiction,
|
|
56
|
-
) => DurableObjectNamespace<Shape>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface DurableObjectShape {
|
|
60
|
-
fetch?: HttpEffect<DurableObjectState>;
|
|
61
|
-
alarm?: (
|
|
62
|
-
alarmInfo?: AlarmInvocationInfo,
|
|
63
|
-
) => Effect.Effect<void, never, never>;
|
|
64
|
-
webSocketMessage?: (
|
|
65
|
-
socket: DurableWebSocket,
|
|
66
|
-
message: string | Uint8Array,
|
|
67
|
-
) => Effect.Effect<void>;
|
|
68
|
-
webSocketClose?: (
|
|
69
|
-
socket: DurableWebSocket,
|
|
70
|
-
code: number,
|
|
71
|
-
reason: string,
|
|
72
|
-
wasClean: boolean,
|
|
73
|
-
) => Effect.Effect<void>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export type DurableObjectServices =
|
|
77
|
-
| DurableObjectNamespace
|
|
78
|
-
| DurableObjectState
|
|
79
|
-
| WorkerServices
|
|
80
|
-
| PlatformServices;
|
|
81
|
-
|
|
82
|
-
export interface DurableObjectNamespaceClass extends Effect.Effect<
|
|
83
|
-
DurableObjectNamespace,
|
|
84
|
-
never,
|
|
85
|
-
DurableObjectNamespace
|
|
86
|
-
> {
|
|
87
|
-
<Self>(): {
|
|
88
|
-
<Shape, InitReq = never>(
|
|
89
|
-
name: string,
|
|
90
|
-
impl: Effect.Effect<
|
|
91
|
-
Effect.Effect<Shape, never, DurableObjectServices>,
|
|
92
|
-
never,
|
|
93
|
-
InitReq
|
|
94
|
-
>,
|
|
95
|
-
): Effect.Effect<
|
|
96
|
-
DurableObjectNamespace<Self>,
|
|
97
|
-
never,
|
|
98
|
-
Worker | Exclude<InitReq, DurableObjectServices>
|
|
99
|
-
> & {
|
|
100
|
-
new (_: never): Shape;
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
<Shape, InitReq = never>(
|
|
104
|
-
name: string,
|
|
105
|
-
impl: Effect.Effect<
|
|
106
|
-
Effect.Effect<Shape, never, DurableObjectServices>,
|
|
107
|
-
never,
|
|
108
|
-
InitReq
|
|
109
|
-
>,
|
|
110
|
-
): Effect.Effect<
|
|
111
|
-
DurableObjectNamespace<Shape>,
|
|
112
|
-
never,
|
|
113
|
-
Worker | Exclude<InitReq, DurableObjectServices>
|
|
114
|
-
>;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export class DurableObjectNamespaceScope extends Context.Service<
|
|
118
|
-
DurableObjectNamespaceScope,
|
|
119
|
-
DurableObjectNamespace
|
|
120
|
-
>()("Cloudflare.DurableObjectNamespace") {}
|
|
121
|
-
|
|
122
|
-
export const DurableObjectNamespace: DurableObjectNamespaceClass =
|
|
123
|
-
taggedFunction(DurableObjectNamespaceScope, ((
|
|
124
|
-
...args:
|
|
125
|
-
| []
|
|
126
|
-
| [
|
|
127
|
-
name: string,
|
|
128
|
-
impl: Effect.Effect<
|
|
129
|
-
Effect.Effect<
|
|
130
|
-
DurableObjectNamespace<any>,
|
|
131
|
-
never,
|
|
132
|
-
DurableObjectState
|
|
133
|
-
>
|
|
134
|
-
>,
|
|
135
|
-
]
|
|
136
|
-
) =>
|
|
137
|
-
args.length === 0
|
|
138
|
-
? DurableObjectNamespace
|
|
139
|
-
: effectClass(
|
|
140
|
-
Effect.gen(function* () {
|
|
141
|
-
const [namespace, impl] = args;
|
|
142
|
-
const worker = yield* Worker;
|
|
143
|
-
|
|
144
|
-
yield* worker.bind`${namespace}`({
|
|
145
|
-
// TODO(sam): automate class migrations, probably in the provider
|
|
146
|
-
bindings: [
|
|
147
|
-
{
|
|
148
|
-
type: "durable_object_namespace",
|
|
149
|
-
name: namespace,
|
|
150
|
-
className: namespace,
|
|
151
|
-
// scriptName:
|
|
152
|
-
// binding.scriptName === props.workerName
|
|
153
|
-
// ? undefined
|
|
154
|
-
// : binding.scriptName,
|
|
155
|
-
// environment: binding.environment,
|
|
156
|
-
// namespaceId: binding.namespaceId,
|
|
157
|
-
},
|
|
158
|
-
],
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const services =
|
|
162
|
-
yield* Effect.context<Effect.Services<typeof impl>>();
|
|
163
|
-
|
|
164
|
-
yield* worker.export(namespace, {
|
|
165
|
-
kind: "durableObject",
|
|
166
|
-
make: (state: cf.DurableObjectState, env: any) => {
|
|
167
|
-
const doState = fromDurableObjectState(state);
|
|
168
|
-
return constructor.pipe(
|
|
169
|
-
Effect.provideContext(services),
|
|
170
|
-
Effect.provideService(DurableObjectState, doState),
|
|
171
|
-
Effect.provideService(WorkerEnvironment, env),
|
|
172
|
-
Effect.map((methods: any) => {
|
|
173
|
-
console.log(
|
|
174
|
-
"[DurableObject] wrapping methods:",
|
|
175
|
-
Object.keys(methods),
|
|
176
|
-
"own:",
|
|
177
|
-
Object.getOwnPropertyNames(methods),
|
|
178
|
-
);
|
|
179
|
-
const wrapped: Record<string, unknown> = {};
|
|
180
|
-
for (const key of Object.getOwnPropertyNames(methods)) {
|
|
181
|
-
const value = methods[key];
|
|
182
|
-
if (Effect.isEffect(value)) {
|
|
183
|
-
wrapped[key] = (
|
|
184
|
-
value as Effect.Effect<any, any, any>
|
|
185
|
-
).pipe(
|
|
186
|
-
Effect.provideService(DurableObjectState, doState),
|
|
187
|
-
);
|
|
188
|
-
} else if (typeof value === "function") {
|
|
189
|
-
wrapped[key] = (...args: unknown[]) => {
|
|
190
|
-
const result = (value as Function)(...args);
|
|
191
|
-
if (Effect.isEffect(result)) {
|
|
192
|
-
return (
|
|
193
|
-
result as Effect.Effect<any, any, any>
|
|
194
|
-
).pipe(
|
|
195
|
-
Effect.provideService(
|
|
196
|
-
DurableObjectState,
|
|
197
|
-
doState,
|
|
198
|
-
),
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
return result;
|
|
202
|
-
};
|
|
203
|
-
} else {
|
|
204
|
-
wrapped[key] = value;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return wrapped;
|
|
208
|
-
}),
|
|
209
|
-
);
|
|
210
|
-
},
|
|
211
|
-
} satisfies DurableObjectExport);
|
|
212
|
-
|
|
213
|
-
const binding = yield* Effect.serviceOption(WorkerEnvironment).pipe(
|
|
214
|
-
Effect.map(Option.getOrUndefined),
|
|
215
|
-
Effect.flatMap((env) => {
|
|
216
|
-
if (env === undefined) {
|
|
217
|
-
// should be fine to return undefined here (it is only undefined at plantime)
|
|
218
|
-
return Effect.succeed(undefined);
|
|
219
|
-
}
|
|
220
|
-
const ns = env[namespace];
|
|
221
|
-
if (!ns) {
|
|
222
|
-
return Effect.die(
|
|
223
|
-
new Error(
|
|
224
|
-
`DurableObjectNamespace '${namespace}' not found`,
|
|
225
|
-
),
|
|
226
|
-
);
|
|
227
|
-
} else if (typeof ns.getByName === "function") {
|
|
228
|
-
return Effect.succeed(ns);
|
|
229
|
-
} else {
|
|
230
|
-
return Effect.die(
|
|
231
|
-
new Error(
|
|
232
|
-
`DurableObjectNamespace '${namespace}' is not a DurableObjectNamespace`,
|
|
233
|
-
),
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
}),
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
const namespaceId = worker.durableObjectNamespaces.pipe(
|
|
240
|
-
Output.map(
|
|
241
|
-
(durableObjectNamespaces) =>
|
|
242
|
-
durableObjectNamespaces?.[namespace],
|
|
243
|
-
),
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
const self = {
|
|
247
|
-
Type: TypeId,
|
|
248
|
-
LogicalId: namespace,
|
|
249
|
-
name: namespace,
|
|
250
|
-
namespaceId,
|
|
251
|
-
getByName: (name: string) => makeRpcStub(binding.getByName(name)),
|
|
252
|
-
// newUniqueId: () => use((ns) => ns.newUniqueId()),
|
|
253
|
-
// idFromName: (name: string) => use((ns) => ns.idFromName(name)),
|
|
254
|
-
// idFromString: (id: string) => use((ns) => ns.idFromString(id)),
|
|
255
|
-
// get: (
|
|
256
|
-
// id: cf.DurableObjectId,
|
|
257
|
-
// options?: cf.DurableObjectNamespaceGetDurableObjectOptions,
|
|
258
|
-
// ) => use((ns) => makeRpcStub(ns.get(id, options))),
|
|
259
|
-
// jurisdiction: (jurisdiction: cf.DurableObjectJurisdiction) =>
|
|
260
|
-
// use((ns) => ns.jurisdiction(jurisdiction) as any),
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
const constructor = yield* impl.pipe(
|
|
264
|
-
Effect.provideService(DurableObjectNamespaceScope, self as any),
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
return self;
|
|
268
|
-
}),
|
|
269
|
-
)) as any);
|
|
270
|
-
|
|
271
|
-
export type DurableObjectStub<Shape> = {
|
|
272
|
-
// TODO(sam): do we need to transform? hopefully not
|
|
273
|
-
[key in keyof Shape]: Shape[key];
|
|
274
|
-
} & {
|
|
275
|
-
fetch: (
|
|
276
|
-
request: HttpServerRequest.HttpServerRequest,
|
|
277
|
-
) => Effect.Effect<
|
|
278
|
-
HttpServerResponse.HttpServerResponse,
|
|
279
|
-
HttpServerError,
|
|
280
|
-
never
|
|
281
|
-
>;
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
export class DurableObjectState extends Context.Service<
|
|
285
|
-
DurableObjectState,
|
|
286
|
-
{
|
|
287
|
-
// TODO(sam): is this needed when we have Effect?
|
|
288
|
-
// waitUntil(promise: Promise<any>): Effect.Effect<void>;
|
|
289
|
-
|
|
290
|
-
// TODO(sam): what are these? Where do they come from?
|
|
291
|
-
// readonly props: Props;
|
|
292
|
-
|
|
293
|
-
readonly id: cf.DurableObjectId;
|
|
294
|
-
readonly storage: DurableObjectStorage;
|
|
295
|
-
// TODO(sam): effect-native interface for container
|
|
296
|
-
container?: cf.Container;
|
|
297
|
-
blockConcurrencyWhile<T>(
|
|
298
|
-
callback: () => Effect.Effect<T>,
|
|
299
|
-
): Effect.Effect<T>;
|
|
300
|
-
acceptWebSocket(ws: DurableWebSocket, tags?: string[]): Effect.Effect<void>;
|
|
301
|
-
getWebSockets(tag?: string): Effect.Effect<DurableWebSocket[]>;
|
|
302
|
-
setWebSocketAutoResponse(
|
|
303
|
-
maybeReqResp?: cf.WebSocketRequestResponsePair,
|
|
304
|
-
): Effect.Effect<void>;
|
|
305
|
-
getWebSocketAutoResponse(): Effect.Effect<cf.WebSocketRequestResponsePair | null>;
|
|
306
|
-
getWebSocketAutoResponseTimestamp(
|
|
307
|
-
ws: cf.WebSocket,
|
|
308
|
-
): Effect.Effect<Date | null>;
|
|
309
|
-
setHibernatableWebSocketEventTimeout(
|
|
310
|
-
timeoutMs?: number,
|
|
311
|
-
): Effect.Effect<void>;
|
|
312
|
-
getHibernatableWebSocketEventTimeout(): Effect.Effect<number | null>;
|
|
313
|
-
getTags(ws: cf.WebSocket): Effect.Effect<string[]>;
|
|
314
|
-
abort(reason?: string): Effect.Effect<void>;
|
|
315
|
-
}
|
|
316
|
-
>()("Cloudflare.DurableObjectState") {}
|
|
317
|
-
|
|
318
|
-
export interface DurableObjectTransaction {
|
|
319
|
-
get<T = unknown>(
|
|
320
|
-
key: string,
|
|
321
|
-
options?: cf.DurableObjectGetOptions,
|
|
322
|
-
): Effect.Effect<T | undefined>;
|
|
323
|
-
get<T = unknown>(
|
|
324
|
-
keys: string[],
|
|
325
|
-
options?: cf.DurableObjectGetOptions,
|
|
326
|
-
): Effect.Effect<Map<string, T>>;
|
|
327
|
-
list<T = unknown>(
|
|
328
|
-
options?: cf.DurableObjectListOptions,
|
|
329
|
-
): Effect.Effect<Map<string, T>>;
|
|
330
|
-
put<T>(
|
|
331
|
-
key: string,
|
|
332
|
-
value: T,
|
|
333
|
-
options?: cf.DurableObjectPutOptions,
|
|
334
|
-
): Effect.Effect<void>;
|
|
335
|
-
put<T>(
|
|
336
|
-
entries: Record<string, T>,
|
|
337
|
-
options?: cf.DurableObjectPutOptions,
|
|
338
|
-
): Effect.Effect<void>;
|
|
339
|
-
delete(
|
|
340
|
-
key: string,
|
|
341
|
-
options?: cf.DurableObjectPutOptions,
|
|
342
|
-
): Effect.Effect<boolean>;
|
|
343
|
-
delete(
|
|
344
|
-
keys: string[],
|
|
345
|
-
options?: cf.DurableObjectPutOptions,
|
|
346
|
-
): Effect.Effect<number>;
|
|
347
|
-
rollback(): Effect.Effect<void>;
|
|
348
|
-
getAlarm(
|
|
349
|
-
options?: cf.DurableObjectGetAlarmOptions,
|
|
350
|
-
): Effect.Effect<number | null>;
|
|
351
|
-
setAlarm(
|
|
352
|
-
scheduledTime: number | Date,
|
|
353
|
-
options?: cf.DurableObjectSetAlarmOptions,
|
|
354
|
-
): Effect.Effect<void>;
|
|
355
|
-
deleteAlarm(options?: cf.DurableObjectSetAlarmOptions): Effect.Effect<void>;
|
|
356
|
-
}
|
|
357
|
-
export interface DurableObjectStorage {
|
|
358
|
-
get<T = unknown>(
|
|
359
|
-
key: string,
|
|
360
|
-
options?: cf.DurableObjectGetOptions,
|
|
361
|
-
): Effect.Effect<T | undefined>;
|
|
362
|
-
get<T = unknown>(
|
|
363
|
-
keys: string[],
|
|
364
|
-
options?: cf.DurableObjectGetOptions,
|
|
365
|
-
): Effect.Effect<Map<string, T>>;
|
|
366
|
-
list<T = unknown>(
|
|
367
|
-
options?: cf.DurableObjectListOptions,
|
|
368
|
-
): Effect.Effect<Map<string, T>>;
|
|
369
|
-
put<T>(
|
|
370
|
-
key: string,
|
|
371
|
-
value: T,
|
|
372
|
-
options?: cf.DurableObjectPutOptions,
|
|
373
|
-
): Effect.Effect<void>;
|
|
374
|
-
put<T>(
|
|
375
|
-
entries: Record<string, T>,
|
|
376
|
-
options?: cf.DurableObjectPutOptions,
|
|
377
|
-
): Effect.Effect<void>;
|
|
378
|
-
delete(
|
|
379
|
-
key: string,
|
|
380
|
-
options?: cf.DurableObjectPutOptions,
|
|
381
|
-
): Effect.Effect<boolean>;
|
|
382
|
-
delete(
|
|
383
|
-
keys: string[],
|
|
384
|
-
options?: cf.DurableObjectPutOptions,
|
|
385
|
-
): Effect.Effect<number>;
|
|
386
|
-
deleteAll(options?: cf.DurableObjectPutOptions): Effect.Effect<void>;
|
|
387
|
-
transaction<T>(
|
|
388
|
-
closure: (txn: DurableObjectTransaction) => Effect.Effect<T>,
|
|
389
|
-
): Effect.Effect<T>;
|
|
390
|
-
getAlarm(
|
|
391
|
-
options?: cf.DurableObjectGetAlarmOptions,
|
|
392
|
-
): Effect.Effect<number | null>;
|
|
393
|
-
setAlarm(
|
|
394
|
-
scheduledTime: number | Date,
|
|
395
|
-
options?: cf.DurableObjectSetAlarmOptions,
|
|
396
|
-
): Effect.Effect<void>;
|
|
397
|
-
deleteAlarm(options?: cf.DurableObjectSetAlarmOptions): Effect.Effect<void>;
|
|
398
|
-
sync(): Effect.Effect<void>;
|
|
399
|
-
sql: cf.SqlStorage;
|
|
400
|
-
kv: cf.SyncKvStorage;
|
|
401
|
-
transactionSync<T>(closure: () => T): T;
|
|
402
|
-
getCurrentBookmark(): Effect.Effect<string>;
|
|
403
|
-
getBookmarkForTime(timestamp: number | Date): Effect.Effect<string>;
|
|
404
|
-
onNextSessionRestoreBookmark(bookmark: string): Effect.Effect<string>;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const fromDurableObjectState = (
|
|
408
|
-
state: cf.DurableObjectState,
|
|
409
|
-
): DurableObjectState["Service"] => ({
|
|
410
|
-
id: state.id,
|
|
411
|
-
container: state.container,
|
|
412
|
-
storage: fromDurableObjectStorage(state.storage),
|
|
413
|
-
blockConcurrencyWhile: <T>(callback: () => Effect.Effect<T>) =>
|
|
414
|
-
Effect.tryPromise(() =>
|
|
415
|
-
state.blockConcurrencyWhile(() => Effect.runPromise(callback())),
|
|
416
|
-
),
|
|
417
|
-
acceptWebSocket: (ws: DurableWebSocket, tags?: string[]) =>
|
|
418
|
-
Effect.sync(() => state.acceptWebSocket(ws.ws, tags)),
|
|
419
|
-
getWebSockets: (tag?: string) =>
|
|
420
|
-
Effect.sync(() => state.getWebSockets(tag).map(fromWebSocket)),
|
|
421
|
-
setWebSocketAutoResponse: (maybeReqResp?: cf.WebSocketRequestResponsePair) =>
|
|
422
|
-
Effect.sync(() => state.setWebSocketAutoResponse(maybeReqResp)),
|
|
423
|
-
getWebSocketAutoResponse: () =>
|
|
424
|
-
Effect.sync(() => state.getWebSocketAutoResponse()),
|
|
425
|
-
getWebSocketAutoResponseTimestamp: (ws: cf.WebSocket) =>
|
|
426
|
-
Effect.sync(() => state.getWebSocketAutoResponseTimestamp(ws)),
|
|
427
|
-
setHibernatableWebSocketEventTimeout: (timeoutMs?: number) =>
|
|
428
|
-
Effect.sync(() => state.setHibernatableWebSocketEventTimeout(timeoutMs)),
|
|
429
|
-
getHibernatableWebSocketEventTimeout: () =>
|
|
430
|
-
Effect.sync(() => state.getHibernatableWebSocketEventTimeout()),
|
|
431
|
-
getTags: (ws: cf.WebSocket) => Effect.sync(() => state.getTags(ws)),
|
|
432
|
-
abort: (reason?: string) => Effect.sync(() => state.abort(reason)),
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
const fromDurableObjectTransaction = (
|
|
436
|
-
txn: cf.DurableObjectTransaction,
|
|
437
|
-
): DurableObjectTransaction => ({
|
|
438
|
-
get: ((keyOrKeys: string | string[], options?: cf.DurableObjectGetOptions) =>
|
|
439
|
-
Effect.tryPromise(() => txn.get(keyOrKeys as any, options))) as any,
|
|
440
|
-
list: (options?: cf.DurableObjectListOptions) =>
|
|
441
|
-
Effect.tryPromise(() => txn.list(options)),
|
|
442
|
-
put: ((
|
|
443
|
-
keyOrEntries: string | Record<string, unknown>,
|
|
444
|
-
valueOrOptions?: unknown,
|
|
445
|
-
maybeOptions?: cf.DurableObjectPutOptions,
|
|
446
|
-
) =>
|
|
447
|
-
typeof keyOrEntries === "string"
|
|
448
|
-
? Effect.tryPromise(() =>
|
|
449
|
-
txn.put(keyOrEntries, valueOrOptions, maybeOptions),
|
|
450
|
-
)
|
|
451
|
-
: Effect.tryPromise(() =>
|
|
452
|
-
txn.put(
|
|
453
|
-
keyOrEntries,
|
|
454
|
-
valueOrOptions as cf.DurableObjectPutOptions | undefined,
|
|
455
|
-
),
|
|
456
|
-
)) as any,
|
|
457
|
-
delete: ((
|
|
458
|
-
keyOrKeys: string | string[],
|
|
459
|
-
options?: cf.DurableObjectPutOptions,
|
|
460
|
-
) => Effect.tryPromise(() => txn.delete(keyOrKeys as any, options))) as any,
|
|
461
|
-
rollback: () => Effect.sync(() => txn.rollback()),
|
|
462
|
-
getAlarm: (options?: cf.DurableObjectGetAlarmOptions) =>
|
|
463
|
-
Effect.tryPromise(() => txn.getAlarm(options)),
|
|
464
|
-
setAlarm: (
|
|
465
|
-
scheduledTime: number | Date,
|
|
466
|
-
options?: cf.DurableObjectSetAlarmOptions,
|
|
467
|
-
) => Effect.tryPromise(() => txn.setAlarm(scheduledTime, options)),
|
|
468
|
-
deleteAlarm: (options?: cf.DurableObjectSetAlarmOptions) =>
|
|
469
|
-
Effect.tryPromise(() => txn.deleteAlarm(options)),
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
const fromDurableObjectStorage = (
|
|
473
|
-
storage: cf.DurableObjectStorage,
|
|
474
|
-
): DurableObjectStorage => ({
|
|
475
|
-
get: ((keyOrKeys: string | string[], options?: cf.DurableObjectGetOptions) =>
|
|
476
|
-
Effect.tryPromise(() => storage.get(keyOrKeys as any, options))) as any,
|
|
477
|
-
list: (options?: cf.DurableObjectListOptions) =>
|
|
478
|
-
Effect.tryPromise(() => storage.list(options)),
|
|
479
|
-
put: ((
|
|
480
|
-
keyOrEntries: string | Record<string, unknown>,
|
|
481
|
-
valueOrOptions?: unknown,
|
|
482
|
-
maybeOptions?: cf.DurableObjectPutOptions,
|
|
483
|
-
) =>
|
|
484
|
-
typeof keyOrEntries === "string"
|
|
485
|
-
? Effect.tryPromise(() =>
|
|
486
|
-
storage.put(keyOrEntries, valueOrOptions, maybeOptions),
|
|
487
|
-
)
|
|
488
|
-
: Effect.tryPromise(() =>
|
|
489
|
-
storage.put(
|
|
490
|
-
keyOrEntries,
|
|
491
|
-
valueOrOptions as cf.DurableObjectPutOptions | undefined,
|
|
492
|
-
),
|
|
493
|
-
)) as any,
|
|
494
|
-
delete: ((
|
|
495
|
-
keyOrKeys: string | string[],
|
|
496
|
-
options?: cf.DurableObjectPutOptions,
|
|
497
|
-
) =>
|
|
498
|
-
Effect.tryPromise(() => storage.delete(keyOrKeys as any, options))) as any,
|
|
499
|
-
deleteAll: (options?: cf.DurableObjectPutOptions) =>
|
|
500
|
-
Effect.tryPromise(() => storage.deleteAll(options)),
|
|
501
|
-
transaction: <T>(
|
|
502
|
-
closure: (txn: DurableObjectTransaction) => Effect.Effect<T>,
|
|
503
|
-
) =>
|
|
504
|
-
Effect.tryPromise(() =>
|
|
505
|
-
storage.transaction((txn) =>
|
|
506
|
-
Effect.runPromise(closure(fromDurableObjectTransaction(txn))),
|
|
507
|
-
),
|
|
508
|
-
),
|
|
509
|
-
getAlarm: (options?: cf.DurableObjectGetAlarmOptions) =>
|
|
510
|
-
Effect.tryPromise(() => storage.getAlarm(options)),
|
|
511
|
-
setAlarm: (
|
|
512
|
-
scheduledTime: number | Date,
|
|
513
|
-
options?: cf.DurableObjectSetAlarmOptions,
|
|
514
|
-
) => Effect.tryPromise(() => storage.setAlarm(scheduledTime, options)),
|
|
515
|
-
deleteAlarm: (options?: cf.DurableObjectSetAlarmOptions) =>
|
|
516
|
-
Effect.tryPromise(() => storage.deleteAlarm(options)),
|
|
517
|
-
sync: () => Effect.tryPromise(() => storage.sync()),
|
|
518
|
-
sql: storage.sql,
|
|
519
|
-
kv: storage.kv,
|
|
520
|
-
transactionSync: <T>(closure: () => T) => storage.transactionSync(closure),
|
|
521
|
-
getCurrentBookmark: () =>
|
|
522
|
-
Effect.tryPromise(() => storage.getCurrentBookmark()),
|
|
523
|
-
getBookmarkForTime: (timestamp: number | Date) =>
|
|
524
|
-
Effect.tryPromise(() => storage.getBookmarkForTime(timestamp)),
|
|
525
|
-
onNextSessionRestoreBookmark: (bookmark: string) =>
|
|
526
|
-
Effect.tryPromise(() => storage.onNextSessionRestoreBookmark(bookmark)),
|
|
527
|
-
});
|
package/src/Daemon/Client.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import * as NodeSocket from "@effect/platform-node/NodeSocket";
|
|
2
|
-
import * as Context from "effect/Context";
|
|
3
|
-
import * as Effect from "effect/Effect";
|
|
4
|
-
import * as FileSystem from "effect/FileSystem";
|
|
5
|
-
import * as Layer from "effect/Layer";
|
|
6
|
-
import * as Path from "effect/Path";
|
|
7
|
-
import * as Schedule from "effect/Schedule";
|
|
8
|
-
import * as ChildProcess from "effect/unstable/process/ChildProcess";
|
|
9
|
-
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
|
|
10
|
-
import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
|
|
11
|
-
import * as Socket from "effect/unstable/socket/Socket";
|
|
12
|
-
import { resolveSocketPath } from "./Config.ts";
|
|
13
|
-
import { DaemonConnectFailed, DaemonSocketNotReady } from "./Errors.ts";
|
|
14
|
-
import { DaemonRpcs } from "./RpcSchema.ts";
|
|
15
|
-
|
|
16
|
-
export type DaemonClient = Effect.Success<ReturnType<typeof makeClient>>;
|
|
17
|
-
|
|
18
|
-
export class Daemon extends Context.Service<Daemon, DaemonClient>()(
|
|
19
|
-
"alchemy/Cli/Daemon",
|
|
20
|
-
) {}
|
|
21
|
-
|
|
22
|
-
export const makeClient = (socketPath: string) =>
|
|
23
|
-
Effect.gen(function* () {
|
|
24
|
-
const socket = yield* NodeSocket.makeNet({ path: socketPath });
|
|
25
|
-
const protocol = yield* RpcClient.makeProtocolSocket().pipe(
|
|
26
|
-
Effect.provideService(Socket.Socket, socket),
|
|
27
|
-
Effect.provide(RpcSerialization.layerJson),
|
|
28
|
-
);
|
|
29
|
-
return yield* RpcClient.make(DaemonRpcs).pipe(
|
|
30
|
-
Effect.provideService(RpcClient.Protocol, protocol),
|
|
31
|
-
);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const DAEMON_BIN = "process-manager.ts";
|
|
35
|
-
const HEARTBEAT_INTERVAL = "1 seconds" as const;
|
|
36
|
-
|
|
37
|
-
const resolveDaemonBin = Effect.gen(function* () {
|
|
38
|
-
const path = yield* Path.Path;
|
|
39
|
-
return path.join(path.dirname(import.meta.dirname), "bin", DAEMON_BIN);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Layer that provides a connected `Daemon` client.
|
|
44
|
-
*
|
|
45
|
-
* On build:
|
|
46
|
-
* 1. Try to connect to the existing daemon socket
|
|
47
|
-
* 2. If that fails, spawn the daemon process and wait for the socket
|
|
48
|
-
* 3. Connect to the socket
|
|
49
|
-
* 4. Fork a heartbeat fiber that pings every second to keep the daemon alive
|
|
50
|
-
* 5. When the scope closes, the heartbeat stops (daemon may idle-shutdown)
|
|
51
|
-
*/
|
|
52
|
-
export const DaemonLive: Layer.Layer<
|
|
53
|
-
Daemon,
|
|
54
|
-
never,
|
|
55
|
-
FileSystem.FileSystem | Path.Path | ChildProcessSpawner
|
|
56
|
-
> = Layer.effect(
|
|
57
|
-
Daemon,
|
|
58
|
-
Effect.gen(function* () {
|
|
59
|
-
const socketPath = yield* resolveSocketPath;
|
|
60
|
-
|
|
61
|
-
const client = yield* tryConnect(socketPath).pipe(
|
|
62
|
-
Effect.catchTag("DaemonConnectFailed", () =>
|
|
63
|
-
Effect.gen(function* () {
|
|
64
|
-
yield* Effect.logInfo("Starting daemon…");
|
|
65
|
-
yield* startDaemonProcess.pipe(Effect.forkChild);
|
|
66
|
-
yield* waitForSocket(socketPath);
|
|
67
|
-
yield* Effect.sleep("200 millis");
|
|
68
|
-
return yield* tryConnect(socketPath);
|
|
69
|
-
}),
|
|
70
|
-
),
|
|
71
|
-
Effect.catchTag("PlatformError", (e) => Effect.die(e)),
|
|
72
|
-
Effect.catchTag("DaemonConnectFailed", () =>
|
|
73
|
-
Effect.die(new Error("Failed to connect to daemon")),
|
|
74
|
-
),
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
yield* client
|
|
78
|
-
.heartbeat(void 0 as any)
|
|
79
|
-
.pipe(
|
|
80
|
-
Effect.ignore,
|
|
81
|
-
Effect.repeat(Schedule.spaced(HEARTBEAT_INTERVAL)),
|
|
82
|
-
Effect.forkScoped,
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
yield* Effect.logInfo("Connected to daemon");
|
|
86
|
-
|
|
87
|
-
return client;
|
|
88
|
-
}),
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const waitForSocket = (socketPath: string) =>
|
|
92
|
-
Effect.gen(function* () {
|
|
93
|
-
const fs = yield* FileSystem.FileSystem;
|
|
94
|
-
yield* fs.exists(socketPath).pipe(
|
|
95
|
-
Effect.flatMap((exists) =>
|
|
96
|
-
exists ? Effect.void : Effect.fail(new DaemonSocketNotReady()),
|
|
97
|
-
),
|
|
98
|
-
Effect.retry(
|
|
99
|
-
Schedule.spaced("100 millis").pipe(Schedule.both(Schedule.recurs(50))),
|
|
100
|
-
),
|
|
101
|
-
Effect.catchTag("DaemonSocketNotReady", () =>
|
|
102
|
-
Effect.fail(new DaemonConnectFailed()),
|
|
103
|
-
),
|
|
104
|
-
);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const tryConnect = (socketPath: string) =>
|
|
108
|
-
makeClient(socketPath).pipe(
|
|
109
|
-
Effect.catch(() => Effect.fail(new DaemonConnectFailed())),
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
const startDaemonProcess = Effect.gen(function* () {
|
|
113
|
-
const binPath = yield* resolveDaemonBin;
|
|
114
|
-
const cmd = ChildProcess.make("bun", ["run", binPath]);
|
|
115
|
-
yield* cmd;
|
|
116
|
-
});
|
package/src/Daemon/Config.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
|
-
import * as FileSystem from "effect/FileSystem";
|
|
3
|
-
import * as Path from "effect/Path";
|
|
4
|
-
|
|
5
|
-
export const ALCHEMY_DIR = ".alchemy";
|
|
6
|
-
export const SOCKET_FILE = "daemon.sock";
|
|
7
|
-
export const LOCK_DIR_NAME = "daemon.lock";
|
|
8
|
-
export const PID_FILE_NAME = "pid";
|
|
9
|
-
export const DB_FILE = "daemon.db";
|
|
10
|
-
export const PROCESSES_DIR = "processes";
|
|
11
|
-
|
|
12
|
-
export const resolveAlchemyDir = Effect.gen(function* () {
|
|
13
|
-
const path = yield* Path.Path;
|
|
14
|
-
return path.resolve(ALCHEMY_DIR);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
export const ensureAlchemyDir = Effect.gen(function* () {
|
|
18
|
-
const fs = yield* FileSystem.FileSystem;
|
|
19
|
-
const dir = yield* resolveAlchemyDir;
|
|
20
|
-
yield* fs.makeDirectory(dir, { recursive: true });
|
|
21
|
-
return dir;
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
export const resolveSocketPath = Effect.gen(function* () {
|
|
25
|
-
const path = yield* Path.Path;
|
|
26
|
-
const dir = yield* resolveAlchemyDir;
|
|
27
|
-
return path.join(dir, SOCKET_FILE);
|
|
28
|
-
});
|