alchemy 2.0.0-beta.53 → 2.0.0-beta.54

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 (74) hide show
  1. package/bin/exec.js +1 -1
  2. package/bin/exec.js.map +1 -1
  3. package/lib/Cloudflare/Hyperdrive/Hyperdrive.d.ts.map +1 -1
  4. package/lib/Cloudflare/Hyperdrive/Hyperdrive.js +6 -6
  5. package/lib/Cloudflare/Hyperdrive/Hyperdrive.js.map +1 -1
  6. package/lib/Cloudflare/Local.js +5 -2
  7. package/lib/Cloudflare/Local.js.map +1 -1
  8. package/lib/Cloudflare/LocalRuntime.d.ts +21 -0
  9. package/lib/Cloudflare/LocalRuntime.d.ts.map +1 -0
  10. package/lib/Cloudflare/LocalRuntime.js +40 -0
  11. package/lib/Cloudflare/LocalRuntime.js.map +1 -0
  12. package/lib/Cloudflare/Providers.d.ts +1 -1
  13. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  14. package/lib/Cloudflare/Providers.js +2 -1
  15. package/lib/Cloudflare/Providers.js.map +1 -1
  16. package/lib/Cloudflare/Queue/Queue.d.ts +4 -1
  17. package/lib/Cloudflare/Queue/Queue.d.ts.map +1 -1
  18. package/lib/Cloudflare/Queue/Queue.js +61 -2
  19. package/lib/Cloudflare/Queue/Queue.js.map +1 -1
  20. package/lib/Cloudflare/Queue/QueueConsumer.d.ts +32 -26
  21. package/lib/Cloudflare/Queue/QueueConsumer.d.ts.map +1 -1
  22. package/lib/Cloudflare/Queue/QueueConsumer.js +73 -3
  23. package/lib/Cloudflare/Queue/QueueConsumer.js.map +1 -1
  24. package/lib/Cloudflare/Workers/Assets.js +2 -2
  25. package/lib/Cloudflare/Workers/Assets.js.map +1 -1
  26. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +3 -5
  27. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  28. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +32 -25
  29. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  30. package/lib/Cloudflare/Workers/Worker.d.ts +2 -4
  31. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  32. package/lib/Cloudflare/Workers/Worker.js +5 -7
  33. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  34. package/lib/Local/ProviderLayer.d.ts +10 -0
  35. package/lib/Local/ProviderLayer.d.ts.map +1 -0
  36. package/lib/Local/ProviderLayer.js +7 -0
  37. package/lib/Local/ProviderLayer.js.map +1 -0
  38. package/lib/Local/RpcProvider.d.ts.map +1 -1
  39. package/lib/Local/RpcProvider.js +2 -1
  40. package/lib/Local/RpcProvider.js.map +1 -1
  41. package/lib/Local/RpcProviderProxy.js.map +1 -1
  42. package/lib/Local/RpcSerialization.js +2 -2
  43. package/lib/Local/RpcSerialization.js.map +1 -1
  44. package/lib/Planetscale/Branch.d.ts.map +1 -1
  45. package/lib/Planetscale/Branch.js +25 -3
  46. package/lib/Planetscale/Branch.js.map +1 -1
  47. package/lib/Planetscale/Database.d.ts +1 -1
  48. package/lib/Planetscale/Postgres/PostgresClusterSize.d.ts +1 -1
  49. package/lib/Planetscale/Postgres/PostgresClusterSize.d.ts.map +1 -1
  50. package/lib/Planetscale/Postgres/PostgresClusterSize.js +13 -3
  51. package/lib/Planetscale/Postgres/PostgresClusterSize.js.map +1 -1
  52. package/lib/Planetscale/Util.d.ts +1 -1
  53. package/lib/Planetscale/Util.d.ts.map +1 -1
  54. package/lib/Planetscale/Util.js +7 -5
  55. package/lib/Planetscale/Util.js.map +1 -1
  56. package/lib/tsconfig.test.tsbuildinfo +1 -1
  57. package/package.json +6 -6
  58. package/src/Cloudflare/Hyperdrive/Hyperdrive.ts +6 -8
  59. package/src/Cloudflare/Local.ts +9 -5
  60. package/src/Cloudflare/LocalRuntime.ts +70 -0
  61. package/src/Cloudflare/Providers.ts +2 -0
  62. package/src/Cloudflare/Queue/Queue.ts +73 -2
  63. package/src/Cloudflare/Queue/QueueConsumer.ts +130 -28
  64. package/src/Cloudflare/Workers/Assets.ts +2 -2
  65. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +39 -30
  66. package/src/Cloudflare/Workers/Worker.ts +5 -26
  67. package/src/Local/ProviderLayer.ts +22 -0
  68. package/src/Local/RpcProvider.ts +2 -0
  69. package/src/Local/RpcProviderProxy.ts +2 -2
  70. package/src/Local/RpcSerialization.ts +2 -2
  71. package/src/Planetscale/Branch.ts +38 -10
  72. package/src/Planetscale/Database.ts +1 -1
  73. package/src/Planetscale/Postgres/PostgresClusterSize.ts +16 -2
  74. package/src/Planetscale/Util.ts +7 -5
