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
@@ -1,10 +1,9 @@
1
1
  import type * as cf from "@cloudflare/workers-types";
2
2
  import * as Cause from "effect/Cause";
3
3
  import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
4
5
  import * as Option from "effect/Option";
5
6
  import type { Scope } from "effect/Scope";
6
- import type { HttpBodyError } from "effect/unstable/http/HttpBody";
7
- import * as HttpServerError from "effect/unstable/http/HttpServerError";
8
7
  import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
9
8
  import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
10
9
  import * as Http from "../../Http.ts";
@@ -13,45 +12,31 @@ import { isWorkerEvent, type WorkerServices } from "./Worker.ts";
13
12
 
14
13
  export type HttpEffect = Http.HttpEffect<WorkerServices>;
15
14
 
16
- export const workersHttpHandler = <Req = never>(
17
- handler: Http.HttpEffect<Req> | Effect.Effect<Http.HttpEffect<Req>>,
18
- ) => {
19
- const safeHandler = Http.safeHttpEffect(handler);
20
- return (event: any) => {
21
- if (isWorkerEvent(event) && event.type === "fetch") {
22
- const webRequest = event.input;
23
- return serveWebRequest(webRequest, safeHandler, {
24
- remoteAddress: webRequest.headers.get("cf-connecting-ip") ?? undefined,
25
- });
26
- }
27
- };
28
- };
15
+ export const makeRequestHandler =
16
+ <Req = never>(
17
+ handler: Http.HttpEffect<Req> | Effect.Effect<Http.HttpEffect<Req>>,
18
+ ) =>
19
+ (event: any) =>
20
+ isWorkerEvent(event) && event.type === "fetch"
21
+ ? makeRequestEffect(event.input, handler)
22
+ : undefined;
29
23
 
