@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.
Files changed (136) hide show
  1. package/README.md +170 -193
  2. package/dist/core/index.d.ts +2 -3
  3. package/dist/core/index.js +1 -1
  4. package/dist/core-BVgloTiP.js +10 -0
  5. package/dist/index-DCqyBVcx.d.ts +1441 -0
  6. package/dist/index-DsZHPmJm.d.ts +225 -0
  7. package/dist/index-KROXtLQh.d.ts +281 -0
  8. package/dist/index.d.ts +4 -5
  9. package/dist/index.js +1 -1
  10. package/dist/react/index.d.ts +2 -51
  11. package/dist/react/index.js +1 -1
  12. package/dist/react-euPrDv24.js +1 -0
  13. package/dist/strategies/index.d.ts +2 -56
  14. package/dist/strategies/index.js +1 -1
  15. package/dist/strategies-CUF0s68j.js +1 -0
  16. package/package.json +3 -2
  17. package/src/{__test__ → __tests__}/emitter.test.ts +4 -4
  18. package/src/{__test__ → __tests__}/fingerprint.test.ts +1 -1
  19. package/src/{__test__ → __tests__}/guards.test.ts +15 -5
  20. package/src/__tests__/id.test.ts +67 -0
  21. package/src/__tests__/mime-sniff.test.ts +142 -0
  22. package/src/{__test__ → __tests__}/persistence.test.ts +43 -8
  23. package/src/__tests__/sanitize-filename.test.ts +109 -0
  24. package/src/__tests__/sleep.test.ts +34 -0
  25. package/src/__tests__/static-analysis.test.ts +145 -0
  26. package/src/{__test__ → __tests__}/store-libs.test.ts +57 -3
  27. package/src/__tests__/transport-fetch.test.ts +108 -0
  28. package/src/{__test__ → __tests__}/transport.test.ts +5 -5
  29. package/src/core/__tests__/errors.test.ts +152 -0
  30. package/src/core/client.ts +3 -0
  31. package/src/core/contracts/contracts.types.ts +335 -141
  32. package/src/core/contracts/index.ts +3 -8
  33. package/src/core/contracts/transport/index.ts +3 -2
  34. package/src/core/contracts/transport/transport.fetch.ts +203 -0
  35. package/src/core/contracts/transport/transport.libs.ts +7 -8
  36. package/src/core/contracts/transport/transport.ts +91 -83
  37. package/src/core/contracts/transport/transport.types.ts +71 -35
  38. package/src/core/engine/__tests__/config-nan-bypass.test.ts +40 -0
  39. package/src/core/engine/engine.libs.ts +35 -0
  40. package/src/core/engine/engine.types.ts +367 -241
  41. package/src/core/engine/index.ts +3 -8
  42. package/src/core/engine/reducer.ts +89 -43
  43. package/src/core/engine/store/handlers/add-file.ts +39 -29
  44. package/src/core/engine/store/handlers/cancel.ts +13 -8
  45. package/src/core/engine/store/handlers/clean-up.ts +11 -8
  46. package/src/core/engine/store/handlers/create-intent.ts +82 -27
  47. package/src/core/engine/store/handlers/finalize.ts +30 -13
  48. package/src/core/engine/store/handlers/index.ts +7 -7
  49. package/src/core/engine/store/handlers/pause.ts +8 -6
  50. package/src/core/engine/store/handlers/run-upload.ts +49 -34
  51. package/src/core/engine/store/index.ts +27 -21
  52. package/src/core/engine/store/store.dispatch.ts +10 -8
  53. package/src/core/engine/store/store.libs.ts +184 -41
  54. package/src/core/engine/store/store.runtime.ts +45 -36
  55. package/src/core/engine/store/store.schedule.ts +27 -21
  56. package/src/core/engine/store/store.types.ts +177 -227
  57. package/src/core/engine/validation/__tests__/intent.test.ts +70 -0
  58. package/src/core/engine/validation/file.ts +7 -3
  59. package/src/core/engine/validation/index.ts +1 -0
  60. package/src/core/engine/validation/intent.ts +49 -27
  61. package/src/core/engine/validation/list.ts +14 -15
  62. package/src/core/engine/validation/mime.ts +39 -0
  63. package/src/core/errors.ts +271 -0
  64. package/src/core/index.ts +28 -5
  65. package/src/core/persistence/__tests__/deserialize-nan-bypass.test.ts +100 -0
  66. package/src/core/persistence/__tests__/persistence.test.ts +85 -0
  67. package/src/core/persistence/adapters.indexeddb.ts +9 -3
  68. package/src/core/persistence/adapters.local.ts +9 -3
  69. package/src/core/persistence/adapters.memory.ts +35 -13
  70. package/src/core/persistence/index.ts +5 -5
  71. package/src/core/persistence/persistence.ts +73 -60
  72. package/src/core/persistence/persistence.types.ts +140 -60
  73. package/src/core/utils/__tests__/guards.test.ts +93 -0
  74. package/src/core/utils/__tests__/url-safety.test.ts +112 -0
  75. package/src/core/utils/async.ts +7 -2
  76. package/src/core/utils/constants.ts +3 -9
  77. package/src/core/utils/emitter/emitter.ts +7 -24
  78. package/src/core/utils/emitter/emitter.types.ts +33 -16
  79. package/src/core/utils/emitter/index.ts +2 -74
  80. package/src/core/utils/fingerprint.ts +16 -5
  81. package/src/core/utils/guards.ts +91 -1
  82. package/src/core/utils/hash/__tests__/sha256.test.ts +61 -0
  83. package/src/core/utils/hash/index.ts +126 -0
  84. package/src/core/utils/hash/sha256.ts +179 -0
  85. package/src/core/utils/id.ts +33 -1
  86. package/src/core/utils/index.ts +15 -6
  87. package/src/core/utils/mime-sniff.ts +119 -0
  88. package/src/core/utils/sanitize-filename.ts +85 -0
  89. package/src/core/utils/url-safety.ts +156 -0
  90. package/src/react/__tests__/use-dropzone.test.ts +64 -0
  91. package/src/react/index.ts +5 -2
  92. package/src/react/upload-provider.tsx +68 -53
  93. package/src/react/uploader.types.ts +58 -61
  94. package/src/react/use-dropzone.tsx +226 -0
  95. package/src/react/use-uploader.tsx +98 -91
  96. package/src/strategies/_shared/__tests__/retry.test.ts +120 -0
  97. package/src/strategies/_shared/retry.ts +79 -0
  98. package/src/strategies/index.ts +9 -15
  99. package/src/strategies/multipart/__tests__/multipart.test.ts +229 -0
  100. package/src/strategies/multipart/index.ts +206 -108
  101. package/src/strategies/post/__tests__/post-url-safety.test.ts +119 -0
  102. package/src/strategies/post/index.ts +115 -33
  103. package/src/strategies/put/__tests__/put.test.ts +132 -0
  104. package/src/strategies/put/index.ts +139 -0
  105. package/src/strategies/registry/index.ts +12 -9
  106. package/src/strategies/tus/__tests__/tus.test.ts +159 -0
  107. package/src/strategies/tus/index.ts +211 -0
  108. package/dist/async-DxRWu4le.js +0 -1
  109. package/dist/core-CMkrP9DX.js +0 -2
  110. package/dist/index-C38Jmizu.d.ts +0 -1005
  111. package/dist/strategy.types-gg9ZStRe.d.ts +0 -225
  112. package/src/__test__/effect-pool.test.ts +0 -5
  113. package/src/__test__/id.test.ts +0 -14
  114. package/src/__test__/store-integration.test.ts +0 -5
  115. package/src/core/client/client.ts +0 -50
  116. package/src/core/client/client.types.ts +0 -184
  117. package/src/core/client/config.types.ts +0 -77
  118. package/src/core/client/index.ts +0 -4
  119. package/src/core/client/plugins.types.ts +0 -46
  120. package/src/core/client/retry.types.ts +0 -6
  121. package/src/core/client/uploadClient.ts +0 -19
  122. package/src/core/contracts/backendApi.ts +0 -62
  123. package/src/core/contracts/cursor.types.ts +0 -23
  124. package/src/core/contracts/errors.types.ts +0 -67
  125. package/src/core/contracts/fingerprint.types.ts +0 -18
  126. package/src/core/contracts/intent.types.ts +0 -39
  127. package/src/core/contracts/result.types.ts +0 -11
  128. package/src/core/contracts/strategy.types.ts +0 -45
  129. package/src/core/engine/commands.types.ts +0 -17
  130. package/src/core/engine/event-map.types.ts +0 -45
  131. package/src/core/engine/internal-events.types.ts +0 -202
  132. package/src/core/engine/outcome.types.ts +0 -9
  133. package/src/core/engine/progress.types.ts +0 -15
  134. package/src/core/engine/store/handlers.ts +0 -819
  135. package/src/core/engine/validation.ts +0 -216
  136. /package/src/{__test__ → __tests__}/transport-libs.test.ts +0 -0
