@uploadista/client-browser 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/.turbo/turbo-check.log +130 -0
- package/AUTO_CAPABILITIES.md +98 -0
- package/FRAMEWORK_INTEGRATION.md +407 -0
- package/LICENSE +21 -0
- package/README.md +795 -0
- package/SMART_CHUNKING.md +140 -0
- package/dist/client/create-uploadista-client.d.ts +182 -0
- package/dist/client/create-uploadista-client.d.ts.map +1 -0
- package/dist/client/create-uploadista-client.js +76 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +1 -0
- package/dist/framework-utils.d.ts +201 -0
- package/dist/framework-utils.d.ts.map +1 -0
- package/dist/framework-utils.js +282 -0
- package/dist/http-client.d.ts +44 -0
- package/dist/http-client.d.ts.map +1 -0
- package/dist/http-client.js +489 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/services/abort-controller-factory.d.ts +30 -0
- package/dist/services/abort-controller-factory.d.ts.map +1 -0
- package/dist/services/abort-controller-factory.js +98 -0
- package/dist/services/checksum-service.d.ts +30 -0
- package/dist/services/checksum-service.d.ts.map +1 -0
- package/dist/services/checksum-service.js +44 -0
- package/dist/services/create-browser-services.d.ts +36 -0
- package/dist/services/create-browser-services.d.ts.map +1 -0
- package/dist/services/create-browser-services.js +56 -0
- package/dist/services/file-reader.d.ts +91 -0
- package/dist/services/file-reader.d.ts.map +1 -0
- package/dist/services/file-reader.js +251 -0
- package/dist/services/fingerprint-service.d.ts +41 -0
- package/dist/services/fingerprint-service.d.ts.map +1 -0
- package/dist/services/fingerprint-service.js +64 -0
- package/dist/services/id-generation/id-generation.d.ts +40 -0
- package/dist/services/id-generation/id-generation.d.ts.map +1 -0
- package/dist/services/id-generation/id-generation.js +58 -0
- package/dist/services/platform-service.d.ts +38 -0
- package/dist/services/platform-service.d.ts.map +1 -0
- package/dist/services/platform-service.js +221 -0
- package/dist/services/storage/local-storage-service.d.ts +55 -0
- package/dist/services/storage/local-storage-service.d.ts.map +1 -0
- package/dist/services/storage/local-storage-service.js +178 -0
- package/dist/services/storage/session-storage-service.d.ts +55 -0
- package/dist/services/storage/session-storage-service.d.ts.map +1 -0
- package/dist/services/storage/session-storage-service.js +179 -0
- package/dist/services/websocket-factory.d.ts +46 -0
- package/dist/services/websocket-factory.d.ts.map +1 -0
- package/dist/services/websocket-factory.js +196 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/upload-input.d.ts +26 -0
- package/dist/types/upload-input.d.ts.map +1 -0
- package/dist/types/upload-input.js +1 -0
- package/dist/utils/hash-util.d.ts +60 -0
- package/dist/utils/hash-util.d.ts.map +1 -0
- package/dist/utils/hash-util.js +75 -0
- package/package.json +32 -0
- package/src/client/create-uploadista-client.ts +150 -0
- package/src/client/index.ts +1 -0
- package/src/framework-utils.ts +446 -0
- package/src/http-client.ts +546 -0
- package/src/index.ts +8 -0
- package/src/services/abort-controller-factory.ts +108 -0
- package/src/services/checksum-service.ts +46 -0
- package/src/services/create-browser-services.ts +81 -0
- package/src/services/file-reader.ts +344 -0
- package/src/services/fingerprint-service.ts +67 -0
- package/src/services/id-generation/id-generation.ts +60 -0
- package/src/services/platform-service.ts +231 -0
- package/src/services/storage/local-storage-service.ts +187 -0
- package/src/services/storage/session-storage-service.ts +188 -0
- package/src/services/websocket-factory.ts +212 -0
- package/src/types/index.ts +1 -0
- package/src/types/upload-input.ts +25 -0
- package/src/utils/hash-util.ts +79 -0
- package/tsconfig.json +22 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Smart Chunking Feature
|
|
2
|
+
|
|
3
|
+
The smart chunking feature provides adaptive chunk size optimization for file uploads based on network conditions and performance metrics.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Instead of using fixed chunk sizes, smart chunking dynamically adjusts chunk sizes based on:
|
|
8
|
+
- Network speed and latency
|
|
9
|
+
- Upload success rates
|
|
10
|
+
- Bandwidth utilization
|
|
11
|
+
- Connection stability
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
Smart chunking works out of the box without any configuration:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// Smart chunking is enabled by default
|
|
19
|
+
const client = createUploadClient({
|
|
20
|
+
baseUrl: 'https://your-api.com',
|
|
21
|
+
storageId: 'your-storage-id',
|
|
22
|
+
chunkSize: 1024 * 1024, // Fallback size if smart chunking needs to be disabled
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For advanced configuration, you can customize the behavior:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { createUploadClient } from '@uploadista/client/create-upload-client';
|
|
30
|
+
|
|
31
|
+
const client = createUploadClient({
|
|
32
|
+
baseUrl: 'https://your-api.com',
|
|
33
|
+
storageId: 'your-storage-id',
|
|
34
|
+
chunkSize: 1024 * 1024, // Fallback chunk size (1MB)
|
|
35
|
+
smartChunking: {
|
|
36
|
+
enabled: true, // Enable smart chunking (enabled by default)
|
|
37
|
+
minChunkSize: 64 * 1024, // 64KB minimum
|
|
38
|
+
maxChunkSize: 32 * 1024 * 1024, // 32MB maximum
|
|
39
|
+
initialChunkSize: 512 * 1024, // 512KB initial size
|
|
40
|
+
targetUtilization: 0.85, // Target 85% bandwidth utilization
|
|
41
|
+
conservativeMode: false, // Use aggressive chunking when possible
|
|
42
|
+
},
|
|
43
|
+
networkMonitoring: {
|
|
44
|
+
maxSamples: 100, // Keep last 100 upload samples
|
|
45
|
+
slowThreshold: 50 * 1024, // 50 KB/s considered slow
|
|
46
|
+
fastThreshold: 5 * 1024 * 1024, // 5 MB/s considered fast
|
|
47
|
+
},
|
|
48
|
+
uploadMetrics: {
|
|
49
|
+
enableDetailedMetrics: true, // Track detailed performance data
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Network Conditions
|
|
55
|
+
|
|
56
|
+
The system automatically detects and adapts to different network conditions:
|
|
57
|
+
|
|
58
|
+
### Slow Networks
|
|
59
|
+
- **Threshold**: < 50 KB/s average speed
|
|
60
|
+
- **Strategy**: Conservative chunking with smaller, more reliable chunks
|
|
61
|
+
- **Chunk Range**: 64KB - 2MB
|
|
62
|
+
|
|
63
|
+
### Fast Networks
|
|
64
|
+
- **Threshold**: > 5 MB/s average speed
|
|
65
|
+
- **Strategy**: Aggressive chunking with larger chunks for efficiency
|
|
66
|
+
- **Chunk Range**: 256KB - 32MB
|
|
67
|
+
|
|
68
|
+
### Unstable Networks
|
|
69
|
+
- **Detection**: High variability in upload speeds (coefficient of variation > 50%)
|
|
70
|
+
- **Strategy**: Conservative chunking with quick recovery on failures
|
|
71
|
+
- **Chunk Range**: 64KB - 2MB
|
|
72
|
+
|
|
73
|
+
## Adaptive Behavior
|
|
74
|
+
|
|
75
|
+
### Success-Based Adaptation
|
|
76
|
+
- **Consecutive Successes**: Gradually increase chunk size to improve efficiency
|
|
77
|
+
- **Consecutive Failures**: Immediately reduce chunk size for reliability
|
|
78
|
+
|
|
79
|
+
### Throughput Optimization
|
|
80
|
+
- **Target Duration**: Aims for 2-5 seconds per chunk based on network condition
|
|
81
|
+
- **Bandwidth Utilization**: Targets 85% bandwidth usage for optimal performance
|
|
82
|
+
- **Real-time Adjustment**: Adapts chunk size based on actual vs. expected throughput
|
|
83
|
+
|
|
84
|
+
## Performance Monitoring
|
|
85
|
+
|
|
86
|
+
Access performance insights and metrics:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// Get current network metrics
|
|
90
|
+
const networkMetrics = client.getNetworkMetrics();
|
|
91
|
+
console.log(`Average speed: ${networkMetrics.averageSpeed / 1024} KB/s`);
|
|
92
|
+
console.log(`Success rate: ${networkMetrics.successRate * 100}%`);
|
|
93
|
+
|
|
94
|
+
// Get network condition assessment
|
|
95
|
+
const condition = client.getNetworkCondition();
|
|
96
|
+
console.log(`Network type: ${condition.type}, confidence: ${condition.confidence}`);
|
|
97
|
+
|
|
98
|
+
// Get performance insights and recommendations
|
|
99
|
+
const insights = client.getChunkingInsights();
|
|
100
|
+
console.log('Recommendations:', insights.recommendations);
|
|
101
|
+
console.log('Optimal chunk size range:', insights.optimalChunkSizeRange);
|
|
102
|
+
|
|
103
|
+
// Export detailed metrics for analysis
|
|
104
|
+
const metrics = client.exportMetrics();
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Backwards Compatibility
|
|
108
|
+
|
|
109
|
+
Smart chunking is fully backwards compatible:
|
|
110
|
+
|
|
111
|
+
- **Default Behavior**: Smart chunking is enabled by default with sensible defaults
|
|
112
|
+
- **Disable Option**: Set `smartChunking.enabled: false` to use fixed chunking
|
|
113
|
+
- **Fallback Support**: If smart chunking fails, automatically falls back to the configured `chunkSize`
|
|
114
|
+
- **No Config Required**: Works out of the box without any configuration
|
|
115
|
+
|
|
116
|
+
## Benefits
|
|
117
|
+
|
|
118
|
+
1. **Improved Performance**: Optimizes chunk sizes for network conditions
|
|
119
|
+
2. **Better Reliability**: Reduces failures on unstable connections
|
|
120
|
+
3. **Bandwidth Efficiency**: Maximizes upload throughput while maintaining stability
|
|
121
|
+
4. **Adaptive Recovery**: Quickly adjusts to changing network conditions
|
|
122
|
+
5. **Detailed Analytics**: Provides insights for performance optimization
|
|
123
|
+
|
|
124
|
+
## Algorithm Details
|
|
125
|
+
|
|
126
|
+
### Chunk Size Calculation
|
|
127
|
+
```
|
|
128
|
+
targetSize = currentSize * (1 - adaptationRate) + theoreticalOptimalSize * adaptationRate
|
|
129
|
+
finalSize = clamp(targetSize, minChunkSize, maxChunkSize)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Network Condition Detection
|
|
133
|
+
- **Speed Classification**: Based on average throughput over recent samples
|
|
134
|
+
- **Stability Assessment**: Using coefficient of variation of upload speeds
|
|
135
|
+
- **Confidence Scoring**: Based on sample size and consistency
|
|
136
|
+
|
|
137
|
+
### Performance Metrics
|
|
138
|
+
- **Efficiency Score**: Combination of speed and success rate
|
|
139
|
+
- **Chunking Effectiveness**: How well chunk sizes correlate with performance
|
|
140
|
+
- **Network Stability**: Inverse of upload speed variability
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { type ConnectionPoolConfig, type UploadistaClientOptions as UploadistaClientOptionsCore } from "@uploadista/client-core";
|
|
2
|
+
import type { BrowserUploadInput } from "../types/upload-input";
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for creating a browser-specific Uploadista client.
|
|
5
|
+
*
|
|
6
|
+
* This interface extends the core client options but omits browser-specific
|
|
7
|
+
* services that are automatically provided by the browser environment.
|
|
8
|
+
* These services include WebSocket factory, AbortController, ID generation,
|
|
9
|
+
* storage, logging, platform detection, fingerprinting, HTTP client, file reader,
|
|
10
|
+
* and checksum calculation.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { createUploadistaClient } from '@uploadista/client-browser';
|
|
15
|
+
*
|
|
16
|
+
* const client = createUploadistaClient({
|
|
17
|
+
* endpoint: 'https://api.uploadista.com/upload',
|
|
18
|
+
* connectionPooling: {
|
|
19
|
+
* maxConnectionsPerHost: 6,
|
|
20
|
+
* enableHttp2: true
|
|
21
|
+
* }
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export interface UploadistaClientOptions extends Omit<UploadistaClientOptionsCore<BrowserUploadInput>, "webSocketFactory" | "abortControllerFactory" | "generateId" | "clientStorage" | "logger" | "platformService" | "fingerprintService" | "httpClient" | "fileReader" | "checksumService"> {
|
|
26
|
+
/**
|
|
27
|
+
* Connection pooling configuration for the HTTP client.
|
|
28
|
+
*
|
|
29
|
+
* Controls how the browser manages HTTP connections for optimal performance.
|
|
30
|
+
* The browser's native fetch API with keep-alive headers is used under the hood.
|
|
31
|
+
*
|
|
32
|
+
* @default
|
|
33
|
+
* ```typescript
|
|
34
|
+
* {
|
|
35
|
+
* maxConnectionsPerHost: 6,
|
|
36
|
+
* connectionTimeout: 30000,
|
|
37
|
+
* keepAliveTimeout: 60000,
|
|
38
|
+
* enableHttp2: true,
|
|
39
|
+
* retryOnConnectionError: true
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* connectionPooling: {
|
|
46
|
+
* maxConnectionsPerHost: 10,
|
|
47
|
+
* enableHttp2: true,
|
|
48
|
+
* keepAliveTimeout: 120000
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
connectionPooling?: ConnectionPoolConfig;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Creates a browser-optimized Uploadista client for file uploads and flow processing.
|
|
56
|
+
*
|
|
57
|
+
* This factory function automatically configures all browser-specific services including:
|
|
58
|
+
* - Fetch-based HTTP client with connection pooling
|
|
59
|
+
* - Native WebSocket support for real-time progress
|
|
60
|
+
* - localStorage for upload state persistence
|
|
61
|
+
* - Web Crypto API for checksums and fingerprints
|
|
62
|
+
* - File API for reading and chunking files
|
|
63
|
+
* - Browser platform detection and capabilities
|
|
64
|
+
*
|
|
65
|
+
* The created client can handle File and Blob objects from file inputs, drag-and-drop,
|
|
66
|
+
* or programmatically created content. It supports resumable uploads, progress tracking,
|
|
67
|
+
* and flow-based file processing.
|
|
68
|
+
*
|
|
69
|
+
* @param options - Configuration options for the browser client
|
|
70
|
+
* @returns A fully configured Uploadista client ready for browser use
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import { createUploadistaClient } from '@uploadista/client-browser';
|
|
75
|
+
*
|
|
76
|
+
* // Basic usage
|
|
77
|
+
* const client = createUploadistaClient({
|
|
78
|
+
* endpoint: 'https://api.uploadista.com/upload'
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* // With custom configuration
|
|
82
|
+
* const client = createUploadistaClient({
|
|
83
|
+
* endpoint: 'https://api.uploadista.com/upload',
|
|
84
|
+
* connectionPooling: {
|
|
85
|
+
* maxConnectionsPerHost: 6,
|
|
86
|
+
* enableHttp2: true,
|
|
87
|
+
* keepAliveTimeout: 60000
|
|
88
|
+
* },
|
|
89
|
+
* chunkSize: 5 * 1024 * 1024, // 5MB chunks
|
|
90
|
+
* retryDelays: [1000, 3000, 5000],
|
|
91
|
+
* allowedMetaFields: ['userId', 'projectId']
|
|
92
|
+
* });
|
|
93
|
+
*
|
|
94
|
+
* // Upload a file
|
|
95
|
+
* const fileInput = document.querySelector('input[type="file"]');
|
|
96
|
+
* const file = fileInput.files[0];
|
|
97
|
+
*
|
|
98
|
+
* const upload = await client.upload(file, {
|
|
99
|
+
* onProgress: (event) => {
|
|
100
|
+
* console.log(`Progress: ${event.progress}%`);
|
|
101
|
+
* }
|
|
102
|
+
* });
|
|
103
|
+
*
|
|
104
|
+
* console.log('Upload complete:', upload.id);
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @see {@link UploadistaClientOptions} for available configuration options
|
|
108
|
+
* @see {@link BrowserUploadInput} for supported file input types
|
|
109
|
+
*/
|
|
110
|
+
export declare function createUploadistaClient(options: UploadistaClientOptions): {
|
|
111
|
+
upload: (file: BrowserUploadInput, { uploadLengthDeferred, uploadSize, onProgress, onChunkComplete, onSuccess, onShouldRetry, onError, }?: import("@uploadista/client-core").UploadistaUploadOptions) => Promise<{
|
|
112
|
+
abort: () => void;
|
|
113
|
+
}>;
|
|
114
|
+
uploadWithFlow: (file: BrowserUploadInput, flowConfig: import("@uploadista/client-core").FlowUploadConfig, { onProgress, onChunkComplete, onSuccess, onShouldRetry, onJobStart, onError, }?: Omit<import("@uploadista/client-core").UploadistaUploadOptions, "uploadLengthDeferred" | "uploadSize" | "metadata">) => Promise<{
|
|
115
|
+
abort: () => void;
|
|
116
|
+
jobId: string;
|
|
117
|
+
}>;
|
|
118
|
+
abort: (params: Parameters<typeof import("node_modules/@uploadista/client-core/src/upload/upload-manager").abort>[0]) => Promise<void>;
|
|
119
|
+
getFlow: (flowId: string) => Promise<{
|
|
120
|
+
status: number;
|
|
121
|
+
flow: import("@uploadista/core").FlowData;
|
|
122
|
+
}>;
|
|
123
|
+
runFlow: ({ flowId, inputs, storageId: flowStorageId, }: {
|
|
124
|
+
flowId: string;
|
|
125
|
+
inputs: Record<string, unknown>;
|
|
126
|
+
storageId?: string;
|
|
127
|
+
}) => Promise<{
|
|
128
|
+
status: number;
|
|
129
|
+
job: import("@uploadista/core").FlowJob;
|
|
130
|
+
}>;
|
|
131
|
+
continueFlow: ({ jobId, nodeId, newData, contentType, }: {
|
|
132
|
+
jobId: string;
|
|
133
|
+
nodeId: string;
|
|
134
|
+
newData: unknown;
|
|
135
|
+
contentType?: "application/json" | "application/octet-stream";
|
|
136
|
+
}) => Promise<import("@uploadista/core").FlowJob>;
|
|
137
|
+
getJobStatus: (jobId: string) => Promise<import("@uploadista/core").FlowJob>;
|
|
138
|
+
openUploadWebSocket: (uploadId: string) => Promise<import("@uploadista/client-core").WebSocketLike>;
|
|
139
|
+
openFlowWebSocket: (jobId: string) => Promise<import("@uploadista/client-core").WebSocketLike>;
|
|
140
|
+
openWebSocket: (id: string) => Promise<import("@uploadista/client-core").WebSocketLike>;
|
|
141
|
+
closeWebSocket: (id: string) => void;
|
|
142
|
+
closeAllWebSockets: () => void;
|
|
143
|
+
sendPing: (jobId: string) => boolean;
|
|
144
|
+
isWebSocketConnected: (id: string) => boolean;
|
|
145
|
+
getWebSocketConnectionCount: () => number;
|
|
146
|
+
getWebSocketConnectionCountByType: () => {
|
|
147
|
+
upload: number;
|
|
148
|
+
flow: number;
|
|
149
|
+
total: number;
|
|
150
|
+
};
|
|
151
|
+
getNetworkMetrics: () => import("@uploadista/client-core").NetworkMetrics;
|
|
152
|
+
getNetworkCondition: () => import("@uploadista/client-core").NetworkCondition;
|
|
153
|
+
getChunkingInsights: () => import("@uploadista/client-core").PerformanceInsights;
|
|
154
|
+
exportMetrics: () => {
|
|
155
|
+
session: Partial<import("@uploadista/client-core").UploadSessionMetrics>;
|
|
156
|
+
chunks: import("@uploadista/client-core").ChunkMetrics[];
|
|
157
|
+
insights: import("@uploadista/client-core").PerformanceInsights;
|
|
158
|
+
};
|
|
159
|
+
getConnectionMetrics: () => import("@uploadista/client-core").ConnectionMetrics;
|
|
160
|
+
getDetailedConnectionMetrics: () => import("@uploadista/client-core").DetailedConnectionMetrics;
|
|
161
|
+
warmupConnections: (urls: string[]) => Promise<void>;
|
|
162
|
+
getConnectionPoolingInsights: () => Promise<{
|
|
163
|
+
isOptimized: boolean;
|
|
164
|
+
reuseRate: number;
|
|
165
|
+
recommendedMinChunkSize: number;
|
|
166
|
+
connectionOverhead: number;
|
|
167
|
+
}>;
|
|
168
|
+
resetMetrics: () => Promise<void>;
|
|
169
|
+
validateConfiguration: (options: UploadistaClientOptionsCore<BrowserUploadInput>) => {
|
|
170
|
+
valid: boolean;
|
|
171
|
+
errors: string[];
|
|
172
|
+
warnings: string[];
|
|
173
|
+
};
|
|
174
|
+
validateConfigurationAsync: (options: UploadistaClientOptionsCore<BrowserUploadInput>) => Promise<{
|
|
175
|
+
valid: boolean;
|
|
176
|
+
errors: string[];
|
|
177
|
+
warnings: string[];
|
|
178
|
+
capabilities: import("@uploadista/core").DataStoreCapabilities;
|
|
179
|
+
}>;
|
|
180
|
+
getCapabilities: () => Promise<import("@uploadista/core").DataStoreCapabilities>;
|
|
181
|
+
};
|
|
182
|
+
//# sourceMappingURL=create-uploadista-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-uploadista-client.d.ts","sourceRoot":"","sources":["../../src/client/create-uploadista-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,oBAAoB,EAIzB,KAAK,uBAAuB,IAAI,2BAA2B,EAC5D,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,uBACf,SAAQ,IAAI,CACV,2BAA2B,CAAC,kBAAkB,CAAC,EAC7C,kBAAkB,GAClB,wBAAwB,GACxB,YAAY,GACZ,eAAe,GACf,QAAQ,GACR,iBAAiB,GACjB,oBAAoB,GACpB,YAAY,GACZ,YAAY,GACZ,iBAAiB,CACpB;IACD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,iBAAiB,CAAC,EAAE,oBAAoB,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkBtE"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createClientStorage, createLogger, createUploadistaClient as createUploadistaClientCore, } from "@uploadista/client-core";
|
|
2
|
+
import { createBrowserServices } from "../services/create-browser-services";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a browser-optimized Uploadista client for file uploads and flow processing.
|
|
5
|
+
*
|
|
6
|
+
* This factory function automatically configures all browser-specific services including:
|
|
7
|
+
* - Fetch-based HTTP client with connection pooling
|
|
8
|
+
* - Native WebSocket support for real-time progress
|
|
9
|
+
* - localStorage for upload state persistence
|
|
10
|
+
* - Web Crypto API for checksums and fingerprints
|
|
11
|
+
* - File API for reading and chunking files
|
|
12
|
+
* - Browser platform detection and capabilities
|
|
13
|
+
*
|
|
14
|
+
* The created client can handle File and Blob objects from file inputs, drag-and-drop,
|
|
15
|
+
* or programmatically created content. It supports resumable uploads, progress tracking,
|
|
16
|
+
* and flow-based file processing.
|
|
17
|
+
*
|
|
18
|
+
* @param options - Configuration options for the browser client
|
|
19
|
+
* @returns A fully configured Uploadista client ready for browser use
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { createUploadistaClient } from '@uploadista/client-browser';
|
|
24
|
+
*
|
|
25
|
+
* // Basic usage
|
|
26
|
+
* const client = createUploadistaClient({
|
|
27
|
+
* endpoint: 'https://api.uploadista.com/upload'
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // With custom configuration
|
|
31
|
+
* const client = createUploadistaClient({
|
|
32
|
+
* endpoint: 'https://api.uploadista.com/upload',
|
|
33
|
+
* connectionPooling: {
|
|
34
|
+
* maxConnectionsPerHost: 6,
|
|
35
|
+
* enableHttp2: true,
|
|
36
|
+
* keepAliveTimeout: 60000
|
|
37
|
+
* },
|
|
38
|
+
* chunkSize: 5 * 1024 * 1024, // 5MB chunks
|
|
39
|
+
* retryDelays: [1000, 3000, 5000],
|
|
40
|
+
* allowedMetaFields: ['userId', 'projectId']
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* // Upload a file
|
|
44
|
+
* const fileInput = document.querySelector('input[type="file"]');
|
|
45
|
+
* const file = fileInput.files[0];
|
|
46
|
+
*
|
|
47
|
+
* const upload = await client.upload(file, {
|
|
48
|
+
* onProgress: (event) => {
|
|
49
|
+
* console.log(`Progress: ${event.progress}%`);
|
|
50
|
+
* }
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* console.log('Upload complete:', upload.id);
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @see {@link UploadistaClientOptions} for available configuration options
|
|
57
|
+
* @see {@link BrowserUploadInput} for supported file input types
|
|
58
|
+
*/
|
|
59
|
+
export function createUploadistaClient(options) {
|
|
60
|
+
const services = createBrowserServices({
|
|
61
|
+
connectionPooling: options.connectionPooling,
|
|
62
|
+
});
|
|
63
|
+
return createUploadistaClientCore({
|
|
64
|
+
...options,
|
|
65
|
+
webSocketFactory: services.websocket,
|
|
66
|
+
abortControllerFactory: services.abortController,
|
|
67
|
+
platformService: services.platform,
|
|
68
|
+
httpClient: services.httpClient,
|
|
69
|
+
fileReader: services.fileReader,
|
|
70
|
+
generateId: services.idGeneration,
|
|
71
|
+
fingerprintService: services.fingerprintService,
|
|
72
|
+
checksumService: services.checksumService,
|
|
73
|
+
logger: createLogger(false, () => { }),
|
|
74
|
+
clientStorage: createClientStorage(services.storage),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./create-uploadista-client";
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework Integration Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides TypeScript utilities and helper types for building
|
|
5
|
+
* framework-specific wrappers around the Uploadista client.
|
|
6
|
+
*
|
|
7
|
+
* @module framework-utils
|
|
8
|
+
*/
|
|
9
|
+
import type { FlowResult, UploadResult } from "@uploadista/client-core";
|
|
10
|
+
import type { FlowEvent } from "@uploadista/core/flow";
|
|
11
|
+
import type { UploadEvent, UploadFile } from "@uploadista/core/types";
|
|
12
|
+
/**
|
|
13
|
+
* Base upload state that framework wrappers should implement
|
|
14
|
+
*/
|
|
15
|
+
export interface BaseUploadState {
|
|
16
|
+
status: "idle" | "uploading" | "success" | "error" | "aborted";
|
|
17
|
+
progress: number;
|
|
18
|
+
bytesUploaded: number;
|
|
19
|
+
totalBytes: number;
|
|
20
|
+
error?: Error;
|
|
21
|
+
result?: UploadResult<UploadFile>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Base flow upload state
|
|
25
|
+
*/
|
|
26
|
+
export interface BaseFlowUploadState extends BaseUploadState {
|
|
27
|
+
jobId?: string;
|
|
28
|
+
flowStatus?: "pending" | "processing" | "completed" | "failed";
|
|
29
|
+
flowResult?: FlowResult<unknown>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Progress callback signature
|
|
33
|
+
*/
|
|
34
|
+
export type ProgressCallback = (uploadId: string, bytesUploaded: number, totalBytes: number) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Complete callback signature
|
|
37
|
+
*/
|
|
38
|
+
export type CompleteCallback = (uploadId: string, result: UploadResult) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Error callback signature
|
|
41
|
+
*/
|
|
42
|
+
export type ErrorCallback = (uploadId: string, error: Error) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Abort callback signature
|
|
45
|
+
*/
|
|
46
|
+
export type AbortCallback = (uploadId: string) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Event handler signature for framework wrappers
|
|
49
|
+
*/
|
|
50
|
+
export type EventHandler<T = unknown> = (event: T) => void;
|
|
51
|
+
/**
|
|
52
|
+
* WebSocket event handler signature
|
|
53
|
+
*/
|
|
54
|
+
export type WebSocketEventHandler = (event: UploadEvent | FlowEvent) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Framework state updater function signature
|
|
57
|
+
* @template T - The state type
|
|
58
|
+
*/
|
|
59
|
+
export type StateUpdater<T> = (updater: (prevState: T) => T) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Cleanup function returned by setup functions
|
|
62
|
+
*/
|
|
63
|
+
export type CleanupFunction = () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Upload item for multi-upload tracking
|
|
66
|
+
*/
|
|
67
|
+
export interface UploadItem {
|
|
68
|
+
id: string;
|
|
69
|
+
file: File;
|
|
70
|
+
status: BaseUploadState["status"];
|
|
71
|
+
progress: number;
|
|
72
|
+
bytesUploaded: number;
|
|
73
|
+
totalBytes: number;
|
|
74
|
+
error?: Error;
|
|
75
|
+
result?: UploadResult;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Multi-upload aggregate statistics
|
|
79
|
+
*/
|
|
80
|
+
export interface MultiUploadStats {
|
|
81
|
+
totalFiles: number;
|
|
82
|
+
completedFiles: number;
|
|
83
|
+
failedFiles: number;
|
|
84
|
+
totalBytes: number;
|
|
85
|
+
uploadedBytes: number;
|
|
86
|
+
totalProgress: number;
|
|
87
|
+
allComplete: boolean;
|
|
88
|
+
hasErrors: boolean;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Drag and drop state
|
|
92
|
+
*/
|
|
93
|
+
export interface DragDropState {
|
|
94
|
+
isDragging: boolean;
|
|
95
|
+
isOver: boolean;
|
|
96
|
+
files: File[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* File validation result
|
|
100
|
+
*/
|
|
101
|
+
export interface FileValidationResult {
|
|
102
|
+
valid: boolean;
|
|
103
|
+
error?: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* File validation function signature
|
|
107
|
+
*/
|
|
108
|
+
export type FileValidator = (file: File) => FileValidationResult;
|
|
109
|
+
/**
|
|
110
|
+
* Utility: Calculate aggregate upload statistics
|
|
111
|
+
*/
|
|
112
|
+
export declare function calculateMultiUploadStats(uploads: UploadItem[]): MultiUploadStats;
|
|
113
|
+
/**
|
|
114
|
+
* Utility: Format file size for display
|
|
115
|
+
*/
|
|
116
|
+
export declare function formatFileSize(bytes: number): string;
|
|
117
|
+
/**
|
|
118
|
+
* Utility: Format progress percentage
|
|
119
|
+
*/
|
|
120
|
+
export declare function formatProgress(progress: number): string;
|
|
121
|
+
/**
|
|
122
|
+
* Utility: Get file extension
|
|
123
|
+
*/
|
|
124
|
+
export declare function getFileExtension(filename: string): string;
|
|
125
|
+
/**
|
|
126
|
+
* Utility: Check if file is an image
|
|
127
|
+
*/
|
|
128
|
+
export declare function isImageFile(file: File): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Utility: Check if file is a video
|
|
131
|
+
*/
|
|
132
|
+
export declare function isVideoFile(file: File): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Utility: Create file size validator
|
|
135
|
+
*/
|
|
136
|
+
export declare function createFileSizeValidator(maxSizeBytes: number): FileValidator;
|
|
137
|
+
/**
|
|
138
|
+
* Utility: Create file type validator
|
|
139
|
+
*/
|
|
140
|
+
export declare function createFileTypeValidator(allowedTypes: string[]): FileValidator;
|
|
141
|
+
/**
|
|
142
|
+
* Utility: Compose multiple validators
|
|
143
|
+
*/
|
|
144
|
+
export declare function composeValidators(...validators: FileValidator[]): FileValidator;
|
|
145
|
+
/**
|
|
146
|
+
* Utility: Generate unique upload ID
|
|
147
|
+
*/
|
|
148
|
+
export declare function generateUploadId(): string;
|
|
149
|
+
/**
|
|
150
|
+
* Utility: Create delay promise for retry logic
|
|
151
|
+
*/
|
|
152
|
+
export declare function delay(ms: number): Promise<void>;
|
|
153
|
+
/**
|
|
154
|
+
* Utility: Calculate exponential backoff delay
|
|
155
|
+
*/
|
|
156
|
+
export declare function calculateBackoff(attempt: number, baseDelay?: number, maxDelay?: number): number;
|
|
157
|
+
/**
|
|
158
|
+
* Utility: Create retry wrapper for upload function
|
|
159
|
+
*/
|
|
160
|
+
export declare function createRetryWrapper<T>(fn: () => Promise<T>, maxAttempts?: number, shouldRetry?: (error: unknown) => boolean): () => Promise<T>;
|
|
161
|
+
/**
|
|
162
|
+
* Type guard: Check if error is network-related (should retry)
|
|
163
|
+
*/
|
|
164
|
+
export declare function isNetworkError(error: unknown): boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Type guard: Check if error is abort-related (should not retry)
|
|
167
|
+
*/
|
|
168
|
+
export declare function isAbortError(error: unknown): boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Format upload speed in human-readable format
|
|
171
|
+
*/
|
|
172
|
+
export declare function formatSpeed(bytesPerSecond: number): string;
|
|
173
|
+
/**
|
|
174
|
+
* Format duration in human-readable format
|
|
175
|
+
*/
|
|
176
|
+
export declare function formatDuration(milliseconds: number): string;
|
|
177
|
+
/**
|
|
178
|
+
* Validate file type against accepted types
|
|
179
|
+
*/
|
|
180
|
+
export declare function validateFileType(file: File, accept: string[]): boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Check if a file is an audio file
|
|
183
|
+
*/
|
|
184
|
+
export declare function isAudioFile(file: File): boolean;
|
|
185
|
+
/**
|
|
186
|
+
* Check if a file is a document
|
|
187
|
+
*/
|
|
188
|
+
export declare function isDocumentFile(file: File): boolean;
|
|
189
|
+
/**
|
|
190
|
+
* Create a preview URL for a file (if supported)
|
|
191
|
+
*/
|
|
192
|
+
export declare function createFilePreview(file: File): string | null;
|
|
193
|
+
/**
|
|
194
|
+
* Clean up a preview URL created with createFilePreview
|
|
195
|
+
*/
|
|
196
|
+
export declare function revokeFilePreview(previewUrl: string): void;
|
|
197
|
+
/**
|
|
198
|
+
* Calculate progress percentage
|
|
199
|
+
*/
|
|
200
|
+
export declare function calculateProgress(current: number, total: number): number;
|
|
201
|
+
//# sourceMappingURL=framework-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework-utils.d.ts","sourceRoot":"","sources":["../src/framework-utils.ts"],"names":[],"mappings":"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"}
|