alchemy 2.0.0-beta.39 → 2.0.0-beta.40

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 (99) hide show
  1. package/bin/exec.js +5 -26
  2. package/bin/exec.js.map +1 -1
  3. package/lib/Auth/Lock.d.ts +4 -3
  4. package/lib/Auth/Lock.d.ts.map +1 -1
  5. package/lib/Auth/Lock.js +6 -5
  6. package/lib/Auth/Lock.js.map +1 -1
  7. package/lib/Axiom/Chart.d.ts +52 -2
  8. package/lib/Axiom/Chart.d.ts.map +1 -1
  9. package/lib/Bundle/Bundle.d.ts +7 -4
  10. package/lib/Bundle/Bundle.d.ts.map +1 -1
  11. package/lib/Bundle/Bundle.js +37 -14
  12. package/lib/Bundle/Bundle.js.map +1 -1
  13. package/lib/Cloudflare/AiGateway/AiGateway.d.ts +11 -11
  14. package/lib/Cloudflare/AiGateway/AiGateway.d.ts.map +1 -1
  15. package/lib/Cloudflare/AiGateway/AiGateway.js +28 -63
  16. package/lib/Cloudflare/AiGateway/AiGateway.js.map +1 -1
  17. package/lib/Cloudflare/Bridge.d.ts +3 -0
  18. package/lib/Cloudflare/Bridge.d.ts.map +1 -0
  19. package/lib/Cloudflare/Bridge.js +3 -0
  20. package/lib/Cloudflare/Bridge.js.map +1 -0
  21. package/lib/Cloudflare/Container/StartContainer.d.ts +2 -2
  22. package/lib/Cloudflare/Container/StartContainer.d.ts.map +1 -1
  23. package/lib/Cloudflare/Container/StartContainer.js +3 -3
  24. package/lib/Cloudflare/Container/StartContainer.js.map +1 -1
  25. package/lib/Cloudflare/Local/Sidecar.d.ts +45 -21
  26. package/lib/Cloudflare/Local/Sidecar.d.ts.map +1 -1
  27. package/lib/Cloudflare/Local/Sidecar.js +30 -6
  28. package/lib/Cloudflare/Local/Sidecar.js.map +1 -1
  29. package/lib/Cloudflare/Local/SidecarHandlers.d.ts +5 -2
  30. package/lib/Cloudflare/Local/SidecarHandlers.d.ts.map +1 -1
  31. package/lib/Cloudflare/Local/SidecarHandlers.js +323 -70
  32. package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -1
  33. package/lib/Cloudflare/Local/SidecarServer.js +9 -5
  34. package/lib/Cloudflare/Local/SidecarServer.js.map +1 -1
  35. package/lib/Cloudflare/StateStore/Api.d.ts +1 -1
  36. package/lib/Cloudflare/StateStore/Api.js +1 -1
  37. package/lib/Cloudflare/StateStore/Store.d.ts +1 -1
  38. package/lib/Cloudflare/Workers/HttpServer.d.ts.map +1 -1
  39. package/lib/Cloudflare/Workers/HttpServer.js +3 -25
  40. package/lib/Cloudflare/Workers/HttpServer.js.map +1 -1
  41. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +1 -2
  42. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  43. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +16 -97
  44. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  45. package/lib/Cloudflare/Workers/Vite.d.ts +9 -0
  46. package/lib/Cloudflare/Workers/Vite.d.ts.map +1 -0
  47. package/lib/Cloudflare/Workers/Vite.js +95 -0
  48. package/lib/Cloudflare/Workers/Vite.js.map +1 -0
  49. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  50. package/lib/Cloudflare/Workers/Worker.js +9 -95
  51. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  52. package/lib/Cloudflare/Workers/WorkerBundle.d.ts +1 -2
  53. package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
  54. package/lib/Cloudflare/Workers/WorkerBundle.js +5 -4
  55. package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
  56. package/lib/Diff.d.ts +15 -1
  57. package/lib/Diff.d.ts.map +1 -1
  58. package/lib/Diff.js +13 -7
  59. package/lib/Diff.js.map +1 -1
  60. package/lib/Http.d.ts.map +1 -1
  61. package/lib/Http.js +12 -7
  62. package/lib/Http.js.map +1 -1
  63. package/lib/Sidecar/Lock.d.ts +4 -5
  64. package/lib/Sidecar/Lock.d.ts.map +1 -1
  65. package/lib/Sidecar/Lock.js +22 -85
  66. package/lib/Sidecar/Lock.js.map +1 -1
  67. package/lib/Sidecar/RpcHandler.d.ts +4 -1
  68. package/lib/Sidecar/RpcHandler.d.ts.map +1 -1
  69. package/lib/Sidecar/RpcHandler.js +21 -2
  70. package/lib/Sidecar/RpcHandler.js.map +1 -1
  71. package/lib/Util/PlatformServices.d.ts.map +1 -1
  72. package/lib/Util/PlatformServices.js +0 -2
  73. package/lib/Util/PlatformServices.js.map +1 -1
  74. package/lib/tsconfig.test.tsbuildinfo +1 -1
  75. package/package.json +17 -12
  76. package/src/AWS/EC2/hosted.ts +1 -1
  77. package/src/AWS/ECS/Task.ts +1 -1
  78. package/src/AWS/Lambda/Function.ts +1 -1
  79. package/src/Auth/Lock.ts +6 -5
  80. package/src/Axiom/Chart.ts +56 -2
  81. package/src/Bundle/Bundle.ts +41 -17
  82. package/src/Cloudflare/AiGateway/AiGateway.ts +43 -76
  83. package/src/Cloudflare/Bridge.ts +2 -0
  84. package/src/Cloudflare/Container/ContainerApplication.ts +1 -1
  85. package/src/Cloudflare/Container/StartContainer.ts +10 -3
  86. package/src/Cloudflare/Local/Sidecar.ts +50 -21
  87. package/src/Cloudflare/Local/SidecarHandlers.ts +395 -91
  88. package/src/Cloudflare/Local/SidecarServer.ts +12 -4
  89. package/src/Cloudflare/StateStore/Api.ts +1 -1
  90. package/src/Cloudflare/Workers/HttpServer.ts +5 -37
  91. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +18 -111
  92. package/src/Cloudflare/Workers/Vite.ts +114 -0
  93. package/src/Cloudflare/Workers/Worker.ts +27 -109
  94. package/src/Cloudflare/Workers/WorkerBundle.ts +7 -7
  95. package/src/Diff.ts +29 -8
  96. package/src/Http.ts +14 -10
  97. package/src/Sidecar/Lock.ts +30 -154
  98. package/src/Sidecar/RpcHandler.ts +67 -40
  99. package/src/Util/PlatformServices.ts +0 -2
