alchemy 0.84.0 → 0.85.1

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 (155) hide show
  1. package/bin/alchemy.js +32 -23
  2. package/lib/cloudflare/account-api-token.d.ts +5 -0
  3. package/lib/cloudflare/account-api-token.d.ts.map +1 -1
  4. package/lib/cloudflare/account-api-token.js +13 -2
  5. package/lib/cloudflare/account-api-token.js.map +1 -1
  6. package/lib/cloudflare/ai-crawler.d.ts +27 -0
  7. package/lib/cloudflare/ai-crawler.d.ts.map +1 -0
  8. package/lib/cloudflare/ai-crawler.js +75 -0
  9. package/lib/cloudflare/ai-crawler.js.map +1 -0
  10. package/lib/cloudflare/ai-search-token.d.ts +150 -0
  11. package/lib/cloudflare/ai-search-token.d.ts.map +1 -0
  12. package/lib/cloudflare/ai-search-token.js +171 -0
  13. package/lib/cloudflare/ai-search-token.js.map +1 -0
  14. package/lib/cloudflare/ai-search.d.ts +397 -0
  15. package/lib/cloudflare/ai-search.d.ts.map +1 -0
  16. package/lib/cloudflare/ai-search.js +307 -0
  17. package/lib/cloudflare/ai-search.js.map +1 -0
  18. package/lib/cloudflare/api-error.d.ts +11 -0
  19. package/lib/cloudflare/api-error.d.ts.map +1 -1
  20. package/lib/cloudflare/api-error.js +11 -0
  21. package/lib/cloudflare/api-error.js.map +1 -1
  22. package/lib/cloudflare/api.js +1 -1
  23. package/lib/cloudflare/api.js.map +1 -1
  24. package/lib/cloudflare/auth.d.ts +3 -2
  25. package/lib/cloudflare/auth.d.ts.map +1 -1
  26. package/lib/cloudflare/auth.js +17 -14
  27. package/lib/cloudflare/auth.js.map +1 -1
  28. package/lib/cloudflare/bindings.d.ts +12 -2
  29. package/lib/cloudflare/bindings.d.ts.map +1 -1
  30. package/lib/cloudflare/bindings.js.map +1 -1
  31. package/lib/cloudflare/bound.d.ts +2 -1
  32. package/lib/cloudflare/bound.d.ts.map +1 -1
  33. package/lib/cloudflare/bucket-custom-domain.d.ts.map +1 -1
  34. package/lib/cloudflare/bucket-custom-domain.js +4 -2
  35. package/lib/cloudflare/bucket-custom-domain.js.map +1 -1
  36. package/lib/cloudflare/bucket.d.ts.map +1 -1
  37. package/lib/cloudflare/bucket.js +8 -2
  38. package/lib/cloudflare/bucket.js.map +1 -1
  39. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  40. package/lib/cloudflare/d1-database.js +9 -1
  41. package/lib/cloudflare/d1-database.js.map +1 -1
  42. package/lib/cloudflare/index.d.ts +4 -0
  43. package/lib/cloudflare/index.d.ts.map +1 -1
  44. package/lib/cloudflare/index.js +4 -0
  45. package/lib/cloudflare/index.js.map +1 -1
  46. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  47. package/lib/cloudflare/miniflare/build-worker-options.js +15 -0
  48. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  49. package/lib/cloudflare/miniflare/miniflare-controller.d.ts +1 -1
  50. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
  51. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
  52. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +41 -3
  53. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
  54. package/lib/cloudflare/permission-groups.d.ts +5 -0
  55. package/lib/cloudflare/permission-groups.d.ts.map +1 -1
  56. package/lib/cloudflare/permission-groups.js +6 -0
  57. package/lib/cloudflare/permission-groups.js.map +1 -1
  58. package/lib/cloudflare/vpc-service.d.ts +288 -0
  59. package/lib/cloudflare/vpc-service.d.ts.map +1 -0
  60. package/lib/cloudflare/vpc-service.js +238 -0
  61. package/lib/cloudflare/vpc-service.js.map +1 -0
  62. package/lib/cloudflare/worker-metadata.d.ts +1 -0
  63. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  64. package/lib/cloudflare/worker-metadata.js +8 -0
  65. package/lib/cloudflare/worker-metadata.js.map +1 -1
  66. package/lib/cloudflare/worker.d.ts +9 -0
  67. package/lib/cloudflare/worker.d.ts.map +1 -1
  68. package/lib/cloudflare/worker.js.map +1 -1
  69. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  70. package/lib/cloudflare/wrangler.json.js +9 -0
  71. package/lib/cloudflare/wrangler.json.js.map +1 -1
  72. package/lib/planetscale/api/sdk.gen.d.ts +874 -190
  73. package/lib/planetscale/api/sdk.gen.d.ts.map +1 -1
  74. package/lib/planetscale/api/sdk.gen.js +1250 -306
  75. package/lib/planetscale/api/sdk.gen.js.map +1 -1
  76. package/lib/planetscale/api/types.gen.d.ts +8241 -4128
  77. package/lib/planetscale/api/types.gen.d.ts.map +1 -1
  78. package/lib/planetscale/branch.js +4 -4
  79. package/lib/planetscale/branch.js.map +1 -1
  80. package/lib/planetscale/database.d.ts +43 -24
  81. package/lib/planetscale/database.d.ts.map +1 -1
  82. package/lib/planetscale/database.js +39 -18
  83. package/lib/planetscale/database.js.map +1 -1
  84. package/lib/planetscale/default-role.d.ts +51 -0
  85. package/lib/planetscale/default-role.d.ts.map +1 -0
  86. package/lib/planetscale/default-role.js +100 -0
  87. package/lib/planetscale/default-role.js.map +1 -0
  88. package/lib/planetscale/index.d.ts +1 -0
  89. package/lib/planetscale/index.d.ts.map +1 -1
  90. package/lib/planetscale/index.js +1 -0
  91. package/lib/planetscale/index.js.map +1 -1
  92. package/lib/planetscale/organization.js +1 -1
  93. package/lib/planetscale/organization.js.map +1 -1
  94. package/lib/planetscale/role.d.ts +1 -1
  95. package/lib/planetscale/role.d.ts.map +1 -1
  96. package/lib/planetscale/role.js +1 -1
  97. package/lib/planetscale/role.js.map +1 -1
  98. package/lib/planetscale/utils.d.ts +1 -1
  99. package/lib/planetscale/utils.d.ts.map +1 -1
  100. package/lib/planetscale/utils.js +9 -10
  101. package/lib/planetscale/utils.js.map +1 -1
  102. package/lib/scope.d.ts +1 -1
  103. package/lib/scope.d.ts.map +1 -1
  104. package/lib/scope.js +10 -5
  105. package/lib/scope.js.map +1 -1
  106. package/lib/state/sqlite-state-store.js +2 -0
  107. package/lib/state/sqlite-state-store.js.map +1 -1
  108. package/lib/test/bun.d.ts +17 -0
  109. package/lib/test/bun.d.ts.map +1 -1
  110. package/lib/test/bun.js +1 -0
  111. package/lib/test/bun.js.map +1 -1
  112. package/lib/test/vitest.d.ts +17 -0
  113. package/lib/test/vitest.d.ts.map +1 -1
  114. package/lib/test/vitest.js +3 -0
  115. package/lib/test/vitest.js.map +1 -1
  116. package/lib/util/snake-to-camel.d.ts +21 -0
  117. package/lib/util/snake-to-camel.d.ts.map +1 -0
  118. package/lib/util/snake-to-camel.js +34 -0
  119. package/lib/util/snake-to-camel.js.map +1 -0
  120. package/package.json +2 -2
  121. package/src/cloudflare/account-api-token.ts +23 -6
  122. package/src/cloudflare/ai-crawler.ts +89 -0
  123. package/src/cloudflare/ai-search-token.ts +324 -0
  124. package/src/cloudflare/ai-search.ts +952 -0
  125. package/src/cloudflare/api-error.ts +19 -0
  126. package/src/cloudflare/api.ts +1 -1
  127. package/src/cloudflare/auth.ts +17 -14
  128. package/src/cloudflare/bindings.ts +15 -2
  129. package/src/cloudflare/bound.ts +6 -3
  130. package/src/cloudflare/bucket-custom-domain.ts +4 -1
  131. package/src/cloudflare/bucket.ts +9 -2
  132. package/src/cloudflare/d1-database.ts +11 -4
  133. package/src/cloudflare/index.ts +4 -0
  134. package/src/cloudflare/miniflare/build-worker-options.ts +30 -18
  135. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +47 -2
  136. package/src/cloudflare/permission-groups.ts +6 -0
  137. package/src/cloudflare/vpc-service.ts +466 -0
  138. package/src/cloudflare/worker-metadata.ts +8 -0
  139. package/src/cloudflare/worker.ts +9 -0
  140. package/src/cloudflare/wrangler.json.ts +8 -0
  141. package/src/planetscale/api/sdk.gen.ts +2331 -987
  142. package/src/planetscale/api/types.gen.ts +8980 -4574
  143. package/src/planetscale/branch.ts +4 -4
  144. package/src/planetscale/database.ts +87 -46
  145. package/src/planetscale/default-role.ts +162 -0
  146. package/src/planetscale/index.ts +1 -0
  147. package/src/planetscale/organization.ts +1 -1
  148. package/src/planetscale/role.ts +3 -2
  149. package/src/planetscale/utils.ts +10 -10
  150. package/src/scope.ts +14 -5
  151. package/src/state/sqlite-state-store.ts +2 -0
  152. package/src/test/bun.ts +25 -0
  153. package/src/test/vitest.ts +33 -0
  154. package/src/util/snake-to-camel.ts +59 -0
  155. package/workers/tunnel-proxy.js +1 -1
