alchemy 2.0.0-beta.50 → 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 (254) hide show
  1. package/bin/exec.js +133 -135
  2. package/bin/exec.js.map +1 -1
  3. package/lib/AWS/Lambda/BucketEventSource.d.ts.map +1 -1
  4. package/lib/AWS/Lambda/BucketEventSource.js +13 -2
  5. package/lib/AWS/Lambda/BucketEventSource.js.map +1 -1
  6. package/lib/AWS/S3/Bucket.d.ts.map +1 -1
  7. package/lib/AWS/S3/Bucket.js +49 -0
  8. package/lib/AWS/S3/Bucket.js.map +1 -1
  9. package/lib/AWS/S3/BucketNotifications.d.ts +10 -0
  10. package/lib/AWS/S3/BucketNotifications.d.ts.map +1 -1
  11. package/lib/AWS/S3/BucketNotifications.js.map +1 -1
  12. package/lib/Apply.js +15 -3
  13. package/lib/Apply.js.map +1 -1
  14. package/lib/Auth/AuthProvider.js +1 -1
  15. package/lib/Auth/AuthProvider.js.map +1 -1
  16. package/lib/Binding.d.ts.map +1 -1
  17. package/lib/Binding.js +12 -8
  18. package/lib/Binding.js.map +1 -1
  19. package/lib/Build/DevServer.d.ts +113 -0
  20. package/lib/Build/DevServer.d.ts.map +1 -0
  21. package/lib/Build/DevServer.js +212 -0
  22. package/lib/Build/DevServer.js.map +1 -0
  23. package/lib/Build/Local.d.ts +2 -0
  24. package/lib/Build/Local.d.ts.map +1 -0
  25. package/lib/Build/Local.js +4 -0
  26. package/lib/Build/Local.js.map +1 -0
  27. package/lib/Bundle/Bundle.js +1 -1
  28. package/lib/Bundle/Bundle.js.map +1 -1
  29. package/lib/Cli/commands/cloudflare.d.ts +1 -1
  30. package/lib/Cloudflare/Access.js +1 -1
  31. package/lib/Cloudflare/Access.js.map +1 -1
  32. package/lib/Cloudflare/AiGateway/AiGateway.d.ts +1 -1
  33. package/lib/Cloudflare/AiGateway/AiGateway.js +1 -1
  34. package/lib/Cloudflare/AiGateway/AiGatewayBinding.d.ts +1 -1
  35. package/lib/Cloudflare/AiGateway/AiGatewayBinding.d.ts.map +1 -1
  36. package/lib/Cloudflare/AiGateway/AiGatewayBinding.js +6 -2
  37. package/lib/Cloudflare/AiGateway/AiGatewayBinding.js.map +1 -1
  38. package/lib/Cloudflare/Browser/Browser.d.ts +84 -12
  39. package/lib/Cloudflare/Browser/Browser.d.ts.map +1 -1
  40. package/lib/Cloudflare/Browser/Browser.js +80 -9
  41. package/lib/Cloudflare/Browser/Browser.js.map +1 -1
  42. package/lib/Cloudflare/Browser/BrowserBinding.d.ts +90 -16
  43. package/lib/Cloudflare/Browser/BrowserBinding.d.ts.map +1 -1
  44. package/lib/Cloudflare/Browser/BrowserBinding.js +41 -6
  45. package/lib/Cloudflare/Browser/BrowserBinding.js.map +1 -1
  46. package/lib/Cloudflare/Browser/index.d.ts +1 -1
  47. package/lib/Cloudflare/Browser/index.d.ts.map +1 -1
  48. package/lib/Cloudflare/Browser/index.js.map +1 -1
  49. package/lib/Cloudflare/Container/ContainerBinding.d.ts.map +1 -1
  50. package/lib/Cloudflare/Container/ContainerBinding.js +2 -4
  51. package/lib/Cloudflare/Container/ContainerBinding.js.map +1 -1
  52. package/lib/Cloudflare/Images/Images.d.ts +4 -3
  53. package/lib/Cloudflare/Images/Images.d.ts.map +1 -1
  54. package/lib/Cloudflare/Images/Images.js.map +1 -1
  55. package/lib/Cloudflare/Providers.d.ts +1 -1
  56. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  57. package/lib/Cloudflare/Providers.js +6 -1
  58. package/lib/Cloudflare/Providers.js.map +1 -1
  59. package/lib/Cloudflare/RateLimit/RateLimit.d.ts +4 -3
  60. package/lib/Cloudflare/RateLimit/RateLimit.d.ts.map +1 -1
  61. package/lib/Cloudflare/RateLimit/RateLimit.js.map +1 -1
  62. package/lib/Cloudflare/Ruleset/Ruleset.d.ts +82 -0
  63. package/lib/Cloudflare/Ruleset/Ruleset.d.ts.map +1 -0
  64. package/lib/Cloudflare/Ruleset/Ruleset.js +126 -0
  65. package/lib/Cloudflare/Ruleset/Ruleset.js.map +1 -0
  66. package/lib/Cloudflare/Ruleset/index.d.ts +2 -0
  67. package/lib/Cloudflare/Ruleset/index.d.ts.map +1 -0
  68. package/lib/Cloudflare/Ruleset/index.js +2 -0
  69. package/lib/Cloudflare/Ruleset/index.js.map +1 -0
  70. package/lib/Cloudflare/SecretsStore/Secret.d.ts +1 -0
  71. package/lib/Cloudflare/SecretsStore/Secret.d.ts.map +1 -1
  72. package/lib/Cloudflare/SecretsStore/Secret.js +2 -0
  73. package/lib/Cloudflare/SecretsStore/Secret.js.map +1 -1
  74. package/lib/Cloudflare/StateStore/State.d.ts +1 -1
  75. package/lib/Cloudflare/StateStore/State.d.ts.map +1 -1
  76. package/lib/Cloudflare/Website/StaticSite.d.ts +64 -12
  77. package/lib/Cloudflare/Website/StaticSite.d.ts.map +1 -1
  78. package/lib/Cloudflare/Website/StaticSite.js +80 -30
  79. package/lib/Cloudflare/Website/StaticSite.js.map +1 -1
  80. package/lib/Cloudflare/Website/Vite.d.ts +36 -7
  81. package/lib/Cloudflare/Website/Vite.d.ts.map +1 -1
  82. package/lib/Cloudflare/Website/Vite.js +26 -8
  83. package/lib/Cloudflare/Website/Vite.js.map +1 -1
  84. package/lib/Cloudflare/Workers/Assets.d.ts +10 -4
  85. package/lib/Cloudflare/Workers/Assets.d.ts.map +1 -1
  86. package/lib/Cloudflare/Workers/Assets.js +6 -6
  87. package/lib/Cloudflare/Workers/Assets.js.map +1 -1
  88. package/lib/Cloudflare/Workers/DurableObjectChatPersistence.d.ts +69 -0
  89. package/lib/Cloudflare/Workers/DurableObjectChatPersistence.d.ts.map +1 -0
  90. package/lib/Cloudflare/Workers/DurableObjectChatPersistence.js +104 -0
  91. package/lib/Cloudflare/Workers/DurableObjectChatPersistence.js.map +1 -0
  92. package/lib/Cloudflare/Workers/DynamicWorkerLoader.d.ts +4 -4
  93. package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
  94. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  95. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  96. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +45 -11
  97. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  98. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.d.ts +5 -4
  99. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.d.ts.map +1 -1
  100. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.js +4 -2
  101. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.js.map +1 -1
  102. package/lib/Cloudflare/Workers/RpcWorker.d.ts +3 -2
  103. package/lib/Cloudflare/Workers/RpcWorker.d.ts.map +1 -1
  104. package/lib/Cloudflare/Workers/RpcWorker.js +5 -3
  105. package/lib/Cloudflare/Workers/RpcWorker.js.map +1 -1
  106. package/lib/Cloudflare/Workers/VersionMetadata.d.ts +99 -0
  107. package/lib/Cloudflare/Workers/VersionMetadata.d.ts.map +1 -0
  108. package/lib/Cloudflare/Workers/VersionMetadata.js +65 -0
  109. package/lib/Cloudflare/Workers/VersionMetadata.js.map +1 -0
  110. package/lib/Cloudflare/Workers/VersionMetadataBinding.d.ts +35 -0
  111. package/lib/Cloudflare/Workers/VersionMetadataBinding.d.ts.map +1 -0
  112. package/lib/Cloudflare/Workers/VersionMetadataBinding.js +31 -0
  113. package/lib/Cloudflare/Workers/VersionMetadataBinding.js.map +1 -0
  114. package/lib/Cloudflare/Workers/Worker.d.ts +20 -19
  115. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  116. package/lib/Cloudflare/Workers/Worker.js +23 -37
  117. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  118. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -1
  119. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +16 -0
  120. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -1
  121. package/lib/Cloudflare/Workers/WorkerBinding.d.ts +3 -1
  122. package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -1
  123. package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -1
  124. package/lib/Cloudflare/Workers/Workflow.d.ts +8 -1
  125. package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
  126. package/lib/Cloudflare/Workers/Workflow.js +1 -0
  127. package/lib/Cloudflare/Workers/Workflow.js.map +1 -1
  128. package/lib/Cloudflare/Workers/WorkflowBridge.d.ts.map +1 -1
  129. package/lib/Cloudflare/Workers/WorkflowBridge.js +31 -2
  130. package/lib/Cloudflare/Workers/WorkflowBridge.js.map +1 -1
  131. package/lib/Cloudflare/Workers/index.d.ts +3 -0
  132. package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
  133. package/lib/Cloudflare/Workers/index.js +3 -0
  134. package/lib/Cloudflare/Workers/index.js.map +1 -1
  135. package/lib/Cloudflare/Zaraz/ZarazConfig.d.ts.map +1 -1
  136. package/lib/Cloudflare/index.d.ts +1 -0
  137. package/lib/Cloudflare/index.d.ts.map +1 -1
  138. package/lib/Cloudflare/index.js +1 -0
  139. package/lib/Cloudflare/index.js.map +1 -1
  140. package/lib/Deploy.d.ts +3 -2
  141. package/lib/Deploy.d.ts.map +1 -1
  142. package/lib/Deploy.js.map +1 -1
  143. package/lib/Destroy.d.ts +3 -2
  144. package/lib/Destroy.d.ts.map +1 -1
  145. package/lib/Destroy.js.map +1 -1
  146. package/lib/Diff.d.ts.map +1 -1
  147. package/lib/Diff.js +7 -0
  148. package/lib/Diff.js.map +1 -1
  149. package/lib/Drizzle/Postgres.d.ts +13 -12
  150. package/lib/Drizzle/Postgres.d.ts.map +1 -1
  151. package/lib/Drizzle/Postgres.js +13 -12
  152. package/lib/Drizzle/Postgres.js.map +1 -1
  153. package/lib/Drizzle/Schema.d.ts +1 -1
  154. package/lib/Drizzle/Schema.d.ts.map +1 -1
  155. package/lib/Drizzle/Schema.js +20 -9
  156. package/lib/Drizzle/Schema.js.map +1 -1
  157. package/lib/Local/RpcSerialization.js +5 -2
  158. package/lib/Local/RpcSerialization.js.map +1 -1
  159. package/lib/Output.d.ts.map +1 -1
  160. package/lib/Output.js +11 -10
  161. package/lib/Output.js.map +1 -1
  162. package/lib/Plan.d.ts.map +1 -1
  163. package/lib/Plan.js +5 -1
  164. package/lib/Plan.js.map +1 -1
  165. package/lib/Planetscale/MySQL/MySQLDatabase.d.ts +0 -2
  166. package/lib/Planetscale/MySQL/MySQLDatabase.d.ts.map +1 -1
  167. package/lib/Planetscale/MySQL/MySQLDatabase.js +1 -3
  168. package/lib/Planetscale/MySQL/MySQLDatabase.js.map +1 -1
  169. package/lib/Planetscale/Postgres/PostgresDatabase.d.ts +0 -2
  170. package/lib/Planetscale/Postgres/PostgresDatabase.d.ts.map +1 -1
  171. package/lib/Planetscale/Postgres/PostgresDatabase.js +1 -3
  172. package/lib/Planetscale/Postgres/PostgresDatabase.js.map +1 -1
  173. package/lib/Platform.d.ts +4 -4
  174. package/lib/Platform.d.ts.map +1 -1
  175. package/lib/Platform.js +27 -25
  176. package/lib/Platform.js.map +1 -1
  177. package/lib/Resource.d.ts +7 -1
  178. package/lib/Resource.d.ts.map +1 -1
  179. package/lib/Resource.js +11 -12
  180. package/lib/Resource.js.map +1 -1
  181. package/lib/Runtime.d.ts +1 -1
  182. package/lib/Runtime.d.ts.map +1 -1
  183. package/lib/Runtime.js +3 -4
  184. package/lib/Runtime.js.map +1 -1
  185. package/lib/Stack.d.ts +5 -4
  186. package/lib/Stack.d.ts.map +1 -1
  187. package/lib/Stack.js.map +1 -1
  188. package/lib/Test/Core.d.ts +2 -2
  189. package/lib/Test/Core.d.ts.map +1 -1
  190. package/lib/Util/effect.d.ts.map +1 -1
  191. package/lib/Util/effect.js +13 -15
  192. package/lib/Util/effect.js.map +1 -1
  193. package/lib/Util/poll.d.ts +0 -1
  194. package/lib/Util/poll.d.ts.map +1 -1
  195. package/lib/Util/poll.js +2 -2
  196. package/lib/Util/poll.js.map +1 -1
  197. package/lib/tsconfig.test.tsbuildinfo +1 -1
  198. package/package.json +20 -20
  199. package/src/AWS/Lambda/BucketEventSource.ts +16 -1
  200. package/src/AWS/S3/Bucket.ts +78 -0
  201. package/src/AWS/S3/BucketNotifications.ts +10 -0
  202. package/src/Apply.ts +22 -3
  203. package/src/Auth/AuthProvider.ts +1 -1
  204. package/src/Binding.ts +45 -38
  205. package/src/Build/DevServer.ts +330 -0
  206. package/src/Build/Local.ts +4 -0
  207. package/src/Bundle/Bundle.ts +1 -1
  208. package/src/Cloudflare/Access.ts +1 -1
  209. package/src/Cloudflare/AiGateway/AiGateway.ts +1 -1
  210. package/src/Cloudflare/AiGateway/AiGatewayBinding.ts +8 -3
  211. package/src/Cloudflare/Browser/Browser.ts +84 -12
  212. package/src/Cloudflare/Browser/BrowserBinding.ts +205 -33
  213. package/src/Cloudflare/Browser/index.ts +15 -2
  214. package/src/Cloudflare/Container/ContainerBinding.ts +5 -10
  215. package/src/Cloudflare/Images/Images.ts +4 -3
  216. package/src/Cloudflare/Providers.ts +8 -0
  217. package/src/Cloudflare/RateLimit/RateLimit.ts +4 -3
  218. package/src/Cloudflare/Ruleset/Ruleset.ts +223 -0
  219. package/src/Cloudflare/Ruleset/index.ts +1 -0
  220. package/src/Cloudflare/SecretsStore/Secret.ts +4 -0
  221. package/src/Cloudflare/Website/StaticSite.ts +154 -41
  222. package/src/Cloudflare/Website/Vite.ts +73 -25
  223. package/src/Cloudflare/Workers/Assets.ts +10 -8
  224. package/src/Cloudflare/Workers/DurableObjectChatPersistence.ts +131 -0
  225. package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +4 -4
  226. package/src/Cloudflare/Workers/InferEnv.ts +14 -12
  227. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +54 -13
  228. package/src/Cloudflare/Workers/RpcDurableObjectNamespace.ts +11 -8
  229. package/src/Cloudflare/Workers/RpcWorker.ts +8 -7
  230. package/src/Cloudflare/Workers/VersionMetadata.ts +129 -0
  231. package/src/Cloudflare/Workers/VersionMetadataBinding.ts +82 -0
  232. package/src/Cloudflare/Workers/Worker.ts +66 -82
  233. package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +14 -0
  234. package/src/Cloudflare/Workers/WorkerBinding.ts +4 -0
  235. package/src/Cloudflare/Workers/Workflow.ts +12 -1
  236. package/src/Cloudflare/Workers/WorkflowBridge.ts +47 -6
  237. package/src/Cloudflare/Workers/index.ts +3 -0
  238. package/src/Cloudflare/index.ts +1 -0
  239. package/src/Deploy.ts +2 -1
  240. package/src/Destroy.ts +2 -1
  241. package/src/Diff.ts +7 -0
  242. package/src/Drizzle/Postgres.ts +13 -12
  243. package/src/Drizzle/Schema.ts +27 -9
  244. package/src/Local/RpcSerialization.ts +5 -2
  245. package/src/Output.ts +12 -10
  246. package/src/Plan.ts +5 -1
  247. package/src/Planetscale/MySQL/MySQLDatabase.ts +1 -6
  248. package/src/Planetscale/Postgres/PostgresDatabase.ts +1 -5
  249. package/src/Platform.ts +48 -36
  250. package/src/Resource.ts +22 -13
  251. package/src/Runtime.ts +3 -7
  252. package/src/Stack.ts +11 -10
  253. package/src/Util/effect.ts +13 -17
  254. package/src/Util/poll.ts +3 -3