@@ -1,5 +1,4 @@
1
1
  import {
2
- layerRuntime,
3
2
  Runtime,
4
3
  RuntimeError,
5
4
  type BindingHook,
@@ -8,6 +7,7 @@ import {
8
7
  type Module,
9
8
  type Assets as RuntimeAssets,
10
9
  type DurableObjectNamespace as RuntimeDurableObjectNamespace,
10
+ type QueueConsumer as RuntimeQueueConsumer,
11
11
  type RuntimeServices,
12
12
  } from "@distilled.cloud/cloudflare-runtime";
13
13
  import {
@@ -26,6 +26,7 @@ import {
26
26
  KvNamespace,
27
27
  MtlsCertificate,
28
28
  Pipelines,
29
+ Queue,
29
30
  R2Bucket,
30
31
  RateLimit,
31
32
  SendEmail,
@@ -44,19 +45,21 @@ import * as Equal from "effect/Equal";
44
45
  import * as Exit from "effect/Exit";
45
46
  import * as Fiber from "effect/Fiber";
46
47
  import * as Hash from "effect/Hash";
48
+ import * as MutableHashMap from "effect/MutableHashMap";
49
+ import * as Option from "effect/Option";
47
50
  import * as Path from "effect/Path";
48
51
  import * as Redacted from "effect/Redacted";
49
52
  import * as Result from "effect/Result";
50
53
  import * as Schema from "effect/Schema";
51
54
  import * as Scope from "effect/Scope";
52
55
  import * as Stream from "effect/Stream";
53
- import { AlchemyContext } from "../../AlchemyContext.ts";
54
56
  import type * as Bundle from "../../Bundle/Bundle.ts";
55
57
  import { isResolved } from "../../Diff.ts";
56
58
  import * as RpcProvider from "../../Local/RpcProvider.ts";
57
59
  import type { ResourceBinding } from "../../Resource.ts";
58
60
  import { Stack } from "../../Stack.ts";
59
61
  import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
62
+ import { LOCAL_ENTRY_URL, LocalRuntimeState } from "../LocalRuntime.ts";
60
63
  import type { WorkerAssetsConfig, WorkerProps } from "../Workers/Worker.ts";
61
64
  import { getCompatibility } from "./Compatibility.ts";
62
65
  import * as Vite from "./Vite.ts";
@@ -79,41 +82,16 @@ export class WorkerValidationError extends Schema.TaggedErrorClass<WorkerValidat
79
82
  },
80
83
  ) {}
81
84
 
82
- export const localRuntimeServices = () =>
83
- RpcProvider.providerServicesEffect(
84
- Effect.gen(function* () {
85
- const { accountId } = yield* yield* CloudflareEnvironment;
86
- const { dotAlchemy } = yield* AlchemyContext;
87
- const path = yield* Path.Path;
88
- return layerRuntime({
89
- api: {
90
- accountId,
91
- },
92
- storage: {
93
- directory: path.join(dotAlchemy, "local"),
94
- },
95
- });
96
- }),
97
- );
98
-
99
85
  export const LocalWorkerProvider = () =>
100
86
  RpcProvider.effect(
101
87
  Worker,
102
- import.meta.resolve(
103
- // `import.meta.resolve(<string>)` is a runtime API — TypeScript's
104
- // `rewriteRelativeImportExtensions` does NOT touch the string literal, so
105
- // we have to pick the right extension ourselves. `import.meta.url` reflects
106
- // the actual on-disk extension of *this* file (`.ts` when loaded from
107
- // `src/` under Bun or vitest, `.js` when loaded from the compiled `lib/`
108
- // under Node), which is exactly the signal we need.
109
- import.meta.url.endsWith(".ts") ? "../Local.ts" : "../Local.js",
110
- import.meta.url,
111
- ),
88
+ LOCAL_ENTRY_URL,
112
89
  Effect.gen(function* () {
113
90
  const bundler = yield* WorkerBundle;
114
91
  const runtime = yield* Runtime;
115
92
  const stack = yield* Stack;
116
93
  const path = yield* Path.Path;
94
+ const localRuntimeState = yield* LocalRuntimeState;
117
95
  const workerProxy = yield* WorkerProxy.WorkerProxy;
118
96
  const proxyInstances = new Map<
119
97
  string,
@@ -124,6 +102,32 @@ export const LocalWorkerProvider = () =>
124
102
  }
125
103
  >();
126
104
 
105
+ const getQueueConsumers = Effect.fnUntraced(function* (
106
+ scriptName: string,
107
+ ) {
108
+ const consumers: RuntimeQueueConsumer[] = [];
109
+ for (const consumer of MutableHashMap.values(
110
+ localRuntimeState.queueConsumers,
111
+ )) {
112
+ if (consumer.scriptName === scriptName) {
113
+ const queue = MutableHashMap.get(
114
+ localRuntimeState.queues,
115
+ consumer.queueId,
116
+ ).pipe(Option.getOrUndefined);
117
+ if (queue) {
118
+ consumers.push({
119
+ queueName: queue.queueName,
120
+ deadLetterQueue: consumer.deadLetterQueue,
121
+ ...consumer.settings,
122
+ });
123
+ } else {
124
+ return yield* Effect.die(`Queue ${consumer.queueId} not found`);
125
+ }
126
+ }
127
+ }
128
+ return consumers;
129
+ });
130
+
127
131
  const startProxy = Effect.fn(function* (
128
132
  id: string,
129
133
  serverOptions: WorkerConfig["dev"],
@@ -211,6 +215,7 @@ export const LocalWorkerProvider = () =>
211
215
  durableObjectNamespaces: toRuntimeDurableObjectNamespaces(
212
216
  worker.durableObjectNamespaces,
213
217
  ),
218
+ queueConsumers: yield* getQueueConsumers(worker.name),
214
219
  modules: yield* toRuntimeModules(bundle),
215
220
  assets: toRuntimeAssets(worker.assets),
216
221
  })
@@ -392,6 +397,7 @@ export const LocalWorkerProvider = () =>
392
397
  worker.durableObjectNamespaces,
393
398
  ),
394
399
  hyperdrives: worker.hyperdrives,
400
+ queueConsumers: yield* getQueueConsumers(worker.name),
395
401
  assets: toRuntimeAssets(worker.assets),
396
402
  },
397
403
  context,
@@ -586,7 +592,10 @@ const toRuntimeBinding = Effect.fnUntraced(function* (b: WorkerBinding) {
586
592
  case "plain_text":
587
593
  return Text.local(b.name, b.text);
588
594
  case "queue":
589
- return yield* unsupported();
595
+ return Queue.local({
596
+ binding: b.name,
597
+ queueName: b.queueName,
598
+ });
590
599
  case "r2_bucket":
591
600
  return R2Bucket.remote(b.name, b.bucketName, b.jurisdiction);
592
601
  case "ratelimit":
@@ -6,20 +6,19 @@ import type { ConfigError } from "effect/Config";
6
6
  import * as Context from "effect/Context";
7
7
  import * as Data from "effect/Data";
8
8
  import * as Effect from "effect/Effect";
9
- import * as Layer from "effect/Layer";
10
9
  import * as Path from "effect/Path";
11
10
  import * as Predicate from "effect/Predicate";
12
11
  import * as Redacted from "effect/Redacted";
13
12
  import * as Schedule from "effect/Schedule";
14
13
  import * as crypto from "node:crypto";
15
14
  import { Unowned } from "../../AdoptPolicy.ts";
16
- import { AlchemyContext } from "../../AlchemyContext.ts";
17
15
  import * as Artifacts from "../../Artifacts.ts";
18
16
  import { hashDirectory, type MemoOptions } from "../../Build/Memo.ts";
19
17
  import * as Bundle from "../../Bundle/Bundle.ts";
20
18
  import type { ScopedPlanStatusSession } from "../../Cli/Cli.ts";
21
19
  import { isResolved } from "../../Diff.ts";
22
20
  import type { InputProps } from "../../Input.ts";
21
+ import * as ProviderLayer from "../../Local/ProviderLayer.ts";
23
22
  import { Platform, type Main, type PlatformProps } from "../../Platform.ts";
24
23
  import * as Provider from "../../Provider.ts";
25
24
  import { Resource, type ResourceBinding } from "../../Resource.ts";
@@ -36,10 +35,7 @@ import {
36
35
  } from "./Assets.ts";
37
36
  import { getCompatibility } from "./Compatibility.ts";
38
37
  import { isDurableObjectExport } from "./DurableObjectNamespace.ts";
39
- import {
40
- LocalWorkerProvider,
41
- localRuntimeServices,
42
- } from "./LocalWorkerProvider.ts";
38
+ import { LocalWorkerProvider } from "./LocalWorkerProvider.ts";
43
39
  import { Request } from "./Request.ts";
44
40
  import * as Vite from "./Vite.ts";
45
41
  import {
@@ -743,27 +739,10 @@ class MissingDurableObjectNamespaces extends Data.TaggedError(
743
739
  expected: string[];
744
740
  }> {}
745
741
 
746
- const selectLayer = <
747
- LayerLive extends Layer.Layer<any, any, any>,
748
- LayerDev extends Layer.Layer<any, any, any>,
749
- >(input: {
750
- live: () => LayerLive;
751
- dev: () => LayerDev;
752
- }): Layer.Layer<
753
- Layer.Success<LayerLive | LayerDev>,
754
- Layer.Error<LayerLive | LayerDev>,
755
- Layer.Services<LayerLive | LayerDev> | AlchemyContext
756
- > =>
757
- Layer.unwrap(
758
- AlchemyContext.useSync((context) =>
759
- context.dev ? input.dev() : input.live(),
760
- ),
761
- );
762
-
763
742
  export const WorkerProvider = () =>
764
- selectLayer({
765
- live: LiveWorkerProvider,
766
- dev: () => Layer.provide(LocalWorkerProvider(), localRuntimeServices()),
743
+ ProviderLayer.select({
744
+ live: () => LiveWorkerProvider(),
745
+ local: () => LocalWorkerProvider(),
767
746
  });
768
747
 
769
748
  export const LiveWorkerProvider = () =>
@@ -0,0 +1,22 @@
1
+ import * as Layer from "effect/Layer";
2
+ import { AlchemyContext } from "../AlchemyContext.ts";
3
+
4
+ /**
5
+ * Returns a layer that selects the live or local provider based on the `AlchemyContext.dev` flag.
6
+ */
7
+ export const select = <
8
+ LayerLive extends Layer.Layer<any, any, any>,
9
+ LayerLocal extends Layer.Layer<any, any, any>,
10
+ >(input: {
11
+ live: () => LayerLive;
12
+ local: () => LayerLocal;
13
+ }): Layer.Layer<
14
+ Layer.Success<LayerLive | LayerLocal>,
15
+ Layer.Error<LayerLive | LayerLocal>,
16
+ Layer.Services<LayerLive | LayerLocal> | AlchemyContext
17
+ > =>
18
+ Layer.unwrap(
19
+ AlchemyContext.useSync((context) =>
20
+ context.dev ? input.local() : input.live(),
21
+ ),
22
+ );
@@ -76,6 +76,7 @@ export const effect = <
76
76
  cls,
77
77
  Effect.gen(function* () {
78
78
  const client = yield* Effect.serviceOption(RpcProviderProxy);
79
+ const context = yield* Effect.context();
79
80
  const stack = yield* Stack;
80
81
 
81
82
  if (client._tag === "None") {
@@ -87,6 +88,7 @@ export const effect = <
87
88
  return (...args: any[]) => {
88
89
  const result = value(...args);
89
90
  const services = Layer.mergeAll(
91
+ Layer.succeedContext(context),
90
92
  layerFallback(Stack, stack),
91
93
  layerFallback(Stage, stack.stage),
92
94
  Predicate.hasProperty(args[0], "instanceId") &&
@@ -1,4 +1,4 @@
1
- import { newWebSocketRpcSession, type RpcStub } from "capnweb";
1
+ import { newWebSocketRpcSession } from "capnweb";
2
2
  import * as Cache from "effect/Cache";
3
3
  import * as Config from "effect/Config";
4
4
  import * as Context from "effect/Context";
@@ -42,7 +42,7 @@ const make = Effect.fnUntraced(function* (spawnerUrl: string) {
42
42
  body: yield* HttpBody.json(payload),
43
43
  });
44
44
  const websocketUrl = yield* response.text;
45
- return newWebSocketRpcSession(websocketUrl) as RpcStub<RpcProxyApi>;
45
+ return newWebSocketRpcSession<RpcProxyApi>(websocketUrl);
46
46
  },
47
47
  (effect, serverEntryUrl) =>
48
48
  Effect.catch(effect, (error) =>
@@ -64,7 +64,7 @@ export const wrapRpcHandlers = <T extends Record<string, any>>(
64
64
  ? streamKeys?.includes(key)
65
65
  ? wrapRpcStreamHandler(value)
66
66
  : wrapRpcEffectHandler(value)
67
- : typeof value === "object" && value !== null
67
+ : typeof value === "object" && value !== null && !Array.isArray(value)
68
68
  ? wrapRpcHandlers(value)
69
69
  : value,
70
70
  ]),
@@ -82,7 +82,7 @@ export const unwrapRpcHandlers = <T extends Record<string, any>>(
82
82
  ? streamKeys?.includes(key)
83
83
  ? unwrapRpcStreamHandler(value)
84
84
  : unwrapRpcEffectHandler(value)
85
- : typeof value === "object" && value !== null
85
+ : typeof value === "object" && value !== null && !Array.isArray(value)
86
86
  ? unwrapRpcHandlers(value)
87
87
  : value,
88
88
  ]),
@@ -1,6 +1,7 @@
1
1
  import * as planetscale from "@distilled.cloud/planetscale";
2
2
  import { Credentials } from "@distilled.cloud/planetscale/Credentials";
3
3
  import * as Effect from "effect/Effect";
4
+ import * as Schedule from "effect/Schedule";
4
5
  import { Unowned } from "../AdoptPolicy.ts";
5
6
  import { isResolved } from "../Diff.ts";
6
7
  import { createPhysicalName } from "../PhysicalName.ts";
@@ -157,6 +158,20 @@ export interface BranchMigrationRunners {
157
158
 
158
159
  const rootDir = Effect.sync(() => process.cwd());
159
160
 
161
+ /**
162
+ * Matches the UnprocessableEntity errors PlanetScale returns when a
163
+ * branch promotion/demotion races an in-flight cluster resize, e.g.
164
+ * "Branches with a cluster resize in progress cannot be demoted."
165
+ */
166
+ const isResizeInProgress = (error: unknown): boolean =>
167
+ typeof error === "object" &&
168
+ error !== null &&
169
+ (error as { readonly _tag?: unknown })._tag === "UnprocessableEntity" &&
170
+ typeof (error as { readonly message?: unknown }).message === "string" &&
171
+ (error as { readonly message: string }).message.includes(
172
+ "cluster resize in progress",
173
+ );
174
+
160
175
  /**
161
176
  * Build a branch provider for a specific PlanetScale engine. The
162
177
  * caller supplies the typed `Resource` token and the engine's migration
@@ -369,17 +384,30 @@ export const makeBranchProvider = <R extends ResourceLike>(opts: {
369
384
  if (opts.expectedKind === "mysql") {
370
385
  const desiredProduction = news.isProduction ?? false;
371
386
  if (current.production !== desiredProduction) {
387
+ // A keyspace can report `resizing: false` while the resize
388
+ // workflow is still finalizing, during which promote/demote is
389
+ // rejected with an UnprocessableEntity. Retry until it clears.
390
+ const retryWhileResizing = Effect.retry({
391
+ while: isResizeInProgress,
392
+ schedule: Schedule.spaced("5 seconds").pipe(
393
+ Schedule.both(Schedule.recurs(120)),
394
+ ),
395
+ });
372
396
  current = desiredProduction
373
- ? yield* planetscale.promoteBranch({
374
- organization,
375
- database: databaseName,
376
- branch: branchName,
377
- })
378
- : yield* planetscale.demoteBranch({
379
- organization,
380
- database: databaseName,
381
- branch: branchName,
382
- });
397
+ ? yield* planetscale
398
+ .promoteBranch({
399
+ organization,
400
+ database: databaseName,
401
+ branch: branchName,
402
+ })
403
+ .pipe(retryWhileResizing)
404
+ : yield* planetscale
405
+ .demoteBranch({
406
+ organization,
407
+ database: databaseName,
408
+ branch: branchName,
409
+ })
410
+ .pipe(retryWhileResizing);
383
411
  }
384
412
  }
385
413
 
@@ -65,7 +65,7 @@ export interface BaseDatabaseProps {
65
65
 
66
66
  /**
67
67
  * Name of the table used to track applied migrations.
68
- * @default "planetscale_migrations"
68
+ * @default "__alchemy_migrations"
69
69
  */
70
70
  migrationsTable?: string;
71
71
 
@@ -1,6 +1,7 @@
1
1
  import * as planetscale from "@distilled.cloud/planetscale";
2
2
  import * as Effect from "effect/Effect";
3
- import { pollUntil } from "../Util.ts";
3
+ import * as Schedule from "effect/Schedule";
4
+ import { pollUntil, waitForBranchReady } from "../Util.ts";
4
5
 
5
6
  /**
6
7
  * Available PlanetScale PostgreSQL cluster sizes.
@@ -54,6 +55,15 @@ export function toPostgresClusterSku(input: {
54
55
  return `${size}_${provider}_${arch}`;
55
56
  }
56
57
 
58
+ /**
59
+ * Schedule for polling branch change requests. Postgres cluster resizes
60
+ * routinely take longer than the default 10-minute polling budget, so
61
+ * give change requests a 60-minute budget (720 × 5s).
62
+ */
63
+ const changeRequestSchedule = Schedule.spaced("5 seconds").pipe(
64
+ Schedule.both(Schedule.recurs(720)),
65
+ );
66
+
57
67
  /**
58
68
  * Polls branch change requests until all visible changes are in a terminal
59
69
  * state (`completed` or `canceled`), or — if `changeId` is provided — until
@@ -85,6 +95,7 @@ export const waitForPendingPostgresChanges = Effect.fn(function* (
85
95
 
86
96
  return page.data.every((change) => isTerminal(change.state));
87
97
  },
98
+ changeRequestSchedule,
88
99
  );
89
100
  });
90
101
 
@@ -98,7 +109,10 @@ export const ensurePostgresProductionBranchClusterSize = Effect.fn(function* (
98
109
  branch: string,
99
110
  expectedClusterSize: PostgresClusterSize,
100
111
  ) {
101
- const data = yield* planetscale.getBranch({ organization, database, branch });
112
+ // A freshly-forked branch can't accept (or complete) change requests
113
+ // until it has finished provisioning — queueing a resize against it
114
+ // just leaves the change pending until the poll budget runs out.
115
+ const data = yield* waitForBranchReady(organization, database, branch);
102
116
 
103
117
  const sku = toPostgresClusterSku({
104
118
  size: expectedClusterSize,
@@ -12,7 +12,7 @@ export const DEFAULT_MIGRATIONS_TABLE = "__alchemy_migrations";
12
12
  * been reached. Used internally with `Effect.retry` to drive long-running
13
13
  * status waits.
14
14
  */
15
- class NotReady extends Data.TaggedError("Planetscale::NotReady")<{
15
+ export class NotReady extends Data.TaggedError("Planetscale::NotReady")<{
16
16
  description: string;
17
17
  }> {}
18
18
 
@@ -29,12 +29,14 @@ export class PlanetscaleConflict extends Data.TaggedError(
29
29
  }> {}
30
30
 
31
31
  /**
32
- * Default polling schedule: 5s spaced retries with a 10-minute total
33
- * budget (120 × 5s). Avoids the exponential-blowup trap where later
34
- * iterations would wait hours, indistinguishable from a hang.
32
+ * Default polling schedule: 5s spaced retries with a 30-minute total
33
+ * budget (360 × 5s). Avoids the exponential-blowup trap where later
34
+ * iterations would wait hours, indistinguishable from a hang. Postgres
35
+ * database creates routinely run 10-12 minutes, so a 10-minute budget
36
+ * regularly false-positives as "stuck".
35
37
  */
36
38
  const defaultSchedule = Schedule.spaced("5 seconds").pipe(
37
- Schedule.both(Schedule.recurs(120)),
39
+ Schedule.both(Schedule.recurs(360)),
38
40
  );
39
41
 
40
42
  /**