@@ -205,7 +205,7 @@ export const Branch = Resource(
205
205
  path: {
206
206
  organization,
207
207
  database,
208
- name: this.output.name,
208
+ branch: this.output.name,
209
209
  },
210
210
  throwOnError: false,
211
211
  });
@@ -236,7 +236,7 @@ export const Branch = Resource(
236
236
  path: {
237
237
  organization,
238
238
  database,
239
- name: branchName,
239
+ branch: branchName,
240
240
  },
241
241
  throwOnError: false,
242
242
  });
@@ -287,7 +287,7 @@ export const Branch = Resource(
287
287
  path: {
288
288
  organization,
289
289
  database,
290
- name: branchName,
290
+ branch: branchName,
291
291
  },
292
292
  });
293
293
  }
@@ -367,7 +367,7 @@ export const Branch = Resource(
367
367
  path: {
368
368
  organization,
369
369
  database,
370
- name: branchName,
370
+ branch: branchName,
371
371
  },
372
372
  });
373
373
  }
@@ -46,63 +46,93 @@ interface BaseDatabaseProps extends PlanetScaleProps {
46
46
  };
47
47
 
48
48
  /**
49
- * Whether to require approval for deployments
49
+ * The number of replicas for the database. 0 for non-HA, 2+ for HA. (create only)
50
50
  */