@@ -1,11 +1,17 @@
1
+ import * as Output from "alchemy/Output";
1
2
  import * as Effect from "effect/Effect";
2
3
  import * as Redacted from "effect/Redacted";
4
+ import { AlchemyContext } from "../../AlchemyContext.ts";
3
5
  import { Command, type CommandProps } from "../../Build/Command.ts";
6
+ import { DevServer } from "../../Build/DevServer.ts";
4
7
  import type { InputProps } from "../../Input.ts";
5
8
  import * as Namespace from "../../Namespace.ts";
9
+ import { effectClass } from "../../Util/effect.ts";
10
+ import type { Providers } from "../Providers.ts";
6
11
  import type { AssetsConfig } from "../Workers/Assets.ts";
7
12
  import {
8
13
  Worker,
14
+ type NormalizedBindings,
9
15
  type WorkerAssetsConfig,
10
16
  type WorkerBindingProps,
11
17
  type WorkerProps,
@@ -19,13 +25,48 @@ export interface StaticSiteProps<Bindings extends WorkerBindingProps = {}>
19
25
  * Optional configuration for static asset routing behavior.
20
26
  * Supports `runWorkerFirst`, `htmlHandling`, `notFoundHandling`, etc.
21
27
  */
22
- assetsConfig?: AssetsConfig;
28
+
29
+ assets?: AssetsConfig;
30
+ /**
31
+ * Local dev configuration. When `alchemy dev` runs, the build command is
32
+ * skipped and `command` is spawned as a long-lived child process tied to
33
+ * the stack's scope. Alchemy does not proxy or interpret the process —
34
+ * the dev server's own URL (e.g. `http://localhost:5173`) is what you
35
+ * open in the browser.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * Cloudflare.StaticSite("App", {
40
+ * command: "npm run build",
41
+ * outdir: "dist",
42
+ * main: "./src/worker.ts",
43
+ * dev: { command: "npm run dev" },
44
+ * });
45
+ * ```
46
+ */
23
47
  dev?: {
48
+ /**
49
+ * Shell command to run as the local dev server (e.g. `npm run dev`).
50
+ */
24
51
  command: string;
52
+ /**
53
+ * Working directory for {@link command}. Defaults to {@link CommandProps.cwd}
54
+ * (the build command's `cwd`), or `process.cwd()` if neither is set.
55
+ */
56
+ cwd?: string;
57
+ /**
58
+ * Override for the `url` output if alchemy fails to detect it from the stdout of the dev command
59
+ */
60
+ url?: string;
25
61
  };
26
62
  }