30
- export const serveWebRequest = <Req = never>(
24
+ export const makeRequestEffect = <Req = never>(
31
25
  webRequest: cf.Request,
32
- handler: Effect.Effect<
33
- HttpServerResponse.HttpServerResponse,
34
- HttpServerError.HttpServerError | HttpBodyError,
35
- Req
36
- >,
37
- options: {
38
- // Preserve transport metadata when this helper is adapting a request
39
- // that originated from another runtime surface.
40
- remoteAddress?: string;
41
- // Durable Objects need to register the accepted socket on object state
42
- // instead of calling `server.accept()` directly.
43
- acceptWebSocket?: (socket: cf.WebSocket) => void;
44
- } = {},
26
+ handler: Http.HttpEffect<Req> | Effect.Effect<Http.HttpEffect<Req>>,
45
27
  ): Effect.Effect<
46
28
  Response,
47
29
  never,
48
30
  Exclude<Req, HttpServerRequest.HttpServerRequest | Scope>
49
- > =>
50
- Effect.gen(function* () {
31
+ > => {
32
+ const safeHandler = Http.safeHttpEffect(handler);
33
+ return Effect.gen(function* () {
51
34
  const request = HttpServerRequest.fromWeb(
52
35
  webRequest as any as globalThis.Request,
53
36
  ).modify({
54
- remoteAddress: Option.fromUndefinedOr(options.remoteAddress),
37
+ remoteAddress: Option.fromUndefinedOr(
38
+ webRequest.headers.get("cf-connecting-ip") ?? undefined,
39
+ ),
55
40
  });
56
41
 
57
42
  Object.defineProperty(request, "raw", {
@@ -61,13 +46,15 @@ export const serveWebRequest = <Req = never>(
61
46
  }),
62
47
  });
63
48
 
64
- const response = yield* handler.pipe(
65
- Effect.provideService(HttpServerRequest.HttpServerRequest, request),
66
- Effect.provideService(Request, webRequest as any),
49
+ const response = yield* safeHandler.pipe(
50
+ Effect.provide([
51
+ Layer.succeed(HttpServerRequest.HttpServerRequest, request),
52
+ Layer.succeed(Request, webRequest as any),
53
+ ]),
67
54
  Effect.catchCause((cause) => {
68
55
  const message = Option.match(Cause.findErrorOption(cause), {
69
56
  onNone: () => "Internal Server Error",
70
- onSome: (error) =>
57
+ onSome: (error: any) =>
71
58
  error instanceof Error && error.message
72
59
  ? error.message
73
60
  : "Internal Server Error",
@@ -81,7 +68,6 @@ export const serveWebRequest = <Req = never>(
81
68
  }),
82
69
  );
83
70
 
84
- return HttpServerResponse.toWeb(response, {
85
- context: yield* Effect.context(),
86
- });
71
+ return HttpServerResponse.toWeb(response);
87
72
  }) as any;
73
+ };
@@ -2,19 +2,52 @@
2
2
 
3
3
  import type * as Effect from "effect/Effect";
4
4
  import type * as Stream from "effect/Stream";
5
- import type { Rpc } from "../../Platform.ts";
6
- import type { UnwrapEffect } from "../../Util/effect.ts";
5
+ import type { Rpc } from "../../Rpc.ts";
7
6
  import type * as Cloudflare from "../index.ts";
8
7
  import type { RpcErrorEnvelope, RpcStreamEnvelope } from "./Rpc.ts";
9
8
  import type { Worker } from "./Worker.ts";
10
9
 
11
- export type InferEnv<W> = W extends
12
- | Worker<infer Bindings>
13
- | Effect.Effect<Worker<infer Bindings>, any, any>
14
- ? {
15
- [K in keyof Bindings]: GetBindingType<UnwrapEffect<Bindings[K]>>;
16
- }
17
- : never;
10
+ export type InferEnv<W> =
11
+ W extends Effect.Effect<infer A, infer _E, infer _R>
12
+ ? InferEnv<A>
13
+ : W extends Worker<any>
14
+ ? InferEnv<Exclude<W["Props"]["bindings"], undefined>>
15
+ : {
16
+ [k in keyof W]: GetBindingType<W[k]>;
17
+ };
18
+
19
+ export type GetBindingType<T> =
20
+ T extends Effect.Effect<infer A, infer _E, infer _R>
21
+ ? GetBindingType<A>
22
+ : T extends Cloudflare.Assets
23
+ ? Service
24
+ : T extends Rpc<infer Shape extends object>
25
+ ? RpcWireShape<Shape> & Service
26
+ : T extends Cloudflare.D1Database
27
+ ? D1Database
28
+ : T extends Cloudflare.R2Bucket
29
+ ? R2Bucket
30
+ : T extends Cloudflare.KVNamespace
31
+ ? KVNamespace
32
+ : T extends Cloudflare.Queue
33
+ ? Queue<unknown>
34
+ : T extends Cloudflare.AiGateway
35
+ ? Ai
36
+ : T extends Cloudflare.SendEmail
37
+ ? SendEmail
38
+ : T extends Cloudflare.AnalyticsEngineDataset
39
+ ? AnalyticsEngineDataset
40
+ : T extends Cloudflare.Artifacts
41
+ ? Artifacts
42
+ : T extends Cloudflare.Images
43
+ ? ImagesBinding
44
+ : T extends Cloudflare.Hyperdrive
45
+ ? Hyperdrive
46
+ : T extends Cloudflare.DurableObjectNamespaceLike
47
+ ? DurableObjectNamespace<
48
+ Exclude<T["Shape"], undefined>
49
+ >
50
+ : T;
18
51
 
19
52
  /**
20
53
  * Cloudflare service-binding wire shape for an Effect-native Worker.
@@ -43,72 +76,3 @@ export type RpcWireShape<Shape> = {
43
76
  ? (...args: A) => Promise<Awaited<R>>
44
77
  : Promise<Shape[K]>;
45
78
  };
46
-
47
- /**
48
- * Recover the user's RPC `Shape` from any of the forms a caller might pass
49
- * to {@link toPromiseApi}:
50
- *
51
- * - the Worker class value's type, e.g. `typeof Backend`, which extends
52
- * `Effect.Effect<Worker & Rpc<Shape>, …>`
53
- * - the unwrapped `Worker & Rpc<Shape>` type
54
- * - a bare `Shape` (when the caller types it explicitly)
55
- */
56
- export type ExtractRpcShape<W> =
57
- W extends Effect.Effect<infer R, any, any>
58
- ? R extends Rpc<infer Shape>
59
- ? Shape
60
- : R
61
- : W extends Rpc<infer Shape>
62
- ? Shape
63
- : W;
64
-
65
- /**
66
- * Promise-flavored consumer view returned by {@link toPromiseApi}: error
67
- * envelopes are thrown as exceptions and stream envelopes are unwrapped to
68
- * their raw `ReadableStream<Uint8Array>` body.
69
- */
70
- export type RpcPromiseShape<Shape> = {
71
- [K in keyof Shape as K extends "fetch" ? never : K]: Shape[K] extends (
72
- ...args: infer A
73
- ) => Effect.Effect<infer T, any, any>
74
- ? (...args: A) => Promise<T>
75
- : Shape[K] extends (...args: infer A) => Stream.Stream<any, any, any>
76
- ? (...args: A) => Promise<ReadableStream<Uint8Array>>
77
- : Shape[K] extends Effect.Effect<infer T, any, any>
78
- ? Promise<T>
79
- : Shape[K] extends Stream.Stream<any, any, any>
80
- ? Promise<ReadableStream<Uint8Array>>
81
- : Shape[K] extends (...args: infer A) => infer R
82
- ? (...args: A) => Promise<Awaited<R>>
83
- : Promise<Shape[K]>;
84
- };
85
-
86
- export type GetBindingType<T> = T extends Cloudflare.Assets
87
- ? Service
88
- : T extends Rpc<infer Shape extends object>
89
- ? RpcWireShape<Shape> & Service
90
- : T extends Cloudflare.D1Database
91
- ? D1Database
92
- : T extends Cloudflare.R2Bucket
93
- ? R2Bucket
94
- : T extends Cloudflare.KVNamespace
95
- ? KVNamespace
96
- : T extends Cloudflare.Queue
97
- ? Queue<unknown>
98
- : T extends Cloudflare.AiGateway
99
- ? Ai
100
- : T extends Cloudflare.SendEmail
101
- ? SendEmail
102
- : T extends Cloudflare.AnalyticsEngineDataset
103
- ? AnalyticsEngineDataset
104
- : T extends Cloudflare.Artifacts
105
- ? Artifacts
106
- : T extends Cloudflare.Images
107
- ? ImagesBinding
108
- : T extends Cloudflare.Hyperdrive
109
- ? Hyperdrive
110
- : T extends Cloudflare.DurableObjectNamespaceLike
111
- ? DurableObjectNamespace<
112
- Exclude<T["Shape"], undefined>
113
- >
114
- : never;
@@ -7,12 +7,8 @@ import * as Option from "effect/Option";
7
7
  import * as Sink from "effect/Sink";
8
8
  import * as Stream from "effect/Stream";
9
9
  import * as Socket from "effect/unstable/socket/Socket";
10
- import type { HttpEffect } from "../../Http.ts";
11
10
  import { isYieldableEffect } from "../../Util/effect.ts";
12
11
  import { fromCloudflareFetcher } from "../Fetcher.ts";
13
- import { serveWebRequest } from "./HttpServer.ts";
14
- import type { ExtractRpcShape, RpcPromiseShape } from "./InferEnv.ts";
15
- import { fromWebSocket } from "./WebSocket.ts";
16
12
 
17
13
  export const StreamTag = "~alchemy/rpc/stream";
18
14
  export const ErrorTag = "~alchemy/rpc/error";
@@ -231,419 +227,6 @@ export const makeRpcStub = <Shape>(
231
227
  }) as Shape;
232
228
  };
233
229
 
234
- /**
235
- * Wrap a raw Cloudflare service binding (the wire-typed view emitted by
236
- * `InferEnv`) into a Promise-flavored API for non-Effect consumers.
237
- *
238
- * Effect-native worker methods serialize their results as envelopes:
239
- * `Effect.fail` becomes `RpcErrorEnvelope`, `Stream` becomes
240
- * `RpcStreamEnvelope`, and `Effect.succeed`/raw values pass through. This
241
- * proxy auto-decodes those envelopes — error envelopes are thrown so the
242
- * caller's `await` rejects, and stream envelopes are unwrapped to their raw
243
- * `ReadableStream<Uint8Array>` body.
244
- *
245
- * `Service.fetch` and `Service.connect` pass through untouched, so the
246
- * returned proxy is still a usable `Service` binding.
247
- *
248
- * For Effect-to-Effect cross-worker calls, prefer `bindWorker(Worker)` —
249
- * it preserves typed errors and decoded streams.
250
- *
251
- * The generic parameter is the Worker *class* (or its Effect form), the
252
- * same value you would pass to `bindWorker`. Shape is recovered from the
253
- * embedded `Rpc<Shape>`.
254
- *
255
- * @example
256
- * ```ts
257
- * import * as Cloudflare from "alchemy/Cloudflare";
258
- * import Backend from "./backend.ts";
259
- *
260
- * const backend = Cloudflare.toPromiseApi<typeof Backend>(env.BACKEND);
261
- * const greeting = await backend.hello(key); // throws on Effect.fail
262
- * ```
263
- */
264
- export const toPromiseApi = <W>(
265
- stub: any,
266
- ): RpcPromiseShape<ExtractRpcShape<W>> & Service =>
267
- new Proxy(stub, {
268
- get: (target, prop) => {
269
- // `Service` methods (fetch/connect) and any non-string keys (Symbol.dispose, etc.)
270
- // pass through to the underlying Cloudflare binding unchanged.
271
- if (typeof prop !== "string" || prop === "fetch" || prop === "connect") {
272
- const value = (target as any)[prop];
273
- return typeof value === "function" ? value.bind(target) : value;
274
- }
275
-
276
- return async (...args: unknown[]) => {
277
- const result = await (target as any)[prop](...args);
278
- if (isRpcErrorEnvelope(result)) {
279
- throw decodeRpcThrowable(result.error);
280
- }
281
- if (isRpcStreamEnvelope(result)) {
282
- return result.body;
283
- }
284
- return result;
285
- };
286
- },
287
- }) as any;
288
-
289
- /**
290
- * Reconstruct a throwable from {@link encodeRpcError}'s wire form. Plain
291
- * `Error` payloads are rebuilt as `Error` instances so `.message`/`.name`/
292
- * `.stack` survive. Tagged errors (anything with a `_tag`) and primitives
293
- * are thrown as-is so `try { ... } catch (e) { if (e._tag === ...) }` keeps
294
- * working.
295
- */
296
- const decodeRpcThrowable = (error: unknown): unknown => {
297
- if (error === null || typeof error !== "object") return error;
298
- const obj = error as Record<string, unknown>;
299
- if ("_tag" in obj) return obj;
300
- if (typeof obj.message === "string" || typeof obj.name === "string") {
301
- const e = new Error(typeof obj.message === "string" ? obj.message : "");
302
- if (typeof obj.name === "string") e.name = obj.name;
303
- if (typeof obj.stack === "string") e.stack = obj.stack;
304
- return e;
305
- }
306
- return error;
307
- };
308
-
309
- /**
310
- * Create a DurableObjectBridge class that proxies RPC method calls through
311
- * the Effect runtime, encoding success/fail/stream results as RPC envelopes.
312
- *
313
- * Accepts the `DurableObject` base class and a `getExport` resolver so the
314
- * implementation lives in real TypeScript instead of a generated string template.
315
- */
316
- export const makeDurableObjectBridge =
317
- (
318
- DurableObject: abstract new (
319
- state: unknown,
320
- env: unknown,
321
- ) => cf.DurableObject,
322
- getExport: (
323
- name: string,
324
- ) => Promise<
325
- (state: unknown, env: unknown) => Effect.Effect<Record<string, unknown>>
326
- >,
327
- ) =>
328
- (className: string) =>
329
- class DurableObjectBridge extends DurableObject {
330
- readonly object: Promise<DurableObjectShape>;
331
-
332
- async fetch(request: cf.Request): Promise<cf.Response> {
333
- const methods = await this.object;
334
- if (methods.fetch) {
335
- const fetch = methods.fetch as HttpEffect<never>;
336
- const response = await serveWebRequest(request, fetch).pipe(
337
- Effect.runPromise,
338
- );
339
- return response as any;
340
- } else {
341
- return new Response("Method not found", { status: 404 }) as any;
342
- }
343
- }
344
-
345
- async alarm(alarmInfo?: cf.AlarmInvocationInfo) {
346
- const methods = await this.object;
347
- if (methods.alarm) {
348
- await Effect.runPromise(methods.alarm(alarmInfo));
349
- }
350
- }
351
-
352
- async webSocketMessage(ws: cf.WebSocket, message: string | ArrayBuffer) {
353
- const methods = await this.object;
354
- if (methods.webSocketMessage) {
355
- const socket = fromWebSocket(ws);
356
- const value = methods.webSocketMessage(socket, message);
357
- if (Effect.isEffect(value)) {
358
- await Effect.runPromise(value as Effect.Effect<void>);
359
- }
360
- }
361
- }
362
-
363
- async webSocketClose(
364
- ws: cf.WebSocket,
365
- code: number,
366
- reason: string,
367
- wasClean: boolean,
368
- ) {
369
- const methods = await this.object;
370
- if (methods.webSocketClose) {
371
- const socket = fromWebSocket(ws);
372
- const value = methods.webSocketClose(socket, code, reason, wasClean);
373
- if (Effect.isEffect(value)) {
374
- await Effect.runPromise(value as Effect.Effect<void>);
375
- }
376
- }
377
- }
378
-
379
- constructor(
380
- state: {
381
- blockConcurrencyWhile: (
382
- fn: () => Promise<unknown>,
383
- ) => Promise<unknown>;
384
- },
385
- env: unknown,
386
- ) {
387
- super(state, env);
388
-
389
- this.object = state.blockConcurrencyWhile(async () => {
390
- const makeDurableObject = await getExport(className);
391
- return await Effect.runPromise(makeDurableObject(state, env));
392
- }) as Promise<any>;
393
-
394
- return new Proxy(this, {
395
- get: (target: any, prop) =>
396
- prop in target
397
- ? target[prop]
398
- : async (...args: unknown[]) => {
399
- const methods = await this.object;
400
- const method = methods[
401
- prop as keyof DurableObjectShape
402
- ] as any; // TODO(sam): what should the type be? Is it safe to always call it?
403
- const value = method(...args);
404
- if (Effect.isEffect(value)) {
405
- const exit = await Effect.runPromiseExit(
406
- value as Effect.Effect<unknown, never>,
407
- );
408
- if (exit._tag === "Success") {
409
- if (Stream.isStream(exit.value)) {
410
- return await Effect.runPromise(
411
- toRpcStream(
412
- exit.value,
413
- ) as Effect.Effect<RpcStreamEnvelope>,
414
- );
415
- }
416
- return exit.value;
417
- }
418
- const failReason = exit.cause.reasons.find(
419
- Cause.isFailReason,
420
- );
421
- if (failReason) {
422
- return {
423
- _tag: ErrorTag,
424
- error: encodeRpcError(failReason.error),
425
- } satisfies RpcErrorEnvelope;
426
- }
427
- const dieReason = exit.cause.reasons.find(
428
- Cause.isDieReason,
429
- );
430
- throw (
431
- dieReason?.defect ??
432
- new Error("RPC method failed with an unexpected cause")
433
- );
434
- }
435
- return value;
436
- },
437
- });
438
- }
439
- };
440
-
441
- /**
442
- * Create a `WorkerBridge` class — a `WorkerEntrypoint` subclass that
443
- * Cloudflare instantiates per request and dispatches both standard
444
- * handlers (`fetch`/`scheduled`/…) and RPC method calls into.
445
- *
446
- * The bridge is intentionally layer-ignorant: all runtime-layer plumbing
447
- * (services, scope, `WorkerExecutionContext`) lives in the platform's
448
- * `runtimeContext.exports` flow, which builds:
449
- *
450
- * - `getDefault()` → `{ fetch, scheduled, … }` — handler dispatchers
451
- * that the bridge forwards `(input, env, ctx)` to.
452
- * - `getRpc()` → `{ method: (args, ctx) => Promise<envelope> }` —
453
- * pre-wrapped RPC dispatchers that already run the user effect with
454
- * the right runtime layer and envelope-encode the result.
455
- *
456
- * Cloudflare's script-validate step requires standard handler methods
457
- * (`fetch`/`scheduled`/etc.) to be visible on the class prototype. We
458
- * declare static stubs there and override them per-instance with
459
- * closure-bound forwarders that capture `ctx`/`env` from the constructor
460
- * (prototype-level `this` doesn't survive the proxy + RPC dispatch
461
- * round-trip in some Cloudflare runtime versions).
462
- */
463
- export const makeWorkerBridge = (
464
- WorkerEntrypoint: abstract new (
465
- ctx: unknown,
466
- env: unknown,
467
- ) => { fetch?(request: cf.Request): Promise<cf.Response> | cf.Response },
468
- ExportedHandlerMethods: ReadonlyArray<string>,
469
- getDefault: () => Promise<Record<string, any>>,
470
- getRpc: () => Promise<Record<string, any>>,
471
- ) => {
472
- class WorkerBridge extends WorkerEntrypoint {
473
- constructor(ctx: unknown, env: unknown) {
474
- super(ctx, env);
475
-
476
- // Override handler methods per-instance with closure-bound forwarders.
477
- // We read `this.ctx`/`this.env` at *call time* rather than capturing
478
- // the constructor args because Cloudflare's runtime appears to
479
- // populate those instance fields differently depending on whether
480
- // the entrypoint is invoked via direct fetch or RPC — `super(ctx, env)`
481
- // alone isn't enough to guarantee they survive into the call.
482
- const self = this as unknown as { ctx: unknown; env: unknown };
483
- // Cloudflare invokes `WorkerEntrypoint.fetch(request)` with only the
484
- // primary input on the *parameter list* — `env`/`ctx` live on `this`
485
- // (and the runtime sometimes passes `(request, undefined, undefined)`
486
- // anyway for legacy compat). Other handlers, including `scheduled`, may
487
- // receive the classic `(input, env, ctx)` arguments. The platform's
488
- // underlying handler dispatch (`handle("fetch")`, `handle("scheduled")`,
489
- // etc.) follows the plain-export signature `(input, env, context)`, so
490
- // prefer explicit handler args when Cloudflare provides them and fall
491
- // back to the WorkerEntrypoint instance fields.
492
- for (const method of ExportedHandlerMethods) {
493
- (this as any)[method] = async (
494
- input: unknown,
495
- envArg?: unknown,
496
- ctxArg?: unknown,
497
- ) => {
498
- const def = await getDefault();
499
- return def?.[method]?.(input, envArg ?? self.env, ctxArg ?? self.ctx);
500
- };
501
- }
502
-
503
- // RPC method dispatch — anything Cloudflare RPC tries to invoke that
504
- // isn't an own property goes through the dispatcher table built by
505
- // the platform's exports flow.
506
- return new Proxy(this, {
507
- get: (target, prop) => {
508
- if (typeof prop !== "string") return (target as any)[prop];
509
- if (prop in target) return (target as any)[prop];
510
- return async (...args: unknown[]) => {
511
- const rpc = await getRpc();
512
- const dispatcher = rpc?.[prop];
513
- if (typeof dispatcher !== "function") {
514
- throw new Error(
515
- `Method "${prop}" not found on worker. ` +
516
- `Make sure it's returned from the worker's default export.`,
517
- );
518
- }
519
- return dispatcher(args, self.ctx);
520
- };
521
- },
522
- });
523
- }
524
- }
525
-
526
- // Stub prototype methods so Cloudflare's script-validate detects the
527
- // standard handler set; per-instance overrides above are what actually
528
- // run.
529
- for (const method of ExportedHandlerMethods) {
530
- Object.defineProperty(WorkerBridge.prototype, method, {
531
- value: function () {
532
- throw new Error(
533
- `Bridge method '${method}' was called before instance setup`,
534
- );
535
- },
536
- writable: true,
537
- configurable: true,
538
- });
539
- }
540
-
541
- return WorkerBridge;
542
- };
543
-
544
- /**
545
- * Create a WorkflowBridge class that extends `WorkflowEntrypoint` and
546
- * delegates the `run(event, step)` call to the Effect-native workflow body
547
- * registered via `worker.export(...)`.
548
- *
549
- * The bridge provides `WorkflowEvent` and `WorkflowStep` as Effect
550
- * services so the user writes `yield* WorkflowEvent` and `yield* task(...)`
551
- * instead of receiving callback parameters.
552
- */
553
- export const makeWorkflowBridge =
554
- (
555
- WorkflowEntrypoint: abstract new (
556
- ctx: unknown,
557
- env: unknown,
558
- ) => { run(event: any, step: any): Promise<unknown> },
559
- getExport: (
560
- name: string,
561
- ) => Promise<
562
- (
563
- env: unknown,
564
- ) => Effect.Effect<(input: unknown) => Effect.Effect<unknown, never, any>>
565
- >,
566
- ) =>
567
- (className: string) =>
568
- class WorkflowBridge extends WorkflowEntrypoint {
569
- readonly fn: Promise<
570
- (input: unknown) => Effect.Effect<unknown, never, any>
571
- >;
572
- readonly env: unknown;
573
-
574
- constructor(ctx: unknown, env: unknown) {
575
- super(ctx, env);
576
- this.env = env;
577
- this.fn = getExport(className).then((factory) =>
578
- Effect.runPromise(factory(env)),
579
- );
580
- }
581
-
582
- async run(event: any, step: any): Promise<unknown> {
583
- const fn = await this.fn;
584
- return Effect.runPromise(
585
- fn(event.payload).pipe(
586
- Effect.provideService(
587
- WorkflowEventService,
588
- wrapWorkflowEvent(event),
589
- ),
590
- Effect.provideService(WorkflowStep, wrapWorkflowStep(step)),
591
- ) as Effect.Effect<unknown>,
592
- );
593
- }
594
- };
595
-
596
- import type { DurableObjectShape } from "./DurableObjectNamespace.ts";
597
- import {
598
- WorkflowEvent as WorkflowEventService,
599
- WorkflowStep,
600
- } from "./Workflow.ts";
601
-
602
- const wrapWorkflowEvent = (
603
- event: any,
604
- ): { payload: unknown; timestamp: Date; instanceId: string } => ({
605
- payload: event.payload,
606
- timestamp:
607
- event.timestamp instanceof Date
608
- ? event.timestamp
609
- : new Date(event.timestamp),
610
- instanceId: event.instanceId ?? "",
611
- });
612
-
613
- const wrapWorkflowStep = (step: any): WorkflowStep["Service"] => ({
614
- do: <T>(name: string, effect: Effect.Effect<T>): Effect.Effect<T> =>
615
- Effect.tryPromise(
616
- () => step.do(name, () => Effect.runPromise(effect)) as Promise<T>,
617
- ),
618
- sleep: (name: string, duration: string | number): Effect.Effect<void> =>
619
- Effect.tryPromise(() => step.sleep(name, duration)),
620
- sleepUntil: (name: string, timestamp: Date | number): Effect.Effect<void> =>
621
- Effect.tryPromise(() =>
622
- step.sleepUntil(
623
- name,
624
- timestamp instanceof Date ? timestamp.toISOString() : timestamp,
625
- ),
626
- ),
627
- });
628
-
629
- const encodeStreamErrorMarker = (cause: Cause.Cause<unknown>): string => {
630
- const failReason = cause.reasons.find(Cause.isFailReason);
631
- const error = failReason ? encodeRpcError(failReason.error) : undefined;
632
- return (
633
- JSON.stringify({
634
- _tag: StreamErrorTag,
635
- error,
636
- } satisfies RpcStreamErrorMarker) + "\n"
637
- );
638
- };
639
-
640
- const appendStreamErrors = (s: Stream.Stream<string, unknown>) =>
641
- s.pipe(
642
- Stream.catchCause((cause) =>
643
- Stream.succeed(encodeStreamErrorMarker(cause)),
644
- ),
645
- );
646
-
647
230
  export const toRpcStream = (stream: Stream.Stream<any, any, any>) =>
648
231
  Effect.scoped(
649
232
  Effect.gen(function* () {
@@ -690,3 +273,21 @@ export const toRpcStream = (stream: Stream.Stream<any, any, any>) =>
690
273
  return Effect.die(Cause.squash(cause));
691
274
  }),
692
275
  );
276
+
277
+ const encodeStreamErrorMarker = (cause: Cause.Cause<unknown>): string => {
278
+ const failReason = cause.reasons.find(Cause.isFailReason);
279
+ const error = failReason ? encodeRpcError(failReason.error) : undefined;
280
+ return (
281
+ JSON.stringify({
282
+ _tag: StreamErrorTag,
283
+ error,
284
+ } satisfies RpcStreamErrorMarker) + "\n"
285
+ );
286
+ };
287
+
288
+ const appendStreamErrors = (s: Stream.Stream<string, unknown>) =>
289
+ s.pipe(
290
+ Stream.catchCause((cause) =>
291
+ Stream.succeed(encodeStreamErrorMarker(cause)),
292
+ ),
293
+ );