@alchemy.run/fly 2.0.0-beta.84 → 2.0.0-beta.86

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 (307) hide show
  1. package/lib/App.d.ts +27 -5
  2. package/lib/App.d.ts.map +1 -1
  3. package/lib/App.js +29 -8
  4. package/lib/App.js.map +1 -1
  5. package/lib/AuthProvider.d.ts +1 -1
  6. package/lib/AuthProvider.d.ts.map +1 -1
  7. package/lib/AuthProvider.js.map +1 -1
  8. package/lib/BindService.d.ts +7 -7
  9. package/lib/BindService.d.ts.map +1 -1
  10. package/lib/BindService.js +21 -28
  11. package/lib/BindService.js.map +1 -1
  12. package/lib/Bootstrap.d.ts.map +1 -1
  13. package/lib/Bootstrap.js +2 -1
  14. package/lib/Bootstrap.js.map +1 -1
  15. package/lib/Bucket.d.ts +38 -4
  16. package/lib/Bucket.d.ts.map +1 -1
  17. package/lib/Bucket.js +190 -37
  18. package/lib/Bucket.js.map +1 -1
  19. package/lib/BucketBinding.d.ts.map +1 -1
  20. package/lib/BucketBinding.js +1 -1
  21. package/lib/BucketBinding.js.map +1 -1
  22. package/lib/Catalog.d.ts.map +1 -1
  23. package/lib/Catalog.js.map +1 -1
  24. package/lib/Certificate.d.ts +25 -1
  25. package/lib/Certificate.d.ts.map +1 -1
  26. package/lib/Certificate.js +22 -4
  27. package/lib/Certificate.js.map +1 -1
  28. package/lib/Checkpoint.d.ts.map +1 -1
  29. package/lib/Checkpoint.js.map +1 -1
  30. package/lib/CheckpointHttp.d.ts.map +1 -1
  31. package/lib/CheckpointHttp.js.map +1 -1
  32. package/lib/ConnectPostgres.d.ts +1 -1
  33. package/lib/ConnectPostgres.d.ts.map +1 -1
  34. package/lib/ConnectPostgres.js.map +1 -1
  35. package/lib/ConnectPostgresHttp.d.ts.map +1 -1
  36. package/lib/ConnectPostgresHttp.js +1 -1
  37. package/lib/ConnectPostgresHttp.js.map +1 -1
  38. package/lib/Contributions.d.ts +1 -1
  39. package/lib/Contributions.d.ts.map +1 -1
  40. package/lib/Contributions.js.map +1 -1
  41. package/lib/Credentials.d.ts +5 -4
  42. package/lib/Credentials.d.ts.map +1 -1
  43. package/lib/Credentials.js +18 -18
  44. package/lib/Credentials.js.map +1 -1
  45. package/lib/Decrypt.d.ts.map +1 -1
  46. package/lib/Decrypt.js.map +1 -1
  47. package/lib/DecryptHttp.d.ts.map +1 -1
  48. package/lib/DecryptHttp.js.map +1 -1
  49. package/lib/DeleteObject.d.ts.map +1 -1
  50. package/lib/DeleteObject.js.map +1 -1
  51. package/lib/DeleteObjectHttp.d.ts.map +1 -1
  52. package/lib/DeleteObjectHttp.js.map +1 -1
  53. package/lib/Deployment.d.ts.map +1 -1
  54. package/lib/Deployment.js.map +1 -1
  55. package/lib/DeploymentImages.d.ts.map +1 -1
  56. package/lib/DeploymentImages.js +1 -1
  57. package/lib/DeploymentImages.js.map +1 -1
  58. package/lib/DeploymentState.d.ts.map +1 -1
  59. package/lib/DeploymentState.js.map +1 -1
  60. package/lib/Encrypt.d.ts.map +1 -1
  61. package/lib/Encrypt.js.map +1 -1
  62. package/lib/EncryptHttp.d.ts.map +1 -1
  63. package/lib/EncryptHttp.js.map +1 -1
  64. package/lib/Environment.d.ts.map +1 -1
  65. package/lib/Environment.js.map +1 -1
  66. package/lib/Errors.d.ts +1 -1
  67. package/lib/Errors.d.ts.map +1 -1
  68. package/lib/Errors.js.map +1 -1
  69. package/lib/Exec.d.ts.map +1 -1
  70. package/lib/Exec.js.map +1 -1
  71. package/lib/ExecHttp.d.ts.map +1 -1
  72. package/lib/ExecHttp.js.map +1 -1
  73. package/lib/GetObject.d.ts.map +1 -1
  74. package/lib/GetObject.js.map +1 -1
  75. package/lib/GetObjectHttp.d.ts.map +1 -1
  76. package/lib/GetObjectHttp.js.map +1 -1
  77. package/lib/GetSecret.d.ts.map +1 -1
  78. package/lib/GetSecret.js.map +1 -1
  79. package/lib/GetSecretHttp.d.ts.map +1 -1
  80. package/lib/GetSecretHttp.js.map +1 -1
  81. package/lib/HeadObject.d.ts.map +1 -1
  82. package/lib/HeadObject.js.map +1 -1
  83. package/lib/HeadObjectHttp.d.ts.map +1 -1
  84. package/lib/HeadObjectHttp.js.map +1 -1
  85. package/lib/IpAssignment.d.ts +24 -1
  86. package/lib/IpAssignment.d.ts.map +1 -1
  87. package/lib/IpAssignment.js +21 -4
  88. package/lib/IpAssignment.js.map +1 -1
  89. package/lib/ListObjectsV2.d.ts.map +1 -1
  90. package/lib/ListObjectsV2.js.map +1 -1
  91. package/lib/ListObjectsV2Http.d.ts.map +1 -1
  92. package/lib/ListObjectsV2Http.js.map +1 -1
  93. package/lib/ListSecrets.d.ts.map +1 -1
  94. package/lib/ListSecrets.js.map +1 -1
  95. package/lib/ListSecretsHttp.d.ts.map +1 -1
  96. package/lib/ListSecretsHttp.js.map +1 -1
  97. package/lib/Machine.d.ts +27 -2
  98. package/lib/Machine.d.ts.map +1 -1
  99. package/lib/Machine.js +18 -3
  100. package/lib/Machine.js.map +1 -1
  101. package/lib/MachineContainers.d.ts.map +1 -1
  102. package/lib/MachineContainers.js.map +1 -1
  103. package/lib/Metadata.d.ts.map +1 -1
  104. package/lib/Metadata.js.map +1 -1
  105. package/lib/MountVolume.d.ts +7 -2
  106. package/lib/MountVolume.d.ts.map +1 -1
  107. package/lib/MountVolume.js.map +1 -1
  108. package/lib/Postgres.d.ts +34 -2
  109. package/lib/Postgres.d.ts.map +1 -1
  110. package/lib/Postgres.js +26 -6
  111. package/lib/Postgres.js.map +1 -1
  112. package/lib/PostgresMigrations.d.ts +7 -22
  113. package/lib/PostgresMigrations.d.ts.map +1 -1
  114. package/lib/PostgresMigrations.js +37 -89
  115. package/lib/PostgresMigrations.js.map +1 -1
  116. package/lib/Providers.d.ts +1 -1
  117. package/lib/Providers.d.ts.map +1 -1
  118. package/lib/Providers.js.map +1 -1
  119. package/lib/PutObject.d.ts.map +1 -1
  120. package/lib/PutObject.js.map +1 -1
  121. package/lib/PutObjectHttp.d.ts.map +1 -1
  122. package/lib/PutObjectHttp.js.map +1 -1
  123. package/lib/ReadRedis.d.ts.map +1 -1
  124. package/lib/ReadRedis.js.map +1 -1
  125. package/lib/ReadRedisHttp.d.ts.map +1 -1
  126. package/lib/ReadRedisHttp.js.map +1 -1
  127. package/lib/ReadWriteRedis.d.ts.map +1 -1
  128. package/lib/ReadWriteRedis.js.map +1 -1
  129. package/lib/ReadWriteRedisHttp.d.ts.map +1 -1
  130. package/lib/ReadWriteRedisHttp.js.map +1 -1
  131. package/lib/Redis.d.ts +23 -3
  132. package/lib/Redis.d.ts.map +1 -1
  133. package/lib/Redis.js +37 -6
  134. package/lib/Redis.js.map +1 -1
  135. package/lib/RedisBinding.d.ts.map +1 -1
  136. package/lib/RedisBinding.js +1 -1
  137. package/lib/RedisBinding.js.map +1 -1
  138. package/lib/RedisHttp.d.ts +1 -1
  139. package/lib/RedisHttp.d.ts.map +1 -1
  140. package/lib/RedisHttp.js +4 -1
  141. package/lib/RedisHttp.js.map +1 -1
  142. package/lib/Region.d.ts.map +1 -1
  143. package/lib/Region.js.map +1 -1
  144. package/lib/Secret.d.ts +23 -2
  145. package/lib/Secret.d.ts.map +1 -1
  146. package/lib/Secret.js +17 -2
  147. package/lib/Secret.js.map +1 -1
  148. package/lib/SecretHttp.d.ts.map +1 -1
  149. package/lib/SecretHttp.js.map +1 -1
  150. package/lib/SecretKey.d.ts +23 -2
  151. package/lib/SecretKey.d.ts.map +1 -1
  152. package/lib/SecretKey.js +17 -2
  153. package/lib/SecretKey.js.map +1 -1
  154. package/lib/SecretKeyHttp.d.ts.map +1 -1
  155. package/lib/SecretKeyHttp.js.map +1 -1
  156. package/lib/Service.d.ts +104 -34
  157. package/lib/Service.d.ts.map +1 -1
  158. package/lib/Service.js +301 -54
  159. package/lib/Service.js.map +1 -1
  160. package/lib/Sign.d.ts.map +1 -1
  161. package/lib/Sign.js.map +1 -1
  162. package/lib/SignHttp.d.ts.map +1 -1
  163. package/lib/SignHttp.js.map +1 -1
  164. package/lib/Sprite.d.ts +24 -5
  165. package/lib/Sprite.d.ts.map +1 -1
  166. package/lib/Sprite.js +22 -5
  167. package/lib/Sprite.js.map +1 -1
  168. package/lib/SpriteHttp.d.ts.map +1 -1
  169. package/lib/SpriteHttp.js.map +1 -1
  170. package/lib/Verify.d.ts.map +1 -1
  171. package/lib/Verify.js.map +1 -1
  172. package/lib/VerifyHttp.d.ts.map +1 -1
  173. package/lib/VerifyHttp.js.map +1 -1
  174. package/lib/Volume.d.ts +4 -4
  175. package/lib/Volume.d.ts.map +1 -1
  176. package/lib/Volume.js +21 -5
  177. package/lib/Volume.js.map +1 -1
  178. package/lib/VolumeSnapshot.d.ts +32 -1
  179. package/lib/VolumeSnapshot.d.ts.map +1 -1
  180. package/lib/VolumeSnapshot.js +24 -1
  181. package/lib/VolumeSnapshot.js.map +1 -1
  182. package/lib/Website/AssetDeployment.d.ts.map +1 -1
  183. package/lib/Website/AssetDeployment.js +9 -9
  184. package/lib/Website/AssetDeployment.js.map +1 -1
  185. package/lib/Website/Astro.d.ts +8 -6
  186. package/lib/Website/Astro.d.ts.map +1 -1
  187. package/lib/Website/Astro.js +12 -13
  188. package/lib/Website/Astro.js.map +1 -1
  189. package/lib/Website/Foldkit.d.ts.map +1 -1
  190. package/lib/Website/Foldkit.js.map +1 -1
  191. package/lib/Website/FrameworkSite.d.ts +39 -2
  192. package/lib/Website/FrameworkSite.d.ts.map +1 -1
  193. package/lib/Website/FrameworkSite.js +46 -3
  194. package/lib/Website/FrameworkSite.js.map +1 -1
  195. package/lib/Website/Nextjs.d.ts.map +1 -1
  196. package/lib/Website/Nextjs.js.map +1 -1
  197. package/lib/Website/Nuxt.d.ts.map +1 -1
  198. package/lib/Website/Nuxt.js.map +1 -1
  199. package/lib/Website/Octane.d.ts.map +1 -1
  200. package/lib/Website/Octane.js.map +1 -1
  201. package/lib/Website/ReactRouter.d.ts.map +1 -1
  202. package/lib/Website/ReactRouter.js.map +1 -1
  203. package/lib/Website/SolidStart.d.ts.map +1 -1
  204. package/lib/Website/SolidStart.js.map +1 -1
  205. package/lib/Website/StaticSite.d.ts +10 -3
  206. package/lib/Website/StaticSite.d.ts.map +1 -1
  207. package/lib/Website/StaticSite.js +16 -4
  208. package/lib/Website/StaticSite.js.map +1 -1
  209. package/lib/Website/SvelteKit.d.ts.map +1 -1
  210. package/lib/Website/SvelteKit.js.map +1 -1
  211. package/lib/Website/TanStackStart.d.ts.map +1 -1
  212. package/lib/Website/TanStackStart.js.map +1 -1
  213. package/lib/Website/Vinext.d.ts.map +1 -1
  214. package/lib/Website/Vinext.js.map +1 -1
  215. package/lib/Website/Vite.d.ts +9 -0
  216. package/lib/Website/Vite.d.ts.map +1 -1
  217. package/lib/Website/Vite.js +9 -0
  218. package/lib/Website/Vite.js.map +1 -1
  219. package/lib/Website/Vocs.d.ts.map +1 -1
  220. package/lib/Website/Vocs.js.map +1 -1
  221. package/lib/Website/Waku.d.ts.map +1 -1
  222. package/lib/Website/Waku.js.map +1 -1
  223. package/lib/Website/index.d.ts.map +1 -1
  224. package/lib/Website/index.js.map +1 -1
  225. package/lib/WriteCertificates.d.ts.map +1 -1
  226. package/lib/WriteCertificates.js.map +1 -1
  227. package/lib/WriteCertificatesHttp.d.ts.map +1 -1
  228. package/lib/WriteCertificatesHttp.js +2 -2
  229. package/lib/WriteCertificatesHttp.js.map +1 -1
  230. package/lib/WriteRedis.d.ts.map +1 -1
  231. package/lib/WriteRedis.js.map +1 -1
  232. package/lib/WriteRedisHttp.d.ts.map +1 -1
  233. package/lib/WriteRedisHttp.js.map +1 -1
  234. package/lib/WriteSecret.d.ts.map +1 -1
  235. package/lib/WriteSecret.js.map +1 -1
  236. package/lib/WriteSecretHttp.d.ts.map +1 -1
  237. package/lib/WriteSecretHttp.js.map +1 -1
  238. package/lib/bluegreen.d.ts +5 -4
  239. package/lib/bluegreen.d.ts.map +1 -1
  240. package/lib/bluegreen.js +7 -5
  241. package/lib/bluegreen.js.map +1 -1
  242. package/lib/hosted.d.ts +19 -4
  243. package/lib/hosted.d.ts.map +1 -1
  244. package/lib/hosted.js +101 -11
  245. package/lib/hosted.js.map +1 -1
  246. package/lib/index.d.ts.map +1 -1
  247. package/lib/index.js.map +1 -1
  248. package/lib/internal/Oidc.d.ts +15 -0
  249. package/lib/internal/Oidc.d.ts.map +1 -0
  250. package/lib/internal/Oidc.js +73 -0
  251. package/lib/internal/Oidc.js.map +1 -0
  252. package/lib/internal/RemoteBuilder.d.ts.map +1 -1
  253. package/lib/internal/RemoteBuilder.js +21 -8
  254. package/lib/internal/RemoteBuilder.js.map +1 -1
  255. package/lib/leases.d.ts +6 -2
  256. package/lib/leases.d.ts.map +1 -1
  257. package/lib/leases.js +25 -5
  258. package/lib/leases.js.map +1 -1
  259. package/lib/ports.d.ts.map +1 -1
  260. package/lib/ports.js.map +1 -1
  261. package/lib/replicas.d.ts +39 -27
  262. package/lib/replicas.d.ts.map +1 -1
  263. package/lib/replicas.js +63 -8
  264. package/lib/replicas.js.map +1 -1
  265. package/lib/rpc.d.ts +14 -12
  266. package/lib/rpc.d.ts.map +1 -1
  267. package/lib/rpc.js +38 -40
  268. package/lib/rpc.js.map +1 -1
  269. package/package.json +15 -15
  270. package/src/App.ts +41 -10
  271. package/src/BindService.ts +28 -36
  272. package/src/Bootstrap.ts +2 -0
  273. package/src/Bucket.ts +278 -45
  274. package/src/BucketBinding.ts +1 -1
  275. package/src/Certificate.ts +36 -5
  276. package/src/ConnectPostgres.ts +1 -1
  277. package/src/ConnectPostgresHttp.ts +1 -1
  278. package/src/Credentials.ts +18 -41
  279. package/src/DeploymentImages.ts +1 -1
  280. package/src/Errors.ts +1 -1
  281. package/src/IpAssignment.ts +35 -5
  282. package/src/Machine.ts +39 -4
  283. package/src/MountVolume.ts +5 -2
  284. package/src/Postgres.ts +50 -8
  285. package/src/PostgresMigrations.ts +39 -99
  286. package/src/Redis.ts +54 -9
  287. package/src/RedisBinding.ts +1 -1
  288. package/src/RedisHttp.ts +4 -1
  289. package/src/Secret.ts +32 -4
  290. package/src/SecretKey.ts +32 -4
  291. package/src/Service.ts +441 -88
  292. package/src/Sprite.ts +45 -17
  293. package/src/Volume.ts +30 -6
  294. package/src/VolumeSnapshot.ts +40 -2
  295. package/src/Website/AssetDeployment.ts +13 -15
  296. package/src/Website/Astro.ts +20 -15
  297. package/src/Website/FrameworkSite.ts +92 -6
  298. package/src/Website/StaticSite.ts +27 -7
  299. package/src/Website/Vite.ts +9 -0
  300. package/src/WriteCertificatesHttp.ts +2 -2
  301. package/src/bluegreen.ts +11 -5
  302. package/src/hosted.ts +121 -17
  303. package/src/internal/Oidc.ts +99 -0
  304. package/src/internal/RemoteBuilder.ts +37 -19
  305. package/src/leases.ts +35 -7
  306. package/src/replicas.ts +70 -9
  307. package/src/rpc.ts +37 -40