27
63
 
28
- export type StaticSite = ReturnType<typeof StaticSite>;
64
+ type StaticSiteWorker<Bindings extends WorkerBindingProps> = Worker<{
65
+ [binding in keyof NormalizedBindings<
66
+ Bindings,
67
+ WorkerAssetsConfig
68
+ >]: NormalizedBindings<Bindings, WorkerAssetsConfig>[binding];
69
+ }>;
29
70
 
30
71
  /**
31
72
  * A Cloudflare Worker that serves static assets built by a shell command.
@@ -68,7 +109,7 @@ export type StaticSite = ReturnType<typeof StaticSite>;
68
109
  * ```
69
110
  *
70
111
  * @section Asset Configuration
71
- * Use `assetsConfig` to control how Cloudflare handles routing for
112
+ * Use `assets` to control how Cloudflare handles routing for
72
113
  * your static files — HTML handling, not-found behavior, etc.
73
114
  *
74
115
  * @example SPA-style routing
@@ -77,7 +118,7 @@ export type StaticSite = ReturnType<typeof StaticSite>;
77
118
  * command: "npm run build",
78
119
  * outdir: "dist",
79
120
  * main: "./src/worker.ts",
80
- * assetsConfig: {
121
+ * assets: {
81
122
  * htmlHandling: "auto-trailing-slash",
82
123
  * notFoundHandling: "single-page-application",
83
124
  * },
@@ -113,54 +154,126 @@ export type StaticSite = ReturnType<typeof StaticSite>;
113
154
  * },
114
155
  * });
115
156
  * ```
157
+ *
158
+ * @section Class Form
159
+ * Calling `StaticSite` with no arguments returns a constructor you can
160
+ * `extend` to declare the Worker as a named class. The class is both
161
+ * an `Effect` you can `yield*` to deploy and a type you can reference
162
+ * elsewhere — useful when other resources need to bind to this Worker.
163
+ *
164
+ * @example Declaring a Worker class
165
+ * ```typescript
166
+ * class Blog extends Cloudflare.StaticSite<Blog>()("Blog", {
167
+ * command: "hugo --minify",
168
+ * outdir: "public",
169
+ * main: "./src/worker.ts",
170
+ * }) {}
171
+ *
172
+ * const site = yield* Blog;
173
+ * ```
116
174
  */
