alchemy 2.0.0-beta.51 → 2.0.0-beta.52

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 (176) hide show
  1. package/bin/exec.js +16 -22
  2. package/bin/exec.js.map +1 -1
  3. package/lib/Apply.js +15 -3
  4. package/lib/Apply.js.map +1 -1
  5. package/lib/Auth/AuthProvider.js +1 -1
  6. package/lib/Auth/AuthProvider.js.map +1 -1
  7. package/lib/Binding.d.ts.map +1 -1
  8. package/lib/Binding.js +12 -8
  9. package/lib/Binding.js.map +1 -1
  10. package/lib/Build/DevServer.d.ts +113 -0
  11. package/lib/Build/DevServer.d.ts.map +1 -0
  12. package/lib/Build/DevServer.js +212 -0
  13. package/lib/Build/DevServer.js.map +1 -0
  14. package/lib/Build/Local.d.ts +2 -0
  15. package/lib/Build/Local.d.ts.map +1 -0
  16. package/lib/Build/Local.js +4 -0
  17. package/lib/Build/Local.js.map +1 -0
  18. package/lib/Bundle/Bundle.js +1 -1
  19. package/lib/Bundle/Bundle.js.map +1 -1
  20. package/lib/Cloudflare/Access.js +1 -1
  21. package/lib/Cloudflare/Access.js.map +1 -1
  22. package/lib/Cloudflare/AiGateway/AiGateway.d.ts +1 -1
  23. package/lib/Cloudflare/AiGateway/AiGateway.js +1 -1
  24. package/lib/Cloudflare/Browser/Browser.d.ts +84 -12
  25. package/lib/Cloudflare/Browser/Browser.d.ts.map +1 -1
  26. package/lib/Cloudflare/Browser/Browser.js +80 -9
  27. package/lib/Cloudflare/Browser/Browser.js.map +1 -1
  28. package/lib/Cloudflare/Browser/BrowserBinding.d.ts +90 -16
  29. package/lib/Cloudflare/Browser/BrowserBinding.d.ts.map +1 -1
  30. package/lib/Cloudflare/Browser/BrowserBinding.js +41 -6
  31. package/lib/Cloudflare/Browser/BrowserBinding.js.map +1 -1
  32. package/lib/Cloudflare/Browser/index.d.ts +1 -1
  33. package/lib/Cloudflare/Browser/index.d.ts.map +1 -1
  34. package/lib/Cloudflare/Browser/index.js.map +1 -1
  35. package/lib/Cloudflare/Container/ContainerBinding.d.ts.map +1 -1
  36. package/lib/Cloudflare/Container/ContainerBinding.js +2 -4
  37. package/lib/Cloudflare/Container/ContainerBinding.js.map +1 -1
  38. package/lib/Cloudflare/Images/Images.d.ts +4 -3
  39. package/lib/Cloudflare/Images/Images.d.ts.map +1 -1
  40. package/lib/Cloudflare/Images/Images.js.map +1 -1
  41. package/lib/Cloudflare/Providers.d.ts +1 -1
  42. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  43. package/lib/Cloudflare/Providers.js +4 -1
  44. package/lib/Cloudflare/Providers.js.map +1 -1
  45. package/lib/Cloudflare/RateLimit/RateLimit.d.ts +4 -3
  46. package/lib/Cloudflare/RateLimit/RateLimit.d.ts.map +1 -1
  47. package/lib/Cloudflare/RateLimit/RateLimit.js.map +1 -1
  48. package/lib/Cloudflare/Website/StaticSite.d.ts +64 -12
  49. package/lib/Cloudflare/Website/StaticSite.d.ts.map +1 -1
  50. package/lib/Cloudflare/Website/StaticSite.js +80 -30
  51. package/lib/Cloudflare/Website/StaticSite.js.map +1 -1
  52. package/lib/Cloudflare/Website/Vite.d.ts +36 -7
  53. package/lib/Cloudflare/Website/Vite.d.ts.map +1 -1
  54. package/lib/Cloudflare/Website/Vite.js +26 -8
  55. package/lib/Cloudflare/Website/Vite.js.map +1 -1
  56. package/lib/Cloudflare/Workers/Assets.d.ts +10 -4
  57. package/lib/Cloudflare/Workers/Assets.d.ts.map +1 -1
  58. package/lib/Cloudflare/Workers/Assets.js +6 -6
  59. package/lib/Cloudflare/Workers/Assets.js.map +1 -1
  60. package/lib/Cloudflare/Workers/DynamicWorkerLoader.d.ts +4 -4
  61. package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
  62. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  63. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  64. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +45 -11
  65. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  66. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.d.ts +5 -4
  67. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.d.ts.map +1 -1
  68. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.js +4 -2
  69. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.js.map +1 -1
  70. package/lib/Cloudflare/Workers/RpcWorker.d.ts +3 -2
  71. package/lib/Cloudflare/Workers/RpcWorker.d.ts.map +1 -1
  72. package/lib/Cloudflare/Workers/RpcWorker.js +5 -3
  73. package/lib/Cloudflare/Workers/RpcWorker.js.map +1 -1
  74. package/lib/Cloudflare/Workers/VersionMetadata.d.ts +99 -0
  75. package/lib/Cloudflare/Workers/VersionMetadata.d.ts.map +1 -0
  76. package/lib/Cloudflare/Workers/VersionMetadata.js +65 -0
  77. package/lib/Cloudflare/Workers/VersionMetadata.js.map +1 -0
  78. package/lib/Cloudflare/Workers/VersionMetadataBinding.d.ts +35 -0
  79. package/lib/Cloudflare/Workers/VersionMetadataBinding.d.ts.map +1 -0
  80. package/lib/Cloudflare/Workers/VersionMetadataBinding.js +31 -0
  81. package/lib/Cloudflare/Workers/VersionMetadataBinding.js.map +1 -0
  82. package/lib/Cloudflare/Workers/Worker.d.ts +18 -18
  83. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  84. package/lib/Cloudflare/Workers/Worker.js +23 -37
  85. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  86. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -1
  87. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +7 -0
  88. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -1
  89. package/lib/Cloudflare/Workers/WorkerBinding.d.ts +2 -1
  90. package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -1
  91. package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -1
  92. package/lib/Cloudflare/Workers/Workflow.d.ts +8 -1
  93. package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
  94. package/lib/Cloudflare/Workers/Workflow.js +1 -0
  95. package/lib/Cloudflare/Workers/Workflow.js.map +1 -1
  96. package/lib/Cloudflare/Workers/WorkflowBridge.d.ts.map +1 -1
  97. package/lib/Cloudflare/Workers/WorkflowBridge.js +31 -2
  98. package/lib/Cloudflare/Workers/WorkflowBridge.js.map +1 -1
  99. package/lib/Cloudflare/Workers/index.d.ts +2 -0
  100. package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
  101. package/lib/Cloudflare/Workers/index.js +2 -0
  102. package/lib/Cloudflare/Workers/index.js.map +1 -1
  103. package/lib/Cloudflare/Zaraz/ZarazConfig.d.ts.map +1 -1
  104. package/lib/Diff.d.ts.map +1 -1
  105. package/lib/Diff.js +7 -0
  106. package/lib/Diff.js.map +1 -1
  107. package/lib/Drizzle/Postgres.d.ts +13 -12
  108. package/lib/Drizzle/Postgres.d.ts.map +1 -1
  109. package/lib/Drizzle/Postgres.js +13 -12
  110. package/lib/Drizzle/Postgres.js.map +1 -1
  111. package/lib/Drizzle/Schema.d.ts +1 -1
  112. package/lib/Drizzle/Schema.d.ts.map +1 -1
  113. package/lib/Drizzle/Schema.js +14 -6
  114. package/lib/Drizzle/Schema.js.map +1 -1
  115. package/lib/Local/RpcSerialization.js +5 -2
  116. package/lib/Local/RpcSerialization.js.map +1 -1
  117. package/lib/Platform.d.ts.map +1 -1
  118. package/lib/Platform.js +27 -25
  119. package/lib/Platform.js.map +1 -1
  120. package/lib/Resource.d.ts +1 -1
  121. package/lib/Resource.d.ts.map +1 -1
  122. package/lib/Resource.js +9 -12
  123. package/lib/Resource.js.map +1 -1
  124. package/lib/Runtime.d.ts +1 -1
  125. package/lib/Runtime.d.ts.map +1 -1
  126. package/lib/Runtime.js +3 -4
  127. package/lib/Runtime.js.map +1 -1
  128. package/lib/Util/effect.d.ts.map +1 -1
  129. package/lib/Util/effect.js +13 -15
  130. package/lib/Util/effect.js.map +1 -1
  131. package/lib/Util/poll.d.ts +0 -1
  132. package/lib/Util/poll.d.ts.map +1 -1
  133. package/lib/Util/poll.js +2 -2
  134. package/lib/Util/poll.js.map +1 -1
  135. package/lib/tsconfig.test.tsbuildinfo +1 -1
  136. package/package.json +20 -20
  137. package/src/Apply.ts +22 -3
  138. package/src/Auth/AuthProvider.ts +1 -1
  139. package/src/Binding.ts +45 -38
  140. package/src/Build/DevServer.ts +330 -0
  141. package/src/Build/Local.ts +4 -0
  142. package/src/Bundle/Bundle.ts +1 -1
  143. package/src/Cloudflare/Access.ts +1 -1
  144. package/src/Cloudflare/AiGateway/AiGateway.ts +1 -1
  145. package/src/Cloudflare/Browser/Browser.ts +84 -12
  146. package/src/Cloudflare/Browser/BrowserBinding.ts +205 -33
  147. package/src/Cloudflare/Browser/index.ts +15 -2
  148. package/src/Cloudflare/Container/ContainerBinding.ts +5 -10
  149. package/src/Cloudflare/Images/Images.ts +4 -3
  150. package/src/Cloudflare/Providers.ts +5 -0
  151. package/src/Cloudflare/RateLimit/RateLimit.ts +4 -3
  152. package/src/Cloudflare/Website/StaticSite.ts +154 -41
  153. package/src/Cloudflare/Website/Vite.ts +73 -25
  154. package/src/Cloudflare/Workers/Assets.ts +10 -8
  155. package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +4 -4
  156. package/src/Cloudflare/Workers/InferEnv.ts +14 -12
  157. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +54 -13
  158. package/src/Cloudflare/Workers/RpcDurableObjectNamespace.ts +11 -8
  159. package/src/Cloudflare/Workers/RpcWorker.ts +8 -7
  160. package/src/Cloudflare/Workers/VersionMetadata.ts +129 -0
  161. package/src/Cloudflare/Workers/VersionMetadataBinding.ts +82 -0
  162. package/src/Cloudflare/Workers/Worker.ts +60 -81
  163. package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +6 -0
  164. package/src/Cloudflare/Workers/WorkerBinding.ts +2 -0
  165. package/src/Cloudflare/Workers/Workflow.ts +12 -1
  166. package/src/Cloudflare/Workers/WorkflowBridge.ts +47 -6
  167. package/src/Cloudflare/Workers/index.ts +2 -0
  168. package/src/Diff.ts +7 -0
  169. package/src/Drizzle/Postgres.ts +13 -12
  170. package/src/Drizzle/Schema.ts +16 -6
  171. package/src/Local/RpcSerialization.ts +5 -2
  172. package/src/Platform.ts +36 -32
  173. package/src/Resource.ts +12 -13
  174. package/src/Runtime.ts +3 -7
  175. package/src/Util/effect.ts +13 -17
  176. package/src/Util/poll.ts +3 -3
