@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.
- package/lib/App.d.ts +4 -4
- package/lib/App.d.ts.map +1 -1
- package/lib/App.js +4 -6
- package/lib/App.js.map +1 -1
- package/lib/AuthProvider.d.ts +1 -1
- package/lib/AuthProvider.d.ts.map +1 -1
- package/lib/BindService.d.ts.map +1 -1
- package/lib/BindService.js +1 -1
- package/lib/BindService.js.map +1 -1
- package/lib/Bucket.d.ts +2 -2
- package/lib/Bucket.d.ts.map +1 -1
- package/lib/Bucket.js +10 -10
- package/lib/Bucket.js.map +1 -1
- package/lib/BucketBinding.js +1 -1
- package/lib/BucketBinding.js.map +1 -1
- package/lib/Certificate.js +4 -4
- package/lib/Certificate.js.map +1 -1
- package/lib/ConnectPostgres.d.ts +1 -1
- package/lib/ConnectPostgres.d.ts.map +1 -1
- package/lib/ConnectPostgresHttp.js +1 -1
- package/lib/ConnectPostgresHttp.js.map +1 -1
- package/lib/Contributions.d.ts +1 -1
- package/lib/Credentials.d.ts +1 -1
- package/lib/Credentials.d.ts.map +1 -1
- package/lib/Credentials.js +10 -14
- package/lib/Credentials.js.map +1 -1
- package/lib/DeploymentImages.js +1 -1
- package/lib/DeploymentImages.js.map +1 -1
- package/lib/Errors.d.ts +1 -1
- package/lib/Errors.d.ts.map +1 -1
- package/lib/IpAssignment.js +4 -4
- package/lib/IpAssignment.js.map +1 -1
- package/lib/Machine.d.ts +1 -1
- package/lib/Machine.d.ts.map +1 -1
- package/lib/Machine.js +2 -2
- package/lib/Machine.js.map +1 -1
- package/lib/Postgres.d.ts +1 -1
- package/lib/Postgres.d.ts.map +1 -1
- package/lib/Postgres.js +6 -6
- package/lib/Postgres.js.map +1 -1
- package/lib/PostgresMigrations.d.ts +7 -22
- package/lib/PostgresMigrations.d.ts.map +1 -1
- package/lib/PostgresMigrations.js +37 -89
- package/lib/PostgresMigrations.js.map +1 -1
- package/lib/Providers.d.ts +1 -1
- package/lib/Providers.d.ts.map +1 -1
- package/lib/Redis.d.ts +2 -2
- package/lib/Redis.d.ts.map +1 -1
- package/lib/Redis.js +9 -6
- package/lib/Redis.js.map +1 -1
- package/lib/RedisBinding.js +1 -1
- package/lib/RedisBinding.js.map +1 -1
- package/lib/RedisHttp.d.ts +1 -1
- package/lib/RedisHttp.d.ts.map +1 -1
- package/lib/RedisHttp.js +4 -1
- package/lib/RedisHttp.js.map +1 -1
- package/lib/Secret.d.ts +1 -1
- package/lib/Secret.d.ts.map +1 -1
- package/lib/Secret.js +2 -2
- package/lib/Secret.js.map +1 -1
- package/lib/SecretKey.d.ts +1 -1
- package/lib/SecretKey.d.ts.map +1 -1
- package/lib/SecretKey.js +2 -2
- package/lib/SecretKey.js.map +1 -1
- package/lib/Service.d.ts +8 -13
- package/lib/Service.d.ts.map +1 -1
- package/lib/Service.js +51 -18
- package/lib/Service.js.map +1 -1
- package/lib/Sprite.d.ts +2 -2
- package/lib/Sprite.d.ts.map +1 -1
- package/lib/Sprite.js +7 -5
- package/lib/Sprite.js.map +1 -1
- package/lib/Volume.d.ts +2 -2
- package/lib/Volume.d.ts.map +1 -1
- package/lib/Volume.js +5 -5
- package/lib/Volume.js.map +1 -1
- package/lib/VolumeSnapshot.js +1 -1
- package/lib/VolumeSnapshot.js.map +1 -1
- package/lib/Website/AssetDeployment.d.ts.map +1 -1
- package/lib/Website/AssetDeployment.js +9 -9
- package/lib/Website/AssetDeployment.js.map +1 -1
- package/lib/Website/Astro.d.ts +8 -6
- package/lib/Website/Astro.d.ts.map +1 -1
- package/lib/Website/Astro.js +12 -13
- package/lib/Website/Astro.js.map +1 -1
- package/lib/Website/FrameworkSite.d.ts +39 -2
- package/lib/Website/FrameworkSite.d.ts.map +1 -1
- package/lib/Website/FrameworkSite.js +46 -3
- package/lib/Website/FrameworkSite.js.map +1 -1
- package/lib/Website/StaticSite.d.ts +10 -3
- package/lib/Website/StaticSite.d.ts.map +1 -1
- package/lib/Website/StaticSite.js +16 -4
- package/lib/Website/StaticSite.js.map +1 -1
- package/lib/Website/Vite.d.ts +9 -0
- package/lib/Website/Vite.d.ts.map +1 -1
- package/lib/Website/Vite.js +9 -0
- package/lib/Website/Vite.js.map +1 -1
- package/lib/WriteCertificatesHttp.js +2 -2
- package/lib/WriteCertificatesHttp.js.map +1 -1
- package/lib/bluegreen.d.ts +4 -4
- package/lib/bluegreen.d.ts.map +1 -1
- package/lib/bluegreen.js +6 -4
- package/lib/bluegreen.js.map +1 -1
- package/lib/hosted.d.ts +3 -4
- package/lib/hosted.d.ts.map +1 -1
- package/lib/hosted.js.map +1 -1
- package/lib/internal/RemoteBuilder.d.ts.map +1 -1
- package/lib/internal/RemoteBuilder.js +21 -8
- package/lib/internal/RemoteBuilder.js.map +1 -1
- package/lib/leases.d.ts +2 -2
- package/lib/replicas.d.ts +10 -10
- package/lib/replicas.d.ts.map +1 -1
- package/lib/replicas.js +7 -7
- package/lib/replicas.js.map +1 -1
- package/package.json +13 -13
- package/src/App.ts +5 -7
- package/src/BindService.ts +1 -2
- package/src/Bucket.ts +15 -13
- package/src/BucketBinding.ts +1 -1
- package/src/Certificate.ts +4 -4
- package/src/ConnectPostgres.ts +1 -1
- package/src/ConnectPostgresHttp.ts +1 -1
- package/src/Credentials.ts +9 -36
- package/src/DeploymentImages.ts +1 -1
- package/src/Errors.ts +1 -1
- package/src/IpAssignment.ts +4 -4
- package/src/Machine.ts +5 -3
- package/src/Postgres.ts +7 -7
- package/src/PostgresMigrations.ts +39 -99
- package/src/Redis.ts +11 -8
- package/src/RedisBinding.ts +1 -1
- package/src/RedisHttp.ts +4 -1
- package/src/Secret.ts +3 -3
- package/src/SecretKey.ts +3 -3
- package/src/Service.ts +53 -19
- package/src/Sprite.ts +11 -9
- package/src/Volume.ts +6 -6
- package/src/VolumeSnapshot.ts +1 -1
- package/src/Website/AssetDeployment.ts +13 -15
- package/src/Website/Astro.ts +20 -15
- package/src/Website/FrameworkSite.ts +92 -6
- package/src/Website/StaticSite.ts +27 -7
- package/src/Website/Vite.ts +9 -0
- package/src/WriteCertificatesHttp.ts +2 -2
- package/src/bluegreen.ts +7 -4
- package/src/hosted.ts +3 -4
- package/src/internal/RemoteBuilder.ts +37 -19
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
835
|
+
.pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
|
|
834
836
|
return detail ?? listed;
|
|
835
837
|
});
|
|
836
838
|
|
package/src/BucketBinding.ts
CHANGED
|
@@ -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
|
-
|
|
109
|
+
resourceName: resolvedName ?? bucket.LogicalId,
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
112
|
const scope = {
|
package/src/Certificate.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
527
|
+
.pipe(Effect.catchTag(["NotFound", "BadRequest"], () => Effect.undefined));
|
|
528
528
|
if (checked !== undefined) {
|
|
529
529
|
current = checked;
|
|
530
530
|
}
|
package/src/ConnectPostgres.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|
package/src/Credentials.ts
CHANGED
|
@@ -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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
);
|
package/src/DeploymentImages.ts
CHANGED
|
@@ -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.
|
|
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
package/src/IpAssignment.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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> => ({ ...
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 {
|
|
8
|
-
import {
|
|
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
|
|
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 = (
|
|
59
|
-
const 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
|
-
/**
|
|
64
|
-
|
|
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: (
|
|
67
|
-
): Effect.Effect<A,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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);
|
package/src/RedisBinding.ts
CHANGED
|
@@ -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
|
-
|
|
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";
|