117
- export const StaticSite = <
175
+ export const StaticSite: {
176
+ <Self>(): {
177
+ <const Bindings extends WorkerBindingProps = {}, Req = never>(
178
+ id: string,
179
+ propsEff:
180
+ | InputProps<StaticSiteProps<Bindings>, "dev">
181
+ | Effect.Effect<
182
+ InputProps<StaticSiteProps<Bindings>, "dev">,
183
+ never,
184
+ Req
185
+ >,
186
+ ): Effect.Effect<Self, never, Req | Providers> & {
187
+ new (): StaticSiteWorker<Bindings>;
188
+ };
189
+ };
190
+ <const Bindings extends WorkerBindingProps = {}, Req = never>(
191
+ id: string,
192
+ propsEff:
193
+ | InputProps<StaticSiteProps<Bindings>, "dev">
194
+ | Effect.Effect<InputProps<StaticSiteProps<Bindings>, "dev">, never, Req>,
195
+ ): Effect.Effect<StaticSiteWorker<Bindings>, never, Req | Providers>;
196
+ } = ((id?: any, propsEff?: any) =>
197
+ id === undefined
198
+ ? (id: string, propsEff: any) => effectClass(makeStaticSite(id, propsEff))
199
+ : makeStaticSite(id, propsEff)) as any;
200
+
201
+ const makeStaticSite = <
118
202
  const Bindings extends WorkerBindingProps = {},
