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

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 (148) hide show
  1. package/lib/App.d.ts +4 -4
  2. package/lib/App.d.ts.map +1 -1
  3. package/lib/App.js +4 -6
  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/BindService.d.ts.map +1 -1
  8. package/lib/BindService.js +1 -1
  9. package/lib/BindService.js.map +1 -1
  10. package/lib/Bucket.d.ts +2 -2
  11. package/lib/Bucket.d.ts.map +1 -1
  12. package/lib/Bucket.js +10 -10
  13. package/lib/Bucket.js.map +1 -1
  14. package/lib/BucketBinding.js +1 -1
  15. package/lib/BucketBinding.js.map +1 -1
  16. package/lib/Certificate.js +4 -4
  17. package/lib/Certificate.js.map +1 -1
  18. package/lib/ConnectPostgres.d.ts +1 -1
  19. package/lib/ConnectPostgres.d.ts.map +1 -1
  20. package/lib/ConnectPostgresHttp.js +1 -1
  21. package/lib/ConnectPostgresHttp.js.map +1 -1
  22. package/lib/Contributions.d.ts +1 -1
  23. package/lib/Credentials.d.ts +1 -1
  24. package/lib/Credentials.d.ts.map +1 -1
  25. package/lib/Credentials.js +10 -14
  26. package/lib/Credentials.js.map +1 -1
  27. package/lib/DeploymentImages.js +1 -1
  28. package/lib/DeploymentImages.js.map +1 -1
  29. package/lib/Errors.d.ts +1 -1
  30. package/lib/Errors.d.ts.map +1 -1
  31. package/lib/IpAssignment.js +4 -4
  32. package/lib/IpAssignment.js.map +1 -1
  33. package/lib/Machine.d.ts +1 -1
  34. package/lib/Machine.d.ts.map +1 -1
  35. package/lib/Machine.js +2 -2
  36. package/lib/Machine.js.map +1 -1
  37. package/lib/Postgres.d.ts +1 -1
  38. package/lib/Postgres.d.ts.map +1 -1
  39. package/lib/Postgres.js +6 -6
  40. package/lib/Postgres.js.map +1 -1
  41. package/lib/PostgresMigrations.d.ts +7 -22
  42. package/lib/PostgresMigrations.d.ts.map +1 -1
  43. package/lib/PostgresMigrations.js +37 -89
  44. package/lib/PostgresMigrations.js.map +1 -1
  45. package/lib/Providers.d.ts +1 -1
  46. package/lib/Providers.d.ts.map +1 -1
  47. package/lib/Redis.d.ts +2 -2
  48. package/lib/Redis.d.ts.map +1 -1
  49. package/lib/Redis.js +9 -6
  50. package/lib/Redis.js.map +1 -1
  51. package/lib/RedisBinding.js +1 -1
  52. package/lib/RedisBinding.js.map +1 -1
  53. package/lib/RedisHttp.d.ts +1 -1
  54. package/lib/RedisHttp.d.ts.map +1 -1
  55. package/lib/RedisHttp.js +4 -1
  56. package/lib/RedisHttp.js.map +1 -1
  57. package/lib/Secret.d.ts +1 -1
  58. package/lib/Secret.d.ts.map +1 -1
  59. package/lib/Secret.js +2 -2
  60. package/lib/Secret.js.map +1 -1
  61. package/lib/SecretKey.d.ts +1 -1
  62. package/lib/SecretKey.d.ts.map +1 -1
  63. package/lib/SecretKey.js +2 -2
  64. package/lib/SecretKey.js.map +1 -1
  65. package/lib/Service.d.ts +8 -13
  66. package/lib/Service.d.ts.map +1 -1
  67. package/lib/Service.js +51 -18
  68. package/lib/Service.js.map +1 -1
  69. package/lib/Sprite.d.ts +2 -2
  70. package/lib/Sprite.d.ts.map +1 -1
  71. package/lib/Sprite.js +7 -5
  72. package/lib/Sprite.js.map +1 -1
  73. package/lib/Volume.d.ts +2 -2
  74. package/lib/Volume.d.ts.map +1 -1
  75. package/lib/Volume.js +5 -5
  76. package/lib/Volume.js.map +1 -1
  77. package/lib/VolumeSnapshot.js +1 -1
  78. package/lib/VolumeSnapshot.js.map +1 -1
  79. package/lib/Website/AssetDeployment.d.ts.map +1 -1
  80. package/lib/Website/AssetDeployment.js +9 -9
  81. package/lib/Website/AssetDeployment.js.map +1 -1
  82. package/lib/Website/Astro.d.ts +8 -6
  83. package/lib/Website/Astro.d.ts.map +1 -1
  84. package/lib/Website/Astro.js +12 -13
  85. package/lib/Website/Astro.js.map +1 -1
  86. package/lib/Website/FrameworkSite.d.ts +39 -2
  87. package/lib/Website/FrameworkSite.d.ts.map +1 -1
  88. package/lib/Website/FrameworkSite.js +46 -3
  89. package/lib/Website/FrameworkSite.js.map +1 -1
  90. package/lib/Website/StaticSite.d.ts +10 -3
  91. package/lib/Website/StaticSite.d.ts.map +1 -1
  92. package/lib/Website/StaticSite.js +16 -4
  93. package/lib/Website/StaticSite.js.map +1 -1
  94. package/lib/Website/Vite.d.ts +9 -0
  95. package/lib/Website/Vite.d.ts.map +1 -1
  96. package/lib/Website/Vite.js +9 -0
  97. package/lib/Website/Vite.js.map +1 -1
  98. package/lib/WriteCertificatesHttp.js +2 -2
  99. package/lib/WriteCertificatesHttp.js.map +1 -1
  100. package/lib/bluegreen.d.ts +4 -4
  101. package/lib/bluegreen.d.ts.map +1 -1
  102. package/lib/bluegreen.js +6 -4
  103. package/lib/bluegreen.js.map +1 -1
  104. package/lib/hosted.d.ts +3 -4
  105. package/lib/hosted.d.ts.map +1 -1
  106. package/lib/hosted.js.map +1 -1
  107. package/lib/internal/RemoteBuilder.d.ts.map +1 -1
  108. package/lib/internal/RemoteBuilder.js +21 -8
  109. package/lib/internal/RemoteBuilder.js.map +1 -1
  110. package/lib/leases.d.ts +2 -2
  111. package/lib/replicas.d.ts +10 -10
  112. package/lib/replicas.d.ts.map +1 -1
  113. package/lib/replicas.js +7 -7
  114. package/lib/replicas.js.map +1 -1
  115. package/package.json +13 -13
  116. package/src/App.ts +5 -7
  117. package/src/BindService.ts +1 -2
  118. package/src/Bucket.ts +15 -13
  119. package/src/BucketBinding.ts +1 -1
  120. package/src/Certificate.ts +4 -4
  121. package/src/ConnectPostgres.ts +1 -1
  122. package/src/ConnectPostgresHttp.ts +1 -1
  123. package/src/Credentials.ts +9 -36
  124. package/src/DeploymentImages.ts +1 -1
  125. package/src/Errors.ts +1 -1
  126. package/src/IpAssignment.ts +4 -4
  127. package/src/Machine.ts +5 -3
  128. package/src/Postgres.ts +7 -7
  129. package/src/PostgresMigrations.ts +39 -99
  130. package/src/Redis.ts +11 -8
  131. package/src/RedisBinding.ts +1 -1
  132. package/src/RedisHttp.ts +4 -1
  133. package/src/Secret.ts +3 -3
  134. package/src/SecretKey.ts +3 -3
  135. package/src/Service.ts +53 -19
  136. package/src/Sprite.ts +11 -9
  137. package/src/Volume.ts +6 -6
  138. package/src/VolumeSnapshot.ts +1 -1
  139. package/src/Website/AssetDeployment.ts +13 -15
  140. package/src/Website/Astro.ts +20 -15
  141. package/src/Website/FrameworkSite.ts +92 -6
  142. package/src/Website/StaticSite.ts +27 -7
  143. package/src/Website/Vite.ts +9 -0
  144. package/src/WriteCertificatesHttp.ts +2 -2
  145. package/src/bluegreen.ts +7 -4
  146. package/src/hosted.ts +3 -4
  147. package/src/internal/RemoteBuilder.ts +37 -19
  148. package/src/replicas.ts +8 -8
