alchemy 2.0.0-beta.35 → 2.0.0-beta.36

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 (58) hide show
  1. package/bin/exec.js +27 -0
  2. package/bin/exec.js.map +1 -1
  3. package/lib/Cli/main.d.ts.map +1 -1
  4. package/lib/Cli/main.js +2 -0
  5. package/lib/Cli/main.js.map +1 -1
  6. package/lib/Cloudflare/Images/Images.d.ts +103 -0
  7. package/lib/Cloudflare/Images/Images.d.ts.map +1 -0
  8. package/lib/Cloudflare/Images/Images.js +82 -0
  9. package/lib/Cloudflare/Images/Images.js.map +1 -0
  10. package/lib/Cloudflare/Images/ImagesBinding.d.ts +73 -0
  11. package/lib/Cloudflare/Images/ImagesBinding.d.ts.map +1 -0
  12. package/lib/Cloudflare/Images/ImagesBinding.js +83 -0
  13. package/lib/Cloudflare/Images/ImagesBinding.js.map +1 -0
  14. package/lib/Cloudflare/Images/index.d.ts +3 -0
  15. package/lib/Cloudflare/Images/index.d.ts.map +1 -0
  16. package/lib/Cloudflare/Images/index.js +3 -0
  17. package/lib/Cloudflare/Images/index.js.map +1 -0
  18. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  19. package/lib/Cloudflare/Providers.js +3 -1
  20. package/lib/Cloudflare/Providers.js.map +1 -1
  21. package/lib/Cloudflare/R2/R2Bucket.d.ts +135 -0
  22. package/lib/Cloudflare/R2/R2Bucket.d.ts.map +1 -1
  23. package/lib/Cloudflare/R2/R2Bucket.js +130 -7
  24. package/lib/Cloudflare/R2/R2Bucket.js.map +1 -1
  25. package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
  26. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  27. package/lib/Cloudflare/Workers/Worker.d.ts +5 -5
  28. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  29. package/lib/Cloudflare/Workers/Worker.js +34 -23
  30. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  31. package/lib/Cloudflare/index.d.ts +1 -0
  32. package/lib/Cloudflare/index.d.ts.map +1 -1
  33. package/lib/Cloudflare/index.js +1 -0
  34. package/lib/Cloudflare/index.js.map +1 -1
  35. package/lib/Test/Vitest.d.ts.map +1 -1
  36. package/lib/Test/Vitest.js +2 -0
  37. package/lib/Test/Vitest.js.map +1 -1
  38. package/lib/Util/LocalhostDns.d.ts +19 -0
  39. package/lib/Util/LocalhostDns.d.ts.map +1 -0
  40. package/lib/Util/LocalhostDns.js +55 -0
  41. package/lib/Util/LocalhostDns.js.map +1 -0
  42. package/lib/Util/PlatformServices.d.ts.map +1 -1
  43. package/lib/Util/PlatformServices.js +2 -0
  44. package/lib/Util/PlatformServices.js.map +1 -1
  45. package/lib/tsconfig.test.tsbuildinfo +1 -1
  46. package/package.json +2 -1
  47. package/src/Cli/main.ts +3 -0
  48. package/src/Cloudflare/Images/Images.ts +121 -0
  49. package/src/Cloudflare/Images/ImagesBinding.ts +192 -0
  50. package/src/Cloudflare/Images/index.ts +2 -0
  51. package/src/Cloudflare/Providers.ts +3 -0
  52. package/src/Cloudflare/R2/R2Bucket.ts +240 -7
  53. package/src/Cloudflare/Workers/InferEnv.ts +7 -3
  54. package/src/Cloudflare/Workers/Worker.ts +47 -28
  55. package/src/Cloudflare/index.ts +1 -0
  56. package/src/Test/Vitest.ts +3 -0
  57. package/src/Util/LocalhostDns.ts +58 -0
  58. package/src/Util/PlatformServices.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "2.0.0-beta.35",
3
+ "version": "2.0.0-beta.36",
4
4
  "homepage": "https://alchemy.run",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Sam Goodwin <sam@alchemy.run>",
