@alchemy.run/planetscale 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.
- package/lib/AuthProvider.d.ts +1 -1
- package/lib/AuthProvider.d.ts.map +1 -1
- package/lib/AuthProvider.js +58 -47
- package/lib/AuthProvider.js.map +1 -1
- package/lib/Branch.d.ts +6 -1
- package/lib/Branch.d.ts.map +1 -1
- package/lib/Branch.js +23 -2
- package/lib/Branch.js.map +1 -1
- package/lib/Contributions.d.ts +1 -1
- package/lib/Contributions.d.ts.map +1 -1
- package/lib/Contributions.js.map +1 -1
- package/lib/Credentials.d.ts +1 -1
- package/lib/Credentials.d.ts.map +1 -1
- package/lib/Credentials.js +6 -12
- package/lib/Credentials.js.map +1 -1
- package/lib/Database.d.ts +7 -0
- package/lib/Database.d.ts.map +1 -1
- package/lib/Database.js.map +1 -1
- package/lib/MySQL/MySQLBranch.d.ts +26 -2
- package/lib/MySQL/MySQLBranch.d.ts.map +1 -1
- package/lib/MySQL/MySQLBranch.js +15 -1
- package/lib/MySQL/MySQLBranch.js.map +1 -1
- package/lib/MySQL/MySQLClusterSize.d.ts +1 -1
- package/lib/MySQL/MySQLClusterSize.d.ts.map +1 -1
- package/lib/MySQL/MySQLClusterSize.js +1 -1
- package/lib/MySQL/MySQLClusterSize.js.map +1 -1
- package/lib/MySQL/MySQLDatabase.d.ts +28 -2
- package/lib/MySQL/MySQLDatabase.d.ts.map +1 -1
- package/lib/MySQL/MySQLDatabase.js +72 -51
- package/lib/MySQL/MySQLDatabase.js.map +1 -1
- package/lib/MySQL/MySQLMigrations.d.ts +7 -11
- package/lib/MySQL/MySQLMigrations.d.ts.map +1 -1
- package/lib/MySQL/MySQLMigrations.js +24 -58
- package/lib/MySQL/MySQLMigrations.js.map +1 -1
- package/lib/MySQL/MySQLOrigin.d.ts.map +1 -1
- package/lib/MySQL/MySQLOrigin.js.map +1 -1
- package/lib/MySQL/MySQLPassword.d.ts.map +1 -1
- package/lib/MySQL/MySQLPassword.js +2 -2
- package/lib/MySQL/MySQLPassword.js.map +1 -1
- package/lib/MySQL/index.d.ts.map +1 -1
- package/lib/MySQL/index.js.map +1 -1
- package/lib/OAuthClient.d.ts.map +1 -1
- package/lib/OAuthClient.js.map +1 -1
- package/lib/Postgres/PostgresBranch.d.ts +26 -2
- package/lib/Postgres/PostgresBranch.d.ts.map +1 -1
- package/lib/Postgres/PostgresBranch.js +15 -1
- package/lib/Postgres/PostgresBranch.js.map +1 -1
- package/lib/Postgres/PostgresClusterSize.d.ts.map +1 -1
- package/lib/Postgres/PostgresClusterSize.js.map +1 -1
- package/lib/Postgres/PostgresDatabase.d.ts +28 -2
- package/lib/Postgres/PostgresDatabase.d.ts.map +1 -1
- package/lib/Postgres/PostgresDatabase.js +77 -56
- package/lib/Postgres/PostgresDatabase.js.map +1 -1
- package/lib/Postgres/PostgresDefaultRole.d.ts.map +1 -1
- package/lib/Postgres/PostgresDefaultRole.js +2 -2
- package/lib/Postgres/PostgresDefaultRole.js.map +1 -1
- package/lib/Postgres/PostgresMigrations.d.ts +3 -11
- package/lib/Postgres/PostgresMigrations.d.ts.map +1 -1
- package/lib/Postgres/PostgresMigrations.js +15 -66
- package/lib/Postgres/PostgresMigrations.js.map +1 -1
- package/lib/Postgres/PostgresOrigin.d.ts.map +1 -1
- package/lib/Postgres/PostgresOrigin.js.map +1 -1
- package/lib/Postgres/PostgresRole.d.ts.map +1 -1
- package/lib/Postgres/PostgresRole.js +3 -3
- package/lib/Postgres/PostgresRole.js.map +1 -1
- package/lib/Postgres/index.d.ts.map +1 -1
- package/lib/Postgres/index.js.map +1 -1
- package/lib/Providers.d.ts +1 -1
- package/lib/Providers.d.ts.map +1 -1
- package/lib/Providers.js +3 -1
- package/lib/Providers.js.map +1 -1
- package/lib/ServiceToken.d.ts +142 -0
- package/lib/ServiceToken.d.ts.map +1 -0
- package/lib/ServiceToken.js +268 -0
- package/lib/ServiceToken.js.map +1 -0
- package/lib/Util.d.ts +3 -3
- package/lib/Util.d.ts.map +1 -1
- package/lib/Util.js +1 -1
- package/lib/Util.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/package.json +9 -9
- package/src/AuthProvider.ts +91 -78
- package/src/Branch.ts +36 -3
- package/src/Credentials.ts +8 -32
- package/src/Database.ts +7 -0
- package/src/MySQL/MySQLBranch.ts +28 -2
- package/src/MySQL/MySQLClusterSize.ts +1 -1
- package/src/MySQL/MySQLDatabase.ts +103 -63
- package/src/MySQL/MySQLMigrations.ts +31 -80
- package/src/MySQL/MySQLPassword.ts +2 -2
- package/src/Postgres/PostgresBranch.ts +28 -2
- package/src/Postgres/PostgresDatabase.ts +104 -63
- package/src/Postgres/PostgresDefaultRole.ts +2 -2
- package/src/Postgres/PostgresMigrations.ts +20 -91
- package/src/Postgres/PostgresRole.ts +4 -4
- package/src/Providers.ts +3 -0
- package/src/ServiceToken.ts +431 -0
- package/src/Util.ts +1 -1
- package/src/index.ts +1 -0
|
@@ -180,12 +180,109 @@ export type MySQLDatabase = Resource<
|
|
|
180
180
|
MySQLDatabaseProps,
|
|
181
181
|
MySQLDatabaseAttributes,
|
|
182
182
|
never,
|
|
183
|
-
Providers
|
|
183
|
+
Providers,
|
|
184
|
+
MySQLDatabaseLookup
|
|
184
185
|
>;
|
|
185
186
|
|
|
186
|
-
/**
|
|
187
|
+
/**
|
|
188
|
+
* Props for {@link MySQLDatabase.lookup}: find a MySQL database this
|
|
189
|
+
* stack doesn't manage by its name, in the credentialed organization.
|
|
190
|
+
*/
|
|
191
|
+
export interface MySQLDatabaseLookup {
|
|
192
|
+
/** Database name. */
|
|
193
|
+
name: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* A PlanetScale MySQL (Vitess) database.
|
|
198
|
+
*
|
|
199
|
+
* ### Looking up an existing MySQL database
|
|
200
|
+
* `MySQLDatabase.lookup` references a MySQL database Alchemy doesn't manage (created in
|
|
201
|
+
* the PlanetScale dashboard or by another stack). It is read during
|
|
202
|
+
* plan/deploy, never created, updated or deleted.
|
|
203
|
+
*
|
|
204
|
+
* **Example:** Branch an existing database
|
|
205
|
+
* ```typescript
|
|
206
|
+
* const db = Planetscale.MySQLDatabase.lookup({ name: "app" });
|
|
207
|
+
* const preview = yield* Planetscale.MySQLBranch("Preview", {
|
|
208
|
+
* database: db,
|
|
209
|
+
* isProduction: false,
|
|
210
|
+
* });
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* @resource
|
|
214
|
+
*/
|
|
187
215
|
export const MySQLDatabase = Resource<MySQLDatabase>("Planetscale.MySQLDatabase");
|
|
188
216
|
|
|
217
|
+
/** Observe a database by name; `read` and `lookup` share it. */
|
|
218
|
+
const readMySQLDatabase = Effect.fn(function* ({
|
|
219
|
+
id,
|
|
220
|
+
output,
|
|
221
|
+
olds,
|
|
222
|
+
}: {
|
|
223
|
+
id: string;
|
|
224
|
+
output?: MySQLDatabase["Attributes"];
|
|
225
|
+
olds?: Partial<MySQLDatabaseProps>;
|
|
226
|
+
}) {
|
|
227
|
+
const { organization } = yield* yield* Credentials;
|
|
228
|
+
const databaseName = output?.name ?? (yield* createDatabaseName(id, olds?.name));
|
|
229
|
+
return yield* planetscale
|
|
230
|
+
.getDatabase({
|
|
231
|
+
organization,
|
|
232
|
+
database: databaseName,
|
|
233
|
+
})
|
|
234
|
+
.pipe(
|
|
235
|
+
Effect.flatMap(
|
|
236
|
+
Effect.fn(function* (data) {
|
|
237
|
+
if (data.kind !== "mysql") {
|
|
238
|
+
return yield* new PlanetscaleConflict({
|
|
239
|
+
message:
|
|
240
|
+
`Planetscale database "${data.name}" has kind "${data.kind}" but this resource ` +
|
|
241
|
+
`is a MySQLDatabase. Use Planetscale.${data.kind === "postgresql" ? "PostgresDatabase" : data.kind}() instead, ` +
|
|
242
|
+
`or delete the existing database and retry.`,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
// Observe the default keyspace's live replica configuration
|
|
246
|
+
// so adopted databases diff against reality.
|
|
247
|
+
const replicas = yield* observeDefaultKeyspaceReplicas(
|
|
248
|
+
organization,
|
|
249
|
+
data.name,
|
|
250
|
+
data.default_branch ?? "main",
|
|
251
|
+
);
|
|
252
|
+
return {
|
|
253
|
+
id: data.id,
|
|
254
|
+
name: data.name,
|
|
255
|
+
organization,
|
|
256
|
+
state: data.state,
|
|
257
|
+
defaultBranch: data.default_branch ?? "main",
|
|
258
|
+
replicas,
|
|
259
|
+
plan: data.plan ?? "hobby",
|
|
260
|
+
createdAt: data.created_at,
|
|
261
|
+
updatedAt: data.updated_at,
|
|
262
|
+
htmlUrl: data.html_url,
|
|
263
|
+
region: { slug: data.region.slug },
|
|
264
|
+
migrationsDir: output?.migrationsDir ?? (olds && migrationsInputOf(olds))?.dir,
|
|
265
|
+
migrationsTable: output?.migrationsTable ?? (olds && migrationsInputOf(olds))?.table,
|
|
266
|
+
migrationsHashes: output?.migrationsHashes ?? {},
|
|
267
|
+
importHashes: output?.importHashes ?? {},
|
|
268
|
+
clusterSize: output?.clusterSize ?? "",
|
|
269
|
+
requireApprovalForDeploy: data.require_approval_for_deploy ?? false,
|
|
270
|
+
restrictBranchRegion: data.restrict_branch_region ?? false,
|
|
271
|
+
insightsRawQueries: data.insights_raw_queries ?? false,
|
|
272
|
+
productionBranchWebConsole: data.production_branch_web_console ?? false,
|
|
273
|
+
deletionProtection: data.deletion_protected ?? false,
|
|
274
|
+
automaticMigrations: data.automatic_migrations ?? false,
|
|
275
|
+
migrationFramework: data.migration_framework ?? undefined,
|
|
276
|
+
migrationTableName: data.migration_table_name ?? undefined,
|
|
277
|
+
allowDataBranching: data.allow_data_branching ?? false,
|
|
278
|
+
allowForeignKeyConstraints: data.foreign_keys_enabled ?? false,
|
|
279
|
+
};
|
|
280
|
+
}),
|
|
281
|
+
),
|
|
282
|
+
Effect.catchTag("NotFound", () => Effect.undefined),
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
|
|
189
286
|
export const MySQLDatabaseProvider = () =>
|
|
190
287
|
Provider.succeed(MySQLDatabase, {
|
|
191
288
|
stables: ["id", "organization", "region"],
|
|
@@ -246,66 +343,9 @@ export const MySQLDatabaseProvider = () =>
|
|
|
246
343
|
return stables ? ({ action: "noop", stables } as const) : undefined;
|
|
247
344
|
}),
|
|
248
345
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return yield* planetscale
|
|
253
|
-
.getDatabase({
|
|
254
|
-
organization,
|
|
255
|
-
database: databaseName,
|
|
256
|
-
})
|
|
257
|
-
.pipe(
|
|
258
|
-
Effect.flatMap(
|
|
259
|
-
Effect.fn(function* (data) {
|
|
260
|
-
if (data.kind !== "mysql") {
|
|
261
|
-
return yield* new PlanetscaleConflict({
|
|
262
|
-
message:
|
|
263
|
-
`Planetscale database "${data.name}" has kind "${data.kind}" but this resource ` +
|
|
264
|
-
`is a MySQLDatabase. Use Planetscale.${data.kind === "postgresql" ? "PostgresDatabase" : data.kind}() instead, ` +
|
|
265
|
-
`or delete the existing database and retry.`,
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
// Observe the default keyspace's live replica configuration
|
|
269
|
-
// so adopted databases diff against reality.
|
|
270
|
-
const replicas = yield* observeDefaultKeyspaceReplicas(
|
|
271
|
-
organization,
|
|
272
|
-
data.name,
|
|
273
|
-
data.default_branch ?? "main",
|
|
274
|
-
);
|
|
275
|
-
return {
|
|
276
|
-
id: data.id,
|
|
277
|
-
name: data.name,
|
|
278
|
-
organization,
|
|
279
|
-
state: data.state,
|
|
280
|
-
defaultBranch: data.default_branch ?? "main",
|
|
281
|
-
replicas,
|
|
282
|
-
plan: data.plan ?? "hobby",
|
|
283
|
-
createdAt: data.created_at,
|
|
284
|
-
updatedAt: data.updated_at,
|
|
285
|
-
htmlUrl: data.html_url,
|
|
286
|
-
region: { slug: data.region.slug },
|
|
287
|
-
migrationsDir: output?.migrationsDir ?? (olds && migrationsInputOf(olds))?.dir,
|
|
288
|
-
migrationsTable:
|
|
289
|
-
output?.migrationsTable ?? (olds && migrationsInputOf(olds))?.table,
|
|
290
|
-
migrationsHashes: output?.migrationsHashes ?? {},
|
|
291
|
-
importHashes: output?.importHashes ?? {},
|
|
292
|
-
clusterSize: output?.clusterSize ?? "",
|
|
293
|
-
requireApprovalForDeploy: data.require_approval_for_deploy ?? false,
|
|
294
|
-
restrictBranchRegion: data.restrict_branch_region ?? false,
|
|
295
|
-
insightsRawQueries: data.insights_raw_queries ?? false,
|
|
296
|
-
productionBranchWebConsole: data.production_branch_web_console ?? false,
|
|
297
|
-
deletionProtection: data.deletion_protected ?? false,
|
|
298
|
-
automaticMigrations: data.automatic_migrations ?? false,
|
|
299
|
-
migrationFramework: data.migration_framework ?? undefined,
|
|
300
|
-
migrationTableName: data.migration_table_name ?? undefined,
|
|
301
|
-
allowDataBranching: data.allow_data_branching ?? false,
|
|
302
|
-
allowForeignKeyConstraints: data.foreign_keys_enabled ?? false,
|
|
303
|
-
};
|
|
304
|
-
}),
|
|
305
|
-
),
|
|
306
|
-
Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
|
|
307
|
-
);
|
|
308
|
-
}),
|
|
346
|
+
lookup: ({ name }) => readMySQLDatabase({ id: name, olds: { name } }),
|
|
347
|
+
|
|
348
|
+
read: readMySQLDatabase,
|
|
309
349
|
|
|
310
350
|
reconcile: Effect.fn(function* ({ id, news, output, session }) {
|
|
311
351
|
const { organization } = yield* yield* Credentials;
|
|
@@ -327,7 +367,7 @@ export const MySQLDatabaseProvider = () =>
|
|
|
327
367
|
organization,
|
|
328
368
|
database: observedName,
|
|
329
369
|
})
|
|
330
|
-
.pipe(Effect.catchTag("NotFound", () => Effect.
|
|
370
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.undefined));
|
|
331
371
|
|
|
332
372
|
// Ensure — if missing, create.
|
|
333
373
|
if (!observed) {
|
|
@@ -1,34 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
makeMySQLMigrationExecutor,
|
|
3
2
|
runMigrations,
|
|
3
|
+
withMySqlExecutor,
|
|
4
4
|
type NormalizedMigrationsInput,
|
|
5
|
+
type SqlExecutor,
|
|
5
6
|
type StampedMigrationsState,
|
|
6
7
|
} from "@alchemy.run/sql/Migrations";
|
|
7
|
-
import {
|
|
8
|
+
import { applySqlImports, splitSqlStatements } from "@alchemy.run/sql/SqlFile";
|
|
8
9
|
import * as ps from "@distilled.cloud/planetscale";
|
|
9
|
-
import * as Data from "effect/Data";
|
|
10
10
|
import * as Effect from "effect/Effect";
|
|
11
11
|
import * as Redacted from "effect/Redacted";
|
|
12
12
|
import * as Schedule from "effect/Schedule";
|
|
13
|
-
import type { Connection } from "mysql2/promise";
|
|
14
13
|
|
|
15
14
|
const MIGRATION_PASSWORD_TTL_SECONDS = 600;
|
|
16
15
|
|
|
17
|
-
// `mysql2` is an optional peer dependency — loaded lazily so importing the
|
|
18
|
-
// Planetscale provider never requires the driver unless migrations run.
|
|
19
|
-
const importMysql = () =>
|
|
20
|
-
import("mysql2/promise").catch((cause) => {
|
|
21
|
-
throw new Error(
|
|
22
|
-
"Failed to load the 'mysql2' driver. Install the optional peer dependency 'mysql2' to run Planetscale MySQL migrations.",
|
|
23
|
-
{ cause },
|
|
24
|
-
);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
export class MySQLMigrationError extends Data.TaggedError("Planetscale::MySQLMigrationError")<{
|
|
28
|
-
message: string;
|
|
29
|
-
cause?: unknown;
|
|
30
|
-
}> {}
|
|
31
|
-
|
|
32
16
|
export interface MySQLMigrationTarget {
|
|
33
17
|
organization: string;
|
|
34
18
|
database: string;
|
|
@@ -47,69 +31,48 @@ export const runMySQLMigrations = (
|
|
|
47
31
|
runMigrations({
|
|
48
32
|
input,
|
|
49
33
|
stamped,
|
|
50
|
-
withExecutor: (apply) =>
|
|
51
|
-
withMySQLConnection(target, (connection) => apply(makeMySQLMigrationExecutor(connection))),
|
|
34
|
+
withExecutor: (apply) => withMySQLExecutor(target, apply),
|
|
52
35
|
});
|
|
53
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Apply `importFiles`, each once and again only when it changes. Vitess
|
|
39
|
+
* rejects drizzle-kit's `--> statement-breakpoint` markers, so statements
|
|
40
|
+
* run one at a time.
|
|
41
|
+
*/
|
|
54
42
|
export const runMySQLImports = (
|
|
55
43
|
target: MySQLMigrationTarget,
|
|
56
44
|
importFiles: ReadonlyArray<string>,
|
|
57
45
|
rootDir: string,
|
|
58
46
|
previous: Record<string, string>,
|
|
59
47
|
) =>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
const tracked = new Set(importFiles);
|
|
72
|
-
for (const key of Object.keys(hashes)) {
|
|
73
|
-
if (!tracked.has(key)) delete hashes[key];
|
|
74
|
-
}
|
|
75
|
-
return hashes;
|
|
48
|
+
applySqlImports({
|
|
49
|
+
importFiles,
|
|
50
|
+
rootDir,
|
|
51
|
+
previous,
|
|
52
|
+
apply: (file) =>
|
|
53
|
+
withMySQLExecutor(target, (executor) =>
|
|
54
|
+
Effect.forEach(splitSqlStatements(file.sql), (statement) => executor.query(statement), {
|
|
55
|
+
discard: true,
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
76
58
|
});
|
|
77
59
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Effect.gen(function* () {
|
|
81
|
-
for (const statement of splitSqlStatements(sql)) {
|
|
82
|
-
yield* mysqlQuery(connection, statement);
|
|
83
|
-
}
|
|
84
|
-
}),
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
const withMySQLConnection = <A, E, R>(
|
|
60
|
+
/** A verified-TLS `mysql2` session as a short-lived admin password. */
|
|
61
|
+
const withMySQLExecutor = <A, E, R>(
|
|
88
62
|
target: MySQLMigrationTarget,
|
|
89
|
-
use: (
|
|
63
|
+
use: (executor: SqlExecutor) => Effect.Effect<A, E, R>,
|
|
90
64
|
) =>
|
|
91
65
|
withTemporaryMySQLPassword(target, (password) =>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
multipleStatements: true,
|
|
102
|
-
ssl: { rejectUnauthorized: true },
|
|
103
|
-
});
|
|
104
|
-
},
|
|
105
|
-
catch: toMigrationError,
|
|
106
|
-
}),
|
|
66
|
+
withMySqlExecutor(
|
|
67
|
+
{
|
|
68
|
+
host: password.host,
|
|
69
|
+
user: password.username,
|
|
70
|
+
password: Redacted.value(password.password),
|
|
71
|
+
database: target.database,
|
|
72
|
+
multipleStatements: true,
|
|
73
|
+
ssl: { rejectUnauthorized: true },
|
|
74
|
+
},
|
|
107
75
|
use,
|
|
108
|
-
(connection) =>
|
|
109
|
-
Effect.tryPromise({
|
|
110
|
-
try: () => connection.end(),
|
|
111
|
-
catch: toMigrationError,
|
|
112
|
-
}).pipe(Effect.ignore),
|
|
113
76
|
),
|
|
114
77
|
);
|
|
115
78
|
|
|
@@ -168,15 +131,3 @@ const withTemporaryMySQLPassword = <A, E, R>(
|
|
|
168
131
|
),
|
|
169
132
|
),
|
|
170
133
|
);
|
|
171
|
-
|
|
172
|
-
const mysqlQuery = (connection: Connection, sql: string) =>
|
|
173
|
-
Effect.tryPromise({
|
|
174
|
-
try: () => connection.query(sql).then(() => undefined),
|
|
175
|
-
catch: toMigrationError,
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const toMigrationError = (cause: unknown) =>
|
|
179
|
-
new MySQLMigrationError({
|
|
180
|
-
message: cause instanceof Error ? cause.message : String(cause),
|
|
181
|
-
cause,
|
|
182
|
-
});
|
|
@@ -189,7 +189,7 @@ export const MySQLPasswordProvider = () =>
|
|
|
189
189
|
branch: output.branch,
|
|
190
190
|
}),
|
|
191
191
|
),
|
|
192
|
-
Effect.catchTag("NotFound", () => Effect.
|
|
192
|
+
Effect.catchTag("NotFound", () => Effect.undefined),
|
|
193
193
|
);
|
|
194
194
|
}),
|
|
195
195
|
|
|
@@ -217,7 +217,7 @@ export const MySQLPasswordProvider = () =>
|
|
|
217
217
|
branch: branchName,
|
|
218
218
|
id: output.id,
|
|
219
219
|
})
|
|
220
|
-
.pipe(Effect.catchTag("NotFound", () => Effect.
|
|
220
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.undefined))
|
|
221
221
|
: undefined;
|
|
222
222
|
|
|
223
223
|
// 2. Ensure — create the password if it doesn't exist live.
|
|
@@ -79,10 +79,36 @@ export type PostgresBranch = Resource<
|
|
|
79
79
|
PostgresBranchProps,
|
|
80
80
|
PostgresBranchAttributes,
|
|
81
81
|
never,
|
|
82
|
-
Providers
|
|
82
|
+
Providers,
|
|
83
|
+
PostgresBranchLookup
|
|
83
84
|
>;
|
|
84
85
|
|
|
85
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Props for {@link PostgresBranch.lookup}: find a Postgres branch this
|
|
88
|
+
* stack doesn't manage by its database and name.
|
|
89
|
+
*/
|
|
90
|
+
export interface PostgresBranchLookup {
|
|
91
|
+
/** Database name. */
|
|
92
|
+
database: string;
|
|
93
|
+
/** Branch name. */
|
|
94
|
+
name: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A branch of a PlanetScale Postgres database.
|
|
99
|
+
*
|
|
100
|
+
* ### Looking up an existing Postgres branch
|
|
101
|
+
* `PostgresBranch.lookup` references a Postgres branch Alchemy doesn't manage (created in
|
|
102
|
+
* the PlanetScale dashboard or by another stack). It is read during
|
|
103
|
+
* plan/deploy, never created, updated or deleted.
|
|
104
|
+
*
|
|
105
|
+
* **Example:** Reference an existing branch
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const main = Planetscale.PostgresBranch.lookup({ database: "app", name: "main" });
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @resource
|
|
111
|
+
*/
|
|
86
112
|
export const PostgresBranch = Resource<PostgresBranch>("Planetscale.PostgresBranch");
|
|
87
113
|
|
|
88
114
|
export const PostgresBranchProvider = () =>
|
|
@@ -124,12 +124,110 @@ export type PostgresDatabase = Resource<
|
|
|
124
124
|
PostgresDatabaseProps,
|
|
125
125
|
PostgresDatabaseAttributes,
|
|
126
126
|
never,
|
|
127
|
-
Providers
|
|
127
|
+
Providers,
|
|
128
|
+
PostgresDatabaseLookup
|
|
128
129
|
>;
|
|
129
130
|
|
|
130
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Props for {@link PostgresDatabase.lookup}: find a Postgres database this
|
|
133
|
+
* stack doesn't manage by its name, in the credentialed organization.
|
|
134
|
+
*/
|
|
135
|
+
export interface PostgresDatabaseLookup {
|
|
136
|
+
/** Database name. */
|
|
137
|
+
name: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A PlanetScale Postgres database.
|
|
142
|
+
*
|
|
143
|
+
* ### Looking up an existing Postgres database
|
|
144
|
+
* `PostgresDatabase.lookup` references a Postgres database Alchemy doesn't manage (created in
|
|
145
|
+
* the PlanetScale dashboard or by another stack). It is read during
|
|
146
|
+
* plan/deploy, never created, updated or deleted.
|
|
147
|
+
*
|
|
148
|
+
* **Example:** Branch an existing database
|
|
149
|
+
* ```typescript
|
|
150
|
+
* const db = Planetscale.PostgresDatabase.lookup({ name: "app" });
|
|
151
|
+
* const preview = yield* Planetscale.PostgresBranch("Preview", {
|
|
152
|
+
* database: db,
|
|
153
|
+
* isProduction: false,
|
|
154
|
+
* });
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
157
|
+
* @resource
|
|
158
|
+
*/
|
|
131
159
|
export const PostgresDatabase = Resource<PostgresDatabase>("Planetscale.PostgresDatabase");
|
|
132
160
|
|
|
161
|
+
/** Observe a database by name; `read` and `lookup` share it. */
|
|
162
|
+
const readPostgresDatabase = Effect.fn(function* ({
|
|
163
|
+
id,
|
|
164
|
+
output,
|
|
165
|
+
olds,
|
|
166
|
+
}: {
|
|
167
|
+
id: string;
|
|
168
|
+
output?: PostgresDatabase["Attributes"];
|
|
169
|
+
olds?: Partial<PostgresDatabaseProps>;
|
|
170
|
+
}) {
|
|
171
|
+
const { organization } = yield* yield* Credentials;
|
|
172
|
+
const databaseName = output?.name ?? (yield* createDatabaseName(id, olds?.name));
|
|
173
|
+
|
|
174
|
+
const data = yield* planetscale
|
|
175
|
+
.getDatabase({
|
|
176
|
+
organization,
|
|
177
|
+
database: databaseName,
|
|
178
|
+
})
|
|
179
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.undefined));
|
|
180
|
+
|
|
181
|
+
if (!data) return undefined;
|
|
182
|
+
|
|
183
|
+
if (data.kind !== "postgresql") {
|
|
184
|
+
return yield* new PlanetscaleConflict({
|
|
185
|
+
message:
|
|
186
|
+
`Planetscale database "${data.name}" has kind "${data.kind}" but this resource ` +
|
|
187
|
+
`is a PostgresDatabase. Use Planetscale.${data.kind === "mysql" ? "MySQLDatabase" : data.kind}() instead, ` +
|
|
188
|
+
`or delete the existing database and retry.`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const defaultBranch = data.default_branch ?? "main";
|
|
193
|
+
|
|
194
|
+
// Observe `arch` and `clusterSize` from the default branch rather
|
|
195
|
+
// than echoing input.
|
|
196
|
+
const branch = yield* planetscale
|
|
197
|
+
.getBranch({
|
|
198
|
+
organization,
|
|
199
|
+
database: data.name,
|
|
200
|
+
branch: defaultBranch,
|
|
201
|
+
})
|
|
202
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.undefined));
|
|
203
|
+
|
|
204
|
+
const arch = toPostgresClusterArch(branch!.cluster_architecture);
|
|
205
|
+
const clusterSize = branch!.cluster_name;
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
id: data.id,
|
|
209
|
+
name: data.name,
|
|
210
|
+
organization,
|
|
211
|
+
state: data.state,
|
|
212
|
+
defaultBranch,
|
|
213
|
+
plan: data.plan ?? "hobby",
|
|
214
|
+
createdAt: data.created_at,
|
|
215
|
+
updatedAt: data.updated_at,
|
|
216
|
+
htmlUrl: data.html_url,
|
|
217
|
+
region: { slug: data.region.slug },
|
|
218
|
+
migrationsDir: output?.migrationsDir ?? (olds && migrationsInputOf(olds))?.dir,
|
|
219
|
+
migrationsTable: output?.migrationsTable ?? (olds && migrationsInputOf(olds))?.table,
|
|
220
|
+
migrationsHashes: output?.migrationsHashes ?? {},
|
|
221
|
+
importHashes: output?.importHashes ?? {},
|
|
222
|
+
clusterSize,
|
|
223
|
+
arch,
|
|
224
|
+
requireApprovalForDeploy: data.require_approval_for_deploy ?? false,
|
|
225
|
+
restrictBranchRegion: data.restrict_branch_region ?? false,
|
|
226
|
+
productionBranchWebConsole: data.production_branch_web_console ?? false,
|
|
227
|
+
deletionProtection: data.deletion_protected ?? false,
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
|
|
133
231
|
export const PostgresDatabaseProvider = () =>
|
|
134
232
|
Provider.succeed(PostgresDatabase, {
|
|
135
233
|
stables: ["id", "organization", "region"],
|
|
@@ -191,66 +289,9 @@ export const PostgresDatabaseProvider = () =>
|
|
|
191
289
|
return stables ? ({ action: "noop", stables } as const) : undefined;
|
|
192
290
|
}),
|
|
193
291
|
|
|
194
|
-
|
|
195
|
-
const { organization } = yield* yield* Credentials;
|
|
196
|
-
const databaseName = output?.name ?? (yield* createDatabaseName(id, olds?.name));
|
|
197
|
-
|
|
198
|
-
const data = yield* planetscale
|
|
199
|
-
.getDatabase({
|
|
200
|
-
organization,
|
|
201
|
-
database: databaseName,
|
|
202
|
-
})
|
|
203
|
-
.pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
|
|
204
|
-
|
|
205
|
-
if (!data) return undefined;
|
|
206
|
-
|
|
207
|
-
if (data.kind !== "postgresql") {
|
|
208
|
-
return yield* new PlanetscaleConflict({
|
|
209
|
-
message:
|
|
210
|
-
`Planetscale database "${data.name}" has kind "${data.kind}" but this resource ` +
|
|
211
|
-
`is a PostgresDatabase. Use Planetscale.${data.kind === "mysql" ? "MySQLDatabase" : data.kind}() instead, ` +
|
|
212
|
-
`or delete the existing database and retry.`,
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const defaultBranch = data.default_branch ?? "main";
|
|
292
|
+
lookup: ({ name }) => readPostgresDatabase({ id: name, olds: { name } }),
|
|
217
293
|
|
|
218
|
-
|
|
219
|
-
// than echoing input.
|
|
220
|
-
const branch = yield* planetscale
|
|
221
|
-
.getBranch({
|
|
222
|
-
organization,
|
|
223
|
-
database: data.name,
|
|
224
|
-
branch: defaultBranch,
|
|
225
|
-
})
|
|
226
|
-
.pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
|
|
227
|
-
|
|
228
|
-
const arch = toPostgresClusterArch(branch!.cluster_architecture);
|
|
229
|
-
const clusterSize = branch!.cluster_name;
|
|
230
|
-
|
|
231
|
-
return {
|
|
232
|
-
id: data.id,
|
|
233
|
-
name: data.name,
|
|
234
|
-
organization,
|
|
235
|
-
state: data.state,
|
|
236
|
-
defaultBranch,
|
|
237
|
-
plan: data.plan ?? "hobby",
|
|
238
|
-
createdAt: data.created_at,
|
|
239
|
-
updatedAt: data.updated_at,
|
|
240
|
-
htmlUrl: data.html_url,
|
|
241
|
-
region: { slug: data.region.slug },
|
|
242
|
-
migrationsDir: output?.migrationsDir ?? (olds && migrationsInputOf(olds))?.dir,
|
|
243
|
-
migrationsTable: output?.migrationsTable ?? (olds && migrationsInputOf(olds))?.table,
|
|
244
|
-
migrationsHashes: output?.migrationsHashes ?? {},
|
|
245
|
-
importHashes: output?.importHashes ?? {},
|
|
246
|
-
clusterSize,
|
|
247
|
-
arch,
|
|
248
|
-
requireApprovalForDeploy: data.require_approval_for_deploy ?? false,
|
|
249
|
-
restrictBranchRegion: data.restrict_branch_region ?? false,
|
|
250
|
-
productionBranchWebConsole: data.production_branch_web_console ?? false,
|
|
251
|
-
deletionProtection: data.deletion_protected ?? false,
|
|
252
|
-
};
|
|
253
|
-
}),
|
|
294
|
+
read: readPostgresDatabase,
|
|
254
295
|
|
|
255
296
|
reconcile: Effect.fn(function* ({ id, news, output, session }) {
|
|
256
297
|
const { organization } = yield* yield* Credentials;
|
|
@@ -276,7 +317,7 @@ export const PostgresDatabaseProvider = () =>
|
|
|
276
317
|
organization,
|
|
277
318
|
database: observedName,
|
|
278
319
|
})
|
|
279
|
-
.pipe(Effect.catchTag("NotFound", () => Effect.
|
|
320
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.undefined));
|
|
280
321
|
|
|
281
322
|
// Ensure — if missing, create.
|
|
282
323
|
if (!observed) {
|
|
@@ -425,7 +466,7 @@ export const PostgresDatabaseProvider = () =>
|
|
|
425
466
|
database: data.name,
|
|
426
467
|
branch: defaultBranch,
|
|
427
468
|
})
|
|
428
|
-
.pipe(Effect.catchTag("NotFound", () => Effect.
|
|
469
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.undefined));
|
|
429
470
|
|
|
430
471
|
const arch = toPostgresClusterArch(branch?.cluster_architecture);
|
|
431
472
|
const clusterSize = branch?.cluster_name ?? "";
|
|
@@ -153,7 +153,7 @@ export const PostgresDefaultRoleProvider = () =>
|
|
|
153
153
|
privateHost: role.private_access_host_url,
|
|
154
154
|
privateConnectionServiceName: role.private_connection_service_name,
|
|
155
155
|
})),
|
|
156
|
-
Effect.catchTag("NotFound", () => Effect.
|
|
156
|
+
Effect.catchTag("NotFound", () => Effect.undefined),
|
|
157
157
|
);
|
|
158
158
|
}),
|
|
159
159
|
|
|
@@ -175,7 +175,7 @@ export const PostgresDefaultRoleProvider = () =>
|
|
|
175
175
|
database: databaseName,
|
|
176
176
|
branch: branchName,
|
|
177
177
|
})
|
|
178
|
-
.pipe(Effect.catchTag("NotFound", () => Effect.
|
|
178
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.undefined));
|
|
179
179
|
|
|
180
180
|
// 2. Sync (no-op) — we own the role and it still exists in the
|
|
181
181
|
// cloud. Default roles have nothing mutable in place: their
|