alchemy 2.0.0-beta.35 → 2.0.0-beta.37

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 (222) hide show
  1. package/bin/exec.js +176 -26
  2. package/bin/exec.js.map +1 -1
  3. package/lib/AWS/Lambda/Function.d.ts.map +1 -1
  4. package/lib/AWS/Lambda/Function.js +22 -2
  5. package/lib/AWS/Lambda/Function.js.map +1 -1
  6. package/lib/Apply.d.ts.map +1 -1
  7. package/lib/Apply.js +6 -1
  8. package/lib/Apply.js.map +1 -1
  9. package/lib/Cli/main.d.ts.map +1 -1
  10. package/lib/Cli/main.js +2 -0
  11. package/lib/Cli/main.js.map +1 -1
  12. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts +54 -0
  13. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts.map +1 -0
  14. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js +17 -0
  15. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js.map +1 -0
  16. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts +37 -0
  17. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts.map +1 -0
  18. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js +45 -0
  19. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js.map +1 -0
  20. package/lib/Cloudflare/AnalyticsEngine/index.d.ts +3 -0
  21. package/lib/Cloudflare/AnalyticsEngine/index.d.ts.map +1 -0
  22. package/lib/Cloudflare/AnalyticsEngine/index.js +3 -0
  23. package/lib/Cloudflare/AnalyticsEngine/index.js.map +1 -0
  24. package/lib/Cloudflare/D1/D1Connection.d.ts +47 -3
  25. package/lib/Cloudflare/D1/D1Connection.d.ts.map +1 -1
  26. package/lib/Cloudflare/D1/D1Connection.js +62 -5
  27. package/lib/Cloudflare/D1/D1Connection.js.map +1 -1
  28. package/lib/Cloudflare/Images/Images.d.ts +103 -0
  29. package/lib/Cloudflare/Images/Images.d.ts.map +1 -0
  30. package/lib/Cloudflare/Images/Images.js +82 -0
  31. package/lib/Cloudflare/Images/Images.js.map +1 -0
  32. package/lib/Cloudflare/Images/ImagesBinding.d.ts +73 -0
  33. package/lib/Cloudflare/Images/ImagesBinding.d.ts.map +1 -0
  34. package/lib/Cloudflare/Images/ImagesBinding.js +83 -0
  35. package/lib/Cloudflare/Images/ImagesBinding.js.map +1 -0
  36. package/lib/Cloudflare/Images/index.d.ts +3 -0
  37. package/lib/Cloudflare/Images/index.d.ts.map +1 -0
  38. package/lib/Cloudflare/Images/index.js +3 -0
  39. package/lib/Cloudflare/Images/index.js.map +1 -0
  40. package/lib/Cloudflare/Local/SidecarHandlers.js +9 -1
  41. package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -1
  42. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  43. package/lib/Cloudflare/Providers.js +6 -1
  44. package/lib/Cloudflare/Providers.js.map +1 -1
  45. package/lib/Cloudflare/R2/R2Bucket.d.ts +135 -0
  46. package/lib/Cloudflare/R2/R2Bucket.d.ts.map +1 -1
  47. package/lib/Cloudflare/R2/R2Bucket.js +146 -7
  48. package/lib/Cloudflare/R2/R2Bucket.js.map +1 -1
  49. package/lib/Cloudflare/StateStore/Api.d.ts +6 -1
  50. package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
  51. package/lib/Cloudflare/StateStore/Api.js +23 -1
  52. package/lib/Cloudflare/StateStore/Api.js.map +1 -1
  53. package/lib/Cloudflare/StateStore/Store.d.ts +16 -1
  54. package/lib/Cloudflare/StateStore/Store.d.ts.map +1 -1
  55. package/lib/Cloudflare/StateStore/Store.js +22 -5
  56. package/lib/Cloudflare/StateStore/Store.js.map +1 -1
  57. package/lib/Cloudflare/Workers/CronEventSource.d.ts +35 -0
  58. package/lib/Cloudflare/Workers/CronEventSource.d.ts.map +1 -0
  59. package/lib/Cloudflare/Workers/CronEventSource.js +55 -0
  60. package/lib/Cloudflare/Workers/CronEventSource.js.map +1 -0
  61. package/lib/Cloudflare/Workers/InferEnv.d.ts +37 -2
  62. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  63. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  64. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +2 -1
  65. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  66. package/lib/Cloudflare/Workers/Rpc.d.ts +76 -3
  67. package/lib/Cloudflare/Workers/Rpc.d.ts.map +1 -1
  68. package/lib/Cloudflare/Workers/Rpc.js +197 -13
  69. package/lib/Cloudflare/Workers/Rpc.js.map +1 -1
  70. package/lib/Cloudflare/Workers/Worker.d.ts +26 -6
  71. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  72. package/lib/Cloudflare/Workers/Worker.js +183 -41
  73. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  74. package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
  75. package/lib/Cloudflare/Workers/WorkerBundle.js +15 -5
  76. package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
  77. package/lib/Cloudflare/Workers/Workflow.d.ts +50 -47
  78. package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
  79. package/lib/Cloudflare/Workers/Workflow.js +41 -35
  80. package/lib/Cloudflare/Workers/Workflow.js.map +1 -1
  81. package/lib/Cloudflare/Workers/index.d.ts +1 -0
  82. package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
  83. package/lib/Cloudflare/Workers/index.js +1 -0
  84. package/lib/Cloudflare/Workers/index.js.map +1 -1
  85. package/lib/Cloudflare/index.d.ts +2 -0
  86. package/lib/Cloudflare/index.d.ts.map +1 -1
  87. package/lib/Cloudflare/index.js +2 -0
  88. package/lib/Cloudflare/index.js.map +1 -1
  89. package/lib/Deploy.d.ts +4 -1
  90. package/lib/Deploy.d.ts.map +1 -1
  91. package/lib/Deploy.js +2 -2
  92. package/lib/Deploy.js.map +1 -1
  93. package/lib/Destroy.d.ts +4 -1
  94. package/lib/Destroy.d.ts.map +1 -1
  95. package/lib/Destroy.js +2 -2
  96. package/lib/Destroy.js.map +1 -1
  97. package/lib/Output.d.ts +45 -5
  98. package/lib/Output.d.ts.map +1 -1
  99. package/lib/Output.js +100 -4
  100. package/lib/Output.js.map +1 -1
  101. package/lib/Plan.d.ts.map +1 -1
  102. package/lib/Plan.js +93 -12
  103. package/lib/Plan.js.map +1 -1
  104. package/lib/Platform.d.ts.map +1 -1
  105. package/lib/Platform.js +16 -3
  106. package/lib/Platform.js.map +1 -1
  107. package/lib/Ref.d.ts +4 -1
  108. package/lib/Ref.d.ts.map +1 -1
  109. package/lib/Ref.js +1 -1
  110. package/lib/Ref.js.map +1 -1
  111. package/lib/Resource.d.ts +8 -0
  112. package/lib/Resource.d.ts.map +1 -1
  113. package/lib/Resource.js +10 -0
  114. package/lib/Resource.js.map +1 -1
  115. package/lib/RuntimeContext.d.ts +3 -1
  116. package/lib/RuntimeContext.d.ts.map +1 -1
  117. package/lib/RuntimeContext.js.map +1 -1
  118. package/lib/Secret.d.ts +50 -0
  119. package/lib/Secret.d.ts.map +1 -0
  120. package/lib/Secret.js +23 -0
  121. package/lib/Secret.js.map +1 -0
  122. package/lib/Serverless/Function.d.ts +9 -1
  123. package/lib/Serverless/Function.d.ts.map +1 -1
  124. package/lib/Stack.d.ts +14 -5
  125. package/lib/Stack.d.ts.map +1 -1
  126. package/lib/Stack.js +31 -19
  127. package/lib/Stack.js.map +1 -1
  128. package/lib/State/HttpStateApi.d.ts +15 -1
  129. package/lib/State/HttpStateApi.d.ts.map +1 -1
  130. package/lib/State/HttpStateApi.js +11 -0
  131. package/lib/State/HttpStateApi.js.map +1 -1
  132. package/lib/State/HttpStateStore.d.ts.map +1 -1
  133. package/lib/State/HttpStateStore.js +11 -0
  134. package/lib/State/HttpStateStore.js.map +1 -1
  135. package/lib/State/InMemoryState.d.ts +2 -2
  136. package/lib/State/InMemoryState.d.ts.map +1 -1
  137. package/lib/State/InMemoryState.js +12 -6
  138. package/lib/State/InMemoryState.js.map +1 -1
  139. package/lib/State/LocalState.d.ts.map +1 -1
  140. package/lib/State/LocalState.js +11 -2
  141. package/lib/State/LocalState.js.map +1 -1
  142. package/lib/State/State.d.ts +21 -0
  143. package/lib/State/State.d.ts.map +1 -1
  144. package/lib/Test/Bun.d.ts.map +1 -1
  145. package/lib/Test/Bun.js +30 -7
  146. package/lib/Test/Bun.js.map +1 -1
  147. package/lib/Test/Core.d.ts +17 -2
  148. package/lib/Test/Core.d.ts.map +1 -1
  149. package/lib/Test/Core.js +29 -11
  150. package/lib/Test/Core.js.map +1 -1
  151. package/lib/Test/Vitest.d.ts.map +1 -1
  152. package/lib/Test/Vitest.js +26 -5
  153. package/lib/Test/Vitest.js.map +1 -1
  154. package/lib/Util/LocalhostDns.d.ts +23 -0
  155. package/lib/Util/LocalhostDns.d.ts.map +1 -0
  156. package/lib/Util/LocalhostDns.js +46 -0
  157. package/lib/Util/LocalhostDns.js.map +1 -0
  158. package/lib/Util/PlatformServices.d.ts.map +1 -1
  159. package/lib/Util/PlatformServices.js +2 -0
  160. package/lib/Util/PlatformServices.js.map +1 -1
  161. package/lib/Util/effect.d.ts +7 -0
  162. package/lib/Util/effect.d.ts.map +1 -1
  163. package/lib/Util/effect.js +6 -0
  164. package/lib/Util/effect.js.map +1 -1
  165. package/lib/Variable.d.ts +29 -0
  166. package/lib/Variable.d.ts.map +1 -0
  167. package/lib/Variable.js +9 -0
  168. package/lib/Variable.js.map +1 -0
  169. package/lib/index.d.ts +3 -0
  170. package/lib/index.d.ts.map +1 -1
  171. package/lib/index.js +3 -0
  172. package/lib/index.js.map +1 -1
  173. package/lib/tsconfig.test.tsbuildinfo +1 -1
  174. package/package.json +7 -7
  175. package/src/AWS/Lambda/Function.ts +30 -2
  176. package/src/Apply.ts +8 -1
  177. package/src/Cli/main.ts +3 -0
  178. package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.ts +82 -0
  179. package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.ts +106 -0
  180. package/src/Cloudflare/AnalyticsEngine/index.ts +2 -0
  181. package/src/Cloudflare/D1/D1Connection.ts +95 -12
  182. package/src/Cloudflare/Images/Images.ts +121 -0
  183. package/src/Cloudflare/Images/ImagesBinding.ts +192 -0
  184. package/src/Cloudflare/Images/index.ts +2 -0
  185. package/src/Cloudflare/Local/SidecarHandlers.ts +9 -1
  186. package/src/Cloudflare/Providers.ts +8 -0
  187. package/src/Cloudflare/R2/R2Bucket.ts +271 -7
  188. package/src/Cloudflare/StateStore/Api.ts +34 -1
  189. package/src/Cloudflare/StateStore/Store.ts +40 -10
  190. package/src/Cloudflare/Workers/CronEventSource.ts +89 -0
  191. package/src/Cloudflare/Workers/InferEnv.ts +94 -16
  192. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +9 -1
  193. package/src/Cloudflare/Workers/Rpc.ts +227 -18
  194. package/src/Cloudflare/Workers/Worker.ts +295 -62
  195. package/src/Cloudflare/Workers/WorkerBundle.ts +15 -5
  196. package/src/Cloudflare/Workers/Workflow.ts +74 -70
  197. package/src/Cloudflare/Workers/index.ts +1 -0
  198. package/src/Cloudflare/index.ts +2 -0
  199. package/src/Deploy.ts +5 -1
  200. package/src/Destroy.ts +5 -1
  201. package/src/Output.ts +121 -12
  202. package/src/Plan.ts +101 -12
  203. package/src/Platform.ts +19 -4
  204. package/src/Ref.ts +10 -5
  205. package/src/Resource.ts +22 -0
  206. package/src/RuntimeContext.ts +1 -0
  207. package/src/Secret.ts +87 -0
  208. package/src/Serverless/Function.ts +10 -1
  209. package/src/Stack.ts +52 -29
  210. package/src/State/HttpStateApi.ts +21 -0
  211. package/src/State/HttpStateStore.ts +21 -0
  212. package/src/State/InMemoryState.ts +29 -8
  213. package/src/State/LocalState.ts +28 -4
  214. package/src/State/State.ts +21 -0
  215. package/src/Test/Bun.ts +43 -7
  216. package/src/Test/Core.ts +29 -8
  217. package/src/Test/Vitest.ts +36 -4
  218. package/src/Util/LocalhostDns.ts +52 -0
  219. package/src/Util/PlatformServices.ts +2 -0
  220. package/src/Util/effect.ts +25 -0
  221. package/src/Variable.ts +44 -0
  222. package/src/index.ts +3 -0