package/src/Bucket.ts CHANGED
@@ -251,7 +251,7 @@ export type Bucket = Resource<
251
251
  export const Bucket = Resource<Bucket>("Fly.Bucket");
252
252
 
253
253
  export class BucketNotCreated extends Data.TaggedError("Fly.BucketNotCreated")<{
254
- name: string;
254
+ resourceName: string;
255
255
  }> {}
256
256
 
257
257
  export class BucketOrgMissing extends Data.TaggedError("Fly.BucketOrgMissing")<{
@@ -259,13 +259,13 @@ export class BucketOrgMissing extends Data.TaggedError("Fly.BucketOrgMissing")<{
259
259
  }> {}
260
260
 
261
261
  export class BucketProvisionFailed extends Data.TaggedError("Fly.BucketProvisionFailed")<{
262
- name: string;
262
+ resourceName: string;
263
263
  status: string | undefined;
264
264
  errorMessage: string | undefined;
265
265
  }> {}
266
266
 
267
267
  class BucketPending extends Data.TaggedError("Fly.BucketPending")<{
268
- name: string;
268
+ resourceName: string;
269
269
  status: string;
270
270
  }> {}
271
271
 
@@ -465,20 +465,22 @@ const waitUntilReady = (name: string, addOnId: string) =>
465
465
  addOn,
466
466
  ): Effect.Effect<AddOnsResponseEdgesItemNode, BucketPending | BucketProvisionFailed> => {
467
467
  if (addOn === undefined) {
468
- return Effect.fail(new BucketPending({ name, status: "missing" }));
468
+ return Effect.fail(new BucketPending({ resourceName: name, status: "missing" }));
469
469
  }
470
470
  const status = addOn.status ?? undefined;
471
471
  if (failedStatus(status)) {
472
472
  return Effect.fail(
473
473
  new BucketProvisionFailed({
474
- name,
474
+ resourceName: name,
475
475
  status,
476
476
  errorMessage: addOn.errorMessage ?? undefined,
477
477
  }),
478
478
  );
479
479
  }
480
480
  if (pendingStatus(status)) {
481
- return Effect.fail(new BucketPending({ name, status: status ?? "creating" }));
481
+ return Effect.fail(
482
+ new BucketPending({ resourceName: name, status: status ?? "creating" }),
483
+ );
482
484
  }
483
485
  return Effect.succeed(addOn);
484
486
  },
