alchemy 2.0.0-beta.38 → 2.0.0-beta.39

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "2.0.0-beta.38",
3
+ "version": "2.0.0-beta.39",
4
4
  "homepage": "https://alchemy.run",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Sam Goodwin <sam@alchemy.run>",
@@ -1,9 +1,12 @@
1
1
  import type * as cf from "@cloudflare/workers-types";
2
2
  import * as Cause from "effect/Cause";
3
+ import * as Context from "effect/Context";
3
4
  import * as Effect from "effect/Effect";
4
5
  import * as Option from "effect/Option";
5
6
  import type { Scope } from "effect/Scope";
6
7
  import type { HttpBodyError } from "effect/unstable/http/HttpBody";
8
+ import * as EffectHttp from "effect/unstable/http/HttpEffect";
9
+ import { ClientAbort } from "effect/unstable/http/HttpServerError";
7
10
  import * as HttpServerError from "effect/unstable/http/HttpServerError";
8
11
  import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
9
12
  import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
@@ -48,6 +51,10 @@ export const serveWebRequest = <Req = never>(
48
51
  Exclude<Req, HttpServerRequest.HttpServerRequest | Scope>
49
52
  > =>
50
53
  Effect.gen(function* () {
54
+ const context =
55
+ yield* Effect.context<
56
+ Exclude<Req, HttpServerRequest.HttpServerRequest | Scope>
57
+ >();
51
58
  const request = HttpServerRequest.fromWeb(
52
59
  webRequest as any as globalThis.Request,
53
60
  ).modify({
@@ -61,9 +68,7 @@ export const serveWebRequest = <Req = never>(
61
68
  }),
62
69
  });
63
70
 
64
- const response = yield* handler.pipe(
65
- Effect.provideService(HttpServerRequest.HttpServerRequest, request),
66
- Effect.provideService(Request, webRequest as any),
71
+ const safeHandler = handler.pipe(
67
72
  Effect.catchCause((cause) => {
68
73
  const message = Option.match(Cause.findErrorOption(cause), {
69
74
  onNone: () => "Internal Server Error",
@@ -81,7 +86,34 @@ export const serveWebRequest = <Req = never>(
81
86
  }),
82
87
  );
83
88
 
84
- return HttpServerResponse.toWeb(response, {
85
- context: yield* Effect.context(),
89
+ const resolveSymbol = Symbol.for("@effect/platform/HttpApp/resolve");
90
+ const httpApp = EffectHttp.toHandled(safeHandler, (request, response) => {
91
+ response = EffectHttp.scopeTransferToStream(response);
92
+ (request as any)[resolveSymbol](
93
+ HttpServerResponse.toWeb(response, {
94
+ withoutBody: request.method === "HEAD",
95
+ context,
96
+ }),
97
+ );
98
+ return Effect.void;
99
+ });
100
+
101
+ return yield* Effect.promise(() => {
102
+ return new Promise<Response>((resolve) => {
103
+ const contextMap = new Map<string, any>(context.mapUnsafe);
104
+ contextMap.set(HttpServerRequest.HttpServerRequest.key, request);
105
+ contextMap.set(Request.key, webRequest);
106
+ (request as any)[resolveSymbol] = resolve;
107
+ const fiber = Effect.runForkWith(Context.makeUnsafe(contextMap))(
108
+ httpApp as any,
109
+ );
110
+ webRequest.signal?.addEventListener(
111
+ "abort",
112
+ () => {
113
+ fiber.interruptUnsafe(undefined, ClientAbort.annotation);
114
+ },
115
+ { once: true },
116
+ );
117
+ });
86
118
  });
87
119
  }) as any;
@@ -97,14 +97,18 @@ export type GetBindingType<T> = T extends Cloudflare.Assets
97
97
  ? Queue<unknown>
98
98
  : T extends Cloudflare.AiGateway
99
99
  ? Ai
100
- : T extends Cloudflare.AnalyticsEngineDataset
101
- ? AnalyticsEngineDataset
102
- : T extends Cloudflare.Artifacts
103
- ? Artifacts
104
- : T extends Cloudflare.Images
105
- ? ImagesBinding
106
- : T extends Cloudflare.Hyperdrive
107
- ? Hyperdrive
108
- : T extends Cloudflare.DurableObjectNamespaceLike
109
- ? DurableObjectNamespace<Exclude<T["Shape"], undefined>>
110
- : never;
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;
@@ -51,6 +51,7 @@ import {
51
51
  } from "../Artifacts/Artifacts.ts";
52
52
  import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
53
53
  import { D1Database } from "../D1/D1Database.ts";
54
+ import { isSendEmail, type SendEmail } from "../Email/SendEmail.ts";
54
55
  import type {
55
56
  Hyperdrive,
56
57
  HyperdriveDevOrigin,
@@ -201,6 +202,7 @@ export type WorkerBindingResource =
201
202
  | CloudflareQueue
202
203
  | AiGateway
203
204
  | AnalyticsEngineDataset
205
+ | SendEmail
204
206
  | ArtifactsBinding
205
207
  | ImagesBinding
206
208
  | Hyperdrive
@@ -779,62 +781,71 @@ export const Worker: Platform<
779
781
  name: bindingName,
780
782
  dataset: binding.dataset,
781
783
  }
782
- : isDurableObjectNamespaceLike(binding)
784
+ : isSendEmail(binding)
783
785
  ? {
784
- type: "durable_object_namespace",
786
+ type: "send_email",
785
787
  name: bindingName,
786
- className: binding.className ?? binding.name,
788
+ destinationAddress: binding.destinationAddress,
789
+ allowedDestinationAddresses:
790
+ binding.allowedDestinationAddresses,
791
+ allowedSenderAddresses: binding.allowedSenderAddresses,
787
792
  }
788
- : binding.Type === "Cloudflare.D1Database"
793
+ : isDurableObjectNamespaceLike(binding)
789
794
  ? {
790
- type: "d1",
791
- id: binding.databaseId,
795
+ type: "durable_object_namespace",
792
796
  name: bindingName,
797
+ className: binding.className ?? binding.name,
793
798
  }
794
- : binding.Type === "Cloudflare.R2Bucket"
799
+ : binding.Type === "Cloudflare.D1Database"
795
800
  ? {
796
- type: "r2_bucket",
801
+ type: "d1",
802
+ id: binding.databaseId,
797
803
  name: bindingName,
798
- bucketName: binding.bucketName,
799
- jurisdiction: binding.jurisdiction.pipe(
800
- Output.map((jurisdiction) =>
801
- jurisdiction === "default"
802
- ? undefined
803
- : jurisdiction,
804
- ),
805
- ),
806
804
  }
807
- : binding.Type === "Cloudflare.KVNamespace"
805
+ : binding.Type === "Cloudflare.R2Bucket"
808
806
  ? {
809
- type: "kv_namespace",
807
+ type: "r2_bucket",
810
808
  name: bindingName,
811
- namespaceId: binding.namespaceId,
809
+ bucketName: binding.bucketName,
810
+ jurisdiction: binding.jurisdiction.pipe(
811
+ Output.map((jurisdiction) =>
812
+ jurisdiction === "default"
813
+ ? undefined
814
+ : jurisdiction,
815
+ ),
816
+ ),
812
817
  }
813
- : binding.Type === "Cloudflare.Queue"
818
+ : binding.Type === "Cloudflare.KVNamespace"
814
819
  ? {
815
- type: "queue",
820
+ type: "kv_namespace",
816
821
  name: bindingName,
817
- queueName: binding.queueName,
822
+ namespaceId: binding.namespaceId,
818
823
  }
819
- : binding.Type === "Cloudflare.AiGateway"
824
+ : binding.Type === "Cloudflare.Queue"
820
825
  ? {
821
- type: "ai",
826
+ type: "queue",
822
827
  name: bindingName,
828
+ queueName: binding.queueName,
823
829
  }
824
- : binding.Type === "Cloudflare.Hyperdrive"
830
+ : binding.Type === "Cloudflare.AiGateway"
825
831
  ? {
826
- type: "hyperdrive",
832
+ type: "ai",
827
833
  name: bindingName,
828
- id: binding.hyperdriveId,
829
834
  }
830
- : isWorker(binding)
835
+ : binding.Type === "Cloudflare.Hyperdrive"
831
836
  ? {
832
- type: "service",
837
+ type: "hyperdrive",
833
838
  name: bindingName,
834
- service: binding.workerName,
839
+ id: binding.hyperdriveId,
835
840
  }
836
- : // TODO(sam): handle others
837
- undefined;
841
+ : isWorker(binding)
842
+ ? {
843
+ type: "service",
844
+ name: bindingName,
845
+ service: binding.workerName,
846
+ }
847
+ : // TODO(sam): handle others
848
+ undefined;
838
849
 
839
850
  if (bindingMeta) {
840
851
  yield* resource.bind`${bindingName}`({
@@ -1607,6 +1618,22 @@ export const LiveWorkerProvider = () =>
1607
1618
  const builder = await vite.createBuilder(
1608
1619
  {
1609
1620
  root: props.vite?.rootDir,
1621
+ // Emulate `vite build` env semantics for `props.env`: only
1622
+ // keys with Vite's default `VITE_` prefix are inlined into
1623
+ // the bundle as `import.meta.env.*`. `Redacted` values are
1624
+ // unwrapped — by prefixing with `VITE_` the user is opting
1625
+ // them into the public bundle.
1626
+ define: Object.fromEntries(
1627
+ Object.entries(props.env ?? {}).flatMap(([key, raw]) => {
1628
+ if (!key.startsWith("VITE_")) return [];
1629
+ const value = Redacted.isRedacted(raw)
1630
+ ? Redacted.value(raw)
1631
+ : raw;
1632
+ return [
1633
+ [`import.meta.env.${key}`, JSON.stringify(value)] as const,
1634
+ ];
1635
+ }),
1636
+ ),
1610
1637
  // Declare the ssr environment so Vite 8+ creates it.
1611
1638
  // The cloudflare-vite-plugin config hook merges its
1612
1639
  // SSR-specific settings on top of this stub.