@ninedeploy/sdk 0.10.6 → 0.16.0
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/LICENSE +21 -21
- package/dist/apiPath.d.ts +5 -0
- package/dist/apiPath.d.ts.map +1 -0
- package/dist/apiPath.js +18 -0
- package/dist/apiPath.js.map +1 -0
- package/dist/client.d.ts +29 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +51 -0
- package/dist/client.js.map +1 -0
- package/dist/databaseImport.d.ts +138 -0
- package/dist/databaseImport.d.ts.map +1 -0
- package/dist/databaseImport.js +126 -0
- package/dist/databaseImport.js.map +1 -0
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +12 -1973
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -641
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +25 -21
- package/dist/manifest.js.map +1 -1
- package/dist/resources/auth.d.ts +282 -0
- package/dist/resources/auth.d.ts.map +1 -0
- package/dist/resources/auth.js +111 -0
- package/dist/resources/auth.js.map +1 -0
- package/dist/resources/databases.d.ts +178 -0
- package/dist/resources/databases.d.ts.map +1 -0
- package/dist/resources/databases.js +82 -0
- package/dist/resources/databases.js.map +1 -0
- package/dist/resources/deploys.d.ts +125 -0
- package/dist/resources/deploys.d.ts.map +1 -0
- package/dist/resources/deploys.js +46 -0
- package/dist/resources/deploys.js.map +1 -0
- package/dist/resources/domains.d.ts +101 -0
- package/dist/resources/domains.d.ts.map +1 -0
- package/dist/resources/domains.js +28 -0
- package/dist/resources/domains.js.map +1 -0
- package/dist/resources/env.d.ts +55 -0
- package/dist/resources/env.d.ts.map +1 -0
- package/dist/resources/env.js +33 -0
- package/dist/resources/env.js.map +1 -0
- package/dist/resources/logs.d.ts +27 -0
- package/dist/resources/logs.d.ts.map +1 -0
- package/dist/resources/logs.js +18 -0
- package/dist/resources/logs.js.map +1 -0
- package/dist/resources/network.d.ts +134 -0
- package/dist/resources/network.d.ts.map +1 -0
- package/dist/resources/network.js +33 -0
- package/dist/resources/network.js.map +1 -0
- package/dist/resources/plugins.d.ts +42 -0
- package/dist/resources/plugins.d.ts.map +1 -0
- package/dist/resources/plugins.js +22 -0
- package/dist/resources/plugins.js.map +1 -0
- package/dist/resources/servers.d.ts +130 -0
- package/dist/resources/servers.d.ts.map +1 -0
- package/dist/resources/servers.js +37 -0
- package/dist/resources/servers.js.map +1 -0
- package/dist/resources/services.d.ts +185 -0
- package/dist/resources/services.d.ts.map +1 -0
- package/dist/resources/services.js +83 -0
- package/dist/resources/services.js.map +1 -0
- package/dist/resources/settings.d.ts +312 -0
- package/dist/resources/settings.d.ts.map +1 -0
- package/dist/resources/settings.js +85 -0
- package/dist/resources/settings.js.map +1 -0
- package/dist/resources/sources.d.ts +87 -0
- package/dist/resources/sources.d.ts.map +1 -0
- package/dist/resources/sources.js +39 -0
- package/dist/resources/sources.js.map +1 -0
- package/dist/resources/system.d.ts +240 -0
- package/dist/resources/system.d.ts.map +1 -0
- package/dist/resources/system.js +86 -0
- package/dist/resources/system.js.map +1 -0
- package/dist/resources/templates.d.ts +36 -0
- package/dist/resources/templates.d.ts.map +1 -0
- package/dist/resources/templates.js +17 -0
- package/dist/resources/templates.js.map +1 -0
- package/dist/resources/terminals.d.ts +60 -0
- package/dist/resources/terminals.d.ts.map +1 -0
- package/dist/resources/terminals.js +35 -0
- package/dist/resources/terminals.js.map +1 -0
- package/dist/resources/traefik.d.ts +84 -0
- package/dist/resources/traefik.d.ts.map +1 -0
- package/dist/resources/traefik.js +35 -0
- package/dist/resources/traefik.js.map +1 -0
- package/dist/resources/volumes.d.ts +114 -0
- package/dist/resources/volumes.d.ts.map +1 -0
- package/dist/resources/volumes.js +47 -0
- package/dist/resources/volumes.js.map +1 -0
- package/dist/terminal.d.ts +98 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +180 -0
- package/dist/terminal.js.map +1 -0
- package/dist/transport.d.ts +61 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +90 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +539 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +5 -4
- package/src/apiPath.ts +23 -0
- package/src/client.ts +70 -0
- package/src/databaseImport.ts +174 -0
- package/src/errors.ts +27 -0
- package/src/index.ts +118 -0
- package/src/manifest.ts +414 -0
- package/src/resources/auth.ts +357 -0
- package/src/resources/databases.ts +227 -0
- package/src/resources/deploys.ts +161 -0
- package/src/resources/domains.ts +142 -0
- package/src/resources/env.ts +84 -0
- package/src/resources/logs.ts +48 -0
- package/src/resources/network.ts +126 -0
- package/src/resources/plugins.ts +52 -0
- package/src/resources/servers.ts +141 -0
- package/src/resources/services.ts +250 -0
- package/src/resources/settings.ts +275 -0
- package/src/resources/sources.ts +131 -0
- package/src/resources/system.ts +281 -0
- package/src/resources/templates.ts +55 -0
- package/src/resources/terminals.ts +112 -0
- package/src/resources/traefik.ts +116 -0
- package/src/resources/volumes.ts +138 -0
- package/src/terminal.ts +254 -0
- package/src/transport.ts +154 -0
- package/src/types.ts +588 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
// Client surface: auth, workspaces, users, scim, sso, accessGrants, access.
|
|
2
|
+
// Split out of the former single-file SDK entry (src/index.ts); the public
|
|
3
|
+
// surface is re-exported unchanged from there.
|
|
4
|
+
import type {
|
|
5
|
+
ActiveSession,
|
|
6
|
+
ApiToken,
|
|
7
|
+
PasskeyCredential,
|
|
8
|
+
CreateApiTokenInput,
|
|
9
|
+
CreatedApiToken,
|
|
10
|
+
Login,
|
|
11
|
+
PasswordChange,
|
|
12
|
+
PasswordChangeResult,
|
|
13
|
+
PasswordReset,
|
|
14
|
+
PublicUser,
|
|
15
|
+
Refresh,
|
|
16
|
+
Register,
|
|
17
|
+
Session,
|
|
18
|
+
UserCreate,
|
|
19
|
+
WorkspaceEntry,
|
|
20
|
+
WorkspaceDetail,
|
|
21
|
+
WorkspaceCreateInput,
|
|
22
|
+
WorkspaceUpdateInput,
|
|
23
|
+
WorkspaceMemberEntry,
|
|
24
|
+
WorkspaceMemberInviteEntry,
|
|
25
|
+
WorkspaceMemberAddInput,
|
|
26
|
+
WorkspaceMemberRoleUpdateInput,
|
|
27
|
+
OidcPublicProvider,
|
|
28
|
+
WorkspaceInvitationEntry,
|
|
29
|
+
WorkspaceInvitationPublic,
|
|
30
|
+
WorkspaceRole,
|
|
31
|
+
OidcProviderEntry,
|
|
32
|
+
OidcProviderCreateInput,
|
|
33
|
+
OidcProviderUpdateInput,
|
|
34
|
+
AccessGrant,
|
|
35
|
+
AccessGrantCreate,
|
|
36
|
+
AccessGrantListQuery,
|
|
37
|
+
AccessGrantUpdate,
|
|
38
|
+
AccessMe,
|
|
39
|
+
ProjectAccessEntry,
|
|
40
|
+
} from '@ninedeploy/schemas';
|
|
41
|
+
import { apiGetUrl } from '../apiPath.js';
|
|
42
|
+
import type { TokenIntrospection } from '../types.js';
|
|
43
|
+
import type { Transport } from '../transport.js';
|
|
44
|
+
|
|
45
|
+
export interface AuthApi {
|
|
46
|
+
auth: {
|
|
47
|
+
/** `setupTokenRequired` (0.16): the first-run bootstrap needs the installer's setup token. */
|
|
48
|
+
status: () => Promise<{ initialized: boolean; allowRegistration?: boolean; setupTokenRequired?: boolean }>;
|
|
49
|
+
/**
|
|
50
|
+
* Introspect the current bearer token. Returns the
|
|
51
|
+
* token's id (when opaque), name, scopes, expiry and
|
|
52
|
+
* the operator flag. Interactive sessions (JWT) report
|
|
53
|
+
* `scopes: ['session']`. The MCP server uses this to
|
|
54
|
+
* discover the token's authority before registering
|
|
55
|
+
* tools.
|
|
56
|
+
*/
|
|
57
|
+
introspectToken: () => Promise<TokenIntrospection>;
|
|
58
|
+
setup: (input: Register) => Promise<Session>;
|
|
59
|
+
register: (input: Register) => Promise<Session>;
|
|
60
|
+
login: (input: Login) => Promise<Session>;
|
|
61
|
+
refresh: (input: Refresh) => Promise<Session>;
|
|
62
|
+
logout: () => Promise<{ ok: boolean }>;
|
|
63
|
+
/** Self-service password change; revokes other sessions, returns a fresh token pair
|
|
64
|
+
* and (r603) how many passkeys still sign in to the account. */
|
|
65
|
+
changePassword: (input: PasswordChange) => Promise<PasswordChangeResult>;
|
|
66
|
+
/** Request a reset link (always 200 — no user enumeration). */
|
|
67
|
+
forgotPassword: (email: string) => Promise<{ ok: boolean }>;
|
|
68
|
+
/** Complete a reset with a single-use token; revokes all sessions. */
|
|
69
|
+
resetPasswordWithToken: (input: { token: string; newPassword: string }) => Promise<{ ok: boolean }>;
|
|
70
|
+
twoFactor: {
|
|
71
|
+
/**
|
|
72
|
+
* Generate a pending secret + otpauth URI (auth required). Since 0.10.36
|
|
73
|
+
* (r502) the server wants step-up proof: the current password, or a
|
|
74
|
+
* sign-in from the last 10 minutes (else 403 `reauth_required`).
|
|
75
|
+
*/
|
|
76
|
+
setup: (input?: { password: string }) => Promise<{ secret: string; otpauthUri: string }>;
|
|
77
|
+
/** Turn 2FA on. `password` is the r502 step-up proof (see `setup`). */
|
|
78
|
+
enable: (code: string, password?: string) => Promise<{ ok: boolean; totpEnabled: boolean }>;
|
|
79
|
+
disable: (input: { password: string; code: string }) => Promise<{ ok: boolean; totpEnabled: boolean }>;
|
|
80
|
+
};
|
|
81
|
+
me: () => Promise<PublicUser>;
|
|
82
|
+
tokens: {
|
|
83
|
+
create: (input?: CreateApiTokenInput) => Promise<CreatedApiToken>;
|
|
84
|
+
list: () => Promise<ApiToken[]>;
|
|
85
|
+
remove: (id: number) => Promise<void>;
|
|
86
|
+
};
|
|
87
|
+
passkeys: {
|
|
88
|
+
/**
|
|
89
|
+
* Start a registration ceremony (returns JSON options for the browser
|
|
90
|
+
* API). Since 0.10.36 (r502) registration needs step-up proof: the
|
|
91
|
+
* current password, or a sign-in from the last 10 minutes (else 403
|
|
92
|
+
* `reauth_required`).
|
|
93
|
+
*/
|
|
94
|
+
registerOptions: (input?: { password?: string }) => Promise<{ options: string }>;
|
|
95
|
+
/** Complete registration: verify the browser response, store the credential. */
|
|
96
|
+
registerVerify: (input: { name: string; response: unknown; password?: string }) => Promise<PasskeyCredential>;
|
|
97
|
+
list: () => Promise<PasskeyCredential[]>;
|
|
98
|
+
remove: (id: number) => Promise<void>;
|
|
99
|
+
/** Start a passwordless login ceremony (discoverable credentials). */
|
|
100
|
+
loginOptions: () => Promise<{ options: string }>;
|
|
101
|
+
/** Complete passkey login — returns a full session on success. */
|
|
102
|
+
loginVerify: (response: unknown) => Promise<Session>;
|
|
103
|
+
};
|
|
104
|
+
sessions: {
|
|
105
|
+
list: () => Promise<ActiveSession[]>;
|
|
106
|
+
revoke: (id: number) => Promise<{ ok: boolean }>;
|
|
107
|
+
};
|
|
108
|
+
oidc: {
|
|
109
|
+
publicProviders: () => Promise<OidcPublicProvider[]>;
|
|
110
|
+
link: (slug: string) => Promise<{ authUrl: string }>;
|
|
111
|
+
listProviders: () => Promise<OidcProviderEntry[]>;
|
|
112
|
+
list: () => Promise<OidcProviderEntry[]>;
|
|
113
|
+
createProvider: (input: OidcProviderCreateInput) => Promise<OidcProviderEntry>;
|
|
114
|
+
create: (input: OidcProviderCreateInput) => Promise<OidcProviderEntry>;
|
|
115
|
+
updateProvider: (id: number, input: OidcProviderUpdateInput) => Promise<OidcProviderEntry>;
|
|
116
|
+
update: (id: number, input: OidcProviderUpdateInput) => Promise<OidcProviderEntry>;
|
|
117
|
+
deleteProvider: (id: number) => Promise<{ ok: boolean }>;
|
|
118
|
+
delete: (id: number) => Promise<{ ok: boolean }>;
|
|
119
|
+
callback: (slug: string, payload: { code: string; state: string }) => Promise<Session>;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
workspaces: {
|
|
123
|
+
list: () => Promise<WorkspaceEntry[]>;
|
|
124
|
+
get: (id: number) => Promise<WorkspaceDetail>;
|
|
125
|
+
create: (input: WorkspaceCreateInput) => Promise<WorkspaceEntry>;
|
|
126
|
+
update: (id: number, input: WorkspaceUpdateInput) => Promise<WorkspaceEntry>;
|
|
127
|
+
delete: (id: number) => Promise<{ ok: boolean }>;
|
|
128
|
+
/**
|
|
129
|
+
* Add a member by email. r604: only an instance operator gets a direct
|
|
130
|
+
* add (a `WorkspaceMemberEntry`) for a registered address; for everyone
|
|
131
|
+
* else the answer is always an invitation (`kind: 'invitation'`), so the
|
|
132
|
+
* response does not reveal whether the address has an account.
|
|
133
|
+
*/
|
|
134
|
+
addMember: (id: number, input: WorkspaceMemberAddInput) => Promise<WorkspaceMemberEntry | WorkspaceMemberInviteEntry>;
|
|
135
|
+
/**
|
|
136
|
+
* Create a pending invitation for an email address that is not a member of
|
|
137
|
+
* the workspace yet (r604: registered or not).
|
|
138
|
+
* r334: the response is the invitation row only — the server does NOT
|
|
139
|
+
* return an `acceptUrl` here (the token travels in the `x-invitation-token`
|
|
140
|
+
* response header and in the invite email). `addMember` returns the accept
|
|
141
|
+
* link in its body for the same case.
|
|
142
|
+
*/
|
|
143
|
+
inviteMember: (id: number, input: WorkspaceMemberAddInput) => Promise<WorkspaceInvitationEntry>;
|
|
144
|
+
listInvitations: (id: number) => Promise<WorkspaceInvitationEntry[]>;
|
|
145
|
+
revokeInvitation: (id: number, inviteId: number) => Promise<{ ok: boolean }>;
|
|
146
|
+
/** Look up a pending invitation by its public token (no auth required). */
|
|
147
|
+
previewInvitation: (token: string) => Promise<WorkspaceInvitationPublic>;
|
|
148
|
+
/** Accept an invitation as the currently authenticated user. */
|
|
149
|
+
acceptInvitation: (token: string) => Promise<{ ok: boolean; workspaceId: number; role: WorkspaceRole }>;
|
|
150
|
+
updateMemberRole: (id: number, memberId: number, input: WorkspaceMemberRoleUpdateInput) => Promise<WorkspaceMemberEntry>;
|
|
151
|
+
removeMember: (id: number, memberId: number) => Promise<{ ok: boolean }>;
|
|
152
|
+
};
|
|
153
|
+
users: {
|
|
154
|
+
list: () => Promise<PublicUser[]>;
|
|
155
|
+
/**
|
|
156
|
+
* Operator-only: create a user directly (no registration flow needed).
|
|
157
|
+
* The new account has no workspace memberships — an existing owner/admin
|
|
158
|
+
* must invite it into at least one workspace before it can act.
|
|
159
|
+
*/
|
|
160
|
+
create: (input: UserCreate) => Promise<PublicUser>;
|
|
161
|
+
/**
|
|
162
|
+
* Operator-only: delete a user. The `users.role` column was removed when
|
|
163
|
+
* authorization became workspace-only; the legacy `setRole` is gone with
|
|
164
|
+
* it. Role changes go through `workspaces.updateMemberRole`.
|
|
165
|
+
* Workspaces the user owns are transferred, not deleted: to the caller,
|
|
166
|
+
* or to `opts.transferTo` (an existing, active user id).
|
|
167
|
+
*/
|
|
168
|
+
remove: (id: number, opts?: { transferTo?: number }) => Promise<void>;
|
|
169
|
+
/** Operator reset of another user's password (revokes their sessions). */
|
|
170
|
+
resetPassword: (id: number, input: PasswordReset) => Promise<{ ok: boolean }>;
|
|
171
|
+
/** Mint a one-time reset link for a user (returned exactly once). */
|
|
172
|
+
resetLink: (id: number) => Promise<{ url: string; expiresAt: string }>;
|
|
173
|
+
/**
|
|
174
|
+
* Operator-only: grant or revoke the INSTANCE-operator flag.
|
|
175
|
+
*
|
|
176
|
+
* Not a workspace role — this is the flag that gates operator-only routes
|
|
177
|
+
* and the host-privilege boundary (PM2/compose deploys, lifecycle hooks,
|
|
178
|
+
* docker-socket templates). Creating a workspace does NOT confer it. The
|
|
179
|
+
* last remaining operator cannot be demoted.
|
|
180
|
+
*/
|
|
181
|
+
setOperator: (id: number, isOperator: boolean) => Promise<{ ok: boolean; id: number; isOperator: boolean }>;
|
|
182
|
+
};
|
|
183
|
+
scim: {
|
|
184
|
+
/** SCIM 2.0 provisioning tokens (operator-managed). */
|
|
185
|
+
listTokens: () => Promise<Array<{ id: number; name: string; workspaceId: number; createdAt: string | null; lastUsedAt: string | null; revoked: boolean }>>;
|
|
186
|
+
/** The plaintext token is returned exactly once — only its sha256 persists. */
|
|
187
|
+
createToken: (input: { name: string; workspaceId: number }) => Promise<{ id: number; token: string }>;
|
|
188
|
+
revokeToken: (id: number) => Promise<{ ok: boolean }>;
|
|
189
|
+
};
|
|
190
|
+
sso: {
|
|
191
|
+
/** Every configured OIDC / SAML provider. G-22. */
|
|
192
|
+
listProviders: () => Promise<{
|
|
193
|
+
providers: Array<{ id: number; type: 'oidc' | 'saml'; name: string; createdAt: string }>;
|
|
194
|
+
}>;
|
|
195
|
+
/** Add a provider (OIDC issuer URL or SAML metadata URL lives in `config`). */
|
|
196
|
+
addProvider: (input: {
|
|
197
|
+
type: 'oidc' | 'saml';
|
|
198
|
+
name: string;
|
|
199
|
+
config: Record<string, unknown>;
|
|
200
|
+
}) => Promise<{ ok: boolean; id?: number; name?: string; type?: 'oidc' | 'saml'; error?: string }>;
|
|
201
|
+
/** Remove a provider by id. */
|
|
202
|
+
removeProvider: (id: number) => Promise<{ ok: boolean }>;
|
|
203
|
+
};
|
|
204
|
+
/** Project and environment access grants (0.15, raise-only). Workspace admin floor. */
|
|
205
|
+
accessGrants: {
|
|
206
|
+
list: (workspaceId: number, query?: Partial<AccessGrantListQuery>) => Promise<AccessGrant[]>;
|
|
207
|
+
create: (workspaceId: number, input: AccessGrantCreate) => Promise<AccessGrant>;
|
|
208
|
+
/**
|
|
209
|
+
* Change the role, suspend (`suspended: true`) or reinstate
|
|
210
|
+
* (`suspended: false`), or both. A grant the user's identity provider
|
|
211
|
+
* suspended cannot be reinstated here (409 `grant_suspended_by_idp`).
|
|
212
|
+
*/
|
|
213
|
+
update: (workspaceId: number, grantId: number, input: AccessGrantUpdate) => Promise<AccessGrant>;
|
|
214
|
+
delete: (workspaceId: number, grantId: number) => Promise<{ ok: boolean }>;
|
|
215
|
+
};
|
|
216
|
+
access: {
|
|
217
|
+
/** The caller's own grants and the workspaces reached only through them. */
|
|
218
|
+
me: () => Promise<AccessMe>;
|
|
219
|
+
/** Who reaches a project and how (project admin). */
|
|
220
|
+
project: (projectId: number) => Promise<ProjectAccessEntry[]>;
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function authApi(t: Transport): AuthApi {
|
|
225
|
+
const { get, send, request } = t;
|
|
226
|
+
return {
|
|
227
|
+
auth: {
|
|
228
|
+
status: () => get<{ initialized: boolean; allowRegistration?: boolean; setupTokenRequired?: boolean }>('/v1/auth/status'),
|
|
229
|
+
introspectToken: () => get<TokenIntrospection>('/v1/auth/token'),
|
|
230
|
+
setup: (input) => send<Session>('POST', '/v1/setup', input),
|
|
231
|
+
register: (input) => send<Session>('POST', '/v1/auth/register', input),
|
|
232
|
+
login: (input) => send<Session>('POST', '/v1/auth/login', input),
|
|
233
|
+
refresh: (input) => send<Session>('POST', '/v1/auth/refresh', input),
|
|
234
|
+
/** Revoke this user's outstanding JWTs server-side (tokenVersion bump). */
|
|
235
|
+
logout: () => send<{ ok: boolean }>('POST', '/v1/auth/logout', {}),
|
|
236
|
+
changePassword: (input) => send<PasswordChangeResult>('POST', '/v1/auth/password', input),
|
|
237
|
+
forgotPassword: (email) => send<{ ok: boolean }>('POST', '/v1/auth/forgot-password', { email }),
|
|
238
|
+
resetPasswordWithToken: (input) => send<{ ok: boolean }>('POST', '/v1/auth/reset-password', input),
|
|
239
|
+
twoFactor: {
|
|
240
|
+
setup: (input?: { password: string }) => send<{ secret: string; otpauthUri: string }>('POST', '/v1/auth/2fa/setup', input ?? {}),
|
|
241
|
+
enable: (code, password) =>
|
|
242
|
+
send<{ ok: boolean; totpEnabled: boolean }>('POST', '/v1/auth/2fa/enable', password ? { code, password } : { code }),
|
|
243
|
+
disable: (input) => send<{ ok: boolean; totpEnabled: boolean }>('POST', '/v1/auth/2fa/disable', input),
|
|
244
|
+
},
|
|
245
|
+
me: () => get<PublicUser>('/v1/auth/me'),
|
|
246
|
+
tokens: {
|
|
247
|
+
create: (input) => send<CreatedApiToken>('POST', '/v1/auth/tokens', input ?? {}),
|
|
248
|
+
list: () => get<ApiToken[]>('/v1/auth/tokens'),
|
|
249
|
+
remove: async (id) => {
|
|
250
|
+
await request(`/v1/auth/tokens/${id}`, { method: 'DELETE' });
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
passkeys: {
|
|
254
|
+
registerOptions: (input) =>
|
|
255
|
+
send<{ options: string }>('POST', '/v1/auth/passkey/register/options', input?.password ? { password: input.password } : undefined),
|
|
256
|
+
registerVerify: (input) =>
|
|
257
|
+
send<PasskeyCredential>('POST', '/v1/auth/passkey/register/verify', {
|
|
258
|
+
name: input.name,
|
|
259
|
+
response: input.response as Record<string, unknown>,
|
|
260
|
+
...(input.password ? { password: input.password } : {}),
|
|
261
|
+
}),
|
|
262
|
+
list: () => get<PasskeyCredential[]>('/v1/auth/passkey'),
|
|
263
|
+
remove: async (id) => {
|
|
264
|
+
await request(`/v1/auth/passkey/${id}`, { method: 'DELETE' });
|
|
265
|
+
},
|
|
266
|
+
loginOptions: () => send<{ options: string }>('POST', '/v1/auth/passkey/login/options'),
|
|
267
|
+
loginVerify: (response) =>
|
|
268
|
+
send<Session>('POST', '/v1/auth/passkey/login/verify', { response: response as Record<string, unknown> }),
|
|
269
|
+
},
|
|
270
|
+
sessions: {
|
|
271
|
+
list: () => get<ActiveSession[]>('/v1/auth/sessions'),
|
|
272
|
+
revoke: (id) => send<{ ok: boolean }>('DELETE', `/v1/auth/sessions/${id}`),
|
|
273
|
+
},
|
|
274
|
+
oidc: {
|
|
275
|
+
publicProviders: () => get<OidcPublicProvider[]>('/v1/auth/oidc/providers/public'),
|
|
276
|
+
link: (slug) => request<{ authUrl: string }>(`/v1/auth/oidc/${encodeURIComponent(slug)}/link`, { method: 'POST', credentials: 'include' }),
|
|
277
|
+
listProviders: () => get<OidcProviderEntry[]>('/v1/auth/oidc/providers'),
|
|
278
|
+
list: () => get<OidcProviderEntry[]>('/v1/auth/oidc/providers'),
|
|
279
|
+
createProvider: (input) => send<OidcProviderEntry>('POST', '/v1/auth/oidc/providers', input),
|
|
280
|
+
create: (input) => send<OidcProviderEntry>('POST', '/v1/auth/oidc/providers', input),
|
|
281
|
+
updateProvider: (id, input) => send<OidcProviderEntry>('PATCH', `/v1/auth/oidc/providers/${id}`, input),
|
|
282
|
+
update: (id, input) => send<OidcProviderEntry>('PATCH', `/v1/auth/oidc/providers/${id}`, input),
|
|
283
|
+
deleteProvider: (id) => send<{ ok: boolean }>('DELETE', `/v1/auth/oidc/providers/${id}`),
|
|
284
|
+
delete: (id) => send<{ ok: boolean }>('DELETE', `/v1/auth/oidc/providers/${id}`),
|
|
285
|
+
callback: (slug, payload) => send<Session>('POST', `/v1/auth/oidc/${slug}/callback`, payload),
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
workspaces: {
|
|
289
|
+
list: () => get<WorkspaceEntry[]>('/v1/workspaces'),
|
|
290
|
+
get: (id) => get<WorkspaceDetail>(`/v1/workspaces/${id}`),
|
|
291
|
+
create: (input) => send<WorkspaceEntry>('POST', '/v1/workspaces', input),
|
|
292
|
+
update: (id, input) => send<WorkspaceEntry>('PATCH', `/v1/workspaces/${id}`, input),
|
|
293
|
+
delete: (id) => send<{ ok: boolean }>('DELETE', `/v1/workspaces/${id}`),
|
|
294
|
+
addMember: (id, input) =>
|
|
295
|
+
send<WorkspaceMemberEntry | WorkspaceMemberInviteEntry>('POST', `/v1/workspaces/${id}/members`, input),
|
|
296
|
+
inviteMember: (id, input) =>
|
|
297
|
+
send<WorkspaceInvitationEntry>('POST', `/v1/workspaces/${id}/invitations`, input),
|
|
298
|
+
listInvitations: (id) => get<WorkspaceInvitationEntry[]>(`/v1/workspaces/${id}/invitations`),
|
|
299
|
+
revokeInvitation: (id, inviteId) =>
|
|
300
|
+
send<{ ok: boolean }>('DELETE', `/v1/workspaces/${id}/invitations/${inviteId}`),
|
|
301
|
+
previewInvitation: (token) => get<WorkspaceInvitationPublic>(`/v1/invitations/${token}`),
|
|
302
|
+
acceptInvitation: (token) =>
|
|
303
|
+
send<{ ok: boolean; workspaceId: number; role: WorkspaceRole }>(
|
|
304
|
+
'POST',
|
|
305
|
+
`/v1/invitations/${token}/accept`,
|
|
306
|
+
),
|
|
307
|
+
updateMemberRole: (id, memberId, input) => send<WorkspaceMemberEntry>('PATCH', `/v1/workspaces/${id}/members/${memberId}`, input),
|
|
308
|
+
removeMember: (id, memberId) => send<{ ok: boolean }>('DELETE', `/v1/workspaces/${id}/members/${memberId}`),
|
|
309
|
+
},
|
|
310
|
+
users: {
|
|
311
|
+
list: () => get<PublicUser[]>('/v1/users'),
|
|
312
|
+
create: (input) => send<PublicUser>('POST', '/v1/users', input),
|
|
313
|
+
resetPassword: (id, input) => send<{ ok: boolean }>('PATCH', `/v1/users/${id}/password`, input),
|
|
314
|
+
resetLink: (id) => send<{ url: string; expiresAt: string }>('POST', `/v1/users/${id}/reset-link`),
|
|
315
|
+
setOperator: (id, isOperator) =>
|
|
316
|
+
send<{ ok: boolean; id: number; isOperator: boolean }>('PATCH', `/v1/users/${id}/operator`, { isOperator }),
|
|
317
|
+
remove: async (id, opts) => {
|
|
318
|
+
// r540: owned workspaces go to the caller unless `transferTo` names another user.
|
|
319
|
+
const q = opts?.transferTo != null ? `?transferTo=${encodeURIComponent(String(opts.transferTo))}` : '';
|
|
320
|
+
await request(`/v1/users/${id}${q}`, { method: 'DELETE' });
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
scim: {
|
|
324
|
+
listTokens: () =>
|
|
325
|
+
get<Array<{ id: number; name: string; workspaceId: number; createdAt: string | null; lastUsedAt: string | null; revoked: boolean }>>('/v1/scim/tokens'),
|
|
326
|
+
createToken: (input) =>
|
|
327
|
+
send<{ id: number; token: string }>('POST', '/v1/scim/tokens', input),
|
|
328
|
+
revokeToken: (id) => send<{ ok: boolean }>('DELETE', `/v1/scim/tokens/${id}`),
|
|
329
|
+
},
|
|
330
|
+
sso: {
|
|
331
|
+
listProviders: () => get<{
|
|
332
|
+
providers: Array<{ id: number; type: 'oidc' | 'saml'; name: string; createdAt: string }>;
|
|
333
|
+
}>('/v1/sso/providers'),
|
|
334
|
+
addProvider: (input) => send<{
|
|
335
|
+
ok: boolean;
|
|
336
|
+
id?: number;
|
|
337
|
+
name?: string;
|
|
338
|
+
type?: 'oidc' | 'saml';
|
|
339
|
+
error?: string;
|
|
340
|
+
}>('POST', '/v1/sso/providers', input),
|
|
341
|
+
removeProvider: (id) => send<{ ok: boolean }>('DELETE', `/v1/sso/providers/${id}`),
|
|
342
|
+
},
|
|
343
|
+
accessGrants: {
|
|
344
|
+
list: (workspaceId, query) =>
|
|
345
|
+
get<AccessGrant[]>(apiGetUrl(`/v1/workspaces/${workspaceId}/access-grants`, { ...query })),
|
|
346
|
+
create: (workspaceId, input) => send<AccessGrant>('POST', `/v1/workspaces/${workspaceId}/access-grants`, input),
|
|
347
|
+
update: (workspaceId, grantId, input) =>
|
|
348
|
+
send<AccessGrant>('PATCH', `/v1/workspaces/${workspaceId}/access-grants/${grantId}`, input),
|
|
349
|
+
delete: (workspaceId, grantId) =>
|
|
350
|
+
send<{ ok: boolean }>('DELETE', `/v1/workspaces/${workspaceId}/access-grants/${grantId}`),
|
|
351
|
+
},
|
|
352
|
+
access: {
|
|
353
|
+
me: () => get<AccessMe>('/v1/access/me'),
|
|
354
|
+
project: (projectId) => get<ProjectAccessEntry[]>(`/v1/projects/${projectId}/access`),
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// Client surface: databases, backups, backupDestinations, volumeBackups.
|
|
2
|
+
// Split out of the former single-file SDK entry (src/index.ts); the public
|
|
3
|
+
// surface is re-exported unchanged from there.
|
|
4
|
+
import type {
|
|
5
|
+
CreateVolumeBackupInput,
|
|
6
|
+
Backup,
|
|
7
|
+
BackupPolicy,
|
|
8
|
+
BackupPolicyInput,
|
|
9
|
+
BackupWithDb,
|
|
10
|
+
CreateDatabaseInput,
|
|
11
|
+
ManagedDatabase,
|
|
12
|
+
DatabaseDetail,
|
|
13
|
+
SetLimitsInput,
|
|
14
|
+
PublicAccessPut,
|
|
15
|
+
PublicAccessStatus,
|
|
16
|
+
DatabaseImport,
|
|
17
|
+
DatabaseImportCreate,
|
|
18
|
+
BackupDestinationObject,
|
|
19
|
+
} from '@ninedeploy/schemas';
|
|
20
|
+
import type { ImportWaitOptions, ImportFileData, ImportFileOptions } from '../databaseImport.js';
|
|
21
|
+
import type { BackupDrillResult, BackupDrillEntry, PgbouncerStatus, DatabaseCredentialsWithPublic } from '../types.js';
|
|
22
|
+
import type { Transport } from '../transport.js';
|
|
23
|
+
import { createImportHelpers } from '../databaseImport.js';
|
|
24
|
+
|
|
25
|
+
export interface DatabasesApi {
|
|
26
|
+
databases: {
|
|
27
|
+
/** `query` is appended verbatim, e.g. `?projectId=3` (project scoping). */
|
|
28
|
+
list: (query?: string) => Promise<ManagedDatabase[]>;
|
|
29
|
+
create: (input: CreateDatabaseInput) => Promise<ManagedDatabase>;
|
|
30
|
+
get: (id: number) => Promise<DatabaseDetail>;
|
|
31
|
+
remove: (id: number, options?: { force?: boolean }) => Promise<void>;
|
|
32
|
+
restart: (id: number) => Promise<void>;
|
|
33
|
+
stop: (id: number) => Promise<void>;
|
|
34
|
+
start: (id: number) => Promise<void>;
|
|
35
|
+
logs: (id: number, lines?: number) => Promise<{ logs: string[] }>;
|
|
36
|
+
credentials: (id: number) => Promise<DatabaseCredentialsWithPublic>;
|
|
37
|
+
setLimits: (id: number, input: SetLimitsInput) => Promise<{ cpuShares: number | null; memLimitMb: number | null }>;
|
|
38
|
+
startStudio: (id: number, port?: number) => Promise<{ ok: boolean; port: number; url: string }>;
|
|
39
|
+
stopStudio: (id: number) => Promise<{ ok: boolean }>;
|
|
40
|
+
/**
|
|
41
|
+
* PgBouncer sidecar (G-32). Returns the current sidecar
|
|
42
|
+
* state — `enabled` + `running` + the URL the services
|
|
43
|
+
* should connect to. Use the same call after
|
|
44
|
+
* `enablePgbouncer` / `disablePgbouncer` for a fresh
|
|
45
|
+
* read (the route returns the post-mutation status).
|
|
46
|
+
*/
|
|
47
|
+
pgbouncerStatus: (id: number) => Promise<PgbouncerStatus>;
|
|
48
|
+
/**
|
|
49
|
+
* Spin up the sidecar. `port` is optional — the row's
|
|
50
|
+
* existing `pgbouncerPort` (default 6432) is used
|
|
51
|
+
* when omitted. Only `engine='postgres'` is supported;
|
|
52
|
+
* the server returns 422 for any other engine.
|
|
53
|
+
*/
|
|
54
|
+
enablePgbouncer: (id: number, input?: { port?: number }) => Promise<PgbouncerStatus>;
|
|
55
|
+
/** Stop + remove the sidecar and clear the row's flags. */
|
|
56
|
+
disablePgbouncer: (id: number) => Promise<PgbouncerStatus>;
|
|
57
|
+
/**
|
|
58
|
+
* Run an engine-specific smoke check against a backup
|
|
59
|
+
* file (pg_restore --list, redis-check-rdb, mysqldump
|
|
60
|
+
* header parse, ...). Returns the drill row with `status`
|
|
61
|
+
* of `passed` or `failed`; a failed drill is a real
|
|
62
|
+
* signal that the backup cannot be restored cleanly, not
|
|
63
|
+
* a warning. `unverifiable` (r356) means the check could
|
|
64
|
+
* not run at all. Requires the `member` role on the database.
|
|
65
|
+
*/
|
|
66
|
+
drillBackup: (id: number, input: { backupId: number }) => Promise<BackupDrillResult>;
|
|
67
|
+
/** List the most recent drills for a database, newest first. */
|
|
68
|
+
drills: (id: number) => Promise<BackupDrillEntry[]>;
|
|
69
|
+
/**
|
|
70
|
+
* 0.14: public access through a per-database Traefik TCP sidecar. Reading
|
|
71
|
+
* needs `admin` on the database; `set` and `disable` are operator-only.
|
|
72
|
+
* Public access exposes the database's ROOT credentials.
|
|
73
|
+
*/
|
|
74
|
+
publicAccess: {
|
|
75
|
+
get: (id: number) => Promise<PublicAccessStatus>;
|
|
76
|
+
/** Applies synchronously; answers the GET shape. */
|
|
77
|
+
set: (id: number, input: PublicAccessPut) => Promise<PublicAccessStatus>;
|
|
78
|
+
disable: (id: number) => Promise<{ ok: boolean }>;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* 0.14: dump imports. Uploads are chunked at the server-advertised
|
|
82
|
+
* `chunkSize`; an S3 source (operator-only) downloads in the background
|
|
83
|
+
* (`uploading` → `pending`) and is then started like an upload.
|
|
84
|
+
*/
|
|
85
|
+
imports: {
|
|
86
|
+
create: (id: number, input: DatabaseImportCreate) => Promise<DatabaseImport>;
|
|
87
|
+
/** Chunk `index` must be the next one (`receivedBytes / chunkSize`). */
|
|
88
|
+
uploadChunk: (id: number, importId: number, index: number, chunk: Uint8Array) => Promise<DatabaseImport>;
|
|
89
|
+
/** 202: the job runs in the background; poll `get` or use `wait`. */
|
|
90
|
+
start: (id: number, importId: number) => Promise<DatabaseImport>;
|
|
91
|
+
list: (id: number) => Promise<DatabaseImport[]>;
|
|
92
|
+
get: (id: number, importId: number) => Promise<DatabaseImport>;
|
|
93
|
+
/** Cancels an `uploading` or `pending` import. */
|
|
94
|
+
cancel: (id: number, importId: number) => Promise<DatabaseImport>;
|
|
95
|
+
/** Poll until the import finishes (or, with `until: 'uploaded'`, leaves `uploading`). */
|
|
96
|
+
wait: (id: number, importId: number, opts?: ImportWaitOptions) => Promise<DatabaseImport>;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* 0.14: upload a dump in chunks (resuming from the server's
|
|
100
|
+
* `receivedBytes` when `resumeImportId` is given), start it, and
|
|
101
|
+
* optionally poll until it finishes.
|
|
102
|
+
*/
|
|
103
|
+
importFile: (id: number, data: ImportFileData, opts?: ImportFileOptions) => Promise<DatabaseImport>;
|
|
104
|
+
};
|
|
105
|
+
backups: {
|
|
106
|
+
storage: (databaseId: number) => Promise<{ sizeBytes: number }>;
|
|
107
|
+
backupNow: (databaseId: number) => Promise<Backup>;
|
|
108
|
+
listForDb: (databaseId: number) => Promise<Backup[]>;
|
|
109
|
+
restore: (databaseId: number, backupId: number) => Promise<{ ok: boolean }>;
|
|
110
|
+
list: () => Promise<BackupWithDb[]>;
|
|
111
|
+
remove: (backupId: number) => Promise<void>;
|
|
112
|
+
downloadUrl: (backupId: number) => string;
|
|
113
|
+
/** 0.12: the database's backup schedule. `configured: false` = no policy
|
|
114
|
+
* saved — the built-in daily run with 7 kept applies. */
|
|
115
|
+
getPolicy: (databaseId: number) => Promise<BackupPolicy>;
|
|
116
|
+
/** 0.12: save (replace) the database's backup schedule, retention and destination. */
|
|
117
|
+
setPolicy: (databaseId: number, input: BackupPolicyInput) => Promise<BackupPolicy>;
|
|
118
|
+
};
|
|
119
|
+
backupDestinations: {
|
|
120
|
+
list: () => Promise<Array<{ id: number; name: string; endpoint: string; region: string; bucket: string; prefix: string; active: boolean; createdAt: string }>>;
|
|
121
|
+
create: (input: { name: string; endpoint: string; region?: string; bucket: string; prefix?: string; accessKeyId: string; secretAccessKey: string }) => Promise<{ id: number }>;
|
|
122
|
+
update: (id: number, input: Partial<{ name: string; endpoint: string; region: string; bucket: string; prefix: string; active: boolean; accessKeyId: string; secretAccessKey: string }>) => Promise<{ ok: boolean }>;
|
|
123
|
+
remove: (id: number) => Promise<{ ok: boolean }>;
|
|
124
|
+
test: (id: number) => Promise<{ ok: boolean }>;
|
|
125
|
+
/** 0.14: list objects (operator-only); `prefix` defaults to the destination prefix. */
|
|
126
|
+
objects: (id: number, prefix?: string) => Promise<BackupDestinationObject[]>;
|
|
127
|
+
};
|
|
128
|
+
/** Per-volume backup history. The base URL is `/v1/volumes/:name/backups`;
|
|
129
|
+
* restore + download go through the same path. The global `/v1/backups`
|
|
130
|
+
* list/delete are exposed as `backups` below. */
|
|
131
|
+
volumeBackups: {
|
|
132
|
+
list: (volumeName: string) => Promise<Backup[]>;
|
|
133
|
+
create: (volumeName: string, input: CreateVolumeBackupInput) => Promise<Backup>;
|
|
134
|
+
restore: (volumeName: string, backupId: number) => Promise<{ ok: boolean }>;
|
|
135
|
+
downloadUrl: (volumeName: string, backupId: number) => string;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function databasesApi(t: Transport): DatabasesApi {
|
|
140
|
+
const { get, send, request } = t;
|
|
141
|
+
const { importPath, getImport, uploadChunk, startImport, waitForImport, importFile } = createImportHelpers(t);
|
|
142
|
+
return {
|
|
143
|
+
databases: {
|
|
144
|
+
list: (query) => get<ManagedDatabase[]>(`/v1/databases${query ?? ''}`),
|
|
145
|
+
create: (input) => send<ManagedDatabase>('POST', '/v1/databases', input),
|
|
146
|
+
get: (id) => get<DatabaseDetail>(`/v1/databases/${id}`),
|
|
147
|
+
remove: async (id, opts) => {
|
|
148
|
+
await request(`/v1/databases/${id}${opts?.force ? '?force=true' : ''}`, { method: 'DELETE' });
|
|
149
|
+
},
|
|
150
|
+
restart: async (id) => {
|
|
151
|
+
await request(`/v1/databases/${id}/restart`, { method: 'POST' });
|
|
152
|
+
},
|
|
153
|
+
stop: async (id) => {
|
|
154
|
+
await request(`/v1/databases/${id}/stop`, { method: 'POST' });
|
|
155
|
+
},
|
|
156
|
+
start: async (id) => {
|
|
157
|
+
await request(`/v1/databases/${id}/start`, { method: 'POST' });
|
|
158
|
+
},
|
|
159
|
+
logs: (id, lines) => get<{ logs: string[] }>(`/v1/databases/${id}/logs${lines ? `?lines=${lines}` : ''}`),
|
|
160
|
+
credentials: (id) => get<DatabaseCredentialsWithPublic>(`/v1/databases/${id}/credentials`),
|
|
161
|
+
setLimits: (id, input) => send<{ cpuShares: number | null; cpuLimitMilli: number | null; memLimitMb: number | null }>('PATCH', `/v1/databases/${id}/limits`, input),
|
|
162
|
+
startStudio: (id, port) => send<{ ok: boolean; port: number; url: string }>('POST', `/v1/databases/${id}/studio`, { port }),
|
|
163
|
+
stopStudio: async (id) => {
|
|
164
|
+
await request(`/v1/databases/${id}/studio`, { method: 'DELETE' });
|
|
165
|
+
return { ok: true };
|
|
166
|
+
},
|
|
167
|
+
drillBackup: (id, input) =>
|
|
168
|
+
send<BackupDrillResult>('POST', `/v1/databases/${id}/backups/drill`, input),
|
|
169
|
+
drills: (id) => get<BackupDrillEntry[]>(`/v1/databases/${id}/drills`),
|
|
170
|
+
pgbouncerStatus: (id) => get<PgbouncerStatus>(`/v1/databases/${id}/pgbouncer`),
|
|
171
|
+
enablePgbouncer: (id, input) =>
|
|
172
|
+
send<PgbouncerStatus>('POST', `/v1/databases/${id}/pgbouncer/enable`, input ?? {}),
|
|
173
|
+
disablePgbouncer: (id) =>
|
|
174
|
+
send<PgbouncerStatus>('POST', `/v1/databases/${id}/pgbouncer/disable`, {}),
|
|
175
|
+
publicAccess: {
|
|
176
|
+
get: (id) => get<PublicAccessStatus>(`/v1/databases/${id}/public-access`),
|
|
177
|
+
set: (id, input) => send<PublicAccessStatus>('PUT', `/v1/databases/${id}/public-access`, input),
|
|
178
|
+
disable: (id) => send<{ ok: boolean }>('DELETE', `/v1/databases/${id}/public-access`),
|
|
179
|
+
},
|
|
180
|
+
imports: {
|
|
181
|
+
create: (id, input) => send<DatabaseImport>('POST', importPath(id), input),
|
|
182
|
+
uploadChunk,
|
|
183
|
+
start: startImport,
|
|
184
|
+
list: (id) => get<DatabaseImport[]>(importPath(id)),
|
|
185
|
+
get: getImport,
|
|
186
|
+
cancel: (id, importId) => send<DatabaseImport>('DELETE', importPath(id, importId)),
|
|
187
|
+
wait: waitForImport,
|
|
188
|
+
},
|
|
189
|
+
importFile,
|
|
190
|
+
},
|
|
191
|
+
backups: {
|
|
192
|
+
storage: (databaseId) => get<{ sizeBytes: number }>(`/v1/databases/${databaseId}/storage`),
|
|
193
|
+
backupNow: (databaseId) => send<Backup>('POST', `/v1/databases/${databaseId}/backups`),
|
|
194
|
+
listForDb: (databaseId) => get<Backup[]>(`/v1/databases/${databaseId}/backups`),
|
|
195
|
+
restore: (databaseId, backupId) => send<{ ok: boolean }>('POST', `/v1/databases/${databaseId}/backups/${backupId}/restore`),
|
|
196
|
+
list: () => get<BackupWithDb[]>('/v1/backups'),
|
|
197
|
+
remove: async (backupId) => {
|
|
198
|
+
await request(`/v1/backups/${backupId}`, { method: 'DELETE' });
|
|
199
|
+
},
|
|
200
|
+
downloadUrl: (backupId) => `/v1/backups/${backupId}/download`,
|
|
201
|
+
getPolicy: (databaseId) => get<BackupPolicy>(`/v1/databases/${databaseId}/backup-policy`),
|
|
202
|
+
setPolicy: (databaseId, input) => send<BackupPolicy>('PUT', `/v1/databases/${databaseId}/backup-policy`, input),
|
|
203
|
+
},
|
|
204
|
+
backupDestinations: {
|
|
205
|
+
list: () => get('/v1/backup-destinations'),
|
|
206
|
+
create: (input) => send<{ id: number }>('POST', '/v1/backup-destinations', input),
|
|
207
|
+
update: (id, input) => send<{ ok: boolean }>('PATCH', `/v1/backup-destinations/${id}`, input),
|
|
208
|
+
remove: (id) => send<{ ok: boolean }>('DELETE', `/v1/backup-destinations/${id}`),
|
|
209
|
+
test: (id) => send<{ ok: boolean }>('POST', `/v1/backup-destinations/${id}/test`),
|
|
210
|
+
objects: (id, prefix) =>
|
|
211
|
+
get<BackupDestinationObject[]>(
|
|
212
|
+
`/v1/backup-destinations/${id}/objects${prefix === undefined ? '' : `?prefix=${encodeURIComponent(prefix)}`}`,
|
|
213
|
+
),
|
|
214
|
+
},
|
|
215
|
+
volumeBackups: {
|
|
216
|
+
// Path segments are user-visible names: encode them like the rest of
|
|
217
|
+
// the client does, or '?'/'#'/'%' in a volume name rewrite the URL.
|
|
218
|
+
list: (volumeName) => get<Backup[]>(`/v1/volumes/${encodeURIComponent(volumeName)}/backups`),
|
|
219
|
+
create: (volumeName, input) =>
|
|
220
|
+
send<Backup>('POST', `/v1/volumes/${encodeURIComponent(volumeName)}/backups`, input),
|
|
221
|
+
restore: (volumeName, backupId) =>
|
|
222
|
+
send<{ ok: boolean }>('POST', `/v1/volumes/${encodeURIComponent(volumeName)}/backups/${backupId}/restore`),
|
|
223
|
+
downloadUrl: (volumeName, backupId) =>
|
|
224
|
+
`/v1/volumes/${encodeURIComponent(volumeName)}/backups/${backupId}/download`,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|