@alchemy.run/planetscale 0.0.0 → 2.0.0-beta.84
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/LICENSE +201 -0
- package/README.md +83 -4
- package/lib/AuthProvider.d.ts +64 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +384 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/Branch.d.ts +128 -0
- package/lib/Branch.d.ts.map +1 -0
- package/lib/Branch.js +420 -0
- package/lib/Branch.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/Credentials.d.ts +33 -0
- package/lib/Credentials.d.ts.map +1 -0
- package/lib/Credentials.js +65 -0
- package/lib/Credentials.js.map +1 -0
- package/lib/Database.d.ts +138 -0
- package/lib/Database.d.ts.map +1 -0
- package/lib/Database.js +1 -0
- package/lib/Database.js.map +1 -0
- package/lib/MySQL/MySQLBranch.d.ts +87 -0
- package/lib/MySQL/MySQLBranch.d.ts.map +1 -0
- package/lib/MySQL/MySQLBranch.js +15 -0
- package/lib/MySQL/MySQLBranch.js.map +1 -0
- package/lib/MySQL/MySQLClusterSize.d.ts +32 -0
- package/lib/MySQL/MySQLClusterSize.d.ts.map +1 -0
- package/lib/MySQL/MySQLClusterSize.js +111 -0
- package/lib/MySQL/MySQLClusterSize.js.map +1 -0
- package/lib/MySQL/MySQLDatabase.d.ts +141 -0
- package/lib/MySQL/MySQLDatabase.d.ts.map +1 -0
- package/lib/MySQL/MySQLDatabase.js +294 -0
- package/lib/MySQL/MySQLDatabase.js.map +1 -0
- package/lib/MySQL/MySQLMigrations.d.ts +24 -0
- package/lib/MySQL/MySQLMigrations.d.ts.map +1 -0
- package/lib/MySQL/MySQLMigrations.js +106 -0
- package/lib/MySQL/MySQLMigrations.js.map +1 -0
- package/lib/MySQL/MySQLOrigin.d.ts +15 -0
- package/lib/MySQL/MySQLOrigin.d.ts.map +1 -0
- package/lib/MySQL/MySQLOrigin.js +2 -0
- package/lib/MySQL/MySQLOrigin.js.map +1 -0
- package/lib/MySQL/MySQLPassword.d.ts +116 -0
- package/lib/MySQL/MySQLPassword.d.ts.map +1 -0
- package/lib/MySQL/MySQLPassword.js +212 -0
- package/lib/MySQL/MySQLPassword.js.map +1 -0
- package/lib/MySQL/index.d.ts +6 -0
- package/lib/MySQL/index.d.ts.map +1 -0
- package/lib/MySQL/index.js +6 -0
- package/lib/MySQL/index.js.map +1 -0
- package/lib/OAuthClient.d.ts +32 -0
- package/lib/OAuthClient.d.ts.map +1 -0
- package/lib/OAuthClient.js +52 -0
- package/lib/OAuthClient.js.map +1 -0
- package/lib/Postgres/PostgresBranch.d.ts +75 -0
- package/lib/Postgres/PostgresBranch.d.ts.map +1 -0
- package/lib/Postgres/PostgresBranch.js +15 -0
- package/lib/Postgres/PostgresBranch.js.map +1 -0
- package/lib/Postgres/PostgresClusterSize.d.ts +77 -0
- package/lib/Postgres/PostgresClusterSize.d.ts.map +1 -0
- package/lib/Postgres/PostgresClusterSize.js +89 -0
- package/lib/Postgres/PostgresClusterSize.js.map +1 -0
- package/lib/Postgres/PostgresDatabase.d.ts +95 -0
- package/lib/Postgres/PostgresDatabase.d.ts.map +1 -0
- package/lib/Postgres/PostgresDatabase.js +294 -0
- package/lib/Postgres/PostgresDatabase.js.map +1 -0
- package/lib/Postgres/PostgresDefaultRole.d.ts +94 -0
- package/lib/Postgres/PostgresDefaultRole.d.ts.map +1 -0
- package/lib/Postgres/PostgresDefaultRole.js +187 -0
- package/lib/Postgres/PostgresDefaultRole.js.map +1 -0
- package/lib/Postgres/PostgresMigrations.d.ts +24 -0
- package/lib/Postgres/PostgresMigrations.d.ts.map +1 -0
- package/lib/Postgres/PostgresMigrations.js +116 -0
- package/lib/Postgres/PostgresMigrations.js.map +1 -0
- package/lib/Postgres/PostgresOrigin.d.ts +15 -0
- package/lib/Postgres/PostgresOrigin.d.ts.map +1 -0
- package/lib/Postgres/PostgresOrigin.js +2 -0
- package/lib/Postgres/PostgresOrigin.js.map +1 -0
- package/lib/Postgres/PostgresRole.d.ts +142 -0
- package/lib/Postgres/PostgresRole.d.ts.map +1 -0
- package/lib/Postgres/PostgresRole.js +268 -0
- package/lib/Postgres/PostgresRole.js.map +1 -0
- package/lib/Postgres/index.d.ts +7 -0
- package/lib/Postgres/index.d.ts.map +1 -0
- package/lib/Postgres/index.js +7 -0
- package/lib/Postgres/index.js.map +1 -0
- package/lib/Providers.d.ts +24 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +40 -0
- package/lib/Providers.js.map +1 -0
- package/lib/Util.d.ts +93 -0
- package/lib/Util.d.ts.map +1 -0
- package/lib/Util.js +135 -0
- package/lib/Util.js.map +1 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +9 -0
- package/lib/index.js.map +1 -0
- package/package.json +79 -3
- package/src/AuthProvider.ts +571 -0
- package/src/Branch.ts +657 -0
- package/src/Contributions.ts +9 -0
- package/src/Credentials.ts +116 -0
- package/src/Database.ts +152 -0
- package/src/MySQL/MySQLBranch.ts +109 -0
- package/src/MySQL/MySQLClusterSize.ts +185 -0
- package/src/MySQL/MySQLDatabase.ts +521 -0
- package/src/MySQL/MySQLMigrations.ts +182 -0
- package/src/MySQL/MySQLOrigin.ts +15 -0
- package/src/MySQL/MySQLPassword.ts +428 -0
- package/src/MySQL/index.ts +5 -0
- package/src/OAuthClient.ts +59 -0
- package/src/Postgres/PostgresBranch.ts +97 -0
- package/src/Postgres/PostgresClusterSize.ts +262 -0
- package/src/Postgres/PostgresDatabase.ts +469 -0
- package/src/Postgres/PostgresDefaultRole.ts +340 -0
- package/src/Postgres/PostgresMigrations.ts +193 -0
- package/src/Postgres/PostgresOrigin.ts +15 -0
- package/src/Postgres/PostgresRole.ts +547 -0
- package/src/Postgres/index.ts +6 -0
- package/src/Providers.ts +67 -0
- package/src/Util.ts +205 -0
- package/src/index.ts +14 -0
package/src/Util.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import * as ps from "@distilled.cloud/planetscale";
|
|
2
|
+
import type { ScopedPlanStatusSession } from "alchemy/Report";
|
|
3
|
+
import * as Clock from "effect/Clock";
|
|
4
|
+
import * as Data from "effect/Data";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Schedule from "effect/Schedule";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Tagged error raised when polling for a state predicate that has not yet
|
|
10
|
+
* been reached. Used internally with `Effect.retry` to drive long-running
|
|
11
|
+
* status waits.
|
|
12
|
+
*/
|
|
13
|
+
export class NotReady extends Data.TaggedError("Planetscale::NotReady")<{
|
|
14
|
+
description: string;
|
|
15
|
+
}> {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tagged error raised by reconcilers when an immutable property of a live
|
|
19
|
+
* PlanetScale resource does not match the desired configuration (e.g.
|
|
20
|
+
* region, kind, parent_branch). User-recoverable: change the config to
|
|
21
|
+
* match, or replace/delete the existing resource and retry.
|
|
22
|
+
*/
|
|
23
|
+
export class PlanetscaleConflict extends Data.TaggedError("Planetscale::Conflict")<{
|
|
24
|
+
message: string;
|
|
25
|
+
}> {}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Tagged error raised when deleting a database that has deletion
|
|
29
|
+
* protection enabled. Alchemy never turns protection off on its own:
|
|
30
|
+
* set `deletionProtection: false`, deploy, then delete.
|
|
31
|
+
*/
|
|
32
|
+
export class PlanetscaleDeletionProtected extends Data.TaggedError(
|
|
33
|
+
"Planetscale::DeletionProtected",
|
|
34
|
+
)<{
|
|
35
|
+
message: string;
|
|
36
|
+
organization: string;
|
|
37
|
+
database: string;
|
|
38
|
+
}> {}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Deletes a database unless it has deletion protection enabled, in which
|
|
42
|
+
* case it fails with {@link PlanetscaleDeletionProtected}. A database that
|
|
43
|
+
* no longer exists counts as deleted.
|
|
44
|
+
*/
|
|
45
|
+
export const deleteUnprotectedDatabase = Effect.fn(function* (
|
|
46
|
+
organization: string,
|
|
47
|
+
database: string,
|
|
48
|
+
) {
|
|
49
|
+
const live = yield* ps
|
|
50
|
+
.getDatabase({ organization, database })
|
|
51
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
|
|
52
|
+
if (!live) return;
|
|
53
|
+
if (live.deletion_protected) {
|
|
54
|
+
return yield* new PlanetscaleDeletionProtected({
|
|
55
|
+
message:
|
|
56
|
+
`Planetscale database "${database}" has deletion protection enabled. ` +
|
|
57
|
+
"Set `deletionProtection: false` and deploy before deleting it. " +
|
|
58
|
+
"If it is the old database of a replacement, the resource now " +
|
|
59
|
+
"manages the new database, so turn protection off on this one in " +
|
|
60
|
+
"PlanetScale directly, then deploy again.",
|
|
61
|
+
organization,
|
|
62
|
+
database,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
yield* ps
|
|
66
|
+
.deleteDatabase({ organization, database })
|
|
67
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.void));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Plans a database replacement. A replacement creates the new database
|
|
72
|
+
* before deleting the old one, so an explicit `name` that stays the same
|
|
73
|
+
* would make both generations the same database: reconcile would converge
|
|
74
|
+
* onto the old database and the cleanup of the old generation would then
|
|
75
|
+
* delete it. That plan fails with {@link PlanetscaleConflict} instead.
|
|
76
|
+
*/
|
|
77
|
+
export const replaceDatabase = (
|
|
78
|
+
news: { name?: string },
|
|
79
|
+
output: { name: string } | undefined,
|
|
80
|
+
change: string,
|
|
81
|
+
) =>
|
|
82
|
+
news.name !== undefined && news.name === output?.name
|
|
83
|
+
? Effect.fail(
|
|
84
|
+
new PlanetscaleConflict({
|
|
85
|
+
message:
|
|
86
|
+
`Changing the ${change} of Planetscale database "${news.name}" ` +
|
|
87
|
+
"requires a new database, but its explicit `name` is unchanged. " +
|
|
88
|
+
"Give the replacement a different `name`, or omit `name` to let " +
|
|
89
|
+
"Alchemy generate one.",
|
|
90
|
+
}),
|
|
91
|
+
)
|
|
92
|
+
: Effect.succeed({ action: "replace" } as const);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Default polling schedule: 5s spaced retries with a 30-minute total
|
|
96
|
+
* budget (360 × 5s). Avoids the exponential-blowup trap where later
|
|
97
|
+
* iterations would wait hours, indistinguishable from a hang. Postgres
|
|
98
|
+
* database creates routinely run 10-12 minutes, so a 10-minute budget
|
|
99
|
+
* regularly false-positives as "stuck".
|
|
100
|
+
*/
|
|
101
|
+
const defaultSchedule = Schedule.max([Schedule.spaced("5 seconds"), Schedule.recurs(360)]);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Generic polling helper that retries until `predicate(value)` returns true
|
|
105
|
+
* (or until the schedule is exhausted). Engine-specific helpers (e.g.
|
|
106
|
+
* `waitForKeyspaceReady`, `waitForPendingPostgresChanges`) are built on top
|
|
107
|
+
* of this primitive.
|
|
108
|
+
*/
|
|
109
|
+
export const pollUntil = <A, E, R>(
|
|
110
|
+
description: string,
|
|
111
|
+
fn: Effect.Effect<A, E, R>,
|
|
112
|
+
predicate: (value: A) => boolean,
|
|
113
|
+
schedule: Schedule.Schedule<unknown, unknown, never> = defaultSchedule,
|
|
114
|
+
): Effect.Effect<A, E, R> =>
|
|
115
|
+
// oxlint-disable-next-line effecttsgo/unsafe-effect-type-assertion -- NotReady only escapes once the poll schedule is exhausted
|
|
116
|
+
fn.pipe(
|
|
117
|
+
Effect.flatMap((value) =>
|
|
118
|
+
predicate(value) ? Effect.succeed(value) : Effect.fail(new NotReady({ description })),
|
|
119
|
+
),
|
|
120
|
+
Effect.retry({
|
|
121
|
+
while: (e: any) => e?._tag === "Planetscale::NotReady",
|
|
122
|
+
schedule,
|
|
123
|
+
}),
|
|
124
|
+
) as Effect.Effect<A, E, R>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Polls a branch via `getBranch` until it reports `ready === true`. Returns
|
|
128
|
+
* the final branch shape. `NotFound` during polling is treated as
|
|
129
|
+
* not-yet-ready (the branch is being provisioned by an upstream operation).
|
|
130
|
+
*
|
|
131
|
+
* If a `session` is supplied, each poll emits a note with elapsed seconds and
|
|
132
|
+
* expectation-setting so the CLI surfaces progress while we sit in the spaced
|
|
133
|
+
* retry loop.
|
|
134
|
+
*/
|
|
135
|
+
export const waitForBranchReady = Effect.fn(function* (
|
|
136
|
+
organization: string,
|
|
137
|
+
database: string,
|
|
138
|
+
branch: string,
|
|
139
|
+
session?: ScopedPlanStatusSession,
|
|
140
|
+
) {
|
|
141
|
+
const startedAt = yield* Clock.currentTimeMillis;
|
|
142
|
+
return yield* pollUntil(
|
|
143
|
+
`branch "${branch}" ready`,
|
|
144
|
+
Effect.gen(function* () {
|
|
145
|
+
if (session) {
|
|
146
|
+
const now = yield* Clock.currentTimeMillis;
|
|
147
|
+
const seconds = Math.floor((now - startedAt) / 1000);
|
|
148
|
+
yield* session.note(
|
|
149
|
+
`Waiting for branch to be ready... (${seconds} seconds elapsed; this can take a few minutes)`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return yield* ps.getBranch({ organization, database, branch });
|
|
153
|
+
}).pipe(
|
|
154
|
+
Effect.catchTag("NotFound", () =>
|
|
155
|
+
Effect.fail(new NotReady({ description: `branch "${branch}" not found yet` })),
|
|
156
|
+
),
|
|
157
|
+
),
|
|
158
|
+
(data) => data.ready,
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Polls a database via `getDatabase` until it reports `ready === true`.
|
|
164
|
+
* `NotFound` during polling is treated as not-yet-ready (the database is
|
|
165
|
+
* being provisioned by an upstream operation).
|
|
166
|
+
*
|
|
167
|
+
* If a `session` is supplied, each poll emits a note with elapsed seconds and
|
|
168
|
+
* expectation-setting so the CLI surfaces progress while we sit in the spaced
|
|
169
|
+
* retry loop.
|
|
170
|
+
*/
|
|
171
|
+
export const waitForDatabaseReady = Effect.fn(function* (
|
|
172
|
+
organization: string,
|
|
173
|
+
database: string,
|
|
174
|
+
session?: ScopedPlanStatusSession,
|
|
175
|
+
) {
|
|
176
|
+
const startedAt = yield* Clock.currentTimeMillis;
|
|
177
|
+
return yield* pollUntil(
|
|
178
|
+
`database "${database}" ready`,
|
|
179
|
+
Effect.gen(function* () {
|
|
180
|
+
if (session) {
|
|
181
|
+
const now = yield* Clock.currentTimeMillis;
|
|
182
|
+
const seconds = Math.floor((now - startedAt) / 1000);
|
|
183
|
+
yield* session.note(
|
|
184
|
+
`Waiting for database to be ready... (${seconds} seconds elapsed; this can take a few minutes)`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return yield* ps.getDatabase({ organization, database });
|
|
188
|
+
}).pipe(
|
|
189
|
+
Effect.catchTag("NotFound", () =>
|
|
190
|
+
Effect.fail(
|
|
191
|
+
new NotReady({
|
|
192
|
+
description: `database "${database}" not found yet`,
|
|
193
|
+
}),
|
|
194
|
+
),
|
|
195
|
+
),
|
|
196
|
+
),
|
|
197
|
+
(data) => data.ready,
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
export const isKnownError = (tag: string, message: string) => (error: unknown) =>
|
|
202
|
+
typeof error === "object" &&
|
|
203
|
+
error !== null &&
|
|
204
|
+
(error as { readonly _tag?: unknown })._tag === tag &&
|
|
205
|
+
(error as { readonly message?: unknown }).message === message;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * as Auth from "./AuthProvider.ts";
|
|
2
|
+
export { type BaseBranchAttributes, type BaseBranchProps, makeBranchProvider } from "./Branch.ts";
|
|
3
|
+
export {
|
|
4
|
+
Credentials,
|
|
5
|
+
CredentialsFromEnv,
|
|
6
|
+
DEFAULT_API_BASE_URL,
|
|
7
|
+
fromAuthProvider,
|
|
8
|
+
fromToken,
|
|
9
|
+
} from "./Credentials.ts";
|
|
10
|
+
export * from "./Database.ts";
|
|
11
|
+
export * from "./MySQL/index.ts";
|
|
12
|
+
export * from "./Postgres/index.ts";
|
|
13
|
+
export * from "./Providers.ts";
|
|
14
|
+
export * from "./Util.ts";
|