@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,130 @@
|
|
|
1
|
+
|
|
2
|
+
> @uploadista/client@ check /Users/denislaboureyras/Documents/uploadista/dev/uploadista/packages/uploadista/clients/client
|
|
3
|
+
> biome check --write ./src
|
|
4
|
+
|
|
5
|
+
src/examples/connection-pooling-example.ts:83:22 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━
|
|
6
|
+
|
|
7
|
+
i Template literals are preferred over string concatenation.
|
|
8
|
+
|
|
9
|
+
81 │ console.log("Connection metrics:", {
|
|
10
|
+
82 │ activeConnections: connectionMetrics.activeConnections,
|
|
11
|
+
> 83 │ reuseRate: Math.round(connectionMetrics.reuseRate * 100) + "%",
|
|
12
|
+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
13
|
+
84 │ averageConnectionTime:
|
|
14
|
+
85 │ Math.round(connectionMetrics.averageConnectionTime) + "ms",
|
|
15
|
+
|
|
16
|
+
i Unsafe fix: Use a template literal.
|
|
17
|
+
|
|
18
|
+
81 81 │ console.log("Connection metrics:", {
|
|
19
|
+
82 82 │ activeConnections: connectionMetrics.activeConnections,
|
|
20
|
+
83 │ - ··········reuseRate:·Math.round(connectionMetrics.reuseRate·*·100)·+·"%",
|
|
21
|
+
83 │ + ··········reuseRate:·`${Math.round(connectionMetrics.reuseRate·*·100)}%`,
|
|
22
|
+
84 84 │ averageConnectionTime:
|
|
23
|
+
85 85 │ Math.round(connectionMetrics.averageConnectionTime) + "ms",
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
src/examples/connection-pooling-example.ts:85:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━
|
|
27
|
+
|
|
28
|
+
i Template literals are preferred over string concatenation.
|
|
29
|
+
|
|
30
|
+
83 │ reuseRate: Math.round(connectionMetrics.reuseRate * 100) + "%",
|
|
31
|
+
84 │ averageConnectionTime:
|
|
32
|
+
> 85 │ Math.round(connectionMetrics.averageConnectionTime) + "ms",
|
|
33
|
+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
34
|
+
86 │ });
|
|
35
|
+
87 │ },
|
|
36
|
+
|
|
37
|
+
i Unsafe fix: Use a template literal.
|
|
38
|
+
|
|
39
|
+
83 83 │ reuseRate: Math.round(connectionMetrics.reuseRate * 100) + "%",
|
|
40
|
+
84 84 │ averageConnectionTime:
|
|
41
|
+
85 │ - ············Math.round(connectionMetrics.averageConnectionTime)·+·"ms",
|
|
42
|
+
85 │ + ············`${Math.round(connectionMetrics.averageConnectionTime)}ms`,
|
|
43
|
+
86 86 │ });
|
|
44
|
+
87 87 │ },
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
src/examples/connection-pooling-example.ts:95:22 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━
|
|
48
|
+
|
|
49
|
+
i Template literals are preferred over string concatenation.
|
|
50
|
+
|
|
51
|
+
93 │ console.log("Final connection pooling performance:", {
|
|
52
|
+
94 │ totalConnections: finalMetrics.totalConnections,
|
|
53
|
+
> 95 │ reuseRate: Math.round(finalMetrics.reuseRate * 100) + "%",
|
|
54
|
+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
55
|
+
96 │ averageSpeed:
|
|
56
|
+
97 │ Math.round(finalMetrics.averageConnectionTime) + "ms per request",
|
|
57
|
+
|
|
58
|
+
i Unsafe fix: Use a template literal.
|
|
59
|
+
|
|
60
|
+
93 93 │ console.log("Final connection pooling performance:", {
|
|
61
|
+
94 94 │ totalConnections: finalMetrics.totalConnections,
|
|
62
|
+
95 │ - ··········reuseRate:·Math.round(finalMetrics.reuseRate·*·100)·+·"%",
|
|
63
|
+
95 │ + ··········reuseRate:·`${Math.round(finalMetrics.reuseRate·*·100)}%`,
|
|
64
|
+
96 96 │ averageSpeed:
|
|
65
|
+
97 97 │ Math.round(finalMetrics.averageConnectionTime) + "ms per request",
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
src/examples/connection-pooling-example.ts:97:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━
|
|
69
|
+
|
|
70
|
+
i Template literals are preferred over string concatenation.
|
|
71
|
+
|
|
72
|
+
95 │ reuseRate: Math.round(finalMetrics.reuseRate * 100) + "%",
|
|
73
|
+
96 │ averageSpeed:
|
|
74
|
+
> 97 │ Math.round(finalMetrics.averageConnectionTime) + "ms per request",
|
|
75
|
+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
76
|
+
98 │ });
|
|
77
|
+
99 │ },
|
|
78
|
+
|
|
79
|
+
i Unsafe fix: Use a template literal.
|
|
80
|
+
|
|
81
|
+
95 95 │ reuseRate: Math.round(finalMetrics.reuseRate * 100) + "%",
|
|
82
|
+
96 96 │ averageSpeed:
|
|
83
|
+
97 │ - ············Math.round(finalMetrics.averageConnectionTime)·+·"ms·per·request",
|
|
84
|
+
97 │ + ············`${Math.round(finalMetrics.averageConnectionTime)}ms·per·request`,
|
|
85
|
+
98 98 │ });
|
|
86
|
+
99 99 │ },
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
src/examples/advanced-connection-pooling-example.ts:201:9 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━
|
|
90
|
+
|
|
91
|
+
! This variable chunkingInsights is unused.
|
|
92
|
+
|
|
93
|
+
199 │ const detailedMetrics = uploadClient.getDetailedConnectionMetrics();
|
|
94
|
+
200 │ const networkMetrics = uploadClient.getNetworkMetrics();
|
|
95
|
+
> 201 │ const chunkingInsights = uploadClient.getChunkingInsights();
|
|
96
|
+
│ ^^^^^^^^^^^^^^^^
|
|
97
|
+
202 │
|
|
98
|
+
203 │ // Performance Summary
|
|
99
|
+
|
|
100
|
+
i Unused variables are often the result of an incomplete refactoring, typos, or other sources of bugs.
|
|
101
|
+
|
|
102
|
+
i Unsafe fix: If this is intentional, prepend chunkingInsights with an underscore.
|
|
103
|
+
Skipped 5 suggested fixes.
|
|
104
|
+
If you wish to apply the suggested (unsafe) fixes, use the command biome check --write --unsafe
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
Checked 35 files in 109ms. No fixes applied.
|
|
108
|
+
199 199 │ const detailedMetrics = uploadClient.getDetailedConnectionMetrics();
|
|
109
|
+
Found 2 warnings.
|
|
110
|
+
200 200 │ const networkMetrics = uploadClient.getNetworkMetrics();
|
|
111
|
+
201 │ - ··const·chunkingInsights·=·uploadClient.getChunkingInsights();
|
|
112
|
+
201 │ + ··const·_chunkingInsights·=·uploadClient.getChunkingInsights();
|
|
113
|
+
202 202 │
|
|
114
|
+
203 203 │ // Performance Summary
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
src/examples/advanced-connection-pooling-example.ts:293:22 lint/correctness/noUnusedVariables ━━━━━━━━━━
|
|
118
|
+
|
|
119
|
+
! This variable abortUpload is unused.
|
|
120
|
+
|
|
121
|
+
291 │ // The upload will automatically negotiate the best strategy
|
|
122
|
+
292 │ try {
|
|
123
|
+
> 293 │ const { abort: abortUpload } = await uploadClient.upload(file, {
|
|
124
|
+
│ ^^^^^^^^^^^
|
|
125
|
+
294 │ onProgress: (uploadId, bytes, total) => {
|
|
126
|
+
295 │ const percentage = Math.round((bytes / (total ?? 1)) * 100);
|
|
127
|
+
|
|
128
|
+
i Unused variables are often the result of an incomplete refactoring, typos, or other sources of bugs.
|
|
129
|
+
|
|
130
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
✅ Data Store Capability Discovery Implementation Complete
|
|
2
|
+
|
|
3
|
+
I have successfully implemented a comprehensive data store capability discovery
|
|
4
|
+
system that enables intelligent upload strategy selection based on actual data store
|
|
5
|
+
capabilities. Here's what was accomplished:
|
|
6
|
+
|
|
7
|
+
🔧 Core Infrastructure
|
|
8
|
+
|
|
9
|
+
1. DataStoreCapabilities Interface
|
|
10
|
+
(@packages/uploadista/core/src/types/data-store.ts)
|
|
11
|
+
- Added DataStoreCapabilities type with properties for parallel uploads,
|
|
12
|
+
concatenation, deferred length, resumable uploads, transactional uploads, concurrency
|
|
13
|
+
limits, and chunk size constraints
|
|
14
|
+
- Extended DataStore interface with getCapabilities() and validateUploadStrategy()
|
|
15
|
+
methods
|
|
16
|
+
- Added UploadStrategy type for "single" | "parallel" strategies
|
|
17
|
+
|
|
18
|
+
2. Upload Strategy Negotiator
|
|
19
|
+
(@packages/uploadista/core/src/upload/upload-strategy-negotiator.ts)
|
|
20
|
+
- Created UploadStrategyNegotiator class for intelligent strategy selection
|
|
21
|
+
- Implements capability-based validation and automatic strategy optimization
|
|
22
|
+
- Considers file size, data store constraints, and client preferences
|
|
23
|
+
- Provides detailed reasoning and warnings for strategy decisions
|
|
24
|
+
|
|
25
|
+
🏪 Data Store Implementations
|
|
26
|
+
|
|
27
|
+
1. S3 Store - Full parallel upload capabilities:
|
|
28
|
+
- ✅ Parallel uploads with up to 60 concurrent parts
|
|
29
|
+
- ✅ Native concatenation through multipart completion
|
|
30
|
+
- ✅ 5MiB-5GiB chunk size limits, 10K parts maximum
|
|
31
|
+
- ✅ Transactional uploads with rollback support
|
|
32
|
+
|
|
33
|
+
2. GCS Store - Limited parallel support:
|
|
34
|
+
- ❌ No native parallel uploads (uses file combination)
|
|
35
|
+
- ✅ Concatenation through bucket.combine()
|
|
36
|
+
- ✅ Sequential operations with patch-based resumption
|
|
37
|
+
- ⚠️ Requires ordered chunks
|
|
38
|
+
|
|
39
|
+
3. Filesystem Store - Basic sequential operations:
|
|
40
|
+
- ❌ No parallel upload support
|
|
41
|
+
- ❌ No concatenation capabilities
|
|
42
|
+
- ✅ Offset-based resumable uploads only
|
|
43
|
+
- ⚠️ Sequential write operations only
|
|
44
|
+
|
|
45
|
+
🚀 Client-Side Integration
|
|
46
|
+
|
|
47
|
+
1. Enhanced Upload Client
|
|
48
|
+
(@packages/uploadista/clients/client/src/upload/create-upload-client.ts)
|
|
49
|
+
- Added UploadStrategyConfig for fine-grained strategy control
|
|
50
|
+
- Integrated capability negotiation into upload workflow
|
|
51
|
+
- Automatic strategy selection with detailed logging
|
|
52
|
+
- Configuration validation on client creation
|
|
53
|
+
- New client methods: validateConfiguration(), getCapabilities()
|
|
54
|
+
|
|
55
|
+
2. Mock Data Store (@packages/uploadista/clients/client/src/mock-data-store.ts)
|
|
56
|
+
- Client-side capability simulation for strategy negotiation
|
|
57
|
+
- Predefined capability sets for S3, GCS, and filesystem backends
|
|
58
|
+
- Enables intelligent client-side strategy decisions
|
|
59
|
+
|
|
60
|
+
📝 Enhanced Examples
|
|
61
|
+
|
|
62
|
+
1. Connection Pooling Example - Updated with:
|
|
63
|
+
- Capability-aware client configuration
|
|
64
|
+
- Backend-specific optimization examples (S3, GCS, filesystem)
|
|
65
|
+
- Strategy comparison demonstrations
|
|
66
|
+
- Configuration validation examples
|
|
67
|
+
|
|
68
|
+
2. Advanced Example - Added:
|
|
69
|
+
- Capability negotiation demonstrations
|
|
70
|
+
- Backend adaptation scenarios
|
|
71
|
+
- Real-world strategy selection examples
|
|
72
|
+
|
|
73
|
+
🎯 Key Benefits
|
|
74
|
+
|
|
75
|
+
For Developers:
|
|
76
|
+
- Automatic Optimization: System automatically selects the best upload strategy based
|
|
77
|
+
on data store capabilities
|
|
78
|
+
- Configuration Validation: Early validation prevents runtime errors and suboptimal
|
|
79
|
+
configurations
|
|
80
|
+
- Backend Agnostic: Same client code works optimally across different storage
|
|
81
|
+
backends
|
|
82
|
+
- Intelligent Fallbacks: Graceful degradation when preferred strategies aren't
|
|
83
|
+
supported
|
|
84
|
+
|
|
85
|
+
For Different Backends:
|
|
86
|
+
- S3: Leverages full multipart upload capabilities with high concurrency
|
|
87
|
+
- GCS: Uses sequential uploads with file combination for optimal performance
|
|
88
|
+
- Filesystem: Employs offset-based resumable uploads with proper error handling
|
|
89
|
+
|
|
90
|
+
Additional Important Capabilities:
|
|
91
|
+
- Deferred Length Uploads: For streaming scenarios where file size is unknown upfront
|
|
92
|
+
- Transactional Uploads: With rollback capabilities for data integrity
|
|
93
|
+
- Resumable Uploads: Cross-session upload continuation
|
|
94
|
+
- Smart Chunking Integration: Chunk sizes optimized per data store constraints
|
|
95
|
+
|
|
96
|
+
The implementation provides a robust foundation for upload optimization that adapts
|
|
97
|
+
to the capabilities of any storage backend, ensuring optimal performance and
|
|
98
|
+
reliability across different deployment scenarios.
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# Framework Integration Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to wrap `@uploadista/client` in a framework-specific client package (React, Vue, Angular, Svelte, etc.).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@uploadista/client` package is designed to be framework-agnostic. It provides:
|
|
8
|
+
|
|
9
|
+
- **UploadistaClient**: Core client for uploads and flow management
|
|
10
|
+
- **Event-based architecture**: Plain JavaScript callbacks, no framework coupling
|
|
11
|
+
- **Type-safe API**: Full TypeScript support
|
|
12
|
+
- **WebSocket management**: Unified WebSocket handling for upload and flow events
|
|
13
|
+
- **Authentication**: Flexible auth strategies
|
|
14
|
+
|
|
15
|
+
## Architecture Pattern
|
|
16
|
+
|
|
17
|
+
Framework clients should wrap the base client with framework-specific primitives:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
21
|
+
│ Framework Client Layer │
|
|
22
|
+
│ - Framework-specific state management (hooks/composables) │
|
|
23
|
+
│ - Component library │
|
|
24
|
+
│ - Dependency injection (Context/Provide-Inject/Services) │
|
|
25
|
+
└─────────────────────────────────────────────────────────────┘
|
|
26
|
+
↓
|
|
27
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
28
|
+
│ Base Client (@uploadista/client) │
|
|
29
|
+
│ - UploadistaClient (framework-agnostic) │
|
|
30
|
+
│ - Event handlers (plain callbacks) │
|
|
31
|
+
│ - WebSocket management │
|
|
32
|
+
└─────────────────────────────────────────────────────────────┘
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Key Integration Points
|
|
36
|
+
|
|
37
|
+
### 1. Client Instance Management
|
|
38
|
+
|
|
39
|
+
**Challenge**: Share a single client instance across the application.
|
|
40
|
+
|
|
41
|
+
**Solutions**:
|
|
42
|
+
- **React**: Context API + `useContext` hook
|
|
43
|
+
- **Vue**: Plugin + `provide`/`inject`
|
|
44
|
+
- **Angular**: Injectable service
|
|
45
|
+
- **Svelte**: Context API
|
|
46
|
+
|
|
47
|
+
**Example (React)**:
|
|
48
|
+
```typescript
|
|
49
|
+
import { createContext, useContext } from 'react'
|
|
50
|
+
import { createUploadistaClient, type UploadistaClient } from '@uploadista/client'
|
|
51
|
+
|
|
52
|
+
const UploadistaContext = createContext<UploadistaClient | null>(null)
|
|
53
|
+
|
|
54
|
+
export function UploadistaProvider({ children, options }) {
|
|
55
|
+
const client = useMemo(() => createUploadistaClient(options), [options])
|
|
56
|
+
return <UploadistaContext.Provider value={client}>{children}</UploadistaContext.Provider>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function useUploadistaClient() {
|
|
60
|
+
const client = useContext(UploadistaContext)
|
|
61
|
+
if (!client) throw new Error('useUploadistaClient must be used within UploadistaProvider')
|
|
62
|
+
return client
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Example (Vue)**:
|
|
67
|
+
```typescript
|
|
68
|
+
import { inject, type App } from 'vue'
|
|
69
|
+
import { createUploadistaClient, type UploadistaClient } from '@uploadista/client'
|
|
70
|
+
|
|
71
|
+
const UPLOADISTA_CLIENT_KEY = Symbol('uploadista-client')
|
|
72
|
+
|
|
73
|
+
export function createUploadistaPlugin(options: UploadistaOptions) {
|
|
74
|
+
return {
|
|
75
|
+
install(app: App) {
|
|
76
|
+
const client = createUploadistaClient(options)
|
|
77
|
+
app.provide(UPLOADISTA_CLIENT_KEY, client)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function useUploadistaClient(): UploadistaClient {
|
|
83
|
+
const client = inject(UPLOADISTA_CLIENT_KEY)
|
|
84
|
+
if (!client) throw new Error('useUploadistaClient must be used within app with uploadista plugin')
|
|
85
|
+
return client
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 2. State Management
|
|
90
|
+
|
|
91
|
+
**Challenge**: Expose upload/flow state reactively using framework primitives.
|
|
92
|
+
|
|
93
|
+
**Approach**:
|
|
94
|
+
1. Wrap client methods with framework state
|
|
95
|
+
2. Subscribe to client events using callbacks
|
|
96
|
+
3. Update reactive state on events
|
|
97
|
+
4. Clean up subscriptions on unmount
|
|
98
|
+
|
|
99
|
+
**Example (React)**:
|
|
100
|
+
```typescript
|
|
101
|
+
import { useState, useCallback, useEffect } from 'react'
|
|
102
|
+
import { useUploadistaClient } from './useUploadistaClient'
|
|
103
|
+
|
|
104
|
+
export function useUpload(options?: UseUploadOptions) {
|
|
105
|
+
const client = useUploadistaClient()
|
|
106
|
+
const [state, setState] = useState<UploadState>({
|
|
107
|
+
status: 'idle',
|
|
108
|
+
progress: 0,
|
|
109
|
+
// ...
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const upload = useCallback(async (file: File) => {
|
|
113
|
+
setState(prev => ({ ...prev, status: 'uploading' }))
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const result = await client.upload(file, {
|
|
117
|
+
onProgress: (_, bytesUploaded, totalBytes) => {
|
|
118
|
+
setState(prev => ({
|
|
119
|
+
...prev,
|
|
120
|
+
progress: totalBytes ? (bytesUploaded / totalBytes) * 100 : 0,
|
|
121
|
+
bytesUploaded,
|
|
122
|
+
totalBytes
|
|
123
|
+
}))
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
setState(prev => ({ ...prev, status: 'success', result }))
|
|
128
|
+
} catch (error) {
|
|
129
|
+
setState(prev => ({ ...prev, status: 'error', error }))
|
|
130
|
+
}
|
|
131
|
+
}, [client])
|
|
132
|
+
|
|
133
|
+
return { state, upload }
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Example (Vue)**:
|
|
138
|
+
```typescript
|
|
139
|
+
import { reactive, readonly } from 'vue'
|
|
140
|
+
import { useUploadistaClient } from './useUploadistaClient'
|
|
141
|
+
|
|
142
|
+
export function useUpload(options?: UseUploadOptions) {
|
|
143
|
+
const client = useUploadistaClient()
|
|
144
|
+
const state = reactive<UploadState>({
|
|
145
|
+
status: 'idle',
|
|
146
|
+
progress: 0,
|
|
147
|
+
// ...
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const upload = async (file: File) => {
|
|
151
|
+
state.status = 'uploading'
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const result = await client.upload(file, {
|
|
155
|
+
onProgress: (_, bytesUploaded, totalBytes) => {
|
|
156
|
+
state.progress = totalBytes ? (bytesUploaded / totalBytes) * 100 : 0
|
|
157
|
+
state.bytesUploaded = bytesUploaded
|
|
158
|
+
state.totalBytes = totalBytes
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
state.status = 'success'
|
|
163
|
+
state.result = result
|
|
164
|
+
} catch (error) {
|
|
165
|
+
state.status = 'error'
|
|
166
|
+
state.error = error
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { state: readonly(state), upload }
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 3. Event Handling
|
|
175
|
+
|
|
176
|
+
**Event System**: The base client uses plain callback functions for events.
|
|
177
|
+
|
|
178
|
+
**Event Types**:
|
|
179
|
+
- `onProgress`: Upload progress updates `(uploadId, bytesUploaded, totalBytes) => void`
|
|
180
|
+
- `onComplete`: Upload completion `(uploadId, result) => void`
|
|
181
|
+
- `onError`: Upload error `(uploadId, error) => void`
|
|
182
|
+
- `onAbort`: Upload aborted `(uploadId) => void`
|
|
183
|
+
|
|
184
|
+
**WebSocket Events**:
|
|
185
|
+
- `UploadEvent`: Upload-related events (progress, complete, error)
|
|
186
|
+
- `FlowEvent`: Flow execution events (node start, node complete, job complete)
|
|
187
|
+
|
|
188
|
+
**Integration Pattern**:
|
|
189
|
+
```typescript
|
|
190
|
+
// Framework wrapper should:
|
|
191
|
+
1. Accept event callbacks from user
|
|
192
|
+
2. Call base client methods with callbacks
|
|
193
|
+
3. Update framework state in callbacks
|
|
194
|
+
4. Clean up on unmount
|
|
195
|
+
|
|
196
|
+
// Example:
|
|
197
|
+
const upload = async (file: File) => {
|
|
198
|
+
await client.upload(file, {
|
|
199
|
+
onProgress: (id, bytes, total) => {
|
|
200
|
+
// Update framework state
|
|
201
|
+
updateState({ progress: (bytes / total) * 100 })
|
|
202
|
+
// Call user callback if provided
|
|
203
|
+
options?.onProgress?.(id, bytes, total)
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 4. Lifecycle Management
|
|
210
|
+
|
|
211
|
+
**Challenge**: Clean up resources when components unmount.
|
|
212
|
+
|
|
213
|
+
**Resources to clean up**:
|
|
214
|
+
- WebSocket connections
|
|
215
|
+
- Ongoing uploads (optionally abort)
|
|
216
|
+
- Event listeners
|
|
217
|
+
- Timers/intervals
|
|
218
|
+
|
|
219
|
+
**Example (React)**:
|
|
220
|
+
```typescript
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
// Setup
|
|
223
|
+
const ws = client.openUploadWebSocket(uploadId)
|
|
224
|
+
|
|
225
|
+
// Cleanup
|
|
226
|
+
return () => {
|
|
227
|
+
client.closeUploadWebSocket(uploadId)
|
|
228
|
+
}
|
|
229
|
+
}, [uploadId])
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Example (Vue)**:
|
|
233
|
+
```typescript
|
|
234
|
+
import { onUnmounted } from 'vue'
|
|
235
|
+
|
|
236
|
+
export function useFlowUpload() {
|
|
237
|
+
// Setup
|
|
238
|
+
const ws = await client.openFlowWebSocket(jobId)
|
|
239
|
+
|
|
240
|
+
// Cleanup
|
|
241
|
+
onUnmounted(() => {
|
|
242
|
+
client.closeFlowWebSocket(jobId)
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### 5. TypeScript Integration
|
|
248
|
+
|
|
249
|
+
**Import types from base client**:
|
|
250
|
+
```typescript
|
|
251
|
+
import type {
|
|
252
|
+
UploadistaClient,
|
|
253
|
+
UploadOptions,
|
|
254
|
+
UploadResult,
|
|
255
|
+
FlowUploadOptions,
|
|
256
|
+
FlowResult
|
|
257
|
+
} from '@uploadista/client'
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Extend types for framework-specific needs**:
|
|
261
|
+
```typescript
|
|
262
|
+
// Add framework-specific options
|
|
263
|
+
export interface UseUploadOptions extends UploadOptions {
|
|
264
|
+
onMounted?: () => void
|
|
265
|
+
suspense?: boolean
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 6. Component Library (Optional)
|
|
270
|
+
|
|
271
|
+
Framework clients can provide basic unstyled components:
|
|
272
|
+
|
|
273
|
+
**Recommended components**:
|
|
274
|
+
- `UploadZone`: File input + drag-drop area
|
|
275
|
+
- `UploadList`: Display upload progress for multiple files
|
|
276
|
+
- `FlowUploadZone`: Upload through flow
|
|
277
|
+
- `FlowUploadList`: Display flow upload progress
|
|
278
|
+
|
|
279
|
+
**Principles**:
|
|
280
|
+
- Keep components unstyled or minimally styled
|
|
281
|
+
- Use slots/render props for customization
|
|
282
|
+
- Expose underlying composables/hooks
|
|
283
|
+
- Focus on functionality, not aesthetics
|
|
284
|
+
|
|
285
|
+
## Testing Strategy
|
|
286
|
+
|
|
287
|
+
### Unit Tests
|
|
288
|
+
- Mock base client responses
|
|
289
|
+
- Test state updates
|
|
290
|
+
- Verify cleanup logic
|
|
291
|
+
- Test error handling
|
|
292
|
+
|
|
293
|
+
### Integration Tests
|
|
294
|
+
- Test with real base client
|
|
295
|
+
- Verify event propagation
|
|
296
|
+
- Test WebSocket connections (mocked)
|
|
297
|
+
- Validate lifecycle management
|
|
298
|
+
|
|
299
|
+
## Framework-Specific Considerations
|
|
300
|
+
|
|
301
|
+
### React
|
|
302
|
+
- Use hooks for state management
|
|
303
|
+
- Memoize callbacks with `useCallback`
|
|
304
|
+
- Memoize derived state with `useMemo`
|
|
305
|
+
- Clean up in `useEffect` return
|
|
306
|
+
- Consider React 18+ features (Suspense, Transitions)
|
|
307
|
+
|
|
308
|
+
### Vue
|
|
309
|
+
- Use Composition API (`ref`, `reactive`, `computed`)
|
|
310
|
+
- Clean up in `onUnmounted`
|
|
311
|
+
- Use `readonly` to prevent external mutations
|
|
312
|
+
- Consider SSR with Nuxt.js
|
|
313
|
+
- Vue DevTools integration for debugging
|
|
314
|
+
|
|
315
|
+
### Angular
|
|
316
|
+
- Use Injectable services
|
|
317
|
+
- RxJS observables for events
|
|
318
|
+
- OnDestroy lifecycle hook for cleanup
|
|
319
|
+
- Angular Signals for reactive state
|
|
320
|
+
- Zone.js considerations
|
|
321
|
+
|
|
322
|
+
### Svelte
|
|
323
|
+
- Use stores for shared state
|
|
324
|
+
- Context API for client sharing
|
|
325
|
+
- `onDestroy` for cleanup
|
|
326
|
+
- Reactive statements for derived state
|
|
327
|
+
- SvelteKit SSR considerations
|
|
328
|
+
|
|
329
|
+
## Common Patterns
|
|
330
|
+
|
|
331
|
+
### Pattern 1: Multiple Upload Management
|
|
332
|
+
|
|
333
|
+
Track array of uploads with aggregate state:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// Pseudo-code
|
|
337
|
+
const uploads = reactive([])
|
|
338
|
+
const aggregate = computed(() => ({
|
|
339
|
+
totalProgress: average(uploads.map(u => u.progress)),
|
|
340
|
+
allComplete: uploads.every(u => u.status === 'success'),
|
|
341
|
+
hasErrors: uploads.some(u => u.status === 'error')
|
|
342
|
+
}))
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Pattern 2: Retry with Exponential Backoff
|
|
346
|
+
|
|
347
|
+
Implement retry logic in framework wrapper:
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
const retry = async (file: File, attempt = 0) => {
|
|
351
|
+
try {
|
|
352
|
+
await upload(file)
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (attempt < maxRetries) {
|
|
355
|
+
await delay(2 ** attempt * 1000)
|
|
356
|
+
return retry(file, attempt + 1)
|
|
357
|
+
}
|
|
358
|
+
throw error
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Pattern 3: Resumable Upload Recovery
|
|
364
|
+
|
|
365
|
+
Recover previous uploads on mount:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// On mount
|
|
369
|
+
const previousUploads = await client.listPreviousUploads()
|
|
370
|
+
if (previousUploads.length > 0) {
|
|
371
|
+
// Prompt user to resume or clear
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Checklist for New Framework Integration
|
|
376
|
+
|
|
377
|
+
- [ ] Client instance sharing mechanism (Context/Plugin/Service)
|
|
378
|
+
- [ ] Composables/Hooks for all upload patterns
|
|
379
|
+
- [ ] Single upload
|
|
380
|
+
- [ ] Multiple uploads
|
|
381
|
+
- [ ] Flow upload
|
|
382
|
+
- [ ] Multiple flow uploads
|
|
383
|
+
- [ ] Drag and drop
|
|
384
|
+
- [ ] Upload metrics
|
|
385
|
+
- [ ] WebSocket lifecycle management
|
|
386
|
+
- [ ] Cleanup on unmount
|
|
387
|
+
- [ ] TypeScript types for all public APIs
|
|
388
|
+
- [ ] Basic unstyled components
|
|
389
|
+
- [ ] Comprehensive example application
|
|
390
|
+
- [ ] Unit tests (>80% coverage)
|
|
391
|
+
- [ ] Integration tests
|
|
392
|
+
- [ ] Documentation (README, API reference)
|
|
393
|
+
- [ ] Migration guide (from other frameworks)
|
|
394
|
+
|
|
395
|
+
## Resources
|
|
396
|
+
|
|
397
|
+
- [Base Client Source](./src/)
|
|
398
|
+
- [React Client Example](../react/)
|
|
399
|
+
- [Vue Client Example](../vue/)
|
|
400
|
+
- [Framework Utilities](./src/framework-utils.ts)
|
|
401
|
+
|
|
402
|
+
## Support
|
|
403
|
+
|
|
404
|
+
For questions about framework integration, please:
|
|
405
|
+
1. Check existing framework clients for patterns
|
|
406
|
+
2. Review this guide
|
|
407
|
+
3. Open an issue with the `framework-integration` label
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 uploadista
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|