alchemy 2.0.0-beta.70 → 2.0.0-beta.71

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 (299) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +5 -0
  3. package/README.md +3 -1
  4. package/THIRD_PARTY_LICENSES.md +127 -0
  5. package/bin/exec.js +25 -9
  6. package/bin/exec.js.map +1 -1
  7. package/lib/AWS/DataZone/Domain.d.ts.map +1 -1
  8. package/lib/AWS/DataZone/Domain.js +7 -7
  9. package/lib/AWS/DataZone/Domain.js.map +1 -1
  10. package/lib/AWS/EC2/InternetGateway.d.ts.map +1 -1
  11. package/lib/AWS/EC2/InternetGateway.js +14 -16
  12. package/lib/AWS/EC2/InternetGateway.js.map +1 -1
  13. package/lib/AWS/EC2/LingeringEnis.d.ts +2 -2
  14. package/lib/AWS/EC2/LingeringEnis.d.ts.map +1 -1
  15. package/lib/AWS/EC2/LingeringEnis.js +9 -7
  16. package/lib/AWS/EC2/LingeringEnis.js.map +1 -1
  17. package/lib/AWS/RDSData/Marshall.d.ts +23 -0
  18. package/lib/AWS/RDSData/Marshall.d.ts.map +1 -0
  19. package/lib/AWS/RDSData/Marshall.js +87 -0
  20. package/lib/AWS/RDSData/Marshall.js.map +1 -0
  21. package/lib/AWS/RDSData/index.d.ts +1 -0
  22. package/lib/AWS/RDSData/index.d.ts.map +1 -1
  23. package/lib/AWS/RDSData/index.js +1 -0
  24. package/lib/AWS/RDSData/index.js.map +1 -1
  25. package/lib/AWS/ResourceGroups/Group.d.ts.map +1 -1
  26. package/lib/AWS/ResourceGroups/Group.js +3 -1
  27. package/lib/AWS/ResourceGroups/Group.js.map +1 -1
  28. package/lib/AWS/S3Tables/Namespace.d.ts.map +1 -1
  29. package/lib/AWS/S3Tables/Namespace.js +3 -1
  30. package/lib/AWS/S3Tables/Namespace.js.map +1 -1
  31. package/lib/AWS/S3Tables/TableBucket.d.ts.map +1 -1
  32. package/lib/AWS/S3Tables/TableBucket.js +8 -2
  33. package/lib/AWS/S3Tables/TableBucket.js.map +1 -1
  34. package/lib/AWS/S3Vectors/VectorBucket.d.ts.map +1 -1
  35. package/lib/AWS/S3Vectors/VectorBucket.js +6 -1
  36. package/lib/AWS/S3Vectors/VectorBucket.js.map +1 -1
  37. package/lib/AWS/Website/cfcode.d.ts.map +1 -1
  38. package/lib/AWS/Website/cfcode.js +2 -0
  39. package/lib/AWS/Website/cfcode.js.map +1 -1
  40. package/lib/Apply.d.ts.map +1 -1
  41. package/lib/Apply.js +22 -8
  42. package/lib/Apply.js.map +1 -1
  43. package/lib/Auth/AuthProvider.d.ts.map +1 -1
  44. package/lib/Auth/AuthProvider.js +1 -1
  45. package/lib/Auth/AuthProvider.js.map +1 -1
  46. package/lib/Bundle/Bundle.js +2 -2
  47. package/lib/Bundle/Bundle.js.map +1 -1
  48. package/lib/Cli/commands/cloudflare.js +1 -1
  49. package/lib/Cli/commands/cloudflare.js.map +1 -1
  50. package/lib/Cli/commands/logs.d.ts.map +1 -1
  51. package/lib/Cli/commands/logs.js +2 -1
  52. package/lib/Cli/commands/logs.js.map +1 -1
  53. package/lib/Cli/commands/nuke.js +1 -1
  54. package/lib/Cli/commands/nuke.js.map +1 -1
  55. package/lib/Cli/commands/tail.d.ts.map +1 -1
  56. package/lib/Cli/commands/tail.js +2 -1
  57. package/lib/Cli/commands/tail.js.map +1 -1
  58. package/lib/Cloudflare/AI/LanguageModel.js +1 -1
  59. package/lib/Cloudflare/AI/LanguageModel.js.map +1 -1
  60. package/lib/Cloudflare/Access.js +1 -1
  61. package/lib/Cloudflare/Access.js.map +1 -1
  62. package/lib/Cloudflare/Containers/ContainerBundle.d.ts +1 -1
  63. package/lib/Cloudflare/Containers/ContainerBundle.js +1 -1
  64. package/lib/Cloudflare/Containers/ContainerProvider.d.ts.map +1 -1
  65. package/lib/Cloudflare/Containers/ContainerProvider.js +24 -2
  66. package/lib/Cloudflare/Containers/ContainerProvider.js.map +1 -1
  67. package/lib/Cloudflare/Containers/LocalContainerProvider.d.ts +1 -1
  68. package/lib/Cloudflare/Containers/LocalContainerProvider.js +1 -1
  69. package/lib/Cloudflare/D1/Database.d.ts +2 -2
  70. package/lib/Cloudflare/D1/Database.d.ts.map +1 -1
  71. package/lib/Cloudflare/D1/LocalD1Gateway.d.ts +3 -3
  72. package/lib/Cloudflare/D1/LocalD1Gateway.d.ts.map +1 -1
  73. package/lib/Cloudflare/D1/LocalD1Gateway.js +3 -3
  74. package/lib/Cloudflare/D1/LocalD1Gateway.js.map +1 -1
  75. package/lib/Cloudflare/KV/LocalKVGateway.js +2 -2
  76. package/lib/Cloudflare/KV/LocalKVGateway.js.map +1 -1
  77. package/lib/Cloudflare/LocalGateway.d.ts +1 -1
  78. package/lib/Cloudflare/LocalGateway.d.ts.map +1 -1
  79. package/lib/Cloudflare/LocalGateway.js +1 -1
  80. package/lib/Cloudflare/LocalGateway.js.map +1 -1
  81. package/lib/Cloudflare/LocalRuntime.d.ts +1 -1
  82. package/lib/Cloudflare/LocalRuntime.d.ts.map +1 -1
  83. package/lib/Cloudflare/LocalRuntime.js +5 -4
  84. package/lib/Cloudflare/LocalRuntime.js.map +1 -1
  85. package/lib/Cloudflare/Queues/Consumer.d.ts +22 -5
  86. package/lib/Cloudflare/Queues/Consumer.d.ts.map +1 -1
  87. package/lib/Cloudflare/Queues/Consumer.js +129 -38
  88. package/lib/Cloudflare/Queues/Consumer.js.map +1 -1
  89. package/lib/Cloudflare/Queues/Queue.d.ts +4 -3
  90. package/lib/Cloudflare/Queues/Queue.d.ts.map +1 -1
  91. package/lib/Cloudflare/Queues/Queue.js +115 -6
  92. package/lib/Cloudflare/Queues/Queue.js.map +1 -1
  93. package/lib/Cloudflare/R2/LocalR2Gateway.js +2 -2
  94. package/lib/Cloudflare/R2/LocalR2Gateway.js.map +1 -1
  95. package/lib/Cloudflare/SecretsStore/LocalSecretsStoreGateway.d.ts +4 -4
  96. package/lib/Cloudflare/SecretsStore/LocalSecretsStoreGateway.d.ts.map +1 -1
  97. package/lib/Cloudflare/SecretsStore/LocalSecretsStoreGateway.js +2 -2
  98. package/lib/Cloudflare/SecretsStore/LocalSecretsStoreGateway.js.map +1 -1
  99. package/lib/Cloudflare/SecretsStore/Secret.d.ts +2 -2
  100. package/lib/Cloudflare/SecretsStore/Secret.d.ts.map +1 -1
  101. package/lib/Cloudflare/StateStore/Api.js +7 -0
  102. package/lib/Cloudflare/StateStore/Api.js.map +1 -1
  103. package/lib/Cloudflare/Website/Astro.d.ts +19 -11
  104. package/lib/Cloudflare/Website/Astro.d.ts.map +1 -1
  105. package/lib/Cloudflare/Website/Astro.js +16 -9
  106. package/lib/Cloudflare/Website/Astro.js.map +1 -1
  107. package/lib/Cloudflare/Website/Nextjs.d.ts +7 -4
  108. package/lib/Cloudflare/Website/Nextjs.d.ts.map +1 -1
  109. package/lib/Cloudflare/Website/Nextjs.js +10 -8
  110. package/lib/Cloudflare/Website/Nextjs.js.map +1 -1
  111. package/lib/Cloudflare/Website/Nuxt.d.ts +5 -4
  112. package/lib/Cloudflare/Website/Nuxt.d.ts.map +1 -1
  113. package/lib/Cloudflare/Website/Nuxt.js +4 -3
  114. package/lib/Cloudflare/Website/Nuxt.js.map +1 -1
  115. package/lib/Cloudflare/Website/Octane.d.ts +4 -3
  116. package/lib/Cloudflare/Website/Octane.d.ts.map +1 -1
  117. package/lib/Cloudflare/Website/Octane.js +5 -4
  118. package/lib/Cloudflare/Website/Octane.js.map +1 -1
  119. package/lib/Cloudflare/Website/SvelteKit.d.ts +17 -10
  120. package/lib/Cloudflare/Website/SvelteKit.d.ts.map +1 -1
  121. package/lib/Cloudflare/Website/SvelteKit.js +9 -5
  122. package/lib/Cloudflare/Website/SvelteKit.js.map +1 -1
  123. package/lib/Cloudflare/Website/Waku.d.ts +20 -9
  124. package/lib/Cloudflare/Website/Waku.d.ts.map +1 -1
  125. package/lib/Cloudflare/Website/Waku.js +10 -4
  126. package/lib/Cloudflare/Website/Waku.js.map +1 -1
  127. package/lib/Cloudflare/Workers/Assets.d.ts +54 -1
  128. package/lib/Cloudflare/Workers/Assets.d.ts.map +1 -1
  129. package/lib/Cloudflare/Workers/Assets.js +60 -26
  130. package/lib/Cloudflare/Workers/Assets.js.map +1 -1
  131. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +2 -2
  132. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  133. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +153 -49
  134. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  135. package/lib/Cloudflare/Workers/RuntimeBindings.d.ts +3 -3
  136. package/lib/Cloudflare/Workers/RuntimeBindings.d.ts.map +1 -1
  137. package/lib/Cloudflare/Workers/RuntimeBindings.js +2 -2
  138. package/lib/Cloudflare/Workers/RuntimeBindings.js.map +1 -1
  139. package/lib/Cloudflare/Workers/Source.d.ts +1 -1
  140. package/lib/Cloudflare/Workers/Source.d.ts.map +1 -1
  141. package/lib/Cloudflare/Workers/Source.js +1 -1
  142. package/lib/Cloudflare/Workers/Source.js.map +1 -1
  143. package/lib/Cloudflare/Workers/Sources/Rolldown.d.ts.map +1 -1
  144. package/lib/Cloudflare/Workers/Sources/Rolldown.js +3 -4
  145. package/lib/Cloudflare/Workers/Sources/Rolldown.js.map +1 -1
  146. package/lib/Cloudflare/Workers/Sources/Vite.d.ts +1 -1
  147. package/lib/Cloudflare/Workers/Sources/Vite.d.ts.map +1 -1
  148. package/lib/Cloudflare/Workers/Sources/Vite.js +36 -17
  149. package/lib/Cloudflare/Workers/Sources/Vite.js.map +1 -1
  150. package/lib/Cloudflare/Workers/ViteChild.d.ts.map +1 -1
  151. package/lib/Cloudflare/Workers/ViteChild.js +6 -0
  152. package/lib/Cloudflare/Workers/ViteChild.js.map +1 -1
  153. package/lib/Cloudflare/Workers/ViteChild.shared.d.ts +1 -1
  154. package/lib/Cloudflare/Workers/ViteChild.shared.d.ts.map +1 -1
  155. package/lib/Cloudflare/Workers/ViteChildRunner.js +5 -10
  156. package/lib/Cloudflare/Workers/ViteChildRunner.js.map +1 -1
  157. package/lib/Cloudflare/Workers/Worker.d.ts +11 -4
  158. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  159. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  160. package/lib/Cloudflare/Workers/WorkerProvider.d.ts +1 -1
  161. package/lib/Cloudflare/Workers/WorkerProvider.d.ts.map +1 -1
  162. package/lib/Cloudflare/Workers/WorkerProvider.js +12 -2
  163. package/lib/Cloudflare/Workers/WorkerProvider.js.map +1 -1
  164. package/lib/Docker/Docker.d.ts.map +1 -1
  165. package/lib/Docker/Docker.js +6 -2
  166. package/lib/Docker/Docker.js.map +1 -1
  167. package/lib/Drizzle/{DurableObject.d.ts → Cloudflare.d.ts} +2 -1
  168. package/lib/Drizzle/Cloudflare.d.ts.map +1 -0
  169. package/lib/Drizzle/{DurableObject.js → Cloudflare.js} +2 -1
  170. package/lib/Drizzle/Cloudflare.js.map +1 -0
  171. package/lib/Drizzle/MySQL.d.ts +2 -2
  172. package/lib/Drizzle/MySQL.d.ts.map +1 -1
  173. package/lib/Drizzle/MySQL.js +5 -3
  174. package/lib/Drizzle/MySQL.js.map +1 -1
  175. package/lib/Drizzle/Postgres.d.ts +1 -1
  176. package/lib/Drizzle/Postgres.d.ts.map +1 -1
  177. package/lib/Drizzle/Postgres.js +4 -2
  178. package/lib/Drizzle/Postgres.js.map +1 -1
  179. package/lib/Drizzle/index.d.ts +0 -3
  180. package/lib/Drizzle/index.d.ts.map +1 -1
  181. package/lib/Drizzle/index.js +5 -3
  182. package/lib/Drizzle/index.js.map +1 -1
  183. package/lib/Neon/Migrations.d.ts.map +1 -1
  184. package/lib/Neon/Migrations.js +1 -5
  185. package/lib/Neon/Migrations.js.map +1 -1
  186. package/lib/PhysicalName.d.ts +8 -0
  187. package/lib/PhysicalName.d.ts.map +1 -1
  188. package/lib/PhysicalName.js +5 -2
  189. package/lib/PhysicalName.js.map +1 -1
  190. package/lib/Plan.d.ts.map +1 -1
  191. package/lib/Plan.js +16 -9
  192. package/lib/Plan.js.map +1 -1
  193. package/lib/ProviderMode.d.ts +45 -0
  194. package/lib/ProviderMode.d.ts.map +1 -1
  195. package/lib/ProviderMode.js +51 -0
  196. package/lib/ProviderMode.js.map +1 -1
  197. package/lib/SQL/ConnectionSource.d.ts +45 -0
  198. package/lib/SQL/ConnectionSource.d.ts.map +1 -0
  199. package/lib/SQL/ConnectionSource.js +51 -0
  200. package/lib/SQL/ConnectionSource.js.map +1 -0
  201. package/lib/SQL/MySQLDriver.d.ts +21 -0
  202. package/lib/SQL/MySQLDriver.d.ts.map +1 -0
  203. package/lib/SQL/MySQLDriver.js +31 -0
  204. package/lib/SQL/MySQLDriver.js.map +1 -0
  205. package/lib/SQL/PostgresDriver.d.ts +21 -0
  206. package/lib/SQL/PostgresDriver.d.ts.map +1 -0
  207. package/lib/SQL/PostgresDriver.js +29 -0
  208. package/lib/SQL/PostgresDriver.js.map +1 -0
  209. package/lib/State/HttpStateApi.d.ts +11 -11
  210. package/lib/State/LocalState.d.ts.map +1 -1
  211. package/lib/State/LocalState.js +17 -1
  212. package/lib/State/LocalState.js.map +1 -1
  213. package/lib/State/ResourceState.d.ts +6 -2
  214. package/lib/State/ResourceState.d.ts.map +1 -1
  215. package/lib/State/StateEncoding.d.ts +8 -0
  216. package/lib/State/StateEncoding.d.ts.map +1 -1
  217. package/lib/State/StateEncoding.js +38 -0
  218. package/lib/State/StateEncoding.js.map +1 -1
  219. package/lib/Sync.d.ts.map +1 -1
  220. package/lib/Sync.js +4 -2
  221. package/lib/Sync.js.map +1 -1
  222. package/lib/Test/Alchemy.d.ts.map +1 -1
  223. package/lib/Test/Alchemy.js +24 -14
  224. package/lib/Test/Alchemy.js.map +1 -1
  225. package/lib/Test/Core.d.ts +25 -8
  226. package/lib/Test/Core.d.ts.map +1 -1
  227. package/lib/Test/Core.js +24 -7
  228. package/lib/Test/Core.js.map +1 -1
  229. package/package.json +49 -40
  230. package/src/AWS/DataZone/Domain.ts +15 -13
  231. package/src/AWS/EC2/InternetGateway.ts +28 -32
  232. package/src/AWS/EC2/LingeringEnis.ts +11 -9
  233. package/src/AWS/RDSData/Marshall.ts +106 -0
  234. package/src/AWS/RDSData/index.ts +1 -0
  235. package/src/AWS/ResourceGroups/Group.ts +5 -1
  236. package/src/AWS/S3Tables/Namespace.ts +3 -1
  237. package/src/AWS/S3Tables/TableBucket.ts +8 -2
  238. package/src/AWS/S3Vectors/VectorBucket.ts +6 -1
  239. package/src/AWS/Website/cfcode.ts +2 -0
  240. package/src/Apply.ts +22 -9
  241. package/src/Auth/AuthProvider.ts +4 -7
  242. package/src/Bundle/Bundle.ts +2 -2
  243. package/src/Cli/commands/cloudflare.ts +1 -1
  244. package/src/Cli/commands/logs.ts +2 -1
  245. package/src/Cli/commands/nuke.ts +1 -1
  246. package/src/Cli/commands/tail.ts +2 -1
  247. package/src/Cloudflare/AI/LanguageModel.ts +3 -0
  248. package/src/Cloudflare/Access.ts +1 -1
  249. package/src/Cloudflare/Containers/ContainerBundle.ts +1 -1
  250. package/src/Cloudflare/Containers/ContainerProvider.ts +35 -5
  251. package/src/Cloudflare/Containers/LocalContainerProvider.ts +1 -1
  252. package/src/Cloudflare/D1/Database.ts +1 -1
  253. package/src/Cloudflare/D1/LocalD1Gateway.ts +4 -4
  254. package/src/Cloudflare/KV/LocalKVGateway.ts +2 -2
  255. package/src/Cloudflare/LocalGateway.ts +1 -1
  256. package/src/Cloudflare/LocalRuntime.ts +6 -4
  257. package/src/Cloudflare/Queues/Consumer.ts +183 -45
  258. package/src/Cloudflare/Queues/Queue.ts +157 -5
  259. package/src/Cloudflare/R2/LocalR2Gateway.ts +2 -2
  260. package/src/Cloudflare/SecretsStore/LocalSecretsStoreGateway.ts +2 -2
  261. package/src/Cloudflare/SecretsStore/Secret.ts +1 -1
  262. package/src/Cloudflare/StateStore/Api.ts +7 -0
  263. package/src/Cloudflare/Website/Astro.ts +20 -12
  264. package/src/Cloudflare/Website/Nextjs.ts +13 -8
  265. package/src/Cloudflare/Website/Nuxt.ts +6 -5
  266. package/src/Cloudflare/Website/Octane.ts +6 -4
  267. package/src/Cloudflare/Website/SvelteKit.ts +18 -11
  268. package/src/Cloudflare/Website/Waku.ts +21 -10
  269. package/src/Cloudflare/Workers/Assets.ts +122 -20
  270. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +228 -81
  271. package/src/Cloudflare/Workers/RuntimeBindings.ts +3 -3
  272. package/src/Cloudflare/Workers/Source.ts +2 -2
  273. package/src/Cloudflare/Workers/Sources/Rolldown.ts +3 -4
  274. package/src/Cloudflare/Workers/Sources/Vite.ts +46 -24
  275. package/src/Cloudflare/Workers/ViteChild.shared.ts +1 -1
  276. package/src/Cloudflare/Workers/ViteChild.ts +6 -0
  277. package/src/Cloudflare/Workers/ViteChildRunner.ts +5 -10
  278. package/src/Cloudflare/Workers/Worker.ts +26 -6
  279. package/src/Cloudflare/Workers/WorkerProvider.ts +19 -2
  280. package/src/Docker/Docker.ts +17 -11
  281. package/src/Drizzle/{DurableObject.ts → Cloudflare.ts} +1 -0
  282. package/src/Drizzle/MySQL.ts +13 -3
  283. package/src/Drizzle/Postgres.ts +10 -2
  284. package/src/Drizzle/index.ts +6 -3
  285. package/src/Neon/Migrations.ts +1 -10
  286. package/src/PhysicalName.ts +17 -1
  287. package/src/Plan.ts +20 -9
  288. package/src/ProviderMode.ts +56 -0
  289. package/src/SQL/ConnectionSource.ts +101 -0
  290. package/src/SQL/MySQLDriver.ts +49 -0
  291. package/src/SQL/PostgresDriver.ts +45 -0
  292. package/src/State/LocalState.ts +24 -0
  293. package/src/State/ResourceState.ts +6 -2
  294. package/src/State/StateEncoding.ts +37 -0
  295. package/src/Sync.ts +4 -2
  296. package/src/Test/Alchemy.ts +17 -6
  297. package/src/Test/Core.ts +42 -15
  298. package/lib/Drizzle/DurableObject.d.ts.map +0 -1
  299. package/lib/Drizzle/DurableObject.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import cloudflare, {
2
2
  type CloudflareVitePluginOptions,
3
- } from "@distilled.cloud/cloudflare-vite-plugin";
3
+ } from "@alchemy.run/cloudflare-runtime/vite";
4
4
  import * as ConsoleService from "effect/Console";