@@ -1,79 +1,221 @@
1
- import * as Server from "@distilled.cloud/cloudflare-runtime/Server";
2
- import type { WorkerModule } from "@distilled.cloud/cloudflare-runtime/WorkerModule";
1
+ import {
2
+ Runtime,
3
+ type BindingHook,
4
+ type BindingServices,
5
+ type HyperdriveOrigin,
6
+ type Module,
7
+ type Assets as RuntimeAssets,
8
+ type DurableObjectNamespace as RuntimeDurableObjectNamespace,
9
+ type RuntimeServices,
10
+ } from "@distilled.cloud/cloudflare-runtime";
11
+ import {
12
+ Ai,
13
+ Assets,
14
+ Browser,
15
+ D1,
16
+ Data,
17
+ DurableObjectNamespace,
18
+ Hyperdrive,
19
+ Images,
20
+ Json,
21
+ KvNamespace,
22
+ R2Bucket,
23
+ Service,
24
+ Text,
25
+ VersionMetadata,
26
+ WasmModule,
27
+ WorkerLoader,
28
+ } from "@distilled.cloud/cloudflare-runtime/bindings";
29
+ import * as LocalProxy from "@distilled.cloud/cloudflare-runtime/proxy/LocalProxy";
3
30
  import * as Cause from "effect/Cause";
