alchemy 2.0.0-beta.22 → 2.0.0-beta.24
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.js +12590 -2452
- package/bin/alchemy.js.map +1 -1
- package/bin/alchemy.ts +2 -0
- package/bin/cli.js +18 -0
- package/bin/commands/bootstrap.ts +88 -4
- package/bin/commands/deploy.ts +54 -20
- package/bin/commands/dev.ts +45 -0
- package/bin/commands/logs.ts +1 -1
- package/bin/commands/tail.ts +1 -1
- package/bin/exec.ts +22 -0
- package/lib/AWS/Lambda/Function.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.js +25 -21
- package/lib/AWS/Lambda/Function.js.map +1 -1
- package/lib/AWS/Lambda/HttpServer.d.ts.map +1 -1
- package/lib/AWS/Lambda/HttpServer.js +3 -1
- package/lib/AWS/Lambda/HttpServer.js.map +1 -1
- package/lib/AdoptPolicy.d.ts +2 -2
- package/lib/AdoptPolicy.d.ts.map +1 -1
- package/lib/AdoptPolicy.js.map +1 -1
- package/lib/AlchemyContext.d.ts +1 -0
- package/lib/AlchemyContext.d.ts.map +1 -1
- package/lib/AlchemyContext.js +1 -0
- package/lib/AlchemyContext.js.map +1 -1
- package/lib/Build/Command.d.ts.map +1 -1
- package/lib/Build/Command.js +8 -1
- package/lib/Build/Command.js.map +1 -1
- package/lib/Bundle/Bundle.d.ts +21 -9
- package/lib/Bundle/Bundle.d.ts.map +1 -1
- package/lib/Bundle/Bundle.js +29 -15
- package/lib/Bundle/Bundle.js.map +1 -1
- package/lib/Cloudflare/Container/Container.d.ts +55 -2
- package/lib/Cloudflare/Container/Container.d.ts.map +1 -1
- package/lib/Cloudflare/Container/Container.js +55 -2
- package/lib/Cloudflare/Container/Container.js.map +1 -1
- package/lib/Cloudflare/D1/D1Export.d.ts.map +1 -1
- package/lib/Cloudflare/D1/D1Export.js.map +1 -1
- package/lib/Cloudflare/D1/D1SqlFile.d.ts.map +1 -1
- package/lib/Cloudflare/D1/D1SqlFile.js.map +1 -1
- package/lib/Cloudflare/Local/Sidecar.d.ts +27 -0
- package/lib/Cloudflare/Local/Sidecar.d.ts.map +1 -0
- package/lib/Cloudflare/Local/Sidecar.js +24 -0
- package/lib/Cloudflare/Local/Sidecar.js.map +1 -0
- package/lib/Cloudflare/Local/SidecarHandlers.d.ts +5 -0
- package/lib/Cloudflare/Local/SidecarHandlers.d.ts.map +1 -0
- package/lib/Cloudflare/Local/SidecarHandlers.js +119 -0
- package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -0
- package/lib/Cloudflare/Local/SidecarServer.d.ts +2 -0
- package/lib/Cloudflare/Local/SidecarServer.d.ts.map +1 -0
- package/lib/Cloudflare/Local/SidecarServer.js +27 -0
- package/lib/Cloudflare/Local/SidecarServer.js.map +1 -0
- package/lib/Cloudflare/Queue/Queue.d.ts +34 -4
- package/lib/Cloudflare/Queue/Queue.d.ts.map +1 -1
- package/lib/Cloudflare/Queue/Queue.js +34 -4
- package/lib/Cloudflare/Queue/Queue.js.map +1 -1
- package/lib/Cloudflare/Queue/QueueBinding.d.ts +33 -0
- package/lib/Cloudflare/Queue/QueueBinding.d.ts.map +1 -1
- package/lib/Cloudflare/Queue/QueueBinding.js +33 -0
- package/lib/Cloudflare/Queue/QueueBinding.js.map +1 -1
- package/lib/Cloudflare/SecretsStore/Secret.d.ts.map +1 -1
- package/lib/Cloudflare/SecretsStore/Secret.js +55 -7
- package/lib/Cloudflare/SecretsStore/Secret.js.map +1 -1
- package/lib/Cloudflare/SecretsStore/SecretsStore.d.ts.map +1 -1
- package/lib/Cloudflare/SecretsStore/SecretsStore.js +35 -11
- package/lib/Cloudflare/SecretsStore/SecretsStore.js.map +1 -1
- package/lib/Cloudflare/StateStore/State.d.ts +35 -0
- package/lib/Cloudflare/StateStore/State.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/State.js +187 -45
- package/lib/Cloudflare/StateStore/State.js.map +1 -1
- package/lib/Cloudflare/StateStore/index.d.ts +1 -1
- package/lib/Cloudflare/StateStore/index.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/index.js +1 -1
- package/lib/Cloudflare/StateStore/index.js.map +1 -1
- package/lib/Cloudflare/Workers/Assets.d.ts +1 -1
- package/lib/Cloudflare/Workers/Assets.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Assets.js +6 -1
- package/lib/Cloudflare/Workers/Assets.js.map +1 -1
- package/lib/Cloudflare/Workers/Compatibility.d.ts +6 -0
- package/lib/Cloudflare/Workers/Compatibility.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/Compatibility.js +10 -0
- package/lib/Cloudflare/Workers/Compatibility.js.map +1 -0
- package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts +103 -6
- package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/DurableObjectNamespace.js +103 -6
- package/lib/Cloudflare/Workers/DurableObjectNamespace.js.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +7 -0
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js +81 -0
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -0
- package/lib/Cloudflare/Workers/Worker.d.ts +3 -1
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +54 -7
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts +31 -0
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/WorkerBundle.js +174 -0
- package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -0
- package/lib/Cloudflare/Workers/WorkerName.d.ts +3 -0
- package/lib/Cloudflare/Workers/WorkerName.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/WorkerName.js +9 -0
- package/lib/Cloudflare/Workers/WorkerName.js.map +1 -0
- package/lib/Cloudflare/Workers/Workflow.d.ts +94 -0
- package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Workflow.js +94 -0
- package/lib/Cloudflare/Workers/Workflow.js.map +1 -1
- package/lib/Cloudflare/Workers/index.d.ts +1 -0
- package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/index.js +1 -0
- package/lib/Cloudflare/Workers/index.js.map +1 -1
- package/lib/Platform.d.ts +1 -1
- package/lib/Platform.d.ts.map +1 -1
- package/lib/Platform.js +1 -1
- package/lib/Platform.js.map +1 -1
- package/lib/Sidecar/Lock.d.ts +24 -0
- package/lib/Sidecar/Lock.d.ts.map +1 -0
- package/lib/Sidecar/Lock.js +102 -0
- package/lib/Sidecar/Lock.js.map +1 -0
- package/lib/Sidecar/RpcClient.d.ts +15 -0
- package/lib/Sidecar/RpcClient.d.ts.map +1 -0
- package/lib/Sidecar/RpcClient.js +75 -0
- package/lib/Sidecar/RpcClient.js.map +1 -0
- package/lib/Sidecar/RpcHandler.d.ts +48 -0
- package/lib/Sidecar/RpcHandler.d.ts.map +1 -0
- package/lib/Sidecar/RpcHandler.js +64 -0
- package/lib/Sidecar/RpcHandler.js.map +1 -0
- package/lib/Sidecar/RpcPaths.d.ts +14 -0
- package/lib/Sidecar/RpcPaths.d.ts.map +1 -0
- package/lib/Sidecar/RpcPaths.js +22 -0
- package/lib/Sidecar/RpcPaths.js.map +1 -0
- package/lib/Sidecar/RpcServer.d.ts +10 -0
- package/lib/Sidecar/RpcServer.d.ts.map +1 -0
- package/lib/Sidecar/RpcServer.js +50 -0
- package/lib/Sidecar/RpcServer.js.map +1 -0
- package/lib/Sidecar/RpcTransport.d.ts +22 -0
- package/lib/Sidecar/RpcTransport.d.ts.map +1 -0
- package/lib/Sidecar/RpcTransport.js +81 -0
- package/lib/Sidecar/RpcTransport.js.map +1 -0
- package/lib/Telemetry/Metrics.d.ts +15 -0
- package/lib/Telemetry/Metrics.d.ts.map +1 -1
- package/lib/Telemetry/Metrics.js +20 -0
- package/lib/Telemetry/Metrics.js.map +1 -1
- package/lib/Util/PlatformServices.d.ts +3 -0
- package/lib/Util/PlatformServices.d.ts.map +1 -1
- package/lib/Util/PlatformServices.js +13 -0
- package/lib/Util/PlatformServices.js.map +1 -1
- package/lib/Util/layer-scoped.d.ts +16 -0
- package/lib/Util/layer-scoped.d.ts.map +1 -0
- package/lib/Util/layer-scoped.js +14 -0
- package/lib/Util/layer-scoped.js.map +1 -0
- package/package.json +4 -2
- package/src/AWS/Lambda/Function.ts +30 -24
- package/src/AWS/Lambda/HttpServer.ts +3 -1
- package/src/AdoptPolicy.ts +9 -9
- package/src/AlchemyContext.ts +2 -0
- package/src/Build/Command.ts +8 -1
- package/src/Bundle/Bundle.ts +65 -25
- package/src/Cloudflare/Container/Container.ts +55 -2
- package/src/Cloudflare/D1/D1Export.ts +1 -5
- package/src/Cloudflare/D1/D1SqlFile.ts +1 -3
- package/src/Cloudflare/Local/Sidecar.ts +44 -0
- package/src/Cloudflare/Local/SidecarHandlers.ts +182 -0
- package/src/Cloudflare/Local/SidecarServer.ts +57 -0
- package/src/Cloudflare/Queue/Queue.ts +34 -4
- package/src/Cloudflare/Queue/QueueBinding.ts +33 -0
- package/src/Cloudflare/SecretsStore/Secret.ts +78 -7
- package/src/Cloudflare/SecretsStore/SecretsStore.ts +47 -12
- package/src/Cloudflare/StateStore/State.ts +281 -64
- package/src/Cloudflare/StateStore/index.ts +1 -1
- package/src/Cloudflare/Workers/Assets.ts +10 -1
- package/src/Cloudflare/Workers/Compatibility.ts +12 -0
- package/src/Cloudflare/Workers/DurableObjectNamespace.ts +103 -6
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +93 -0
- package/src/Cloudflare/Workers/Worker.ts +102 -19
- package/src/Cloudflare/Workers/WorkerBundle.ts +234 -0
- package/src/Cloudflare/Workers/WorkerName.ts +10 -0
- package/src/Cloudflare/Workers/Workflow.ts +94 -0
- package/src/Cloudflare/Workers/index.ts +1 -0
- package/src/Platform.ts +2 -2
- package/src/Sidecar/Lock.ts +190 -0
- package/src/Sidecar/RpcClient.ts +141 -0
- package/src/Sidecar/RpcHandler.ts +169 -0
- package/src/Sidecar/RpcPaths.ts +29 -0
- package/src/Sidecar/RpcServer.ts +80 -0
- package/src/Sidecar/RpcTransport.ts +85 -0
- package/src/Telemetry/Metrics.ts +38 -0
- package/src/Util/PlatformServices.ts +19 -0
- package/src/Util/layer-scoped.ts +25 -0
package/src/Build/Command.ts
CHANGED
|
@@ -143,12 +143,19 @@ export const CommandProvider = () =>
|
|
|
143
143
|
if (!output) {
|
|
144
144
|
return undefined;
|
|
145
145
|
}
|
|
146
|
+
// Recompute the output path against the *current* cwd. State
|
|
147
|
+
// may have been written by a different machine (e.g. CI) and
|
|
148
|
+
// the absolute path baked into `output.outdir` won't exist
|
|
149
|
+
// locally. As long as the path resolved against this
|
|
150
|
+
// machine's cwd is present, return the refreshed output so
|
|
151
|
+
// downstream consumers (e.g. `Cloudflare.Worker` assets)
|
|
152
|
+
// don't try to read a stale, foreign-machine path.
|
|
146
153
|
const outputPath = getOutputPath(olds);
|
|
147
154
|
const exists = yield* fs.exists(outputPath);
|
|
148
155
|
if (!exists) {
|
|
149
156
|
return undefined;
|
|
150
157
|
}
|
|
151
|
-
return output;
|
|
158
|
+
return { ...output, outdir: outputPath };
|
|
152
159
|
}),
|
|
153
160
|
create: Effect.fnUntraced(function* ({ news, session }) {
|
|
154
161
|
const hash = yield* hashDirectory(news);
|
package/src/Bundle/Bundle.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import * as Data from "effect/Data";
|
|
2
1
|
import * as Effect from "effect/Effect";
|
|
3
2
|
import * as Path from "effect/Path";
|
|
4
3
|
import * as Queue from "effect/Queue";
|
|
5
|
-
import * as
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
6
5
|
import * as Stream from "effect/Stream";
|
|
7
6
|
import assert from "node:assert";
|
|
8
7
|
import * as rolldown from "rolldown";
|
|
@@ -45,10 +44,32 @@ export interface BundleFile {
|
|
|
45
44
|
readonly hash: string;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
export class BundleError extends
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
export class BundleError extends Schema.TaggedErrorClass<BundleError>()(
|
|
48
|
+
"BundleError",
|
|
49
|
+
{
|
|
50
|
+
message: Schema.String,
|
|
51
|
+
cause: Schema.optional(Schema.DefectWithStack),
|
|
52
|
+
},
|
|
53
|
+
) {}
|
|
54
|
+
|
|
55
|
+
export type BundleWatchEvent =
|
|
56
|
+
| BundleWatchEvent.Start
|
|
57
|
+
| BundleWatchEvent.Success
|
|
58
|
+
| BundleWatchEvent.Error;
|
|
59
|
+
|
|
60
|
+
export declare namespace BundleWatchEvent {
|
|
61
|
+
interface Start {
|
|
62
|
+
readonly _tag: "Start";
|
|
63
|
+
}
|
|
64
|
+
interface Success {
|
|
65
|
+
readonly _tag: "Success";
|
|
66
|
+
readonly output: BundleOutput;
|
|
67
|
+
}
|
|
68
|
+
interface Error {
|
|
69
|
+
readonly _tag: "Error";
|
|
70
|
+
readonly error: BundleError;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
52
73
|
|
|
53
74
|
/**
|
|
54
75
|
* Build a bundle using rolldown from the given input options and output options.
|
|
@@ -93,8 +114,15 @@ export const watch = (
|
|
|
93
114
|
inputOptions: rolldown.InputOptions,
|
|
94
115
|
outputOptions?: rolldown.OutputOptions,
|
|
95
116
|
extra?: BundleExtraOptions,
|
|
96
|
-
): Stream.Stream<
|
|
97
|
-
Stream.callback<
|
|
117
|
+
): Stream.Stream<BundleWatchEvent> =>
|
|
118
|
+
Stream.callback<
|
|
119
|
+
| BundleWatchEvent.Start
|
|
120
|
+
| BundleWatchEvent.Error
|
|
121
|
+
| {
|
|
122
|
+
readonly _tag: "Success";
|
|
123
|
+
readonly output: rolldown.OutputBundle;
|
|
124
|
+
}
|
|
125
|
+
>((queue) =>
|
|
98
126
|
Effect.acquireRelease(
|
|
99
127
|
Effect.sync(() => {
|
|
100
128
|
const watcher = rolldown.watch({
|
|
@@ -104,8 +132,16 @@ export const watch = (
|
|
|
104
132
|
// The watcher event listener does not receive the bundle output, so we grab it using a plugin.
|
|
105
133
|
{
|
|
106
134
|
name: "alchemy:watch-bundle",
|
|
135
|
+
watchChange() {
|
|
136
|
+
Queue.offerUnsafe(queue, {
|
|
137
|
+
_tag: "Start",
|
|
138
|
+
});
|
|
139
|
+
},
|
|
107
140
|
generateBundle(_outputOptions, bundle) {
|
|
108
|
-
Queue.offerUnsafe(queue,
|
|
141
|
+
Queue.offerUnsafe(queue, {
|
|
142
|
+
_tag: "Success",
|
|
143
|
+
output: bundle,
|
|
144
|
+
});
|
|
109
145
|
},
|
|
110
146
|
},
|
|
111
147
|
],
|
|
@@ -113,10 +149,10 @@ export const watch = (
|
|
|
113
149
|
});
|
|
114
150
|
watcher.on("event", (event) => {
|
|
115
151
|
if (event.code === "ERROR") {
|
|
116
|
-
Queue.offerUnsafe(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
);
|
|
152
|
+
Queue.offerUnsafe(queue, {
|
|
153
|
+
_tag: "Error",
|
|
154
|
+
error: bundleErrorFromUnknown(event.error),
|
|
155
|
+
});
|
|
120
156
|
} else if (event.code === "BUNDLE_END") {
|
|
121
157
|
// This must be called to avoid resource leaks.
|
|
122
158
|
event.result.close().catch(() => {});
|
|
@@ -127,23 +163,27 @@ export const watch = (
|
|
|
127
163
|
(watcher) => Effect.promise(() => watcher.close()),
|
|
128
164
|
),
|
|
129
165
|
).pipe(
|
|
130
|
-
Stream.mapEffect((
|
|
166
|
+
Stream.mapEffect((event) =>
|
|
131
167
|
Effect.gen(function* () {
|
|
132
|
-
if (
|
|
133
|
-
return
|
|
168
|
+
if (event._tag !== "Success") {
|
|
169
|
+
return event;
|
|
134
170
|
}
|
|
135
|
-
return yield* bundleOutputFromRolldownOutputBundle(
|
|
136
|
-
Effect.map(
|
|
137
|
-
|
|
171
|
+
return yield* bundleOutputFromRolldownOutputBundle(event.output).pipe(
|
|
172
|
+
Effect.map(
|
|
173
|
+
(output): BundleWatchEvent.Success => ({
|
|
174
|
+
_tag: "Success",
|
|
175
|
+
output,
|
|
176
|
+
}),
|
|
177
|
+
),
|
|
178
|
+
Effect.catch((error) =>
|
|
179
|
+
Effect.succeed<BundleWatchEvent.Error>({
|
|
180
|
+
_tag: "Error",
|
|
181
|
+
error: bundleErrorFromUnknown(error),
|
|
182
|
+
}),
|
|
183
|
+
),
|
|
138
184
|
);
|
|
139
185
|
}),
|
|
140
186
|
),
|
|
141
|
-
Stream.changesWith((left, right) => {
|
|
142
|
-
if (left._tag === "Success" && right._tag === "Success") {
|
|
143
|
-
return left.success.hash === right.success.hash;
|
|
144
|
-
}
|
|
145
|
-
return false;
|
|
146
|
-
}),
|
|
147
187
|
);
|
|
148
188
|
|
|
149
189
|
const ENTRY_MODULE_ID = "virtual:alchemy-entry";
|
|
@@ -118,8 +118,10 @@ export type Container = {
|
|
|
118
118
|
* @section Configuration
|
|
119
119
|
* The props object accepts `main` (entrypoint file), `instanceType`
|
|
120
120
|
* (compute size), `runtime` (`"bun"` or `"node"`), and
|
|
121
|
-
* `observability` settings. Use `Stack.useSync` to
|
|
122
|
-
*
|
|
121
|
+
* `observability` settings. Use `Stack.useSync` to read the
|
|
122
|
+
* surrounding stack at declaration time and pick a beefier
|
|
123
|
+
* `instanceType` in prod while keeping the cheap `dev` instance for
|
|
124
|
+
* preview environments.
|
|
123
125
|
*
|
|
124
126
|
* @example Stage-dependent configuration
|
|
125
127
|
* ```typescript
|
|
@@ -133,6 +135,57 @@ export type Container = {
|
|
|
133
135
|
* ) {}
|
|
134
136
|
* ```
|
|
135
137
|
*
|
|
138
|
+
* @section Stack-level wiring
|
|
139
|
+
* The `.make()` `export default` is the side-effect that registers
|
|
140
|
+
* the container's runtime. It must be reachable from your
|
|
141
|
+
* `alchemy.run.ts` so the bundler emits the runtime entrypoint.
|
|
142
|
+
* Provide it on the Stack's generator with `Effect.provide`.
|
|
143
|
+
*
|
|
144
|
+
* @example Wiring SandboxLive into the Stack
|
|
145
|
+
* ```typescript
|
|
146
|
+
* // alchemy.run.ts
|
|
147
|
+
* import SandboxLive from "./src/Sandbox.runtime.ts";
|
|
148
|
+
*
|
|
149
|
+
* export default Alchemy.Stack(
|
|
150
|
+
* "MyApp",
|
|
151
|
+
* { providers: Cloudflare.providers(), state: Cloudflare.state() },
|
|
152
|
+
* Effect.gen(function* () {
|
|
153
|
+
* const worker = yield* Worker;
|
|
154
|
+
* return { url: worker.url };
|
|
155
|
+
* }).pipe(Effect.provide(SandboxLive)),
|
|
156
|
+
* );
|
|
157
|
+
* ```
|
|
158
|
+
*
|
|
159
|
+
* @section Calling from a Durable Object
|
|
160
|
+
* Use `Cloudflare.Container.bind(Sandbox)` in the **outer** init
|
|
161
|
+
* phase of a Durable Object — only the class is imported, so the
|
|
162
|
+
* DO bundle stays tiny. Then `Cloudflare.start(sandbox)` in the
|
|
163
|
+
* **inner** per-instance phase ensures the container is running
|
|
164
|
+
* and gives you a typed handle that exposes every method declared
|
|
165
|
+
* on the container's shape **plus** a `getTcpPort` helper.
|
|
166
|
+
*
|
|
167
|
+
* @example Binding and starting a container from a DO
|
|
168
|
+
* ```typescript
|
|
169
|
+
* export default class Agent extends Cloudflare.DurableObjectNamespace<Agent>()(
|
|
170
|
+
* "Agents",
|
|
171
|
+
* Effect.gen(function* () {
|
|
172
|
+
* // OUTER (init): only the class is referenced — the runtime
|
|
173
|
+
* // implementation in `Sandbox.runtime.ts` is tree-shaken out
|
|
174
|
+
* // of this DO's bundle.
|
|
175
|
+
* const sandbox = yield* Cloudflare.Container.bind(Sandbox);
|
|
176
|
+
*
|
|
177
|
+
* return Effect.gen(function* () {
|
|
178
|
+
* // INNER (per-instance): start the container and expose RPC.
|
|
179
|
+
* const container = yield* Cloudflare.start(sandbox);
|
|
180
|
+
*
|
|
181
|
+
* return {
|
|
182
|
+
* exec: (cmd: string) => container.exec(cmd),
|
|
183
|
+
* };
|
|
184
|
+
* });
|
|
185
|
+
* }),
|
|
186
|
+
* ) {}
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
136
189
|
* @section Starting from a Durable Object
|
|
137
190
|
* Use `Cloudflare.Container.bind` in the outer init phase to bind
|
|
138
191
|
* the container class, then `Cloudflare.start` in the inner
|
|
@@ -35,11 +35,7 @@ export const exportD1Database = (
|
|
|
35
35
|
|
|
36
36
|
const poll = (
|
|
37
37
|
currentBookmark?: string,
|
|
38
|
-
): Effect.Effect<
|
|
39
|
-
ExportD1DatabaseResult,
|
|
40
|
-
d1.ExportDatabaseError,
|
|
41
|
-
never
|
|
42
|
-
> =>
|
|
38
|
+
): Effect.Effect<ExportD1DatabaseResult, d1.ExportDatabaseError, never> =>
|
|
43
39
|
Effect.gen(function* () {
|
|
44
40
|
const data = yield* exportDb({
|
|
45
41
|
accountId: options.accountId,
|
|
@@ -29,9 +29,7 @@ export const listSqlFiles = (directory: string) =>
|
|
|
29
29
|
return a.localeCompare(b);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
return yield* Effect.all(
|
|
33
|
-
sqlFiles.map((id) => readSqlFile(directory, id)),
|
|
34
|
-
);
|
|
32
|
+
return yield* Effect.all(sqlFiles.map((id) => readSqlFile(directory, id)));
|
|
35
33
|
});
|
|
36
34
|
|
|
37
35
|
/**
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ServeError,
|
|
3
|
+
type ServeResult,
|
|
4
|
+
} from "@distilled.cloud/cloudflare-runtime/Server";
|
|
5
|
+
import * as Worker from "@distilled.cloud/cloudflare-runtime/Worker";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Schema from "effect/Schema";
|
|
8
|
+
import { BundleError } from "../../Bundle/Bundle.ts";
|
|
9
|
+
import * as RpcClient from "../../Sidecar/RpcClient.ts";
|
|
10
|
+
import { defineSchema } from "../../Sidecar/RpcHandler.ts";
|
|
11
|
+
import type { WorkerBinding } from "../Workers/Worker.ts";
|
|
12
|
+
import type { WorkerBundleOptions } from "../Workers/WorkerBundle.ts";
|
|
13
|
+
|
|
14
|
+
export interface ServeOptions extends WorkerBundleOptions {
|
|
15
|
+
name: string;
|
|
16
|
+
bindings: WorkerBinding[];
|
|
17
|
+
durableObjectNamespaces: Worker.DurableObjectNamespace[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const SidecarSchema = defineSchema<Sidecar["Service"]>({
|
|
21
|
+
serve: {
|
|
22
|
+
success: Schema.Struct({
|
|
23
|
+
name: Schema.String,
|
|
24
|
+
address: Schema.String,
|
|
25
|
+
}),
|
|
26
|
+
error: Schema.Union([ServeError, BundleError]),
|
|
27
|
+
},
|
|
28
|
+
stop: { success: Schema.Void, error: Schema.Never },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export class Sidecar extends RpcClient.RpcClientService<
|
|
32
|
+
Sidecar,
|
|
33
|
+
{
|
|
34
|
+
readonly serve: (
|
|
35
|
+
options: ServeOptions,
|
|
36
|
+
) => Effect.Effect<ServeResult, ServeError | BundleError>;
|
|
37
|
+
readonly stop: (name: string) => Effect.Effect<void>;
|
|
38
|
+
}
|
|
39
|
+
>()("Sidecar") {}
|
|
40
|
+
|
|
41
|
+
export const SidecarLive = RpcClient.layer(Sidecar, {
|
|
42
|
+
main: import.meta.resolve("./SidecarServer.ts", import.meta.url),
|
|
43
|
+
schema: SidecarSchema,
|
|
44
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import * as Server from "@distilled.cloud/cloudflare-runtime/Server";
|
|
2
|
+
import type { WorkerModule } from "@distilled.cloud/cloudflare-runtime/WorkerModule";
|
|
3
|
+
import * as Cause from "effect/Cause";
|
|
4
|
+
import * as Deferred from "effect/Deferred";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Exit from "effect/Exit";
|
|
7
|
+
import * as Fiber from "effect/Fiber";
|
|
8
|
+
import * as Hash from "effect/Hash";
|
|
9
|
+
import * as Layer from "effect/Layer";
|
|
10
|
+
import * as Result from "effect/Result";
|
|
11
|
+
import * as Scope from "effect/Scope";
|
|
12
|
+
import * as Stream from "effect/Stream";
|
|
13
|
+
import * as Bundle from "../../Bundle/Bundle.ts";
|
|
14
|
+
import { WorkerBundle } from "../Workers/WorkerBundle.ts";
|
|
15
|
+
import { Sidecar, type ServeOptions } from "./Sidecar.ts";
|
|
16
|
+
|
|
17
|
+
export const SidecarHandlers = Layer.effect(
|
|
18
|
+
Sidecar,
|
|
19
|
+
Effect.gen(function* () {
|
|
20
|
+
const bundle = yield* WorkerBundle;
|
|
21
|
+
const server = yield* Server.Server;
|
|
22
|
+
|
|
23
|
+
const rootScope = yield* Effect.scope;
|
|
24
|
+
const serverScopes = new Map<string, Scope.Closeable>();
|
|
25
|
+
|
|
26
|
+
const serveScoped = Effect.fnUntraced(function* (
|
|
27
|
+
worker: ServeOptions,
|
|
28
|
+
modules: WorkerModule[],
|
|
29
|
+
) {
|
|
30
|
+
const scope = yield* Effect.flatMap(Effect.scope, Scope.fork);
|
|
31
|
+
const result = yield* server
|
|
32
|
+
.serve({
|
|
33
|
+
name: worker.id.toLowerCase(),
|
|
34
|
+
compatibilityDate: worker.compatibility.date,
|
|
35
|
+
compatibilityFlags: worker.compatibility.flags,
|
|
36
|
+
bindings: worker.bindings,
|
|
37
|
+
durableObjectNamespaces: worker.durableObjectNamespaces,
|
|
38
|
+
modules,
|
|
39
|
+
})
|
|
40
|
+
.pipe(Scope.provide(scope));
|
|
41
|
+
const previous = serverScopes.get(worker.name);
|
|
42
|
+
if (previous) {
|
|
43
|
+
yield* Effect.forkDetach(Scope.close(previous, Exit.void));
|
|
44
|
+
}
|
|
45
|
+
serverScopes.set(worker.name, scope);
|
|
46
|
+
return result;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const watchers = new Map<
|
|
50
|
+
string,
|
|
51
|
+
{
|
|
52
|
+
hash: number;
|
|
53
|
+
fiber: Fiber.Fiber<
|
|
54
|
+
Server.ServeResult,
|
|
55
|
+
Server.ServeError | Bundle.BundleError
|
|
56
|
+
>;
|
|
57
|
+
scope: Scope.Closeable;
|
|
58
|
+
}
|
|
59
|
+
>();
|
|
60
|
+
|
|
61
|
+
const serveFiber = Effect.fnUntraced(function* (worker: ServeOptions) {
|
|
62
|
+
const result = yield* Deferred.make<
|
|
63
|
+
Server.ServeResult,
|
|
64
|
+
Server.ServeError | Bundle.BundleError
|
|
65
|
+
>();
|
|
66
|
+
let start = Date.now();
|
|
67
|
+
yield* bundle.watch(worker).pipe(
|
|
68
|
+
Stream.mapEffect((event) =>
|
|
69
|
+
event._tag === "Error" && !Deferred.isDoneUnsafe(result)
|
|
70
|
+
? Effect.fail(event.error)
|
|
71
|
+
: Effect.succeed(event),
|
|
72
|
+
),
|
|
73
|
+
Stream.tap((event) => {
|
|
74
|
+
if (event._tag === "Start") {
|
|
75
|
+
start = Date.now();
|
|
76
|
+
if (Deferred.isDoneUnsafe(result)) {
|
|
77
|
+
return Effect.log(`[${worker.id}] Rebuilding`);
|
|
78
|
+
}
|
|
79
|
+
} else if (event._tag === "Error") {
|
|
80
|
+
return Effect.logError(`[${worker.id}] Bundle error`, event.error);
|
|
81
|
+
}
|
|
82
|
+
return Effect.void;
|
|
83
|
+
}),
|
|
84
|
+
Stream.filterMap((event) =>
|
|
85
|
+
event._tag === "Success"
|
|
86
|
+
? Result.succeed(event.output)
|
|
87
|
+
: Result.failVoid,
|
|
88
|
+
),
|
|
89
|
+
Stream.map(bundleOutputToWorkerModules),
|
|
90
|
+
Stream.mapEffect((modules) =>
|
|
91
|
+
serveScoped(worker, modules).pipe(
|
|
92
|
+
Effect.exit,
|
|
93
|
+
Effect.tap((exit) => {
|
|
94
|
+
const isDone = Deferred.isDoneUnsafe(result);
|
|
95
|
+
if (exit._tag === "Success") {
|
|
96
|
+
return Effect.log(
|
|
97
|
+
`[${worker.id}] ${isDone ? "Updated" : "Started"} in ${Math.round(Date.now() - start)}ms`,
|
|
98
|
+
);
|
|
99
|
+
} else if (isDone) {
|
|
100
|
+
return Effect.logError(
|
|
101
|
+
`[${worker.id}] Error`,
|
|
102
|
+
Cause.squash(exit.cause),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return Effect.void;
|
|
106
|
+
}),
|
|
107
|
+
Effect.tap((exit) => Deferred.complete(result, exit)),
|
|
108
|
+
),
|
|
109
|
+
),
|
|
110
|
+
Stream.onExit((exit) =>
|
|
111
|
+
exit._tag === "Failure" && !Deferred.isDoneUnsafe(result)
|
|
112
|
+
? Deferred.failCause(result, exit.cause)
|
|
113
|
+
: Effect.void,
|
|
114
|
+
),
|
|
115
|
+
Stream.runDrain,
|
|
116
|
+
Effect.forkScoped,
|
|
117
|
+
);
|
|
118
|
+
return yield* Deferred.await(result);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return Sidecar.of({
|
|
122
|
+
serve: Effect.fn(function* (worker: ServeOptions) {
|
|
123
|
+
const hash = Hash.structure(worker);
|
|
124
|
+
const existing = watchers.get(worker.name);
|
|
125
|
+
if (existing) {
|
|
126
|
+
if (existing.hash === hash) {
|
|
127
|
+
yield* Effect.log(
|
|
128
|
+
`[${worker.id}] No changes, using existing watcher`,
|
|
129
|
+
);
|
|
130
|
+
return yield* Fiber.join(existing.fiber);
|
|
131
|
+
}
|
|
132
|
+
yield* Effect.log(
|
|
133
|
+
`[${worker.id}] Changes detected, interrupting existing watcher`,
|
|
134
|
+
);
|
|
135
|
+
yield* Fiber.interrupt(existing.fiber);
|
|
136
|
+
yield* Scope.close(existing.scope, Exit.void);
|
|
137
|
+
watchers.delete(worker.name);
|
|
138
|
+
}
|
|
139
|
+
const scope = yield* Scope.fork(rootScope);
|
|
140
|
+
const fiber = yield* serveFiber(worker).pipe(
|
|
141
|
+
Effect.forkDetach,
|
|
142
|
+
Scope.provide(scope),
|
|
143
|
+
);
|
|
144
|
+
watchers.set(worker.name, { hash, fiber, scope });
|
|
145
|
+
return yield* Fiber.join(fiber).pipe(
|
|
146
|
+
Effect.onExit((exit) =>
|
|
147
|
+
Effect.sync(() => {
|
|
148
|
+
if (exit._tag === "Failure") {
|
|
149
|
+
watchers.delete(worker.name);
|
|
150
|
+
}
|
|
151
|
+
}),
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
}),
|
|
155
|
+
stop: Effect.fn(function* (name: string) {
|
|
156
|
+
const watcher = watchers.get(name);
|
|
157
|
+
if (watcher) {
|
|
158
|
+
yield* Fiber.interrupt(watcher.fiber);
|
|
159
|
+
yield* Scope.close(watcher.scope, Exit.void);
|
|
160
|
+
watchers.delete(name);
|
|
161
|
+
}
|
|
162
|
+
}),
|
|
163
|
+
});
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
function bundleOutputToWorkerModules(
|
|
168
|
+
bundle: Bundle.BundleOutput,
|
|
169
|
+
): WorkerModule[] {
|
|
170
|
+
const modules: WorkerModule[] = [];
|
|
171
|
+
for (const file of bundle.files) {
|
|
172
|
+
if (file.path.endsWith(".map") || file.content instanceof Uint8Array) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
modules.push({
|
|
176
|
+
name: file.path,
|
|
177
|
+
type: "ESModule",
|
|
178
|
+
content: file.content,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return modules;
|
|
182
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as RuntimeServices from "@distilled.cloud/cloudflare-runtime/RuntimeServices";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as Path from "effect/Path";
|
|
5
|
+
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
6
|
+
import * as AlchemyContext from "../../AlchemyContext.ts";
|
|
7
|
+
import { AuthProviders } from "../../Auth/AuthProvider.ts";
|
|
8
|
+
import * as RpcServer from "../../Sidecar/RpcServer.ts";
|
|
9
|
+
import {
|
|
10
|
+
httpServer,
|
|
11
|
+
PlatformServices,
|
|
12
|
+
runMain,
|
|
13
|
+
} from "../../Util/PlatformServices.ts";
|
|
14
|
+
import { CloudflareAuth } from "../Auth/AuthProvider.ts";
|
|
15
|
+
import * as CloudflareEnvironment from "../CloudflareEnvironment.ts";
|
|
16
|
+
import * as Credentials from "../Credentials.ts";
|
|
17
|
+
import { Sidecar, SidecarSchema } from "./Sidecar.ts";
|
|
18
|
+
import { SidecarHandlers } from "./SidecarHandlers.ts";
|
|
19
|
+
|
|
20
|
+
const apiServices = Layer.merge(
|
|
21
|
+
Credentials.fromAuthProvider(),
|
|
22
|
+
CloudflareEnvironment.fromProfile(),
|
|
23
|
+
).pipe(
|
|
24
|
+
Layer.provide(CloudflareAuth),
|
|
25
|
+
Layer.provide(Layer.succeed(AuthProviders, {})),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const runtimeServices = SidecarHandlers.pipe(
|
|
29
|
+
Layer.provide(
|
|
30
|
+
Layer.unwrap(
|
|
31
|
+
Effect.gen(function* () {
|
|
32
|
+
const { accountId } =
|
|
33
|
+
yield* CloudflareEnvironment.CloudflareEnvironment;
|
|
34
|
+
const { dotAlchemy } = yield* AlchemyContext.AlchemyContext;
|
|
35
|
+
const path = yield* Path.Path;
|
|
36
|
+
return RuntimeServices.layer({
|
|
37
|
+
accountId,
|
|
38
|
+
storage: path.join(dotAlchemy, "local"),
|
|
39
|
+
});
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
Layer.provide(
|
|
44
|
+
Layer.mergeAll(apiServices, FetchHttpClient.layer, httpServer()),
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const services = Layer.provideMerge(
|
|
49
|
+
Layer.provideMerge(runtimeServices, RpcServer.layerServices(import.meta.url)),
|
|
50
|
+
PlatformServices,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
RpcServer.makeRpcServer(Sidecar.asEffect(), SidecarSchema).pipe(
|
|
54
|
+
Effect.provide(services),
|
|
55
|
+
Effect.scoped,
|
|
56
|
+
runMain,
|
|
57
|
+
);
|
|
@@ -49,12 +49,42 @@ export type Queue = Resource<
|
|
|
49
49
|
* ```
|
|
50
50
|
*
|
|
51
51
|
* @section Binding to a Worker
|
|
52
|
+
* In an Effect-style Worker, use `Cloudflare.QueueBinding.bind` in
|
|
53
|
+
* the init phase and provide `Cloudflare.QueueBindingLive` in the
|
|
54
|
+
* runtime layer. The returned `QueueSender` exposes `send` and
|
|
55
|
+
* `sendBatch`.
|
|
56
|
+
*
|
|
52
57
|
* @example Sending messages from a Worker
|
|
53
58
|
* ```typescript
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
59
|
+
* import * as Cloudflare from "alchemy/Cloudflare";
|
|
60
|
+
* import * as Effect from "effect/Effect";
|
|
61
|
+
* import { HttpServerRequest } from "effect/unstable/http/HttpServerRequest";
|
|
62
|
+
* import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
|
|
63
|
+
*
|
|
64
|
+
* export const Queue = Cloudflare.Queue("Queue");
|
|
65
|
+
*
|
|
66
|
+
* export default Cloudflare.Worker(
|
|
67
|
+
* "Worker",
|
|
68
|
+
* { main: import.meta.path },
|
|
69
|
+
* Effect.gen(function* () {
|
|
70
|
+
* const queue = yield* Cloudflare.QueueBinding.bind(Queue);
|
|
71
|
+
*
|
|
72
|
+
* return {
|
|
73
|
+
* fetch: Effect.gen(function* () {
|
|
74
|
+
* const request = yield* HttpServerRequest;
|
|
75
|
+
* if (request.url === "/queue/send" && request.method === "POST") {
|
|
76
|
+
* const text = yield* request.text;
|
|
77
|
+
* yield* queue.send({ text, sentAt: Date.now() }).pipe(Effect.orDie);
|
|
78
|
+
* return yield* HttpServerResponse.json(
|
|
79
|
+
* { sent: { text } },
|
|
80
|
+
* { status: 202 },
|
|
81
|
+
* );
|
|
82
|
+
* }
|
|
83
|
+
* return HttpServerResponse.text("Not Found", { status: 404 });
|
|
84
|
+
* }),
|
|
85
|
+
* };
|
|
86
|
+
* }).pipe(Effect.provide(Cloudflare.QueueBindingLive)),
|
|
87
|
+
* );
|
|
58
88
|
* ```
|
|
59
89
|
*/
|
|
60
90
|
export const Queue = Resource<Queue>("Cloudflare.Queue")({
|
|
@@ -26,6 +26,39 @@ export class QueueSendError extends Data.TaggedError("QueueSendError")<{
|
|
|
26
26
|
cause?: unknown;
|
|
27
27
|
}> {}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Binding service that turns a {@link Queue} resource into a typed
|
|
31
|
+
* {@link QueueSender} you can call from a Worker's runtime Effect.
|
|
32
|
+
*
|
|
33
|
+
* @section Sending Messages
|
|
34
|
+
* Bind the queue in the Worker's init phase, then use `send` for
|
|
35
|
+
* single messages or `sendBatch` for many messages in one call.
|
|
36
|
+
* Messages can be any JSON-serializable value.
|
|
37
|
+
*
|
|
38
|
+
* @example Producer route
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const queue = yield* Cloudflare.QueueBinding.bind(Queue);
|
|
41
|
+
*
|
|
42
|
+
* return {
|
|
43
|
+
* fetch: Effect.gen(function* () {
|
|
44
|
+
* yield* queue.send({ text: "hi", sentAt: Date.now() });
|
|
45
|
+
* return HttpServerResponse.empty({ status: 202 });
|
|
46
|
+
* }),
|
|
47
|
+
* };
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example Sending a batch
|
|
51
|
+
* ```typescript
|
|
52
|
+
* yield* queue.sendBatch([
|
|
53
|
+
* { body: { event: "click", id: 1 } },
|
|
54
|
+
* { body: { event: "click", id: 2 } },
|
|
55
|
+
* { body: "raw text", contentType: "text" },
|
|
56
|
+
* ]);
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* Provide {@link QueueBindingLive} in the worker's runtime layer to
|
|
60
|
+
* resolve the underlying Cloudflare queue at request time.
|
|
61
|
+
*/
|
|
29
62
|
export class QueueBinding extends Binding.Service<
|
|
30
63
|
QueueBinding,
|
|
31
64
|
(queue: Queue) => Effect.Effect<QueueSender>
|