@chunkflowjs/core 0.0.1-alpha.5 → 0.0.1-alpha.6
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/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/xhr-adapter.d.ts +11 -0
- package/dist/adapters/xhr-adapter.d.ts.map +1 -0
- package/dist/index.d.mts +12 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +133 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAIA,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAIA,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RequestAdapter } from "@chunkflowjs/protocol";
|
|
2
|
+
export interface XHRAdapterOptions {
|
|
3
|
+
baseURL: string;
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
withCredentials?: boolean;
|
|
7
|
+
onUploadProgress?: (event: ProgressEvent) => void;
|
|
8
|
+
onError?: (error: Error) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function createXHRAdapter(options: XHRAdapterOptions): RequestAdapter;
|
|
11
|
+
//# sourceMappingURL=xhr-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xhr-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/xhr-adapter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAK5D,MAAM,WAAW,iBAAiB;IAEhC,OAAO,EAAE,MAAM,CAAC;IAEhB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAElD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAuBD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CA2M3E"}
|
package/dist/index.d.mts
CHANGED
|
@@ -252,5 +252,16 @@ interface FetchAdapterOptions {
|
|
|
252
252
|
}
|
|
253
253
|
declare function createFetchAdapter(options: FetchAdapterOptions): RequestAdapter;
|
|
254
254
|
//#endregion
|
|
255
|
-
|
|
255
|
+
//#region src/adapters/xhr-adapter.d.ts
|
|
256
|
+
interface XHRAdapterOptions {
|
|
257
|
+
baseURL: string;
|
|
258
|
+
headers?: Record<string, string>;
|
|
259
|
+
timeout?: number;
|
|
260
|
+
withCredentials?: boolean;
|
|
261
|
+
onUploadProgress?: (event: ProgressEvent) => void;
|
|
262
|
+
onError?: (error: Error) => void;
|
|
263
|
+
}
|
|
264
|
+
declare function createXHRAdapter(options: XHRAdapterOptions): RequestAdapter;
|
|
265
|
+
//#endregion
|
|
266
|
+
export { BaseChunkSizeAdjusterOptions, ChunkSizeAdjuster, ChunkSizeAdjusterOptions, CongestionState, FetchAdapterOptions, IChunkSizeAdjuster, LoggerPlugin, Plugin, StatisticsPlugin, TCPChunkSizeAdjuster, TCPChunkSizeAdjusterOptions, UploadManager, UploadManagerOptions, UploadProgress, UploadTask, UploadTaskOptions, XHRAdapterOptions, createFetchAdapter, createXHRAdapter };
|
|
256
267
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/chunk-size-adjuster-interface.ts","../src/chunk-size-adjuster.ts","../src/chunk-size-adjuster-tcp.ts","../src/upload-task.ts","../src/upload-manager.ts","../src/plugins.ts","../src/adapters/fetch-adapter.ts"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/chunk-size-adjuster-interface.ts","../src/chunk-size-adjuster.ts","../src/chunk-size-adjuster-tcp.ts","../src/upload-task.ts","../src/upload-manager.ts","../src/plugins.ts","../src/adapters/fetch-adapter.ts","../src/adapters/xhr-adapter.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -2100,5 +2100,137 @@ function createFetchAdapter(options) {
|
|
|
2100
2100
|
}
|
|
2101
2101
|
|
|
2102
2102
|
//#endregion
|
|
2103
|
-
|
|
2103
|
+
//#region src/adapters/xhr-adapter.ts
|
|
2104
|
+
/**
|
|
2105
|
+
* Create an XMLHttpRequest-based RequestAdapter
|
|
2106
|
+
*
|
|
2107
|
+
* @param options - Configuration options
|
|
2108
|
+
* @returns RequestAdapter instance
|
|
2109
|
+
*
|
|
2110
|
+
* @example
|
|
2111
|
+
* ```typescript
|
|
2112
|
+
* const adapter = createXHRAdapter({
|
|
2113
|
+
* baseURL: 'http://localhost:3000/api',
|
|
2114
|
+
* headers: {
|
|
2115
|
+
* 'Authorization': 'Bearer token123'
|
|
2116
|
+
* },
|
|
2117
|
+
* onUploadProgress: (event) => {
|
|
2118
|
+
* console.log(`Upload progress: ${(event.loaded / event.total) * 100}%`);
|
|
2119
|
+
* }
|
|
2120
|
+
* });
|
|
2121
|
+
*
|
|
2122
|
+
* const manager = new UploadManager({ requestAdapter: adapter });
|
|
2123
|
+
* ```
|
|
2124
|
+
*/
|
|
2125
|
+
function createXHRAdapter(options) {
|
|
2126
|
+
const { baseURL, headers = {}, timeout = 3e4, withCredentials = false, onUploadProgress, onError } = options;
|
|
2127
|
+
const normalizedBaseURL = baseURL.replace(/\/$/, "");
|
|
2128
|
+
/**
|
|
2129
|
+
* Make an XHR request
|
|
2130
|
+
*/
|
|
2131
|
+
function makeRequest(method, url, data, customHeaders) {
|
|
2132
|
+
return new Promise((resolve, reject) => {
|
|
2133
|
+
const xhr = new XMLHttpRequest();
|
|
2134
|
+
xhr.open(method, url, true);
|
|
2135
|
+
xhr.timeout = timeout;
|
|
2136
|
+
xhr.withCredentials = withCredentials;
|
|
2137
|
+
const allHeaders = {
|
|
2138
|
+
...headers,
|
|
2139
|
+
...customHeaders
|
|
2140
|
+
};
|
|
2141
|
+
Object.entries(allHeaders).forEach(([key, value]) => {
|
|
2142
|
+
xhr.setRequestHeader(key, value);
|
|
2143
|
+
});
|
|
2144
|
+
if (onUploadProgress && xhr.upload) xhr.upload.addEventListener("progress", onUploadProgress);
|
|
2145
|
+
xhr.onload = () => {
|
|
2146
|
+
if (xhr.status >= 200 && xhr.status < 300) try {
|
|
2147
|
+
resolve(JSON.parse(xhr.responseText));
|
|
2148
|
+
} catch (error) {
|
|
2149
|
+
const parseError = /* @__PURE__ */ new Error(`Failed to parse response: ${error.message}`);
|
|
2150
|
+
if (onError) onError(parseError);
|
|
2151
|
+
reject(parseError);
|
|
2152
|
+
}
|
|
2153
|
+
else {
|
|
2154
|
+
let errorMessage = `HTTP ${xhr.status}: ${xhr.statusText}`;
|
|
2155
|
+
try {
|
|
2156
|
+
const errorResponse = JSON.parse(xhr.responseText);
|
|
2157
|
+
if (errorResponse.error?.message) errorMessage = errorResponse.error.message;
|
|
2158
|
+
} catch {}
|
|
2159
|
+
const error = new Error(errorMessage);
|
|
2160
|
+
if (onError) onError(error);
|
|
2161
|
+
reject(error);
|
|
2162
|
+
}
|
|
2163
|
+
};
|
|
2164
|
+
xhr.onerror = () => {
|
|
2165
|
+
const error = /* @__PURE__ */ new Error("Network error");
|
|
2166
|
+
if (onError) onError(error);
|
|
2167
|
+
reject(error);
|
|
2168
|
+
};
|
|
2169
|
+
xhr.ontimeout = () => {
|
|
2170
|
+
const error = /* @__PURE__ */ new Error(`Request timeout after ${timeout}ms`);
|
|
2171
|
+
if (onError) onError(error);
|
|
2172
|
+
reject(error);
|
|
2173
|
+
};
|
|
2174
|
+
xhr.onabort = () => {
|
|
2175
|
+
const error = /* @__PURE__ */ new Error("Request aborted");
|
|
2176
|
+
if (onError) onError(error);
|
|
2177
|
+
reject(error);
|
|
2178
|
+
};
|
|
2179
|
+
if (data instanceof FormData) xhr.send(data);
|
|
2180
|
+
else if (data) xhr.send(JSON.stringify(data));
|
|
2181
|
+
else xhr.send();
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
return {
|
|
2185
|
+
async createFile(request) {
|
|
2186
|
+
const response = await makeRequest("POST", `${normalizedBaseURL}/upload/create`, {
|
|
2187
|
+
fileName: request.fileName,
|
|
2188
|
+
fileSize: request.fileSize,
|
|
2189
|
+
fileType: request.fileType,
|
|
2190
|
+
preferredChunkSize: request.preferredChunkSize
|
|
2191
|
+
}, { "Content-Type": "application/json" });
|
|
2192
|
+
return {
|
|
2193
|
+
uploadToken: {
|
|
2194
|
+
token: response.uploadToken,
|
|
2195
|
+
fileId: "",
|
|
2196
|
+
chunkSize: response.negotiatedChunkSize,
|
|
2197
|
+
expiresAt: Date.now() + 1440 * 60 * 1e3
|
|
2198
|
+
},
|
|
2199
|
+
negotiatedChunkSize: response.negotiatedChunkSize
|
|
2200
|
+
};
|
|
2201
|
+
},
|
|
2202
|
+
async verifyHash(request) {
|
|
2203
|
+
const token = typeof request.uploadToken === "string" ? request.uploadToken : request.uploadToken.token;
|
|
2204
|
+
return makeRequest("POST", `${normalizedBaseURL}/upload/verify`, {
|
|
2205
|
+
fileHash: request.fileHash,
|
|
2206
|
+
chunkHashes: request.chunkHashes,
|
|
2207
|
+
uploadToken: token
|
|
2208
|
+
}, { "Content-Type": "application/json" });
|
|
2209
|
+
},
|
|
2210
|
+
async uploadChunk(request) {
|
|
2211
|
+
const token = typeof request.uploadToken === "string" ? request.uploadToken : request.uploadToken.token;
|
|
2212
|
+
const formData = new FormData();
|
|
2213
|
+
formData.append("uploadToken", token);
|
|
2214
|
+
formData.append("chunkIndex", request.chunkIndex.toString());
|
|
2215
|
+
formData.append("chunkHash", request.chunkHash);
|
|
2216
|
+
if (request.chunk instanceof Blob) formData.append("chunk", request.chunk);
|
|
2217
|
+
else {
|
|
2218
|
+
const blob = new Blob([request.chunk]);
|
|
2219
|
+
formData.append("chunk", blob);
|
|
2220
|
+
}
|
|
2221
|
+
return makeRequest("POST", `${normalizedBaseURL}/upload/chunk`, formData);
|
|
2222
|
+
},
|
|
2223
|
+
async mergeFile(request) {
|
|
2224
|
+
const token = typeof request.uploadToken === "string" ? request.uploadToken : request.uploadToken.token;
|
|
2225
|
+
return makeRequest("POST", `${normalizedBaseURL}/upload/merge`, {
|
|
2226
|
+
uploadToken: token,
|
|
2227
|
+
fileHash: request.fileHash,
|
|
2228
|
+
chunkHashes: request.chunkHashes
|
|
2229
|
+
}, { "Content-Type": "application/json" });
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
//#endregion
|
|
2235
|
+
export { ChunkSizeAdjuster, CongestionState, LoggerPlugin, StatisticsPlugin, TCPChunkSizeAdjuster, UploadManager, UploadTask, createFetchAdapter, createXHRAdapter };
|
|
2104
2236
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/chunk-size-adjuster.ts","../src/chunk-size-adjuster-tcp.ts","../src/upload-task.ts","../src/upload-manager.ts","../src/plugins.ts","../src/adapters/fetch-adapter.ts"],"sourcesContent":["import type {\n IChunkSizeAdjuster,\n BaseChunkSizeAdjusterOptions,\n} from \"./chunk-size-adjuster-interface\";\n\n/**\n * Options for configuring the ChunkSizeAdjuster\n */\nexport interface ChunkSizeAdjusterOptions extends BaseChunkSizeAdjusterOptions {}\n\n/**\n * ChunkSizeAdjuster dynamically adjusts chunk sizes based on upload performance.\n * Uses a simple binary strategy: doubles size when fast, halves when slow.\n *\n * @example\n * ```typescript\n * const adjuster = new ChunkSizeAdjuster({\n * initialSize: 1024 * 1024, // 1MB\n * minSize: 256 * 1024, // 256KB\n * maxSize: 10 * 1024 * 1024, // 10MB\n * targetTime: 3000 // 3 seconds\n * });\n *\n * // After uploading a chunk\n * const uploadTimeMs = 1500;\n * const newSize = adjuster.adjust(uploadTimeMs);\n * ```\n */\nexport class ChunkSizeAdjuster implements IChunkSizeAdjuster {\n private currentSize: number;\n private readonly options: Required<ChunkSizeAdjusterOptions>;\n\n constructor(options: ChunkSizeAdjusterOptions) {\n this.currentSize = options.initialSize;\n this.options = {\n targetTime: 3000, // Default 3 seconds\n ...options,\n };\n\n // Validate options\n if (this.options.minSize > this.options.maxSize) {\n throw new Error(\"minSize cannot be greater than maxSize\");\n }\n if (\n this.options.initialSize < this.options.minSize ||\n this.options.initialSize > this.options.maxSize\n ) {\n throw new Error(\"initialSize must be between minSize and maxSize\");\n }\n if (this.options.targetTime <= 0) {\n throw new Error(\"targetTime must be positive\");\n }\n }\n\n /**\n * Adjusts the chunk size based on the upload time of the previous chunk.\n * Uses a simple binary strategy:\n * - If upload is fast (< 50% of target time): double the chunk size\n * - If upload is slow (> 150% of target time): halve the chunk size\n * - Otherwise: keep the current size\n *\n * @param uploadTimeMs - The time taken to upload the previous chunk in milliseconds\n * @returns The new chunk size in bytes\n */\n adjust(uploadTimeMs: number): number {\n if (uploadTimeMs < 0) {\n throw new Error(\"uploadTimeMs cannot be negative\");\n }\n\n const { targetTime, minSize, maxSize } = this.options;\n\n // Fast upload: increase chunk size (similar to TCP slow start)\n if (uploadTimeMs < targetTime * 0.5) {\n this.currentSize = Math.min(this.currentSize * 2, maxSize);\n }\n // Slow upload: decrease chunk size (congestion avoidance)\n else if (uploadTimeMs > targetTime * 1.5) {\n this.currentSize = Math.max(this.currentSize / 2, minSize);\n }\n // Upload time is within acceptable range: keep current size\n\n return this.currentSize;\n }\n\n /**\n * Gets the current chunk size without adjusting it.\n *\n * @returns The current chunk size in bytes\n */\n getCurrentSize(): number {\n return this.currentSize;\n }\n\n /**\n * Resets the chunk size to the initial size.\n * Useful when starting a new upload or after an error.\n */\n reset(): void {\n this.currentSize = this.options.initialSize;\n }\n}\n","import type {\n IChunkSizeAdjuster,\n BaseChunkSizeAdjusterOptions,\n} from \"./chunk-size-adjuster-interface\";\n\n/**\n * TCP Slow Start inspired chunk size adjuster\n * Implements a more accurate TCP-like congestion control algorithm\n */\n\nexport interface TCPChunkSizeAdjusterOptions extends BaseChunkSizeAdjusterOptions {\n /**\n * Initial slow start threshold (default: maxSize / 2)\n */\n initialSsthresh?: number;\n}\n\nexport enum CongestionState {\n SLOW_START = \"slow_start\",\n CONGESTION_AVOIDANCE = \"congestion_avoidance\",\n FAST_RECOVERY = \"fast_recovery\",\n}\n\n/**\n * TCP-inspired chunk size adjuster with proper slow start and congestion avoidance\n *\n * Algorithm:\n * 1. Slow Start: Exponential growth (double size) until ssthresh\n * 2. Congestion Avoidance: Linear growth (add increment) after ssthresh\n * 3. Fast Recovery: On congestion, set ssthresh = currentSize / 2, reduce size\n *\n * @example\n * ```typescript\n * const adjuster = new TCPChunkSizeAdjuster({\n * initialSize: 256 * 1024, // 256KB (like TCP initial cwnd)\n * minSize: 256 * 1024, // 256KB\n * maxSize: 10 * 1024 * 1024, // 10MB\n * targetTime: 3000, // 3 seconds\n * initialSsthresh: 5 * 1024 * 1024 // 5MB\n * });\n *\n * // After each chunk upload\n * const newSize = adjuster.adjust(uploadTimeMs);\n * ```\n */\nexport class TCPChunkSizeAdjuster implements IChunkSizeAdjuster {\n private currentSize: number;\n private ssthresh: number; // Slow start threshold\n private state: CongestionState;\n private readonly options: Required<TCPChunkSizeAdjusterOptions>;\n private consecutiveFastUploads: number = 0;\n private consecutiveSlowUploads: number = 0;\n\n constructor(options: TCPChunkSizeAdjusterOptions) {\n this.currentSize = options.initialSize;\n this.options = {\n targetTime: 3000,\n initialSsthresh: options.initialSsthresh ?? options.maxSize / 2,\n ...options,\n };\n this.ssthresh = this.options.initialSsthresh;\n this.state = CongestionState.SLOW_START;\n\n this.validate();\n }\n\n private validate(): void {\n const { minSize, maxSize, initialSize, targetTime, initialSsthresh } = this.options;\n\n if (minSize > maxSize) {\n throw new Error(\"minSize cannot be greater than maxSize\");\n }\n if (initialSize < minSize || initialSize > maxSize) {\n throw new Error(\"initialSize must be between minSize and maxSize\");\n }\n if (targetTime <= 0) {\n throw new Error(\"targetTime must be positive\");\n }\n if (initialSsthresh < minSize || initialSsthresh > maxSize) {\n throw new Error(\"initialSsthresh must be between minSize and maxSize\");\n }\n }\n\n /**\n * Adjusts chunk size based on upload performance using TCP-like algorithm\n *\n * @param uploadTimeMs - Time taken to upload the previous chunk\n * @returns New chunk size in bytes\n */\n adjust(uploadTimeMs: number): number {\n if (uploadTimeMs < 0) {\n throw new Error(\"uploadTimeMs cannot be negative\");\n }\n\n const { targetTime, minSize, maxSize } = this.options;\n const ratio = uploadTimeMs / targetTime;\n\n // Fast upload (< 50% of target time)\n if (ratio < 0.5) {\n this.consecutiveFastUploads++;\n this.consecutiveSlowUploads = 0;\n this.handleFastUpload();\n }\n // Slow upload (> 150% of target time) - congestion detected\n else if (ratio > 1.5) {\n this.consecutiveSlowUploads++;\n this.consecutiveFastUploads = 0;\n this.handleSlowUpload();\n }\n // Normal upload - maintain current size\n else {\n this.consecutiveFastUploads = 0;\n this.consecutiveSlowUploads = 0;\n }\n\n // Ensure size is within bounds\n this.currentSize = Math.max(minSize, Math.min(this.currentSize, maxSize));\n\n return this.currentSize;\n }\n\n private handleFastUpload(): void {\n const { maxSize } = this.options;\n\n switch (this.state) {\n case CongestionState.SLOW_START:\n // Exponential growth: double the size\n const newSize = this.currentSize * 2;\n\n if (newSize >= this.ssthresh) {\n // Reached ssthresh, switch to congestion avoidance\n this.currentSize = this.ssthresh;\n this.state = CongestionState.CONGESTION_AVOIDANCE;\n } else {\n this.currentSize = Math.min(newSize, maxSize);\n }\n break;\n\n case CongestionState.CONGESTION_AVOIDANCE:\n // Linear growth: add a fraction of current size\n // Similar to TCP's cwnd += MSS * MSS / cwnd\n const increment = Math.max(\n this.options.minSize,\n Math.floor(this.currentSize * 0.1), // 10% increment\n );\n this.currentSize = Math.min(this.currentSize + increment, maxSize);\n break;\n\n case CongestionState.FAST_RECOVERY:\n // Exit fast recovery, enter congestion avoidance\n this.state = CongestionState.CONGESTION_AVOIDANCE;\n break;\n }\n }\n\n private handleSlowUpload(): void {\n const { minSize } = this.options;\n\n // Congestion detected - similar to TCP's multiplicative decrease\n // Set ssthresh to half of current size\n this.ssthresh = Math.max(minSize, Math.floor(this.currentSize / 2));\n\n // Reduce current size\n this.currentSize = this.ssthresh;\n\n // Enter fast recovery state\n this.state = CongestionState.FAST_RECOVERY;\n }\n\n /**\n * Gets the current chunk size\n */\n getCurrentSize(): number {\n return this.currentSize;\n }\n\n /**\n * Gets the current slow start threshold\n */\n getSsthresh(): number {\n return this.ssthresh;\n }\n\n /**\n * Gets the current congestion state\n */\n getState(): CongestionState {\n return this.state;\n }\n\n /**\n * Resets to initial state\n */\n reset(): void {\n this.currentSize = this.options.initialSize;\n this.ssthresh = this.options.initialSsthresh;\n this.state = CongestionState.SLOW_START;\n this.consecutiveFastUploads = 0;\n this.consecutiveSlowUploads = 0;\n }\n\n /**\n * Gets statistics about the adjuster's behavior\n */\n getStats() {\n return {\n currentSize: this.currentSize,\n ssthresh: this.ssthresh,\n state: this.state,\n consecutiveFastUploads: this.consecutiveFastUploads,\n consecutiveSlowUploads: this.consecutiveSlowUploads,\n };\n }\n}\n","/**\n * UploadTask - Single file upload task manager\n *\n * Manages the complete lifecycle of a single file upload including:\n * - File chunking\n * - Hash calculation\n * - Chunk upload with retry\n * - Progress tracking\n * - State management\n * - Event emission\n */\n\nimport type { ChunkInfo, UploadStatus, UploadToken } from \"@chunkflowjs/protocol\";\nimport type { RequestAdapter } from \"@chunkflowjs/protocol\";\nimport {\n createEventBus,\n type UploadEventBus,\n ConcurrencyController,\n UploadStorage,\n sliceFile,\n calculateChunkHash,\n calculateFileHash,\n calculateSpeed,\n estimateRemainingTime,\n} from \"@chunkflowjs/shared\";\nimport type { IChunkSizeAdjuster } from \"./chunk-size-adjuster-interface\";\nimport { TCPChunkSizeAdjuster } from \"./chunk-size-adjuster-tcp\";\nimport { ChunkSizeAdjuster } from \"./chunk-size-adjuster\";\n\n/**\n * Upload progress information\n */\nexport interface UploadProgress {\n /** Number of bytes uploaded */\n uploadedBytes: number;\n /** Total file size in bytes */\n totalBytes: number;\n /** Upload percentage (0-100) */\n percentage: number;\n /** Upload speed in bytes per second */\n speed: number;\n /** Estimated remaining time in seconds */\n remainingTime: number;\n /** Number of chunks uploaded */\n uploadedChunks: number;\n /** Total number of chunks */\n totalChunks: number;\n}\n\n/**\n * Options for configuring an upload task\n */\nexport interface UploadTaskOptions {\n /** File to upload */\n file: File;\n /** Request adapter for API calls */\n requestAdapter: RequestAdapter;\n /** Initial chunk size in bytes (default: 1MB) */\n chunkSize?: number;\n /** Number of concurrent chunk uploads (default: 3) */\n concurrency?: number;\n /** Maximum number of retries per chunk (default: 3) */\n retryCount?: number;\n /** Base delay for retry in milliseconds (default: 1000) */\n retryDelay?: number;\n /** Whether to start upload automatically (default: false) */\n autoStart?: boolean;\n /** Task ID to resume (for resuming interrupted uploads) */\n resumeTaskId?: string;\n /** Upload token from previous session (for resuming) */\n resumeUploadToken?: string;\n /** List of already uploaded chunk indices (for resuming) */\n resumeUploadedChunks?: number[];\n /**\n * Chunk size adjustment strategy (default: 'tcp-like')\n * - 'simple': Simple binary adjustment (fast → double, slow → halve)\n * - 'tcp-like': TCP slow start inspired algorithm with state machine (recommended)\n * - Custom: Provide your own IChunkSizeAdjuster implementation\n */\n chunkSizeStrategy?: \"simple\" | \"tcp-like\" | IChunkSizeAdjuster;\n /**\n * Initial slow start threshold for TCP-like strategy (default: 5MB)\n * Only used when chunkSizeStrategy is 'tcp-like'\n */\n initialSsthresh?: number;\n}\n\n/**\n * UploadTask class\n *\n * Manages a single file upload with support for:\n * - Chunked upload with dynamic chunk size adjustment\n * - Hash calculation and verification (instant upload/resume)\n * - Concurrent chunk uploads with retry\n * - Progress tracking and persistence\n * - Pause/resume/cancel operations\n * - Event-driven lifecycle\n *\n * @example\n * ```typescript\n * const task = new UploadTask({\n * file: myFile,\n * requestAdapter: myAdapter,\n * chunkSize: 1024 * 1024, // 1MB\n * concurrency: 3,\n * });\n *\n * // Listen to events\n * task.on('progress', ({ progress, speed }) => {\n * console.log(`Progress: ${progress}%, Speed: ${speed} bytes/s`);\n * });\n *\n * task.on('success', ({ fileUrl }) => {\n * console.log(`Upload complete: ${fileUrl}`);\n * });\n *\n * // Start upload\n * await task.start();\n * ```\n */\nexport class UploadTask {\n /** Unique task identifier */\n readonly id: string;\n\n /** File being uploaded */\n readonly file: File;\n\n /** Current upload status */\n private status: UploadStatus;\n\n /** Current upload progress */\n private progress: UploadProgress;\n\n /** Array of chunk information */\n private chunks: ChunkInfo[];\n\n /** Upload token from server */\n private uploadToken: UploadToken | null;\n\n /** Calculated file hash */\n private fileHash: string | null;\n\n /** Event bus for lifecycle events */\n private eventBus: UploadEventBus;\n\n /** Concurrency controller for chunk uploads */\n private concurrencyController: ConcurrencyController;\n\n /** Storage for persisting upload progress */\n private storage: UploadStorage;\n\n /** Request adapter for API calls */\n private requestAdapter: RequestAdapter;\n\n /** Upload task options with defaults */\n private options: Required<UploadTaskOptions>;\n\n /** Upload start timestamp */\n private startTime: number;\n\n /** Upload end timestamp */\n private endTime: number | null;\n\n /** Chunk size adjuster for dynamic sizing */\n private chunkSizeAdjuster: IChunkSizeAdjuster | null;\n\n /** Flag to indicate if upload should be cancelled (e.g., instant upload) */\n private shouldCancelUpload: boolean;\n\n /**\n * Creates a new UploadTask\n *\n * @param options - Upload task configuration options\n */\n constructor(options: UploadTaskOptions) {\n // Use resume task ID if provided, otherwise generate new one\n this.id = options.resumeTaskId ?? this.generateTaskId();\n\n // Store file reference\n this.file = options.file;\n\n // Initialize status\n this.status = \"idle\" as UploadStatus;\n\n // Initialize progress\n this.progress = {\n uploadedBytes: 0,\n totalBytes: options.file.size,\n percentage: 0,\n speed: 0,\n remainingTime: 0,\n uploadedChunks: 0,\n totalChunks: 0,\n };\n\n // Initialize chunks array\n this.chunks = [];\n\n // Initialize upload token (use resume token if provided)\n if (options.resumeUploadToken) {\n // Parse the resume token to reconstruct UploadToken\n // Note: This is a simplified version - in production you'd decode the JWT\n this.uploadToken = {\n token: options.resumeUploadToken,\n fileId: \"\", // Will be populated from token\n chunkSize: options.chunkSize ?? 1024 * 1024,\n expiresAt: Date.now() + 24 * 60 * 60 * 1000, // Assume 24h expiry\n };\n } else {\n this.uploadToken = null;\n }\n\n this.fileHash = null;\n\n // Create event bus\n this.eventBus = createEventBus();\n\n // Store request adapter\n this.requestAdapter = options.requestAdapter;\n\n // Apply default options\n this.options = {\n file: options.file,\n requestAdapter: options.requestAdapter,\n chunkSize: options.chunkSize ?? 1024 * 1024, // 1MB default\n concurrency: options.concurrency ?? 3,\n retryCount: options.retryCount ?? 3,\n retryDelay: options.retryDelay ?? 1000,\n autoStart: options.autoStart ?? false,\n resumeTaskId: options.resumeTaskId ?? \"\",\n resumeUploadToken: options.resumeUploadToken ?? \"\",\n resumeUploadedChunks: options.resumeUploadedChunks ?? [],\n chunkSizeStrategy: options.chunkSizeStrategy ?? \"tcp-like\", // Default to TCP-like\n initialSsthresh: options.initialSsthresh ?? 5 * 1024 * 1024, // 5MB default\n };\n\n // Create concurrency controller\n this.concurrencyController = new ConcurrencyController({\n limit: this.options.concurrency,\n });\n\n // Create storage instance\n this.storage = new UploadStorage();\n\n // Initialize timestamps\n this.startTime = 0;\n this.endTime = null;\n\n // Initialize chunk size adjuster (will be created when upload starts)\n this.chunkSizeAdjuster = null;\n\n // Initialize cancel flag\n this.shouldCancelUpload = false;\n }\n\n /**\n * Generates a unique task ID\n * Uses timestamp and random string for uniqueness\n *\n * @returns Unique task identifier\n */\n private generateTaskId(): string {\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).substring(2, 9);\n return `task_${timestamp}_${random}`;\n }\n\n /**\n * Gets the current upload status\n *\n * @returns Current upload status\n */\n getStatus(): UploadStatus {\n return this.status;\n }\n\n /**\n * Gets the current upload progress\n * Returns a copy to prevent external modification\n *\n * @returns Current upload progress\n */\n getProgress(): UploadProgress {\n return { ...this.progress };\n }\n\n /**\n * Gets the upload duration in milliseconds\n * Returns null if upload hasn't completed\n *\n * @returns Upload duration or null\n */\n getDuration(): number | null {\n if (this.endTime === null) {\n return null;\n }\n return this.endTime - this.startTime;\n }\n\n /**\n * Subscribes to upload events\n *\n * @param event - Event name to listen to\n * @param handler - Event handler function\n *\n * @example\n * ```typescript\n * task.on('progress', ({ progress, speed }) => {\n * console.log(`${progress}% at ${speed} bytes/s`);\n * });\n * ```\n */\n on<K extends keyof import(\"@chunkflowjs/shared\").UploadEvents>(\n event: K,\n handler: (payload: import(\"@chunkflowjs/shared\").UploadEvents[K]) => void,\n ): void {\n this.eventBus.on(event, handler);\n }\n\n /**\n * Unsubscribes from upload events\n *\n * @param event - Event name to stop listening to\n * @param handler - Event handler function to remove\n */\n off<K extends keyof import(\"@chunkflowjs/shared\").UploadEvents>(\n event: K,\n handler: (payload: import(\"@chunkflowjs/shared\").UploadEvents[K]) => void,\n ): void {\n this.eventBus.off(event, handler);\n }\n\n /**\n * Creates chunk information array based on negotiated chunk size\n * Divides the file into chunks and creates ChunkInfo objects for each\n *\n * @param chunkSize - Size of each chunk in bytes (negotiated with server)\n * @returns Array of ChunkInfo objects\n *\n * @remarks\n * - Chunks are created sequentially from start to end of file\n * - Last chunk may be smaller than chunkSize\n * - Hash field is initially empty and will be calculated during upload\n * - Validates: Requirement 2.1 (chunk size based splitting)\n *\n * @internal This method will be used in task 5.3\n */\n private createChunks(chunkSize: number): ChunkInfo[] {\n const chunks: ChunkInfo[] = [];\n const totalChunks = Math.ceil(this.file.size / chunkSize);\n\n for (let i = 0; i < totalChunks; i++) {\n const start = i * chunkSize;\n const end = Math.min(start + chunkSize, this.file.size);\n\n chunks.push({\n index: i,\n hash: \"\", // Will be calculated during upload\n size: end - start,\n start,\n end,\n });\n }\n\n return chunks;\n }\n\n /**\n * Starts the upload process\n *\n * Workflow:\n * 1. Create file on server and get upload token\n * 2. Split file into chunks based on negotiated chunk size\n * 3. Start concurrent chunk upload\n *\n * @throws Error if upload is already in progress or completed\n *\n * @remarks\n * - Validates: Requirements 1.1, 1.2, 2.2, 5.1, 5.2, 5.3, 20.1, 20.2, 20.3, 20.5\n * - Sets status to 'uploading'\n * - Emits 'start' event\n * - Creates chunks based on negotiated size\n * - Initiates concurrent upload with retry\n */\n async start(): Promise<void> {\n // Validate current status\n if (this.status !== \"idle\") {\n throw new Error(`Cannot start upload: current status is ${this.status}`);\n }\n\n try {\n // Initialize storage for persistence\n await this.initializeStorage();\n\n // Update status to uploading\n this.status = \"uploading\" as UploadStatus;\n this.startTime = Date.now();\n\n // Emit start event\n this.eventBus.emit(\"start\", { taskId: this.id, file: this.file });\n\n // Check if this is a resume operation\n const isResume = this.options.resumeUploadToken && this.options.resumeUploadedChunks;\n\n let negotiatedChunkSize: number;\n\n if (isResume) {\n // Resuming: use existing upload token\n // Upload token was already set in constructor\n negotiatedChunkSize = this.uploadToken!.chunkSize;\n\n console.info(\n `Resuming upload for task ${this.id}: ` +\n `${this.options.resumeUploadedChunks!.length} chunks already uploaded`,\n );\n } else {\n // New upload: Create file on server and get upload token\n const createResponse = await this.requestAdapter.createFile({\n fileName: this.file.name,\n fileSize: this.file.size,\n fileType: this.file.type,\n preferredChunkSize: this.options.chunkSize,\n });\n\n this.uploadToken = createResponse.uploadToken;\n negotiatedChunkSize = createResponse.negotiatedChunkSize;\n }\n\n // Step 2: Split file into chunks\n this.chunks = this.createChunks(negotiatedChunkSize);\n this.progress.totalChunks = this.chunks.length;\n\n // If resuming, mark already uploaded chunks\n if (isResume && this.options.resumeUploadedChunks) {\n const uploadedChunks = this.options.resumeUploadedChunks;\n let uploadedBytes = 0;\n\n for (const chunkIndex of uploadedChunks) {\n if (chunkIndex < this.chunks.length) {\n const chunk = this.chunks[chunkIndex];\n // Mark chunk as uploaded by setting a flag\n (chunk as any).uploaded = true;\n uploadedBytes += chunk.size;\n }\n }\n\n // Update progress to reflect already uploaded chunks\n this.progress.uploadedChunks = uploadedChunks.length;\n this.progress.uploadedBytes = uploadedBytes;\n this.progress.percentage = (uploadedBytes / this.file.size) * 100;\n\n console.info(\n `Resume progress: ${this.progress.percentage.toFixed(1)}% ` +\n `(${uploadedChunks.length}/${this.chunks.length} chunks)`,\n );\n }\n\n // Initialize chunk size adjuster for dynamic sizing\n const strategy = this.options.chunkSizeStrategy;\n const minSize = 256 * 1024; // 256KB\n const maxSize = 10 * 1024 * 1024; // 10MB\n const targetTime = 3000; // 3 seconds target per chunk\n\n if (typeof strategy === \"object\") {\n // Custom adjuster provided\n this.chunkSizeAdjuster = strategy;\n } else if (strategy === \"tcp-like\") {\n // Use TCP-like adjuster (default, recommended)\n this.chunkSizeAdjuster = new TCPChunkSizeAdjuster({\n initialSize: negotiatedChunkSize,\n minSize,\n maxSize,\n targetTime,\n initialSsthresh: this.options.initialSsthresh,\n });\n } else {\n // Use simple adjuster\n this.chunkSizeAdjuster = new ChunkSizeAdjuster({\n initialSize: negotiatedChunkSize,\n minSize,\n maxSize,\n targetTime,\n });\n }\n\n // Step 3: Start uploading chunks AND calculate hash in parallel\n // This implements requirement 3.6 and 17.2 - hash calculation and upload should be parallel\n await Promise.all([this.startUpload(), this.calculateAndVerifyHash()]);\n\n // Check if upload was paused or cancelled during the process\n if (this.status === \"paused\") {\n console.info(`Upload paused at ${this.progress.percentage.toFixed(1)}%`);\n return;\n }\n\n if (this.status === \"cancelled\" || this.shouldCancelUpload) {\n // Upload was cancelled - silently return\n return;\n }\n\n // If instant upload was triggered, status is already success\n if (this.status === \"success\") {\n console.info(\"Instant upload already completed\");\n return;\n }\n\n // Step 4: Verify hash with server (now that all chunk hashes are calculated)\n // Only do this if we're still in uploading state and have fileHash\n if (this.status === \"uploading\" && this.fileHash) {\n try {\n const chunkHashes = this.chunks.map((chunk) => chunk.hash);\n\n const verifyResponse = await this.requestAdapter.verifyHash({\n fileHash: this.fileHash,\n chunkHashes,\n uploadToken: this.uploadToken!.token,\n });\n\n // Check if file already exists (instant upload)\n // Note: This is the second verifyHash call after upload completes\n // At this point, if file exists, it means we just uploaded it successfully\n if (verifyResponse.fileExists && verifyResponse.fileUrl) {\n // File already exists on server - instant upload (秒传)\n this.status = \"success\" as UploadStatus;\n this.endTime = Date.now();\n\n // Update progress to 100%\n this.progress.uploadedBytes = this.file.size;\n this.progress.uploadedChunks = this.chunks.length;\n this.progress.percentage = 100;\n\n // Emit success event with file URL\n this.eventBus.emit(\"success\", {\n taskId: this.id,\n fileUrl: verifyResponse.fileUrl,\n });\n\n return;\n }\n } catch (error) {\n // Hash verification failed, continue with merge\n console.warn(\"Hash verification failed:\", error);\n }\n } else if (this.status === \"uploading\" && !this.fileHash) {\n console.warn(\"No fileHash available, skipping hash verification\");\n }\n\n // Step 5: Merge file if upload completed successfully\n if (this.status === \"uploading\" && !this.shouldCancelUpload) {\n // Call merge API\n const mergeResponse = await this.requestAdapter.mergeFile({\n uploadToken: this.uploadToken!.token,\n fileHash: this.fileHash || \"\",\n chunkHashes: this.chunks.map((chunk) => chunk.hash),\n });\n\n if (mergeResponse.success) {\n // Upload completed successfully\n this.status = \"success\" as UploadStatus;\n this.endTime = Date.now();\n\n // Emit success event with file URL\n this.eventBus.emit(\"success\", {\n taskId: this.id,\n fileUrl: mergeResponse.fileUrl,\n });\n } else {\n throw new Error(\"Merge failed: response.success is false\");\n }\n }\n // If we reach here without uploading status, it means upload was paused/cancelled/instant\n // The status and events were already handled above\n } catch (error) {\n // Only set error status if not paused or cancelled\n if (this.status !== \"paused\" && this.status !== \"cancelled\") {\n this.status = \"error\" as UploadStatus;\n this.endTime = Date.now();\n this.eventBus.emit(\"error\", {\n taskId: this.id,\n error: error as Error,\n });\n }\n throw error;\n }\n }\n\n /**\n * Starts concurrent chunk upload with priority for first chunks\n *\n * Uploads all chunks with concurrency control and dynamic chunk size adjustment.\n * Uses the concurrency controller to limit parallel uploads.\n * Implements priority upload for the first few chunks to get quick feedback.\n *\n * @remarks\n * - Validates: Requirements 5.1, 5.2, 5.3, 17.5, 20.1, 20.2, 20.3\n * - Respects concurrency limits\n * - Tracks upload time for dynamic chunk size adjustment\n * - Stops if status changes (pause/cancel) or shouldCancelUpload is set\n * - Prioritizes first 3 chunks for quick server feedback\n *\n * @internal\n */\n private async startUpload(): Promise<void> {\n // Filter out already uploaded chunks (for resume functionality)\n const chunksToUpload = this.chunks.filter((chunk) => !(chunk as any).uploaded);\n\n if (chunksToUpload.length === 0) {\n // All chunks already uploaded (resume case)\n console.info(\"All chunks already uploaded, skipping upload phase\");\n return;\n }\n\n // Requirement 17.5: Priority upload for first few chunks\n // Upload first 3 chunks with priority to get quick feedback\n const priorityChunkCount = Math.min(3, chunksToUpload.length);\n const priorityChunks = chunksToUpload.slice(0, priorityChunkCount);\n const remainingChunks = chunksToUpload.slice(priorityChunkCount);\n\n // Upload priority chunks first\n const priorityPromises = priorityChunks.map((chunk) => {\n return this.concurrencyController.run(async () => {\n // Check if upload should continue\n if (this.status !== \"uploading\" || this.shouldCancelUpload) {\n return;\n }\n\n // Track upload start time for this chunk\n const chunkStartTime = Date.now();\n\n // Upload chunk with retry\n await this.uploadChunkWithRetry(chunk);\n\n // Track upload end time and adjust chunk size for next uploads\n const chunkUploadTime = Date.now() - chunkStartTime;\n if (this.chunkSizeAdjuster) {\n this.chunkSizeAdjuster.adjust(chunkUploadTime);\n }\n });\n });\n\n // Upload remaining chunks concurrently with priority chunks\n const remainingPromises = remainingChunks.map((chunk) => {\n return this.concurrencyController.run(async () => {\n // Check if upload should continue\n if (this.status !== \"uploading\" || this.shouldCancelUpload) {\n return;\n }\n\n // Track upload start time for this chunk\n const chunkStartTime = Date.now();\n\n // Upload chunk with retry\n await this.uploadChunkWithRetry(chunk);\n\n // Track upload end time and adjust chunk size for next uploads\n const chunkUploadTime = Date.now() - chunkStartTime;\n if (this.chunkSizeAdjuster) {\n this.chunkSizeAdjuster.adjust(chunkUploadTime);\n }\n });\n });\n\n // Wait for all chunks to complete\n // Priority chunks are started first, but all run concurrently\n await Promise.all([...priorityPromises, ...remainingPromises]);\n }\n\n /**\n * Uploads a single chunk with retry logic\n *\n * Implements exponential backoff retry strategy for failed uploads.\n * Calculates chunk hash before upload for verification.\n * Updates progress after successful upload.\n *\n * @param chunk - Chunk information to upload\n * @throws Error if all retries are exhausted\n *\n * @remarks\n * - Validates: Requirements 20.1, 20.2, 20.3, 20.5\n * - Retries up to configured retry count\n * - Uses exponential backoff delay\n * - Emits chunkSuccess or chunkError events\n * - Updates progress and persists to storage\n * - Skips upload if shouldCancelUpload is set (instant upload)\n *\n * @internal\n */\n private async uploadChunkWithRetry(chunk: ChunkInfo): Promise<void> {\n let retries = 0;\n let lastError: Error | null = null;\n\n while (retries <= this.options.retryCount) {\n try {\n // Check if upload should continue\n if (this.status !== \"uploading\" || this.shouldCancelUpload) {\n return;\n }\n\n // Slice the file to get chunk blob\n const blob = sliceFile(this.file, chunk.start, chunk.end);\n\n // Calculate chunk hash\n const chunkHash = await calculateChunkHash(blob);\n chunk.hash = chunkHash;\n\n // Upload chunk to server\n await this.requestAdapter.uploadChunk({\n uploadToken: this.uploadToken!.token,\n chunkIndex: chunk.index,\n chunkHash,\n chunk: blob,\n });\n\n // Mark chunk as uploaded to prevent duplicate uploads\n (chunk as any).uploaded = true;\n\n // Upload successful - update progress\n await this.updateProgress(chunk);\n\n // Emit chunk success event\n this.eventBus.emit(\"chunkSuccess\", {\n taskId: this.id,\n chunkIndex: chunk.index,\n });\n\n // Successfully uploaded, exit retry loop\n return;\n } catch (error) {\n lastError = error as Error;\n retries++;\n\n // Emit chunk error event\n this.eventBus.emit(\"chunkError\", {\n taskId: this.id,\n chunkIndex: chunk.index,\n error: lastError,\n });\n\n // Check if we should retry\n if (retries > this.options.retryCount) {\n // All retries exhausted\n throw new Error(\n `Failed to upload chunk ${chunk.index} after ${this.options.retryCount} retries: ${lastError.message}`,\n );\n }\n\n // Calculate exponential backoff delay\n const delay = this.options.retryDelay * Math.pow(2, retries - 1);\n await this.delay(delay);\n }\n }\n\n // This should never be reached, but TypeScript needs it\n if (lastError) {\n throw lastError;\n }\n }\n\n /**\n * Updates upload progress after a chunk is successfully uploaded\n *\n * Calculates:\n * - Uploaded bytes and percentage\n * - Upload speed\n * - Estimated remaining time\n *\n * Emits progress event with updated information.\n * Persists progress to IndexedDB for resume functionality.\n *\n * @param chunk - The chunk that was just uploaded\n *\n * @remarks\n * - Validates: Requirements 4.1, 6.3, 6.4 (progress tracking and persistence)\n * - Updates all progress metrics\n * - Emits progress event\n * - Persists to IndexedDB for resume capability\n *\n * @internal\n */\n private async updateProgress(chunk: ChunkInfo): Promise<void> {\n // Check if this chunk was already counted to prevent duplicate progress updates\n if ((chunk as any).progressCounted) {\n console.warn(`Chunk ${chunk.index} progress already counted, skipping update`);\n return;\n }\n\n // Mark chunk as counted\n (chunk as any).progressCounted = true;\n\n // Update uploaded bytes and chunks\n this.progress.uploadedBytes += chunk.size;\n this.progress.uploadedChunks++;\n\n // Ensure progress doesn't exceed 100%\n if (this.progress.uploadedBytes > this.file.size) {\n console.warn(\n `Progress overflow detected: ${this.progress.uploadedBytes} > ${this.file.size}, capping to file size`,\n );\n this.progress.uploadedBytes = this.file.size;\n }\n\n if (this.progress.uploadedChunks > this.progress.totalChunks) {\n console.warn(\n `Chunk count overflow detected: ${this.progress.uploadedChunks} > ${this.progress.totalChunks}, capping to total chunks`,\n );\n this.progress.uploadedChunks = this.progress.totalChunks;\n }\n\n // Calculate percentage\n this.progress.percentage = Math.min(100, (this.progress.uploadedBytes / this.file.size) * 100);\n\n // Calculate speed and remaining time\n const elapsedTime = Date.now() - this.startTime;\n this.progress.speed = calculateSpeed(this.progress.uploadedBytes, elapsedTime);\n\n const remainingBytes = this.file.size - this.progress.uploadedBytes;\n this.progress.remainingTime = estimateRemainingTime(remainingBytes, this.progress.speed);\n\n // Emit progress event\n this.eventBus.emit(\"progress\", {\n taskId: this.id,\n progress: this.progress.percentage,\n speed: this.progress.speed,\n });\n\n // Persist progress to IndexedDB for resume functionality\n // Requirement 4.1: Write progress to IndexedDB after each chunk\n await this.persistProgress();\n }\n\n /**\n * Delays execution for a specified time\n * Used for retry backoff\n *\n * @param ms - Milliseconds to delay\n * @returns Promise that resolves after the delay\n *\n * @internal\n */\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Calculates file hash and verifies with server for instant upload\n *\n * This method runs in parallel with chunk upload to optimize performance.\n * It implements the following features:\n * - Calculate file hash using Web Worker or requestIdleCallback (non-blocking)\n * - Send hash verification request to server\n * - Handle instant upload (秒传) when file already exists\n * - Handle partial instant upload (skip existing chunks)\n *\n * @remarks\n * - Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5\n * - Emits hashProgress events during calculation\n * - Emits hashComplete event when calculation finishes\n * - If file exists on server, cancels ongoing upload and emits success\n * - If some chunks exist, marks them as uploaded to skip them\n *\n * @internal\n */\n private async calculateAndVerifyHash(): Promise<void> {\n try {\n // Calculate file hash with progress reporting\n // Requirement 3.1: Calculate hash when file is selected\n // Requirement 3.2: Use non-blocking hash calculation\n this.fileHash = await calculateFileHash(this.file, (progress) => {\n // Emit hash progress event\n this.eventBus.emit(\"hashProgress\", {\n taskId: this.id,\n progress,\n });\n });\n\n // Emit hash complete event\n // Requirement 3.3: Send hash verification request after calculation\n this.eventBus.emit(\"hashComplete\", {\n taskId: this.id,\n hash: this.fileHash,\n });\n\n // Verify hash with server (without chunk hashes for now)\n // This allows early detection of full instant upload\n if (!this.uploadToken) {\n // Upload token not available yet, skip verification\n return;\n }\n\n const verifyResponse = await this.requestAdapter.verifyHash({\n fileHash: this.fileHash,\n uploadToken: this.uploadToken.token,\n // Note: chunkHashes not included here because they're calculated during upload\n // We'll do a second verification with chunk hashes before merge\n });\n\n // Requirement 3.4: Handle instant upload (file already exists)\n if (verifyResponse.fileExists && verifyResponse.fileUrl) {\n // Check if upload was paused or cancelled by user\n if (this.status === \"paused\" || this.status === \"cancelled\") {\n return;\n }\n\n // File already exists on server - instant upload (秒传)\n // Cancel ongoing chunk uploads\n this.shouldCancelUpload = true;\n this.status = \"success\" as UploadStatus;\n this.endTime = Date.now();\n\n // Update progress to 100%\n this.progress.uploadedBytes = this.file.size;\n this.progress.uploadedChunks = this.chunks.length;\n this.progress.percentage = 100;\n\n // Emit success event with file URL\n this.eventBus.emit(\"success\", {\n taskId: this.id,\n fileUrl: verifyResponse.fileUrl,\n });\n\n return;\n }\n\n // Requirement 3.5: Handle partial instant upload (skip existing chunks)\n // Note: This won't work well without chunk hashes, but server might return\n // existing chunks based on file hash if it has seen this file before\n if (verifyResponse.existingChunks && verifyResponse.existingChunks.length > 0) {\n // Some chunks already exist on server\n // Mark them as uploaded to skip them\n this.skipExistingChunks(verifyResponse.existingChunks);\n }\n } catch (error) {\n // Hash calculation or verification failed\n // Log error but don't fail the upload - continue with normal upload\n console.warn(\"Hash calculation/verification failed:\", error);\n // The upload will continue normally without hash optimization\n }\n }\n\n /**\n * Skips existing chunks by marking them as uploaded\n *\n * This is used for partial instant upload when some chunks already exist on server.\n * Updates progress to reflect the skipped chunks.\n *\n * @param existingChunkIndices - Array of chunk indices that already exist on server\n *\n * @remarks\n * - Validates: Requirement 3.5, 17.4 (partial instant upload)\n * - Updates progress for skipped chunks\n * - Emits chunkSuccess events for skipped chunks\n *\n * @internal\n */\n private skipExistingChunks(existingChunkIndices: number[]): void {\n for (const chunkIndex of existingChunkIndices) {\n const chunk = this.chunks[chunkIndex];\n if (!chunk) continue;\n\n // Mark chunk as uploaded to skip it in startUpload\n (chunk as any).uploaded = true;\n\n // Mark chunk as progress counted to prevent duplicate counting\n (chunk as any).progressCounted = true;\n\n // Update progress for skipped chunk\n this.progress.uploadedBytes += chunk.size;\n this.progress.uploadedChunks++;\n this.progress.percentage = (this.progress.uploadedBytes / this.file.size) * 100;\n\n // Emit chunk success event for skipped chunk\n this.eventBus.emit(\"chunkSuccess\", {\n taskId: this.id,\n chunkIndex: chunk.index,\n });\n }\n\n // Emit progress event with updated information\n this.eventBus.emit(\"progress\", {\n taskId: this.id,\n progress: this.progress.percentage,\n speed: this.progress.speed,\n });\n }\n\n /**\n * Initializes the IndexedDB storage for progress persistence\n *\n * Creates the initial upload record in IndexedDB.\n * If storage is not available, logs a warning but continues upload.\n *\n * @remarks\n * - Validates: Requirement 4.1 (persist progress to IndexedDB)\n * - Gracefully handles storage unavailability\n * - Creates initial record with empty uploaded chunks\n *\n * @internal\n */\n private async initializeStorage(): Promise<void> {\n try {\n // Initialize the storage database\n await this.storage.init();\n\n // Create initial upload record\n const record: import(\"@chunkflowjs/shared\").UploadRecord = {\n taskId: this.id,\n fileInfo: {\n name: this.file.name,\n size: this.file.size,\n type: this.file.type,\n lastModified: this.file.lastModified,\n },\n uploadedChunks: [],\n uploadToken: this.uploadToken?.token || \"\",\n createdAt: Date.now(),\n updatedAt: Date.now(),\n };\n\n await this.storage.saveRecord(record);\n } catch (error) {\n // Storage initialization failed - silently ignore\n // This implements graceful degradation when IndexedDB is unavailable\n // Upload will continue without persistence\n }\n }\n\n /**\n * Persists current upload progress to IndexedDB\n *\n * Updates the upload record with the list of successfully uploaded chunks.\n * This enables resume functionality if the upload is interrupted.\n *\n * @remarks\n * - Validates: Requirement 4.1 (write progress to IndexedDB)\n * - Gracefully handles storage errors\n * - Updates uploadedChunks array and timestamp\n *\n * @internal\n */\n private async persistProgress(): Promise<void> {\n try {\n // Check if storage is available\n if (!this.storage.isAvailable()) {\n return;\n }\n\n // Get list of uploaded chunk indices\n const uploadedChunkIndices = this.chunks\n .filter((_, index) => index < this.progress.uploadedChunks)\n .map((chunk) => chunk.index);\n\n try {\n // Try to update the existing record\n await this.storage.updateRecord(this.id, {\n uploadedChunks: uploadedChunkIndices,\n uploadToken: this.uploadToken?.token || \"\",\n updatedAt: Date.now(),\n });\n } catch (error) {\n // If record doesn't exist, create it (upsert behavior)\n if ((error as any).code === \"OPERATION_FAILED\") {\n const record: import(\"@chunkflowjs/shared\").UploadRecord = {\n taskId: this.id,\n fileInfo: {\n name: this.file.name,\n size: this.file.size,\n type: this.file.type,\n lastModified: this.file.lastModified,\n },\n uploadedChunks: uploadedChunkIndices,\n uploadToken: this.uploadToken?.token || \"\",\n createdAt: Date.now(),\n updatedAt: Date.now(),\n };\n await this.storage.saveRecord(record);\n } else {\n throw error;\n }\n }\n } catch (error) {\n // Storage update failed - log warning but continue upload\n // This ensures upload continues even if persistence fails\n // Silently ignore to avoid console noise in tests\n }\n }\n\n /**\n * Pauses the upload\n *\n * Pauses an ongoing upload by changing the status to 'paused'.\n * The upload can be resumed later from where it left off.\n *\n * @remarks\n * - Validates: Requirements 4.3, 6.3 (pause functionality and lifecycle events)\n * - Only works when status is 'uploading'\n * - Emits 'pause' event\n * - Progress is persisted to IndexedDB for resume\n * - Ongoing chunk uploads will complete, but no new chunks will start\n *\n * @example\n * ```typescript\n * task.on('pause', () => {\n * console.log('Upload paused');\n * });\n *\n * task.pause();\n * ```\n */\n pause(): void {\n // Only allow pausing when upload is in progress\n if (this.status !== \"uploading\") {\n console.warn(`Cannot pause upload: current status is ${this.status}`);\n return;\n }\n\n // Update status to paused\n this.status = \"paused\" as UploadStatus;\n\n // Emit pause event\n this.eventBus.emit(\"pause\", { taskId: this.id });\n\n // Note: Ongoing chunk uploads will complete naturally\n // The startUpload method checks status before starting new chunks\n // Progress is already persisted to IndexedDB via updateProgress\n }\n\n /**\n * Resumes a paused upload (断点续传)\n *\n * Resumes an upload that was previously paused.\n * Continues uploading from where it left off, skipping already uploaded chunks.\n *\n * @throws Error if upload is not in paused state\n *\n * @remarks\n * - Validates: Requirements 4.3, 4.4, 6.3 (resume functionality and lifecycle events)\n * - Only works when status is 'paused'\n * - Emits 'resume' event\n * - Continues from last uploaded chunk\n * - Uses persisted progress from IndexedDB\n *\n * @example\n * ```typescript\n * task.on('resume', () => {\n * console.log('Upload resumed');\n * });\n *\n * await task.resume();\n * ```\n */\n async resume(): Promise<void> {\n // Only allow resuming when upload is paused\n if (this.status !== \"paused\") {\n throw new Error(`Cannot resume upload: current status is ${this.status}`);\n }\n\n try {\n // Check if we have upload token (required for resume)\n if (!this.uploadToken) {\n // No upload token available - cannot resume\n // This can happen if resume is called on a task that was never started\n throw new Error(\"Cannot resume: upload token not available\");\n }\n\n // Update status to uploading\n this.status = \"uploading\" as UploadStatus;\n\n // Emit resume event\n this.eventBus.emit(\"resume\", { taskId: this.id });\n\n // Continue uploading remaining chunks\n // The startUpload method will skip already uploaded chunks\n // because progress.uploadedChunks tracks which chunks are done\n await this.startUpload();\n\n // Check if upload was cancelled during resume\n if (this.status === \"cancelled\" || this.shouldCancelUpload) {\n // Upload was cancelled - silently return\n return;\n }\n\n // If status is still uploading, proceed with verification and merge\n if (this.status === \"uploading\") {\n // Step 1: Verify hash with server (if we have fileHash and chunk hashes)\n if (this.fileHash) {\n try {\n const chunkHashes = this.chunks.map((chunk) => chunk.hash);\n\n const verifyResponse = await this.requestAdapter.verifyHash({\n fileHash: this.fileHash,\n chunkHashes,\n uploadToken: this.uploadToken.token,\n });\n\n // Check if file already exists (instant upload)\n if (verifyResponse.fileExists && verifyResponse.fileUrl) {\n this.status = \"success\" as UploadStatus;\n this.endTime = Date.now();\n\n // Update progress to 100%\n this.progress.uploadedBytes = this.file.size;\n this.progress.uploadedChunks = this.chunks.length;\n this.progress.percentage = 100;\n\n // Emit success event with file URL\n this.eventBus.emit(\"success\", {\n taskId: this.id,\n fileUrl: verifyResponse.fileUrl,\n });\n\n return;\n }\n } catch (error) {\n // Hash verification failed, continue with merge\n console.warn(\"Hash verification failed:\", error);\n }\n }\n\n // Step 2: Merge file\n const mergeResponse = await this.requestAdapter.mergeFile({\n uploadToken: this.uploadToken.token,\n fileHash: this.fileHash || \"\",\n chunkHashes: this.chunks.map((chunk) => chunk.hash),\n });\n\n if (mergeResponse.success) {\n // Upload completed successfully\n this.status = \"success\" as UploadStatus;\n this.endTime = Date.now();\n\n // Emit success event with file URL\n this.eventBus.emit(\"success\", {\n taskId: this.id,\n fileUrl: mergeResponse.fileUrl,\n });\n } else {\n throw new Error(\"Merge failed: response.success is false\");\n }\n }\n } catch (error) {\n this.status = \"error\" as UploadStatus;\n this.endTime = Date.now();\n this.eventBus.emit(\"error\", {\n taskId: this.id,\n error: error as Error,\n });\n throw error;\n }\n }\n\n /**\n * Cancels the upload\n *\n * Cancels an ongoing or paused upload.\n * Once cancelled, the upload cannot be resumed.\n *\n * @remarks\n * - Validates: Requirements 6.3 (cancel functionality and lifecycle events)\n * - Works when status is 'uploading' or 'paused'\n * - Emits 'cancel' event\n * - Sets shouldCancelUpload flag to stop ongoing chunk uploads\n * - Cleans up upload record from IndexedDB\n * - Status becomes 'cancelled' (terminal state)\n *\n * @example\n * ```typescript\n * task.on('cancel', () => {\n * console.log('Upload cancelled');\n * });\n *\n * task.cancel();\n * ```\n */\n cancel(): void {\n // Only allow cancelling when upload is in progress or paused\n if (this.status !== \"uploading\" && this.status !== \"paused\") {\n console.warn(`Cannot cancel upload: current status is ${this.status}`);\n return;\n }\n\n // Set cancel flag to stop ongoing uploads\n this.shouldCancelUpload = true;\n\n // Update status to cancelled\n this.status = \"cancelled\" as UploadStatus;\n this.endTime = Date.now();\n\n // Emit cancel event\n this.eventBus.emit(\"cancel\", { taskId: this.id });\n\n // Clean up upload record from IndexedDB\n // This is done asynchronously and errors are ignored\n this.cleanupStorage().catch((error) => {\n console.warn(\"Failed to cleanup upload storage:\", error);\n });\n }\n\n /**\n * Cleans up the upload record from IndexedDB\n *\n * Removes the upload record to free up storage space.\n * Called when upload is cancelled or completed.\n *\n * @remarks\n * - Gracefully handles storage errors\n * - Does not throw errors\n *\n * @internal\n */\n private async cleanupStorage(): Promise<void> {\n try {\n if (this.storage.isAvailable()) {\n await this.storage.deleteRecord(this.id);\n }\n } catch (error) {\n // Ignore cleanup errors\n console.warn(\"Failed to cleanup storage:\", error);\n }\n }\n}\n","/**\n * UploadManager - Manages multiple upload tasks\n *\n * Provides centralized management for multiple file uploads including:\n * - Task creation and lifecycle management\n * - Task queue management\n * - Plugin system for extensibility\n * - Automatic resume of unfinished tasks\n * - Persistent storage integration\n */\n\nimport type { RequestAdapter, UploadProgress } from \"@chunkflowjs/protocol\";\nimport { UploadStorage } from \"@chunkflowjs/shared\";\nimport { UploadTask, type UploadTaskOptions } from \"./upload-task\";\n\n/**\n * Plugin interface for extending UploadManager functionality\n *\n * Plugins can hook into various lifecycle events of the upload manager\n * and individual tasks to add custom behavior, logging, analytics, etc.\n *\n * @remarks\n * - Validates: Requirement 6.5 (Plugin mechanism for extensibility)\n * - Validates: Requirement 8.5 (Hook and Plugin mechanism)\n * - All methods are optional - implement only what you need\n * - Plugins are called in the order they were registered\n * - Plugin errors are caught and logged but don't stop execution\n *\n * @example\n * ```typescript\n * class LoggerPlugin implements Plugin {\n * name = 'logger';\n *\n * onTaskCreated(task: UploadTask): void {\n * console.log(`Task created: ${task.id}`);\n * }\n *\n * onTaskProgress(task: UploadTask, progress: UploadProgress): void {\n * console.log(`Task ${task.id}: ${progress.percentage}%`);\n * }\n * }\n * ```\n */\nexport interface Plugin {\n /** Unique plugin name */\n name: string;\n\n /**\n * Called when the plugin is installed\n * @param manager - The UploadManager instance\n */\n install?(manager: UploadManager): void;\n\n /**\n * Called when a new task is created\n * @param task - The newly created UploadTask\n */\n onTaskCreated?(task: UploadTask): void;\n\n /**\n * Called when a task starts uploading\n * @param task - The UploadTask that started\n */\n onTaskStart?(task: UploadTask): void;\n\n /**\n * Called when a task's progress updates\n * @param task - The UploadTask with updated progress\n * @param progress - Current upload progress\n */\n onTaskProgress?(task: UploadTask, progress: UploadProgress): void;\n\n /**\n * Called when a task completes successfully\n * @param task - The completed UploadTask\n * @param fileUrl - URL of the uploaded file\n */\n onTaskSuccess?(task: UploadTask, fileUrl: string): void;\n\n /**\n * Called when a task encounters an error\n * @param task - The UploadTask that errored\n * @param error - The error that occurred\n */\n onTaskError?(task: UploadTask, error: Error): void;\n\n /**\n * Called when a task is paused\n * @param task - The paused UploadTask\n */\n onTaskPause?(task: UploadTask): void;\n\n /**\n * Called when a task is resumed\n * @param task - The resumed UploadTask\n */\n onTaskResume?(task: UploadTask): void;\n\n /**\n * Called when a task is cancelled\n * @param task - The cancelled UploadTask\n */\n onTaskCancel?(task: UploadTask): void;\n}\n\n/**\n * Options for configuring the UploadManager\n */\nexport interface UploadManagerOptions {\n /** Request adapter for API calls */\n requestAdapter: RequestAdapter;\n /** Maximum number of concurrent tasks (default: 3) */\n maxConcurrentTasks?: number;\n /** Default chunk size for new tasks in bytes (default: 1MB) */\n defaultChunkSize?: number;\n /** Default concurrency for chunk uploads per task (default: 3) */\n defaultConcurrency?: number;\n /** Whether to automatically resume unfinished tasks on init (default: true) */\n autoResumeUnfinished?: boolean;\n}\n\n/**\n * UploadManager class\n *\n * Central manager for handling multiple file upload tasks.\n * Provides task lifecycle management, plugin system, and persistent storage.\n *\n * @example\n * ```typescript\n * const manager = new UploadManager({\n * requestAdapter: myAdapter,\n * maxConcurrentTasks: 3,\n * defaultChunkSize: 1024 * 1024, // 1MB\n * });\n *\n * // Initialize (loads unfinished tasks if enabled)\n * await manager.init();\n *\n * // Create and start a task\n * const task = manager.createTask(file);\n * await task.start();\n *\n * // Get all tasks\n * const allTasks = manager.getAllTasks();\n *\n * // Delete a task\n * await manager.deleteTask(task.id);\n * ```\n */\nexport class UploadManager {\n /** Map of task ID to UploadTask instances */\n private tasks: Map<string, UploadTask>;\n\n /** Manager options with defaults applied */\n private options: Required<UploadManagerOptions>;\n\n /** Storage instance for persistent task data */\n private storage: UploadStorage;\n\n /** Flag indicating if manager has been initialized */\n private initialized: boolean;\n\n /** Registered plugins */\n private plugins: Plugin[];\n\n /**\n * Creates a new UploadManager instance\n *\n * @param options - Configuration options for the manager\n *\n * @remarks\n * - Validates: Requirement 8.6 (UploadManager manages multiple tasks)\n * - Applies default values for optional parameters\n * - Creates storage instance for persistence\n * - Does not automatically initialize - call init() explicitly\n */\n constructor(options: UploadManagerOptions) {\n // Initialize tasks map\n this.tasks = new Map();\n\n // Apply default options\n this.options = {\n requestAdapter: options.requestAdapter,\n maxConcurrentTasks: options.maxConcurrentTasks ?? 3,\n defaultChunkSize: options.defaultChunkSize ?? 1024 * 1024, // 1MB\n defaultConcurrency: options.defaultConcurrency ?? 3,\n autoResumeUnfinished: options.autoResumeUnfinished ?? true,\n };\n\n // Create storage instance\n this.storage = new UploadStorage();\n\n // Initialize plugins array\n this.plugins = [];\n\n // Initialize flag\n this.initialized = false;\n }\n\n /**\n * Registers a plugin with the manager\n *\n * Plugins can hook into task lifecycle events to add custom behavior.\n * Plugins are called in the order they were registered.\n *\n * @param plugin - Plugin instance to register\n *\n * @remarks\n * - Validates: Requirement 6.5 (Plugin mechanism)\n * - Validates: Requirement 8.5 (Plugin system)\n * - Plugin's install() method is called immediately if provided\n * - Plugin errors are caught and logged but don't stop execution\n * - Duplicate plugin names are allowed (no uniqueness check)\n *\n * @example\n * ```typescript\n * const logger = new LoggerPlugin();\n * manager.use(logger);\n *\n * const stats = new StatisticsPlugin();\n * manager.use(stats);\n * ```\n */\n use(plugin: Plugin): void {\n // Add plugin to array\n this.plugins.push(plugin);\n\n // Call install hook if provided\n if (plugin.install) {\n try {\n plugin.install(this);\n } catch (error) {\n console.error(`Plugin \"${plugin.name}\" install failed:`, error);\n }\n }\n }\n\n /**\n * Calls a plugin hook for all registered plugins\n *\n * @param hookName - Name of the hook to call\n * @param args - Arguments to pass to the hook\n *\n * @internal\n */\n private callPluginHook<K extends keyof Plugin>(hookName: K, ...args: unknown[]): void {\n for (const plugin of this.plugins) {\n const hook = plugin[hookName];\n if (hook && typeof hook === \"function\") {\n try {\n (hook as (...args: unknown[]) => void).apply(plugin, args);\n } catch (error) {\n console.error(`Plugin \"${plugin.name}\" hook \"${String(hookName)}\" failed:`, error);\n }\n }\n }\n }\n\n /**\n * Initializes the UploadManager\n *\n * Performs initialization tasks including:\n * - Initializing IndexedDB storage\n * - Loading unfinished tasks if autoResumeUnfinished is enabled\n *\n * @remarks\n * - Validates: Requirement 8.6 (initialization and task management)\n * - Should be called once before using the manager\n * - Safe to call multiple times (idempotent)\n * - Gracefully handles storage initialization failures\n *\n * @example\n * ```typescript\n * const manager = new UploadManager({ requestAdapter });\n * await manager.init();\n * ```\n */\n async init(): Promise<void> {\n // Skip if already initialized\n if (this.initialized) {\n return;\n }\n\n try {\n // Initialize storage\n await this.storage.init();\n\n // Load unfinished tasks if enabled\n if (this.options.autoResumeUnfinished) {\n await this.loadUnfinishedTasks();\n }\n\n // Mark as initialized\n this.initialized = true;\n } catch (error) {\n // Silently ignore storage initialization errors\n // Manager can still work without storage\n this.initialized = true; // Still mark as initialized\n }\n }\n\n /**\n * Creates a new upload task\n *\n * Creates an UploadTask instance for the given file and adds it to the manager.\n * The task is not automatically started - call task.start() to begin upload.\n *\n * @param file - File to upload\n * @param options - Optional task-specific configuration (overrides defaults)\n * @returns Created UploadTask instance\n *\n * @remarks\n * - Validates: Requirement 8.6 (task creation and management)\n * - Task is added to the manager's task map\n * - Uses manager's default options unless overridden\n * - Task is not started automatically\n *\n * @example\n * ```typescript\n * const task = manager.createTask(file, {\n * chunkSize: 2 * 1024 * 1024, // 2MB\n * concurrency: 5,\n * });\n *\n * task.on('progress', ({ progress }) => {\n * console.log(`Progress: ${progress}%`);\n * });\n *\n * await task.start();\n * ```\n */\n createTask(file: File, options?: Partial<UploadTaskOptions>): UploadTask {\n // Create task with merged options\n const task = new UploadTask({\n file,\n requestAdapter: this.options.requestAdapter,\n chunkSize: options?.chunkSize ?? this.options.defaultChunkSize,\n concurrency: options?.concurrency ?? this.options.defaultConcurrency,\n retryCount: options?.retryCount ?? 3,\n retryDelay: options?.retryDelay ?? 1000,\n autoStart: options?.autoStart ?? false,\n });\n\n // Add task to map\n this.tasks.set(task.id, task);\n\n // Call plugin hook\n this.callPluginHook(\"onTaskCreated\", task);\n\n // Set up event listeners for plugin hooks\n this.setupTaskPluginHooks(task);\n\n return task;\n }\n\n /**\n * Sets up event listeners on a task to call plugin hooks\n *\n * @param task - Task to set up listeners for\n *\n * @internal\n */\n private setupTaskPluginHooks(task: UploadTask): void {\n // Start event\n task.on(\"start\", () => {\n this.callPluginHook(\"onTaskStart\", task);\n });\n\n // Progress event\n task.on(\"progress\", () => {\n const progressData = task.getProgress();\n this.callPluginHook(\"onTaskProgress\", task, progressData);\n });\n\n // Success event\n task.on(\"success\", ({ fileUrl }) => {\n this.callPluginHook(\"onTaskSuccess\", task, fileUrl);\n });\n\n // Error event\n task.on(\"error\", ({ error }) => {\n this.callPluginHook(\"onTaskError\", task, error);\n });\n\n // Pause event\n task.on(\"pause\", () => {\n this.callPluginHook(\"onTaskPause\", task);\n });\n\n // Resume event\n task.on(\"resume\", () => {\n this.callPluginHook(\"onTaskResume\", task);\n });\n\n // Cancel event\n task.on(\"cancel\", () => {\n this.callPluginHook(\"onTaskCancel\", task);\n });\n }\n\n /**\n * Gets a task by its ID\n *\n * @param taskId - Unique task identifier\n * @returns UploadTask instance or undefined if not found\n *\n * @remarks\n * - Validates: Requirement 8.6 (task retrieval)\n *\n * @example\n * ```typescript\n * const task = manager.getTask('task_abc123');\n * if (task) {\n * console.log(`Status: ${task.getStatus()}`);\n * }\n * ```\n */\n getTask(taskId: string): UploadTask | undefined {\n return this.tasks.get(taskId);\n }\n\n /**\n * Gets all tasks managed by this manager\n *\n * @returns Array of all UploadTask instances\n *\n * @remarks\n * - Validates: Requirement 8.6 (task retrieval)\n * - Returns a new array (safe to modify)\n * - Tasks are in insertion order\n *\n * @example\n * ```typescript\n * const allTasks = manager.getAllTasks();\n * console.log(`Total tasks: ${allTasks.length}`);\n *\n * // Filter by status\n * const uploadingTasks = allTasks.filter(\n * task => task.getStatus() === 'uploading'\n * );\n * ```\n */\n getAllTasks(): UploadTask[] {\n return Array.from(this.tasks.values());\n }\n\n /**\n * Deletes a task from the manager\n *\n * Cancels the task if it's still running and removes it from the manager.\n * Also cleans up the task's storage record.\n *\n * @param taskId - Unique task identifier\n *\n * @remarks\n * - Validates: Requirement 8.6 (task deletion)\n * - Cancels the task if it's still running\n * - Removes task from manager's task map\n * - Cleans up storage record\n * - Safe to call even if task doesn't exist\n *\n * @example\n * ```typescript\n * // Delete a specific task\n * await manager.deleteTask('task_abc123');\n *\n * // Delete all completed tasks\n * const tasks = manager.getAllTasks();\n * for (const task of tasks) {\n * if (task.getStatus() === 'success') {\n * await manager.deleteTask(task.id);\n * }\n * }\n * ```\n */\n async deleteTask(taskId: string): Promise<void> {\n const task = this.tasks.get(taskId);\n\n if (task) {\n // Cancel the task if it's still running\n const status = task.getStatus();\n if (status === \"uploading\" || status === \"paused\") {\n task.cancel();\n }\n\n // Remove from tasks map\n this.tasks.delete(taskId);\n\n // Clean up storage record\n try {\n if (this.storage.isAvailable()) {\n await this.storage.deleteRecord(taskId);\n }\n } catch (error) {\n // Log warning but don't fail deletion\n console.warn(`Failed to delete storage record for task ${taskId}:`, error);\n }\n }\n }\n\n /**\n * Loads unfinished tasks from storage\n *\n * Retrieves upload records from IndexedDB and creates task placeholders.\n * Note: Tasks cannot be automatically resumed because File objects cannot\n * be persisted. Users must re-select files to resume uploads.\n *\n * @remarks\n * - Validates: Requirement 4.2 (read unfinished tasks from IndexedDB)\n * - Creates task entries in the manager\n * - Tasks are in 'paused' state and require file re-selection to resume\n * - Gracefully handles storage errors\n *\n * @internal\n */\n private async loadUnfinishedTasks(): Promise<void> {\n try {\n // Check if storage is available\n if (!this.storage.isAvailable()) {\n return;\n }\n\n // Get all records from storage\n await this.storage.getAllRecords();\n\n // Note: We cannot automatically create UploadTask instances because\n // File objects cannot be persisted to IndexedDB. The user would need\n // to re-select the files to resume uploads.\n //\n // This is a limitation of the browser File API - File objects are\n // references to files on the user's filesystem and cannot be serialized.\n //\n // A future enhancement could:\n // 1. Store file metadata (name, size, type, lastModified)\n // 2. Provide a UI for users to re-select files\n // 3. Match re-selected files with stored metadata\n // 4. Resume uploads from stored progress\n //\n // For now, we silently track unfinished tasks\n // (logging removed to avoid test noise)\n } catch (error) {\n // Silently ignore errors loading unfinished tasks\n }\n }\n\n /**\n * Gets information about unfinished tasks from storage\n *\n * Returns metadata about uploads that were not completed in previous sessions.\n * This allows UI layers to prompt users to resume uploads by re-selecting files.\n *\n * @returns Array of unfinished upload records with file metadata\n *\n * @remarks\n * - Validates: Requirement 4.2 (read unfinished tasks from IndexedDB)\n * - Validates: Requirement 4.3 (provide interface for resuming tasks)\n * - Returns empty array if storage is unavailable or on error\n * - File objects cannot be restored - users must re-select files\n *\n * @example\n * ```typescript\n * const unfinished = await manager.getUnfinishedTasksInfo();\n * if (unfinished.length > 0) {\n * console.log('Found unfinished uploads:');\n * unfinished.forEach(record => {\n * console.log(`- ${record.fileInfo.name} (${record.uploadedChunks.length} chunks uploaded)`);\n * });\n * }\n * ```\n */\n async getUnfinishedTasksInfo(): Promise<\n Array<{\n taskId: string;\n fileInfo: {\n name: string;\n size: number;\n type: string;\n lastModified: number;\n };\n uploadedChunks: number[];\n uploadToken: string;\n createdAt: number;\n updatedAt: number;\n }>\n > {\n try {\n // Check if storage is available\n if (!this.storage.isAvailable()) {\n return [];\n }\n\n // Get all records from storage\n const records = await this.storage.getAllRecords();\n\n // Return records with file metadata\n return records.map((record) => ({\n taskId: record.taskId,\n fileInfo: {\n name: record.fileInfo.name,\n size: record.fileInfo.size,\n type: record.fileInfo.type,\n lastModified: record.fileInfo.lastModified,\n },\n uploadedChunks: record.uploadedChunks,\n uploadToken: record.uploadToken,\n createdAt: record.createdAt,\n updatedAt: record.updatedAt,\n }));\n } catch (error) {\n console.warn(\"Failed to get unfinished tasks info:\", error);\n return [];\n }\n }\n\n /**\n * Resumes an unfinished upload task with a re-selected file\n *\n * Allows users to resume a previously interrupted upload by providing the\n * original task ID and re-selecting the file. The method validates that the\n * file matches the stored metadata and creates a new task that continues\n * from the last uploaded chunk.\n *\n * @param taskId - ID of the unfinished task to resume\n * @param file - Re-selected file (must match original file metadata)\n * @param options - Optional task configuration overrides\n * @returns Created UploadTask instance ready to resume\n * @throws Error if task record not found or file doesn't match\n *\n * @remarks\n * - Validates: Requirement 4.3 (resume unfinished tasks)\n * - Validates: Requirement 4.4 (continue from last uploaded chunk)\n * - Verifies file matches stored metadata (name, size, type, lastModified)\n * - Creates new task with stored progress\n * - Removes old storage record and creates new one with same ID\n *\n * @example\n * ```typescript\n * // Get unfinished tasks\n * const unfinished = await manager.getUnfinishedTasksInfo();\n *\n * // User re-selects file\n * const file = await selectFile();\n *\n * // Resume upload\n * try {\n * const task = await manager.resumeTask(unfinished[0].taskId, file);\n * await task.start();\n * } catch (error) {\n * console.error('Failed to resume:', error);\n * }\n * ```\n */\n async resumeTask(\n taskId: string,\n file: File,\n options?: Partial<UploadTaskOptions>,\n ): Promise<UploadTask> {\n // Check if storage is available\n if (!this.storage.isAvailable()) {\n throw new Error(\"Storage is not available - cannot resume task\");\n }\n\n // Get the stored record\n const record = await this.storage.getRecord(taskId);\n if (!record) {\n throw new Error(`No unfinished task found with ID: ${taskId}`);\n }\n\n // Validate file matches stored metadata\n if (file.name !== record.fileInfo.name) {\n throw new Error(`File name mismatch: expected \"${record.fileInfo.name}\", got \"${file.name}\"`);\n }\n\n if (file.size !== record.fileInfo.size) {\n throw new Error(`File size mismatch: expected ${record.fileInfo.size}, got ${file.size}`);\n }\n\n if (file.type !== record.fileInfo.type) {\n throw new Error(`File type mismatch: expected \"${record.fileInfo.type}\", got \"${file.type}\"`);\n }\n\n // Note: lastModified check is optional as it may change if file is copied\n // We rely on name, size, and type for validation\n\n // Create a new task with the same ID\n const task = new UploadTask({\n file,\n requestAdapter: this.options.requestAdapter,\n chunkSize: options?.chunkSize ?? this.options.defaultChunkSize,\n concurrency: options?.concurrency ?? this.options.defaultConcurrency,\n retryCount: options?.retryCount ?? 3,\n retryDelay: options?.retryDelay ?? 1000,\n autoStart: options?.autoStart ?? false,\n resumeTaskId: taskId, // Pass the task ID to resume\n resumeUploadToken: record.uploadToken, // Pass the upload token\n resumeUploadedChunks: record.uploadedChunks, // Pass uploaded chunks\n });\n\n // Add task to manager\n this.tasks.set(task.id, task);\n\n // Call plugin hook\n this.callPluginHook(\"onTaskCreated\", task);\n\n // Set up event listeners for plugin hooks\n this.setupTaskPluginHooks(task);\n\n // Delete old storage record (new one will be created when task starts)\n try {\n await this.storage.deleteRecord(taskId);\n } catch (error) {\n console.warn(`Failed to delete old storage record for task ${taskId}:`, error);\n }\n\n return task;\n }\n\n /**\n * Clears a specific unfinished task record from storage\n *\n * Removes the storage record for an unfinished task without resuming it.\n * Useful for cleaning up tasks that the user no longer wants to resume.\n *\n * @param taskId - ID of the unfinished task to clear\n *\n * @remarks\n * - Validates: Requirement 4.5 (clear saved upload records)\n * - Safe to call even if record doesn't exist\n * - Does not affect active tasks in the manager\n *\n * @example\n * ```typescript\n * // Clear a specific unfinished task\n * await manager.clearUnfinishedTask('task_abc123');\n *\n * // Clear all unfinished tasks\n * const unfinished = await manager.getUnfinishedTasksInfo();\n * for (const record of unfinished) {\n * await manager.clearUnfinishedTask(record.taskId);\n * }\n * ```\n */\n async clearUnfinishedTask(taskId: string): Promise<void> {\n try {\n if (this.storage.isAvailable()) {\n await this.storage.deleteRecord(taskId);\n }\n } catch (error) {\n console.warn(`Failed to clear unfinished task ${taskId}:`, error);\n }\n }\n\n /**\n * Clears all unfinished task records from storage\n *\n * Removes all storage records for unfinished tasks.\n * Useful for cleaning up when users don't want to resume any uploads.\n *\n * @returns Number of records cleared\n *\n * @remarks\n * - Validates: Requirement 4.5 (clear saved upload records)\n * - Does not affect active tasks in the manager\n * - Returns 0 if storage is unavailable or on error\n *\n * @example\n * ```typescript\n * const cleared = await manager.clearAllUnfinishedTasks();\n * console.log(`Cleared ${cleared} unfinished task(s)`);\n * ```\n */\n async clearAllUnfinishedTasks(): Promise<number> {\n try {\n if (!this.storage.isAvailable()) {\n return 0;\n }\n\n const records = await this.storage.getAllRecords();\n const count = records.length;\n\n await this.storage.clearAll();\n\n return count;\n } catch (error) {\n console.warn(\"Failed to clear all unfinished tasks:\", error);\n return 0;\n }\n }\n\n /**\n * Gets the number of tasks in the manager\n *\n * @returns Total number of tasks\n *\n * @example\n * ```typescript\n * console.log(`Total tasks: ${manager.getTaskCount()}`);\n * ```\n */\n getTaskCount(): number {\n return this.tasks.size;\n }\n\n /**\n * Checks if the manager has been initialized\n *\n * @returns True if initialized, false otherwise\n *\n * @example\n * ```typescript\n * if (!manager.isInitialized()) {\n * await manager.init();\n * }\n * ```\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n\n /**\n * Clears all completed tasks from the manager\n *\n * Removes tasks with 'success', 'error', or 'cancelled' status.\n * Does not affect running or paused tasks.\n *\n * @returns Number of tasks cleared\n *\n * @example\n * ```typescript\n * const cleared = await manager.clearCompletedTasks();\n * console.log(`Cleared ${cleared} completed task(s)`);\n * ```\n */\n async clearCompletedTasks(): Promise<number> {\n const tasks = this.getAllTasks();\n let clearedCount = 0;\n\n for (const task of tasks) {\n const status = task.getStatus();\n if (status === \"success\" || status === \"error\" || status === \"cancelled\") {\n await this.deleteTask(task.id);\n clearedCount++;\n }\n }\n\n return clearedCount;\n }\n\n /**\n * Pauses all running tasks\n *\n * Calls pause() on all tasks with 'uploading' status.\n *\n * @returns Number of tasks paused\n *\n * @example\n * ```typescript\n * const paused = manager.pauseAll();\n * console.log(`Paused ${paused} task(s)`);\n * ```\n */\n pauseAll(): number {\n const tasks = this.getAllTasks();\n let pausedCount = 0;\n\n for (const task of tasks) {\n if (task.getStatus() === \"uploading\") {\n task.pause();\n pausedCount++;\n }\n }\n\n return pausedCount;\n }\n\n /**\n * Resumes all paused tasks\n *\n * Calls resume() on all tasks with 'paused' status.\n *\n * @returns Number of tasks resumed\n *\n * @example\n * ```typescript\n * const resumed = await manager.resumeAll();\n * console.log(`Resumed ${resumed} task(s)`);\n * ```\n */\n async resumeAll(): Promise<number> {\n const tasks = this.getAllTasks();\n let resumedCount = 0;\n\n for (const task of tasks) {\n if (task.getStatus() === \"paused\") {\n try {\n await task.resume();\n resumedCount++;\n } catch (error) {\n console.warn(`Failed to resume task ${task.id}:`, error);\n }\n }\n }\n\n return resumedCount;\n }\n\n /**\n * Cancels all running and paused tasks\n *\n * Calls cancel() on all tasks that are not in a terminal state.\n *\n * @returns Number of tasks cancelled\n *\n * @example\n * ```typescript\n * const cancelled = manager.cancelAll();\n * console.log(`Cancelled ${cancelled} task(s)`);\n * ```\n */\n cancelAll(): number {\n const tasks = this.getAllTasks();\n let cancelledCount = 0;\n\n for (const task of tasks) {\n const status = task.getStatus();\n if (status === \"uploading\" || status === \"paused\") {\n task.cancel();\n cancelledCount++;\n }\n }\n\n return cancelledCount;\n }\n\n /**\n * Gets statistics about all tasks\n *\n * @returns Object containing task statistics\n *\n * @example\n * ```typescript\n * const stats = manager.getStatistics();\n * console.log(`Total: ${stats.total}`);\n * console.log(`Uploading: ${stats.uploading}`);\n * console.log(`Success: ${stats.success}`);\n * ```\n */\n getStatistics(): {\n total: number;\n idle: number;\n uploading: number;\n paused: number;\n success: number;\n error: number;\n cancelled: number;\n } {\n const tasks = this.getAllTasks();\n\n const stats = {\n total: tasks.length,\n idle: 0,\n uploading: 0,\n paused: 0,\n success: 0,\n error: 0,\n cancelled: 0,\n };\n\n for (const task of tasks) {\n const status = task.getStatus();\n switch (status) {\n case \"idle\":\n stats.idle++;\n break;\n case \"uploading\":\n stats.uploading++;\n break;\n case \"paused\":\n stats.paused++;\n break;\n case \"success\":\n stats.success++;\n break;\n case \"error\":\n stats.error++;\n break;\n case \"cancelled\":\n stats.cancelled++;\n break;\n }\n }\n\n return stats;\n }\n\n /**\n * Closes the manager and cleans up resources\n *\n * Cancels all running tasks and closes the storage connection.\n * The manager should not be used after calling this method.\n *\n * @example\n * ```typescript\n * // Clean up when done\n * manager.close();\n * ```\n */\n close(): void {\n // Cancel all running tasks\n this.cancelAll();\n\n // Close storage connection\n this.storage.close();\n\n // Clear tasks map\n this.tasks.clear();\n\n // Reset initialized flag\n this.initialized = false;\n }\n}\n","/**\n * Built-in plugins for UploadManager\n *\n * This module provides example plugin implementations that demonstrate\n * how to extend the UploadManager with custom functionality.\n */\n\nimport type { UploadProgress } from \"@chunkflowjs/protocol\";\nimport type { Plugin } from \"./upload-manager\";\nimport type { UploadTask } from \"./upload-task\";\n\n/**\n * Logger plugin for debugging and monitoring uploads\n *\n * Logs all task lifecycle events to the console with timestamps.\n * Useful for development and debugging.\n *\n * @remarks\n * - Validates: Requirement 6.5 (Plugin mechanism example)\n * - Validates: Requirement 8.5 (Plugin system example)\n * - Logs to console with timestamps\n * - Can be configured to log only specific events\n *\n * @example\n * ```typescript\n * const manager = new UploadManager({ requestAdapter });\n * manager.use(new LoggerPlugin());\n *\n * // With custom options\n * manager.use(new LoggerPlugin({\n * logProgress: false, // Don't log progress updates\n * prefix: '[Upload]' // Custom log prefix\n * }));\n * ```\n */\nexport class LoggerPlugin implements Plugin {\n name = \"logger\";\n\n private options: {\n logProgress: boolean;\n logStart: boolean;\n logSuccess: boolean;\n logError: boolean;\n logPause: boolean;\n logResume: boolean;\n logCancel: boolean;\n prefix: string;\n };\n\n constructor(\n options?: Partial<{\n logProgress: boolean;\n logStart: boolean;\n logSuccess: boolean;\n logError: boolean;\n logPause: boolean;\n logResume: boolean;\n logCancel: boolean;\n prefix: string;\n }>,\n ) {\n this.options = {\n logProgress: options?.logProgress ?? true,\n logStart: options?.logStart ?? true,\n logSuccess: options?.logSuccess ?? true,\n logError: options?.logError ?? true,\n logPause: options?.logPause ?? true,\n logResume: options?.logResume ?? true,\n logCancel: options?.logCancel ?? true,\n prefix: options?.prefix ?? \"[LoggerPlugin]\",\n };\n }\n\n private log(message: string, ...args: unknown[]): void {\n const timestamp = new Date().toISOString();\n console.log(`${this.options.prefix} [${timestamp}]`, message, ...args);\n }\n\n install(): void {\n this.log(\"Plugin installed\");\n }\n\n onTaskCreated(task: UploadTask): void {\n this.log(`Task created: ${task.id}`, {\n fileName: task.file.name,\n fileSize: task.file.size,\n fileType: task.file.type,\n });\n }\n\n onTaskStart(task: UploadTask): void {\n if (this.options.logStart) {\n this.log(`Task started: ${task.id}`, {\n fileName: task.file.name,\n });\n }\n }\n\n onTaskProgress(task: UploadTask, progress: UploadProgress): void {\n if (this.options.logProgress) {\n this.log(`Task progress: ${task.id}`, {\n percentage: `${progress.percentage.toFixed(2)}%`,\n uploadedBytes: progress.uploadedBytes,\n totalBytes: progress.totalBytes,\n speed: `${(progress.speed / 1024 / 1024).toFixed(2)} MB/s`,\n remainingTime: `${progress.remainingTime.toFixed(0)}s`,\n });\n }\n }\n\n onTaskSuccess(task: UploadTask, fileUrl: string): void {\n if (this.options.logSuccess) {\n this.log(`Task completed: ${task.id}`, {\n fileName: task.file.name,\n fileUrl,\n });\n }\n }\n\n onTaskError(task: UploadTask, error: Error): void {\n if (this.options.logError) {\n this.log(`Task error: ${task.id}`, {\n fileName: task.file.name,\n error: error.message,\n stack: error.stack,\n });\n }\n }\n\n onTaskPause(task: UploadTask): void {\n if (this.options.logPause) {\n this.log(`Task paused: ${task.id}`, {\n fileName: task.file.name,\n });\n }\n }\n\n onTaskResume(task: UploadTask): void {\n if (this.options.logResume) {\n this.log(`Task resumed: ${task.id}`, {\n fileName: task.file.name,\n });\n }\n }\n\n onTaskCancel(task: UploadTask): void {\n if (this.options.logCancel) {\n this.log(`Task cancelled: ${task.id}`, {\n fileName: task.file.name,\n });\n }\n }\n}\n\n/**\n * Statistics plugin for tracking upload metrics\n *\n * Collects statistics about uploads including success/error counts,\n * total bytes uploaded, average speed, etc.\n *\n * @remarks\n * - Validates: Requirement 6.5 (Plugin mechanism example)\n * - Validates: Requirement 8.5 (Plugin system example)\n * - Tracks upload statistics in memory\n * - Provides methods to retrieve and reset statistics\n * - Thread-safe for concurrent uploads\n *\n * @example\n * ```typescript\n * const stats = new StatisticsPlugin();\n * manager.use(stats);\n *\n * // Later, get statistics\n * const metrics = stats.getStats();\n * console.log(`Success rate: ${metrics.successRate}%`);\n * console.log(`Total uploaded: ${metrics.totalBytesUploaded} bytes`);\n * console.log(`Average speed: ${metrics.averageSpeed} bytes/s`);\n * ```\n */\nexport class StatisticsPlugin implements Plugin {\n name = \"statistics\";\n\n private stats = {\n totalFiles: 0,\n successCount: 0,\n errorCount: 0,\n cancelledCount: 0,\n totalBytesUploaded: 0,\n totalUploadTime: 0, // milliseconds\n startTimes: new Map<string, number>(),\n };\n\n install(): void {\n // No initialization needed\n }\n\n onTaskCreated(_task: UploadTask): void {\n this.stats.totalFiles++;\n }\n\n onTaskStart(task: UploadTask): void {\n // Record start time for this task\n this.stats.startTimes.set(task.id, Date.now());\n }\n\n onTaskSuccess(task: UploadTask, _fileUrl: string): void {\n this.stats.successCount++;\n this.stats.totalBytesUploaded += task.file.size;\n\n // Calculate upload time\n const startTime = this.stats.startTimes.get(task.id);\n if (startTime) {\n const uploadTime = Date.now() - startTime;\n this.stats.totalUploadTime += uploadTime;\n this.stats.startTimes.delete(task.id);\n }\n }\n\n onTaskError(task: UploadTask, _error: Error): void {\n this.stats.errorCount++;\n\n // Clean up start time\n this.stats.startTimes.delete(task.id);\n }\n\n onTaskCancel(task: UploadTask): void {\n this.stats.cancelledCount++;\n\n // Clean up start time\n this.stats.startTimes.delete(task.id);\n }\n\n /**\n * Gets current statistics\n *\n * @returns Object containing upload statistics\n *\n * @example\n * ```typescript\n * const stats = plugin.getStats();\n * console.log(`Success rate: ${stats.successRate}%`);\n * ```\n */\n getStats(): {\n totalFiles: number;\n successCount: number;\n errorCount: number;\n cancelledCount: number;\n totalBytesUploaded: number;\n averageSpeed: number; // bytes per second\n averageUploadTime: number; // milliseconds\n successRate: number; // percentage\n errorRate: number; // percentage\n } {\n const completedCount =\n this.stats.successCount + this.stats.errorCount + this.stats.cancelledCount;\n const averageSpeed =\n this.stats.totalUploadTime > 0\n ? (this.stats.totalBytesUploaded / this.stats.totalUploadTime) * 1000\n : 0;\n const averageUploadTime =\n this.stats.successCount > 0 ? this.stats.totalUploadTime / this.stats.successCount : 0;\n const successRate = completedCount > 0 ? (this.stats.successCount / completedCount) * 100 : 0;\n const errorRate = completedCount > 0 ? (this.stats.errorCount / completedCount) * 100 : 0;\n\n return {\n totalFiles: this.stats.totalFiles,\n successCount: this.stats.successCount,\n errorCount: this.stats.errorCount,\n cancelledCount: this.stats.cancelledCount,\n totalBytesUploaded: this.stats.totalBytesUploaded,\n averageSpeed,\n averageUploadTime,\n successRate,\n errorRate,\n };\n }\n\n /**\n * Resets all statistics to zero\n *\n * @example\n * ```typescript\n * plugin.reset();\n * ```\n */\n reset(): void {\n this.stats = {\n totalFiles: 0,\n successCount: 0,\n errorCount: 0,\n cancelledCount: 0,\n totalBytesUploaded: 0,\n totalUploadTime: 0,\n startTimes: new Map(),\n };\n }\n\n /**\n * Gets a formatted summary of statistics\n *\n * @returns Human-readable statistics summary\n *\n * @example\n * ```typescript\n * console.log(plugin.getSummary());\n * // Output:\n * // Upload Statistics:\n * // Total Files: 10\n * // Success: 8 (80.00%)\n * // Errors: 1 (10.00%)\n * // Cancelled: 1 (10.00%)\n * // Total Uploaded: 52.43 MB\n * // Average Speed: 2.15 MB/s\n * // Average Time: 24.5s\n * ```\n */\n getSummary(): string {\n const stats = this.getStats();\n const formatBytes = (bytes: number): string => {\n const mb = bytes / 1024 / 1024;\n return `${mb.toFixed(2)} MB`;\n };\n const formatSpeed = (bytesPerSecond: number): string => {\n const mbps = bytesPerSecond / 1024 / 1024;\n return `${mbps.toFixed(2)} MB/s`;\n };\n const formatTime = (ms: number): string => {\n const seconds = ms / 1000;\n return `${seconds.toFixed(1)}s`;\n };\n\n return `Upload Statistics:\n Total Files: ${stats.totalFiles}\n Success: ${stats.successCount} (${stats.successRate.toFixed(2)}%)\n Errors: ${stats.errorCount} (${stats.errorRate.toFixed(2)}%)\n Cancelled: ${stats.cancelledCount}\n Total Uploaded: ${formatBytes(stats.totalBytesUploaded)}\n Average Speed: ${formatSpeed(stats.averageSpeed)}\n Average Time: ${formatTime(stats.averageUploadTime)}`;\n }\n}\n","/**\n * Fetch-based RequestAdapter implementation\n * Provides a simple way to create an adapter using the Fetch API\n */\n\nimport type { RequestAdapter } from \"@chunkflowjs/protocol\";\n\n/**\n * Options for creating a Fetch adapter\n */\nexport interface FetchAdapterOptions {\n /** Base URL for API requests */\n baseURL: string;\n /** Custom headers to include in all requests */\n headers?: Record<string, string>;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Custom fetch implementation (default: global fetch) */\n fetch?: typeof fetch;\n /** Callback for handling errors */\n onError?: (error: Error) => void;\n}\n\n/**\n * Create a Fetch-based RequestAdapter\n *\n * @param options - Configuration options\n * @returns RequestAdapter instance\n *\n * @example\n * ```typescript\n * const adapter = createFetchAdapter({\n * baseURL: 'http://localhost:3000/api',\n * headers: {\n * 'Authorization': 'Bearer token123'\n * }\n * });\n *\n * const manager = new UploadManager({ requestAdapter: adapter });\n * ```\n */\nexport function createFetchAdapter(options: FetchAdapterOptions): RequestAdapter {\n const {\n baseURL,\n headers = {},\n timeout = 30000,\n fetch: customFetch = globalThis.fetch,\n onError,\n } = options;\n\n // Ensure baseURL doesn't end with slash\n const normalizedBaseURL = baseURL.replace(/\\/$/, \"\");\n\n /**\n * Make a fetch request with timeout\n */\n async function fetchWithTimeout(url: string, init?: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await customFetch(url, {\n ...init,\n signal: controller.signal,\n headers: {\n ...headers,\n ...init?.headers,\n },\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const error = new Error(`HTTP ${response.status}: ${response.statusText}`);\n if (onError) onError(error);\n throw error;\n }\n\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n if (onError && error instanceof Error) onError(error);\n throw error;\n }\n }\n\n return {\n /**\n * Create a new file upload session\n */\n async createFile(request) {\n const response = await fetchWithTimeout(`${normalizedBaseURL}/upload/create`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n fileName: request.fileName,\n fileSize: request.fileSize,\n fileType: request.fileType,\n preferredChunkSize: request.preferredChunkSize,\n }),\n });\n\n const data = await response.json();\n\n // Convert server response to protocol format\n // Server returns { uploadToken: string, negotiatedChunkSize: number }\n // Protocol expects { uploadToken: UploadToken, negotiatedChunkSize: number }\n return {\n uploadToken: {\n token: data.uploadToken,\n fileId: \"\", // Will be extracted from JWT if needed\n chunkSize: data.negotiatedChunkSize,\n expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours\n },\n negotiatedChunkSize: data.negotiatedChunkSize,\n };\n },\n\n /**\n * Verify file and chunk hashes\n */\n async verifyHash(request) {\n // Extract token string from UploadToken object or use string directly\n const token =\n typeof request.uploadToken === \"string\"\n ? request.uploadToken\n : (request.uploadToken as any).token;\n\n const response = await fetchWithTimeout(`${normalizedBaseURL}/upload/verify`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n fileHash: request.fileHash,\n chunkHashes: request.chunkHashes,\n uploadToken: token,\n }),\n });\n\n return response.json();\n },\n\n /**\n * Upload a single chunk\n */\n async uploadChunk(request) {\n // Extract token string from UploadToken object or use string directly\n const token =\n typeof request.uploadToken === \"string\"\n ? request.uploadToken\n : (request.uploadToken as any).token;\n\n const formData = new FormData();\n formData.append(\"uploadToken\", token);\n formData.append(\"chunkIndex\", request.chunkIndex.toString());\n formData.append(\"chunkHash\", request.chunkHash);\n\n // Handle both Blob and Buffer types\n if (request.chunk instanceof Blob) {\n formData.append(\"chunk\", request.chunk);\n } else {\n // Convert Buffer to Blob for Node.js environments\n // Use type assertion as Buffer is compatible with BlobPart at runtime\n const blob = new Blob([request.chunk as unknown as BlobPart]);\n formData.append(\"chunk\", blob);\n }\n\n const response = await fetchWithTimeout(`${normalizedBaseURL}/upload/chunk`, {\n method: \"POST\",\n body: formData,\n });\n\n return response.json();\n },\n\n /**\n * Merge all chunks into final file\n */\n async mergeFile(request) {\n // Extract token string from UploadToken object or use string directly\n const token =\n typeof request.uploadToken === \"string\"\n ? request.uploadToken\n : (request.uploadToken as any).token;\n\n const response = await fetchWithTimeout(`${normalizedBaseURL}/upload/merge`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n uploadToken: token,\n fileHash: request.fileHash,\n chunkHashes: request.chunkHashes,\n }),\n });\n\n return response.json();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/chunk-size-adjuster.ts","../src/chunk-size-adjuster-tcp.ts","../src/upload-task.ts","../src/upload-manager.ts","../src/plugins.ts","../src/adapters/fetch-adapter.ts","../src/adapters/xhr-adapter.ts"],"sourcesContent":["import type {\n IChunkSizeAdjuster,\n BaseChunkSizeAdjusterOptions,\n} from \"./chunk-size-adjuster-interface\";\n\n/**\n * Options for configuring the ChunkSizeAdjuster\n */\nexport interface ChunkSizeAdjusterOptions extends BaseChunkSizeAdjusterOptions {}\n\n/**\n * ChunkSizeAdjuster dynamically adjusts chunk sizes based on upload performance.\n * Uses a simple binary strategy: doubles size when fast, halves when slow.\n *\n * @example\n * ```typescript\n * const adjuster = new ChunkSizeAdjuster({\n * initialSize: 1024 * 1024, // 1MB\n * minSize: 256 * 1024, // 256KB\n * maxSize: 10 * 1024 * 1024, // 10MB\n * targetTime: 3000 // 3 seconds\n * });\n *\n * // After uploading a chunk\n * const uploadTimeMs = 1500;\n * const newSize = adjuster.adjust(uploadTimeMs);\n * ```\n */\nexport class ChunkSizeAdjuster implements IChunkSizeAdjuster {\n private currentSize: number;\n private readonly options: Required<ChunkSizeAdjusterOptions>;\n\n constructor(options: ChunkSizeAdjusterOptions) {\n this.currentSize = options.initialSize;\n this.options = {\n targetTime: 3000, // Default 3 seconds\n ...options,\n };\n\n // Validate options\n if (this.options.minSize > this.options.maxSize) {\n throw new Error(\"minSize cannot be greater than maxSize\");\n }\n if (\n this.options.initialSize < this.options.minSize ||\n this.options.initialSize > this.options.maxSize\n ) {\n throw new Error(\"initialSize must be between minSize and maxSize\");\n }\n if (this.options.targetTime <= 0) {\n throw new Error(\"targetTime must be positive\");\n }\n }\n\n /**\n * Adjusts the chunk size based on the upload time of the previous chunk.\n * Uses a simple binary strategy:\n * - If upload is fast (< 50% of target time): double the chunk size\n * - If upload is slow (> 150% of target time): halve the chunk size\n * - Otherwise: keep the current size\n *\n * @param uploadTimeMs - The time taken to upload the previous chunk in milliseconds\n * @returns The new chunk size in bytes\n */\n adjust(uploadTimeMs: number): number {\n if (uploadTimeMs < 0) {\n throw new Error(\"uploadTimeMs cannot be negative\");\n }\n\n const { targetTime, minSize, maxSize } = this.options;\n\n // Fast upload: increase chunk size (similar to TCP slow start)\n if (uploadTimeMs < targetTime * 0.5) {\n this.currentSize = Math.min(this.currentSize * 2, maxSize);\n }\n // Slow upload: decrease chunk size (congestion avoidance)\n else if (uploadTimeMs > targetTime * 1.5) {\n this.currentSize = Math.max(this.currentSize / 2, minSize);\n }\n // Upload time is within acceptable range: keep current size\n\n return this.currentSize;\n }\n\n /**\n * Gets the current chunk size without adjusting it.\n *\n * @returns The current chunk size in bytes\n */\n getCurrentSize(): number {\n return this.currentSize;\n }\n\n /**\n * Resets the chunk size to the initial size.\n * Useful when starting a new upload or after an error.\n */\n reset(): void {\n this.currentSize = this.options.initialSize;\n }\n}\n","import type {\n IChunkSizeAdjuster,\n BaseChunkSizeAdjusterOptions,\n} from \"./chunk-size-adjuster-interface\";\n\n/**\n * TCP Slow Start inspired chunk size adjuster\n * Implements a more accurate TCP-like congestion control algorithm\n */\n\nexport interface TCPChunkSizeAdjusterOptions extends BaseChunkSizeAdjusterOptions {\n /**\n * Initial slow start threshold (default: maxSize / 2)\n */\n initialSsthresh?: number;\n}\n\nexport enum CongestionState {\n SLOW_START = \"slow_start\",\n CONGESTION_AVOIDANCE = \"congestion_avoidance\",\n FAST_RECOVERY = \"fast_recovery\",\n}\n\n/**\n * TCP-inspired chunk size adjuster with proper slow start and congestion avoidance\n *\n * Algorithm:\n * 1. Slow Start: Exponential growth (double size) until ssthresh\n * 2. Congestion Avoidance: Linear growth (add increment) after ssthresh\n * 3. Fast Recovery: On congestion, set ssthresh = currentSize / 2, reduce size\n *\n * @example\n * ```typescript\n * const adjuster = new TCPChunkSizeAdjuster({\n * initialSize: 256 * 1024, // 256KB (like TCP initial cwnd)\n * minSize: 256 * 1024, // 256KB\n * maxSize: 10 * 1024 * 1024, // 10MB\n * targetTime: 3000, // 3 seconds\n * initialSsthresh: 5 * 1024 * 1024 // 5MB\n * });\n *\n * // After each chunk upload\n * const newSize = adjuster.adjust(uploadTimeMs);\n * ```\n */\nexport class TCPChunkSizeAdjuster implements IChunkSizeAdjuster {\n private currentSize: number;\n private ssthresh: number; // Slow start threshold\n private state: CongestionState;\n private readonly options: Required<TCPChunkSizeAdjusterOptions>;\n private consecutiveFastUploads: number = 0;\n private consecutiveSlowUploads: number = 0;\n\n constructor(options: TCPChunkSizeAdjusterOptions) {\n this.currentSize = options.initialSize;\n this.options = {\n targetTime: 3000,\n initialSsthresh: options.initialSsthresh ?? options.maxSize / 2,\n ...options,\n };\n this.ssthresh = this.options.initialSsthresh;\n this.state = CongestionState.SLOW_START;\n\n this.validate();\n }\n\n private validate(): void {\n const { minSize, maxSize, initialSize, targetTime, initialSsthresh } = this.options;\n\n if (minSize > maxSize) {\n throw new Error(\"minSize cannot be greater than maxSize\");\n }\n if (initialSize < minSize || initialSize > maxSize) {\n throw new Error(\"initialSize must be between minSize and maxSize\");\n }\n if (targetTime <= 0) {\n throw new Error(\"targetTime must be positive\");\n }\n if (initialSsthresh < minSize || initialSsthresh > maxSize) {\n throw new Error(\"initialSsthresh must be between minSize and maxSize\");\n }\n }\n\n /**\n * Adjusts chunk size based on upload performance using TCP-like algorithm\n *\n * @param uploadTimeMs - Time taken to upload the previous chunk\n * @returns New chunk size in bytes\n */\n adjust(uploadTimeMs: number): number {\n if (uploadTimeMs < 0) {\n throw new Error(\"uploadTimeMs cannot be negative\");\n }\n\n const { targetTime, minSize, maxSize } = this.options;\n const ratio = uploadTimeMs / targetTime;\n\n // Fast upload (< 50% of target time)\n if (ratio < 0.5) {\n this.consecutiveFastUploads++;\n this.consecutiveSlowUploads = 0;\n this.handleFastUpload();\n }\n // Slow upload (> 150% of target time) - congestion detected\n else if (ratio > 1.5) {\n this.consecutiveSlowUploads++;\n this.consecutiveFastUploads = 0;\n this.handleSlowUpload();\n }\n // Normal upload - maintain current size\n else {\n this.consecutiveFastUploads = 0;\n this.consecutiveSlowUploads = 0;\n }\n\n // Ensure size is within bounds\n this.currentSize = Math.max(minSize, Math.min(this.currentSize, maxSize));\n\n return this.currentSize;\n }\n\n private handleFastUpload(): void {\n const { maxSize } = this.options;\n\n switch (this.state) {\n case CongestionState.SLOW_START:\n // Exponential growth: double the size\n const newSize = this.currentSize * 2;\n\n if (newSize >= this.ssthresh) {\n // Reached ssthresh, switch to congestion avoidance\n this.currentSize = this.ssthresh;\n this.state = CongestionState.CONGESTION_AVOIDANCE;\n } else {\n this.currentSize = Math.min(newSize, maxSize);\n }\n break;\n\n case CongestionState.CONGESTION_AVOIDANCE:\n // Linear growth: add a fraction of current size\n // Similar to TCP's cwnd += MSS * MSS / cwnd\n const increment = Math.max(\n this.options.minSize,\n Math.floor(this.currentSize * 0.1), // 10% increment\n );\n this.currentSize = Math.min(this.currentSize + increment, maxSize);\n break;\n\n case CongestionState.FAST_RECOVERY:\n // Exit fast recovery, enter congestion avoidance\n this.state = CongestionState.CONGESTION_AVOIDANCE;\n break;\n }\n }\n\n private handleSlowUpload(): void {\n const { minSize } = this.options;\n\n // Congestion detected - similar to TCP's multiplicative decrease\n // Set ssthresh to half of current size\n this.ssthresh = Math.max(minSize, Math.floor(this.currentSize / 2));\n\n // Reduce current size\n this.currentSize = this.ssthresh;\n\n // Enter fast recovery state\n this.state = CongestionState.FAST_RECOVERY;\n }\n\n /**\n * Gets the current chunk size\n */\n getCurrentSize(): number {\n return this.currentSize;\n }\n\n /**\n * Gets the current slow start threshold\n */\n getSsthresh(): number {\n return this.ssthresh;\n }\n\n /**\n * Gets the current congestion state\n */\n getState(): CongestionState {\n return this.state;\n }\n\n /**\n * Resets to initial state\n */\n reset(): void {\n this.currentSize = this.options.initialSize;\n this.ssthresh = this.options.initialSsthresh;\n this.state = CongestionState.SLOW_START;\n this.consecutiveFastUploads = 0;\n this.consecutiveSlowUploads = 0;\n }\n\n /**\n * Gets statistics about the adjuster's behavior\n */\n getStats() {\n return {\n currentSize: this.currentSize,\n ssthresh: this.ssthresh,\n state: this.state,\n consecutiveFastUploads: this.consecutiveFastUploads,\n consecutiveSlowUploads: this.consecutiveSlowUploads,\n };\n }\n}\n","/**\n * UploadTask - Single file upload task manager\n *\n * Manages the complete lifecycle of a single file upload including:\n * - File chunking\n * - Hash calculation\n * - Chunk upload with retry\n * - Progress tracking\n * - State management\n * - Event emission\n */\n\nimport type { ChunkInfo, UploadStatus, UploadToken } from \"@chunkflowjs/protocol\";\nimport type { RequestAdapter } from \"@chunkflowjs/protocol\";\nimport {\n createEventBus,\n type UploadEventBus,\n ConcurrencyController,\n UploadStorage,\n sliceFile,\n calculateChunkHash,\n calculateFileHash,\n calculateSpeed,\n estimateRemainingTime,\n} from \"@chunkflowjs/shared\";\nimport type { IChunkSizeAdjuster } from \"./chunk-size-adjuster-interface\";\nimport { TCPChunkSizeAdjuster } from \"./chunk-size-adjuster-tcp\";\nimport { ChunkSizeAdjuster } from \"./chunk-size-adjuster\";\n\n/**\n * Upload progress information\n */\nexport interface UploadProgress {\n /** Number of bytes uploaded */\n uploadedBytes: number;\n /** Total file size in bytes */\n totalBytes: number;\n /** Upload percentage (0-100) */\n percentage: number;\n /** Upload speed in bytes per second */\n speed: number;\n /** Estimated remaining time in seconds */\n remainingTime: number;\n /** Number of chunks uploaded */\n uploadedChunks: number;\n /** Total number of chunks */\n totalChunks: number;\n}\n\n/**\n * Options for configuring an upload task\n */\nexport interface UploadTaskOptions {\n /** File to upload */\n file: File;\n /** Request adapter for API calls */\n requestAdapter: RequestAdapter;\n /** Initial chunk size in bytes (default: 1MB) */\n chunkSize?: number;\n /** Number of concurrent chunk uploads (default: 3) */\n concurrency?: number;\n /** Maximum number of retries per chunk (default: 3) */\n retryCount?: number;\n /** Base delay for retry in milliseconds (default: 1000) */\n retryDelay?: number;\n /** Whether to start upload automatically (default: false) */\n autoStart?: boolean;\n /** Task ID to resume (for resuming interrupted uploads) */\n resumeTaskId?: string;\n /** Upload token from previous session (for resuming) */\n resumeUploadToken?: string;\n /** List of already uploaded chunk indices (for resuming) */\n resumeUploadedChunks?: number[];\n /**\n * Chunk size adjustment strategy (default: 'tcp-like')\n * - 'simple': Simple binary adjustment (fast → double, slow → halve)\n * - 'tcp-like': TCP slow start inspired algorithm with state machine (recommended)\n * - Custom: Provide your own IChunkSizeAdjuster implementation\n */\n chunkSizeStrategy?: \"simple\" | \"tcp-like\" | IChunkSizeAdjuster;\n /**\n * Initial slow start threshold for TCP-like strategy (default: 5MB)\n * Only used when chunkSizeStrategy is 'tcp-like'\n */\n initialSsthresh?: number;\n}\n\n/**\n * UploadTask class\n *\n * Manages a single file upload with support for:\n * - Chunked upload with dynamic chunk size adjustment\n * - Hash calculation and verification (instant upload/resume)\n * - Concurrent chunk uploads with retry\n * - Progress tracking and persistence\n * - Pause/resume/cancel operations\n * - Event-driven lifecycle\n *\n * @example\n * ```typescript\n * const task = new UploadTask({\n * file: myFile,\n * requestAdapter: myAdapter,\n * chunkSize: 1024 * 1024, // 1MB\n * concurrency: 3,\n * });\n *\n * // Listen to events\n * task.on('progress', ({ progress, speed }) => {\n * console.log(`Progress: ${progress}%, Speed: ${speed} bytes/s`);\n * });\n *\n * task.on('success', ({ fileUrl }) => {\n * console.log(`Upload complete: ${fileUrl}`);\n * });\n *\n * // Start upload\n * await task.start();\n * ```\n */\nexport class UploadTask {\n /** Unique task identifier */\n readonly id: string;\n\n /** File being uploaded */\n readonly file: File;\n\n /** Current upload status */\n private status: UploadStatus;\n\n /** Current upload progress */\n private progress: UploadProgress;\n\n /** Array of chunk information */\n private chunks: ChunkInfo[];\n\n /** Upload token from server */\n private uploadToken: UploadToken | null;\n\n /** Calculated file hash */\n private fileHash: string | null;\n\n /** Event bus for lifecycle events */\n private eventBus: UploadEventBus;\n\n /** Concurrency controller for chunk uploads */\n private concurrencyController: ConcurrencyController;\n\n /** Storage for persisting upload progress */\n private storage: UploadStorage;\n\n /** Request adapter for API calls */\n private requestAdapter: RequestAdapter;\n\n /** Upload task options with defaults */\n private options: Required<UploadTaskOptions>;\n\n /** Upload start timestamp */\n private startTime: number;\n\n /** Upload end timestamp */\n private endTime: number | null;\n\n /** Chunk size adjuster for dynamic sizing */\n private chunkSizeAdjuster: IChunkSizeAdjuster | null;\n\n /** Flag to indicate if upload should be cancelled (e.g., instant upload) */\n private shouldCancelUpload: boolean;\n\n /**\n * Creates a new UploadTask\n *\n * @param options - Upload task configuration options\n */\n constructor(options: UploadTaskOptions) {\n // Use resume task ID if provided, otherwise generate new one\n this.id = options.resumeTaskId ?? this.generateTaskId();\n\n // Store file reference\n this.file = options.file;\n\n // Initialize status\n this.status = \"idle\" as UploadStatus;\n\n // Initialize progress\n this.progress = {\n uploadedBytes: 0,\n totalBytes: options.file.size,\n percentage: 0,\n speed: 0,\n remainingTime: 0,\n uploadedChunks: 0,\n totalChunks: 0,\n };\n\n // Initialize chunks array\n this.chunks = [];\n\n // Initialize upload token (use resume token if provided)\n if (options.resumeUploadToken) {\n // Parse the resume token to reconstruct UploadToken\n // Note: This is a simplified version - in production you'd decode the JWT\n this.uploadToken = {\n token: options.resumeUploadToken,\n fileId: \"\", // Will be populated from token\n chunkSize: options.chunkSize ?? 1024 * 1024,\n expiresAt: Date.now() + 24 * 60 * 60 * 1000, // Assume 24h expiry\n };\n } else {\n this.uploadToken = null;\n }\n\n this.fileHash = null;\n\n // Create event bus\n this.eventBus = createEventBus();\n\n // Store request adapter\n this.requestAdapter = options.requestAdapter;\n\n // Apply default options\n this.options = {\n file: options.file,\n requestAdapter: options.requestAdapter,\n chunkSize: options.chunkSize ?? 1024 * 1024, // 1MB default\n concurrency: options.concurrency ?? 3,\n retryCount: options.retryCount ?? 3,\n retryDelay: options.retryDelay ?? 1000,\n autoStart: options.autoStart ?? false,\n resumeTaskId: options.resumeTaskId ?? \"\",\n resumeUploadToken: options.resumeUploadToken ?? \"\",\n resumeUploadedChunks: options.resumeUploadedChunks ?? [],\n chunkSizeStrategy: options.chunkSizeStrategy ?? \"tcp-like\", // Default to TCP-like\n initialSsthresh: options.initialSsthresh ?? 5 * 1024 * 1024, // 5MB default\n };\n\n // Create concurrency controller\n this.concurrencyController = new ConcurrencyController({\n limit: this.options.concurrency,\n });\n\n // Create storage instance\n this.storage = new UploadStorage();\n\n // Initialize timestamps\n this.startTime = 0;\n this.endTime = null;\n\n // Initialize chunk size adjuster (will be created when upload starts)\n this.chunkSizeAdjuster = null;\n\n // Initialize cancel flag\n this.shouldCancelUpload = false;\n }\n\n /**\n * Generates a unique task ID\n * Uses timestamp and random string for uniqueness\n *\n * @returns Unique task identifier\n */\n private generateTaskId(): string {\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).substring(2, 9);\n return `task_${timestamp}_${random}`;\n }\n\n /**\n * Gets the current upload status\n *\n * @returns Current upload status\n */\n getStatus(): UploadStatus {\n return this.status;\n }\n\n /**\n * Gets the current upload progress\n * Returns a copy to prevent external modification\n *\n * @returns Current upload progress\n */\n getProgress(): UploadProgress {\n return { ...this.progress };\n }\n\n /**\n * Gets the upload duration in milliseconds\n * Returns null if upload hasn't completed\n *\n * @returns Upload duration or null\n */\n getDuration(): number | null {\n if (this.endTime === null) {\n return null;\n }\n return this.endTime - this.startTime;\n }\n\n /**\n * Subscribes to upload events\n *\n * @param event - Event name to listen to\n * @param handler - Event handler function\n *\n * @example\n * ```typescript\n * task.on('progress', ({ progress, speed }) => {\n * console.log(`${progress}% at ${speed} bytes/s`);\n * });\n * ```\n */\n on<K extends keyof import(\"@chunkflowjs/shared\").UploadEvents>(\n event: K,\n handler: (payload: import(\"@chunkflowjs/shared\").UploadEvents[K]) => void,\n ): void {\n this.eventBus.on(event, handler);\n }\n\n /**\n * Unsubscribes from upload events\n *\n * @param event - Event name to stop listening to\n * @param handler - Event handler function to remove\n */\n off<K extends keyof import(\"@chunkflowjs/shared\").UploadEvents>(\n event: K,\n handler: (payload: import(\"@chunkflowjs/shared\").UploadEvents[K]) => void,\n ): void {\n this.eventBus.off(event, handler);\n }\n\n /**\n * Creates chunk information array based on negotiated chunk size\n * Divides the file into chunks and creates ChunkInfo objects for each\n *\n * @param chunkSize - Size of each chunk in bytes (negotiated with server)\n * @returns Array of ChunkInfo objects\n *\n * @remarks\n * - Chunks are created sequentially from start to end of file\n * - Last chunk may be smaller than chunkSize\n * - Hash field is initially empty and will be calculated during upload\n * - Validates: Requirement 2.1 (chunk size based splitting)\n *\n * @internal This method will be used in task 5.3\n */\n private createChunks(chunkSize: number): ChunkInfo[] {\n const chunks: ChunkInfo[] = [];\n const totalChunks = Math.ceil(this.file.size / chunkSize);\n\n for (let i = 0; i < totalChunks; i++) {\n const start = i * chunkSize;\n const end = Math.min(start + chunkSize, this.file.size);\n\n chunks.push({\n index: i,\n hash: \"\", // Will be calculated during upload\n size: end - start,\n start,\n end,\n });\n }\n\n return chunks;\n }\n\n /**\n * Starts the upload process\n *\n * Workflow:\n * 1. Create file on server and get upload token\n * 2. Split file into chunks based on negotiated chunk size\n * 3. Start concurrent chunk upload\n *\n * @throws Error if upload is already in progress or completed\n *\n * @remarks\n * - Validates: Requirements 1.1, 1.2, 2.2, 5.1, 5.2, 5.3, 20.1, 20.2, 20.3, 20.5\n * - Sets status to 'uploading'\n * - Emits 'start' event\n * - Creates chunks based on negotiated size\n * - Initiates concurrent upload with retry\n */\n async start(): Promise<void> {\n // Validate current status\n if (this.status !== \"idle\") {\n throw new Error(`Cannot start upload: current status is ${this.status}`);\n }\n\n try {\n // Initialize storage for persistence\n await this.initializeStorage();\n\n // Update status to uploading\n this.status = \"uploading\" as UploadStatus;\n this.startTime = Date.now();\n\n // Emit start event\n this.eventBus.emit(\"start\", { taskId: this.id, file: this.file });\n\n // Check if this is a resume operation\n const isResume = this.options.resumeUploadToken && this.options.resumeUploadedChunks;\n\n let negotiatedChunkSize: number;\n\n if (isResume) {\n // Resuming: use existing upload token\n // Upload token was already set in constructor\n negotiatedChunkSize = this.uploadToken!.chunkSize;\n\n console.info(\n `Resuming upload for task ${this.id}: ` +\n `${this.options.resumeUploadedChunks!.length} chunks already uploaded`,\n );\n } else {\n // New upload: Create file on server and get upload token\n const createResponse = await this.requestAdapter.createFile({\n fileName: this.file.name,\n fileSize: this.file.size,\n fileType: this.file.type,\n preferredChunkSize: this.options.chunkSize,\n });\n\n this.uploadToken = createResponse.uploadToken;\n negotiatedChunkSize = createResponse.negotiatedChunkSize;\n }\n\n // Step 2: Split file into chunks\n this.chunks = this.createChunks(negotiatedChunkSize);\n this.progress.totalChunks = this.chunks.length;\n\n // If resuming, mark already uploaded chunks\n if (isResume && this.options.resumeUploadedChunks) {\n const uploadedChunks = this.options.resumeUploadedChunks;\n let uploadedBytes = 0;\n\n for (const chunkIndex of uploadedChunks) {\n if (chunkIndex < this.chunks.length) {\n const chunk = this.chunks[chunkIndex];\n // Mark chunk as uploaded by setting a flag\n (chunk as any).uploaded = true;\n uploadedBytes += chunk.size;\n }\n }\n\n // Update progress to reflect already uploaded chunks\n this.progress.uploadedChunks = uploadedChunks.length;\n this.progress.uploadedBytes = uploadedBytes;\n this.progress.percentage = (uploadedBytes / this.file.size) * 100;\n\n console.info(\n `Resume progress: ${this.progress.percentage.toFixed(1)}% ` +\n `(${uploadedChunks.length}/${this.chunks.length} chunks)`,\n );\n }\n\n // Initialize chunk size adjuster for dynamic sizing\n const strategy = this.options.chunkSizeStrategy;\n const minSize = 256 * 1024; // 256KB\n const maxSize = 10 * 1024 * 1024; // 10MB\n const targetTime = 3000; // 3 seconds target per chunk\n\n if (typeof strategy === \"object\") {\n // Custom adjuster provided\n this.chunkSizeAdjuster = strategy;\n } else if (strategy === \"tcp-like\") {\n // Use TCP-like adjuster (default, recommended)\n this.chunkSizeAdjuster = new TCPChunkSizeAdjuster({\n initialSize: negotiatedChunkSize,\n minSize,\n maxSize,\n targetTime,\n initialSsthresh: this.options.initialSsthresh,\n });\n } else {\n // Use simple adjuster\n this.chunkSizeAdjuster = new ChunkSizeAdjuster({\n initialSize: negotiatedChunkSize,\n minSize,\n maxSize,\n targetTime,\n });\n }\n\n // Step 3: Start uploading chunks AND calculate hash in parallel\n // This implements requirement 3.6 and 17.2 - hash calculation and upload should be parallel\n await Promise.all([this.startUpload(), this.calculateAndVerifyHash()]);\n\n // Check if upload was paused or cancelled during the process\n if (this.status === \"paused\") {\n console.info(`Upload paused at ${this.progress.percentage.toFixed(1)}%`);\n return;\n }\n\n if (this.status === \"cancelled\" || this.shouldCancelUpload) {\n // Upload was cancelled - silently return\n return;\n }\n\n // If instant upload was triggered, status is already success\n if (this.status === \"success\") {\n console.info(\"Instant upload already completed\");\n return;\n }\n\n // Step 4: Verify hash with server (now that all chunk hashes are calculated)\n // Only do this if we're still in uploading state and have fileHash\n if (this.status === \"uploading\" && this.fileHash) {\n try {\n const chunkHashes = this.chunks.map((chunk) => chunk.hash);\n\n const verifyResponse = await this.requestAdapter.verifyHash({\n fileHash: this.fileHash,\n chunkHashes,\n uploadToken: this.uploadToken!.token,\n });\n\n // Check if file already exists (instant upload)\n // Note: This is the second verifyHash call after upload completes\n // At this point, if file exists, it means we just uploaded it successfully\n if (verifyResponse.fileExists && verifyResponse.fileUrl) {\n // File already exists on server - instant upload (秒传)\n this.status = \"success\" as UploadStatus;\n this.endTime = Date.now();\n\n // Update progress to 100%\n this.progress.uploadedBytes = this.file.size;\n this.progress.uploadedChunks = this.chunks.length;\n this.progress.percentage = 100;\n\n // Emit success event with file URL\n this.eventBus.emit(\"success\", {\n taskId: this.id,\n fileUrl: verifyResponse.fileUrl,\n });\n\n return;\n }\n } catch (error) {\n // Hash verification failed, continue with merge\n console.warn(\"Hash verification failed:\", error);\n }\n } else if (this.status === \"uploading\" && !this.fileHash) {\n console.warn(\"No fileHash available, skipping hash verification\");\n }\n\n // Step 5: Merge file if upload completed successfully\n if (this.status === \"uploading\" && !this.shouldCancelUpload) {\n // Call merge API\n const mergeResponse = await this.requestAdapter.mergeFile({\n uploadToken: this.uploadToken!.token,\n fileHash: this.fileHash || \"\",\n chunkHashes: this.chunks.map((chunk) => chunk.hash),\n });\n\n if (mergeResponse.success) {\n // Upload completed successfully\n this.status = \"success\" as UploadStatus;\n this.endTime = Date.now();\n\n // Emit success event with file URL\n this.eventBus.emit(\"success\", {\n taskId: this.id,\n fileUrl: mergeResponse.fileUrl,\n });\n } else {\n throw new Error(\"Merge failed: response.success is false\");\n }\n }\n // If we reach here without uploading status, it means upload was paused/cancelled/instant\n // The status and events were already handled above\n } catch (error) {\n // Only set error status if not paused or cancelled\n if (this.status !== \"paused\" && this.status !== \"cancelled\") {\n this.status = \"error\" as UploadStatus;\n this.endTime = Date.now();\n this.eventBus.emit(\"error\", {\n taskId: this.id,\n error: error as Error,\n });\n }\n throw error;\n }\n }\n\n /**\n * Starts concurrent chunk upload with priority for first chunks\n *\n * Uploads all chunks with concurrency control and dynamic chunk size adjustment.\n * Uses the concurrency controller to limit parallel uploads.\n * Implements priority upload for the first few chunks to get quick feedback.\n *\n * @remarks\n * - Validates: Requirements 5.1, 5.2, 5.3, 17.5, 20.1, 20.2, 20.3\n * - Respects concurrency limits\n * - Tracks upload time for dynamic chunk size adjustment\n * - Stops if status changes (pause/cancel) or shouldCancelUpload is set\n * - Prioritizes first 3 chunks for quick server feedback\n *\n * @internal\n */\n private async startUpload(): Promise<void> {\n // Filter out already uploaded chunks (for resume functionality)\n const chunksToUpload = this.chunks.filter((chunk) => !(chunk as any).uploaded);\n\n if (chunksToUpload.length === 0) {\n // All chunks already uploaded (resume case)\n console.info(\"All chunks already uploaded, skipping upload phase\");\n return;\n }\n\n // Requirement 17.5: Priority upload for first few chunks\n // Upload first 3 chunks with priority to get quick feedback\n const priorityChunkCount = Math.min(3, chunksToUpload.length);\n const priorityChunks = chunksToUpload.slice(0, priorityChunkCount);\n const remainingChunks = chunksToUpload.slice(priorityChunkCount);\n\n // Upload priority chunks first\n const priorityPromises = priorityChunks.map((chunk) => {\n return this.concurrencyController.run(async () => {\n // Check if upload should continue\n if (this.status !== \"uploading\" || this.shouldCancelUpload) {\n return;\n }\n\n // Track upload start time for this chunk\n const chunkStartTime = Date.now();\n\n // Upload chunk with retry\n await this.uploadChunkWithRetry(chunk);\n\n // Track upload end time and adjust chunk size for next uploads\n const chunkUploadTime = Date.now() - chunkStartTime;\n if (this.chunkSizeAdjuster) {\n this.chunkSizeAdjuster.adjust(chunkUploadTime);\n }\n });\n });\n\n // Upload remaining chunks concurrently with priority chunks\n const remainingPromises = remainingChunks.map((chunk) => {\n return this.concurrencyController.run(async () => {\n // Check if upload should continue\n if (this.status !== \"uploading\" || this.shouldCancelUpload) {\n return;\n }\n\n // Track upload start time for this chunk\n const chunkStartTime = Date.now();\n\n // Upload chunk with retry\n await this.uploadChunkWithRetry(chunk);\n\n // Track upload end time and adjust chunk size for next uploads\n const chunkUploadTime = Date.now() - chunkStartTime;\n if (this.chunkSizeAdjuster) {\n this.chunkSizeAdjuster.adjust(chunkUploadTime);\n }\n });\n });\n\n // Wait for all chunks to complete\n // Priority chunks are started first, but all run concurrently\n await Promise.all([...priorityPromises, ...remainingPromises]);\n }\n\n /**\n * Uploads a single chunk with retry logic\n *\n * Implements exponential backoff retry strategy for failed uploads.\n * Calculates chunk hash before upload for verification.\n * Updates progress after successful upload.\n *\n * @param chunk - Chunk information to upload\n * @throws Error if all retries are exhausted\n *\n * @remarks\n * - Validates: Requirements 20.1, 20.2, 20.3, 20.5\n * - Retries up to configured retry count\n * - Uses exponential backoff delay\n * - Emits chunkSuccess or chunkError events\n * - Updates progress and persists to storage\n * - Skips upload if shouldCancelUpload is set (instant upload)\n *\n * @internal\n */\n private async uploadChunkWithRetry(chunk: ChunkInfo): Promise<void> {\n let retries = 0;\n let lastError: Error | null = null;\n\n while (retries <= this.options.retryCount) {\n try {\n // Check if upload should continue\n if (this.status !== \"uploading\" || this.shouldCancelUpload) {\n return;\n }\n\n // Slice the file to get chunk blob\n const blob = sliceFile(this.file, chunk.start, chunk.end);\n\n // Calculate chunk hash\n const chunkHash = await calculateChunkHash(blob);\n chunk.hash = chunkHash;\n\n // Upload chunk to server\n await this.requestAdapter.uploadChunk({\n uploadToken: this.uploadToken!.token,\n chunkIndex: chunk.index,\n chunkHash,\n chunk: blob,\n });\n\n // Mark chunk as uploaded to prevent duplicate uploads\n (chunk as any).uploaded = true;\n\n // Upload successful - update progress\n await this.updateProgress(chunk);\n\n // Emit chunk success event\n this.eventBus.emit(\"chunkSuccess\", {\n taskId: this.id,\n chunkIndex: chunk.index,\n });\n\n // Successfully uploaded, exit retry loop\n return;\n } catch (error) {\n lastError = error as Error;\n retries++;\n\n // Emit chunk error event\n this.eventBus.emit(\"chunkError\", {\n taskId: this.id,\n chunkIndex: chunk.index,\n error: lastError,\n });\n\n // Check if we should retry\n if (retries > this.options.retryCount) {\n // All retries exhausted\n throw new Error(\n `Failed to upload chunk ${chunk.index} after ${this.options.retryCount} retries: ${lastError.message}`,\n );\n }\n\n // Calculate exponential backoff delay\n const delay = this.options.retryDelay * Math.pow(2, retries - 1);\n await this.delay(delay);\n }\n }\n\n // This should never be reached, but TypeScript needs it\n if (lastError) {\n throw lastError;\n }\n }\n\n /**\n * Updates upload progress after a chunk is successfully uploaded\n *\n * Calculates:\n * - Uploaded bytes and percentage\n * - Upload speed\n * - Estimated remaining time\n *\n * Emits progress event with updated information.\n * Persists progress to IndexedDB for resume functionality.\n *\n * @param chunk - The chunk that was just uploaded\n *\n * @remarks\n * - Validates: Requirements 4.1, 6.3, 6.4 (progress tracking and persistence)\n * - Updates all progress metrics\n * - Emits progress event\n * - Persists to IndexedDB for resume capability\n *\n * @internal\n */\n private async updateProgress(chunk: ChunkInfo): Promise<void> {\n // Check if this chunk was already counted to prevent duplicate progress updates\n if ((chunk as any).progressCounted) {\n console.warn(`Chunk ${chunk.index} progress already counted, skipping update`);\n return;\n }\n\n // Mark chunk as counted\n (chunk as any).progressCounted = true;\n\n // Update uploaded bytes and chunks\n this.progress.uploadedBytes += chunk.size;\n this.progress.uploadedChunks++;\n\n // Ensure progress doesn't exceed 100%\n if (this.progress.uploadedBytes > this.file.size) {\n console.warn(\n `Progress overflow detected: ${this.progress.uploadedBytes} > ${this.file.size}, capping to file size`,\n );\n this.progress.uploadedBytes = this.file.size;\n }\n\n if (this.progress.uploadedChunks > this.progress.totalChunks) {\n console.warn(\n `Chunk count overflow detected: ${this.progress.uploadedChunks} > ${this.progress.totalChunks}, capping to total chunks`,\n );\n this.progress.uploadedChunks = this.progress.totalChunks;\n }\n\n // Calculate percentage\n this.progress.percentage = Math.min(100, (this.progress.uploadedBytes / this.file.size) * 100);\n\n // Calculate speed and remaining time\n const elapsedTime = Date.now() - this.startTime;\n this.progress.speed = calculateSpeed(this.progress.uploadedBytes, elapsedTime);\n\n const remainingBytes = this.file.size - this.progress.uploadedBytes;\n this.progress.remainingTime = estimateRemainingTime(remainingBytes, this.progress.speed);\n\n // Emit progress event\n this.eventBus.emit(\"progress\", {\n taskId: this.id,\n progress: this.progress.percentage,\n speed: this.progress.speed,\n });\n\n // Persist progress to IndexedDB for resume functionality\n // Requirement 4.1: Write progress to IndexedDB after each chunk\n await this.persistProgress();\n }\n\n /**\n * Delays execution for a specified time\n * Used for retry backoff\n *\n * @param ms - Milliseconds to delay\n * @returns Promise that resolves after the delay\n *\n * @internal\n */\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Calculates file hash and verifies with server for instant upload\n *\n * This method runs in parallel with chunk upload to optimize performance.\n * It implements the following features:\n * - Calculate file hash using Web Worker or requestIdleCallback (non-blocking)\n * - Send hash verification request to server\n * - Handle instant upload (秒传) when file already exists\n * - Handle partial instant upload (skip existing chunks)\n *\n * @remarks\n * - Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5\n * - Emits hashProgress events during calculation\n * - Emits hashComplete event when calculation finishes\n * - If file exists on server, cancels ongoing upload and emits success\n * - If some chunks exist, marks them as uploaded to skip them\n *\n * @internal\n */\n private async calculateAndVerifyHash(): Promise<void> {\n try {\n // Calculate file hash with progress reporting\n // Requirement 3.1: Calculate hash when file is selected\n // Requirement 3.2: Use non-blocking hash calculation\n this.fileHash = await calculateFileHash(this.file, (progress) => {\n // Emit hash progress event\n this.eventBus.emit(\"hashProgress\", {\n taskId: this.id,\n progress,\n });\n });\n\n // Emit hash complete event\n // Requirement 3.3: Send hash verification request after calculation\n this.eventBus.emit(\"hashComplete\", {\n taskId: this.id,\n hash: this.fileHash,\n });\n\n // Verify hash with server (without chunk hashes for now)\n // This allows early detection of full instant upload\n if (!this.uploadToken) {\n // Upload token not available yet, skip verification\n return;\n }\n\n const verifyResponse = await this.requestAdapter.verifyHash({\n fileHash: this.fileHash,\n uploadToken: this.uploadToken.token,\n // Note: chunkHashes not included here because they're calculated during upload\n // We'll do a second verification with chunk hashes before merge\n });\n\n // Requirement 3.4: Handle instant upload (file already exists)\n if (verifyResponse.fileExists && verifyResponse.fileUrl) {\n // Check if upload was paused or cancelled by user\n if (this.status === \"paused\" || this.status === \"cancelled\") {\n return;\n }\n\n // File already exists on server - instant upload (秒传)\n // Cancel ongoing chunk uploads\n this.shouldCancelUpload = true;\n this.status = \"success\" as UploadStatus;\n this.endTime = Date.now();\n\n // Update progress to 100%\n this.progress.uploadedBytes = this.file.size;\n this.progress.uploadedChunks = this.chunks.length;\n this.progress.percentage = 100;\n\n // Emit success event with file URL\n this.eventBus.emit(\"success\", {\n taskId: this.id,\n fileUrl: verifyResponse.fileUrl,\n });\n\n return;\n }\n\n // Requirement 3.5: Handle partial instant upload (skip existing chunks)\n // Note: This won't work well without chunk hashes, but server might return\n // existing chunks based on file hash if it has seen this file before\n if (verifyResponse.existingChunks && verifyResponse.existingChunks.length > 0) {\n // Some chunks already exist on server\n // Mark them as uploaded to skip them\n this.skipExistingChunks(verifyResponse.existingChunks);\n }\n } catch (error) {\n // Hash calculation or verification failed\n // Log error but don't fail the upload - continue with normal upload\n console.warn(\"Hash calculation/verification failed:\", error);\n // The upload will continue normally without hash optimization\n }\n }\n\n /**\n * Skips existing chunks by marking them as uploaded\n *\n * This is used for partial instant upload when some chunks already exist on server.\n * Updates progress to reflect the skipped chunks.\n *\n * @param existingChunkIndices - Array of chunk indices that already exist on server\n *\n * @remarks\n * - Validates: Requirement 3.5, 17.4 (partial instant upload)\n * - Updates progress for skipped chunks\n * - Emits chunkSuccess events for skipped chunks\n *\n * @internal\n */\n private skipExistingChunks(existingChunkIndices: number[]): void {\n for (const chunkIndex of existingChunkIndices) {\n const chunk = this.chunks[chunkIndex];\n if (!chunk) continue;\n\n // Mark chunk as uploaded to skip it in startUpload\n (chunk as any).uploaded = true;\n\n // Mark chunk as progress counted to prevent duplicate counting\n (chunk as any).progressCounted = true;\n\n // Update progress for skipped chunk\n this.progress.uploadedBytes += chunk.size;\n this.progress.uploadedChunks++;\n this.progress.percentage = (this.progress.uploadedBytes / this.file.size) * 100;\n\n // Emit chunk success event for skipped chunk\n this.eventBus.emit(\"chunkSuccess\", {\n taskId: this.id,\n chunkIndex: chunk.index,\n });\n }\n\n // Emit progress event with updated information\n this.eventBus.emit(\"progress\", {\n taskId: this.id,\n progress: this.progress.percentage,\n speed: this.progress.speed,\n });\n }\n\n /**\n * Initializes the IndexedDB storage for progress persistence\n *\n * Creates the initial upload record in IndexedDB.\n * If storage is not available, logs a warning but continues upload.\n *\n * @remarks\n * - Validates: Requirement 4.1 (persist progress to IndexedDB)\n * - Gracefully handles storage unavailability\n * - Creates initial record with empty uploaded chunks\n *\n * @internal\n */\n private async initializeStorage(): Promise<void> {\n try {\n // Initialize the storage database\n await this.storage.init();\n\n // Create initial upload record\n const record: import(\"@chunkflowjs/shared\").UploadRecord = {\n taskId: this.id,\n fileInfo: {\n name: this.file.name,\n size: this.file.size,\n type: this.file.type,\n lastModified: this.file.lastModified,\n },\n uploadedChunks: [],\n uploadToken: this.uploadToken?.token || \"\",\n createdAt: Date.now(),\n updatedAt: Date.now(),\n };\n\n await this.storage.saveRecord(record);\n } catch (error) {\n // Storage initialization failed - silently ignore\n // This implements graceful degradation when IndexedDB is unavailable\n // Upload will continue without persistence\n }\n }\n\n /**\n * Persists current upload progress to IndexedDB\n *\n * Updates the upload record with the list of successfully uploaded chunks.\n * This enables resume functionality if the upload is interrupted.\n *\n * @remarks\n * - Validates: Requirement 4.1 (write progress to IndexedDB)\n * - Gracefully handles storage errors\n * - Updates uploadedChunks array and timestamp\n *\n * @internal\n */\n private async persistProgress(): Promise<void> {\n try {\n // Check if storage is available\n if (!this.storage.isAvailable()) {\n return;\n }\n\n // Get list of uploaded chunk indices\n const uploadedChunkIndices = this.chunks\n .filter((_, index) => index < this.progress.uploadedChunks)\n .map((chunk) => chunk.index);\n\n try {\n // Try to update the existing record\n await this.storage.updateRecord(this.id, {\n uploadedChunks: uploadedChunkIndices,\n uploadToken: this.uploadToken?.token || \"\",\n updatedAt: Date.now(),\n });\n } catch (error) {\n // If record doesn't exist, create it (upsert behavior)\n if ((error as any).code === \"OPERATION_FAILED\") {\n const record: import(\"@chunkflowjs/shared\").UploadRecord = {\n taskId: this.id,\n fileInfo: {\n name: this.file.name,\n size: this.file.size,\n type: this.file.type,\n lastModified: this.file.lastModified,\n },\n uploadedChunks: uploadedChunkIndices,\n uploadToken: this.uploadToken?.token || \"\",\n createdAt: Date.now(),\n updatedAt: Date.now(),\n };\n await this.storage.saveRecord(record);\n } else {\n throw error;\n }\n }\n } catch (error) {\n // Storage update failed - log warning but continue upload\n // This ensures upload continues even if persistence fails\n // Silently ignore to avoid console noise in tests\n }\n }\n\n /**\n * Pauses the upload\n *\n * Pauses an ongoing upload by changing the status to 'paused'.\n * The upload can be resumed later from where it left off.\n *\n * @remarks\n * - Validates: Requirements 4.3, 6.3 (pause functionality and lifecycle events)\n * - Only works when status is 'uploading'\n * - Emits 'pause' event\n * - Progress is persisted to IndexedDB for resume\n * - Ongoing chunk uploads will complete, but no new chunks will start\n *\n * @example\n * ```typescript\n * task.on('pause', () => {\n * console.log('Upload paused');\n * });\n *\n * task.pause();\n * ```\n */\n pause(): void {\n // Only allow pausing when upload is in progress\n if (this.status !== \"uploading\") {\n console.warn(`Cannot pause upload: current status is ${this.status}`);\n return;\n }\n\n // Update status to paused\n this.status = \"paused\" as UploadStatus;\n\n // Emit pause event\n this.eventBus.emit(\"pause\", { taskId: this.id });\n\n // Note: Ongoing chunk uploads will complete naturally\n // The startUpload method checks status before starting new chunks\n // Progress is already persisted to IndexedDB via updateProgress\n }\n\n /**\n * Resumes a paused upload (断点续传)\n *\n * Resumes an upload that was previously paused.\n * Continues uploading from where it left off, skipping already uploaded chunks.\n *\n * @throws Error if upload is not in paused state\n *\n * @remarks\n * - Validates: Requirements 4.3, 4.4, 6.3 (resume functionality and lifecycle events)\n * - Only works when status is 'paused'\n * - Emits 'resume' event\n * - Continues from last uploaded chunk\n * - Uses persisted progress from IndexedDB\n *\n * @example\n * ```typescript\n * task.on('resume', () => {\n * console.log('Upload resumed');\n * });\n *\n * await task.resume();\n * ```\n */\n async resume(): Promise<void> {\n // Only allow resuming when upload is paused\n if (this.status !== \"paused\") {\n throw new Error(`Cannot resume upload: current status is ${this.status}`);\n }\n\n try {\n // Check if we have upload token (required for resume)\n if (!this.uploadToken) {\n // No upload token available - cannot resume\n // This can happen if resume is called on a task that was never started\n throw new Error(\"Cannot resume: upload token not available\");\n }\n\n // Update status to uploading\n this.status = \"uploading\" as UploadStatus;\n\n // Emit resume event\n this.eventBus.emit(\"resume\", { taskId: this.id });\n\n // Continue uploading remaining chunks\n // The startUpload method will skip already uploaded chunks\n // because progress.uploadedChunks tracks which chunks are done\n await this.startUpload();\n\n // Check if upload was cancelled during resume\n if (this.status === \"cancelled\" || this.shouldCancelUpload) {\n // Upload was cancelled - silently return\n return;\n }\n\n // If status is still uploading, proceed with verification and merge\n if (this.status === \"uploading\") {\n // Step 1: Verify hash with server (if we have fileHash and chunk hashes)\n if (this.fileHash) {\n try {\n const chunkHashes = this.chunks.map((chunk) => chunk.hash);\n\n const verifyResponse = await this.requestAdapter.verifyHash({\n fileHash: this.fileHash,\n chunkHashes,\n uploadToken: this.uploadToken.token,\n });\n\n // Check if file already exists (instant upload)\n if (verifyResponse.fileExists && verifyResponse.fileUrl) {\n this.status = \"success\" as UploadStatus;\n this.endTime = Date.now();\n\n // Update progress to 100%\n this.progress.uploadedBytes = this.file.size;\n this.progress.uploadedChunks = this.chunks.length;\n this.progress.percentage = 100;\n\n // Emit success event with file URL\n this.eventBus.emit(\"success\", {\n taskId: this.id,\n fileUrl: verifyResponse.fileUrl,\n });\n\n return;\n }\n } catch (error) {\n // Hash verification failed, continue with merge\n console.warn(\"Hash verification failed:\", error);\n }\n }\n\n // Step 2: Merge file\n const mergeResponse = await this.requestAdapter.mergeFile({\n uploadToken: this.uploadToken.token,\n fileHash: this.fileHash || \"\",\n chunkHashes: this.chunks.map((chunk) => chunk.hash),\n });\n\n if (mergeResponse.success) {\n // Upload completed successfully\n this.status = \"success\" as UploadStatus;\n this.endTime = Date.now();\n\n // Emit success event with file URL\n this.eventBus.emit(\"success\", {\n taskId: this.id,\n fileUrl: mergeResponse.fileUrl,\n });\n } else {\n throw new Error(\"Merge failed: response.success is false\");\n }\n }\n } catch (error) {\n this.status = \"error\" as UploadStatus;\n this.endTime = Date.now();\n this.eventBus.emit(\"error\", {\n taskId: this.id,\n error: error as Error,\n });\n throw error;\n }\n }\n\n /**\n * Cancels the upload\n *\n * Cancels an ongoing or paused upload.\n * Once cancelled, the upload cannot be resumed.\n *\n * @remarks\n * - Validates: Requirements 6.3 (cancel functionality and lifecycle events)\n * - Works when status is 'uploading' or 'paused'\n * - Emits 'cancel' event\n * - Sets shouldCancelUpload flag to stop ongoing chunk uploads\n * - Cleans up upload record from IndexedDB\n * - Status becomes 'cancelled' (terminal state)\n *\n * @example\n * ```typescript\n * task.on('cancel', () => {\n * console.log('Upload cancelled');\n * });\n *\n * task.cancel();\n * ```\n */\n cancel(): void {\n // Only allow cancelling when upload is in progress or paused\n if (this.status !== \"uploading\" && this.status !== \"paused\") {\n console.warn(`Cannot cancel upload: current status is ${this.status}`);\n return;\n }\n\n // Set cancel flag to stop ongoing uploads\n this.shouldCancelUpload = true;\n\n // Update status to cancelled\n this.status = \"cancelled\" as UploadStatus;\n this.endTime = Date.now();\n\n // Emit cancel event\n this.eventBus.emit(\"cancel\", { taskId: this.id });\n\n // Clean up upload record from IndexedDB\n // This is done asynchronously and errors are ignored\n this.cleanupStorage().catch((error) => {\n console.warn(\"Failed to cleanup upload storage:\", error);\n });\n }\n\n /**\n * Cleans up the upload record from IndexedDB\n *\n * Removes the upload record to free up storage space.\n * Called when upload is cancelled or completed.\n *\n * @remarks\n * - Gracefully handles storage errors\n * - Does not throw errors\n *\n * @internal\n */\n private async cleanupStorage(): Promise<void> {\n try {\n if (this.storage.isAvailable()) {\n await this.storage.deleteRecord(this.id);\n }\n } catch (error) {\n // Ignore cleanup errors\n console.warn(\"Failed to cleanup storage:\", error);\n }\n }\n}\n","/**\n * UploadManager - Manages multiple upload tasks\n *\n * Provides centralized management for multiple file uploads including:\n * - Task creation and lifecycle management\n * - Task queue management\n * - Plugin system for extensibility\n * - Automatic resume of unfinished tasks\n * - Persistent storage integration\n */\n\nimport type { RequestAdapter, UploadProgress } from \"@chunkflowjs/protocol\";\nimport { UploadStorage } from \"@chunkflowjs/shared\";\nimport { UploadTask, type UploadTaskOptions } from \"./upload-task\";\n\n/**\n * Plugin interface for extending UploadManager functionality\n *\n * Plugins can hook into various lifecycle events of the upload manager\n * and individual tasks to add custom behavior, logging, analytics, etc.\n *\n * @remarks\n * - Validates: Requirement 6.5 (Plugin mechanism for extensibility)\n * - Validates: Requirement 8.5 (Hook and Plugin mechanism)\n * - All methods are optional - implement only what you need\n * - Plugins are called in the order they were registered\n * - Plugin errors are caught and logged but don't stop execution\n *\n * @example\n * ```typescript\n * class LoggerPlugin implements Plugin {\n * name = 'logger';\n *\n * onTaskCreated(task: UploadTask): void {\n * console.log(`Task created: ${task.id}`);\n * }\n *\n * onTaskProgress(task: UploadTask, progress: UploadProgress): void {\n * console.log(`Task ${task.id}: ${progress.percentage}%`);\n * }\n * }\n * ```\n */\nexport interface Plugin {\n /** Unique plugin name */\n name: string;\n\n /**\n * Called when the plugin is installed\n * @param manager - The UploadManager instance\n */\n install?(manager: UploadManager): void;\n\n /**\n * Called when a new task is created\n * @param task - The newly created UploadTask\n */\n onTaskCreated?(task: UploadTask): void;\n\n /**\n * Called when a task starts uploading\n * @param task - The UploadTask that started\n */\n onTaskStart?(task: UploadTask): void;\n\n /**\n * Called when a task's progress updates\n * @param task - The UploadTask with updated progress\n * @param progress - Current upload progress\n */\n onTaskProgress?(task: UploadTask, progress: UploadProgress): void;\n\n /**\n * Called when a task completes successfully\n * @param task - The completed UploadTask\n * @param fileUrl - URL of the uploaded file\n */\n onTaskSuccess?(task: UploadTask, fileUrl: string): void;\n\n /**\n * Called when a task encounters an error\n * @param task - The UploadTask that errored\n * @param error - The error that occurred\n */\n onTaskError?(task: UploadTask, error: Error): void;\n\n /**\n * Called when a task is paused\n * @param task - The paused UploadTask\n */\n onTaskPause?(task: UploadTask): void;\n\n /**\n * Called when a task is resumed\n * @param task - The resumed UploadTask\n */\n onTaskResume?(task: UploadTask): void;\n\n /**\n * Called when a task is cancelled\n * @param task - The cancelled UploadTask\n */\n onTaskCancel?(task: UploadTask): void;\n}\n\n/**\n * Options for configuring the UploadManager\n */\nexport interface UploadManagerOptions {\n /** Request adapter for API calls */\n requestAdapter: RequestAdapter;\n /** Maximum number of concurrent tasks (default: 3) */\n maxConcurrentTasks?: number;\n /** Default chunk size for new tasks in bytes (default: 1MB) */\n defaultChunkSize?: number;\n /** Default concurrency for chunk uploads per task (default: 3) */\n defaultConcurrency?: number;\n /** Whether to automatically resume unfinished tasks on init (default: true) */\n autoResumeUnfinished?: boolean;\n}\n\n/**\n * UploadManager class\n *\n * Central manager for handling multiple file upload tasks.\n * Provides task lifecycle management, plugin system, and persistent storage.\n *\n * @example\n * ```typescript\n * const manager = new UploadManager({\n * requestAdapter: myAdapter,\n * maxConcurrentTasks: 3,\n * defaultChunkSize: 1024 * 1024, // 1MB\n * });\n *\n * // Initialize (loads unfinished tasks if enabled)\n * await manager.init();\n *\n * // Create and start a task\n * const task = manager.createTask(file);\n * await task.start();\n *\n * // Get all tasks\n * const allTasks = manager.getAllTasks();\n *\n * // Delete a task\n * await manager.deleteTask(task.id);\n * ```\n */\nexport class UploadManager {\n /** Map of task ID to UploadTask instances */\n private tasks: Map<string, UploadTask>;\n\n /** Manager options with defaults applied */\n private options: Required<UploadManagerOptions>;\n\n /** Storage instance for persistent task data */\n private storage: UploadStorage;\n\n /** Flag indicating if manager has been initialized */\n private initialized: boolean;\n\n /** Registered plugins */\n private plugins: Plugin[];\n\n /**\n * Creates a new UploadManager instance\n *\n * @param options - Configuration options for the manager\n *\n * @remarks\n * - Validates: Requirement 8.6 (UploadManager manages multiple tasks)\n * - Applies default values for optional parameters\n * - Creates storage instance for persistence\n * - Does not automatically initialize - call init() explicitly\n */\n constructor(options: UploadManagerOptions) {\n // Initialize tasks map\n this.tasks = new Map();\n\n // Apply default options\n this.options = {\n requestAdapter: options.requestAdapter,\n maxConcurrentTasks: options.maxConcurrentTasks ?? 3,\n defaultChunkSize: options.defaultChunkSize ?? 1024 * 1024, // 1MB\n defaultConcurrency: options.defaultConcurrency ?? 3,\n autoResumeUnfinished: options.autoResumeUnfinished ?? true,\n };\n\n // Create storage instance\n this.storage = new UploadStorage();\n\n // Initialize plugins array\n this.plugins = [];\n\n // Initialize flag\n this.initialized = false;\n }\n\n /**\n * Registers a plugin with the manager\n *\n * Plugins can hook into task lifecycle events to add custom behavior.\n * Plugins are called in the order they were registered.\n *\n * @param plugin - Plugin instance to register\n *\n * @remarks\n * - Validates: Requirement 6.5 (Plugin mechanism)\n * - Validates: Requirement 8.5 (Plugin system)\n * - Plugin's install() method is called immediately if provided\n * - Plugin errors are caught and logged but don't stop execution\n * - Duplicate plugin names are allowed (no uniqueness check)\n *\n * @example\n * ```typescript\n * const logger = new LoggerPlugin();\n * manager.use(logger);\n *\n * const stats = new StatisticsPlugin();\n * manager.use(stats);\n * ```\n */\n use(plugin: Plugin): void {\n // Add plugin to array\n this.plugins.push(plugin);\n\n // Call install hook if provided\n if (plugin.install) {\n try {\n plugin.install(this);\n } catch (error) {\n console.error(`Plugin \"${plugin.name}\" install failed:`, error);\n }\n }\n }\n\n /**\n * Calls a plugin hook for all registered plugins\n *\n * @param hookName - Name of the hook to call\n * @param args - Arguments to pass to the hook\n *\n * @internal\n */\n private callPluginHook<K extends keyof Plugin>(hookName: K, ...args: unknown[]): void {\n for (const plugin of this.plugins) {\n const hook = plugin[hookName];\n if (hook && typeof hook === \"function\") {\n try {\n (hook as (...args: unknown[]) => void).apply(plugin, args);\n } catch (error) {\n console.error(`Plugin \"${plugin.name}\" hook \"${String(hookName)}\" failed:`, error);\n }\n }\n }\n }\n\n /**\n * Initializes the UploadManager\n *\n * Performs initialization tasks including:\n * - Initializing IndexedDB storage\n * - Loading unfinished tasks if autoResumeUnfinished is enabled\n *\n * @remarks\n * - Validates: Requirement 8.6 (initialization and task management)\n * - Should be called once before using the manager\n * - Safe to call multiple times (idempotent)\n * - Gracefully handles storage initialization failures\n *\n * @example\n * ```typescript\n * const manager = new UploadManager({ requestAdapter });\n * await manager.init();\n * ```\n */\n async init(): Promise<void> {\n // Skip if already initialized\n if (this.initialized) {\n return;\n }\n\n try {\n // Initialize storage\n await this.storage.init();\n\n // Load unfinished tasks if enabled\n if (this.options.autoResumeUnfinished) {\n await this.loadUnfinishedTasks();\n }\n\n // Mark as initialized\n this.initialized = true;\n } catch (error) {\n // Silently ignore storage initialization errors\n // Manager can still work without storage\n this.initialized = true; // Still mark as initialized\n }\n }\n\n /**\n * Creates a new upload task\n *\n * Creates an UploadTask instance for the given file and adds it to the manager.\n * The task is not automatically started - call task.start() to begin upload.\n *\n * @param file - File to upload\n * @param options - Optional task-specific configuration (overrides defaults)\n * @returns Created UploadTask instance\n *\n * @remarks\n * - Validates: Requirement 8.6 (task creation and management)\n * - Task is added to the manager's task map\n * - Uses manager's default options unless overridden\n * - Task is not started automatically\n *\n * @example\n * ```typescript\n * const task = manager.createTask(file, {\n * chunkSize: 2 * 1024 * 1024, // 2MB\n * concurrency: 5,\n * });\n *\n * task.on('progress', ({ progress }) => {\n * console.log(`Progress: ${progress}%`);\n * });\n *\n * await task.start();\n * ```\n */\n createTask(file: File, options?: Partial<UploadTaskOptions>): UploadTask {\n // Create task with merged options\n const task = new UploadTask({\n file,\n requestAdapter: this.options.requestAdapter,\n chunkSize: options?.chunkSize ?? this.options.defaultChunkSize,\n concurrency: options?.concurrency ?? this.options.defaultConcurrency,\n retryCount: options?.retryCount ?? 3,\n retryDelay: options?.retryDelay ?? 1000,\n autoStart: options?.autoStart ?? false,\n });\n\n // Add task to map\n this.tasks.set(task.id, task);\n\n // Call plugin hook\n this.callPluginHook(\"onTaskCreated\", task);\n\n // Set up event listeners for plugin hooks\n this.setupTaskPluginHooks(task);\n\n return task;\n }\n\n /**\n * Sets up event listeners on a task to call plugin hooks\n *\n * @param task - Task to set up listeners for\n *\n * @internal\n */\n private setupTaskPluginHooks(task: UploadTask): void {\n // Start event\n task.on(\"start\", () => {\n this.callPluginHook(\"onTaskStart\", task);\n });\n\n // Progress event\n task.on(\"progress\", () => {\n const progressData = task.getProgress();\n this.callPluginHook(\"onTaskProgress\", task, progressData);\n });\n\n // Success event\n task.on(\"success\", ({ fileUrl }) => {\n this.callPluginHook(\"onTaskSuccess\", task, fileUrl);\n });\n\n // Error event\n task.on(\"error\", ({ error }) => {\n this.callPluginHook(\"onTaskError\", task, error);\n });\n\n // Pause event\n task.on(\"pause\", () => {\n this.callPluginHook(\"onTaskPause\", task);\n });\n\n // Resume event\n task.on(\"resume\", () => {\n this.callPluginHook(\"onTaskResume\", task);\n });\n\n // Cancel event\n task.on(\"cancel\", () => {\n this.callPluginHook(\"onTaskCancel\", task);\n });\n }\n\n /**\n * Gets a task by its ID\n *\n * @param taskId - Unique task identifier\n * @returns UploadTask instance or undefined if not found\n *\n * @remarks\n * - Validates: Requirement 8.6 (task retrieval)\n *\n * @example\n * ```typescript\n * const task = manager.getTask('task_abc123');\n * if (task) {\n * console.log(`Status: ${task.getStatus()}`);\n * }\n * ```\n */\n getTask(taskId: string): UploadTask | undefined {\n return this.tasks.get(taskId);\n }\n\n /**\n * Gets all tasks managed by this manager\n *\n * @returns Array of all UploadTask instances\n *\n * @remarks\n * - Validates: Requirement 8.6 (task retrieval)\n * - Returns a new array (safe to modify)\n * - Tasks are in insertion order\n *\n * @example\n * ```typescript\n * const allTasks = manager.getAllTasks();\n * console.log(`Total tasks: ${allTasks.length}`);\n *\n * // Filter by status\n * const uploadingTasks = allTasks.filter(\n * task => task.getStatus() === 'uploading'\n * );\n * ```\n */\n getAllTasks(): UploadTask[] {\n return Array.from(this.tasks.values());\n }\n\n /**\n * Deletes a task from the manager\n *\n * Cancels the task if it's still running and removes it from the manager.\n * Also cleans up the task's storage record.\n *\n * @param taskId - Unique task identifier\n *\n * @remarks\n * - Validates: Requirement 8.6 (task deletion)\n * - Cancels the task if it's still running\n * - Removes task from manager's task map\n * - Cleans up storage record\n * - Safe to call even if task doesn't exist\n *\n * @example\n * ```typescript\n * // Delete a specific task\n * await manager.deleteTask('task_abc123');\n *\n * // Delete all completed tasks\n * const tasks = manager.getAllTasks();\n * for (const task of tasks) {\n * if (task.getStatus() === 'success') {\n * await manager.deleteTask(task.id);\n * }\n * }\n * ```\n */\n async deleteTask(taskId: string): Promise<void> {\n const task = this.tasks.get(taskId);\n\n if (task) {\n // Cancel the task if it's still running\n const status = task.getStatus();\n if (status === \"uploading\" || status === \"paused\") {\n task.cancel();\n }\n\n // Remove from tasks map\n this.tasks.delete(taskId);\n\n // Clean up storage record\n try {\n if (this.storage.isAvailable()) {\n await this.storage.deleteRecord(taskId);\n }\n } catch (error) {\n // Log warning but don't fail deletion\n console.warn(`Failed to delete storage record for task ${taskId}:`, error);\n }\n }\n }\n\n /**\n * Loads unfinished tasks from storage\n *\n * Retrieves upload records from IndexedDB and creates task placeholders.\n * Note: Tasks cannot be automatically resumed because File objects cannot\n * be persisted. Users must re-select files to resume uploads.\n *\n * @remarks\n * - Validates: Requirement 4.2 (read unfinished tasks from IndexedDB)\n * - Creates task entries in the manager\n * - Tasks are in 'paused' state and require file re-selection to resume\n * - Gracefully handles storage errors\n *\n * @internal\n */\n private async loadUnfinishedTasks(): Promise<void> {\n try {\n // Check if storage is available\n if (!this.storage.isAvailable()) {\n return;\n }\n\n // Get all records from storage\n await this.storage.getAllRecords();\n\n // Note: We cannot automatically create UploadTask instances because\n // File objects cannot be persisted to IndexedDB. The user would need\n // to re-select the files to resume uploads.\n //\n // This is a limitation of the browser File API - File objects are\n // references to files on the user's filesystem and cannot be serialized.\n //\n // A future enhancement could:\n // 1. Store file metadata (name, size, type, lastModified)\n // 2. Provide a UI for users to re-select files\n // 3. Match re-selected files with stored metadata\n // 4. Resume uploads from stored progress\n //\n // For now, we silently track unfinished tasks\n // (logging removed to avoid test noise)\n } catch (error) {\n // Silently ignore errors loading unfinished tasks\n }\n }\n\n /**\n * Gets information about unfinished tasks from storage\n *\n * Returns metadata about uploads that were not completed in previous sessions.\n * This allows UI layers to prompt users to resume uploads by re-selecting files.\n *\n * @returns Array of unfinished upload records with file metadata\n *\n * @remarks\n * - Validates: Requirement 4.2 (read unfinished tasks from IndexedDB)\n * - Validates: Requirement 4.3 (provide interface for resuming tasks)\n * - Returns empty array if storage is unavailable or on error\n * - File objects cannot be restored - users must re-select files\n *\n * @example\n * ```typescript\n * const unfinished = await manager.getUnfinishedTasksInfo();\n * if (unfinished.length > 0) {\n * console.log('Found unfinished uploads:');\n * unfinished.forEach(record => {\n * console.log(`- ${record.fileInfo.name} (${record.uploadedChunks.length} chunks uploaded)`);\n * });\n * }\n * ```\n */\n async getUnfinishedTasksInfo(): Promise<\n Array<{\n taskId: string;\n fileInfo: {\n name: string;\n size: number;\n type: string;\n lastModified: number;\n };\n uploadedChunks: number[];\n uploadToken: string;\n createdAt: number;\n updatedAt: number;\n }>\n > {\n try {\n // Check if storage is available\n if (!this.storage.isAvailable()) {\n return [];\n }\n\n // Get all records from storage\n const records = await this.storage.getAllRecords();\n\n // Return records with file metadata\n return records.map((record) => ({\n taskId: record.taskId,\n fileInfo: {\n name: record.fileInfo.name,\n size: record.fileInfo.size,\n type: record.fileInfo.type,\n lastModified: record.fileInfo.lastModified,\n },\n uploadedChunks: record.uploadedChunks,\n uploadToken: record.uploadToken,\n createdAt: record.createdAt,\n updatedAt: record.updatedAt,\n }));\n } catch (error) {\n console.warn(\"Failed to get unfinished tasks info:\", error);\n return [];\n }\n }\n\n /**\n * Resumes an unfinished upload task with a re-selected file\n *\n * Allows users to resume a previously interrupted upload by providing the\n * original task ID and re-selecting the file. The method validates that the\n * file matches the stored metadata and creates a new task that continues\n * from the last uploaded chunk.\n *\n * @param taskId - ID of the unfinished task to resume\n * @param file - Re-selected file (must match original file metadata)\n * @param options - Optional task configuration overrides\n * @returns Created UploadTask instance ready to resume\n * @throws Error if task record not found or file doesn't match\n *\n * @remarks\n * - Validates: Requirement 4.3 (resume unfinished tasks)\n * - Validates: Requirement 4.4 (continue from last uploaded chunk)\n * - Verifies file matches stored metadata (name, size, type, lastModified)\n * - Creates new task with stored progress\n * - Removes old storage record and creates new one with same ID\n *\n * @example\n * ```typescript\n * // Get unfinished tasks\n * const unfinished = await manager.getUnfinishedTasksInfo();\n *\n * // User re-selects file\n * const file = await selectFile();\n *\n * // Resume upload\n * try {\n * const task = await manager.resumeTask(unfinished[0].taskId, file);\n * await task.start();\n * } catch (error) {\n * console.error('Failed to resume:', error);\n * }\n * ```\n */\n async resumeTask(\n taskId: string,\n file: File,\n options?: Partial<UploadTaskOptions>,\n ): Promise<UploadTask> {\n // Check if storage is available\n if (!this.storage.isAvailable()) {\n throw new Error(\"Storage is not available - cannot resume task\");\n }\n\n // Get the stored record\n const record = await this.storage.getRecord(taskId);\n if (!record) {\n throw new Error(`No unfinished task found with ID: ${taskId}`);\n }\n\n // Validate file matches stored metadata\n if (file.name !== record.fileInfo.name) {\n throw new Error(`File name mismatch: expected \"${record.fileInfo.name}\", got \"${file.name}\"`);\n }\n\n if (file.size !== record.fileInfo.size) {\n throw new Error(`File size mismatch: expected ${record.fileInfo.size}, got ${file.size}`);\n }\n\n if (file.type !== record.fileInfo.type) {\n throw new Error(`File type mismatch: expected \"${record.fileInfo.type}\", got \"${file.type}\"`);\n }\n\n // Note: lastModified check is optional as it may change if file is copied\n // We rely on name, size, and type for validation\n\n // Create a new task with the same ID\n const task = new UploadTask({\n file,\n requestAdapter: this.options.requestAdapter,\n chunkSize: options?.chunkSize ?? this.options.defaultChunkSize,\n concurrency: options?.concurrency ?? this.options.defaultConcurrency,\n retryCount: options?.retryCount ?? 3,\n retryDelay: options?.retryDelay ?? 1000,\n autoStart: options?.autoStart ?? false,\n resumeTaskId: taskId, // Pass the task ID to resume\n resumeUploadToken: record.uploadToken, // Pass the upload token\n resumeUploadedChunks: record.uploadedChunks, // Pass uploaded chunks\n });\n\n // Add task to manager\n this.tasks.set(task.id, task);\n\n // Call plugin hook\n this.callPluginHook(\"onTaskCreated\", task);\n\n // Set up event listeners for plugin hooks\n this.setupTaskPluginHooks(task);\n\n // Delete old storage record (new one will be created when task starts)\n try {\n await this.storage.deleteRecord(taskId);\n } catch (error) {\n console.warn(`Failed to delete old storage record for task ${taskId}:`, error);\n }\n\n return task;\n }\n\n /**\n * Clears a specific unfinished task record from storage\n *\n * Removes the storage record for an unfinished task without resuming it.\n * Useful for cleaning up tasks that the user no longer wants to resume.\n *\n * @param taskId - ID of the unfinished task to clear\n *\n * @remarks\n * - Validates: Requirement 4.5 (clear saved upload records)\n * - Safe to call even if record doesn't exist\n * - Does not affect active tasks in the manager\n *\n * @example\n * ```typescript\n * // Clear a specific unfinished task\n * await manager.clearUnfinishedTask('task_abc123');\n *\n * // Clear all unfinished tasks\n * const unfinished = await manager.getUnfinishedTasksInfo();\n * for (const record of unfinished) {\n * await manager.clearUnfinishedTask(record.taskId);\n * }\n * ```\n */\n async clearUnfinishedTask(taskId: string): Promise<void> {\n try {\n if (this.storage.isAvailable()) {\n await this.storage.deleteRecord(taskId);\n }\n } catch (error) {\n console.warn(`Failed to clear unfinished task ${taskId}:`, error);\n }\n }\n\n /**\n * Clears all unfinished task records from storage\n *\n * Removes all storage records for unfinished tasks.\n * Useful for cleaning up when users don't want to resume any uploads.\n *\n * @returns Number of records cleared\n *\n * @remarks\n * - Validates: Requirement 4.5 (clear saved upload records)\n * - Does not affect active tasks in the manager\n * - Returns 0 if storage is unavailable or on error\n *\n * @example\n * ```typescript\n * const cleared = await manager.clearAllUnfinishedTasks();\n * console.log(`Cleared ${cleared} unfinished task(s)`);\n * ```\n */\n async clearAllUnfinishedTasks(): Promise<number> {\n try {\n if (!this.storage.isAvailable()) {\n return 0;\n }\n\n const records = await this.storage.getAllRecords();\n const count = records.length;\n\n await this.storage.clearAll();\n\n return count;\n } catch (error) {\n console.warn(\"Failed to clear all unfinished tasks:\", error);\n return 0;\n }\n }\n\n /**\n * Gets the number of tasks in the manager\n *\n * @returns Total number of tasks\n *\n * @example\n * ```typescript\n * console.log(`Total tasks: ${manager.getTaskCount()}`);\n * ```\n */\n getTaskCount(): number {\n return this.tasks.size;\n }\n\n /**\n * Checks if the manager has been initialized\n *\n * @returns True if initialized, false otherwise\n *\n * @example\n * ```typescript\n * if (!manager.isInitialized()) {\n * await manager.init();\n * }\n * ```\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n\n /**\n * Clears all completed tasks from the manager\n *\n * Removes tasks with 'success', 'error', or 'cancelled' status.\n * Does not affect running or paused tasks.\n *\n * @returns Number of tasks cleared\n *\n * @example\n * ```typescript\n * const cleared = await manager.clearCompletedTasks();\n * console.log(`Cleared ${cleared} completed task(s)`);\n * ```\n */\n async clearCompletedTasks(): Promise<number> {\n const tasks = this.getAllTasks();\n let clearedCount = 0;\n\n for (const task of tasks) {\n const status = task.getStatus();\n if (status === \"success\" || status === \"error\" || status === \"cancelled\") {\n await this.deleteTask(task.id);\n clearedCount++;\n }\n }\n\n return clearedCount;\n }\n\n /**\n * Pauses all running tasks\n *\n * Calls pause() on all tasks with 'uploading' status.\n *\n * @returns Number of tasks paused\n *\n * @example\n * ```typescript\n * const paused = manager.pauseAll();\n * console.log(`Paused ${paused} task(s)`);\n * ```\n */\n pauseAll(): number {\n const tasks = this.getAllTasks();\n let pausedCount = 0;\n\n for (const task of tasks) {\n if (task.getStatus() === \"uploading\") {\n task.pause();\n pausedCount++;\n }\n }\n\n return pausedCount;\n }\n\n /**\n * Resumes all paused tasks\n *\n * Calls resume() on all tasks with 'paused' status.\n *\n * @returns Number of tasks resumed\n *\n * @example\n * ```typescript\n * const resumed = await manager.resumeAll();\n * console.log(`Resumed ${resumed} task(s)`);\n * ```\n */\n async resumeAll(): Promise<number> {\n const tasks = this.getAllTasks();\n let resumedCount = 0;\n\n for (const task of tasks) {\n if (task.getStatus() === \"paused\") {\n try {\n await task.resume();\n resumedCount++;\n } catch (error) {\n console.warn(`Failed to resume task ${task.id}:`, error);\n }\n }\n }\n\n return resumedCount;\n }\n\n /**\n * Cancels all running and paused tasks\n *\n * Calls cancel() on all tasks that are not in a terminal state.\n *\n * @returns Number of tasks cancelled\n *\n * @example\n * ```typescript\n * const cancelled = manager.cancelAll();\n * console.log(`Cancelled ${cancelled} task(s)`);\n * ```\n */\n cancelAll(): number {\n const tasks = this.getAllTasks();\n let cancelledCount = 0;\n\n for (const task of tasks) {\n const status = task.getStatus();\n if (status === \"uploading\" || status === \"paused\") {\n task.cancel();\n cancelledCount++;\n }\n }\n\n return cancelledCount;\n }\n\n /**\n * Gets statistics about all tasks\n *\n * @returns Object containing task statistics\n *\n * @example\n * ```typescript\n * const stats = manager.getStatistics();\n * console.log(`Total: ${stats.total}`);\n * console.log(`Uploading: ${stats.uploading}`);\n * console.log(`Success: ${stats.success}`);\n * ```\n */\n getStatistics(): {\n total: number;\n idle: number;\n uploading: number;\n paused: number;\n success: number;\n error: number;\n cancelled: number;\n } {\n const tasks = this.getAllTasks();\n\n const stats = {\n total: tasks.length,\n idle: 0,\n uploading: 0,\n paused: 0,\n success: 0,\n error: 0,\n cancelled: 0,\n };\n\n for (const task of tasks) {\n const status = task.getStatus();\n switch (status) {\n case \"idle\":\n stats.idle++;\n break;\n case \"uploading\":\n stats.uploading++;\n break;\n case \"paused\":\n stats.paused++;\n break;\n case \"success\":\n stats.success++;\n break;\n case \"error\":\n stats.error++;\n break;\n case \"cancelled\":\n stats.cancelled++;\n break;\n }\n }\n\n return stats;\n }\n\n /**\n * Closes the manager and cleans up resources\n *\n * Cancels all running tasks and closes the storage connection.\n * The manager should not be used after calling this method.\n *\n * @example\n * ```typescript\n * // Clean up when done\n * manager.close();\n * ```\n */\n close(): void {\n // Cancel all running tasks\n this.cancelAll();\n\n // Close storage connection\n this.storage.close();\n\n // Clear tasks map\n this.tasks.clear();\n\n // Reset initialized flag\n this.initialized = false;\n }\n}\n","/**\n * Built-in plugins for UploadManager\n *\n * This module provides example plugin implementations that demonstrate\n * how to extend the UploadManager with custom functionality.\n */\n\nimport type { UploadProgress } from \"@chunkflowjs/protocol\";\nimport type { Plugin } from \"./upload-manager\";\nimport type { UploadTask } from \"./upload-task\";\n\n/**\n * Logger plugin for debugging and monitoring uploads\n *\n * Logs all task lifecycle events to the console with timestamps.\n * Useful for development and debugging.\n *\n * @remarks\n * - Validates: Requirement 6.5 (Plugin mechanism example)\n * - Validates: Requirement 8.5 (Plugin system example)\n * - Logs to console with timestamps\n * - Can be configured to log only specific events\n *\n * @example\n * ```typescript\n * const manager = new UploadManager({ requestAdapter });\n * manager.use(new LoggerPlugin());\n *\n * // With custom options\n * manager.use(new LoggerPlugin({\n * logProgress: false, // Don't log progress updates\n * prefix: '[Upload]' // Custom log prefix\n * }));\n * ```\n */\nexport class LoggerPlugin implements Plugin {\n name = \"logger\";\n\n private options: {\n logProgress: boolean;\n logStart: boolean;\n logSuccess: boolean;\n logError: boolean;\n logPause: boolean;\n logResume: boolean;\n logCancel: boolean;\n prefix: string;\n };\n\n constructor(\n options?: Partial<{\n logProgress: boolean;\n logStart: boolean;\n logSuccess: boolean;\n logError: boolean;\n logPause: boolean;\n logResume: boolean;\n logCancel: boolean;\n prefix: string;\n }>,\n ) {\n this.options = {\n logProgress: options?.logProgress ?? true,\n logStart: options?.logStart ?? true,\n logSuccess: options?.logSuccess ?? true,\n logError: options?.logError ?? true,\n logPause: options?.logPause ?? true,\n logResume: options?.logResume ?? true,\n logCancel: options?.logCancel ?? true,\n prefix: options?.prefix ?? \"[LoggerPlugin]\",\n };\n }\n\n private log(message: string, ...args: unknown[]): void {\n const timestamp = new Date().toISOString();\n console.log(`${this.options.prefix} [${timestamp}]`, message, ...args);\n }\n\n install(): void {\n this.log(\"Plugin installed\");\n }\n\n onTaskCreated(task: UploadTask): void {\n this.log(`Task created: ${task.id}`, {\n fileName: task.file.name,\n fileSize: task.file.size,\n fileType: task.file.type,\n });\n }\n\n onTaskStart(task: UploadTask): void {\n if (this.options.logStart) {\n this.log(`Task started: ${task.id}`, {\n fileName: task.file.name,\n });\n }\n }\n\n onTaskProgress(task: UploadTask, progress: UploadProgress): void {\n if (this.options.logProgress) {\n this.log(`Task progress: ${task.id}`, {\n percentage: `${progress.percentage.toFixed(2)}%`,\n uploadedBytes: progress.uploadedBytes,\n totalBytes: progress.totalBytes,\n speed: `${(progress.speed / 1024 / 1024).toFixed(2)} MB/s`,\n remainingTime: `${progress.remainingTime.toFixed(0)}s`,\n });\n }\n }\n\n onTaskSuccess(task: UploadTask, fileUrl: string): void {\n if (this.options.logSuccess) {\n this.log(`Task completed: ${task.id}`, {\n fileName: task.file.name,\n fileUrl,\n });\n }\n }\n\n onTaskError(task: UploadTask, error: Error): void {\n if (this.options.logError) {\n this.log(`Task error: ${task.id}`, {\n fileName: task.file.name,\n error: error.message,\n stack: error.stack,\n });\n }\n }\n\n onTaskPause(task: UploadTask): void {\n if (this.options.logPause) {\n this.log(`Task paused: ${task.id}`, {\n fileName: task.file.name,\n });\n }\n }\n\n onTaskResume(task: UploadTask): void {\n if (this.options.logResume) {\n this.log(`Task resumed: ${task.id}`, {\n fileName: task.file.name,\n });\n }\n }\n\n onTaskCancel(task: UploadTask): void {\n if (this.options.logCancel) {\n this.log(`Task cancelled: ${task.id}`, {\n fileName: task.file.name,\n });\n }\n }\n}\n\n/**\n * Statistics plugin for tracking upload metrics\n *\n * Collects statistics about uploads including success/error counts,\n * total bytes uploaded, average speed, etc.\n *\n * @remarks\n * - Validates: Requirement 6.5 (Plugin mechanism example)\n * - Validates: Requirement 8.5 (Plugin system example)\n * - Tracks upload statistics in memory\n * - Provides methods to retrieve and reset statistics\n * - Thread-safe for concurrent uploads\n *\n * @example\n * ```typescript\n * const stats = new StatisticsPlugin();\n * manager.use(stats);\n *\n * // Later, get statistics\n * const metrics = stats.getStats();\n * console.log(`Success rate: ${metrics.successRate}%`);\n * console.log(`Total uploaded: ${metrics.totalBytesUploaded} bytes`);\n * console.log(`Average speed: ${metrics.averageSpeed} bytes/s`);\n * ```\n */\nexport class StatisticsPlugin implements Plugin {\n name = \"statistics\";\n\n private stats = {\n totalFiles: 0,\n successCount: 0,\n errorCount: 0,\n cancelledCount: 0,\n totalBytesUploaded: 0,\n totalUploadTime: 0, // milliseconds\n startTimes: new Map<string, number>(),\n };\n\n install(): void {\n // No initialization needed\n }\n\n onTaskCreated(_task: UploadTask): void {\n this.stats.totalFiles++;\n }\n\n onTaskStart(task: UploadTask): void {\n // Record start time for this task\n this.stats.startTimes.set(task.id, Date.now());\n }\n\n onTaskSuccess(task: UploadTask, _fileUrl: string): void {\n this.stats.successCount++;\n this.stats.totalBytesUploaded += task.file.size;\n\n // Calculate upload time\n const startTime = this.stats.startTimes.get(task.id);\n if (startTime) {\n const uploadTime = Date.now() - startTime;\n this.stats.totalUploadTime += uploadTime;\n this.stats.startTimes.delete(task.id);\n }\n }\n\n onTaskError(task: UploadTask, _error: Error): void {\n this.stats.errorCount++;\n\n // Clean up start time\n this.stats.startTimes.delete(task.id);\n }\n\n onTaskCancel(task: UploadTask): void {\n this.stats.cancelledCount++;\n\n // Clean up start time\n this.stats.startTimes.delete(task.id);\n }\n\n /**\n * Gets current statistics\n *\n * @returns Object containing upload statistics\n *\n * @example\n * ```typescript\n * const stats = plugin.getStats();\n * console.log(`Success rate: ${stats.successRate}%`);\n * ```\n */\n getStats(): {\n totalFiles: number;\n successCount: number;\n errorCount: number;\n cancelledCount: number;\n totalBytesUploaded: number;\n averageSpeed: number; // bytes per second\n averageUploadTime: number; // milliseconds\n successRate: number; // percentage\n errorRate: number; // percentage\n } {\n const completedCount =\n this.stats.successCount + this.stats.errorCount + this.stats.cancelledCount;\n const averageSpeed =\n this.stats.totalUploadTime > 0\n ? (this.stats.totalBytesUploaded / this.stats.totalUploadTime) * 1000\n : 0;\n const averageUploadTime =\n this.stats.successCount > 0 ? this.stats.totalUploadTime / this.stats.successCount : 0;\n const successRate = completedCount > 0 ? (this.stats.successCount / completedCount) * 100 : 0;\n const errorRate = completedCount > 0 ? (this.stats.errorCount / completedCount) * 100 : 0;\n\n return {\n totalFiles: this.stats.totalFiles,\n successCount: this.stats.successCount,\n errorCount: this.stats.errorCount,\n cancelledCount: this.stats.cancelledCount,\n totalBytesUploaded: this.stats.totalBytesUploaded,\n averageSpeed,\n averageUploadTime,\n successRate,\n errorRate,\n };\n }\n\n /**\n * Resets all statistics to zero\n *\n * @example\n * ```typescript\n * plugin.reset();\n * ```\n */\n reset(): void {\n this.stats = {\n totalFiles: 0,\n successCount: 0,\n errorCount: 0,\n cancelledCount: 0,\n totalBytesUploaded: 0,\n totalUploadTime: 0,\n startTimes: new Map(),\n };\n }\n\n /**\n * Gets a formatted summary of statistics\n *\n * @returns Human-readable statistics summary\n *\n * @example\n * ```typescript\n * console.log(plugin.getSummary());\n * // Output:\n * // Upload Statistics:\n * // Total Files: 10\n * // Success: 8 (80.00%)\n * // Errors: 1 (10.00%)\n * // Cancelled: 1 (10.00%)\n * // Total Uploaded: 52.43 MB\n * // Average Speed: 2.15 MB/s\n * // Average Time: 24.5s\n * ```\n */\n getSummary(): string {\n const stats = this.getStats();\n const formatBytes = (bytes: number): string => {\n const mb = bytes / 1024 / 1024;\n return `${mb.toFixed(2)} MB`;\n };\n const formatSpeed = (bytesPerSecond: number): string => {\n const mbps = bytesPerSecond / 1024 / 1024;\n return `${mbps.toFixed(2)} MB/s`;\n };\n const formatTime = (ms: number): string => {\n const seconds = ms / 1000;\n return `${seconds.toFixed(1)}s`;\n };\n\n return `Upload Statistics:\n Total Files: ${stats.totalFiles}\n Success: ${stats.successCount} (${stats.successRate.toFixed(2)}%)\n Errors: ${stats.errorCount} (${stats.errorRate.toFixed(2)}%)\n Cancelled: ${stats.cancelledCount}\n Total Uploaded: ${formatBytes(stats.totalBytesUploaded)}\n Average Speed: ${formatSpeed(stats.averageSpeed)}\n Average Time: ${formatTime(stats.averageUploadTime)}`;\n }\n}\n","/**\n * Fetch-based RequestAdapter implementation\n * Provides a simple way to create an adapter using the Fetch API\n */\n\nimport type { RequestAdapter } from \"@chunkflowjs/protocol\";\n\n/**\n * Options for creating a Fetch adapter\n */\nexport interface FetchAdapterOptions {\n /** Base URL for API requests */\n baseURL: string;\n /** Custom headers to include in all requests */\n headers?: Record<string, string>;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Custom fetch implementation (default: global fetch) */\n fetch?: typeof fetch;\n /** Callback for handling errors */\n onError?: (error: Error) => void;\n}\n\n/**\n * Create a Fetch-based RequestAdapter\n *\n * @param options - Configuration options\n * @returns RequestAdapter instance\n *\n * @example\n * ```typescript\n * const adapter = createFetchAdapter({\n * baseURL: 'http://localhost:3000/api',\n * headers: {\n * 'Authorization': 'Bearer token123'\n * }\n * });\n *\n * const manager = new UploadManager({ requestAdapter: adapter });\n * ```\n */\nexport function createFetchAdapter(options: FetchAdapterOptions): RequestAdapter {\n const {\n baseURL,\n headers = {},\n timeout = 30000,\n fetch: customFetch = globalThis.fetch,\n onError,\n } = options;\n\n // Ensure baseURL doesn't end with slash\n const normalizedBaseURL = baseURL.replace(/\\/$/, \"\");\n\n /**\n * Make a fetch request with timeout\n */\n async function fetchWithTimeout(url: string, init?: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await customFetch(url, {\n ...init,\n signal: controller.signal,\n headers: {\n ...headers,\n ...init?.headers,\n },\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const error = new Error(`HTTP ${response.status}: ${response.statusText}`);\n if (onError) onError(error);\n throw error;\n }\n\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n if (onError && error instanceof Error) onError(error);\n throw error;\n }\n }\n\n return {\n /**\n * Create a new file upload session\n */\n async createFile(request) {\n const response = await fetchWithTimeout(`${normalizedBaseURL}/upload/create`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n fileName: request.fileName,\n fileSize: request.fileSize,\n fileType: request.fileType,\n preferredChunkSize: request.preferredChunkSize,\n }),\n });\n\n const data = await response.json();\n\n // Convert server response to protocol format\n // Server returns { uploadToken: string, negotiatedChunkSize: number }\n // Protocol expects { uploadToken: UploadToken, negotiatedChunkSize: number }\n return {\n uploadToken: {\n token: data.uploadToken,\n fileId: \"\", // Will be extracted from JWT if needed\n chunkSize: data.negotiatedChunkSize,\n expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours\n },\n negotiatedChunkSize: data.negotiatedChunkSize,\n };\n },\n\n /**\n * Verify file and chunk hashes\n */\n async verifyHash(request) {\n // Extract token string from UploadToken object or use string directly\n const token =\n typeof request.uploadToken === \"string\"\n ? request.uploadToken\n : (request.uploadToken as any).token;\n\n const response = await fetchWithTimeout(`${normalizedBaseURL}/upload/verify`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n fileHash: request.fileHash,\n chunkHashes: request.chunkHashes,\n uploadToken: token,\n }),\n });\n\n return response.json();\n },\n\n /**\n * Upload a single chunk\n */\n async uploadChunk(request) {\n // Extract token string from UploadToken object or use string directly\n const token =\n typeof request.uploadToken === \"string\"\n ? request.uploadToken\n : (request.uploadToken as any).token;\n\n const formData = new FormData();\n formData.append(\"uploadToken\", token);\n formData.append(\"chunkIndex\", request.chunkIndex.toString());\n formData.append(\"chunkHash\", request.chunkHash);\n\n // Handle both Blob and Buffer types\n if (request.chunk instanceof Blob) {\n formData.append(\"chunk\", request.chunk);\n } else {\n // Convert Buffer to Blob for Node.js environments\n // Use type assertion as Buffer is compatible with BlobPart at runtime\n const blob = new Blob([request.chunk as unknown as BlobPart]);\n formData.append(\"chunk\", blob);\n }\n\n const response = await fetchWithTimeout(`${normalizedBaseURL}/upload/chunk`, {\n method: \"POST\",\n body: formData,\n });\n\n return response.json();\n },\n\n /**\n * Merge all chunks into final file\n */\n async mergeFile(request) {\n // Extract token string from UploadToken object or use string directly\n const token =\n typeof request.uploadToken === \"string\"\n ? request.uploadToken\n : (request.uploadToken as any).token;\n\n const response = await fetchWithTimeout(`${normalizedBaseURL}/upload/merge`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n uploadToken: token,\n fileHash: request.fileHash,\n chunkHashes: request.chunkHashes,\n }),\n });\n\n return response.json();\n },\n };\n}\n","/**\n * XMLHttpRequest-based RequestAdapter implementation\n * Provides a way to create an adapter using XMLHttpRequest with progress tracking\n */\n\nimport type { RequestAdapter } from \"@chunkflowjs/protocol\";\n\n/**\n * Options for creating an XHR adapter\n */\nexport interface XHRAdapterOptions {\n /** Base URL for API requests */\n baseURL: string;\n /** Custom headers to include in all requests */\n headers?: Record<string, string>;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Whether to send credentials (cookies) with requests (default: false) */\n withCredentials?: boolean;\n /** Callback for upload progress events */\n onUploadProgress?: (event: ProgressEvent) => void;\n /** Callback for handling errors */\n onError?: (error: Error) => void;\n}\n\n/**\n * Create an XMLHttpRequest-based RequestAdapter\n *\n * @param options - Configuration options\n * @returns RequestAdapter instance\n *\n * @example\n * ```typescript\n * const adapter = createXHRAdapter({\n * baseURL: 'http://localhost:3000/api',\n * headers: {\n * 'Authorization': 'Bearer token123'\n * },\n * onUploadProgress: (event) => {\n * console.log(`Upload progress: ${(event.loaded / event.total) * 100}%`);\n * }\n * });\n *\n * const manager = new UploadManager({ requestAdapter: adapter });\n * ```\n */\nexport function createXHRAdapter(options: XHRAdapterOptions): RequestAdapter {\n const {\n baseURL,\n headers = {},\n timeout = 30000,\n withCredentials = false,\n onUploadProgress,\n onError,\n } = options;\n\n // Ensure baseURL doesn't end with slash\n const normalizedBaseURL = baseURL.replace(/\\/$/, \"\");\n\n /**\n * Make an XHR request\n */\n function makeRequest<T>(\n method: string,\n url: string,\n data?: any,\n customHeaders?: Record<string, string>,\n ): Promise<T> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.open(method, url, true);\n xhr.timeout = timeout;\n xhr.withCredentials = withCredentials;\n\n // Set headers\n const allHeaders = { ...headers, ...customHeaders };\n Object.entries(allHeaders).forEach(([key, value]) => {\n xhr.setRequestHeader(key, value);\n });\n\n // Handle upload progress\n if (onUploadProgress && xhr.upload) {\n xhr.upload.addEventListener(\"progress\", onUploadProgress);\n }\n\n // Handle response\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n const response = JSON.parse(xhr.responseText);\n resolve(response);\n } catch (error) {\n const parseError = new Error(`Failed to parse response: ${(error as Error).message}`);\n if (onError) onError(parseError);\n reject(parseError);\n }\n } else {\n let errorMessage = `HTTP ${xhr.status}: ${xhr.statusText}`;\n try {\n const errorResponse = JSON.parse(xhr.responseText);\n if (errorResponse.error?.message) {\n errorMessage = errorResponse.error.message;\n }\n } catch {\n // Use default error message\n }\n const error = new Error(errorMessage);\n if (onError) onError(error);\n reject(error);\n }\n };\n\n // Handle errors\n xhr.onerror = () => {\n const error = new Error(\"Network error\");\n if (onError) onError(error);\n reject(error);\n };\n\n xhr.ontimeout = () => {\n const error = new Error(`Request timeout after ${timeout}ms`);\n if (onError) onError(error);\n reject(error);\n };\n\n xhr.onabort = () => {\n const error = new Error(\"Request aborted\");\n if (onError) onError(error);\n reject(error);\n };\n\n // Send request\n if (data instanceof FormData) {\n xhr.send(data);\n } else if (data) {\n xhr.send(JSON.stringify(data));\n } else {\n xhr.send();\n }\n });\n }\n\n return {\n /**\n * Create a new file upload session\n */\n async createFile(request) {\n const response = await makeRequest<any>(\n \"POST\",\n `${normalizedBaseURL}/upload/create`,\n {\n fileName: request.fileName,\n fileSize: request.fileSize,\n fileType: request.fileType,\n preferredChunkSize: request.preferredChunkSize,\n },\n {\n \"Content-Type\": \"application/json\",\n },\n );\n\n // Convert server response to protocol format\n return {\n uploadToken: {\n token: response.uploadToken,\n fileId: \"\",\n chunkSize: response.negotiatedChunkSize,\n expiresAt: Date.now() + 24 * 60 * 60 * 1000,\n },\n negotiatedChunkSize: response.negotiatedChunkSize,\n };\n },\n\n /**\n * Verify file and chunk hashes\n */\n async verifyHash(request) {\n // Extract token string from UploadToken object or use string directly\n const token =\n typeof request.uploadToken === \"string\"\n ? request.uploadToken\n : (request.uploadToken as any).token;\n\n return makeRequest(\n \"POST\",\n `${normalizedBaseURL}/upload/verify`,\n {\n fileHash: request.fileHash,\n chunkHashes: request.chunkHashes,\n uploadToken: token,\n },\n {\n \"Content-Type\": \"application/json\",\n },\n );\n },\n\n /**\n * Upload a single chunk\n */\n async uploadChunk(request) {\n // Extract token string from UploadToken object or use string directly\n const token =\n typeof request.uploadToken === \"string\"\n ? request.uploadToken\n : (request.uploadToken as any).token;\n\n const formData = new FormData();\n formData.append(\"uploadToken\", token);\n formData.append(\"chunkIndex\", request.chunkIndex.toString());\n formData.append(\"chunkHash\", request.chunkHash);\n\n // Handle both Blob and Buffer types\n if (request.chunk instanceof Blob) {\n formData.append(\"chunk\", request.chunk);\n } else {\n // Convert Buffer to Blob for Node.js environments\n const blob = new Blob([request.chunk as unknown as BlobPart]);\n formData.append(\"chunk\", blob);\n }\n\n return makeRequest(\"POST\", `${normalizedBaseURL}/upload/chunk`, formData);\n },\n\n /**\n * Merge all chunks into final file\n */\n async mergeFile(request) {\n // Extract token string from UploadToken object or use string directly\n const token =\n typeof request.uploadToken === \"string\"\n ? request.uploadToken\n : (request.uploadToken as any).token;\n\n return makeRequest(\n \"POST\",\n `${normalizedBaseURL}/upload/merge`,\n {\n uploadToken: token,\n fileHash: request.fileHash,\n chunkHashes: request.chunkHashes,\n },\n {\n \"Content-Type\": \"application/json\",\n },\n );\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA4BA,IAAa,oBAAb,MAA6D;CAC3D,AAAQ;CACR,AAAiB;CAEjB,YAAY,SAAmC;AAC7C,OAAK,cAAc,QAAQ;AAC3B,OAAK,UAAU;GACb,YAAY;GACZ,GAAG;GACJ;AAGD,MAAI,KAAK,QAAQ,UAAU,KAAK,QAAQ,QACtC,OAAM,IAAI,MAAM,yCAAyC;AAE3D,MACE,KAAK,QAAQ,cAAc,KAAK,QAAQ,WACxC,KAAK,QAAQ,cAAc,KAAK,QAAQ,QAExC,OAAM,IAAI,MAAM,kDAAkD;AAEpE,MAAI,KAAK,QAAQ,cAAc,EAC7B,OAAM,IAAI,MAAM,8BAA8B;;;;;;;;;;;;CAclD,OAAO,cAA8B;AACnC,MAAI,eAAe,EACjB,OAAM,IAAI,MAAM,kCAAkC;EAGpD,MAAM,EAAE,YAAY,SAAS,YAAY,KAAK;AAG9C,MAAI,eAAe,aAAa,GAC9B,MAAK,cAAc,KAAK,IAAI,KAAK,cAAc,GAAG,QAAQ;WAGnD,eAAe,aAAa,IACnC,MAAK,cAAc,KAAK,IAAI,KAAK,cAAc,GAAG,QAAQ;AAI5D,SAAO,KAAK;;;;;;;CAQd,iBAAyB;AACvB,SAAO,KAAK;;;;;;CAOd,QAAc;AACZ,OAAK,cAAc,KAAK,QAAQ;;;;;;ACjFpC,IAAY,4DAAL;AACL;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,IAAa,uBAAb,MAAgE;CAC9D,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAiB;CACjB,AAAQ,yBAAiC;CACzC,AAAQ,yBAAiC;CAEzC,YAAY,SAAsC;AAChD,OAAK,cAAc,QAAQ;AAC3B,OAAK,UAAU;GACb,YAAY;GACZ,iBAAiB,QAAQ,mBAAmB,QAAQ,UAAU;GAC9D,GAAG;GACJ;AACD,OAAK,WAAW,KAAK,QAAQ;AAC7B,OAAK,QAAQ,gBAAgB;AAE7B,OAAK,UAAU;;CAGjB,AAAQ,WAAiB;EACvB,MAAM,EAAE,SAAS,SAAS,aAAa,YAAY,oBAAoB,KAAK;AAE5E,MAAI,UAAU,QACZ,OAAM,IAAI,MAAM,yCAAyC;AAE3D,MAAI,cAAc,WAAW,cAAc,QACzC,OAAM,IAAI,MAAM,kDAAkD;AAEpE,MAAI,cAAc,EAChB,OAAM,IAAI,MAAM,8BAA8B;AAEhD,MAAI,kBAAkB,WAAW,kBAAkB,QACjD,OAAM,IAAI,MAAM,sDAAsD;;;;;;;;CAU1E,OAAO,cAA8B;AACnC,MAAI,eAAe,EACjB,OAAM,IAAI,MAAM,kCAAkC;EAGpD,MAAM,EAAE,YAAY,SAAS,YAAY,KAAK;EAC9C,MAAM,QAAQ,eAAe;AAG7B,MAAI,QAAQ,IAAK;AACf,QAAK;AACL,QAAK,yBAAyB;AAC9B,QAAK,kBAAkB;aAGhB,QAAQ,KAAK;AACpB,QAAK;AACL,QAAK,yBAAyB;AAC9B,QAAK,kBAAkB;SAGpB;AACH,QAAK,yBAAyB;AAC9B,QAAK,yBAAyB;;AAIhC,OAAK,cAAc,KAAK,IAAI,SAAS,KAAK,IAAI,KAAK,aAAa,QAAQ,CAAC;AAEzE,SAAO,KAAK;;CAGd,AAAQ,mBAAyB;EAC/B,MAAM,EAAE,YAAY,KAAK;AAEzB,UAAQ,KAAK,OAAb;GACE,KAAK,gBAAgB;IAEnB,MAAM,UAAU,KAAK,cAAc;AAEnC,QAAI,WAAW,KAAK,UAAU;AAE5B,UAAK,cAAc,KAAK;AACxB,UAAK,QAAQ,gBAAgB;UAE7B,MAAK,cAAc,KAAK,IAAI,SAAS,QAAQ;AAE/C;GAEF,KAAK,gBAAgB;IAGnB,MAAM,YAAY,KAAK,IACrB,KAAK,QAAQ,SACb,KAAK,MAAM,KAAK,cAAc,GAAI,CACnC;AACD,SAAK,cAAc,KAAK,IAAI,KAAK,cAAc,WAAW,QAAQ;AAClE;GAEF,KAAK,gBAAgB;AAEnB,SAAK,QAAQ,gBAAgB;AAC7B;;;CAIN,AAAQ,mBAAyB;EAC/B,MAAM,EAAE,YAAY,KAAK;AAIzB,OAAK,WAAW,KAAK,IAAI,SAAS,KAAK,MAAM,KAAK,cAAc,EAAE,CAAC;AAGnE,OAAK,cAAc,KAAK;AAGxB,OAAK,QAAQ,gBAAgB;;;;;CAM/B,iBAAyB;AACvB,SAAO,KAAK;;;;;CAMd,cAAsB;AACpB,SAAO,KAAK;;;;;CAMd,WAA4B;AAC1B,SAAO,KAAK;;;;;CAMd,QAAc;AACZ,OAAK,cAAc,KAAK,QAAQ;AAChC,OAAK,WAAW,KAAK,QAAQ;AAC7B,OAAK,QAAQ,gBAAgB;AAC7B,OAAK,yBAAyB;AAC9B,OAAK,yBAAyB;;;;;CAMhC,WAAW;AACT,SAAO;GACL,aAAa,KAAK;GAClB,UAAU,KAAK;GACf,OAAO,KAAK;GACZ,wBAAwB,KAAK;GAC7B,wBAAwB,KAAK;GAC9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3FL,IAAa,aAAb,MAAwB;;CAEtB,AAAS;;CAGT,AAAS;;CAGT,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;;;;;CAOR,YAAY,SAA4B;AAEtC,OAAK,KAAK,QAAQ,gBAAgB,KAAK,gBAAgB;AAGvD,OAAK,OAAO,QAAQ;AAGpB,OAAK,SAAS;AAGd,OAAK,WAAW;GACd,eAAe;GACf,YAAY,QAAQ,KAAK;GACzB,YAAY;GACZ,OAAO;GACP,eAAe;GACf,gBAAgB;GAChB,aAAa;GACd;AAGD,OAAK,SAAS,EAAE;AAGhB,MAAI,QAAQ,kBAGV,MAAK,cAAc;GACjB,OAAO,QAAQ;GACf,QAAQ;GACR,WAAW,QAAQ,aAAa,OAAO;GACvC,WAAW,KAAK,KAAK,GAAG,OAAU,KAAK;GACxC;MAED,MAAK,cAAc;AAGrB,OAAK,WAAW;AAGhB,OAAK,WAAW,gBAAgB;AAGhC,OAAK,iBAAiB,QAAQ;AAG9B,OAAK,UAAU;GACb,MAAM,QAAQ;GACd,gBAAgB,QAAQ;GACxB,WAAW,QAAQ,aAAa,OAAO;GACvC,aAAa,QAAQ,eAAe;GACpC,YAAY,QAAQ,cAAc;GAClC,YAAY,QAAQ,cAAc;GAClC,WAAW,QAAQ,aAAa;GAChC,cAAc,QAAQ,gBAAgB;GACtC,mBAAmB,QAAQ,qBAAqB;GAChD,sBAAsB,QAAQ,wBAAwB,EAAE;GACxD,mBAAmB,QAAQ,qBAAqB;GAChD,iBAAiB,QAAQ,mBAAmB,IAAI,OAAO;GACxD;AAGD,OAAK,wBAAwB,IAAI,sBAAsB,EACrD,OAAO,KAAK,QAAQ,aACrB,CAAC;AAGF,OAAK,UAAU,IAAI,eAAe;AAGlC,OAAK,YAAY;AACjB,OAAK,UAAU;AAGf,OAAK,oBAAoB;AAGzB,OAAK,qBAAqB;;;;;;;;CAS5B,AAAQ,iBAAyB;AAG/B,SAAO,QAFW,KAAK,KAAK,CAAC,SAAS,GAAG,CAEhB,GADV,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,UAAU,GAAG,EAAE;;;;;;;CAS3D,YAA0B;AACxB,SAAO,KAAK;;;;;;;;CASd,cAA8B;AAC5B,SAAO,EAAE,GAAG,KAAK,UAAU;;;;;;;;CAS7B,cAA6B;AAC3B,MAAI,KAAK,YAAY,KACnB,QAAO;AAET,SAAO,KAAK,UAAU,KAAK;;;;;;;;;;;;;;;CAgB7B,GACE,OACA,SACM;AACN,OAAK,SAAS,GAAG,OAAO,QAAQ;;;;;;;;CASlC,IACE,OACA,SACM;AACN,OAAK,SAAS,IAAI,OAAO,QAAQ;;;;;;;;;;;;;;;;;CAkBnC,AAAQ,aAAa,WAAgC;EACnD,MAAM,SAAsB,EAAE;EAC9B,MAAM,cAAc,KAAK,KAAK,KAAK,KAAK,OAAO,UAAU;AAEzD,OAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAAK;GACpC,MAAM,QAAQ,IAAI;GAClB,MAAM,MAAM,KAAK,IAAI,QAAQ,WAAW,KAAK,KAAK,KAAK;AAEvD,UAAO,KAAK;IACV,OAAO;IACP,MAAM;IACN,MAAM,MAAM;IACZ;IACA;IACD,CAAC;;AAGJ,SAAO;;;;;;;;;;;;;;;;;;;CAoBT,MAAM,QAAuB;AAE3B,MAAI,KAAK,WAAW,OAClB,OAAM,IAAI,MAAM,0CAA0C,KAAK,SAAS;AAG1E,MAAI;AAEF,SAAM,KAAK,mBAAmB;AAG9B,QAAK,SAAS;AACd,QAAK,YAAY,KAAK,KAAK;AAG3B,QAAK,SAAS,KAAK,SAAS;IAAE,QAAQ,KAAK;IAAI,MAAM,KAAK;IAAM,CAAC;GAGjE,MAAM,WAAW,KAAK,QAAQ,qBAAqB,KAAK,QAAQ;GAEhE,IAAI;AAEJ,OAAI,UAAU;AAGZ,0BAAsB,KAAK,YAAa;AAExC,YAAQ,KACN,4BAA4B,KAAK,GAAG,IAC/B,KAAK,QAAQ,qBAAsB,OAAO,0BAChD;UACI;IAEL,MAAM,iBAAiB,MAAM,KAAK,eAAe,WAAW;KAC1D,UAAU,KAAK,KAAK;KACpB,UAAU,KAAK,KAAK;KACpB,UAAU,KAAK,KAAK;KACpB,oBAAoB,KAAK,QAAQ;KAClC,CAAC;AAEF,SAAK,cAAc,eAAe;AAClC,0BAAsB,eAAe;;AAIvC,QAAK,SAAS,KAAK,aAAa,oBAAoB;AACpD,QAAK,SAAS,cAAc,KAAK,OAAO;AAGxC,OAAI,YAAY,KAAK,QAAQ,sBAAsB;IACjD,MAAM,iBAAiB,KAAK,QAAQ;IACpC,IAAI,gBAAgB;AAEpB,SAAK,MAAM,cAAc,eACvB,KAAI,aAAa,KAAK,OAAO,QAAQ;KACnC,MAAM,QAAQ,KAAK,OAAO;AAE1B,KAAC,MAAc,WAAW;AAC1B,sBAAiB,MAAM;;AAK3B,SAAK,SAAS,iBAAiB,eAAe;AAC9C,SAAK,SAAS,gBAAgB;AAC9B,SAAK,SAAS,aAAc,gBAAgB,KAAK,KAAK,OAAQ;AAE9D,YAAQ,KACN,oBAAoB,KAAK,SAAS,WAAW,QAAQ,EAAE,CAAC,KAClD,eAAe,OAAO,GAAG,KAAK,OAAO,OAAO,UACnD;;GAIH,MAAM,WAAW,KAAK,QAAQ;GAC9B,MAAM,UAAU,MAAM;GACtB,MAAM,UAAU,KAAK,OAAO;GAC5B,MAAM,aAAa;AAEnB,OAAI,OAAO,aAAa,SAEtB,MAAK,oBAAoB;YAChB,aAAa,WAEtB,MAAK,oBAAoB,IAAI,qBAAqB;IAChD,aAAa;IACb;IACA;IACA;IACA,iBAAiB,KAAK,QAAQ;IAC/B,CAAC;OAGF,MAAK,oBAAoB,IAAI,kBAAkB;IAC7C,aAAa;IACb;IACA;IACA;IACD,CAAC;AAKJ,SAAM,QAAQ,IAAI,CAAC,KAAK,aAAa,EAAE,KAAK,wBAAwB,CAAC,CAAC;AAGtE,OAAI,KAAK,WAAW,UAAU;AAC5B,YAAQ,KAAK,oBAAoB,KAAK,SAAS,WAAW,QAAQ,EAAE,CAAC,GAAG;AACxE;;AAGF,OAAI,KAAK,WAAW,eAAe,KAAK,mBAEtC;AAIF,OAAI,KAAK,WAAW,WAAW;AAC7B,YAAQ,KAAK,mCAAmC;AAChD;;AAKF,OAAI,KAAK,WAAW,eAAe,KAAK,SACtC,KAAI;IACF,MAAM,cAAc,KAAK,OAAO,KAAK,UAAU,MAAM,KAAK;IAE1D,MAAM,iBAAiB,MAAM,KAAK,eAAe,WAAW;KAC1D,UAAU,KAAK;KACf;KACA,aAAa,KAAK,YAAa;KAChC,CAAC;AAKF,QAAI,eAAe,cAAc,eAAe,SAAS;AAEvD,UAAK,SAAS;AACd,UAAK,UAAU,KAAK,KAAK;AAGzB,UAAK,SAAS,gBAAgB,KAAK,KAAK;AACxC,UAAK,SAAS,iBAAiB,KAAK,OAAO;AAC3C,UAAK,SAAS,aAAa;AAG3B,UAAK,SAAS,KAAK,WAAW;MAC5B,QAAQ,KAAK;MACb,SAAS,eAAe;MACzB,CAAC;AAEF;;YAEK,OAAO;AAEd,YAAQ,KAAK,6BAA6B,MAAM;;YAEzC,KAAK,WAAW,eAAe,CAAC,KAAK,SAC9C,SAAQ,KAAK,oDAAoD;AAInE,OAAI,KAAK,WAAW,eAAe,CAAC,KAAK,oBAAoB;IAE3D,MAAM,gBAAgB,MAAM,KAAK,eAAe,UAAU;KACxD,aAAa,KAAK,YAAa;KAC/B,UAAU,KAAK,YAAY;KAC3B,aAAa,KAAK,OAAO,KAAK,UAAU,MAAM,KAAK;KACpD,CAAC;AAEF,QAAI,cAAc,SAAS;AAEzB,UAAK,SAAS;AACd,UAAK,UAAU,KAAK,KAAK;AAGzB,UAAK,SAAS,KAAK,WAAW;MAC5B,QAAQ,KAAK;MACb,SAAS,cAAc;MACxB,CAAC;UAEF,OAAM,IAAI,MAAM,0CAA0C;;WAKvD,OAAO;AAEd,OAAI,KAAK,WAAW,YAAY,KAAK,WAAW,aAAa;AAC3D,SAAK,SAAS;AACd,SAAK,UAAU,KAAK,KAAK;AACzB,SAAK,SAAS,KAAK,SAAS;KAC1B,QAAQ,KAAK;KACN;KACR,CAAC;;AAEJ,SAAM;;;;;;;;;;;;;;;;;;;CAoBV,MAAc,cAA6B;EAEzC,MAAM,iBAAiB,KAAK,OAAO,QAAQ,UAAU,CAAE,MAAc,SAAS;AAE9E,MAAI,eAAe,WAAW,GAAG;AAE/B,WAAQ,KAAK,qDAAqD;AAClE;;EAKF,MAAM,qBAAqB,KAAK,IAAI,GAAG,eAAe,OAAO;EAC7D,MAAM,iBAAiB,eAAe,MAAM,GAAG,mBAAmB;EAClE,MAAM,kBAAkB,eAAe,MAAM,mBAAmB;EAGhE,MAAM,mBAAmB,eAAe,KAAK,UAAU;AACrD,UAAO,KAAK,sBAAsB,IAAI,YAAY;AAEhD,QAAI,KAAK,WAAW,eAAe,KAAK,mBACtC;IAIF,MAAM,iBAAiB,KAAK,KAAK;AAGjC,UAAM,KAAK,qBAAqB,MAAM;IAGtC,MAAM,kBAAkB,KAAK,KAAK,GAAG;AACrC,QAAI,KAAK,kBACP,MAAK,kBAAkB,OAAO,gBAAgB;KAEhD;IACF;EAGF,MAAM,oBAAoB,gBAAgB,KAAK,UAAU;AACvD,UAAO,KAAK,sBAAsB,IAAI,YAAY;AAEhD,QAAI,KAAK,WAAW,eAAe,KAAK,mBACtC;IAIF,MAAM,iBAAiB,KAAK,KAAK;AAGjC,UAAM,KAAK,qBAAqB,MAAM;IAGtC,MAAM,kBAAkB,KAAK,KAAK,GAAG;AACrC,QAAI,KAAK,kBACP,MAAK,kBAAkB,OAAO,gBAAgB;KAEhD;IACF;AAIF,QAAM,QAAQ,IAAI,CAAC,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;CAuBhE,MAAc,qBAAqB,OAAiC;EAClE,IAAI,UAAU;EACd,IAAI,YAA0B;AAE9B,SAAO,WAAW,KAAK,QAAQ,WAC7B,KAAI;AAEF,OAAI,KAAK,WAAW,eAAe,KAAK,mBACtC;GAIF,MAAM,OAAO,UAAU,KAAK,MAAM,MAAM,OAAO,MAAM,IAAI;GAGzD,MAAM,YAAY,MAAM,mBAAmB,KAAK;AAChD,SAAM,OAAO;AAGb,SAAM,KAAK,eAAe,YAAY;IACpC,aAAa,KAAK,YAAa;IAC/B,YAAY,MAAM;IAClB;IACA,OAAO;IACR,CAAC;AAGF,GAAC,MAAc,WAAW;AAG1B,SAAM,KAAK,eAAe,MAAM;AAGhC,QAAK,SAAS,KAAK,gBAAgB;IACjC,QAAQ,KAAK;IACb,YAAY,MAAM;IACnB,CAAC;AAGF;WACO,OAAO;AACd,eAAY;AACZ;AAGA,QAAK,SAAS,KAAK,cAAc;IAC/B,QAAQ,KAAK;IACb,YAAY,MAAM;IAClB,OAAO;IACR,CAAC;AAGF,OAAI,UAAU,KAAK,QAAQ,WAEzB,OAAM,IAAI,MACR,0BAA0B,MAAM,MAAM,SAAS,KAAK,QAAQ,WAAW,YAAY,UAAU,UAC9F;GAIH,MAAM,QAAQ,KAAK,QAAQ,aAAa,KAAK,IAAI,GAAG,UAAU,EAAE;AAChE,SAAM,KAAK,MAAM,MAAM;;AAK3B,MAAI,UACF,OAAM;;;;;;;;;;;;;;;;;;;;;;;CAyBV,MAAc,eAAe,OAAiC;AAE5D,MAAK,MAAc,iBAAiB;AAClC,WAAQ,KAAK,SAAS,MAAM,MAAM,4CAA4C;AAC9E;;AAIF,EAAC,MAAc,kBAAkB;AAGjC,OAAK,SAAS,iBAAiB,MAAM;AACrC,OAAK,SAAS;AAGd,MAAI,KAAK,SAAS,gBAAgB,KAAK,KAAK,MAAM;AAChD,WAAQ,KACN,+BAA+B,KAAK,SAAS,cAAc,KAAK,KAAK,KAAK,KAAK,wBAChF;AACD,QAAK,SAAS,gBAAgB,KAAK,KAAK;;AAG1C,MAAI,KAAK,SAAS,iBAAiB,KAAK,SAAS,aAAa;AAC5D,WAAQ,KACN,kCAAkC,KAAK,SAAS,eAAe,KAAK,KAAK,SAAS,YAAY,2BAC/F;AACD,QAAK,SAAS,iBAAiB,KAAK,SAAS;;AAI/C,OAAK,SAAS,aAAa,KAAK,IAAI,KAAM,KAAK,SAAS,gBAAgB,KAAK,KAAK,OAAQ,IAAI;EAG9F,MAAM,cAAc,KAAK,KAAK,GAAG,KAAK;AACtC,OAAK,SAAS,QAAQ,eAAe,KAAK,SAAS,eAAe,YAAY;EAE9E,MAAM,iBAAiB,KAAK,KAAK,OAAO,KAAK,SAAS;AACtD,OAAK,SAAS,gBAAgB,sBAAsB,gBAAgB,KAAK,SAAS,MAAM;AAGxF,OAAK,SAAS,KAAK,YAAY;GAC7B,QAAQ,KAAK;GACb,UAAU,KAAK,SAAS;GACxB,OAAO,KAAK,SAAS;GACtB,CAAC;AAIF,QAAM,KAAK,iBAAiB;;;;;;;;;;;CAY9B,AAAQ,MAAM,IAA2B;AACvC,SAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;CAsB1D,MAAc,yBAAwC;AACpD,MAAI;AAIF,QAAK,WAAW,MAAM,kBAAkB,KAAK,OAAO,aAAa;AAE/D,SAAK,SAAS,KAAK,gBAAgB;KACjC,QAAQ,KAAK;KACb;KACD,CAAC;KACF;AAIF,QAAK,SAAS,KAAK,gBAAgB;IACjC,QAAQ,KAAK;IACb,MAAM,KAAK;IACZ,CAAC;AAIF,OAAI,CAAC,KAAK,YAER;GAGF,MAAM,iBAAiB,MAAM,KAAK,eAAe,WAAW;IAC1D,UAAU,KAAK;IACf,aAAa,KAAK,YAAY;IAG/B,CAAC;AAGF,OAAI,eAAe,cAAc,eAAe,SAAS;AAEvD,QAAI,KAAK,WAAW,YAAY,KAAK,WAAW,YAC9C;AAKF,SAAK,qBAAqB;AAC1B,SAAK,SAAS;AACd,SAAK,UAAU,KAAK,KAAK;AAGzB,SAAK,SAAS,gBAAgB,KAAK,KAAK;AACxC,SAAK,SAAS,iBAAiB,KAAK,OAAO;AAC3C,SAAK,SAAS,aAAa;AAG3B,SAAK,SAAS,KAAK,WAAW;KAC5B,QAAQ,KAAK;KACb,SAAS,eAAe;KACzB,CAAC;AAEF;;AAMF,OAAI,eAAe,kBAAkB,eAAe,eAAe,SAAS,EAG1E,MAAK,mBAAmB,eAAe,eAAe;WAEjD,OAAO;AAGd,WAAQ,KAAK,yCAAyC,MAAM;;;;;;;;;;;;;;;;;;CAoBhE,AAAQ,mBAAmB,sBAAsC;AAC/D,OAAK,MAAM,cAAc,sBAAsB;GAC7C,MAAM,QAAQ,KAAK,OAAO;AAC1B,OAAI,CAAC,MAAO;AAGZ,GAAC,MAAc,WAAW;AAG1B,GAAC,MAAc,kBAAkB;AAGjC,QAAK,SAAS,iBAAiB,MAAM;AACrC,QAAK,SAAS;AACd,QAAK,SAAS,aAAc,KAAK,SAAS,gBAAgB,KAAK,KAAK,OAAQ;AAG5E,QAAK,SAAS,KAAK,gBAAgB;IACjC,QAAQ,KAAK;IACb,YAAY,MAAM;IACnB,CAAC;;AAIJ,OAAK,SAAS,KAAK,YAAY;GAC7B,QAAQ,KAAK;GACb,UAAU,KAAK,SAAS;GACxB,OAAO,KAAK,SAAS;GACtB,CAAC;;;;;;;;;;;;;;;CAgBJ,MAAc,oBAAmC;AAC/C,MAAI;AAEF,SAAM,KAAK,QAAQ,MAAM;GAGzB,MAAM,SAAqD;IACzD,QAAQ,KAAK;IACb,UAAU;KACR,MAAM,KAAK,KAAK;KAChB,MAAM,KAAK,KAAK;KAChB,MAAM,KAAK,KAAK;KAChB,cAAc,KAAK,KAAK;KACzB;IACD,gBAAgB,EAAE;IAClB,aAAa,KAAK,aAAa,SAAS;IACxC,WAAW,KAAK,KAAK;IACrB,WAAW,KAAK,KAAK;IACtB;AAED,SAAM,KAAK,QAAQ,WAAW,OAAO;WAC9B,OAAO;;;;;;;;;;;;;;;CAoBlB,MAAc,kBAAiC;AAC7C,MAAI;AAEF,OAAI,CAAC,KAAK,QAAQ,aAAa,CAC7B;GAIF,MAAM,uBAAuB,KAAK,OAC/B,QAAQ,GAAG,UAAU,QAAQ,KAAK,SAAS,eAAe,CAC1D,KAAK,UAAU,MAAM,MAAM;AAE9B,OAAI;AAEF,UAAM,KAAK,QAAQ,aAAa,KAAK,IAAI;KACvC,gBAAgB;KAChB,aAAa,KAAK,aAAa,SAAS;KACxC,WAAW,KAAK,KAAK;KACtB,CAAC;YACK,OAAO;AAEd,QAAK,MAAc,SAAS,oBAAoB;KAC9C,MAAM,SAAqD;MACzD,QAAQ,KAAK;MACb,UAAU;OACR,MAAM,KAAK,KAAK;OAChB,MAAM,KAAK,KAAK;OAChB,MAAM,KAAK,KAAK;OAChB,cAAc,KAAK,KAAK;OACzB;MACD,gBAAgB;MAChB,aAAa,KAAK,aAAa,SAAS;MACxC,WAAW,KAAK,KAAK;MACrB,WAAW,KAAK,KAAK;MACtB;AACD,WAAM,KAAK,QAAQ,WAAW,OAAO;UAErC,OAAM;;WAGH,OAAO;;;;;;;;;;;;;;;;;;;;;;;;CA6BlB,QAAc;AAEZ,MAAI,KAAK,WAAW,aAAa;AAC/B,WAAQ,KAAK,0CAA0C,KAAK,SAAS;AACrE;;AAIF,OAAK,SAAS;AAGd,OAAK,SAAS,KAAK,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BlD,MAAM,SAAwB;AAE5B,MAAI,KAAK,WAAW,SAClB,OAAM,IAAI,MAAM,2CAA2C,KAAK,SAAS;AAG3E,MAAI;AAEF,OAAI,CAAC,KAAK,YAGR,OAAM,IAAI,MAAM,4CAA4C;AAI9D,QAAK,SAAS;AAGd,QAAK,SAAS,KAAK,UAAU,EAAE,QAAQ,KAAK,IAAI,CAAC;AAKjD,SAAM,KAAK,aAAa;AAGxB,OAAI,KAAK,WAAW,eAAe,KAAK,mBAEtC;AAIF,OAAI,KAAK,WAAW,aAAa;AAE/B,QAAI,KAAK,SACP,KAAI;KACF,MAAM,cAAc,KAAK,OAAO,KAAK,UAAU,MAAM,KAAK;KAE1D,MAAM,iBAAiB,MAAM,KAAK,eAAe,WAAW;MAC1D,UAAU,KAAK;MACf;MACA,aAAa,KAAK,YAAY;MAC/B,CAAC;AAGF,SAAI,eAAe,cAAc,eAAe,SAAS;AACvD,WAAK,SAAS;AACd,WAAK,UAAU,KAAK,KAAK;AAGzB,WAAK,SAAS,gBAAgB,KAAK,KAAK;AACxC,WAAK,SAAS,iBAAiB,KAAK,OAAO;AAC3C,WAAK,SAAS,aAAa;AAG3B,WAAK,SAAS,KAAK,WAAW;OAC5B,QAAQ,KAAK;OACb,SAAS,eAAe;OACzB,CAAC;AAEF;;aAEK,OAAO;AAEd,aAAQ,KAAK,6BAA6B,MAAM;;IAKpD,MAAM,gBAAgB,MAAM,KAAK,eAAe,UAAU;KACxD,aAAa,KAAK,YAAY;KAC9B,UAAU,KAAK,YAAY;KAC3B,aAAa,KAAK,OAAO,KAAK,UAAU,MAAM,KAAK;KACpD,CAAC;AAEF,QAAI,cAAc,SAAS;AAEzB,UAAK,SAAS;AACd,UAAK,UAAU,KAAK,KAAK;AAGzB,UAAK,SAAS,KAAK,WAAW;MAC5B,QAAQ,KAAK;MACb,SAAS,cAAc;MACxB,CAAC;UAEF,OAAM,IAAI,MAAM,0CAA0C;;WAGvD,OAAO;AACd,QAAK,SAAS;AACd,QAAK,UAAU,KAAK,KAAK;AACzB,QAAK,SAAS,KAAK,SAAS;IAC1B,QAAQ,KAAK;IACN;IACR,CAAC;AACF,SAAM;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BV,SAAe;AAEb,MAAI,KAAK,WAAW,eAAe,KAAK,WAAW,UAAU;AAC3D,WAAQ,KAAK,2CAA2C,KAAK,SAAS;AACtE;;AAIF,OAAK,qBAAqB;AAG1B,OAAK,SAAS;AACd,OAAK,UAAU,KAAK,KAAK;AAGzB,OAAK,SAAS,KAAK,UAAU,EAAE,QAAQ,KAAK,IAAI,CAAC;AAIjD,OAAK,gBAAgB,CAAC,OAAO,UAAU;AACrC,WAAQ,KAAK,qCAAqC,MAAM;IACxD;;;;;;;;;;;;;;CAeJ,MAAc,iBAAgC;AAC5C,MAAI;AACF,OAAI,KAAK,QAAQ,aAAa,CAC5B,OAAM,KAAK,QAAQ,aAAa,KAAK,GAAG;WAEnC,OAAO;AAEd,WAAQ,KAAK,8BAA8B,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC/oCvD,IAAa,gBAAb,MAA2B;;CAEzB,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;CAGR,AAAQ;;;;;;;;;;;;CAaR,YAAY,SAA+B;AAEzC,OAAK,wBAAQ,IAAI,KAAK;AAGtB,OAAK,UAAU;GACb,gBAAgB,QAAQ;GACxB,oBAAoB,QAAQ,sBAAsB;GAClD,kBAAkB,QAAQ,oBAAoB,OAAO;GACrD,oBAAoB,QAAQ,sBAAsB;GAClD,sBAAsB,QAAQ,wBAAwB;GACvD;AAGD,OAAK,UAAU,IAAI,eAAe;AAGlC,OAAK,UAAU,EAAE;AAGjB,OAAK,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BrB,IAAI,QAAsB;AAExB,OAAK,QAAQ,KAAK,OAAO;AAGzB,MAAI,OAAO,QACT,KAAI;AACF,UAAO,QAAQ,KAAK;WACb,OAAO;AACd,WAAQ,MAAM,WAAW,OAAO,KAAK,oBAAoB,MAAM;;;;;;;;;;;CAarE,AAAQ,eAAuC,UAAa,GAAG,MAAuB;AACpF,OAAK,MAAM,UAAU,KAAK,SAAS;GACjC,MAAM,OAAO,OAAO;AACpB,OAAI,QAAQ,OAAO,SAAS,WAC1B,KAAI;AACF,IAAC,KAAsC,MAAM,QAAQ,KAAK;YACnD,OAAO;AACd,YAAQ,MAAM,WAAW,OAAO,KAAK,UAAU,OAAO,SAAS,CAAC,YAAY,MAAM;;;;;;;;;;;;;;;;;;;;;;;CAyB1F,MAAM,OAAsB;AAE1B,MAAI,KAAK,YACP;AAGF,MAAI;AAEF,SAAM,KAAK,QAAQ,MAAM;AAGzB,OAAI,KAAK,QAAQ,qBACf,OAAM,KAAK,qBAAqB;AAIlC,QAAK,cAAc;WACZ,OAAO;AAGd,QAAK,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCvB,WAAW,MAAY,SAAkD;EAEvE,MAAM,OAAO,IAAI,WAAW;GAC1B;GACA,gBAAgB,KAAK,QAAQ;GAC7B,WAAW,SAAS,aAAa,KAAK,QAAQ;GAC9C,aAAa,SAAS,eAAe,KAAK,QAAQ;GAClD,YAAY,SAAS,cAAc;GACnC,YAAY,SAAS,cAAc;GACnC,WAAW,SAAS,aAAa;GAClC,CAAC;AAGF,OAAK,MAAM,IAAI,KAAK,IAAI,KAAK;AAG7B,OAAK,eAAe,iBAAiB,KAAK;AAG1C,OAAK,qBAAqB,KAAK;AAE/B,SAAO;;;;;;;;;CAUT,AAAQ,qBAAqB,MAAwB;AAEnD,OAAK,GAAG,eAAe;AACrB,QAAK,eAAe,eAAe,KAAK;IACxC;AAGF,OAAK,GAAG,kBAAkB;GACxB,MAAM,eAAe,KAAK,aAAa;AACvC,QAAK,eAAe,kBAAkB,MAAM,aAAa;IACzD;AAGF,OAAK,GAAG,YAAY,EAAE,cAAc;AAClC,QAAK,eAAe,iBAAiB,MAAM,QAAQ;IACnD;AAGF,OAAK,GAAG,UAAU,EAAE,YAAY;AAC9B,QAAK,eAAe,eAAe,MAAM,MAAM;IAC/C;AAGF,OAAK,GAAG,eAAe;AACrB,QAAK,eAAe,eAAe,KAAK;IACxC;AAGF,OAAK,GAAG,gBAAgB;AACtB,QAAK,eAAe,gBAAgB,KAAK;IACzC;AAGF,OAAK,GAAG,gBAAgB;AACtB,QAAK,eAAe,gBAAgB,KAAK;IACzC;;;;;;;;;;;;;;;;;;;CAoBJ,QAAQ,QAAwC;AAC9C,SAAO,KAAK,MAAM,IAAI,OAAO;;;;;;;;;;;;;;;;;;;;;;;CAwB/B,cAA4B;AAC1B,SAAO,MAAM,KAAK,KAAK,MAAM,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCxC,MAAM,WAAW,QAA+B;EAC9C,MAAM,OAAO,KAAK,MAAM,IAAI,OAAO;AAEnC,MAAI,MAAM;GAER,MAAM,SAAS,KAAK,WAAW;AAC/B,OAAI,WAAW,eAAe,WAAW,SACvC,MAAK,QAAQ;AAIf,QAAK,MAAM,OAAO,OAAO;AAGzB,OAAI;AACF,QAAI,KAAK,QAAQ,aAAa,CAC5B,OAAM,KAAK,QAAQ,aAAa,OAAO;YAElC,OAAO;AAEd,YAAQ,KAAK,4CAA4C,OAAO,IAAI,MAAM;;;;;;;;;;;;;;;;;;;CAoBhF,MAAc,sBAAqC;AACjD,MAAI;AAEF,OAAI,CAAC,KAAK,QAAQ,aAAa,CAC7B;AAIF,SAAM,KAAK,QAAQ,eAAe;WAiB3B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BlB,MAAM,yBAcJ;AACA,MAAI;AAEF,OAAI,CAAC,KAAK,QAAQ,aAAa,CAC7B,QAAO,EAAE;AAOX,WAHgB,MAAM,KAAK,QAAQ,eAAe,EAGnC,KAAK,YAAY;IAC9B,QAAQ,OAAO;IACf,UAAU;KACR,MAAM,OAAO,SAAS;KACtB,MAAM,OAAO,SAAS;KACtB,MAAM,OAAO,SAAS;KACtB,cAAc,OAAO,SAAS;KAC/B;IACD,gBAAgB,OAAO;IACvB,aAAa,OAAO;IACpB,WAAW,OAAO;IAClB,WAAW,OAAO;IACnB,EAAE;WACI,OAAO;AACd,WAAQ,KAAK,wCAAwC,MAAM;AAC3D,UAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0Cb,MAAM,WACJ,QACA,MACA,SACqB;AAErB,MAAI,CAAC,KAAK,QAAQ,aAAa,CAC7B,OAAM,IAAI,MAAM,gDAAgD;EAIlE,MAAM,SAAS,MAAM,KAAK,QAAQ,UAAU,OAAO;AACnD,MAAI,CAAC,OACH,OAAM,IAAI,MAAM,qCAAqC,SAAS;AAIhE,MAAI,KAAK,SAAS,OAAO,SAAS,KAChC,OAAM,IAAI,MAAM,iCAAiC,OAAO,SAAS,KAAK,UAAU,KAAK,KAAK,GAAG;AAG/F,MAAI,KAAK,SAAS,OAAO,SAAS,KAChC,OAAM,IAAI,MAAM,gCAAgC,OAAO,SAAS,KAAK,QAAQ,KAAK,OAAO;AAG3F,MAAI,KAAK,SAAS,OAAO,SAAS,KAChC,OAAM,IAAI,MAAM,iCAAiC,OAAO,SAAS,KAAK,UAAU,KAAK,KAAK,GAAG;EAO/F,MAAM,OAAO,IAAI,WAAW;GAC1B;GACA,gBAAgB,KAAK,QAAQ;GAC7B,WAAW,SAAS,aAAa,KAAK,QAAQ;GAC9C,aAAa,SAAS,eAAe,KAAK,QAAQ;GAClD,YAAY,SAAS,cAAc;GACnC,YAAY,SAAS,cAAc;GACnC,WAAW,SAAS,aAAa;GACjC,cAAc;GACd,mBAAmB,OAAO;GAC1B,sBAAsB,OAAO;GAC9B,CAAC;AAGF,OAAK,MAAM,IAAI,KAAK,IAAI,KAAK;AAG7B,OAAK,eAAe,iBAAiB,KAAK;AAG1C,OAAK,qBAAqB,KAAK;AAG/B,MAAI;AACF,SAAM,KAAK,QAAQ,aAAa,OAAO;WAChC,OAAO;AACd,WAAQ,KAAK,gDAAgD,OAAO,IAAI,MAAM;;AAGhF,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BT,MAAM,oBAAoB,QAA+B;AACvD,MAAI;AACF,OAAI,KAAK,QAAQ,aAAa,CAC5B,OAAM,KAAK,QAAQ,aAAa,OAAO;WAElC,OAAO;AACd,WAAQ,KAAK,mCAAmC,OAAO,IAAI,MAAM;;;;;;;;;;;;;;;;;;;;;;CAuBrE,MAAM,0BAA2C;AAC/C,MAAI;AACF,OAAI,CAAC,KAAK,QAAQ,aAAa,CAC7B,QAAO;GAIT,MAAM,SADU,MAAM,KAAK,QAAQ,eAAe,EAC5B;AAEtB,SAAM,KAAK,QAAQ,UAAU;AAE7B,UAAO;WACA,OAAO;AACd,WAAQ,KAAK,yCAAyC,MAAM;AAC5D,UAAO;;;;;;;;;;;;;CAcX,eAAuB;AACrB,SAAO,KAAK,MAAM;;;;;;;;;;;;;;CAepB,gBAAyB;AACvB,SAAO,KAAK;;;;;;;;;;;;;;;;CAiBd,MAAM,sBAAuC;EAC3C,MAAM,QAAQ,KAAK,aAAa;EAChC,IAAI,eAAe;AAEnB,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,SAAS,KAAK,WAAW;AAC/B,OAAI,WAAW,aAAa,WAAW,WAAW,WAAW,aAAa;AACxE,UAAM,KAAK,WAAW,KAAK,GAAG;AAC9B;;;AAIJ,SAAO;;;;;;;;;;;;;;;CAgBT,WAAmB;EACjB,MAAM,QAAQ,KAAK,aAAa;EAChC,IAAI,cAAc;AAElB,OAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,WAAW,KAAK,aAAa;AACpC,QAAK,OAAO;AACZ;;AAIJ,SAAO;;;;;;;;;;;;;;;CAgBT,MAAM,YAA6B;EACjC,MAAM,QAAQ,KAAK,aAAa;EAChC,IAAI,eAAe;AAEnB,OAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,WAAW,KAAK,SACvB,KAAI;AACF,SAAM,KAAK,QAAQ;AACnB;WACO,OAAO;AACd,WAAQ,KAAK,yBAAyB,KAAK,GAAG,IAAI,MAAM;;AAK9D,SAAO;;;;;;;;;;;;;;;CAgBT,YAAoB;EAClB,MAAM,QAAQ,KAAK,aAAa;EAChC,IAAI,iBAAiB;AAErB,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,SAAS,KAAK,WAAW;AAC/B,OAAI,WAAW,eAAe,WAAW,UAAU;AACjD,SAAK,QAAQ;AACb;;;AAIJ,SAAO;;;;;;;;;;;;;;;CAgBT,gBAQE;EACA,MAAM,QAAQ,KAAK,aAAa;EAEhC,MAAM,QAAQ;GACZ,OAAO,MAAM;GACb,MAAM;GACN,WAAW;GACX,QAAQ;GACR,SAAS;GACT,OAAO;GACP,WAAW;GACZ;AAED,OAAK,MAAM,QAAQ,MAEjB,SADe,KAAK,WAAW,EAC/B;GACE,KAAK;AACH,UAAM;AACN;GACF,KAAK;AACH,UAAM;AACN;GACF,KAAK;AACH,UAAM;AACN;GACF,KAAK;AACH,UAAM;AACN;GACF,KAAK;AACH,UAAM;AACN;GACF,KAAK;AACH,UAAM;AACN;;AAIN,SAAO;;;;;;;;;;;;;;CAeT,QAAc;AAEZ,OAAK,WAAW;AAGhB,OAAK,QAAQ,OAAO;AAGpB,OAAK,MAAM,OAAO;AAGlB,OAAK,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACv9BvB,IAAa,eAAb,MAA4C;CAC1C,OAAO;CAEP,AAAQ;CAWR,YACE,SAUA;AACA,OAAK,UAAU;GACb,aAAa,SAAS,eAAe;GACrC,UAAU,SAAS,YAAY;GAC/B,YAAY,SAAS,cAAc;GACnC,UAAU,SAAS,YAAY;GAC/B,UAAU,SAAS,YAAY;GAC/B,WAAW,SAAS,aAAa;GACjC,WAAW,SAAS,aAAa;GACjC,QAAQ,SAAS,UAAU;GAC5B;;CAGH,AAAQ,IAAI,SAAiB,GAAG,MAAuB;EACrD,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAC1C,UAAQ,IAAI,GAAG,KAAK,QAAQ,OAAO,IAAI,UAAU,IAAI,SAAS,GAAG,KAAK;;CAGxE,UAAgB;AACd,OAAK,IAAI,mBAAmB;;CAG9B,cAAc,MAAwB;AACpC,OAAK,IAAI,iBAAiB,KAAK,MAAM;GACnC,UAAU,KAAK,KAAK;GACpB,UAAU,KAAK,KAAK;GACpB,UAAU,KAAK,KAAK;GACrB,CAAC;;CAGJ,YAAY,MAAwB;AAClC,MAAI,KAAK,QAAQ,SACf,MAAK,IAAI,iBAAiB,KAAK,MAAM,EACnC,UAAU,KAAK,KAAK,MACrB,CAAC;;CAIN,eAAe,MAAkB,UAAgC;AAC/D,MAAI,KAAK,QAAQ,YACf,MAAK,IAAI,kBAAkB,KAAK,MAAM;GACpC,YAAY,GAAG,SAAS,WAAW,QAAQ,EAAE,CAAC;GAC9C,eAAe,SAAS;GACxB,YAAY,SAAS;GACrB,OAAO,IAAI,SAAS,QAAQ,OAAO,MAAM,QAAQ,EAAE,CAAC;GACpD,eAAe,GAAG,SAAS,cAAc,QAAQ,EAAE,CAAC;GACrD,CAAC;;CAIN,cAAc,MAAkB,SAAuB;AACrD,MAAI,KAAK,QAAQ,WACf,MAAK,IAAI,mBAAmB,KAAK,MAAM;GACrC,UAAU,KAAK,KAAK;GACpB;GACD,CAAC;;CAIN,YAAY,MAAkB,OAAoB;AAChD,MAAI,KAAK,QAAQ,SACf,MAAK,IAAI,eAAe,KAAK,MAAM;GACjC,UAAU,KAAK,KAAK;GACpB,OAAO,MAAM;GACb,OAAO,MAAM;GACd,CAAC;;CAIN,YAAY,MAAwB;AAClC,MAAI,KAAK,QAAQ,SACf,MAAK,IAAI,gBAAgB,KAAK,MAAM,EAClC,UAAU,KAAK,KAAK,MACrB,CAAC;;CAIN,aAAa,MAAwB;AACnC,MAAI,KAAK,QAAQ,UACf,MAAK,IAAI,iBAAiB,KAAK,MAAM,EACnC,UAAU,KAAK,KAAK,MACrB,CAAC;;CAIN,aAAa,MAAwB;AACnC,MAAI,KAAK,QAAQ,UACf,MAAK,IAAI,mBAAmB,KAAK,MAAM,EACrC,UAAU,KAAK,KAAK,MACrB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BR,IAAa,mBAAb,MAAgD;CAC9C,OAAO;CAEP,AAAQ,QAAQ;EACd,YAAY;EACZ,cAAc;EACd,YAAY;EACZ,gBAAgB;EAChB,oBAAoB;EACpB,iBAAiB;EACjB,4BAAY,IAAI,KAAqB;EACtC;CAED,UAAgB;CAIhB,cAAc,OAAyB;AACrC,OAAK,MAAM;;CAGb,YAAY,MAAwB;AAElC,OAAK,MAAM,WAAW,IAAI,KAAK,IAAI,KAAK,KAAK,CAAC;;CAGhD,cAAc,MAAkB,UAAwB;AACtD,OAAK,MAAM;AACX,OAAK,MAAM,sBAAsB,KAAK,KAAK;EAG3C,MAAM,YAAY,KAAK,MAAM,WAAW,IAAI,KAAK,GAAG;AACpD,MAAI,WAAW;GACb,MAAM,aAAa,KAAK,KAAK,GAAG;AAChC,QAAK,MAAM,mBAAmB;AAC9B,QAAK,MAAM,WAAW,OAAO,KAAK,GAAG;;;CAIzC,YAAY,MAAkB,QAAqB;AACjD,OAAK,MAAM;AAGX,OAAK,MAAM,WAAW,OAAO,KAAK,GAAG;;CAGvC,aAAa,MAAwB;AACnC,OAAK,MAAM;AAGX,OAAK,MAAM,WAAW,OAAO,KAAK,GAAG;;;;;;;;;;;;;CAcvC,WAUE;EACA,MAAM,iBACJ,KAAK,MAAM,eAAe,KAAK,MAAM,aAAa,KAAK,MAAM;EAC/D,MAAM,eACJ,KAAK,MAAM,kBAAkB,IACxB,KAAK,MAAM,qBAAqB,KAAK,MAAM,kBAAmB,MAC/D;EACN,MAAM,oBACJ,KAAK,MAAM,eAAe,IAAI,KAAK,MAAM,kBAAkB,KAAK,MAAM,eAAe;EACvF,MAAM,cAAc,iBAAiB,IAAK,KAAK,MAAM,eAAe,iBAAkB,MAAM;EAC5F,MAAM,YAAY,iBAAiB,IAAK,KAAK,MAAM,aAAa,iBAAkB,MAAM;AAExF,SAAO;GACL,YAAY,KAAK,MAAM;GACvB,cAAc,KAAK,MAAM;GACzB,YAAY,KAAK,MAAM;GACvB,gBAAgB,KAAK,MAAM;GAC3B,oBAAoB,KAAK,MAAM;GAC/B;GACA;GACA;GACA;GACD;;;;;;;;;;CAWH,QAAc;AACZ,OAAK,QAAQ;GACX,YAAY;GACZ,cAAc;GACd,YAAY;GACZ,gBAAgB;GAChB,oBAAoB;GACpB,iBAAiB;GACjB,4BAAY,IAAI,KAAK;GACtB;;;;;;;;;;;;;;;;;;;;;CAsBH,aAAqB;EACnB,MAAM,QAAQ,KAAK,UAAU;EAC7B,MAAM,eAAe,UAA0B;AAE7C,UAAO,IADI,QAAQ,OAAO,MACb,QAAQ,EAAE,CAAC;;EAE1B,MAAM,eAAe,mBAAmC;AAEtD,UAAO,IADM,iBAAiB,OAAO,MACtB,QAAQ,EAAE,CAAC;;EAE5B,MAAM,cAAc,OAAuB;AAEzC,UAAO,IADS,KAAK,KACH,QAAQ,EAAE,CAAC;;AAG/B,SAAO;iBACM,MAAM,WAAW;aACrB,MAAM,aAAa,IAAI,MAAM,YAAY,QAAQ,EAAE,CAAC;YACrD,MAAM,WAAW,IAAI,MAAM,UAAU,QAAQ,EAAE,CAAC;eAC7C,MAAM,eAAe;oBAChB,YAAY,MAAM,mBAAmB,CAAC;mBACvC,YAAY,MAAM,aAAa,CAAC;kBACjC,WAAW,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;AC1SrD,SAAgB,mBAAmB,SAA8C;CAC/E,MAAM,EACJ,SACA,UAAU,EAAE,EACZ,UAAU,KACV,OAAO,cAAc,WAAW,OAChC,YACE;CAGJ,MAAM,oBAAoB,QAAQ,QAAQ,OAAO,GAAG;;;;CAKpD,eAAe,iBAAiB,KAAa,MAAuC;EAClF,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,QAAQ;AAE/D,MAAI;GACF,MAAM,WAAW,MAAM,YAAY,KAAK;IACtC,GAAG;IACH,QAAQ,WAAW;IACnB,SAAS;KACP,GAAG;KACH,GAAG,MAAM;KACV;IACF,CAAC;AAEF,gBAAa,UAAU;AAEvB,OAAI,CAAC,SAAS,IAAI;IAChB,MAAM,wBAAQ,IAAI,MAAM,QAAQ,SAAS,OAAO,IAAI,SAAS,aAAa;AAC1E,QAAI,QAAS,SAAQ,MAAM;AAC3B,UAAM;;AAGR,UAAO;WACA,OAAO;AACd,gBAAa,UAAU;AACvB,OAAI,WAAW,iBAAiB,MAAO,SAAQ,MAAM;AACrD,SAAM;;;AAIV,QAAO;EAIL,MAAM,WAAW,SAAS;GAcxB,MAAM,OAAO,OAbI,MAAM,iBAAiB,GAAG,kBAAkB,iBAAiB;IAC5E,QAAQ;IACR,SAAS,EACP,gBAAgB,oBACjB;IACD,MAAM,KAAK,UAAU;KACnB,UAAU,QAAQ;KAClB,UAAU,QAAQ;KAClB,UAAU,QAAQ;KAClB,oBAAoB,QAAQ;KAC7B,CAAC;IACH,CAAC,EAE0B,MAAM;AAKlC,UAAO;IACL,aAAa;KACX,OAAO,KAAK;KACZ,QAAQ;KACR,WAAW,KAAK;KAChB,WAAW,KAAK,KAAK,GAAG,OAAU,KAAK;KACxC;IACD,qBAAqB,KAAK;IAC3B;;EAMH,MAAM,WAAW,SAAS;GAExB,MAAM,QACJ,OAAO,QAAQ,gBAAgB,WAC3B,QAAQ,cACP,QAAQ,YAAoB;AAcnC,WAZiB,MAAM,iBAAiB,GAAG,kBAAkB,iBAAiB;IAC5E,QAAQ;IACR,SAAS,EACP,gBAAgB,oBACjB;IACD,MAAM,KAAK,UAAU;KACnB,UAAU,QAAQ;KAClB,aAAa,QAAQ;KACrB,aAAa;KACd,CAAC;IACH,CAAC,EAEc,MAAM;;EAMxB,MAAM,YAAY,SAAS;GAEzB,MAAM,QACJ,OAAO,QAAQ,gBAAgB,WAC3B,QAAQ,cACP,QAAQ,YAAoB;GAEnC,MAAM,WAAW,IAAI,UAAU;AAC/B,YAAS,OAAO,eAAe,MAAM;AACrC,YAAS,OAAO,cAAc,QAAQ,WAAW,UAAU,CAAC;AAC5D,YAAS,OAAO,aAAa,QAAQ,UAAU;AAG/C,OAAI,QAAQ,iBAAiB,KAC3B,UAAS,OAAO,SAAS,QAAQ,MAAM;QAClC;IAGL,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,MAA6B,CAAC;AAC7D,aAAS,OAAO,SAAS,KAAK;;AAQhC,WALiB,MAAM,iBAAiB,GAAG,kBAAkB,gBAAgB;IAC3E,QAAQ;IACR,MAAM;IACP,CAAC,EAEc,MAAM;;EAMxB,MAAM,UAAU,SAAS;GAEvB,MAAM,QACJ,OAAO,QAAQ,gBAAgB,WAC3B,QAAQ,cACP,QAAQ,YAAoB;AAcnC,WAZiB,MAAM,iBAAiB,GAAG,kBAAkB,gBAAgB;IAC3E,QAAQ;IACR,SAAS,EACP,gBAAgB,oBACjB;IACD,MAAM,KAAK,UAAU;KACnB,aAAa;KACb,UAAU,QAAQ;KAClB,aAAa,QAAQ;KACtB,CAAC;IACH,CAAC,EAEc,MAAM;;EAEzB;;;;;;;;;;;;;;;;;;;;;;;;;;AC5JH,SAAgB,iBAAiB,SAA4C;CAC3E,MAAM,EACJ,SACA,UAAU,EAAE,EACZ,UAAU,KACV,kBAAkB,OAClB,kBACA,YACE;CAGJ,MAAM,oBAAoB,QAAQ,QAAQ,OAAO,GAAG;;;;CAKpD,SAAS,YACP,QACA,KACA,MACA,eACY;AACZ,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,MAAM,IAAI,gBAAgB;AAEhC,OAAI,KAAK,QAAQ,KAAK,KAAK;AAC3B,OAAI,UAAU;AACd,OAAI,kBAAkB;GAGtB,MAAM,aAAa;IAAE,GAAG;IAAS,GAAG;IAAe;AACnD,UAAO,QAAQ,WAAW,CAAC,SAAS,CAAC,KAAK,WAAW;AACnD,QAAI,iBAAiB,KAAK,MAAM;KAChC;AAGF,OAAI,oBAAoB,IAAI,OAC1B,KAAI,OAAO,iBAAiB,YAAY,iBAAiB;AAI3D,OAAI,eAAe;AACjB,QAAI,IAAI,UAAU,OAAO,IAAI,SAAS,IACpC,KAAI;AAEF,aADiB,KAAK,MAAM,IAAI,aAAa,CAC5B;aACV,OAAO;KACd,MAAM,6BAAa,IAAI,MAAM,6BAA8B,MAAgB,UAAU;AACrF,SAAI,QAAS,SAAQ,WAAW;AAChC,YAAO,WAAW;;SAEf;KACL,IAAI,eAAe,QAAQ,IAAI,OAAO,IAAI,IAAI;AAC9C,SAAI;MACF,MAAM,gBAAgB,KAAK,MAAM,IAAI,aAAa;AAClD,UAAI,cAAc,OAAO,QACvB,gBAAe,cAAc,MAAM;aAE/B;KAGR,MAAM,QAAQ,IAAI,MAAM,aAAa;AACrC,SAAI,QAAS,SAAQ,MAAM;AAC3B,YAAO,MAAM;;;AAKjB,OAAI,gBAAgB;IAClB,MAAM,wBAAQ,IAAI,MAAM,gBAAgB;AACxC,QAAI,QAAS,SAAQ,MAAM;AAC3B,WAAO,MAAM;;AAGf,OAAI,kBAAkB;IACpB,MAAM,wBAAQ,IAAI,MAAM,yBAAyB,QAAQ,IAAI;AAC7D,QAAI,QAAS,SAAQ,MAAM;AAC3B,WAAO,MAAM;;AAGf,OAAI,gBAAgB;IAClB,MAAM,wBAAQ,IAAI,MAAM,kBAAkB;AAC1C,QAAI,QAAS,SAAQ,MAAM;AAC3B,WAAO,MAAM;;AAIf,OAAI,gBAAgB,SAClB,KAAI,KAAK,KAAK;YACL,KACT,KAAI,KAAK,KAAK,UAAU,KAAK,CAAC;OAE9B,KAAI,MAAM;IAEZ;;AAGJ,QAAO;EAIL,MAAM,WAAW,SAAS;GACxB,MAAM,WAAW,MAAM,YACrB,QACA,GAAG,kBAAkB,iBACrB;IACE,UAAU,QAAQ;IAClB,UAAU,QAAQ;IAClB,UAAU,QAAQ;IAClB,oBAAoB,QAAQ;IAC7B,EACD,EACE,gBAAgB,oBACjB,CACF;AAGD,UAAO;IACL,aAAa;KACX,OAAO,SAAS;KAChB,QAAQ;KACR,WAAW,SAAS;KACpB,WAAW,KAAK,KAAK,GAAG,OAAU,KAAK;KACxC;IACD,qBAAqB,SAAS;IAC/B;;EAMH,MAAM,WAAW,SAAS;GAExB,MAAM,QACJ,OAAO,QAAQ,gBAAgB,WAC3B,QAAQ,cACP,QAAQ,YAAoB;AAEnC,UAAO,YACL,QACA,GAAG,kBAAkB,iBACrB;IACE,UAAU,QAAQ;IAClB,aAAa,QAAQ;IACrB,aAAa;IACd,EACD,EACE,gBAAgB,oBACjB,CACF;;EAMH,MAAM,YAAY,SAAS;GAEzB,MAAM,QACJ,OAAO,QAAQ,gBAAgB,WAC3B,QAAQ,cACP,QAAQ,YAAoB;GAEnC,MAAM,WAAW,IAAI,UAAU;AAC/B,YAAS,OAAO,eAAe,MAAM;AACrC,YAAS,OAAO,cAAc,QAAQ,WAAW,UAAU,CAAC;AAC5D,YAAS,OAAO,aAAa,QAAQ,UAAU;AAG/C,OAAI,QAAQ,iBAAiB,KAC3B,UAAS,OAAO,SAAS,QAAQ,MAAM;QAClC;IAEL,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,MAA6B,CAAC;AAC7D,aAAS,OAAO,SAAS,KAAK;;AAGhC,UAAO,YAAY,QAAQ,GAAG,kBAAkB,gBAAgB,SAAS;;EAM3E,MAAM,UAAU,SAAS;GAEvB,MAAM,QACJ,OAAO,QAAQ,gBAAgB,WAC3B,QAAQ,cACP,QAAQ,YAAoB;AAEnC,UAAO,YACL,QACA,GAAG,kBAAkB,gBACrB;IACE,aAAa;IACb,UAAU,QAAQ;IAClB,aAAa,QAAQ;IACtB,EACD,EACE,gBAAgB,oBACjB,CACF;;EAEJ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chunkflowjs/core",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.6",
|
|
4
4
|
"description": "Core upload engine for ChunkFlow Upload SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chunked-upload",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@chunkflowjs/
|
|
28
|
-
"@chunkflowjs/
|
|
27
|
+
"@chunkflowjs/shared": "0.0.1-alpha.6",
|
|
28
|
+
"@chunkflowjs/protocol": "0.0.1-alpha.6"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"fast-check": "^4.5.3",
|