@miosa/sdk 0.3.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.
Files changed (81) hide show
  1. package/README.md +181 -0
  2. package/dist/index.d.ts +4689 -0
  3. package/dist/index.js +6045 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +63 -0
  6. package/src/client.ts +249 -0
  7. package/src/errors.ts +136 -0
  8. package/src/http.test.ts +374 -0
  9. package/src/http.ts +390 -0
  10. package/src/index.ts +452 -0
  11. package/src/resources/admin.ts +348 -0
  12. package/src/resources/analytics.ts +60 -0
  13. package/src/resources/api-keys.ts +119 -0
  14. package/src/resources/audit-log.ts +64 -0
  15. package/src/resources/benchmarks.ts +104 -0
  16. package/src/resources/builder-sessions.ts +75 -0
  17. package/src/resources/channels.ts +143 -0
  18. package/src/resources/checkpoints.ts +225 -0
  19. package/src/resources/command-center.ts +73 -0
  20. package/src/resources/community.ts +103 -0
  21. package/src/resources/completions.ts +104 -0
  22. package/src/resources/computer-auto-stop.ts +43 -0
  23. package/src/resources/computer-env.ts +76 -0
  24. package/src/resources/computer-logs.ts +50 -0
  25. package/src/resources/computer-osa.ts +76 -0
  26. package/src/resources/computer-ports.ts +91 -0
  27. package/src/resources/computer-terminal.ts +61 -0
  28. package/src/resources/computer-volumes.ts +64 -0
  29. package/src/resources/computer.ts +530 -0
  30. package/src/resources/computers.ts +75 -0
  31. package/src/resources/credits.ts +43 -0
  32. package/src/resources/cron-jobs.ts +191 -0
  33. package/src/resources/custom_domains.ts +123 -0
  34. package/src/resources/dashboard.ts +49 -0
  35. package/src/resources/databases.ts +218 -0
  36. package/src/resources/deployments.ts +777 -0
  37. package/src/resources/desktop.ts +134 -0
  38. package/src/resources/email.ts +212 -0
  39. package/src/resources/embeddings.ts +36 -0
  40. package/src/resources/events.ts +296 -0
  41. package/src/resources/exec.ts +319 -0
  42. package/src/resources/external-keys.ts +79 -0
  43. package/src/resources/files.test.ts +339 -0
  44. package/src/resources/files.ts +220 -0
  45. package/src/resources/flat-custom-domains.ts +127 -0
  46. package/src/resources/functions.ts +178 -0
  47. package/src/resources/health-checks.ts +165 -0
  48. package/src/resources/integrations.ts +183 -0
  49. package/src/resources/mcp.ts +70 -0
  50. package/src/resources/models.ts +45 -0
  51. package/src/resources/network_policy.ts +73 -0
  52. package/src/resources/open-computers/agents.ts +91 -0
  53. package/src/resources/open-computers/apps.ts +102 -0
  54. package/src/resources/open-computers/clusters.ts +88 -0
  55. package/src/resources/open-computers/desktop.ts +34 -0
  56. package/src/resources/open-computers/files.ts +97 -0
  57. package/src/resources/open-computers/hosts.ts +85 -0
  58. package/src/resources/open-computers/index.ts +98 -0
  59. package/src/resources/open-computers/jobs.ts +75 -0
  60. package/src/resources/open-computers/open_computers.test.ts +288 -0
  61. package/src/resources/open-computers/secrets.ts +115 -0
  62. package/src/resources/open-computers/terminal.ts +33 -0
  63. package/src/resources/open-computers/tunnels.ts +87 -0
  64. package/src/resources/open-computers/types.ts +343 -0
  65. package/src/resources/open-computers/workspaces.ts +135 -0
  66. package/src/resources/project-auth.ts +142 -0
  67. package/src/resources/project-integrations.ts +133 -0
  68. package/src/resources/provider-defaults.ts +89 -0
  69. package/src/resources/regions.ts +94 -0
  70. package/src/resources/sandbox-templates.ts +195 -0
  71. package/src/resources/sandboxes.live.test.ts +92 -0
  72. package/src/resources/sandboxes.test.ts +624 -0
  73. package/src/resources/sandboxes.ts +1173 -0
  74. package/src/resources/settings.ts +143 -0
  75. package/src/resources/snapshots-standalone.ts +51 -0
  76. package/src/resources/storage.ts +221 -0
  77. package/src/resources/tenant.ts +39 -0
  78. package/src/resources/usage.ts +85 -0
  79. package/src/resources/volumes.ts +117 -0
  80. package/src/resources/webhooks.ts +171 -0
  81. package/src/types.ts +460 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Settings — tenant config, branding, BYOK provider keys.
