@miosa/sdk 1.2.1 → 1.2.2
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 -1
- package/dist/index.d.ts +84 -8
- package/dist/index.js +90 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +9 -2
- package/src/resources/admin.ts +11 -0
- package/src/resources/api-keys.ts +16 -0
- package/src/resources/custom_domains.ts +1 -1
- package/src/resources/deployments.test.ts +55 -0
- package/src/resources/deployments.ts +30 -1
- package/src/resources/governance.test.ts +355 -0
- package/src/resources/governance.ts +528 -0
- package/src/resources/sandboxes.test.ts +26 -0
- package/src/resources/sandboxes.ts +31 -0
- package/src/resources/tenant.ts +27 -0
- package/src/resources/webhooks.ts +69 -1
- package/src/types.ts +5 -5
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 6 governance resources — policy, members, workspaces, bulk ops,
|
|
3
|
+
* billing, impersonation, and scoped API keys.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { HttpClient } from "../http.js";
|
|
7
|
+
|
|
8
|
+
// ── Types ──────────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export type PolicyDoc = Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
export interface BulkJobResponse {
|
|
13
|
+
queued: number;
|
|
14
|
+
job_id: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface BulkJobStatus {
|
|
19
|
+
id: string;
|
|
20
|
+
status: "queued" | "running" | "completed" | "failed";
|
|
21
|
+
processed?: number;
|
|
22
|
+
errors?: unknown[];
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface MemberRecord {
|
|
27
|
+
id: string;
|
|
28
|
+
email?: string;
|
|
29
|
+
role: string;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Invoice {
|
|
34
|
+
id: string;
|
|
35
|
+
amount: number;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface PaymentMethod {
|
|
40
|
+
id: string;
|
|
41
|
+
brand: string;
|
|
42
|
+
last4: string;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Effective policy typed accessors ─────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
export interface EffectivePolicyField<T = unknown> {
|
|
49
|
+
value: T;
|
|
50
|
+
source: "user" | "workspace" | "tenant" | "platform";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface EffectiveLifecycle {
|
|
54
|
+
default_idle_timeout_sec?: EffectivePolicyField<number>;
|
|
55
|
+
default_timeout_sec?: EffectivePolicyField<number>;
|
|
56
|
+
default_always_on?: EffectivePolicyField<boolean>;
|
|
57
|
+
[key: string]: EffectivePolicyField | undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface EffectiveQuotas {
|
|
61
|
+
max_sandboxes?: EffectivePolicyField<number>;
|
|
62
|
+
max_concurrent_sandboxes?: EffectivePolicyField<number>;
|
|
63
|
+
max_computers?: EffectivePolicyField<number>;
|
|
64
|
+
[key: string]: EffectivePolicyField | undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface EffectivePolicyDoc {
|
|
68
|
+
lifecycle: Record<string, EffectivePolicyField>;
|
|
69
|
+
quotas: Record<string, EffectivePolicyField>;
|
|
70
|
+
sizing?: Record<string, EffectivePolicyField>;
|
|
71
|
+
features?: Record<string, EffectivePolicyField>;
|
|
72
|
+
egress?: Record<string, EffectivePolicyField>;
|
|
73
|
+
[key: string]: Record<string, EffectivePolicyField> | undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Policy sub-resources ──────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
export class TenantPolicy {
|
|
79
|
+
constructor(private readonly http: HttpClient) {}
|
|
80
|
+
|
|
81
|
+
async get(): Promise<PolicyDoc> {
|
|
82
|
+
const r = await this.http.get<{ data?: PolicyDoc }>("/tenant/policy");
|
|
83
|
+
return (r as { data?: PolicyDoc }).data ?? (r as PolicyDoc);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async set(policy: PolicyDoc): Promise<PolicyDoc> {
|
|
87
|
+
const r = await this.http.put<{ data?: PolicyDoc }>(
|
|
88
|
+
"/tenant/policy",
|
|
89
|
+
policy,
|
|
90
|
+
);
|
|
91
|
+
return (r as { data?: PolicyDoc }).data ?? (r as PolicyDoc);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async delete(): Promise<void> {
|
|
95
|
+
await this.http.delete<unknown>("/tenant/policy");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class WorkspacePolicyResource {
|
|
100
|
+
constructor(
|
|
101
|
+
private readonly http: HttpClient,
|
|
102
|
+
private readonly workspaceId: string,
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
async get(): Promise<PolicyDoc> {
|
|
106
|
+
const r = await this.http.get<{ data?: PolicyDoc }>(
|
|
107
|
+
`/workspaces/${this.workspaceId}/policy`,
|
|
108
|
+
);
|
|
109
|
+
return (r as { data?: PolicyDoc }).data ?? (r as PolicyDoc);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async set(policy: PolicyDoc): Promise<PolicyDoc> {
|
|
113
|
+
const r = await this.http.put<{ data?: PolicyDoc }>(
|
|
114
|
+
`/workspaces/${this.workspaceId}/policy`,
|
|
115
|
+
policy,
|
|
116
|
+
);
|
|
117
|
+
return (r as { data?: PolicyDoc }).data ?? (r as PolicyDoc);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async delete(): Promise<void> {
|
|
121
|
+
await this.http.delete<unknown>(`/workspaces/${this.workspaceId}/policy`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export class ExternalUserPolicyResource {
|
|
126
|
+
constructor(
|
|
127
|
+
private readonly http: HttpClient,
|
|
128
|
+
private readonly userId: string,
|
|
129
|
+
) {}
|
|
130
|
+
|
|
131
|
+
async get(): Promise<PolicyDoc> {
|
|
132
|
+
const r = await this.http.get<{ data?: PolicyDoc }>(
|
|
133
|
+
`/external-users/${this.userId}/policy`,
|
|
134
|
+
);
|
|
135
|
+
return (r as { data?: PolicyDoc }).data ?? (r as PolicyDoc);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async set(policy: PolicyDoc): Promise<PolicyDoc> {
|
|
139
|
+
const r = await this.http.put<{ data?: PolicyDoc }>(
|
|
140
|
+
`/external-users/${this.userId}/policy`,
|
|
141
|
+
policy,
|
|
142
|
+
);
|
|
143
|
+
return (r as { data?: PolicyDoc }).data ?? (r as PolicyDoc);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async delete(): Promise<void> {
|
|
147
|
+
await this.http.delete<unknown>(`/external-users/${this.userId}/policy`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async effective(): Promise<EffectivePolicyDoc> {
|
|
151
|
+
const r = await this.http.get<unknown>(
|
|
152
|
+
`/external-users/${this.userId}/effective-policy`,
|
|
153
|
+
);
|
|
154
|
+
const data =
|
|
155
|
+
(r as { data?: EffectivePolicyDoc }).data ?? (r as EffectivePolicyDoc);
|
|
156
|
+
return data as EffectivePolicyDoc;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── ExternalUsers proxy ───────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
export class ExternalUserProxy {
|
|
163
|
+
readonly policy: ExternalUserPolicyResource;
|
|
164
|
+
|
|
165
|
+
constructor(http: HttpClient, userId: string) {
|
|
166
|
+
this.policy = new ExternalUserPolicyResource(http, userId);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export class ExternalUsers {
|
|
171
|
+
constructor(private readonly http: HttpClient) {}
|
|
172
|
+
|
|
173
|
+
call(externalUserId: string): ExternalUserProxy {
|
|
174
|
+
return new ExternalUserProxy(this.http, externalUserId);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Tenant members ─────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
export class TenantMembersResource {
|
|
181
|
+
constructor(private readonly http: HttpClient) {}
|
|
182
|
+
|
|
183
|
+
async list(): Promise<MemberRecord[]> {
|
|
184
|
+
const r = await this.http.get<{ data?: MemberRecord[] }>("/tenant/members");
|
|
185
|
+
return (r as { data?: MemberRecord[] }).data ?? (r as MemberRecord[]) ?? [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async invite(email: string, role: string): Promise<MemberRecord> {
|
|
189
|
+
const r = await this.http.post<{ data?: MemberRecord }>("/tenant/members", {
|
|
190
|
+
email,
|
|
191
|
+
role,
|
|
192
|
+
});
|
|
193
|
+
return (r as { data?: MemberRecord }).data ?? (r as MemberRecord);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async updateRole(memberId: string, role: string): Promise<MemberRecord> {
|
|
197
|
+
const r = await this.http.patch<{ data?: MemberRecord }>(
|
|
198
|
+
`/tenant/members/${memberId}/role`,
|
|
199
|
+
{ role },
|
|
200
|
+
);
|
|
201
|
+
return (r as { data?: MemberRecord }).data ?? (r as MemberRecord);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async remove(memberId: string): Promise<void> {
|
|
205
|
+
await this.http.delete<unknown>(`/tenant/members/${memberId}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async transferOwnership(
|
|
209
|
+
newOwnerUserId: string,
|
|
210
|
+
): Promise<Record<string, unknown>> {
|
|
211
|
+
const r = await this.http.post<{ data?: Record<string, unknown> }>(
|
|
212
|
+
"/tenant/transfer-ownership",
|
|
213
|
+
{ new_owner_user_id: newOwnerUserId },
|
|
214
|
+
);
|
|
215
|
+
return (
|
|
216
|
+
(r as { data?: Record<string, unknown> }).data ??
|
|
217
|
+
(r as Record<string, unknown>)
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── Tenant events ─────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
export interface AdminStreamEvent {
|
|
225
|
+
_event_type?: string;
|
|
226
|
+
[key: string]: unknown;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export class TenantEventStreamResource {
|
|
230
|
+
constructor(private readonly http: HttpClient) {}
|
|
231
|
+
|
|
232
|
+
stream(options?: {
|
|
233
|
+
types?: string[];
|
|
234
|
+
scope?: string;
|
|
235
|
+
}): AsyncIterableIterator<AdminStreamEvent> {
|
|
236
|
+
const params = new URLSearchParams();
|
|
237
|
+
if (options?.types?.length) params.set("types", options.types.join(","));
|
|
238
|
+
if (options?.scope) params.set("scope", options.scope);
|
|
239
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
240
|
+
return this.http.stream<AdminStreamEvent>(`/tenant/events/stream${qs}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ── GovernanceTenant ──────────────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
export class GovernanceTenant {
|
|
247
|
+
readonly policy: TenantPolicy;
|
|
248
|
+
readonly members: TenantMembersResource;
|
|
249
|
+
readonly events: TenantEventStreamResource;
|
|
250
|
+
|
|
251
|
+
constructor(http: HttpClient) {
|
|
252
|
+
this.policy = new TenantPolicy(http);
|
|
253
|
+
this.members = new TenantMembersResource(http);
|
|
254
|
+
this.events = new TenantEventStreamResource(http);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async current(): Promise<Record<string, unknown>> {
|
|
258
|
+
return (await this.policy["http" as never]) as never;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ── Workspace sub-resources ────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
export class WorkspaceMembersResource {
|
|
265
|
+
constructor(
|
|
266
|
+
private readonly http: HttpClient,
|
|
267
|
+
private readonly workspaceId: string,
|
|
268
|
+
) {}
|
|
269
|
+
|
|
270
|
+
async list(): Promise<MemberRecord[]> {
|
|
271
|
+
const r = await this.http.get<{ data?: MemberRecord[] }>(
|
|
272
|
+
`/workspaces/${this.workspaceId}/members`,
|
|
273
|
+
);
|
|
274
|
+
return (r as { data?: MemberRecord[] }).data ?? (r as MemberRecord[]) ?? [];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async invite(email: string, role: string): Promise<MemberRecord> {
|
|
278
|
+
const r = await this.http.post<{ data?: MemberRecord }>(
|
|
279
|
+
`/workspaces/${this.workspaceId}/members`,
|
|
280
|
+
{ email, role },
|
|
281
|
+
);
|
|
282
|
+
return (r as { data?: MemberRecord }).data ?? (r as MemberRecord);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async updateRole(memberId: string, role: string): Promise<MemberRecord> {
|
|
286
|
+
const r = await this.http.patch<{ data?: MemberRecord }>(
|
|
287
|
+
`/workspaces/${this.workspaceId}/members/${memberId}/role`,
|
|
288
|
+
{ role },
|
|
289
|
+
);
|
|
290
|
+
return (r as { data?: MemberRecord }).data ?? (r as MemberRecord);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async remove(memberId: string): Promise<void> {
|
|
294
|
+
await this.http.delete<unknown>(
|
|
295
|
+
`/workspaces/${this.workspaceId}/members/${memberId}`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export class WorkspaceProxy {
|
|
301
|
+
readonly policy: WorkspacePolicyResource;
|
|
302
|
+
readonly members: WorkspaceMembersResource;
|
|
303
|
+
|
|
304
|
+
constructor(
|
|
305
|
+
private readonly http: HttpClient,
|
|
306
|
+
private readonly workspaceId: string,
|
|
307
|
+
) {
|
|
308
|
+
this.policy = new WorkspacePolicyResource(http, workspaceId);
|
|
309
|
+
this.members = new WorkspaceMembersResource(http, workspaceId);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async transfer(
|
|
313
|
+
resourceIds: string[],
|
|
314
|
+
targetWorkspaceId: string,
|
|
315
|
+
): Promise<Record<string, unknown>> {
|
|
316
|
+
const r = await this.http.post<{ data?: Record<string, unknown> }>(
|
|
317
|
+
`/workspaces/${this.workspaceId}/transfer`,
|
|
318
|
+
{ resource_ids: resourceIds, target_workspace_id: targetWorkspaceId },
|
|
319
|
+
);
|
|
320
|
+
return (
|
|
321
|
+
(r as { data?: Record<string, unknown> }).data ??
|
|
322
|
+
(r as Record<string, unknown>)
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── GovernanceWorkspaces ──────────────────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
export class GovernanceWorkspaces {
|
|
330
|
+
constructor(private readonly http: HttpClient) {}
|
|
331
|
+
|
|
332
|
+
/** client.workspaces("ws_id") → proxy with .policy, .members, .transfer() */
|
|
333
|
+
workspace(workspaceId: string): WorkspaceProxy {
|
|
334
|
+
return new WorkspaceProxy(this.http, workspaceId);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async list(): Promise<Record<string, unknown>[]> {
|
|
338
|
+
const r = await this.http.get<{ data?: Record<string, unknown>[] }>(
|
|
339
|
+
"/workspaces",
|
|
340
|
+
);
|
|
341
|
+
const items =
|
|
342
|
+
(r as { data?: Record<string, unknown>[] }).data ??
|
|
343
|
+
(r as Record<string, unknown>[]);
|
|
344
|
+
return Array.isArray(items) ? items : [];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async create(
|
|
348
|
+
name: string,
|
|
349
|
+
opts?: { description?: string; metadata?: Record<string, unknown> },
|
|
350
|
+
): Promise<Record<string, unknown>> {
|
|
351
|
+
const r = await this.http.post<{ data?: Record<string, unknown> }>(
|
|
352
|
+
"/workspaces",
|
|
353
|
+
{ name, ...opts },
|
|
354
|
+
);
|
|
355
|
+
return (
|
|
356
|
+
(r as { data?: Record<string, unknown> }).data ??
|
|
357
|
+
(r as Record<string, unknown>)
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async get(workspaceId: string): Promise<Record<string, unknown>> {
|
|
362
|
+
const r = await this.http.get<{ data?: Record<string, unknown> }>(
|
|
363
|
+
`/workspaces/${workspaceId}`,
|
|
364
|
+
);
|
|
365
|
+
return (
|
|
366
|
+
(r as { data?: Record<string, unknown> }).data ??
|
|
367
|
+
(r as Record<string, unknown>)
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async update(
|
|
372
|
+
workspaceId: string,
|
|
373
|
+
fields: Record<string, unknown>,
|
|
374
|
+
): Promise<Record<string, unknown>> {
|
|
375
|
+
const r = await this.http.patch<{ data?: Record<string, unknown> }>(
|
|
376
|
+
`/workspaces/${workspaceId}`,
|
|
377
|
+
fields,
|
|
378
|
+
);
|
|
379
|
+
return (
|
|
380
|
+
(r as { data?: Record<string, unknown> }).data ??
|
|
381
|
+
(r as Record<string, unknown>)
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async delete(workspaceId: string): Promise<void> {
|
|
386
|
+
await this.http.delete<unknown>(`/workspaces/${workspaceId}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ── Bulk ops ──────────────────────────────────────────────────────────────────
|
|
391
|
+
|
|
392
|
+
function bulkBody(opts: {
|
|
393
|
+
ids?: string[];
|
|
394
|
+
filter?: Record<string, unknown>;
|
|
395
|
+
}): Record<string, unknown> {
|
|
396
|
+
if (opts.ids !== undefined) return { ids: opts.ids };
|
|
397
|
+
if (opts.filter !== undefined) return { filter: opts.filter };
|
|
398
|
+
throw new Error("Provide either ids or filter");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export class BulkSandboxesResource {
|
|
402
|
+
constructor(private readonly http: HttpClient) {}
|
|
403
|
+
|
|
404
|
+
async pause(opts: {
|
|
405
|
+
ids?: string[];
|
|
406
|
+
filter?: Record<string, unknown>;
|
|
407
|
+
}): Promise<BulkJobResponse> {
|
|
408
|
+
return this.http.post<BulkJobResponse>(
|
|
409
|
+
"/bulk/sandboxes/pause",
|
|
410
|
+
bulkBody(opts),
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async resume(opts: {
|
|
415
|
+
ids?: string[];
|
|
416
|
+
filter?: Record<string, unknown>;
|
|
417
|
+
}): Promise<BulkJobResponse> {
|
|
418
|
+
return this.http.post<BulkJobResponse>(
|
|
419
|
+
"/bulk/sandboxes/resume",
|
|
420
|
+
bulkBody(opts),
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async destroy(opts: {
|
|
425
|
+
ids?: string[];
|
|
426
|
+
filter?: Record<string, unknown>;
|
|
427
|
+
}): Promise<BulkJobResponse> {
|
|
428
|
+
return this.http.post<BulkJobResponse>(
|
|
429
|
+
"/bulk/sandboxes/destroy",
|
|
430
|
+
bulkBody(opts),
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export class BulkPolicyResource {
|
|
436
|
+
constructor(private readonly http: HttpClient) {}
|
|
437
|
+
|
|
438
|
+
async apply(opts: {
|
|
439
|
+
tier: string;
|
|
440
|
+
idsOrFilter: string[] | Record<string, unknown>;
|
|
441
|
+
policy: PolicyDoc;
|
|
442
|
+
}): Promise<BulkJobResponse> {
|
|
443
|
+
const body: Record<string, unknown> = {
|
|
444
|
+
tier: opts.tier,
|
|
445
|
+
policy: opts.policy,
|
|
446
|
+
};
|
|
447
|
+
if (Array.isArray(opts.idsOrFilter)) {
|
|
448
|
+
body["ids"] = opts.idsOrFilter;
|
|
449
|
+
} else {
|
|
450
|
+
body["filter"] = opts.idsOrFilter;
|
|
451
|
+
}
|
|
452
|
+
return this.http.post<BulkJobResponse>("/bulk/policy/apply", body);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export class BulkJobsResource {
|
|
457
|
+
constructor(private readonly http: HttpClient) {}
|
|
458
|
+
|
|
459
|
+
async get(jobId: string): Promise<BulkJobStatus> {
|
|
460
|
+
const r = await this.http.get<{ data?: BulkJobStatus }>(
|
|
461
|
+
`/bulk/jobs/${jobId}`,
|
|
462
|
+
);
|
|
463
|
+
return (r as { data?: BulkJobStatus }).data ?? (r as BulkJobStatus);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export class BulkResource {
|
|
468
|
+
readonly sandboxes: BulkSandboxesResource;
|
|
469
|
+
readonly policy: BulkPolicyResource;
|
|
470
|
+
readonly jobs: BulkJobsResource;
|
|
471
|
+
|
|
472
|
+
constructor(http: HttpClient) {
|
|
473
|
+
this.sandboxes = new BulkSandboxesResource(http);
|
|
474
|
+
this.policy = new BulkPolicyResource(http);
|
|
475
|
+
this.jobs = new BulkJobsResource(http);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ── Billing ───────────────────────────────────────────────────────────────────
|
|
480
|
+
|
|
481
|
+
export class BillingInvoicesResource {
|
|
482
|
+
constructor(private readonly http: HttpClient) {}
|
|
483
|
+
|
|
484
|
+
async list(opts?: { limit?: number; cursor?: string }): Promise<Invoice[]> {
|
|
485
|
+
const params = new URLSearchParams();
|
|
486
|
+
if (opts?.limit !== undefined) params.set("limit", String(opts.limit));
|
|
487
|
+
if (opts?.cursor) params.set("cursor", opts.cursor);
|
|
488
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
489
|
+
const r = await this.http.get<{ data?: Invoice[] }>(
|
|
490
|
+
`/billing/invoices${qs}`,
|
|
491
|
+
);
|
|
492
|
+
return (r as { data?: Invoice[] }).data ?? (r as Invoice[]) ?? [];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async get(invoiceId: string): Promise<Invoice> {
|
|
496
|
+
const r = await this.http.get<{ data?: Invoice }>(
|
|
497
|
+
`/billing/invoices/${invoiceId}`,
|
|
498
|
+
);
|
|
499
|
+
return (r as { data?: Invoice }).data ?? (r as Invoice);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export class BillingResource {
|
|
504
|
+
readonly invoices: BillingInvoicesResource;
|
|
505
|
+
|
|
506
|
+
constructor(private readonly http: HttpClient) {
|
|
507
|
+
this.invoices = new BillingInvoicesResource(http);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async paymentMethods(): Promise<PaymentMethod[]> {
|
|
511
|
+
const r = await this.http.get<{ data?: PaymentMethod[] }>(
|
|
512
|
+
"/billing/payment-methods",
|
|
513
|
+
);
|
|
514
|
+
return (
|
|
515
|
+
(r as { data?: PaymentMethod[] }).data ?? (r as PaymentMethod[]) ?? []
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
async upcoming(): Promise<Record<string, unknown>> {
|
|
520
|
+
const r = await this.http.get<{ data?: Record<string, unknown> }>(
|
|
521
|
+
"/billing/upcoming",
|
|
522
|
+
);
|
|
523
|
+
return (
|
|
524
|
+
(r as { data?: Record<string, unknown> }).data ??
|
|
525
|
+
(r as Record<string, unknown>)
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
@@ -424,6 +424,32 @@ describe("Sandbox handle", () => {
|
|
|
424
424
|
expect(deployment.deployment_id).toBe("dep_1");
|
|
425
425
|
});
|
|
426
426
|
|
|
427
|
+
it("deployDocker marks sandbox deployment for Docker Deploy", async () => {
|
|
428
|
+
mockRequest.mockResolvedValueOnce({
|
|
429
|
+
data: {
|
|
430
|
+
deployment_id: "dep_2",
|
|
431
|
+
deployment_product: "docker_deploy",
|
|
432
|
+
data: { deployment: { docker_deploy_host_id: "ddh_123" } },
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const sandbox = new Sandbox(makeHttp(), sandboxData());
|
|
437
|
+
const deployment = await sandbox.deployDocker({
|
|
438
|
+
name: "docker-site",
|
|
439
|
+
port: 3000,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
expect(mockRequest).toHaveBeenCalledWith("/sandboxes/sbx_123/deploy", {
|
|
443
|
+
method: "POST",
|
|
444
|
+
body: {
|
|
445
|
+
name: "docker-site",
|
|
446
|
+
port: 3000,
|
|
447
|
+
deployment_type: "docker_deploy",
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
expect(deployment.deployment_product).toBe("docker_deploy");
|
|
451
|
+
});
|
|
452
|
+
|
|
427
453
|
it("throws before operations when not running", async () => {
|
|
428
454
|
const sandbox = new Sandbox(
|
|
429
455
|
makeHttp(),
|
|
@@ -318,6 +318,21 @@ export interface SandboxDeployParams {
|
|
|
318
318
|
sourceSnapshotPath?: string;
|
|
319
319
|
source_snapshot_path?: string;
|
|
320
320
|
entrypoint?: string;
|
|
321
|
+
buildCommand?: string;
|
|
322
|
+
build_command?: string;
|
|
323
|
+
runCommand?: string;
|
|
324
|
+
run_command?: string;
|
|
325
|
+
startCommand?: string;
|
|
326
|
+
start_command?: string;
|
|
327
|
+
port?: number;
|
|
328
|
+
healthCheckPath?: string;
|
|
329
|
+
health_check_path?: string;
|
|
330
|
+
deploymentType?: "miosa_deploy" | "docker_deploy" | "docker-deploy" | string;
|
|
331
|
+
deployment_type?: "miosa_deploy" | "docker_deploy" | "docker-deploy" | string;
|
|
332
|
+
type?: "static" | "dynamic" | "server" | string;
|
|
333
|
+
mode?: "static" | "dynamic" | "server" | string;
|
|
334
|
+
database?: boolean | Record<string, unknown>;
|
|
335
|
+
resources?: Record<string, unknown>;
|
|
321
336
|
domain?: string;
|
|
322
337
|
customDomain?: string;
|
|
323
338
|
custom_domain?: string;
|
|
@@ -1092,6 +1107,16 @@ export class Sandbox {
|
|
|
1092
1107
|
source_snapshot_path:
|
|
1093
1108
|
params.sourceSnapshotPath ?? params.source_snapshot_path,
|
|
1094
1109
|
entrypoint: params.entrypoint,
|
|
1110
|
+
build_command: params.buildCommand ?? params.build_command,
|
|
1111
|
+
run_command: params.runCommand ?? params.run_command,
|
|
1112
|
+
start_command: params.startCommand ?? params.start_command,
|
|
1113
|
+
port: params.port,
|
|
1114
|
+
health_check_path: params.healthCheckPath ?? params.health_check_path,
|
|
1115
|
+
deployment_type: params.deploymentType ?? params.deployment_type,
|
|
1116
|
+
type: params.type,
|
|
1117
|
+
mode: params.mode,
|
|
1118
|
+
database: params.database,
|
|
1119
|
+
resources: params.resources,
|
|
1095
1120
|
domain: params.domain,
|
|
1096
1121
|
custom_domain: params.customDomain ?? params.custom_domain,
|
|
1097
1122
|
}),
|
|
@@ -1108,6 +1133,12 @@ export class Sandbox {
|
|
|
1108
1133
|
);
|
|
1109
1134
|
}
|
|
1110
1135
|
|
|
1136
|
+
async deployDocker(
|
|
1137
|
+
params: SandboxDeployParams = {},
|
|
1138
|
+
): Promise<Record<string, unknown>> {
|
|
1139
|
+
return this.deploy({ ...params, deploymentType: "docker_deploy" });
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1111
1142
|
/** Check readiness of the sandbox (GET /sandboxes/:id/readiness). */
|
|
1112
1143
|
async readiness(): Promise<Record<string, unknown>> {
|
|
1113
1144
|
return unwrap(
|
package/src/resources/tenant.ts
CHANGED
|
@@ -9,11 +9,38 @@ import type { HttpClient } from "../http.js";
|
|
|
9
9
|
export interface TenantPlan {
|
|
10
10
|
id?: string;
|
|
11
11
|
name?: string;
|
|
12
|
+
preview_domain?: string | null;
|
|
13
|
+
deployment_domain?: string | null;
|
|
14
|
+
fallback_miosa_domain?: string | null;
|
|
12
15
|
limits?: Record<string, unknown>;
|
|
13
16
|
usage?: Record<string, unknown>;
|
|
14
17
|
[key: string]: unknown;
|
|
15
18
|
}
|
|
16
19
|
|
|
20
|
+
export interface BrandingData {
|
|
21
|
+
logo_url?: string | null;
|
|
22
|
+
primary_color?: string | null;
|
|
23
|
+
wordmark?: string | null;
|
|
24
|
+
favicon_url?: string | null;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PreviewDomainData {
|
|
29
|
+
preview_domain?: string | null;
|
|
30
|
+
deployment_domain?: string | null;
|
|
31
|
+
fallback_miosa_domain?: string | null;
|
|
32
|
+
status?: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface TenantBrandingUpdateParams {
|
|
37
|
+
logo_url?: string | null;
|
|
38
|
+
primary_color?: string | null;
|
|
39
|
+
wordmark?: string | null;
|
|
40
|
+
favicon_url?: string | null;
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
}
|
|
43
|
+
|
|
17
44
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
18
45
|
|
|
19
46
|
function unwrap<T>(payload: unknown): T {
|