51
- requireApprovalForDeploy?: boolean;
51
+ replicas?: number;
52
52
 
53
53
  /**
54
- * Whether to allow data branching
54
+ * The database cluster size (required)
55
55
  */
56
- allowDataBranching?: boolean;
56
+ clusterSize: PlanetScaleClusterSize;
57
+
58
+ /**
59
+ * The engine kind for the database (create only)
60
+ * @default "mysql"
61
+ */
62
+ kind?: "mysql" | "postgresql";
57
63
 
58
64
  /**
59
- * Whether to enable automatic migrations
65
+ * Whether or not deploy requests must be approved by a database administrator other than the request creator
60
66
  */
61
- automaticMigrations?: boolean;
67
+ requireApprovalForDeploy?: boolean;
62
68
 
63
69
  /**
64
- * Whether to restrict branch creation to the same region as database
70
+ * Whether or not to limit branch creation to the same region as the one selected during database creation.
65
71
  */
66
72
  restrictBranchRegion?: boolean;
67
73
 
68
74
  /**
69
- * Whether to collect full queries from the database
75
+ * Whether or not full queries should be collected from the database
70
76
  */
71
77
  insightsRawQueries?: boolean;
72
78
 
73
79
  /**
74
- * Whether web console can be used on production branch
80
+ * Whether or not the web console can be used on the production branch of the database
75
81
  */
76
82
  productionBranchWebConsole?: boolean;
77
83
 
78
84
  /**
79
85
  * The default branch of the database
86
+ * @default "main"
80
87
  */
81
88
  defaultBranch?: string;
