@gentleduck/upload 0.2.2 → 0.2.5

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.
@@ -22,7 +22,12 @@ export function serializeSnapshot<
22
22
  if (!hasIntent(item)) continue
23
23
 
24
24
  // Do not persist terminal items. Persistence is for resuming and recovery, not history.
25
- if ((item.phase as string) === 'completed' || (item.phase as string) === 'canceled' || (item.phase as string) === 'error') continue
25
+ if (
26
+ (item.phase as string) === 'completed' ||
27
+ (item.phase as string) === 'canceled' ||
28
+ (item.phase as string) === 'error'
29
+ )
30
+ continue
26
31
 
27
32
  const cursor = hasCursor(item) ? item.cursor : undefined
28
33
 
@@ -1,4 +1,5 @@
1
1
  import type { TypedEmitter } from './emitter.types'
2
+
2
3
  export type { TypedEmitter } from './emitter.types'
3
4
 
4
5
  /**
@@ -29,7 +30,11 @@ export function createTypedEmitter<E extends Record<string, unknown>>(): TypedEm
29
30
  return {
30
31
  on<K extends keyof E & string>(type: K, cb: (payload: E[K]) => void): () => void {
31
32
  // Create listener set for this event type if it doesn't exist
32
- const set = listeners[type] ?? (listeners[type] = new Set<(payload: E[K]) => void>())
33
+ let set = listeners[type]
34
+ if (!set) {
35
+ set = new Set<(payload: E[K]) => void>()
36
+ listeners[type] = set
37
+ }
33
38
  set.add(cb)
34
39
 
35
40
  // Return unsubscribe function
@@ -52,6 +52,31 @@ export function UploadProvider<
52
52
  return <UploadContext.Provider value={store}>{children}</UploadContext.Provider>
53
53
  }
54
54
 
55
+ /**
56
+ * Hook to access the upload store from React context when present.
57
+ *
58
+ * Some higher-level hooks support either an explicit store argument or context.
59
+ * They use this helper so the context read stays unconditional without throwing
60
+ * when a caller intentionally passes a store instance.
61
+ */
62
+ export function useOptionalUploadStore<
63
+ M extends IntentMap,
64
+ C extends CursorMap<M>,
65
+ P extends string,
66
+ R extends UploadResultBase = UploadResultBase,
67
+ >(): UploadStore<M, C, P, R> | null {
68
+ const store = useContext(UploadContext)
69
+ if (store === null) {
70
+ return null
71
+ }
72
+
73
+ if (!isUploadStore<M, C, P, R>(store)) {
74
+ throw new Error('UploadProvider received an invalid store value')
75
+ }
76
+
77
+ return store
78
+ }
79
+
55
80
  /**
56
81
  * Hook to access the upload store from React context.
57
82
  *
@@ -74,8 +99,8 @@ export function useUploadStore<
74
99
  P extends string,
75
100
  R extends UploadResultBase = UploadResultBase,
76
101
  >(): UploadStore<M, C, P, R> {
77
- const store = useContext(UploadContext)
78
- if (!store || !isUploadStore<M, C, P, R>(store)) {
102
+ const store = useOptionalUploadStore<M, C, P, R>()
103
+ if (!store) {
79
104
  throw new Error('useUploadStore must be used within UploadProvider')
80
105
  }
81
106
  return store
@@ -11,7 +11,7 @@ import type {
11
11
  UploadResultBase,
12
12
  } from '../core'
13
13
  import type { UploadStore } from '../core/engine/store'
14
- import { useUploadStore } from './upload-provider'
14
+ import { useOptionalUploadStore, useUploadStore } from './upload-provider'
15
15
 
16
16
  export function createUploadFactory<
17
17
  M extends IntentMap,
@@ -46,6 +46,22 @@ type Uploader<M extends IntentMap, C extends CursorMap<M>, P extends string, R e
46
46
  ready: UploadItem<M, C, P, R>[]
47
47
  }
48
48
 
49
+ /**
50
+ * Stable action surface for React consumers that need imperative store access.
51
+ *
52
+ * This is declared explicitly instead of relying on an inferred object return
53
+ * type so the generated package declarations do not leak the internal generic
54
+ * names created by `store.on.bind(store)`.
55
+ */
56
+ export type UploaderActions<
57
+ M extends IntentMap,
58
+ C extends CursorMap<M>,
59
+ P extends string,
60
+ R extends UploadResultBase = UploadResultBase,
61
+ > = Pick<UploadStore<M, C, P, R>, 'dispatch' | 'on'> & {
62
+ store: UploadStore<M, C, P, R>
63
+ }
64
+
49
65
  export function useUploader<
50
66
  M extends IntentMap,
51
67
  C extends CursorMap<M>,
@@ -63,8 +79,13 @@ export function useUploader<
63
79
  C extends CursorMap<M>,
64
80
  P extends string,
65
81
  R extends UploadResultBase = UploadResultBase,
66
- >(_store?: UploadStore<M, C, P, R> | undefined) {
67
- const store = _store ?? useUploadStore<M, C, P, R>()
82
+ >(providedStore?: UploadStore<M, C, P, R> | undefined) {
83
+ const contextStore = useOptionalUploadStore<M, C, P, R>()
84
+ const store = providedStore ?? contextStore
85
+ if (!store) {
86
+ throw new Error('useUploader must be used within UploadProvider when no store argument is provided')
87
+ }
88
+
68
89
  const snapshot = React.useSyncExternalStore(
69
90
  store.subscribe.bind(store),
70
91
  store.getSnapshot.bind(store),
@@ -105,7 +126,7 @@ export function useUploaderActions<
105
126
  C extends CursorMap<M>,
106
127
  P extends string,
107
128
  R extends UploadResultBase = UploadResultBase,
108
- >() {
129
+ >(): UploaderActions<M, C, P, R> {
109
130
  const store = useUploadStore<M, C, P, R>()
110
131
 
111
132
  const dispatch = React.useMemo(() => store.dispatch.bind(store), [store])
@@ -160,7 +160,7 @@ export function multipartStrategy<
160
160
  .map(([partNumber, v]) => ({ partNumber, etag: v.etag, size: v.size }))
161
161
  .sort((a, b) => a.partNumber - b.partNumber),
162
162
  }
163
- ctx.persistCursor(snapshot as C["multipart"])
163
+ ctx.persistCursor(snapshot as C['multipart'])
164
164
  report()
165
165
  } catch (err) {
166
166
  inflightBytes.delete(p.partNumber)
@@ -236,7 +236,7 @@ export function multipartStrategy<
236
236
  .sort((a, b) => a.partNumber - b.partNumber),
237
237
  completed: true,
238
238
  }
239
- ctx.persistCursor(snapshot as C["multipart"])
239
+ ctx.persistCursor(snapshot as C['multipart'])
240
240
  }
241
241
  },
242
242
  }