@knime/product-features 2.2.2 → 2.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 (54) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/core-CVWznjtP.js +739 -0
  3. package/dist/core-CVWznjtP.js.map +1 -0
  4. package/dist/dataTransfer/core.js +2 -0
  5. package/dist/dataTransfer.css +12 -12
  6. package/dist/dataTransfer.js +420 -767
  7. package/dist/dataTransfer.js.map +1 -1
  8. package/dist/src/dataTransfer/core.d.ts +2 -0
  9. package/dist/src/dataTransfer/core.d.ts.map +1 -0
  10. package/dist/src/dataTransfer/index.d.ts +1 -2
  11. package/dist/src/dataTransfer/index.d.ts.map +1 -1
  12. package/dist/src/dataTransfer/upload/{ui/UploadProgressPanel.vue.d.ts → UploadProgressPanel.vue.d.ts} +2 -0
  13. package/dist/src/dataTransfer/upload/UploadProgressPanel.vue.d.ts.map +1 -0
  14. package/dist/src/dataTransfer/upload/{ui/UploadProgressPanelItem.vue.d.ts → UploadProgressPanelItem.vue.d.ts} +2 -1
  15. package/dist/src/dataTransfer/upload/UploadProgressPanelItem.vue.d.ts.map +1 -0
  16. package/dist/src/dataTransfer/upload/core/index.d.ts +3 -0
  17. package/dist/src/dataTransfer/upload/core/index.d.ts.map +1 -0
  18. package/dist/src/dataTransfer/upload/core/uploadClient/createUploadStatusPoller.d.ts +26 -0
  19. package/dist/src/dataTransfer/upload/core/uploadClient/createUploadStatusPoller.d.ts.map +1 -0
  20. package/dist/src/dataTransfer/upload/core/uploadClient/index.d.ts +3 -0
  21. package/dist/src/dataTransfer/upload/core/uploadClient/index.d.ts.map +1 -0
  22. package/dist/src/dataTransfer/upload/core/uploadClient/uploadClient.d.ts +59 -0
  23. package/dist/src/dataTransfer/upload/core/uploadClient/uploadClient.d.ts.map +1 -0
  24. package/dist/src/dataTransfer/upload/{manager/index.d.ts → core/uploadManager/chunkedUploadManager.d.ts} +21 -60
  25. package/dist/src/dataTransfer/upload/core/uploadManager/chunkedUploadManager.d.ts.map +1 -0
  26. package/dist/src/dataTransfer/upload/core/uploadManager/config.d.ts +12 -0
  27. package/dist/src/dataTransfer/upload/core/uploadManager/config.d.ts.map +1 -0
  28. package/dist/src/dataTransfer/upload/core/uploadManager/index.d.ts +5 -0
  29. package/dist/src/dataTransfer/upload/core/uploadManager/index.d.ts.map +1 -0
  30. package/dist/src/dataTransfer/upload/core/uploadManager/state.d.ts +30 -0
  31. package/dist/src/dataTransfer/upload/core/uploadManager/state.d.ts.map +1 -0
  32. package/dist/src/dataTransfer/upload/core/uploadManager/streamingUploadManager.d.ts +52 -0
  33. package/dist/src/dataTransfer/upload/core/uploadManager/streamingUploadManager.d.ts.map +1 -0
  34. package/dist/src/dataTransfer/upload/core/uploadManager/types.d.ts +46 -0
  35. package/dist/src/dataTransfer/upload/core/uploadManager/types.d.ts.map +1 -0
  36. package/dist/src/dataTransfer/upload/core/uploadManager/uploadPartBytes.d.ts +17 -0
  37. package/dist/src/dataTransfer/upload/core/uploadManager/uploadPartBytes.d.ts.map +1 -0
  38. package/dist/src/dataTransfer/upload/index.d.ts +6 -0
  39. package/dist/src/dataTransfer/upload/index.d.ts.map +1 -0
  40. package/dist/src/dataTransfer/upload/types.d.ts +80 -0
  41. package/dist/src/dataTransfer/upload/types.d.ts.map +1 -0
  42. package/dist/src/dataTransfer/upload/useFileUpload.d.ts +5 -13
  43. package/dist/src/dataTransfer/upload/useFileUpload.d.ts.map +1 -1
  44. package/dist/src/dataTransfer/upload/{ui/useUploadManager.d.ts → useUploadManager.d.ts} +17 -5
  45. package/dist/src/dataTransfer/upload/useUploadManager.d.ts.map +1 -0
  46. package/package.json +8 -4
  47. package/dist/src/dataTransfer/upload/manager/index.d.ts.map +0 -1
  48. package/dist/src/dataTransfer/upload/ui/UploadProgressPanel.vue.d.ts.map +0 -1
  49. package/dist/src/dataTransfer/upload/ui/UploadProgressPanelItem.vue.d.ts.map +0 -1
  50. package/dist/src/dataTransfer/upload/ui/index.d.ts +0 -5
  51. package/dist/src/dataTransfer/upload/ui/index.d.ts.map +0 -1
  52. package/dist/src/dataTransfer/upload/ui/types.d.ts +0 -60
  53. package/dist/src/dataTransfer/upload/ui/types.d.ts.map +0 -1
  54. package/dist/src/dataTransfer/upload/ui/useUploadManager.d.ts.map +0 -1