89
+ }
90
+
91
+ /**
92
+ * Properties for creating or updating a PlanetScale MySQL database
93
+ */
94
+ interface MySQLDatabaseProps extends BaseDatabaseProps {
95
+ kind?: "mysql";
82
96
 
83
97
  /**
84
- * Migration framework to use on the database
98
+ * Whether or not to copy migration data to new branches and in deploy requests. (Vitess only)
99
+ */
100
+ automaticMigrations?: boolean;
101
+
102
+ /**
103
+ * A migration framework to use on the database. (Vitess only)
85
104
  */
86
105
  migrationFramework?: string;
87
106
 
88
107
  /**
89
- * Name of table to use as migration table
108
+ * Name of table to use as migration table for the database. (Vitess only)
90
109
  */
91
110
  migrationTableName?: string;
92
111
 
93
112
  /**
94
- * The database cluster size (required)
113
+ * Whether or not data branching is allowed on the database. (Vitess only)
95
114
  */
96
- clusterSize: PlanetScaleClusterSize;
115
+ allowDataBranching?: boolean;
97
116
 
98
117
  /**
99
- * The engine kind for the database
100
- * @default "mysql"
118
+ * Whether or not foreign key constraints are allowed on the database. (Vitess only)
101
119
  */
102
- kind?: "mysql" | "postgresql";
120
+ allowForeignKeyConstraints?: boolean;
121
+ }
122
+
123
+ /**
124
+ * Properties for creating or updating a PlanetScale PostgreSQL database
125
+ */
126
+ interface PostgreSQLDatabaseProps extends BaseDatabaseProps {
127
+ kind: "postgresql";
103
128
 
104
129
  /**
105
- * The CPU architecture for the database. Only available for PostgreSQL databases.
130
+ * The PostgreSQL major version to use for the database. Defaults to the latest available major version. (PostgreSQL only)
131
+ */
132
+ majorVersion?: string;
133
+
134
+ /**
135
+ * The CPU architecture for the database (PostgreSQL only)
106
136
  */
107
137
  arch?: "x86" | "arm";
108
138
  }
