preact-missing-hooks 3.1.0 → 4.1.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 (82) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/.husky/pre-push +1 -0
  3. package/.prettierignore +3 -0
  4. package/.prettierrc +6 -0
  5. package/Readme.md +333 -137
  6. package/dist/entry.cjs +21 -0
  7. package/dist/entry.js +2 -0
  8. package/dist/entry.js.map +1 -0
  9. package/dist/entry.modern.mjs +2 -0
  10. package/dist/entry.modern.mjs.map +1 -0
  11. package/dist/entry.module.js +2 -0
  12. package/dist/entry.module.js.map +1 -0
  13. package/dist/entry.umd.js +2 -0
  14. package/dist/entry.umd.js.map +1 -0
  15. package/dist/index.d.ts +14 -13
  16. package/dist/index.js +1 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.modern.mjs +2 -0
  19. package/dist/index.modern.mjs.map +1 -0
  20. package/dist/index.module.js +1 -1
  21. package/dist/index.module.js.map +1 -1
  22. package/dist/index.umd.js +1 -1
  23. package/dist/index.umd.js.map +1 -1
  24. package/dist/indexedDB/dbController.d.ts +2 -2
  25. package/dist/indexedDB/index.d.ts +6 -6
  26. package/dist/indexedDB/openDB.d.ts +1 -1
  27. package/dist/indexedDB/tableController.d.ts +1 -1
  28. package/dist/indexedDB/types.d.ts +1 -2
  29. package/dist/react.js +1 -0
  30. package/dist/react.modern.mjs +1 -0
  31. package/dist/react.module.js +1 -0
  32. package/dist/react.umd.js +1 -0
  33. package/dist/useEventBus.d.ts +1 -1
  34. package/dist/useIndexedDB.d.ts +3 -3
  35. package/dist/useLLMMetadata.d.ts +71 -0
  36. package/dist/useMutationObserver.d.ts +1 -1
  37. package/dist/useNetworkState.d.ts +3 -3
  38. package/dist/usePreferredTheme.d.ts +1 -1
  39. package/dist/useRageClick.d.ts +1 -1
  40. package/dist/useThreadedWorker.d.ts +1 -1
  41. package/dist/useTransition.d.ts +4 -1
  42. package/dist/useWorkerNotifications.d.ts +1 -1
  43. package/dist/useWrappedChildren.d.ts +3 -3
  44. package/docs/README.md +111 -0
  45. package/docs/index.html +58 -20
  46. package/docs/main.js +49 -0
  47. package/eslint.config.mjs +10 -0
  48. package/package.json +60 -6
  49. package/scripts/generate-entry.cjs +34 -0
  50. package/src/index.ts +14 -13
  51. package/src/indexedDB/dbController.ts +101 -92
  52. package/src/indexedDB/index.ts +16 -11
  53. package/src/indexedDB/openDB.ts +49 -49
  54. package/src/indexedDB/requestToPromise.ts +17 -16
  55. package/src/indexedDB/tableController.ts +331 -257
  56. package/src/indexedDB/types.ts +35 -35
  57. package/src/useClipboard.ts +99 -97
  58. package/src/useEventBus.ts +39 -36
  59. package/src/useIndexedDB.ts +111 -111
  60. package/src/useLLMMetadata.ts +418 -0
  61. package/src/useMutationObserver.ts +26 -26
  62. package/src/useNetworkState.ts +124 -122
  63. package/src/usePreferredTheme.ts +68 -68
  64. package/src/useRageClick.ts +103 -103
  65. package/src/useThreadedWorker.ts +165 -165
  66. package/src/useTransition.ts +22 -19
  67. package/src/useWasmCompute.ts +209 -204
  68. package/src/useWebRTCIP.ts +181 -176
  69. package/src/useWorkerNotifications.ts +28 -20
  70. package/src/useWrappedChildren.ts +72 -58
  71. package/tests/preact-as-react.ts +5 -0
  72. package/tests/react-adapter.tsx +12 -0
  73. package/tests/setup-react.ts +4 -0
  74. package/tests/useClipboard.test.tsx +4 -2
  75. package/tests/useLLMMetadata.test.tsx +149 -0
  76. package/tests/useThreadedWorker.test.tsx +3 -1
  77. package/tests/useWasmCompute.test.tsx +1 -1
  78. package/tests/useWebRTCIP.test.tsx +3 -1
  79. package/vite.config.ts +11 -4
  80. package/vitest.config.preact.ts +21 -0
  81. package/vitest.config.react.ts +36 -0
  82. package/vitest.workspace.ts +6 -0
