@daytonaio/sdk 0.110.2-alpha.9 → 0.111.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/package.json +2 -3
- package/src/FileSystem.d.ts +16 -4
- package/src/FileSystem.js +35 -458
- package/src/FileSystem.js.map +1 -1
- package/src/Process.js +1 -1
- package/src/Process.js.map +1 -1
- package/src/Sandbox.d.ts +7 -2
- package/src/Sandbox.js +20 -6
- package/src/Sandbox.js.map +1 -1
- package/src/index.d.ts +0 -1
- package/src/index.js +1 -3
- package/src/index.js.map +1 -1
- package/src/utils/Binary.d.ts +38 -0
- package/src/utils/Binary.js +168 -0
- package/src/utils/Binary.js.map +1 -0
- package/src/utils/FileTransfer.d.ts +14 -0
- package/src/utils/FileTransfer.js +216 -0
- package/src/utils/FileTransfer.js.map +1 -0
- package/src/utils/Import.js +6 -1
- package/src/utils/Import.js.map +1 -1
- package/src/utils/Multipart.d.ts +26 -0
- package/src/utils/Multipart.js +113 -0
- package/src/utils/Multipart.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daytonaio/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.111.0",
|
|
4
4
|
"description": "TypeScript SDK for Daytona",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"@aws-sdk/lib-storage": "^3.798.0",
|
|
27
27
|
"@iarna/toml": "^2.2.5",
|
|
28
28
|
"axios": "^1.11.0",
|
|
29
|
-
"buffer": "^6.0.3",
|
|
30
29
|
"busboy": "^1.0.0",
|
|
31
30
|
"dotenv": "^17.0.1",
|
|
32
31
|
"expand-tilde": "^2.0.2",
|
|
@@ -36,7 +35,7 @@
|
|
|
36
35
|
"pathe": "^2.0.3",
|
|
37
36
|
"shell-quote": "^1.8.2",
|
|
38
37
|
"tar": "^6.2.0",
|
|
39
|
-
"@daytonaio/api-client": "0.
|
|
38
|
+
"@daytonaio/api-client": "0.111.0"
|
|
40
39
|
},
|
|
41
40
|
"packageManager": "yarn@4.6.0",
|
|
42
41
|
"type": "commonjs"
|
package/src/FileSystem.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Configuration, FileInfo, Match, ReplaceResult, SearchFilesResponse, ToolboxApi } from '@daytonaio/api-client';
|
|
2
|
-
import { Buffer } from 'buffer';
|
|
3
2
|
/**
|
|
4
3
|
* Parameters for setting file permissions in the Sandbox.
|
|
5
4
|
*
|
|
@@ -44,7 +43,7 @@ export interface FileUpload {
|
|
|
44
43
|
* @property {string} [destination] - Destination path in the local filesystem where the file content will be
|
|
45
44
|
* streamed to. If not provided, the file will be downloaded in the bytes buffer (might cause memory issues if the file is large).
|
|
46
45
|
*/
|
|
47
|
-
interface FileDownloadRequest {
|
|
46
|
+
export interface FileDownloadRequest {
|
|
48
47
|
source: string;
|
|
49
48
|
destination?: string;
|
|
50
49
|
}
|
|
@@ -57,11 +56,25 @@ interface FileDownloadRequest {
|
|
|
57
56
|
* or bytes content (if no destination in the request), undefined if failed or no data received.
|
|
58
57
|
* @property {string | undefined} [error] - Error message if the download failed, undefined if successful.
|
|
59
58
|
*/
|
|
60
|
-
interface FileDownloadResponse {
|
|
59
|
+
export interface FileDownloadResponse {
|
|
61
60
|
source: string;
|
|
62
61
|
result?: Buffer | string;
|
|
63
62
|
error?: string;
|
|
64
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Represents metadata for a file download operation.
|
|
66
|
+
*
|
|
67
|
+
* @interface
|
|
68
|
+
* @property {string | undefined} [destination] - Destination path in the local filesystem where the file content will be streamed to.
|
|
69
|
+
* @property {string | undefined} [error] - Error message if the download failed, undefined if successful.
|
|
70
|
+
* @property {Buffer | string | Uint8Array | undefined} [result] - The download result - file path (if destination provided in the request)
|
|
71
|
+
* or bytes content (if no destination in the request), undefined if failed or no data received.
|
|
72
|
+
*/
|
|
73
|
+
export interface DownloadMetadata {
|
|
74
|
+
destination?: string;
|
|
75
|
+
error?: string;
|
|
76
|
+
result?: Buffer | string | Uint8Array;
|
|
77
|
+
}
|
|
65
78
|
/**
|
|
66
79
|
* Provides file system operations within a Sandbox.
|
|
67
80
|
*
|
|
@@ -311,4 +324,3 @@ export declare class FileSystem {
|
|
|
311
324
|
uploadFiles(files: FileUpload[], timeout?: number): Promise<void>;
|
|
312
325
|
private makeFilePayload;
|
|
313
326
|
}
|
|
314
|
-
export {};
|
package/src/FileSystem.js
CHANGED
|
@@ -7,12 +7,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.FileSystem = void 0;
|
|
8
8
|
const tslib_1 = require("tslib");
|
|
9
9
|
const pathe = tslib_1.__importStar(require("pathe"));
|
|
10
|
-
const
|
|
10
|
+
const DaytonaError_1 = require("./errors/DaytonaError");
|
|
11
|
+
const FileTransfer_1 = require("./utils/FileTransfer");
|
|
11
12
|
const Import_1 = require("./utils/Import");
|
|
12
13
|
const Runtime_1 = require("./utils/Runtime");
|
|
13
|
-
const busboy_1 = tslib_1.__importDefault(require("busboy"));
|
|
14
|
-
const DaytonaError_1 = require("./errors/DaytonaError");
|
|
15
|
-
const buffer_1 = require("buffer");
|
|
16
14
|
/**
|
|
17
15
|
* Provides file system operations within a Sandbox.
|
|
18
16
|
*
|
|
@@ -102,247 +100,37 @@ class FileSystem {
|
|
|
102
100
|
async downloadFiles(files, timeoutSec = 30 * 60) {
|
|
103
101
|
if (files.length === 0)
|
|
104
102
|
return [];
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const srcFileMetaMap = new Map();
|
|
109
|
-
const fileTasks = [];
|
|
110
|
-
// Validate & prepare destinations
|
|
103
|
+
const isNonStreamingRuntime = Runtime_1.RUNTIME === Runtime_1.Runtime.BROWSER || Runtime_1.RUNTIME === Runtime_1.Runtime.SERVERLESS;
|
|
104
|
+
// Prepare destinations and metadata
|
|
105
|
+
const metadataMap = new Map();
|
|
111
106
|
for (const f of files) {
|
|
112
|
-
|
|
107
|
+
metadataMap.set(f.source, { destination: f.destination });
|
|
113
108
|
if (f.destination) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
await fs.promises.mkdir(pathe.dirname(f.destination), { recursive: true });
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
throw new DaytonaError_1.DaytonaError('Downloading files to local files is not supported in this runtime.');
|
|
120
|
-
}
|
|
109
|
+
const fs = await (0, Import_1.dynamicImport)('fs', 'Downloading files to local files is not supported: ');
|
|
110
|
+
await fs.promises.mkdir(pathe.dirname(f.destination), { recursive: true });
|
|
121
111
|
}
|
|
122
112
|
}
|
|
123
|
-
const response = await this.toolboxApi.downloadFiles(this.sandboxId, { paths:
|
|
124
|
-
responseType: 'stream',
|
|
113
|
+
const response = await this.toolboxApi.downloadFiles(this.sandboxId, { paths: files.map((f) => f.source) }, undefined, {
|
|
114
|
+
responseType: isNonStreamingRuntime ? 'arraybuffer' : 'stream',
|
|
125
115
|
timeout: timeoutSec * 1000,
|
|
126
116
|
});
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (responseData.body && typeof responseData.body.getReader === 'function') {
|
|
132
|
-
responseData = responseData.body; // WHATWG ReadableStream
|
|
133
|
-
}
|
|
134
|
-
else if (responseData.stream) {
|
|
135
|
-
responseData = responseData.stream; // Some adapters use .stream
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
// -------------------- NODE PATH (busboy) --------------------
|
|
139
|
-
if (isNodeLike) {
|
|
140
|
-
await new Promise((resolve, reject) => {
|
|
141
|
-
const bb = (0, busboy_1.default)({
|
|
142
|
-
headers: response.headers,
|
|
143
|
-
preservePath: true,
|
|
144
|
-
});
|
|
145
|
-
// busboy emits: ('file', fieldName, fileStream, fileInfo)
|
|
146
|
-
bb.on('file', (fieldName, fileStream, fileInfo) => {
|
|
147
|
-
const source = fileInfo?.filename;
|
|
148
|
-
if (!source) {
|
|
149
|
-
safeAbort(responseData);
|
|
150
|
-
reject(new DaytonaError_1.DaytonaError(`Received unexpected file "${fileInfo?.filename}".`));
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
const meta = srcFileMetaMap.get(source);
|
|
154
|
-
if (!meta) {
|
|
155
|
-
safeAbort(responseData);
|
|
156
|
-
reject(new DaytonaError_1.DaytonaError(`Target metadata missing for valid source: ${source}`));
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
if (fieldName === 'error') {
|
|
160
|
-
let buf = buffer_1.Buffer.alloc(0);
|
|
161
|
-
fileStream.on('data', (chunk) => {
|
|
162
|
-
buf = buffer_1.Buffer.concat([buf, chunk]);
|
|
163
|
-
});
|
|
164
|
-
fileStream.on('end', () => {
|
|
165
|
-
meta.error = buf.toString('utf-8').trim();
|
|
166
|
-
});
|
|
167
|
-
fileStream.on('error', (err) => {
|
|
168
|
-
meta.error = `Stream error: ${err.message}`;
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
else if (fieldName === 'file') {
|
|
172
|
-
if (meta.dst) {
|
|
173
|
-
fileTasks.push(new Promise((resolveInner) => {
|
|
174
|
-
(0, Import_1.dynamicImport)('fs', 'Downloading files to local files is not supported: ').then((fs) => {
|
|
175
|
-
const writeStream = fs.createWriteStream(meta.dst, { autoClose: true });
|
|
176
|
-
fileStream.pipe(writeStream);
|
|
177
|
-
writeStream.on('finish', () => {
|
|
178
|
-
meta.result = meta.dst;
|
|
179
|
-
resolveInner();
|
|
180
|
-
});
|
|
181
|
-
writeStream.on('error', (err) => {
|
|
182
|
-
meta.error = `Write stream failed: ${err.message}`;
|
|
183
|
-
resolveInner();
|
|
184
|
-
});
|
|
185
|
-
fileStream.on('error', (err) => {
|
|
186
|
-
meta.error = `Read stream failed: ${err.message}`;
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
}));
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
const chunks = [];
|
|
193
|
-
fileStream.on('data', (chunk) => {
|
|
194
|
-
// Ensure chunk is Buffer (it is in Node)
|
|
195
|
-
chunks.push(buffer_1.Buffer.isBuffer(chunk) ? chunk : buffer_1.Buffer.from(chunk));
|
|
196
|
-
});
|
|
197
|
-
fileStream.on('end', () => {
|
|
198
|
-
meta.result = buffer_1.Buffer.concat(chunks);
|
|
199
|
-
});
|
|
200
|
-
fileStream.on('error', (err) => {
|
|
201
|
-
meta.error = `Read failed: ${err.message}`;
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
// Unknown fieldname; drain
|
|
207
|
-
fileStream.resume();
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
bb.on('error', (err) => {
|
|
211
|
-
safeAbort(responseData);
|
|
212
|
-
reject(err);
|
|
213
|
-
});
|
|
214
|
-
bb.on('finish', resolve);
|
|
215
|
-
// Feed body into busboy, coercing to Buffer as needed
|
|
216
|
-
if (typeof responseData?.pipe === 'function') {
|
|
217
|
-
responseData.pipe(bb);
|
|
218
|
-
}
|
|
219
|
-
else if (typeof responseData === 'string' ||
|
|
220
|
-
responseData instanceof ArrayBuffer ||
|
|
221
|
-
ArrayBuffer.isView(responseData)) {
|
|
222
|
-
try {
|
|
223
|
-
const u8 = toUint8Array(responseData);
|
|
224
|
-
bb.write(buffer_1.Buffer.from(u8));
|
|
225
|
-
bb.end();
|
|
226
|
-
}
|
|
227
|
-
catch (err) {
|
|
228
|
-
bb.destroy(err);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
else if (typeof responseData?.getReader === 'function') {
|
|
232
|
-
;
|
|
233
|
-
(async () => {
|
|
234
|
-
try {
|
|
235
|
-
const reader = responseData.getReader();
|
|
236
|
-
while (true) {
|
|
237
|
-
const { done, value } = await reader.read();
|
|
238
|
-
if (done)
|
|
239
|
-
break;
|
|
240
|
-
bb.write(buffer_1.Buffer.from(value));
|
|
241
|
-
}
|
|
242
|
-
bb.end();
|
|
243
|
-
}
|
|
244
|
-
catch (err) {
|
|
245
|
-
bb.destroy(err);
|
|
246
|
-
}
|
|
247
|
-
})();
|
|
248
|
-
}
|
|
249
|
-
else if (responseData?.[Symbol.asyncIterator]) {
|
|
250
|
-
;
|
|
251
|
-
(async () => {
|
|
252
|
-
try {
|
|
253
|
-
for await (const chunk of responseData) {
|
|
254
|
-
const buf = buffer_1.Buffer.isBuffer(chunk) ? chunk : buffer_1.Buffer.from(toUint8Array(chunk));
|
|
255
|
-
bb.write(buf);
|
|
256
|
-
}
|
|
257
|
-
bb.end();
|
|
258
|
-
}
|
|
259
|
-
catch (err) {
|
|
260
|
-
bb.destroy(err);
|
|
261
|
-
}
|
|
262
|
-
})();
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
const typeInfo = responseData
|
|
266
|
-
? `type: ${typeof responseData}, constructor: ${responseData.constructor?.name}, keys: ${Object.keys(responseData)
|
|
267
|
-
.slice(0, 10)
|
|
268
|
-
.join(', ')}`
|
|
269
|
-
: 'null or undefined';
|
|
270
|
-
reject(new DaytonaError_1.DaytonaError(`Unsupported stream type for response data. ${typeInfo}`));
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
await Promise.all(fileTasks);
|
|
274
|
-
return finalizeResults(files, srcFileMetaMap);
|
|
275
|
-
}
|
|
276
|
-
// -------------------- BROWSER / EDGE PATH (no busboy) --------------------
|
|
277
|
-
// Collect all bytes first
|
|
278
|
-
const bodyBytes = await collectBodyBytes(responseData);
|
|
279
|
-
// Path A: multipart/form-data → let platform parse it
|
|
280
|
-
if (/^multipart\/form-data/i.test(contentType) && typeof Response !== 'undefined') {
|
|
281
|
-
try {
|
|
282
|
-
// Make a fresh Uint8Array so its .buffer is a concrete ArrayBuffer (not ArrayBufferLike/SAB)
|
|
283
|
-
const bytesCopy = new Uint8Array(bodyBytes.byteLength);
|
|
284
|
-
bytesCopy.set(bodyBytes);
|
|
285
|
-
// Use the ArrayBuffer directly to satisfy BodyInit/BlobPart typing
|
|
286
|
-
const blob = new Blob([bytesCopy.buffer], { type: contentType });
|
|
287
|
-
const res = new Response(blob);
|
|
288
|
-
const fd = await res.formData();
|
|
289
|
-
// No fd.entries() (avoids dom.iterable). Use forEach + Promise bucket.
|
|
290
|
-
const ops = [];
|
|
291
|
-
fd.forEach((value, field) => {
|
|
292
|
-
ops.push((async () => {
|
|
293
|
-
if (isRuntimeFile(value)) {
|
|
294
|
-
const file = value;
|
|
295
|
-
const source = file.name;
|
|
296
|
-
const meta = srcFileMetaMap.get(source);
|
|
297
|
-
if (!meta)
|
|
298
|
-
return;
|
|
299
|
-
if (field === 'error') {
|
|
300
|
-
const text = await file.text();
|
|
301
|
-
meta.error = String(text).trim();
|
|
302
|
-
}
|
|
303
|
-
else if (field === 'file') {
|
|
304
|
-
const ab = await file.arrayBuffer();
|
|
305
|
-
meta.result = toBufferIfAvailable(new Uint8Array(ab));
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
// Non-file field (rare)
|
|
310
|
-
if (field === 'error') {
|
|
311
|
-
for (const meta of srcFileMetaMap.values()) {
|
|
312
|
-
meta.error = String(value);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
})());
|
|
317
|
-
});
|
|
318
|
-
await Promise.all(ops);
|
|
319
|
-
return finalizeResults(files, srcFileMetaMap);
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
// Fall through to manual multipart parsing
|
|
323
|
-
}
|
|
117
|
+
const stream = (0, FileTransfer_1.normalizeResponseStream)(response.data);
|
|
118
|
+
// Node.js path: use busboy for efficient streaming
|
|
119
|
+
if (isNonStreamingRuntime) {
|
|
120
|
+
await (0, FileTransfer_1.processDownloadFilesResponseWithBuffered)(stream, response.headers, metadataMap);
|
|
324
121
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (!boundary) {
|
|
328
|
-
throw new DaytonaError_1.DaytonaError(`Missing multipart boundary in Content-Type: "${contentType}"`);
|
|
329
|
-
}
|
|
330
|
-
const parts = parseMultipart(bodyBytes, boundary);
|
|
331
|
-
for (const p of parts) {
|
|
332
|
-
const source = p.filename;
|
|
333
|
-
if (!source)
|
|
334
|
-
continue;
|
|
335
|
-
const meta = srcFileMetaMap.get(source);
|
|
336
|
-
if (!meta)
|
|
337
|
-
continue;
|
|
338
|
-
if (p.name === 'error') {
|
|
339
|
-
meta.error = utf8Decode(p.data).trim();
|
|
340
|
-
}
|
|
341
|
-
else if (p.name === 'file') {
|
|
342
|
-
meta.result = toBufferIfAvailable(p.data);
|
|
343
|
-
}
|
|
122
|
+
else {
|
|
123
|
+
await (0, FileTransfer_1.processDownloadFilesResponseWithBusboy)(stream, response.headers, metadataMap);
|
|
344
124
|
}
|
|
345
|
-
return
|
|
125
|
+
return files.map((f) => {
|
|
126
|
+
const metadata = metadataMap.get(f.source);
|
|
127
|
+
const error = metadata?.error || (!metadata?.result ? 'No data received for this file' : undefined);
|
|
128
|
+
return {
|
|
129
|
+
source: f.source,
|
|
130
|
+
result: error ? undefined : metadata.result,
|
|
131
|
+
error,
|
|
132
|
+
};
|
|
133
|
+
});
|
|
346
134
|
}
|
|
347
135
|
/**
|
|
348
136
|
* Searches for text patterns within files in the Sandbox.
|
|
@@ -500,18 +288,18 @@ class FileSystem {
|
|
|
500
288
|
* await fs.uploadFiles(files);
|
|
501
289
|
*/
|
|
502
290
|
async uploadFiles(files, timeout = 30 * 60) {
|
|
291
|
+
const isNonStreamingRuntime = Runtime_1.RUNTIME === Runtime_1.Runtime.DENO || Runtime_1.RUNTIME === Runtime_1.Runtime.BROWSER || Runtime_1.RUNTIME === Runtime_1.Runtime.SERVERLESS;
|
|
503
292
|
// Use native FormData in Deno
|
|
504
|
-
const FormDataClass =
|
|
505
|
-
?
|
|
293
|
+
const FormDataClass = isNonStreamingRuntime
|
|
294
|
+
? FormData
|
|
506
295
|
: (await (0, Import_1.dynamicImport)('form-data', 'Uploading files is not supported: '));
|
|
507
296
|
const form = new FormDataClass();
|
|
508
297
|
for (const [i, { source, destination }] of files.entries()) {
|
|
509
298
|
form.append(`files[${i}].path`, destination);
|
|
510
299
|
const payload = await this.makeFilePayload(source);
|
|
511
|
-
// the third arg sets filename in Content-Disposition
|
|
512
300
|
form.append(`files[${i}].file`, payload, destination);
|
|
513
301
|
}
|
|
514
|
-
if (
|
|
302
|
+
if (isNonStreamingRuntime) {
|
|
515
303
|
const url = `${this.clientConfig.basePath}/toolbox/${this.sandboxId}/toolbox/files/bulk-upload`;
|
|
516
304
|
await fetch(url, {
|
|
517
305
|
method: 'POST',
|
|
@@ -529,231 +317,20 @@ class FileSystem {
|
|
|
529
317
|
}
|
|
530
318
|
}
|
|
531
319
|
async makeFilePayload(source) {
|
|
532
|
-
//
|
|
320
|
+
// String = file path
|
|
533
321
|
if (typeof source === 'string') {
|
|
534
322
|
const fs = await (0, Import_1.dynamicImport)('fs', 'Uploading file from local file system is not supported: ');
|
|
535
323
|
return fs.createReadStream(source);
|
|
536
324
|
}
|
|
537
|
-
//
|
|
538
|
-
if (Runtime_1.RUNTIME === Runtime_1.Runtime.BROWSER || Runtime_1.RUNTIME === Runtime_1.Runtime.SERVERLESS) {
|
|
539
|
-
|
|
325
|
+
// Blob
|
|
326
|
+
if (Runtime_1.RUNTIME === Runtime_1.Runtime.BROWSER || Runtime_1.RUNTIME === Runtime_1.Runtime.SERVERLESS || Runtime_1.RUNTIME === Runtime_1.Runtime.DENO) {
|
|
327
|
+
// Use .slice() to ensure we have a concrete ArrayBuffer, not ArrayBufferLike
|
|
328
|
+
return new Blob([source.slice()], { type: 'application/octet-stream' });
|
|
540
329
|
}
|
|
541
|
-
//
|
|
330
|
+
// Readable stream
|
|
542
331
|
const stream = await (0, Import_1.dynamicImport)('stream', 'Uploading file is not supported: ');
|
|
543
332
|
return stream.Readable.from(source);
|
|
544
333
|
}
|
|
545
334
|
}
|
|
546
335
|
exports.FileSystem = FileSystem;
|
|
547
|
-
/* ==================== Helpers (keep in same file) ==================== */
|
|
548
|
-
function getHeader(headers, key) {
|
|
549
|
-
if (!headers)
|
|
550
|
-
return undefined;
|
|
551
|
-
// Axios can give lowercase keys; some envs may differ
|
|
552
|
-
const k = Object.keys(headers).find((h) => h.toLowerCase() === key.toLowerCase());
|
|
553
|
-
const val = k ? headers[k] : undefined;
|
|
554
|
-
// Flatten array-ish values
|
|
555
|
-
return Array.isArray(val) ? val[0] : val;
|
|
556
|
-
}
|
|
557
|
-
function safeAbort(streamish) {
|
|
558
|
-
if (streamish && typeof streamish.destroy === 'function')
|
|
559
|
-
streamish.destroy();
|
|
560
|
-
else if (streamish && typeof streamish.cancel === 'function')
|
|
561
|
-
streamish.cancel();
|
|
562
|
-
}
|
|
563
|
-
function toUint8Array(data) {
|
|
564
|
-
if (typeof data === 'string') {
|
|
565
|
-
return new TextEncoder().encode(data);
|
|
566
|
-
}
|
|
567
|
-
if (data instanceof ArrayBuffer)
|
|
568
|
-
return new Uint8Array(data);
|
|
569
|
-
if (ArrayBuffer.isView(data)) {
|
|
570
|
-
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
571
|
-
}
|
|
572
|
-
throw new DaytonaError_1.DaytonaError('Unsupported data type for byte conversion.');
|
|
573
|
-
}
|
|
574
|
-
async function collectBodyBytes(body) {
|
|
575
|
-
if (!body)
|
|
576
|
-
return new Uint8Array(0);
|
|
577
|
-
if (typeof body.getReader === 'function') {
|
|
578
|
-
const reader = body.getReader();
|
|
579
|
-
const chunks = [];
|
|
580
|
-
let total = 0;
|
|
581
|
-
while (true) {
|
|
582
|
-
const { done, value } = await reader.read();
|
|
583
|
-
if (done)
|
|
584
|
-
break;
|
|
585
|
-
if (value && value.byteLength) {
|
|
586
|
-
chunks.push(value);
|
|
587
|
-
total += value.byteLength;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
return concatU8(chunks, total);
|
|
591
|
-
}
|
|
592
|
-
if (body?.[Symbol.asyncIterator]) {
|
|
593
|
-
const chunks = [];
|
|
594
|
-
let total = 0;
|
|
595
|
-
for await (const chunk of body) {
|
|
596
|
-
const u8 = toUint8Array(chunk);
|
|
597
|
-
chunks.push(u8);
|
|
598
|
-
total += u8.byteLength;
|
|
599
|
-
}
|
|
600
|
-
return concatU8(chunks, total);
|
|
601
|
-
}
|
|
602
|
-
if (typeof body === 'string' || body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
|
|
603
|
-
return toUint8Array(body);
|
|
604
|
-
}
|
|
605
|
-
if (typeof Blob !== 'undefined' && body instanceof Blob) {
|
|
606
|
-
const ab = await body.arrayBuffer();
|
|
607
|
-
return new Uint8Array(ab);
|
|
608
|
-
}
|
|
609
|
-
if (typeof Response !== 'undefined' && body instanceof Response) {
|
|
610
|
-
const ab = await body.arrayBuffer();
|
|
611
|
-
return new Uint8Array(ab);
|
|
612
|
-
}
|
|
613
|
-
throw new DaytonaError_1.DaytonaError('Unsupported response body for browser parsing.');
|
|
614
|
-
}
|
|
615
|
-
function concatU8(parts, total) {
|
|
616
|
-
const size = total ?? parts.reduce((s, p) => s + p.byteLength, 0);
|
|
617
|
-
const out = new Uint8Array(size);
|
|
618
|
-
let off = 0;
|
|
619
|
-
for (const p of parts) {
|
|
620
|
-
out.set(p, off);
|
|
621
|
-
off += p.byteLength;
|
|
622
|
-
}
|
|
623
|
-
return out;
|
|
624
|
-
}
|
|
625
|
-
function getMultipartBoundary(ct) {
|
|
626
|
-
const m = /boundary="?([^";]+)"?/i.exec(ct || '');
|
|
627
|
-
return m ? m[1] : null;
|
|
628
|
-
}
|
|
629
|
-
/** Minimal multipart parser (binary-safe), supports CRLF line breaks. */
|
|
630
|
-
function parseMultipart(body, boundary) {
|
|
631
|
-
const enc = new TextEncoder();
|
|
632
|
-
const dashBoundary = enc.encode(`--${boundary}`);
|
|
633
|
-
const crlf = enc.encode('\r\n');
|
|
634
|
-
const boundaryLine = concatU8([dashBoundary, crlf]);
|
|
635
|
-
const starts = findAll(body, dashBoundary);
|
|
636
|
-
if (starts.length === 0)
|
|
637
|
-
return [];
|
|
638
|
-
const parts = [];
|
|
639
|
-
for (let i = 0; i < starts.length; i++) {
|
|
640
|
-
const start = starts[i];
|
|
641
|
-
// Part header starts after "--boundary\r\n"
|
|
642
|
-
const headerStart = indexAfter(body, start, boundaryLine);
|
|
643
|
-
if (headerStart < 0)
|
|
644
|
-
continue;
|
|
645
|
-
// Part ends before next "--boundary" (trim trailing CRLF)
|
|
646
|
-
const nextStart = starts[i + 1] ?? body.length;
|
|
647
|
-
let partEnd = nextStart - 2;
|
|
648
|
-
if (partEnd < headerStart)
|
|
649
|
-
partEnd = headerStart;
|
|
650
|
-
// Split headers/body by \r\n\r\n
|
|
651
|
-
const sep = findSequence(body, headerStart, partEnd, enc.encode('\r\n\r\n'));
|
|
652
|
-
if (sep < 0)
|
|
653
|
-
continue;
|
|
654
|
-
const headersBytes = body.subarray(headerStart, sep);
|
|
655
|
-
const dataStart = sep + 4;
|
|
656
|
-
const data = body.subarray(dataStart, partEnd);
|
|
657
|
-
const headersText = new TextDecoder('utf-8').decode(headersBytes);
|
|
658
|
-
const headers = {};
|
|
659
|
-
for (const line of headersText.split(/\r\n/)) {
|
|
660
|
-
const idx = line.indexOf(':');
|
|
661
|
-
if (idx > -1) {
|
|
662
|
-
const k = line.slice(0, idx).trim().toLowerCase();
|
|
663
|
-
const v = line.slice(idx + 1).trim();
|
|
664
|
-
headers[k] = v;
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
const cd = headers['content-disposition'] || '';
|
|
668
|
-
const name = getDispositionParam(cd, 'name');
|
|
669
|
-
const filename = getDispositionParam(cd, 'filename');
|
|
670
|
-
parts.push({ name, filename, headers, data });
|
|
671
|
-
}
|
|
672
|
-
return parts;
|
|
673
|
-
}
|
|
674
|
-
function findAll(hay, needle) {
|
|
675
|
-
const res = [];
|
|
676
|
-
let i = 0;
|
|
677
|
-
while (i <= hay.length - needle.length) {
|
|
678
|
-
let ok = true;
|
|
679
|
-
for (let j = 0; j < needle.length; j++) {
|
|
680
|
-
if (hay[i + j] !== needle[j]) {
|
|
681
|
-
ok = false;
|
|
682
|
-
break;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
if (ok) {
|
|
686
|
-
res.push(i);
|
|
687
|
-
i += needle.length;
|
|
688
|
-
}
|
|
689
|
-
else {
|
|
690
|
-
i++;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
return res;
|
|
694
|
-
}
|
|
695
|
-
function findSequence(hay, start, end, needle) {
|
|
696
|
-
let i = start;
|
|
697
|
-
while (i <= end - needle.length) {
|
|
698
|
-
let ok = true;
|
|
699
|
-
for (let j = 0; j < needle.length; j++) {
|
|
700
|
-
if (hay[i + j] !== needle[j]) {
|
|
701
|
-
ok = false;
|
|
702
|
-
break;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
if (ok)
|
|
706
|
-
return i;
|
|
707
|
-
i++;
|
|
708
|
-
}
|
|
709
|
-
return -1;
|
|
710
|
-
}
|
|
711
|
-
function indexAfter(hay, start, seq) {
|
|
712
|
-
for (let j = 0; j < seq.length; j++) {
|
|
713
|
-
if (hay[start + j] !== seq[j])
|
|
714
|
-
return -1;
|
|
715
|
-
}
|
|
716
|
-
return start + seq.length;
|
|
717
|
-
}
|
|
718
|
-
function getDispositionParam(cd, key) {
|
|
719
|
-
const re = new RegExp(`${key}\\*?=("(?:[^"]+)"|[^;]+)`, 'i');
|
|
720
|
-
const m = re.exec(cd);
|
|
721
|
-
if (!m)
|
|
722
|
-
return undefined;
|
|
723
|
-
let val = m[1];
|
|
724
|
-
if (val.startsWith('"') && val.endsWith('"'))
|
|
725
|
-
val = val.slice(1, -1);
|
|
726
|
-
return val;
|
|
727
|
-
}
|
|
728
|
-
function utf8Decode(u8) {
|
|
729
|
-
return new TextDecoder('utf-8').decode(u8);
|
|
730
|
-
}
|
|
731
|
-
/** Return Buffer in Node / with polyfill, else keep Uint8Array. */
|
|
732
|
-
function toBufferIfAvailable(u8) {
|
|
733
|
-
const B = globalThis.Buffer;
|
|
734
|
-
return B && typeof B.from === 'function' ? B.from(u8) : u8;
|
|
735
|
-
}
|
|
736
|
-
function finalizeResults(files, metaMap) {
|
|
737
|
-
const results = [];
|
|
738
|
-
for (const f of files) {
|
|
739
|
-
const meta = metaMap.get(f.source);
|
|
740
|
-
let err = meta?.error;
|
|
741
|
-
if (!err && !meta?.result)
|
|
742
|
-
err = 'No data received for this file';
|
|
743
|
-
let res;
|
|
744
|
-
if (!err && meta) {
|
|
745
|
-
// In browsers this may be Uint8Array; cast so public type stays Buffer|string
|
|
746
|
-
res = meta.result;
|
|
747
|
-
}
|
|
748
|
-
else if (!meta) {
|
|
749
|
-
err = 'No writer metadata found';
|
|
750
|
-
}
|
|
751
|
-
results.push({ source: f.source, result: res, error: err });
|
|
752
|
-
}
|
|
753
|
-
return results;
|
|
754
|
-
}
|
|
755
|
-
function isRuntimeFile(v) {
|
|
756
|
-
const F = globalThis.File;
|
|
757
|
-
return typeof F !== 'undefined' && v instanceof F;
|
|
758
|
-
}
|
|
759
336
|
//# sourceMappingURL=FileSystem.js.map
|