@@ -506,7 +508,7 @@ const waitUntilGone = (addOnId: string, name: string) =>
506
508
  (yield* findById(addOnId)) ?? (name.length > 0 ? yield* findByName(name) : undefined);
507
509
  if (still !== undefined) {
508
510
  return yield* new BucketPending({
509
- name,
511
+ resourceName: name,
510
512
  status: still.status ?? "deleting",
511
513
  });
512
514
  }
@@ -521,7 +523,7 @@ const waitUntilGone = (addOnId: string, name: string) =>
521
523
  const resolveOrganization = (orgSlug: string) =>
522
524
  addons
523
525
  .organization({ slug: orgSlug })
524
- .pipe(Effect.catchTag("FlyIoParseError", () => Effect.succeed(undefined)));
526
+ .pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
525
527
 
526
528
  const ensureTos = (orgSlug: string, organizationId: string) =>
527
529
  Effect.gen(function* () {
@@ -650,13 +652,13 @@ export const BucketProvider = () =>
650
652
  }
651
653
 
652
654
  if (current === undefined || current.id.length === 0) {
653
- return yield* new BucketNotCreated({ name });
655
+ return yield* new BucketNotCreated({ resourceName: name });
654
656
  }
655
657
 
656
658
  const status = current.status ?? undefined;
657
659
  if (failedStatus(status)) {
658
660
  return yield* new BucketProvisionFailed({
659
- name,
661
+ resourceName: name,
660
662
  status,
661
663
  errorMessage: current.errorMessage ?? undefined,
662
664
  });
@@ -749,7 +751,7 @@ const requireBucketSecrets = Effect.fn(function* (appName: string, values: Recor
749
751
  ];
750
752
  if (required.some((key) => !values[key]?.trim())) {
751
753
  return yield* new TigrisCredentialsMissing({
752
- name: values.BUCKET_NAME || appName,
754
+ resourceName: values.BUCKET_NAME || appName,
753
755
  });
754
756
  }
755
757
  });
@@ -820,7 +822,7 @@ const findAttachedBucket = (input: { id?: string; name: string }) =>
820
822
  : undefined;
821
823
  if (listed === undefined) {
822
824
  return yield* new BucketPending({
823
- name: input.name || input.id || "",
825
+ resourceName: input.name || input.id || "",
824
826
  status: "missing",
825
827
  });
826
828
  }
@@ -830,7 +832,7 @@ const findAttachedBucket = (input: { id?: string; name: string }) =>
830
832
  name: listed.name ?? input.name,
831
833
  provider: TIGRIS,
832
834
  })
