@gentleduck/upload 0.2.6 → 0.3.0
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/README.md +170 -193
- package/dist/core/index.d.ts +2 -3
- package/dist/core/index.js +1 -1
- package/dist/core-BVgloTiP.js +10 -0
- package/dist/index-DCqyBVcx.d.ts +1441 -0
- package/dist/index-DsZHPmJm.d.ts +225 -0
- package/dist/index-KROXtLQh.d.ts +281 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.js +1 -1
- package/dist/react/index.d.ts +2 -51
- package/dist/react/index.js +1 -1
- package/dist/react-euPrDv24.js +1 -0
- package/dist/strategies/index.d.ts +2 -56
- package/dist/strategies/index.js +1 -1
- package/dist/strategies-CUF0s68j.js +1 -0
- package/package.json +3 -2
- package/src/{__test__ → __tests__}/emitter.test.ts +4 -4
- package/src/{__test__ → __tests__}/fingerprint.test.ts +1 -1
- package/src/{__test__ → __tests__}/guards.test.ts +15 -5
- package/src/__tests__/id.test.ts +67 -0
- package/src/__tests__/mime-sniff.test.ts +142 -0
- package/src/{__test__ → __tests__}/persistence.test.ts +43 -8
- package/src/__tests__/sanitize-filename.test.ts +109 -0
- package/src/__tests__/sleep.test.ts +34 -0
- package/src/__tests__/static-analysis.test.ts +145 -0
- package/src/{__test__ → __tests__}/store-libs.test.ts +57 -3
- package/src/__tests__/transport-fetch.test.ts +108 -0
- package/src/{__test__ → __tests__}/transport.test.ts +5 -5
- package/src/core/__tests__/errors.test.ts +152 -0
- package/src/core/client.ts +3 -0
- package/src/core/contracts/contracts.types.ts +335 -141
- package/src/core/contracts/index.ts +3 -8
- package/src/core/contracts/transport/index.ts +3 -2
- package/src/core/contracts/transport/transport.fetch.ts +203 -0
- package/src/core/contracts/transport/transport.libs.ts +7 -8
- package/src/core/contracts/transport/transport.ts +91 -83
- package/src/core/contracts/transport/transport.types.ts +71 -35
- package/src/core/engine/__tests__/config-nan-bypass.test.ts +40 -0
- package/src/core/engine/engine.libs.ts +35 -0
- package/src/core/engine/engine.types.ts +367 -241
- package/src/core/engine/index.ts +3 -8
- package/src/core/engine/reducer.ts +89 -43
- package/src/core/engine/store/handlers/add-file.ts +39 -29
- package/src/core/engine/store/handlers/cancel.ts +13 -8
- package/src/core/engine/store/handlers/clean-up.ts +11 -8
- package/src/core/engine/store/handlers/create-intent.ts +82 -27
- package/src/core/engine/store/handlers/finalize.ts +30 -13
- package/src/core/engine/store/handlers/index.ts +7 -7
- package/src/core/engine/store/handlers/pause.ts +8 -6
- package/src/core/engine/store/handlers/run-upload.ts +49 -34
- package/src/core/engine/store/index.ts +27 -21
- package/src/core/engine/store/store.dispatch.ts +10 -8
- package/src/core/engine/store/store.libs.ts +184 -41
- package/src/core/engine/store/store.runtime.ts +45 -36
- package/src/core/engine/store/store.schedule.ts +27 -21
- package/src/core/engine/store/store.types.ts +177 -227
- package/src/core/engine/validation/__tests__/intent.test.ts +70 -0
- package/src/core/engine/validation/file.ts +7 -3
- package/src/core/engine/validation/index.ts +1 -0
- package/src/core/engine/validation/intent.ts +49 -27
- package/src/core/engine/validation/list.ts +14 -15
- package/src/core/engine/validation/mime.ts +39 -0
- package/src/core/errors.ts +271 -0
- package/src/core/index.ts +28 -5
- package/src/core/persistence/__tests__/deserialize-nan-bypass.test.ts +100 -0
- package/src/core/persistence/__tests__/persistence.test.ts +85 -0
- package/src/core/persistence/adapters.indexeddb.ts +9 -3
- package/src/core/persistence/adapters.local.ts +9 -3
- package/src/core/persistence/adapters.memory.ts +35 -13
- package/src/core/persistence/index.ts +5 -5
- package/src/core/persistence/persistence.ts +73 -60
- package/src/core/persistence/persistence.types.ts +140 -60
- package/src/core/utils/__tests__/guards.test.ts +93 -0
- package/src/core/utils/__tests__/url-safety.test.ts +112 -0
- package/src/core/utils/async.ts +7 -2
- package/src/core/utils/constants.ts +3 -9
- package/src/core/utils/emitter/emitter.ts +7 -24
- package/src/core/utils/emitter/emitter.types.ts +33 -16
- package/src/core/utils/emitter/index.ts +2 -74
- package/src/core/utils/fingerprint.ts +16 -5
- package/src/core/utils/guards.ts +91 -1
- package/src/core/utils/hash/__tests__/sha256.test.ts +61 -0
- package/src/core/utils/hash/index.ts +126 -0
- package/src/core/utils/hash/sha256.ts +179 -0
- package/src/core/utils/id.ts +33 -1
- package/src/core/utils/index.ts +15 -6
- package/src/core/utils/mime-sniff.ts +119 -0
- package/src/core/utils/sanitize-filename.ts +85 -0
- package/src/core/utils/url-safety.ts +156 -0
- package/src/react/__tests__/use-dropzone.test.ts +64 -0
- package/src/react/index.ts +5 -2
- package/src/react/upload-provider.tsx +68 -53
- package/src/react/uploader.types.ts +58 -61
- package/src/react/use-dropzone.tsx +226 -0
- package/src/react/use-uploader.tsx +98 -91
- package/src/strategies/_shared/__tests__/retry.test.ts +120 -0
- package/src/strategies/_shared/retry.ts +79 -0
- package/src/strategies/index.ts +9 -15
- package/src/strategies/multipart/__tests__/multipart.test.ts +229 -0
- package/src/strategies/multipart/index.ts +206 -108
- package/src/strategies/post/__tests__/post-url-safety.test.ts +119 -0
- package/src/strategies/post/index.ts +115 -33
- package/src/strategies/put/__tests__/put.test.ts +132 -0
- package/src/strategies/put/index.ts +139 -0
- package/src/strategies/registry/index.ts +12 -9
- package/src/strategies/tus/__tests__/tus.test.ts +159 -0
- package/src/strategies/tus/index.ts +211 -0
- package/dist/async-DxRWu4le.js +0 -1
- package/dist/core-CMkrP9DX.js +0 -2
- package/dist/index-C38Jmizu.d.ts +0 -1005
- package/dist/strategy.types-gg9ZStRe.d.ts +0 -225
- package/src/__test__/effect-pool.test.ts +0 -5
- package/src/__test__/id.test.ts +0 -14
- package/src/__test__/store-integration.test.ts +0 -5
- package/src/core/client/client.ts +0 -50
- package/src/core/client/client.types.ts +0 -184
- package/src/core/client/config.types.ts +0 -77
- package/src/core/client/index.ts +0 -4
- package/src/core/client/plugins.types.ts +0 -46
- package/src/core/client/retry.types.ts +0 -6
- package/src/core/client/uploadClient.ts +0 -19
- package/src/core/contracts/backendApi.ts +0 -62
- package/src/core/contracts/cursor.types.ts +0 -23
- package/src/core/contracts/errors.types.ts +0 -67
- package/src/core/contracts/fingerprint.types.ts +0 -18
- package/src/core/contracts/intent.types.ts +0 -39
- package/src/core/contracts/result.types.ts +0 -11
- package/src/core/contracts/strategy.types.ts +0 -45
- package/src/core/engine/commands.types.ts +0 -17
- package/src/core/engine/event-map.types.ts +0 -45
- package/src/core/engine/internal-events.types.ts +0 -202
- package/src/core/engine/outcome.types.ts +0 -9
- package/src/core/engine/progress.types.ts +0 -15
- package/src/core/engine/store/handlers.ts +0 -819
- package/src/core/engine/validation.ts +0 -216
- /package/src/{__test__ → __tests__}/transport-libs.test.ts +0 -0
|
@@ -1,179 +1,373 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public contract types grouped under the `Contracts` namespace.
|
|
3
3
|
*
|
|
4
|
-
* Each sub-namespace owns one concern (Intent, Cursor, Result,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Each sub-namespace owns one concern (Intent, Cursor, Result, Validation,
|
|
5
|
+
* Fingerprint, Errors, Strategy, BackendApi).
|
|
7
6
|
* @author wildduck2 <https://github.com/wildduck2>
|
|
8
7
|
*/
|
|
9
8
|
export namespace Contracts {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Registry of intent types mapped by strategy name. */
|
|
21
|
-
export type IntentMap = Record<string, IIntentBase<string>>
|
|
22
|
-
|
|
23
|
-
/** Union of strategy keys for an intent map. */
|
|
24
|
-
export type StrategyKey<M extends IntentMap> = keyof M & string
|
|
9
|
+
export namespace Intent {
|
|
10
|
+
/** Base shape for all upload intents. */
|
|
11
|
+
export type Base<K extends string = string> = {
|
|
12
|
+
/** Strategy name (discriminant). */
|
|
13
|
+
strategy: K
|
|
14
|
+
/** Unique backend file identifier. */
|
|
15
|
+
fileId: string
|
|
16
|
+
}
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
/** Registry of intent types mapped by strategy name. */
|
|
19
|
+
export type Map = Record<string, Base<string>>
|
|
28
20
|
|
|
29
|
-
|
|
21
|
+
/** Union of strategy keys for an intent map. */
|
|
22
|
+
export type StrategyKey<M extends Map> = keyof M & string
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
/** Union of all intent variants for an intent map. */
|
|
25
|
+
export type Any<M extends Map> = M[StrategyKey<M>]
|
|
26
|
+
}
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}[keyof C & string]
|
|
28
|
+
export namespace Cursor {
|
|
29
|
+
/** Registry of cursor payload types mapped by strategy name. */
|
|
30
|
+
export type Map<M extends Intent.Map> = Partial<Record<Intent.StrategyKey<M>, unknown>>
|
|
38
31
|
|
|
39
|
-
|
|
32
|
+
/** Discriminated union of all cursor variants. */
|
|
33
|
+
export type Any<C extends Record<string, unknown>> = {
|
|
34
|
+
[K in keyof C & string]: {
|
|
35
|
+
/** Strategy name associated with this cursor. */
|
|
36
|
+
strategy: K
|
|
37
|
+
/** The custom strategy-defined state needed to resume the upload. */
|
|
38
|
+
value?: C[K] | undefined
|
|
39
|
+
}
|
|
40
|
+
}[keyof C & string]
|
|
41
|
+
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
export namespace Result {
|
|
44
|
+
/** Successful upload payload returned by your backend API's complete endpoint. */
|
|
45
|
+
export type Base = {
|
|
46
|
+
/** Unique backend file identifier. */
|
|
47
|
+
fileId: string
|
|
48
|
+
/** Storage key/path or public address of the uploaded file. */
|
|
49
|
+
key: string
|
|
50
|
+
}
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
export namespace Validation {
|
|
54
|
+
/** Why a file was rejected during client-side validation. */
|
|
55
|
+
export type Rejection =
|
|
56
|
+
| { code: 'empty_file' }
|
|
57
|
+
| { code: 'file_too_large'; maxBytes: number; size: number }
|
|
58
|
+
| { code: 'type_not_allowed'; allowed: string[]; got: string }
|
|
59
|
+
| { code: 'too_many_files'; max: number }
|
|
60
|
+
| { code: 'mime_mismatch'; claimed: string; sniffed: string }
|
|
61
|
+
| { code: 'filename_rejected'; reason: 'reserved' | 'too-long' | 'empty' | 'path-sep' }
|
|
62
|
+
}
|
|
50
63
|
|
|
51
|
-
/** Deterministic
|
|
52
|
-
export
|
|
53
|
-
/**
|
|
64
|
+
/** Deterministic identity signature of a file on the client. */
|
|
65
|
+
export type FingerprintFile = {
|
|
66
|
+
/** Base name of the file (excluding directory path). */
|
|
54
67
|
name: string
|
|
55
|
-
/**
|
|
68
|
+
/** Size of the file in bytes. */
|
|
56
69
|
size: number
|
|
57
|
-
/**
|
|
70
|
+
/** MIME type of the file. */
|
|
58
71
|
type: string
|
|
59
|
-
/**
|
|
72
|
+
/** Timestamp when the file was last modified on disk (milliseconds since epoch). */
|
|
60
73
|
lastModified: number
|
|
61
|
-
/** Optional checksum (e.g. SHA-256 hex). */
|
|
62
|
-
checksum?: string
|
|
74
|
+
/** Optional pre-computed checksum of the file (e.g. SHA-256 hex). */
|
|
75
|
+
checksum?: string | undefined
|
|
63
76
|
}
|
|
64
77
|
|
|
65
|
-
|
|
78
|
+
export namespace Errors {
|
|
79
|
+
/** Base schema for upload-related runtime errors. */
|
|
80
|
+
export type Base = {
|
|
81
|
+
/** Stable machine-readable error classification. */
|
|
82
|
+
code: string
|
|
83
|
+
/** Human-friendly message suitable for presentation in standard warning elements. */
|
|
84
|
+
message: string
|
|
85
|
+
/** Original thrown error or reason context if available. */
|
|
86
|
+
cause?: unknown
|
|
87
|
+
/** Flag specifying whether the error recovery system should schedule retries. */
|
|
88
|
+
retryable?: boolean
|
|
89
|
+
}
|
|
66
90
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
/** Discriminant union of built-in engine failure structures. */
|
|
92
|
+
export type BuiltIn =
|
|
93
|
+
| { code: 'validation_failed'; message: string; reason: Validation.Rejection; retryable?: false }
|
|
94
|
+
| { code: 'strategy_missing'; message: string; strategy: string; retryable?: false }
|
|
95
|
+
| { code: 'aborted'; message: string; reason: 'pause' | 'cancel' | 'unknown'; cause?: unknown; retryable?: false }
|
|
96
|
+
| { code: 'network'; message: string; cause?: unknown; retryable?: boolean }
|
|
97
|
+
| { code: 'http'; message: string; status: number; statusText?: string; cause?: unknown; retryable?: boolean }
|
|
98
|
+
| { code: 'timeout'; message: string; cause?: unknown; retryable?: boolean }
|
|
99
|
+
| { code: 'auth'; message: string; cause?: unknown; retryable?: false }
|
|
100
|
+
| { code: 'rate_limit'; message: string; retryAfterMs?: number; cause?: unknown; retryable?: boolean }
|
|
101
|
+
| { code: 'server'; message: string; serverCode?: string; cause?: unknown; retryable?: boolean }
|
|
102
|
+
| { code: 'unknown'; message: string; cause?: unknown; retryable?: boolean }
|
|
73
103
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
/** Stable error code. */
|
|
77
|
-
code: string
|
|
78
|
-
/** Human-friendly message (safe for UI). */
|
|
79
|
-
message: string
|
|
80
|
-
/** Optional cause (original error, response, etc.). */
|
|
81
|
-
cause?: unknown
|
|
82
|
-
/** Whether the error should be retried. */
|
|
83
|
-
retryable?: boolean
|
|
104
|
+
/** The canonical error shape handled by the engine, plugins, and handlers. */
|
|
105
|
+
export type Error = BuiltIn | (Base & Record<string, unknown>)
|
|
84
106
|
}
|
|
85
107
|
|
|
86
|
-
/**
|
|
87
|
-
export type
|
|
88
|
-
|
|
89
|
-
| { code: 'strategy_missing'; message: string; strategy: string; retryable?: false }
|
|
90
|
-
| { code: 'aborted'; message: string; reason: 'pause' | 'cancel' | 'unknown'; cause?: unknown; retryable?: false }
|
|
91
|
-
| { code: 'network'; message: string; cause?: unknown; retryable?: boolean }
|
|
92
|
-
| { code: 'http'; message: string; status: number; statusText?: string; cause?: unknown; retryable?: boolean }
|
|
93
|
-
| { code: 'timeout'; message: string; cause?: unknown; retryable?: boolean }
|
|
94
|
-
| { code: 'auth'; message: string; cause?: unknown; retryable?: false }
|
|
95
|
-
| { code: 'rate_limit'; message: string; retryAfterMs?: number; cause?: unknown; retryable?: boolean }
|
|
96
|
-
| { code: 'server'; message: string; serverCode?: string; cause?: unknown; retryable?: boolean }
|
|
97
|
-
| { code: 'unknown'; message: string; cause?: unknown; retryable?: boolean }
|
|
98
|
-
|
|
99
|
-
/** The error type used everywhere in the engine. */
|
|
100
|
-
export type Error = BuiltInError | (IErrorBase & Record<string, unknown>)
|
|
101
|
-
|
|
102
|
-
// -------------------- Validation --------------------
|
|
103
|
-
|
|
104
|
-
/** Validation rules applied on `addFiles`. */
|
|
105
|
-
export interface IValidationRules {
|
|
106
|
-
/** Maximum number of files allowed. */
|
|
108
|
+
/** Validation rules applied to files on registration before intents are initiated. */
|
|
109
|
+
export type ValidationRules = {
|
|
110
|
+
/** Maximum number of files permitted in a single upload batch. */
|
|
107
111
|
maxFiles?: number
|
|
108
|
-
/** Maximum
|
|
112
|
+
/** Maximum size in bytes allowed for a single file. */
|
|
109
113
|
maxSizeBytes?: number
|
|
110
|
-
/** Minimum
|
|
114
|
+
/** Minimum size in bytes allowed for a single file. */
|
|
111
115
|
minSizeBytes?: number
|
|
112
|
-
/**
|
|
116
|
+
/** White-list of allowed MIME types (e.g. `["image/png", "application/pdf"]`). */
|
|
113
117
|
allowedTypes?: string[]
|
|
114
|
-
/**
|
|
118
|
+
/** White-list of allowed file extension strings (e.g. `["png", "pdf"]`). */
|
|
115
119
|
allowedExtensions?: string[]
|
|
116
120
|
}
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Safe, cause-stripped error summary stored in step history and forwarded in API context.
|
|
124
|
+
* The raw `cause` field is omitted because it may contain sensitive server response bodies.
|
|
125
|
+
*/
|
|
126
|
+
export type StepError = {
|
|
127
|
+
code: string
|
|
128
|
+
message: string
|
|
129
|
+
retryable?: boolean
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** A single recorded phase transition on an upload item. */
|
|
133
|
+
export type UploadStep = {
|
|
134
|
+
/** Phase name when this step was entered. */
|
|
135
|
+
phase: string
|
|
136
|
+
/** Epoch ms when this phase was entered. */
|
|
137
|
+
enteredAt: number
|
|
138
|
+
/** Epoch ms when this phase was left. Undefined while still in this phase. */
|
|
139
|
+
leftAt?: number
|
|
140
|
+
/** Attempt number at the time of entering this phase. */
|
|
141
|
+
attempt: number
|
|
142
|
+
/** Safe error summary if this phase ended in failure. Cause is stripped. */
|
|
143
|
+
error?: StepError | undefined
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export namespace Api {
|
|
147
|
+
/** Context passed to every backend API method alongside the call-specific args. */
|
|
148
|
+
export type Context<P extends string = string, M extends Intent.Map = Intent.Map> = {
|
|
149
|
+
/** Client-side identifier for this file item. */
|
|
150
|
+
localId: string
|
|
151
|
+
/** Upload purpose. */
|
|
152
|
+
purpose: P
|
|
153
|
+
/** File identity: name, size, MIME type, checksum. Always present. */
|
|
154
|
+
fingerprint: FingerprintFile
|
|
155
|
+
/** Which attempt this is, starting at 1. Increments on retry. */
|
|
156
|
+
attempt: number
|
|
157
|
+
/** Epoch ms when this item was registered. */
|
|
158
|
+
createdAt: number
|
|
159
|
+
/** Phase history up to this call. Errors have cause stripped. */
|
|
160
|
+
steps: readonly UploadStep[]
|
|
161
|
+
/** Caller-supplied metadata from the addFiles command. Never persisted. */
|
|
162
|
+
meta: Record<string, unknown>
|
|
163
|
+
/** Safe error from the most recent failed step, if any. */
|
|
164
|
+
lastError?: StepError | undefined
|
|
165
|
+
/** Browser File object. Present in most phases; absent when file is not loaded. */
|
|
166
|
+
file?: File | undefined
|
|
167
|
+
/** Upload intent from the backend. Present after createIntent succeeds. */
|
|
168
|
+
intent?: Intent.Any<M> | undefined
|
|
169
|
+
/** Cancellation signal. */
|
|
170
|
+
signal?: AbortSignal | undefined
|
|
165
171
|
}
|
|
166
172
|
|
|
167
|
-
/**
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
/** Context for API calls where intent is not yet created but file is loaded (e.g. createIntent) */
|
|
174
|
+
export type CreateIntentContext<P extends string, M extends Intent.Map> = Context<P, M> &
|
|
175
|
+
Required<Pick<Context<P, M>, 'file' | 'signal'>>
|
|
176
|
+
|
|
177
|
+
/** Context for API calls verifying file checksum (e.g. findByChecksum) */
|
|
178
|
+
export type FindByChecksumContext<P extends string, M extends Intent.Map> = Context<P, M> &
|
|
179
|
+
Required<Pick<Context<P, M>, 'file'>>
|
|
180
|
+
|
|
181
|
+
/** Context for API calls executing during/after upload where intent is active (e.g. complete, signPart, completeMultipart) */
|
|
182
|
+
export type CompleteContext<P extends string, M extends Intent.Map> = Context<P, M> &
|
|
183
|
+
Required<Pick<Context<P, M>, 'file' | 'intent' | 'signal'>>
|
|
184
|
+
|
|
185
|
+
/** Context for API abort calls where intent is active but file/signal might be absent (e.g. abort) */
|
|
186
|
+
export type AbortContext<P extends string, M extends Intent.Map> = Context<P, M> &
|
|
187
|
+
Required<Pick<Context<P, M>, 'intent'>>
|
|
188
|
+
|
|
189
|
+
/** Contract definition for your backend API endpoints. */
|
|
190
|
+
export type Me<M extends Intent.Map, P extends string, R extends Result.Base = Result.Base> = {
|
|
191
|
+
/** Requests an upload intent from the server. */
|
|
192
|
+
createIntent(
|
|
193
|
+
args: {
|
|
194
|
+
purpose: P
|
|
195
|
+
contentType: string
|
|
196
|
+
size: number
|
|
197
|
+
filename: string
|
|
198
|
+
checksum?: string
|
|
199
|
+
attempt: number
|
|
200
|
+
},
|
|
201
|
+
ctx: CreateIntentContext<P, M>,
|
|
202
|
+
...extra: any[]
|
|
203
|
+
): Promise<M[keyof M]>
|
|
204
|
+
|
|
205
|
+
/** Finalizes the upload on the server after bytes have been transferred. */
|
|
206
|
+
complete(
|
|
207
|
+
args: {
|
|
208
|
+
fileId: string
|
|
209
|
+
/** Sanitized filename. Do not use the raw File.name from the browser. */
|
|
210
|
+
filename: string
|
|
211
|
+
contentType: string
|
|
212
|
+
size: number
|
|
213
|
+
checksum?: string
|
|
214
|
+
attempt: number
|
|
215
|
+
},
|
|
216
|
+
ctx: CompleteContext<P, M>,
|
|
217
|
+
...extra: any[]
|
|
218
|
+
): Promise<R>
|
|
219
|
+
|
|
220
|
+
/** Optional endpoint to fetch a signed preview/access URL for completed resources. */
|
|
221
|
+
getSignedPreviewUrl?(
|
|
222
|
+
args: { fileId: string; key: string; purpose: P },
|
|
223
|
+
ctx: Context<P, M>,
|
|
224
|
+
...extra: any[]
|
|
225
|
+
): Promise<string>
|
|
226
|
+
|
|
227
|
+
/** Optional endpoint to verify if the file has already been uploaded previously. */
|
|
228
|
+
findByChecksum?(
|
|
229
|
+
args: { checksum: string; purpose: P },
|
|
230
|
+
ctx: FindByChecksumContext<P, M>,
|
|
231
|
+
...extra: any[]
|
|
232
|
+
): Promise<R | null>
|
|
233
|
+
|
|
234
|
+
/** Multipart-specific backend commands. Required if using the multipart strategy. */
|
|
235
|
+
multipart?: {
|
|
236
|
+
signPart(
|
|
237
|
+
args: {
|
|
238
|
+
fileId: string
|
|
239
|
+
uploadId: string
|
|
240
|
+
partNumber: number
|
|
241
|
+
checksum?: string
|
|
242
|
+
attempt: number
|
|
243
|
+
},
|
|
244
|
+
ctx: CompleteContext<P, M>,
|
|
245
|
+
...extra: any[]
|
|
246
|
+
): Promise<{
|
|
247
|
+
url: string
|
|
248
|
+
headers?: Record<string, string>
|
|
249
|
+
}>
|
|
250
|
+
|
|
251
|
+
completeMultipart(
|
|
252
|
+
args: {
|
|
253
|
+
fileId: string
|
|
254
|
+
uploadId: string
|
|
255
|
+
parts: Array<{ partNumber: number; etag: string }>
|
|
256
|
+
},
|
|
257
|
+
ctx: CompleteContext<P, M>,
|
|
258
|
+
...extra: any[]
|
|
259
|
+
): Promise<unknown>
|
|
260
|
+
|
|
261
|
+
listParts?(
|
|
262
|
+
args: { fileId: string; uploadId: string },
|
|
263
|
+
ctx: CompleteContext<P, M>,
|
|
264
|
+
...extra: any[]
|
|
265
|
+
): Promise<Array<{ partNumber: number; etag?: string; size?: number }>>
|
|
266
|
+
|
|
267
|
+
abort?(args: { fileId: string; uploadId: string }, ctx: AbortContext<P, M>, ...extra: any[]): Promise<void>
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** TUS resumable protocol operations. */
|
|
271
|
+
tus?: {
|
|
272
|
+
create(
|
|
273
|
+
args: { fileId: string; size: number; filename: string; contentType: string; attempt: number },
|
|
274
|
+
ctx: CompleteContext<P, M>,
|
|
275
|
+
...extra: any[]
|
|
276
|
+
): Promise<{ uploadUrl: string }>
|
|
277
|
+
|
|
278
|
+
getOffset(args: { uploadUrl: string }, ctx: CompleteContext<P, M>, ...extra: any[]): Promise<{ offset: number }>
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export namespace Strategy {
|
|
284
|
+
/** Per-call context handed to a strategy's `start()` method. */
|
|
285
|
+
export type Ctx<
|
|
286
|
+
M extends Intent.Map,
|
|
287
|
+
C extends Cursor.Map<M>,
|
|
288
|
+
P extends string,
|
|
289
|
+
R extends Result.Base,
|
|
290
|
+
K extends keyof M & string,
|
|
291
|
+
> = {
|
|
292
|
+
localId: string
|
|
293
|
+
purpose: P
|
|
294
|
+
file: File
|
|
295
|
+
fingerprint: FingerprintFile
|
|
296
|
+
intent: M[K]
|
|
297
|
+
signal: AbortSignal
|
|
298
|
+
attempt: number
|
|
299
|
+
createdAt: number
|
|
300
|
+
steps: readonly UploadStep[]
|
|
301
|
+
lastError?: StepError | undefined
|
|
302
|
+
meta: Record<string, unknown>
|
|
303
|
+
transport: {
|
|
304
|
+
put(args: {
|
|
305
|
+
url: string
|
|
306
|
+
body: Blob
|
|
307
|
+
headers?: Record<string, string>
|
|
308
|
+
signal: AbortSignal
|
|
309
|
+
onProgress?: (u: number, t: number) => void
|
|
310
|
+
}): Promise<{ etag?: string | undefined; headers?: Record<string, string> }>
|
|
311
|
+
|
|
312
|
+
postForm(args: {
|
|
313
|
+
url: string
|
|
314
|
+
fields: Record<string, string>
|
|
315
|
+
file: File | Blob
|
|
316
|
+
filename?: string
|
|
317
|
+
signal: AbortSignal
|
|
318
|
+
onProgress?: (uploadedBytes: number, totalBytes: number) => void
|
|
319
|
+
}): Promise<{ etag?: string; headers?: Record<string, string> }>
|
|
320
|
+
|
|
321
|
+
patch(args: {
|
|
322
|
+
url: string
|
|
323
|
+
body: Blob | ArrayBuffer
|
|
324
|
+
headers?: Record<string, string>
|
|
325
|
+
signal: AbortSignal
|
|
326
|
+
onProgress?: (u: number, t: number) => void
|
|
327
|
+
}): Promise<{ headers?: Record<string, string> }>
|
|
328
|
+
|
|
329
|
+
get?(args: {
|
|
330
|
+
url: string
|
|
331
|
+
headers?: Record<string, string>
|
|
332
|
+
signal: AbortSignal
|
|
333
|
+
onProgress?: (loaded: number, total: number) => void
|
|
334
|
+
}): Promise<{ blob: Blob; status: number; headers?: Record<string, string> }>
|
|
335
|
+
|
|
336
|
+
head?(args: {
|
|
337
|
+
url: string
|
|
338
|
+
headers?: Record<string, string>
|
|
339
|
+
signal: AbortSignal
|
|
340
|
+
}): Promise<{ status: number; headers: Record<string, string> }>
|
|
341
|
+
}
|
|
342
|
+
api: Api.Me<M, P, R>
|
|
343
|
+
reportProgress: (p: { uploadedBytes: number; totalBytes: number }) => void
|
|
344
|
+
readCursor: () => C[K] | undefined
|
|
345
|
+
persistCursor: (cursor: C[K] | undefined) => void
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** A single upload strategy implementation. */
|
|
349
|
+
export type Me<
|
|
350
|
+
M extends Intent.Map,
|
|
351
|
+
C extends Cursor.Map<M>,
|
|
352
|
+
P extends string,
|
|
353
|
+
R extends Result.Base,
|
|
354
|
+
K extends keyof M & string,
|
|
355
|
+
> = {
|
|
356
|
+
id: K
|
|
357
|
+
resumable: boolean
|
|
358
|
+
start(ctx: Ctx<M, C, P, R, K>): Promise<void>
|
|
359
|
+
}
|
|
174
360
|
|
|
175
|
-
|
|
176
|
-
|
|
361
|
+
/** Registry of available upload strategies, keyed by strategy id. */
|
|
362
|
+
export type Registry<
|
|
363
|
+
M extends Intent.Map,
|
|
364
|
+
C extends Cursor.Map<M>,
|
|
365
|
+
P extends string = string,
|
|
366
|
+
R extends Result.Base = Result.Base,
|
|
367
|
+
> = {
|
|
368
|
+
get<K extends keyof M & string>(id: K): Me<M, C, P, R, K> | undefined
|
|
369
|
+
has(id: string): id is keyof M & string
|
|
370
|
+
set<K extends keyof M & string>(strategy: Me<M, C, P, R, K>): void
|
|
177
371
|
}
|
|
178
372
|
}
|
|
179
373
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export * from './fingerprint.types'
|
|
5
|
-
export * from './intent.types'
|
|
6
|
-
export * from './result.types'
|
|
7
|
-
export * from './strategy.types'
|
|
8
|
-
export * from './transport'
|
|
1
|
+
export type { Contracts } from './contracts.types'
|
|
2
|
+
export type { Transport } from './transport'
|
|
3
|
+
export { createFetchTransport, createXHRTransport } from './transport'
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export { createXHRTransport } from './transport'
|
|
2
|
+
export { createFetchTransport } from './transport.fetch'
|
|
3
|
+
export type { Transport } from './transport.types'
|