@@ -110,17 +140,7 @@ interface BaseDatabaseProps extends PlanetScaleProps {
110
140
  /**
111
141
  * Properties for creating or updating a PlanetScale Database
112
142
  */
113
- export type DatabaseProps = BaseDatabaseProps &
114
- (
115
- | {
116
- kind?: "mysql";
117
- arch?: undefined;
118
- }
119
- | {
120
- kind: "postgresql";
121
- arch?: "x86" | "arm";
122
- }
123
- );
143
+ export type DatabaseProps = MySQLDatabaseProps | PostgreSQLDatabaseProps;
124
144
 
125
145
  /**
126
146
  * Represents a PlanetScale Database
@@ -220,7 +240,11 @@ export const Database = Resource(
220
240
  const clusterSize = sanitizeClusterSize({
221
241
  size: props.clusterSize,
222
242
  kind: props.kind,
223
- arch: props.arch,
243
+ ...(props.kind === "postgresql"
244
+ ? {
245
+ arch: props.arch,
246
+ }
247
+ : {}),
224
248
  region: props.region?.slug,
225
249
  });
226
250
  const organization =
@@ -234,13 +258,14 @@ export const Database = Resource(
234
258
  "PlanetScale organization is required. Please set the `organization` property or the `PLANETSCALE_ORGANIZATION` environment variable.",
235
259
  );
236
260
  }
261
+ const adopt = props.adopt ?? this.scope.adopt;
237
262
  const shouldDelete = props.delete ?? false;
238
263
 
239
264
  if (this.phase === "update" && this.output.name !== databaseName) {
240
265
  await api.updateDatabaseSettings({
241
266
  path: {
242
267
  organization,
243
- name: this.output.name,
268
+ database: this.output.name,
244
269
  },
245
270
  body: { new_name: databaseName },
246
271
  });
@@ -251,7 +276,7 @@ export const Database = Resource(
251
276
  const response = await api.deleteDatabase({
252
277
  path: {
253
278
  organization,
254
- name: this.output.name,
279
+ database: this.output.name,
255
280
  },
256
281
  throwOnError: false,
257
282
  });
@@ -269,11 +294,11 @@ export const Database = Resource(
269
294
  const getResponse = await api.getDatabase({
270
295
  path: {
271
296
  organization,
272
- name: databaseName,
297
+ database: databaseName,
273
298
  },
274
299
  throwOnError: false,
275
300
  });
276
- if (this.phase === "update" || (props.adopt && getResponse.data)) {
301
+ if (this.phase === "update" || (adopt && getResponse.data)) {
277
302
  if (!getResponse.data) {
278
303
  throw new Error(`Database "${databaseName}" not found`, {
279
304
  cause: getResponse.error,
@@ -286,7 +311,7 @@ export const Database = Resource(
286
311
  path: {
287
312
  organization,
288
313
  database: databaseName,
289
- name: props.defaultBranch,
314
+ branch: props.defaultBranch,
290
315
  },
291
316
  throwOnError: false,
292
317
  });
@@ -311,15 +336,20 @@ export const Database = Resource(
311
336
  const { data } = await api.updateDatabaseSettings({
312
337
  path: {
313
338
  organization,
314
- name: databaseName,
339
+ database: databaseName,
315
340
  },
316
341
  body: {
317
- automatic_migrations: props.automaticMigrations,
318
- migration_framework: props.migrationFramework,
319
- migration_table_name: props.migrationTableName,
342
+ ...(props.kind !== "postgresql"
343
+ ? {
344
+ automatic_migrations: props.automaticMigrations,
345
+ migration_framework: props.migrationFramework,
346
+ migration_table_name: props.migrationTableName,
347
+ allow_foreign_key_constraints: props.allowForeignKeyConstraints,
348
+ allow_data_branching: props.allowDataBranching,
349
+ }
350
+ : {}),
320
351
  require_approval_for_deploy: props.requireApprovalForDeploy,
321
352
  restrict_branch_region: props.restrictBranchRegion,
322
- allow_data_branching: props.allowDataBranching,
323
353
  insights_raw_queries: props.insightsRawQueries,
324
354
  production_branch_web_console: props.productionBranchWebConsole,
325
355
  default_branch: props.defaultBranch,
@@ -363,6 +393,12 @@ export const Database = Resource(
363
393
  region: props.region?.slug,
364
394
  kind: props.kind,
365
395
  cluster_size: clusterSize,
396
+ replicas: props.replicas,
397
+ ...(props.kind === "postgresql"
398
+ ? {
399
+ major_version: props.majorVersion,
400
+ }
401
+ : {}),
366
402
  },
367
403
  });
368
404
 
@@ -370,17 +406,22 @@ export const Database = Resource(
370
406
  const { data } = await api.updateDatabaseSettings({
371
407
  path: {
372
408
  organization,
373
- name: databaseName,
409
+ database: databaseName,
374
410
  },
375
411
  body: {
412
+ ...(props.kind !== "postgresql"
413
+ ? {
414
+ automatic_migrations: props.automaticMigrations,
415
+ migration_framework: props.migrationFramework,
416
+ migration_table_name: props.migrationTableName,
417
+ allow_foreign_key_constraints: props.allowForeignKeyConstraints,
418
+ allow_data_branching: props.allowDataBranching,
419
+ }
420
+ : {}),
376
421
  require_approval_for_deploy: props.requireApprovalForDeploy,
377
- allow_data_branching: props.allowDataBranching,
378
- automatic_migrations: props.automaticMigrations,
379
422
  restrict_branch_region: props.restrictBranchRegion,
380
423
  insights_raw_queries: props.insightsRawQueries,
381
424
  production_branch_web_console: props.productionBranchWebConsole,
382
- migration_framework: props.migrationFramework,
383
- migration_table_name: props.migrationTableName,
384
425
  },
385
426
  });
386
427
 
@@ -393,7 +434,7 @@ export const Database = Resource(
393
434
  path: {
394
435
  organization,
395
436
  database: databaseName,
396
- name: props.defaultBranch,
437
+ branch: props.defaultBranch,
397
438
  },
398
439
  throwOnError: false,
399
440
  });
@@ -424,7 +465,7 @@ export const Database = Resource(
424
465
  const { data: updatedData } = await api.updateDatabaseSettings({
425
466
  path: {
426
467
  organization,
427
- name: databaseName,
468
+ database: databaseName,
428
469
  },
429
470
  body: {
430
471
  default_branch: props.defaultBranch,
@@ -0,0 +1,162 @@
1
+ import { alchemy } from "../alchemy.ts";
2
+ import type { Context } from "../context.ts";
3
+ import { Resource } from "../resource.ts";
4
+ import { createPlanetScaleClient, type PlanetScaleProps } from "./api.ts";
5
+ import type { Branch } from "./branch.ts";
6
+ import type { Database } from "./database.ts";
7
+ import type { Role } from "./role.ts";
8
+ import { waitForBranchReady } from "./utils.ts";
9
+
10
+ export interface DefaultRoleProps extends PlanetScaleProps {
11
+ /**
12
+ * The organization where the role will be created
13
+ * Required when using string database name, optional when using Database resource
14
+ * @default process.env.PLANETSCALE_ORGANIZATION
15
+ */
16
+ organization?: string;
17
+
18
+ /**
19
+ * The database where the role will be created
20
+ * Can be either a database name (string) or Database resource
21
+ */
22
+ database: string | Database;
23
+
24
+ /**
25
+ * The branch where the role will be created
26
+ * Can be either a branch name (string) or Branch resource
27
+ * @default "main"
28
+ */
29
+ branch?: string | Branch;
30
+
31
+ /**
32
+ * Whether to force reset the default role if it already exists.
33
+ * This will delete the existing role and create a new one.
34
+ *
35
+ * Note: Adopting an existing role is not supported because the password is only returned after create.
36
+ *
37
+ * @default false
38
+ */
39
+ forceReset?: boolean;
40
+ }
41
+
42
+ export type DefaultRole = Omit<Role, "successor">;
43
+
44
+ /**
45
+ * Create and manage the default role for a PlanetScale PostgreSQL database branch.
46
+ *
47
+ * If a default role already exists for this database and branch combination, you may need to use the `forceReset` property to reset the role.
48
+ *
49
+ * For MySQL, use [Passwords](./password.ts) instead.
50
+ *
51
+ * @example
52
+ * const database = await Database("my-database", {
53
+ * kind: "postgresql",
54
+ * });
55
+ * const defaultRole = await DefaultRole("my-default-role", {
56
+ * database,
57
+ * });
58
+ */
59
+ export const DefaultRole = Resource(
60
+ "planetscale::DefaultRole",
61
+ async function (
62
+ this: Context<DefaultRole, DefaultRoleProps>,
63
+ _id: string,
64
+ props: DefaultRoleProps,
65
+ ): Promise<DefaultRole> {
66
+ const api = createPlanetScaleClient(props);
67
+ const organization =
68
+ props.organization ??
69
+ (typeof props.database !== "string"
70
+ ? props.database.organization
71
+ : (process.env.PLANETSCALE_ORGANIZATION ??
72
+ process.env.PLANETSCALE_ORG_ID));
73
+ if (!organization) {
74
+ throw new Error(
75
+ "PlanetScale organization is required. Please set the `organization` property or the `PLANETSCALE_ORGANIZATION` environment variable.",
76
+ );
77
+ }
78
+
79
+ const database =
80
+ typeof props.database === "string" ? props.database : props.database.name;
81
+ const branch =
82
+ typeof props.branch === "string"
83
+ ? props.branch
84
+ : (props.branch?.name ?? "main");
85
+
86
+ switch (this.phase) {
87
+ case "create": {
88
+ if (!props.forceReset) {
89
+ const existing = await api.getDefaultRole({
90
+ path: {
91
+ organization,
92
+ database,
93
+ branch,
94
+ },
95
+ throwOnError: false,
96
+ });
97
+ if (existing.data) {
98
+ throw new Error(
99
+ `Default role already exists for database "${database}" branch "${branch}". Use forceReset to reset the role.`,
100
+ );
101
+ } else if (existing.error && existing.response.status !== 404) {
102
+ throw new Error(
103
+ `Failed to check for default role in database "${database}" branch "${branch}".`,
104
+ {
105
+ cause: existing.error,
106
+ },
107
+ );
108
+ }
109
+ }
110
+ await waitForBranchReady(api, organization, database, branch);
111
+ const { data } = await api.resetDefaultRole({
112
+ path: {
113
+ organization,
114
+ database,
115
+ branch,
116
+ },
117
+ });
118
+ return {
119
+ id: data.id,
120
+ name: data.name,
121
+ expiresAt: data.expires_at,
122
+ host: data.access_host_url,
123
+ username: data.username,
124
+ ttl: data.ttl,
125
+ password: alchemy.secret(data.password),
126
+ databaseName: data.database_name,
127
+ connectionUrl: alchemy.secret(
128
+ `postgresql://${data.username}:${data.password}@${data.access_host_url}:5432/${data.database_name}?sslmode=verify-full`,
129
+ ),
130
+ connectionUrlPooled: alchemy.secret(
131
+ `postgresql://${data.username}:${data.password}@${data.access_host_url}:6432/${data.database_name}?sslmode=verify-full`,
132
+ ),
133
+ inheritedRoles: data.inherited_roles,
134
+ database,
135
+ branch,
136
+ organization,
137
+ };
138
+ }
139
+ case "update": {
140
+ if (
141
+ database !== this.output.database ||
142
+ branch !== this.output.branch
143
+ ) {
144
+ return this.replace();
145
+ }
146
+ return this.output;
147
+ }
148
+ case "delete": {
149
+ // reset default role - even though we don't need new credentials,
150
+ // it's best to invalidate existing ones, and there's no delete endpoint
151
+ await api.resetDefaultRole({
152
+ path: {
153
+ organization,
154
+ database,
155
+ branch,
156
+ },
157
+ });
158
+ return this.destroy();
159
+ }
160
+ }
161
+ },
162
+ );
@@ -1,6 +1,7 @@
1
1
  export * from "./api.ts";
