@uploadista/client-core 0.0.3
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/.turbo/turbo-build.log +5 -0
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/auth/auth-http-client.d.ts +50 -0
- package/dist/auth/auth-http-client.d.ts.map +1 -0
- package/dist/auth/auth-http-client.js +110 -0
- package/dist/auth/direct-auth.d.ts +38 -0
- package/dist/auth/direct-auth.d.ts.map +1 -0
- package/dist/auth/direct-auth.js +95 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/no-auth.d.ts +26 -0
- package/dist/auth/no-auth.d.ts.map +1 -0
- package/dist/auth/no-auth.js +33 -0
- package/dist/auth/saas-auth.d.ts +80 -0
- package/dist/auth/saas-auth.d.ts.map +1 -0
- package/dist/auth/saas-auth.js +167 -0
- package/dist/auth/types.d.ts +101 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +8 -0
- package/dist/chunk-buffer.d.ts +209 -0
- package/dist/chunk-buffer.d.ts.map +1 -0
- package/dist/chunk-buffer.js +236 -0
- package/dist/client/create-uploadista-client.d.ts +369 -0
- package/dist/client/create-uploadista-client.d.ts.map +1 -0
- package/dist/client/create-uploadista-client.js +518 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +3 -0
- package/dist/client/uploadista-api.d.ts +284 -0
- package/dist/client/uploadista-api.d.ts.map +1 -0
- package/dist/client/uploadista-api.js +444 -0
- package/dist/client/uploadista-websocket-manager.d.ts +110 -0
- package/dist/client/uploadista-websocket-manager.d.ts.map +1 -0
- package/dist/client/uploadista-websocket-manager.js +207 -0
- package/dist/error.d.ts +106 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +69 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/logger.d.ts +70 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +59 -0
- package/dist/mock-data-store.d.ts +30 -0
- package/dist/mock-data-store.d.ts.map +1 -0
- package/dist/mock-data-store.js +88 -0
- package/dist/network-monitor.d.ts +262 -0
- package/dist/network-monitor.d.ts.map +1 -0
- package/dist/network-monitor.js +291 -0
- package/dist/services/abort-controller-service.d.ts +19 -0
- package/dist/services/abort-controller-service.d.ts.map +1 -0
- package/dist/services/abort-controller-service.js +4 -0
- package/dist/services/checksum-service.d.ts +4 -0
- package/dist/services/checksum-service.d.ts.map +1 -0
- package/dist/services/checksum-service.js +1 -0
- package/dist/services/file-reader-service.d.ts +38 -0
- package/dist/services/file-reader-service.d.ts.map +1 -0
- package/dist/services/file-reader-service.js +4 -0
- package/dist/services/fingerprint-service.d.ts +4 -0
- package/dist/services/fingerprint-service.d.ts.map +1 -0
- package/dist/services/fingerprint-service.js +1 -0
- package/dist/services/http-client.d.ts +182 -0
- package/dist/services/http-client.d.ts.map +1 -0
- package/dist/services/http-client.js +1 -0
- package/dist/services/id-generation-service.d.ts +10 -0
- package/dist/services/id-generation-service.d.ts.map +1 -0
- package/dist/services/id-generation-service.js +1 -0
- package/dist/services/index.d.ts +11 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +10 -0
- package/dist/services/platform-service.d.ts +48 -0
- package/dist/services/platform-service.d.ts.map +1 -0
- package/dist/services/platform-service.js +10 -0
- package/dist/services/service-container.d.ts +25 -0
- package/dist/services/service-container.d.ts.map +1 -0
- package/dist/services/service-container.js +1 -0
- package/dist/services/storage-service.d.ts +26 -0
- package/dist/services/storage-service.d.ts.map +1 -0
- package/dist/services/storage-service.js +1 -0
- package/dist/services/websocket-service.d.ts +36 -0
- package/dist/services/websocket-service.d.ts.map +1 -0
- package/dist/services/websocket-service.js +4 -0
- package/dist/smart-chunker.d.ts +72 -0
- package/dist/smart-chunker.d.ts.map +1 -0
- package/dist/smart-chunker.js +317 -0
- package/dist/storage/client-storage.d.ts +148 -0
- package/dist/storage/client-storage.d.ts.map +1 -0
- package/dist/storage/client-storage.js +62 -0
- package/dist/storage/in-memory-storage-service.d.ts +7 -0
- package/dist/storage/in-memory-storage-service.d.ts.map +1 -0
- package/dist/storage/in-memory-storage-service.js +24 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/types/buffered-chunk.d.ts +6 -0
- package/dist/types/buffered-chunk.d.ts.map +1 -0
- package/dist/types/buffered-chunk.js +1 -0
- package/dist/types/chunk-metrics.d.ts +12 -0
- package/dist/types/chunk-metrics.d.ts.map +1 -0
- package/dist/types/chunk-metrics.js +1 -0
- package/dist/types/flow-result.d.ts +11 -0
- package/dist/types/flow-result.d.ts.map +1 -0
- package/dist/types/flow-result.js +1 -0
- package/dist/types/flow-upload-config.d.ts +54 -0
- package/dist/types/flow-upload-config.d.ts.map +1 -0
- package/dist/types/flow-upload-config.js +1 -0
- package/dist/types/flow-upload-item.d.ts +16 -0
- package/dist/types/flow-upload-item.d.ts.map +1 -0
- package/dist/types/flow-upload-item.js +1 -0
- package/dist/types/flow-upload-options.d.ts +41 -0
- package/dist/types/flow-upload-options.d.ts.map +1 -0
- package/dist/types/flow-upload-options.js +1 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/multi-flow-upload-options.d.ts +33 -0
- package/dist/types/multi-flow-upload-options.d.ts.map +1 -0
- package/dist/types/multi-flow-upload-options.js +1 -0
- package/dist/types/multi-flow-upload-state.d.ts +9 -0
- package/dist/types/multi-flow-upload-state.d.ts.map +1 -0
- package/dist/types/multi-flow-upload-state.js +1 -0
- package/dist/types/performance-insights.d.ts +11 -0
- package/dist/types/performance-insights.d.ts.map +1 -0
- package/dist/types/performance-insights.js +1 -0
- package/dist/types/previous-upload.d.ts +20 -0
- package/dist/types/previous-upload.d.ts.map +1 -0
- package/dist/types/previous-upload.js +9 -0
- package/dist/types/upload-options.d.ts +40 -0
- package/dist/types/upload-options.d.ts.map +1 -0
- package/dist/types/upload-options.js +1 -0
- package/dist/types/upload-response.d.ts +6 -0
- package/dist/types/upload-response.d.ts.map +1 -0
- package/dist/types/upload-response.js +1 -0
- package/dist/types/upload-result.d.ts +57 -0
- package/dist/types/upload-result.d.ts.map +1 -0
- package/dist/types/upload-result.js +1 -0
- package/dist/types/upload-session-metrics.d.ts +16 -0
- package/dist/types/upload-session-metrics.d.ts.map +1 -0
- package/dist/types/upload-session-metrics.js +1 -0
- package/dist/upload/chunk-upload.d.ts +40 -0
- package/dist/upload/chunk-upload.d.ts.map +1 -0
- package/dist/upload/chunk-upload.js +82 -0
- package/dist/upload/flow-upload.d.ts +48 -0
- package/dist/upload/flow-upload.d.ts.map +1 -0
- package/dist/upload/flow-upload.js +240 -0
- package/dist/upload/index.d.ts +3 -0
- package/dist/upload/index.d.ts.map +1 -0
- package/dist/upload/index.js +2 -0
- package/dist/upload/parallel-upload.d.ts +65 -0
- package/dist/upload/parallel-upload.d.ts.map +1 -0
- package/dist/upload/parallel-upload.js +231 -0
- package/dist/upload/single-upload.d.ts +118 -0
- package/dist/upload/single-upload.d.ts.map +1 -0
- package/dist/upload/single-upload.js +332 -0
- package/dist/upload/upload-manager.d.ts +30 -0
- package/dist/upload/upload-manager.d.ts.map +1 -0
- package/dist/upload/upload-manager.js +57 -0
- package/dist/upload/upload-metrics.d.ts +37 -0
- package/dist/upload/upload-metrics.d.ts.map +1 -0
- package/dist/upload/upload-metrics.js +236 -0
- package/dist/upload/upload-storage.d.ts +32 -0
- package/dist/upload/upload-storage.d.ts.map +1 -0
- package/dist/upload/upload-storage.js +46 -0
- package/dist/upload/upload-strategy.d.ts +66 -0
- package/dist/upload/upload-strategy.d.ts.map +1 -0
- package/dist/upload/upload-strategy.js +171 -0
- package/dist/upload/upload-utils.d.ts +26 -0
- package/dist/upload/upload-utils.d.ts.map +1 -0
- package/dist/upload/upload-utils.js +80 -0
- package/package.json +29 -0
- package/src/__tests__/smart-chunking.test.ts +399 -0
- package/src/auth/__tests__/auth-http-client.test.ts +327 -0
- package/src/auth/__tests__/direct-auth.test.ts +135 -0
- package/src/auth/__tests__/no-auth.test.ts +40 -0
- package/src/auth/__tests__/saas-auth.test.ts +337 -0
- package/src/auth/auth-http-client.ts +150 -0
- package/src/auth/direct-auth.ts +121 -0
- package/src/auth/index.ts +5 -0
- package/src/auth/no-auth.ts +39 -0
- package/src/auth/saas-auth.ts +218 -0
- package/src/auth/types.ts +105 -0
- package/src/chunk-buffer.ts +287 -0
- package/src/client/create-uploadista-client.ts +901 -0
- package/src/client/index.ts +3 -0
- package/src/client/uploadista-api.ts +857 -0
- package/src/client/uploadista-websocket-manager.ts +275 -0
- package/src/error.ts +149 -0
- package/src/index.ts +13 -0
- package/src/logger.ts +104 -0
- package/src/mock-data-store.ts +97 -0
- package/src/network-monitor.ts +445 -0
- package/src/services/abort-controller-service.ts +21 -0
- package/src/services/checksum-service.ts +3 -0
- package/src/services/file-reader-service.ts +44 -0
- package/src/services/fingerprint-service.ts +6 -0
- package/src/services/http-client.ts +229 -0
- package/src/services/id-generation-service.ts +9 -0
- package/src/services/index.ts +10 -0
- package/src/services/platform-service.ts +65 -0
- package/src/services/service-container.ts +24 -0
- package/src/services/storage-service.ts +29 -0
- package/src/services/websocket-service.ts +33 -0
- package/src/smart-chunker.ts +451 -0
- package/src/storage/client-storage.ts +186 -0
- package/src/storage/in-memory-storage-service.ts +33 -0
- package/src/storage/index.ts +2 -0
- package/src/types/buffered-chunk.ts +5 -0
- package/src/types/chunk-metrics.ts +11 -0
- package/src/types/flow-result.ts +14 -0
- package/src/types/flow-upload-config.ts +56 -0
- package/src/types/flow-upload-item.ts +16 -0
- package/src/types/flow-upload-options.ts +56 -0
- package/src/types/index.ts +13 -0
- package/src/types/multi-flow-upload-options.ts +39 -0
- package/src/types/multi-flow-upload-state.ts +9 -0
- package/src/types/performance-insights.ts +7 -0
- package/src/types/previous-upload.ts +22 -0
- package/src/types/upload-options.ts +56 -0
- package/src/types/upload-response.ts +6 -0
- package/src/types/upload-result.ts +60 -0
- package/src/types/upload-session-metrics.ts +15 -0
- package/src/upload/chunk-upload.ts +151 -0
- package/src/upload/flow-upload.ts +367 -0
- package/src/upload/index.ts +2 -0
- package/src/upload/parallel-upload.ts +387 -0
- package/src/upload/single-upload.ts +554 -0
- package/src/upload/upload-manager.ts +106 -0
- package/src/upload/upload-metrics.ts +340 -0
- package/src/upload/upload-storage.ts +87 -0
- package/src/upload/upload-strategy.ts +296 -0
- package/src/upload/upload-utils.ts +114 -0
- package/tsconfig.json +23 -0
- package/tsconfig.tsbuildinfo +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uploadista/client-core",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.3",
|
|
5
|
+
"description": "Platform-agnostic core upload client logic for Uploadista",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Uploadista",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./types": "./src/types/index.ts",
|
|
11
|
+
"./services": "./src/services/index.ts",
|
|
12
|
+
"./upload": "./src/upload/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"js-base64": "3.7.8",
|
|
16
|
+
"zod": "4.1.12",
|
|
17
|
+
"@uploadista/core": "0.0.3"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"vitest": "3.2.4",
|
|
21
|
+
"@uploadista/typescript-config": "0.0.3"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -b",
|
|
25
|
+
"format": "biome format --write ./src",
|
|
26
|
+
"lint": "biome lint --write ./src",
|
|
27
|
+
"check": "biome check --write ./src"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { SmartChunker } from "../../../core/src/smart-chunker";
|
|
3
|
+
import { ChunkBuffer } from "../chunk-buffer";
|
|
4
|
+
import { NetworkMonitor } from "../network-monitor";
|
|
5
|
+
import { UploadMetrics } from "../upload/upload-metrics";
|
|
6
|
+
|
|
7
|
+
describe("NetworkMonitor", () => {
|
|
8
|
+
let monitor: NetworkMonitor;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
monitor = new NetworkMonitor();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should initialize with empty metrics", () => {
|
|
15
|
+
const metrics = monitor.getCurrentMetrics();
|
|
16
|
+
expect(metrics.totalRequests).toBe(0);
|
|
17
|
+
expect(metrics.averageSpeed).toBe(0);
|
|
18
|
+
expect(metrics.successRate).toBe(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should record successful uploads", () => {
|
|
22
|
+
monitor.recordUpload(1024, 1000, true); // 1KB in 1 second
|
|
23
|
+
|
|
24
|
+
const metrics = monitor.getCurrentMetrics();
|
|
25
|
+
expect(metrics.totalRequests).toBe(1);
|
|
26
|
+
expect(metrics.successRate).toBe(1);
|
|
27
|
+
expect(metrics.averageSpeed).toBe(1024); // 1KB/s
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should detect network conditions", () => {
|
|
31
|
+
// Record slow uploads
|
|
32
|
+
for (let i = 0; i < 5; i++) {
|
|
33
|
+
monitor.recordUpload(1024, 2000, true); // 1KB in 2 seconds = 512 B/s
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const condition = monitor.getNetworkCondition();
|
|
37
|
+
expect(condition.type).toBe("slow");
|
|
38
|
+
expect(condition.confidence).toBeGreaterThan(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should detect fast network conditions", () => {
|
|
42
|
+
// Record fast uploads
|
|
43
|
+
for (let i = 0; i < 5; i++) {
|
|
44
|
+
monitor.recordUpload(10 * 1024 * 1024, 1000, true); // 10MB in 1 second = 10MB/s
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const condition = monitor.getNetworkCondition();
|
|
48
|
+
expect(condition.type).toBe("fast");
|
|
49
|
+
expect(condition.confidence).toBeGreaterThan(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should detect unstable network", () => {
|
|
53
|
+
// Record variable upload speeds
|
|
54
|
+
monitor.recordUpload(1024, 1000, true); // 1KB/s
|
|
55
|
+
monitor.recordUpload(10 * 1024 * 1024, 1000, true); // 10MB/s
|
|
56
|
+
monitor.recordUpload(1024, 1000, true); // 1KB/s
|
|
57
|
+
monitor.recordUpload(10 * 1024 * 1024, 1000, true); // 10MB/s
|
|
58
|
+
monitor.recordUpload(1024, 1000, true); // 1KB/s
|
|
59
|
+
|
|
60
|
+
const condition = monitor.getNetworkCondition();
|
|
61
|
+
expect(condition.type).toBe("unstable");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("SmartChunker", () => {
|
|
66
|
+
let monitor: NetworkMonitor;
|
|
67
|
+
let chunker: SmartChunker;
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
monitor = new NetworkMonitor();
|
|
71
|
+
chunker = new SmartChunker(monitor);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should start with initial chunk size", () => {
|
|
75
|
+
const decision = chunker.getNextChunkSize();
|
|
76
|
+
expect(decision.size).toBe(512 * 1024); // Default initial size
|
|
77
|
+
expect(decision.strategy).toBe("initial");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should adapt chunk size based on performance", () => {
|
|
81
|
+
// Simulate successful uploads
|
|
82
|
+
chunker.recordChunkResult(512 * 1024, 1000, true);
|
|
83
|
+
chunker.recordChunkResult(512 * 1024, 1000, true);
|
|
84
|
+
chunker.recordChunkResult(512 * 1024, 1000, true);
|
|
85
|
+
|
|
86
|
+
const decision = chunker.getNextChunkSize();
|
|
87
|
+
expect(decision.size).toBeGreaterThan(0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should reduce chunk size on failures", () => {
|
|
91
|
+
// First, establish some baseline by recording network data
|
|
92
|
+
monitor.recordUpload(512 * 1024, 1000, true);
|
|
93
|
+
monitor.recordUpload(512 * 1024, 1000, true);
|
|
94
|
+
monitor.recordUpload(512 * 1024, 1000, true);
|
|
95
|
+
monitor.recordUpload(512 * 1024, 1000, true);
|
|
96
|
+
monitor.recordUpload(512 * 1024, 1000, true);
|
|
97
|
+
|
|
98
|
+
const initialDecision = chunker.getNextChunkSize();
|
|
99
|
+
const initialSize = initialDecision.size;
|
|
100
|
+
|
|
101
|
+
// Record failures
|
|
102
|
+
chunker.recordChunkResult(initialSize, 5000, false);
|
|
103
|
+
chunker.recordChunkResult(initialSize, 5000, false);
|
|
104
|
+
|
|
105
|
+
const newDecision = chunker.getNextChunkSize();
|
|
106
|
+
expect(newDecision.size).toBeLessThan(initialSize);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should respect minimum and maximum bounds", () => {
|
|
110
|
+
const config = {
|
|
111
|
+
minChunkSize: 64 * 1024,
|
|
112
|
+
maxChunkSize: 2 * 1024 * 1024,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const boundedChunker = new SmartChunker(monitor, config);
|
|
116
|
+
|
|
117
|
+
// Force very small size
|
|
118
|
+
for (let i = 0; i < 10; i++) {
|
|
119
|
+
boundedChunker.recordChunkResult(1024, 10000, false);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const smallDecision = boundedChunker.getNextChunkSize();
|
|
123
|
+
expect(smallDecision.size).toBeGreaterThanOrEqual(config.minChunkSize);
|
|
124
|
+
|
|
125
|
+
// Reset and force very large size
|
|
126
|
+
boundedChunker.reset();
|
|
127
|
+
for (let i = 0; i < 10; i++) {
|
|
128
|
+
boundedChunker.recordChunkResult(10 * 1024 * 1024, 100, true);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const largeDecision = boundedChunker.getNextChunkSize();
|
|
132
|
+
expect(largeDecision.size).toBeLessThanOrEqual(config.maxChunkSize);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should limit chunk size by remaining bytes", () => {
|
|
136
|
+
const remainingBytes = 100 * 1024; // 100KB remaining
|
|
137
|
+
const decision = chunker.getNextChunkSize(remainingBytes);
|
|
138
|
+
expect(decision.size).toBeLessThanOrEqual(remainingBytes);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should respect datastore constraints (S3-like)", () => {
|
|
142
|
+
const s3Constraints = {
|
|
143
|
+
minChunkSize: 5 * 1024 * 1024, // 5MB - S3 minimum
|
|
144
|
+
maxChunkSize: 5 * 1024 * 1024 * 1024, // 5GB - S3 maximum
|
|
145
|
+
optimalChunkSize: 16 * 1024 * 1024, // 16MB optimal
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const s3AwareChunker = new SmartChunker(monitor, {
|
|
149
|
+
datastoreConstraints: s3Constraints,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const decision = s3AwareChunker.getNextChunkSize();
|
|
153
|
+
|
|
154
|
+
// Should use optimal chunk size as initial, which is >= 5MB minimum
|
|
155
|
+
expect(decision.size).toBeGreaterThanOrEqual(s3Constraints.minChunkSize);
|
|
156
|
+
expect(decision.size).toBeLessThanOrEqual(s3Constraints.maxChunkSize);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("should use S3-optimized strategies when S3 constraints are present", () => {
|
|
160
|
+
const s3Constraints = {
|
|
161
|
+
minChunkSize: 5 * 1024 * 1024, // 5MB - S3 minimum (this triggers S3 mode)
|
|
162
|
+
maxChunkSize: 5 * 1024 * 1024 * 1024, // 5GB
|
|
163
|
+
optimalChunkSize: 16 * 1024 * 1024, // 16MB
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const s3AwareChunker = new SmartChunker(monitor, {
|
|
167
|
+
datastoreConstraints: s3Constraints,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Simulate fast network conditions
|
|
171
|
+
for (let i = 0; i < 5; i++) {
|
|
172
|
+
monitor.recordUpload(10 * 1024 * 1024, 1000, true); // 10MB/s
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const decision = s3AwareChunker.getNextChunkSize();
|
|
176
|
+
expect(decision.strategy).toContain("s3-"); // Should use S3-optimized strategy
|
|
177
|
+
expect(decision.size).toBeGreaterThanOrEqual(5 * 1024 * 1024); // Never below 5MB
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should enforce minimum chunk size even with failures", () => {
|
|
181
|
+
const s3Constraints = {
|
|
182
|
+
minChunkSize: 5 * 1024 * 1024, // 5MB
|
|
183
|
+
maxChunkSize: 5 * 1024 * 1024 * 1024, // 5GB
|
|
184
|
+
optimalChunkSize: 16 * 1024 * 1024, // 16MB
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const s3AwareChunker = new SmartChunker(monitor, {
|
|
188
|
+
datastoreConstraints: s3Constraints,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Record many failures to try to force smaller chunks
|
|
192
|
+
for (let i = 0; i < 10; i++) {
|
|
193
|
+
s3AwareChunker.recordChunkResult(16 * 1024 * 1024, 10000, false);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const decision = s3AwareChunker.getNextChunkSize();
|
|
197
|
+
// Even with failures, should never go below datastore minimum
|
|
198
|
+
expect(decision.size).toBeGreaterThanOrEqual(s3Constraints.minChunkSize);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("UploadMetrics", () => {
|
|
203
|
+
let metrics: UploadMetrics;
|
|
204
|
+
|
|
205
|
+
beforeEach(() => {
|
|
206
|
+
metrics = new UploadMetrics();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should track upload session", () => {
|
|
210
|
+
const uploadId = "test-upload";
|
|
211
|
+
const totalSize = 1024 * 1024; // 1MB
|
|
212
|
+
|
|
213
|
+
metrics.startSession(uploadId, totalSize, true);
|
|
214
|
+
|
|
215
|
+
// Record some chunks
|
|
216
|
+
metrics.recordChunk({
|
|
217
|
+
chunkIndex: 0,
|
|
218
|
+
size: 512 * 1024,
|
|
219
|
+
duration: 1000,
|
|
220
|
+
speed: 512 * 1024, // 512 KB/s
|
|
221
|
+
success: true,
|
|
222
|
+
retryCount: 0,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
metrics.recordChunk({
|
|
226
|
+
chunkIndex: 1,
|
|
227
|
+
size: 512 * 1024,
|
|
228
|
+
duration: 1000,
|
|
229
|
+
speed: 512 * 1024, // 512 KB/s
|
|
230
|
+
success: true,
|
|
231
|
+
retryCount: 0,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const sessionMetrics = metrics.endSession();
|
|
235
|
+
expect(sessionMetrics).toBeDefined();
|
|
236
|
+
expect(sessionMetrics?.uploadId).toBe(uploadId);
|
|
237
|
+
expect(sessionMetrics?.totalSize).toBe(totalSize);
|
|
238
|
+
expect(sessionMetrics?.chunksCompleted).toBe(2);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("should generate performance insights", () => {
|
|
242
|
+
// Record multiple chunks with varying performance and sizes
|
|
243
|
+
for (let i = 0; i < 10; i++) {
|
|
244
|
+
const chunkSize = (128 + i * 128) * 1024; // Varying chunk sizes from 128KB to 1.25MB
|
|
245
|
+
metrics.recordChunk({
|
|
246
|
+
chunkIndex: i,
|
|
247
|
+
size: chunkSize,
|
|
248
|
+
duration: 1000 + Math.random() * 500, // 1-1.5 seconds
|
|
249
|
+
speed: chunkSize / (1 + Math.random() * 0.5),
|
|
250
|
+
success: true,
|
|
251
|
+
retryCount: 0,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const insights = metrics.getPerformanceInsights();
|
|
256
|
+
expect(insights.overallEfficiency).toBeGreaterThan(0);
|
|
257
|
+
expect(insights.networkStability).toBeGreaterThan(0);
|
|
258
|
+
expect(insights.recommendations).toBeInstanceOf(Array);
|
|
259
|
+
expect(insights.optimalChunkSizeRange.min).toBeGreaterThan(0);
|
|
260
|
+
expect(insights.optimalChunkSizeRange.max).toBeGreaterThanOrEqual(
|
|
261
|
+
insights.optimalChunkSizeRange.min,
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("should export all metrics data", () => {
|
|
266
|
+
metrics.startSession("test", 1024, true);
|
|
267
|
+
metrics.recordChunk({
|
|
268
|
+
chunkIndex: 0,
|
|
269
|
+
size: 1024,
|
|
270
|
+
duration: 1000,
|
|
271
|
+
speed: 1024,
|
|
272
|
+
success: true,
|
|
273
|
+
retryCount: 0,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const exported = metrics.exportMetrics();
|
|
277
|
+
expect(exported.session).toBeDefined();
|
|
278
|
+
expect(exported.chunks).toBeInstanceOf(Array);
|
|
279
|
+
expect(exported.chunks).toHaveLength(1);
|
|
280
|
+
expect(exported.insights).toBeDefined();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe("Integration", () => {
|
|
285
|
+
it("should work together for adaptive chunking", () => {
|
|
286
|
+
const monitor = new NetworkMonitor();
|
|
287
|
+
const chunker = new SmartChunker(monitor);
|
|
288
|
+
const metrics = new UploadMetrics();
|
|
289
|
+
|
|
290
|
+
// Start a session
|
|
291
|
+
metrics.startSession("integration-test", 10 * 1024 * 1024, true);
|
|
292
|
+
|
|
293
|
+
// Simulate upload process
|
|
294
|
+
for (let i = 0; i < 10; i++) {
|
|
295
|
+
const decision = chunker.getNextChunkSize();
|
|
296
|
+
const chunkSize = decision.size;
|
|
297
|
+
|
|
298
|
+
// Simulate upload (vary performance)
|
|
299
|
+
const duration = 1000 + Math.random() * 2000; // 1-3 seconds
|
|
300
|
+
const success = Math.random() > 0.1; // 90% success rate
|
|
301
|
+
|
|
302
|
+
// Record in monitor and chunker
|
|
303
|
+
monitor.recordUpload(chunkSize, duration, success);
|
|
304
|
+
chunker.recordChunkResult(chunkSize, duration, success);
|
|
305
|
+
|
|
306
|
+
// Record in metrics
|
|
307
|
+
metrics.recordChunk({
|
|
308
|
+
chunkIndex: i,
|
|
309
|
+
size: chunkSize,
|
|
310
|
+
duration,
|
|
311
|
+
speed: success ? chunkSize / (duration / 1000) : 0,
|
|
312
|
+
success,
|
|
313
|
+
retryCount: success ? 0 : 1,
|
|
314
|
+
networkCondition: monitor.getNetworkCondition().type,
|
|
315
|
+
chunkingStrategy: decision.strategy,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const sessionMetrics = metrics.endSession();
|
|
320
|
+
const networkCondition = monitor.getNetworkCondition();
|
|
321
|
+
const insights = metrics.getPerformanceInsights();
|
|
322
|
+
|
|
323
|
+
expect(sessionMetrics).toBeDefined();
|
|
324
|
+
expect(networkCondition.type).toBeDefined();
|
|
325
|
+
expect(insights.recommendations.length).toBeGreaterThan(0);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe("ChunkBuffer", () => {
|
|
330
|
+
it("should buffer small chunks until threshold is met", () => {
|
|
331
|
+
const buffer = new ChunkBuffer({
|
|
332
|
+
minThreshold: 5 * 1024 * 1024, // 5MB threshold like S3
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Add several small chunks
|
|
336
|
+
const chunk1 = new Uint8Array(1024 * 1024); // 1MB
|
|
337
|
+
const chunk2 = new Uint8Array(2 * 1024 * 1024); // 2MB
|
|
338
|
+
const chunk3 = new Uint8Array(1024 * 1024); // 1MB - total 4MB so far
|
|
339
|
+
const chunk4 = new Uint8Array(2 * 1024 * 1024); // 2MB - this will make it 6MB, >= 5MB threshold
|
|
340
|
+
|
|
341
|
+
expect(buffer.add(chunk1)).toBeNull(); // 1MB - not enough yet
|
|
342
|
+
expect(buffer.add(chunk2)).toBeNull(); // 3MB - still not enough
|
|
343
|
+
expect(buffer.add(chunk3)).toBeNull(); // 4MB - still not enough
|
|
344
|
+
|
|
345
|
+
const result = buffer.add(chunk4); // Now 6MB total, should flush
|
|
346
|
+
expect(result).not.toBeNull();
|
|
347
|
+
expect(result?.size).toBe(6 * 1024 * 1024); // 6MB total
|
|
348
|
+
expect(result?.data.length).toBe(6 * 1024 * 1024);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("should flush on timeout even if threshold not met", async () => {
|
|
352
|
+
const buffer = new ChunkBuffer({
|
|
353
|
+
minThreshold: 5 * 1024 * 1024, // 5MB
|
|
354
|
+
timeoutMs: 100, // 100ms timeout
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const smallChunk = new Uint8Array(1024 * 1024); // 1MB
|
|
358
|
+
expect(buffer.add(smallChunk)).toBeNull();
|
|
359
|
+
|
|
360
|
+
// Wait for timeout
|
|
361
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
362
|
+
|
|
363
|
+
// Should flush due to timeout
|
|
364
|
+
expect(buffer.shouldFlush()).toBe(true);
|
|
365
|
+
const result = buffer.flush();
|
|
366
|
+
expect(result).not.toBeNull();
|
|
367
|
+
expect(result?.size).toBe(1024 * 1024); // 1MB
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("should flush when max buffer size is reached", () => {
|
|
371
|
+
const buffer = new ChunkBuffer({
|
|
372
|
+
minThreshold: 10 * 1024 * 1024, // 10MB threshold
|
|
373
|
+
maxBufferSize: 6 * 1024 * 1024, // 6MB max buffer
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const chunk = new Uint8Array(3 * 1024 * 1024); // 3MB chunks
|
|
377
|
+
|
|
378
|
+
expect(buffer.add(chunk)).toBeNull(); // 3MB - not max yet
|
|
379
|
+
const result = buffer.add(chunk); // 6MB - should hit max buffer
|
|
380
|
+
|
|
381
|
+
expect(result).not.toBeNull();
|
|
382
|
+
expect(result?.size).toBe(6 * 1024 * 1024);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("should provide accurate buffer info", () => {
|
|
386
|
+
const buffer = new ChunkBuffer({
|
|
387
|
+
minThreshold: 5 * 1024 * 1024,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const chunk = new Uint8Array(2 * 1024 * 1024); // 2MB
|
|
391
|
+
buffer.add(chunk);
|
|
392
|
+
|
|
393
|
+
const info = buffer.getBufferInfo();
|
|
394
|
+
expect(info.size).toBe(2 * 1024 * 1024);
|
|
395
|
+
expect(info.chunkCount).toBe(1);
|
|
396
|
+
expect(info.isReadyToFlush).toBe(false);
|
|
397
|
+
expect(info.timeSinceLastAdd).toBeLessThan(100); // Recent
|
|
398
|
+
});
|
|
399
|
+
});
|