@@ -9,17 +9,18 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
9
9
  import cloudflareVite from "@distilled.cloud/cloudflare-vite-plugin";
10
10
  import * as workers from "@distilled.cloud/cloudflare/workers";
11
11
  import * as zones from "@distilled.cloud/cloudflare/zones";
12
+ import * as Cause from "effect/Cause";
12
13
  import * as Context from "effect/Context";
13
14
  import * as Data from "effect/Data";
14
15
  import * as Effect from "effect/Effect";
15
16
  import * as Exit from "effect/Exit";
16
- import * as FileSystem from "effect/FileSystem";
17
17
  import * as Layer from "effect/Layer";
18
18
  import * as Option from "effect/Option";
19
19
  import * as Path from "effect/Path";
20
20
  import * as Redacted from "effect/Redacted";
21
21
  import * as Schedule from "effect/Schedule";
22
22
  import * as Scope from "effect/Scope";
23
+ import * as Stream from "effect/Stream";
23
24
  import { createRequire } from "node:module";
24
25
  import path from "node:path";
25
26
  import { pathToFileURL } from "node:url";
@@ -32,14 +33,17 @@ import { isResolved } from "../../Diff.js";
32
33
  import { ExecutionContext } from "../../ExecutionContext.js";
33
34
  import * as Output from "../../Output.js";
