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
@@ -0,0 +1,101 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Redacted from "effect/Redacted";
3
+ import * as Output from "../Output.ts";
4
+ import type { RuntimeContext } from "../RuntimeContext.ts";
5
+ import { sha256 } from "../Util/sha256.ts";
6
+
7
+ /**
8
+ * A database connection-string source, in every shape alchemy hands them
9
+ * out:
10
+ *
11
+ * - a literal / `Redacted` string
12
+ * - a resource Output (Neon `project.connectionUri`, PlanetScale
13
+ * `role.connectionUrl`, Prisma `connection.databaseUrl`, ...) — bound
14
+ * into the host environment at deploy and read back at runtime
15
+ * - a runtime-only Effect (Cloudflare Hyperdrive's per-invocation
16
+ * `connectionString`)
17
+ */
18
+ export type ConnectionSource =
19
+ | string
20
+ | Redacted.Redacted<string>
21
+ | Output.Output<string>
22
+ | Output.Output<Redacted.Redacted<string>>
23
+ | Effect.Effect<Redacted.Redacted<string>, never, RuntimeContext>;
24
+
25
+ /**
26
+ * The deploy-resolvable subset of {@link ConnectionSource}: literals and
27
+ * resource Outputs. Runtime-only Effects (Hyperdrive) are excluded —
28
+ * deploy-time consumers (migrations, seeds) need a value that resolves
29
+ * during stack evaluation.
30
+ */
31
+ export type StaticConnectionSource = Exclude<
32
+ ConnectionSource,
33
+ Effect.Effect<Redacted.Redacted<string>, never, RuntimeContext>
34
+ >;
35
+
36
+ const toRedacted = (
37
+ value: string | Redacted.Redacted<string>,
38
+ ): Redacted.Redacted<string> =>
39
+ typeof value === "string" ? Redacted.make(value) : value;
40
+
41
+ /**
42
+ * Resolve a {@link ConnectionSource} to its runtime accessor effect.
43
+ *
44
+ * Outputs are yielded NOW (binding into the host environment during a
45
+ * host init, or recording an Action capture during an Action init) and
46
+ * the returned accessor reads the resolved value back later; Effects pass
47
+ * through untouched; literals wrap.
48
+ */
49
+ export const resolveConnectionSource = (
50
+ source: ConnectionSource,
51
+ ): Effect.Effect<Effect.Effect<Redacted.Redacted<string>>> =>
52
+ Effect.gen(function* () {
53
+ if (Output.isOutput(source)) {
54
+ const accessor = yield* source;
55
+ return Effect.map(
56
+ accessor as Effect.Effect<string | Redacted.Redacted<string>>,
57
+ toRedacted,
58
+ );
59
+ }
60
+ if (Effect.isEffect(source)) {
61
+ return source as Effect.Effect<Redacted.Redacted<string>>;
62
+ }
63
+ return Effect.succeed(toRedacted(source));
64
+ }) as Effect.Effect<Effect.Effect<Redacted.Redacted<string>>>;
65
+
66
+ /**
67
+ * Pick the deploy-resolvable source for deploy-time work (migrations,
68
+ * seeds): an explicit override wins, `false` disables, and the primary
69
+ * source is a valid default only when it is itself deploy-resolvable.
70
+ */
71
+ export const staticConnectionSource = (
72
+ source: ConnectionSource,
73
+ override: StaticConnectionSource | false | undefined,
74
+ ): StaticConnectionSource | undefined => {
75
+ if (override === false) {
76
+ return undefined;
77
+ }
78
+ if (override !== undefined) {
79
+ return override;
80
+ }
81
+ return Effect.isEffect(source) && !Output.isOutput(source)
82
+ ? undefined
83
+ : (source as StaticConnectionSource);
84
+ };
85
+
86
+ /**
87
+ * Non-secret digest of a connection source — a sha256 Output suitable for
88
+ * persisted identity inputs (e.g. a migration Action's diff key) where the
89
+ * connection string itself must never be stored.
90
+ */
91
+ export const connectionSourceDigest = (
92
+ source: StaticConnectionSource,
93
+ ): Output.Output<string> => {
94
+ const digest = (value: string | Redacted.Redacted<string>) =>
95
+ sha256(typeof value === "string" ? value : Redacted.value(value));
96
+ return Output.isOutput(source)
97
+ ? (Output.mapEffect(digest)(
98
+ source as Output.Output<string | Redacted.Redacted<string>>,
99
+ ) as Output.Output<string>)
100
+ : (Output.fromEffect(digest(source)) as Output.Output<string>);
101
+ };
@@ -0,0 +1,49 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Redacted from "effect/Redacted";
3
+ import type * as Scope from "effect/Scope";
4
+ import type { Pool, PoolOptions } from "mysql2/promise";
5
+
6
+ /**
7
+ * Lazily load the `mysql2/promise` driver (an optional peer dependency of
8
+ * alchemy) with a descriptive failure when it isn't installed. The
9
+ * promise flavor is the canonical surface — its `.pool` property exposes
10
+ * the callback pool for consumers (like Kysely) that drive that API.
11
+ */
12
+ export const importMySql = (): Promise<typeof import("mysql2/promise")> =>
13
+ import("mysql2/promise")
14
+ .then((mod) =>
15
+ (mod as { default?: { createPool?: unknown } }).default?.createPool !==
16
+ undefined
17
+ ? (mod as unknown as { default: typeof import("mysql2/promise") })
18
+ .default
19
+ : mod,
20
+ )
21
+ .catch((cause) => {
22
+ throw new Error(
23
+ "Failed to load the 'mysql2' driver. Install the optional peer dependency 'mysql2' to connect to MySQL.",
24
+ { cause },
25
+ );
26
+ });
27
+
28
+ /**
29
+ * Open a raw `mysql2/promise` pool on the current `Scope` — `pool.end()`
30
+ * runs when the scope closes. Pair with `makeExecutionMemo` for the
31
+ * one-pool-per-event shape workerd and Lambda require.
32
+ *
33
+ * `connectionLimit` defaults to 1: per-execution pools never need more
34
+ * than one connection.
35
+ */
36
+ export const openMySQLPool = (
37
+ url: Effect.Effect<Redacted.Redacted<string>>,
38
+ config?: Omit<PoolOptions, "uri">,
39
+ ): Effect.Effect<Pool, never, Scope.Scope> =>
40
+ Effect.gen(function* () {
41
+ const mysql = yield* Effect.promise(importMySql);
42
+ const uri = Redacted.value(yield* url);
43
+ return yield* Effect.acquireRelease(
44
+ Effect.sync(() =>
45
+ mysql.createPool({ uri, connectionLimit: 1, ...config }),
46
+ ),
47
+ (pool) => Effect.promise(() => pool.end()),
48
+ );
49
+ });
@@ -0,0 +1,45 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Redacted from "effect/Redacted";
3
+ import type * as Scope from "effect/Scope";
4
+ import type { Pool, PoolConfig } from "pg";
5
+
6
+ /**
7
+ * Lazily load the raw `pg` driver (an optional peer dependency of
8
+ * alchemy) with a descriptive failure when it isn't installed. CJS/ESM
9
+ * interop is normalized — callers always get the module's named surface.
10
+ */
11
+ export const importPg = (): Promise<typeof import("pg")> =>
12
+ import("pg")
13
+ .then((mod) =>
14
+ (mod as { default?: { Pool?: unknown } }).default?.Pool !== undefined
15
+ ? (mod as unknown as { default: typeof import("pg") }).default
16
+ : mod,
17
+ )
18
+ .catch((cause) => {
19
+ throw new Error(
20
+ "Failed to load the 'pg' driver. Install the optional peer dependency 'pg' to connect to Postgres.",
21
+ { cause },
22
+ );
23
+ });
24
+
25
+ /**
26
+ * Open a raw `pg.Pool` on the current `Scope` — `pool.end()` runs when the
27
+ * scope closes. Pair with `makeExecutionMemo` for the one-pool-per-event
28
+ * shape workerd and Lambda require (see `SQL/Postgres.ts` for the
29
+ * effect-sql equivalent).
30
+ *
31
+ * `max` defaults to 1: per-execution pools never need more than one
32
+ * connection.
33
+ */
34
+ export const openPostgresPool = (
35
+ url: Effect.Effect<Redacted.Redacted<string>>,
36
+ config?: Omit<PoolConfig, "connectionString">,
37
+ ): Effect.Effect<Pool, never, Scope.Scope> =>
38
+ Effect.gen(function* () {
39
+ const pg = yield* Effect.promise(importPg);
40
+ const connectionString = Redacted.value(yield* url);
41
+ return yield* Effect.acquireRelease(
42
+ Effect.sync(() => new pg.Pool({ connectionString, max: 1, ...config })),
43
+ (pool) => Effect.promise(() => pool.end()),
44
+ );
45
+ });
@@ -215,6 +215,30 @@ export const makeLocalState = () =>
215
215
  }
