@koolbase/core 12.20.0 → 12.21.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/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +5 -1
- package/dist/cjs/react/index.d.ts +1 -0
- package/dist/cjs/react/index.js +3 -1
- package/dist/cjs/react/use-upload-queue.d.ts +18 -0
- package/dist/cjs/react/use-upload-queue.js +31 -0
- package/dist/cjs/upload-queue.d.ts +81 -0
- package/dist/cjs/upload-queue.js +190 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/react/index.d.ts +1 -0
- package/dist/esm/react/index.js +1 -0
- package/dist/esm/react/use-upload-queue.d.ts +18 -0
- package/dist/esm/react/use-upload-queue.js +28 -0
- package/dist/esm/upload-queue.d.ts +81 -0
- package/dist/esm/upload-queue.js +186 -0
- package/package.json +1 -1
package/dist/cjs/index.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export { KoolbaseFilesController, initialFilesState, filesQueryKey } from './fil
|
|
|
27
27
|
export type { FilesQuery, FilesState, FilesStatus } from './files-controller.js';
|
|
28
28
|
export { KoolbaseFileController, initialFileState } from './file-controller.js';
|
|
29
29
|
export type { FileState, FileStatus } from './file-controller.js';
|
|
30
|
+
export { KoolbaseUploadQueue, initialUploadQueueState } from './upload-queue.js';
|
|
31
|
+
export type { KoolbaseUploader, QueuedUpload, QueuedUploadStatus, QueueFile, UploadQueueState } from './upload-queue.js';
|
|
30
32
|
export { KoolbaseAnalytics, disabledAnalytics } from './analytics.js';
|
|
31
33
|
export { KoolbaseFiscal, KoolbaseFiscalError, decodeFiscalResponse, isFiscalized, isPending } from './fiscal.js';
|
|
32
34
|
export type { FiscalIntentResult, FiscalStatus } from './fiscal.js';
|
package/dist/cjs/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
19
19
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
20
|
};
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.memoryPlatform = exports.getPlatform = exports.setPlatform = exports.RestoreResult = exports.koolbaseSdkVersion = exports.getOrCreateDeviceId = exports.KoolbaseMessaging = exports.isPending = exports.isFiscalized = exports.decodeFiscalResponse = exports.KoolbaseFiscalError = exports.KoolbaseFiscal = exports.disabledAnalytics = exports.KoolbaseAnalytics = exports.initialFileState = exports.KoolbaseFileController = exports.filesQueryKey = exports.initialFilesState = exports.KoolbaseFilesController = exports.KoolbaseUploadTask = exports.KoolbaseStorage = exports.KoolbaseConnectivity = exports.KoolbaseRealtime = exports.KoolbaseFunctions = exports.KoolbaseFlags = exports.initialRecordState = exports.KoolbaseRecordController = exports.initialCollectionState = exports.collectionQueryKey = exports.KoolbaseCollectionController = exports.KoolbaseDatabase = exports.KoolbaseAuth = void 0;
|
|
22
|
+
exports.memoryPlatform = exports.getPlatform = exports.setPlatform = exports.RestoreResult = exports.koolbaseSdkVersion = exports.getOrCreateDeviceId = exports.KoolbaseMessaging = exports.isPending = exports.isFiscalized = exports.decodeFiscalResponse = exports.KoolbaseFiscalError = exports.KoolbaseFiscal = exports.disabledAnalytics = exports.KoolbaseAnalytics = exports.initialUploadQueueState = exports.KoolbaseUploadQueue = exports.initialFileState = exports.KoolbaseFileController = exports.filesQueryKey = exports.initialFilesState = exports.KoolbaseFilesController = exports.KoolbaseUploadTask = exports.KoolbaseStorage = exports.KoolbaseConnectivity = exports.KoolbaseRealtime = exports.KoolbaseFunctions = exports.KoolbaseFlags = exports.initialRecordState = exports.KoolbaseRecordController = exports.initialCollectionState = exports.collectionQueryKey = exports.KoolbaseCollectionController = exports.KoolbaseDatabase = exports.KoolbaseAuth = void 0;
|
|
23
23
|
__exportStar(require("./types.js"), exports);
|
|
24
24
|
__exportStar(require("./errors.js"), exports);
|
|
25
25
|
__exportStar(require("./network.js"), exports);
|
|
@@ -62,6 +62,10 @@ Object.defineProperty(exports, "filesQueryKey", { enumerable: true, get: functio
|
|
|
62
62
|
var file_controller_js_1 = require("./file-controller.js");
|
|
63
63
|
Object.defineProperty(exports, "KoolbaseFileController", { enumerable: true, get: function () { return file_controller_js_1.KoolbaseFileController; } });
|
|
64
64
|
Object.defineProperty(exports, "initialFileState", { enumerable: true, get: function () { return file_controller_js_1.initialFileState; } });
|
|
65
|
+
// Several uploads in a queue (storage S4).
|
|
66
|
+
var upload_queue_js_1 = require("./upload-queue.js");
|
|
67
|
+
Object.defineProperty(exports, "KoolbaseUploadQueue", { enumerable: true, get: function () { return upload_queue_js_1.KoolbaseUploadQueue; } });
|
|
68
|
+
Object.defineProperty(exports, "initialUploadQueueState", { enumerable: true, get: function () { return upload_queue_js_1.initialUploadQueueState; } });
|
|
65
69
|
var analytics_js_1 = require("./analytics.js");
|
|
66
70
|
Object.defineProperty(exports, "KoolbaseAnalytics", { enumerable: true, get: function () { return analytics_js_1.KoolbaseAnalytics; } });
|
|
67
71
|
Object.defineProperty(exports, "disabledAnalytics", { enumerable: true, get: function () { return analytics_js_1.disabledAnalytics; } });
|
|
@@ -2,3 +2,4 @@ export { createUseCollection, type UseCollectionResult } from './use-collection.
|
|
|
2
2
|
export { createUseRecord, type UseRecordResult } from './use-record.js';
|
|
3
3
|
export { createUseFiles, type UseFilesResult } from './use-files.js';
|
|
4
4
|
export { createUseFile, type UseFileResult } from './use-file.js';
|
|
5
|
+
export { createUseUploadQueue, type UseUploadQueueResult } from './use-upload-queue.js';
|
package/dist/cjs/react/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createUseFile = exports.createUseFiles = exports.createUseRecord = exports.createUseCollection = void 0;
|
|
3
|
+
exports.createUseUploadQueue = exports.createUseFile = exports.createUseFiles = exports.createUseRecord = exports.createUseCollection = void 0;
|
|
4
4
|
// React hooks over core's controllers: ONE implementation, exported to apps by
|
|
5
5
|
// @koolbase/react-native (from its root, as always) and @koolbase/js/react.
|
|
6
6
|
// React only -- nothing native, nothing DOM. Deliberately not exported from
|
|
@@ -13,3 +13,5 @@ var use_files_js_1 = require("./use-files.js");
|
|
|
13
13
|
Object.defineProperty(exports, "createUseFiles", { enumerable: true, get: function () { return use_files_js_1.createUseFiles; } });
|
|
14
14
|
var use_file_js_1 = require("./use-file.js");
|
|
15
15
|
Object.defineProperty(exports, "createUseFile", { enumerable: true, get: function () { return use_file_js_1.createUseFile; } });
|
|
16
|
+
var use_upload_queue_js_1 = require("./use-upload-queue.js");
|
|
17
|
+
Object.defineProperty(exports, "createUseUploadQueue", { enumerable: true, get: function () { return use_upload_queue_js_1.createUseUploadQueue; } });
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type KoolbaseUploader, type QueueFile, type UploadQueueState } from '../index.js';
|
|
2
|
+
export interface UseUploadQueueResult extends UploadQueueState {
|
|
3
|
+
/** Adds files and starts as many as may run; returns their ids. */
|
|
4
|
+
add: (files: readonly QueueFile[]) => string[];
|
|
5
|
+
/** Stops one file. */
|
|
6
|
+
cancel: (id: string) => boolean;
|
|
7
|
+
/** Stops every file that can still be stopped. */
|
|
8
|
+
cancelAll: () => void;
|
|
9
|
+
/** Tries a failed or cancelled file again. */
|
|
10
|
+
retry: (id: string) => boolean;
|
|
11
|
+
/** Removes the files that have finished. */
|
|
12
|
+
clearFinished: () => void;
|
|
13
|
+
/** Resolves once nothing is running. */
|
|
14
|
+
whenIdle: () => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare function createUseUploadQueue(getStorage: () => KoolbaseUploader): (options?: {
|
|
17
|
+
concurrency?: number;
|
|
18
|
+
}) => UseUploadQueueResult;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// useUploadQueue: several uploads as React state (storage S4).
|
|
3
|
+
//
|
|
4
|
+
// A thin wrapper. Everything about the queue lives in @koolbase/core's
|
|
5
|
+
// KoolbaseUploadQueue, which is tested there; this file only gives a
|
|
6
|
+
// component one queue for its lifetime. Uploads still running when the
|
|
7
|
+
// component goes finish on their own.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createUseUploadQueue = createUseUploadQueue;
|
|
10
|
+
const react_1 = require("react");
|
|
11
|
+
const index_js_1 = require("../index.js");
|
|
12
|
+
function createUseUploadQueue(getStorage) {
|
|
13
|
+
return function useUploadQueue(options = {}) {
|
|
14
|
+
const ref = (0, react_1.useRef)(null);
|
|
15
|
+
if (ref.current === null)
|
|
16
|
+
ref.current = new index_js_1.KoolbaseUploadQueue(getStorage(), options);
|
|
17
|
+
const queue = ref.current;
|
|
18
|
+
const subscribe = (0, react_1.useCallback)((onChange) => queue.subscribe(onChange), [queue]);
|
|
19
|
+
const getSnapshot = (0, react_1.useCallback)(() => queue.getState(), [queue]);
|
|
20
|
+
const state = (0, react_1.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
21
|
+
return {
|
|
22
|
+
...state,
|
|
23
|
+
add: (0, react_1.useCallback)((files) => queue.add(files), [queue]),
|
|
24
|
+
cancel: (0, react_1.useCallback)((id) => queue.cancel(id), [queue]),
|
|
25
|
+
cancelAll: (0, react_1.useCallback)(() => queue.cancelAll(), [queue]),
|
|
26
|
+
retry: (0, react_1.useCallback)((id) => queue.retry(id), [queue]),
|
|
27
|
+
clearFinished: (0, react_1.useCallback)(() => queue.clearFinished(), [queue]),
|
|
28
|
+
whenIdle: (0, react_1.useCallback)(() => queue.whenIdle(), [queue]),
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { KoolbaseUploadTask } from './storage-transfer.js';
|
|
2
|
+
import type { UploadOptions } from './types.js';
|
|
3
|
+
export type QueuedUploadStatus = 'queued' | 'uploading' | 'verifying' | 'done' | 'failed' | 'cancelled';
|
|
4
|
+
/** One file in the queue. */
|
|
5
|
+
export interface QueuedUpload {
|
|
6
|
+
/** The queue's own id for it: what cancel and retry take. */
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly bucket: string;
|
|
9
|
+
/** Where it goes in the bucket. */
|
|
10
|
+
readonly path: string;
|
|
11
|
+
/** What a person calls it: the name given, else the path's last part. */
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly status: QueuedUploadStatus;
|
|
14
|
+
/** 0 to 1: the share of its bytes sent; 1 once verifying. */
|
|
15
|
+
readonly progress: number;
|
|
16
|
+
/** Why it failed, while status is 'failed'. */
|
|
17
|
+
readonly error: unknown;
|
|
18
|
+
}
|
|
19
|
+
export interface UploadQueueState {
|
|
20
|
+
readonly items: readonly QueuedUpload[];
|
|
21
|
+
/** 0 to 1: the files' average progress, a cancelled one not counted. */
|
|
22
|
+
readonly progress: number;
|
|
23
|
+
/** Whether any file is still queued, uploading or verifying. */
|
|
24
|
+
readonly running: boolean;
|
|
25
|
+
/** How many have landed, and how many failed. */
|
|
26
|
+
readonly done: number;
|
|
27
|
+
readonly failed: number;
|
|
28
|
+
}
|
|
29
|
+
export declare const initialUploadQueueState: UploadQueueState;
|
|
30
|
+
/** A file to add: where it goes, and the bytes as startUpload takes them. */
|
|
31
|
+
export interface QueueFile {
|
|
32
|
+
bucket: string;
|
|
33
|
+
path: string;
|
|
34
|
+
file: UploadOptions['file'];
|
|
35
|
+
/** Shown for it; the path's last part unless set. */
|
|
36
|
+
name?: string;
|
|
37
|
+
overwrite?: boolean;
|
|
38
|
+
metadata?: UploadOptions['metadata'];
|
|
39
|
+
}
|
|
40
|
+
type QueueTask = Pick<KoolbaseUploadTask, 'onProgress' | 'cancel' | 'result'>;
|
|
41
|
+
/** What the queue needs from storage: startUpload. */
|
|
42
|
+
export type KoolbaseUploader = {
|
|
43
|
+
startUpload(options: UploadOptions): QueueTask;
|
|
44
|
+
};
|
|
45
|
+
export declare class KoolbaseUploadQueue {
|
|
46
|
+
readonly concurrency: number;
|
|
47
|
+
private readonly storage;
|
|
48
|
+
private entries;
|
|
49
|
+
private state;
|
|
50
|
+
private readonly listeners;
|
|
51
|
+
private idleWaiters;
|
|
52
|
+
private nextId;
|
|
53
|
+
private disposed;
|
|
54
|
+
constructor(storage: KoolbaseUploader, options?: {
|
|
55
|
+
concurrency?: number;
|
|
56
|
+
});
|
|
57
|
+
getState(): UploadQueueState;
|
|
58
|
+
/** Called on every change. Returns the unsubscribe. */
|
|
59
|
+
subscribe(listener: () => void): () => void;
|
|
60
|
+
/** Adds files to the end of the queue and starts as many as may run. Returns their ids. */
|
|
61
|
+
add(files: readonly QueueFile[]): string[];
|
|
62
|
+
/** Stops one file: a queued one never starts; one uploading stops. Returns whether it was stopped. */
|
|
63
|
+
cancel(id: string): boolean;
|
|
64
|
+
/** Stops every file that can still be stopped. */
|
|
65
|
+
cancelAll(): void;
|
|
66
|
+
/** Tries a failed or cancelled file again, from the start. Returns whether it was queued. */
|
|
67
|
+
retry(id: string): boolean;
|
|
68
|
+
/** Removes the files that have finished: landed, failed or cancelled. */
|
|
69
|
+
clearFinished(): void;
|
|
70
|
+
/** Resolves once nothing is queued, uploading or verifying. */
|
|
71
|
+
whenIdle(): Promise<void>;
|
|
72
|
+
/** Stops reporting. Uploads already running finish on their own. */
|
|
73
|
+
dispose(): void;
|
|
74
|
+
private active;
|
|
75
|
+
private pump;
|
|
76
|
+
private start;
|
|
77
|
+
private finish;
|
|
78
|
+
private patch;
|
|
79
|
+
private publish;
|
|
80
|
+
}
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Several uploads in a queue, framework-free (storage S4).
|
|
3
|
+
//
|
|
4
|
+
// Files added together go a few at a time (three unless set), each its own
|
|
5
|
+
// SDK upload task -- so a large one still goes in parts, and a bucket's
|
|
6
|
+
// file lists still reload as each lands. Every file has its own state and
|
|
7
|
+
// progress, and can be cancelled or tried again on its own. No React here,
|
|
8
|
+
// so it is tested in this package; useUploadQueue in @koolbase/react-native
|
|
9
|
+
// and @koolbase/js/react is a thin wrapper. Mirrors the Flutter SDK's
|
|
10
|
+
// KoolbaseUploadQueue.
|
|
11
|
+
//
|
|
12
|
+
// - queued -> uploading -> verifying -> done, or failed / cancelled
|
|
13
|
+
// - a queued file cancelled never starts; one uploading stops (once the
|
|
14
|
+
// server is verifying it, it lands or fails on its own)
|
|
15
|
+
// - a failed or cancelled file can be tried again: it queues once more,
|
|
16
|
+
// from the start, under the same path
|
|
17
|
+
// - a file that fails never stops the others
|
|
18
|
+
// - progress is the files' average, a cancelled one not counted
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.KoolbaseUploadQueue = exports.initialUploadQueueState = void 0;
|
|
21
|
+
const storage_errors_js_1 = require("./storage-errors.js");
|
|
22
|
+
exports.initialUploadQueueState = Object.freeze({
|
|
23
|
+
items: Object.freeze([]),
|
|
24
|
+
progress: 0,
|
|
25
|
+
running: false,
|
|
26
|
+
done: 0,
|
|
27
|
+
failed: 0,
|
|
28
|
+
});
|
|
29
|
+
class KoolbaseUploadQueue {
|
|
30
|
+
constructor(storage, options = {}) {
|
|
31
|
+
this.entries = [];
|
|
32
|
+
this.state = exports.initialUploadQueueState;
|
|
33
|
+
this.listeners = new Set();
|
|
34
|
+
this.idleWaiters = [];
|
|
35
|
+
this.nextId = 0;
|
|
36
|
+
this.disposed = false;
|
|
37
|
+
this.storage = storage;
|
|
38
|
+
this.concurrency = Math.max(1, Math.floor(options.concurrency ?? 3));
|
|
39
|
+
}
|
|
40
|
+
getState() {
|
|
41
|
+
return this.state;
|
|
42
|
+
}
|
|
43
|
+
/** Called on every change. Returns the unsubscribe. */
|
|
44
|
+
subscribe(listener) {
|
|
45
|
+
this.listeners.add(listener);
|
|
46
|
+
return () => {
|
|
47
|
+
this.listeners.delete(listener);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Adds files to the end of the queue and starts as many as may run. Returns their ids. */
|
|
51
|
+
add(files) {
|
|
52
|
+
if (this.disposed)
|
|
53
|
+
return [];
|
|
54
|
+
const ids = [];
|
|
55
|
+
for (const source of files) {
|
|
56
|
+
const id = `u${++this.nextId}`;
|
|
57
|
+
ids.push(id);
|
|
58
|
+
this.entries.push({
|
|
59
|
+
item: { id, bucket: source.bucket, path: source.path, name: source.name || source.path.split('/').pop() || source.path, status: 'queued', progress: 0, error: null },
|
|
60
|
+
source,
|
|
61
|
+
task: null,
|
|
62
|
+
run: 0,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
this.pump();
|
|
66
|
+
this.publish();
|
|
67
|
+
return ids;
|
|
68
|
+
}
|
|
69
|
+
/** Stops one file: a queued one never starts; one uploading stops. Returns whether it was stopped. */
|
|
70
|
+
cancel(id) {
|
|
71
|
+
const e = this.entries.find((x) => x.item.id === id);
|
|
72
|
+
if (!e)
|
|
73
|
+
return false;
|
|
74
|
+
if (e.item.status === 'queued') {
|
|
75
|
+
this.patch(e, { status: 'cancelled', progress: 0 });
|
|
76
|
+
this.publish();
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
if (e.item.status === 'uploading')
|
|
80
|
+
return e.task?.cancel() ?? false;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
/** Stops every file that can still be stopped. */
|
|
84
|
+
cancelAll() {
|
|
85
|
+
for (const e of [...this.entries])
|
|
86
|
+
this.cancel(e.item.id);
|
|
87
|
+
}
|
|
88
|
+
/** Tries a failed or cancelled file again, from the start. Returns whether it was queued. */
|
|
89
|
+
retry(id) {
|
|
90
|
+
const e = this.entries.find((x) => x.item.id === id);
|
|
91
|
+
if (!e || (e.item.status !== 'failed' && e.item.status !== 'cancelled') || this.disposed)
|
|
92
|
+
return false;
|
|
93
|
+
e.task = null;
|
|
94
|
+
this.patch(e, { status: 'queued', progress: 0, error: null });
|
|
95
|
+
this.pump();
|
|
96
|
+
this.publish();
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
/** Removes the files that have finished: landed, failed or cancelled. */
|
|
100
|
+
clearFinished() {
|
|
101
|
+
const before = this.entries.length;
|
|
102
|
+
this.entries = this.entries.filter((e) => e.item.status === 'queued' || e.item.status === 'uploading' || e.item.status === 'verifying');
|
|
103
|
+
if (this.entries.length !== before)
|
|
104
|
+
this.publish();
|
|
105
|
+
}
|
|
106
|
+
/** Resolves once nothing is queued, uploading or verifying. */
|
|
107
|
+
whenIdle() {
|
|
108
|
+
if (!this.state.running)
|
|
109
|
+
return Promise.resolve();
|
|
110
|
+
return new Promise((resolve) => this.idleWaiters.push(resolve));
|
|
111
|
+
}
|
|
112
|
+
/** Stops reporting. Uploads already running finish on their own. */
|
|
113
|
+
dispose() {
|
|
114
|
+
this.disposed = true;
|
|
115
|
+
this.listeners.clear();
|
|
116
|
+
}
|
|
117
|
+
active() {
|
|
118
|
+
return this.entries.filter((e) => e.item.status === 'uploading' || e.item.status === 'verifying').length;
|
|
119
|
+
}
|
|
120
|
+
pump() {
|
|
121
|
+
if (this.disposed)
|
|
122
|
+
return;
|
|
123
|
+
let free = this.concurrency - this.active();
|
|
124
|
+
for (const e of this.entries) {
|
|
125
|
+
if (free <= 0)
|
|
126
|
+
break;
|
|
127
|
+
if (e.item.status !== 'queued')
|
|
128
|
+
continue;
|
|
129
|
+
free--;
|
|
130
|
+
this.start(e);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
start(e) {
|
|
134
|
+
const run = ++e.run;
|
|
135
|
+
this.patch(e, { status: 'uploading', progress: 0, error: null });
|
|
136
|
+
let task;
|
|
137
|
+
try {
|
|
138
|
+
task = this.storage.startUpload({ bucket: e.source.bucket, path: e.source.path, file: e.source.file, overwrite: e.source.overwrite, metadata: e.source.metadata });
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
this.finish(e, run, 'failed', err);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
e.task = task;
|
|
145
|
+
task.onProgress((p) => {
|
|
146
|
+
if (e.run !== run || e.item.status === 'cancelled' || p.isDone)
|
|
147
|
+
return;
|
|
148
|
+
const status = p.state === 'verifying' ? 'verifying' : 'uploading';
|
|
149
|
+
if (status === e.item.status && p.fraction === e.item.progress)
|
|
150
|
+
return;
|
|
151
|
+
this.patch(e, { status, progress: p.fraction });
|
|
152
|
+
this.publish();
|
|
153
|
+
});
|
|
154
|
+
task.result.then(() => this.finish(e, run, 'done', null), (err) => this.finish(e, run, err instanceof storage_errors_js_1.KoolbaseUploadCancelledError ? 'cancelled' : 'failed', err));
|
|
155
|
+
}
|
|
156
|
+
finish(e, run, status, err) {
|
|
157
|
+
if (e.run !== run)
|
|
158
|
+
return;
|
|
159
|
+
e.task = null;
|
|
160
|
+
this.patch(e, { status, progress: status === 'done' ? 1 : status === 'cancelled' ? 0 : e.item.progress, error: status === 'failed' ? err : null });
|
|
161
|
+
this.pump();
|
|
162
|
+
this.publish();
|
|
163
|
+
}
|
|
164
|
+
patch(e, change) {
|
|
165
|
+
e.item = Object.freeze({ ...e.item, ...change });
|
|
166
|
+
}
|
|
167
|
+
publish() {
|
|
168
|
+
const items = Object.freeze(this.entries.map((e) => e.item));
|
|
169
|
+
const counted = items.filter((i) => i.status !== 'cancelled');
|
|
170
|
+
const running = items.some((i) => i.status === 'queued' || i.status === 'uploading' || i.status === 'verifying');
|
|
171
|
+
this.state = Object.freeze({
|
|
172
|
+
items,
|
|
173
|
+
progress: counted.length ? counted.reduce((s, i) => s + i.progress, 0) / counted.length : 0,
|
|
174
|
+
running,
|
|
175
|
+
done: items.filter((i) => i.status === 'done').length,
|
|
176
|
+
failed: items.filter((i) => i.status === 'failed').length,
|
|
177
|
+
});
|
|
178
|
+
if (!running && this.idleWaiters.length) {
|
|
179
|
+
const waiters = this.idleWaiters;
|
|
180
|
+
this.idleWaiters = [];
|
|
181
|
+
for (const w of waiters)
|
|
182
|
+
w();
|
|
183
|
+
}
|
|
184
|
+
if (this.disposed)
|
|
185
|
+
return;
|
|
186
|
+
for (const l of [...this.listeners])
|
|
187
|
+
l();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
exports.KoolbaseUploadQueue = KoolbaseUploadQueue;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export { KoolbaseFilesController, initialFilesState, filesQueryKey } from './fil
|
|
|
27
27
|
export type { FilesQuery, FilesState, FilesStatus } from './files-controller.js';
|
|
28
28
|
export { KoolbaseFileController, initialFileState } from './file-controller.js';
|
|
29
29
|
export type { FileState, FileStatus } from './file-controller.js';
|
|
30
|
+
export { KoolbaseUploadQueue, initialUploadQueueState } from './upload-queue.js';
|
|
31
|
+
export type { KoolbaseUploader, QueuedUpload, QueuedUploadStatus, QueueFile, UploadQueueState } from './upload-queue.js';
|
|
30
32
|
export { KoolbaseAnalytics, disabledAnalytics } from './analytics.js';
|
|
31
33
|
export { KoolbaseFiscal, KoolbaseFiscalError, decodeFiscalResponse, isFiscalized, isPending } from './fiscal.js';
|
|
32
34
|
export type { FiscalIntentResult, FiscalStatus } from './fiscal.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -27,6 +27,8 @@ export { KoolbaseUploadTask } from './storage-transfer.js';
|
|
|
27
27
|
export { KoolbaseFilesController, initialFilesState, filesQueryKey } from './files-controller.js';
|
|
28
28
|
// One file's details as state (storage S4).
|
|
29
29
|
export { KoolbaseFileController, initialFileState } from './file-controller.js';
|
|
30
|
+
// Several uploads in a queue (storage S4).
|
|
31
|
+
export { KoolbaseUploadQueue, initialUploadQueueState } from './upload-queue.js';
|
|
30
32
|
export { KoolbaseAnalytics, disabledAnalytics } from './analytics.js';
|
|
31
33
|
export { KoolbaseFiscal, KoolbaseFiscalError, decodeFiscalResponse, isFiscalized, isPending } from './fiscal.js';
|
|
32
34
|
export { KoolbaseMessaging } from './messaging.js';
|
|
@@ -2,3 +2,4 @@ export { createUseCollection, type UseCollectionResult } from './use-collection.
|
|
|
2
2
|
export { createUseRecord, type UseRecordResult } from './use-record.js';
|
|
3
3
|
export { createUseFiles, type UseFilesResult } from './use-files.js';
|
|
4
4
|
export { createUseFile, type UseFileResult } from './use-file.js';
|
|
5
|
+
export { createUseUploadQueue, type UseUploadQueueResult } from './use-upload-queue.js';
|
package/dist/esm/react/index.js
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type KoolbaseUploader, type QueueFile, type UploadQueueState } from '../index.js';
|
|
2
|
+
export interface UseUploadQueueResult extends UploadQueueState {
|
|
3
|
+
/** Adds files and starts as many as may run; returns their ids. */
|
|
4
|
+
add: (files: readonly QueueFile[]) => string[];
|
|
5
|
+
/** Stops one file. */
|
|
6
|
+
cancel: (id: string) => boolean;
|
|
7
|
+
/** Stops every file that can still be stopped. */
|
|
8
|
+
cancelAll: () => void;
|
|
9
|
+
/** Tries a failed or cancelled file again. */
|
|
10
|
+
retry: (id: string) => boolean;
|
|
11
|
+
/** Removes the files that have finished. */
|
|
12
|
+
clearFinished: () => void;
|
|
13
|
+
/** Resolves once nothing is running. */
|
|
14
|
+
whenIdle: () => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare function createUseUploadQueue(getStorage: () => KoolbaseUploader): (options?: {
|
|
17
|
+
concurrency?: number;
|
|
18
|
+
}) => UseUploadQueueResult;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// useUploadQueue: several uploads as React state (storage S4).
|
|
2
|
+
//
|
|
3
|
+
// A thin wrapper. Everything about the queue lives in @koolbase/core's
|
|
4
|
+
// KoolbaseUploadQueue, which is tested there; this file only gives a
|
|
5
|
+
// component one queue for its lifetime. Uploads still running when the
|
|
6
|
+
// component goes finish on their own.
|
|
7
|
+
import { useCallback, useRef, useSyncExternalStore } from 'react';
|
|
8
|
+
import { KoolbaseUploadQueue } from '../index.js';
|
|
9
|
+
export function createUseUploadQueue(getStorage) {
|
|
10
|
+
return function useUploadQueue(options = {}) {
|
|
11
|
+
const ref = useRef(null);
|
|
12
|
+
if (ref.current === null)
|
|
13
|
+
ref.current = new KoolbaseUploadQueue(getStorage(), options);
|
|
14
|
+
const queue = ref.current;
|
|
15
|
+
const subscribe = useCallback((onChange) => queue.subscribe(onChange), [queue]);
|
|
16
|
+
const getSnapshot = useCallback(() => queue.getState(), [queue]);
|
|
17
|
+
const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
18
|
+
return {
|
|
19
|
+
...state,
|
|
20
|
+
add: useCallback((files) => queue.add(files), [queue]),
|
|
21
|
+
cancel: useCallback((id) => queue.cancel(id), [queue]),
|
|
22
|
+
cancelAll: useCallback(() => queue.cancelAll(), [queue]),
|
|
23
|
+
retry: useCallback((id) => queue.retry(id), [queue]),
|
|
24
|
+
clearFinished: useCallback(() => queue.clearFinished(), [queue]),
|
|
25
|
+
whenIdle: useCallback(() => queue.whenIdle(), [queue]),
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { KoolbaseUploadTask } from './storage-transfer.js';
|
|
2
|
+
import type { UploadOptions } from './types.js';
|
|
3
|
+
export type QueuedUploadStatus = 'queued' | 'uploading' | 'verifying' | 'done' | 'failed' | 'cancelled';
|
|
4
|
+
/** One file in the queue. */
|
|
5
|
+
export interface QueuedUpload {
|
|
6
|
+
/** The queue's own id for it: what cancel and retry take. */
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly bucket: string;
|
|
9
|
+
/** Where it goes in the bucket. */
|
|
10
|
+
readonly path: string;
|
|
11
|
+
/** What a person calls it: the name given, else the path's last part. */
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly status: QueuedUploadStatus;
|
|
14
|
+
/** 0 to 1: the share of its bytes sent; 1 once verifying. */
|
|
15
|
+
readonly progress: number;
|
|
16
|
+
/** Why it failed, while status is 'failed'. */
|
|
17
|
+
readonly error: unknown;
|
|
18
|
+
}
|
|
19
|
+
export interface UploadQueueState {
|
|
20
|
+
readonly items: readonly QueuedUpload[];
|
|
21
|
+
/** 0 to 1: the files' average progress, a cancelled one not counted. */
|
|
22
|
+
readonly progress: number;
|
|
23
|
+
/** Whether any file is still queued, uploading or verifying. */
|
|
24
|
+
readonly running: boolean;
|
|
25
|
+
/** How many have landed, and how many failed. */
|
|
26
|
+
readonly done: number;
|
|
27
|
+
readonly failed: number;
|
|
28
|
+
}
|
|
29
|
+
export declare const initialUploadQueueState: UploadQueueState;
|
|
30
|
+
/** A file to add: where it goes, and the bytes as startUpload takes them. */
|
|
31
|
+
export interface QueueFile {
|
|
32
|
+
bucket: string;
|
|
33
|
+
path: string;
|
|
34
|
+
file: UploadOptions['file'];
|
|
35
|
+
/** Shown for it; the path's last part unless set. */
|
|
36
|
+
name?: string;
|
|
37
|
+
overwrite?: boolean;
|
|
38
|
+
metadata?: UploadOptions['metadata'];
|
|
39
|
+
}
|
|
40
|
+
type QueueTask = Pick<KoolbaseUploadTask, 'onProgress' | 'cancel' | 'result'>;
|
|
41
|
+
/** What the queue needs from storage: startUpload. */
|
|
42
|
+
export type KoolbaseUploader = {
|
|
43
|
+
startUpload(options: UploadOptions): QueueTask;
|
|
44
|
+
};
|
|
45
|
+
export declare class KoolbaseUploadQueue {
|
|
46
|
+
readonly concurrency: number;
|
|
47
|
+
private readonly storage;
|
|
48
|
+
private entries;
|
|
49
|
+
private state;
|
|
50
|
+
private readonly listeners;
|
|
51
|
+
private idleWaiters;
|
|
52
|
+
private nextId;
|
|
53
|
+
private disposed;
|
|
54
|
+
constructor(storage: KoolbaseUploader, options?: {
|
|
55
|
+
concurrency?: number;
|
|
56
|
+
});
|
|
57
|
+
getState(): UploadQueueState;
|
|
58
|
+
/** Called on every change. Returns the unsubscribe. */
|
|
59
|
+
subscribe(listener: () => void): () => void;
|
|
60
|
+
/** Adds files to the end of the queue and starts as many as may run. Returns their ids. */
|
|
61
|
+
add(files: readonly QueueFile[]): string[];
|
|
62
|
+
/** Stops one file: a queued one never starts; one uploading stops. Returns whether it was stopped. */
|
|
63
|
+
cancel(id: string): boolean;
|
|
64
|
+
/** Stops every file that can still be stopped. */
|
|
65
|
+
cancelAll(): void;
|
|
66
|
+
/** Tries a failed or cancelled file again, from the start. Returns whether it was queued. */
|
|
67
|
+
retry(id: string): boolean;
|
|
68
|
+
/** Removes the files that have finished: landed, failed or cancelled. */
|
|
69
|
+
clearFinished(): void;
|
|
70
|
+
/** Resolves once nothing is queued, uploading or verifying. */
|
|
71
|
+
whenIdle(): Promise<void>;
|
|
72
|
+
/** Stops reporting. Uploads already running finish on their own. */
|
|
73
|
+
dispose(): void;
|
|
74
|
+
private active;
|
|
75
|
+
private pump;
|
|
76
|
+
private start;
|
|
77
|
+
private finish;
|
|
78
|
+
private patch;
|
|
79
|
+
private publish;
|
|
80
|
+
}
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Several uploads in a queue, framework-free (storage S4).
|
|
2
|
+
//
|
|
3
|
+
// Files added together go a few at a time (three unless set), each its own
|
|
4
|
+
// SDK upload task -- so a large one still goes in parts, and a bucket's
|
|
5
|
+
// file lists still reload as each lands. Every file has its own state and
|
|
6
|
+
// progress, and can be cancelled or tried again on its own. No React here,
|
|
7
|
+
// so it is tested in this package; useUploadQueue in @koolbase/react-native
|
|
8
|
+
// and @koolbase/js/react is a thin wrapper. Mirrors the Flutter SDK's
|
|
9
|
+
// KoolbaseUploadQueue.
|
|
10
|
+
//
|
|
11
|
+
// - queued -> uploading -> verifying -> done, or failed / cancelled
|
|
12
|
+
// - a queued file cancelled never starts; one uploading stops (once the
|
|
13
|
+
// server is verifying it, it lands or fails on its own)
|
|
14
|
+
// - a failed or cancelled file can be tried again: it queues once more,
|
|
15
|
+
// from the start, under the same path
|
|
16
|
+
// - a file that fails never stops the others
|
|
17
|
+
// - progress is the files' average, a cancelled one not counted
|
|
18
|
+
import { KoolbaseUploadCancelledError } from './storage-errors.js';
|
|
19
|
+
export const initialUploadQueueState = Object.freeze({
|
|
20
|
+
items: Object.freeze([]),
|
|
21
|
+
progress: 0,
|
|
22
|
+
running: false,
|
|
23
|
+
done: 0,
|
|
24
|
+
failed: 0,
|
|
25
|
+
});
|
|
26
|
+
export class KoolbaseUploadQueue {
|
|
27
|
+
constructor(storage, options = {}) {
|
|
28
|
+
this.entries = [];
|
|
29
|
+
this.state = initialUploadQueueState;
|
|
30
|
+
this.listeners = new Set();
|
|
31
|
+
this.idleWaiters = [];
|
|
32
|
+
this.nextId = 0;
|
|
33
|
+
this.disposed = false;
|
|
34
|
+
this.storage = storage;
|
|
35
|
+
this.concurrency = Math.max(1, Math.floor(options.concurrency ?? 3));
|
|
36
|
+
}
|
|
37
|
+
getState() {
|
|
38
|
+
return this.state;
|
|
39
|
+
}
|
|
40
|
+
/** Called on every change. Returns the unsubscribe. */
|
|
41
|
+
subscribe(listener) {
|
|
42
|
+
this.listeners.add(listener);
|
|
43
|
+
return () => {
|
|
44
|
+
this.listeners.delete(listener);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Adds files to the end of the queue and starts as many as may run. Returns their ids. */
|
|
48
|
+
add(files) {
|
|
49
|
+
if (this.disposed)
|
|
50
|
+
return [];
|
|
51
|
+
const ids = [];
|
|
52
|
+
for (const source of files) {
|
|
53
|
+
const id = `u${++this.nextId}`;
|
|
54
|
+
ids.push(id);
|
|
55
|
+
this.entries.push({
|
|
56
|
+
item: { id, bucket: source.bucket, path: source.path, name: source.name || source.path.split('/').pop() || source.path, status: 'queued', progress: 0, error: null },
|
|
57
|
+
source,
|
|
58
|
+
task: null,
|
|
59
|
+
run: 0,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
this.pump();
|
|
63
|
+
this.publish();
|
|
64
|
+
return ids;
|
|
65
|
+
}
|
|
66
|
+
/** Stops one file: a queued one never starts; one uploading stops. Returns whether it was stopped. */
|
|
67
|
+
cancel(id) {
|
|
68
|
+
const e = this.entries.find((x) => x.item.id === id);
|
|
69
|
+
if (!e)
|
|
70
|
+
return false;
|
|
71
|
+
if (e.item.status === 'queued') {
|
|
72
|
+
this.patch(e, { status: 'cancelled', progress: 0 });
|
|
73
|
+
this.publish();
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (e.item.status === 'uploading')
|
|
77
|
+
return e.task?.cancel() ?? false;
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
/** Stops every file that can still be stopped. */
|
|
81
|
+
cancelAll() {
|
|
82
|
+
for (const e of [...this.entries])
|
|
83
|
+
this.cancel(e.item.id);
|
|
84
|
+
}
|
|
85
|
+
/** Tries a failed or cancelled file again, from the start. Returns whether it was queued. */
|
|
86
|
+
retry(id) {
|
|
87
|
+
const e = this.entries.find((x) => x.item.id === id);
|
|
88
|
+
if (!e || (e.item.status !== 'failed' && e.item.status !== 'cancelled') || this.disposed)
|
|
89
|
+
return false;
|
|
90
|
+
e.task = null;
|
|
91
|
+
this.patch(e, { status: 'queued', progress: 0, error: null });
|
|
92
|
+
this.pump();
|
|
93
|
+
this.publish();
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
/** Removes the files that have finished: landed, failed or cancelled. */
|
|
97
|
+
clearFinished() {
|
|
98
|
+
const before = this.entries.length;
|
|
99
|
+
this.entries = this.entries.filter((e) => e.item.status === 'queued' || e.item.status === 'uploading' || e.item.status === 'verifying');
|
|
100
|
+
if (this.entries.length !== before)
|
|
101
|
+
this.publish();
|
|
102
|
+
}
|
|
103
|
+
/** Resolves once nothing is queued, uploading or verifying. */
|
|
104
|
+
whenIdle() {
|
|
105
|
+
if (!this.state.running)
|
|
106
|
+
return Promise.resolve();
|
|
107
|
+
return new Promise((resolve) => this.idleWaiters.push(resolve));
|
|
108
|
+
}
|
|
109
|
+
/** Stops reporting. Uploads already running finish on their own. */
|
|
110
|
+
dispose() {
|
|
111
|
+
this.disposed = true;
|
|
112
|
+
this.listeners.clear();
|
|
113
|
+
}
|
|
114
|
+
active() {
|
|
115
|
+
return this.entries.filter((e) => e.item.status === 'uploading' || e.item.status === 'verifying').length;
|
|
116
|
+
}
|
|
117
|
+
pump() {
|
|
118
|
+
if (this.disposed)
|
|
119
|
+
return;
|
|
120
|
+
let free = this.concurrency - this.active();
|
|
121
|
+
for (const e of this.entries) {
|
|
122
|
+
if (free <= 0)
|
|
123
|
+
break;
|
|
124
|
+
if (e.item.status !== 'queued')
|
|
125
|
+
continue;
|
|
126
|
+
free--;
|
|
127
|
+
this.start(e);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
start(e) {
|
|
131
|
+
const run = ++e.run;
|
|
132
|
+
this.patch(e, { status: 'uploading', progress: 0, error: null });
|
|
133
|
+
let task;
|
|
134
|
+
try {
|
|
135
|
+
task = this.storage.startUpload({ bucket: e.source.bucket, path: e.source.path, file: e.source.file, overwrite: e.source.overwrite, metadata: e.source.metadata });
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
this.finish(e, run, 'failed', err);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
e.task = task;
|
|
142
|
+
task.onProgress((p) => {
|
|
143
|
+
if (e.run !== run || e.item.status === 'cancelled' || p.isDone)
|
|
144
|
+
return;
|
|
145
|
+
const status = p.state === 'verifying' ? 'verifying' : 'uploading';
|
|
146
|
+
if (status === e.item.status && p.fraction === e.item.progress)
|
|
147
|
+
return;
|
|
148
|
+
this.patch(e, { status, progress: p.fraction });
|
|
149
|
+
this.publish();
|
|
150
|
+
});
|
|
151
|
+
task.result.then(() => this.finish(e, run, 'done', null), (err) => this.finish(e, run, err instanceof KoolbaseUploadCancelledError ? 'cancelled' : 'failed', err));
|
|
152
|
+
}
|
|
153
|
+
finish(e, run, status, err) {
|
|
154
|
+
if (e.run !== run)
|
|
155
|
+
return;
|
|
156
|
+
e.task = null;
|
|
157
|
+
this.patch(e, { status, progress: status === 'done' ? 1 : status === 'cancelled' ? 0 : e.item.progress, error: status === 'failed' ? err : null });
|
|
158
|
+
this.pump();
|
|
159
|
+
this.publish();
|
|
160
|
+
}
|
|
161
|
+
patch(e, change) {
|
|
162
|
+
e.item = Object.freeze({ ...e.item, ...change });
|
|
163
|
+
}
|
|
164
|
+
publish() {
|
|
165
|
+
const items = Object.freeze(this.entries.map((e) => e.item));
|
|
166
|
+
const counted = items.filter((i) => i.status !== 'cancelled');
|
|
167
|
+
const running = items.some((i) => i.status === 'queued' || i.status === 'uploading' || i.status === 'verifying');
|
|
168
|
+
this.state = Object.freeze({
|
|
169
|
+
items,
|
|
170
|
+
progress: counted.length ? counted.reduce((s, i) => s + i.progress, 0) / counted.length : 0,
|
|
171
|
+
running,
|
|
172
|
+
done: items.filter((i) => i.status === 'done').length,
|
|
173
|
+
failed: items.filter((i) => i.status === 'failed').length,
|
|
174
|
+
});
|
|
175
|
+
if (!running && this.idleWaiters.length) {
|
|
176
|
+
const waiters = this.idleWaiters;
|
|
177
|
+
this.idleWaiters = [];
|
|
178
|
+
for (const w of waiters)
|
|
179
|
+
w();
|
|
180
|
+
}
|
|
181
|
+
if (this.disposed)
|
|
182
|
+
return;
|
|
183
|
+
for (const l of [...this.listeners])
|
|
184
|
+
l();
|
|
185
|
+
}
|
|
186
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koolbase/core",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.21.0",
|
|
4
4
|
"description": "Koolbase SDK core \u2014 shared behaviour behind @koolbase/react-native and @koolbase/js. Install one of those, not this.",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"types": "./dist/esm/index.d.ts",
|