833
- .pipe(Effect.catchTag("FlyIoParseError", () => Effect.succeed(undefined)));
835
+ .pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
834
836
  return detail ?? listed;
835
837
  });
836
838
 
@@ -106,7 +106,7 @@ export const makeTigrisS3Binding = <I extends { Bucket?: string }, A, E>(options
106
106
  resolvedEndpoint === undefined
107
107
  ) {
108
108
  return yield* new TigrisCredentialsMissing({
109
- name: resolvedName ?? bucket.LogicalId,
109
+ resourceName: resolvedName ?? bucket.LogicalId,
110
110
  });
111
111
  }
112
112
  const scope = {
@@ -314,7 +314,7 @@ const toAttrsFromSummary = (
314
314
  const getByHostname = (appName: string, hostname: string) =>
315
315
  machines
316
316
  .getAppCertificate({ app_name: appName, hostname })
317
- .pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
317
+ .pipe(Effect.catchTag("NotFound", () => Effect.undefined));
318
318
 
319
319
  const listAppCertificates = (appName: string) =>
320
320
  Effect.gen(function* () {
@@ -372,7 +372,7 @@ const createAcme = (appName: string, hostname: string) =>
372
372
  app_name: appName,
373
373
  hostname,
374
374
  })
375
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
375
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
376
376
 
377
377
  const createCustom = (appName: string, hostname: string, fullchain: string, privateKey: string) =>
378
378
  machines
@@ -382,7 +382,7 @@ const createCustom = (appName: string, hostname: string, fullchain: string, priv
382
382
  fullchain,
383
383
  private_key: privateKey,
384
384
  })
385
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
385
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
386
386
 
387
387
  const replaceCustom = (appName: string, hostname: string, fullchain: string, privateKey: string) =>
388
388
  Effect.gen(function* () {
@@ -524,7 +524,7 @@ export const CertificateProvider = () =>
524
524
  app_name: appName,
525
525
  hostname,
526
526
  })
527
- .pipe(Effect.catchTag(["NotFound", "BadRequest"], () => Effect.succeed(undefined)));
527
+ .pipe(Effect.catchTag(["NotFound", "BadRequest"], () => Effect.undefined));
528
528
  if (checked !== undefined) {
529
529
  current = checked;
530
530
  }
@@ -46,7 +46,7 @@ export const connectEnvKeys = (postgres: Pick<Postgres, "LogicalId">) => {
46
46
  };
47
47
 
48
48
  export class PostgresUrlMissing extends Data.TaggedError("Fly.PostgresUrlMissing")<{
49
- name: string;
49
+ resourceName: string;
50
50
  }> {}
51
51
 
52
52
  export interface ConnectPostgresClient {
@@ -26,7 +26,7 @@ const asRedactedUrl = (
26
26
  const url = typeof plain === "string" ? plain : "";
27
27
  return url.length > 0
28
28
  ? Effect.succeed(Redacted.make(url))
29
- : Effect.fail(new PostgresUrlMissing({ name }));
29
+ : Effect.fail(new PostgresUrlMissing({ resourceName: name }));
30
30
  };
31
31
 
32
32
  /**
@@ -1,20 +1,12 @@
1
1
  import { ConfigError } from "@distilled.cloud/core/errors";
2
2
  import { Credentials, CredentialsFromEnv } from "@distilled.cloud/fly-io";
3
- import {
4
- deferUntilFirstUse,
5
- orDieCredentialsUnavailable,
6
- resolveProviderConfig,
7
- } from "alchemy/Auth/Resolve";
3
+ import { lazyCredentials, orDieCredentialsUnavailable } from "alchemy/Auth/Resolve";
8
4
  import * as Output from "alchemy/Output";
9
5
  import * as Effect from "effect/Effect";
10
6
  import * as Layer from "effect/Layer";
11
7
  import * as Option from "effect/Option";
12
8
  import * as Redacted from "effect/Redacted";
13
- import {
14
- FLY_AUTH_PROVIDER_NAME,
15
- type FlyAuthConfig,
16
- type FlyResolvedCredentials,
17
- } from "./AuthProvider.ts";
9
+ import { FLY_AUTH_PROVIDER_NAME, type FlyResolvedCredentials } from "./AuthProvider.ts";
18
10
 
19
11
  export {
20
12
  Credentials,
@@ -37,6 +29,9 @@ export const bindFlyApiToken = (): Effect.Effect<void, never, Credentials> =>
37
29
  : yield* Credentials.pipe(
38
30
  Effect.flatMap((resolve) => resolve),
39
31
  Effect.map((cfg) => Redacted.value(cfg.apiKey)),
32
+ // Binding initialization cannot fail; the engine reports this
33
+ // defect as `CredentialsRequired`.
34
+ orDieCredentialsUnavailable("Fly"),
40
35
  );
41
36
  yield* Output.named(Output.asOutput(token), "FLY_API_TOKEN").asEffect().pipe(Effect.asVoid);
42
37
  });
@@ -73,30 +68,8 @@ export const CredentialsFromAmbientOrEnv: Layer.Layer<Credentials> = Layer.effec
73
68
  export const fromAuthProvider = () =>
74
69
  Layer.effect(
75
70
  Credentials,
76
- Effect.gen(function* () {
77
- // Defer profile lookup and credential resolution until first use, so
78
- // building the provider layers never requires a configured profile.
79
- const resolve = yield* resolveProviderConfig<FlyAuthConfig, FlyResolvedCredentials>(
80
- FLY_AUTH_PROVIDER_NAME,
81
- ).pipe(
82
- Effect.flatMap(({ profileName, resolve }) =>
83
- resolve.pipe(
84
- Effect.map((creds) => ({
85
- apiKey: creds.apiKey,
86
- apiBaseUrl: creds.apiBaseUrl,
87
- })),
88
- Effect.mapError((e) =>
89
- ConfigError.make({
90
- message: `Failed to resolve Fly credentials from ${profileName === undefined ? "the CI environment" : `profile '${profileName}'`}: ${(e as { message?: string }).message ?? String(e)}`,
91
- }),
92
- ),
93
- ),
94
- ),
95
- deferUntilFirstUse,
96
- );
97
- return yield* resolve.pipe(
98
- orDieCredentialsUnavailable(FLY_AUTH_PROVIDER_NAME),
99
- Effect.cached,
100
- );
101
- }),
71
+ lazyCredentials<FlyResolvedCredentials, ConfigError>()(FLY_AUTH_PROVIDER_NAME, (creds) => ({
72
+ apiKey: creds.apiKey,
73
+ apiBaseUrl: creds.apiBaseUrl,
74
+ })),
102
75
  );
@@ -35,7 +35,7 @@ export const encodeImageSet = (pins: readonly ContainerImagePin[]): string | und
35
35
 
36
36
  export const decodeImageSet = (raw: string | undefined): ContainerImagePin[] | undefined => {
37
37
  if (raw === undefined) return undefined;
38
- const decoded = Schema.decodeUnknownOption(Schema.fromJsonString(ImageSet))(raw);
38
+ const decoded = Schema.decodeOption(Schema.fromJsonString(ImageSet))(raw);
39
39
  if (!Option.isSome(decoded)) return undefined;
40
40
  const pins = decoded.value;
41
41
  return valid(pins) ? canonical(pins) : undefined;
package/src/Errors.ts CHANGED
@@ -10,5 +10,5 @@ import * as Data from "effect/Data";
10
10
  * operation fails with this instead of signing an anonymous request.
11
11
  */
12
12
  export class TigrisCredentialsMissing extends Data.TaggedError("Fly.TigrisCredentialsMissing")<{
13
- name: string;
13
+ resourceName: string;
14
14
  }> {}
@@ -382,14 +382,14 @@ export const syncOwnedAppAddresses = Effect.fn(function* (
382
382
  if (!observed.some(isDesiredFlycast)) {
383
383
  yield* machines
384
384
  .createAppIPAssignment({ app_name: appName, type: "private_v6", network })
385
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
385
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
386
386
  }
387
387
  if (isPublic) {
388
388
  for (const type of ["shared_v4", "v6"] as const) {
389
389
  if (observed.some((item) => inferType(item) === type)) continue;
390
390
  yield* machines
391
391
  .createAppIPAssignment({ app_name: appName, type })
392
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
392
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
393
393
  }
394
394
  }
395
395
  // Release public addresses of a private Service and Flycast addresses
@@ -415,7 +415,7 @@ export const ensureFlycastAddress = Effect.fn(function* (appName: string, networ
415
415
  return;
416
416
  yield* machines
417
417
  .createAppIPAssignment({ app_name: appName, type: "private_v6", network })
418
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
418
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
419
419
  });
420
420
 
421
421
  export const IpAssignmentProvider = () =>
@@ -495,7 +495,7 @@ export const IpAssignmentProvider = () =>
495
495
  org_slug: props.orgSlug,
496
496
  service_name: props.serviceName,
497
497
  })
498
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
498
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
499
499
  if (created !== undefined && created.ip !== undefined) {
500
500
  current = created;
501
501
  } else {
package/src/Machine.ts CHANGED
@@ -844,7 +844,7 @@ export type Machine = Resource<
844
844
  export const Machine = Resource<Machine>("Fly.Machine");
845
845
 
846
846
  export class MachineNotCreated extends Data.TaggedError("Fly.MachineNotCreated")<{
847
- name: string;
847
+ resourceName: string;
848
848
  appName: string;
849
849
  }> {}
850
850
 
@@ -928,7 +928,7 @@ const desiredEnv = (
928
928
  const desiredMetadata = (
929
929
  props: MachineProps,
930
930
  alchemy: Record<string, string>,
931
- ): Record<string, string> => ({ ...(props.metadata ?? {}), ...alchemy });
931
+ ): Record<string, string> => ({ ...props.metadata, ...alchemy });
932
932
 
933
933
  const buildConfig = (input: {
934
934
  image: string | undefined;
@@ -1245,7 +1245,9 @@ export const MachineProvider = () =>
1245
1245
  }),
1246
1246
  }).pipe(
1247
1247
  Effect.catchTag("Fly.ReplicaNotCreated", (error) =>
1248
- Effect.fail(new MachineNotCreated({ name: error.name, appName: error.appName })),
1248
+ Effect.fail(
1249
+ new MachineNotCreated({ resourceName: error.resourceName, appName: error.appName }),
1250
+ ),
1249
1251
  ),
1250
1252
  );
1251
1253
  return toAttrs(set);
package/src/Postgres.ts CHANGED
@@ -286,7 +286,7 @@ export type Postgres = Resource<
286
286
  export const Postgres = Resource<Postgres>("Fly.Postgres");
287
287
 
288
288
  export class PostgresNotCreated extends Data.TaggedError("Fly.PostgresNotCreated")<{
289
- name: string;
289
+ resourceName: string;
290
290
  orgSlug: string;
291
291
  }> {}
292
292
 
@@ -369,7 +369,7 @@ const resolveName = (id: string, name: string | undefined, existing?: string) =>
369
369
  export const getLiveCluster = (clusterId: string) =>
370
370
  mpg.getClusterById({ id: clusterId }).pipe(
371
371
  Effect.map((res) => (isLiveCluster(res.data) ? res.data : undefined)),
372
- Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
372
+ Effect.catchTag("NotFound", () => Effect.undefined),
373
373
  );
374
374
 
375
375
  export const getClusterResponse = (clusterId: string) =>
@@ -377,7 +377,7 @@ export const getClusterResponse = (clusterId: string) =>
377
377
  Effect.map((res): GetClusterResponse | undefined =>
378
378
  isLiveCluster(res.data) ? res : undefined,
379
379
  ),
380
- Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
380
+ Effect.catchTag("NotFound", () => Effect.undefined),
381
381
  );
