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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "2.0.0-beta.40",
3
+ "version": "2.0.0-beta.41",
4
4
  "homepage": "https://alchemy.run",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Sam Goodwin <sam@alchemy.run>",
package/src/Apply.ts CHANGED
@@ -22,18 +22,17 @@ import type { Input } from "./Input.ts";
22
22
  import { generateInstanceId, InstanceId } from "./InstanceId.ts";
23
23
  import * as Output from "./Output.ts";
24
24
  import {
25
+ type ActionApply,
25
26
  type Apply,
26
27
  type Delete,
27
28
  type Plan,
28
- type ActionApply,
29
- type ActionDelete,
30
29
  } from "./Plan.ts";
31
30
  import { findProviderByType } from "./Provider.ts";
32
31
  import type { ResourceBinding } from "./Resource.ts";
33
32
  import { Stack } from "./Stack.ts";
34
33
  import { Stage } from "./Stage.ts";
35
- import { recordResourceOp, type ResourceOp } from "./Telemetry/Metrics.ts";
36
34
  import {
35
+ type ActionState,
37
36
  type CreatedResourceState,
38
37
  type CreatingResourceState,
39
38
  type DeletingResourceState,
@@ -43,12 +42,12 @@ import {
43
42
  type ReplacingResourceState,
44
43
  type ResourceState,
45
44
  type RunningActionState,
46
- type ActionState,
47
45
  type UpdatedResourceState,
48
46
  type UpdatingReourceState,
49
47
  State,
50
48
  StateStoreError,
51
49
  } from "./State/index.ts";
50
+ import { type ResourceOp, recordResourceOp } from "./Telemetry/Metrics.ts";
52
51
  import { hashInput } from "./Util/hash.ts";
53
52
 
54
53
  export type ApplyEffect<
@@ -405,12 +404,31 @@ const executeNode = (
405
404
  });
406
405
 
407
406
  const markTerminal = (status: "created" | "updated") =>
408
- Effect.sync(() => {
407
+ Effect.gen(function* () {
409
408
  terminalStatuses.set(fqn, {
410
409
  id: logicalId,
411
410
  type: node.resource.Type,
412
411
  status,
413
412
  });
413
+ // Emit immediately so the CLI surfaces the terminal status as soon
414
+ // as the resource is actually done — instead of batching every
415
+ // resource's "created"/"updated" event to the end of apply(), which
416
+ // makes long-running siblings appear stuck in "creating" until the
417
+ // entire deploy finishes.
418
+ //
419
+ // Cycle members are exempt: their initial pass produces an
420
+ // intermediate result that Phase 3 (`converge`) will overwrite once
421
+ // the SCC reaches a fixed point. Emitting "created"/"updated" here
422
+ // would surface that intermediate state to the CLI before the real
423
+ // terminal status is known. Their final status is flushed from
424
+ // `terminalStatuses` after `converge` completes.
425
+ if (inCycle) return;
426
+ yield* session.emit({
427
+ kind: "status-change",
428
+ id: logicalId,
429
+ type: node.resource.Type,
430
+ status,
431
+ });
414
432
  });
415
433
 
416
434
  const signalReady = Deferred.succeed(ready[fqn], void 0);
@@ -576,11 +594,16 @@ const executeNode = (
576
594
  });
577
595
  }
578
596
 
579
- yield* report("creating");
597
+ // While we're waiting on upstream outputs the resource isn't actually
598
+ // creating anything yet — surface that as "pending" so the CLI doesn't
599
+ // look stuck in "creating" for slow-upstream deploys.
600
+ yield* report("pending");
580
601
 
581
602
  // Create runs against fully resolved upstream outputs and bindings, not the
582
603
  // raw Output expressions stored in the plan.
583
604
  yield* waitForDeps(allUpstreamFqns());
605
+
606
+ yield* report("creating");
584
607
  const outputs = getOutputs();
585
608
 
586
609
  const news = (yield* Output.evaluate(node.props, outputs)) as Record<
@@ -662,6 +685,9 @@ const executeNode = (
662
685
  });
663
686
  }
664
687
 
688
+ // See create-flow note: while we're waiting on upstream outputs
689
+ // this resource isn't actually updating yet.
690
+ yield* report("pending");
665
691
  yield* waitForDeps(allUpstreamFqns());
666
692
  const outputs = getOutputs();
667
693
 
@@ -867,11 +893,15 @@ const executeNode = (
867
893
  });
868
894
  }
