@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
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import type { ChunkMetrics } from "../types/chunk-metrics";
|
|
2
|
+
import type { PerformanceInsights } from "../types/performance-insights";
|
|
3
|
+
import type { UploadSessionMetrics } from "../types/upload-session-metrics";
|
|
4
|
+
|
|
5
|
+
export interface UploadMetricsConfig {
|
|
6
|
+
maxChunkHistory?: number;
|
|
7
|
+
enableDetailedMetrics?: boolean;
|
|
8
|
+
performanceThresholds?: {
|
|
9
|
+
slowSpeed: number; // bytes per second
|
|
10
|
+
fastSpeed: number; // bytes per second
|
|
11
|
+
highRetryRate: number; // ratio
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class UploadMetrics {
|
|
16
|
+
private config: Required<UploadMetricsConfig>;
|
|
17
|
+
private chunkHistory: ChunkMetrics[] = [];
|
|
18
|
+
private currentSession: Partial<UploadSessionMetrics> = {};
|
|
19
|
+
private sessionStartTime = 0;
|
|
20
|
+
|
|
21
|
+
constructor(config: UploadMetricsConfig = {}) {
|
|
22
|
+
this.config = {
|
|
23
|
+
maxChunkHistory: config.maxChunkHistory ?? 1000,
|
|
24
|
+
enableDetailedMetrics: config.enableDetailedMetrics ?? true,
|
|
25
|
+
performanceThresholds: {
|
|
26
|
+
slowSpeed: 100 * 1024, // 100 KB/s
|
|
27
|
+
fastSpeed: 5 * 1024 * 1024, // 5 MB/s
|
|
28
|
+
highRetryRate: 0.2, // 20%
|
|
29
|
+
...config.performanceThresholds,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
startSession(
|
|
35
|
+
uploadId: string,
|
|
36
|
+
totalSize: number,
|
|
37
|
+
adaptiveChunkingEnabled: boolean,
|
|
38
|
+
): void {
|
|
39
|
+
this.sessionStartTime = Date.now();
|
|
40
|
+
this.currentSession = {
|
|
41
|
+
uploadId,
|
|
42
|
+
totalSize,
|
|
43
|
+
chunksCompleted: 0,
|
|
44
|
+
chunksTotal: Math.ceil(totalSize / (1024 * 1024)), // rough estimate
|
|
45
|
+
totalDuration: 0,
|
|
46
|
+
totalRetries: 0,
|
|
47
|
+
adaptiveChunkingEnabled,
|
|
48
|
+
startTime: this.sessionStartTime,
|
|
49
|
+
};
|
|
50
|
+
this.chunkHistory = [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
recordChunk(metrics: Omit<ChunkMetrics, "timestamp">): void {
|
|
54
|
+
const chunkMetrics: ChunkMetrics = {
|
|
55
|
+
...metrics,
|
|
56
|
+
timestamp: Date.now(),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
this.chunkHistory.push(chunkMetrics);
|
|
60
|
+
|
|
61
|
+
// Keep history within limits
|
|
62
|
+
if (this.chunkHistory.length > this.config.maxChunkHistory) {
|
|
63
|
+
this.chunkHistory = this.chunkHistory.slice(-this.config.maxChunkHistory);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Update session metrics
|
|
67
|
+
if (this.currentSession && chunkMetrics.success) {
|
|
68
|
+
this.currentSession.chunksCompleted =
|
|
69
|
+
(this.currentSession.chunksCompleted || 0) + 1;
|
|
70
|
+
this.currentSession.totalDuration =
|
|
71
|
+
(this.currentSession.totalDuration || 0) + chunkMetrics.duration;
|
|
72
|
+
this.currentSession.totalRetries =
|
|
73
|
+
(this.currentSession.totalRetries || 0) + chunkMetrics.retryCount;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
endSession(): UploadSessionMetrics | null {
|
|
78
|
+
if (!this.currentSession.uploadId) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const endTime = Date.now();
|
|
83
|
+
const totalDuration = endTime - this.sessionStartTime;
|
|
84
|
+
const successfulChunks = this.chunkHistory.filter((chunk) => chunk.success);
|
|
85
|
+
|
|
86
|
+
if (successfulChunks.length === 0) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const speeds = successfulChunks.map((chunk) => chunk.speed);
|
|
91
|
+
const averageSpeed =
|
|
92
|
+
speeds.reduce((sum, speed) => sum + speed, 0) / speeds.length;
|
|
93
|
+
const peakSpeed = Math.max(...speeds);
|
|
94
|
+
const minSpeed = Math.min(...speeds);
|
|
95
|
+
const successRate = successfulChunks.length / this.chunkHistory.length;
|
|
96
|
+
|
|
97
|
+
const sessionMetrics: UploadSessionMetrics = {
|
|
98
|
+
uploadId: this.currentSession.uploadId || "",
|
|
99
|
+
totalSize: this.currentSession.totalSize || 0,
|
|
100
|
+
totalDuration,
|
|
101
|
+
chunksCompleted: successfulChunks.length,
|
|
102
|
+
chunksTotal: this.chunkHistory.length,
|
|
103
|
+
averageSpeed,
|
|
104
|
+
peakSpeed,
|
|
105
|
+
minSpeed,
|
|
106
|
+
totalRetries: this.currentSession.totalRetries || 0,
|
|
107
|
+
successRate,
|
|
108
|
+
adaptiveChunkingEnabled:
|
|
109
|
+
this.currentSession.adaptiveChunkingEnabled || false,
|
|
110
|
+
startTime: this.currentSession.startTime || 0,
|
|
111
|
+
endTime,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Reset current session
|
|
115
|
+
this.currentSession = {};
|
|
116
|
+
|
|
117
|
+
return sessionMetrics;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getCurrentSessionMetrics(): Partial<UploadSessionMetrics> {
|
|
121
|
+
return { ...this.currentSession };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
getChunkHistory(count?: number): ChunkMetrics[] {
|
|
125
|
+
const history = this.chunkHistory.slice();
|
|
126
|
+
return count ? history.slice(-count) : history;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
getPerformanceInsights(): PerformanceInsights {
|
|
130
|
+
if (this.chunkHistory.length < 5) {
|
|
131
|
+
return {
|
|
132
|
+
overallEfficiency: 0,
|
|
133
|
+
chunkingEffectiveness: 0,
|
|
134
|
+
networkStability: 0,
|
|
135
|
+
recommendations: ["Insufficient data for analysis"],
|
|
136
|
+
optimalChunkSizeRange: { min: 256 * 1024, max: 2 * 1024 * 1024 },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const successfulChunks = this.chunkHistory.filter((chunk) => chunk.success);
|
|
141
|
+
const speeds = successfulChunks.map((chunk) => chunk.speed);
|
|
142
|
+
|
|
143
|
+
// Calculate metrics
|
|
144
|
+
const averageSpeed =
|
|
145
|
+
speeds.length > 0
|
|
146
|
+
? speeds.reduce((sum, speed) => sum + speed, 0) / speeds.length
|
|
147
|
+
: 0;
|
|
148
|
+
const speedVariance = this.calculateVariance(speeds);
|
|
149
|
+
const speedStdDev = Math.sqrt(speedVariance);
|
|
150
|
+
const coefficientOfVariation = speedStdDev / averageSpeed;
|
|
151
|
+
|
|
152
|
+
// Overall efficiency based on speed and retry rate
|
|
153
|
+
const successRate = successfulChunks.length / this.chunkHistory.length;
|
|
154
|
+
const speedScore = Math.min(
|
|
155
|
+
1,
|
|
156
|
+
averageSpeed / this.config.performanceThresholds.fastSpeed,
|
|
157
|
+
);
|
|
158
|
+
const overallEfficiency = speedScore * 0.7 + successRate * 0.3;
|
|
159
|
+
|
|
160
|
+
// Network stability (lower coefficient of variation = higher stability)
|
|
161
|
+
const networkStability = Math.max(
|
|
162
|
+
0,
|
|
163
|
+
1 - Math.min(1, coefficientOfVariation),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Chunking effectiveness based on how well chunk sizes correlate with performance
|
|
167
|
+
const chunkingEffectiveness =
|
|
168
|
+
this.calculateChunkingEffectiveness(successfulChunks);
|
|
169
|
+
|
|
170
|
+
// Generate recommendations
|
|
171
|
+
const recommendations = this.generateRecommendations(
|
|
172
|
+
averageSpeed,
|
|
173
|
+
successRate,
|
|
174
|
+
coefficientOfVariation,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Calculate optimal chunk size range
|
|
178
|
+
const optimalChunkSizeRange =
|
|
179
|
+
this.calculateOptimalChunkSizeRange(successfulChunks);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
overallEfficiency,
|
|
183
|
+
chunkingEffectiveness,
|
|
184
|
+
networkStability,
|
|
185
|
+
recommendations,
|
|
186
|
+
optimalChunkSizeRange,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
exportMetrics(): {
|
|
191
|
+
session: Partial<UploadSessionMetrics>;
|
|
192
|
+
chunks: ChunkMetrics[];
|
|
193
|
+
insights: PerformanceInsights;
|
|
194
|
+
} {
|
|
195
|
+
return {
|
|
196
|
+
session: this.getCurrentSessionMetrics(),
|
|
197
|
+
chunks: this.getChunkHistory(),
|
|
198
|
+
insights: this.getPerformanceInsights(),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
reset(): void {
|
|
203
|
+
this.chunkHistory = [];
|
|
204
|
+
this.currentSession = {};
|
|
205
|
+
this.sessionStartTime = 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private calculateVariance(values: number[]): number {
|
|
209
|
+
if (values.length === 0) return 0;
|
|
210
|
+
|
|
211
|
+
const mean = values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
212
|
+
const squaredDifferences = values.map((value) => (value - mean) ** 2);
|
|
213
|
+
return (
|
|
214
|
+
squaredDifferences.reduce((sum, diff) => sum + diff, 0) / values.length
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private calculateChunkingEffectiveness(chunks: ChunkMetrics[]): number {
|
|
219
|
+
if (chunks.length < 3) return 0.5;
|
|
220
|
+
|
|
221
|
+
// Look for correlation between chunk size and upload speed
|
|
222
|
+
// Better chunking should show consistent performance across different sizes
|
|
223
|
+
const sizeGroups = this.groupChunksBySize(chunks);
|
|
224
|
+
|
|
225
|
+
if (Object.keys(sizeGroups).length < 2) return 0.5;
|
|
226
|
+
|
|
227
|
+
// Calculate coefficient of variation for each size group
|
|
228
|
+
const groupVariations = Object.values(sizeGroups).map((group) => {
|
|
229
|
+
const speeds = group.map((chunk) => chunk.speed);
|
|
230
|
+
const mean =
|
|
231
|
+
speeds.reduce((sum, speed) => sum + speed, 0) / speeds.length;
|
|
232
|
+
const variance = this.calculateVariance(speeds);
|
|
233
|
+
return Math.sqrt(variance) / mean;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Lower average variation indicates better chunking effectiveness
|
|
237
|
+
const averageVariation =
|
|
238
|
+
groupVariations.reduce((sum, cv) => sum + cv, 0) / groupVariations.length;
|
|
239
|
+
return Math.max(0, 1 - Math.min(1, averageVariation));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private groupChunksBySize(
|
|
243
|
+
chunks: ChunkMetrics[],
|
|
244
|
+
): Record<string, ChunkMetrics[]> {
|
|
245
|
+
const groups: Record<string, ChunkMetrics[]> = {};
|
|
246
|
+
|
|
247
|
+
chunks.forEach((chunk) => {
|
|
248
|
+
// Group by size ranges (64KB, 128KB, 256KB, 512KB, 1MB, 2MB, 4MB, 8MB+)
|
|
249
|
+
let sizeGroup: string;
|
|
250
|
+
if (chunk.size < 128 * 1024) sizeGroup = "64KB";
|
|
251
|
+
else if (chunk.size < 256 * 1024) sizeGroup = "128KB";
|
|
252
|
+
else if (chunk.size < 512 * 1024) sizeGroup = "256KB";
|
|
253
|
+
else if (chunk.size < 1024 * 1024) sizeGroup = "512KB";
|
|
254
|
+
else if (chunk.size < 2 * 1024 * 1024) sizeGroup = "1MB";
|
|
255
|
+
else if (chunk.size < 4 * 1024 * 1024) sizeGroup = "2MB";
|
|
256
|
+
else if (chunk.size < 8 * 1024 * 1024) sizeGroup = "4MB";
|
|
257
|
+
else sizeGroup = "8MB+";
|
|
258
|
+
|
|
259
|
+
if (!groups[sizeGroup]) groups[sizeGroup] = [];
|
|
260
|
+
const group = groups[sizeGroup];
|
|
261
|
+
if (group) group.push(chunk);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return groups;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private generateRecommendations(
|
|
268
|
+
averageSpeed: number,
|
|
269
|
+
successRate: number,
|
|
270
|
+
coefficientOfVariation: number,
|
|
271
|
+
): string[] {
|
|
272
|
+
const recommendations: string[] = [];
|
|
273
|
+
|
|
274
|
+
if (averageSpeed < this.config.performanceThresholds.slowSpeed) {
|
|
275
|
+
recommendations.push(
|
|
276
|
+
"Consider using smaller chunk sizes for better performance on slow connections",
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (averageSpeed > this.config.performanceThresholds.fastSpeed) {
|
|
281
|
+
recommendations.push(
|
|
282
|
+
"Network is fast - larger chunk sizes may improve efficiency",
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (successRate < 0.9) {
|
|
287
|
+
recommendations.push(
|
|
288
|
+
"High failure rate detected - consider more conservative chunking strategy",
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (coefficientOfVariation > 0.5) {
|
|
293
|
+
recommendations.push(
|
|
294
|
+
"Network appears unstable - smaller, more frequent chunks may be more reliable",
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (
|
|
299
|
+
coefficientOfVariation < 0.2 &&
|
|
300
|
+
averageSpeed > this.config.performanceThresholds.slowSpeed
|
|
301
|
+
) {
|
|
302
|
+
recommendations.push(
|
|
303
|
+
"Stable network detected - larger chunks may improve efficiency",
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (recommendations.length === 0) {
|
|
308
|
+
recommendations.push(
|
|
309
|
+
"Performance appears optimal with current configuration",
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return recommendations;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private calculateOptimalChunkSizeRange(chunks: ChunkMetrics[]): {
|
|
317
|
+
min: number;
|
|
318
|
+
max: number;
|
|
319
|
+
} {
|
|
320
|
+
if (chunks.length < 5) {
|
|
321
|
+
return { min: 256 * 1024, max: 2 * 1024 * 1024 };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Find chunks with best performance (top 30% by speed)
|
|
325
|
+
const sortedBySpeed = chunks.slice().sort((a, b) => b.speed - a.speed);
|
|
326
|
+
const topPerformers = sortedBySpeed.slice(
|
|
327
|
+
0,
|
|
328
|
+
Math.ceil(chunks.length * 0.3),
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
const topSizes = topPerformers.map((chunk) => chunk.size);
|
|
332
|
+
const minOptimal = Math.min(...topSizes);
|
|
333
|
+
const maxOptimal = Math.max(...topSizes);
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
min: Math.max(64 * 1024, minOptimal), // At least 64KB
|
|
337
|
+
max: Math.min(32 * 1024 * 1024, maxOptimal), // At most 32MB
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { IdGenerationService } from "../services/id-generation-service";
|
|
2
|
+
import type { ClientStorage } from "../storage/client-storage";
|
|
3
|
+
import type { PreviousUpload } from "../types/previous-upload";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Find previous uploads by fingerprint
|
|
7
|
+
*/
|
|
8
|
+
export async function findPreviousUploads(
|
|
9
|
+
clientStorage: ClientStorage,
|
|
10
|
+
fingerprint: string,
|
|
11
|
+
): Promise<PreviousUpload[]> {
|
|
12
|
+
return clientStorage.findUploadsByFingerprint(fingerprint);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resume from a previous upload
|
|
17
|
+
*/
|
|
18
|
+
export function resumeFromPreviousUpload(previousUpload: PreviousUpload): {
|
|
19
|
+
uploadId: string | null;
|
|
20
|
+
parallelUploadUrls: string[] | undefined;
|
|
21
|
+
clientStorageKey: string | null;
|
|
22
|
+
} {
|
|
23
|
+
return {
|
|
24
|
+
uploadId: previousUpload.uploadId ?? null,
|
|
25
|
+
parallelUploadUrls: previousUpload.parallelUploadUrls,
|
|
26
|
+
clientStorageKey: previousUpload.clientStorageKey,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Add the upload URL to the URL storage, if possible.
|
|
32
|
+
*/
|
|
33
|
+
export async function saveUploadInClientStorage({
|
|
34
|
+
clientStorage,
|
|
35
|
+
fingerprint,
|
|
36
|
+
size,
|
|
37
|
+
metadata,
|
|
38
|
+
clientStorageKey,
|
|
39
|
+
storeFingerprintForResuming,
|
|
40
|
+
generateId,
|
|
41
|
+
}: {
|
|
42
|
+
clientStorage: ClientStorage;
|
|
43
|
+
fingerprint: string;
|
|
44
|
+
size: number;
|
|
45
|
+
metadata: Record<string, string | number | boolean>;
|
|
46
|
+
clientStorageKey: string | null;
|
|
47
|
+
storeFingerprintForResuming: boolean;
|
|
48
|
+
generateId: IdGenerationService;
|
|
49
|
+
}): Promise<string | undefined> {
|
|
50
|
+
// We do not store the upload key
|
|
51
|
+
// - if it was disabled in the option, or
|
|
52
|
+
// - if no fingerprint was calculated for the input (i.e. a stream), or
|
|
53
|
+
// - if the key is already stored.
|
|
54
|
+
if (
|
|
55
|
+
!storeFingerprintForResuming ||
|
|
56
|
+
!fingerprint ||
|
|
57
|
+
clientStorageKey != null
|
|
58
|
+
) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const storedUpload: PreviousUpload = {
|
|
63
|
+
size,
|
|
64
|
+
metadata,
|
|
65
|
+
creationTime: new Date().toString(),
|
|
66
|
+
clientStorageKey: fingerprint,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const newClientStorageKey = await clientStorage.addUpload(
|
|
70
|
+
fingerprint,
|
|
71
|
+
storedUpload,
|
|
72
|
+
{ generateId },
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return newClientStorageKey;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Remove the entry in the URL storage, if it has been saved before.
|
|
80
|
+
*/
|
|
81
|
+
export async function removeFromClientStorage(
|
|
82
|
+
clientStorage: ClientStorage,
|
|
83
|
+
clientStorageKey: string,
|
|
84
|
+
): Promise<void> {
|
|
85
|
+
if (!clientStorageKey) return;
|
|
86
|
+
await clientStorage.removeUpload(clientStorageKey);
|
|
87
|
+
}
|