@@ -85,9 +85,21 @@ export type IpAssignment = Resource<
85
85
  createdAt: string | undefined;
86
86
  },
87
87
  never,
88
- Providers
88
+ Providers,
89
+ IpAssignmentLookup
89
90
  >;
90
91
 
92
+ /**
93
+ * Props for {@link IpAssignment.lookup}: find an address this stack
94
+ * doesn't manage by its App and IP.
95
+ */
96
+ export interface IpAssignmentLookup {
97
+ /** Fly App name the address is assigned to. */
98
+ appName: string;
99
+ /** The allocated address. */
100
+ ip: string;
101
+ }
102
+
91
103
  const resolveIpAssignmentProps = (
92
104
  props: IpAssignmentProps | Effect.Effect<IpAssignmentProps, never, Providers>,
93
105
  ): Effect.Effect<IpAssignmentProps, never, Providers> =>
@@ -247,6 +259,19 @@ const IpAssignmentResource = Resource<IpAssignment>("Fly.IpAssignment");
247
259
  * );
248
260
  * ```
249
261
  *
262
+ * ### Looking up an existing address
263
+ * `IpAssignment.lookup` references an address Alchemy doesn't manage
264
+ * (allocated with `fly ips allocate-v4` or by another stack). It is read
265
+ * during plan/deploy, never allocated or released.
266
+ *
267
+ * **Example:** Reference an existing address
268
+ * ```typescript
269
+ * const ip = Fly.IpAssignment.lookup({
270
+ * appName: "my-site",
271
+ * ip: "2a09:8280:1::1",
272
+ * });
273
+ * ```
274
+ *
250
275
  * @resource
251
276
  * @product App
252
277
  */
@@ -382,14 +407,14 @@ export const syncOwnedAppAddresses = Effect.fn(function* (
382
407
  if (!observed.some(isDesiredFlycast)) {
383
408
  yield* machines
384
409
  .createAppIPAssignment({ app_name: appName, type: "private_v6", network })
385
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
410
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
386
411
  }
387
412
  if (isPublic) {
388
413
  for (const type of ["shared_v4", "v6"] as const) {
389
414
  if (observed.some((item) => inferType(item) === type)) continue;
390
415
  yield* machines
391
416
  .createAppIPAssignment({ app_name: appName, type })
392
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
417
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
393
418
  }
394
419
  }
395
420
  // Release public addresses of a private Service and Flycast addresses
@@ -415,7 +440,7 @@ export const ensureFlycastAddress = Effect.fn(function* (appName: string, networ
415
440
  return;
416
441
  yield* machines
417
442
  .createAppIPAssignment({ app_name: appName, type: "private_v6", network })
418
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
443
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
419
444
  });
420
445
 
421
446
  export const IpAssignmentProvider = () =>
@@ -439,6 +464,11 @@ export const IpAssignmentProvider = () =>
439
464
  return undefined;
440
465
  }),
441
466
 
467
+ lookup: Effect.fn(function* ({ appName, ip }) {
468
+ const found = yield* findByIp(appName, ip);
469
+ return found?.ip === undefined ? undefined : toAttrs(appName, found);
470
+ }),
471
+
442
472
  read: Effect.fn(function* ({ olds, output }) {
443
473
  const appName = output?.appName ?? appNameOf(olds?.app);
444
474
  if (appName === undefined) return undefined;
@@ -495,7 +525,7 @@ export const IpAssignmentProvider = () =>
495
525
  org_slug: props.orgSlug,
496
526
  service_name: props.serviceName,
497
527
  })
498
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
528
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
499
529
  if (created !== undefined && created.ip !== undefined) {
500
530
  current = created;
501
531
  } else {
package/src/Machine.ts CHANGED
@@ -33,6 +33,7 @@ import type { Providers } from "./Providers.ts";
33
33
  import {
34
34
  deleteReplicaSet,
35
35
  listReplicaSets,
36
+ lookupReplicaSet,
36
37
  observeReplicaSet,
37
38
  reconcileReplicas,
38
39
  resolveCount,
@@ -379,9 +380,25 @@ export type Machine = Resource<
379
380
  replicas: Replica[];
380
381
  },
381
382
  ServiceBinding,
382
- Providers
383
+ Providers,
384
+ MachineLookup
383
385
  >;
384
386
 
387
+ /**
388
+ * Props for {@link Machine.lookup}: find Machines this stack doesn't
389
+ * manage by their App and name.
390
+ */
391
+ export interface MachineLookup {
392
+ /** Fly App name the Machines run in. */
393
+ appName: string;
394
+ /**
395
+ * Machine name (unique per App). Replicas named `{name}-{N}` are
396
+ * found with it, as are the replicas of an Alchemy Machine whose base
397
+ * name it is.
398
+ */
399
+ name: string;
400
+ }
401
+
385
402
  /**
386
403
  * A Fly.Machine is a Firecracker VM running one image or a named container group.
387
404
  *
@@ -838,13 +855,24 @@ export type Machine = Resource<
838
855
  * discovers unfinished owned generations.
839
856
  * See the [deployment guide](/fly/compute/deployments) for recovery and limits.
840
857
  *
858
+ * ### Looking up existing Machines
859
+ * `Machine.lookup` references Machines Alchemy doesn't manage (started with
860
+ * `fly machine run` or by another stack). It is read during plan/deploy,
861
+ * never created, updated or deleted.
862
+ *
863
+ * **Example:** Reference an existing Machine
864
+ * ```typescript
865
+ * const worker = Fly.Machine.lookup({ appName: "my-app", name: "worker" });
866
+ * const address = worker.privateIp;
867
+ * ```
868
+ *
841
869
  * @resource
842
870
  * @product Machine
843
871
  */
844
872
  export const Machine = Resource<Machine>("Fly.Machine");
845
873
 
846
874
  export class MachineNotCreated extends Data.TaggedError("Fly.MachineNotCreated")<{
847
- name: string;
875
+ resourceName: string;
848
876
  appName: string;
849
877
  }> {}
850
878
 
@@ -928,7 +956,7 @@ const desiredEnv = (
928
956
  const desiredMetadata = (
929
957
  props: MachineProps,
930
958
  alchemy: Record<string, string>,
931
- ): Record<string, string> => ({ ...(props.metadata ?? {}), ...alchemy });
959
+ ): Record<string, string> => ({ ...props.metadata, ...alchemy });
932
960
 
933
961
  const buildConfig = (input: {
934
962
  image: string | undefined;
@@ -1154,6 +1182,11 @@ export const MachineProvider = () =>
1154
1182
  return output.rolloutPending ? { action: "update" as const } : undefined;
1155
1183
  }),
1156
1184
 
1185
+ lookup: Effect.fn(function* ({ appName, name }) {
1186
+ const found = yield* lookupReplicaSet({ appName, name });
1187
+ return found === undefined ? undefined : toAttrs(found);
1188
+ }),
1189
+
1157
1190
  read: Effect.fn(function* ({ id, fqn, instanceId, olds, output }) {
1158
1191
  const appName = appNameOf(olds?.app) ?? output?.appName;
1159
1192
  const name = yield* resolveMachineName(id, olds?.name, output?.baseName ?? output?.name);
@@ -1245,7 +1278,9 @@ export const MachineProvider = () =>
1245
1278
  }),
1246
1279
  }).pipe(
1247
1280
  Effect.catchTag("Fly.ReplicaNotCreated", (error) =>
1248
- Effect.fail(new MachineNotCreated({ name: error.name, appName: error.appName })),
1281
+ Effect.fail(
1282
+ new MachineNotCreated({ resourceName: error.resourceName, appName: error.appName }),
1283
+ ),
1249
1284
  ),
1250
1285
  );
1251
1286
  return toAttrs(set);
@@ -116,6 +116,11 @@ export interface ServiceBinding {
116
116
  * published.
117
117
  */
118
118
  target?: BoundTarget;
119
+ /**
120
+ * A Service that calls this one, recorded on the target by
121
+ * `Fly.bindService`. Only the Apps recorded here may call its methods.
122
+ */
123
+ caller?: { appName: string };
119
124
  }
120
125
 
121
126
  /** A Service bound by `Fly.bindService` or `Fly.bindEndpoint`. */
@@ -128,8 +133,6 @@ export interface BoundTarget {
128
133
  network: string | undefined;
129
134
  /** The bound Service's private address; `undefined` when it publishes nothing. */
130
135
  privateUrl: string | undefined;
131
- /** The bound Service's caller token. */
132
- rpcToken?: import("effect/Redacted").Redacted<string>;
133
136
  /** Port `Fly.bindEndpoint` targets; the binding port for `bindService`. */
134
137
  port?: number;
135
138
  /** Ports the bound Service publishes, for `Fly.bindEndpoint`. */
package/src/Postgres.ts CHANGED
@@ -93,6 +93,13 @@ export interface PostgresProps {
93
93
  * is reachable on the org private network; deploy-time apply needs a
94
94
  * route to that network (a WireGuard peer, `fly mpg proxy`, or a
95
95
  * machine already on 6PN).
96
+ *
97
+ * History is forward-only and linear. A new file must sort after every
98
+ * applied one, or the deploy fails with `NonLinearMigrationHistoryError`;
99
+ * an edited or deleted applied file fails with
100
+ * `RewrittenMigrationHistoryError`. Renaming an applied file without
101
+ * changing its contents keeps it applied. See
102
+ * [Migrations](https://alchemy.run/sql/effect-sql/migrations#change-migration-history-safely).
96
103
  */
97
104
  migrations?: MigrationsInput;
98
105
  /**
@@ -143,9 +150,24 @@ export type Postgres = Resource<
143
150
  importHashes: Record<string, string>;
144
151
  },
145
152
  never,
146
- Providers
153
+ Providers,
154
+ PostgresLookup
147
155
  >;
148
156
 
157
+ /**
158
+ * Props for {@link Postgres.lookup}: find a Managed Postgres cluster
159
+ * this stack doesn't manage by its name.
160
+ */
161
+ export interface PostgresLookup {
162
+ /** Cluster name (unique in the org). */
163
+ name: string;
164
+ /**
165
+ * Organization slug.
166
+ * @default the current token's org
167
+ */
168
+ orgSlug?: string;
169
+ }
170
+
149
171
  /**
150
172
  * A Fly.Postgres is a Managed Postgres (MPG) cluster. It is billed.
151
173
  * Do not wrap unmanaged `fly postgres`.
@@ -280,13 +302,25 @@ export type Postgres = Resource<
280
302
  * });
281
303
  * ```
