@mainbrella/sdk 0.0.0-stage → 0.1.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 +674 -0
- package/README.md +190 -2
- package/cli.mjs +155 -0
- package/index.d.ts +88 -0
- package/index.js +298 -0
- package/package.json +34 -4
package/index.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
const keyPattern = /^mb_[a-f0-9]{64}$/i;
|
|
2
|
+
const terminal = new Set(['succeeded', 'failed', 'canceled', 'timed_out', 'output_limit', 'interrupted']);
|
|
3
|
+
|
|
4
|
+
export class MainbrellaError extends Error {
|
|
5
|
+
constructor(code, status = 0, details = {}) {
|
|
6
|
+
super(code);
|
|
7
|
+
this.name = 'MainbrellaError';
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.status = status;
|
|
10
|
+
Object.assign(this, details);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function apiOrigin(value) {
|
|
15
|
+
const url = new URL(value);
|
|
16
|
+
if (url.username || url.password || url.search || url.hash || url.pathname !== '/'
|
|
17
|
+
|| (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)))) {
|
|
18
|
+
throw new MainbrellaError('invalid_api_url');
|
|
19
|
+
}
|
|
20
|
+
return url.origin;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function identity(value) {
|
|
24
|
+
if (!value || !/^(small|c[1-9]\d{0,2})$/.test(value.id) || !value.createdAt
|
|
25
|
+
|| !Number.isFinite(Date.parse(value.createdAt)) || new Date(value.createdAt).toISOString() !== value.createdAt) {
|
|
26
|
+
throw new MainbrellaError('invalid_container_identity');
|
|
27
|
+
}
|
|
28
|
+
return { id: value.id, createdAt: value.createdAt };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function fileBoolean(value) {
|
|
32
|
+
if (typeof value !== 'boolean') throw new MainbrellaError('invalid_file_options');
|
|
33
|
+
return String(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class Mainbrella {
|
|
37
|
+
#apiKey;
|
|
38
|
+
#fetch;
|
|
39
|
+
constructor({ apiKey, baseUrl = 'https://api.mainbrella.com', fetch: fetcher = globalThis.fetch, timeoutMs = 90_000 } = {}) {
|
|
40
|
+
if (!keyPattern.test(apiKey ?? '')) throw new MainbrellaError('invalid_api_key');
|
|
41
|
+
if (typeof fetcher !== 'function' || !Number.isInteger(timeoutMs) || timeoutMs < 1) throw new MainbrellaError('invalid_client_options');
|
|
42
|
+
this.#apiKey = apiKey;
|
|
43
|
+
this.#fetch = fetcher;
|
|
44
|
+
this.baseUrl = apiOrigin(baseUrl);
|
|
45
|
+
this.timeoutMs = timeoutMs;
|
|
46
|
+
const workspacePath = id => {
|
|
47
|
+
if(!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(id??''))throw new MainbrellaError('invalid_workspace_identity');
|
|
48
|
+
return `/workspaces/${id}`;
|
|
49
|
+
};
|
|
50
|
+
this.workspaces = {
|
|
51
|
+
list:()=>this.request('/workspaces'), get:id=>this.request(workspacePath(id)),
|
|
52
|
+
update:(id,options)=>this.request(workspacePath(id),{method:'PATCH',body:options}),
|
|
53
|
+
delete:id=>this.request(workspacePath(id),{method:'DELETE'}),
|
|
54
|
+
restore:(id,options={})=>{workspacePath(id);return this.create({...options,workspaceId:id});},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async request(path, { method = 'GET', body, headers = {}, binary = false, stream = false, signal } = {}) {
|
|
58
|
+
if (!path.startsWith('/') || path.startsWith('//')) throw new MainbrellaError('invalid_api_path');
|
|
59
|
+
const url = new URL(path, this.baseUrl);
|
|
60
|
+
if (url.origin !== this.baseUrl) throw new MainbrellaError('invalid_api_path');
|
|
61
|
+
let response;
|
|
62
|
+
try {
|
|
63
|
+
response = await this.#fetch(url, { method, redirect: 'error', credentials: 'omit',
|
|
64
|
+
headers: { ...headers, Authorization: `Bearer ${this.#apiKey}`,
|
|
65
|
+
...(body !== undefined ? { 'Content-Type': body instanceof Uint8Array ? 'application/octet-stream' : 'application/json' } : {}) },
|
|
66
|
+
body: body instanceof Uint8Array ? body : body !== undefined ? JSON.stringify(body) : undefined,
|
|
67
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(this.timeoutMs)]) : AbortSignal.timeout(this.timeoutMs) });
|
|
68
|
+
} catch { throw new MainbrellaError('transport_unavailable'); }
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
let data;
|
|
71
|
+
try { data = await response.json(); } catch {}
|
|
72
|
+
const code = typeof data?.error === 'string' && /^[a-z][a-z0-9_]{0,79}$/.test(data.error) ? data.error : 'request_failed';
|
|
73
|
+
const details = code === 'preview_reconciliation_required' && /^[a-f0-9]{32}$/.test(data?.previewId ?? '')
|
|
74
|
+
? { previewId: data.previewId } : {};
|
|
75
|
+
throw new MainbrellaError(code, response.status, details);
|
|
76
|
+
}
|
|
77
|
+
if (stream) return response;
|
|
78
|
+
try { return binary ? new Uint8Array(await response.arrayBuffer()) : await response.json(); }
|
|
79
|
+
catch { throw new MainbrellaError('invalid_response', response.status); }
|
|
80
|
+
}
|
|
81
|
+
capabilities() { return this.request('/capabilities'); }
|
|
82
|
+
list() { return this.request('/containers'); }
|
|
83
|
+
connect(value) { return new Sandbox(this, value); }
|
|
84
|
+
async create({ catalogId, imageId, size, internet, workspaceId, idempotencyKey = crypto.randomUUID(), waitTimeoutMs = 120_000, pollIntervalMs = 1000 } = {}) {
|
|
85
|
+
if (catalogId && imageId || workspaceId && (catalogId || imageId || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(workspaceId)) || !/^[A-Za-z0-9_-]{1,128}$/.test(idempotencyKey)
|
|
86
|
+
|| !Number.isInteger(waitTimeoutMs) || waitTimeoutMs < 1 || !Number.isInteger(pollIntervalMs) || pollIntervalMs < 1) {
|
|
87
|
+
throw new MainbrellaError('invalid_creation_options');
|
|
88
|
+
}
|
|
89
|
+
const deadline = Date.now() + waitTimeoutMs;
|
|
90
|
+
if(workspaceId && (await this.capabilities())?.persistence?.snapshots!==true)throw new MainbrellaError('persistence_unavailable',503,{idempotencyKey});
|
|
91
|
+
if (size !== undefined && !['lite', 'small', 'medium', 'large', 'xl'].includes(size)) throw new MainbrellaError('invalid_creation_options');
|
|
92
|
+
if (internet !== undefined && typeof internet !== 'boolean') throw new MainbrellaError('invalid_creation_options');
|
|
93
|
+
if (internet === false && (await this.capabilities())?.networking?.internetControl !== true) throw new MainbrellaError('network_policy_unavailable', 503, { idempotencyKey });
|
|
94
|
+
const body = { ...(workspaceId?{workspaceId}:imageId ? { imageId } : catalogId ? { catalogId } : {}), ...(size !== undefined ? { size } : {}), ...(internet !== undefined ? { internet } : {}) };
|
|
95
|
+
while (Date.now() < deadline) {
|
|
96
|
+
try {
|
|
97
|
+
const data = await this.request('/containers', { method: 'POST', body,
|
|
98
|
+
headers: { 'Idempotency-Key': idempotencyKey }, signal: AbortSignal.timeout(Math.max(1, deadline - Date.now())) });
|
|
99
|
+
const creation = data.creation;
|
|
100
|
+
if (!creation?.id || !['starting', 'running'].includes(creation.status)) throw new MainbrellaError('invalid_creation_response');
|
|
101
|
+
if (creation.status === 'running') {
|
|
102
|
+
const selected = data.containers?.find(c => c.id === creation.containerId && c.createdAt === creation.createdAt && c.status === 'running');
|
|
103
|
+
if (!selected) throw new MainbrellaError('invalid_creation_response');
|
|
104
|
+
if (internet === false && selected.internet !== false) throw new MainbrellaError('network_policy_unconfirmed', 409);
|
|
105
|
+
if(workspaceId && selected.workspaceId!==workspaceId)throw new MainbrellaError('workspace_restore_unconfirmed',409);
|
|
106
|
+
const sandbox = this.connect(selected);
|
|
107
|
+
sandbox.creationId = creation.id;
|
|
108
|
+
return sandbox;
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (!(error instanceof MainbrellaError) || ['network_policy_unavailable','persistence_unavailable'].includes(error.code) || error.status && error.status !== 503 || error.status === 0 && error.code !== 'transport_unavailable') {
|
|
112
|
+
error.idempotencyKey = idempotencyKey;
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const remaining = deadline - Date.now();
|
|
117
|
+
if (remaining > 0) await new Promise(resolve => setTimeout(resolve, Math.min(pollIntervalMs, remaining)));
|
|
118
|
+
}
|
|
119
|
+
throw new MainbrellaError('creation_ambiguous', 0, { idempotencyKey });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export class Sandbox {
|
|
124
|
+
constructor(client, value) {
|
|
125
|
+
this.client = client;
|
|
126
|
+
Object.assign(this, identity(value));
|
|
127
|
+
if (typeof value.internet === 'boolean') this.internet = value.internet;
|
|
128
|
+
for (const name of ['imageDigest', 'catalogId', 'imageId', 'instance','workspaceId']) if (typeof value[name] === 'string') this[name] = value[name];
|
|
129
|
+
this.files = {
|
|
130
|
+
read: path => this.client.request(this.path('/containers/files', { path }), { binary: true }),
|
|
131
|
+
write: (path, bytes) => {
|
|
132
|
+
if (!(bytes instanceof Uint8Array)) throw new MainbrellaError('file_bytes_required');
|
|
133
|
+
return this.client.request(this.path('/containers/files', { path }), { method: 'PUT', body: bytes });
|
|
134
|
+
},
|
|
135
|
+
list: (path, { limit, offset } = {}) => this.client.request(this.path('/containers/files/list', {
|
|
136
|
+
path, ...(limit !== undefined ? { limit: String(limit) } : {}), ...(offset !== undefined ? { offset: String(offset) } : {}),
|
|
137
|
+
})),
|
|
138
|
+
stat: (path, { followSymlinks = false } = {}) => this.client.request(this.path('/containers/files/stat', { path, followSymlinks: fileBoolean(followSymlinks) })),
|
|
139
|
+
mkdir: (path, { recursive = false, mode } = {}) => this.client.request(this.path('/containers/files/mkdir'), {
|
|
140
|
+
method: 'POST', body: { path, recursive, ...(mode !== undefined ? { mode } : {}) },
|
|
141
|
+
}),
|
|
142
|
+
remove: (path, { recursive = false } = {}) => this.client.request(this.path('/containers/files/remove', { path, recursive: fileBoolean(recursive) }), { method: 'DELETE' }),
|
|
143
|
+
move: (path, destination) => this.client.request(this.path('/containers/files/move'), { method: 'POST', body: { path, destination } }),
|
|
144
|
+
chmod: (path, mode) => this.client.request(this.path('/containers/files/chmod', { path }), { method: 'PATCH', body: { mode } }),
|
|
145
|
+
};
|
|
146
|
+
this.previews = {
|
|
147
|
+
create: (port, { ttlSeconds } = {}) => {
|
|
148
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535
|
|
149
|
+
|| ttlSeconds !== undefined && (!Number.isInteger(ttlSeconds) || ttlSeconds < 60 || ttlSeconds > 3600)) {
|
|
150
|
+
throw new MainbrellaError('invalid_preview_options');
|
|
151
|
+
}
|
|
152
|
+
return this.client.request(this.path('/containers/previews'), { method: 'POST',
|
|
153
|
+
body: { port, ...(ttlSeconds !== undefined ? { ttlSeconds } : {}) } });
|
|
154
|
+
},
|
|
155
|
+
list: () => this.client.request(this.path('/containers/previews')),
|
|
156
|
+
revoke: previewId => {
|
|
157
|
+
if (!/^[a-f0-9]{32}$/.test(previewId ?? '')) throw new MainbrellaError('invalid_preview_identity');
|
|
158
|
+
return this.client.request(this.path('/containers/previews', { previewId }), { method: 'DELETE' });
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
this.webhook = {
|
|
162
|
+
get: () => this.client.request(this.path('/containers/webhook')),
|
|
163
|
+
configure: (url, { replayFromCursor } = {}) => this.client.request(this.path('/containers/webhook'), {
|
|
164
|
+
method: 'PUT', body: { url, ...(replayFromCursor !== undefined ? { replayFromCursor } : {}) },
|
|
165
|
+
}),
|
|
166
|
+
remove: () => this.client.request(this.path('/containers/webhook'), { method: 'DELETE' }),
|
|
167
|
+
deliveries: () => this.client.request(this.path('/containers/webhook/deliveries')),
|
|
168
|
+
retry: eventId => {
|
|
169
|
+
if (!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(eventId ?? '')) throw new MainbrellaError('invalid_event_identity');
|
|
170
|
+
return this.client.request(this.path('/containers/webhook/retry'), { method: 'POST', body: { eventId } });
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
this.commands = {
|
|
174
|
+
list: () => this.client.request(this.path('/containers/executions')),
|
|
175
|
+
attach: id => new Execution(this, id),
|
|
176
|
+
run: (command, { timeoutMs, signal } = {}) => this.client.request(this.path('/containers/exec'),
|
|
177
|
+
{ method: 'POST', body: { command, ...(timeoutMs !== undefined ? { timeoutMs } : {}) }, signal }),
|
|
178
|
+
start: async (command, { timeoutMs, stdin, cwd, env, pty, idempotencyKey = crypto.randomUUID() } = {}) => {
|
|
179
|
+
try {
|
|
180
|
+
const record = await this.client.request(this.path('/containers/executions'), { method: 'POST',
|
|
181
|
+
body: { ...(Array.isArray(command) ? { argv: command } : { command }), ...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
182
|
+
...(pty !== undefined ? { pty } : {}), ...(stdin !== undefined ? { stdin } : {}), ...(cwd !== undefined ? { cwd } : {}), ...(env !== undefined ? { env } : {}) }, headers: { 'Idempotency-Key': idempotencyKey } });
|
|
183
|
+
return new Execution(this, record.id);
|
|
184
|
+
} catch (error) { error.idempotencyKey = idempotencyKey; throw error; }
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
path(path, extra = {}) { return `${path}?${new URLSearchParams({ ...extra, id: this.id, createdAt: this.createdAt })}`; }
|
|
189
|
+
async saveWorkspace(name,{stop=false,idempotencyKey=crypto.randomUUID()}={}) {
|
|
190
|
+
if(typeof name!=='string'||!name.length||name.length>80||typeof stop!=='boolean'||!/^[A-Za-z0-9_-]{1,128}$/.test(idempotencyKey))throw new MainbrellaError('invalid_workspace_options');
|
|
191
|
+
try{return await this.client.request('/workspaces',{method:'POST',body:{id:this.id,createdAt:this.createdAt,name,stop},headers:{'Idempotency-Key':idempotencyKey}});}
|
|
192
|
+
catch(error){error.idempotencyKey=idempotencyKey;throw error;}
|
|
193
|
+
}
|
|
194
|
+
exportWorkspace(){return this.client.request(this.path('/containers/export'),{binary:true});}
|
|
195
|
+
events({ cursor, limit } = {}) {
|
|
196
|
+
return this.client.request(this.path('/containers/events', { ...(cursor !== undefined ? { cursor: String(cursor) } : {}), ...(limit !== undefined ? { limit: String(limit) } : {}) }));
|
|
197
|
+
}
|
|
198
|
+
metrics({ from, to } = {}) {
|
|
199
|
+
return this.client.request(this.path('/containers/metrics', { ...(from !== undefined ? { from } : {}), ...(to !== undefined ? { to } : {}) }));
|
|
200
|
+
}
|
|
201
|
+
async kill() {
|
|
202
|
+
const data = await this.client.request(this.path('/containers'), { method: 'DELETE' });
|
|
203
|
+
if (!Array.isArray(data.containers) || data.containers.some(c => c.id === this.id && c.createdAt === this.createdAt)) {
|
|
204
|
+
throw new MainbrellaError('cleanup_unconfirmed');
|
|
205
|
+
}
|
|
206
|
+
return data;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function verifyWebhookSignature(bytes, signature, signingSecret, { nowMs = Date.now(), toleranceSeconds = 300 } = {}) {
|
|
211
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength > 16 * 1024 || typeof signature !== 'string'
|
|
212
|
+
|| typeof signingSecret !== 'string' || !/^mbwh_[a-f0-9]{64}$/.test(signingSecret) || !Number.isFinite(nowMs) || nowMs < 0
|
|
213
|
+
|| !Number.isInteger(toleranceSeconds) || toleranceSeconds < 1 || toleranceSeconds > 900) return false;
|
|
214
|
+
const match = /^t=(\d{1,13}),v1=([a-f0-9]{64})$/.exec(signature);
|
|
215
|
+
if (!match || Math.abs(Math.floor(nowMs / 1000) - Number(match[1])) > toleranceSeconds) return false;
|
|
216
|
+
const prefix = new TextEncoder().encode(`${match[1]}.`), message = new Uint8Array(prefix.length + bytes.byteLength);
|
|
217
|
+
message.set(prefix); message.set(bytes, prefix.length);
|
|
218
|
+
const expected = Uint8Array.from(match[2].match(/.{2}/g), pair => Number.parseInt(pair, 16));
|
|
219
|
+
const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(signingSecret), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
|
|
220
|
+
return crypto.subtle.verify('HMAC', key, expected, message);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export class Execution {
|
|
224
|
+
constructor(sandbox, id) {
|
|
225
|
+
if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(id ?? '')) throw new MainbrellaError('invalid_execution_identity');
|
|
226
|
+
this.sandbox = sandbox;
|
|
227
|
+
this.id = id;
|
|
228
|
+
this.cursor = 0;
|
|
229
|
+
this.stdin = {
|
|
230
|
+
write: bytes => {
|
|
231
|
+
if (!(bytes instanceof Uint8Array)) throw new MainbrellaError('stdin_bytes_required');
|
|
232
|
+
return this.sandbox.client.request(this.path('/stdin'), { method: 'POST', body: bytes });
|
|
233
|
+
},
|
|
234
|
+
close: () => this.sandbox.client.request(this.path('/stdin'), { method: 'DELETE' }),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
path(suffix = '', extra = {}) { return this.sandbox.path(`/containers/executions/${this.id}${suffix}`, extra); }
|
|
238
|
+
get() { return this.sandbox.client.request(this.path()); }
|
|
239
|
+
cancel() { return this.sandbox.client.request(this.path(), { method: 'DELETE' }); }
|
|
240
|
+
signal(signal) {
|
|
241
|
+
if (!['SIGINT', 'SIGTERM', 'SIGKILL'].includes(signal)) throw new MainbrellaError('invalid_execution_signal');
|
|
242
|
+
return this.sandbox.client.request(this.path('/signal'), { method: 'POST', body: { signal } });
|
|
243
|
+
}
|
|
244
|
+
resize(cols, rows) {
|
|
245
|
+
if (![cols, rows].every(value => Number.isInteger(value) && value >= 1 && value <= 1000)) throw new MainbrellaError('invalid_terminal_size');
|
|
246
|
+
return this.sandbox.client.request(this.path('/resize'), { method: 'POST', body: { cols, rows } });
|
|
247
|
+
}
|
|
248
|
+
async wait({ timeoutMs = 15 * 60_000, pollIntervalMs = 1000 } = {}) {
|
|
249
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || !Number.isInteger(pollIntervalMs) || pollIntervalMs < 1) throw new MainbrellaError('invalid_wait_options');
|
|
250
|
+
const deadline = Date.now() + timeoutMs;
|
|
251
|
+
while (Date.now() < deadline) {
|
|
252
|
+
const result = await this.get();
|
|
253
|
+
if (terminal.has(result.status)) return result;
|
|
254
|
+
await new Promise(resolve => setTimeout(resolve, Math.max(0, Math.min(pollIntervalMs, deadline - Date.now()))));
|
|
255
|
+
}
|
|
256
|
+
throw new MainbrellaError('execution_wait_timeout');
|
|
257
|
+
}
|
|
258
|
+
async *events({ cursor = this.cursor, signal } = {}) {
|
|
259
|
+
if (!Number.isSafeInteger(cursor) || cursor < 0) throw new MainbrellaError('invalid_cursor');
|
|
260
|
+
while (!signal?.aborted) {
|
|
261
|
+
const response = await this.sandbox.client.request(this.path('/events', { cursor: String(cursor) }), { stream: true, signal });
|
|
262
|
+
const reader = response.body?.getReader();
|
|
263
|
+
if (!reader) throw new MainbrellaError('invalid_stream_response');
|
|
264
|
+
const decoder = new TextDecoder();
|
|
265
|
+
let buffer = '', completed = false, sawStatus = false;
|
|
266
|
+
try {
|
|
267
|
+
while (true) {
|
|
268
|
+
const { done, value } = await reader.read();
|
|
269
|
+
buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
|
|
270
|
+
let boundary;
|
|
271
|
+
while ((boundary = buffer.indexOf('\n\n')) !== -1) {
|
|
272
|
+
if (boundary > 64 * 1024) throw new Error();
|
|
273
|
+
const frame = buffer.slice(0, boundary); buffer = buffer.slice(boundary + 2);
|
|
274
|
+
const type = frame.split('\n').find(line => line.startsWith('event: '))?.slice(7);
|
|
275
|
+
const data = frame.split('\n').find(line => line.startsWith('data: '))?.slice(6);
|
|
276
|
+
if (!data) continue;
|
|
277
|
+
const item = JSON.parse(data);
|
|
278
|
+
if (type === 'status') {
|
|
279
|
+
sawStatus = true;
|
|
280
|
+
completed = terminal.has(item.status);
|
|
281
|
+
yield { type: 'status', execution: item };
|
|
282
|
+
} else if (['stdout', 'stderr'].includes(type)) {
|
|
283
|
+
if (!Number.isSafeInteger(item.sequence) || typeof item.data !== 'string') throw new Error();
|
|
284
|
+
if (item.sequence > cursor) { this.cursor = cursor = item.sequence; yield item; }
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (buffer.length > 64 * 1024) throw new Error();
|
|
288
|
+
if (done && (buffer.trim() || !sawStatus)) throw new Error();
|
|
289
|
+
if (done || completed) break;
|
|
290
|
+
}
|
|
291
|
+
} catch { throw new MainbrellaError('execution_stream_unavailable', 0, { cursor }); }
|
|
292
|
+
finally { await reader.cancel().catch(() => {}); }
|
|
293
|
+
if (completed) return;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export { terminal as terminalExecutionStates };
|
package/package.json
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mainbrella/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Mainbrella container automation client",
|
|
6
|
+
"license": "GPL-3.0-only",
|
|
7
|
+
"homepage": "https://mainbrella.com/docs/",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/mainbrella/backend.git",
|
|
11
|
+
"directory": "sdk/javascript"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/mainbrella/backend/issues",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./index.d.ts",
|
|
17
|
+
"import": "./index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"index.js",
|
|
22
|
+
"index.d.ts",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"cli.mjs"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "node --test *.test.mjs"
|
|
32
|
+
},
|
|
33
|
+
"bin": {
|
|
34
|
+
"mainbrella": "cli.mjs"
|
|
35
|
+
}
|
|
36
|
+
}
|