alchemy 2.0.0-beta.40 → 2.0.0-beta.41

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 (179) hide show
  1. package/bin/exec.js +16 -3
  2. package/bin/exec.js.map +1 -1
  3. package/lib/AWS/EC2/hosted.js +1 -1
  4. package/lib/AWS/EC2/hosted.js.map +1 -1
  5. package/lib/AWS/ECS/Task.js +1 -1
  6. package/lib/AWS/ECS/Task.js.map +1 -1
  7. package/lib/AWS/Lambda/BucketEventSource.d.ts +1 -1
  8. package/lib/AWS/Lambda/BucketEventSource.d.ts.map +1 -1
  9. package/lib/AWS/Lambda/EventBridgeEventSource.d.ts +1 -1
  10. package/lib/AWS/Lambda/EventBridgeEventSource.d.ts.map +1 -1
  11. package/lib/AWS/Lambda/Function.js +1 -1
  12. package/lib/AWS/Lambda/Function.js.map +1 -1
  13. package/lib/AWS/Lambda/QueueEventSource.d.ts +1 -1
  14. package/lib/AWS/Lambda/QueueEventSource.d.ts.map +1 -1
  15. package/lib/AWS/Lambda/StreamEventSource.d.ts +1 -1
  16. package/lib/AWS/Lambda/StreamEventSource.d.ts.map +1 -1
  17. package/lib/AWS/Lambda/TableEventSource.d.ts +1 -1
  18. package/lib/AWS/Lambda/TableEventSource.d.ts.map +1 -1
  19. package/lib/AWS/Lambda/TopicEventSource.d.ts +1 -1
  20. package/lib/AWS/Lambda/TopicEventSource.d.ts.map +1 -1
  21. package/lib/Apply.d.ts.map +1 -1
  22. package/lib/Apply.js +41 -7
  23. package/lib/Apply.js.map +1 -1
  24. package/lib/Cloudflare/Bridge.d.ts +1 -0
  25. package/lib/Cloudflare/Bridge.d.ts.map +1 -1
  26. package/lib/Cloudflare/Bridge.js +1 -0
  27. package/lib/Cloudflare/Bridge.js.map +1 -1
  28. package/lib/Cloudflare/Container/ContainerApplication.js +1 -1
  29. package/lib/Cloudflare/Container/ContainerApplication.js.map +1 -1
  30. package/lib/Cloudflare/Container/ContainerBinding.d.ts +1 -1
  31. package/lib/Cloudflare/Container/ContainerBinding.d.ts.map +1 -1
  32. package/lib/Cloudflare/Container/ContainerBinding.js +1 -1
  33. package/lib/Cloudflare/Container/ContainerBinding.js.map +1 -1
  34. package/lib/Cloudflare/Fetcher.d.ts +9 -0
  35. package/lib/Cloudflare/Fetcher.d.ts.map +1 -1
  36. package/lib/Cloudflare/Fetcher.js +20 -1
  37. package/lib/Cloudflare/Fetcher.js.map +1 -1
  38. package/lib/Cloudflare/Local/SidecarHandlers.d.ts.map +1 -1
  39. package/lib/Cloudflare/Local/SidecarHandlers.js +1 -1
  40. package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -1
  41. package/lib/Cloudflare/StateStore/Api.d.ts +2 -2
  42. package/lib/Cloudflare/Website/StaticSite.d.ts +5 -4
  43. package/lib/Cloudflare/Website/StaticSite.d.ts.map +1 -1
  44. package/lib/Cloudflare/Website/StaticSite.js.map +1 -1
  45. package/lib/Cloudflare/Website/Vite.d.ts +4 -4
  46. package/lib/Cloudflare/Website/Vite.d.ts.map +1 -1
  47. package/lib/Cloudflare/Website/Vite.js.map +1 -1
  48. package/lib/Cloudflare/Workers/DurableObjectBridge.d.ts +17 -0
  49. package/lib/Cloudflare/Workers/DurableObjectBridge.d.ts.map +1 -0
  50. package/lib/Cloudflare/Workers/DurableObjectBridge.js +86 -0
  51. package/lib/Cloudflare/Workers/DurableObjectBridge.js.map +1 -0
  52. package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts +2 -1
  53. package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts.map +1 -1
  54. package/lib/Cloudflare/Workers/DurableObjectNamespace.js +10 -39
  55. package/lib/Cloudflare/Workers/DurableObjectNamespace.js.map +1 -1
  56. package/lib/Cloudflare/Workers/HttpServer.d.ts +2 -8
  57. package/lib/Cloudflare/Workers/HttpServer.d.ts.map +1 -1
  58. package/lib/Cloudflare/Workers/HttpServer.js +29 -33
  59. package/lib/Cloudflare/Workers/HttpServer.js.map +1 -1
  60. package/lib/Cloudflare/Workers/InferEnv.d.ts +5 -24
  61. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  62. package/lib/Cloudflare/Workers/Rpc.d.ts +0 -101
  63. package/lib/Cloudflare/Workers/Rpc.d.ts.map +1 -1
  64. package/lib/Cloudflare/Workers/Rpc.js +9 -286
  65. package/lib/Cloudflare/Workers/Rpc.js.map +1 -1
  66. package/lib/Cloudflare/Workers/RpcAsync.d.ts +8 -0
  67. package/lib/Cloudflare/Workers/RpcAsync.d.ts.map +1 -0
  68. package/lib/Cloudflare/Workers/RpcAsync.js +45 -0
  69. package/lib/Cloudflare/Workers/RpcAsync.js.map +1 -0
  70. package/lib/Cloudflare/Workers/Worker.d.ts +16 -162
  71. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  72. package/lib/Cloudflare/Workers/Worker.js +49 -354
  73. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  74. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts +8 -0
  75. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -0
  76. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +129 -0
  77. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -0
  78. package/lib/Cloudflare/Workers/WorkerBinding.d.ts +154 -0
  79. package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -0
  80. package/lib/Cloudflare/Workers/WorkerBinding.js +51 -0
  81. package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -0
  82. package/lib/Cloudflare/Workers/WorkerBridge.d.ts +35 -0
  83. package/lib/Cloudflare/Workers/WorkerBridge.d.ts.map +1 -0
  84. package/lib/Cloudflare/Workers/WorkerBridge.js +149 -0
  85. package/lib/Cloudflare/Workers/WorkerBridge.js.map +1 -0
  86. package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
  87. package/lib/Cloudflare/Workers/WorkerBundle.js +18 -84
  88. package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
  89. package/lib/Cloudflare/Workers/WorkerRuntimeContext.d.ts +8 -0
  90. package/lib/Cloudflare/Workers/WorkerRuntimeContext.d.ts.map +1 -0
  91. package/lib/Cloudflare/Workers/WorkerRuntimeContext.js +113 -0
  92. package/lib/Cloudflare/Workers/WorkerRuntimeContext.js.map +1 -0
  93. package/lib/Cloudflare/Workers/WorkflowBridge.d.ts +26 -0
  94. package/lib/Cloudflare/Workers/WorkflowBridge.d.ts.map +1 -0
  95. package/lib/Cloudflare/Workers/WorkflowBridge.js +41 -0
  96. package/lib/Cloudflare/Workers/WorkflowBridge.js.map +1 -0
  97. package/lib/Cloudflare/Workers/index.d.ts +4 -0
  98. package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
  99. package/lib/Cloudflare/Workers/index.js +4 -0
  100. package/lib/Cloudflare/Workers/index.js.map +1 -1
  101. package/lib/Cloudflare/index.d.ts +4 -2
  102. package/lib/Cloudflare/index.d.ts.map +1 -1
  103. package/lib/Cloudflare/index.js +4 -2
  104. package/lib/Cloudflare/index.js.map +1 -1
  105. package/lib/Neon/Project.d.ts.map +1 -1
  106. package/lib/Neon/Project.js +2 -1
  107. package/lib/Neon/Project.js.map +1 -1
  108. package/lib/Platform.d.ts +3 -4
  109. package/lib/Platform.d.ts.map +1 -1
  110. package/lib/Platform.js.map +1 -1
  111. package/lib/Rpc.d.ts +17 -0
  112. package/lib/Rpc.d.ts.map +1 -0
  113. package/lib/Rpc.js +1 -0
  114. package/lib/Rpc.js.map +1 -0
  115. package/lib/RuntimeContext.d.ts +1 -0
  116. package/lib/RuntimeContext.d.ts.map +1 -1
  117. package/lib/RuntimeContext.js.map +1 -1
  118. package/lib/Sidecar/RpcHandler.d.ts.map +1 -1
  119. package/lib/Sidecar/RpcHandler.js +13 -9
  120. package/lib/Sidecar/RpcHandler.js.map +1 -1
  121. package/lib/State/HttpStateApi.d.ts +10 -0
  122. package/lib/State/HttpStateApi.d.ts.map +1 -1
  123. package/lib/State/HttpStateApi.js +10 -0
  124. package/lib/State/HttpStateApi.js.map +1 -1
  125. package/lib/State/HttpStateStore.d.ts.map +1 -1
  126. package/lib/State/HttpStateStore.js +2 -1
  127. package/lib/State/HttpStateStore.js.map +1 -1
  128. package/lib/State/InMemoryState.d.ts.map +1 -1
  129. package/lib/State/InMemoryState.js +2 -0
  130. package/lib/State/InMemoryState.js.map +1 -1
  131. package/lib/State/LocalState.d.ts.map +1 -1
  132. package/lib/State/LocalState.js +2 -0
  133. package/lib/State/LocalState.js.map +1 -1
  134. package/lib/State/State.d.ts +8 -0
  135. package/lib/State/State.d.ts.map +1 -1
  136. package/lib/State/StateEncoding.d.ts.map +1 -1
  137. package/lib/State/StateEncoding.js +5 -1
  138. package/lib/State/StateEncoding.js.map +1 -1
  139. package/lib/index.d.ts +2 -1
  140. package/lib/index.d.ts.map +1 -1
  141. package/lib/index.js +1 -1
  142. package/lib/index.js.map +1 -1
  143. package/lib/tsconfig.test.tsbuildinfo +1 -1
  144. package/package.json +1 -1
  145. package/src/Apply.ts +44 -10
  146. package/src/Cli/Event.ts +1 -1
  147. package/src/Cloudflare/Bridge.ts +1 -0
  148. package/src/Cloudflare/Container/ContainerBinding.ts +1 -1
  149. package/src/Cloudflare/Fetcher.ts +38 -1
  150. package/src/Cloudflare/Local/SidecarHandlers.ts +3 -6
  151. package/src/Cloudflare/Website/StaticSite.ts +10 -5
  152. package/src/Cloudflare/Website/Vite.ts +7 -4
  153. package/src/Cloudflare/Workers/DurableObjectBridge.ts +180 -0
  154. package/src/Cloudflare/Workers/DurableObjectNamespace.ts +22 -70
  155. package/src/Cloudflare/Workers/HttpServer.ts +25 -39
  156. package/src/Cloudflare/Workers/InferEnv.ts +42 -78
  157. package/src/Cloudflare/Workers/Rpc.ts +18 -417
  158. package/src/Cloudflare/Workers/RpcAsync.ts +63 -0
  159. package/src/Cloudflare/Workers/Worker.ts +107 -548
  160. package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +149 -0
  161. package/src/Cloudflare/Workers/WorkerBinding.ts +103 -0
  162. package/src/Cloudflare/Workers/WorkerBridge.ts +304 -0
  163. package/src/Cloudflare/Workers/WorkerBundle.ts +17 -83
  164. package/src/Cloudflare/Workers/WorkerRuntimeContext.ts +164 -0
  165. package/src/Cloudflare/Workers/WorkflowBridge.ts +92 -0
  166. package/src/Cloudflare/Workers/index.ts +4 -0
  167. package/src/Cloudflare/index.ts +4 -2
  168. package/src/Neon/Project.ts +3 -1
  169. package/src/Platform.ts +3 -4
  170. package/src/Rpc.ts +25 -0
  171. package/src/RuntimeContext.ts +1 -0
  172. package/src/Sidecar/RpcHandler.ts +18 -9
  173. package/src/State/HttpStateApi.ts +11 -0
  174. package/src/State/HttpStateStore.ts +7 -2
  175. package/src/State/InMemoryState.ts +2 -0
  176. package/src/State/LocalState.ts +2 -0
  177. package/src/State/State.ts +8 -0
  178. package/src/State/StateEncoding.ts +8 -1
  179. package/src/index.ts +7 -6