282
304
  *
305
+ * ### Looking up an existing cluster
306
+ * `Postgres.lookup` references a cluster Alchemy doesn't manage (created
307
+ * with `fly mpg create` or by another stack). It is read during
308
+ * plan/deploy, never created, updated or deleted, and its connection URIs
309
+ * come from Fly like a declared cluster's.
310
+ *
311
+ * **Example:** Connect to an existing cluster
312
+ * ```typescript
313
+ * const db = Fly.Postgres.lookup({ name: "my-db" });
314
+ * const sql = yield* Fly.ConnectPostgres(db);
315
+ * ```
316
+ *
283
317
  * @resource
284
318
  * @product Postgres
285
319
  */
286
320
  export const Postgres = Resource<Postgres>("Fly.Postgres");
287
321
 
288
322
  export class PostgresNotCreated extends Data.TaggedError("Fly.PostgresNotCreated")<{
289
- name: string;
323
+ resourceName: string;
290
324
  orgSlug: string;
291
325
  }> {}
292
326
 
@@ -369,7 +403,7 @@ const resolveName = (id: string, name: string | undefined, existing?: string) =>
369
403
  export const getLiveCluster = (clusterId: string) =>
370
404
  mpg.getClusterById({ id: clusterId }).pipe(
371
405
  Effect.map((res) => (isLiveCluster(res.data) ? res.data : undefined)),
372
- Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
406
+ Effect.catchTag("NotFound", () => Effect.undefined),
373
407
  );