34
35
  import { Platform, } from "../../Platform.js";
36
+ import { isYieldableEffectLike } from "../../Util/effect.js";
35
37
  import * as Provider from "../../Provider.js";
36
38
  import { Resource } from "../../Resource.js";
37
39
  import * as Serverless from "../../Serverless/index.js";
38
40
  import { Stack } from "../../Stack.js";
41
+ import { isAnalyticsEngineDataset, } from "../AnalyticsEngine/AnalyticsEngineDataset.js";
39
42
  import { isArtifacts as isArtifactsBinding, } from "../Artifacts/Artifacts.js";
40
43
  import { CloudflareEnvironment } from "../CloudflareEnvironment.js";
41
44
  import { D1Database } from "../D1/D1Database.js";
42
45
  import { fromCloudflareFetcher } from "../Fetcher.js";
46
+ import { isImages as isImagesBinding, } from "../Images/Images.js";
43
47
  import { SidecarLive } from "../Local/Sidecar.js";
44
48
  import { CloudflareLogs } from "../Logs.js";
45
49
  import { isAssets, readAssets, uploadAssets, } from "./Assets.js";
@@ -48,7 +52,7 @@ import { isDurableObjectExport, isDurableObjectNamespaceLike, } from "./DurableO
48
52
  import { workersHttpHandler } from "./HttpServer.js";