382
382
 
383
383
  const listOrgClusters = (orgSlug: string) =>
@@ -535,7 +535,7 @@ export const attachPostgresSecrets = Effect.fn(function* (
535
535
  times: 10,
536
536
  schedule: backoff,
537
537
  }),
538
- Effect.catchTag("Fly.PostgresCredentialsPending", () => Effect.succeed(undefined)),
538
+ Effect.catchTag("Fly.PostgresCredentialsPending", () => Effect.undefined),
539
539
  );
540
540
  if (creds === undefined) {
541
541
  return yield* new PostgresCredentialsMissing({ clusterId });
@@ -660,7 +660,7 @@ export const PostgresProvider = () =>
660
660
 
661
661
  if (current === undefined) {
662
662
  if (region === undefined || region.length === 0) {
663
- return yield* new PostgresNotCreated({ name, orgSlug });
663
+ return yield* new PostgresNotCreated({ resourceName: name, orgSlug });
664
664
  }
665
665
  const created = yield* mpg
666
666
  .createCluster({
@@ -674,13 +674,13 @@ export const PostgresProvider = () =>
674
674
  })
675
675
  .pipe(
676
676
  Effect.map((res) => res.data),
677
- Effect.catchTag("Conflict", () => Effect.succeed(undefined)),
677
+ Effect.catchTag("Conflict", () => Effect.undefined),
678
678
  );
679
679
  current = isLiveCluster(created) ? created : yield* findByName(orgSlug, name);
680
680
  }
681
681
 
682
682
  if (current === undefined || current.id === undefined) {
683
- return yield* new PostgresNotCreated({ name, orgSlug });
683
+ return yield* new PostgresNotCreated({ resourceName: name, orgSlug });
684
684
  }
685
685
  // `current` is reassigned below; the id is stable across the waits.
686
686
  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,13 @@ 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";
31
34
 
32
35
  export interface RedisProps {
33
36
  /**
@@ -222,7 +225,7 @@ export type Redis = Resource<
222
225
  export const Redis = Resource<Redis>("Fly.Redis");
223
226
 
224
227
  export class RedisNotCreated extends Data.TaggedError("Fly.RedisNotCreated")<{
225
- name: string;
228
+ resourceName: string;
226
229
  errorMessage?: string;
227
230
  }> {}
228
231
 
@@ -454,7 +457,7 @@ const resolvePlan = (plan: string | undefined, existingId: string | undefined) =
454
457
  const resolveOrganization = (orgSlug: string) =>
455
458
  addons
456
459
  .organization({ slug: orgSlug })
457
- .pipe(Effect.catchTag("FlyIoParseError", () => Effect.succeed(undefined)));
460
+ .pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
458
461
 
459
462
  const ensureTos = (orgSlug: string, organizationId: string) =>
460
463
  Effect.gen(function* () {
@@ -485,7 +488,7 @@ const waitUntilReady = (id: string, name: string) =>
485
488
  if (failedStatus(row.status)) {
486
489
  return Effect.fail(
487
490
  new RedisNotCreated({
488
- name: row.name ?? name,
491
+ resourceName: row.name ?? name,
489
492
  errorMessage: row.errorMessage ?? row.status ?? undefined,
490
493
  }),
491
494
  );
@@ -522,7 +525,7 @@ const waitUntilGone = (id: string, name: string) =>
522
525
  const desiredOptions = (
523
526
  props: RedisProps,
524
527
  plan: RedisPlan,
525
- observed: unknown | undefined,
528
+ observed: unknown,
526
529
  ): Record<string, unknown> => {
527
530
  const options = recordOf(observed);
528
531
  if (props.eviction !== undefined) options.eviction = props.eviction;
@@ -595,7 +598,7 @@ export const attachRedisSecrets = Effect.fn(function* (
595
598
  if ((url === undefined || url.length === 0) && row.id !== undefined) {
596
599
  const detail = yield* addons
597
600
  .addOn({ id: row.id })
598
- .pipe(Effect.catchTag("FlyIoParseError", () => Effect.succeed(undefined)));
601
+ .pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
599
602
  url = unwrapSensitive(detail?.publicUrl);
600
603
  }
601
604
  if (url === undefined || url.length === 0) {
@@ -735,7 +738,7 @@ export const RedisProvider = () =>
735
738
  }
736
739
 
737
740
  if (current === undefined) {
738
- return yield* new RedisNotCreated({ name });
741
+ return yield* new RedisNotCreated({ resourceName: name });
739
742
  }
740
743
 
741
744
  if (pendingStatus(current.status)) {
@@ -744,7 +747,7 @@ export const RedisProvider = () =>
744
747
  }
745
748
 
746
749
  if (current === undefined) {
747
- return yield* new RedisNotCreated({ name });
750
+ return yield* new RedisNotCreated({ resourceName: name });
748
751
  }
749
752
 
750
753
  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";