5
5
  import * as Effect from "effect/Effect";
6
6
  import * as Path from "effect/Path";
@@ -13,8 +13,9 @@ import {
13
13
  viteBuildOutputPlugin,
14
14
  type ViteBuildOutput,
15
15
  } from "../../../Bundle/Vite.ts";
16
+ import { viteSupportsPortZero } from "@alchemy.run/cloudflare-runtime/core/internal/Port";
16
17
  import { hashDirectory, type MemoOptions } from "../../../Command/Memo.ts";
17
- import { initialCwd } from "../../../Util/Node.ts";
18
+ import { findAvailablePort, initialCwd } from "../../../Util/Node.ts";
18
19
  import { sha256Object } from "../../../Util/sha256.ts";
19
20
  import { readAssets } from "../Assets.ts";
20
21
  import type { SourceDevHandle, SourceProvider } from "../Source.ts";
@@ -23,7 +24,7 @@ import type { ViteOptions } from "../Worker.ts";
23
24
  import { isWorkerLoader } from "../WorkerLoader.ts";
24
25
 
25
26
  /**
26
- * This module statically imports `@distilled.cloud/cloudflare-vite-plugin`
27
+ * This module statically imports `@alchemy.run/cloudflare-runtime/vite`
27
28
  * (~0.5s to load), which is only needed for vite-based workers. Importers
28
29
  * MUST load it lazily (`Effect.promise(() => import("./Sources/Vite.ts"))`
29
30
  * from the dispatch in `Source.ts`, or the legacy vite arms in the
@@ -104,27 +105,48 @@ export const viteDev = (
104
105
  pluginOptions: CloudflareVitePluginOptions,
105
106
  serverOptions: vite.ServerOptions,
106
107
  ) =>
107
- Effect.acquireRelease(
108
- ConsoleService.consoleWith((console) =>
109
- Effect.promise(async () => {
110
- process.env[ALCHEMY_CLOUDFLARE_VITE_INJECTED] = "1";
111
- const vite = await loadVite(rootDir);
112
- const devServer = await vite.createServer({
113
- root: rootDir,
114
- define: getDefine(env),
115
- plugins: [cloudflare(pluginOptions)],
116
- server: serverOptions,
117
- customLogger: makeViteLogger(console),
118
- });
119
- await devServer.listen();
120
- return devServer;
121
- }),
122
- ),
123
- (devServer) =>
124
- Effect.promise(async () => {
125
- await devServer.close();
126
- }),
127
- );
108
+ Effect.gen(function* () {
109
+ yield* Effect.sync(() => {
110
+ process.env[ALCHEMY_CLOUDFLARE_VITE_INJECTED] = "1";
111
+ });
112
+ const vite = yield* Effect.promise(() => loadVite(rootDir));
113
+ // `port: 0` is a true OS-assigned random port on Vite >= 8.2.1
114
+ // (vitejs/vite#23158); older Vite treats it as "no port given" and
115
+ // hunts upward from the 5173 default — colliding with (or
116
+ // IPv6-shadowing) user-facing dev ports. Substitute a probed
117
+ // ephemeral port there; `strictPort: false` handles the small
118
+ // probe→bind race by moving to the next ephemeral port.
119
+ const server =
120
+ serverOptions.port === 0 && !viteSupportsPortZero(vite.version)
121
+ ? {
122
+ ...serverOptions,
123
+ port: yield* findAvailablePort(
124
+ typeof serverOptions.host === "string"
125
+ ? serverOptions.host
126
+ : undefined,
127
+ ).pipe(Effect.orDie),
128
+ }
129
+ : serverOptions;
130
+ return yield* Effect.acquireRelease(
131
+ ConsoleService.consoleWith((console) =>
132
+ Effect.promise(async () => {
133
+ const devServer = await vite.createServer({
134
+ root: rootDir,
135
+ define: getDefine(env),
136
+ plugins: [cloudflare(pluginOptions)],
137
+ server,
138
+ customLogger: makeViteLogger(console),
139
+ });
140
+ await devServer.listen();
141
+ return devServer;
142
+ }),
143
+ ),
144
+ (devServer) =>
145
+ Effect.promise(async () => {
146
+ await devServer.close();
147
+ }),
148
+ );
149
+ });
128
150
 
129
151
  /**
130
152
  * Run a production Vite build in a child process rooted at the project
@@ -4,7 +4,7 @@ import type {
4
4
  QueueConsumer,
5
5
  RuntimeWorker,
6
6
  Workflow,
7
- } from "@distilled.cloud/cloudflare-runtime";
7
+ } from "@alchemy.run/cloudflare-runtime/core";
8
8
  import type { BundleOutput } from "../../Bundle/Bundle.ts";
9
9
  import type { WorkerBinding } from "./WorkerBinding.ts";
10
10
  import type { WorkerAssetsConfig, WorkerSourceDescriptor } from "./Worker.ts";
@@ -154,6 +154,12 @@ export const runViteBuildChild = (
154
154
  stdout: "pipe",
155
155
  stderr: "pipe",
156
156
  extendEnv: true,
157
+ // A production build must not inherit the parent's NODE_ENV: a
158
+ // deploy driven from a test process (`bun test` sets
159
+ // NODE_ENV=test) would otherwise bake `import.meta.env.DEV`
160
+ // into the server bundle (e.g. SolidStart then ships its
161
+ // dev-only manifest and every SSR request 500s).
162
+ env: { NODE_ENV: "production" },
157
163
  killSignal: "SIGKILL",
158
164
  },
159
165
  ),
@@ -1,4 +1,4 @@
1
- import { layerRuntime } from "@distilled.cloud/cloudflare-runtime";
1
+ import { layerRuntime } from "@alchemy.run/cloudflare-runtime/core";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Layer from "effect/Layer";
4
4
  import * as Stdio from "effect/Stdio";
@@ -13,7 +13,6 @@ import {
13
13
  import { CloudflareAuth } from "../Auth/AuthProvider.ts";
14
14
  import * as Credentials from "../Credentials.ts";
15
15
  import * as RpcServerEnvironment from "../../Local/RpcServerEnvironment.ts";
16
- import { findAvailablePort } from "../../Util/Node.ts";
17
16
  import { PlatformServices, runMain } from "../../Util/PlatformServices.ts";
18
17
  import { materializeRuntimeBindings } from "./RuntimeBindings.ts";
19
18
  import { loadSource, SourceProviderError } from "./Source.ts";
@@ -71,14 +70,10 @@ const program = Effect.scoped(
71
70
  );
72
71
  const source = config.source;
73
72
  const viteHost = "127.0.0.1";
74
- // TODO(BlankParticle): replace this probe with `port: 0` once the Vite fix
75
- // is released: https://github.com/vitejs/vite/pull/23158
76
- // Vite currently treats `port: 0` as an absent value and falls back to
77
- // its default port. Probe an ephemeral port ourselves until the fixed
78
- // Vite release is available.
79
- // `strictPort: false` still handles the small race between releasing the
80
- // probe socket and Vite binding the selected port.
81
- const vitePort = source ? undefined : yield* findAvailablePort(viteHost);
73
+ // `viteDev` resolves `port: 0` per the loaded Vite: a true OS-assigned
74
+ // random port on Vite >= 8.2.1 (vitejs/vite#23158), a probed ephemeral
75
+ // port on older Vite.
76
+ const vitePort = source ? undefined : 0;
82
77
  const url = source
83
78
  ? yield* loadSource(source.descriptor).pipe(
84
79
  // `loadSource` is typed against the full `SourceServices` union
@@ -39,14 +39,14 @@ import type { Reference as ZoneReference } from "../Zone/lookup.ts";
39
39
  import { type Assets, type AssetsProps } from "./Assets.ts";
40
40
  import { type DurableObjectExport } from "./DurableObject.ts";
41
41
  import { Request } from "./Request.ts";
42
+ import type { ModuleRule } from "./Sources/Prebuilt.ts";
43
+ import type { WorkerBuildOptions } from "./Sources/Rolldown.ts";
42
44
  import { bindWorkerAsyncBindings } from "./WorkerAsyncBindings.ts";
43
45
  import type {
44
46
  WorkerBinding,
45
47
  WorkerBindingResource,
46
48
  WorkerBindings,
47
49
  } from "./WorkerBinding.ts";
48
- import type { ModuleRule } from "./Sources/Prebuilt.ts";
49
- import type { WorkerBuildOptions } from "./Sources/Rolldown.ts";
50
50
  import {
51
51
  makeWorkerRuntimeContext,
52
52
  type WorkerRuntimeContext,
@@ -565,7 +565,10 @@ export interface WorkerVersionOptions {
565
565
  }
566
566
 
567
567
  export interface WorkerProps<
568
- Bindings extends WorkerBindingProps = any,
568
+ // PERF: unconstrained for the same reason as `Worker<Bindings>` above —
569
+ // the `extends WorkerBindingProps` proof is expensive for generic mapped
570
+ // types and the call-site overloads already constrain user input.
571
+ Bindings = any,
569
572
  Assets extends WorkerAssetsConfig | undefined =
570
573
  | WorkerAssetsConfig
571
574
  | undefined,
@@ -628,6 +631,13 @@ export interface WorkerProps<
628
631
  * that hash authoritative instead — the directory is not read during
629
632
  * planning at all.
630
633
  *
634
+ * Requests are served assets-first by default: a request matching a file
635
+ * never invokes the Worker. `runWorkerFirst` inverts that — `true` routes
636
+ * every request through the Worker ahead of the asset layer (serve files
637
+ * yourself via the `ASSETS` binding), and a glob array (e.g. `["/api/*"]`)
638
+ * routes only matching paths worker-first. The same routing applies under
639
+ * `alchemy dev`.
640
+ *
631
641
  * When neither {@link main} nor {@link script} is provided, the Worker is
632
642
  * deployed **assets-only**: no script is uploaded at all and Cloudflare's
633
643
  * asset layer serves every request, applying `htmlHandling` /
@@ -1026,7 +1036,14 @@ export interface ViteOptions {
1026
1036
  };
1027
1037
  }
1028
1038
 
1029
- export type Worker<Bindings extends WorkerBindings = any> = Resource<
1039
+ // PERF: deliberately NOT `Bindings extends WorkerBindings`. The constraint
1040
+ // forced the checker to prove the generic `NormalizedBindings<...>` mapped
1041
+ // type assignable to the ~30-member `WorkerBindingResource` union at every
1042
+ // `Worker<...>` instantiation — a single 28s structural relation that was 45%
1043
+ // of the whole program's check time. Input is already constrained at the
1044
+ // call boundary (`Bindings extends WorkerBindingProps`), so this type
1045
+ // argument is only ever produced from validated shapes.
1046
+ export type Worker<Bindings = any> = Resource<
1030
1047
  WorkerTypeId,
1031
1048
  WorkerProps<Bindings>,
1032
1049
  {
@@ -2126,14 +2143,17 @@ export const Worker: ResourceClassLike<Worker> &
2126
2143
  | Container.Application<any>
2127
2144
  | PlatformServices
2128
2145
  | Tag,
2146
+ PropsReq = never,
2129
2147
  >(
2130
2148
  id: Id,
2131
- props: InputProps<WorkerProps>,
2149
+ props:
2150
+ | InputProps<WorkerProps>
2151
+ | Effect.Effect<InputProps<WorkerProps>, ConfigError, PropsReq>,
2132
2152
  impl: Effect.Effect<Shape, ConfigError, Req>,
2133
2153
  ): Effect.Effect<
2134
2154
  Worker & Rpc<Self>,
2135
2155
  never,
2136
- Extract<Req, Container.Application<any>> | Providers
2156
+ Extract<Req, Container.Application<any>> | Providers | PropsReq
2137
2157
  > &
2138
2158
  Named<Id> & {
2139
2159
  new (): MakeShape<Shape, WorkerShape> & Named<Id> & Tag;
@@ -29,6 +29,7 @@ import { initialCwd } from "../../Util/Node.ts";
29
29
  import { sha256Object } from "../../Util/sha256.ts";
30
30
  import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
31
31
  import { localRuntimeServices } from "../LocalRuntime.ts";
32
+ import { detachQueueConsumersOfScript } from "../Queues/Consumer.ts";
32
33
  import { CloudflareLogs } from "../Logs.ts";
33
34
  import { resolveZoneId } from "../Zone/lookup.ts";
34
35
  import {
@@ -762,7 +763,23 @@ const deleteWorkerScript = (
762
763
  force: true,
763
764
  });
764
765
  }
765
- return yield* workers.deleteScript({ accountId, scriptName, force: true });
766
+ return yield* workers
767
+ .deleteScript({ accountId, scriptName, force: true })
768
+ .pipe(
769
+ // The script is still registered as a queue consumer (even with
770
+ // `force`). Normally the sibling Consumer resource detaches first,
771
+ // but state loss (e.g. a consumer row rewritten by a pre-stamping
772
+ // dev run) can strand a live consumer pointing at this script with
773
+ // nothing left to delete it. The script is going away, so any
774
+ // consumer wiring pointing at it is dead — detach and retry.
775
+ Effect.catchTag("QueueConsumerConflict", () =>
776
+ detachQueueConsumersOfScript(accountId, scriptName).pipe(
777
+ Effect.andThen(
778
+ workers.deleteScript({ accountId, scriptName, force: true }),
779
+ ),
780
+ ),
781
+ ),
782
+ );
766
783
  });
767
784
 
768
785
  /**
@@ -2224,7 +2241,7 @@ export const LiveWorkerProvider = () =>
2224
2241
  });
2225
2242
 
2226
2243
  // Loaded lazily: `./Sources/Vite.ts` pulls in
2227
- // `@distilled.cloud/cloudflare-vite-plugin` (~0.5s), which is only
2244
+ // `@alchemy.run/cloudflare-runtime/vite` (~0.5s), which is only
2228
2245
  // needed for vite-based workers at build time — not for every Worker
2229
2246
  // definition at module-load time.
2230
2247
  const loadVite = Effect.promise(() => import("./Sources/Vite.ts"));
@@ -582,17 +582,23 @@ export const DockerLive = Layer.effect(
582
582
  ),
583
583
  container: {
584
584
  create: ({ image, env, command, context, ...options }) =>
585
- run([
586
- ...formatArgs({ context }),
587
- "container",
588
- "create",
589
- ...formatArgs({
590
- ...options,
591
- env: env ? Object.keys(env) : undefined,
592
- }),
593
- image,
594
- ...(command ?? []),
595
- ]),
585
+ // `--env KEY` (name-only) keeps values off CLI arguments; Docker
586
+ // copies each value from the CLI process environment, so the
587
+ // normalized container env must be passed to the child process.
588
+ run(
589
+ [
590
+ ...formatArgs({ context }),
591
+ "container",
592
+ "create",
593
+ ...formatArgs({
594
+ ...options,
595
+ env: env ? Object.keys(env) : undefined,
596
+ }),
597
+ image,
598
+ ...(command ?? []),
599
+ ],
600
+ env,
601
+ ),
596
602
  inspect: (name, context) =>
597
603
  runInspect<Docker.Container>([
598
604
  ...formatArgs({ context }),
@@ -69,6 +69,7 @@ export interface DurableObjectConfig<
69
69
  * ```
70
70
  *
71
71
  * ```typescript
72
+ * import * as Drizzle from "alchemy/Drizzle/Cloudflare";
72
73
  * import migrations from "./drizzle/migrations.js";
73
74
  * import { posts, relations, users } from "./schema.ts";
74
75
  *
@@ -1,13 +1,15 @@
1
- import * as MysqlClient from "@effect/sql-mysql2/MysqlClient";
1
+ // `@effect/sql-mysql2` (and mysql2 underneath) are optional peers — value
2
+ // imports are deferred to first use so `alchemy/Drizzle` resolves without
3
+ // them installed.
4
+ import type * as MysqlClient from "@effect/sql-mysql2/MysqlClient";
2
5
  import type { AnyRelations, EmptyRelations } from "drizzle-orm";
3
6
  import type { EffectMysql2Database } from "drizzle-orm/effect-mysql2";
4
- import * as MySqlDrizzle from "drizzle-orm/effect-mysql2";
5
7
  import type { EffectDrizzleMySqlConfig } from "drizzle-orm/mysql-core/effect/utils";
6
8
  import * as Effect from "effect/Effect";
7
9
  import * as Layer from "effect/Layer";
8
10
  import type * as Redacted from "effect/Redacted";
9
11
  import { makeExecutionMemo } from "../Runtime/ExecutionMemo.ts";
10
- import { resolveMySQLConfig, type MySQLConfig } from "../SQL/MySQL.ts";
12
+ import type { MySQLConfig } from "../SQL/MySQL.ts";
11
13
  import { proxyChain } from "../Util/proxy-chain.ts";
12
14
 
13
15
  /**
@@ -55,6 +57,14 @@ export const MySQL = <
55
57
  Effect.map(
56
58
  makeExecutionMemo(
57
59
  Effect.gen(function* () {
60
+ const [MysqlClient, MySqlDrizzle, { resolveMySQLConfig }] =
61
+ yield* Effect.promise(() =>
62
+ Promise.all([
63
+ import("@effect/sql-mysql2/MysqlClient"),
64
+ import("drizzle-orm/effect-mysql2"),
65
+ import("../SQL/MySQL.ts"),
66
+ ]),
67
+ );
58
68
  const { client, ...drizzleConfig } = config ?? {};
59
69
  const mysqlCtx = yield* Layer.build(
60
70
  MysqlClient.layer(
@@ -1,7 +1,9 @@
1
- import * as PgClient from "@effect/sql-pg/PgClient";
1
+ // `@effect/sql-pg` (and pg underneath) are optional peers — value imports
2
+ // are deferred to first use so `alchemy/Drizzle` resolves without them
3
+ // installed.
4
+ import type * as PgClient from "@effect/sql-pg/PgClient";
2
5
  import type { AnyRelations, EmptyRelations } from "drizzle-orm";
3
6
  import type { EffectPgDatabase } from "drizzle-orm/effect-postgres";
4
- import * as PgDrizzle from "drizzle-orm/effect-postgres";
5
7
  import type { EffectDrizzlePgConfig } from "drizzle-orm/pg-core/effect/utils";
6
8
  import * as Effect from "effect/Effect";
7
9
  import * as Layer from "effect/Layer";
@@ -56,6 +58,12 @@ export const Postgres = <
56
58
  Effect.map(
57
59
  makeExecutionMemo(
58
60
  Effect.gen(function* () {
61
+ const [PgClient, PgDrizzle] = yield* Effect.promise(() =>
62
+ Promise.all([
63
+ import("@effect/sql-pg/PgClient"),
64
+ import("drizzle-orm/effect-postgres"),
65
+ ]),
66
+ );
59
67
  const pgCtx = yield* Layer.build(
60
68
  PgClient.layer({ url: yield* connectionString }),
61
69
  );
@@ -1,5 +1,5 @@
1
1
  export * from "./D1.ts";
2
- export * from "./DurableObject.ts";
2
+
3
3
  // The tagged errors drizzle's effect drivers fail with — re-exported so
4
4
  // consumers can reference them without reaching into drizzle-orm
5
5
  // internals. (`Effect.catchTag` needs only the tag string; these are for
@@ -10,7 +10,10 @@ export {
10
10
  EffectTransactionRollbackError,
11
11
  MigratorInitError,
12
12
  } from "drizzle-orm/effect-core";
13
- export * from "./MySQL.ts";
14
- export * from "./Postgres.ts";
15
13
  export * from "./Providers.ts";
16
14
  export * from "./Schema.ts";
15
+ // MySQL and Postgres are deliberately NOT re-exported here: their drivers
16
+ // (`@effect/sql-mysql2`/`mysql2`, `@effect/sql-pg`/`pg`) are optional peer
17
+ // dependencies, and keeping them out of the barrel keeps them out of every
18
+ // non-user's module graph. Import them via their subpaths instead:
19
+ // `alchemy/Drizzle/MySQL`, `alchemy/Drizzle/Postgres`.
@@ -2,18 +2,9 @@ import * as Data from "effect/Data";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Redacted from "effect/Redacted";
4
4
  import type { Client } from "pg";
5
+ import { importPg } from "../SQL/PostgresDriver.ts";
5
6
  import type { SqlFile } from "../SQL/SqlFile.ts";
6
7
 
7
- // `pg` is an optional peer dependency — loaded lazily so importing the Neon
8
- // provider never requires the driver unless migrations actually run.
9
- const importPg = () =>
10
- import("pg").catch((cause) => {
11
- throw new Error(
12
- "Failed to load the 'pg' driver. Install the optional peer dependency 'pg' to run Neon migrations.",
13
- { cause },
14
- );
15
- });
16
-
17
8
  export class PgError extends Data.TaggedError("PgError")<{
18
9
  message: string;
19
10
  cause?: unknown;
@@ -19,6 +19,7 @@ export const createPhysicalName = Effect.fn(function* ({
19
19
  maxLength = 64,
20
20
  delimiter = "-",
21
21
  lowercase = false,
22
+ forbiddenPrefixes = [],
22
23
  }: {
23
24
  id: string;
24
25
  /**
@@ -46,6 +47,14 @@ export const createPhysicalName = Effect.fn(function* ({
46
47
  delimiter?: string;
47
48
  /** Whether to lowercase the physical name. @default false */