@@ -32,9 +32,10 @@ type BindEffect = Effect.Effect<BrowserClient, never, BrowserBinding>;
32
32
  * Worker and returns the runtime {@link BrowserClient} — no separate
33
33
  * `.bind(...)` step required.
34
34
  *
35
- * The divergence is achieved via `[Symbol.iterator]`: the object is not an
36
- * `Effect` (so `InferEnv` resolves it to the native `Fetcher` in the `env`
37
- * position), but it is iterable as one when `yield*`-ed.
35
+ * The divergence is achieved via `[Symbol.iterator]`: the object is
36
+ * deliberately not an `Effect` (so `InferEnv` and the Worker `env` resolver
37
+ * keep it as the native `BrowserRun` rather than `yield*`-ing it), but it is
38
+ * iterable as one when `yield*`-ed.
38
39
  */
39
40
  export interface Browser {
40
41
  kind: BrowserTypeId;
@@ -53,7 +54,73 @@ export const isBrowser = (value: unknown): value is Browser =>
53
54
  * @binding
54
55
  *
55
56
  * @section Effect-style Worker (recommended)
56
- * @example Render a page title with managed browser cleanup
57
+ * @example Bind the runtime client and convert a page to Markdown
58
+ * Yielding the marker attaches the binding to the surrounding Worker and
59
+ * returns the runtime {@link BrowserClient}. Every `cf.BrowserRun` method is
60
+ * mirrored as an Effect, so no `Effect.tryPromise` wrapping is needed.
61
+ * ```typescript
62
+ * import * as Effect from "effect/Effect";
63
+ *
64
+ * Cloudflare.Worker(
65
+ * "BrowserWorker",
66
+ * { main: import.meta.filename },
67
+ * Effect.gen(function* () {
68
+ * const browser = yield* Cloudflare.Browser({ name: "BROWSER" });
69
+ *
70
+ * return {
71
+ * fetch: Effect.gen(function* () {
72
+ * return yield* browser.markdown({ url: "https://example.com" });
73
+ * }),
74
+ * };
75
+ * }).pipe(Effect.provide(Cloudflare.BrowserBindingLive)),
76
+ * );
77
+ * ```
78
+ *
79
+ * @section Quick Actions
80
+ * @example Render content, screenshot, PDF, and structured data
81
+ * JSON quick actions resolve to their parsed payload; binary actions
82
+ * (`screenshot`, `pdf`) resolve to a `Stream` of bytes. No `Promise` or
83
+ * `Response.json()` in sight.
84
+ * ```typescript
85
+ * import * as Effect from "effect/Effect";
86
+ * import * as Stream from "effect/Stream";
87
+ *
88
+ * const browser = yield* Cloudflare.Browser({ name: "BROWSER" });
89
+ * const url = "https://example.com";
90
+ *
91
+ * // HTML content — parsed payload, title lives in `meta`.
92
+ * const content = yield* browser.content({ url });
93
+ * const title = content.meta.title;
94
+ *
95
+ * // Scrape elements by CSS selector.
96
+ * yield* browser.scrape({ url, elements: [{ selector: "h1" }] });
97
+ *
98
+ * // Extract all links.
99
+ * const { result: links } = yield* browser.links({ url });
100
+ *
101
+ * // Binary actions stream bytes — collect or pipe them.
102
+ * const png = yield* browser.screenshot({ url }).pipe(Stream.runCollect);
103
+ * const pdf = yield* browser.pdf({ url }).pipe(Stream.runCollect);
104
+ *
105
+ * // AI-extracted structured data.
106
+ * yield* browser.json({ url, prompt: "Extract the page heading" });
107
+ * ```
108
+ *
109
+ * @example Call the generic `quickAction` directly
110
+ * The named methods are thin wrappers over `quickAction`, which mirrors
111
+ * `cf.BrowserRun["quickAction"]` one-to-one.
112
+ * ```typescript
113
+ * const browser = yield* Cloudflare.Browser({ name: "BROWSER" });
114
+ *
115
+ * const res = yield* browser.quickAction("snapshot", {
116
+ * url: "https://example.com",
117
+ * });
118
+ * ```
119
+ *
120
+ * @section Driving Puppeteer
121
+ * @example Use the raw `cf.BrowserRun` binding with `@cloudflare/puppeteer`
122
+ * `raw` hands you the underlying runtime binding. Puppeteer is promise-based,
123
+ * so this is the one place you reach for `Effect.tryPromise`.
57
124
  * ```typescript
58
125
  * import puppeteer from "@cloudflare/puppeteer";
59
126
  * import * as Effect from "effect/Effect";
@@ -62,25 +129,30 @@ export const isBrowser = (value: unknown): value is Browser =>
62
129
  * "BrowserWorker",
63
130
  * { main: import.meta.filename },
64
131
  * Effect.gen(function* () {
65
- * // Attaches the binding to this Worker AND returns the runtime client.
66
132
  * const browser = yield* Cloudflare.Browser({ name: "BROWSER" });
67
133
  *
68
134
  * return {
69
- * fetch: browser.withBrowser(puppeteer, (browser) =>
70
- * Effect.gen(function* () {
71
- * const page = yield* Effect.tryPromise(() => browser.newPage());
135
+ * fetch: Effect.gen(function* () {
136
+ * const binding = yield* browser.raw;
137
+ * const session = yield* Effect.tryPromise(() =>
138
+ * puppeteer.launch(binding),
139
+ * );
140
+ * try {
141
+ * const page = yield* Effect.tryPromise(() => session.newPage());
72
142
  * yield* Effect.tryPromise(() => page.goto("https://example.com"));
73
143
  * const title = yield* Effect.tryPromise(() => page.title());
74
144
  * return Response.json({ title });
75
- * }),
76
- * ),
145
+ * } finally {
146
+ * yield* Effect.promise(() => session.close());
147
+ * }
148
+ * }),
77
149
  * };
78
150
  * }).pipe(Effect.provide(Cloudflare.BrowserBindingLive)),
79
151
  * );
80
152
  * ```
81
153
  *
82
154
  * @section Worker binding metadata
83
- * @example
155
+ * @example Declare the binding on `env`
84
156
  * ```typescript
85
157
  * export const Worker = Cloudflare.Worker("Worker", {
86
158
  * main: "./src/worker.ts",
@@ -90,7 +162,7 @@ export const isBrowser = (value: unknown): value is Browser =>
90
162
  * });
91
163
  *
92
164
  * export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
93
- * // { BROWSER: Fetcher }
165
+ * // { BROWSER: BrowserRun }
94
166
  * ```
95
167
  *
96
168
  * @example Async-style worker with the raw runtime binding
@@ -2,8 +2,10 @@ import type * as cf from "@cloudflare/workers-types";
2
2
  import * as Data from "effect/Data";
3
3
  import * as Effect from "effect/Effect";
4
4
  import * as Layer from "effect/Layer";
5
+ import * as Stream from "effect/Stream";
5
6
  import * as Binding from "../../Binding.ts";
6
7
  import type { ResourceLike } from "../../Resource.ts";
8
+ import type { RuntimeContext } from "../../RuntimeContext.ts";
7
9
  import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
8
10
  import type { Browser as BrowserLike } from "./Browser.ts";
9
11
 
@@ -12,39 +14,143 @@ export class BrowserError extends Data.TaggedError("BrowserError")<{
12
14
  cause: unknown;
13
15
  }> {}
14
16
 
15
- export interface BrowserHandle {
16
- close(): Promise<void>;
17
- }
17
+ /** The `Response` type returned by the Cloudflare Browser Rendering binding. */
18
+ type BrowserResponse = Awaited<ReturnType<cf.BrowserRun["fetch"]>>;
18
19
 
19
- export interface BrowserPuppeteer<Browser extends BrowserHandle> {
20
- launch(binding: cf.Fetcher): Promise<Browser>;
21
- }
20
+ /** An Effect produced by a {@link BrowserClient} operation. */
21
+ type BrowserEffect<A> = Effect.Effect<A, BrowserError, RuntimeContext>;
22
+
23
+ /** A byte stream produced by a binary {@link BrowserClient} action. */
24
+ type BrowserByteStream = Stream.Stream<
25
+ Uint8Array,
26
+ BrowserError,
27
+ RuntimeContext
28
+ >;
29
+
30
+ // Quick action option types, re-exported so callers don't reach into the
31
+ // `@cloudflare/workers-types` namespace directly.
32
+ export type BrowserScreenshotOptions = cf.BrowserRunScreenshotOptions;
33
+ export type BrowserPDFOptions = cf.BrowserRunPDFOptions;
34
+ export type BrowserContentOptions = cf.BrowserRunContentOptions;
35
+ export type BrowserScrapeOptions = cf.BrowserRunScrapeOptions;
36
+ export type BrowserLinksOptions = cf.BrowserRunLinksOptions;
37
+ export type BrowserSnapshotOptions = cf.BrowserRunSnapshotOptions;
38
+ export type BrowserJsonOptions = cf.BrowserRunJsonOptions;
39
+ export type BrowserMarkdownOptions = cf.BrowserRunMarkdownOptions;
40
+
41
+ // Quick action success payloads.
42
+ export type BrowserContentResult = cf.BrowserRunContentSuccessResponse;
43
+ export type BrowserScrapeResult = cf.BrowserRunScrapeSuccessResponse;
44
+ export type BrowserLinksResult = cf.BrowserRunLinksSuccessResponse;
45
+ export type BrowserSnapshotResult = cf.BrowserRunSnapshotSuccessResponse;
46
+ export type BrowserJsonResult = cf.BrowserRunJsonSuccessResponse;
47
+ export type BrowserMarkdownResult = cf.BrowserRunMarkdownSuccessResponse;
48
+ export type BrowserErrorResponse = cf.BrowserRunErrorResponse;
22
49
 
23
50
  /**
24
51
  * Effect-native client for a Cloudflare Browser Rendering binding.
25
52
  *
26
- * Browser Rendering's Workers binding is consumed by `@cloudflare/puppeteer`.
27
- * Alchemy keeps Puppeteer as a caller-provided dependency while wrapping
28
- * launch and cleanup in Effects.
53
+ * Mirrors the runtime {@link cf.BrowserRun} binding, translating its shapes
54
+ * into Effect-native ones: JSON quick actions resolve to their parsed success
55
+ * payload, and binary actions (`screenshot`, `pdf`) resolve to a `Stream` of
56
+ * the response bytes. Non-success responses fail with {@link BrowserError}, so
57
+ * callers never touch a `Promise` or `Response.json()` themselves. Use
58
+ * `Cloudflare.Browser.bind(browser)` (or `yield* Cloudflare.Browser(...)`)
59
+ * inside a Worker's init phase to obtain it.
60
+ *
61
+ * The {@link raw} accessor and {@link fetch} are the only promise-shaped
62
+ * escape hatches — they exist for libraries like `@cloudflare/puppeteer` that
63
+ * consume the binding directly.
29
64
  */
30
65
  export interface BrowserClient {
31
66
  /**
32
67
  * Effect resolving to the raw Cloudflare Browser Rendering runtime binding.
68
+ *
69
+ * Pass it to `@cloudflare/puppeteer`'s `puppeteer.launch(binding)` to drive a
70
+ * full browser session.
71
+ */
72
+ raw: Effect.Effect<cf.BrowserRun, never, RuntimeContext>;
73
+ /**
74
+ * Send a raw HTTP request to the Browser Run API and resolve to the raw
75
+ * `Response`. Used by libraries like `@cloudflare/puppeteer` to acquire and
76
+ * connect to a browser instance.
77
+ */
78
+ fetch(
79
+ ...args: Parameters<cf.BrowserRun["fetch"]>
80
+ ): BrowserEffect<BrowserResponse>;
81
+ /**
82
+ * Run a Browser Run quick action, resolving to the parsed success payload
83
+ * (or a byte `Stream` for binary actions). Mirrors
84
+ * `cf.BrowserRun["quickAction"]`; the {@link screenshot}, {@link pdf},
85
+ * {@link content}, {@link scrape}, {@link links}, {@link snapshot},
86
+ * {@link json}, and {@link markdown} methods are thin wrappers over the
87
+ * corresponding action.
88
+ */
89
+ quickAction(
90
+ action: "screenshot",
91
+ options: BrowserScreenshotOptions,
92
+ ): BrowserByteStream;
93
+ quickAction(action: "pdf", options: BrowserPDFOptions): BrowserByteStream;
94
+ quickAction(
95
+ action: "content",
96
+ options: BrowserContentOptions,
97
+ ): BrowserEffect<BrowserContentResult>;
98
+ quickAction(
99
+ action: "scrape",
100
+ options: BrowserScrapeOptions,
101
+ ): BrowserEffect<BrowserScrapeResult>;
102
+ quickAction(
103
+ action: "links",
104
+ options: BrowserLinksOptions,
105
+ ): BrowserEffect<BrowserLinksResult>;
106
+ quickAction(
107
+ action: "snapshot",
108
+ options: BrowserSnapshotOptions,
109
+ ): BrowserEffect<BrowserSnapshotResult>;
110
+ quickAction(
111
+ action: "json",
112
+ options: BrowserJsonOptions,
113
+ ): BrowserEffect<BrowserJsonResult>;
114
+ quickAction(
115
+ action: "markdown",
116
+ options: BrowserMarkdownOptions,
117
+ ): BrowserEffect<BrowserMarkdownResult>;
118
+ /**
119
+ * Take a screenshot of a web page, streaming the raw image bytes.
120
+ */
121
+ screenshot(options: BrowserScreenshotOptions): BrowserByteStream;
122
+ /**
123
+ * Generate a PDF of a web page, streaming the raw PDF bytes.
124
+ */
125
+ pdf(options: BrowserPDFOptions): BrowserByteStream;
126
+ /**
127
+ * Get the HTML content of a web page.
128
+ */
129
+ content(options: BrowserContentOptions): BrowserEffect<BrowserContentResult>;
130
+ /**
131
+ * Scrape elements from a web page by CSS selector.
132
+ */
133
+ scrape(options: BrowserScrapeOptions): BrowserEffect<BrowserScrapeResult>;
134
+ /**
135
+ * Extract all links from a web page.
136
+ */
137
+ links(options: BrowserLinksOptions): BrowserEffect<BrowserLinksResult>;
138
+ /**
139
+ * Get both the HTML content and a base64-encoded screenshot of a web page.
33
140
  */
34
- raw: Effect.Effect<cf.Fetcher, never, WorkerEnvironment>;
141
+ snapshot(
142
+ options: BrowserSnapshotOptions,
143
+ ): BrowserEffect<BrowserSnapshotResult>;
35
144
  /**
36
- * Launch a Browser Rendering session through `@cloudflare/puppeteer`.
145
+ * Extract structured JSON data from a web page using AI.
37
146
  */
38
- launch<Browser extends BrowserHandle>(
39
- puppeteer: BrowserPuppeteer<Browser>,
40
- ): Effect.Effect<Browser, BrowserError, WorkerEnvironment>;
147
+ json(options: BrowserJsonOptions): BrowserEffect<BrowserJsonResult>;
41
148
  /**
42
- * Launch a browser, run an Effect with it, and close it afterward.
149
+ * Convert a web page to Markdown.
43
150
  */
44
- withBrowser<Browser extends BrowserHandle, A, E, R>(
45
- puppeteer: BrowserPuppeteer<Browser>,
46
- use: (browser: Browser) => Effect.Effect<A, E, R>,
47
- ): Effect.Effect<A, BrowserError | E, WorkerEnvironment | R>;
151
+ markdown(
152
+ options: BrowserMarkdownOptions,
153
+ ): BrowserEffect<BrowserMarkdownResult>;
48
154
  }
49
155
 
50
156
  export class BrowserBinding extends Binding.Service<
@@ -56,14 +162,14 @@ export const BrowserBindingLive = Layer.effect(
56
162
  BrowserBinding,
57
163
  Effect.gen(function* () {
58
164
  const Policy = yield* BrowserBindingPolicy;
165
+ const env = yield* WorkerEnvironment;
59
166
 
60
167
  return Effect.fn(function* (browser: BrowserLike) {
61
168
  yield* Policy(browser);
62
- // Cloudflare exposes Browser Rendering as a service-style binding for
63
- // @cloudflare/puppeteer; workers-types has no narrower interface.
64
- const raw = WorkerEnvironment.useSync(
65
- (env) => (env as Record<string, cf.Fetcher>)[browser.name]!,
66
- );
169
+ const raw: Effect.Effect<cf.BrowserRun, never, RuntimeContext> =
170
+ Effect.sync(
171
+ () => (env as Record<string, cf.BrowserRun>)[browser.name]!,
172
+ );
67
173
  return makeBrowserClient(raw);
68
174
  });
69
175
  }),
@@ -106,23 +212,89 @@ const tryPromise = <T>(fn: () => Promise<T>): Effect.Effect<T, BrowserError> =>
106
212
  }),