2
2
  export * from "./branch.ts";
3
3
  export * from "./database.ts";
4
+ export * from "./default-role.ts";
4
5
  export * from "./organization.ts";
5
6
  export * from "./password.ts";
6
7
  export * from "./role.ts";
@@ -10,7 +10,7 @@ export async function OrganizationRef(
10
10
  const api = createPlanetScaleClient(options);
11
11
  const organization = await api.getOrganization({
12
12
  path: {
13
- name: typeof name === "string" ? name : name.id,
13
+ organization: typeof name === "string" ? name : name.id,
14
14
  },
15
15
  });
16
16
  return organization.data;
@@ -63,7 +63,7 @@ export interface RoleProps extends PlanetScaleProps {
63
63
  * Roles that can be inherited from.
64
64
  */
65
65
  export type InheritedRole =
66
- | "postgres"
66
+ | "pscale_managed"
67
67
  | "pg_checkpoint"
68
68
  | "pg_create_subscription"
69
69
  | "pg_maintain"
@@ -75,6 +75,7 @@ export type InheritedRole =
75
75
  | "pg_stat_scan_tables"
76
76
  | "pg_use_reserved_connections"
77
77
  | "pg_write_all_data"
78
+ | "postgres"
78
79
  | (string & {});
79
80
 
80
81
  export interface Role extends Omit<RoleProps, "inheritedRoles"> {
@@ -261,7 +262,7 @@ export const Role = Resource(
261
262
  path: {
262
263
  organization,
263
264
  database,
264
- name: branch,
265
+ branch,
265
266
  },
266
267
  });
267
268
  if (kind !== "postgresql") {
@@ -4,6 +4,7 @@ import type { DatabaseBranch } from "./api/types.gen.ts";
4
4
 
5
5
  export type PlanetScaleClusterSize =
6
6
  | "PS_DEV"
7
+ | "PS_5"
7
8
  | "PS_10"
8
9
  | "PS_20"
9
10
  | "PS_40"
@@ -59,7 +60,7 @@ export async function waitForBranchReady(
59
60
  description: `branch "${branch}" ready`,
60
61
  fn: () =>
61
62
  api.getBranch({
62
- path: { organization, database, name: branch },
63
+ path: { organization, database, branch },
63
64
  }),
64
65
  predicate: ({ data }) => data.ready,
65
66
  });
@@ -78,7 +79,7 @@ export async function waitForDatabaseReady(
78
79
  description: `database "${database}" ready`,
79
80
  fn: () =>
80
81
  api.getDatabase({
81
- path: { organization, name: database },
82
+ path: { organization, database },
82
83
  }),
83
84
  predicate: ({ data }) => data.ready,
84
85
  });
@@ -134,7 +135,7 @@ export async function ensureProductionBranchClusterSize(
134
135
  break;
135
136
  }
136
137
  case "postgresql": {
137
- // Postgres databases must be resized first before promoting, otherwise 500 error
138
+ // Postgres databases do not need to be promoted; PS_DEV is development and all others are production
138
139
  await ensurePostgresClusterSize(
139
140
  api,
140
141
  organization,
@@ -142,7 +143,6 @@ export async function ensureProductionBranchClusterSize(
142
143
  branch,
143
144
  expectedClusterSize,
144
145
  );
145
- await ensureProductionBranch(api, organization, database, branch);
146
146
  break;
147
147
  }
148
148
  }
@@ -155,21 +155,21 @@ async function ensureProductionBranch(
155
155
  api: PlanetScaleClient,
156
156
  organization: string,
157
157
  database: string,
158
- name: string,
158
+ branch: string,
159
159
  ): Promise<void> {
160
160
  const { data } = await api.getBranch({
161
161
  path: {
162
162
  organization,
163
163
  database,
164
- name,
164
+ branch,
165
165
  },
166
166
  });
167
167
  if (!data.production) {
168
168
  if (!data.ready) {
169
- await waitForBranchReady(api, organization, database, name);
169
+ await waitForBranchReady(api, organization, database, branch);
170
170
  }
171
171
  await api.promoteBranch({
172
- path: { organization, database, name },
172
+ path: { organization, database, branch },
173
173
  });
174
174
  }
175
175
  }
@@ -213,7 +213,7 @@ async function ensureMySQLClusterSize(
213
213
  path: {
214
214
  organization,
215
215
  database,
216
- name: branch,
216
+ branch,
217
217
  },
218
218
  body: { cluster_size: expectedClusterSize },
219
219
  });
