@heroku/sdk 0.8.0 → 0.8.1
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 +10 -0
- package/dist/resources/data/internal/postgres-utils.js +17 -0
- package/dist/resources/data/internal/resolve-pg-database.d.ts +8 -0
- package/dist/resources/data/internal/resolve-pg-database.js +15 -45
- package/dist/resources/data/transfer-schedule.d.ts +2 -2
- package/dist/resources/data/transfer-schedule.js +14 -5
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AddOn } from '@heroku/types/3.sdk';
|
|
2
|
+
import { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
/**
|
|
4
|
+
* Returns an arbitrary legacy database add-on based on the provided app name.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getArbitraryLegacyDB(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string): Promise<AddOn>;
|
|
7
|
+
export declare const getAddonService: () => string;
|
|
8
|
+
export declare const isPostgresAddon: (addon: AddOn) => boolean;
|
|
9
|
+
export declare const isAdvancedDatabase: (addon: AddOn) => boolean;
|
|
10
|
+
export declare const isLegacyDatabase: (addon: AddOn) => boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import createDebug from 'debug';
|
|
2
|
+
const debug = createDebug('postgres-utils');
|
|
3
|
+
/**
|
|
4
|
+
* Returns an arbitrary legacy database add-on based on the provided app name.
|
|
5
|
+
*/
|
|
6
|
+
export async function getArbitraryLegacyDB(ctx, appIdentity) {
|
|
7
|
+
debug(`fetching arbitrary legacy database on ${appIdentity}`);
|
|
8
|
+
const addons = await ctx.platform.addOn.listByApp(appIdentity);
|
|
9
|
+
const addon = addons.find(a => a.app.name === appIdentity && isLegacyDatabase(a));
|
|
10
|
+
if (!addon)
|
|
11
|
+
throw new Error(`No Heroku Postgres legacy database on ${appIdentity}`);
|
|
12
|
+
return addon;
|
|
13
|
+
}
|
|
14
|
+
export const getAddonService = () => process.env.HEROKU_POSTGRESQL_ADDON_NAME || process.env.HEROKU_DATA_SERVICE || 'heroku-postgresql';
|
|
15
|
+
export const isPostgresAddon = (addon) => addon.plan?.name?.split(':', 2)[0] === getAddonService();
|
|
16
|
+
export const isAdvancedDatabase = (addon) => isPostgresAddon(addon) && /^(advanced|performance)/.test(addon.plan?.name?.split(':', 2)[1] ?? '');
|
|
17
|
+
export const isLegacyDatabase = (addon) => isPostgresAddon(addon) && !isAdvancedDatabase(addon);
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
2
2
|
import type { ResolveAddonAttachmentOptions, ResolvedAddOnAttachment } from '../../platform/add-on-attachment/resolve.js';
|
|
3
3
|
export type ResolvedPgDatabase = ResolvedAddOnAttachment['addon'];
|
|
4
|
+
export type ResolvedPgDatabaseWithAttachment = {
|
|
5
|
+
addon: ResolvedPgDatabase;
|
|
6
|
+
attachment: ResolvedAddOnAttachment;
|
|
7
|
+
};
|
|
4
8
|
export type ResolvePgDatabaseOptions = ResolveAddonAttachmentOptions & {
|
|
5
9
|
appIdentity?: string;
|
|
10
|
+
includeAttachment?: boolean;
|
|
6
11
|
input?: string;
|
|
7
12
|
};
|
|
8
13
|
/**
|
|
@@ -36,4 +41,7 @@ export type ResolvePgDatabaseOptions = ResolveAddonAttachmentOptions & {
|
|
|
36
41
|
* from the underlying resolver. Throws if no input is given and no `appIdentity`
|
|
37
42
|
* is available to default the attachment lookup to.
|
|
38
43
|
*/
|
|
44
|
+
export declare function resolvePgDatabase(ctx: Pick<ResourceCtx, 'platform'>, options: ResolvePgDatabaseOptions & {
|
|
45
|
+
includeAttachment: true;
|
|
46
|
+
}): Promise<ResolvedPgDatabaseWithAttachment>;
|
|
39
47
|
export declare function resolvePgDatabase(ctx: Pick<ResourceCtx, 'platform'>, options?: ResolvePgDatabaseOptions): Promise<ResolvedPgDatabase>;
|
|
@@ -1,41 +1,11 @@
|
|
|
1
1
|
import createDebug from 'debug';
|
|
2
2
|
import { AddonAttachmentAmbiguousError, resolveAddonAttachment } from '../../platform/add-on-attachment/resolve.js';
|
|
3
|
+
import { getAddonService } from './postgres-utils.js';
|
|
3
4
|
const debug = createDebug('heroku:sdk:resources:database');
|
|
4
5
|
const DEFAULT_PG_ATTACHMENT = 'DATABASE_URL';
|
|
5
|
-
const PG_ADDON_SERVICE =
|
|
6
|
-
/**
|
|
7
|
-
* Resolve a Heroku Postgres database add-on from an attachment identity.
|
|
8
|
-
*
|
|
9
|
-
* `input` selects the attachment as follows:
|
|
10
|
-
* - omitted → the `DATABASE_URL` attachment on `appIdentity`.
|
|
11
|
-
* - `parent-app::attachment-or-config-var` (e.g. `my-app::DATABASE_URL`)
|
|
12
|
-
* → an attachment or config var on `parent-app`.
|
|
13
|
-
* - `parent-app::branch-name` (e.g. `my-app::my-branch`) → branch add-on
|
|
14
|
-
* name on `parent-app`.
|
|
15
|
-
* - Both `::` forms are handled the same way here: we split on the first
|
|
16
|
-
* `::` ourselves and send `app`/`addon` as separate fields, which
|
|
17
|
-
* works for either shape.
|
|
18
|
-
* - any other string (attachment name, config var, UUID, or
|
|
19
|
-
* globally-unique add-on name) → routed through `resolveAddonAttachment`.
|
|
20
|
-
*
|
|
21
|
-
* This function only uses the attachment resolver. To resolve a database
|
|
22
|
-
* through the add-on resolve endpoint, call `resolveAddon` directly.
|
|
23
|
-
*
|
|
24
|
-
* The returned add-on is the identity nested on the attachment (`id`,
|
|
25
|
-
* `name`, `app`, and the plan stub), not a full `ResolvedAddOn`. To get
|
|
26
|
-
* that shape, call `resolveAddon` with the returned add-on's `id`.
|
|
27
|
-
*
|
|
28
|
-
* If the resolver finds multiple matches that all point at the same add-on
|
|
29
|
-
* (same `addon.id`/attachment `app.id`) and whose own `_URL` config var
|
|
30
|
-
* resolves to the same value on the app, they're treated as equivalent
|
|
31
|
-
* (e.g. the same database attached under two names) rather than ambiguous.
|
|
32
|
-
*
|
|
33
|
-
* Throws `AddonAttachmentNotFoundError` (or `AddonAttachmentAmbiguousError`)
|
|
34
|
-
* from the underlying resolver. Throws if no input is given and no `appIdentity`
|
|
35
|
-
* is available to default the attachment lookup to.
|
|
36
|
-
*/
|
|
6
|
+
const PG_ADDON_SERVICE = getAddonService();
|
|
37
7
|
export async function resolvePgDatabase(ctx, options = {}) {
|
|
38
|
-
const { appIdentity, input, ...rest } = options;
|
|
8
|
+
const { appIdentity, includeAttachment, input, ...rest } = options;
|
|
39
9
|
if (!input && !appIdentity) {
|
|
40
10
|
throw new Error('resolvePgDatabase requires either input or appIdentity to default to DATABASE_URL.');
|
|
41
11
|
}
|
|
@@ -44,22 +14,22 @@ export async function resolvePgDatabase(ctx, options = {}) {
|
|
|
44
14
|
? parseBranchReference(databaseReference, appIdentity)
|
|
45
15
|
: { addon: databaseReference, app: appIdentity };
|
|
46
16
|
debug('resolve input=%s addon=%s app=%s', input ?? '<default>', addon, app ?? '<global>');
|
|
17
|
+
let attachment;
|
|
47
18
|
try {
|
|
48
|
-
|
|
49
|
-
return attachment.addon;
|
|
19
|
+
attachment = await resolveAddonAttachment(ctx, app, addon, { ...rest, addonService: PG_ADDON_SERVICE });
|
|
50
20
|
}
|
|
51
21
|
catch (error) {
|
|
52
|
-
if (error instanceof AddonAttachmentAmbiguousError)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
throw error;
|
|
22
|
+
if (!(error instanceof AddonAttachmentAmbiguousError))
|
|
23
|
+
throw error;
|
|
24
|
+
const equivalent = collapseIfEquivalent(error.matches);
|
|
25
|
+
if (!equivalent)
|
|
26
|
+
throw error;
|
|
27
|
+
const config = await ctx.platform.configVar.infoForApp(equivalent[0].app.name);
|
|
28
|
+
if (!allMatchSameUrl(equivalent, config))
|
|
29
|
+
throw error;
|
|
30
|
+
attachment = equivalent[0];
|
|
62
31
|
}
|
|
32
|
+
return includeAttachment ? { addon: attachment.addon, attachment } : attachment.addon;
|
|
63
33
|
}
|
|
64
34
|
/**
|
|
65
35
|
* Narrows ambiguous matches to a same-add-on group worth comparing by
|
|
@@ -9,11 +9,11 @@ export type TransferSchedule = {
|
|
|
9
9
|
export type TransferScheduleOptions = {
|
|
10
10
|
signal?: AbortSignal;
|
|
11
11
|
};
|
|
12
|
-
export declare function list(ctx: Pick<ResourceCtx, 'data' | 'platform'>, appIdentity: string,
|
|
12
|
+
export declare function list(ctx: Pick<ResourceCtx, 'data' | 'platform'>, appIdentity: string, options?: TransferScheduleOptions): Promise<TransferSchedule[]>;
|
|
13
13
|
export declare function create(ctx: Pick<ResourceCtx, 'data' | 'platform'>, appIdentity: string, addonIdentity: string | undefined, body: TransferScheduleCreateOpts, options?: TransferScheduleOptions): Promise<TransferScheduleCreateResult>;
|
|
14
14
|
export declare function del(ctx: Pick<ResourceCtx, 'data' | 'platform'>, appIdentity: string, addonIdentity: string | undefined, scheduleId: string, options?: TransferScheduleOptions): Promise<TransferScheduleDeleteResult>;
|
|
15
15
|
export declare const transferScheduleExtensions: import("../../core/extend-resource.js").ResourceExtension<"data", "transferSchedule", {
|
|
16
16
|
create: (appIdentity: string, addonIdentity: string | undefined, body: TransferScheduleCreateOpts, options?: TransferScheduleOptions) => Promise<TransferScheduleCreateResult>;
|
|
17
17
|
delete: (appIdentity: string, addonIdentity: string | undefined, scheduleId: string, options?: TransferScheduleOptions) => Promise<TransferScheduleDeleteResult>;
|
|
18
|
-
list: (appIdentity: string,
|
|
18
|
+
list: (appIdentity: string, options?: TransferScheduleOptions) => Promise<TransferSchedule[]>;
|
|
19
19
|
}>;
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { extendResource } from '../../core/extend-resource.js';
|
|
2
|
+
import { getArbitraryLegacyDB } from './internal/postgres-utils.js';
|
|
2
3
|
import { resolvePgDatabase } from './internal/resolve-pg-database.js';
|
|
3
|
-
export async function list(ctx, appIdentity,
|
|
4
|
+
export async function list(ctx, appIdentity, options = {}) {
|
|
4
5
|
options.signal?.throwIfAborted();
|
|
5
|
-
|
|
6
|
+
// Schedules are app-scoped, so any legacy DB ID unlocks the app's list.
|
|
7
|
+
const addon = await getArbitraryLegacyDB(ctx, appIdentity);
|
|
6
8
|
return ctx.data.transferSchedule.list(addon.id);
|
|
7
9
|
}
|
|
8
10
|
export async function create(ctx, appIdentity, addonIdentity, body, options = {}) {
|
|
9
11
|
options.signal?.throwIfAborted();
|
|
10
|
-
const addon = await resolvePgDatabase(ctx, {
|
|
11
|
-
|
|
12
|
+
const { addon, attachment } = await resolvePgDatabase(ctx, {
|
|
13
|
+
appIdentity, includeAttachment: true, input: addonIdentity, ...options,
|
|
14
|
+
});
|
|
15
|
+
let requestBody = body;
|
|
16
|
+
if (!requestBody.schedule_name) {
|
|
17
|
+
// eslint-disable-next-line camelcase
|
|
18
|
+
requestBody = { ...body, schedule_name: `${attachment.name}_URL` };
|
|
19
|
+
}
|
|
20
|
+
return ctx.data.transferSchedule.create(addon.id, requestBody);
|
|
12
21
|
}
|
|
13
22
|
export async function del(ctx, appIdentity, addonIdentity, scheduleId, options = {}) {
|
|
14
23
|
options.signal?.throwIfAborted();
|
|
@@ -18,5 +27,5 @@ export async function del(ctx, appIdentity, addonIdentity, scheduleId, options =
|
|
|
18
27
|
export const transferScheduleExtensions = extendResource('data', 'transferSchedule', ctx => ({
|
|
19
28
|
create: (appIdentity, addonIdentity, body, options) => create(ctx, appIdentity, addonIdentity, body, options),
|
|
20
29
|
delete: (appIdentity, addonIdentity, scheduleId, options) => del(ctx, appIdentity, addonIdentity, scheduleId, options),
|
|
21
|
-
list: (appIdentity,
|
|
30
|
+
list: (appIdentity, options) => list(ctx, appIdentity, options),
|
|
22
31
|
}));
|