@lacrous/omnicloud 0.2.32 → 0.2.33
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/dist/index.cjs +90 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +90 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/server/index.js +1 -1
package/dist/index.cjs
CHANGED
|
@@ -77,6 +77,51 @@ var OmniCloudError = class extends Error {
|
|
|
77
77
|
details;
|
|
78
78
|
};
|
|
79
79
|
var DEFAULT_RETRY = { attempts: 3, baseDelayMs: 300 };
|
|
80
|
+
function randomId() {
|
|
81
|
+
return globalThis.crypto.randomUUID().replace(/-/g, "");
|
|
82
|
+
}
|
|
83
|
+
function multipartHead(boundary, fields, fileName) {
|
|
84
|
+
const parts = fields.map(
|
|
85
|
+
([name, value]) => `--${boundary}\r
|
|
86
|
+
Content-Disposition: form-data; name="${name}"\r
|
|
87
|
+
\r
|
|
88
|
+
${value}\r
|
|
89
|
+
`
|
|
90
|
+
);
|
|
91
|
+
const safeName = fileName.replace(/["\\\r\n]/g, "_");
|
|
92
|
+
parts.push(
|
|
93
|
+
`--${boundary}\r
|
|
94
|
+
Content-Disposition: form-data; name="file"; filename="${safeName}"\r
|
|
95
|
+
Content-Type: application/octet-stream\r
|
|
96
|
+
\r
|
|
97
|
+
`
|
|
98
|
+
);
|
|
99
|
+
return parts.join("");
|
|
100
|
+
}
|
|
101
|
+
function nodeMultipartStream(head, file, tail) {
|
|
102
|
+
const encoder = new TextEncoder();
|
|
103
|
+
const iterator = file[Symbol.asyncIterator]();
|
|
104
|
+
let sentHead = false;
|
|
105
|
+
return new ReadableStream({
|
|
106
|
+
async pull(controller) {
|
|
107
|
+
if (!sentHead) {
|
|
108
|
+
sentHead = true;
|
|
109
|
+
controller.enqueue(encoder.encode(head));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const { done, value } = await iterator.next();
|
|
113
|
+
if (done) {
|
|
114
|
+
controller.enqueue(encoder.encode(tail));
|
|
115
|
+
controller.close();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
controller.enqueue(typeof value === "string" ? encoder.encode(value) : new Uint8Array(value));
|
|
119
|
+
},
|
|
120
|
+
async cancel(reason) {
|
|
121
|
+
await iterator.return?.(reason);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
80
125
|
function sleep(ms, signal) {
|
|
81
126
|
return new Promise((resolve, reject) => {
|
|
82
127
|
const timer = setTimeout(resolve, ms);
|
|
@@ -368,10 +413,13 @@ var FilesApi = class {
|
|
|
368
413
|
async upload(input, options = {}) {
|
|
369
414
|
const attempts = Math.max(1, (options.retry ?? 2) + 1);
|
|
370
415
|
let lastError;
|
|
371
|
-
const
|
|
416
|
+
const source = input.path !== void 0 && this.canStreamFromDisk() ? { kind: "path", path: input.path } : { kind: "blob", blob: await toBlob(input) };
|
|
372
417
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
373
418
|
try {
|
|
374
|
-
|
|
419
|
+
if (source.kind === "path") {
|
|
420
|
+
return await this.uploadPathOnce(source.path, input.name, options);
|
|
421
|
+
}
|
|
422
|
+
return await this.uploadOnce(source.blob, input.name, options);
|
|
375
423
|
} catch (error) {
|
|
376
424
|
lastError = error;
|
|
377
425
|
if (options.signal?.aborted) throw error;
|
|
@@ -381,6 +429,46 @@ var FilesApi = class {
|
|
|
381
429
|
}
|
|
382
430
|
throw lastError;
|
|
383
431
|
}
|
|
432
|
+
canStreamFromDisk() {
|
|
433
|
+
return typeof XMLHttpRequest !== "function" && typeof ReadableStream === "function";
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Node: streams a file from disk as a multipart body. The file is re-opened on
|
|
437
|
+
* every attempt, so a retry sends the same bytes without holding them in memory.
|
|
438
|
+
*/
|
|
439
|
+
async uploadPathOnce(path, name, options) {
|
|
440
|
+
const { createReadStream, statSync } = await import("fs");
|
|
441
|
+
const size = statSync(path).size;
|
|
442
|
+
const boundary = `omnicloud-${randomId()}`;
|
|
443
|
+
const fields = [];
|
|
444
|
+
if (options.folderId) fields.push(["folderId", options.folderId]);
|
|
445
|
+
const head = multipartHead(boundary, fields, name);
|
|
446
|
+
const tail = `\r
|
|
447
|
+
--${boundary}--\r
|
|
448
|
+
`;
|
|
449
|
+
const body = nodeMultipartStream(head, createReadStream(path), tail);
|
|
450
|
+
const response = await this.client.fetchImpl(`${this.client.baseUrl}/api/files`, {
|
|
451
|
+
method: "POST",
|
|
452
|
+
headers: {
|
|
453
|
+
...this.client.defaultHeaders,
|
|
454
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
455
|
+
"Content-Length": String(Buffer.byteLength(head) + size + Buffer.byteLength(tail))
|
|
456
|
+
},
|
|
457
|
+
body,
|
|
458
|
+
credentials: "include",
|
|
459
|
+
duplex: "half"
|
|
460
|
+
});
|
|
461
|
+
const text = await response.text();
|
|
462
|
+
if (!response.ok) {
|
|
463
|
+
const error = safeJsonParse(text)?.error;
|
|
464
|
+
throw new OmniCloudError(
|
|
465
|
+
response.status,
|
|
466
|
+
error?.code ?? "UPLOAD_FAILED",
|
|
467
|
+
error?.message ?? `Upload failed (${response.status})`
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
return JSON.parse(text).file;
|
|
471
|
+
}
|
|
384
472
|
async uploadOnce(blob, name, options) {
|
|
385
473
|
const form = new FormData();
|
|
386
474
|
form.append("file", blob, name);
|