@@ -0,0 +1,149 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Redacted from "effect/Redacted";
3
+ import type { InputProps } from "../../Input.ts";
4
+ import * as Output from "../../Output.ts";
5
+ import type { ResourceBinding } from "../../Resource.ts";
6
+ import { isYieldableEffectLike } from "../../Util/effect.ts";
7
+ import { isAnalyticsEngineDataset } from "../AnalyticsEngine/AnalyticsEngineDataset.ts";
8
+ import { isArtifacts } from "../Artifacts/Artifacts.ts";
9
+ import { isSendEmail } from "../Email/SendEmail.ts";
10
+ import { isImages } from "../Images/Images.ts";
11
+ import { isAssets } from "./Assets.ts";
12
+ import { isDurableObjectNamespaceLike } from "./DurableObjectNamespace.ts";
13
+ import type { WorkerBindingProps } from "./Worker.ts";
14
+ import { isWorker, type Worker, type WorkerProps } from "./Worker.ts";
15
+ import type { WorkerBinding, WorkerBindingResource } from "./WorkerBinding.ts";
16
+
17
+ export const bindWorkerAsyncBindings = Effect.fnUntraced(function* (
18
+ resource: Worker,
19
+ props: InputProps<WorkerProps<WorkerBindingProps>>,
20
+ ) {
21
+ if (props.bindings) {
22
+ for (const bindingName in props.bindings) {
23
+ // @ts-expect-error
24
+ const bindingEff = props.bindings?.[bindingName] as
25
+ | WorkerBindingResource
26
+ | Effect.Effect<WorkerBindingResource>;
27
+ // Bindings can be passed as a plain resource value, an Effect that
28
+ // yields a resource, or an effect-class (e.g. a `Cloudflare.Worker`
29
+ // class). Resolve the yieldable forms before deriving binding metadata.
30
+ const binding = isYieldableEffectLike(bindingEff)
31
+ ? ((yield* bindingEff as Effect.Effect<unknown>) as WorkerBindingResource)
32
+ : bindingEff;
33
+
34
+ const bindingMeta: InputProps<WorkerBinding> | undefined =
35
+ typeof binding === "string"
36
+ ? {
37
+ type: "plain_text",
38
+ name: bindingName,
39
+ value: binding,
40
+ }
41
+ : Redacted.isRedacted(binding)
42
+ ? {
43
+ type: "secret_text",
44
+ name: bindingName,
45
+ value: Redacted.value(binding),
46
+ }
47
+ : isAssets(binding)
48
+ ? {
49
+ type: "assets",
50
+ name: bindingName,
51
+ }
52
+ : isArtifacts(binding)
53
+ ? ({
54
+ type: "artifacts",
55
+ name: bindingName,
56
+ namespace: binding.namespace,
57
+ } as any)
58
+ : isImages(binding)
59
+ ? {
60
+ type: "images",
61
+ name: bindingName,
62
+ }
63
+ : isAnalyticsEngineDataset(binding)
64
+ ? {
65
+ type: "analytics_engine",
66
+ name: bindingName,
67
+ dataset: binding.dataset,
68
+ }
69
+ : isSendEmail(binding)
70
+ ? {
71
+ type: "send_email",
72
+ name: bindingName,
73
+ destinationAddress: binding.destinationAddress,
74
+ allowedDestinationAddresses:
75
+ binding.allowedDestinationAddresses,
76
+ allowedSenderAddresses:
77
+ binding.allowedSenderAddresses,
78
+ }
79
+ : isDurableObjectNamespaceLike(binding)
80
+ ? {
81
+ type: "durable_object_namespace",
82
+ name: bindingName,
83
+ className: binding.className ?? binding.name,
84
+ }
85
+ : binding.Type === "Cloudflare.D1Database"
86
+ ? {
87
+ type: "d1",
88
+ id: binding.databaseId,
89
+ name: bindingName,
90
+ }
91
+ : binding.Type === "Cloudflare.R2Bucket"
92
+ ? {
93
+ type: "r2_bucket",
94
+ name: bindingName,
95
+ bucketName: binding.bucketName,
96
+ jurisdiction: binding.jurisdiction.pipe(
97
+ Output.map((jurisdiction) =>
98
+ jurisdiction === "default"
99
+ ? undefined
100
+ : jurisdiction,
101
+ ),
102
+ ),
103
+ }
104
+ : binding.Type === "Cloudflare.KVNamespace"
105
+ ? {
106
+ type: "kv_namespace",
107
+ name: bindingName,
108
+ namespaceId: binding.namespaceId,
109
+ }
110
+ : binding.Type === "Cloudflare.Queue"
111
+ ? {
112
+ type: "queue",
113
+ name: bindingName,
114
+ queueName: binding.queueName,
115
+ }
116
+ : binding.Type === "Cloudflare.AiGateway"
117
+ ? {
118
+ type: "ai",
119
+ name: bindingName,
120
+ }
121
+ : binding.Type === "Cloudflare.Hyperdrive"
122
+ ? {
123
+ type: "hyperdrive",
124
+ name: bindingName,
125
+ id: binding.hyperdriveId,
126
+ }
127
+ : isWorker(binding)
128
+ ? {
129
+ type: "service",
130
+ name: bindingName,
131
+ service: binding.workerName,
132
+ }
133
+ : // TODO(sam): handle others
134
+ undefined;
135
+
136
+ if (bindingMeta) {
137
+ yield* resource.bind`${bindingName}`({
138
+ bindings: [bindingMeta],
139
+ });
140
+ } else {
141
+ return yield* Effect.die(`Unknown binding type: ${bindingName}`);
142
+ }
143
+ }
144
+ }
145
+ });
146
+
147
+ export const getCronBindings = (
148
+ bindings: ReadonlyArray<ResourceBinding<Worker["Binding"]>>,
149
+ ) => Array.from(new Set(bindings.flatMap((b) => b.data.crons ?? [])));
@@ -0,0 +1,103 @@
1
+ import type * as workers from "@distilled.cloud/cloudflare/workers";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Binding from "../../Binding.ts";
4
+ import type { Rpc } from "../../Rpc.ts";
5
+ import { isYieldableEffectLike } from "../../Util/effect.ts";
6
+ import type { AiGateway } from "../AiGateway/AiGateway.ts";
7
+ import { AnalyticsEngineDataset } from "../AnalyticsEngine/AnalyticsEngineDataset.ts";
8
+ import { Artifacts } from "../Artifacts/Artifacts.ts";
9
+ import type { D1Database } from "../D1/D1Database.ts";
10
+ import { SendEmail } from "../Email/SendEmail.ts";
11
+ import { Hyperdrive } from "../Hyperdrive/Hyperdrive.ts";
12
+ import { Images } from "../Images/Images.ts";
13
+ import type { KVNamespace } from "../KV/KVNamespace.ts";
14
+ import type { Queue } from "../Queue/Queue.ts";
15
+ import type { R2Bucket } from "../R2/R2Bucket.ts";
16
+ import type { Assets } from "./Assets.ts";
17
+ import type { DurableObjectNamespaceLike } from "./DurableObjectNamespace.ts";
18
+ import { makeRpcStub } from "./Rpc.ts";
19
+ import { isWorker, Worker, WorkerEnvironment } from "./Worker.ts";
20
+
21
+ export type WorkerBinding = Exclude<
22
+ workers.PutScriptRequest["metadata"]["bindings"],
23
+ undefined
24
+ >[number];
25
+
26
+ export type WorkerSettingsBinding = Exclude<
27
+ workers.GetScriptScriptAndVersionSettingResponse["bindings"],
28
+ null | undefined
29
+ >[number];
30
+
31
+ export type WorkerBindingResource =
32
+ | Assets
33
+ | R2Bucket
34
+ | D1Database
35
+ | KVNamespace
36
+ | Queue
37
+ | AiGateway
38
+ | AnalyticsEngineDataset
39
+ | SendEmail
40
+ | Artifacts
41
+ | Images
42
+ | Hyperdrive
43
+ | Worker
44
+ | DurableObjectNamespaceLike<any>;
45
+
46
+ export type WorkerBindings = {
47
+ [bindingName in string]: WorkerBindingResource;
48
+ };
49
+
50
+ export const bindWorker = Effect.fnUntraced(function* <Shape, Req = never>(
51
+ workerEff:
52
+ | (Worker & Rpc<Shape>)
53
+ | Effect.Effect<Worker & Rpc<Shape>, never, Req>,
54
+ ) {
55
+ // Worker classes and regular Effects are both yieldable here.
56
+ const worker = isYieldableEffectLike(workerEff)
57
+ ? yield* workerEff as Effect.Effect<Worker & Rpc<Shape>, never, Req>
58
+ : workerEff;
59
+ const self = yield* Worker;
60
+ yield* self.bind`${worker}`({
61
+ bindings: [
62
+ {
63
+ type: "service",
64
+ name: worker.LogicalId,
65
+ service: worker.workerName,
66
+ },
67
+ ],
68
+ });
69
+
70
+ // `bindWorker` runs at *init* phase (both at plantime and at runtime
71
+ // cold-start). `WorkerEnvironment` only exists at exec phase on the
72
+ // deployed worker, so we hand `makeRpcStub` an `Effect<stub>` that
73
+ // resolves the binding lazily on each method call.
74
+ const stubEff = WorkerEnvironment.pipe(
75
+ Effect.map((env) => (env as Record<string, unknown>)[worker.LogicalId]),
76
+ );
77
+ return makeRpcStub<Shape>(stubEff);
78
+ });
79
+
80
+ export class BindWorkerPolicy extends Binding.Policy<
81
+ BindWorkerPolicy,
82
+ (worker: Worker) => Effect.Effect<void>
83
+ >()("Cloudflare.Worker.Bind") {}
84
+
85
+ export const BindWorkerPolicyLive = BindWorkerPolicy.layer.succeed(
86
+ Effect.fn(function* (host, worker: Worker) {
87
+ if (isWorker(host)) {
88
+ yield* host.bind`${worker}`({
89
+ bindings: [
90
+ {
91
+ type: "service",
92
+ name: worker.LogicalId,
93
+ service: worker.workerName,
94
+ },
95
+ ],
96
+ });
97
+ } else {
98
+ return yield* Effect.die(
99
+ new Error(`BindWorkerPolicy does not support runtime '${host.Type}'`),
100
+ );
101
+ }
102
+ }),
103
+ );
@@ -0,0 +1,304 @@
1
+ import type * as cf from "@cloudflare/workers-types";
2
+ import { NodeServices } from "@effect/platform-node";
3
+ import type { DurableObject, WorkerEntrypoint } from "cloudflare:workers";
4
+ import { Stream } from "effect";
5
+ import * as Cause from "effect/Cause";
6
+ import * as ConfigProvider from "effect/ConfigProvider";
7
+ import * as Context from "effect/Context";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Exit from "effect/Exit";
10
+ import { pipe } from "effect/Function";
11
+ import * as Layer from "effect/Layer";
12
+ import * as Logger from "effect/Logger";
13
+ import { MinimumLogLevel } from "effect/References";
14
+ import * as Scope from "effect/Scope";
15
+ import { FetchHttpClient } from "effect/unstable/http";
16
+ import { ExecutionContext } from "../../ExecutionContext.ts";
17
+ import { makeEntrypointLayer } from "../../Runtime.ts";
18
+ import { Self } from "../../Self.ts";
19
+ import { Stack } from "../../Stack.ts";
20
+ import {
21
+ ErrorTag,
22
+ type RpcErrorEnvelope,
23
+ type RpcStreamEnvelope,
24
+ encodeRpcError,
25
+ toRpcStream,
26
+ } from "./Rpc.ts";
27
+ import {
28
+ ExportedHandlerMethods,
29
+ Worker,
30
+ WorkerEnvironment,
31
+ WorkerExecutionContext,
32
+ } from "./Worker.ts";
33
+ import type { WorkerRuntimeContext } from "./WorkerRuntimeContext.ts";
34
+ import cloudflare_workers from "./cloudflare_workers.ts";
35
+
36
+ /**
37
+ * Makes the WorkerEntrypoint class and bridges to Effect fetch and RPC calls.
38
+ */
39
+ export const makeWorkerBridge = (
40
+ Base: typeof WorkerEntrypoint | typeof DurableObject,
41
+ {
42
+ stack,
43
+ entrypoint,
44
+ }: {
45
+ stack: {
46
+ name: string;
47
+ stage: string;
48
+ };
49
+ entrypoint: any;
50
+ },
51
+ ) => {
52
+ const { globalContext, exported, shape } = getWorkerExport({
53
+ entrypoint,
54
+ stack,
55
+ exportName: "default",
56
+ });
57
+
58
+ const processEvent = (
59
+ eff: Effect.Effect<
60
+ readonly [Effect.Effect<any, any, any>, Context.Context<never>],
61
+ any,
62
+ any
63
+ >,
64
+ ctx: cf.ExecutionContext,
65
+ ) => {
66
+ const scope = Scope.makeUnsafe();
67
+ return eff
68
+ .pipe(
69
+ Effect.flatMap(([eff, context]) =>
70
+ Effect.provide(
71
+ eff,
72
+ pipe(
73
+ Layer.succeedContext(context),
74
+ Layer.provideMerge(Layer.succeedContext(context)),
75
+ Layer.provideMerge(Layer.succeed(WorkerExecutionContext, ctx)),
76
+ Layer.provideMerge(
77
+ Layer.succeed(ExecutionContext, {
78
+ scope,
79
+ cache: {},
80
+ }),
81
+ ),
82
+ ),
83
+ ),
84
+ ),
85
+ Effect.provide(
86
+ Layer.provideMerge(globalContext, Layer.succeed(Scope.Scope, scope)),
87
+ ),
88
+ Effect.runPromiseExit,
89
+ )
90
+ .finally(() =>
91
+ Scope.close(scope, Exit.void).pipe(Effect.runPromise, (promise) =>
92
+ ctx.waitUntil(promise),
93
+ ),
94
+ );
95
+ };
96
+ class WorkerBridge extends Base {
97
+ constructor(
98
+ public readonly ctx: any,
99
+ public readonly env: any,
100
+ ) {
101
+ super(ctx, env);
102
+
103
+ for (const methodName of ExportedHandlerMethods) {
104
+ (this as any)[methodName] = async (input: any) =>
105
+ exported
106
+ .pipe(
107
+ Effect.map((_default) => _default[methodName]),
108
+ Effect.map(
109
+ (f) =>
110
+ f(input, this.env, this.ctx) as [
111
+ Effect.Effect<any>,
112
+ Context.Context<never>,
113
+ ],
114
+ ),
115
+ (eff) => processEvent(eff, this.ctx),
116
+ )
117
+ .then((exit) =>
118
+ exit._tag === "Success"
119
+ ? Promise.resolve(exit.value)
120
+ : Promise.reject(Cause.squash(exit.cause)),
121
+ );
122
+ }
123
+
124
+ return new Proxy(this, {
125
+ get: (target, prop) => {
126
+ if (typeof prop !== "string") return (target as any)[prop];
127
+ if (prop in target) return (target as any)[prop];
128
+ return (...args: unknown[]) =>
129
+ shape
130
+ .pipe(
131
+ Effect.map((shape: any) => shape[prop]),
132
+ Effect.flatMap((dispatcher) => {
133
+ if (typeof dispatcher !== "function") {
134
+ return Effect.die(
135
+ new Error(
136
+ `Method "${prop}" not found on worker. ` +
137
+ `Make sure it's returned from the worker's default export.`,
138
+ ),
139
+ );
140
+ }
141
+ console.log("dispatcher", dispatcher.toString());
142
+ return Effect.succeed([
143
+ dispatcher(...args) as Effect.Effect<any>,
144
+ Context.empty(),
145
+ ] as const);
146
+ }),
147
+ (eff) => processEvent(eff, this.ctx),
148
+ )
149
+ .then(handleRpcExit);
150
+ },
151
+ });
152
+ }
153
+ }
154
+
155
+ // Stub prototype methods so Cloudflare's script-validate detects the
156
+ // standard handler set; per-instance overrides above are what actually
157
+ // run.
158
+ for (const method of ExportedHandlerMethods) {
159
+ Object.defineProperty(WorkerBridge.prototype, method, {
160
+ value: function () {
161
+ throw new Error(
162
+ `Bridge method '${method}' was called before instance setup`,
163
+ );
164
+ },
165
+ writable: true,
166
+ configurable: true,
167
+ });
168
+ }
169
+
170
+ return WorkerBridge;
171
+ };
172
+
173
+ export const getWorkerExport = <Export = any>({
174
+ entrypoint,
175
+ stack,
176
+ exportName,
177
+ }: {
178
+ entrypoint: any;
179
+ stack: { name: string; stage: string };
180
+ exportName: string;
181
+ }) => {
182
+ const tag = Self as any as Context.Service<
183
+ never,
184
+ Worker & {
185
+ RuntimeContext: WorkerRuntimeContext;
186
+ }
187
+ >;
188
+
189
+ const runtimeContext = tag.pipe(Effect.map((func) => func.RuntimeContext));
190
+ const shape = runtimeContext.pipe(Effect.map((context) => context.shape()));
191
+ const exported = runtimeContext.pipe(
192
+ Effect.flatMap((context) => context.exports),
193
+ Effect.flatMap((exports) =>
194
+ Effect.isEffect(exports[exportName])
195
+ ? exports[exportName]
196
+ : Effect.succeed(exports[exportName]),
197
+ ),
198
+ ) as Effect.Effect<Export>;
199
+
200
+ const layer = makeEntrypointLayer(tag, entrypoint);
201
+
202
+ const platform = Layer.mergeAll(
203
+ NodeServices.layer,
204
+ FetchHttpClient.layer,
205
+ // TODO(sam): wire this up to telemetry more directly
206
+ Logger.layer([Logger.consolePretty()]),
207
+ );
208
+
209
+ const globalContext = Layer.unwrap(
210
+ cloudflare_workers.pipe(
211
+ Effect.map(({ env }) =>
212
+ layer.pipe(
213
+ Layer.provideMerge(
214
+ Layer.succeed(Stack, {
215
+ name: stack.name,
216
+ stage: stack.stage,
217
+ bindings: {},
218
+ resources: {},
219
+ actions: {},
220
+ }),
221
+ ),
222
+ Layer.provideMerge(platform),
223
+ Layer.provideMerge(
224
+ Layer.succeed(
225
+ ConfigProvider.ConfigProvider,
226
+ ConfigProvider.orElse(
227
+ ConfigProvider.fromUnknown({ ALCHEMY_PHASE: "runtime" }),
228
+ ConfigProvider.fromUnknown(env),
229
+ ),
230
+ ),
231
+ ),
232
+ Layer.provideMerge(Layer.succeed(WorkerEnvironment, env)),
233
+ Layer.provideMerge(
234
+ Layer.succeed(
235
+ MinimumLogLevel,
236
+ (env as any).DEBUG ? "Debug" : "Info",
237
+ ),
238
+ ),
239
+ ),
240
+ ),
241
+ ),
242
+ );
243
+
244
+ return {
245
+ globalContext,
246
+ exported,
247
+ shape,
248
+ };
249
+ };
250
+
251
+ export const makeRpcProxy = (
252
+ self: any,
253
+ userShape: Effect.Effect<any>,
254
+ processEvent: (
255
+ eff: Effect.Effect<[Effect.Effect<any>, Context.Context<never>]>,
256
+ ) => Promise<any>,
257
+ ) =>
258
+ new Proxy(self, {
259
+ get: (target, prop) => {
260
+ if (typeof prop !== "string") return (target as any)[prop];
261
+ if (prop in target) return (target as any)[prop];
262
+ return (...args: unknown[]) =>
263
+ userShape
264
+ .pipe(
265
+ Effect.map((shape) => shape[prop]),
266
+ Effect.flatMap((dispatcher) => {
267
+ if (typeof dispatcher !== "function") {
268
+ return Effect.die(
269
+ new Error(
270
+ `Method "${prop}" not found on worker. ` +
271
+ `Make sure it's returned from the worker's default export.`,
272
+ ),
273
+ );
274
+ }
275
+ console.log("dispatcher", dispatcher.toString());
276
+ return dispatcher(...args) as Effect.Effect<any>;
277
+ }),
278
+ processEvent,
279
+ )
280
+ .then(handleRpcExit);
281
+ },
282
+ });
283
+
284
+ export const handleRpcExit = async (exit: Exit.Exit<any, any>) => {
285
+ if (exit._tag === "Success") {
286
+ if (Stream.isStream(exit.value)) {
287
+ return await Effect.runPromise(
288
+ toRpcStream(exit.value) as Effect.Effect<RpcStreamEnvelope>,
289
+ );
290
+ }
291
+ return exit.value;
292
+ }
293
+ const failReason = exit.cause.reasons.find(Cause.isFailReason);
294
+ if (failReason) {
295
+ return {
296
+ _tag: ErrorTag,
297
+ error: encodeRpcError(failReason.error),
298
+ } satisfies RpcErrorEnvelope;
299
+ }
300
+ const dieReason = exit.cause.reasons.find(Cause.isDieReason);
301
+ throw (
302
+ dieReason?.defect ?? new Error("RPC method failed with an unexpected cause")
303
+ );
304
+ };
@@ -9,7 +9,6 @@ import type * as rolldown from "rolldown";
9
9
  import Sonda from "sonda/rolldown";