4
- import * as Deferred from "effect/Deferred";
5
31
  import * as Effect from "effect/Effect";
6
32
  import * as Exit from "effect/Exit";
7
33
  import * as Fiber from "effect/Fiber";
8
34
  import * as Hash from "effect/Hash";
9
35
  import * as Layer from "effect/Layer";
36
+ import * as Path from "effect/Path";
37
+ import * as Redacted from "effect/Redacted";
10
38
  import * as Result from "effect/Result";
11
39
  import * as Scope from "effect/Scope";
12
40
  import * as Stream from "effect/Stream";
13
- import * as Bundle from "../../Bundle/Bundle.ts";
14
- import { WorkerBundle } from "../Workers/WorkerBundle.ts";
41
+ import type * as Bundle from "../../Bundle/Bundle.ts";
42
+ import { InstanceId } from "../../InstanceId.ts";
43
+ import { Stack } from "../../Stack.ts";
44
+ import { Stage } from "../../Stage.ts";
45
+ import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
46
+ import { getCompatibility } from "../Workers/Compatibility.ts";
47
+ import { getCronBindings } from "../Workers/index.ts";
48
+ import * as Vite from "../Workers/Vite.ts";
49
+ import type {
50
+ Worker,
51
+ WorkerAssetsConfig,
52
+ WorkerBinding,
53
+ } from "../Workers/Worker.ts";
15
54
  import {
55
+ WorkerBundle,
56
+ type WorkerBundleOptions,
57
+ } from "../Workers/WorkerBundle.ts";
58
+ import { createWorkerName } from "../Workers/WorkerName.ts";
59
+ import {
60
+ ServeError,
61
+ ServeResult,
16
62
  Sidecar,
17
- type ServeError,
18
- type ServeOptions,
19
- type ServeResult,
63
+ ValidationError,
64
+ type ReconcileOptions,
20
65
  } from "./Sidecar.ts";
21
66
 