@@ -243,7 +243,7 @@ async function ensurePostgresClusterSize(
243
243
  path: {
244
244
  organization,
245
245
  database,
246
- name: branch,
246
+ branch,
247
247
  },
248
248
  });
249
249
  if (data.cluster_name === expectedClusterSize) {
package/src/scope.ts CHANGED
@@ -342,12 +342,21 @@ export class Scope {
342
342
  return state !== undefined && (type === undefined || state.kind === type);
343
343
  }
344
344
 
345
- public createPhysicalName(id: string, delimiter = "-"): string {
346
- const app = this.appName;
347
- const stage = this.stage;
348
- return [app, ...this.chain.slice(2), id, stage]
349
- .map((s) => s.replaceAll(/[^a-z0-9_-]/gi, delimiter))
345
+ public createPhysicalName(
346
+ id: string,
347
+ delimiter = "-",
348
+ maxLength?: number,
349
+ ): string {
350
+ let name = [this.appName, ...this.chain.slice(2), id, this.stage]
351
+ .map((part) => part.replaceAll(/[^a-z0-9_-]/gi, delimiter))
350
352
  .join(delimiter);
353
+ while (maxLength && name.length > maxLength) {
354
+ name = name
355
+ .split(delimiter)
356
+ .map((part) => part.slice(0, -1))
357
+ .join(delimiter);
358
+ }
359
+ return name;
351
360
  }
352
361
 
353
362
  public async spawn<
@@ -162,6 +162,7 @@ async function createBunSQLiteDatabase(
162
162
  create: true,
163
163
  ...bunOptions,
164
164
  });
165
+ client.exec("PRAGMA busy_timeout = 5000;");
165
166
  client.exec("PRAGMA journal_mode = WAL;");
166
167
  const db = drizzle(client, {
167
168
  schema,
@@ -192,6 +193,7 @@ async function createLibSQLDatabase(
192
193
  const { migrate } = await import("drizzle-orm/libsql/migrator");
193
194
  const schema = await import("./schema.js");
194
195
  const client = createClient({ url, ...options });
196
+ await client.execute("PRAGMA busy_timeout = 5000;");
195
197
  await client.execute("PRAGMA journal_mode = WAL;");
196
198
  const db = drizzle(client, {
197
199
  schema,
package/src/test/bun.ts CHANGED
@@ -51,6 +51,29 @@ type test = {
51
51
  * @param condition If true, test will be skipped
52
52
  */
53
53
  skipIf(condition: boolean): test;
54
+ skip: {
55
+ /**
56
+ * Create a test with default options
57
+ * @param name Test name
58
+ * @param fn Test function
59
+ * @param timeout Optional timeout in milliseconds
60
+ */
61
+ (name: string, fn: (scope: Scope) => Promise<any>, timeout?: number): void;
62
+
63
+ /**
64
+ * Create a test with custom options
65
+ * @param name Test name
66
+ * @param options Test configuration options
67
+ * @param fn Test function
68
+ * @param timeout Optional timeout in milliseconds
69
+ */
70
+ (
71
+ name: string,
72
+ options: TestOptions,
73
+ fn: (scope: Scope) => Promise<any>,
74
+ timeout?: number,
75
+ ): void;
76
+ };
54
77
 
55
78
  beforeAll(fn: (scope: Scope) => Promise<void>, timeout?: number): void;
56
79
 
@@ -95,6 +118,8 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
95
118
  // Add skipIf functionality
96
119
  test.skipIf = it.skipIf.bind(it);
97
120
 
121
+ test.skip = it.skip.bind(it);
122
+
98
123
  // Create local test scope based on filename
99
124
  const scope = new Scope({
100
125
  parent: undefined,