869
895
 
870
- yield* report("creating replacement");
896
+ // See create-flow note: while we're waiting on upstream outputs
897
+ // the replacement isn't actually being created yet.
898
+ yield* report("pending");
871
899
 
872
900
  // Replacement create is evaluated exactly like create, but against the new
873
901
  // generation's instance id and with the previous generations preserved in `old`.
874
902
  yield* waitForDeps(allUpstreamFqns());
903
+
904
+ yield* report("creating replacement");
875
905
  const outputs = getOutputs();
876
906
 
877
907
  const news = (yield* Output.evaluate(node.props, outputs)) as Record<
@@ -1052,13 +1082,16 @@ const executeActionNode = (
1052
1082
  type: task.Type,
1053
1083
  status: "skipped",
1054
1084
  });
1085
+ yield* report("skipped");
1055
1086
  return;
1056
1087
  }
1057
1088
 
1058
1089
  // ── run ──
1059
- // Wait for stable (post-reconcile) upstream attrs — not precreate stubs.
1060
- // This is `waitForStableDeps` at the call site, passed in as the
1061
- // `waitForDeps` parameter here.
1090
+ // Tasks wait on `waitForStableDeps` (post-reconcile attrs) for their
1091
+ // upstreams, which is often the slowest dep chain in the deploy.
1092
+ // Surface that as "pending" instead of having the task show no status
1093
+ // until its run actually starts.
1094
+ yield* report("pending");
1062
1095
  yield* waitForDeps(
1063
1096
  Object.keys(Output.resolveUpstream(node.input)).filter(
1064
1097
  (f) => f in readyStable,
@@ -1108,6 +1141,7 @@ const executeActionNode = (
1108
1141
  type: task.Type,
1109
1142
  status: "ran",
1110
1143
  });
1144
+ yield* report("ran");
1111
1145
  }).pipe(
1112
1146
  Effect.catchCause((cause) =>
1113
1147
  Effect.gen(function* () {
package/src/Cli/Event.ts CHANGED
@@ -12,7 +12,7 @@ export type ApplyStatus =
12
12
  | "deleted"
13
13
  | "replacing"
14
14
  | "replaced"
15
- // Task lifecycle (see {@link Task})
15
+ // Action lifecycle (see {@link Action})
16
16
  | "running"
17
17
  | "ran"
18
18
  | "skipped"
@@ -1,2 +1,3 @@
1
1
  export * from "./Workers/InferEnv.ts";
2
2
  export * from "./Workers/Rpc.ts";
3
+ export * from "./Workers/RpcAsync.ts";
@@ -1,5 +1,5 @@
1
1
  import * as Effect from "effect/Effect";
2
- import { type Rpc } from "../../Platform.ts";
2
+ import { type Rpc } from "../../Rpc.ts";
3
3
  import {
4
4
  fromCloudflareFetcher,
5
5
  toCloudflareFetcher,
@@ -6,7 +6,11 @@ import { pipe } from "effect/Function";
6
6
  import * as Latch from "effect/Latch";
7
7
  import * as Scope from "effect/Scope";
8
8
  import * as HttpBody from "effect/unstable/http/HttpBody";
9
- import { HttpClientError } from "effect/unstable/http/HttpClientError";
9
+ import * as HttpClient from "effect/unstable/http/HttpClient";
10
+ import {
11
+ HttpClientError,
12
+ TransportError,
13
+ } from "effect/unstable/http/HttpClientError";
10
14
  import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
11
15
  import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
12
16
  import type { HttpServerError } from "effect/unstable/http/HttpServerError";
@@ -118,6 +122,39 @@ export const fromCloudflareFetcher = (fetcher: cf.Fetcher): Fetcher => {
118
122
  };
119
123
  };
120
124
 
125
+ /**
126
+ * Adapt anything that exposes a server-shaped `fetch` (e.g. a Durable Object
127
+ * stub, a Worker service binding) into an Effect `HttpClient`. Lets HttpApi
128
+ * clients address bindings without a base URL via `transformClient`.
129
+ */
130
+ export const toHttpClient = (fetcher: {
131
+ fetch: (
132
+ request: HttpServerRequest.HttpServerRequest,
133
+ ) => Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError>;
134
+ }) =>
135
+ HttpClient.make((request) => {
136
+ console.log("toHttpClient: request", request.url);
137
+ return fetcher.fetch(HttpServerRequest.fromClientRequest(request)).pipe(
138
+ Effect.map((response) => {
139
+ console.log("toHttpClient: response", response);
140
+ return HttpClientResponse.fromWeb(
141
+ request,
142
+ HttpServerResponse.toWeb(response),
143
+ );
144
+ }),
145
+ Effect.mapError(
146
+ (cause) =>
147
+ new HttpClientError({
148
+ reason: new TransportError({
149
+ request,
150
+ cause,
151
+ description: "Fetcher-backed HttpClient request failed",
152
+ }),
153
+ }),
154
+ ),
155
+ );
156
+ });
157
+
121
158
  export const fromCloudflareSocket = (cfSocket: cf.Socket): Socket.Socket => {
122
159
  const latch = Latch.makeUnsafe(false);
123
160
  let currentFiberSet: FiberSet.FiberSet<any, any> | undefined;
@@ -44,13 +44,10 @@ import { Stack } from "../../Stack.ts";
44
44
  import { Stage } from "../../Stage.ts";
45
45
  import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
46
46
  import { getCompatibility } from "../Workers/Compatibility.ts";
47
- import { getCronBindings } from "../Workers/index.ts";
48
47
  import * as Vite from "../Workers/Vite.ts";
49
- import type {
50
- Worker,
51
- WorkerAssetsConfig,
52
- WorkerBinding,
53
- } from "../Workers/Worker.ts";
48
+ import type { Worker, WorkerAssetsConfig } from "../Workers/Worker.ts";
49
+ import { getCronBindings } from "../Workers/WorkerAsyncBindings.ts";
50
+ import type { WorkerBinding } from "../Workers/WorkerBinding.ts";
54
51
  import {
55
52
  WorkerBundle,
56
53
  type WorkerBundleOptions,
@@ -8,12 +8,16 @@ import {
8
8
  Worker,
9
9
  type WorkerAssetsConfig,
10
10
  type WorkerBindingProps,
11
+ type WorkerEnv,
11
12
  type WorkerProps,
12
13
  } from "../Workers/Worker.ts";
13
14
 
14
- export interface StaticSiteProps<Bindings extends WorkerBindingProps = {}>
15
+ export interface StaticSiteProps<
16
+ Bindings extends WorkerBindingProps = {},
17
+ Env extends WorkerEnv = WorkerEnv,
18
+ >
15
19
  extends
16
- Omit<WorkerProps<Bindings, WorkerAssetsConfig>, "assets">,
20
+ Omit<WorkerProps<Bindings, Env, WorkerAssetsConfig>, "assets">,
17
21
  Omit<CommandProps, "env"> {
18
22
  /**
19
23
  * Optional configuration for static asset routing behavior.
@@ -116,12 +120,13 @@ export type StaticSite = ReturnType<typeof StaticSite>;
116
120
  */
117
121
  export const StaticSite = <
118
122
  const Bindings extends WorkerBindingProps = {},
123
+ const Env extends WorkerEnv = WorkerEnv,
119
124
  Req = never,
120
125
  >(
121
126
  id: string,
122
127
  propsEff:
123
- | InputProps<StaticSiteProps<Bindings>>
124
- | Effect.Effect<InputProps<StaticSiteProps<Bindings>>, never, Req>,
128
+ | InputProps<StaticSiteProps<Bindings, Env>>
129
+ | Effect.Effect<InputProps<StaticSiteProps<Bindings, Env>>, never, Req>,
125
130
  ) =>
126
131
  Effect.gen(function* () {
127
132
  const props = Effect.isEffect(propsEff)
@@ -149,7 +154,7 @@ export const StaticSite = <
149
154
  })),
150
155
  );
151
156
 
152
- return yield* Worker<Bindings, WorkerAssetsConfig, Req>(
157
+ return yield* Worker<Bindings, Env, WorkerAssetsConfig, Req>(
153
158
  "Worker",
154
159
  Effect.map(props, (props) => ({
155
160
  ...props,
@@ -5,12 +5,14 @@ import {
5
5
  Worker,
6
6
  type WorkerAssetsConfig,
7
7
  type WorkerBindingProps,
8
+ type WorkerEnv,
8
9
  type WorkerProps,
9
10
  } from "../Workers/Worker.ts";
10
11
 
11
12
  export interface ViteProps<
12
13
  Bindings extends WorkerBindingProps = {},
13
- > extends Omit<WorkerProps<Bindings>, "vite" | "main"> {
14
+ Env extends WorkerEnv = {},
15
+ > extends Omit<WorkerProps<Bindings, Env>, "vite" | "main"> {
14
16
  /**
15
17
  * Root directory passed to Vite's `root` option.
16
18
  * Defaults to the current working directory (`process.cwd()`).
@@ -109,14 +111,15 @@ export interface ViteProps<
109
111
  */
110
112
  export const Vite = <
111
113
  const Bindings extends WorkerBindingProps = {},
114
+ const Env extends WorkerEnv = {},
112
115
  Req = never,
113
116
  >(
114
117
  id: string,
115
118
  propsEff?:
116
- | InputProps<ViteProps<Bindings>>
117
- | Effect.Effect<InputProps<ViteProps<Bindings>>, never, Req>,
119
+ | InputProps<ViteProps<Bindings, Env>>
120
+ | Effect.Effect<InputProps<ViteProps<Bindings, Env>>, never, Req>,
118
121
  ) =>
119
- Worker<Bindings, WorkerAssetsConfig, Req>(
122
+ Worker<Bindings, Env, WorkerAssetsConfig, Req>(
120
123
  id,
121
124
  Effect.map(
122
125
  Effect.isEffect(propsEff) ? propsEff : Effect.succeed(propsEff),
@@ -0,0 +1,180 @@
1
+ import type * as cf from "@cloudflare/workers-types";
2
+ import type { DurableObject as DurableObjectClass } from "cloudflare:workers";
3
+
4
+ import * as Cause from "effect/Cause";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Exit from "effect/Exit";
7
+ import * as Layer from "effect/Layer";
8
+ import * as Scope from "effect/Scope";
9
+
10
+ import { HttpServerResponse } from "effect/unstable/http";
11
+ import type {
12
+ DurableObjectExport,
13
+ DurableObjectShape,
14
+ } from "./DurableObjectNamespace.ts";
15
+ import {
16
+ DurableObjectState,
17
+ fromDurableObjectState,
18
+ } from "./DurableObjectState.ts";
19
+ import { makeRequestEffect } from "./HttpServer.ts";
20
+ import { fromWebSocket } from "./WebSocket.ts";
21
+ import { getWorkerExport, handleRpcExit } from "./WorkerBridge.ts";
22
+
23
+ /**
24
+ * Create a DurableObjectBridge class that proxies RPC method calls through
25
+ * the Effect runtime, encoding success/fail/stream results as RPC envelopes.
26
+ *
27
+ * Accepts the `DurableObject` base class and a `getExport` resolver so the
28
+ * implementation lives in real TypeScript instead of a generated string template.
29
+ */
30
+ export const makeDurableObjectBridge =
31
+ (
32
+ DurableObject: typeof DurableObjectClass,
33
+ {
34
+ entrypoint,
35
+ stack,
36
+ }: {
37
+ entrypoint: Effect.Effect<Record<string, any>>;
38
+ stack: {
39
+ name: string;
40
+ stage: string;
41
+ };
42
+ },
43
+ ) =>
44
+ (className: string) =>
45
+ class DurableObjectBridge extends DurableObject {
46
+ #state;
47
+ #globalContext;
48
+ #exported;
49
+ #instance;
50
+ constructor(state: cf.DurableObjectState, env: any) {
51
+ super(state as any, env);
52
+ this.#state = state;
53
+
54
+ const { globalContext, exported } =
55
+ getWorkerExport<DurableObjectExport>({
56
+ entrypoint,
57
+ stack,
58
+ exportName: className,
59
+ });
60
+
61
+ this.#globalContext = globalContext;
62
+ this.#exported = exported;
63
+
64
+ this.#instance = state.blockConcurrencyWhile(() =>
65
+ this.#exported.pipe(
66
+ Effect.flatMap(({ constructor, services }) =>
67
+ constructor.pipe(
68
+ Effect.provide(
69
+ Layer.succeed(
70
+ DurableObjectState,
71
+ fromDurableObjectState(this.#state),
72
+ ).pipe(Layer.provideMerge(Layer.succeedContext(services))),
73
+ ),
74
+ Effect.map((instance) => ({ instance, services })),
75
+ ),
76
+ ),
77
+ Effect.provide(this.#globalContext),
78
+ Effect.runPromise,
79
+ ),
80
+ );
81
+
82
+ return new Proxy(this, {
83
+ get: (target, prop) => {
84
+ console.log(target, prop, prop in target);
85
+ const bind = (f: any) =>
86
+ typeof f === "function" ? f.bind(target) : f;
87
+ if (typeof prop !== "string") return bind((target as any)[prop]);
88
+ if (prop in target) return bind((target as any)[prop]);
89
+ return async (...args: any[]) =>
90
+ this.#execute((instance) => {
91
+ const method = instance[prop as keyof DurableObjectShape];
92
+ if (typeof method === "function") {
93
+ return (method as any)(...args);
94
+ } else if (Effect.isEffect(method)) {
95
+ return method;
96
+ } else {
97
+ return Effect.succeed(method);
98
+ }
99
+ }, handleRpcExit);
100
+ },
101
+ });
102
+ }
103
+
104
+ async #execute(
105
+ fn: (instance: DurableObjectShape) => Effect.Effect<any, any, any>,
106
+ onExit?: (exit: Exit.Exit<any, any>) => Promise<any>,
107
+ ) {
108
+ const scope = Scope.makeUnsafe();
109
+
110
+ const { instance, services } = await this.#instance;
111
+
112
+ return fn(instance)
113
+ .pipe(
114
+ Effect.provide(
115
+ Layer.succeed(
116
+ DurableObjectState,
117
+ fromDurableObjectState(this.#state),
118
+ ).pipe(
119
+ Layer.provideMerge(Layer.succeed(Scope.Scope, scope)),
120
+ Layer.provideMerge(Layer.succeedContext(services)),
121
+ Layer.provideMerge(this.#globalContext),
122
+ ),
123
+ ),
124
+ Effect.runPromiseExit,
125
+ )
126
+ .then(
127
+ onExit ??
128
+ ((exit) =>
129
+ exit._tag === "Success"
130
+ ? Promise.resolve(exit.value)
131
+ : Promise.reject(Cause.squash(exit.cause))),
132
+ )
133
+ .finally(() =>
134
+ Scope.close(scope, Exit.void).pipe(Effect.runPromise, (promise) =>
135
+ this.#state.waitUntil(promise),
136
+ ),
137
+ );
138
+ }
139
+
140
+ async fetch(request: Request): Promise<any> {
141
+ return this.#execute((instance) =>
142
+ instance.fetch
143
+ ? makeRequestEffect(request as any, instance.fetch)
144
+ : Effect.succeed(
145
+ HttpServerResponse.text("Not implemented", {
146
+ status: 404,
147
+ }),
148
+ ),
149
+ );
150
+ }
151
+
152
+ async alarm(alarmInfo?: cf.AlarmInvocationInfo) {
153
+ return this.#execute((instance) => instance.alarm!(alarmInfo));
154
+ }
155
+
156
+ async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
157
+ return this.#execute(
158
+ (instance) =>
159
+ instance.webSocketMessage?.(fromWebSocket(ws as any), message) ??
160
+ Effect.void,
161
+ );
162
+ }
163
+
164
+ async webSocketClose(
165
+ ws: WebSocket,
166
+ code: number,
167
+ reason: string,
168
+ wasClean: boolean,
169
+ ) {
170
+ return this.#execute(
171
+ (instance) =>
172
+ instance.webSocketClose?.(
173
+ fromWebSocket(ws as any),
174
+ code,
175
+ reason,
176
+ wasClean,
177
+ ) ?? Effect.void,
178
+ );
179
+ }
180
+ } as any;
@@ -9,29 +9,25 @@ import type { HttpEffect } from "../../Http.ts";
9
9
  import * as Output from "../../Output.ts";
10
10
  import type { PlatformServices } from "../../Platform.ts";
11
11
  import { effectClass, taggedFunction } from "../../Util/effect.ts";
12
- import {
13
- DurableObjectState,
14
- fromDurableObjectState,
15
- } from "./DurableObjectState.ts";
12
+ import { DurableObjectState } from "./DurableObjectState.ts";
16
13
  import { makeRpcStub } from "./Rpc.ts";
17
14
  import { type DurableWebSocket } from "./WebSocket.ts";
18
15
  import { Worker, WorkerEnvironment, type WorkerServices } from "./Worker.ts";
19
16
 
20
17
  export interface DurableObjectExport {
21
18
  readonly kind: "durableObject";
22
- readonly make: (
23
- state: cf.DurableObjectState,
24
- env: any,
25
- ) => Effect.Effect<Record<string, unknown>>;
19
+ readonly constructor: Effect.Effect<
20
+ DurableObjectShape,
21
+ never,
22
+ DurableObjectState
23
+ >;
24
+ readonly services: Context.Context<never>;
26
25
  }
27
26
 
28
27
  export const isDurableObjectExport = (
29
28
  value: unknown,
30
29
  ): value is DurableObjectExport =>
31
- typeof value === "object" &&
32
- value !== null &&
33
- "kind" in value &&
34
- (value as any).kind === "durableObject";
30
+ typeof value === "object" && (value as any)?.kind === "durableObject";
35
31
 
36
32
  export type DurableObjectId = cf.DurableObjectId;
37
33
  export type DurableObjectJurisdiction = cf.DurableObjectJurisdiction;
@@ -46,10 +42,7 @@ const TypeId = "Cloudflare.DurableObjectNamespace";
46
42
  export const isDurableObjectNamespaceLike = (
47
43
  value: unknown,
48
44
  ): value is DurableObjectNamespaceLike =>
49
- typeof value === "object" &&
50
- value !== null &&
51
- "kind" in value &&
52
- value.kind === TypeId;
45
+ typeof value === "object" && (value as any)?.kind === TypeId;
53
46
 
54
47
  export interface DurableObjectNamespaceLike<Shape = any> {
55
48
  kind: TypeId;
@@ -574,11 +567,7 @@ export const DurableObjectNamespace: DurableObjectNamespaceClass =
574
567
  const [namespace, impl] = args as any as [
575
568
  name: string,
576
569
  impl: Effect.Effect<
577
- Effect.Effect<
578
- DurableObjectNamespace<any>,
579
- never,
580
- DurableObjectState
581
- >
570
+ Effect.Effect<DurableObjectShape, never, DurableObjectState>
582
571
  >,
583
572
  ];
584
573
  const worker = yield* Worker;
@@ -600,52 +589,6 @@ export const DurableObjectNamespace: DurableObjectNamespaceClass =
600
589
  ],
601
590
  });
602
591
 
603
- const services =
604
- yield* Effect.context<Effect.Services<typeof impl>>();
605
-
606
- yield* worker.export(namespace, {
607
- kind: "durableObject",
608
- make: (state: cf.DurableObjectState, env: any) => {
609
- const doState = fromDurableObjectState(state);
610
- return constructor.pipe(
611
- Effect.provideContext(services),
612
- Effect.provideService(DurableObjectState, doState),
613
- Effect.provideService(WorkerEnvironment, env),
614
- Effect.map((methods: any) => {
615
- const wrapped: Record<string, unknown> = {};
616
- for (const key of Object.getOwnPropertyNames(methods)) {
617
- const value = methods[key];
618
- if (Effect.isEffect(value)) {
619
- wrapped[key] = (
620
- value as Effect.Effect<any, any, any>
621
- ).pipe(
622
- Effect.provideService(DurableObjectState, doState),
623
- );
624
- } else if (typeof value === "function") {
625
- wrapped[key] = (...args: unknown[]) => {
626
- const result = (value as Function)(...args);
627
- if (Effect.isEffect(result)) {
628
- return (
629
- result as Effect.Effect<any, any, any>
630
- ).pipe(
631
- Effect.provideService(
632
- DurableObjectState,
633
- doState,
634
- ),
635
- );
636
- }
637
- return result;
638
- };
639
- } else {
640
- wrapped[key] = value;
641
- }
642
- }
643
- return wrapped;
644
- }),
645
- );
646
- },
647
- } satisfies DurableObjectExport);
648
-
649
592
  const binding = yield* Effect.serviceOption(
650
593
  WorkerEnvironment,
651
594
  ).pipe(
@@ -699,9 +642,18 @@ export const DurableObjectNamespace: DurableObjectNamespaceClass =
699
642
  // use((ns) => ns.jurisdiction(jurisdiction) as any),
700
643
  };
701
644
 
702
- const constructor = yield* impl.pipe(
703
- Effect.provideService(DurableObjectNamespaceScope, self as any),
704
- );
645
+ yield* worker.export(namespace, {
646
+ kind: "durableObject",
647
+ // initialize the object's constructor (apply infra dependencies)
648
+ constructor: yield* impl.pipe(
649
+ Effect.provideService(
650
+ DurableObjectNamespaceScope,
651
+ self as any,
652
+ ),
653
+ ),
654
+ // grab the object's infra dependencies so we can apply them when calling the instance's methods
655
+ services: yield* Effect.context<Effect.Services<typeof impl>>(),
656
+ } satisfies DurableObjectExport);
705
657
 
706
658
  return self;
707
659
  }),