374
408
 
375
409
  export const getClusterResponse = (clusterId: string) =>
@@ -377,7 +411,7 @@ export const getClusterResponse = (clusterId: string) =>
377
411
  Effect.map((res): GetClusterResponse | undefined =>
378
412
  isLiveCluster(res.data) ? res : undefined,
379
413
  ),
380
- Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
414
+ Effect.catchTag("NotFound", () => Effect.undefined),
381
415
  );
382
416
 
383
417
  const listOrgClusters = (orgSlug: string) =>
@@ -535,7 +569,7 @@ export const attachPostgresSecrets = Effect.fn(function* (
535
569
  times: 10,
536
570
  schedule: backoff,
537
571
  }),
538
- Effect.catchTag("Fly.PostgresCredentialsPending", () => Effect.succeed(undefined)),
572
+ Effect.catchTag("Fly.PostgresCredentialsPending", () => Effect.undefined),
539
573
  );
540
574
  if (creds === undefined) {
541
575
  return yield* new PostgresCredentialsMissing({ clusterId });
@@ -602,6 +636,14 @@ export const PostgresProvider = () =>
602
636
  return undefined;
603
637
  }),
604
638
 
639
+ lookup: Effect.fn(function* ({ name, orgSlug }) {
640
+ const org = orgSlug ?? (yield* resolveOrgSlug());
641
+ const found = yield* findByName(org, name);
642
+ if (found === undefined) return undefined;
643
+ const hydrated = found.id !== undefined ? yield* getClusterResponse(found.id) : undefined;
644
+ return toAttrs(hydrated?.data ?? found, { name, orgSlug: org }, hydrated?.credentials);
645
+ }),
646
+
605
647
  read: Effect.fn(function* ({ id, olds, output }) {
606
648
  const found =
607
649
  output?.clusterId !== undefined && output.clusterId.length > 0
@@ -660,7 +702,7 @@ export const PostgresProvider = () =>
660
702
 
661
703
  if (current === undefined) {
662
704
  if (region === undefined || region.length === 0) {
663
- return yield* new PostgresNotCreated({ name, orgSlug });
705
+ return yield* new PostgresNotCreated({ resourceName: name, orgSlug });
664
706
  }
665
707
  const created = yield* mpg
666
708
  .createCluster({
@@ -674,13 +716,13 @@ export const PostgresProvider = () =>
674
716
  })
675
717
  .pipe(
676
718
  Effect.map((res) => res.data),
677
- Effect.catchTag("Conflict", () => Effect.succeed(undefined)),
719
+ Effect.catchTag("Conflict", () => Effect.undefined),
678
720
  );
679
721
  current = isLiveCluster(created) ? created : yield* findByName(orgSlug, name);
680
722
  }
681
723
 
682
724
  if (current === undefined || current.id === undefined) {
683
- return yield* new PostgresNotCreated({ name, orgSlug });
725
+ return yield* new PostgresNotCreated({ resourceName: name, orgSlug });
684
726
  }
685
727
  // `current` is reassigned below; the id is stable across the waits.
686
728
  const clusterId = current.id;
@@ -1,52 +1,18 @@
1
1
  import {
2
- makePgMigrationExecutor,
2
+ MigrationError,
3
3
  runMigrations,
4
+ withPgExecutor,
4
5
  type NormalizedMigrationsInput,
6
+ type SqlExecutor,
5
7
  type StampedMigrationsState,
6
8
  } from "@alchemy.run/sql/Migrations";
7
- import { importPg } from "@alchemy.run/sql/PostgresDriver";
8
- import { readSqlFile } from "@alchemy.run/sql/SqlFile";
9
- import * as Data from "effect/Data";
9
+ import { stripSslQueryParams } from "@alchemy.run/sql/PostgresDriver";
10
+ import { applySqlImports } from "@alchemy.run/sql/SqlFile";
10
11
  import * as Effect from "effect/Effect";
11
12
  import * as Redacted from "effect/Redacted";
12
- import * as Schedule from "effect/Schedule";
13
- import type { Client } from "pg";
14
13
 
15
- export class PostgresMigrationError extends Data.TaggedError("Fly.PostgresMigrationError")<{
16
- message: string;
17
- cause?: unknown;
18
- }> {}
14
+ export { stripSslQueryParams };
19
15
 
20
- /**
21
- * Strip query-string SSL flags so `pg-connection-string` does not treat
22
- * `sslmode=require` as `verify-full`. TLS and certificate verification
23
- * are set on the client (`ssl.rejectUnauthorized: true`).
24
- */
25
- export const stripSslQueryParams = (uri: string): string => {
26
- try {
27
- const url = new URL(uri);
28
- url.searchParams.delete("sslmode");
29
- url.searchParams.delete("channel_binding");
30
- return url.toString();
31
- } catch {
32
- return uri;
33
- }
34
- };
35
-
36
- const toMigrationError = (cause: unknown) =>
37
- new PostgresMigrationError({
38
- message: cause instanceof Error ? cause.message : String(cause),
39
- cause,
40
- });
41
-
42
- /**
43
- * A freshly-provisioned Fly Managed Postgres cluster reports `ready` on the
44
- * API before its hostname is resolvable and its endpoint accepts
45
- * connections — reconcile's first migration connect can fail with
46
- * `ENOTFOUND` (DNS still propagating) or a refused/reset socket. These are
47
- * eventual consistency, not failures: retry the connect on a bounded
48
- * schedule (~45s) before surfacing the error.
49
- */
50
16
  const TRANSIENT_CONNECT_CODES = new Set([
51
17
  "ENOTFOUND",
52
18
  "EAI_AGAIN",
@@ -55,40 +21,32 @@ const TRANSIENT_CONNECT_CODES = new Set([
55
21
  "ETIMEDOUT",
56
22
  ]);
57
23
 
58
- const isTransientConnectError = (error: PostgresMigrationError): boolean => {
59
- const code = (error.cause as { code?: unknown } | null | undefined)?.code;
24
+ const isTransientConnectError = (cause: unknown): boolean => {
25
+ const code = (cause as { code?: unknown } | null | undefined)?.code;
60
26
  return typeof code === "string" && TRANSIENT_CONNECT_CODES.has(code);
61
27
  };
62
28
 
63
- /** Open a pg client for the scope of `use`, closing it afterwards. */
64
- export const withPgClient = <A, E, R>(
29
+ /**
30
+ * A verified-TLS `pg` session on a Fly Managed Postgres URI for the scope
31
+ * of `use`. A fresh cluster's hostname takes a moment to resolve, so
32
+ * connection failures are retried; one that persists is almost always a
33
+ * missing route — MPG hostnames only exist on the org's private network.
34
+ */
35
+ const withFlyPgExecutor = <A, E, R>(
65
36
  connectionUri: Redacted.Redacted<string>,
66
- use: (client: Client) => Effect.Effect<A, E, R>,
67
- ): Effect.Effect<A, PostgresMigrationError | E, R> =>
68
- Effect.acquireUseRelease(
69
- Effect.tryPromise({
70
- try: async () => {
71
- const { Client } = await importPg();
72
- const client = new Client({
73
- connectionString: connectionUri.pipe(Redacted.value, stripSslQueryParams),
74
- ssl: { rejectUnauthorized: true },
75
- });
76
- await client.connect();
77
- return client;
78
- },
79
- catch: toMigrationError,
80
- }).pipe(
81
- Effect.retry({
82
- while: isTransientConnectError,
83
- schedule: Schedule.spaced("3 seconds"),
84
- times: 15,
85
- }),
86
- // Still unreachable after the bounded retry: almost always a missing
87
- // route rather than propagation — MPG hostnames only exist on the org
88
- // private network. Say so instead of a bare `getaddrinfo ENOTFOUND`.
89
- Effect.mapError((error) =>
90
- isTransientConnectError(error)
91
- ? new PostgresMigrationError({
37
+ use: (executor: SqlExecutor) => Effect.Effect<A, E, R>,
38
+ ): Effect.Effect<A, MigrationError | E, R> =>
39
+ withPgExecutor(
40
+ {
41
+ connectionString: connectionUri.pipe(Redacted.value, stripSslQueryParams),
42
+ ssl: { rejectUnauthorized: true },
43
+ },
44
+ use,
45
+ {
46
+ retryConnect: { while: isTransientConnectError, times: 15, interval: "3 seconds" },
47
+ onConnectFailure: (error) =>
48
+ isTransientConnectError(error.cause)
49
+ ? new MigrationError({
92
50
  message:
93
51
  `${error.message} — Fly Managed Postgres is only reachable ` +
94
52
  "on the org's private network. Run the deploy behind a " +
@@ -97,10 +55,7 @@ export const withPgClient = <A, E, R>(
97
55
  cause: error.cause,
98
56
  })
99
57
  : error,
100
- ),
101
- ),
102
- use,
103
- (client) => Effect.promise(() => client.end().catch(() => {})),
58
+ },
104
59
  );
105
60
 
106
61
  /**
@@ -114,40 +69,25 @@ export const runPgMigrations = (options: {
114
69
  stamped: StampedMigrationsState;
115
70
  }) =>
116
71
  runMigrations({
117
- ...options,
118
- withExecutor: (apply) =>
119
- withPgClient(options.connectionUri, (client) => apply(makePgMigrationExecutor(client))),
72
+ input: options.input,
73
+ stamped: options.stamped,
74
+ withExecutor: (apply) => withFlyPgExecutor(options.connectionUri, apply),
120
75
  });
121
76
 
122
77
  /** Run a single SQL script against the database (used for `importFiles`). */
123
78
  export const runSql = (connectionUri: Redacted.Redacted<string>, sql: string) =>
124
- withPgClient(connectionUri, (client) =>
125
- Effect.tryPromise({
126
- try: () => client.query(sql),
127
- catch: toMigrationError,
128
- }),
129
- ).pipe(Effect.asVoid);
79
+ withFlyPgExecutor(connectionUri, (executor) => executor.query(sql)).pipe(Effect.asVoid);
130
80
 
81
+ /** Apply `importFiles`, each once and again only when it changes. */
131
82
  export const runImports = (
132
83
  connectionUri: Redacted.Redacted<string>,
133
84
  importFiles: ReadonlyArray<string>,
134
85
  rootDir: string,
135
86
  previous: Record<string, string>,
136
87
  ) =>
137
- Effect.gen(function* () {
138
- const hashes: Record<string, string> = { ...previous };
139
- for (const filePath of importFiles) {
140
- const file = yield* readSqlFile(rootDir, filePath);
141
- if (previous[filePath] === file.hash) {
142
- hashes[filePath] = file.hash;
143
- continue;
144
- }
145
- yield* runSql(connectionUri, file.sql);
146
- hashes[filePath] = file.hash;
147
- }
148
- const tracked = new Set(importFiles);
149
- for (const key of Object.keys(hashes)) {
150
- if (!tracked.has(key)) delete hashes[key];
151
- }
152
- return hashes;
88
+ applySqlImports({
89
+ importFiles,
90
+ rootDir,
91
+ previous,
92
+ apply: (file) => runSql(connectionUri, file.sql),
153
93
  });
package/src/Redis.ts CHANGED
@@ -24,10 +24,17 @@ export const REDIS_ADDON_TYPE = "upstash_redis";
24
24
  export const REDIS_PROVIDER = "upstash_redis";
25
25
  export const REDIS_URL_ENV = "REDIS_URL";
26
26
 
27
+ // Re-export from the defining module, not the `@alchemy.run/redis` barrel:
28
+ // rolldown's lazyBarrel rejects an unused re-export from an `export *` barrel
29
+ // (https://github.com/rolldown/rolldown/issues/11344).
27
30
  export {
28
31
  CommandError as RedisCommandError,
29
32
  UrlMissing as RedisUrlMissing,
30
- } from "@alchemy.run/redis";
33
+ } from "@alchemy.run/redis/Errors";
34
+ // Clients' runtime effects require redis's `RuntimeContext` twin, so it shows
35
+ // up in users' inferred types. Exporting it here lets TypeScript name it
36
+ // through this package without users depending on @alchemy.run/redis.
37
+ export type { RuntimeContext as RedisRuntimeContext } from "@alchemy.run/redis/RuntimeContext";
31
38
 
32
39
  export interface RedisProps {
33
40
  /**
@@ -105,9 +112,19 @@ export type Redis = Resource<
105
112
  url: Redacted.Redacted<string> | undefined;
106
113
  },
107
114
  never,
108
- Providers
115
+ Providers,
116
+ RedisLookup
109
117
  >;
110
118
 
119
+ /**
120
+ * Props for {@link Redis.lookup}: find an Upstash Redis this stack
121
+ * doesn't manage by its name.
122
+ */
123
+ export interface RedisLookup {
124
+ /** Upstash Redis name. */
125
+ name: string;
126
+ }
127
+
111
128
  /**
112
129
  * Managed Upstash Redis in a Fly org. Bind {@link ReadRedis},
113
130
  * {@link WriteRedis}, or {@link ReadWriteRedis} on a {@link Service}.
@@ -216,13 +233,24 @@ export type Redis = Resource<
216
233
  * need a specific size.
217
234
  * :::
218
235
  *
236
+ * ### Looking up an existing Redis
237
+ * `Redis.lookup` references a Redis Alchemy doesn't manage (created with
238
+ * `fly redis create` or by another stack). It is read during plan/deploy,
239
+ * never created, updated or deleted. Bind it like a declared Redis.
240
+ *
241
+ * **Example:** Read an existing Redis
242
+ * ```typescript
243
+ * const cache = Fly.Redis.lookup({ name: "my-cache" });
244
+ * const redis = yield* Fly.ReadRedis(cache);
245
+ * ```
246
+ *
219
247
  * @resource
220
248
  * @product Redis
221
249
  */
222
250
  export const Redis = Resource<Redis>("Fly.Redis");
223
251
 
224
252
  export class RedisNotCreated extends Data.TaggedError("Fly.RedisNotCreated")<{
225
- name: string;
253
+ resourceName: string;
226
254
  errorMessage?: string;
227
255
  }> {}
228
256
 
@@ -454,7 +482,7 @@ const resolvePlan = (plan: string | undefined, existingId: string | undefined) =
454
482
  const resolveOrganization = (orgSlug: string) =>
455
483
  addons
456
484
  .organization({ slug: orgSlug })
457
- .pipe(Effect.catchTag("FlyIoParseError", () => Effect.succeed(undefined)));
485
+ .pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
458
486
 
459
487
  const ensureTos = (orgSlug: string, organizationId: string) =>
460
488
  Effect.gen(function* () {
@@ -485,7 +513,7 @@ const waitUntilReady = (id: string, name: string) =>
485
513
  if (failedStatus(row.status)) {
486
514
  return Effect.fail(
487
515
  new RedisNotCreated({
488
- name: row.name ?? name,
516
+ resourceName: row.name ?? name,
489
517
  errorMessage: row.errorMessage ?? row.status ?? undefined,
490
518
  }),
491
519
  );
@@ -522,7 +550,7 @@ const waitUntilGone = (id: string, name: string) =>
522
550
  const desiredOptions = (
523
551
  props: RedisProps,
524
552
  plan: RedisPlan,
525
- observed: unknown | undefined,
553
+ observed: unknown,
526
554
  ): Record<string, unknown> => {
527
555
  const options = recordOf(observed);
528
556
  if (props.eviction !== undefined) options.eviction = props.eviction;
@@ -595,7 +623,7 @@ export const attachRedisSecrets = Effect.fn(function* (
595
623
  if ((url === undefined || url.length === 0) && row.id !== undefined) {
596
624
  const detail = yield* addons
597
625
  .addOn({ id: row.id })
598
- .pipe(Effect.catchTag("FlyIoParseError", () => Effect.succeed(undefined)));
626
+ .pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
599
627
  url = unwrapSensitive(detail?.publicUrl);
600
628
  }
601
629
  if (url === undefined || url.length === 0) {
@@ -651,6 +679,23 @@ export const RedisProvider = () =>
651
679
  return undefined;
652
680
  }),
653
681
 
682
+ lookup: Effect.fn(function* ({ name }) {
683
+ const found = yield* findRedisAddOn({ name });
684
+ if (found === undefined) return undefined;
685
+ let url = unwrapSensitive(found.publicUrl);
686
+ if ((url === undefined || url.length === 0) && found.id !== undefined) {
687
+ const detail = yield* addons
688
+ .addOn({ id: found.id })
689
+ .pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
690
+ url = unwrapSensitive(detail?.publicUrl);
691
+ }
692
+ return toAttrs(found, {
693
+ name,
694
+ primaryRegion: DEFAULT_REDIS_REGION,
695
+ url: url === undefined || url.length === 0 ? undefined : Redacted.make(url),
696
+ });
697
+ }),
698
+
654
699
  read: Effect.fn(function* ({ id, olds, output }) {
655
700
  const name = yield* resolveName(id, olds?.name, output?.name);
656
701
  const found =
@@ -735,7 +780,7 @@ export const RedisProvider = () =>
735
780
  }
736
781
 
737
782
  if (current === undefined) {
738
- return yield* new RedisNotCreated({ name });
783
+ return yield* new RedisNotCreated({ resourceName: name });
739
784
  }
740
785
 
741
786
  if (pendingStatus(current.status)) {
@@ -744,7 +789,7 @@ export const RedisProvider = () =>
744
789
  }
745
790
 
746
791
  if (current === undefined) {
747
- return yield* new RedisNotCreated({ name });
792
+ return yield* new RedisNotCreated({ resourceName: name });
748
793
  }
749
794
 
750
795
  const observedOptions = recordOf(current.options);
@@ -52,7 +52,7 @@ export const makeRedisBinding = <Client>(options: { makeClient: (url: Url) => Cl
52
52
 
53
53
  const urlFromResource = yield* redis.url;
54
54
  const missing = new RedisUrlMissing({
55
- name: asPlain(redis.name) ?? redis.LogicalId,
55
+ resourceName: asPlain(redis.name) ?? redis.LogicalId,
56
56
  });
57
57
  const url: Url = Effect.gen(function* () {
58
58
  const fromOutput = asPlain(yield* urlFromResource);
package/src/RedisHttp.ts CHANGED
@@ -4,8 +4,11 @@
4
4
  * The RESP client lives in `@alchemy.run/redis`. These aliases keep the
5
5
  * `{Op}Http.ts` layers stable.
6
6
  */
7
+ // Re-export from the defining module, not the `@alchemy.run/redis` barrel:
8
+ // rolldown's lazyBarrel rejects an unused re-export from an `export *` barrel
9
+ // (https://github.com/rolldown/rolldown/issues/11344).
7
10
  export {
8
11
  makeRead as makeReadRedisClient,
9
12
  makeReadWrite as makeReadWriteRedisClient,
10
13
  makeWrite as makeWriteRedisClient,
11
- } from "@alchemy.run/redis";
14
+ } from "@alchemy.run/redis/Client";