22
67
  export const SidecarHandlers = Layer.effect(
23
68
  Sidecar,
24
69
  Effect.gen(function* () {
25
- const bundle = yield* WorkerBundle;
26
- const server = yield* Server.Server;
70
+ const { accountId } = yield* CloudflareEnvironment;
71
+ const bundler = yield* WorkerBundle;
72
+ const runtime = yield* Runtime;
73
+ const path = yield* Path.Path;
74
+ const localProxy = yield* LocalProxy.LocalProxy;
27
75
 
28
- const rootScope = yield* Effect.scope;
29
- const serverScopes = new Map<string, Scope.Closeable>();
76
+ const toRuntimeModules = Effect.fn(function* (bundle: Bundle.BundleOutput) {
77
+ const modules: Module[] = [];
78
+ for (const file of bundle.files) {
79
+ const ext = path.extname(file.path);
80
+ const type = moduleTypeFromExtension(ext);
81
+ if (type === "SourceMap") continue;
82
+ if (type === "Data" || type === "Wasm") {
83
+ if (!(file.content instanceof Uint8Array)) {
84
+ return yield* new ValidationError({
85
+ message: `Expected Uint8Array for ${file.path} (${type})`,
86
+ value: file.content,
87
+ });
88
+ }
89
+ modules.push({
90
+ name: file.path,
91
+ type,
92
+ content: file.content,
93
+ });
94
+ } else {
95
+ if (typeof file.content !== "string") {
96
+ return yield* new ValidationError({
97
+ message: `Expected string for ${file.path} (${type})`,
98
+ value: file.content,
99
+ });
100
+ }
101
+ modules.push({
102
+ name: file.path,
103
+ type,
104
+ content: file.content,
105
+ });
106
+ }
107
+ }
108
+ return modules;
109
+ });
30
110
 
31
111
  const serveScoped = Effect.fnUntraced(function* (
32
- worker: ServeOptions,
33
- modules: WorkerModule[],
112
+ worker: WorkerConfig,
113
+ bundle: Bundle.BundleOutput,
34
114
  ) {
35
115
  const scope = yield* Effect.flatMap(Effect.scope, Scope.fork);
36
- const result = yield* server
37
- .serve({
38
- name: worker.id.toLowerCase(),
116
+ const address = yield* runtime
117
+ .start({
118
+ name: worker.name,
39
119
  compatibilityDate: worker.compatibility.date,
40
120
  compatibilityFlags: worker.compatibility.flags,
41
- bindings: worker.bindings,
121
+ bindings: worker.workerBindings as never,
42
122
  hyperdrives: worker.hyperdrives,
43
- durableObjectNamespaces: worker.durableObjectNamespaces,
44
- modules,
123
+ durableObjectNamespaces: toRuntimeDurableObjectNamespaces(
124
+ worker.durableObjectNamespaces,
125
+ ),
126
+ modules: yield* toRuntimeModules(bundle),
127
+ assets: toRuntimeAssets(worker.assets),
45
128
  })
46
129
  .pipe(Scope.provide(scope));
47
- const previous = serverScopes.get(worker.name);
130
+ const previous = workerdScopes.get(worker.id);
48
131
  if (previous) {
49
132
  yield* Effect.forkDetach(Scope.close(previous, Exit.void));
50
133
  }
51
- serverScopes.set(worker.name, scope);
52
- return result;
134
+ workerdScopes.set(worker.id, scope);
135
+ yield* localProxy.setLocalAddress(worker.id, address);
136
+ return address;
53
137
  });
54
138
 
55
- const watchers = new Map<
56
- string,
57
- {
58
- hash: number;
59
- fiber: Fiber.Fiber<ServeResult, ServeError>;
60
- scope: Scope.Closeable;
139
+ const buildConfig = Effect.fn(function* ({
140
+ id,
141
+ props,
142
+ bindings,
143
+ stack,
144
+ instanceId,
145
+ }: ReconcileOptions) {
146
+ const name = yield* createWorkerName(id, props.name).pipe(
147
+ Effect.provideService(Stack, stack),
148
+ Effect.provideService(Stage, stack.stage),
149
+ Effect.provideService(InstanceId, instanceId),
150
+ );
151
+ const compatibility = getCompatibility(props);
152
+ const workerBindings: BindingHook<BindingServices>[] = [];
153
+ const durableObjectNamespaces: Record<string, string> = {};
154
+ const hyperdrives: Record<string, Required<HyperdriveOrigin>> = {};
155
+ for (const { data } of bindings) {
156
+ for (const binding of data.bindings ?? []) {
157
+ if (binding.type === "durable_object_namespace") {
158
+ durableObjectNamespaces[binding.name] = binding.className!;
159
+ }
160
+ workerBindings.push(yield* toRuntimeBinding(binding));
161
+ }
162
+ if (data.hyperdrives) {
163
+ for (const [id, origin] of Object.entries(data.hyperdrives)) {
164
+ hyperdrives[id] = {
165
+ scheme: origin.scheme,
166
+ host: origin.host,
167
+ port: origin.port,
168
+ user: origin.user,
169
+ database: origin.database,
170
+ password: Redacted.isRedacted(origin.password)
171
+ ? Redacted.value(origin.password)
172
+ : origin.password,
173
+ sslmode: origin.sslmode,
174
+ };
175
+ }
176
+ }
61
177
  }
62
- >();
178
+ for (const [key, value] of Object.entries(props.env ?? {})) {
179
+ if (value === undefined) continue;
180
+ if (Redacted.isRedacted(value)) {
181
+ workerBindings.push(Text.binding(key, Redacted.value(value)));
182
+ } else if (typeof value === "string") {
183
+ workerBindings.push(Text.binding(key, value));
184
+ } else {
185
+ workerBindings.push(Json.binding(key, value));
186
+ }
187
+ }
188
+ return {
189
+ id,
190
+ name,
191
+ compatibility,
192
+ workerBindings,
193
+ durableObjectNamespaces,
194
+ hyperdrives,
195
+ bundleOptions: {
196
+ id,
197
+ main: props.main,
198
+ compatibility,
199
+ entry: props.isExternal
200
+ ? { kind: "external" }
201
+ : { kind: "effect", exports: (props.exports ?? {}) as any },
202
+ stack: { name: stack.name, stage: stack.stage },
203
+ userOptions: props.build,
204
+ } satisfies WorkerBundleOptions,
205
+ assets: props.assets,
206
+ };
207
+ });
63
208
 
64
- const serveFiber = Effect.fnUntraced(function* (worker: ServeOptions) {
65
- const result = yield* Deferred.make<ServeResult, ServeError>();
209
+ type WorkerConfig = Effect.Success<ReturnType<typeof buildConfig>>;
210
+
211
+ const runServer = Effect.fnUntraced(function* (worker: WorkerConfig) {
66
212
  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
- ),
213
+ let status: "start" | "update" = "start";
214
+ yield* bundler.watch(worker.bundleOptions).pipe(
73
215
  Stream.tap((event) => {
74
216
  if (event._tag === "Start") {
75
217
  start = Date.now();
76
- if (Deferred.isDoneUnsafe(result)) {
218
+ if (status === "update") {
77
219
  return Effect.log(`[${worker.id}] Rebuilding`);
78
220
  }
79
221
  } else if (event._tag === "Error") {
@@ -86,105 +228,267 @@ export const SidecarHandlers = Layer.effect(
86
228
  ? Result.succeed(event.output)
87
229
  : Result.failVoid,
88
230
  ),
89
- Stream.map(bundleOutputToWorkerModules),
90
- Stream.mapEffect((modules) =>
91
- serveScoped(worker, modules).pipe(
231
+ Stream.mapEffect((bundle) =>
232
+ serveScoped(worker, bundle).pipe(
92
233
  Effect.exit,
93
234
  Effect.tap((exit) => {
94
- const isDone = Deferred.isDoneUnsafe(result);
95
235
  if (exit._tag === "Success") {
96
- return Effect.log(
97
- `[${worker.id}] ${isDone ? "Updated" : "Started"} in ${Math.round(Date.now() - start)}ms`,
236
+ const message = Effect.log(
237
+ `[${worker.id}] ${status === "update" ? "Updated" : "Started"} in ${Math.round(Date.now() - start)}ms`,
98
238
  );
99
- } else if (isDone) {
239
+ status = "update";
240
+ return message;
241
+ } else {
100
242
  return Effect.logError(
101
243
  `[${worker.id}] Error`,
102
244
  Cause.squash(exit.cause),
103
245
  );
104
246
  }
105
- return Effect.void;
106
247
  }),
107
- Effect.tap((exit) => Deferred.complete(result, exit)),
108
248
  ),
109
249
  ),
110
- Stream.onExit((exit) =>
111
- exit._tag === "Failure" && !Deferred.isDoneUnsafe(result)
112
- ? Deferred.failCause(result, exit.cause)
113
- : Effect.void,
114
- ),
115
250
  Stream.runDrain,
116
251
  Effect.forkScoped,
117
252
  );
118
- return yield* Deferred.await(result);
253
+ });
254
+
255
+ const rootScope = yield* Effect.scope;
256
+ const workerdScopes = new Map<string, Scope.Closeable>();
257
+
258
+ const context = yield* Effect.context<RuntimeServices>();
259
+ const instances = new Map<
260
+ string,
261
+ {
262
+ hash: number;
263
+ fiber: Fiber.Fiber<ServeResult, ServeError>;
264
+ scope: Scope.Closeable;
265
+ }
266
+ >();
267
+
268
+ const runInstance = Effect.fn(function* (options: ReconcileOptions) {
269
+ const { id, props, bindings } = options;
270
+ const config = yield* buildConfig(options);
271
+ const url = yield* localProxy.registerWorker(id);
272
+ if (props.vite) {
273
+ console.log("starting vite dev server", id);
274
+ const devServer = yield* Vite.viteDev(
275
+ props.vite.rootDir,
276
+ props.env ?? {},
277
+ {
278
+ compatibilityDate: config.compatibility.date,
279
+ compatibilityFlags: config.compatibility.flags,
280
+ worker: {
281
+ name: config.name,
282
+ bindings: config.workerBindings,
283
+ durableObjectNamespaces: toRuntimeDurableObjectNamespaces(
284
+ config.durableObjectNamespaces,
285
+ ),
286
+ hyperdrives: config.hyperdrives,
287
+ assets: toRuntimeAssets(config.assets),
288
+ },
289
+ context,
290
+ },
291
+ );
292
+ console.log("vite dev server started", id);
293
+ const localAddress = devServer.resolvedUrls!.local[0].slice(0, -1);
294
+ yield* localProxy.setLocalAddress(id, localAddress);
295
+ } else {
296
+ yield* runServer(config);
297
+ }
298
+ return {
299
+ workerId: config.name,
300
+ workerName: config.name,
301
+ logpush: undefined,
302
+ url,
303
+ tags: [],
304
+ durableObjectNamespaces: config.durableObjectNamespaces,
305
+ domains: [],
306
+ crons: Array.from(
307
+ new Set([...getCronBindings(bindings), ...(props.crons ?? [])]),
308
+ ),
309
+ accountId,
310
+ } satisfies Worker["Attributes"];
119
311
  });
120
312
 
121
313
  return Sidecar.of({
122
- serve: Effect.fn(function* (worker: ServeOptions) {
123
- const hash = Hash.structure(worker);
124
- const existing = watchers.get(worker.name);
314
+ diff: Effect.fn(function* (options) {
315
+ const hash = Hash.structure(options);
316
+ return {
317
+ action: instances.get(options.id)?.hash === hash ? "noop" : "update",
318
+ };
319
+ }),
320
+ reconcile: Effect.fn(function* (options) {
321
+ const hash = Hash.structure(options);
322
+ const existing = instances.get(options.id);
125
323
  if (existing) {
126
324
  if (existing.hash === hash) {
127
325
  yield* Effect.log(
128
- `[${worker.id}] No changes, using existing watcher`,
326
+ `[${options.id}] No changes, using existing instance`,
129
327
  );
130
328
  return yield* Fiber.join(existing.fiber);
131
329
  }
132
330
  yield* Effect.log(
133
- `[${worker.id}] Changes detected, interrupting existing watcher`,
331
+ `[${options.id}] Changes detected, interrupting existing instance`,
134
332
  );
135
333
  yield* Fiber.interrupt(existing.fiber);
136
334
  yield* Scope.close(existing.scope, Exit.void);
137
- watchers.delete(worker.name);
335
+ instances.delete(options.id);
138
336
  }
139
337
  const scope = yield* Scope.fork(rootScope);
140
- const fiber = yield* serveFiber(worker).pipe(
338
+ const fiber = yield* runInstance(options).pipe(
141
339
  Effect.forkDetach,
142
340
  Scope.provide(scope),
143
341
  );
144
- watchers.set(worker.name, { hash, fiber, scope });
342
+ instances.set(options.id, { hash, fiber, scope });
145
343
  return yield* Fiber.join(fiber).pipe(
146
344
  Effect.onExit((exit) =>
147
345
  Effect.sync(() => {
148
346
  if (exit._tag === "Failure") {
149
- watchers.delete(worker.name);
347
+ instances.delete(options.id);
150
348
  }
151
349
  }),
152
350
  ),
153
351
  );
154
352
  }),
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);
353
+ delete: Effect.fn(function* (id) {
354
+ const existing = instances.get(id);
355
+ if (existing) {
356
+ yield* Fiber.interrupt(existing.fiber);
357
+ yield* Scope.close(existing.scope, Exit.void);
358
+ instances.delete(id);
161
359
  }
162
360
  }),
163
361
  });
164
362
  }),
165
363
  );
166
364
 
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")) {
173
- continue;
174
- }
175
- if (file.content instanceof Uint8Array) {
176
- modules.push({
177
- name: file.path,
178
- type: file.path.endsWith(".wasm") ? "Wasm" : "Data",
179
- content: file.content,
180
- });
181
- continue;
182
- }
183
- modules.push({
184
- name: file.path,
185
- type: "ESModule",
186
- content: file.content,
365
+ const toRuntimeBinding = Effect.fnUntraced(function* (b: WorkerBinding) {
366
+ const unsupported = () =>
367
+ new ValidationError({
368
+ message: `${b.type} bindings are not supported in local mode`,
369
+ value: b,
187
370
  });
371
+ switch (b.type) {
372
+ case "ai":
373
+ return Ai.remote(b.name);
374
+ case "analytics_engine":
375
+ return yield* unsupported();
376
+ case "artifacts":
377
+ return yield* unsupported();
378
+ case "assets":
379
+ return Assets.binding(b.name);
380
+ case "browser":
381
+ return Browser.binding(b.name);
382
+ case "d1":
383
+ return D1.remote(b.name, b.id);
384
+ case "data_blob":
385
+ return Data.binding(b.name, Buffer.from(b.part));
386
+ case "dispatch_namespace":
387
+ return yield* unsupported();
388
+ case "durable_object_namespace":
389
+ return DurableObjectNamespace.local({
390
+ name: b.name,
391
+ className: b.className!,
392
+ scriptName: b.scriptName,
393
+ });
394
+ case "hyperdrive":
395
+ return Hyperdrive.binding(b.name, b.id);
396
+ case "images":
397
+ return Images.remote(b.name);
398
+ case "inherit":
399
+ return yield* unsupported();
400
+ case "json":
401
+ return Json.binding(b.name, b.json);
402
+ case "kv_namespace":
403
+ return KvNamespace.remote(b.name, b.namespaceId);
404
+ case "mtls_certificate":
405
+ return yield* unsupported();
406
+ case "pipelines":
407
+ return yield* unsupported();
408
+ case "plain_text":
409
+ return Text.binding(b.name, b.text);
410
+ case "queue":
411
+ return yield* unsupported();
412
+ case "r2_bucket":
413
+ return R2Bucket.remote(b.name, b.bucketName, b.jurisdiction);
414
+ case "ratelimit":
415
+ return yield* unsupported();
416
+ case "secret_key":
417
+ return yield* unsupported();
418
+ case "secret_text":
419
+ return Text.binding(b.name, b.text);
420
+ case "secrets_store_secret":
421
+ return yield* unsupported();
422
+ case "send_email":
423
+ return yield* unsupported();
424
+ case "service":
425
+ return Service.local({ name: b.name, scriptName: b.service });
426
+ case "text_blob":
427
+ return Data.binding(b.name, Buffer.from(b.part));
428
+ case "vectorize":
429
+ return yield* unsupported();
430
+ case "version_metadata":
431
+ return VersionMetadata.binding(b.name);
432
+ case "wasm_module":
433
+ return WasmModule.binding(b.name, Buffer.from(b.part));
434
+ case "worker_loader":
435
+ return WorkerLoader.binding(b.name);
436
+ case "workflow":
437
+ return yield* unsupported();
438
+ default:
439
+ return yield* unsupported();
440
+ }
441
+ });
442
+
443
+ const toRuntimeAssets = (
444
+ assets: WorkerAssetsConfig | undefined,
445
+ ): RuntimeAssets | undefined => {
446
+ if (!assets) return undefined;
447
+ if (typeof assets === "string") {
448
+ return {
449
+ directory: assets,
450
+ };
451
+ }
452
+ return {
453
+ directory: "directory" in assets ? assets.directory : assets.path,
454
+ headers: assets.config?.headers,
455
+ redirects: assets.config?.redirects,
456
+ htmlHandling: assets.config?.htmlHandling,
457
+ notFoundHandling: assets.config?.notFoundHandling,
458
+ runWorkerFirst: assets.config?.runWorkerFirst,
459
+ serveDirectly: assets.config?.serveDirectly,
460
+ };
461
+ };
462
+
463
+ const toRuntimeDurableObjectNamespaces = (
464
+ namespaces: Record<string, string>,
465
+ ): RuntimeDurableObjectNamespace[] => {
466
+ return Object.entries(namespaces).map(([className, namespaceId]) => ({
467
+ className,
468
+ uniqueKey: namespaceId,
469
+ sql: true,
470
+ }));
471
+ };
472
+
473
+ const moduleTypeFromExtension = (ext: string): Module["type"] | "SourceMap" => {
474
+ switch (ext) {
475
+ case ".wasm":
476
+ return "Wasm";
477
+ case ".txt":
478
+ case ".html":
479
+ case ".sql":
480
+ case ".custom":
481
+ return "Text";
482
+ case ".bin":
483
+ return "Data";
484
+ case ".mjs":
485
+ case ".js":
486
+ return "ESModule";
487
+ case ".cjs":
488
+ return "CommonJsModule";
489
+ case ".map":
490
+ return "SourceMap";
491
+ default:
492
+ return "Text";
188
493
  }
189
- return modules;
190
- }
494
+ };
@@ -1,4 +1,7 @@
1
- import * as RuntimeServices from "@distilled.cloud/cloudflare-runtime/RuntimeServices";
1
+ import {
2
+ layerLocalProxy,
3
+ layerRuntime,
4
+ } from "@distilled.cloud/cloudflare-runtime/RuntimeServices";
2
5
  import * as Effect from "effect/Effect";
3
6
  import * as Layer from "effect/Layer";
4
7
  import * as Path from "effect/Path";
@@ -31,6 +34,7 @@ const apiServices = Layer.merge(
31
34
  );
32
35
 
33
36
  const runtimeServices = SidecarHandlers.pipe(
37
+ Layer.provide(layerLocalProxy(1337)),
34
38
  Layer.provide(
35
39
  Layer.unwrap(
36
40
  Effect.gen(function* () {
@@ -38,9 +42,13 @@ const runtimeServices = SidecarHandlers.pipe(
38
42
  yield* CloudflareEnvironment.CloudflareEnvironment;
39
43
  const { dotAlchemy } = yield* AlchemyContext.AlchemyContext;
40
44
  const path = yield* Path.Path;
41
- return RuntimeServices.layer({
42
- accountId,
43
- storage: path.join(dotAlchemy, "local"),
45
+ return layerRuntime({
46
+ api: {
47
+ accountId,
48
+ },
49
+ storage: {
50
+ directory: path.join(dotAlchemy, "local"),
51
+ },
44
52
  });
45
53
  }),
46
54
  ),
@@ -29,7 +29,7 @@ export const STATE_STORE_SCRIPT_NAME = "alchemy-state-store" as const;
29
29
  * compare against this constant; a mismatch (or 404) triggers a
30
30
  * forced redeploy via the bootstrap flow.
31
31
  */
32
- export const STATE_STORE_VERSION = 4 as const;
32
+ export const STATE_STORE_VERSION = 5 as const;
33
33
 
34
34
  /**
35
35
  * Hard-coded OTLP/HTTP endpoints. Point at the public ingest relay