@@ -309,6 +309,7 @@
309
309
  "react": "^19.2.0",
310
310
  "rolldown": "1.0.0-rc.17",
311
311
  "sonda": "^0.11.1",
312
+ "undici": "^7.16.0",
312
313
  "web-tree-sitter": "0.25.10",
313
314
  "yaml": "^2.0.0"
314
315
  },
package/src/Cli/main.ts CHANGED
@@ -8,9 +8,12 @@ import { AlchemyContextLive } from "alchemy/AlchemyContext";
8
8
  import { CredentialsStoreLive } from "alchemy/Auth/Credentials";
9
9
  import { ProfileLive } from "alchemy/Auth/Profile";
10
10
  import { TelemetryLive } from "alchemy/Telemetry/Layer";
11
+ import { installLocalhostDns } from "alchemy/Util/LocalhostDns";
11
12
  import { PlatformServices } from "alchemy/Util/PlatformServices";
12
13
  import packageJson from "../../package.json" with { type: "json" };
13
14
 
15
+ installLocalhostDns();
16
+
14
17
  import { checkLatestVersion } from "./checkVersion.ts";
15
18
  import { handleCancellation } from "./commands/_shared.ts";
16
19
  import { awsCommand } from "./commands/aws.ts";
@@ -0,0 +1,121 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { ImagesBinding } from "./ImagesBinding.ts";
3
+
4
+ type ImagesTypeId = typeof ImagesTypeId;
5
+ const ImagesTypeId = "Cloudflare.Images" as const;
6
+
7
+ export type ImagesProps = {
8
+ /**
9
+ * Binding name used when `Cloudflare.Images.bind(images)` attaches Images
10
+ * from inside a Worker init phase. When Images is passed through
11
+ * `Worker({ bindings: { ... } })`, the object key remains the binding name.
12
+ *
13
+ * @default "IMAGES"
14
+ */
15
+ name?: string;
16
+ };
17
+
18
+ /**
19
+ * Marker for a Cloudflare Images binding.
20
+ *
21
+ * Images bindings are configured directly on Workers and do not have a
22
+ * standalone provisioning API. The Worker provider sees this object in
23
+ * `bindings: { ... }` and emits the corresponding `{ type: "images" }`
24
+ * metadata binding to the script.
25
+ */
26
+ export type Images = {
27
+ kind: ImagesTypeId;
28
+ name: string;
29
+ };
30
+
31
+ export const isImages = (value: unknown): value is Images =>
32
+ typeof value === "object" &&
33
+ value !== null &&
34
+ "kind" in value &&
35
+ (value as Images).kind === ImagesTypeId;
36
+
37
+ /**
38
+ * A Cloudflare Images binding for image transformation and manipulation inside
39
+ * Workers.
40
+ *
41
+ * The Effect-native interface (`Cloudflare.Images.bind(...)`) returns an
42
+ * `ImagesClient` whose methods take Effect `Stream.Stream<Uint8Array>`
43
+ * inputs and return `Effect`s — `info`, `input(...).transform(...)
44
+ * .draw(...).output(...)`. The runtime conversion to Cloudflare's
45
+ * `ReadableStream` is handled internally.
46
+ *
47
+ * @section Declaring Images
48
+ * @example
49
+ * ```typescript
50
+ * const Pipeline = yield* Cloudflare.Images({ name: "PIPELINE" });
51
+ * ```
52
+ *
53
+ * @section Effect-style Worker (recommended)
54
+ * @example Read image format and dimensions from the request body
55
+ * ```typescript
56
+ * import { HttpServerRequest } from "effect/unstable/http/HttpServerRequest";
57
+ *
58
+ * Cloudflare.Worker("ImageWorker", { main: import.meta.filename },
59
+ * Effect.gen(function* () {
60
+ * const pipeline = yield* Pipeline;
61
+ * const images = yield* Cloudflare.Images.bind(pipeline);
62
+ * return {
63
+ * fetch: Effect.gen(function* () {
64
+ * const request = yield* HttpServerRequest;
65
+ * // request.stream is Stream.Stream<Uint8Array>
66
+ * const info = yield* images.info(request.stream);
67
+ * return yield* HttpServerResponse.json(info);
68
+ * }),
69
+ * };
70
+ * }).pipe(Effect.provide(Cloudflare.ImagesBindingLive)),
71
+ * );
72
+ * ```
73
+ *
74
+ * @example Transform an image — chainable pipeline, single Effect at the end
75
+ * ```typescript
76
+ * const result = yield* (yield* images.input(request.stream))
77
+ * .transform({ width: 128 })
78
+ * .output({ format: "image/jpeg" });
79
+ *
80
+ * const response = yield* result.response;
81
+ * ```
82
+ *
83
+ * @section Binding to a Worker (declarative)
84
+ * @example
85
+ * ```typescript
86
+ * export const Worker = Cloudflare.Worker("Worker", {
87
+ * main: "./src/worker.ts",
88
+ * bindings: { MEDIA: Pipeline },
89
+ * });
90
+ *
91
+ * export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
92
+ * // { MEDIA: ImagesBinding }
93
+ * ```
94
+ *
95
+ * Inside the Worker, the raw Cloudflare runtime binding is reachable via
96
+ * `client.raw` if you need to call `info()` / `input()` directly with
97
+ * `async`/`await`. The Effect-native interface is preferred — it returns
98
+ * tagged `ImagesError`s, threads `WorkerEnvironment`, and lets you stream
99
+ * Effect `Stream<Uint8Array>` sources without manual conversion.
100
+ *
101
+ * @see https://developers.cloudflare.com/images/transform-images/bindings/
102
+ */
103
+ export const Images: {
104
+ (props?: ImagesProps): Effect.Effect<Images>;
105
+ /**
106
+ * Bind Cloudflare Images to the surrounding Worker, returning an
107
+ * Effect-native client with access to the native Workers runtime binding.
108
+ */
109
+ bind: typeof ImagesBinding.bind;
110
+ } = Object.assign(
111
+ Effect.fn(function* (props?: ImagesProps) {
112
+ return {
113
+ kind: ImagesTypeId,
114
+ name: props?.name ?? "IMAGES",
115
+ } satisfies Images;
116
+ }),
117
+ {
118
+ bind: (...args: Parameters<typeof ImagesBinding.bind>) =>
119
+ ImagesBinding.bind(...args),
120
+ },
121
+ );
@@ -0,0 +1,192 @@
1
+ import type * as cf from "@cloudflare/workers-types";
2
+ import * as Data from "effect/Data";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Stream from "effect/Stream";
6
+ import * as Binding from "../../Binding.ts";
7
+ import type { ResourceLike } from "../../Resource.ts";
8
+ import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
9
+ import { type Images as ImagesLike } from "./Images.ts";
10
+
11
+ export class ImagesError extends Data.TaggedError("ImagesError")<{
12
+ message: string;
13
+ code?: number;
14
+ cause: unknown;
15
+ }> {}
16
+
17
+ /**
18
+ * Effect-native handle to the result of `input(...).output(...)`.
19
+ * Mirrors the runtime `ImageTransformationResult` but exposes side
20
+ * effects (response/image/contentType reads) as plain sync effects.
21
+ */
22
+ export interface ImageTransformationResultClient {
23
+ raw: cf.ImageTransformationResult;
24
+ response: Effect.Effect<cf.Response>;
25
+ contentType: Effect.Effect<string>;
26
+ image(
27
+ options?: cf.ImageTransformationOutputOptions,
28
+ ): Effect.Effect<cf.ReadableStream<Uint8Array>>;
29
+ }
30
+
31
+ /**
32
+ * Effect-native chainable transformer. `transform`/`draw` are pure
33
+ * (return a new client wrapping the next runtime transformer);
34
+ * `output` is the only step that crosses into Cloudflare's runtime
35
+ * and therefore returns an Effect.
36
+ */
37
+ export interface ImageTransformerClient {
38
+ raw: cf.ImageTransformer;
39
+ transform(transform: cf.ImageTransform): ImageTransformerClient;
40
+ draw<E = never, R = never>(
41
+ image: Stream.Stream<Uint8Array, E, R> | ImageTransformerClient,
42
+ options?: cf.ImageDrawOptions,
43
+ ): Effect.Effect<ImageTransformerClient, never, R>;
44
+ output(
45
+ options: cf.ImageOutputOptions,
46
+ ): Effect.Effect<ImageTransformationResultClient, ImagesError>;
47
+ }
48
+
49
+ /**
50
+ * Effect-native client for a Cloudflare Images binding.
51
+ *
52
+ * Wraps the runtime {@link cf.ImagesBinding} so each method returns
53
+ * an Effect tagged with {@link ImagesError}. Use
54
+ * `Cloudflare.Images.bind(images)` inside a Worker's init phase.
55
+ */
56
+ export interface ImagesClient {
57
+ /** Effect resolving to the raw Cloudflare runtime binding. */
58
+ raw: Effect.Effect<cf.ImagesBinding, never, WorkerEnvironment>;
59
+ /**
60
+ * Read image format and dimensions from a stream of bytes.
61
+ * Fails with {@link ImagesError} (code 9412) if the input is not
62
+ * a recognized image.
63
+ */
64
+ info<E = never, R = never>(
65
+ stream: Stream.Stream<Uint8Array, E, R>,
66
+ options?: cf.ImageInputOptions,
67
+ ): Effect.Effect<cf.ImageInfoResponse, ImagesError, WorkerEnvironment | R>;
68
+ /**
69
+ * Begin a transformation pipeline. Subsequent `.transform()` /
70
+ * `.draw()` calls are pure; `.output(opts)` runs the pipeline.
71
+ */
72
+ input<E = never, R = never>(
73
+ stream: Stream.Stream<Uint8Array, E, R>,
74
+ options?: cf.ImageInputOptions,
75
+ ): Effect.Effect<ImageTransformerClient, never, WorkerEnvironment | R>;
76
+ }
77
+
78
+ export class ImagesBinding extends Binding.Service<
79
+ ImagesBinding,
80
+ (images: ImagesLike) => Effect.Effect<ImagesClient>
81
+ >()("Cloudflare.Images.Binding") {}
82
+
83
+ export const ImagesBindingLive = Layer.effect(
84
+ ImagesBinding,
85
+ Effect.gen(function* () {
86
+ const Policy = yield* ImagesBindingPolicy;
87
+
88
+ return Effect.fn(function* (images: ImagesLike) {
89
+ yield* Policy(images);
90
+ const env = WorkerEnvironment.asEffect();
91
+ const raw = env.pipe(
92
+ Effect.map(
93
+ (env) => (env as Record<string, cf.ImagesBinding>)[images.name]!,
94
+ ),
95
+ );
96
+
97
+ return {
98
+ raw,
99
+ info: (stream, options) =>
100
+ Effect.gen(function* () {
101
+ const binding = yield* raw;
102
+ const readable = yield* toCfReadable(stream);
103
+ return yield* tryPromise(() => binding.info(readable, options));
104
+ }),
105
+ input: (stream, options) =>
106
+ Effect.gen(function* () {
107
+ const binding = yield* raw;
108
+ const readable = yield* toCfReadable(stream);
109
+ return wrapTransformer(binding.input(readable, options));
110
+ }),
111
+ } satisfies ImagesClient;
112
+ });
113
+ }),
114
+ );
115
+
116
+ export class ImagesBindingPolicy extends Binding.Policy<
117
+ ImagesBindingPolicy,
118
+ (images: ImagesLike) => Effect.Effect<void>
119
+ >()("Cloudflare.Images.Binding") {}
120
+
121
+ export const ImagesBindingPolicyLive = ImagesBindingPolicy.layer.succeed(
122
+ Effect.fn(function* (host: ResourceLike, images: ImagesLike) {
123
+ if (isWorker(host)) {
124
+ yield* host.bind(images.name, {
125
+ bindings: [
126
+ {
127
+ type: "images",
128
+ name: images.name,
129
+ },
130
+ ],
131
+ });
132
+ } else {
133
+ return yield* Effect.die(
134
+ new Error(`ImagesBinding does not support runtime '${host.Type}'`),
135
+ );
136
+ }
137
+ }),
138
+ );
139
+
140
+ const tryPromise = <T>(fn: () => Promise<T>): Effect.Effect<T, ImagesError> =>
141
+ Effect.tryPromise({
142
+ try: fn,
143
+ catch: (error: any) =>
144
+ new ImagesError({
145
+ message: error?.message ?? "Unknown error",
146
+ code: typeof error?.code === "number" ? error.code : undefined,
147
+ cause: error,
148
+ }),
149
+ });
150
+
151
+ /**
152
+ * Convert an Effect `Stream<Uint8Array>` into the `cf.ReadableStream<Uint8Array>`
153
+ * shape that the Cloudflare Images runtime binding expects. The two
154
+ * `ReadableStream` types only differ at the type level; at runtime they
155
+ * are the same Web Streams API.
156
+ */
157
+ const toCfReadable = <E, R>(
158
+ stream: Stream.Stream<Uint8Array, E, R>,
159
+ ): Effect.Effect<cf.ReadableStream<Uint8Array>, never, R> =>
160
+ Stream.toReadableStreamEffect(stream).pipe(
161
+ Effect.map((s) => s as unknown as cf.ReadableStream<Uint8Array>),
162
+ );
163
+
164
+ const isTransformerClient = (image: unknown): image is ImageTransformerClient =>
165
+ typeof image === "object" && image !== null && "raw" in image;
166
+
167
+ const wrapTransformer = (raw: cf.ImageTransformer): ImageTransformerClient => ({
168
+ raw,
169
+ transform: (transform) => wrapTransformer(raw.transform(transform)),
170
+ draw: <E, R>(
171
+ image: Stream.Stream<Uint8Array, E, R> | ImageTransformerClient,
172
+ options?: cf.ImageDrawOptions,
173
+ ): Effect.Effect<ImageTransformerClient, never, R> => {
174
+ if (isTransformerClient(image)) {
175
+ return Effect.succeed(wrapTransformer(raw.draw(image.raw, options)));
176
+ }
177
+ return toCfReadable(image).pipe(
178
+ Effect.map((readable) => wrapTransformer(raw.draw(readable, options))),
179
+ );
180
+ },
181
+ output: (options) =>
182
+ tryPromise(() => raw.output(options)).pipe(Effect.map(wrapResult)),
183
+ });
184
+
185
+ const wrapResult = (
186
+ raw: cf.ImageTransformationResult,
187
+ ): ImageTransformationResultClient => ({
188
+ raw,
189
+ response: Effect.sync(() => raw.response()),
190
+ contentType: Effect.sync(() => raw.contentType()),
191
+ image: (options) => Effect.sync(() => raw.image(options)),
192
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./Images.ts";
2
+ export * from "./ImagesBinding.ts";
@@ -21,6 +21,7 @@ import * as Containers from "./Container/index.ts";
21
21
  import * as Credentials from "./Credentials.ts";
22
22
  import * as D1 from "./D1/index.ts";
23
23
  import * as Hyperdrive from "./Hyperdrive/index.ts";
24
+ import * as Images from "./Images/index.ts";
24
25
  import * as KV from "./KV/index.ts";
25
26
  import * as Queue from "./Queue/index.ts";
26
27
  import * as R2 from "./R2/index.ts";
@@ -56,6 +57,7 @@ export const providers = () =>
56
57
  D1.D1Database,
57
58
  Hyperdrive.Hyperdrive,
58
59
  Hyperdrive.HyperdriveBindingPolicy,
60
+ Images.ImagesBindingPolicy,
59
61
  KV.KVNamespace,
60
62
  KV.KVNamespaceBindingPolicy,
61
63
  Queue.Queue,
@@ -88,6 +90,7 @@ export const providers = () =>
88
90
  D1.DatabaseProvider(),
89
91
  Hyperdrive.HyperdriveBindingPolicyLive,
90
92
  Hyperdrive.HyperdriveProvider(),
93
+ Images.ImagesBindingPolicyLive,
91
94
  KV.KVNamespaceBindingPolicyLive,
92
95
  KV.KVNamespaceProvider(),
93
96
  Queue.QueueBindingPolicyLive,