@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.
- package/dist/resources/data/internal/postgres-utils.d.ts +15 -8
- package/dist/resources/data/postgres-database.d.ts +61 -2
- package/dist/resources/data/postgres-database.js +130 -7
- package/dist/resources/data/postgres-migration.d.ts +182 -0
- package/dist/resources/data/postgres-migration.js +313 -0
- package/dist/resources/data/postgres-pool.d.ts +77 -0
- package/dist/resources/data/postgres-pool.js +50 -0
- package/dist/resources/data/postgres-settings.d.ts +33 -0
- package/dist/resources/data/postgres-settings.js +47 -0
- package/dist/resources/data/postgres.d.ts +203 -1
- package/dist/resources/data/postgres.js +243 -1
- package/dist/resources/data/redis/index.d.ts +1 -1
- package/dist/resources/extensions/data.d.ts +4 -1
- package/dist/resources/extensions/data.js +4 -1
- package/dist/resources/platform/add-on/destroy-and-wait.d.ts +2 -3
- package/dist/resources/platform/add-on/destroy-and-wait.js +7 -1
- package/dist/resources/platform/add-on/errors.d.ts +35 -2
- package/dist/resources/platform/add-on/errors.js +52 -0
- package/dist/resources/platform/add-on/index.d.ts +3 -3
- package/dist/resources/platform/add-on/index.js +1 -1
- package/dist/resources/platform/add-on/types.d.ts +14 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -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:
|
|
18
|
+
readonly addon: PostgresAddonLike;
|
|
12
19
|
readonly id = "not_advanced_database";
|
|
13
|
-
constructor(addon:
|
|
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:
|
|
26
|
-
export declare const isAdvancedDatabase: (addon:
|
|
27
|
-
export declare const isLegacyDatabase: (addon:
|
|
28
|
-
export declare const isLegacyEssentialDatabase: (addon:
|
|
29
|
-
export declare const isEssentialDatabase: (addon:
|
|
30
|
-
export declare const isEssentialTier: (addon:
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
+
}>;
|