@ninedeploy/sdk 0.10.5 → 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,161 @@
|
|
|
1
|
+
// Client surface: deploys, webhooks, jobs, insights, ai.
|
|
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
|
+
AnalyzeRepoInput,
|
|
6
|
+
WebhookCreate,
|
|
7
|
+
CreatedWebhook,
|
|
8
|
+
Deployment,
|
|
9
|
+
QueueResponse,
|
|
10
|
+
AiConfigStatus,
|
|
11
|
+
AiConfigUpdate,
|
|
12
|
+
RepoInsights,
|
|
13
|
+
TriggerDeploy,
|
|
14
|
+
Webhook,
|
|
15
|
+
ImageTransfer,
|
|
16
|
+
} from '@ninedeploy/schemas';
|
|
17
|
+
import type { JobKind, ScheduledJob, JobRun } from '../types.js';
|
|
18
|
+
import type { Transport } from '../transport.js';
|
|
19
|
+
|
|
20
|
+
export interface DeploysApi {
|
|
21
|
+
deploys: {
|
|
22
|
+
/**
|
|
23
|
+
* Queue a deployment of the service's configured branch head. Answers
|
|
24
|
+
* `alreadyInProgress: true` with the in-flight deployment's id when one
|
|
25
|
+
* is already building or deploying.
|
|
26
|
+
*/
|
|
27
|
+
trigger: (serviceId: number, input?: TriggerDeploy) => Promise<{ deploymentId: number; alreadyInProgress?: boolean }>;
|
|
28
|
+
list: (serviceId: number) => Promise<Deployment[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Global deploy queue view: every in-flight (queued / building /
|
|
31
|
+
* deploying) deployment the caller can see, with service + project
|
|
32
|
+
* metadata. The optional `status` filter is a comma-separated token
|
|
33
|
+
* list (e.g. `?status=queued,building`).
|
|
34
|
+
*/
|
|
35
|
+
queue: (query?: string) => Promise<QueueResponse>;
|
|
36
|
+
rollback: (serviceId: number, deploymentId: number) => Promise<{ deploymentId: number }>;
|
|
37
|
+
/** Cancel a queued/in-flight deployment (checkpoints abort at step boundaries). */
|
|
38
|
+
cancel: (serviceId: number, deploymentId: number) => Promise<{ ok: boolean; status: string }>;
|
|
39
|
+
/**
|
|
40
|
+
* Remove a finished deployment from history, with its build log.
|
|
41
|
+
* Refused for an in-flight deployment (cancel it first) and for the one
|
|
42
|
+
* currently serving traffic. Requires the `admin` role on the service.
|
|
43
|
+
*/
|
|
44
|
+
remove: (serviceId: number, deploymentId: number) => Promise<{ ok: boolean; id: number }>;
|
|
45
|
+
/** Build-config + env-key diff against the previous deployment. */
|
|
46
|
+
configDiff: (serviceId: number, deploymentId: number) => Promise<{ deploymentId: number; previousDeploymentId: number | null; changed: boolean; diff: string }>;
|
|
47
|
+
/** Build log download URL (serves the raw log file as an attachment). */
|
|
48
|
+
logDownloadUrl: (serviceId: number, deploymentId: number) => string;
|
|
49
|
+
/**
|
|
50
|
+
* Promote: deploy ANOTHER service at this service's exact running
|
|
51
|
+
* commit — the staging → production lane hop. Both services must
|
|
52
|
+
* track the same repository and the caller needs `member` on both.
|
|
53
|
+
*/
|
|
54
|
+
promote: (serviceId: number, targetServiceId: number) => Promise<{ ok: boolean; deploymentId: number; commitSha: string; promotedFrom: string }>;
|
|
55
|
+
// ── 0.16 T8 surfaces ──
|
|
56
|
+
/** Image transfers of one deployment (`GET /v1/deployments/:id/image-transfers`, any seat on its service). */
|
|
57
|
+
imageTransfers: (deploymentId: number) => Promise<ImageTransfer[]>;
|
|
58
|
+
// ── end 0.16 T8 ──
|
|
59
|
+
};
|
|
60
|
+
webhooks: {
|
|
61
|
+
list: (serviceId: number) => Promise<Webhook[]>;
|
|
62
|
+
create: (serviceId: number, input?: WebhookCreate) => Promise<CreatedWebhook>;
|
|
63
|
+
remove: (serviceId: number, hookId: number) => Promise<void>;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* r552: aligned with apps/server/src/modules/jobs.ts — `kind` includes
|
|
67
|
+
* `'backup'`, and create/update return the full serialized job (the PATCH
|
|
68
|
+
* was typed `{ ok }`, a field the server never sent).
|
|
69
|
+
*/
|
|
70
|
+
jobs: {
|
|
71
|
+
list: (serviceId: number) => Promise<ScheduledJob[]>;
|
|
72
|
+
create: (serviceId: number, input: { name: string; cron: string; kind: JobKind; command?: string; enabled?: boolean }) => Promise<ScheduledJob>;
|
|
73
|
+
update: (serviceId: number, jobId: number, input: Partial<{ name: string; cron: string; kind: JobKind; command: string; enabled: boolean }>) => Promise<ScheduledJob>;
|
|
74
|
+
remove: (serviceId: number, jobId: number) => Promise<{ ok: boolean }>;
|
|
75
|
+
run: (serviceId: number, jobId: number) => Promise<{ ok: boolean }>;
|
|
76
|
+
runs: (serviceId: number, jobId: number) => Promise<JobRun[]>;
|
|
77
|
+
};
|
|
78
|
+
insights: {
|
|
79
|
+
/** Pre-deploy repository inspection (DeployWizard): clone + framework detection. */
|
|
80
|
+
analyze: (input: AnalyzeRepoInput) => Promise<RepoInsights>;
|
|
81
|
+
/** Latest stored analysis for a service (null before the first deploy/refresh). */
|
|
82
|
+
get: (serviceId: number) => Promise<RepoInsights | null>;
|
|
83
|
+
/** Re-analyze the service's repository now and store the result. */
|
|
84
|
+
refresh: (serviceId: number) => Promise<RepoInsights>;
|
|
85
|
+
};
|
|
86
|
+
/** AI failure diagnosis (BYO-key): operator-configured, OpenAI-compatible. */
|
|
87
|
+
ai: {
|
|
88
|
+
/** Never carries key material — only endpoint/model + hasApiKey. */
|
|
89
|
+
getConfig: () => Promise<AiConfigStatus>;
|
|
90
|
+
updateConfig: (input: AiConfigUpdate) => Promise<{ ok: boolean }>;
|
|
91
|
+
/** Ask the configured provider to diagnose a failed build log. */
|
|
92
|
+
diagnose: (serviceId: number, deploymentId: number) => Promise<{ diagnosis: string; model: string }>;
|
|
93
|
+
/** Describe the app in plain language, get a schema-valid .ninedeploy manifest. */
|
|
94
|
+
suggestManifest: (description: string) => Promise<{ manifest: Record<string, unknown>; model: string }>;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function deploysApi(t: Transport): DeploysApi {
|
|
99
|
+
const { get, send, request } = t;
|
|
100
|
+
return {
|
|
101
|
+
deploys: {
|
|
102
|
+
trigger: (serviceId, input) =>
|
|
103
|
+
send<{ deploymentId: number; alreadyInProgress?: boolean }>('POST', `/v1/services/${serviceId}/deploys`, input ?? {}),
|
|
104
|
+
list: (serviceId) => get<Deployment[]>(`/v1/services/${serviceId}/deploys`),
|
|
105
|
+
queue: (query) =>
|
|
106
|
+
get<QueueResponse>(`/v1/services/queue${query ? (query.startsWith('?') ? query : `?${query}`) : ''}`),
|
|
107
|
+
rollback: (serviceId, deploymentId) =>
|
|
108
|
+
send<{ deploymentId: number }>('POST', `/v1/services/${serviceId}/deploys/${deploymentId}/rollback`),
|
|
109
|
+
cancel: (serviceId, deploymentId) =>
|
|
110
|
+
send<{ ok: boolean; status: string }>('POST', `/v1/services/${serviceId}/deploys/${deploymentId}/cancel`),
|
|
111
|
+
remove: (serviceId, deploymentId) =>
|
|
112
|
+
send<{ ok: boolean; id: number }>('DELETE', `/v1/services/${serviceId}/deploys/${deploymentId}`),
|
|
113
|
+
configDiff: (serviceId, deploymentId) =>
|
|
114
|
+
get<{ deploymentId: number; previousDeploymentId: number | null; changed: boolean; diff: string }>(
|
|
115
|
+
`/v1/services/${serviceId}/deploys/${deploymentId}/diff`,
|
|
116
|
+
),
|
|
117
|
+
promote: (serviceId, targetServiceId) =>
|
|
118
|
+
send<{ ok: boolean; deploymentId: number; commitSha: string; promotedFrom: string }>(
|
|
119
|
+
'POST',
|
|
120
|
+
`/v1/services/${serviceId}/promote`,
|
|
121
|
+
{ targetServiceId },
|
|
122
|
+
),
|
|
123
|
+
logDownloadUrl: (serviceId, deploymentId) =>
|
|
124
|
+
`/v1/services/${serviceId}/deploys/${deploymentId}/logs/download`,
|
|
125
|
+
// ── 0.16 T8 surfaces ──
|
|
126
|
+
imageTransfers: (deploymentId) => get<ImageTransfer[]>(`/v1/deployments/${deploymentId}/image-transfers`),
|
|
127
|
+
// ── end 0.16 T8 ──
|
|
128
|
+
},
|
|
129
|
+
webhooks: {
|
|
130
|
+
list: (serviceId) => get<Webhook[]>(`/v1/services/${serviceId}/webhooks`),
|
|
131
|
+
create: (serviceId, input) => send<CreatedWebhook>('POST', `/v1/services/${serviceId}/webhooks`, input ?? {}),
|
|
132
|
+
remove: async (serviceId, hookId) => {
|
|
133
|
+
await request(`/v1/services/${serviceId}/webhooks/${hookId}`, { method: 'DELETE' });
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
jobs: {
|
|
137
|
+
list: (serviceId) => get(`/v1/services/${serviceId}/jobs`),
|
|
138
|
+
create: (serviceId, input) => send<ScheduledJob>('POST', `/v1/services/${serviceId}/jobs`, input),
|
|
139
|
+
update: (serviceId, jobId, input) => send<ScheduledJob>('PATCH', `/v1/services/${serviceId}/jobs/${jobId}`, input),
|
|
140
|
+
remove: (serviceId, jobId) => send<{ ok: boolean }>('DELETE', `/v1/services/${serviceId}/jobs/${jobId}`),
|
|
141
|
+
run: (serviceId, jobId) => send<{ ok: boolean }>('POST', `/v1/services/${serviceId}/jobs/${jobId}/run`),
|
|
142
|
+
runs: (serviceId, jobId) => get(`/v1/services/${serviceId}/jobs/${jobId}/runs`),
|
|
143
|
+
},
|
|
144
|
+
insights: {
|
|
145
|
+
analyze: (input) => send<RepoInsights>('POST', '/v1/insights', input),
|
|
146
|
+
get: (serviceId) => get<RepoInsights | null>(`/v1/services/${serviceId}/insights`),
|
|
147
|
+
refresh: (serviceId) => send<RepoInsights>('POST', `/v1/services/${serviceId}/insights/refresh`),
|
|
148
|
+
},
|
|
149
|
+
ai: {
|
|
150
|
+
getConfig: () => get<AiConfigStatus>('/v1/ai/config'),
|
|
151
|
+
updateConfig: (input) => send<{ ok: boolean }>('PUT', '/v1/ai/config', input),
|
|
152
|
+
diagnose: (serviceId, deploymentId) =>
|
|
153
|
+
send<{ diagnosis: string; model: string }>(
|
|
154
|
+
'POST',
|
|
155
|
+
`/v1/ai/services/${serviceId}/deploys/${deploymentId}/diagnose`,
|
|
156
|
+
),
|
|
157
|
+
suggestManifest: (description) =>
|
|
158
|
+
send<{ manifest: Record<string, unknown>; model: string }>('POST', '/v1/ai/suggest-manifest', { description }),
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Client surface: domains, domainPresets.
|
|
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
|
+
CreateDomainInput,
|
|
6
|
+
Domain,
|
|
7
|
+
DomainPatch,
|
|
8
|
+
DomainEntry,
|
|
9
|
+
} from '@ninedeploy/schemas';
|
|
10
|
+
import type { CreatedDomain, DomainVerifyResult, StartDomainTransferResult, DomainTransferPreview, AcceptDomainTransferInput, AcceptDomainTransferResult } from '../types.js';
|
|
11
|
+
import type { Transport } from '../transport.js';
|
|
12
|
+
|
|
13
|
+
export interface DomainsApi {
|
|
14
|
+
domains: {
|
|
15
|
+
list: (serviceId: number) => Promise<Domain[]>;
|
|
16
|
+
/** Add a domain. A non-operator's hostname outside the instance zone comes back `pending` with a TXT `verification` challenge. */
|
|
17
|
+
create: (serviceId: number, input: CreateDomainInput) => Promise<CreatedDomain>;
|
|
18
|
+
/**
|
|
19
|
+
* r332: prove ownership of a `pending` domain and bring it live. Checks
|
|
20
|
+
* the TXT challenge `create` returned; idempotent (an `active` domain
|
|
21
|
+
* answers `verified: true`) and safe to poll while DNS propagates.
|
|
22
|
+
*/
|
|
23
|
+
verify: (serviceId: number, domainId: number) => Promise<DomainVerifyResult>;
|
|
24
|
+
remove: (serviceId: number, domainId: number) => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* DNS status for a domain: does the hostname resolve to the addresses
|
|
27
|
+
* this panel expects? Advisory — routing is governed by ownership.
|
|
28
|
+
*/
|
|
29
|
+
dnsCheck: (
|
|
30
|
+
serviceId: number,
|
|
31
|
+
domainId: number,
|
|
32
|
+
) => Promise<{
|
|
33
|
+
hostname: string;
|
|
34
|
+
status: 'ok' | 'mismatch' | 'unresolved';
|
|
35
|
+
addresses: { a: string[]; aaaa: string[] };
|
|
36
|
+
expected: string[];
|
|
37
|
+
}>;
|
|
38
|
+
/** Update routing extras: ssl toggle, www→apex redirect, custom headers, basicAuth, ipAllowlist, rateLimit. */
|
|
39
|
+
update: (serviceId: number, domainId: number, input: DomainPatch) => Promise<Domain>;
|
|
40
|
+
all: () => Promise<DomainEntry[]>;
|
|
41
|
+
setSsl: (domainId: number, ssl: boolean) => Promise<{ id: number; ssl: boolean }>;
|
|
42
|
+
/** Toggle sticky-session routing for the service (G-28). */
|
|
43
|
+
setStickySession: (serviceId: number, enabled: boolean) => Promise<{ id: number; enabled: boolean; active: boolean }>;
|
|
44
|
+
/**
|
|
45
|
+
* Start a domain transfer (G-29). The source user (admin
|
|
46
|
+
* on the source service) names the target email; the
|
|
47
|
+
* server returns a one-time `acceptUrl` to forward. The
|
|
48
|
+
* target user accepts via `acceptDomainTransfer(token,
|
|
49
|
+
* { targetServiceId })`. The transfer expires after 7
|
|
50
|
+
* days; cancelling is one call.
|
|
51
|
+
*/
|
|
52
|
+
transfer: (domainId: number, input: { targetEmail: string }) => Promise<StartDomainTransferResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Preview a transfer by token. Does NOT require auth
|
|
55
|
+
* (the token is the secret) — the panel uses this to
|
|
56
|
+
* render the accept page to a logged-out visitor.
|
|
57
|
+
*/
|
|
58
|
+
previewTransfer: (token: string) => Promise<DomainTransferPreview>;
|
|
59
|
+
/**
|
|
60
|
+
* Accept a transfer. The caller must be authenticated
|
|
61
|
+
* and the caller's email must equal the transfer's
|
|
62
|
+
* target email; the panel / CLI surfaces the preview
|
|
63
|
+
* first so the user knows what they're signing in to.
|
|
64
|
+
*/
|
|
65
|
+
acceptTransfer: (token: string, input: AcceptDomainTransferInput) => Promise<AcceptDomainTransferResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Cancel a pending transfer. Only the source user
|
|
68
|
+
* (or an instance operator) can cancel.
|
|
69
|
+
*/
|
|
70
|
+
cancelTransfer: (token: string) => Promise<{ transferId: number; status: 'cancelled' }>;
|
|
71
|
+
};
|
|
72
|
+
domainPresets: {
|
|
73
|
+
/** Every `IDomainProvider` the running kernel has registered. */
|
|
74
|
+
list: () => Promise<{ providers: string[] }>;
|
|
75
|
+
/**
|
|
76
|
+
* Manually create the DNS record for `hostname` via the operator's
|
|
77
|
+
* active provider. `content` is optional: when omitted the route
|
|
78
|
+
* falls back to the panel's `dns_records_content` setting, then to
|
|
79
|
+
* `detectPublicIp()`. Returns the upstream recordId so the caller
|
|
80
|
+
* can correlate.
|
|
81
|
+
*/
|
|
82
|
+
apply: (input: { hostname: string; content?: string }) => Promise<{
|
|
83
|
+
hostname: string;
|
|
84
|
+
provider: string;
|
|
85
|
+
zone: string;
|
|
86
|
+
recordId: string;
|
|
87
|
+
type: 'A' | 'CNAME';
|
|
88
|
+
content: string;
|
|
89
|
+
}>;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function domainsApi(t: Transport): DomainsApi {
|
|
94
|
+
const { get, send, request } = t;
|
|
95
|
+
return {
|
|
96
|
+
domains: {
|
|
97
|
+
list: (serviceId) => get<Domain[]>(`/v1/services/${serviceId}/domains`),
|
|
98
|
+
create: (serviceId, input) => send<CreatedDomain>('POST', `/v1/services/${serviceId}/domains`, input),
|
|
99
|
+
verify: (serviceId, domainId) =>
|
|
100
|
+
send<DomainVerifyResult>('POST', `/v1/services/${serviceId}/domains/${domainId}/verify`, {}),
|
|
101
|
+
dnsCheck: (serviceId, domainId) =>
|
|
102
|
+
get<{
|
|
103
|
+
hostname: string;
|
|
104
|
+
status: 'ok' | 'mismatch' | 'unresolved';
|
|
105
|
+
addresses: { a: string[]; aaaa: string[] };
|
|
106
|
+
expected: string[];
|
|
107
|
+
}>(`/v1/services/${serviceId}/domains/${domainId}/dns`),
|
|
108
|
+
update: (serviceId, domainId, input) =>
|
|
109
|
+
send<Domain>('PATCH', `/v1/services/${serviceId}/domains/${domainId}`, input),
|
|
110
|
+
remove: async (serviceId, domainId) => {
|
|
111
|
+
await request(`/v1/services/${serviceId}/domains/${domainId}`, { method: 'DELETE' });
|
|
112
|
+
},
|
|
113
|
+
all: () => get<DomainEntry[]>('/v1/domains'),
|
|
114
|
+
setSsl: (domainId, ssl) => send<{ id: number; ssl: boolean }>('PATCH', `/v1/domains/${domainId}`, { ssl }),
|
|
115
|
+
/** Toggle sticky-session routing (G-28) — sits under `/domains` for legacy compatibility. */
|
|
116
|
+
setStickySession: (serviceId, enabled) =>
|
|
117
|
+
send<{ id: number; enabled: boolean; active: boolean }>(
|
|
118
|
+
'POST',
|
|
119
|
+
`/v1/services/${serviceId}/sticky-session`,
|
|
120
|
+
{ enabled },
|
|
121
|
+
),
|
|
122
|
+
transfer: (domainId, input) =>
|
|
123
|
+
send<StartDomainTransferResult>('POST', `/v1/domains/${domainId}/transfer`, input),
|
|
124
|
+
previewTransfer: (token) => get<DomainTransferPreview>(`/v1/domain-transfers/${token}`),
|
|
125
|
+
acceptTransfer: (token, input) =>
|
|
126
|
+
send<AcceptDomainTransferResult>('POST', `/v1/domain-transfers/${token}/accept`, input),
|
|
127
|
+
cancelTransfer: (token) =>
|
|
128
|
+
send<{ transferId: number; status: 'cancelled' }>('POST', `/v1/domain-transfers/${token}/cancel`, {}),
|
|
129
|
+
},
|
|
130
|
+
domainPresets: {
|
|
131
|
+
list: () => get<{ providers: string[] }>('/v1/domain-presets'),
|
|
132
|
+
apply: (input) => send<{
|
|
133
|
+
hostname: string;
|
|
134
|
+
provider: string;
|
|
135
|
+
zone: string;
|
|
136
|
+
recordId: string;
|
|
137
|
+
type: 'A' | 'CNAME';
|
|
138
|
+
content: string;
|
|
139
|
+
}>('POST', '/v1/domain-presets/apply', input),
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Client surface: env, projectEnv, previewEnv.
|
|
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
|
+
EnvVar,
|
|
6
|
+
UpsertEnvVarInput,
|
|
7
|
+
} from '@ninedeploy/schemas';
|
|
8
|
+
import type { Transport } from '../transport.js';
|
|
9
|
+
|
|
10
|
+
export interface EnvApi {
|
|
11
|
+
env: {
|
|
12
|
+
list: (serviceId: number) => Promise<EnvVar[]>;
|
|
13
|
+
create: (serviceId: number, input: UpsertEnvVarInput) => Promise<EnvVar>;
|
|
14
|
+
update: (serviceId: number, varId: number, input: UpsertEnvVarInput) => Promise<EnvVar>;
|
|
15
|
+
remove: (serviceId: number, varId: number) => Promise<void>;
|
|
16
|
+
/** Bulk import from a .env-formatted string. Returns imported/skipped counts. */
|
|
17
|
+
import: (serviceId: number, content: string) => Promise<{ imported: number; skipped: number; errors: Array<{ line: number; message: string }> }>;
|
|
18
|
+
/** Export non-secret env vars as a .env-formatted string (admin floor). */
|
|
19
|
+
export: (serviceId: number) => Promise<{ content: string; count: number }>;
|
|
20
|
+
/** Cross-scope key search: where a given env key is defined. */
|
|
21
|
+
search: (q: string) => Promise<{ results: Array<{ key: string; isSecret: boolean; scope: string; serviceId: number | null; serviceName: string | null }> }>;
|
|
22
|
+
};
|
|
23
|
+
projectEnv: {
|
|
24
|
+
/** Shared env vars applied to every service in a project (service env wins). */
|
|
25
|
+
list: (projectId: number) => Promise<EnvVar[]>;
|
|
26
|
+
create: (projectId: number, input: UpsertEnvVarInput) => Promise<EnvVar>;
|
|
27
|
+
update: (projectId: number, varId: number, input: UpsertEnvVarInput) => Promise<EnvVar>;
|
|
28
|
+
remove: (projectId: number, varId: number) => Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* 0.12 preview-only env: values a service hands ONLY to its PR previews
|
|
32
|
+
* (they override the parent's non-secret values there; production never
|
|
33
|
+
* sees them). Same floors as `env`: viewer reads, member writes. A panel
|
|
34
|
+
* older than 0.12 answers 404.
|
|
35
|
+
*/
|
|
36
|
+
previewEnv: {
|
|
37
|
+
list: (serviceId: number) => Promise<EnvVar[]>;
|
|
38
|
+
create: (serviceId: number, input: UpsertEnvVarInput) => Promise<EnvVar>;
|
|
39
|
+
update: (serviceId: number, varId: number, input: UpsertEnvVarInput) => Promise<EnvVar>;
|
|
40
|
+
remove: (serviceId: number, varId: number) => Promise<void>;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function envApi(t: Transport): EnvApi {
|
|
45
|
+
const { get, send, request } = t;
|
|
46
|
+
return {
|
|
47
|
+
env: {
|
|
48
|
+
list: (serviceId) => get<EnvVar[]>(`/v1/services/${serviceId}/env`),
|
|
49
|
+
create: (serviceId, input) => send<EnvVar>('POST', `/v1/services/${serviceId}/env`, input),
|
|
50
|
+
update: (serviceId, varId, input) => send<EnvVar>('PATCH', `/v1/services/${serviceId}/env/${varId}`, input),
|
|
51
|
+
remove: async (serviceId, varId) => {
|
|
52
|
+
await request(`/v1/services/${serviceId}/env/${varId}`, { method: 'DELETE' });
|
|
53
|
+
},
|
|
54
|
+
import: (serviceId, content) =>
|
|
55
|
+
send<{ imported: number; skipped: number; errors: Array<{ line: number; message: string }> }>(
|
|
56
|
+
'POST',
|
|
57
|
+
`/v1/services/${serviceId}/env/import`,
|
|
58
|
+
{ content },
|
|
59
|
+
),
|
|
60
|
+
export: (serviceId) =>
|
|
61
|
+
get<{ content: string; count: number }>(`/v1/services/${serviceId}/env/export`),
|
|
62
|
+
search: (q) =>
|
|
63
|
+
get<{ results: Array<{ key: string; isSecret: boolean; scope: string; serviceId: number | null; serviceName: string | null }> }>(
|
|
64
|
+
`/v1/env/search?q=${encodeURIComponent(q)}`,
|
|
65
|
+
),
|
|
66
|
+
},
|
|
67
|
+
projectEnv: {
|
|
68
|
+
list: (projectId) => get<EnvVar[]>(`/v1/projects/${projectId}/env`),
|
|
69
|
+
create: (projectId, input) => send<EnvVar>('POST', `/v1/projects/${projectId}/env`, input),
|
|
70
|
+
update: (projectId, varId, input) => send<EnvVar>('PATCH', `/v1/projects/${projectId}/env/${varId}`, input),
|
|
71
|
+
remove: async (projectId, varId) => {
|
|
72
|
+
await request(`/v1/projects/${projectId}/env/${varId}`, { method: 'DELETE' });
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
previewEnv: {
|
|
76
|
+
list: (serviceId) => get<EnvVar[]>(`/v1/services/${serviceId}/env/preview`),
|
|
77
|
+
create: (serviceId, input) => send<EnvVar>('POST', `/v1/services/${serviceId}/env/preview`, input),
|
|
78
|
+
update: (serviceId, varId, input) => send<EnvVar>('PATCH', `/v1/services/${serviceId}/env/preview/${varId}`, input),
|
|
79
|
+
remove: async (serviceId, varId) => {
|
|
80
|
+
await request(`/v1/services/${serviceId}/env/preview/${varId}`, { method: 'DELETE' });
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Client surface: logDrains.
|
|
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
|
+
LogDrain,
|
|
6
|
+
LogDrainCreateInput,
|
|
7
|
+
LogDrainUpdateInput,
|
|
8
|
+
LogDrainTestResult,
|
|
9
|
+
} from '@ninedeploy/schemas';
|
|
10
|
+
import type { LogSearchInput, LogSearchResult } from '../types.js';
|
|
11
|
+
import type { Transport } from '../transport.js';
|
|
12
|
+
|
|
13
|
+
export interface LogsApi {
|
|
14
|
+
logDrains: {
|
|
15
|
+
list: (query?: { serviceId?: number }) => Promise<LogDrain[]>;
|
|
16
|
+
get: (id: number) => Promise<LogDrain>;
|
|
17
|
+
create: (input: LogDrainCreateInput) => Promise<LogDrain>;
|
|
18
|
+
update: (id: number, input: LogDrainUpdateInput) => Promise<LogDrain>;
|
|
19
|
+
remove: (id: number) => Promise<{ ok: boolean }>;
|
|
20
|
+
test: (id: number) => Promise<LogDrainTestResult>;
|
|
21
|
+
/**
|
|
22
|
+
* Search the configured Loki drain for `query` over
|
|
23
|
+
* the last `sinceMinutes` (default 15). The route
|
|
24
|
+
* round-trips to the drain's `url` so the upstream
|
|
25
|
+
* does the heavy lifting; this is the same shape
|
|
26
|
+
* the operator sees in the Loki Grafana panel.
|
|
27
|
+
*/
|
|
28
|
+
search: (input: LogSearchInput) => Promise<LogSearchResult>;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function logsApi(t: Transport): LogsApi {
|
|
33
|
+
const { get, send } = t;
|
|
34
|
+
return {
|
|
35
|
+
logDrains: {
|
|
36
|
+
list: (query) => {
|
|
37
|
+
const qs = query?.serviceId !== undefined ? `?serviceId=${encodeURIComponent(String(query.serviceId))}` : '';
|
|
38
|
+
return get<LogDrain[]>(`/v1/log-drains${qs}`);
|
|
39
|
+
},
|
|
40
|
+
get: (id) => get<LogDrain>(`/v1/log-drains/${id}`),
|
|
41
|
+
create: (input) => send<LogDrain>('POST', '/v1/log-drains', input),
|
|
42
|
+
update: (id, input) => send<LogDrain>('PATCH', `/v1/log-drains/${id}`, input),
|
|
43
|
+
remove: (id) => send<{ ok: boolean }>('DELETE', `/v1/log-drains/${id}`),
|
|
44
|
+
test: (id) => send<LogDrainTestResult>('POST', `/v1/log-drains/${id}/test`),
|
|
45
|
+
search: (input) => send<LogSearchResult>('POST', '/v1/log-drains/search', input),
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Client surface: networks, tunnels, firewall, egress.
|
|
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
|
+
CreateTunnelInput,
|
|
6
|
+
TunnelEntry,
|
|
7
|
+
} from '@ninedeploy/schemas';
|
|
8
|
+
import type { Transport } from '../transport.js';
|
|
9
|
+
|
|
10
|
+
export interface NetworkApi {
|
|
11
|
+
networks: {
|
|
12
|
+
list: () => Promise<{
|
|
13
|
+
networks: Array<{
|
|
14
|
+
name: string;
|
|
15
|
+
driver: string;
|
|
16
|
+
members: string[];
|
|
17
|
+
/** True when the network is reserved by NineDeploy (cannot be removed). */
|
|
18
|
+
isManaged?: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
remote: number | null;
|
|
21
|
+
}>;
|
|
22
|
+
create: (input: { name: string; driver?: 'bridge' | 'overlay'; serverId?: number | null }) => Promise<{ ok: boolean; name: string }>;
|
|
23
|
+
remove: (name: string, serverId?: number) => Promise<{ ok: boolean }>;
|
|
24
|
+
attach: (input: { network: string; container: string; serverId?: number | null }) => Promise<{ ok: boolean }>;
|
|
25
|
+
detach: (input: { network: string; container: string; serverId?: number | null }) => Promise<{ ok: boolean }>;
|
|
26
|
+
/**
|
|
27
|
+
* r557: container names attached to one network (operator-only). The
|
|
28
|
+
* server answers `{ members: [] }` when docker cannot inspect it.
|
|
29
|
+
*/
|
|
30
|
+
members: (name: string) => Promise<{ members: string[] }>;
|
|
31
|
+
};
|
|
32
|
+
tunnels: {
|
|
33
|
+
list: () => Promise<TunnelEntry[]>;
|
|
34
|
+
create: (input: CreateTunnelInput) => Promise<TunnelEntry>;
|
|
35
|
+
remove: (id: number) => Promise<void>;
|
|
36
|
+
};
|
|
37
|
+
firewall: {
|
|
38
|
+
status: () => Promise<{
|
|
39
|
+
installed: boolean;
|
|
40
|
+
active: boolean;
|
|
41
|
+
supported: boolean;
|
|
42
|
+
rules: Array<{ id: number; to: string; action: string; from: string; comment?: string }>;
|
|
43
|
+
defaultIncoming: string;
|
|
44
|
+
defaultOutgoing: string;
|
|
45
|
+
}>;
|
|
46
|
+
toggle: (enabled: boolean) => Promise<{ ok: boolean; status: any }>;
|
|
47
|
+
addRule: (input: { port: number | string; proto?: 'tcp' | 'udp' | 'any'; action?: 'allow' | 'deny' | 'limit'; from?: string; comment?: string }) => Promise<{ ok: boolean; status: any }>;
|
|
48
|
+
deleteRule: (id: number | string) => Promise<{ ok: boolean; status: any }>;
|
|
49
|
+
applyRecommended: () => Promise<{ ok: boolean; status: any }>;
|
|
50
|
+
};
|
|
51
|
+
egress: {
|
|
52
|
+
/** Every egress IP rule across every registered driver. G-15. */
|
|
53
|
+
list: () => Promise<{
|
|
54
|
+
drivers: Array<{
|
|
55
|
+
name: string;
|
|
56
|
+
rules: Array<{
|
|
57
|
+
selector: { projectId: number; sourceCidr?: string };
|
|
58
|
+
ip: string;
|
|
59
|
+
createdAt: string;
|
|
60
|
+
}>;
|
|
61
|
+
}>;
|
|
62
|
+
}>;
|
|
63
|
+
/** Attach an egress IP to a project on the named (or first) driver. */
|
|
64
|
+
set: (input: { projectId: number; ip: string; driver?: string }) => Promise<{
|
|
65
|
+
ok: boolean;
|
|
66
|
+
driver: string;
|
|
67
|
+
rule: { selector: { projectId: number; sourceCidr?: string }; ip: string; createdAt: string };
|
|
68
|
+
/** Present (with `ok: false`) when the route refused the request. */
|
|
69
|
+
error?: string;
|
|
70
|
+
}>;
|
|
71
|
+
/** Detach the rule for a project (on the named or first driver). */
|
|
72
|
+
clear: (projectId: number, driver?: string) => Promise<{ ok: boolean; driver: string; error?: string }>;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function networkApi(t: Transport): NetworkApi {
|
|
77
|
+
const { get, send, request } = t;
|
|
78
|
+
return {
|
|
79
|
+
networks: {
|
|
80
|
+
list: () => get<{ networks: Array<{ name: string; driver: string; members: string[] }>; remote: number | null }>('/v1/networks'),
|
|
81
|
+
create: (input) => send<{ ok: boolean; name: string }>('POST', '/v1/networks', { driver: 'bridge', ...input }),
|
|
82
|
+
remove: (name, serverId) =>
|
|
83
|
+
send<{ ok: boolean }>('DELETE', `/v1/networks/${encodeURIComponent(name)}${serverId != null ? `?serverId=${serverId}` : ''}`),
|
|
84
|
+
attach: (input) => send<{ ok: boolean }>('POST', '/v1/networks/attach', input),
|
|
85
|
+
detach: (input) => send<{ ok: boolean }>('POST', '/v1/networks/detach', input),
|
|
86
|
+
members: (name) => get<{ members: string[] }>(`/v1/networks/${encodeURIComponent(name)}/members`),
|
|
87
|
+
},
|
|
88
|
+
tunnels: {
|
|
89
|
+
list: () => get<TunnelEntry[]>('/v1/tunnels'),
|
|
90
|
+
create: (input) => send<TunnelEntry>('POST', '/v1/tunnels', input),
|
|
91
|
+
remove: async (id) => {
|
|
92
|
+
await request(`/v1/tunnels/${id}`, { method: 'DELETE' });
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
firewall: {
|
|
96
|
+
status: () => get<any>('/v1/firewall'),
|
|
97
|
+
toggle: (enabled) => send<any>('POST', '/v1/firewall/toggle', { enabled }),
|
|
98
|
+
addRule: (input) => send<any>('POST', '/v1/firewall/rules', input),
|
|
99
|
+
deleteRule: (id) => request(`/v1/firewall/rules/${id}`, { method: 'DELETE' }),
|
|
100
|
+
applyRecommended: () => send<any>('POST', '/v1/firewall/recommended'),
|
|
101
|
+
},
|
|
102
|
+
egress: {
|
|
103
|
+
list: () => get<{
|
|
104
|
+
drivers: Array<{
|
|
105
|
+
name: string;
|
|
106
|
+
rules: Array<{
|
|
107
|
+
selector: { projectId: number; sourceCidr?: string };
|
|
108
|
+
ip: string;
|
|
109
|
+
createdAt: string;
|
|
110
|
+
}>;
|
|
111
|
+
}>;
|
|
112
|
+
}>('/v1/egress'),
|
|
113
|
+
set: (input) => send<{
|
|
114
|
+
ok: boolean;
|
|
115
|
+
driver: string;
|
|
116
|
+
rule: { selector: { projectId: number; sourceCidr?: string }; ip: string; createdAt: string };
|
|
117
|
+
error?: string;
|
|
118
|
+
}>('POST', '/v1/egress', input),
|
|
119
|
+
clear: (projectId, driver) =>
|
|
120
|
+
send<{ ok: boolean; driver: string; error?: string }>(
|
|
121
|
+
'DELETE',
|
|
122
|
+
`/v1/egress/${projectId}${driver ? `?driver=${encodeURIComponent(driver)}` : ''}`,
|
|
123
|
+
),
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Client surface: plugins, menus.
|
|
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
|
+
PluginListResponse,
|
|
6
|
+
PluginInspectResponse,
|
|
7
|
+
InstallPluginInput,
|
|
8
|
+
MarketplaceCatalogResponse,
|
|
9
|
+
MenuListResponse,
|
|
10
|
+
} from '@ninedeploy/schemas';
|
|
11
|
+
import type { Transport } from '../transport.js';
|
|
12
|
+
|
|
13
|
+
export interface PluginsApi {
|
|
14
|
+
plugins: {
|
|
15
|
+
list: () => Promise<PluginListResponse>;
|
|
16
|
+
marketplace: (opts?: { refresh?: boolean }) => Promise<MarketplaceCatalogResponse>;
|
|
17
|
+
install: (input: InstallPluginInput) => Promise<{ ok: boolean; id: string; status: string }>;
|
|
18
|
+
enable: (id: string) => Promise<{ ok: boolean; id: string; status: string }>;
|
|
19
|
+
disable: (id: string) => Promise<{ ok: boolean; id: string; status: string }>;
|
|
20
|
+
reload: (id: string) => Promise<{ ok: boolean; id: string; status: string }>;
|
|
21
|
+
inspect: (id: string) => Promise<PluginInspectResponse>;
|
|
22
|
+
uninstall: (id: string) => Promise<{ ok: boolean; id: string }>;
|
|
23
|
+
};
|
|
24
|
+
menus: {
|
|
25
|
+
list: (query?: { slot?: string }) => Promise<MenuListResponse>;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function pluginsApi(t: Transport): PluginsApi {
|
|
30
|
+
const { get, send } = t;
|
|
31
|
+
return {
|
|
32
|
+
plugins: {
|
|
33
|
+
list: () => get<PluginListResponse>('/v1/plugins'),
|
|
34
|
+
marketplace: (opts) =>
|
|
35
|
+
get<MarketplaceCatalogResponse>(
|
|
36
|
+
`/v1/plugins/marketplace${opts?.refresh ? '?refresh=true' : ''}`,
|
|
37
|
+
),
|
|
38
|
+
install: (input) => send<{ ok: boolean; id: string; status: string }>('POST', '/v1/plugins/install', input),
|
|
39
|
+
enable: (id) => send<{ ok: boolean; id: string; status: string }>('POST', `/v1/plugins/${encodeURIComponent(id)}/enable`),
|
|
40
|
+
disable: (id) => send<{ ok: boolean; id: string; status: string }>('POST', `/v1/plugins/${encodeURIComponent(id)}/disable`),
|
|
41
|
+
reload: (id) => send<{ ok: boolean; id: string; status: string }>('POST', `/v1/plugins/${encodeURIComponent(id)}/reload`),
|
|
42
|
+
inspect: (id) => get<PluginInspectResponse>(`/v1/plugins/${encodeURIComponent(id)}/inspect`),
|
|
43
|
+
uninstall: (id) => send<{ ok: boolean; id: string }>('POST', `/v1/plugins/${encodeURIComponent(id)}/uninstall`),
|
|
44
|
+
},
|
|
45
|
+
menus: {
|
|
46
|
+
list: (query) => {
|
|
47
|
+
const qs = query?.slot ? `?slot=${encodeURIComponent(query.slot)}` : '';
|
|
48
|
+
return get<MenuListResponse>(`/v1/menus${qs}`);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|