@@ -1,165 +1,165 @@
1
- import { useState, useCallback, useRef, useEffect } from 'preact/hooks';
2
-
3
- /** Lower number = higher priority. Default priority when not specified. */
4
- const DEFAULT_PRIORITY = 1;
5
-
6
- export type ThreadedWorkerMode = 'sequential' | 'parallel';
7
-
8
- export interface UseThreadedWorkerOptions {
9
- /** Sequential: single worker, priority-ordered. Parallel: worker pool. */
10
- mode: ThreadedWorkerMode;
11
- /** Max concurrent workers. Only used when mode is "parallel". Default 4. */
12
- concurrency?: number;
13
- }
14
-
15
- export interface RunOptions {
16
- /** 1 = highest priority. Lower number runs first. FIFO within same priority. */
17
- priority?: number;
18
- }
19
-
20
- interface QueuedTask<TData, TResult> {
21
- data: TData;
22
- priority: number;
23
- sequence: number;
24
- resolve: (value: TResult) => void;
25
- reject: (reason: unknown) => void;
26
- }
27
-
28
- export interface UseThreadedWorkerReturn<TData, TResult> {
29
- /** Enqueue work. Returns a Promise that resolves with the worker result. */
30
- run: (data: TData, options?: RunOptions) => Promise<TResult>;
31
- /** True while any task is queued or running. */
32
- loading: boolean;
33
- /** Result of the most recently completed successful task. */
34
- result: TResult | undefined;
35
- /** Error from the most recently failed task. */
36
- error: unknown;
37
- /** Number of tasks currently queued + running. */
38
- queueSize: number;
39
- /** Clear all pending (not yet started) tasks. Running tasks continue. */
40
- clearQueue: () => void;
41
- /** Stop accepting new work and clear pending queue. Running tasks finish. */
42
- terminate: () => void;
43
- }
44
-
45
- /**
46
- * Production-grade hook to run async work in a queue with optional priority
47
- * and either sequential or parallel execution.
48
- *
49
- * @param workerFn - Async function to run for each task (e.g. API call, heavy compute).
50
- * @param options - mode: "sequential" | "parallel", concurrency (parallel only).
51
- * @returns run, loading, result, error, queueSize, clearQueue, terminate.
52
- */
53
- export function useThreadedWorker<TData, TResult>(
54
- workerFn: (data: TData) => Promise<TResult>,
55
- options: UseThreadedWorkerOptions
56
- ): UseThreadedWorkerReturn<TData, TResult> {
57
- const { mode, concurrency = 4 } = options;
58
- const maxConcurrent = mode === 'sequential' ? 1 : Math.max(1, concurrency);
59
-
60
- const [loading, setLoading] = useState(false);
61
- const [result, setResult] = useState<TResult | undefined>(undefined);
62
- const [error, setError] = useState<unknown>(undefined);
63
- const [queueSize, setQueueSize] = useState(0);
64
-
65
- const queueRef = useRef<QueuedTask<TData, TResult>[]>([]);
66
- const sequenceRef = useRef(0);
67
- const activeCountRef = useRef(0);
68
- const terminatedRef = useRef(false);
69
- const workerFnRef = useRef(workerFn);
70
- workerFnRef.current = workerFn;
71
-
72
- const updateQueueSize = useCallback(() => {
73
- setQueueSize(queueRef.current.length + activeCountRef.current);
74
- }, []);
75
-
76
- const processNext = useCallback(() => {
77
- if (terminatedRef.current) return;
78
- if (activeCountRef.current >= maxConcurrent) return;
79
- if (queueRef.current.length === 0) {
80
- if (activeCountRef.current === 0) setLoading(false);
81
- updateQueueSize();
82
- return;
83
- }
84
-
85
- // Sort by priority (asc), then by sequence (FIFO within same priority).
86
- queueRef.current.sort((a, b) => {
87
- if (a.priority !== b.priority) return a.priority - b.priority;
88
- return a.sequence - b.sequence;
89
- });
90
- const task = queueRef.current.shift()!;
91
- activeCountRef.current += 1;
92
- setLoading(true);
93
- updateQueueSize();
94
-
95
- const fn = workerFnRef.current;
96
- fn(task.data)
97
- .then((value) => {
98
- setResult(value);
99
- setError(undefined);
100
- task.resolve(value);
101
- })
102
- .catch((err) => {
103
- setError(err);
104
- task.reject(err);
105
- })
106
- .finally(() => {
107
- activeCountRef.current -= 1;
108
- updateQueueSize();
109
- processNext();
110
- });
111
-
112
- // Fill remaining slots (parallel mode).
113
- if (queueRef.current.length > 0 && activeCountRef.current < maxConcurrent) {
114
- processNext();
115
- }
116
- }, [maxConcurrent, updateQueueSize]);
117
-
118
- const run = useCallback(
119
- (data: TData, runOptions?: RunOptions): Promise<TResult> => {
120
- if (terminatedRef.current) {
121
- return Promise.reject(new Error('Worker is terminated'));
122
- }
123
- const priority = runOptions?.priority ?? DEFAULT_PRIORITY;
124
- const sequence = ++sequenceRef.current;
125
- const promise = new Promise<TResult>((resolve, reject) => {
126
- queueRef.current.push({ data, priority, sequence, resolve, reject });
127
- });
128
- updateQueueSize();
129
- setLoading(true);
130
- queueMicrotask(processNext);
131
- return promise;
132
- },
133
- [processNext, updateQueueSize]
134
- );
135
-
136
- const clearQueue = useCallback(() => {
137
- const pending = queueRef.current;
138
- queueRef.current = [];
139
- pending.forEach((t) => t.reject(new Error('Task cleared from queue')));
140
- updateQueueSize();
141
- if (activeCountRef.current === 0) setLoading(false);
142
- }, [updateQueueSize]);
143
-
144
- const terminate = useCallback(() => {
145
- terminatedRef.current = true;
146
- clearQueue();
147
- }, [clearQueue]);
148
-
149
- // Reset terminated on unmount so the same hook instance can't be "revived" without options change.
150
- useEffect(() => {
151
- return () => {
152
- terminatedRef.current = true;
153
- };
154
- }, []);
155
-
156
- return {
157
- run,
158
- loading,
159
- result,
160
- error,
161
- queueSize,
162
- clearQueue,
163
- terminate,
164
- };
165
- }
1
+ import { useState, useCallback, useRef, useEffect } from "preact/hooks";
2
+
3
+ /** Lower number = higher priority. Default priority when not specified. */
4
+ const DEFAULT_PRIORITY = 1;
5
+
6
+ export type ThreadedWorkerMode = "sequential" | "parallel";
7
+
8
+ export interface UseThreadedWorkerOptions {
9
+ /** Sequential: single worker, priority-ordered. Parallel: worker pool. */
10
+ mode: ThreadedWorkerMode;
11
+ /** Max concurrent workers. Only used when mode is "parallel". Default 4. */
12
+ concurrency?: number;
13
+ }
14
+
15
+ export interface RunOptions {
16
+ /** 1 = highest priority. Lower number runs first. FIFO within same priority. */
17
+ priority?: number;
18
+ }
19
+
20
+ interface QueuedTask<TData, TResult> {
21
+ data: TData;
22
+ priority: number;
23
+ sequence: number;
24
+ resolve: (value: TResult) => void;
25
+ reject: (reason: unknown) => void;
26
+ }
27
+
28
+ export interface UseThreadedWorkerReturn<TData, TResult> {
29
+ /** Enqueue work. Returns a Promise that resolves with the worker result. */
30
+ run: (data: TData, options?: RunOptions) => Promise<TResult>;
31
+ /** True while any task is queued or running. */
32
+ loading: boolean;
33
+ /** Result of the most recently completed successful task. */
34
+ result: TResult | undefined;
35
+ /** Error from the most recently failed task. */
36
+ error: unknown;
37
+ /** Number of tasks currently queued + running. */
38
+ queueSize: number;
39
+ /** Clear all pending (not yet started) tasks. Running tasks continue. */
40
+ clearQueue: () => void;
41
+ /** Stop accepting new work and clear pending queue. Running tasks finish. */
42
+ terminate: () => void;
43
+ }
44
+
45
+ /**
46
+ * Production-grade hook to run async work in a queue with optional priority
47
+ * and either sequential or parallel execution.
48
+ *
49
+ * @param workerFn - Async function to run for each task (e.g. API call, heavy compute).
50
+ * @param options - mode: "sequential" | "parallel", concurrency (parallel only).
51
+ * @returns run, loading, result, error, queueSize, clearQueue, terminate.
52
+ */
53
+ export function useThreadedWorker<TData, TResult>(
54
+ workerFn: (data: TData) => Promise<TResult>,
55
+ options: UseThreadedWorkerOptions
56
+ ): UseThreadedWorkerReturn<TData, TResult> {
57
+ const { mode, concurrency = 4 } = options;
58
+ const maxConcurrent = mode === "sequential" ? 1 : Math.max(1, concurrency);
59
+
60
+ const [loading, setLoading] = useState(false);
61
+ const [result, setResult] = useState<TResult | undefined>(undefined);
62
+ const [error, setError] = useState<unknown>(undefined);
63
+ const [queueSize, setQueueSize] = useState(0);
64
+
65
+ const queueRef = useRef<QueuedTask<TData, TResult>[]>([]);
66
+ const sequenceRef = useRef(0);
67
+ const activeCountRef = useRef(0);
68
+ const terminatedRef = useRef(false);
69
+ const workerFnRef = useRef(workerFn);
70
+ workerFnRef.current = workerFn;
71
+
72
+ const updateQueueSize = useCallback(() => {
73
+ setQueueSize(queueRef.current.length + activeCountRef.current);
74
+ }, []);
75
+
76
+ const processNext = useCallback(() => {
77
+ if (terminatedRef.current) return;
78
+ if (activeCountRef.current >= maxConcurrent) return;
79
+ if (queueRef.current.length === 0) {
80
+ if (activeCountRef.current === 0) setLoading(false);
81
+ updateQueueSize();
82
+ return;
83
+ }
84
+
85
+ // Sort by priority (asc), then by sequence (FIFO within same priority).
86
+ queueRef.current.sort((a, b) => {
87
+ if (a.priority !== b.priority) return a.priority - b.priority;
88
+ return a.sequence - b.sequence;
89
+ });
90
+ const task = queueRef.current.shift()!;
91
+ activeCountRef.current += 1;
92
+ setLoading(true);
93
+ updateQueueSize();
94
+
95
+ const fn = workerFnRef.current;
96
+ fn(task.data)
97
+ .then((value) => {
98
+ setResult(value);
99
+ setError(undefined);
100
+ task.resolve(value);
101
+ })
102
+ .catch((err) => {
103
+ setError(err);
104
+ task.reject(err);
105
+ })
106
+ .finally(() => {
107
+ activeCountRef.current -= 1;
108
+ updateQueueSize();
109
+ processNext();
110
+ });
111
+
112
+ // Fill remaining slots (parallel mode).
113
+ if (queueRef.current.length > 0 && activeCountRef.current < maxConcurrent) {
114
+ processNext();
115
+ }
116
+ }, [maxConcurrent, updateQueueSize]);
117
+
118
+ const run = useCallback(
119
+ (data: TData, runOptions?: RunOptions): Promise<TResult> => {
120
+ if (terminatedRef.current) {
121
+ return Promise.reject(new Error("Worker is terminated"));
122
+ }
123
+ const priority = runOptions?.priority ?? DEFAULT_PRIORITY;
124
+ const sequence = ++sequenceRef.current;
125
+ const promise = new Promise<TResult>((resolve, reject) => {
126
+ queueRef.current.push({ data, priority, sequence, resolve, reject });
127
+ });
128
+ updateQueueSize();
129
+ setLoading(true);
130
+ queueMicrotask(processNext);
131
+ return promise;
132
+ },
133
+ [processNext, updateQueueSize]
134
+ );
135
+
136
+ const clearQueue = useCallback(() => {
137
+ const pending = queueRef.current;
138
+ queueRef.current = [];
139
+ pending.forEach((t) => t.reject(new Error("Task cleared from queue")));
140
+ updateQueueSize();
141
+ if (activeCountRef.current === 0) setLoading(false);
142
+ }, [updateQueueSize]);
143
+
144
+ const terminate = useCallback(() => {
145
+ terminatedRef.current = true;
146
+ clearQueue();
147
+ }, [clearQueue]);
148
+
149
+ // Reset terminated on unmount so the same hook instance can't be "revived" without options change.
150
+ useEffect(() => {
151
+ return () => {
152
+ terminatedRef.current = true;
153
+ };
154
+ }, []);
155
+
156
+ return {
157
+ run,
158
+ loading,
159
+ result,
160
+ error,
161
+ queueSize,
162
+ clearQueue,
163
+ terminate,
164
+ };
165
+ }
@@ -1,19 +1,22 @@
1
- import { useState, useCallback } from 'preact/hooks';
2
-
3
- /**
4
- * Mimics React's useTransition hook in Preact.
5
- * @returns [startTransition, isPending]
6
- */
7
- export function useTransition(): [startTransition: (callback: () => void) => void, isPending: boolean] {
8
- const [isPending, setIsPending] = useState(false);
9
-
10
- const startTransition = useCallback((callback: () => void) => {
11
- setIsPending(true);
12
- Promise.resolve().then(() => {
13
- callback();
14
- setIsPending(false);
15
- });
16
- }, []);
17
-
18
- return [startTransition, isPending];
19
- }
1
+ import { useState, useCallback } from "preact/hooks";
2
+
3
+ /**
4
+ * Mimics React's useTransition hook in Preact.
5
+ * @returns [startTransition, isPending]
6
+ */
7
+ export function useTransition(): [
8
+ startTransition: (callback: () => void) => void,
9
+ isPending: boolean,
10
+ ] {
11
+ const [isPending, setIsPending] = useState(false);
12
+
13
+ const startTransition = useCallback((callback: () => void) => {
14
+ setIsPending(true);
15
+ Promise.resolve().then(() => {
16
+ callback();
17
+ setIsPending(false);
18
+ });
19
+ }, []);
20
+
21
+ return [startTransition, isPending];
22
+ }