@@ -1,62 +0,0 @@
1
- import type { IntentMap } from './intent.types'
2
- import type { UploadResultBase } from './result.types'
3
-
4
- export type WithSignal = { signal?: AbortSignal }
5
-
6
- // Backend API contract (typed, injected)
7
- export interface UploadApi<M extends IntentMap, P extends string, R extends UploadResultBase = UploadResultBase> {
8
- // Create upload intent - backend decides strategy, bucket, key
9
- createIntent(
10
- args: {
11
- purpose: P
12
- contentType: string
13
- size: number
14
- filename: string
15
- checksum?: string
16
- },
17
- opts?: WithSignal,
18
- ): Promise<M[keyof M]>
19
-
20
- // Finalize upload (mark as complete in DB, return metadata)
21
- complete(args: { fileId: string }, opts?: WithSignal): Promise<R>
22
-
23
- // Get signed preview URL
24
- getSignedPreviewUrl?(args: { fileId: string; key: string; purpose: P }, opts?: WithSignal): Promise<string>
25
-
26
- /**
27
- * Optional support for finding existing files by checksum for deduplication.
28
- * If implemented, the upload engine will check for existing files before uploading.
29
- * If a file with the same checksum exists, the upload will be skipped and the existing file will be linked.
30
- */
31
- findByChecksum?(args: { checksum: string; purpose: P }, opts?: WithSignal): Promise<R | null>
32
-
33
- // Multipart-specific operations (optional, for multipart strategy)
34
- multipart?: {
35
- signPart(
36
- args: { fileId: string; uploadId: string; partNumber: number; checksum?: string },
37
- opts?: WithSignal,
38
- ): Promise<{ url: string; headers?: Record<string, string> }>
39
-
40
- completeMultipart(
41
- args: { fileId: string; uploadId: string; parts: Array<{ partNumber: number; etag: string }> },
42
- opts?: WithSignal,
43
- ): Promise<unknown>
44
-
45
- listParts?(
46
- args: { fileId: string; uploadId: string },
47
- opts?: WithSignal,
48
- ): Promise<Array<{ partNumber: number; etag?: string; size?: number }>>
49
-
50
- abort?(args: { fileId: string; uploadId: string }, opts?: WithSignal): Promise<void>
51
- }
52
-
53
- // TUS-specific operations (optional, for tus strategy)
54
- tus?: {
55
- create(
56
- args: { fileId: string; size: number; filename: string; contentType: string },
57
- opts?: WithSignal,
58
- ): Promise<{ uploadUrl: string }>
59
-
60
- getOffset(args: { uploadUrl: string }, opts?: WithSignal): Promise<{ offset: number }>
61
- }
62
- }
@@ -1,23 +0,0 @@
1
- import type { IntentMap, StrategyKey } from './intent.types'
2
-
3
- /**
4
- * Registry of cursor payload types mapped by strategy name.
5
- *
6
- * A "cursor" is strategy-defined resume state (ex: multipart uploaded parts, next byte offset).
7
- * Use `unknown` here and define concrete cursor payload types in your app.
8
- *
9
- * @typeParam M - The intent map, used to constrain strategy keys.
10
- */
11
- export type CursorMap<M extends IntentMap> = Partial<Record<StrategyKey<M>, unknown>>
12
-
13
- /**
14
- * Discriminated union of all cursor variants.
15
- *
16
- * The cursor is always tagged with a `strategy` so it can be routed back to the correct strategy
17
- * on resume/rebind.
18
- *
19
- * @typeParam C - Cursor registry (usually {@link CursorMap}).
20
- */
21
- export type AnyCursor<C extends Record<string, unknown>> = {
22
- [K in keyof C & string]: { strategy: K; value?: C[K] }
23
- }[keyof C & string]
@@ -1,67 +0,0 @@
1
- /**
2
- * File rejection reasons produced by client-side validation.
3
- *
4
- * These should be safe to show in UI.
5
- */
6
- export type RejectReason =
7
- | { code: 'empty_file' }
8
- | { code: 'file_too_large'; maxBytes: number; size: number }
9
- | { code: 'type_not_allowed'; allowed: string[]; got: string }
10
- | { code: 'too_many_files'; max: number }
11
-
12
- /**
13
- * Base interface for custom errors.
14
- *
15
- * If you want to add domain-specific fields, extend this and intersect in {@link UploadError}.
16
- */
17
- export interface UploadErrorBase {
18
- /** Stable error code. */
19
- code: string
20
- /** Human-friendly message (safe for UI). */
21
- message: string
22
- /** Optional cause (original error, response, etc). */
23
- cause?: unknown
24
- /** Whether the error should be retried. */
25
- retryable?: boolean
26
- }
27
-
28
- /**
29
- * Built-in engine errors.
30
- *
31
- * Keep these narrow and explicit so reducer logic can rely on `code` as a discriminant.
32
- */
33
- export type BuiltInUploadError =
34
- | { code: 'validation_failed'; message: string; reason: RejectReason; retryable?: false }
35
- | { code: 'strategy_missing'; message: string; strategy: string; retryable?: false }
36
- | { code: 'aborted'; message: string; reason: 'pause' | 'cancel' | 'unknown'; cause?: unknown; retryable?: false }
37
- | { code: 'network'; message: string; cause?: unknown; retryable?: boolean }
38
- | { code: 'http'; message: string; status: number; statusText?: string; cause?: unknown; retryable?: boolean }
39
- | { code: 'timeout'; message: string; cause?: unknown; retryable?: boolean }
40
- | { code: 'auth'; message: string; cause?: unknown; retryable?: false }
41
- | { code: 'rate_limit'; message: string; retryAfterMs?: number; cause?: unknown; retryable?: boolean }
42
- | { code: 'server'; message: string; serverCode?: string; cause?: unknown; retryable?: boolean }
43
- | { code: 'unknown'; message: string; cause?: unknown; retryable?: boolean }
44
-
45
- /**
46
- * The error type used everywhere in the engine.
47
- *
48
- * You can extend this by intersecting your own shape with {@link UploadErrorBase}
49
- * to add fields (example: `{ endpoint: string }`).
50
- */
51
- export type UploadError = BuiltInUploadError | (UploadErrorBase & Record<string, unknown>)
52
-
53
- /**
54
- * Validation rules applied on `addFiles` before an item enters the upload pipeline.
55
- */
56
- export type UploadValidationRules = {
57
- /** Maximum number of files allowed. */
58
- maxFiles?: number
59
- /** Maximum file size in bytes. */
60
- maxSizeBytes?: number
61
- /** Minimum file size in bytes. */
62
- minSizeBytes?: number
63
- /** Allowed MIME types. */
64
- allowedTypes?: string[]
65
- /** Allowed file extensions. */
66
- allowedExtensions?: string[]
67
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Deterministic client-side identity for a file.
3
- *
4
- * Used to match the same file across refreshes (for rebind) and to deduplicate.
5
- * If you include `checksum`, it should be stable and computed consistently.
6
- */
7
- export type FileFingerprint = {
8
- /** File name. */
9
- name: string
10
- /** File size in bytes. */
11
- size: number
12
- /** File MIME type. */
13
- type: string
14
- /** File lastModified timestamp (ms). */
15
- lastModified: number
16
- /** Optional checksum (example: SHA-256 hex). */
17
- checksum?: string
18
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * Base shape for all upload intents.
3
- *
4
- * An "intent" is data provided by your backend that tells the client how to upload a file
5
- * (for example: a presigned POST, presigned PUT, multipart upload IDs, part URLs, headers, etc).
6
- *
7
- * Every intent must include:
8
- * - `strategy`: a discriminant used to select an upload strategy implementation
9
- * - `fileId`: a stable backend identifier for the file being uploaded
10
- *
11
- * @typeParam K - Strategy key / discriminant string (example: `"direct" | "multipart"`).
12
- */
13
- export type IntentBase<K extends string = string> = {
14
- /** Strategy name (discriminant). */
15
- strategy: K
16
- /** Unique backend file identifier. */
17
- fileId: string
18
- }
19
-
20
- /**
21
- * Registry of intent types mapped by strategy name.
22
- *
23
- * Example:
24
- * ```ts
25
- * type MyIntents = {
26
- * direct: DirectIntent
27
- * multipart: MultipartIntent
28
- * }
29
- * ```
30
- *
31
- * `M[keyof M]` is used throughout the engine as the "any intent" union.
32
- */
33
- export type IntentMap = Record<string, IntentBase<string>>
34
-
35
- /** Union of strategy keys for an intent map. */
36
- export type StrategyKey<M extends IntentMap> = keyof M & string
37
-
38
- /** Union of all intent variants for an intent map. */
39
- export type AnyIntent<M extends IntentMap> = M[StrategyKey<M>]
@@ -1,11 +0,0 @@
1
- /**
2
- * Base shape for upload completion results returned by your backend.
3
- *
4
- * Extend this with any additional metadata you return (e.g. width/height, variants).
5
- */
6
- export type UploadResultBase = {
7
- /** Backend file identifier. */
8
- fileId: string
9
- /** Storage key or path used for signed URLs. */
10
- key: string
11
- }
@@ -1,45 +0,0 @@
1
- import type { UploadApi } from './backendApi'
2
- import type { CursorMap } from './cursor.types'
3
- import type { IntentMap } from './intent.types'
4
- import type { UploadResultBase } from './result.types'
5
- import type { UploadTransport } from './transport/transport.types'
6
-
7
- export type StrategyCtx<
8
- M extends IntentMap,
9
- C extends CursorMap<M>,
10
- P extends string,
11
- R extends UploadResultBase,
12
- K extends keyof M & string,
13
- > = {
14
- file: File
15
- intent: M[K]
16
- signal: AbortSignal
17
- transport: UploadTransport
18
- api: UploadApi<M, P, R>
19
- reportProgress: (p: { uploadedBytes: number; totalBytes: number }) => void
20
- readCursor: () => C[K] | undefined
21
- persistCursor: (cursor: C[K] | undefined) => void
22
- }
23
-
24
- export type UploadStrategy<
25
- M extends IntentMap,
26
- C extends CursorMap<M>,
27
- P extends string,
28
- R extends UploadResultBase,
29
- K extends keyof M & string,
30
- > = {
31
- id: K
32
- resumable: boolean
33
- start(ctx: StrategyCtx<M, C, P, R, K>): Promise<void>
34
- }
35
-
36
- export interface StrategyRegistry<
37
- M extends IntentMap,
38
- C extends CursorMap<M>,
39
- P extends string = string,
40
- R extends UploadResultBase = UploadResultBase,
41
- > {
42
- get<K extends keyof M & string>(id: K): UploadStrategy<M, C, P, R, K> | undefined
43
- has(id: string): id is keyof M & string
44
- set<K extends keyof M & string>(strategy: UploadStrategy<M, C, P, R, K>): void
45
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * Public commands accepted by the upload store.
3
- *
4
- * Reducer/effects should treat commands as "user intent".
5
- */
6
- export type UploadCommand<P extends string> =
7
- | { type: 'addFiles'; files: File[]; purpose: P }
8
- | { type: 'start'; localId: string }
9
- | { type: 'startAll'; purpose?: P }
10
- | { type: 'pause'; localId: string }
11
- | { type: 'pauseAll'; purpose?: P }
12
- | { type: 'resume'; localId: string }
13
- | { type: 'cancel'; localId: string }
14
- | { type: 'cancelAll'; purpose?: P }
15
- | { type: 'retry'; localId: string }
16
- | { type: 'rebind'; localId: string; file: File }
17
- | { type: 'remove'; localId: string }
@@ -1,45 +0,0 @@
1
- import type {
2
- AnyCursor,
3
- CursorMap,
4
- FileFingerprint,
5
- IntentMap,
6
- RejectReason,
7
- UploadError,
8
- UploadResultBase,
9
- } from '../contracts'
10
- import type { UploadCompletionKind } from './outcome.types'
11
-
12
- /**
13
- * Public event map emitted to consumers.
14
- *
15
- * Consumers subscribe via {@link TypedEmitter.on}.
16
- */
17
- export type UploadEventMap<
18
- M extends IntentMap,
19
- C extends CursorMap<M>,
20
- P extends string,
21
- R extends UploadResultBase = UploadResultBase,
22
- > = {
23
- 'file.added': { localId: string; purpose: P; file: File; fingerprint: FileFingerprint }
24
- 'file.rejected': { file: File; reason: RejectReason }
25
-
26
- 'validation.ok': { localId: string }
27
- 'validation.failed': { localId: string; reason: RejectReason }
28
-
29
- 'intent.creating': { localId: string }
30
- 'intent.created': { localId: string; intent: M[keyof M] }
31
- 'intent.failed': { localId: string; error: UploadError; retryable: boolean }
32
-
33
- 'upload.queued': { localId: string }
34
- 'upload.resumed': { localId: string }
35
- 'upload.started': { localId: string }
36
- 'upload.progress': { localId: string; pct: number; uploadedBytes: number; totalBytes: number }
37
- 'upload.cursor': { localId: string; cursor: AnyCursor<C> }
38
- 'upload.paused': { localId: string; cursor: AnyCursor<C> }
39
- 'upload.canceled': { localId: string }
40
-
41
- 'upload.completing': { localId: string }
42
- 'upload.completed': { localId: string; result: R; completedBy: UploadCompletionKind }
43
-
44
- 'upload.error': { localId: string; error: UploadError; retryable: boolean }
45
- }
@@ -1,202 +0,0 @@
1
- import type {
2
- AnyCursor,
3
- AnyIntent,
4
- CursorMap,
5
- FileFingerprint,
6
- IntentMap,
7
- RejectReason,
8
- UploadError,
9
- UploadResultBase,
10
- } from '../contracts'
11
- import type { UploadCompletionKind } from './outcome.types'
12
- import type { UploadProgress } from './progress.types'
13
-
14
- /**
15
- * Internal events emitted by effects and consumed by the reducer.
16
- * Distinct from the public event surface — these drive state transitions.
17
- */
18
- export type InternalEvent<
19
- M extends IntentMap,
20
- C extends CursorMap<M>,
21
- P extends string,
22
- R extends UploadResultBase = UploadResultBase,
23
- > =
24
- | {
25
- type: 'files.added'
26
- items: Array<{ localId: string; purpose: P; file: File; fingerprint: FileFingerprint; createdAt: number }>
27
- }
28
- | { type: 'fingerprint.updated'; localId: string; fingerprint: FileFingerprint }
29
- | { type: 'validation.ok'; localId: string }
30
- | { type: 'validation.failed'; localId: string; reason: RejectReason }
31
- | { type: 'intent.ok'; localId: string; intent: AnyIntent<M> }
32
- | { type: 'intent.failed'; localId: string; error: UploadError; retryable: boolean }
33
- | { type: 'upload.begin'; localId: string; startedAt: number }
34
- | { type: 'upload.progress'; localId: string; uploadedBytes: number; totalBytes: number }
35
- | { type: 'cursor.updated'; localId: string; cursor: AnyCursor<C> }
36
- | { type: 'upload.ok'; localId: string }
37
- | { type: 'upload.failed'; localId: string; error: UploadError; retryable: boolean }
38
- | { type: 'dedupe.ok'; localId: string; result: R }
39
- | { type: 'complete.ok'; localId: string; result: R }
40
- | { type: 'complete.failed'; localId: string; error: UploadError; retryable: boolean }
41
- /** Internal completion for pause requests. */
42
- | { type: 'paused'; localId: string; cursor: AnyCursor<C>; pausedAt: number }
43
- /** Internal completion for cancel requests. */
44
- | { type: 'canceled'; localId: string; canceledAt: number }
45
-
46
- /** Every upload phase; the `phase` discriminator of {@link UploadItem}. */
47
- export type UploadPhase =
48
- | 'validating'
49
- | 'creating_intent'
50
- | 'ready'
51
- | 'queued'
52
- | 'uploading'
53
- | 'paused'
54
- | 'completing'
55
- | 'completed'
56
- | 'error'
57
- | 'canceled'
58
-
59
- /** Discriminated union on `phase` describing a single upload's state. */
60
- export type UploadItem<
61
- M extends IntentMap,
62
- C extends CursorMap<M>,
63
- P extends string,
64
- R extends UploadResultBase = UploadResultBase,
65
- > =
66
- | {
67
- /** Validating file against rules. */
68
- phase: 'validating'
69
- localId: string
70
- purpose: P
71
- fingerprint: FileFingerprint
72
- file: File
73
- createdAt: number
74
- }
75
- | {
76
- /** Requesting upload intent from backend. */
77
- phase: 'creating_intent'
78
- localId: string
79
- purpose: P
80
- fingerprint: FileFingerprint
81
- file: File
82
- /** Current attempt number for this phase. */
83
- attempt: number
84
- createdAt: number
85
- }
86
- | {
87
- /** Intent received, ready to start upload. */
88
- phase: 'ready'
89
- localId: string
90
- purpose: P
91
- fingerprint: FileFingerprint
92
- file: File
93
- intent: AnyIntent<M>
94
- createdAt: number
95
- cursor?: AnyCursor<C>
96
- progress?: UploadProgress
97
- attempt?: number
98
- }
99
- | {
100
- /** Queued for upload (waiting for slot). */
101
- phase: 'queued'
102
- localId: string
103
- purpose: P
104
- fingerprint: FileFingerprint
105
- file: File
106
- intent: AnyIntent<M>
107
- /** Timestamp when this item requested a concurrency slot. */
108
- requestedAt: number
109
- createdAt: number
110
- cursor?: AnyCursor<C>
111
- progress?: UploadProgress
112
- attempt?: number
113
- }
114
- | {
115
- /** Actively uploading bytes. */
116
- phase: 'uploading'
117
- localId: string
118
- purpose: P
119
- fingerprint: FileFingerprint
120
- file: File
121
- intent: AnyIntent<M>
122
- startedAt: number
123
- progress: UploadProgress
124
- createdAt: number
125
- cursor?: AnyCursor<C>
126
- attempt?: number
127
- }
128
- | {
129
- /** Upload paused (resumable). */
130
- phase: 'paused'
131
- localId: string
132
- purpose: P
133
- fingerprint: FileFingerprint
134
- intent: AnyIntent<M>
135
- cursor: AnyCursor<C>
136
- progress: UploadProgress
137
- pausedAt: number
138
- createdAt: number
139
- /**
140
- * File is optional because a paused item can be restored from persistence without a File.
141
- * Use `rebind` to attach the File again before resuming (when required).
142
- */
143
- file?: File
144
- attempt?: number
145
- }
146
- | {
147
- /** Bytes sent, finalizing with backend. */
148
- phase: 'completing'
149
- localId: string
150
- purpose: P
151
- fingerprint: FileFingerprint
152
- file: File
153
- intent: AnyIntent<M>
154
- progress: UploadProgress
155
- completingAt: number
156
- createdAt: number
157
- attempt?: number
158
- }
159
- | {
160
- /** Upload completed successfully. */
161
- phase: 'completed'
162
- localId: string
163
- file: File
164
- purpose: P
165
- fingerprint: FileFingerprint
166
- intent?: AnyIntent<M>
167
- completedBy: UploadCompletionKind
168
- result: R
169
- completedAt: number
170
- createdAt: number
171
- attempt?: number
172
- }
173
- | {
174
- /** Upload failed (retryable or final). */
175
- phase: 'error'
176
- localId: string
177
- purpose: P
178
- fingerprint: FileFingerprint
179
- error: UploadError
180
- retryable: boolean
181
- attempt: number
182
- failedAt: number
183
- createdAt: number
184
- file?: File
185
- intent?: AnyIntent<M>
186
- cursor?: AnyCursor<C>
187
- progress?: UploadProgress
188
- }
189
- | {
190
- /** Upload canceled by user/system. */
191
- phase: 'canceled'
192
- localId: string
193
- purpose: P
194
- fingerprint: FileFingerprint
195
- canceledAt: number
196
- createdAt: number
197
- file?: File
198
- intent?: AnyIntent<M>
199
- cursor?: AnyCursor<C>
200
- progress?: UploadProgress
201
- attempt?: number
202
- }
@@ -1,9 +0,0 @@
1
- import type { UploadError, UploadResultBase } from '../contracts'
2
-
3
- export type UploadCompletionKind = 'upload' | 'dedupe'
4
-
5
- export type UploadOutcome<R extends UploadResultBase> =
6
- | { localId: string; status: 'completed'; completedBy: UploadCompletionKind; result: R }
7
- | { localId: string; status: 'error'; error: UploadError }
8
- | { localId: string; status: 'canceled' }
9
- | { localId: string; status: 'missing' }
@@ -1,15 +0,0 @@
1
- /**
2
- * Upload progress snapshot.
3
- *
4
- * Invariants:
5
- * - `totalBytes` should be `file.size` when known.
6
- * - `pct` should be clamped to 0..100.
7
- */
8
- export type UploadProgress = {
9
- /** Bytes uploaded so far. */
10
- uploadedBytes: number
11
- /** Total bytes to upload. */
12
- totalBytes: number
13
- /** Percent complete (0..100). */
14
- pct: number
15
- }