@miosa/sdk 1.2.5 → 1.2.7
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 +107 -59
- package/dist/index.d.ts +513 -193
- package/dist/index.js +1266 -891
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
- package/src/client.ts +0 -293
- package/src/errors.ts +0 -136
- package/src/http.test.ts +0 -374
- package/src/http.ts +0 -390
- package/src/index.ts +0 -569
- package/src/resources/admin.ts +0 -348
- package/src/resources/analytics.ts +0 -60
- package/src/resources/api-keys.ts +0 -119
- package/src/resources/audit-log.ts +0 -64
- package/src/resources/benchmarks.ts +0 -104
- package/src/resources/builder-sessions.ts +0 -75
- package/src/resources/channels.ts +0 -143
- package/src/resources/checkpoints.ts +0 -225
- package/src/resources/command-center.ts +0 -73
- package/src/resources/community.ts +0 -103
- package/src/resources/completions.ts +0 -104
- package/src/resources/computer-auto-stop.ts +0 -43
- package/src/resources/computer-env.ts +0 -76
- package/src/resources/computer-logs.ts +0 -50
- package/src/resources/computer-osa.ts +0 -76
- package/src/resources/computer-ports.ts +0 -91
- package/src/resources/computer-terminal.ts +0 -61
- package/src/resources/computer-volumes.ts +0 -64
- package/src/resources/computer.ts +0 -551
- package/src/resources/computers.ts +0 -75
- package/src/resources/credits.ts +0 -43
- package/src/resources/cron-jobs.ts +0 -191
- package/src/resources/custom_domains.ts +0 -123
- package/src/resources/dashboard.ts +0 -49
- package/src/resources/databases.ts +0 -218
- package/src/resources/deployments.test.ts +0 -197
- package/src/resources/deployments.ts +0 -1208
- package/src/resources/desktop.ts +0 -134
- package/src/resources/devices.test.ts +0 -92
- package/src/resources/devices.ts +0 -291
- package/src/resources/egress.test.ts +0 -318
- package/src/resources/egressAudit.ts +0 -245
- package/src/resources/egressNetwork.ts +0 -450
- package/src/resources/egressSecrets.ts +0 -577
- package/src/resources/email.ts +0 -212
- package/src/resources/embeddings.ts +0 -36
- package/src/resources/events.ts +0 -296
- package/src/resources/exec.ts +0 -319
- package/src/resources/external-keys.ts +0 -79
- package/src/resources/files.test.ts +0 -339
- package/src/resources/files.ts +0 -220
- package/src/resources/flat-custom-domains.ts +0 -127
- package/src/resources/functions.ts +0 -178
- package/src/resources/health-checks.ts +0 -165
- package/src/resources/integrations.ts +0 -183
- package/src/resources/mcp.ts +0 -70
- package/src/resources/models.ts +0 -45
- package/src/resources/network_policy.ts +0 -73
- package/src/resources/open-computers/agents.ts +0 -91
- package/src/resources/open-computers/apps.ts +0 -102
- package/src/resources/open-computers/clusters.ts +0 -88
- package/src/resources/open-computers/desktop.ts +0 -34
- package/src/resources/open-computers/files.ts +0 -97
- package/src/resources/open-computers/hosts.ts +0 -85
- package/src/resources/open-computers/index.ts +0 -98
- package/src/resources/open-computers/jobs.ts +0 -75
- package/src/resources/open-computers/open_computers.test.ts +0 -288
- package/src/resources/open-computers/secrets.ts +0 -115
- package/src/resources/open-computers/terminal.ts +0 -33
- package/src/resources/open-computers/tunnels.ts +0 -87
- package/src/resources/open-computers/types.ts +0 -343
- package/src/resources/open-computers/workspaces.ts +0 -135
- package/src/resources/org-invites.ts +0 -189
- package/src/resources/project-auth.ts +0 -142
- package/src/resources/project-integrations.ts +0 -133
- package/src/resources/provider-defaults.ts +0 -89
- package/src/resources/regions.ts +0 -94
- package/src/resources/sandbox-templates.ts +0 -195
- package/src/resources/sandboxes.live.test.ts +0 -92
- package/src/resources/sandboxes.test.ts +0 -655
- package/src/resources/sandboxes.ts +0 -1437
- package/src/resources/settings.ts +0 -143
- package/src/resources/snapshots-standalone.ts +0 -51
- package/src/resources/storage.ts +0 -221
- package/src/resources/tenant.ts +0 -66
- package/src/resources/usage.ts +0 -85
- package/src/resources/volumes.ts +0 -117
- package/src/resources/webhooks.ts +0 -239
- package/src/resources/workspace-invites.ts +0 -188
- package/src/resources/workspace-members.test.ts +0 -121
- package/src/resources/workspace-members.ts +0 -143
- package/src/types.ts +0 -463
package/src/http.ts
DELETED
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
import { MiosaError, NetworkError, TimeoutError } from "./errors.js";
|
|
2
|
-
import type { MiosaErrorBody } from "./errors.js";
|
|
3
|
-
|
|
4
|
-
// ─── Transport tuning ─────────────────────────────────────────────────────────
|
|
5
|
-
//
|
|
6
|
-
// Node's built-in ``fetch`` is backed by undici and already reuses connections
|
|
7
|
-
// via HTTP/1.1 keep-alive. We additionally try to install a custom undici
|
|
8
|
-
// ``Agent`` with HTTP/2 enabled so sequential calls can multiplex on a single
|
|
9
|
-
// TLS session and skip the per-call handshake tax (~80-150 ms cold,
|
|
10
|
-
// ~20-40 ms warm).
|
|
11
|
-
//
|
|
12
|
-
// undici is an optional dependency: when not installed, we fall back to the
|
|
13
|
-
// default fetch dispatcher (still pooled, just HTTP/1.1).
|
|
14
|
-
|
|
15
|
-
interface UndiciAgentOptions {
|
|
16
|
-
keepAliveTimeout?: number;
|
|
17
|
-
keepAliveMaxTimeout?: number;
|
|
18
|
-
allowH2?: boolean;
|
|
19
|
-
connections?: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface UndiciModule {
|
|
23
|
-
Agent: new (opts: UndiciAgentOptions) => unknown;
|
|
24
|
-
setGlobalDispatcher: (dispatcher: unknown) => void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
let _h2Configured = false;
|
|
28
|
-
let _h2Available = false;
|
|
29
|
-
let _agentSingleton: unknown = null;
|
|
30
|
-
|
|
31
|
-
async function ensureHttp2Agent(): Promise<void> {
|
|
32
|
-
if (_h2Configured) return;
|
|
33
|
-
_h2Configured = true;
|
|
34
|
-
try {
|
|
35
|
-
// Indirection through a variable specifier prevents TypeScript from
|
|
36
|
-
// trying to resolve the optional ``undici`` module at compile time.
|
|
37
|
-
const undiciSpecifier = "undici";
|
|
38
|
-
const undici = (await import(
|
|
39
|
-
/* @vite-ignore */ undiciSpecifier
|
|
40
|
-
)) as unknown as UndiciModule;
|
|
41
|
-
const agent = new undici.Agent({
|
|
42
|
-
keepAliveTimeout: 60_000,
|
|
43
|
-
keepAliveMaxTimeout: 600_000,
|
|
44
|
-
allowH2: true,
|
|
45
|
-
connections: 20,
|
|
46
|
-
});
|
|
47
|
-
undici.setGlobalDispatcher(agent);
|
|
48
|
-
_agentSingleton = agent;
|
|
49
|
-
_h2Available = true;
|
|
50
|
-
} catch {
|
|
51
|
-
// undici not installed (e.g. browser bundle, or user opted out).
|
|
52
|
-
// Fall back to native fetch's default keep-alive pool.
|
|
53
|
-
_h2Available = false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Fire-and-forget at module load. Subsequent fetch() calls will pick up the
|
|
58
|
-
// configured dispatcher. Tests can re-import and observe ``isHttp2Configured``.
|
|
59
|
-
const _h2Bootstrap = ensureHttp2Agent();
|
|
60
|
-
|
|
61
|
-
/** Exposed for tests — resolves after the HTTP/2 agent setup completes. */
|
|
62
|
-
export function whenTransportReady(): Promise<void> {
|
|
63
|
-
return _h2Bootstrap;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Exposed for tests — true iff undici with allowH2 was installed. */
|
|
67
|
-
export function isHttp2Available(): boolean {
|
|
68
|
-
return _h2Available;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Exposed for tests — the cached undici Agent (or null). */
|
|
72
|
-
export function getDispatcher(): unknown {
|
|
73
|
-
return _agentSingleton;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface RequestOptions {
|
|
77
|
-
method?: string;
|
|
78
|
-
headers?: Record<string, string>;
|
|
79
|
-
body?: unknown;
|
|
80
|
-
timeout?: number;
|
|
81
|
-
/** Return raw Response instead of parsing JSON */
|
|
82
|
-
rawResponse?: boolean;
|
|
83
|
-
/** Send body as multipart/form-data */
|
|
84
|
-
formData?: FormData;
|
|
85
|
-
/** Expected response is binary */
|
|
86
|
-
binary?: boolean;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export interface HttpClientConfig {
|
|
90
|
-
baseUrl: string;
|
|
91
|
-
apiKey: string;
|
|
92
|
-
timeout: number;
|
|
93
|
-
maxRetries: number;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const DEFAULT_TIMEOUT = 30_000;
|
|
97
|
-
const DEFAULT_MAX_RETRIES = 3;
|
|
98
|
-
const RETRY_STATUS = new Set([429, 500, 502, 503, 504]);
|
|
99
|
-
const BASE_DELAY_MS = 500;
|
|
100
|
-
|
|
101
|
-
function sleep(ms: number): Promise<void> {
|
|
102
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function retryDelay(attempt: number, retryAfterHeader: string | null): number {
|
|
106
|
-
if (retryAfterHeader !== null) {
|
|
107
|
-
const seconds = parseInt(retryAfterHeader, 10);
|
|
108
|
-
if (!isNaN(seconds)) return seconds * 1000;
|
|
109
|
-
}
|
|
110
|
-
return BASE_DELAY_MS * 2 ** attempt + Math.random() * 100;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export class HttpClient {
|
|
114
|
-
/** Public for WebSocket clients that need to derive their own URL. */
|
|
115
|
-
readonly baseUrl: string;
|
|
116
|
-
/** Public for WebSocket clients that need to send the same auth. */
|
|
117
|
-
readonly apiKey: string;
|
|
118
|
-
private readonly timeout: number;
|
|
119
|
-
private readonly maxRetries: number;
|
|
120
|
-
|
|
121
|
-
constructor(config: HttpClientConfig) {
|
|
122
|
-
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
123
|
-
this.apiKey = config.apiKey;
|
|
124
|
-
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
125
|
-
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private buildUrl(
|
|
129
|
-
path: string,
|
|
130
|
-
query?: Record<string, string | number | boolean | undefined>,
|
|
131
|
-
): string {
|
|
132
|
-
const url = new URL(`${this.baseUrl}${path}`);
|
|
133
|
-
if (query) {
|
|
134
|
-
for (const [key, value] of Object.entries(query)) {
|
|
135
|
-
if (value !== undefined) {
|
|
136
|
-
url.searchParams.set(key, String(value));
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return url.toString();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private baseHeaders(extra?: Record<string, string>): Record<string, string> {
|
|
144
|
-
return {
|
|
145
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
146
|
-
Accept: "application/json",
|
|
147
|
-
"User-Agent": "@miosa/sdk/1.0.0",
|
|
148
|
-
...extra,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async request<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
|
153
|
-
const {
|
|
154
|
-
method = "GET",
|
|
155
|
-
body,
|
|
156
|
-
formData,
|
|
157
|
-
binary = false,
|
|
158
|
-
timeout = this.timeout,
|
|
159
|
-
} = options;
|
|
160
|
-
|
|
161
|
-
let headers = this.baseHeaders(options.headers);
|
|
162
|
-
|
|
163
|
-
let fetchBody: BodyInit | undefined;
|
|
164
|
-
if (formData) {
|
|
165
|
-
fetchBody = formData;
|
|
166
|
-
// Let fetch set Content-Type with boundary for multipart
|
|
167
|
-
} else if (body !== undefined) {
|
|
168
|
-
headers = { ...headers, "Content-Type": "application/json" };
|
|
169
|
-
fetchBody = JSON.stringify(body);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
let lastError: MiosaError | undefined;
|
|
173
|
-
|
|
174
|
-
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
175
|
-
const controller = new AbortController();
|
|
176
|
-
const timer = setTimeout(() => controller.abort(), timeout);
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
const response = await fetch(this.buildUrl(path), {
|
|
180
|
-
method,
|
|
181
|
-
headers,
|
|
182
|
-
// exactOptionalPropertyTypes requires null, not undefined, for body
|
|
183
|
-
body: fetchBody ?? null,
|
|
184
|
-
signal: controller.signal,
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
clearTimeout(timer);
|
|
188
|
-
|
|
189
|
-
if (!response.ok) {
|
|
190
|
-
const requestId = response.headers.get("x-request-id") ?? undefined;
|
|
191
|
-
const retryAfter = response.headers.get("retry-after");
|
|
192
|
-
|
|
193
|
-
let errorBody: MiosaErrorBody = {};
|
|
194
|
-
try {
|
|
195
|
-
errorBody = (await response.json()) as MiosaErrorBody;
|
|
196
|
-
} catch {
|
|
197
|
-
// body may not be JSON on 5xx errors
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const error = MiosaError.fromResponse(
|
|
201
|
-
response.status,
|
|
202
|
-
errorBody,
|
|
203
|
-
requestId,
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
if (RETRY_STATUS.has(response.status) && attempt < this.maxRetries) {
|
|
207
|
-
lastError = error;
|
|
208
|
-
await sleep(retryDelay(attempt, retryAfter));
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
throw error;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (binary) {
|
|
216
|
-
const buffer = await response.arrayBuffer();
|
|
217
|
-
return new Uint8Array(buffer) as unknown as T;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// 204 No Content
|
|
221
|
-
if (response.status === 204) {
|
|
222
|
-
return undefined as unknown as T;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return (await response.json()) as T;
|
|
226
|
-
} catch (err) {
|
|
227
|
-
clearTimeout(timer);
|
|
228
|
-
|
|
229
|
-
if (err instanceof MiosaError) {
|
|
230
|
-
throw err;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
234
|
-
throw new TimeoutError(
|
|
235
|
-
`Request to ${path} timed out after ${timeout}ms`,
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (err instanceof TypeError) {
|
|
240
|
-
const networkErr = new NetworkError(
|
|
241
|
-
`Network error reaching ${path}: ${err.message}`,
|
|
242
|
-
err,
|
|
243
|
-
);
|
|
244
|
-
if (attempt < this.maxRetries) {
|
|
245
|
-
lastError = networkErr;
|
|
246
|
-
await sleep(retryDelay(attempt, null));
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
throw networkErr;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
throw err;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Exhausted retries
|
|
257
|
-
throw (
|
|
258
|
-
lastError ??
|
|
259
|
-
new MiosaError("Max retries exceeded", 0, "MAX_RETRIES_EXCEEDED")
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
async get<T>(
|
|
264
|
-
path: string,
|
|
265
|
-
query?: Record<string, string | number | boolean | undefined>,
|
|
266
|
-
): Promise<T> {
|
|
267
|
-
let fullPath = path;
|
|
268
|
-
if (query) {
|
|
269
|
-
const params = new URLSearchParams();
|
|
270
|
-
for (const [k, v] of Object.entries(query)) {
|
|
271
|
-
if (v !== undefined && v !== null) params.set(k, String(v));
|
|
272
|
-
}
|
|
273
|
-
const qs = params.toString();
|
|
274
|
-
if (qs) fullPath = `${path}${path.includes("?") ? "&" : "?"}${qs}`;
|
|
275
|
-
}
|
|
276
|
-
return this.request<T>(fullPath, { method: "GET" });
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async post<T>(path: string, body?: unknown): Promise<T> {
|
|
280
|
-
return this.request<T>(path, { method: "POST", body });
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async patch<T>(path: string, body?: unknown): Promise<T> {
|
|
284
|
-
return this.request<T>(path, { method: "PATCH", body });
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async put<T>(path: string, body?: unknown): Promise<T> {
|
|
288
|
-
return this.request<T>(path, { method: "PUT", body });
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
async delete<T>(path: string, body?: unknown): Promise<T> {
|
|
292
|
-
return this.request<T>(path, { method: "DELETE", body });
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async getBinary(path: string): Promise<Uint8Array> {
|
|
296
|
-
return this.request<Uint8Array>(path, { method: "GET", binary: true });
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async postFormData<T>(path: string, formData: FormData): Promise<T> {
|
|
300
|
-
return this.request<T>(path, { method: "POST", formData });
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Open a Server-Sent Events stream. Returns an AsyncIterableIterator of
|
|
305
|
-
* parsed event data objects. The caller is responsible for breaking the loop.
|
|
306
|
-
*/
|
|
307
|
-
async *stream<T>(
|
|
308
|
-
path: string,
|
|
309
|
-
options: RequestOptions = {},
|
|
310
|
-
): AsyncIterableIterator<T> {
|
|
311
|
-
const method = options.method ?? "GET";
|
|
312
|
-
let headers = this.baseHeaders({
|
|
313
|
-
Accept: "text/event-stream",
|
|
314
|
-
...options.headers,
|
|
315
|
-
});
|
|
316
|
-
let body: BodyInit | null = null;
|
|
317
|
-
if (options.body !== undefined) {
|
|
318
|
-
headers = { ...headers, "Content-Type": "application/json" };
|
|
319
|
-
body = JSON.stringify(options.body);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const controller = new AbortController();
|
|
323
|
-
|
|
324
|
-
let response: Response;
|
|
325
|
-
try {
|
|
326
|
-
response = await fetch(`${this.baseUrl}${path}`, {
|
|
327
|
-
method,
|
|
328
|
-
headers,
|
|
329
|
-
body,
|
|
330
|
-
signal: controller.signal,
|
|
331
|
-
});
|
|
332
|
-
} catch (err) {
|
|
333
|
-
if (err instanceof TypeError) {
|
|
334
|
-
throw new NetworkError(
|
|
335
|
-
`Network error opening SSE stream: ${err.message}`,
|
|
336
|
-
err,
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
throw err;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (!response.ok) {
|
|
343
|
-
let errorBody: MiosaErrorBody = {};
|
|
344
|
-
try {
|
|
345
|
-
errorBody = (await response.json()) as MiosaErrorBody;
|
|
346
|
-
} catch {
|
|
347
|
-
// ignore
|
|
348
|
-
}
|
|
349
|
-
throw MiosaError.fromResponse(
|
|
350
|
-
response.status,
|
|
351
|
-
errorBody,
|
|
352
|
-
response.headers.get("x-request-id") ?? undefined,
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (!response.body) {
|
|
357
|
-
throw new MiosaError("SSE stream has no body", 0, "NO_STREAM_BODY");
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const reader = response.body.getReader();
|
|
361
|
-
const decoder = new TextDecoder();
|
|
362
|
-
let buffer = "";
|
|
363
|
-
|
|
364
|
-
try {
|
|
365
|
-
while (true) {
|
|
366
|
-
const { done, value } = await reader.read();
|
|
367
|
-
if (done) break;
|
|
368
|
-
|
|
369
|
-
buffer += decoder.decode(value, { stream: true });
|
|
370
|
-
const lines = buffer.split("\n");
|
|
371
|
-
buffer = lines.pop() ?? "";
|
|
372
|
-
|
|
373
|
-
for (const line of lines) {
|
|
374
|
-
if (line.startsWith("data:")) {
|
|
375
|
-
const raw = line.slice(5).trim();
|
|
376
|
-
if (raw === "[DONE]" || raw === "") continue;
|
|
377
|
-
try {
|
|
378
|
-
yield JSON.parse(raw) as T;
|
|
379
|
-
} catch {
|
|
380
|
-
// skip malformed SSE data lines
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
} finally {
|
|
386
|
-
controller.abort();
|
|
387
|
-
reader.releaseLock();
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|