49
53
  import { LocalWorkerProvider } from "./LocalWorkerProvider.js";
50
54
  import { Request } from "./Request.js";
51
- import { makeRpcStub } from "./Rpc.js";
55
+ import { encodeRpcError, ErrorTag, makeRpcStub, toRpcStream, } from "./Rpc.js";
52
56
  import { WorkerBundle } from "./WorkerBundle.js";
53
57
  import { createWorkerName } from "./WorkerName.js";
54
58
  const WorkerTypeId = "Cloudflare.Worker";
@@ -434,8 +438,11 @@ export const Worker = Platform(WorkerTypeId, {
434
438
  for (const bindingName in props.bindings) {
435
439
  // @ts-expect-error
436
440
  const bindingEff = props.bindings?.[bindingName];
437
- const binding = Effect.isEffect(bindingEff)
438
- ? yield* bindingEff
441
+ // Bindings can be passed as a plain resource value, an Effect that
442
+ // yields a resource, or an effect-class (e.g. a `Cloudflare.Worker`
443
+ // class). Resolve the yieldable forms before deriving binding metadata.
444
+ const binding = isYieldableEffectLike(bindingEff)
445
+ ? (yield* bindingEff)
439
446
  : bindingEff;
440
447
  const bindingMeta = isAssets(binding)
441
448
  ? {
@@ -448,44 +455,69 @@ export const Worker = Platform(WorkerTypeId, {
448
455
  name: bindingName,
449
456
  namespace: binding.namespace,
450
457
  }
451
- : isDurableObjectNamespaceLike(binding)
458
+ : isImagesBinding(binding)
452
459
  ? {
453
- type: "durable_object_namespace",
460
+ type: "images",
454
461
  name: bindingName,
455
- className: binding.className ?? binding.name,
456
462
  }
457
- : binding.Type === "Cloudflare.D1Database"
463
+ : isAnalyticsEngineDataset(binding)
458
464
  ? {
459
- type: "d1",
460
- id: binding.databaseId,
465
+ type: "analytics_engine",
461
466
  name: bindingName,
467
+ dataset: binding.dataset,
462
468
  }
463
- : binding.Type === "Cloudflare.R2Bucket"
469
+ : isDurableObjectNamespaceLike(binding)
464
470
  ? {
465
- type: "r2_bucket",
471
+ type: "durable_object_namespace",
466
472
  name: bindingName,
467
- bucketName: binding.bucketName,
468
- jurisdiction: binding.jurisdiction.pipe(Output.map((jurisdiction) => jurisdiction === "default" ? undefined : jurisdiction)),
473
+ className: binding.className ?? binding.name,
469
474
  }
470
- : binding.Type === "Cloudflare.KVNamespace"
475
+ : binding.Type === "Cloudflare.D1Database"
471
476
  ? {
472
- type: "kv_namespace",
477
+ type: "d1",
478
+ id: binding.databaseId,
473
479
  name: bindingName,
474
- namespaceId: binding.namespaceId,
475
480
  }
476
- : binding.Type === "Cloudflare.Queue"
481
+ : binding.Type === "Cloudflare.R2Bucket"
477
482
  ? {
478
- type: "queue",
483
+ type: "r2_bucket",
479
484
  name: bindingName,
480
- queueName: binding.queueName,
485
+ bucketName: binding.bucketName,
486
+ jurisdiction: binding.jurisdiction.pipe(Output.map((jurisdiction) => jurisdiction === "default"
487
+ ? undefined
488
+ : jurisdiction)),
481
489
  }
482
- : binding.Type === "Cloudflare.AiGateway"
490
+ : binding.Type === "Cloudflare.KVNamespace"
483
491
  ? {
484
- type: "ai",
492
+ type: "kv_namespace",
485
493
  name: bindingName,
494
+ namespaceId: binding.namespaceId,
486
495
  }
487
- : // TODO(sam): handle others
488
- undefined;
496
+ : binding.Type === "Cloudflare.Queue"
497
+ ? {
498
+ type: "queue",
499
+ name: bindingName,
500
+ queueName: binding.queueName,
501
+ }
502
+ : binding.Type === "Cloudflare.AiGateway"
503
+ ? {
504
+ type: "ai",
505
+ name: bindingName,
506
+ }
507
+ : binding.Type === "Cloudflare.Hyperdrive"
508
+ ? {
509
+ type: "hyperdrive",
510
+ name: bindingName,
511
+ id: binding.hyperdriveId,
512
+ }
513
+ : isWorker(binding)
514
+ ? {
515
+ type: "service",
516
+ name: bindingName,
517
+ service: binding.workerName,
518
+ }
519
+ : // TODO(sam): handle others
520
+ undefined;
489
521
  if (bindingMeta) {
490
522
  yield* resource.bind `${bindingName}`({
491
523
  bindings: [bindingMeta],
@@ -501,6 +533,7 @@ export const Worker = Platform(WorkerTypeId, {
501
533
  const listeners = [];
502
534
  const exports = {};
503
535
  const env = {};
536
+ let userShape;
504
537
  const ctx = {
505
538
  Type: WorkerTypeId,
506
539
  id,
@@ -546,7 +579,14 @@ export const Worker = Platform(WorkerTypeId, {
546
579
  : JSON.stringify(value)));
547
580
  return key;
548
581
  }),
549
- serve: (handler) => ctx.listen(workersHttpHandler(handler)),
582
+ serve: (handler, options) => {
583
+ // Capture the user's full default-export shape so `exports` can
584
+ // expose any non-handler methods on it as RPC methods on the
585
+ // deployed `WorkerEntrypoint` subclass — see `__rpc__` below.
586
+ if (options?.shape)
587
+ userShape = options.shape;
588
+ return ctx.listen(workersHttpHandler(handler));
589
+ },
550
590
  listen: ((handler) => Effect.sync(() => Effect.isEffect(handler)
551
591
  ? listeners.push(handler)
552
592
  : listeners.push(Effect.succeed(handler)))),
@@ -558,6 +598,18 @@ export const Worker = Platform(WorkerTypeId, {
558
598
  concurrency: "unbounded",
559
599
  });
560
600
  const services = yield* Effect.context();
601
+ // Provide the per-request runtime layer (services + `WorkerExecutionContext`
602
+ // + a fresh `ExecutionContext`/`Scope`) to a user effect, then run it.
603
+ // This is the bridge between Cloudflare's request-time callback and
604
+ // the user's Effect-native handler. Used by both the standard
605
+ // `handle()` dispatch (fetch/scheduled/…) and the per-RPC dispatchers
606
+ // below — keeping them on a single layer-provision path.
607
+ const runUserEffect = (eff, context) => {
608
+ const scope = Scope.makeUnsafe();
609
+ return eff
610
+ .pipe(Effect.provideContext(services), Scope.provide(scope), Effect.provide(Layer.succeed(WorkerExecutionContext, context)), Effect.provide(Layer.succeed(ExecutionContext, { scope, cache: {} })), Effect.runPromiseExit)
611
+ .finally(() => context.waitUntil(Effect.runPromise(Scope.close(scope, Exit.void))));
612
+ };
561
613
  const handle = (type) => (request, env, context) => {
562
614
  const event = {
563
615
  kind: "Cloudflare.Workers.WorkerEvent",
@@ -569,20 +621,60 @@ export const Worker = Platform(WorkerTypeId, {
569
621
  for (const handler of handlers) {
570
622
  const eff = handler(event);
571
623
  if (Effect.isEffect(eff)) {
572
- const scope = Scope.makeUnsafe();
573
- return eff
574
- .pipe(Effect.provideContext(services), Scope.provide(scope), Effect.provide(Layer.succeed(WorkerExecutionContext, context)), Effect.provide(Layer.succeed(ExecutionContext, {
575
- scope,
576
- cache: {},
577
- })), Effect.runPromise)
578
- .finally(() => context.waitUntil(Effect.runPromise(Scope.close(scope, Exit.void))));
624
+ return runUserEffect(eff, context).then((exit) => {
625
+ if (exit._tag === "Success")
626
+ return exit.value;
627
+ throw Cause.squash(exit.cause);
628
+ });
579
629
  }
580
630
  }
581
631
  return Promise.reject(new Error("No event handler found"));
582
632
  };
633
+ // RPC method dispatchers — one per non-handler method on the user's
634
+ // shape. Each dispatcher is invoked by the WorkerEntrypoint bridge
635
+ // as `dispatcher(args, ctx)`: `args` are the user-facing call args,
636
+ // `ctx` is the `this.ctx` that Cloudflare hands the bridge per RPC
637
+ // request. The dispatcher runs the user effect with the same runtime
638
+ // layer the fetch path uses, then envelope-encodes the result so
639
+ // `Effect.fail` round-trips as `RpcErrorEnvelope` and `Stream` as
640
+ // `RpcStreamEnvelope` (consumers wrap the binding with
641
+ // `toPromiseApi`/`bindWorker` to decode).
642
+ const exportedHandlerSet = new Set(ExportedHandlerMethods);
643
+ const rpcDispatchers = {};
644
+ if (userShape) {
645
+ for (const [name, value] of Object.entries(userShape)) {
646
+ if (exportedHandlerSet.has(name))
647
+ continue;
648
+ if (typeof value !== "function")
649
+ continue;
650
+ rpcDispatchers[name] = async (args, context) => {
651
+ const result = value(...args);
652
+ if (!Effect.isEffect(result))
653
+ return result;
654
+ const exit = await runUserEffect(result, context);
655
+ if (exit._tag === "Success") {
656
+ if (Stream.isStream(exit.value)) {
657
+ return await Effect.runPromise(toRpcStream(exit.value));
658
+ }
659
+ return exit.value;
660
+ }
661
+ const failReason = exit.cause.reasons.find(Cause.isFailReason);
662
+ if (failReason) {
663
+ return {
664
+ _tag: ErrorTag,
665
+ error: encodeRpcError(failReason.error),
666
+ };
667
+ }
668
+ const dieReason = exit.cause.reasons.find(Cause.isDieReason);
669
+ throw (dieReason?.defect ??
670
+ new Error("RPC method failed with an unexpected cause"));
671
+ };
672
+ }
673
+ }
583
674
  return {
584
675
  ...exports,
585
676
  default: Object.fromEntries(ExportedHandlerMethods.map((method) => [method, handle(method)])),
677
+ __rpc__: rpcDispatchers,
586
678
  };
587
679
  }),
588
680
  };
@@ -590,7 +682,10 @@ export const Worker = Platform(WorkerTypeId, {
590
682
  },
591
683
  });
592
684
  export const bindWorker = Effect.fnUntraced(function* (workerEff) {
593
- const worker = Effect.isEffect(workerEff) ? yield* workerEff : workerEff;
685
+ // Worker classes and regular Effects are both yieldable here.
686
+ const worker = isYieldableEffectLike(workerEff)
687
+ ? yield* workerEff
688
+ : workerEff;
594
689
  const self = yield* Worker;
595
690
  yield* self.bind `${worker}`({
596
691
  bindings: [
@@ -601,10 +696,12 @@ export const bindWorker = Effect.fnUntraced(function* (workerEff) {
601
696
  },
602
697
  ],
603
698
  });
604
- const workerBinding = WorkerEnvironment.asEffect().pipe(Effect.map((env) => env[worker.LogicalId]));
605
- const fetcher = workerBinding.pipe(Effect.map(fromCloudflareFetcher));
606
- // TODO(sam): update makeRpcStub to support lazily evaluating the Effect<Fetcher>
607
- return makeRpcStub(fetcher);
699
+ // `bindWorker` runs at *init* phase (both at plantime and at runtime
700
+ // cold-start). `WorkerEnvironment` only exists at exec phase on the
701
+ // deployed worker, so we hand `makeRpcStub` an `Effect<stub>` that
702
+ // resolves the binding lazily on each method call.
703
+ const stubEff = WorkerEnvironment.asEffect().pipe(Effect.map((env) => env[worker.LogicalId]));
704
+ return makeRpcStub(stubEff);
608
705
  });
609
706
  export class BindWorkerPolicy extends Binding.Policy()("Cloudflare.Worker.Bind") {
610
707
  }
@@ -650,6 +747,9 @@ function getDurableObjectBindings(bindings, workerName) {
650
747
  ]
651
748
  : []));
652
749
  }
750
+ export function getCronBindings(bindings) {
751
+ return Array.from(new Set(bindings.flatMap((b) => b.data.crons ?? [])));
752
+ }
653
753
  function getDurableObjectTagMap(tags) {
654
754
  return Object.fromEntries(tags.flatMap((tag) => {
655
755
  if (!tag.startsWith("alchemy:do:")) {
@@ -667,18 +767,18 @@ export const WorkerProvider = () => selectLayer({
667
767
  dev: () => Layer.provide(LocalWorkerProvider(), SidecarLive),
668
768
  });
669
769
  export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(function* () {
670
- const fs = yield* FileSystem.FileSystem;
671
770
  const path = yield* Path.Path;
672
771
  const { accountId } = yield* CloudflareEnvironment;
673
- const virtualEntryPlugin = yield* Bundle.virtualEntryPlugin;
674
772
  const bundler = yield* WorkerBundle;
675
773
  const stack = yield* Stack;
676
774
  const createScriptSubdomain = yield* workers.createScriptSubdomain;
677
775
  const deleteScript = yield* workers.deleteScript;
678
776
  const getScriptSubdomain = yield* workers.getScriptSubdomain;
777
+ const getScriptSchedule = yield* workers.getScriptSchedule;
679
778
  const getScriptSettings = yield* workers.getScriptScriptAndVersionSetting;
680
779
  const getSubdomain = yield* workers.getSubdomain;
681
780
  const putScript = yield* workers.putScript;
781
+ const putScriptSchedule = yield* workers.putScriptSchedule;
682
782
  const putDomain = yield* workers.putDomain;
683
783
  const listDomains = yield* workers.listDomains;
684
784
  const deleteDomain = yield* workers.deleteDomain;
@@ -713,6 +813,32 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
713
813
  const normalizeDomains = (domain) => domain === undefined
714
814
  ? []
715
815
  : Array.from(new Set((Array.isArray(domain) ? domain : [domain]).map(toPunycode)));
816
+ const normalizeCrons = (crons) => Array.from(new Set(crons ?? []));
817
+ const getWorkerCrons = (scriptName) => getScriptSchedule({
818
+ accountId,
819
+ scriptName,
820
+ }).pipe(Effect.map((response) => normalizeCrons(response.schedules.map((schedule) => schedule.cron))), Effect.catchTag("WorkerNotFound", () => Effect.succeed([])));
821
+ const reconcileCrons = (scriptName, desired, previous, session) => Effect.gen(function* () {
822
+ const live = yield* getWorkerCrons(scriptName);
823
+ const desiredSorted = [...desired].sort();
824
+ const liveSorted = [...live].sort();
825
+ const changed = desiredSorted.length !== liveSorted.length ||
826
+ desiredSorted.some((cron, index) => cron !== liveSorted[index]);
827
+ if (!changed)
828
+ return live;
829
+ if (desired.length > 0 || previous.length > 0 || live.length > 0) {
830
+ yield* session.note(`Reconciling Cron Triggers (${desired.length}) ...`);
831
+ }
832
+ const result = yield* putScriptSchedule({
833
+ accountId,
834
+ scriptName,
835
+ body: desired.map((cron) => ({ cron })),
836
+ }).pipe(Effect.retry({
837
+ while: (error) => error?._tag === "WorkerNotFound",
838
+ schedule: Schedule.exponential(200).pipe(Schedule.both(Schedule.recurs(15))),
839
+ }));
840
+ return normalizeCrons(result.schedules.map((schedule) => schedule.cron));
841
+ });
716
842
  /**
717
843
  * Infer the Cloudflare Zone ID for a given hostname by listing the
718
844
  * account's zones and matching the hostname against each zone's name —
@@ -1304,6 +1430,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
1304
1430
  yield* session.note(`Reconciling custom domains (${desiredDomains.length}) ...`);
1305
1431
  }
1306
1432
  const domains = yield* reconcileDomains(name, desiredDomains, previousDomains);
1433
+ const crons = yield* reconcileCrons(name, normalizeCrons([...getCronBindings(bindings), ...(news.crons ?? [])]), output?.crons ?? [], session);
1307
1434
  return {
1308
1435
  workerId: worker.id ?? name,
1309
1436
  workerName: name,
@@ -1315,6 +1442,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
1315
1442
  durableObjectNamespaces,
1316
1443
  accountId,
1317
1444
  domains,
1445
+ crons,
1318
1446
  hash,
1319
1447
  };
1320
1448
  });
@@ -1361,7 +1489,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
1361
1489
  });
1362
1490
  return Worker.Provider.of({
1363
1491
  stables: ["workerId", "workerName"],
1364
- diff: Effect.fnUntraced(function* ({ id, news, olds, output }) {
1492
+ diff: Effect.fnUntraced(function* ({ id, news, olds, output, newBindings, }) {
1365
1493
  if (!isResolved(news))
1366
1494
  return undefined;
1367
1495
  if ((output?.accountId ?? accountId) !== accountId) {
@@ -1383,7 +1511,18 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
1383
1511
  .sort();
1384
1512
  const domainsChanged = newDomains.length !== oldDomains.length ||
1385
1513
  newDomains.some((d, i) => d !== oldDomains[i]);
1386
- if (domainsChanged || (yield* hasChanged(id, news, output))) {
1514
+ const newCrons = normalizeCrons([
1515
+ ...(Array.isArray(newBindings)
1516
+ ? getCronBindings(newBindings)
1517
+ : []),
1518
+ ...(news.crons ?? []),
1519
+ ]).sort();
1520
+ const oldCrons = [...(output?.crons ?? [])].sort();
1521
+ const cronsChanged = newCrons.length !== oldCrons.length ||
1522
+ newCrons.some((cron, index) => cron !== oldCrons[index]);
1523
+ if (domainsChanged ||
1524
+ cronsChanged ||
1525
+ (yield* hasChanged(id, news, output))) {
1387
1526
  return {
1388
1527
  action: "update",
1389
1528
  stables: oldWorkerName === workerName ? ["workerName"] : undefined,
@@ -1484,6 +1623,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
1484
1623
  durableObjectNamespaces,
1485
1624
  accountId,
1486
1625
  domains: [],
1626
+ crons: [],
1487
1627
  };
1488
1628
  }),
1489
1629
  read: Effect.fnUntraced(function* ({ id, output, olds }) {
@@ -1511,6 +1651,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
1511
1651
  service: workerName,
1512
1652
  }).pipe(Effect.map((r) => r.result ?? [])),
1513
1653
  ]);
1654
+ const crons = yield* getWorkerCrons(workerName);
1514
1655
  yield* Effect.logInfo(`Cloudflare Worker read: found ${workerName}`);
1515
1656
  const attrs = {
1516
1657
  accountId,
@@ -1525,6 +1666,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
1525
1666
  domains: domainsList.flatMap((d) => d.id && d.hostname && d.zoneId
1526
1667
  ? [{ id: d.id, hostname: d.hostname, zoneId: d.zoneId }]
1527
1668
  : []),
1669
+ crons,
1528
1670
  };
1529
1671
  // Centralized ownership decision: the engine routes `read`'s
1530
1672
  // return value based on `AdoptPolicy`. We hand it the attrs