@heroku/sdk 0.12.3 → 0.12.4

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.
@@ -3,14 +3,21 @@ import { PostgresCredentialInfoResult as AdvancedCredentialInfo } from '@heroku/
3
3
  import type { ResourceCtx } from '../../../core/extend-resource.js';
4
4
  import type { ResolvedAddOnAttachment } from '../../platform/add-on-attachment/resolve.js';
5
5
  import { CredentialInfo as NonAdvancedCredentialInfo } from '../postgres-database.js';
6
+ /**
7
+ * The add-on shapes the Postgres tier predicates accept: either a full
8
+ * platform `AddOn` or the lighter add-on carried on a resolved attachment.
9
+ * Both expose the `plan.name` these checks read, so they're interchangeable
10
+ * here.
11
+ */
12
+ export type PostgresAddonLike = AddOn | ResolvedAddOnAttachment['addon'];
6
13
  /**
7
14
  * Thrown when an add-on is not an Advanced-tier database. Carries the add-on
8
15
  * so callers that need its identity don't need a second resolve call.
9
16
  */
10
17
  export declare class NotAdvancedDatabaseError extends Error {
11
- readonly addon: AddOn | ResolvedAddOnAttachment['addon'];
18
+ readonly addon: PostgresAddonLike;
12
19
  readonly id = "not_advanced_database";
13
- constructor(addon: AddOn | ResolvedAddOnAttachment['addon'], message: string);
20
+ constructor(addon: PostgresAddonLike, message: string);
14
21
  get body(): {
15
22
  id: string;
16
23
  message: string;
@@ -22,9 +29,9 @@ export declare class NotAdvancedDatabaseError extends Error {
22
29
  export declare function findLegacyDatabase(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string): Promise<AddOn>;
23
30
  export declare function isAdvancedCredential(credential: AdvancedCredentialInfo | NonAdvancedCredentialInfo): credential is AdvancedCredentialInfo;
24
31
  export declare const getPostgresServiceName: () => string;
25
- export declare const isPostgresAddon: (addon: AddOn | ResolvedAddOnAttachment["addon"]) => boolean;
26
- export declare const isAdvancedDatabase: (addon: AddOn | ResolvedAddOnAttachment["addon"]) => boolean;
27
- export declare const isLegacyDatabase: (addon: AddOn) => boolean;
28
- export declare const isLegacyEssentialDatabase: (addon: AddOn) => boolean;
29
- export declare const isEssentialDatabase: (addon: AddOn) => boolean;
30
- export declare const isEssentialTier: (addon: AddOn) => boolean;
32
+ export declare const isPostgresAddon: (addon: PostgresAddonLike) => boolean;
33
+ export declare const isAdvancedDatabase: (addon: PostgresAddonLike) => boolean;
34
+ export declare const isLegacyDatabase: (addon: PostgresAddonLike) => boolean;
35
+ export declare const isLegacyEssentialDatabase: (addon: PostgresAddonLike) => boolean;
36
+ export declare const isEssentialDatabase: (addon: PostgresAddonLike) => boolean;
37
+ export declare const isEssentialTier: (addon: PostgresAddonLike) => boolean;
@@ -1,6 +1,7 @@
1
1
  import type { AddOnAttachment, AddOnAttachmentCreateOpts } from '@heroku/types/3.sdk';
2
+ import type { PostgresCredentialListResult } from '@heroku/types/data';
2
3
  import type { ResourceCtx } from '../../core/extend-resource.js';
3
- import type { ResolvedAddOn } from '../platform/add-on/types.js';
4
+ import type { DeprovisioningAddOn, ResolvedAddOn } from '../platform/add-on/types.js';
4
5
  export type ListCredentialsOptions = {
5
6
  signal?: AbortSignal;
6
7
  };
@@ -9,11 +10,23 @@ export type DeleteAttachmentOptions = {
9
10
  addon: ResolvedAddOn;
10
11
  attachment: AddOnAttachment;
11
12
  }) => Promise<void> | void;
13
+ onDetached?: (attachment: AddOnAttachment) => Promise<void> | void;
12
14
  signal?: AbortSignal;
13
15
  };
14
16
  export type DeleteAttachmentResult = {
15
17
  addon: ResolvedAddOn;
16
18
  attachment: AddOnAttachment;
19
+ releaseVersion?: number;
20
+ };
21
+ export type PostgresCredential = PostgresCredentialListResult['items'][number];
22
+ export type ListAttachmentsOptions = {
23
+ appIdentity?: string;
24
+ signal?: AbortSignal;
25
+ };
26
+ export type ListAttachmentsResult = {
27
+ addon: ResolvedAddOn;
28
+ attachments: AddOnAttachment[];
29
+ credentials: PostgresCredential[];
17
30
  };
18
31
  export type CreateAttachmentBody = Omit<AddOnAttachmentCreateOpts, 'addon' | 'app'>;
19
32
  export type CreateAttachmentOptions = {
@@ -25,6 +38,26 @@ export type CreateAttachmentResult = {
25
38
  attachment: AddOnAttachment;
26
39
  releaseVersion?: number;
27
40
  };
41
+ export type DestroyOptions = {
42
+ appIdentity?: string;
43
+ /**
44
+ * Runs after the database is resolved but before the delete request fires.
45
+ * An abort (or throw) during the hook prevents the delete — use it to
46
+ * confirm destruction against the resolved add-on.
47
+ */
48
+ beforeDestroy?: (preview: {
49
+ addon: ResolvedAddOn;
50
+ }) => Promise<void> | void;
51
+ force?: boolean;
52
+ onDeprovisioning?: (addon: DeprovisioningAddOn) => Promise<void> | void;
53
+ signal?: AbortSignal;
54
+ wait?: boolean;
55
+ waitIntervalMs?: number;
56
+ };
57
+ export type DestroyResult = {
58
+ addon: ResolvedAddOn;
59
+ result: DeprovisioningAddOn;
60
+ };
28
61
  export type CredentialInfo = {
29
62
  credentials: Array<{
30
63
  connections?: null | number;
@@ -48,9 +81,21 @@ export declare function listCredentials(ctx: Pick<ResourceCtx, 'data' | 'platfor
48
81
  * attachment's database isn't an Advanced-tier database.
49
82
  *
50
83
  * If provided, `beforeDelete` runs after resolution/tier-checking but before
51
- * the delete request fires. An abort during the hook prevents the delete.
84
+ * the delete request fires (an abort during the hook prevents the delete), and
85
+ * `onDetached` runs once the attachment is deleted but before the release
86
+ * version fetch. The returned `releaseVersion` is the latest release created by
87
+ * unsetting the attachment's config vars.
52
88
  */
53
89
  export declare function deleteAttachment(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string, attachmentName: string, options?: DeleteAttachmentOptions): Promise<DeleteAttachmentResult>;
90
+ /**
91
+ * Resolves a Postgres database by add-on identity and lists its attachments
92
+ * alongside its credentials, fanning the two requests out in parallel.
93
+ *
94
+ * Resolution is scoped by `appIdentity` when given. Throws
95
+ * `NotAdvancedDatabaseError` (carrying the resolved add-on) if the database
96
+ * isn't an Advanced-tier database.
97
+ */
98
+ export declare function listAttachments(ctx: Pick<ResourceCtx, 'data' | 'platform'>, addonIdentity: string, options?: ListAttachmentsOptions): Promise<ListAttachmentsResult>;
54
99
  /**
55
100
  * Resolves a source Postgres database by add-on identity, attaches it
56
101
  * to an app, and fetches the latest release version.
@@ -62,10 +107,24 @@ export declare function deleteAttachment(ctx: Pick<ResourceCtx, 'platform'>, app
62
107
  * the release version fetch.
63
108
  */
64
109
  export declare function createAttachment(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string, addonIdentity: string, body?: CreateAttachmentBody, options?: CreateAttachmentOptions): Promise<CreateAttachmentResult>;
110
+ /**
111
+ * Resolves a Postgres database by add-on identity and destroys it.
112
+ *
113
+ * Resolution is scoped by `appIdentity` when given. If provided,
114
+ * `beforeDestroy` runs after resolution but before the delete request fires,
115
+ * so callers can confirm against the resolved add-on; an abort or throw in
116
+ * the hook prevents the delete. Delegates the delete (and optional
117
+ * deprovisioning poll when `wait` is set) to `addOn.destroyAndWait`.
118
+ */
119
+ export declare function destroy(ctx: Pick<ResourceCtx, 'platform'>, addonIdentity: string, options?: DestroyOptions): Promise<DestroyResult>;
65
120
  export declare const postgresDatabaseExtensions: import("../../core/extend-resource.js").ResourceExtension<"data", "postgresDatabase", {
66
121
  createAttachment: (appIdentity: string, addonIdentity: string, body?: CreateAttachmentBody, options?: CreateAttachmentOptions) => Promise<CreateAttachmentResult>;
67
122
  deleteAttachment: (appIdentity: string, attachmentName: string, options?: DeleteAttachmentOptions) => Promise<DeleteAttachmentResult>;
123
+ destroy: (addonIdentity: string, options?: DestroyOptions) => Promise<DestroyResult>;
124
+ listAttachments: (addonIdentity: string, options?: ListAttachmentsOptions) => Promise<ListAttachmentsResult>;
68
125
  listCredentials: (appIdentity: string, addonIdentity?: string, options?: ListCredentialsOptions) => Promise<CredentialInfo[]>;
69
126
  }>;
127
+ export { AddonAppMismatchError, AddonConfirmationRequiredError, AddonDestroyFailedError, AddonNotFoundError, } from '../platform/add-on/errors.js';
128
+ export type { DeprovisioningAddOn, ResolvedAddOn } from '../platform/add-on/types.js';
70
129
  export { type AttachmentFactors, parseAttachmentFactors, } from './internal/parse-attachment-factors.js';
71
130
  export { findLegacyDatabase, getPostgresServiceName, isAdvancedCredential, isAdvancedDatabase, isEssentialDatabase, isEssentialTier, isLegacyDatabase, isLegacyEssentialDatabase, isPostgresAddon, NotAdvancedDatabaseError, } from './internal/postgres-utils.js';
@@ -1,7 +1,8 @@
1
- import { HerokuApiError } from '@heroku/heroku-fetch';
1
+ import { HerokuApiError, NotFoundError } from '@heroku/heroku-fetch';
2
2
  import { extendResource } from '../../core/extend-resource.js';
3
3
  import { withSignal } from '../../core/with-signal.js';
4
- import { AddonConfirmationRequiredError, AddonNotFoundError } from '../platform/add-on/errors.js';
4
+ import { destroyAndWait } from '../platform/add-on/destroy-and-wait.js';
5
+ import { AddonAppMismatchError, AddonConfirmationRequiredError, AddonDestroyFailedError, AddonNotFoundError, } from '../platform/add-on/errors.js';
5
6
  import { resolveAddonInternal } from '../platform/add-on/resolve.js';
6
7
  import { getPostgresServiceName, isAdvancedDatabase, NotAdvancedDatabaseError } from './internal/postgres-utils.js';
7
8
  import { resolvePgDatabase } from './internal/resolve-pg-database.js';
@@ -20,12 +21,15 @@ export async function listCredentials(ctx, appIdentity, addonIdentity, options =
20
21
  * attachment's database isn't an Advanced-tier database.
21
22
  *
22
23
  * If provided, `beforeDelete` runs after resolution/tier-checking but before
23
- * the delete request fires. An abort during the hook prevents the delete.
24
+ * the delete request fires (an abort during the hook prevents the delete), and
25
+ * `onDetached` runs once the attachment is deleted but before the release
26
+ * version fetch. The returned `releaseVersion` is the latest release created by
27
+ * unsetting the attachment's config vars.
24
28
  */
25
29
  export async function deleteAttachment(ctx, appIdentity, attachmentName, options = {}) {
26
- const { beforeDelete, signal } = options;
30
+ const { beforeDelete, onDetached, signal } = options;
27
31
  signal?.throwIfAborted();
28
- // Bind the signal so the lookup and delete requests abort in flight.
32
+ // Bind the signal so the lookup, delete, and release-list requests abort in flight.
29
33
  const platform = withSignal(ctx.platform, signal);
30
34
  const attachment = await platform.addOnAttachment.infoByApp(appIdentity, attachmentName);
31
35
  const addon = await resolveAddonInternal(platform, attachment.addon.name, { addonService: getPostgresServiceName() });
@@ -35,7 +39,48 @@ export async function deleteAttachment(ctx, appIdentity, attachmentName, options
35
39
  await beforeDelete?.({ addon, attachment });
36
40
  signal?.throwIfAborted();
37
41
  const deletedAttachment = await platform.addOnAttachment.delete(attachment.id);
38
- return { addon, attachment: deletedAttachment };
42
+ await onDetached?.(deletedAttachment);
43
+ signal?.throwIfAborted();
44
+ // The detach already succeeded; a failed release lookup shouldn't fail the
45
+ // whole operation. Preserve aborts, otherwise report an unknown version.
46
+ let releaseVersion;
47
+ try {
48
+ const releases = await platform
49
+ .withHeaders({ Range: 'version ..; max=1, order=desc' })
50
+ .release.list(appIdentity);
51
+ releaseVersion = releases[0]?.version;
52
+ }
53
+ catch (error) {
54
+ // Preserve aborts; otherwise leave releaseVersion undefined (best-effort).
55
+ if (signal?.aborted) {
56
+ throw error;
57
+ }
58
+ }
59
+ return { addon, attachment: deletedAttachment, releaseVersion };
60
+ }
61
+ /**
62
+ * Resolves a Postgres database by add-on identity and lists its attachments
63
+ * alongside its credentials, fanning the two requests out in parallel.
64
+ *
65
+ * Resolution is scoped by `appIdentity` when given. Throws
66
+ * `NotAdvancedDatabaseError` (carrying the resolved add-on) if the database
67
+ * isn't an Advanced-tier database.
68
+ */
69
+ export async function listAttachments(ctx, addonIdentity, options = {}) {
70
+ const { appIdentity, signal } = options;
71
+ signal?.throwIfAborted();
72
+ // Bind the signal so the resolve and fan-out requests abort in flight.
73
+ const platform = withSignal(ctx.platform, signal);
74
+ const data = withSignal(ctx.data, signal);
75
+ const addon = await resolveAddonInternal(platform, addonIdentity, { addonService: getPostgresServiceName(), appIdentity });
76
+ if (!isAdvancedDatabase(addon)) {
77
+ throw new NotAdvancedDatabaseError(addon, `You can only list attachments on Advanced-tier databases with postgresDatabase.listAttachments(). ${addon.name} isn't an Advanced-tier database.`);
78
+ }
79
+ const [{ items: credentials }, attachments] = await Promise.all([
80
+ data.postgresCredential.list(addon.id),
81
+ platform.addOnAttachment.listByAddOn(addon.id),
82
+ ]);
83
+ return { addon, attachments, credentials };
39
84
  }
40
85
  /**
41
86
  * Resolves a source Postgres database by add-on identity, attaches it
@@ -60,7 +105,7 @@ export async function createAttachment(ctx, appIdentity, addonIdentity, body = {
60
105
  addon = await resolveAddonInternal(platform, addonIdentity, { addonService: getPostgresServiceName(), appIdentity });
61
106
  }
62
107
  catch (error) {
63
- if (!(error instanceof AddonNotFoundError)) {
108
+ if (!(error instanceof NotFoundError)) {
64
109
  throw error;
65
110
  }
66
111
  addon = await resolveAddonInternal(platform, addonIdentity, { addonService: getPostgresServiceName() });
@@ -89,10 +134,88 @@ export async function createAttachment(ctx, appIdentity, addonIdentity, body = {
89
134
  .release.list(appIdentity);
90
135
  return { addon, attachment, releaseVersion: releases[0]?.version };
91
136
  }
137
+ /**
138
+ * Resolves a Postgres database by add-on identity and destroys it.
139
+ *
140
+ * Resolution is scoped by `appIdentity` when given. If provided,
141
+ * `beforeDestroy` runs after resolution but before the delete request fires,
142
+ * so callers can confirm against the resolved add-on; an abort or throw in
143
+ * the hook prevents the delete. Delegates the delete (and optional
144
+ * deprovisioning poll when `wait` is set) to `addOn.destroyAndWait`.
145
+ */
146
+ export async function destroy(ctx, addonIdentity, options = {}) {
147
+ const { appIdentity, beforeDestroy, force, onDeprovisioning, signal, wait, waitIntervalMs, } = options;
148
+ signal?.throwIfAborted();
149
+ // Bind the signal so the resolve request aborts in flight.
150
+ const platform = withSignal(ctx.platform, signal);
151
+ let addon;
152
+ try {
153
+ addon = await resolveAddonInternal(platform, addonIdentity, {
154
+ addonService: getPostgresServiceName(), appIdentity,
155
+ });
156
+ }
157
+ catch (error) {
158
+ // When scoped resolution misses, check whether the database exists under a
159
+ // different app so we can surface a targeted mismatch error instead of a
160
+ // bare not-found. Skip the probe for `::`-qualified identities: those name
161
+ // their app inline, so `resolveAddonInternal` ignores `appIdentity` and the
162
+ // unscoped retry would just repeat the same failing request.
163
+ if (appIdentity && !addonIdentity.includes('::') && error instanceof AddonNotFoundError) {
164
+ let elsewhere;
165
+ try {
166
+ elsewhere = await resolveAddonInternal(platform, addonIdentity, {
167
+ addonService: getPostgresServiceName(),
168
+ });
169
+ }
170
+ catch (retryError) {
171
+ // Preserve aborts; an ambiguous/other failure here can't produce a
172
+ // clean mismatch message, so fall through to the original not-found.
173
+ if (signal?.aborted) {
174
+ throw retryError;
175
+ }
176
+ }
177
+ // `appIdentity` may be an app name or UUID; compare against both so a
178
+ // UUID-scoped caller isn't always flagged as a mismatch (and doesn't get
179
+ // a raw UUID echoed back in the error message).
180
+ if (elsewhere && elsewhere.app.id !== appIdentity && elsewhere.app.name !== appIdentity) {
181
+ throw new AddonAppMismatchError(elsewhere, appIdentity);
182
+ }
183
+ }
184
+ throw error;
185
+ }
186
+ await beforeDestroy?.({ addon });
187
+ signal?.throwIfAborted();
188
+ // Resolution and beforeDestroy aborts already happened above; a failure here
189
+ // is the delete request (or wait poll) itself. Preserve aborts, but wrap other
190
+ // failures so callers get the resolved add-on (and thus tier) without
191
+ // re-resolving.
192
+ try {
193
+ const result = await destroyAndWait(ctx, addon.app.id, addon.id, {
194
+ force, onDeprovisioning, signal, wait, waitIntervalMs,
195
+ });
196
+ return { addon, result };
197
+ }
198
+ catch (error) {
199
+ if (signal?.aborted) {
200
+ throw error;
201
+ }
202
+ // A platform `confirmation_required` (e.g. destroying a database attached
203
+ // to other apps without `force`) is a distinct, recoverable condition —
204
+ // surface it as the typed error so callers can prompt and retry, matching
205
+ // `createAttachment`, rather than collapsing it into a generic failure.
206
+ if (error instanceof HerokuApiError && error.id === 'confirmation_required') {
207
+ throw new AddonConfirmationRequiredError(error.message);
208
+ }
209
+ throw new AddonDestroyFailedError(addon, error);
210
+ }
211
+ }
92
212
  export const postgresDatabaseExtensions = extendResource('data', 'postgresDatabase', ctx => ({
93
213
  createAttachment: (appIdentity, addonIdentity, body, options) => createAttachment(ctx, appIdentity, addonIdentity, body, options),
94
214
  deleteAttachment: (appIdentity, attachmentName, options) => deleteAttachment(ctx, appIdentity, attachmentName, options),
215
+ destroy: (addonIdentity, options) => destroy(ctx, addonIdentity, options),
216
+ listAttachments: (addonIdentity, options) => listAttachments(ctx, addonIdentity, options),
95
217
  listCredentials: (appIdentity, addonIdentity, options) => listCredentials(ctx, appIdentity, addonIdentity, options),
96
218
  }));
219
+ export { AddonAppMismatchError, AddonConfirmationRequiredError, AddonDestroyFailedError, AddonNotFoundError, } from '../platform/add-on/errors.js';
97
220
  export { parseAttachmentFactors, } from './internal/parse-attachment-factors.js';
98
221
  export { findLegacyDatabase, getPostgresServiceName, isAdvancedCredential, isAdvancedDatabase, isEssentialDatabase, isEssentialTier, isLegacyDatabase, isLegacyEssentialDatabase, isPostgresAddon, NotAdvancedDatabaseError, } from './internal/postgres-utils.js';
@@ -0,0 +1,182 @@
1
+ import type { PostgresInfoResult, PostgresMigrationCreateOpts, PostgresMigrationCreateResult, PostgresMigrationInfoResult } from '@heroku/types/data';
2
+ import type { ResourceCtx } from '../../core/extend-resource.js';
3
+ import type { AddOnAttachmentWithInclusions } from '../platform/add-on-attachment/resolve.js';
4
+ /**
5
+ * A database migration record, as returned by `find`, `startWhenReady`, and
6
+ * `abortBeforeRun`. The create/prepare response
7
+ * (`PostgresMigrationCreateResult`) is structurally identical today — same
8
+ * fields, same closed `status` union — but keeps its own generated type rather
9
+ * than sharing this alias.
10
+ */
11
+ export type Migration = PostgresMigrationInfoResult;
12
+ /** The closed migration-status union, re-derived so callers don't restate it. */
13
+ export type MigrationStatus = Migration['status'];
14
+ /** Request body for configuring a migration (the generated create opts). */
15
+ export type MigrationCreateBody = PostgresMigrationCreateOpts;
16
+ export type MigrationOptions = {
17
+ signal?: AbortSignal;
18
+ };
19
+ /**
20
+ * The reason a migration create request was rejected client-side. A
21
+ * single-member discriminated union today; new guards add members here so
22
+ * callers keep exhaustive handling. Server-side validation rejections are not
23
+ * yet mapped onto this type — they surface as the raw API error.
24
+ */
25
+ export type MigrationInvalidReason = 'source_equals_target';
26
+ /**
27
+ * A classic Postgres database eligible to be migrated *from*, with the names
28
+ * it's attached under on the app.
29
+ */
30
+ export type MigrationSource = AddOnAttachmentWithInclusions['addon'] & {
31
+ attachmentNames: string[];
32
+ };
33
+ /**
34
+ * An Advanced Postgres database eligible to be migrated *to*, with its
35
+ * attachment names, its current `info` (for availability gating), and any
36
+ * migration currently targeting it. `info`/`migration` are absent when the
37
+ * database's `/info` or `/migrations` endpoint 404s (not yet a target).
38
+ */
39
+ export type MigrationTarget = AddOnAttachmentWithInclusions['addon'] & {
40
+ attachmentNames: string[];
41
+ info?: PostgresInfoResult;
42
+ migration?: Migration;
43
+ };
44
+ export type MigrationContext = {
45
+ /** All migration records currently configured on the app's Advanced databases. */
46
+ migrations: Migration[];
47
+ /** Classic (non-Essential) databases that can be migration sources. */
48
+ sources: MigrationSource[];
49
+ /** Advanced databases that can be migration targets. */
50
+ targets: MigrationTarget[];
51
+ };
52
+ /**
53
+ * Thrown by `create` when the migration inputs are invalid before any request
54
+ * fires. Carries a machine `reason` so callers can branch without string
55
+ * matching.
56
+ */
57
+ export declare class MigrationInvalidError extends Error {
58
+ readonly reason: MigrationInvalidReason;
59
+ readonly id = "migration_invalid";
60
+ constructor(reason: MigrationInvalidReason, message: string);
61
+ get body(): {
62
+ id: string;
63
+ message: string;
64
+ reason: "source_equals_target";
65
+ };
66
+ }
67
+ /**
68
+ * Thrown by `run` when the migration isn't in a startable state. Carries the
69
+ * migration (and thus its `status`) so callers can report the drift without
70
+ * re-fetching.
71
+ */
72
+ export declare class MigrationNotRunnableError extends Error {
73
+ readonly migration: Migration;
74
+ readonly id = "migration_not_runnable";
75
+ constructor(migration: Migration);
76
+ get body(): {
77
+ id: string;
78
+ message: string;
79
+ status: "creating_target" | "preparing" | "migrating" | "completed" | "failed" | "cancelled" | "ready" | "promoting" | "unknown";
80
+ };
81
+ /**
82
+ * The migration's status. Named `migrationStatus` (not `status`) to avoid
83
+ * shadowing the HTTP-status convention other `Error` subclasses in the SDK
84
+ * carry.
85
+ */
86
+ get migrationStatus(): MigrationStatus;
87
+ }
88
+ /**
89
+ * Thrown by `cancel` when the migration isn't in a cancellable state. Carries
90
+ * the migration (and thus its `status`).
91
+ */
92
+ export declare class MigrationNotCancellableError extends Error {
93
+ readonly migration: Migration;
94
+ readonly id = "migration_not_cancellable";
95
+ constructor(migration: Migration);
96
+ get body(): {
97
+ id: string;
98
+ message: string;
99
+ status: "creating_target" | "preparing" | "migrating" | "completed" | "failed" | "cancelled" | "ready" | "promoting" | "unknown";
100
+ };
101
+ /**
102
+ * The migration's status. Named `migrationStatus` (not `status`) to avoid
103
+ * shadowing the HTTP-status convention other `Error` subclasses in the SDK
104
+ * carry.
105
+ */
106
+ get migrationStatus(): MigrationStatus;
107
+ }
108
+ /**
109
+ * Whether a migration can be started. Only `ready` migrations are runnable,
110
+ * matching the CLI's menu gate. The exhaustive `switch` makes a newly
111
+ * generated status a compile error until its policy is decided here, rather
112
+ * than silently defaulting to runnable or not.
113
+ */
114
+ export declare function isRunnable(status: MigrationStatus): boolean;
115
+ /**
116
+ * Whether a migration can be canceled. Only `ready` migrations are
117
+ * cancellable, matching the CLI's menu gate. Exhaustive for the same reason as
118
+ * {@link isRunnable}.
119
+ */
120
+ export declare function isCancellable(status: MigrationStatus): boolean;
121
+ /**
122
+ * Fetches the migration currently targeting `targetAddonId`.
123
+ *
124
+ * The data API's migration endpoint is a single-object `GET` (not a list);
125
+ * when no migration exists it 404s, which this surfaces as `undefined` rather
126
+ * than throwing, so callers can treat "no migration" as a normal state.
127
+ */
128
+ export declare function info(ctx: Pick<ResourceCtx, 'data'>, targetAddonId: string, options?: MigrationOptions): Promise<Migration | undefined>;
129
+ /**
130
+ * Configures a new migration *to* `targetAddonId` *from* the database named in
131
+ * `body.source_id`.
132
+ *
133
+ * The URL identifies the **target**; the body's `source_id` identifies the
134
+ * **source** — both are add-on ids, so the two are easy to swap. A
135
+ * client-side guard rejects a source that equals the target before any request
136
+ * fires.
137
+ */
138
+ export declare function create(ctx: Pick<ResourceCtx, 'data'>, targetAddonId: string, body: MigrationCreateBody, options?: MigrationOptions): Promise<PostgresMigrationCreateResult>;
139
+ /**
140
+ * Starts the migration targeting `targetAddonId`.
141
+ *
142
+ * Fetches the migration first and gates on its status — a defensive backstop
143
+ * against state drift between listing and action: throws
144
+ * `MigrationNotRunnableError` (carrying the migration) if it's no longer
145
+ * `ready`, and lets a `NotFoundError` propagate if the migration is already
146
+ * gone. Once the run POST succeeds, returns the migration's post-action state
147
+ * via a best-effort re-fetch (see {@link refetchAfterAction}); never throws
148
+ * after the action has fired.
149
+ */
150
+ export declare function run(ctx: Pick<ResourceCtx, 'data'>, targetAddonId: string, options?: MigrationOptions): Promise<Migration>;
151
+ /**
152
+ * Cancels the migration targeting `targetAddonId`.
153
+ *
154
+ * Gates on status like {@link run}: throws `MigrationNotCancellableError`
155
+ * (carrying the migration) if it's not `ready`, and lets a `NotFoundError`
156
+ * propagate if the migration is already gone. Once the cancel POST succeeds,
157
+ * returns the migration's post-action state via a best-effort re-fetch (see
158
+ * {@link refetchAfterAction}); never throws after the action has fired.
159
+ */
160
+ export declare function cancel(ctx: Pick<ResourceCtx, 'data'>, targetAddonId: string, options?: MigrationOptions): Promise<Migration>;
161
+ /**
162
+ * Lists everything the migration UI needs for an app in one pass: the classic
163
+ * databases that can be sources, the Advanced databases that can be targets
164
+ * (each with its `info` and any migration targeting it), and the flat list of
165
+ * configured migrations.
166
+ *
167
+ * Replaces the CLI's `getAppDatabases` + `getMigrationTargetsAndInfo`: resolves
168
+ * the app's add-on attachments (with `addon:plan,config_vars` inclusions so
169
+ * plans are present for tier classification), keeps only Postgres databases
170
+ * the app owns, dedupes to unique add-ons with their attachment names, then
171
+ * fans out `/migrations` and `/info` per Advanced database. A per-database 404
172
+ * is tolerated (that database simply isn't a migration target yet); the first
173
+ * non-404 rejection is rethrown.
174
+ */
175
+ export declare function listMigrationContext(ctx: Pick<ResourceCtx, 'data' | 'platform'>, appIdentity: string, options?: MigrationOptions): Promise<MigrationContext>;
176
+ export declare const postgresMigrationExtensions: import("../../core/extend-resource.js").ResourceExtension<"data", "postgresMigration", {
177
+ abortBeforeRun: (targetAddonId: string, options?: MigrationOptions) => Promise<PostgresMigrationInfoResult>;
178
+ find: (targetAddonId: string, options?: MigrationOptions) => Promise<PostgresMigrationInfoResult | undefined>;
179
+ listMigrationContext: (appIdentity: string, options?: MigrationOptions) => Promise<MigrationContext>;
180
+ prepare: (targetAddonId: string, body: MigrationCreateBody, options?: MigrationOptions) => Promise<PostgresMigrationCreateResult>;
181
+ startWhenReady: (targetAddonId: string, options?: MigrationOptions) => Promise<PostgresMigrationInfoResult>;
182
+ }>;