119
203
  Req = never,
120
204
  >(
121
205
  id: string,
122
206
  propsEff:
123
- | InputProps<StaticSiteProps<Bindings>>
124
- | Effect.Effect<InputProps<StaticSiteProps<Bindings>>, never, Req>,
207
+ | InputProps<StaticSiteProps<Bindings>, "dev">
208
+ | Effect.Effect<InputProps<StaticSiteProps<Bindings>, "dev">, never, Req>,
125
209
  ) =>
126
210
  Effect.gen(function* () {
127
211
  const props = Effect.isEffect(propsEff)
128
212
  ? propsEff
129
213
  : Effect.succeed(propsEff);
214
+ const { dev: isDevPhase } = yield* AlchemyContext;
130
215
 
131
- // TODO(sam): local dev/hmr support?
132
- const build = yield* Command(
133
- "Build",
134
- Effect.map(props, (props) => ({
135
- command: props.command,
136
- cwd: props.cwd,
137
- memo: props.memo,
138
- outdir: props.outdir,
139
- env: props.env
140
- ? Object.fromEntries(
141
- Object.entries(props.env).flatMap(([k, v]) => {
142
- if (v === undefined) return [];
143
- if (typeof v === "string" || Redacted.isRedacted(v))
144
- return [[k, v]];
145
- return [[k, JSON.stringify(v)]];
146
- }),
147
- )
148
- : undefined,
149
- })),
150
- );
216
+ return yield* Effect.gen(function* () {
217
+ const resolved = yield* props;
218
+ const useDevServer = isDevPhase && resolved.dev !== undefined;
151
219
 
152
- return yield* Worker<Bindings, WorkerAssetsConfig, Req>(
153
- "Worker",
154
- Effect.map(props, (props) => ({
155
- ...props,
156
- assets: {
157
- path: build.outdir,
158
- hash: build.hash,
159
- config: props.assetsConfig,
160
- },
161
- // Omit the dev command from WorkerProps since it's different from WorkerProps["dev"].
162
- // TODO: we'll need to update this when we add local dev support for StaticSite.
163
- dev: undefined,
164
- })),
165
- );
220
+ // In dev mode with a dev.command, declare a DevCommand resource so
221
+ // the sidecar owns the process lifecycle (survives user-code HMR),
222
+ // skip the build, and tell Worker not to start a local instance.
223
+ let devUrl = yield* useDevServer
224
+ ? DevServer("Dev", {
225
+ command: resolved.dev!.command,
226
+ cwd:
227
+ resolved.dev!.cwd ??
228
+ (typeof resolved.cwd === "string" ? resolved.cwd : undefined),
229
+ }).pipe(
230
+ Effect.map((d) =>
231
+ Output.map(d.url, (url) => url ?? resolved.dev?.url ?? false),
232
+ ),
233
+ )
234
+ : Effect.succeed(false as const);
235
+
236
+ const build = useDevServer
237
+ ? undefined
238
+ : yield* Command("Build", {
239
+ command: resolved.command,
240
+ cwd: resolved.cwd,
241
+ memo: resolved.memo,
242
+ outdir: resolved.outdir,
243
+ env: resolved.env
244
+ ? Object.fromEntries(
245
+ Object.entries(resolved.env).flatMap(([k, v]) => {
246
+ if (v === undefined) return [];
247
+ if (typeof v === "string" || Redacted.isRedacted(v))
248
+ return [[k, v]];
249
+ return [[k, JSON.stringify(v)]];
250
+ }),
251
+ )
252
+ : undefined,
253
+ });
254
+
255
+ // Pure-static sites don't need a custom Worker entrypoint —
256
+ // delegate every request straight to the ASSETS binding. Only
257
+ // injected when the user provided neither `main` nor `script`.
258
+ const fallbackScript =
259
+ resolved.main == null && resolved.script == null
260
+ ? `export default { fetch: (request, env) => env.ASSETS.fetch(request) };`
261
+ : undefined;
262
+
263
+ return yield* Worker<Bindings, WorkerAssetsConfig, Req>("Worker", {
264
+ ...resolved,
265
+ assets: build
266
+ ? {
267
+ directory: build.outdir,
268
+ hash: build.hash,
269
+ ...resolved.assets,
270
+ }
271
+ : undefined,
272
+ // Opt out of the local Worker in dev when the external DevCommand
273
+ // is serving the content. The Worker resource still exists in
274
+ // state with a stub Attributes shape.
275
+ dev: useDevServer ? devUrl : undefined,
276
+ script: fallbackScript ?? resolved.script,
277
+ });
278
+ });
166
279
  }).pipe(Namespace.push(id));