@@ -0,0 +1,739 @@
1
+ import { c as promise_exports, l as sleep, o as getFileMimeType } from "./dist-DvDWkkNg.js";
2
+ import { ofetch } from "ofetch";
3
+ //#region ../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
4
+ /**
5
+ * Checks if a given value is a plain object.
6
+ *
7
+ * @param value - The value to check.
8
+ * @returns True if the value is a plain object, otherwise false.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * // ✅👇 True
13
+ *
14
+ * isPlainObject({ }); // ✅
15
+ * isPlainObject({ key: 'value' }); // ✅
16
+ * isPlainObject({ key: new Date() }); // ✅
17
+ * isPlainObject(new Object()); // ✅
18
+ * isPlainObject(Object.create(null)); // ✅
19
+ * isPlainObject({ nested: { key: true} }); // ✅
20
+ * isPlainObject(new Proxy({}, {})); // ✅
21
+ * isPlainObject({ [Symbol('tag')]: 'A' }); // ✅
22
+ *
23
+ * // ✅👇 (cross-realms, node context, workers, ...)
24
+ * const runInNewContext = await import('node:vm').then(
25
+ * (mod) => mod.runInNewContext
26
+ * );
27
+ * isPlainObject(runInNewContext('({})')); // ✅
28
+ *
29
+ * // ❌👇 False
30
+ *
31
+ * class Test { };
32
+ * isPlainObject(new Test()) // ❌
33
+ * isPlainObject(10); // ❌
34
+ * isPlainObject(null); // ❌
35
+ * isPlainObject('hello'); // ❌
36
+ * isPlainObject([]); // ❌
37
+ * isPlainObject(new Date()); // ❌
38
+ * isPlainObject(new Uint8Array([1])); // ❌
39
+ * isPlainObject(Buffer.from('ABC')); // ❌
40
+ * isPlainObject(Promise.resolve({})); // ❌
41
+ * isPlainObject(Object.create({})); // ❌
42
+ * isPlainObject(new (class Cls {})); // ❌
43
+ * isPlainObject(globalThis); // ❌,
44
+ * ```
45
+ */
46
+ function isPlainObject(value) {
47
+ if (!value || typeof value !== "object") return false;
48
+ const proto = Object.getPrototypeOf(value);
49
+ if (!(proto === null || proto === Object.prototype || Object.getPrototypeOf(proto) === null)) return false;
50
+ return Object.prototype.toString.call(value) === "[object Object]";
51
+ }
52
+ //#endregion
53
+ //#region ../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
54
+ /**
55
+ * Checks if a property key is unsafe to modify directly.
56
+ *
57
+ * This function is used in functions like `merge` to prevent prototype pollution attacks
58
+ * by identifying property keys that could modify the object's prototype chain or constructor.
59
+ *
60
+ * @param key - The property key to check
61
+ * @returns `true` if the property is unsafe to modify directly, `false` otherwise
62
+ * @internal
63
+ */
64
+ function isUnsafeProperty(key) {
65
+ return key === "__proto__";
66
+ }
67
+ //#endregion
68
+ //#region ../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/object/merge.mjs
69
+ /**
70
+ * Merges the properties of the source object into the target object.
71
+ *
72
+ * This function performs a deep merge, meaning nested objects and arrays are merged recursively.
73
+ * If a property in the source object is an array or an object and the corresponding property in the target object is also an array or object, they will be merged.
74
+ * If a property in the source object is undefined, it will not overwrite a defined property in the target object.
75
+ *
76
+ * Note that this function mutates the target object.
77
+ *
78
+ * @param target - The target object into which the source object properties will be merged. This object is modified in place.
79
+ * @param source - The source object whose properties will be merged into the target object.
80
+ * @returns The updated target object with properties from the source object merged in.
81
+ *
82
+ * @template T - Type of the target object.
83
+ * @template S - Type of the source object.
84
+ *
85
+ * @example
86
+ * const target = { a: 1, b: { x: 1, y: 2 } };
87
+ * const source = { b: { y: 3, z: 4 }, c: 5 };
88
+ *
89
+ * const result = merge(target, source);
90
+ * console.log(result);
91
+ * // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
92
+ *
93
+ * @example
94
+ * const target = { a: [1, 2], b: { x: 1 } };
95
+ * const source = { a: [3], b: { y: 2 } };
96
+ *
97
+ * const result = merge(target, source);
98
+ * console.log(result);
99
+ * // Output: { a: [3, 2], b: { x: 1, y: 2 } }
100
+ *
101
+ * @example
102
+ * const target = { a: null };
103
+ * const source = { a: [1, 2, 3] };
104
+ *
105
+ * const result = merge(target, source);
106
+ * console.log(result);
107
+ * // Output: { a: [1, 2, 3] }
108
+ */
109
+ function merge(target, source) {
110
+ const sourceKeys = Object.keys(source);
111
+ for (let i = 0; i < sourceKeys.length; i++) {
112
+ const key = sourceKeys[i];
113
+ if (isUnsafeProperty(key)) continue;
114
+ const sourceValue = source[key];
115
+ const targetValue = target[key];
116
+ if (isMergeableValue(sourceValue) && isMergeableValue(targetValue)) target[key] = merge(targetValue, sourceValue);
117
+ else if (Array.isArray(sourceValue)) target[key] = merge([], sourceValue);
118
+ else if (isPlainObject(sourceValue)) target[key] = merge({}, sourceValue);
119
+ else if (targetValue === void 0 || sourceValue !== void 0) target[key] = sourceValue;
120
+ }
121
+ return target;
122
+ }
123
+ function isMergeableValue(value) {
124
+ return isPlainObject(value) || Array.isArray(value);
125
+ }
126
+ //#endregion
127
+ //#region src/dataTransfer/shared/httpClient/constants.ts
128
+ var API_BASE_URL = "/_/api";
129
+ //#endregion
130
+ //#region src/dataTransfer/shared/httpClient/createHttpClient.ts
131
+ var defaultConfig = {
132
+ baseURL: API_BASE_URL,
133
+ headers: {
134
+ "Content-Type": "application/json",
135
+ Accept: "application/json; application/problem+json"
136
+ }
137
+ };
138
+ var createHttpClient = (customOptions) => {
139
+ const { baseURL = API_BASE_URL, ...otherCustomOptions } = customOptions ?? {};
140
+ return ofetch.create(merge({
141
+ baseURL,
142
+ ...otherCustomOptions
143
+ }, defaultConfig));
144
+ };
145
+ //#endregion
146
+ //#region src/dataTransfer/upload/core/uploadManager/config.ts
147
+ /**
148
+ * Configuration constants shared by the upload managers
149
+ */
150
+ var CONFIG = Object.freeze({
151
+ /** Size, in bytes, of each uploaded file part. */
152
+ CHUNK_SIZE: 50 * 1024 * 1024,
153
+ /** Max number of retries for a failed request (part-url resolution or the chunk upload itself). */
154
+ DEFAULT_RETRY_COUNT: 5,
155
+ /** Delay, in ms, between retries. */
156
+ DEFAULT_RETRY_DELAY_MS: 50
157
+ });
158
+ //#endregion
159
+ //#region src/dataTransfer/upload/core/uploadManager/state.ts
160
+ var createStateManager = () => {
161
+ const uploadStateMap = /* @__PURE__ */ new Map();
162
+ const completedUploads = /* @__PURE__ */ new Set();
163
+ const cancelledUploads = /* @__PURE__ */ new Set();
164
+ const failedUploads = /* @__PURE__ */ new Set();
165
+ const assertState = (uploadId, state) => uploadStateMap.has(uploadId) && uploadStateMap.get(uploadId).state === state;
166
+ const getUploadState = (uploadId) => uploadStateMap.get(uploadId);
167
+ const setUploadState = (uploadId, state) => {
168
+ uploadStateMap.set(uploadId, {
169
+ ...uploadStateMap.get(uploadId),
170
+ ...state
171
+ });
172
+ };
173
+ const isFailed = (uploadId) => assertState(uploadId, "failed");
174
+ const isCancelled = (uploadId) => assertState(uploadId, "cancelled");
175
+ const abortInFlightUpload = (uploadId, reason) => {
176
+ const currentState = uploadStateMap.get(uploadId);
177
+ if (!currentState) return;
178
+ const message = currentState.fileDescriptor ? `Upload (${currentState.uploadId}) for file "${currentState.fileDescriptor.name}" aborted. Reason: "${reason}"` : `Upload (${currentState.uploadId}) aborted. Reason: "${reason}"`;
179
+ currentState.abortController.abort(new promise_exports.AbortError(message));
180
+ };
181
+ const cancel = (uploadId) => {
182
+ if (!uploadStateMap.has(uploadId)) {
183
+ consola.error("cancel:: Invalid upload id");
184
+ return;
185
+ }
186
+ setUploadState(uploadId, { state: "cancelled" });
187
+ abortInFlightUpload(uploadId, "upload cancelled");
188
+ cancelledUploads.add(uploadId);
189
+ };
190
+ const cancelAll = () => {
191
+ for (const [id, upload] of uploadStateMap.entries()) if (upload.state === "inprogress") cancel(id);
192
+ };
193
+ const setCompleted = (uploadId, completedChunkCount) => {
194
+ setUploadState(uploadId, {
195
+ completedChunkCount,
196
+ state: "complete"
197
+ });
198
+ completedUploads.add(uploadId);
199
+ };
200
+ const setFailed = (uploadId) => {
201
+ if (!uploadStateMap.has(uploadId)) {
202
+ consola.error("failed:: Invalid upload id");
203
+ return;
204
+ }
205
+ setUploadState(uploadId, { state: "failed" });
206
+ failedUploads.add(uploadId);
207
+ if (completedUploads.has(uploadId)) completedUploads.delete(uploadId);
208
+ };
209
+ const flush = () => {
210
+ const completed = [...completedUploads.values()];
211
+ const cancelled = [...cancelledUploads.values()];
212
+ const failed = [...failedUploads.values()];
213
+ [
214
+ ...completed,
215
+ ...cancelled,
216
+ ...failed
217
+ ].forEach((uploadId) => uploadStateMap.delete(uploadId));
218
+ completedUploads.clear();
219
+ cancelledUploads.clear();
220
+ failedUploads.clear();
221
+ return {
222
+ completed,
223
+ cancelled,
224
+ failed
225
+ };
226
+ };
227
+ return {
228
+ get: getUploadState,
229
+ set: setUploadState,
230
+ isFailed,
231
+ isCancelled,
232
+ cancel,
233
+ cancelAll,
234
+ setCompleted,
235
+ setFailed,
236
+ abortInFlightUpload,
237
+ flush
238
+ };
239
+ };
240
+ //#endregion
241
+ //#region src/dataTransfer/upload/core/uploadManager/uploadPartBytes.ts
242
+ /**
243
+ * Uploads a single file part via XHR, reporting upload progress and
244
+ * resolving with the part's identifying ETag (or an empty string if the
245
+ * backend doesn't return one).
246
+ */
247
+ var uploadPartBytesWithProgress = (params) => {
248
+ const { method, url, partBytes, partIndex, onProgress, abortSignal } = params;
249
+ const OK = 200;
250
+ const CREATED = 201;
251
+ return new Promise((resolve, reject) => {
252
+ const xhr = new XMLHttpRequest();
253
+ xhr.open(method, url, true);
254
+ abortSignal.addEventListener("abort", () => xhr.abort());
255
+ xhr.upload.onprogress = (event) => {
256
+ if (event.lengthComputable) onProgress(event.loaded);
257
+ };
258
+ xhr.onload = () => {
259
+ if (xhr.status === OK || xhr.status === CREATED) resolve({ partId: xhr.getResponseHeader("ETag")?.replace(/^(?:W\/)?"|"$/g, "") ?? "" });
260
+ else {
261
+ consola.error("Failed or unexpected XHR response", {
262
+ xhrStatus: xhr.status,
263
+ xhrResponse: xhr.response,
264
+ partIndex
265
+ });
266
+ reject(/* @__PURE__ */ new Error("Failed to upload file"));
267
+ }
268
+ };
269
+ xhr.onerror = (event) => {
270
+ consola.error("Failed to upload part", {
271
+ xhrErrorEvent: event,
272
+ partIndex
273
+ });
274
+ reject(/* @__PURE__ */ new Error("Failed to upload file"));
275
+ };
276
+ xhr.send(partBytes);
277
+ });
278
+ };
279
+ //#endregion
280
+ //#region src/dataTransfer/upload/core/uploadManager/chunkedUploadManager.ts
281
+ /**
282
+ * Creates and returns an `uploadManager`, a comprehensive multi-file, multi-part upload handler.
283
+ *
284
+ * The `uploadManager` offers functionality for handling file uploads in parts and provides
285
+ * several built-in mechanisms to ensure the process is reliable and adaptable.
286
+ *
287
+ * Key features include:
288
+ *
289
+ * - **Chunked File Uploads**: Splits provided files into manageable chunks and initiates the upload
290
+ * for each part using the `resolveFilePartUploadURL` function provided in the configuration.
291
+ *
292
+ * - **Retry Mechanism**: Automatically retries failed upload attempts up to a maximum of 5 times
293
+ * per failed request.
294
+ *
295
+ * - **Upload Cancellation**: Allows cancellation of ongoing uploads, giving users control over the process.
296
+ *
297
+ * - **Progress Tracking**: Monitors the progress of each file part upload and triggers the `onProgress`
298
+ * callback to provide updates.
299
+ *
300
+ * - **Error Handling**: The returned `uploadFiles` function does not throw errors directly. Instead,
301
+ * it manages retries and stops any failed uploads after the retry limit is reached, marking them as failed.
302
+ * This ensures that the promise it returns completes gracefully without uncaught exceptions.
303
+ *
304
+ * This is the "slice-based" manager: it needs each file's complete bytes to already be
305
+ * available (as a real `File`/`Blob` with a known `.size`), since it produces each part via
306
+ * `file.slice()`. For uploading from a source whose total size isn't known upfront (e.g. a
307
+ * stream of compressed output), see `createStreamingUploadManager` instead.
308
+ */
309
+ var createUploadManager = (config) => {
310
+ const { resolveFilePartUploadURL: getUploadFilePartUrl, onFileUploadComplete, onFileUploadFailed } = config;
311
+ const state = createStateManager();
312
+ const getTotalFileParts = (file) => Math.ceil(file.size / CONFIG.CHUNK_SIZE);
313
+ const uploadFileFromChunkIndex = async (uploadId, file) => {
314
+ const totalChunks = getTotalFileParts(file);
315
+ const totalBytes = file.size;
316
+ const completedPartIds = {};
317
+ const { abortController, runAbortablePromise } = promise_exports.createAbortablePromise();
318
+ state.set(uploadId, {
319
+ uploadId,
320
+ fileDescriptor: {
321
+ name: file.name,
322
+ type: file.type
323
+ },
324
+ state: "inprogress",
325
+ uploadedByteCount: state.get(uploadId)?.uploadedByteCount ?? 0,
326
+ completedChunkCount: state.get(uploadId)?.completedChunkCount ?? 0,
327
+ abortController
328
+ });
329
+ let chunkIndex = 0;
330
+ let uploadedBytes = state.get(uploadId)?.uploadedByteCount;
331
+ while (chunkIndex < totalChunks) {
332
+ if (state.isCancelled(uploadId) || state.isFailed(uploadId)) break;
333
+ const start = chunkIndex * CONFIG.CHUNK_SIZE;
334
+ const end = Math.min(start + CONFIG.CHUNK_SIZE, file.size);
335
+ const chunk = file.slice(start, end);
336
+ try {
337
+ const { method, url } = await promise_exports.retryPromise({
338
+ fn: () => getUploadFilePartUrl(uploadId, chunkIndex + 1),
339
+ retryCount: CONFIG.DEFAULT_RETRY_COUNT,
340
+ retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS
341
+ });
342
+ consola.trace("File part upload url resolved", {
343
+ method,
344
+ url
345
+ });
346
+ const onChunkUploadProgress = (uploadedChunkBytes) => {
347
+ uploadedBytes = uploadedChunkBytes + start;
348
+ state.set(uploadId, { uploadedByteCount: uploadedBytes });
349
+ const progress = Math.round(uploadedBytes / totalBytes * 100);
350
+ config.onProgress?.(uploadId, progress);
351
+ };
352
+ const filePartResponse = await runAbortablePromise(() => promise_exports.retryPromise({
353
+ fn: () => uploadPartBytesWithProgress({
354
+ method,
355
+ url,
356
+ partBytes: chunk,
357
+ partIndex: chunkIndex,
358
+ onProgress: onChunkUploadProgress,
359
+ abortSignal: abortController.signal
360
+ }),
361
+ retryCount: CONFIG.DEFAULT_RETRY_COUNT,
362
+ retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS
363
+ }));
364
+ consola.trace(`${file.name}: Chunk ${chunkIndex + 1} of ${totalChunks} uploaded successfully`);
365
+ completedPartIds[chunkIndex + 1] = filePartResponse.partId;
366
+ state.set(uploadId, { completedChunkCount: state.get(uploadId).completedChunkCount + 1 });
367
+ } catch (_error) {
368
+ if (_error instanceof promise_exports.AbortError) {
369
+ consola.info("Aborted::", _error);
370
+ return;
371
+ }
372
+ const error = _error instanceof Error ? _error : new Error(_error);
373
+ state.setFailed(uploadId);
374
+ onFileUploadFailed?.({
375
+ uploadId,
376
+ error
377
+ });
378
+ return;
379
+ }
380
+ chunkIndex++;
381
+ }
382
+ if (state.isCancelled(uploadId) || state.isFailed(uploadId)) return;
383
+ state.setCompleted(uploadId, totalChunks);
384
+ onFileUploadComplete?.({
385
+ uploadId,
386
+ filePartIds: completedPartIds
387
+ });
388
+ };
389
+ const uploadFiles = (files) => {
390
+ const result = [];
391
+ for (const { uploadId, file } of files) result.push(uploadFileFromChunkIndex(uploadId, file));
392
+ return Promise.allSettled(result).then(() => state.flush());
393
+ };
394
+ return {
395
+ uploadFiles,
396
+ getUploadState: state.get,
397
+ isFailed: state.isFailed,
398
+ isCancelled: state.isCancelled,
399
+ cancel: state.cancel,
400
+ cancelAll: state.cancelAll,
401
+ setFailed: state.setFailed
402
+ };
403
+ };
404
+ //#endregion
405
+ //#region src/dataTransfer/upload/core/uploadManager/streamingUploadManager.ts
406
+ /**
407
+ * A small FIFO byte buffer. Pushed chunks are stored by reference (no
408
+ * copying on push).
409
+ */
410
+ var ByteQueue = class {
411
+ #chunks = [];
412
+ #length = 0;
413
+ get length() {
414
+ return this.#length;
415
+ }
416
+ push(chunk) {
417
+ if (chunk.length === 0) return;
418
+ this.#chunks.push(chunk);
419
+ this.#length += chunk.length;
420
+ }
421
+ /**
422
+ * FIFO exactly `n` bytes from the buffer. If the first chunk has more bytes than needed,
423
+ * it will be split and only the requested amount returned. This ensures each byte is copied
424
+ * at most once, regardless of how many small chunks it took to accumulate them.
425
+ * @param n
426
+ * @returns
427
+ */
428
+ take(n) {
429
+ const size = Math.min(n, this.#length);
430
+ const result = new Uint8Array(size);
431
+ let offset = 0;
432
+ while (offset < size) {
433
+ const chunk = this.#chunks[0];
434
+ const needed = size - offset;
435
+ if (chunk.length <= needed) {
436
+ result.set(chunk, offset);
437
+ offset += chunk.length;
438
+ this.#chunks.shift();
439
+ } else {
440
+ result.set(chunk.subarray(0, needed), offset);
441
+ this.#chunks[0] = chunk.subarray(needed);
442
+ offset += needed;
443
+ }
444
+ }
445
+ this.#length -= size;
446
+ return result;
447
+ }
448
+ takeAll() {
449
+ return this.take(this.#length);
450
+ }
451
+ };
452
+ var readStreamAndUpload = async (state, uploadId, stream, uploadPart, config) => {
453
+ const buffer = new ByteQueue();
454
+ const reader = stream.getReader();
455
+ const closeReader = async () => {
456
+ try {
457
+ await reader.cancel();
458
+ } catch {} finally {
459
+ reader.releaseLock();
460
+ }
461
+ };
462
+ try {
463
+ while (true) {
464
+ if (state.isCancelled(uploadId) || state.isFailed(uploadId)) return;
465
+ const { value, done } = await reader.read();
466
+ if (value) buffer.push(value);
467
+ while (buffer.length >= CONFIG.CHUNK_SIZE) {
468
+ if (state.isCancelled(uploadId) || state.isFailed(uploadId)) return;
469
+ await uploadPart(buffer.take(CONFIG.CHUNK_SIZE));
470
+ }
471
+ if (done) {
472
+ if (buffer.length > 0 && !state.isCancelled(uploadId) && !state.isFailed(uploadId)) await uploadPart(buffer.takeAll());
473
+ break;
474
+ }
475
+ }
476
+ } catch (_error) {
477
+ if (_error instanceof promise_exports.AbortError) {
478
+ consola.info("Aborted::", _error);
479
+ return;
480
+ }
481
+ state.setFailed(uploadId);
482
+ const error = _error instanceof Error ? _error : new Error(_error);
483
+ config.onFileUploadFailed?.({
484
+ uploadId,
485
+ error
486
+ });
487
+ } finally {
488
+ await closeReader();
489
+ }
490
+ };
491
+ /**
492
+ * Creates and returns a `streamingUploadManager` - a multi-part upload handler
493
+ * driven by a `ReadableStream<Uint8Array>` rather than a `File`/`Blob`.
494
+ *
495
+ * Unlike the chunkedUploadManager, this doesn't need to know the total size of the upload upfront.
496
+ * Instead, it accumulates stream chunks into a buffer and uploadsa part every time
497
+ * the buffer crosses the configured threshold for upload chunks, flushing whatever remains
498
+ * as a final (possibly smaller) part once the stream ends.
499
+ *
500
+ * This bounds memory to roughly one chunk's worth of buffered bytes, plus
501
+ * whatever the stream source itself buffers, regardless of the eventual
502
+ * total size - useful for sources whose size can't be known in advance,
503
+ * such as compressed output from a streaming zip encoder.
504
+ *
505
+ * Shares the same part-upload mechanics (retries, progress, abort) as
506
+ * the chunkedUploadManager
507
+ */
508
+ var createStreamingUploadManager = (config) => {
509
+ const { resolveFilePartUploadURL: getUploadFilePartUrl, onFileUploadComplete } = config;
510
+ const state = createStateManager();
511
+ const uploadStreamFromPartIndex = async (uploadId, stream) => {
512
+ const completedPartIds = {};
513
+ const { abortController, runAbortablePromise } = promise_exports.createAbortablePromise();
514
+ state.set(uploadId, {
515
+ uploadId,
516
+ state: "inprogress",
517
+ uploadedByteCount: state.get(uploadId)?.uploadedByteCount ?? 0,
518
+ completedChunkCount: state.get(uploadId)?.completedChunkCount ?? 0,
519
+ abortController
520
+ });
521
+ let partIndex = 0;
522
+ let uploadedBytes = state.get(uploadId)?.uploadedByteCount ?? 0;
523
+ const uploadPart = async (bytes) => {
524
+ const chunk = new Blob([bytes]);
525
+ const { method, url } = await promise_exports.retryPromise({
526
+ fn: () => getUploadFilePartUrl(uploadId, partIndex + 1),
527
+ retryCount: CONFIG.DEFAULT_RETRY_COUNT,
528
+ retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS
529
+ });
530
+ const uploadedBeforeThisPart = uploadedBytes;
531
+ const onChunkUploadProgress = (uploadedChunkBytes) => {
532
+ uploadedBytes = uploadedBeforeThisPart + uploadedChunkBytes;
533
+ state.set(uploadId, { uploadedByteCount: uploadedBytes });
534
+ config.onProgress?.(uploadId, uploadedBytes);
535
+ };
536
+ const filePartResponse = await runAbortablePromise(() => promise_exports.retryPromise({
537
+ fn: () => uploadPartBytesWithProgress({
538
+ method,
539
+ url,
540
+ partBytes: chunk,
541
+ partIndex,
542
+ onProgress: onChunkUploadProgress,
543
+ abortSignal: abortController.signal
544
+ }),
545
+ retryCount: CONFIG.DEFAULT_RETRY_COUNT,
546
+ retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS
547
+ }));
548
+ completedPartIds[partIndex + 1] = filePartResponse.partId;
549
+ uploadedBytes = uploadedBeforeThisPart + bytes.length;
550
+ partIndex++;
551
+ state.set(uploadId, {
552
+ completedChunkCount: partIndex,
553
+ uploadedByteCount: uploadedBytes
554
+ });
555
+ };
556
+ await readStreamAndUpload(state, uploadId, stream, uploadPart, config);
557
+ if (state.isCancelled(uploadId) || state.isFailed(uploadId)) return;
558
+ state.setCompleted(uploadId, state.get(uploadId).completedChunkCount);
559
+ onFileUploadComplete?.({
560
+ uploadId,
561
+ filePartIds: completedPartIds
562
+ });
563
+ };
564
+ const uploadStreams = (streams) => {
565
+ const result = [];
566
+ for (const { uploadId, stream } of streams) result.push(uploadStreamFromPartIndex(uploadId, stream));
567
+ return Promise.allSettled(result).then(() => state.flush());
568
+ };
569
+ return {
570
+ uploadStreams,
571
+ getUploadState: state.get,
572
+ isFailed: state.isFailed,
573
+ isCancelled: state.isCancelled,
574
+ cancel: state.cancel,
575
+ cancelAll: state.cancelAll,
576
+ setFailed: state.setFailed
577
+ };
578
+ };
579
+ //#endregion
580
+ //#region src/dataTransfer/upload/core/uploadManager/types.ts
581
+ var UploadStates = {
582
+ IDLE: "idle",
583
+ IN_PROGRESS: "inprogress",
584
+ CANCELLED: "cancelled",
585
+ FAILED: "failed",
586
+ COMPLETE: "complete"
587
+ };
588
+ //#endregion
589
+ //#region src/dataTransfer/upload/core/uploadClient/uploadClient.ts
590
+ /**
591
+ * Creates the raw upload lifecycle client:
592
+ *
593
+ * (1) - prepare a batch of items for upload
594
+ * (2) - resolve a presigned URL for a given file part
595
+ * (3) - complete an upload once all its parts are uploaded, or cancel one in progress.
596
+ */
597
+ var createUploadClient = (options = {}) => {
598
+ const $ofetch = createHttpClient(options.customFetchClientOptions);
599
+ const prepareUpload = (parentId, items) => {
600
+ const fileByName = new Map(items.map(({ path: name, file }) => [name, file]));
601
+ const body = { items: Object.fromEntries(items.map(({ path: name, file }) => [name, {
602
+ itemContentType: getFileMimeType(file),
603
+ itemContentSize: file.size
604
+ }])) };
605
+ return $ofetch(`/repository/${parentId}/manifest`, {
606
+ method: "POST",
607
+ body
608
+ }).then(({ items: responseItems }) => Object.keys(responseItems).map((name) => ({
609
+ uploadId: responseItems[name].uploadId,
610
+ file: fileByName.get(name),
611
+ path: name
612
+ })));
613
+ };
614
+ const resolveFilePartUploadURL = (uploadId, partNumber) => {
615
+ return $ofetch(`/uploads/${uploadId}/parts/?partNumber=${partNumber}`, { method: "POST" });
616
+ };
617
+ const completeUpload = (uploadId, partIds) => {
618
+ return $ofetch(`/uploads/${uploadId}`, {
619
+ method: "POST",
620
+ body: partIds
621
+ });
622
+ };
623
+ const cancelUpload = (uploadId) => {
624
+ return $ofetch(`/uploads/${uploadId}`, { method: "DELETE" });
625
+ };
626
+ const getUploadStatus = (uploadIds) => {
627
+ return $ofetch("/uploads/status", {
628
+ method: "GET",
629
+ query: { uploadIds: uploadIds.join(",") }
630
+ });
631
+ };
632
+ return {
633
+ prepareUpload,
634
+ resolveFilePartUploadURL,
635
+ completeUpload,
636
+ cancelUpload,
637
+ getUploadStatus
638
+ };
639
+ };
640
+ //#endregion
641
+ //#region ../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/array/chunk.mjs
642
+ /**
643
+ * Splits an array into smaller arrays of a specified length.
644
+ *
645
+ * This function takes an input array and divides it into multiple smaller arrays,
646
+ * each of a specified length. If the input array cannot be evenly divided,
647
+ * the final sub-array will contain the remaining elements.
648
+ *
649
+ * @template T The type of elements in the array.
650
+ * @param arr - The array to be chunked into smaller arrays.
651
+ * @param size - The size of each smaller array. Must be a positive integer.
652
+ * @returns A two-dimensional array where each sub-array has a maximum length of `size`.
653
+ * @throws {Error} Throws an error if `size` is not a positive integer.
654
+ *
655
+ * @example
656
+ * // Splits an array of numbers into sub-arrays of length 2
657
+ * chunk([1, 2, 3, 4, 5], 2);
658
+ * // Returns: [[1, 2], [3, 4], [5]]
659
+ *
660
+ * @example
661
+ * // Splits an array of strings into sub-arrays of length 3
662
+ * chunk(['a', 'b', 'c', 'd', 'e', 'f', 'g'], 3);
663
+ * // Returns: [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']]
664
+ */
665
+ function chunk(arr, size) {
666
+ if (!Number.isInteger(size) || size <= 0) throw new Error("Size must be an integer greater than zero.");
667
+ const chunkLength = Math.ceil(arr.length / size);
668
+ const result = Array(chunkLength);
669
+ for (let index = 0; index < chunkLength; index++) {
670
+ const start = index * size;
671
+ const end = start + size;
672
+ result[index] = arr.slice(start, end);
673
+ }
674
+ return result;
675
+ }
676
+ //#endregion
677
+ //#region src/dataTransfer/upload/core/uploadClient/createUploadStatusPoller.ts
678
+ var DEFAULT_POLL_INTERVAL_MS = 1500;
679
+ var DEFAULT_POLL_TIMEOUT = 300 * 1e3;
680
+ var MAX_IDS_PER_REQUEST = 100;
681
+ var logger = () => consola.withTag("Upload status poller");
682
+ function createUploadStatusPoller(uploadClient, options) {
683
+ const pending = /* @__PURE__ */ new Set();
684
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
685
+ const pollTimeout = options.pollTimeout ?? DEFAULT_POLL_TIMEOUT;
686
+ let inFlight = null;
687
+ const poll = () => {
688
+ if (inFlight) return inFlight;
689
+ inFlight = (async () => {
690
+ const deadline = Date.now() + pollTimeout;
691
+ while (pending.size > 0 && Date.now() < deadline) try {
692
+ const promises = chunk([...pending.values()], MAX_IDS_PER_REQUEST).map((batch) => uploadClient.getUploadStatus(batch));
693
+ const statuses = (await Promise.all(promises)).flat();
694
+ for (const { uploadId, status, statusMessage } of statuses) if (status === "COMPLETED") {
695
+ pending.delete(uploadId);
696
+ options.onComplete(uploadId);
697
+ } else if (status === "FAILED" || status === "ABORTED") {
698
+ pending.delete(uploadId);
699
+ options.onFail(uploadId, statusMessage);
700
+ }
701
+ if (pending.size === 0) break;
702
+ await sleep(pollIntervalMs);
703
+ } catch (error) {
704
+ logger().error("Failed while polling for upload batch status", { error });
705
+ await sleep(pollIntervalMs);
706
+ continue;
707
+ }
708
+ for (const uploadId of pending) {
709
+ pending.delete(uploadId);
710
+ options.onFail(uploadId, "Timed out waiting for processing to complete");
711
+ }
712
+ })().finally(() => {
713
+ inFlight = null;
714
+ });
715
+ return inFlight;
716
+ };
717
+ return {
718
+ /**
719
+ * Tracks an id for polling. Will start the polling requests immediately unless indicated otherwise
720
+ * by the `startPolling` parameter.
721
+ *
722
+ * Adding newer ids to an already-running poller is not a problem, since new ids just
723
+ * increase the poll batch size upon the next execution of the polling request
724
+ *
725
+ * @param uploadId
726
+ * @param startPolling
727
+ */
728
+ track: (uploadId, startPolling = true) => {
729
+ pending.add(uploadId);
730
+ if (startPolling) return poll();
731
+ return Promise.resolve();
732
+ },
733
+ stop: () => pending.clear()
734
+ };
735
+ }
736
+ //#endregion
737
+ export { createUploadManager as a, API_BASE_URL as c, createStreamingUploadManager as i, createUploadClient as n, createHttpClient as o, UploadStates as r, defaultConfig as s, createUploadStatusPoller as t };
738
+
739
+ //# sourceMappingURL=core-CVWznjtP.js.map