216
216
  }),
217
217
  ),
218
+ // Deleting the last stage leaves an empty `{stack}/` husk that
219
+ // `listStacks` would keep reporting forever (and durable
220
+ // per-test scratch stacks would accumulate one per test). Prune
221
+ // it when no stages remain. The read-then-remove is not racy:
222
+ // nothing recreates a stage dir concurrently with the session
223
+ // that is deleting it (same invariant as recoverMissingDir).
224
+ Effect.tap(() => {
225
+ if (stage === undefined) return Effect.void;
226
+ const stackDir = path.join(stateDir, stack);
227
+ return fs.readDirectory(stackDir).pipe(
228
+ Effect.flatMap((entries) =>
229
+ entries.length === 0
230
+ ? fs
231
+ .remove(stackDir, { recursive: true })
232
+ .pipe(
233
+ Effect.tap(() =>
234
+ Effect.sync(() => created.delete(stackDir)),
235
+ ),
236
+ )
237
+ : Effect.void,
238
+ ),
239
+ Effect.ignore,
240
+ );
241
+ }),
218
242
  );
219
243
  }),
220
244
  list: (request) =>
@@ -68,8 +68,12 @@ interface BaseResourceState {
68
68
  *
69
69
  * `undefined` means either a mode-agnostic provider (single
70
70
  * implementation serves both dev and deploy) or a legacy row written
71
- * before modes were persisted — both are treated as "whatever the
72
- * current run's mode is" (no replacement churn).
71
+ * before modes were persisted — both acted on the real cloud, so an
72
+ * unstamped row is assumed **live**, unless its attrs carry a local
73
+ * identity marker (`dev:`-prefixed ids) proving it was reconciled
74
+ * locally, in which case it is assumed **local** (see `stampedMode`).
75
+ * Same-mode runs see no churn; a run in the other mode replaces it like
76
+ * any stamped row.
73
77
  */
74
78
  providerMode?: ProviderMode;
75
79
  }
