preact-missing-hooks 3.1.0 → 4.0.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/.husky/pre-commit +1 -0
- package/.husky/pre-push +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc +6 -0
- package/Readme.md +149 -136
- package/dist/entry.cjs +21 -0
- package/dist/entry.js +2 -0
- package/dist/entry.js.map +1 -0
- package/dist/entry.modern.mjs +2 -0
- package/dist/entry.modern.mjs.map +1 -0
- package/dist/entry.module.js +2 -0
- package/dist/entry.module.js.map +1 -0
- package/dist/entry.umd.js +2 -0
- package/dist/entry.umd.js.map +1 -0
- package/dist/index.d.ts +13 -13
- package/dist/index.js.map +1 -1
- package/dist/index.modern.mjs +2 -0
- package/dist/index.modern.mjs.map +1 -0
- package/dist/index.module.js.map +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/indexedDB/dbController.d.ts +2 -2
- package/dist/indexedDB/index.d.ts +6 -6
- package/dist/indexedDB/openDB.d.ts +1 -1
- package/dist/indexedDB/tableController.d.ts +1 -1
- package/dist/indexedDB/types.d.ts +1 -2
- package/dist/react.js +1 -0
- package/dist/react.modern.mjs +1 -0
- package/dist/react.module.js +1 -0
- package/dist/react.umd.js +1 -0
- package/dist/useEventBus.d.ts +1 -1
- package/dist/useIndexedDB.d.ts +3 -3
- package/dist/useMutationObserver.d.ts +1 -1
- package/dist/useNetworkState.d.ts +3 -3
- package/dist/usePreferredTheme.d.ts +1 -1
- package/dist/useRageClick.d.ts +1 -1
- package/dist/useThreadedWorker.d.ts +1 -1
- package/dist/useTransition.d.ts +4 -1
- package/dist/useWorkerNotifications.d.ts +1 -1
- package/dist/useWrappedChildren.d.ts +3 -3
- package/eslint.config.mjs +10 -0
- package/package.json +60 -6
- package/scripts/generate-entry.cjs +34 -0
- package/src/index.ts +13 -13
- package/src/indexedDB/dbController.ts +101 -92
- package/src/indexedDB/index.ts +16 -11
- package/src/indexedDB/openDB.ts +49 -49
- package/src/indexedDB/requestToPromise.ts +17 -16
- package/src/indexedDB/tableController.ts +331 -257
- package/src/indexedDB/types.ts +35 -35
- package/src/useClipboard.ts +99 -97
- package/src/useEventBus.ts +39 -36
- package/src/useIndexedDB.ts +111 -111
- package/src/useMutationObserver.ts +26 -26
- package/src/useNetworkState.ts +124 -122
- package/src/usePreferredTheme.ts +68 -68
- package/src/useRageClick.ts +103 -103
- package/src/useThreadedWorker.ts +165 -165
- package/src/useTransition.ts +22 -19
- package/src/useWasmCompute.ts +209 -204
- package/src/useWebRTCIP.ts +181 -176
- package/src/useWorkerNotifications.ts +28 -20
- package/src/useWrappedChildren.ts +72 -58
- package/tests/react-adapter.tsx +12 -0
- package/tests/setup-react.ts +4 -0
- package/tests/useClipboard.test.tsx +4 -2
- package/tests/useThreadedWorker.test.tsx +3 -1
- package/tests/useWasmCompute.test.tsx +1 -1
- package/tests/useWebRTCIP.test.tsx +3 -1
- package/vite.config.ts +11 -4
- package/vitest.config.preact.ts +20 -0
- package/vitest.config.react.ts +36 -0
- package/vitest.workspace.ts +6 -0
package/src/useWasmCompute.ts
CHANGED
|
@@ -1,204 +1,209 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useWasmCompute – run WebAssembly computation off the main thread via a Web Worker.
|
|
3
|
-
* Flow: Preact Component → useWasmCompute() → Web Worker → WASM Module → Return result.
|
|
4
|
-
* @module useWasmCompute
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useState, useCallback, useRef, useEffect } from
|
|
8
|
-
|
|
9
|
-
const WASM_WORKER_SCRIPT = `
|
|
10
|
-
self.onmessage = async (e) => {
|
|
11
|
-
const d = e.data;
|
|
12
|
-
if (d.type === 'init') {
|
|
13
|
-
try {
|
|
14
|
-
const res = await fetch(d.wasmUrl);
|
|
15
|
-
const buf = await res.arrayBuffer();
|
|
16
|
-
const mod = await WebAssembly.instantiate(buf, d.importObject || {});
|
|
17
|
-
self.wasmInstance = mod.instance;
|
|
18
|
-
self.exportName = d.exportName || 'compute';
|
|
19
|
-
self.postMessage({ type: 'ready' });
|
|
20
|
-
} catch (err) {
|
|
21
|
-
self.postMessage({ type: 'error', error: (err && err.message) || String(err) });
|
|
22
|
-
}
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if (d.type === 'compute') {
|
|
26
|
-
try {
|
|
27
|
-
const fn = self.wasmInstance.exports[self.exportName];
|
|
28
|
-
if (typeof fn !== 'function') {
|
|
29
|
-
self.postMessage({ type: 'error', error: 'Export "' + self.exportName + '" is not a function' });
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const result = fn(d.input);
|
|
33
|
-
self.postMessage({ type: 'result', result: result });
|
|
34
|
-
} catch (err) {
|
|
35
|
-
self.postMessage({ type: 'error', error: (err && err.message) || String(err) });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
`;
|
|
40
|
-
|
|
41
|
-
export interface UseWasmComputeOptions {
|
|
42
|
-
/** URL of the .wasm module to load in the worker. */
|
|
43
|
-
wasmUrl: string;
|
|
44
|
-
/** Name of the exported function to call for compute (default: 'compute'). */
|
|
45
|
-
exportName?: string;
|
|
46
|
-
/** Optional custom worker script URL. If provided, worker must handle init (wasmUrl, exportName) and compute(input) messages. */
|
|
47
|
-
workerUrl?: string;
|
|
48
|
-
/** Optional import object for WebAssembly.instantiate (only used when using default inline worker; must be serializable). */
|
|
49
|
-
importObject?: WebAssembly.Imports;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface UseWasmComputeReturn<TInput = number, TResult = number> {
|
|
53
|
-
/** Invoke the WASM export with the given input. Resolves with the return value when ready. */
|
|
54
|
-
compute: (input: TInput) => Promise<TResult>;
|
|
55
|
-
/** Last result from a successful compute call. */
|
|
56
|
-
result: TResult | undefined;
|
|
57
|
-
/** True while WASM is loading or a compute is in progress. */
|
|
58
|
-
loading: boolean;
|
|
59
|
-
/** Error message if environment is unsupported, init failed, or compute failed. */
|
|
60
|
-
error: string | null;
|
|
61
|
-
/** True when the WASM module is loaded and compute can be called. */
|
|
62
|
-
ready: boolean;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function isSSR(): boolean {
|
|
66
|
-
return typeof window ===
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function isWorkerSupported(): boolean {
|
|
70
|
-
return typeof Worker !==
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function isWebAssemblySupported(): boolean {
|
|
74
|
-
return
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
setError(
|
|
121
|
-
setLoading(false);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (!
|
|
125
|
-
setError(
|
|
126
|
-
setLoading(false);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
if (type ===
|
|
143
|
-
|
|
144
|
-
setLoading(false);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* useWasmCompute – run WebAssembly computation off the main thread via a Web Worker.
|
|
3
|
+
* Flow: Preact Component → useWasmCompute() → Web Worker → WASM Module → Return result.
|
|
4
|
+
* @module useWasmCompute
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useCallback, useRef, useEffect } from "preact/hooks";
|
|
8
|
+
|
|
9
|
+
const WASM_WORKER_SCRIPT = `
|
|
10
|
+
self.onmessage = async (e) => {
|
|
11
|
+
const d = e.data;
|
|
12
|
+
if (d.type === 'init') {
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(d.wasmUrl);
|
|
15
|
+
const buf = await res.arrayBuffer();
|
|
16
|
+
const mod = await WebAssembly.instantiate(buf, d.importObject || {});
|
|
17
|
+
self.wasmInstance = mod.instance;
|
|
18
|
+
self.exportName = d.exportName || 'compute';
|
|
19
|
+
self.postMessage({ type: 'ready' });
|
|
20
|
+
} catch (err) {
|
|
21
|
+
self.postMessage({ type: 'error', error: (err && err.message) || String(err) });
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (d.type === 'compute') {
|
|
26
|
+
try {
|
|
27
|
+
const fn = self.wasmInstance.exports[self.exportName];
|
|
28
|
+
if (typeof fn !== 'function') {
|
|
29
|
+
self.postMessage({ type: 'error', error: 'Export "' + self.exportName + '" is not a function' });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const result = fn(d.input);
|
|
33
|
+
self.postMessage({ type: 'result', result: result });
|
|
34
|
+
} catch (err) {
|
|
35
|
+
self.postMessage({ type: 'error', error: (err && err.message) || String(err) });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
export interface UseWasmComputeOptions {
|
|
42
|
+
/** URL of the .wasm module to load in the worker. */
|
|
43
|
+
wasmUrl: string;
|
|
44
|
+
/** Name of the exported function to call for compute (default: 'compute'). */
|
|
45
|
+
exportName?: string;
|
|
46
|
+
/** Optional custom worker script URL. If provided, worker must handle init (wasmUrl, exportName) and compute(input) messages. */
|
|
47
|
+
workerUrl?: string;
|
|
48
|
+
/** Optional import object for WebAssembly.instantiate (only used when using default inline worker; must be serializable). */
|
|
49
|
+
importObject?: WebAssembly.Imports;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface UseWasmComputeReturn<TInput = number, TResult = number> {
|
|
53
|
+
/** Invoke the WASM export with the given input. Resolves with the return value when ready. */
|
|
54
|
+
compute: (input: TInput) => Promise<TResult>;
|
|
55
|
+
/** Last result from a successful compute call. */
|
|
56
|
+
result: TResult | undefined;
|
|
57
|
+
/** True while WASM is loading or a compute is in progress. */
|
|
58
|
+
loading: boolean;
|
|
59
|
+
/** Error message if environment is unsupported, init failed, or compute failed. */
|
|
60
|
+
error: string | null;
|
|
61
|
+
/** True when the WASM module is loaded and compute can be called. */
|
|
62
|
+
ready: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isSSR(): boolean {
|
|
66
|
+
return typeof window === "undefined";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isWorkerSupported(): boolean {
|
|
70
|
+
return typeof Worker !== "undefined";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isWebAssemblySupported(): boolean {
|
|
74
|
+
return (
|
|
75
|
+
typeof WebAssembly !== "undefined" &&
|
|
76
|
+
typeof WebAssembly.instantiate === "function"
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createWorker(workerUrl?: string): Worker {
|
|
81
|
+
if (workerUrl) {
|
|
82
|
+
return new Worker(workerUrl);
|
|
83
|
+
}
|
|
84
|
+
const blob = new Blob([WASM_WORKER_SCRIPT], {
|
|
85
|
+
type: "application/javascript",
|
|
86
|
+
});
|
|
87
|
+
const url = URL.createObjectURL(blob);
|
|
88
|
+
const w = new Worker(url);
|
|
89
|
+
URL.revokeObjectURL(url);
|
|
90
|
+
return w;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Runs WebAssembly computation in a Web Worker. Validates environment (browser, Worker, WebAssembly)
|
|
95
|
+
* and returns a stable compute function plus result/loading/error/ready state.
|
|
96
|
+
*
|
|
97
|
+
* @param options - wasmUrl, optional exportName, optional workerUrl, optional importObject.
|
|
98
|
+
* @returns { compute, result, loading, error, ready }.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* const { compute, result, loading, error, ready } = useWasmCompute({ wasmUrl: '/add.wasm', exportName: 'add' });
|
|
102
|
+
* // When ready: compute(2).then(sum => ...); result will update with the last return value.
|
|
103
|
+
*/
|
|
104
|
+
export function useWasmCompute<TInput = number, TResult = number>(
|
|
105
|
+
options: UseWasmComputeOptions
|
|
106
|
+
): UseWasmComputeReturn<TInput, TResult> {
|
|
107
|
+
const { wasmUrl, exportName = "compute", workerUrl, importObject } = options;
|
|
108
|
+
|
|
109
|
+
const [result, setResult] = useState<TResult | undefined>(undefined);
|
|
110
|
+
const [loading, setLoading] = useState(true);
|
|
111
|
+
const [error, setError] = useState<string | null>(null);
|
|
112
|
+
const [ready, setReady] = useState(false);
|
|
113
|
+
|
|
114
|
+
const workerRef = useRef<Worker | null>(null);
|
|
115
|
+
const pendingResolveRef = useRef<((value: TResult) => void) | null>(null);
|
|
116
|
+
const pendingRejectRef = useRef<((reason: Error) => void) | null>(null);
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (isSSR()) {
|
|
120
|
+
setError("useWasmCompute is not available during SSR");
|
|
121
|
+
setLoading(false);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!isWorkerSupported()) {
|
|
125
|
+
setError("Worker is not supported in this environment");
|
|
126
|
+
setLoading(false);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!isWebAssemblySupported()) {
|
|
130
|
+
setError("WebAssembly is not supported in this environment");
|
|
131
|
+
setLoading(false);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
setError(null);
|
|
136
|
+
setReady(false);
|
|
137
|
+
const worker = createWorker(workerUrl);
|
|
138
|
+
workerRef.current = worker;
|
|
139
|
+
|
|
140
|
+
const onMessage = (e: MessageEvent) => {
|
|
141
|
+
const { type, result: msgResult, error: msgError } = e.data ?? {};
|
|
142
|
+
if (type === "ready") {
|
|
143
|
+
setReady(true);
|
|
144
|
+
setLoading(false);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (type === "error") {
|
|
148
|
+
setError(msgError ?? "Unknown error");
|
|
149
|
+
setLoading(false);
|
|
150
|
+
if (pendingRejectRef.current) {
|
|
151
|
+
pendingRejectRef.current(new Error(msgError));
|
|
152
|
+
pendingResolveRef.current = null;
|
|
153
|
+
pendingRejectRef.current = null;
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (type === "result") {
|
|
158
|
+
setResult(msgResult);
|
|
159
|
+
setLoading(false);
|
|
160
|
+
if (pendingResolveRef.current) {
|
|
161
|
+
pendingResolveRef.current(msgResult);
|
|
162
|
+
pendingResolveRef.current = null;
|
|
163
|
+
pendingRejectRef.current = null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
worker.addEventListener("message", onMessage);
|
|
169
|
+
worker.postMessage({
|
|
170
|
+
type: "init",
|
|
171
|
+
wasmUrl,
|
|
172
|
+
exportName,
|
|
173
|
+
importObject: importObject ?? {},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return () => {
|
|
177
|
+
worker.removeEventListener("message", onMessage);
|
|
178
|
+
worker.terminate();
|
|
179
|
+
workerRef.current = null;
|
|
180
|
+
if (pendingRejectRef.current) {
|
|
181
|
+
pendingRejectRef.current(new Error("Worker terminated"));
|
|
182
|
+
pendingResolveRef.current = null;
|
|
183
|
+
pendingRejectRef.current = null;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}, [wasmUrl, exportName, workerUrl, importObject]);
|
|
187
|
+
|
|
188
|
+
const compute = useCallback(
|
|
189
|
+
(input: TInput): Promise<TResult> => {
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
if (!workerRef.current || !ready) {
|
|
192
|
+
reject(new Error("WASM not ready"));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (error) {
|
|
196
|
+
reject(new Error(error));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
pendingResolveRef.current = resolve;
|
|
200
|
+
pendingRejectRef.current = reject;
|
|
201
|
+
setLoading(true);
|
|
202
|
+
workerRef.current.postMessage({ type: "compute", input });
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
[ready, error]
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return { compute, result, loading, error, ready };
|
|
209
|
+
}
|