48
49
  lowercase?: boolean;
50
+ /**
51
+ * Service-reserved name prefixes (matched case-insensitively). When the
52
+ * generated prefix would start with one of these — e.g. a stack named
53
+ * `aws-*` colliding with S3 Tables' reserved `aws` bucket prefix — a safe
54
+ * `x${delimiter}` is prepended so the name stays valid. Names that don't
55
+ * collide are unaffected, so existing deployments never rename.
56
+ */
57
+ forbiddenPrefixes?: string[];
49
58
  }) {
50
59
  // Always generate DNS-compatible names (letters, numbers, and hyphens only).
51
60
  // This ensures physical names work across all services including S3 buckets.
@@ -56,8 +65,15 @@ export const createPhysicalName = Effect.fn(function* ({
56
65
  );
57
66
  const stack = yield* Stack;
58
67
  const stage = yield* Stage;
59
- const prefix =
68
+ let prefix =
60
69
  _prefix ?? `${stack.name}${delimiter}${id}${delimiter}${stage}${delimiter}`;
70
+ if (
71
+ forbiddenPrefixes.some((forbidden) =>
72
+ prefix.toLowerCase().startsWith(forbidden.toLowerCase()),
73
+ )
74
+ ) {
75
+ prefix = `x${delimiter}${prefix}`;
76
+ }
61
77
  const randomId = base32(
62
78
  Buffer.from(instanceId ?? (yield* InstanceId), "hex"),
63
79
  );
package/src/Plan.ts CHANGED
@@ -40,7 +40,11 @@ import {
40
40
  tryFindProviderByType,
41
41
  type ProviderService,
42
42
  } from "./Provider.ts";
43
- import { defaultProviderMode, type ProviderMode } from "./ProviderMode.ts";
43
+ import {
44
+ defaultProviderMode,
45
+ stampedMode,
46
+ type ProviderMode,
47
+ } from "./ProviderMode.ts";
44
48
  import {
45
49
  isResource,
46
50
  type ResourceBinding,
@@ -343,17 +347,20 @@ export const make = <A>(
343
347
 
344
348
  /**
345
349
  * Has this resource switched provider modes since it was last
346
- * reconciled? Rows without a persisted mode (legacy rows, or rows
347
- * written by a mode-agnostic provider) are assumed to already be in
348
- * the current run's mode — no replacement churn.
350
+ * reconciled? Rows without a persisted mode were written by a
351
+ * pre-provider-mode engine (or a provider that only became dual-mode
352
+ * later) — their physical resource is LIVE unless its attrs carry a
353
+ * `dev:` identity marker proving it was reconciled locally (see
354
+ * {@link stampedMode}). A dev run replaces unstamped live rows exactly
355
+ * like stamped live rows; live runs replace unstamped marker rows.
349
356
  */
350
357
  const hasModeSwitched = (
351
358
  mode: ProviderMode | undefined,
352
359
  oldState: ResourceState | undefined,
353
360
  ): boolean =>
354
361
  mode !== undefined &&
355
- oldState?.providerMode !== undefined &&
356
- oldState.providerMode !== mode;
362
+ oldState !== undefined &&
363
+ stampedMode(oldState) !== mode;
357
364
 
358
365
  const resourceFqns = yield* state.list({
359
366
  stack: stackName,
@@ -1781,12 +1788,16 @@ export const make = <A>(
1781
1788
  // and the remediation instead of limping into a partial apply.
1782
1789
  //
1783
1790
  // Orphan deletes resolve the provider variant for the mode
1784
- // that created the row (`providerMode`), so e.g. a local dev
1791
+ // that created the row (`providerMode`, or the `dev:` marker
1792
+ // inference for legacy unstamped rows), so e.g. a local dev
1785
1793
  // worker's row is deleted by the local provider even during a
1786
- // live deploy — and vice versa.
1794
+ // live deploy — and vice versa. Unstamped rows are physically
1795
+ // live unless their attrs carry the marker (see stampedMode),
1796
+ // never the run default.
1797
+ const rowMode = stampedMode(oldState);
1787
1798
  const providerOption = yield* tryFindProviderByType(
1788
1799
  resourceType,
1789
- oldState.providerMode,
1800
+ rowMode,
1790
1801
  );
1791
1802
  if (Option.isNone(providerOption)) {
1792
1803
  return yield* Effect.die(
@@ -59,6 +59,62 @@ export class ConflictingProviderModeError extends Data.TaggedError(
59
59
  conflictingMode: ProviderMode | undefined;
60
60
  }> {}
61
61
 
62
+ /**
63
+ * Prefix marking a locally-emulated resource's physical identity. Local
64
+ * providers fabricate ids/names with this prefix (`dev:<uuid>` queue and
65
+ * namespace ids, `dev:`-prefixed bucket names, ...) so a resource's
66
+ * persisted attributes reveal which runtime hosts it even without the
67
+ * `providerMode` stamp.
68
+ */
69
+ export const LOCAL_ID_PREFIX = "dev:";
70
+
71
+ /**
72
+ * Does a persisted attributes value carry a local identity marker — any
73
+ * string value (at any depth) with the {@link LOCAL_ID_PREFIX}? Persisted
74
+ * attrs are plain JSON (state commits strip unresolved values), so a
75
+ * structural scan is safe.
76
+ */
77
+ export const hasLocalIdentity = (value: unknown): boolean => {
78
+ if (typeof value === "string") return value.startsWith(LOCAL_ID_PREFIX);
79
+ if (Array.isArray(value)) return value.some(hasLocalIdentity);
80
+ if (typeof value === "object" && value !== null) {
81
+ return Object.values(value).some(hasLocalIdentity);
82
+ }
83
+ return false;
84
+ };
85
+
86
+ /**
87
+ * The mode a persisted state row was actually reconciled with: its stamped
88
+ * `providerMode`, or — for legacy rows written before stamping existed —
89
+ * `"local"` when the persisted attributes carry a {@link LOCAL_ID_PREFIX}
90
+ * identity marker, `"live"` otherwise.
91
+ *
92
+ * An unstamped row without a marker predates provider modes or was written
93
+ * by a mode-agnostic provider — in both cases the write acted on the REAL
94
+ * cloud, so the row's physical resource is `"live"`. Assuming the current
95
+ * run's mode instead silently adopts a deployed cloud resource as a local
96
+ * instance during `alchemy dev`: the row noops (or restarts locally over
97
+ * the live attrs), gets re-stamped `"local"`, and the live resource becomes
98
+ * untracked — it is never deleted by a later destroy or replacement, and
99
+ * its deployed URL keeps serving.
100
+ *
101
+ * An unstamped row WITH a marker (e.g. a queue with a `dev:<uuid>` id
102
+ * written by `alchemy dev` on a pre-stamping version) is the mirror case:
103
+ * assuming `"live"` hands the `dev:` identity to the live provider, which
104
+ * sends it to the real cloud API (Cloudflare rejects it as a malformed
105
+ * parameter, permanently wedging the destroy). The marker proves the row
106
+ * was reconciled locally, so it is handled as `"local"`.
107
+ *
108
+ * For mode-agnostic providers the returned mode is harmless either way:
109
+ * `providerForMode` ignores the mode when the provider has a single
110
+ * implementation.
111
+ */
112
+ export const stampedMode = (row: {
113
+ readonly providerMode?: ProviderMode | undefined;
114
+ readonly attr?: unknown;
115
+ }): ProviderMode =>
116
+ row.providerMode ?? (hasLocalIdentity(row.attr) ? "local" : "live");
117
+
62
118
  /**
63
119
  * Run the wrapped resources **remotely (against the real cloud) even during
64
120
  * `alchemy dev`** — the opt-out from local emulation. During `alchemy deploy`