3
+ */
4
+
5
+ import type { HttpClient } from "../http.js";
6
+
7
+ // ── Request payloads ─────────────────────────────────────────────────────────
8
+
9
+ export interface SettingsUpdateParams {
10
+ [key: string]: unknown;
11
+ }
12
+
13
+ export interface BrandingUpdateParams {
14
+ logo_url?: string;
15
+ primary_color?: string;
16
+ wordmark?: string;
17
+ [key: string]: unknown;
18
+ }
19
+
20
+ export interface ProviderKeyUpsertParams {
21
+ key: string;
22
+ [key: string]: unknown;
23
+ }
24
+
25
+ // ── Helpers ───────────────────────────────────────────────────────────────────
26
+
27
+ function unwrap<T>(payload: unknown): T {
28
+ if (payload && typeof payload === "object") {
29
+ const p = payload as Record<string, unknown>;
30
+ for (const k of [
31
+ "data",
32
+ "settings",
33
+ "branding",
34
+ "provider_keys",
35
+ "items",
36
+ ]) {
37
+ if (k in p) return p[k] as T;
38
+ }
39
+ }
40
+ return payload as T;
41
+ }
42
+
43
+ function listItems<T>(payload: unknown): T[] {
44
+ const result = unwrap<T[] | unknown>(payload);
45
+ if (Array.isArray(result)) return result;
46
+ return [];
47
+ }
48
+
49
+ function stripUndefined(
50
+ input: Record<string, unknown>,
51
+ ): Record<string, unknown> {
52
+ return Object.fromEntries(
53
+ Object.entries(input).filter(([, v]) => v !== undefined),
54
+ );
55
+ }
56
+
57
+ // ── Main resource ─────────────────────────────────────────────────────────────
58
+
59
+ export class Settings {
60
+ constructor(private readonly http: HttpClient) {}
61
+
62
+ /** Get the current tenant settings. */
63
+ async get(): Promise<Record<string, unknown>> {
64
+ const data = await this.http.get<unknown>("/settings");
65
+ return unwrap<Record<string, unknown>>(data);
66
+ }
67
+
68
+ /** Update tenant settings. */
69
+ async update(params: SettingsUpdateParams): Promise<Record<string, unknown>> {
70
+ const body = stripUndefined(params as Record<string, unknown>);
71
+ const data = await this.http.put<unknown>("/settings", body);
72
+ return unwrap<Record<string, unknown>>(data);
73
+ }
74
+
75
+ // ── Branding ──────────────────────────────────────────────────────────────
76
+
77
+ /** Get tenant branding (logo, colors, custom wordmark). */
78
+ async getBranding(): Promise<Record<string, unknown>> {
79
+ const data = await this.http.get<unknown>("/settings/branding");
80
+ return unwrap<Record<string, unknown>>(data);
81
+ }
82
+
83
+ /** Update tenant branding. */
84
+ async updateBranding(
85
+ params: BrandingUpdateParams,
86
+ ): Promise<Record<string, unknown>> {
87
+ const body = stripUndefined(params as Record<string, unknown>);
88
+ const data = await this.http.put<unknown>("/settings/branding", body);
89
+ return unwrap<Record<string, unknown>>(data);
90
+ }
91
+
92
+ // ── Read-only reference data ───────────────────────────────────────────────
93
+
94
+ /** Get tenant-scoped compute pricing. */
95
+ async computePricing(): Promise<unknown> {
96
+ const data = await this.http.get<unknown>("/settings/compute-pricing");
97
+ return unwrap(data);
98
+ }
99
+
100
+ /** Get tenant-scoped GPU pricing. */
101
+ async gpuPricing(): Promise<unknown> {
102
+ const data = await this.http.get<unknown>("/settings/gpu-pricing");
103
+ return unwrap(data);
104
+ }
105
+
106
+ /** List models available to this tenant. */
107
+ async availableModels(): Promise<unknown> {
108
+ const data = await this.http.get<unknown>("/settings/available-models");
109
+ return unwrap(data);
110
+ }
111
+
112
+ /** List regions enabled for this tenant. */
113
+ async regions(): Promise<unknown> {
114
+ const data = await this.http.get<unknown>("/settings/regions");
115
+ return unwrap(data);
116
+ }
117
+
118
+ // ── BYOK provider keys ────────────────────────────────────────────────────
119
+
120
+ /** List tenant-level BYOK provider keys (Anthropic, OpenAI, etc.). */
121
+ async listProviderKeys(): Promise<Record<string, unknown>[]> {
122
+ const data = await this.http.get<unknown>("/settings/provider-keys");
123
+ return listItems<Record<string, unknown>>(data);
124
+ }
125
+
126
+ /** Create or update a BYOK provider key. */
127
+ async upsertProviderKey(
128
+ provider: string,
129
+ params: ProviderKeyUpsertParams,
130
+ ): Promise<Record<string, unknown>> {
131
+ const body = stripUndefined(params as Record<string, unknown>);
132
+ const data = await this.http.put<unknown>(
133
+ `/settings/provider-keys/${provider}`,
134
+ body,
135
+ );
136
+ return unwrap<Record<string, unknown>>(data);
137
+ }
138
+
139
+ /** Delete a BYOK provider key. */
140
+ async deleteProviderKey(provider: string): Promise<void> {
141
+ await this.http.delete<unknown>(`/settings/provider-keys/${provider}`);
142
+ }
143
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * SnapshotsStandalone — fleet-wide snapshot index for admin callers.
3
+ *
4
+ * Routes: /admin/snapshots/*
5
+ * Requires admin credential.
6
+ *
7
+ * Per-computer snapshots remain nested under client.computers.get(id).checkpoints.
8
+ * This resource exposes the fleet-wide read-only index used by the admin dashboard.
9
+ */
10
+
11
+ import type { HttpClient } from "../http.js";
12
+
13
+ function unwrap(data: unknown): Record<string, unknown> {
14
+ if (data && typeof data === "object") {
15
+ const d = data as Record<string, unknown>;
16
+ for (const k of ["data", "snapshots", "items"]) {
17
+ if (k in d) return d[k] as Record<string, unknown>;
18
+ }
19
+ }
20
+ return data as Record<string, unknown>;
21
+ }
22
+
23
+ function unwrapList(data: unknown): Record<string, unknown>[] {
24
+ if (Array.isArray(data)) return data as Record<string, unknown>[];
25
+ if (data && typeof data === "object") {
26
+ const d = data as Record<string, unknown>;
27
+ for (const k of ["data", "snapshots", "items"]) {
28
+ if (Array.isArray(d[k])) return d[k] as Record<string, unknown>[];
29
+ }
30
+ }
31
+ return [];
32
+ }
33
+
34
+ export class SnapshotsStandalone {
35
+ constructor(private readonly http: HttpClient) {}
36
+
37
+ async list(
38
+ filters: Record<string, string | number | boolean | undefined> = {},
39
+ ): Promise<Record<string, unknown>[]> {
40
+ const query = Object.fromEntries(
41
+ Object.entries(filters).filter(([, v]) => v !== undefined),
42
+ ) as Record<string, string | number | boolean | undefined>;
43
+ return unwrapList(await this.http.get<unknown>("/admin/snapshots", query));
44
+ }
45
+
46
+ async get(snapshotId: string): Promise<Record<string, unknown>> {
47
+ return unwrap(
48
+ await this.http.get<unknown>(`/admin/snapshots/${snapshotId}`),
49
+ );
50
+ }
51
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Storage resource — managed S3-compatible buckets, objects, and presigned URLs.
3
+ */
4
+
5
+ import type { HttpClient } from "../http.js";
6
+
7
+ // ── Branded IDs ──────────────────────────────────────────────────────────────
8
+
9
+ export type BucketId = string & { readonly __brand: "BucketId" };
10
+
11
+ // ── Resource shapes ──────────────────────────────────────────────────────────
12
+
13
+ export interface BucketData {
14
+ id: BucketId;
15
+ tenant_id: string;
16
+ name: string;
17
+ region?: string | null;
18
+ visibility?: "private" | "public" | string;
19
+ quota_bytes?: number | null;
20
+ used_bytes?: number | null;
21
+ public?: boolean;
22
+ object_count?: number | null;
23
+ size_bytes?: number | null;
24
+ created_at?: string;
25
+ updated_at?: string;
26
+ [key: string]: unknown;
27
+ }
28
+
29
+ export interface StorageObjectData {
30
+ key: string;
31
+ size_bytes?: number;
32
+ content_type?: string;
33
+ etag?: string | null;
34
+ last_modified?: string | null;
35
+ [key: string]: unknown;
36
+ }
37
+
38
+ export interface PresignResult {
39
+ url: string;
40
+ expires_at?: string;
41
+ [key: string]: unknown;
42
+ }
43
+
44
+ // ── Request payloads ─────────────────────────────────────────────────────────
45
+
46
+ export interface BucketCreateParams {
47
+ name: string;
48
+ region?: string;
49
+ visibility?: "private" | "public";
50
+ quota_bytes?: number;
51
+ public?: boolean;
52
+ [key: string]: unknown;
53
+ }
54
+
55
+ export interface ObjectListParams {
56
+ prefix?: string;
57
+ maxKeys?: number;
58
+ max_keys?: number;
59
+ marker?: string;
60
+ /** @deprecated use maxKeys/max_keys; kept as a convenience alias. */
61
+ limit?: number;
62
+ /** @deprecated use marker; kept as a convenience alias. */
63
+ cursor?: string;
64
+ }
65
+
66
+ export interface PresignParams {
67
+ key: string;
68
+ method?: "GET" | "PUT";
69
+ expiresIn?: number;
70
+ expires_in?: number;
71
+ /** @deprecated use method; kept as a convenience alias. */
72
+ operation?: "get" | "put";
73
+ /** @deprecated use expiresIn/expires_in; kept as a convenience alias. */
74
+ expiresInSec?: number;
75
+ /** @deprecated use expiresIn/expires_in; kept as a convenience alias. */
76
+ expires_in_sec?: number;
77
+ contentType?: string;
78
+ content_type?: string;
79
+ }
80
+
81
+ // ── Helpers ───────────────────────────────────────────────────────────────────
82
+
83
+ function unwrap<T>(payload: unknown): T {
84
+ if (payload && typeof payload === "object" && "data" in (payload as object)) {
85
+ return (payload as { data: T }).data;
86
+ }
87
+ return payload as T;
88
+ }
89
+
90
+ function listItems<T>(
91
+ payload: unknown,
92
+ candidateKeys: string[] = ["data", "buckets", "objects", "items"],
93
+ ): T[] {
94
+ if (Array.isArray(payload)) return payload;
95
+ if (!payload || typeof payload !== "object") return [];
96
+ const p = payload as Record<string, unknown>;
97
+ if (Array.isArray(p.data)) return p.data as T[];
98
+ for (const key of candidateKeys) {
99
+ if (Array.isArray(p[key])) return p[key] as T[];
100
+ }
101
+ return [];
102
+ }
103
+
104
+ function stripUndefined(
105
+ input: Record<string, unknown>,
106
+ ): Record<string, unknown> {
107
+ return Object.fromEntries(
108
+ Object.entries(input).filter(([, v]) => v !== undefined),
109
+ );
110
+ }
111
+
112
+ // ── Main resource ─────────────────────────────────────────────────────────────
113
+
114
+ export class Storage {
115
+ constructor(private readonly http: HttpClient) {}
116
+
117
+ // ── Buckets ────────────────────────────────────────────────────────────────
118
+
119
+ async listBuckets(): Promise<BucketData[]> {
120
+ const data = await this.http.get<unknown>("/storage/buckets");
121
+ return listItems<BucketData>(data);
122
+ }
123
+
124
+ async createBucket(params: BucketCreateParams): Promise<BucketData> {
125
+ const { name, public: isPublic, visibility, ...rest } = params;
126
+ const body = stripUndefined({
127
+ name,
128
+ visibility:
129
+ visibility ??
130
+ (isPublic === undefined ? undefined : isPublic ? "public" : "private"),
131
+ ...rest,
132
+ });
133
+ const data = await this.http.post<unknown>("/storage/buckets", body);
134
+ return unwrap<BucketData>(data);
135
+ }
136
+
137
+ async getBucket(bucketId: string): Promise<BucketData> {
138
+ const data = await this.http.get<unknown>(`/storage/buckets/${bucketId}`);
139
+ return unwrap<BucketData>(data);
140
+ }
141
+
142
+ async deleteBucket(bucketId: string): Promise<void> {
143
+ await this.http.delete<unknown>(`/storage/buckets/${bucketId}`);
144
+ }
145
+
146
+ // ── Objects ────────────────────────────────────────────────────────────────
147
+
148
+ async listObjects(
149
+ bucketId: string,
150
+ params: ObjectListParams = {},
151
+ ): Promise<StorageObjectData[]> {
152
+ const query = stripUndefined({
153
+ prefix: params.prefix,
154
+ max_keys: params.max_keys ?? params.maxKeys ?? params.limit,
155
+ marker: params.marker ?? params.cursor,
156
+ }) as Record<string, string | number | boolean | undefined>;
157
+ const data = await this.http.get<unknown>(
158
+ `/storage/buckets/${bucketId}/objects`,
159
+ query,
160
+ );
161
+ return listItems<StorageObjectData>(data, ["data", "objects", "items"]);
162
+ }
163
+
164
+ async putObject(
165
+ bucketId: string,
166
+ key: string,
167
+ content: Uint8Array | ArrayBuffer,
168
+ opts: { contentType?: string } = {},
169
+ ): Promise<Record<string, unknown>> {
170
+ const contentType = opts.contentType ?? "application/octet-stream";
171
+ const data = await this.http.request<unknown>(
172
+ `/storage/buckets/${bucketId}/objects/${key}`,
173
+ {
174
+ method: "PUT",
175
+ body: content,
176
+ headers: { "Content-Type": contentType },
177
+ },
178
+ );
179
+ return (data ?? {}) as Record<string, unknown>;
180
+ }
181
+
182
+ async getObject(bucketId: string, key: string): Promise<Uint8Array> {
183
+ return this.http.getBinary(`/storage/buckets/${bucketId}/objects/${key}`);
184
+ }
185
+
186
+ async deleteObject(bucketId: string, key: string): Promise<void> {
187
+ await this.http.delete<unknown>(
188
+ `/storage/buckets/${bucketId}/objects/${key}`,
189
+ );
190
+ }
191
+
192
+ // ── Presigned URLs ─────────────────────────────────────────────────────────
193
+
194
+ async presign(
195
+ bucketId: string,
196
+ params: PresignParams,
197
+ ): Promise<PresignResult> {
198
+ const operationMethod =
199
+ params.operation === "put"
200
+ ? "PUT"
201
+ : params.operation === "get"
202
+ ? "GET"
203
+ : undefined;
204
+
205
+ const body = stripUndefined({
206
+ key: params.key,
207
+ method: params.method ?? operationMethod ?? "GET",
208
+ expires_in:
209
+ params.expiresIn ??
210
+ params.expires_in ??
211
+ params.expiresInSec ??
212
+ params.expires_in_sec ??
213
+ 3600,
214
+ });
215
+ const data = await this.http.post<unknown>(
216
+ `/storage/buckets/${bucketId}/presign`,
217
+ body,
218
+ );
219
+ return unwrap<PresignResult>(data);
220
+ }
221
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Tenant — current tenant info and plan/usage.
3
+ */
4
+
5
+ import type { HttpClient } from "../http.js";
6
+
7
+ // ── Resource shapes ──────────────────────────────────────────────────────────
8
+
9
+ export interface TenantPlan {
10
+ id?: string;
11
+ name?: string;
12
+ limits?: Record<string, unknown>;
13
+ usage?: Record<string, unknown>;
14
+ [key: string]: unknown;
15
+ }
16
+
17
+ // ── Helpers ───────────────────────────────────────────────────────────────────
18
+
19
+ function unwrap<T>(payload: unknown): T {
20
+ if (payload && typeof payload === "object") {
21
+ const p = payload as Record<string, unknown>;
22
+ for (const k of ["data", "tenant", "items"]) {
23
+ if (k in p) return p[k] as T;
24
+ }
25
+ }
26
+ return payload as T;
27
+ }
28
+
29
+ // ── Main resource ─────────────────────────────────────────────────────────────
30
+
31
+ export class Tenant {
32
+ constructor(private readonly http: HttpClient) {}
33
+
34
+ /** Get the current tenant's plan, limits, and live usage counters. */
35
+ async current(): Promise<TenantPlan> {
36
+ const data = await this.http.get<unknown>("/tenant/plan");
37
+ return unwrap<TenantPlan>(data);
38
+ }
39
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Usage — per-session metering, summary, and reports.
3
+ */
4
+
5
+ import type { HttpClient } from "../http.js";
6
+
7
+ // ── Resource shapes ──────────────────────────────────────────────────────────
8
+
9
+ export interface UsageSummary {
10
+ period_start?: string;
11
+ period_end?: string;
12
+ total_credits?: number;
13
+ [key: string]: unknown;
14
+ }
15
+
16
+ export interface UsageSession {
17
+ id?: string;
18
+ computer_id?: string;
19
+ started_at?: string;
20
+ ended_at?: string;
21
+ credits_used?: number;
22
+ [key: string]: unknown;
23
+ }
24
+
25
+ // ── Request payloads ─────────────────────────────────────────────────────────
26
+
27
+ export interface UsageSessionsParams {
28
+ computer_id?: string;
29
+ limit?: number;
30
+ cursor?: string;
31
+ [key: string]: string | number | boolean | undefined;
32
+ }
33
+
34
+ export interface UsageReportParams extends UsageSessionsParams {
35
+ period_start?: string;
36
+ period_end?: string;
37
+ }
38
+
39
+ // ── Helpers ───────────────────────────────────────────────────────────────────
40
+
41
+ function unwrap<T>(payload: unknown): T {
42
+ if (payload && typeof payload === "object") {
43
+ const p = payload as Record<string, unknown>;
44
+ for (const k of ["data", "usage", "sessions", "summary", "items"]) {
45
+ if (k in p) return p[k] as T;
46
+ }
47
+ }
48
+ return payload as T;
49
+ }
50
+
51
+ function stripUndefined(
52
+ input: Record<string, unknown>,
53
+ ): Record<string, string | number | boolean | undefined> {
54
+ return Object.fromEntries(
55
+ Object.entries(input).filter(([, v]) => v !== undefined),
56
+ ) as Record<string, string | number | boolean | undefined>;
57
+ }
58
+
59
+ // ── Main resource ─────────────────────────────────────────────────────────────
60
+
61
+ export class Usage {
62
+ constructor(private readonly http: HttpClient) {}
63
+
64
+ /** Get the current period usage summary. */
65
+ async current(): Promise<UsageSummary> {
66
+ const data = await this.http.get<unknown>("/usage/summary");
67
+ return unwrap<UsageSummary>(data);
68
+ }
69
+
70
+ /** List per-session metering events. */
71
+ async sessions(params: UsageSessionsParams = {}): Promise<UsageSession[]> {
72
+ const query = stripUndefined(params as Record<string, unknown>);
73
+ const data = await this.http.get<unknown>("/usage/sessions", query);
74
+ const result = unwrap<UsageSession[] | unknown>(data);
75
+ if (Array.isArray(result)) return result;
76
+ return [];
77
+ }
78
+
79
+ /** Get a usage report for a period. */
80
+ async report(params: UsageReportParams = {}): Promise<UsageSummary> {
81
+ const query = stripUndefined(params as Record<string, unknown>);
82
+ const data = await this.http.get<unknown>("/usage/summary", query);
83
+ return unwrap<UsageSummary>(data);
84
+ }
85
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Volumes resource — persistent block storage.
3
+ */
4
+
5
+ import { randomUUID } from "node:crypto";
6
+
7
+ import type { HttpClient } from "../http.js";
8
+
9
+ // ── Branded IDs ──────────────────────────────────────────────────────────────
10
+
11
+ export type VolumeId = string & { readonly __brand: "VolumeId" };
12
+
13
+ // ── Resource shapes ──────────────────────────────────────────────────────────
14
+
15
+ export interface VolumeData {
16
+ id: VolumeId;
17
+ tenant_id: string;
18
+ name: string;
19
+ size_gb: number;
20
+ state?: string;
21
+ region?: string | null;
22
+ attached_to?: string | null;
23
+ created_at?: string;
24
+ updated_at?: string;
25
+ [key: string]: unknown;
26
+ }
27
+
28
+ // ── Request payloads ─────────────────────────────────────────────────────────
29
+
30
+ export interface VolumeListParams {
31
+ limit?: number;
32
+ cursor?: string;
33
+ state?: string;
34
+ [key: string]: string | number | boolean | undefined;
35
+ }
36
+
37
+ export interface VolumeCreateParams {
38
+ name: string;
39
+ size_gb: number;
40
+ sizeGb?: number;
41
+ region?: string;
42
+ idempotencyKey?: string;
43
+ [key: string]: unknown;
44
+ }
45
+
46
+ // ── Helpers ───────────────────────────────────────────────────────────────────
47
+
48
+ function unwrap<T>(payload: unknown): T {
49
+ if (payload && typeof payload === "object" && "data" in (payload as object)) {
50
+ return (payload as { data: T }).data;
51
+ }
52
+ return payload as T;
53
+ }
54
+
55
+ function listItems<T>(
56
+ payload: unknown,
57
+ candidateKeys: string[] = ["data", "volumes", "items"],
58
+ ): T[] {
59
+ if (Array.isArray(payload)) return payload;
60
+ if (!payload || typeof payload !== "object") return [];
61
+ const p = payload as Record<string, unknown>;
62
+ if (Array.isArray(p.data)) return p.data as T[];
63
+ for (const key of candidateKeys) {
64
+ if (Array.isArray(p[key])) return p[key] as T[];
65
+ }
66
+ return [];
67
+ }
68
+
69
+ function stripUndefined(
70
+ input: Record<string, unknown>,
71
+ ): Record<string, unknown> {
72
+ return Object.fromEntries(
73
+ Object.entries(input).filter(([, v]) => v !== undefined),
74
+ );
75
+ }
76
+
77
+ function idempotencyKey(key?: string): string {
78
+ return key ?? randomUUID();
79
+ }
80
+
81
+ // ── Main resource ─────────────────────────────────────────────────────────────
82
+
83
+ export class Volumes {
84
+ constructor(private readonly http: HttpClient) {}
85
+
86
+ async list(params: VolumeListParams = {}): Promise<VolumeData[]> {
87
+ const query = stripUndefined({ ...params }) as Record<
88
+ string,
89
+ string | number | boolean | undefined
90
+ >;
91
+ const data = await this.http.get<unknown>("/volumes", query);
92
+ return listItems<VolumeData>(data);
93
+ }
94
+
95
+ async get(volumeId: string): Promise<VolumeData> {
96
+ const data = await this.http.get<unknown>(`/volumes/${volumeId}`);
97
+ return unwrap<VolumeData>(data);
98
+ }
99
+
100
+ async create(params: VolumeCreateParams): Promise<VolumeData> {
101
+ const { idempotencyKey: ikey, sizeGb, ...rest } = params;
102
+ const body = stripUndefined({
103
+ ...rest,
104
+ size_gb: sizeGb ?? rest.size_gb,
105
+ });
106
+ const data = await this.http.request<unknown>("/volumes", {
107
+ method: "POST",
108
+ body,
109
+ headers: { "Idempotency-Key": idempotencyKey(ikey) },
110
+ });
111
+ return unwrap<VolumeData>(data);
112
+ }
113
+
114
+ async delete(volumeId: string): Promise<void> {
115
+ await this.http.delete<unknown>(`/volumes/${volumeId}`);
116
+ }
117
+ }