@@ -1,16 +1,19 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import type { MemoOptions } from "../../Build/Memo.ts";
3
3
  import type { InputProps } from "../../Input.ts";
4
+ import { effectClass } from "../../Util/effect.ts";
5
+ import type { Providers } from "../Providers.ts";
6
+ import type { AssetsConfig } from "../Workers/Assets.ts";
4
7
  import {
5
8
  Worker,
9
+ type NormalizedBindings,
6
10
  type WorkerAssetsConfig,
7
11
  type WorkerBindingProps,
8
12
  type WorkerProps,
9
13
  } from "../Workers/Worker.ts";
10
-
11
14
  export interface ViteProps<
12
15
  Bindings extends WorkerBindingProps = {},
13
- > extends Omit<WorkerProps<Bindings>, "vite" | "main"> {
16
+ > extends Omit<WorkerProps<Bindings>, "vite" | "main" | "assets"> {
14
17
  /**
15
18
  * Root directory passed to Vite's `root` option.
16
19
  * Defaults to the current working directory (`process.cwd()`).
@@ -24,6 +27,11 @@ export interface ViteProps<
24
27
  * @see {@link MemoOptions}
25
28
  */
26
29
  memo?: MemoOptions;
30
+ /**
31
+ * Optional configuration for static asset routing behavior.
32
+ * Supports `runWorkerFirst`, `htmlHandling`, `notFoundHandling`, etc.
33
+ */
34
+ assets?: AssetsConfig;
27
35
  }
28
36
 
29
37
  /**
@@ -106,27 +114,67 @@ export interface ViteProps<
106
114
  * },
107
115
  * });
108
116
  * ```
117
+ *
118
+ * @section Class Form
119
+ * Calling `Vite` with no arguments returns a constructor you can
120
+ * `extend` to declare the Worker as a named class. The class is both
121
+ * an `Effect` you can `yield*` to deploy and a type you can reference
122
+ * elsewhere — useful when other resources need to bind to this Worker.
123
+ *
124
+ * @example Declaring a Worker class
125
+ * ```typescript
126
+ * class Website extends Cloudflare.Vite<Website>()("Website", {
127
+ * compatibility: { flags: ["nodejs_compat"] },
128
+ * }) {}
129
+ *
130
+ * const site = yield* Website;
131
+ * ```
109
132
  */
110
- export const Vite = <
111
- const Bindings extends WorkerBindingProps = {},
112
- Req = never,
113
- >(
114
- id: string,
115
- propsEff?:
116
- | InputProps<ViteProps<Bindings>>
117
- | Effect.Effect<InputProps<ViteProps<Bindings>>, never, Req>,
118
- ) =>
119
- Worker<Bindings, WorkerAssetsConfig, Req>(
120
- id,
121
- Effect.map(
122
- Effect.isEffect(propsEff) ? propsEff : Effect.succeed(propsEff),
123
- (props) => ({
124
- ...props,
125
- main: undefined!,
126
- vite: {
127
- rootDir: props?.rootDir,
128
- memo: props?.memo,
129
- },
130
- }),
131
- ),
132
- );
133
+ export const Vite: {
134
+ <Self>(): {
135
+ <const Bindings extends WorkerBindingProps = {}, Req = never>(
136
+ id: string,
137
+ propsEff?:
138
+ | InputProps<ViteProps<Bindings>>
139
+ | Effect.Effect<InputProps<ViteProps<Bindings>>, never, Req>,
140
+ ): Effect.Effect<Self, never, Req | Providers> & {
141
+ new (): Worker<{
142
+ [binding in keyof NormalizedBindings<
143
+ Bindings,
144
+ WorkerAssetsConfig
145
+ >]: NormalizedBindings<Bindings, WorkerAssetsConfig>[binding];
146
+ }>;
147
+ };
148
+ };
149
+ <const Bindings extends WorkerBindingProps = {}, Req = never>(
150
+ id: string,
151
+ propsEff?:
152
+ | InputProps<ViteProps<Bindings>>
153
+ | Effect.Effect<InputProps<ViteProps<Bindings>>, never, Req>,
154
+ ): Effect.Effect<
155
+ Worker<{
156
+ [binding in keyof NormalizedBindings<
157
+ Bindings,
158
+ WorkerAssetsConfig
159
+ >]: NormalizedBindings<Bindings, WorkerAssetsConfig>[binding];
160
+ }>,
161
+ never,
162
+ Req | Providers
163
+ >;
164
+ } = ((id?: any, propsEff?: any) =>
165
+ id === undefined
166
+ ? (id: string, propsEff: any) => effectClass(Vite(id, propsEff))
167
+ : Worker(
168
+ id,
169
+ Effect.map(
170
+ Effect.isEffect(propsEff) ? propsEff : Effect.succeed(propsEff),
171
+ (props) => ({
172
+ ...props,
173
+ main: undefined!,
174
+ vite: {
175
+ rootDir: props?.rootDir,
176
+ memo: props?.memo,
177
+ },
178
+ }),
179
+ ),
180
+ )) as any;
@@ -32,9 +32,8 @@ export interface AssetReadResult {
32
32
  hash: string;
33
33
  }
34
34
 
35
- export interface AssetsProps {
35
+ export interface AssetsProps extends AssetsConfig {
36
36
  directory: string;
37
- config?: AssetsConfig;
38
37
  }
39
38
 
40
39
  export type ValidationError =
@@ -103,10 +102,13 @@ const createIgnoreMatcher = (patterns: string[]) => {
103
102
  return (file: string) => matcher.ignores(file);
104
103
  };
105
104
 
106
- export const readAssets = Effect.fnUntraced(function* (props: AssetsProps) {
105
+ export const readAssets = Effect.fnUntraced(function* ({
106
+ directory,
107
+ ...config
108
+ }: AssetsProps) {
107
109
  const fs = yield* FileSystem.FileSystem;
108
110
  const path = yield* Path.Path;
109
- const resolvedDirectory = path.resolve(props.directory);
111
+ const resolvedDirectory = path.resolve(directory);
110
112
  const [files, ignore, _headers, _redirects] = yield* Effect.all([
111
113
  fs.readDirectory(resolvedDirectory, { recursive: true }),
112
114
  maybeReadString(path.join(resolvedDirectory, ".assetsignore")),
@@ -151,7 +153,7 @@ export const readAssets = Effect.fnUntraced(function* (props: AssetsProps) {
151
153
  if (count > MAX_ASSET_COUNT) {
152
154
  return yield* new TooManyAssetsError({
153
155
  message: `Too many assets (the maximum count is ${MAX_ASSET_COUNT}; this directory has ${count} assets)`,
154
- directory: props.directory,
156
+ directory,
155
157
  count,
156
158
  });
157
159
  }
@@ -176,14 +178,14 @@ export const readAssets = Effect.fnUntraced(function* (props: AssetsProps) {
176
178
  // causes both unnecessary re-uploads and `NotFound` failures when
177
179
  // the previously-recorded path is gone.
178
180
  const hash = yield* sha256Object({
179
- config: props.config,
181
+ config,
180
182
  manifest: sortedManifest,
181
183
  _headers,
182
184
  _redirects,
183
185
  });
184
186
  return {
185
- directory: props.directory,
186
- config: props.config,
187
+ directory,
188
+ config,
187
189
  manifest: sortedManifest,
188
190
  _headers,
189
191
  _redirects,
@@ -0,0 +1,131 @@
1
+ import * as Arr from "effect/Array";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import {
5
+ BackingPersistence,
6
+ PersistenceError,
7
+ type BackingPersistenceStore,
8
+ } from "effect/unstable/persistence/Persistence";
9
+ import { DurableObjectState } from "./DurableObjectState.ts";
10
+
11
+ /**
12
+ * A `BackingPersistence` layer (Effect AI persistence module) backed
13
+ * by the surrounding Durable Object's `state.storage`. Drop-in storage
14
+ * for `Persistence.layerResultPersisted({ storeId })` so chat history,
15
+ * cached AI responses, or any other persisted state lives in the DO
16
+ * SQLite store with `${storeId}:` key namespacing.
17
+ *
18
+ * Multiple `storeId`s can coexist within a single Durable Object —
19
+ * keys are namespaced with a `${storeId}:` prefix so they don't
20
+ * collide.
21
+ *
22
+ * :::caution
23
+ * TTL is currently ignored; Durable Object storage has no native TTL.
24
+ * If you need expiry, layer a TTL-aware backing store on top, or run
25
+ * a periodic `clear`.
26
+ * :::
27
+ *
28
+ * @binding
29
+ *
30
+ * @section Wiring it into a chat-backing DO
31
+ * @example Persisted chat history per DO instance
32
+ * `Persistence.layerResultPersisted({ storeId })` is the seam Effect
33
+ * AI exposes for cached/replayable AI calls. Layer
34
+ * `DurableObjectChatPersistence` underneath and every entry is stored
35
+ * in the DO's `state.storage` under the `alchemy.chat:` prefix.
36
+ * ```typescript
37
+ * import * as Cloudflare from "alchemy/Cloudflare";
38
+ * import * as Effect from "effect/Effect";
39
+ * import * as Layer from "effect/Layer";
40
+ * import { Chat, LanguageModel } from "effect/unstable/ai";
41
+ * import { Persistence } from "effect/unstable/persistence";
42
+ *
43
+ * export default class ChatBackend extends Cloudflare.DurableObjectNamespace<ChatBackend>()(
44
+ * "ChatBackend",
45
+ * Effect.gen(function* () {
46
+ * return Effect.gen(function* () {
47
+ * const persistence = yield* Persistence.layerResultPersisted({
48
+ * storeId: "alchemy.chat",
49
+ * }).pipe(Layer.provide(Cloudflare.DurableObjectChatPersistence));
50
+ *
51
+ * return {
52
+ * send: (threadId: string, prompt: string) =>
53
+ * LanguageModel.generateText({ prompt }).pipe(Effect.provide(persistence)),
54
+ * };
55
+ * });
56
+ * }),
57
+ * ) {}
58
+ * ```
59
+ *
60
+ * @section Multiple stores in the same DO
61
+ * @example Separate `storeId`s coexist
62
+ * Different `storeId`s namespace their keys with `${storeId}:`, so
63
+ * one DO can keep, say, chat history *and* an audit log in separate
64
+ * stores without colliding.
65
+ * ```typescript
66
+ * const aiPersistence = yield* Persistence.layerResultPersisted({
67
+ * storeId: "alchemy.chat",
68
+ * }).pipe(Layer.provide(Cloudflare.DurableObjectChatPersistence));
69
+ *
70
+ * const auditPersistence = yield* Persistence.layerResultPersisted({
71
+ * storeId: "alchemy.audit",
72
+ * }).pipe(Layer.provide(Cloudflare.DurableObjectChatPersistence));
73
+ * ```
74
+ */
75
+ export const DurableObjectChatPersistence = Layer.effect(BackingPersistence)(
76
+ Effect.gen(function* () {
77
+ const state = yield* DurableObjectState;
78
+ const storage = state.storage;
79
+
80
+ const wrapErr = (op: string, key?: string) => (cause: unknown) =>
81
+ new PersistenceError({
82
+ message: `Failed to ${op}${key !== undefined ? ` key ${key}` : ""} in DurableObject storage`,
83
+ cause,
84
+ });
85
+
86
+ return BackingPersistence.of({
87
+ make: (storeId) =>
88
+ Effect.sync(() => {
89
+ const prefixed = (k: string) => `${storeId}:${k}`;
90
+ return {
91
+ get: (key) =>
92
+ storage
93
+ .get<object>(prefixed(key))
94
+ .pipe(Effect.mapError(wrapErr("get", key))),
95
+ getMany: (keys) =>
96
+ storage.get<object>(keys.map(prefixed)).pipe(
97
+ Effect.mapError(wrapErr("getMany")),
98
+ Effect.map(
99
+ (map) =>
100
+ keys.map((k) => map.get(prefixed(k))) as Arr.NonEmptyArray<
101
+ object | undefined
102
+ >,
103
+ ),
104
+ ),
105
+ set: (key, value, _ttl) =>
106
+ storage
107
+ .put(prefixed(key), value)
108
+ .pipe(Effect.mapError(wrapErr("set", key))),
109
+ setMany: (entries) =>
110
+ storage
111
+ .put(
112
+ Object.fromEntries(entries.map(([k, v]) => [prefixed(k), v])),
113
+ )
114
+ .pipe(Effect.mapError(wrapErr("setMany"))),
115
+ remove: (key) =>
116
+ storage
117
+ .delete(prefixed(key))
118
+ .pipe(Effect.asVoid, Effect.mapError(wrapErr("remove", key))),
119
+ clear: storage.list({ prefix: `${storeId}:` }).pipe(
120
+ Effect.flatMap((map) => {
121
+ const ks = [...map.keys()];
122
+ if (ks.length === 0) return Effect.void;
123
+ return Effect.asVoid(storage.delete(ks));
124
+ }),
125
+ Effect.mapError(wrapErr("clear")),
126
+ ),
127
+ } satisfies BackingPersistenceStore;
128
+ }),
129
+ });
130
+ }),
131
+ );
@@ -100,10 +100,10 @@ type BindEffect = ReturnType<typeof bindLoader>;
100
100
  * surrounding Worker and returns a {@link DynamicWorkerLoaderHandle} with
101
101
  * `.load()` — no separate `.bind(...)` step required.
102
102
  *
103
- * The divergence is achieved via `[Symbol.iterator]`: the object is not an
104
- * `Effect` (so `InferEnv` resolves it to the native
105
- * {@link DynamicWorkerLoaderBinding} in the `env` position), but it is iterable
106
- * as one when `yield*`-ed.
103
+ * The divergence is achieved via `[Symbol.iterator]`: the object is
104
+ * deliberately not an `Effect` (so `InferEnv` and the Worker `env` resolver
105
+ * keep it as the native {@link DynamicWorkerLoaderBinding} rather than
106
+ * `yield*`-ing it), but it is iterable as one when `yield*`-ed.
107
107
  */
108
108
  export interface DynamicWorkerLoader {
109
109
  kind: DynamicWorkerTypeId;
@@ -45,20 +45,22 @@ export type GetBindingType<T> =
45
45
  : T extends Cloudflare.Images
46
46
  ? ImagesBinding
47
47
  : T extends Cloudflare.Browser
48
- ? Fetcher
48
+ ? BrowserRun
49
49
  : T extends Cloudflare.Hyperdrive
50
50
  ? Hyperdrive
51
- : T extends Cloudflare.DynamicWorkerLoader
52
- ? Cloudflare.DynamicWorkerLoaderBinding
53
- : T extends Cloudflare.DurableObjectNamespaceLike
54
- ? DurableObjectNamespace<
55
- Exclude<T["Shape"], undefined>
56
- >
57
- : T extends Redacted<any>
58
- ? // redacteds are always stored as secret_text, so are always string
59
- // we JSON.stringify when not a Redacted<string>
60
- string
61
- : T;
51
+ : T extends Cloudflare.VersionMetadata
52
+ ? WorkerVersionMetadata
53
+ : T extends Cloudflare.DynamicWorkerLoader
54
+ ? Cloudflare.DynamicWorkerLoaderBinding
55
+ : T extends Cloudflare.DurableObjectNamespaceLike
56
+ ? DurableObjectNamespace<
57
+ Exclude<T["Shape"], undefined>
58
+ >
59
+ : T extends Redacted<any>
60
+ ? // redacteds are always stored as secret_text, so are always string
61
+ // we JSON.stringify when not a Redacted<string>
62
+ string
63
+ : T;
62
64
 
63
65
  /**
64
66
  * Cloudflare service-binding wire shape for an Effect-native Worker.