@heroku/sdk 0.12.1 → 0.12.3
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/README.md +1 -0
- package/dist/core/dispatcher.js +13 -2
- package/dist/resources/dashboard-backend/favorite/add-by-app-name.d.ts +7 -0
- package/dist/resources/dashboard-backend/favorite/add-by-app-name.js +33 -0
- package/dist/resources/dashboard-backend/favorite/errors.d.ts +16 -0
- package/dist/resources/dashboard-backend/favorite/errors.js +32 -0
- package/dist/resources/dashboard-backend/favorite/index.d.ts +8 -0
- package/dist/resources/dashboard-backend/favorite/index.js +10 -0
- package/dist/resources/dashboard-backend/favorite/remove-by-app-name.d.ts +4 -0
- package/dist/resources/dashboard-backend/favorite/remove-by-app-name.js +15 -0
- 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 +16 -7
- 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 +48 -1
- package/dist/resources/data/postgres-database.js +84 -1
- package/dist/resources/data/postgres-quota.d.ts +45 -0
- package/dist/resources/data/postgres-quota.js +61 -0
- package/dist/resources/data/postgres.d.ts +27 -0
- package/dist/resources/data/postgres.js +31 -0
- package/dist/resources/extensions/dashboard-backend.d.ts +1 -0
- package/dist/resources/extensions/dashboard-backend.js +1 -0
- package/dist/resources/extensions/data.d.ts +2 -0
- package/dist/resources/extensions/data.js +2 -0
- package/dist/resources/extensions/platform.d.ts +2 -0
- package/dist/resources/extensions/platform.js +2 -0
- package/dist/resources/platform/app-collaborator/index.d.ts +5 -0
- package/dist/resources/platform/app-collaborator/index.js +6 -0
- package/dist/resources/platform/app-collaborator/remove-self.d.ts +10 -0
- package/dist/resources/platform/app-collaborator/remove-self.js +21 -0
- package/dist/resources/platform/team-app/index.d.ts +5 -0
- package/dist/resources/platform/team-app/index.js +6 -0
- package/dist/resources/platform/team-app/join-self.d.ts +7 -0
- package/dist/resources/platform/team-app/join-self.js +9 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -133,6 +133,7 @@ const favorites = await dashboardBackend.favorite.list({ type: 'app' })
|
|
|
133
133
|
| `createRepositoriesClient`, `RepositoriesClient` | `@heroku/sdk/repositories` |
|
|
134
134
|
| `createRepositoriesApiClient`, `RepositoriesApiClient` | `@heroku/sdk/repositories-api` |
|
|
135
135
|
| `appExtensions`, `dynoExtensions`, … | `@heroku/sdk/extensions/platform` |
|
|
136
|
+
| `favoriteExtensions`, `addByAppName`, `removeByAppName`, favorite errors and types | `@heroku/sdk/extensions/dashboard-backend` |
|
|
136
137
|
| `databaseExtensions`, … | `@heroku/sdk/extensions/data` |
|
|
137
138
|
|
|
138
139
|
## Development
|
package/dist/core/dispatcher.js
CHANGED
|
@@ -7,11 +7,22 @@ function hasCallerRangeHeader(requestOptions) {
|
|
|
7
7
|
return false;
|
|
8
8
|
return Object.keys(requestOptions.headers).some(k => k.toLowerCase() === 'range');
|
|
9
9
|
}
|
|
10
|
+
function redactEmailPathSegments(path) {
|
|
11
|
+
return path.split('/').map(segment => {
|
|
12
|
+
try {
|
|
13
|
+
return decodeURIComponent(segment).includes('@') ? '[REDACTED]' : segment;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return segment;
|
|
17
|
+
}
|
|
18
|
+
}).join('/');
|
|
19
|
+
}
|
|
10
20
|
export async function dispatch(client, route, args, invocation, requestOptions) {
|
|
11
21
|
const placeholderCount = countPlaceholders(route.path);
|
|
12
22
|
const pathParams = args.slice(0, placeholderCount);
|
|
13
23
|
const remaining = args.slice(placeholderCount);
|
|
14
24
|
const path = interpolatePath(route.path, pathParams);
|
|
25
|
+
const debugPath = redactEmailPathSegments(path);
|
|
15
26
|
let body;
|
|
16
27
|
if (route.hasRequestBody && remaining.length > 0) {
|
|
17
28
|
body = remaining[0];
|
|
@@ -30,7 +41,7 @@ export async function dispatch(client, route, args, invocation, requestOptions)
|
|
|
30
41
|
effectiveOptions = { ...requestOptions, searchParams };
|
|
31
42
|
}
|
|
32
43
|
}
|
|
33
|
-
debug('%s %s %s -> %s hasBody=%s', invocation ?? 'dispatch', route.method, route.path,
|
|
44
|
+
debug('%s %s %s -> %s hasBody=%s', invocation ?? 'dispatch', route.method, route.path, debugPath, body !== undefined);
|
|
34
45
|
const response = await callMethod(client, route.method, path, body, effectiveOptions);
|
|
35
46
|
const contentLength = response.headers.get('content-length');
|
|
36
47
|
if (response.status === 204 || contentLength === '0') {
|
|
@@ -87,7 +98,7 @@ function callMethod(client, method, path, body, requestOptions) {
|
|
|
87
98
|
return requestOptions ? client.put(path, body, requestOptions) : client.put(path, body);
|
|
88
99
|
}
|
|
89
100
|
default: {
|
|
90
|
-
debug('unsupported HTTP method=%s path=%s', method, path);
|
|
101
|
+
debug('unsupported HTTP method=%s path=%s', method, redactEmailPathSegments(path));
|
|
91
102
|
throw new Error(`Unsupported HTTP method: ${method}`);
|
|
92
103
|
}
|
|
93
104
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FavoriteCreateResult } from '@heroku/types/dashboard-backend';
|
|
2
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
export type FavoriteOptions = {
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
};
|
|
6
|
+
/** Add an app to the current user's favorites by its exact app name. */
|
|
7
|
+
export declare function addByAppName(ctx: Pick<ResourceCtx, 'dashboardBackend'>, appName: string, options?: FavoriteOptions): Promise<FavoriteCreateResult | void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
2
|
+
import { isAbortError } from '../../../utils/is-abort-error.js';
|
|
3
|
+
import { AlreadyFavoriteError, AppNotFoundError, FavoriteCreateError } from './errors.js';
|
|
4
|
+
/** Add an app to the current user's favorites by its exact app name. */
|
|
5
|
+
export async function addByAppName(ctx, appName, options = {}) {
|
|
6
|
+
const { signal } = options;
|
|
7
|
+
signal?.throwIfAborted();
|
|
8
|
+
const dashboardBackend = withSignal(ctx.dashboardBackend, signal);
|
|
9
|
+
const favorites = await dashboardBackend.favorite.list({ type: 'app' });
|
|
10
|
+
signal?.throwIfAborted();
|
|
11
|
+
if (favorites.some(favorite => favorite.resource_name === appName)) {
|
|
12
|
+
throw new AlreadyFavoriteError(appName);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return await dashboardBackend.favorite.create({ resource_id: appName, type: 'app' });
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
// Preserve request cancellation, including custom abort reasons, but don't
|
|
19
|
+
// let an unrelated abort race mask the create request's actual failure.
|
|
20
|
+
if (isAbortError(error) || (signal?.aborted && error === signal.reason))
|
|
21
|
+
throw error;
|
|
22
|
+
if (isNotFound(error)) {
|
|
23
|
+
throw new AppNotFoundError(appName, error);
|
|
24
|
+
}
|
|
25
|
+
throw new FavoriteCreateError(appName, error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function isNotFound(error) {
|
|
29
|
+
return (typeof error === 'object'
|
|
30
|
+
&& error !== null
|
|
31
|
+
&& 'statusCode' in error
|
|
32
|
+
&& error.statusCode === 404);
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class AlreadyFavoriteError extends Error {
|
|
2
|
+
readonly appName: string;
|
|
3
|
+
constructor(appName: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class NotFavoriteError extends Error {
|
|
6
|
+
readonly appName: string;
|
|
7
|
+
constructor(appName: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class AppNotFoundError extends Error {
|
|
10
|
+
readonly appName: string;
|
|
11
|
+
constructor(appName: string, cause: unknown);
|
|
12
|
+
}
|
|
13
|
+
export declare class FavoriteCreateError extends Error {
|
|
14
|
+
readonly appName: string;
|
|
15
|
+
constructor(appName: string, cause: unknown);
|
|
16
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class AlreadyFavoriteError extends Error {
|
|
2
|
+
appName;
|
|
3
|
+
constructor(appName) {
|
|
4
|
+
super(`App ${appName} is already a favorite.`);
|
|
5
|
+
this.appName = appName;
|
|
6
|
+
this.name = 'AlreadyFavoriteError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class NotFavoriteError extends Error {
|
|
10
|
+
appName;
|
|
11
|
+
constructor(appName) {
|
|
12
|
+
super(`App ${appName} is not a favorite.`);
|
|
13
|
+
this.appName = appName;
|
|
14
|
+
this.name = 'NotFavoriteError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class AppNotFoundError extends Error {
|
|
18
|
+
appName;
|
|
19
|
+
constructor(appName, cause) {
|
|
20
|
+
super(`Couldn't find app ${appName}.`, { cause });
|
|
21
|
+
this.appName = appName;
|
|
22
|
+
this.name = 'AppNotFoundError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class FavoriteCreateError extends Error {
|
|
26
|
+
appName;
|
|
27
|
+
constructor(appName, cause) {
|
|
28
|
+
super(`Failed to add app ${appName} as a favorite.`, { cause });
|
|
29
|
+
this.appName = appName;
|
|
30
|
+
this.name = 'FavoriteCreateError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FavoriteOptions } from './add-by-app-name.js';
|
|
2
|
+
export { addByAppName, type FavoriteOptions } from './add-by-app-name.js';
|
|
3
|
+
export { AlreadyFavoriteError, AppNotFoundError, FavoriteCreateError, NotFavoriteError, } from './errors.js';
|
|
4
|
+
export { removeByAppName } from './remove-by-app-name.js';
|
|
5
|
+
export declare const favoriteExtensions: import("../../../core/extend-resource.js").ResourceExtension<"dashboardBackend", "favorite", {
|
|
6
|
+
addByAppName: (appName: string, options?: FavoriteOptions) => Promise<void | import("@heroku/types/dashboard-backend").FavoriteCreateResult>;
|
|
7
|
+
removeByAppName: (appName: string, options?: FavoriteOptions) => Promise<void>;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { addByAppName } from './add-by-app-name.js';
|
|
3
|
+
import { removeByAppName } from './remove-by-app-name.js';
|
|
4
|
+
export { addByAppName } from './add-by-app-name.js';
|
|
5
|
+
export { AlreadyFavoriteError, AppNotFoundError, FavoriteCreateError, NotFavoriteError, } from './errors.js';
|
|
6
|
+
export { removeByAppName } from './remove-by-app-name.js';
|
|
7
|
+
export const favoriteExtensions = extendResource('dashboardBackend', 'favorite', ctx => ({
|
|
8
|
+
addByAppName: (appName, options) => addByAppName(ctx, appName, options),
|
|
9
|
+
removeByAppName: (appName, options) => removeByAppName(ctx, appName, options),
|
|
10
|
+
}));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
2
|
+
import type { FavoriteOptions } from './add-by-app-name.js';
|
|
3
|
+
/** Remove an app from the current user's favorites by its exact app name. */
|
|
4
|
+
export declare function removeByAppName(ctx: Pick<ResourceCtx, 'dashboardBackend'>, appName: string, options?: FavoriteOptions): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
2
|
+
import { NotFavoriteError } from './errors.js';
|
|
3
|
+
/** Remove an app from the current user's favorites by its exact app name. */
|
|
4
|
+
export async function removeByAppName(ctx, appName, options = {}) {
|
|
5
|
+
const { signal } = options;
|
|
6
|
+
signal?.throwIfAborted();
|
|
7
|
+
const dashboardBackend = withSignal(ctx.dashboardBackend, signal);
|
|
8
|
+
const favorites = await dashboardBackend.favorite.list({ type: 'app' });
|
|
9
|
+
signal?.throwIfAborted();
|
|
10
|
+
const favorite = favorites.find(candidate => candidate.resource_name === appName);
|
|
11
|
+
if (!favorite) {
|
|
12
|
+
throw new NotFavoriteError(appName);
|
|
13
|
+
}
|
|
14
|
+
await dashboardBackend.favorite.delete(favorite.id);
|
|
15
|
+
}
|
|
@@ -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,20 +1,29 @@
|
|
|
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
|
+
* Thrown when an add-on is not an Advanced-tier database. Carries the add-on
|
|
8
|
+
* so callers that need its identity don't need a second resolve call.
|
|
9
|
+
*/
|
|
10
|
+
export declare class NotAdvancedDatabaseError extends Error {
|
|
11
|
+
readonly addon: AddOn | ResolvedAddOnAttachment['addon'];
|
|
12
|
+
readonly id = "not_advanced_database";
|
|
13
|
+
constructor(addon: AddOn | ResolvedAddOnAttachment['addon'], message: string);
|
|
14
|
+
get body(): {
|
|
15
|
+
id: string;
|
|
16
|
+
message: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
6
19
|
/**
|
|
7
20
|
* Returns the first legacy database add-on on the app.
|
|
8
21
|
*/
|
|
9
22
|
export declare function findLegacyDatabase(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string): Promise<AddOn>;
|
|
10
23
|
export declare function isAdvancedCredential(credential: AdvancedCredentialInfo | NonAdvancedCredentialInfo): credential is AdvancedCredentialInfo;
|
|
11
24
|
export declare const getPostgresServiceName: () => string;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
export declare const isPostgresAddonInternal: (addon: AddOn | AddOnAttachmentWithInclusions["addon"]) => boolean;
|
|
16
|
-
export declare const isPostgresAddon: (addon: AddOn) => boolean;
|
|
17
|
-
export declare const isAdvancedDatabase: (addon: AddOn) => boolean;
|
|
25
|
+
export declare const isPostgresAddon: (addon: AddOn | ResolvedAddOnAttachment["addon"]) => boolean;
|
|
26
|
+
export declare const isAdvancedDatabase: (addon: AddOn | ResolvedAddOnAttachment["addon"]) => boolean;
|
|
18
27
|
export declare const isLegacyDatabase: (addon: AddOn) => boolean;
|
|
19
28
|
export declare const isLegacyEssentialDatabase: (addon: AddOn) => boolean;
|
|
20
29
|
export declare const isEssentialDatabase: (addon: AddOn) => 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,30 @@
|
|
|
1
|
+
import type { AddOnAttachment, AddOnAttachmentCreateOpts } from '@heroku/types/3.sdk';
|
|
1
2
|
import type { ResourceCtx } from '../../core/extend-resource.js';
|
|
3
|
+
import type { ResolvedAddOn } from '../platform/add-on/types.js';
|
|
2
4
|
export type ListCredentialsOptions = {
|
|
3
5
|
signal?: AbortSignal;
|
|
4
6
|
};
|
|
7
|
+
export type DeleteAttachmentOptions = {
|
|
8
|
+
beforeDelete?: (preview: {
|
|
9
|
+
addon: ResolvedAddOn;
|
|
10
|
+
attachment: AddOnAttachment;
|
|
11
|
+
}) => Promise<void> | void;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
};
|
|
14
|
+
export type DeleteAttachmentResult = {
|
|
15
|
+
addon: ResolvedAddOn;
|
|
16
|
+
attachment: AddOnAttachment;
|
|
17
|
+
};
|
|
18
|
+
export type CreateAttachmentBody = Omit<AddOnAttachmentCreateOpts, 'addon' | 'app'>;
|
|
19
|
+
export type CreateAttachmentOptions = {
|
|
20
|
+
onAttached?: (attachment: AddOnAttachment) => Promise<void> | void;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
};
|
|
23
|
+
export type CreateAttachmentResult = {
|
|
24
|
+
addon: ResolvedAddOn;
|
|
25
|
+
attachment: AddOnAttachment;
|
|
26
|
+
releaseVersion?: number;
|
|
27
|
+
};
|
|
5
28
|
export type CredentialInfo = {
|
|
6
29
|
credentials: Array<{
|
|
7
30
|
connections?: null | number;
|
|
@@ -17,8 +40,32 @@ export type CredentialInfo = {
|
|
|
17
40
|
uuid: string;
|
|
18
41
|
};
|
|
19
42
|
export declare function listCredentials(ctx: Pick<ResourceCtx, 'data' | 'platform'>, appIdentity: string, addonIdentity?: string, options?: ListCredentialsOptions): Promise<CredentialInfo[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Looks up a Postgres attachment by name under an app, resolves its add-on,
|
|
45
|
+
* and deletes the attachment.
|
|
46
|
+
*
|
|
47
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved add-on) if the
|
|
48
|
+
* attachment's database isn't an Advanced-tier database.
|
|
49
|
+
*
|
|
50
|
+
* If provided, `beforeDelete` runs after resolution/tier-checking but before
|
|
51
|
+
* the delete request fires. An abort during the hook prevents the delete.
|
|
52
|
+
*/
|
|
53
|
+
export declare function deleteAttachment(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string, attachmentName: string, options?: DeleteAttachmentOptions): Promise<DeleteAttachmentResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Resolves a source Postgres database by add-on identity, attaches it
|
|
56
|
+
* to an app, and fetches the latest release version.
|
|
57
|
+
*
|
|
58
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved add-on) if the
|
|
59
|
+
* database isn't an Advanced-tier database.
|
|
60
|
+
*
|
|
61
|
+
* If provided, `onAttached` fires once the attachment is created but before
|
|
62
|
+
* the release version fetch.
|
|
63
|
+
*/
|
|
64
|
+
export declare function createAttachment(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string, addonIdentity: string, body?: CreateAttachmentBody, options?: CreateAttachmentOptions): Promise<CreateAttachmentResult>;
|
|
20
65
|
export declare const postgresDatabaseExtensions: import("../../core/extend-resource.js").ResourceExtension<"data", "postgresDatabase", {
|
|
66
|
+
createAttachment: (appIdentity: string, addonIdentity: string, body?: CreateAttachmentBody, options?: CreateAttachmentOptions) => Promise<CreateAttachmentResult>;
|
|
67
|
+
deleteAttachment: (appIdentity: string, attachmentName: string, options?: DeleteAttachmentOptions) => Promise<DeleteAttachmentResult>;
|
|
21
68
|
listCredentials: (appIdentity: string, addonIdentity?: string, options?: ListCredentialsOptions) => Promise<CredentialInfo[]>;
|
|
22
69
|
}>;
|
|
23
70
|
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';
|
|
71
|
+
export { findLegacyDatabase, getPostgresServiceName, isAdvancedCredential, isAdvancedDatabase, isEssentialDatabase, isEssentialTier, isLegacyDatabase, isLegacyEssentialDatabase, isPostgresAddon, NotAdvancedDatabaseError, } from './internal/postgres-utils.js';
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { HerokuApiError } from '@heroku/heroku-fetch';
|
|
1
2
|
import { extendResource } from '../../core/extend-resource.js';
|
|
2
3
|
import { withSignal } from '../../core/with-signal.js';
|
|
4
|
+
import { AddonConfirmationRequiredError, AddonNotFoundError } from '../platform/add-on/errors.js';
|
|
5
|
+
import { resolveAddonInternal } from '../platform/add-on/resolve.js';
|
|
6
|
+
import { getPostgresServiceName, isAdvancedDatabase, NotAdvancedDatabaseError } from './internal/postgres-utils.js';
|
|
3
7
|
import { resolvePgDatabase } from './internal/resolve-pg-database.js';
|
|
4
8
|
export async function listCredentials(ctx, appIdentity, addonIdentity, options = {}) {
|
|
5
9
|
options.signal?.throwIfAborted();
|
|
@@ -8,8 +12,87 @@ export async function listCredentials(ctx, appIdentity, addonIdentity, options =
|
|
|
8
12
|
const data = withSignal(ctx.data, options.signal);
|
|
9
13
|
return data.postgresDatabase.listCredentials(addon.id);
|
|
10
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Looks up a Postgres attachment by name under an app, resolves its add-on,
|
|
17
|
+
* and deletes the attachment.
|
|
18
|
+
*
|
|
19
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved add-on) if the
|
|
20
|
+
* attachment's database isn't an Advanced-tier database.
|
|
21
|
+
*
|
|
22
|
+
* If provided, `beforeDelete` runs after resolution/tier-checking but before
|
|
23
|
+
* the delete request fires. An abort during the hook prevents the delete.
|
|
24
|
+
*/
|
|
25
|
+
export async function deleteAttachment(ctx, appIdentity, attachmentName, options = {}) {
|
|
26
|
+
const { beforeDelete, signal } = options;
|
|
27
|
+
signal?.throwIfAborted();
|
|
28
|
+
// Bind the signal so the lookup and delete requests abort in flight.
|
|
29
|
+
const platform = withSignal(ctx.platform, signal);
|
|
30
|
+
const attachment = await platform.addOnAttachment.infoByApp(appIdentity, attachmentName);
|
|
31
|
+
const addon = await resolveAddonInternal(platform, attachment.addon.name, { addonService: getPostgresServiceName() });
|
|
32
|
+
if (!isAdvancedDatabase(addon)) {
|
|
33
|
+
throw new NotAdvancedDatabaseError(addon, `You can only detach Advanced-tier databases with postgresDatabase.deleteAttachment(). ${addon.name} isn't an Advanced-tier database.`);
|
|
34
|
+
}
|
|
35
|
+
await beforeDelete?.({ addon, attachment });
|
|
36
|
+
signal?.throwIfAborted();
|
|
37
|
+
const deletedAttachment = await platform.addOnAttachment.delete(attachment.id);
|
|
38
|
+
return { addon, attachment: deletedAttachment };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolves a source Postgres database by add-on identity, attaches it
|
|
42
|
+
* to an app, and fetches the latest release version.
|
|
43
|
+
*
|
|
44
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved add-on) if the
|
|
45
|
+
* database isn't an Advanced-tier database.
|
|
46
|
+
*
|
|
47
|
+
* If provided, `onAttached` fires once the attachment is created but before
|
|
48
|
+
* the release version fetch.
|
|
49
|
+
*/
|
|
50
|
+
export async function createAttachment(ctx, appIdentity, addonIdentity, body = {}, options = {}) {
|
|
51
|
+
const { onAttached, signal } = options;
|
|
52
|
+
signal?.throwIfAborted();
|
|
53
|
+
// Bind the signal so the resolve, create, and release-list requests abort in flight.
|
|
54
|
+
const platform = withSignal(ctx.platform, signal);
|
|
55
|
+
// Resolve scoped to the target app first. If the target app has no match, retry
|
|
56
|
+
// unscoped so attaching a database owned by a *different* app still resolves by
|
|
57
|
+
// its globally-unique name.
|
|
58
|
+
let addon;
|
|
59
|
+
try {
|
|
60
|
+
addon = await resolveAddonInternal(platform, addonIdentity, { addonService: getPostgresServiceName(), appIdentity });
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (!(error instanceof AddonNotFoundError)) {
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
addon = await resolveAddonInternal(platform, addonIdentity, { addonService: getPostgresServiceName() });
|
|
67
|
+
}
|
|
68
|
+
if (!isAdvancedDatabase(addon)) {
|
|
69
|
+
throw new NotAdvancedDatabaseError(addon, `You can only attach Advanced-tier databases with postgresDatabase.createAttachment(). ${addon.name} isn't an Advanced-tier database.`);
|
|
70
|
+
}
|
|
71
|
+
let attachment;
|
|
72
|
+
try {
|
|
73
|
+
attachment = await platform.addOnAttachment.create({
|
|
74
|
+
...body,
|
|
75
|
+
addon: addon.name,
|
|
76
|
+
app: appIdentity,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error instanceof HerokuApiError && error.id === 'confirmation_required') {
|
|
81
|
+
throw new AddonConfirmationRequiredError(error.message);
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
await onAttached?.(attachment);
|
|
86
|
+
signal?.throwIfAborted();
|
|
87
|
+
const releases = await platform
|
|
88
|
+
.withHeaders({ Range: 'version ..; max=1, order=desc' })
|
|
89
|
+
.release.list(appIdentity);
|
|
90
|
+
return { addon, attachment, releaseVersion: releases[0]?.version };
|
|
91
|
+
}
|
|
11
92
|
export const postgresDatabaseExtensions = extendResource('data', 'postgresDatabase', ctx => ({
|
|
93
|
+
createAttachment: (appIdentity, addonIdentity, body, options) => createAttachment(ctx, appIdentity, addonIdentity, body, options),
|
|
94
|
+
deleteAttachment: (appIdentity, attachmentName, options) => deleteAttachment(ctx, appIdentity, attachmentName, options),
|
|
12
95
|
listCredentials: (appIdentity, addonIdentity, options) => listCredentials(ctx, appIdentity, addonIdentity, options),
|
|
13
96
|
}));
|
|
14
97
|
export { parseAttachmentFactors, } from './internal/parse-attachment-factors.js';
|
|
15
|
-
export { findLegacyDatabase, getPostgresServiceName, isAdvancedCredential, isAdvancedDatabase, isEssentialDatabase, isEssentialTier, isLegacyDatabase, isLegacyEssentialDatabase, isPostgresAddon, } from './internal/postgres-utils.js';
|
|
98
|
+
export { findLegacyDatabase, getPostgresServiceName, isAdvancedCredential, isAdvancedDatabase, isEssentialDatabase, isEssentialTier, isLegacyDatabase, isLegacyEssentialDatabase, isPostgresAddon, NotAdvancedDatabaseError, } from './internal/postgres-utils.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { PostgresQuotaInfoResult, PostgresQuotaListResult, PostgresQuotaUpdateOpts, PostgresQuotaUpdateResult } from '@heroku/types/data';
|
|
2
|
+
import type { ResourceCtx } from '../../core/extend-resource.js';
|
|
3
|
+
import type { ResolvedAddOn } from '../platform/add-on/types.js';
|
|
4
|
+
export type PostgresQuotaOptions = {
|
|
5
|
+
appIdentity?: string;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
};
|
|
8
|
+
export type ListPostgresQuotasResult = {
|
|
9
|
+
addon: ResolvedAddOn;
|
|
10
|
+
quotas: PostgresQuotaListResult;
|
|
11
|
+
};
|
|
12
|
+
export type GetPostgresQuotaResult = {
|
|
13
|
+
addon: ResolvedAddOn;
|
|
14
|
+
quota: PostgresQuotaInfoResult;
|
|
15
|
+
};
|
|
16
|
+
export type UpdatePostgresQuotaResult = {
|
|
17
|
+
addon: ResolvedAddOn;
|
|
18
|
+
quota: PostgresQuotaUpdateResult;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Lists quotas on an Advanced-tier Postgres database.
|
|
22
|
+
*
|
|
23
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved addon) if
|
|
24
|
+
* `addonIdentity` doesn't resolve to an Advanced-tier database.
|
|
25
|
+
*/
|
|
26
|
+
export declare function list(ctx: Pick<ResourceCtx, 'data' | 'platform'>, addonIdentity: string, options?: PostgresQuotaOptions): Promise<ListPostgresQuotasResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Looks up a single quota on an Advanced-tier Postgres database.
|
|
29
|
+
*
|
|
30
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved addon) if
|
|
31
|
+
* `addonIdentity` doesn't resolve to an Advanced-tier database.
|
|
32
|
+
*/
|
|
33
|
+
export declare function info(ctx: Pick<ResourceCtx, 'data' | 'platform'>, addonIdentity: string, quotaType: string, options?: PostgresQuotaOptions): Promise<GetPostgresQuotaResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Updates a quota's settings on an Advanced-tier Postgres database.
|
|
36
|
+
*
|
|
37
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved addon) if
|
|
38
|
+
* `addonIdentity` doesn't resolve to an Advanced-tier database.
|
|
39
|
+
*/
|
|
40
|
+
export declare function update(ctx: Pick<ResourceCtx, 'data' | 'platform'>, addonIdentity: string, quotaType: string, body: PostgresQuotaUpdateOpts, options?: PostgresQuotaOptions): Promise<UpdatePostgresQuotaResult>;
|
|
41
|
+
export declare const postgresQuotaExtensions: import("../../core/extend-resource.js").ResourceExtension<"data", "postgresQuota", {
|
|
42
|
+
info: (addonIdentity: string, quotaType: string, options?: PostgresQuotaOptions) => Promise<GetPostgresQuotaResult>;
|
|
43
|
+
list: (addonIdentity: string, options?: PostgresQuotaOptions) => Promise<ListPostgresQuotasResult>;
|
|
44
|
+
update: (addonIdentity: string, quotaType: string, body: PostgresQuotaUpdateOpts, options?: PostgresQuotaOptions) => Promise<UpdatePostgresQuotaResult>;
|
|
45
|
+
}>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { extendResource } from '../../core/extend-resource.js';
|
|
2
|
+
import { withSignal } from '../../core/with-signal.js';
|
|
3
|
+
import { resolveAddonInternal } from '../platform/add-on/resolve.js';
|
|
4
|
+
import { getPostgresServiceName, isAdvancedDatabase, NotAdvancedDatabaseError } from './internal/postgres-utils.js';
|
|
5
|
+
async function resolveTieredAddon(ctx, addonIdentity, options) {
|
|
6
|
+
const { appIdentity, signal } = options;
|
|
7
|
+
signal?.throwIfAborted();
|
|
8
|
+
// Bind the signal so the resolve request aborts in flight.
|
|
9
|
+
const platform = withSignal(ctx.platform, signal);
|
|
10
|
+
const addon = await resolveAddonInternal(platform, addonIdentity, {
|
|
11
|
+
addonService: getPostgresServiceName(), appIdentity,
|
|
12
|
+
});
|
|
13
|
+
if (!isAdvancedDatabase(addon)) {
|
|
14
|
+
throw new NotAdvancedDatabaseError(addon, `You can only manage quotas on Advanced-tier databases. ${addon.name} isn't an Advanced-tier database.`);
|
|
15
|
+
}
|
|
16
|
+
return addon;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Lists quotas on an Advanced-tier Postgres database.
|
|
20
|
+
*
|
|
21
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved addon) if
|
|
22
|
+
* `addonIdentity` doesn't resolve to an Advanced-tier database.
|
|
23
|
+
*/
|
|
24
|
+
export async function list(ctx, addonIdentity, options = {}) {
|
|
25
|
+
const addon = await resolveTieredAddon(ctx, addonIdentity, options);
|
|
26
|
+
// Bind the signal so the list request aborts in flight.
|
|
27
|
+
const data = withSignal(ctx.data, options.signal);
|
|
28
|
+
const quotas = await data.postgresQuota.list(addon.id);
|
|
29
|
+
return { addon, quotas };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Looks up a single quota on an Advanced-tier Postgres database.
|
|
33
|
+
*
|
|
34
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved addon) if
|
|
35
|
+
* `addonIdentity` doesn't resolve to an Advanced-tier database.
|
|
36
|
+
*/
|
|
37
|
+
export async function info(ctx, addonIdentity, quotaType, options = {}) {
|
|
38
|
+
const addon = await resolveTieredAddon(ctx, addonIdentity, options);
|
|
39
|
+
// Bind the signal so the info request aborts in flight.
|
|
40
|
+
const data = withSignal(ctx.data, options.signal);
|
|
41
|
+
const quota = await data.postgresQuota.info(addon.id, quotaType);
|
|
42
|
+
return { addon, quota };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Updates a quota's settings on an Advanced-tier Postgres database.
|
|
46
|
+
*
|
|
47
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved addon) if
|
|
48
|
+
* `addonIdentity` doesn't resolve to an Advanced-tier database.
|
|
49
|
+
*/
|
|
50
|
+
export async function update(ctx, addonIdentity, quotaType, body, options = {}) {
|
|
51
|
+
const addon = await resolveTieredAddon(ctx, addonIdentity, options);
|
|
52
|
+
// Bind the signal so the update request aborts in flight.
|
|
53
|
+
const data = withSignal(ctx.data, options.signal);
|
|
54
|
+
const quota = await data.postgresQuota.update(addon.id, quotaType, body);
|
|
55
|
+
return { addon, quota };
|
|
56
|
+
}
|
|
57
|
+
export const postgresQuotaExtensions = extendResource('data', 'postgresQuota', ctx => ({
|
|
58
|
+
info: (addonIdentity, quotaType, options) => info(ctx, addonIdentity, quotaType, options),
|
|
59
|
+
list: (addonIdentity, options) => list(ctx, addonIdentity, options),
|
|
60
|
+
update: (addonIdentity, quotaType, body, options) => update(ctx, addonIdentity, quotaType, body, options),
|
|
61
|
+
}));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PostgresRunUpgradeOpts, PostgresRunUpgradeResult } from '@heroku/types/data';
|
|
2
|
+
import type { ResourceCtx } from '../../core/extend-resource.js';
|
|
3
|
+
import type { ResolvedPgDatabase } from './internal/resolve-pg-database.js';
|
|
4
|
+
export type RunUpgradeOptions = {
|
|
5
|
+
beforeRunUpgrade?: (preview: {
|
|
6
|
+
addon: ResolvedPgDatabase;
|
|
7
|
+
currentVersion: string;
|
|
8
|
+
}) => Promise<void> | void;
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
};
|
|
11
|
+
export type RunUpgradeResult = {
|
|
12
|
+
addon: ResolvedPgDatabase;
|
|
13
|
+
result: PostgresRunUpgradeResult;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Resolves a Postgres database by attachment identity and runs a version upgrade.
|
|
17
|
+
*
|
|
18
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved add-on) if the
|
|
19
|
+
* database isn't an Advanced-tier database. If provided, `beforeRunUpgrade`
|
|
20
|
+
* runs after resolution/tier-checking (and the current-version lookup) but
|
|
21
|
+
* before the upgrade request fires; an abort during the hook prevents the
|
|
22
|
+
* upgrade.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runUpgrade(ctx: Pick<ResourceCtx, 'data' | 'platform'>, appIdentity: string, addonIdentity?: string, body?: PostgresRunUpgradeOpts, options?: RunUpgradeOptions): Promise<RunUpgradeResult>;
|
|
25
|
+
export declare const postgresExtensions: import("../../core/extend-resource.js").ResourceExtension<"data", "postgres", {
|
|
26
|
+
runUpgrade: (appIdentity: string, addonIdentity?: string, body?: PostgresRunUpgradeOpts, options?: RunUpgradeOptions) => Promise<RunUpgradeResult>;
|
|
27
|
+
}>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { extendResource } from '../../core/extend-resource.js';
|
|
2
|
+
import { withSignal } from '../../core/with-signal.js';
|
|
3
|
+
import { isAdvancedDatabase, NotAdvancedDatabaseError } from './internal/postgres-utils.js';
|
|
4
|
+
import { resolvePgDatabase } from './internal/resolve-pg-database.js';
|
|
5
|
+
/**
|
|
6
|
+
* Resolves a Postgres database by attachment identity and runs a version upgrade.
|
|
7
|
+
*
|
|
8
|
+
* Throws `NotAdvancedDatabaseError` (carrying the resolved add-on) if the
|
|
9
|
+
* database isn't an Advanced-tier database. If provided, `beforeRunUpgrade`
|
|
10
|
+
* runs after resolution/tier-checking (and the current-version lookup) but
|
|
11
|
+
* before the upgrade request fires; an abort during the hook prevents the
|
|
12
|
+
* upgrade.
|
|
13
|
+
*/
|
|
14
|
+
export async function runUpgrade(ctx, appIdentity, addonIdentity, body = {}, options = {}) {
|
|
15
|
+
const { beforeRunUpgrade, signal } = options;
|
|
16
|
+
signal?.throwIfAborted();
|
|
17
|
+
const addon = await resolvePgDatabase(ctx, appIdentity, { input: addonIdentity, signal });
|
|
18
|
+
if (!isAdvancedDatabase(addon)) {
|
|
19
|
+
throw new NotAdvancedDatabaseError(addon, `You can only use postgres.runUpgrade() on Advanced-tier databases. ${addon.name} isn't an Advanced-tier database.`);
|
|
20
|
+
}
|
|
21
|
+
// Bind the signal so the info and upgrade requests abort in flight.
|
|
22
|
+
const data = withSignal(ctx.data, signal);
|
|
23
|
+
const { version: currentVersion } = await data.postgres.info(addon.id);
|
|
24
|
+
await beforeRunUpgrade?.({ addon, currentVersion });
|
|
25
|
+
signal?.throwIfAborted();
|
|
26
|
+
const result = await data.postgres.runUpgrade(addon.id, body);
|
|
27
|
+
return { addon, result };
|
|
28
|
+
}
|
|
29
|
+
export const postgresExtensions = extendResource('data', 'postgres', ctx => ({
|
|
30
|
+
runUpgrade: (appIdentity, addonIdentity, body, options) => runUpgrade(ctx, appIdentity, addonIdentity, body, options),
|
|
31
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { addByAppName, AlreadyFavoriteError, AppNotFoundError, FavoriteCreateError, favoriteExtensions, type FavoriteOptions, NotFavoriteError, removeByAppName, } from '../dashboard-backend/favorite/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { addByAppName, AlreadyFavoriteError, AppNotFoundError, FavoriteCreateError, favoriteExtensions, NotFavoriteError, removeByAppName, } from '../dashboard-backend/favorite/index.js';
|
|
@@ -2,6 +2,8 @@ export { databaseCredentialExtensions } from '../data/database-credential/index.
|
|
|
2
2
|
export { databaseExtensions } from '../data/database.js';
|
|
3
3
|
export { maintenanceExtensions } from '../data/maintenance.js';
|
|
4
4
|
export { postgresDatabaseExtensions } from '../data/postgres-database.js';
|
|
5
|
+
export { postgresQuotaExtensions } from '../data/postgres-quota.js';
|
|
6
|
+
export { postgresExtensions } from '../data/postgres.js';
|
|
5
7
|
export { redisExtensions } from '../data/redis/index.js';
|
|
6
8
|
export { type TransferSchedule, transferScheduleExtensions } from '../data/transfer-schedule.js';
|
|
7
9
|
export { backupExtensions, type CaptureAndWaitOptions, type RestoreAndWaitOptions, restoreExtensions, transferExtensions, TransferFailedError, TransferTimeoutError, type WaitForTransferOptions, } from '../data/transfer/index.js';
|
|
@@ -2,6 +2,8 @@ export { databaseCredentialExtensions } from '../data/database-credential/index.
|
|
|
2
2
|
export { databaseExtensions } from '../data/database.js';
|
|
3
3
|
export { maintenanceExtensions } from '../data/maintenance.js';
|
|
4
4
|
export { postgresDatabaseExtensions } from '../data/postgres-database.js';
|
|
5
|
+
export { postgresQuotaExtensions } from '../data/postgres-quota.js';
|
|
6
|
+
export { postgresExtensions } from '../data/postgres.js';
|
|
5
7
|
export { redisExtensions } from '../data/redis/index.js';
|
|
6
8
|
export { transferScheduleExtensions } from '../data/transfer-schedule.js';
|
|
7
9
|
export { backupExtensions, restoreExtensions, transferExtensions, TransferFailedError, TransferTimeoutError, } from '../data/transfer/index.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { accountExtensions, type AccountWithDelinquency, type GetAccountWithDelinquencyOptions } from '../platform/account.js';
|
|
2
2
|
export { addOnAttachmentExtensions } from '../platform/add-on-attachment/index.js';
|
|
3
3
|
export { addOnExtensions } from '../platform/add-on/index.js';
|
|
4
|
+
export { appCollaboratorExtensions, type BeforeRemove, type RemoveSelfOptions, } from '../platform/app-collaborator/index.js';
|
|
4
5
|
export { appExtensions, type AppInfo, type DiffAppsOptions, type DiffRow, type PipelineCouplingDetail, } from '../platform/app/index.js';
|
|
5
6
|
export { containerExtensions, type DockerReleasesBatchUpdateOpts, NotAContainerAppError, type ReleaseDockerImagesResult, type RemoveProcessTypesOpts, } from '../platform/container/index.js';
|
|
6
7
|
export { type DelinquencyResource, type DelinquencySchedule, formatDelinquencyWarning, } from '../platform/delinquency.js';
|
|
@@ -14,6 +15,7 @@ export { type ResolveRepoNameOptions, reviewAppConfigExtensions } from '../platf
|
|
|
14
15
|
export { privateToShield, shieldToPrivate } from '../platform/shield.js';
|
|
15
16
|
export { createAndAssociate, type CreateAndAssociateOptions, sniEndpointExtensions } from '../platform/sni-endpoint/index.js';
|
|
16
17
|
export { spaceExtensions, SpaceNotAllocatedError, SpaceNotReadyError, type SpaceOptions, type SpaceWithNat, waitForAllocated, type WaitForAllocatedOptions, } from '../platform/space/index.js';
|
|
18
|
+
export { type JoinSelfOptions, teamAppExtensions } from '../platform/team-app/index.js';
|
|
17
19
|
export { type GetTeamWithDelinquencyOptions, teamExtensions, type TeamWithDelinquency } from '../platform/team.js';
|
|
18
20
|
export { telemetryDrainExtensions, type TelemetryDrainOptions, type TelemetryDrainTarget } from '../platform/telemetry-drain/index.js';
|
|
19
21
|
export { testRunExtensions, TestRunNotReadyError, type TestRunStatus, waitForState, type WaitForStateOptions, } from '../platform/test-run/index.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { accountExtensions } from '../platform/account.js';
|
|
2
2
|
export { addOnAttachmentExtensions } from '../platform/add-on-attachment/index.js';
|
|
3
3
|
export { addOnExtensions } from '../platform/add-on/index.js';
|
|
4
|
+
export { appCollaboratorExtensions, } from '../platform/app-collaborator/index.js';
|
|
4
5
|
export { appExtensions, } from '../platform/app/index.js';
|
|
5
6
|
export { containerExtensions, NotAContainerAppError, } from '../platform/container/index.js';
|
|
6
7
|
export { formatDelinquencyWarning, } from '../platform/delinquency.js';
|
|
@@ -14,6 +15,7 @@ export { reviewAppConfigExtensions } from '../platform/review-app-config/index.j
|
|
|
14
15
|
export { privateToShield, shieldToPrivate } from '../platform/shield.js';
|
|
15
16
|
export { createAndAssociate, sniEndpointExtensions } from '../platform/sni-endpoint/index.js';
|
|
16
17
|
export { spaceExtensions, SpaceNotAllocatedError, SpaceNotReadyError, waitForAllocated, } from '../platform/space/index.js';
|
|
18
|
+
export { teamAppExtensions } from '../platform/team-app/index.js';
|
|
17
19
|
export { teamExtensions } from '../platform/team.js';
|
|
18
20
|
export { telemetryDrainExtensions } from '../platform/telemetry-drain/index.js';
|
|
19
21
|
export { testRunExtensions, TestRunNotReadyError, waitForState, } from '../platform/test-run/index.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RemoveSelfOptions } from './remove-self.js';
|
|
2
|
+
export { type BeforeRemove, removeSelf, type RemoveSelfOptions } from './remove-self.js';
|
|
3
|
+
export declare const appCollaboratorExtensions: import("../../../core/extend-resource.js").ResourceExtension<"platform", "appCollaborator", {
|
|
4
|
+
removeSelf: (appIdentity: string, options?: RemoveSelfOptions) => Promise<import("@heroku/types/3.sdk").Collaborator>;
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { removeSelf } from './remove-self.js';
|
|
3
|
+
export { removeSelf } from './remove-self.js';
|
|
4
|
+
export const appCollaboratorExtensions = extendResource('platform', 'appCollaborator', ctx => ({
|
|
5
|
+
removeSelf: (appIdentity, options) => removeSelf(ctx, appIdentity, options),
|
|
6
|
+
}));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Collaborator } from '@heroku/types/3.sdk';
|
|
2
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
import type { AccountWithDelinquency } from '../account.js';
|
|
4
|
+
export type BeforeRemove = (details: AccountWithDelinquency) => Promise<void> | void;
|
|
5
|
+
export type RemoveSelfOptions = {
|
|
6
|
+
beforeRemove?: BeforeRemove;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
};
|
|
9
|
+
/** Remove the authenticated account from an app's collaborator list. */
|
|
10
|
+
export declare function removeSelf(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string, options?: RemoveSelfOptions): Promise<Collaborator>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
2
|
+
import { getAccountWithDelinquency } from '../account.js';
|
|
3
|
+
/** Remove the authenticated account from an app's collaborator list. */
|
|
4
|
+
export async function removeSelf(ctx, appIdentity, options = {}) {
|
|
5
|
+
options.signal?.throwIfAborted();
|
|
6
|
+
let account;
|
|
7
|
+
if (options.beforeRemove) {
|
|
8
|
+
const details = await getAccountWithDelinquency(ctx, options.signal ? { signal: options.signal } : {});
|
|
9
|
+
options.signal?.throwIfAborted();
|
|
10
|
+
await options.beforeRemove(details);
|
|
11
|
+
options.signal?.throwIfAborted();
|
|
12
|
+
account = details.account;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
16
|
+
account = await platform.account.info();
|
|
17
|
+
options.signal?.throwIfAborted();
|
|
18
|
+
}
|
|
19
|
+
// Keep the email raw: the generated route encodes path parameters once.
|
|
20
|
+
return withSignal(ctx.platform, options.signal).collaborator.delete(appIdentity, account.email);
|
|
21
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { JoinSelfOptions } from './join-self.js';
|
|
2
|
+
export { joinSelf, type JoinSelfOptions } from './join-self.js';
|
|
3
|
+
export declare const teamAppExtensions: import("../../../core/extend-resource.js").ResourceExtension<"platform", "teamApp", {
|
|
4
|
+
joinSelf: (appIdentity: string, options?: JoinSelfOptions) => Promise<import("@heroku/types/3.sdk").TeamAppCollaborator>;
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { joinSelf } from './join-self.js';
|
|
3
|
+
export { joinSelf } from './join-self.js';
|
|
4
|
+
export const teamAppExtensions = extendResource('platform', 'teamApp', ctx => ({
|
|
5
|
+
joinSelf: (appIdentity, options) => joinSelf(ctx, appIdentity, options),
|
|
6
|
+
}));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TeamAppCollaborator } from '@heroku/types/3.sdk';
|
|
2
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
export type JoinSelfOptions = {
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
};
|
|
6
|
+
/** Add the authenticated account as a collaborator on a team app. */
|
|
7
|
+
export declare function joinSelf(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string, options?: JoinSelfOptions): Promise<TeamAppCollaborator>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
2
|
+
/** Add the authenticated account as a collaborator on a team app. */
|
|
3
|
+
export async function joinSelf(ctx, appIdentity, options = {}) {
|
|
4
|
+
options.signal?.throwIfAborted();
|
|
5
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
6
|
+
const account = await platform.account.info();
|
|
7
|
+
options.signal?.throwIfAborted();
|
|
8
|
+
return platform.teamAppCollaborator.create(appIdentity, { user: account.email });
|
|
9
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.12.
|
|
1
|
+
export declare const SDK_VERSION = "0.12.3";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated from package.json by scripts/write-version.mjs (build prebuild). Do not edit.
|
|
2
|
-
export const SDK_VERSION = '0.12.
|
|
2
|
+
export const SDK_VERSION = '0.12.3';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroku/sdk",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "Heroku SDK",
|
|
5
5
|
"author": "Heroku",
|
|
6
6
|
"type": "module",
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
"types": "./dist/resources/extensions/platform.d.ts",
|
|
43
43
|
"default": "./dist/resources/extensions/platform.js"
|
|
44
44
|
},
|
|
45
|
+
"./extensions/dashboard-backend": {
|
|
46
|
+
"types": "./dist/resources/extensions/dashboard-backend.d.ts",
|
|
47
|
+
"default": "./dist/resources/extensions/dashboard-backend.js"
|
|
48
|
+
},
|
|
45
49
|
"./extensions/data": {
|
|
46
50
|
"types": "./dist/resources/extensions/data.d.ts",
|
|
47
51
|
"default": "./dist/resources/extensions/data.js"
|