@@ -18,6 +18,21 @@ export const REDACTED_MARKER = "__redacted__";
18
18
  */
19
19
  export const DURATION_MARKER = "__duration__";
20
20
 
21
+ /**
22
+ * JSON marker used to tag a `Date` value when writing state. The reviver
23
+ * recognises objects with exactly this key and rebuilds a real `Date`
24
+ * instance on read, so a Date-typed prop read back out of persisted state
25
+ * (`olds` in provider `diff`/`delete`/`read`) is a `Date` again — not the
26
+ * bare ISO string `JSON.stringify` would otherwise leave behind.
27
+ */
28
+ export const DATE_MARKER = "__date__";
29
+
30
+ const decodeDate = (encoded: unknown): Date | undefined => {
31
+ if (typeof encoded !== "string") return undefined;
32
+ const date = new Date(encoded);
33
+ return Number.isNaN(date.getTime()) ? undefined : date;
34
+ };
35
+
21
36
  const decodeDuration = (encoded: unknown): Duration.Duration | undefined => {
22
37
  if (encoded === null || typeof encoded !== "object") return undefined;
23
38
  const json = encoded as {
@@ -77,6 +92,16 @@ export const encodeState = (value: unknown): unknown => {
77
92
  [DURATION_MARKER]: value.toJSON(),
78
93
  };
79
94
  }
95
+ // Dates are serializable leaves and must round-trip: walking one
96
+ // structurally would persist `{}` (`Object.entries(new Date())` is empty)
97
+ // — and every later plan would then diff the empty object against the
98
+ // program's Date and churn a phantom update forever. Tag with our marker
99
+ // so the reviver rebuilds a real Date instance; rows written before the
100
+ // marker carry a bare ISO string, which `canonicalize` in Diff.ts still
101
+ // compares as equal to a live Date, so legacy rows never churn either.
102
+ if (value instanceof Date) {
103
+ return { [DATE_MARKER]: value.toJSON() };
104
+ }
80
105
  if (isResource(value)) {
81
106
  return {
82
107
  id: value.LogicalId,
@@ -110,6 +135,14 @@ export const reviveState = (_key: string, value: unknown): unknown => {
110
135
  const decoded = decodeDuration(obj[DURATION_MARKER]);
111
136
  if (decoded !== undefined) return decoded;
112
137
  }
138
+ // Exact single-key match only — unlike the legacy REDACTED/DURATION
139
+ // markers there is no pre-marker data to tolerate, so a user object
140
+ // that merely CONTAINS the key alongside other fields is never
141
+ // collapsed into a Date (mirrors reviveStateRecursive's strictness).
142
+ if (DATE_MARKER in obj && Object.keys(obj).length === 1) {
143
+ const decoded = decodeDate(obj[DATE_MARKER]);
144
+ if (decoded !== undefined) return decoded;
145
+ }
113
146
  }
114
147
  return value;
115
148
  };
@@ -133,6 +166,10 @@ export const reviveStateRecursive = (value: unknown): unknown => {
133
166
  const decoded = decodeDuration(obj[DURATION_MARKER]);
134
167
  if (decoded !== undefined) return decoded;
135
168
  }
169
+ if (keys.length === 1 && keys[0] === DATE_MARKER) {
170
+ const decoded = decodeDate(obj[DATE_MARKER]);
171
+ if (decoded !== undefined) return decoded;
172
+ }
136
173
  const result: Record<string, unknown> = {};
137
174
  for (const [k, v] of Object.entries(obj)) {
138
175
  result[k] = reviveStateRecursive(v);
package/src/Sync.ts CHANGED
@@ -15,6 +15,7 @@ import { deepEqual } from "./Diff.ts";
15
15
  import { InstanceId } from "./InstanceId.ts";
16
16
  import type { Apply, Plan } from "./Plan.ts";
17
17
  import { findProviderByType, Provider } from "./Provider.ts";
18
+ import { stampedMode } from "./ProviderMode.ts";
18
19
  import type { ResourceLike } from "./Resource.ts";
19
20
  import {
20
21
  isActionState,
@@ -187,9 +188,10 @@ export const sync = (
187
188
 
188
189
  // Observe with the provider variant of the mode that created the row —
189
190
  // a local dev worker's state must be read by the local provider.
191
+ // Legacy unstamped rows infer "local" from a `dev:` identity marker.
190
192
  const provider = yield* findProviderByType(
191
193
  resourceType,
192
- old.providerMode,
194
+ stampedMode(old),
193
195
  );
194
196
  if (!provider.read) {
195
197
  return yield* skip(
@@ -445,7 +447,7 @@ export const plan = (stack: {
445
447
  // switches modes — a local ⇄ live switch is a plan-time replacement).
446
448
  const provider = yield* findProviderByType(
447
449
  persisted.resourceType,
448
- persisted.providerMode,
450
+ stampedMode(persisted),
449
451
  );
450
452
  const action =
451
453
  r.action === "drifted"
@@ -9,6 +9,7 @@
9
9
  * concurrency + timeouts itself.
10
10
  */
11
11
  import {
12
+ currentFile,
12
13
  exclusiveOf,
13
14
  registerHook,
14
15
  registerTest,
@@ -166,14 +167,20 @@ export const make = <ROut = any>(options: MakeOptions<ROut>): TestApi => {
166
167
  const wrapProvider = (
167
168
  name: string,
168
169
  fn: (stack: ScratchStack) => Effect.Effect<void, any, any>,
170
+ file: string | undefined,
169
171
  ) => {
170
- const scratch = Core.scratchStack(options, name);
172
+ // Durable, file-namespaced scratch state (`.alchemy/state`). A run that
173
+ // dies mid-delete — e.g. the runner abandons teardown 10s after a test
174
+ // timeout while a CloudFront disable-wait is still in flight — leaves
175
+ // its `deleting` rows on disk, so the NEXT run's leading
176
+ // `stack.destroy()` (or the ensuring teardown below) resumes and drains
177
+ // them instead of planning "no changes" and silently orphaning the
178
+ // cloud resource.
179
+ const scratch = Core.scratchStack(options, name, file);
171
180
  // Guarantee teardown. `test.provider` has no built-in cleanup, so a body
172
181
  // that fails (assertion, API error like a 409/Unauthorized) or is
173
182
  // interrupted (timeout) BEFORE its trailing `stack.destroy()` would
174
- // otherwise leak every cloud resource it deployed: the scratch's
175
- // in-memory state is discarded with the process, so no later run can
176
- // reclaim the orphan (only an account-wide `nuke` can).
183
+ // otherwise leak every cloud resource it deployed.
177
184
  // `scratch.destroy()` is idempotent — a no-op when the body already
178
185
  // destroyed, and it reclaims the orphans otherwise. `Effect.ensuring`
179
186
  // runs the finalizer on success, failure, AND interruption.
@@ -193,15 +200,19 @@ export const make = <ROut = any>(options: MakeOptions<ROut>): TestApi => {
193
200
  fn: (stack: ScratchStack) => Effect.Effect<void, any, any>,
194
201
  opts: TestOptions | undefined,
195
202
  mode: "run" | "skip",
196
- ) =>
203
+ ) => {
204
+ // Captured at registration (module evaluation during collection) — the
205
+ // AsyncLocalStorage file context is gone by the time the body runs.
206
+ const file = currentFile();
197
207
  registerTest({
198
208
  name,
199
209
  mode,
200
210
  exclusive: exclusiveOf(opts),
201
211
  retry: retryOf(opts),
202
212
  timeout: timeoutOf(opts),
203
- body: mode === "skip" ? undefined : () => wrapProvider(name, fn),
213
+ body: mode === "skip" ? undefined : () => wrapProvider(name, fn, file),
204
214
  });
215
+ };
205
216
 
206
217
  const provider = ((name, fn, opts) => {
207
218
  addProvider(name, fn, opts, "run");
package/src/Test/Core.ts CHANGED
@@ -316,11 +316,21 @@ export const destroy = (
316
316
  /**
317
317
  * In-test scratch stack handed to `test.provider(name, (stack) => ...)`.
318
318
  *
319
- * Each scratch stack owns a private in-memory state store that is shared
320
- * between successive `deploy`/`destroy` calls AND visible to the user's test
321
- * body (so `yield* State` / `state.get(...)` see the same store the deploys
319
+ * Each scratch stack owns a private state store that is shared between
320
+ * successive `deploy`/`destroy` calls AND visible to the user's test body
321
+ * (so `yield* State` / `state.get(...)` see the same store the deploys
322
322
  * mutated). This makes create / update / replace / delete paths exercisable
323
- * without polluting `.alchemy/` or other tests in the same file.
323
+ * without polluting other tests in the same file.
324
+ *
325
+ * When the adapter can name the test file (the alchemy-test runner), the
326
+ * store is DURABLE — rows live under `.alchemy/state/{file}-{test}/{stage}`
327
+ * on disk. Durability is what makes an interrupted destroy recoverable: the
328
+ * engine persists a `deleting` row before every `provider.delete` and only
329
+ * drops it on success, so a run killed mid-delete (e.g. the runner's
330
+ * teardown-abandonment after a test timeout) leaves resumable rows that the
331
+ * NEXT run's leading `stack.destroy()` (or the `Effect.ensuring` teardown)
332
+ * picks up and drains. An in-memory scratch dies with the process, silently
333
+ * orphaning every cloud resource whose delete was still in flight.
324
334
  */
325
335
  export interface ScratchStack<ROut = any> {
326
336
  readonly name: string;
@@ -347,24 +357,41 @@ const sanitizeStackName = (name: string) =>
347
357
  name.replaceAll(/[^a-zA-Z0-9_]/g, "-").replace(/-+/g, "-");
348
358
 
349
359
  /**
350
- * Build a fresh `ScratchStack` for `test.provider`. Allocates a private
351
- * in-memory state store so the test is isolated from `.alchemy/` and from
352
- * other tests in the same file.
360
+ * Turn a test-file path (relative to the run root) into a stack-name
361
+ * namespace: `test/AWS/Website/Router.test.ts` -> `AWS/Website/Router`.
362
+ * Test names repeat across files (e.g. "create and delete bucket with
363
+ * default props"), so the durable per-test store MUST be namespaced by file
364
+ * or two concurrently-running same-named tests would read, write and — far
365
+ * worse — destroy each other's rows.
366
+ */
367
+ const scratchNamespace = (file: string) =>
368
+ file.replace(/^test[/\\]/, "").replace(/\.test\.ts$/, "");
369
+
370
+ /**
371
+ * Build a fresh `ScratchStack` for `test.provider`.
372
+ *
373
+ * With `file` (the registration-time test file, supplied by the
374
+ * alchemy-test adapter): the store is the durable `.alchemy/state` local
375
+ * store and the stack name is namespaced by file so interrupted destroys
376
+ * leave resumable rows for the next run (see {@link ScratchStack}).
377
+ *
378
+ * Without `file` (bun/vitest adapters, which cannot name their file at
379
+ * registration time): falls back to a private in-memory store — isolated,
380
+ * but discarded with the process.
353
381
  */
354
382
  export const scratchStack = <ROut>(
355
383
  options: MakeOptions<ROut>,
356
384
  name: string,
385
+ file?: string,
357
386
  ): ScratchStack<ROut> => {
358
387
  const stage = options.stage ?? "test";
359
- const stackName = sanitizeStackName(name);
360
- const inMemory: Record<
361
- string,
362
- Record<string, Record<string, State.ResourceState>>
363
- > = {};
364
- const stateLayer = Layer.succeed(
365
- State.State,
366
- State.InMemoryService(inMemory),
388
+ const stackName = sanitizeStackName(
389
+ file === undefined ? name : `${scratchNamespace(file)}-${name}`,
367
390
  );
391
+ const stateLayer: Layer.Layer<State.State> =
392
+ file === undefined
393
+ ? Layer.succeed(State.State, State.InMemoryService({}))
394
+ : Layer.provide(State.localState(), PlatformServices);
368
395
 
369
396
  const buildAndApply = (effect: Effect.Effect<any, any, any>) =>
370
397
  (effect as Effect.Effect<any, any, never>).pipe(
@@ -1 +0,0 @@
1
- {"version":3,"file":"DurableObject.d.ts","sourceRoot":"","sources":["../../src/Drizzle/DurableObject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,cAAc,MAAM,oCAAoC,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,KAAK,eAAe,MAAM,8BAA8B,CAAC;AAEhE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AAEjF;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C;AAED,MAAM,WAAW,mBAAmB,CAClC,UAAU,SAAS,YAAY,GAAG,cAAc,CAChD,SAAQ,IAAI,CACZ,eAAe,CAAC,2BAA2B,CAAC,UAAU,CAAC,EACvD,SAAS,CACV;IACC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,uBAAuB,GAAG,SAAS,CAAC;CAC3D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AACH,eAAO,MAAM,aAAa,GACxB,UAAU,SAAS,YAAY;;4DA2B/B,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"DurableObject.js","sourceRoot":"","sources":["../../src/Drizzle/DurableObject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,cAAc,MAAM,oCAAoC,CAAC;AAErE,OAAO,KAAK,eAAe,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,uCAAuC,CAAC;AAChE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AAyBjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,QAAQ,CAAC,EAEvE,MAAwC;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,kBAAkB,CAAC;IACxC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;IAClC,MAAM,EAAE,UAAU,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,IAAI,EAAE,CAAC;IACtD,oEAAoE;IACpE,sEAAsE;IACtE,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IACvE,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,gBAAgB,CAAC;QACjD,GAAI,aAGF;QACF,OAAO;KACR,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,qEAAqE;QACrE,oEAAoE;QACpE,sDAAsD;QACtD,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE;YACjB,UAAU,EAAE,UAAU,CAAC,UAAU;YACjC,GAAG,CAAC,UAAU,CAAC,eAAe,KAAK,SAAS;gBAC1C,CAAC,CAAC,EAAE,eAAe,EAAE,UAAU,CAAC,eAAe,EAAE;gBACjD,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC,CAAC"}