107
213
  });
108
214
 
215
+ /** Actions whose successful response is raw binary rather than JSON. */
216
+ const BINARY_ACTIONS = new Set(["screenshot", "pdf"]);
217
+
218
+ /** Build a {@link BrowserError} from a non-success Browser Run response. */
219
+ const failResponse = (
220
+ action: string,
221
+ response: BrowserResponse,
222
+ ): Effect.Effect<never, BrowserError> =>
223
+ tryPromise(() => response.text()).pipe(
224
+ Effect.flatMap((body) => {
225
+ let cause: unknown = body;
226
+ try {
227
+ cause = JSON.parse(body);
228
+ } catch {
229
+ // keep the raw text as the cause
230
+ }
231
+ const message =
232
+ (cause as cf.BrowserRunErrorResponse | undefined)?.errors?.[0]
233
+ ?.message ??
234
+ `Browser Rendering '${action}' failed with status ${response.status}`;
235
+ return Effect.fail(new BrowserError({ message, cause }));
236
+ }),
237
+ );
238
+
109
239
  /** @internal */
110
240
  export const makeBrowserClient = (
111
- raw: Effect.Effect<cf.Fetcher, never, WorkerEnvironment>,
241
+ raw: Effect.Effect<cf.BrowserRun, never, RuntimeContext>,
112
242
  ): BrowserClient => {
113
- const launch = <Browser extends BrowserHandle>(
114
- puppeteer: BrowserPuppeteer<Browser>,
115
- ) =>
243
+ const respond = (
244
+ action: string,
245
+ options: unknown,
246
+ ): Effect.Effect<BrowserResponse, BrowserError, RuntimeContext> =>
116
247
  raw.pipe(
117
- Effect.flatMap((binding) => tryPromise(() => puppeteer.launch(binding))),
248
+ Effect.flatMap((binding) =>
249
+ tryPromise(() => binding.quickAction(action as any, options as any)),
250
+ ),
251
+ Effect.flatMap((response) =>
252
+ response.ok ? Effect.succeed(response) : failResponse(action, response),
253
+ ),
254
+ );
255
+
256
+ const jsonAction = <T>(action: string, options: unknown): BrowserEffect<T> =>
257
+ respond(action, options).pipe(
258
+ Effect.flatMap((response) =>
259
+ tryPromise(() => response.json() as Promise<T>),
260
+ ),
118
261
  );
119
262
 
263
+ const streamAction = (action: string, options: unknown): BrowserByteStream =>
264
+ respond(action, options).pipe(
265
+ Effect.map((response) =>
266
+ Stream.fromReadableStream({
267
+ evaluate: () =>
268
+ response.body as any as ReadableStream<Uint8Array<ArrayBufferLike>>,
269
+ onError: (cause) =>
270
+ new BrowserError({
271
+ message: `Browser Rendering '${action}' stream failed`,
272
+ cause,
273
+ }),
274
+ }),
275
+ ),
276
+ Stream.unwrap,
277
+ );
278
+
279
+ const quickAction = ((action: string, options: unknown) =>
280
+ BINARY_ACTIONS.has(action)
281
+ ? streamAction(action, options)
282
+ : jsonAction(action, options)) as BrowserClient["quickAction"];
283
+
120
284
  return {
121
285
  raw,
122
- launch,
123
- withBrowser: (puppeteer, use) =>
124
- Effect.acquireUseRelease(launch(puppeteer), use, (browser) =>
125
- tryPromise(() => browser.close()),
286
+ fetch: (...args) =>
287
+ raw.pipe(
288
+ Effect.flatMap((binding) => tryPromise(() => binding.fetch(...args))),
126
289
  ),
290
+ quickAction,
291
+ screenshot: (options) => streamAction("screenshot", options),
292
+ pdf: (options) => streamAction("pdf", options),
293
+ content: (options) => jsonAction("content", options),
294
+ scrape: (options) => jsonAction("scrape", options),
295
+ links: (options) => jsonAction("links", options),
296
+ snapshot: (options) => jsonAction("snapshot", options),
297
+ json: (options) => jsonAction("json", options),
298
+ markdown: (options) => jsonAction("markdown", options),
127
299
  } satisfies BrowserClient;
128
300
  };
@@ -6,6 +6,19 @@ export {
6
6
  BrowserBindingPolicyLive,
7
7
  BrowserError,
8
8
  type BrowserClient,
9
- type BrowserHandle,
10
- type BrowserPuppeteer,
9
+ type BrowserScreenshotOptions,
10
+ type BrowserPDFOptions,
11
+ type BrowserContentOptions,
12
+ type BrowserScrapeOptions,
13
+ type BrowserLinksOptions,
14
+ type BrowserSnapshotOptions,
15
+ type BrowserJsonOptions,
16
+ type BrowserMarkdownOptions,
17
+ type BrowserContentResult,
18
+ type BrowserScrapeResult,
19
+ type BrowserLinksResult,
20
+ type BrowserSnapshotResult,
21
+ type BrowserJsonResult,
22
+ type BrowserMarkdownResult,
23
+ type BrowserErrorResponse,
11
24
  } from "./BrowserBinding.ts";
@@ -18,16 +18,11 @@ export const bindContainer = Effect.fnUntraced(function* <Shape, Req = never>(
18
18
  ) {
19
19
  const namespace = yield* DurableObjectNamespace;
20
20
 
21
- const container =
22
- "asEffect" in containerEff
23
- ? yield* containerEff as any as Effect.Effect<
24
- ContainerApplication & Rpc<Shape>
25
- >
26
- : Effect.isEffect(containerEff)
27
- ? yield* containerEff as unknown as Effect.Effect<
28
- ContainerApplication & Rpc<Shape>
29
- >
30
- : containerEff;
21
+ const container = Effect.isEffect(containerEff)
22
+ ? yield* containerEff as unknown as Effect.Effect<
23
+ ContainerApplication & Rpc<Shape>
24
+ >
25
+ : containerEff;
31
26
 
32
27
  yield* container.bind`${namespace}`({
33
28
  durableObjects: {
@@ -32,9 +32,10 @@ type BindEffect = Effect.Effect<ImagesClient, never, ImagesBinding>;
32
32
  * Worker and returns the runtime {@link ImagesClient} — no separate `.bind(...)`
33
33
  * step required.
34
34
  *
35
- * The divergence is achieved via `[Symbol.iterator]`: the object is not an
36
- * `Effect` (so `InferEnv` resolves it to the native `ImagesBinding` in the
37
- * `env` position), but it is iterable as one when `yield*`-ed.
35
+ * The divergence is achieved via `[Symbol.iterator]`: the object is
36
+ * deliberately not an `Effect` (so `InferEnv` and the Worker `env` resolver
37
+ * keep it as the native `ImagesBinding` rather than `yield*`-ing it), but it
38
+ * is iterable as one when `yield*`-ed.
38
39
  */
39
40
  export interface Images {
40
41
  kind: ImagesTypeId;
@@ -8,6 +8,7 @@ import * as Schedule from "effect/Schedule";
8
8
  import { CredentialsStoreLive } from "../Auth/Credentials.ts";
9
9
  import { ProfileLive } from "../Auth/Profile.ts";
10
10
  import { Command } from "../Build/Command.ts";
11
+ import { DevServer, DevServerProvider } from "../Build/DevServer.ts";
11
12
  import * as Build from "../Build/index.ts";
12
13
  import { KeyPair, KeyPairProvider } from "../KeyPair.ts";
13
14
  import * as Provider from "../Provider.ts";
@@ -67,6 +68,7 @@ export const providers = () =>
67
68
  Containers.Container,
68
69
  D1.D1ConnectionPolicy,
69
70
  D1.D1Database,
71
+ DevServer,
70
72
  Dns.DnsReadPolicy,
71
73
  Dns.DnsReadWritePolicy,
72
74
  Dns.DnsWritePolicy,
@@ -103,6 +105,7 @@ export const providers = () =>
103
105
  Workers.BindWorkerPolicy,
104
106
  Workers.CronEventSourcePolicy,
105
107
  Workers.FetchPolicy,
108
+ Workers.VersionMetadataBindingPolicy,
106
109
  Workers.Worker,
107
110
  Workflows.WorkflowResource,
108
111
  Zaraz.ZarazConfig,
@@ -121,6 +124,7 @@ export const providers = () =>
121
124
  Containers.ContainerProvider(),
122
125
  D1.D1ConnectionPolicyLive,
123
126
  D1.DatabaseProvider(),
127
+ DevServerProvider(),
124
128
  Dns.DnsReadPolicyLive,
125
129
  Dns.DnsReadWritePolicyLive,
126
130
  Dns.DnsWritePolicyLive,
@@ -155,6 +159,7 @@ export const providers = () =>
155
159
  Workers.BindWorkerPolicyLive,
156
160
  Workers.CronEventSourcePolicyLive,
157
161
  Workers.FetchPolicyLive,
162
+ Workers.VersionMetadataBindingPolicyLive,
158
163
  Workers.WorkerProvider(),
159
164
  Workflows.WorkflowProvider(),
160
165
  Zaraz.ZarazConfigProvider(),
@@ -52,9 +52,10 @@ type BindEffect = Effect.Effect<RateLimitClient, never, RateLimitBinding>;
52
52
  * Worker and returns the runtime {@link RateLimitClient} — no separate
53
53
  * `.bind(...)` step required.
54
54
  *
55
- * The divergence is achieved via `[Symbol.iterator]`: the object is not an
56
- * `Effect` (so `InferEnv` resolves it to the native `cf.RateLimit` in the
57
- * `env` position), but it is iterable as one when `yield*`-ed.
55
+ * The divergence is achieved via `[Symbol.iterator]`: the object is
56
+ * deliberately not an `Effect` (so `InferEnv` and the Worker `env` resolver
57
+ * keep it as the native `cf.RateLimit` rather than `yield*`-ing it), but it is
58
+ * iterable as one when `yield*`-ed.
58
59
  */
59
60
  export interface RateLimit {
60
61
  kind: RateLimitTypeId;