@heroku/sdk 0.12.2 → 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/database-credential/index.d.ts +0 -1
- package/dist/resources/data/database-credential/index.js +0 -1
- package/dist/resources/data/database-credential/list.d.ts +0 -15
- package/dist/resources/data/database-credential/list.js +2 -20
- package/dist/resources/data/database-credential/rotate.d.ts +1 -3
- package/dist/resources/data/internal/postgres-utils.d.ts +27 -11
- package/dist/resources/data/internal/postgres-utils.js +19 -7
- package/dist/resources/data/internal/resolve-pg-database.js +2 -2
- package/dist/resources/data/postgres-database.d.ts +107 -1
- package/dist/resources/data/postgres-database.js +207 -1
- 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-quota.d.ts +45 -0
- package/dist/resources/data/postgres-quota.js +61 -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 +229 -0
- package/dist/resources/data/postgres.js +273 -0
- package/dist/resources/data/redis/index.d.ts +1 -1
- package/dist/resources/extensions/data.d.ts +5 -0
- package/dist/resources/extensions/data.js +5 -0
- 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
|
@@ -7,7 +7,6 @@ export type DatabaseCredentialOptions = {
|
|
|
7
7
|
export { create, type CreateDatabaseCredentialResult } from './create.js';
|
|
8
8
|
export { destroy, type DestroyDatabaseCredentialResult } from './destroy.js';
|
|
9
9
|
export { type GetDatabaseCredentialUrlResult, getUrl, } from './get-url.js';
|
|
10
|
-
export { NotAdvancedDatabaseError } from './list.js';
|
|
11
10
|
export { list, type ListDatabaseCredentialsResult } from './list.js';
|
|
12
11
|
export { rotate, type RotateAllDatabaseCredentialsResult, type RotateDatabaseCredentialOptions, type RotateDatabaseCredentialOpts, type RotateDatabaseCredentialPreview, type RotateDatabaseCredentialResult, } from './rotate.js';
|
|
13
12
|
export declare const databaseCredentialExtensions: import("../../../core/extend-resource.js").ResourceExtension<"data", "databaseCredential", {
|
|
@@ -7,7 +7,6 @@ import { rotate } from './rotate.js';
|
|
|
7
7
|
export { create } from './create.js';
|
|
8
8
|
export { destroy } from './destroy.js';
|
|
9
9
|
export { getUrl, } from './get-url.js';
|
|
10
|
-
export { NotAdvancedDatabaseError } from './list.js';
|
|
11
10
|
export { list } from './list.js';
|
|
12
11
|
export { rotate, } from './rotate.js';
|
|
13
12
|
export const databaseCredentialExtensions = extendResource('data', 'databaseCredential', ctx => {
|
|
@@ -2,21 +2,6 @@ import type { PostgresCredentialListResult } from '@heroku/types/data';
|
|
|
2
2
|
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
3
|
import type { ResolvedAddOn } from '../../platform/add-on/types.js';
|
|
4
4
|
import { DatabaseCredentialOptions } from './index.js';
|
|
5
|
-
/**
|
|
6
|
-
* Thrown by `list` when the resolved add-on is not an Advanced-tier
|
|
7
|
-
* database. Carries the resolved addon so callers that need its identity
|
|
8
|
-
* (e.g. to point the user at the classic-tier equivalent) don't need a
|
|
9
|
-
* second resolve call.
|
|
10
|
-
*/
|
|
11
|
-
export declare class NotAdvancedDatabaseError extends Error {
|
|
12
|
-
readonly addon: ResolvedAddOn;
|
|
13
|
-
readonly id = "not_advanced_database";
|
|
14
|
-
constructor(addon: ResolvedAddOn);
|
|
15
|
-
get body(): {
|
|
16
|
-
id: string;
|
|
17
|
-
message: string;
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
5
|
export type ListDatabaseCredentialsResult = {
|
|
21
6
|
addon: ResolvedAddOn;
|
|
22
7
|
credentials: PostgresCredentialListResult;
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
import { withSignal } from '../../../core/with-signal.js';
|
|
2
2
|
import { resolveAddonInternal } from '../../platform/add-on/resolve.js';
|
|
3
|
-
import { getPostgresServiceName, isAdvancedDatabase } from '../internal/postgres-utils.js';
|
|
4
|
-
/**
|
|
5
|
-
* Thrown by `list` when the resolved add-on is not an Advanced-tier
|
|
6
|
-
* database. Carries the resolved addon so callers that need its identity
|
|
7
|
-
* (e.g. to point the user at the classic-tier equivalent) don't need a
|
|
8
|
-
* second resolve call.
|
|
9
|
-
*/
|
|
10
|
-
export class NotAdvancedDatabaseError extends Error {
|
|
11
|
-
addon;
|
|
12
|
-
id = 'not_advanced_database';
|
|
13
|
-
constructor(addon) {
|
|
14
|
-
super(`You can only list credentials on Advanced-tier databases. ${addon.name} isn't an Advanced-tier database.`);
|
|
15
|
-
this.addon = addon;
|
|
16
|
-
this.name = 'NotAdvancedDatabaseError';
|
|
17
|
-
}
|
|
18
|
-
get body() {
|
|
19
|
-
return { id: this.id, message: this.message };
|
|
20
|
-
}
|
|
21
|
-
}
|
|
3
|
+
import { getPostgresServiceName, isAdvancedDatabase, NotAdvancedDatabaseError } from '../internal/postgres-utils.js';
|
|
22
4
|
/**
|
|
23
5
|
* Lists credentials on an Advanced-tier Postgres database. Classic-tier
|
|
24
6
|
* databases don't support this endpoint; use `postgresDatabase.listCredentials`
|
|
@@ -36,7 +18,7 @@ export async function list(ctx, addonIdentity, options = {}) {
|
|
|
36
18
|
addonService: getPostgresServiceName(), appIdentity,
|
|
37
19
|
});
|
|
38
20
|
if (!isAdvancedDatabase(addon)) {
|
|
39
|
-
throw new NotAdvancedDatabaseError(addon);
|
|
21
|
+
throw new NotAdvancedDatabaseError(addon, `You can only list credentials on Advanced-tier databases. ${addon.name} isn't an Advanced-tier database.`);
|
|
40
22
|
}
|
|
41
23
|
const data = withSignal(ctx.data, signal);
|
|
42
24
|
const credentials = await data.postgresCredential.list(addon.id);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type { PostgresDatabaseInfoCredentialResult as NonAdvancedCredentialInfo,
|
|
1
|
+
import type { PostgresCredentialInfoResult as AdvancedCredentialInfo, PostgresDatabaseInfoCredentialResult as NonAdvancedCredentialInfo, PostgresDatabaseRotateCredentialResult, PostgresDatabaseRotateCredentialsResult, PostgresRotateCredentialsResult } from '@heroku/types/data';
|
|
2
2
|
import { AddOnAttachment } from '@heroku/types/3.sdk';
|
|
3
3
|
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
4
4
|
import type { ResolvedAddOn } from '../../platform/add-on/types.js';
|
|
5
5
|
import type { DatabaseCredentialOptions } from './index.js';
|
|
6
|
-
type AdvancedCredentialInfo = PostgresCredentialListResult['items'][number];
|
|
7
6
|
export type RotateDatabaseCredentialOptions = DatabaseCredentialOptions & {
|
|
8
7
|
/**
|
|
9
8
|
* Fires before the rotate request, allowing the caller to perform any
|
|
@@ -53,4 +52,3 @@ export declare function rotate(ctx: Pick<ResourceCtx, 'data' | 'platform'>, addo
|
|
|
53
52
|
all: true;
|
|
54
53
|
}, options?: RotateDatabaseCredentialOptions): Promise<RotateAllDatabaseCredentialsResult>;
|
|
55
54
|
export declare function rotate(ctx: Pick<ResourceCtx, 'data' | 'platform'>, addonIdentity: string, body?: RotateDatabaseCredentialOpts, options?: RotateDatabaseCredentialOptions): Promise<RotateDatabaseCredentialResult>;
|
|
56
|
-
export {};
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
import type { AddOn } from '@heroku/types/3.sdk';
|
|
2
2
|
import { PostgresCredentialInfoResult as AdvancedCredentialInfo } from '@heroku/types/data';
|
|
3
3
|
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
4
|
-
import type {
|
|
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'];
|
|
13
|
+
/**
|
|
14
|
+
* Thrown when an add-on is not an Advanced-tier database. Carries the add-on
|
|
15
|
+
* so callers that need its identity don't need a second resolve call.
|
|
16
|
+
*/
|
|
17
|
+
export declare class NotAdvancedDatabaseError extends Error {
|
|
18
|
+
readonly addon: PostgresAddonLike;
|
|
19
|
+
readonly id = "not_advanced_database";
|
|
20
|
+
constructor(addon: PostgresAddonLike, message: string);
|
|
21
|
+
get body(): {
|
|
22
|
+
id: string;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
6
26
|
/**
|
|
7
27
|
* Returns the first legacy database add-on on the app.
|
|
8
28
|
*/
|
|
9
29
|
export declare function findLegacyDatabase(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string): Promise<AddOn>;
|
|
10
30
|
export declare function isAdvancedCredential(credential: AdvancedCredentialInfo | NonAdvancedCredentialInfo): credential is AdvancedCredentialInfo;
|
|
11
31
|
export declare const getPostgresServiceName: () => string;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export declare const
|
|
16
|
-
export declare const
|
|
17
|
-
export declare const
|
|
18
|
-
export declare const isLegacyDatabase: (addon: AddOn) => boolean;
|
|
19
|
-
export declare const isLegacyEssentialDatabase: (addon: AddOn) => boolean;
|
|
20
|
-
export declare const isEssentialDatabase: (addon: AddOn) => boolean;
|
|
21
|
-
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,5 +1,21 @@
|
|
|
1
1
|
import createDebug from 'debug';
|
|
2
2
|
const debug = createDebug('heroku:sdk:resources:postgres-database');
|
|
3
|
+
/**
|
|
4
|
+
* Thrown when an add-on is not an Advanced-tier database. Carries the add-on
|
|
5
|
+
* so callers that need its identity don't need a second resolve call.
|
|
6
|
+
*/
|
|
7
|
+
export class NotAdvancedDatabaseError extends Error {
|
|
8
|
+
addon;
|
|
9
|
+
id = 'not_advanced_database';
|
|
10
|
+
constructor(addon, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.addon = addon;
|
|
13
|
+
this.name = 'NotAdvancedDatabaseError';
|
|
14
|
+
}
|
|
15
|
+
get body() {
|
|
16
|
+
return { id: this.id, message: this.message };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
3
19
|
/**
|
|
4
20
|
* Returns the first legacy database add-on on the app.
|
|
5
21
|
*/
|
|
@@ -15,13 +31,9 @@ export function isAdvancedCredential(credential) {
|
|
|
15
31
|
return 'roles' in credential;
|
|
16
32
|
}
|
|
17
33
|
export const getPostgresServiceName = () => process.env.HEROKU_POSTGRESQL_ADDON_NAME || process.env.HEROKU_DATA_SERVICE || 'heroku-postgresql';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export const isPostgresAddonInternal = (addon) => addon.plan?.name?.split(':', 2)[0] === getPostgresServiceName();
|
|
22
|
-
export const isPostgresAddon = (addon) => isPostgresAddonInternal(addon);
|
|
23
|
-
export const isAdvancedDatabase = (addon) => isPostgresAddonInternal(addon) && /^(advanced|performance)/.test(addon.plan?.name?.split(':', 2)[1] ?? '');
|
|
24
|
-
export const isLegacyDatabase = (addon) => isPostgresAddonInternal(addon) && !isAdvancedDatabase(addon);
|
|
34
|
+
export const isPostgresAddon = (addon) => addon.plan?.name?.split(':', 2)[0] === getPostgresServiceName();
|
|
35
|
+
export const isAdvancedDatabase = (addon) => isPostgresAddon(addon) && /^(advanced|performance)/.test(addon.plan?.name?.split(':', 2)[1] ?? '');
|
|
36
|
+
export const isLegacyDatabase = (addon) => isPostgresAddon(addon) && !isAdvancedDatabase(addon);
|
|
25
37
|
export const isLegacyEssentialDatabase = (addon) => isLegacyDatabase(addon) && /^(dev|basic|mini)/.test(addon.plan?.name?.split(':', 2)[1] ?? '');
|
|
26
38
|
export const isEssentialDatabase = (addon) => isLegacyDatabase(addon) && (addon.plan?.name?.split(':', 2)[1] ?? '').startsWith('essential');
|
|
27
39
|
export const isEssentialTier = (addon) => isEssentialDatabase(addon) || isLegacyEssentialDatabase(addon);
|
|
@@ -2,7 +2,7 @@ import { NotFoundError } from '@heroku/heroku-fetch';
|
|
|
2
2
|
import createDebug from 'debug';
|
|
3
3
|
import { withSignal } from '../../../core/with-signal.js';
|
|
4
4
|
import { AddonAttachmentAmbiguousError, AddonAttachmentNotFoundError, resolveAddonAttachment } from '../../platform/add-on-attachment/resolve.js';
|
|
5
|
-
import { getPostgresServiceName,
|
|
5
|
+
import { getPostgresServiceName, isPostgresAddon } from './postgres-utils.js';
|
|
6
6
|
const debug = createDebug('heroku:sdk:resources:database');
|
|
7
7
|
const DEFAULT_PG_ATTACHMENT = 'DATABASE_URL';
|
|
8
8
|
/**
|
|
@@ -118,7 +118,7 @@ async function listPostgresAttachments(ctx, appIdentity, attachmentId, error, si
|
|
|
118
118
|
debug('listing attachments failed while building not-found error: %O', listError);
|
|
119
119
|
throw new PgDatabaseNotFoundError(`Could not resolve ${stripUrlSuffix(attachmentId)} on ${appIdentity}`, { cause: error });
|
|
120
120
|
}
|
|
121
|
-
return attachments.filter(attachment =>
|
|
121
|
+
return attachments.filter(attachment => isPostgresAddon(attachment.addon));
|
|
122
122
|
}
|
|
123
123
|
/**
|
|
124
124
|
* Used by `attachmentResolutionFallback` to resolve attachments via app config vars
|
|
@@ -1,7 +1,63 @@
|
|
|
1
|
+
import type { AddOnAttachment, AddOnAttachmentCreateOpts } from '@heroku/types/3.sdk';
|
|
2
|
+
import type { PostgresCredentialListResult } from '@heroku/types/data';
|
|
1
3
|
import type { ResourceCtx } from '../../core/extend-resource.js';
|
|
4
|
+
import type { DeprovisioningAddOn, ResolvedAddOn } from '../platform/add-on/types.js';
|
|
2
5
|
export type ListCredentialsOptions = {
|
|
3
6
|
signal?: AbortSignal;
|
|
4
7
|
};
|
|
8
|
+
export type DeleteAttachmentOptions = {
|
|
9
|
+
beforeDelete?: (preview: {
|
|
10
|
+
addon: ResolvedAddOn;
|
|
11
|
+
attachment: AddOnAttachment;
|
|
12
|
+
}) => Promise<void> | void;
|
|
13
|
+
onDetached?: (attachment: AddOnAttachment) => Promise<void> | void;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
};
|
|
16
|
+
export type DeleteAttachmentResult = {
|
|
17
|
+
addon: ResolvedAddOn;
|
|
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[];
|
|
30
|
+
};
|
|
31
|
+
export type CreateAttachmentBody = Omit<AddOnAttachmentCreateOpts, 'addon' | 'app'>;
|
|
32
|
+
export type CreateAttachmentOptions = {
|
|
33
|
+
onAttached?: (attachment: AddOnAttachment) => Promise<void> | void;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
};
|
|
36
|
+
export type CreateAttachmentResult = {
|
|
37
|
+
addon: ResolvedAddOn;
|
|
38
|
+
attachment: AddOnAttachment;
|
|
39
|
+
releaseVersion?: number;
|
|
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
|
+
};
|
|
5
61
|
export type CredentialInfo = {
|
|
6
62
|
credentials: Array<{
|
|
7
63
|
connections?: null | number;
|
|
@@ -17,8 +73,58 @@ export type CredentialInfo = {
|
|
|
17
73
|
uuid: string;
|
|
18
74
|
};
|
|
19
75
|
export declare function listCredentials(ctx: Pick<ResourceCtx, 'data' | 'platform'>, appIdentity: string, addonIdentity?: string, options?: ListCredentialsOptions): Promise<CredentialInfo[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Looks up a Postgres attachment by name under an app, resolves its add-on,
|
|
78
|
+
* and deletes the attachment.
|
|
79
|
+
*
|
|
80
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved add-on) if the
|
|
81
|
+
* attachment's database isn't an Advanced-tier database.
|
|
82
|
+
*
|
|
83
|
+
* If provided, `beforeDelete` runs after resolution/tier-checking but before
|
|
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.
|
|
88
|
+
*/
|
|
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>;
|
|
99
|
+
/**
|
|
100
|
+
* Resolves a source Postgres database by add-on identity, attaches it
|
|
101
|
+
* to an app, and fetches the latest release version.
|
|
102
|
+
*
|
|
103
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved add-on) if the
|
|
104
|
+
* database isn't an Advanced-tier database.
|
|
105
|
+
*
|
|
106
|
+
* If provided, `onAttached` fires once the attachment is created but before
|
|
107
|
+
* the release version fetch.
|
|
108
|
+
*/
|
|
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>;
|
|
20
120
|
export declare const postgresDatabaseExtensions: import("../../core/extend-resource.js").ResourceExtension<"data", "postgresDatabase", {
|
|
121
|
+
createAttachment: (appIdentity: string, addonIdentity: string, body?: CreateAttachmentBody, options?: CreateAttachmentOptions) => Promise<CreateAttachmentResult>;
|
|
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>;
|
|
21
125
|
listCredentials: (appIdentity: string, addonIdentity?: string, options?: ListCredentialsOptions) => Promise<CredentialInfo[]>;
|
|
22
126
|
}>;
|
|
127
|
+
export { AddonAppMismatchError, AddonConfirmationRequiredError, AddonDestroyFailedError, AddonNotFoundError, } from '../platform/add-on/errors.js';
|
|
128
|
+
export type { DeprovisioningAddOn, ResolvedAddOn } from '../platform/add-on/types.js';
|
|
23
129
|
export { type AttachmentFactors, parseAttachmentFactors, } from './internal/parse-attachment-factors.js';
|
|
24
|
-
export { findLegacyDatabase, getPostgresServiceName, isAdvancedCredential, isAdvancedDatabase, isEssentialDatabase, isEssentialTier, isLegacyDatabase, isLegacyEssentialDatabase, isPostgresAddon, } from './internal/postgres-utils.js';
|
|
130
|
+
export { findLegacyDatabase, getPostgresServiceName, isAdvancedCredential, isAdvancedDatabase, isEssentialDatabase, isEssentialTier, isLegacyDatabase, isLegacyEssentialDatabase, isPostgresAddon, NotAdvancedDatabaseError, } from './internal/postgres-utils.js';
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { HerokuApiError, NotFoundError } from '@heroku/heroku-fetch';
|
|
1
2
|
import { extendResource } from '../../core/extend-resource.js';
|
|
2
3
|
import { withSignal } from '../../core/with-signal.js';
|
|
4
|
+
import { destroyAndWait } from '../platform/add-on/destroy-and-wait.js';
|
|
5
|
+
import { AddonAppMismatchError, AddonConfirmationRequiredError, AddonDestroyFailedError, AddonNotFoundError, } from '../platform/add-on/errors.js';
|
|
6
|
+
import { resolveAddonInternal } from '../platform/add-on/resolve.js';
|
|
7
|
+
import { getPostgresServiceName, isAdvancedDatabase, NotAdvancedDatabaseError } from './internal/postgres-utils.js';
|
|
3
8
|
import { resolvePgDatabase } from './internal/resolve-pg-database.js';
|
|
4
9
|
export async function listCredentials(ctx, appIdentity, addonIdentity, options = {}) {
|
|
5
10
|
options.signal?.throwIfAborted();
|
|
@@ -8,8 +13,209 @@ export async function listCredentials(ctx, appIdentity, addonIdentity, options =
|
|
|
8
13
|
const data = withSignal(ctx.data, options.signal);
|
|
9
14
|
return data.postgresDatabase.listCredentials(addon.id);
|
|
10
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Looks up a Postgres attachment by name under an app, resolves its add-on,
|
|
18
|
+
* and deletes the attachment.
|
|
19
|
+
*
|
|
20
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved add-on) if the
|
|
21
|
+
* attachment's database isn't an Advanced-tier database.
|
|
22
|
+
*
|
|
23
|
+
* If provided, `beforeDelete` runs after resolution/tier-checking but before
|
|
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.
|
|
28
|
+
*/
|
|
29
|
+
export async function deleteAttachment(ctx, appIdentity, attachmentName, options = {}) {
|
|
30
|
+
const { beforeDelete, onDetached, signal } = options;
|
|
31
|
+
signal?.throwIfAborted();
|
|
32
|
+
// Bind the signal so the lookup, delete, and release-list requests abort in flight.
|
|
33
|
+
const platform = withSignal(ctx.platform, signal);
|
|
34
|
+
const attachment = await platform.addOnAttachment.infoByApp(appIdentity, attachmentName);
|
|
35
|
+
const addon = await resolveAddonInternal(platform, attachment.addon.name, { addonService: getPostgresServiceName() });
|
|
36
|
+
if (!isAdvancedDatabase(addon)) {
|
|
37
|
+
throw new NotAdvancedDatabaseError(addon, `You can only detach Advanced-tier databases with postgresDatabase.deleteAttachment(). ${addon.name} isn't an Advanced-tier database.`);
|
|
38
|
+
}
|
|
39
|
+
await beforeDelete?.({ addon, attachment });
|
|
40
|
+
signal?.throwIfAborted();
|
|
41
|
+
const deletedAttachment = await platform.addOnAttachment.delete(attachment.id);
|
|
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 };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolves a source Postgres database by add-on identity, attaches it
|
|
87
|
+
* to an app, and fetches the latest release version.
|
|
88
|
+
*
|
|
89
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved add-on) if the
|
|
90
|
+
* database isn't an Advanced-tier database.
|
|
91
|
+
*
|
|
92
|
+
* If provided, `onAttached` fires once the attachment is created but before
|
|
93
|
+
* the release version fetch.
|
|
94
|
+
*/
|
|
95
|
+
export async function createAttachment(ctx, appIdentity, addonIdentity, body = {}, options = {}) {
|
|
96
|
+
const { onAttached, signal } = options;
|
|
97
|
+
signal?.throwIfAborted();
|
|
98
|
+
// Bind the signal so the resolve, create, and release-list requests abort in flight.
|
|
99
|
+
const platform = withSignal(ctx.platform, signal);
|
|
100
|
+
// Resolve scoped to the target app first. If the target app has no match, retry
|
|
101
|
+
// unscoped so attaching a database owned by a *different* app still resolves by
|
|
102
|
+
// its globally-unique name.
|
|
103
|
+
let addon;
|
|
104
|
+
try {
|
|
105
|
+
addon = await resolveAddonInternal(platform, addonIdentity, { addonService: getPostgresServiceName(), appIdentity });
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (!(error instanceof NotFoundError)) {
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
addon = await resolveAddonInternal(platform, addonIdentity, { addonService: getPostgresServiceName() });
|
|
112
|
+
}
|
|
113
|
+
if (!isAdvancedDatabase(addon)) {
|
|
114
|
+
throw new NotAdvancedDatabaseError(addon, `You can only attach Advanced-tier databases with postgresDatabase.createAttachment(). ${addon.name} isn't an Advanced-tier database.`);
|
|
115
|
+
}
|
|
116
|
+
let attachment;
|
|
117
|
+
try {
|
|
118
|
+
attachment = await platform.addOnAttachment.create({
|
|
119
|
+
...body,
|
|
120
|
+
addon: addon.name,
|
|
121
|
+
app: appIdentity,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (error instanceof HerokuApiError && error.id === 'confirmation_required') {
|
|
126
|
+
throw new AddonConfirmationRequiredError(error.message);
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
await onAttached?.(attachment);
|
|
131
|
+
signal?.throwIfAborted();
|
|
132
|
+
const releases = await platform
|
|
133
|
+
.withHeaders({ Range: 'version ..; max=1, order=desc' })
|
|
134
|
+
.release.list(appIdentity);
|
|
135
|
+
return { addon, attachment, releaseVersion: releases[0]?.version };
|
|
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
|
+
}
|
|
11
212
|
export const postgresDatabaseExtensions = extendResource('data', 'postgresDatabase', ctx => ({
|
|
213
|
+
createAttachment: (appIdentity, addonIdentity, body, options) => createAttachment(ctx, appIdentity, addonIdentity, body, options),
|
|
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),
|
|
12
217
|
listCredentials: (appIdentity, addonIdentity, options) => listCredentials(ctx, appIdentity, addonIdentity, options),
|
|
13
218
|
}));
|
|
219
|
+
export { AddonAppMismatchError, AddonConfirmationRequiredError, AddonDestroyFailedError, AddonNotFoundError, } from '../platform/add-on/errors.js';
|
|
14
220
|
export { parseAttachmentFactors, } from './internal/parse-attachment-factors.js';
|
|
15
|
-
export { findLegacyDatabase, getPostgresServiceName, isAdvancedCredential, isAdvancedDatabase, isEssentialDatabase, isEssentialTier, isLegacyDatabase, isLegacyEssentialDatabase, isPostgresAddon, } from './internal/postgres-utils.js';
|
|
221
|
+
export { findLegacyDatabase, getPostgresServiceName, isAdvancedCredential, isAdvancedDatabase, isEssentialDatabase, isEssentialTier, isLegacyDatabase, isLegacyEssentialDatabase, isPostgresAddon, NotAdvancedDatabaseError, } from './internal/postgres-utils.js';
|