10
10
  import * as Bundle from "../../Bundle/Bundle.ts";
11
11
  import { findCwdForBundle } from "../../Bundle/TempRoot.ts";
12
- import { Self } from "../../Self.ts";
13
12
  import {
14
13
  isDurableObjectExport,
15
14
  type DurableObjectExport,
@@ -47,6 +46,12 @@ export const WorkerBundle = Effect.gen(function* () {
47
46
  const realMain = yield* sanitizeMain(options.main);
48
47
  const inputOptions: rolldown.InputOptions = {
49
48
  input: realMain,
49
+ // Forever-devtool native modules that vite/chokidar reference behind
50
+ // runtime guards. Rolldown resolves before tree-shaking, so the dead
51
+ // `require('../pkg')` (lightningcss < 1.32) and `require('fsevents')`
52
+ // (darwin-only) trip [UNRESOLVED_IMPORT] before DCE can prune them.
53
+ // See rolldown/tsdown#212.
54
+ external: ["lightningcss", "fsevents"],
50
55
  cwd: yield* findCwdForBundle(realMain).pipe(
51
56
  Effect.mapError(
52
57
  (cause) =>
@@ -148,100 +153,29 @@ export const makeEffectVirtualEntry = (
148
153
  const hasDoClasses = doClasses.length > 0;
149
154
  const hasWfClasses = wfClasses.length > 0;
150
155
  return (importPath: string) => `
151
- import * as Config from "effect/Config";
152
- import * as ConfigProvider from "effect/ConfigProvider";
153
- import * as Console from "effect/Console";
154
156
  import * as Effect from "effect/Effect";
155
- import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
156
- import * as Layer from "effect/Layer";
157
- import * as Logger from "effect/Logger";
158
- import * as Context from "effect/Context";
159
- import * as Stream from "effect/Stream";
160
157
 
161
158
  import { env, DurableObject, WorkerEntrypoint${hasWfClasses ? ", WorkflowEntrypoint" : ""} } from "cloudflare:workers";
162
- import { MinimumLogLevel } from "effect/References";
163
- import * as NodeServices from "@effect/platform-node/NodeServices";
164
- import { Stack } from "alchemy/Stack";
165
- import { WorkerEnvironment, makeDurableObjectBridge, makeWorkerBridge${hasWfClasses ? ", makeWorkflowBridge" : ""}, ExportedHandlerMethods } from "alchemy/Cloudflare";
159
+ import { makeDurableObjectBridge, makeWorkerBridge${hasWfClasses ? ", makeWorkflowBridge" : ""} } from "alchemy/Cloudflare";
166
160
  import { makeEntrypointLayer } from "alchemy/Runtime";
167
161
 
168
162
  import entrypoint from ${JSON.stringify(importPath)};
169
163
 
170
- const tag = Context.Service("${Self.key}")
171
- const layer = makeEntrypointLayer(tag, entrypoint);
172
-
173
- const platform = Layer.mergeAll(
174
- NodeServices.layer,
175
- FetchHttpClient.layer,
176
- // TODO(sam): wire this up to telemetry more directly
177
- Logger.layer([Logger.consolePretty()]),
178
- );
179
-
180
- const stack = Layer.succeed(
181
- Stack,
182
- {
183
- name: "${stack.name}",
184
- stage: "${stack.stage}",
185
- bindings: {},
186
- resources: {}
187
- }
188
- );
189
-
190
- const exportsEffect = tag.pipe(
191
- Effect.flatMap(func => func.RuntimeContext.exports),
192
- Effect.provide(
193
- layer.pipe(
194
- Layer.provideMerge(stack),
195
- Layer.provideMerge(platform),
196
- Layer.provideMerge(
197
- Layer.succeed(
198
- ConfigProvider.ConfigProvider,
199
- ConfigProvider.orElse(
200
- ConfigProvider.fromUnknown({ ALCHEMY_PHASE: "runtime" }),
201
- ConfigProvider.fromUnknown(env),
202
- ),
203
- ),
204
- ),
205
- Layer.provideMerge(Layer.succeed(WorkerEnvironment, env)),
206
- Layer.provideMerge(
207
- Layer.succeed(MinimumLogLevel, env.DEBUG ? "Debug" : "Info"),
208
- ),
209
- ),
210
- ),
211
- Effect.scoped,
212
- );
213
-
214
- // TODO(sam): we could kick this off during module init, but any I/O will break deploy
215
- // let exportsPromise = Effect.runPromise(exportsEffect);
216
-
217
- // for now, we delay initializing the worker until the first request
218
- let exportsPromise;
219
-
220
- // don't initialize the workerEffect during module init because Cloudflare does not allow I/O during module init
221
- // we cache it synchronously (??=) to guarnatee only one initialization ever happens
222
- const getExports = () => (exportsPromise ??= Effect.runPromise(exportsEffect))
223
- const getExport = (name) => getExports().then(exports => exports[name]?.make)
224
- const getDefault = () => getExports().then(exports => exports.default)
225
- const getRpc = () => getExports().then(exports => exports.__rpc__ ?? {})
164
+ const meta = {
165
+ entrypoint,
166
+ stack: {
167
+ name: ${JSON.stringify(stack.name)},
168
+ stage: ${JSON.stringify(stack.stage)},
169
+ },
170
+ };
226
171
 
227
- // Bridge the user's default-export shape onto a real \`WorkerEntrypoint\`
228
- // subclass so Cloudflare service bindings can dispatch both the standard
229
- // handler methods (fetch, scheduled, …) and any user-defined RPC methods.
230
- // RPC method results are wire-encoded by \`runtimeContext.exports\`;
231
- // consumers unwrap them with \`Cloudflare.toPromiseApi(env.X)\` (Promise
232
- // API) or \`bindWorker(WorkerClass)\` (Effect API).
233
- export default makeWorkerBridge(
234
- WorkerEntrypoint,
235
- ExportedHandlerMethods,
236
- getDefault,
237
- getRpc,
238
- );
172
+ export default makeWorkerBridge(WorkerEntrypoint, meta);
239
173
 
240
174
  // export class proxy stubs for Durable Objects and Workflows
241
175
  ${[
242
176
  ...(hasDoClasses
243
177
  ? [
244
- "const DurableObjectBridge = makeDurableObjectBridge(DurableObject, getExport);",
178
+ "const DurableObjectBridge = makeDurableObjectBridge(DurableObject, meta);",
245
179
  ...doClasses.map(
246
180
  (id) => `export class ${id} extends DurableObjectBridge("${id}") {}`,
247
181
  ),
@@ -249,7 +183,7 @@ ${[
249
183
  : []),
250
184
  ...(hasWfClasses
251
185
  ? [
252
- "const WorkflowBridgeFn = makeWorkflowBridge(WorkflowEntrypoint, getExport);",
186
+ "const WorkflowBridgeFn = makeWorkflowBridge(WorkflowEntrypoint, meta);",
253
187
  ...wfClasses.map(
254
188
  (id) => `export class ${id} extends WorkflowBridgeFn("${id}") {}`,
255
189
  ),