preact-missing-hooks 3.0.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 +179 -131
- 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 -12
- package/dist/index.js +1 -1
- 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 +1 -1
- package/dist/index.module.js.map +1 -1
- package/dist/index.umd.js +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 +57 -0
- package/dist/useWrappedChildren.d.ts +3 -3
- package/{demo → docs}/index.html +56 -0
- package/{demo → docs}/main.js +437 -312
- package/eslint.config.mjs +10 -0
- package/package.json +65 -6
- package/scripts/generate-entry.cjs +34 -0
- package/src/index.ts +13 -12
- 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 +203 -0
- 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/tests/useWorkerNotifications.test.tsx +170 -0
- 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/{demo → docs}/add.wasm +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm run lint && npm run format
|
package/.husky/pre-push
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm run test && npm run size
|
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/Readme.md
CHANGED
|
@@ -32,10 +32,11 @@ A lightweight, extendable collection of React-like hooks for Preact, including u
|
|
|
32
32
|
- **`useThreadedWorker`** — Run async work in a queue with **sequential** (single worker, priority-ordered) or **parallel** (worker pool) mode. Optional priority (1 = highest); FIFO within same priority.
|
|
33
33
|
- **`useIndexedDB`** — IndexedDB abstraction with database/table init, insert, update, delete, exists, query (cursor + filter), upsert, bulk insert, clear, count, and full transaction support. Singleton connection, Promise-based API, optional `onSuccess`/`onError` callbacks.
|
|
34
34
|
- **`useWebRTCIP`** — Detects client IP addresses using WebRTC ICE candidates and a STUN server (frontend-only). **Not highly reliable**; use as a first-priority hint and fall back to a public IP API (e.g. [ipapi.co](https://ipapi.co), [ipify](https://www.ipify.org), [ip-api.com](https://ip-api.com)) when it fails or returns empty.
|
|
35
|
-
- **`useWasmCompute`** — Runs WebAssembly computation off the main thread via a Web Worker.
|
|
35
|
+
- **`useWasmCompute`** — Runs WebAssembly computation off the main thread via a Web Worker. Validates environment (browser, Worker, WebAssembly) and returns `compute(input)`, `result`, `loading`, `error`, `ready`.
|
|
36
|
+
- **`useWorkerNotifications`** — Listens to a Worker's messages and maintains state: running tasks, completed/failed counts, event history, average task duration, throughput per second, and queue size. Worker posts `task_start` / `task_end` / `task_fail` / `queue_size`; returns `progress` (default view of all active worker data) plus individual stats.
|
|
36
37
|
- Fully TypeScript compatible
|
|
37
38
|
- Bundled with Microbundle
|
|
38
|
-
- Zero dependencies (
|
|
39
|
+
- Zero dependencies (peer: `preact` or `react` — use `/react` for React)
|
|
39
40
|
|
|
40
41
|
---
|
|
41
42
|
|
|
@@ -45,22 +46,36 @@ A lightweight, extendable collection of React-like hooks for Preact, including u
|
|
|
45
46
|
npm install preact-missing-hooks
|
|
46
47
|
```
|
|
47
48
|
|
|
49
|
+
Ensure your app has either **preact** or **react** installed (the package uses whichever is present).
|
|
50
|
+
|
|
48
51
|
---
|
|
49
52
|
|
|
50
53
|
## Import options
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
Use the same import in Preact and React projects:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { useThreadedWorker, useClipboard } from "preact-missing-hooks";
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- **How it picks Preact vs React**
|
|
62
|
+
- **CommonJS / Node:** The package detects which of `preact` or `react` is installed and uses that build automatically.
|
|
63
|
+
- **ESM (Vite, Webpack, etc.):** Default is the Preact build. In a **React** app, add the `react` condition so the package resolves to the React build:
|
|
64
|
+
- **Vite:** `vite.config.ts` → `resolve: { conditions: ['react'] }`
|
|
65
|
+
- **Webpack:** `resolve.conditionNames` (or similar) to include `'react'`
|
|
66
|
+
- **Or** in React projects you can always import from the explicit entry: `preact-missing-hooks/react`.
|
|
67
|
+
|
|
56
68
|
- **Subpath exports (tree-shakeable)** — Import a single hook:
|
|
69
|
+
|
|
57
70
|
```ts
|
|
58
|
-
import { useThreadedWorker } from
|
|
59
|
-
import { useClipboard } from
|
|
60
|
-
import { useWebRTCIP } from
|
|
61
|
-
import { useWasmCompute } from
|
|
71
|
+
import { useThreadedWorker } from "preact-missing-hooks/useThreadedWorker";
|
|
72
|
+
import { useClipboard } from "preact-missing-hooks/useClipboard";
|
|
73
|
+
import { useWebRTCIP } from "preact-missing-hooks/useWebRTCIP";
|
|
74
|
+
import { useWasmCompute } from "preact-missing-hooks/useWasmCompute";
|
|
75
|
+
import { useWorkerNotifications } from "preact-missing-hooks/useWorkerNotifications";
|
|
62
76
|
```
|
|
63
|
-
|
|
77
|
+
|
|
78
|
+
All hooks are available: `useTransition`, `useMutationObserver`, `useEventBus`, `useWrappedChildren`, `usePreferredTheme`, `useNetworkState`, `useClipboard`, `useRageClick`, `useThreadedWorker`, `useIndexedDB`, `useWebRTCIP`, `useWasmCompute`, `useWorkerNotifications`.
|
|
64
79
|
|
|
65
80
|
---
|
|
66
81
|
|
|
@@ -69,22 +84,22 @@ npm install preact-missing-hooks
|
|
|
69
84
|
### `useTransition`
|
|
70
85
|
|
|
71
86
|
```tsx
|
|
72
|
-
import { useTransition } from
|
|
87
|
+
import { useTransition } from "preact-missing-hooks";
|
|
73
88
|
|
|
74
89
|
function ExampleTransition() {
|
|
75
|
-
const [startTransition, isPending] = useTransition()
|
|
90
|
+
const [startTransition, isPending] = useTransition();
|
|
76
91
|
|
|
77
92
|
const handleClick = () => {
|
|
78
93
|
startTransition(() => {
|
|
79
94
|
// perform an expensive update here
|
|
80
|
-
})
|
|
81
|
-
}
|
|
95
|
+
});
|
|
96
|
+
};
|
|
82
97
|
|
|
83
98
|
return (
|
|
84
99
|
<button onClick={handleClick} disabled={isPending}>
|
|
85
|
-
{isPending ?
|
|
100
|
+
{isPending ? "Loading..." : "Click Me"}
|
|
86
101
|
</button>
|
|
87
|
-
)
|
|
102
|
+
);
|
|
88
103
|
}
|
|
89
104
|
```
|
|
90
105
|
|
|
@@ -93,21 +108,21 @@ function ExampleTransition() {
|
|
|
93
108
|
### `useMutationObserver`
|
|
94
109
|
|
|
95
110
|
```tsx
|
|
96
|
-
import { useRef } from
|
|
97
|
-
import { useMutationObserver } from
|
|
111
|
+
import { useRef } from "preact/hooks";
|
|
112
|
+
import { useMutationObserver } from "preact-missing-hooks";
|
|
98
113
|
|
|
99
114
|
function ExampleMutation() {
|
|
100
|
-
const ref = useRef<HTMLDivElement>(null)
|
|
115
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
101
116
|
|
|
102
117
|
useMutationObserver(
|
|
103
118
|
ref,
|
|
104
119
|
(mutations) => {
|
|
105
|
-
console.log(
|
|
120
|
+
console.log("Detected mutations:", mutations);
|
|
106
121
|
},
|
|
107
|
-
{ childList: true, subtree: true }
|
|
108
|
-
)
|
|
122
|
+
{ childList: true, subtree: true }
|
|
123
|
+
);
|
|
109
124
|
|
|
110
|
-
return <div ref={ref}>Observe this content</div
|
|
125
|
+
return <div ref={ref}>Observe this content</div>;
|
|
111
126
|
}
|
|
112
127
|
```
|
|
113
128
|
|
|
@@ -118,33 +133,33 @@ function ExampleMutation() {
|
|
|
118
133
|
```tsx
|
|
119
134
|
// types.ts
|
|
120
135
|
export type Events = {
|
|
121
|
-
notify: (message: string) => void
|
|
122
|
-
}
|
|
136
|
+
notify: (message: string) => void;
|
|
137
|
+
};
|
|
123
138
|
|
|
124
139
|
// Sender.tsx
|
|
125
|
-
import { useEventBus } from
|
|
126
|
-
import type { Events } from
|
|
140
|
+
import { useEventBus } from "preact-missing-hooks";
|
|
141
|
+
import type { Events } from "./types";
|
|
127
142
|
|
|
128
143
|
function Sender() {
|
|
129
|
-
const { emit } = useEventBus<Events>()
|
|
130
|
-
return <button onClick={() => emit(
|
|
144
|
+
const { emit } = useEventBus<Events>();
|
|
145
|
+
return <button onClick={() => emit("notify", "Hello World!")}>Send</button>;
|
|
131
146
|
}
|
|
132
147
|
|
|
133
148
|
// Receiver.tsx
|
|
134
|
-
import { useEventBus } from
|
|
135
|
-
import { useState, useEffect } from
|
|
136
|
-
import type { Events } from
|
|
149
|
+
import { useEventBus } from "preact-missing-hooks";
|
|
150
|
+
import { useState, useEffect } from "preact/hooks";
|
|
151
|
+
import type { Events } from "./types";
|
|
137
152
|
|
|
138
153
|
function Receiver() {
|
|
139
|
-
const [msg, setMsg] = useState<string>(
|
|
140
|
-
const { on } = useEventBus<Events>()
|
|
154
|
+
const [msg, setMsg] = useState<string>("");
|
|
155
|
+
const { on } = useEventBus<Events>();
|
|
141
156
|
|
|
142
157
|
useEffect(() => {
|
|
143
|
-
const unsubscribe = on(
|
|
144
|
-
return unsubscribe
|
|
145
|
-
}, [])
|
|
158
|
+
const unsubscribe = on("notify", setMsg);
|
|
159
|
+
return unsubscribe;
|
|
160
|
+
}, []);
|
|
146
161
|
|
|
147
|
-
return <div>Message: {msg}</div
|
|
162
|
+
return <div>Message: {msg}</div>;
|
|
148
163
|
}
|
|
149
164
|
```
|
|
150
165
|
|
|
@@ -153,19 +168,19 @@ function Receiver() {
|
|
|
153
168
|
### `useWrappedChildren`
|
|
154
169
|
|
|
155
170
|
```tsx
|
|
156
|
-
import { useWrappedChildren } from
|
|
171
|
+
import { useWrappedChildren } from "preact-missing-hooks";
|
|
157
172
|
|
|
158
173
|
function ParentComponent({ children }) {
|
|
159
174
|
// Inject common props into all children
|
|
160
175
|
const injectProps = {
|
|
161
|
-
className:
|
|
162
|
-
onClick: () => console.log(
|
|
163
|
-
style: { border:
|
|
164
|
-
}
|
|
176
|
+
className: "enhanced-child",
|
|
177
|
+
onClick: () => console.log("Child clicked!"),
|
|
178
|
+
style: { border: "1px solid #ccc" },
|
|
179
|
+
};
|
|
165
180
|
|
|
166
|
-
const wrappedChildren = useWrappedChildren(children, injectProps)
|
|
181
|
+
const wrappedChildren = useWrappedChildren(children, injectProps);
|
|
167
182
|
|
|
168
|
-
return <div className="parent">{wrappedChildren}</div
|
|
183
|
+
return <div className="parent">{wrappedChildren}</div>;
|
|
169
184
|
}
|
|
170
185
|
|
|
171
186
|
// Usage with preserve strategy (default - existing props are preserved)
|
|
@@ -173,21 +188,21 @@ function PreserveExample() {
|
|
|
173
188
|
return (
|
|
174
189
|
<ParentComponent>
|
|
175
190
|
<button className="btn">Existing class preserved</button>
|
|
176
|
-
<span style={{ color:
|
|
191
|
+
<span style={{ color: "red" }}>Both styles applied</span>
|
|
177
192
|
</ParentComponent>
|
|
178
|
-
)
|
|
193
|
+
);
|
|
179
194
|
}
|
|
180
195
|
|
|
181
196
|
// Usage with override strategy (injected props override existing ones)
|
|
182
197
|
function OverrideExample() {
|
|
183
|
-
const injectProps = { className:
|
|
198
|
+
const injectProps = { className: "new-class" };
|
|
184
199
|
const children = (
|
|
185
200
|
<button className="old-class">Class will be overridden</button>
|
|
186
|
-
)
|
|
201
|
+
);
|
|
187
202
|
|
|
188
|
-
const wrappedChildren = useWrappedChildren(children, injectProps,
|
|
203
|
+
const wrappedChildren = useWrappedChildren(children, injectProps, "override");
|
|
189
204
|
|
|
190
|
-
return <div>{wrappedChildren}</div
|
|
205
|
+
return <div>{wrappedChildren}</div>;
|
|
191
206
|
}
|
|
192
207
|
```
|
|
193
208
|
|
|
@@ -196,12 +211,12 @@ function OverrideExample() {
|
|
|
196
211
|
### `usePreferredTheme`
|
|
197
212
|
|
|
198
213
|
```tsx
|
|
199
|
-
import { usePreferredTheme } from
|
|
214
|
+
import { usePreferredTheme } from "preact-missing-hooks";
|
|
200
215
|
|
|
201
216
|
function ThemeAwareComponent() {
|
|
202
|
-
const theme = usePreferredTheme() // 'light' | 'dark' | 'no-preference'
|
|
217
|
+
const theme = usePreferredTheme(); // 'light' | 'dark' | 'no-preference'
|
|
203
218
|
|
|
204
|
-
return <div data-theme={theme}>Your system prefers: {theme}</div
|
|
219
|
+
return <div data-theme={theme}>Your system prefers: {theme}</div>;
|
|
205
220
|
}
|
|
206
221
|
```
|
|
207
222
|
|
|
@@ -210,18 +225,18 @@ function ThemeAwareComponent() {
|
|
|
210
225
|
### `useNetworkState`
|
|
211
226
|
|
|
212
227
|
```tsx
|
|
213
|
-
import { useNetworkState } from
|
|
228
|
+
import { useNetworkState } from "preact-missing-hooks";
|
|
214
229
|
|
|
215
230
|
function NetworkStatus() {
|
|
216
|
-
const { online, effectiveType, saveData } = useNetworkState()
|
|
231
|
+
const { online, effectiveType, saveData } = useNetworkState();
|
|
217
232
|
|
|
218
233
|
return (
|
|
219
234
|
<div>
|
|
220
|
-
Status: {online ?
|
|
235
|
+
Status: {online ? "Online" : "Offline"}
|
|
221
236
|
{effectiveType && ` (${effectiveType})`}
|
|
222
|
-
{saveData &&
|
|
237
|
+
{saveData && " — Reduced data mode enabled"}
|
|
223
238
|
</div>
|
|
224
|
-
)
|
|
239
|
+
);
|
|
225
240
|
}
|
|
226
241
|
```
|
|
227
242
|
|
|
@@ -230,34 +245,34 @@ function NetworkStatus() {
|
|
|
230
245
|
### `useClipboard`
|
|
231
246
|
|
|
232
247
|
```tsx
|
|
233
|
-
import { useState } from
|
|
234
|
-
import { useClipboard } from
|
|
248
|
+
import { useState } from "preact/hooks";
|
|
249
|
+
import { useClipboard } from "preact-missing-hooks";
|
|
235
250
|
|
|
236
251
|
function CopyButton() {
|
|
237
|
-
const { copy, copied, error } = useClipboard({ resetDelay: 2000 })
|
|
252
|
+
const { copy, copied, error } = useClipboard({ resetDelay: 2000 });
|
|
238
253
|
|
|
239
254
|
return (
|
|
240
|
-
<button onClick={() => copy(
|
|
241
|
-
{copied ?
|
|
255
|
+
<button onClick={() => copy("Hello, World!")}>
|
|
256
|
+
{copied ? "Copied!" : "Copy"}
|
|
242
257
|
</button>
|
|
243
|
-
)
|
|
258
|
+
);
|
|
244
259
|
}
|
|
245
260
|
|
|
246
261
|
function PasteInput() {
|
|
247
|
-
const [text, setText] = useState(
|
|
248
|
-
const { paste } = useClipboard()
|
|
262
|
+
const [text, setText] = useState("");
|
|
263
|
+
const { paste } = useClipboard();
|
|
249
264
|
|
|
250
265
|
const handlePaste = async () => {
|
|
251
|
-
const content = await paste()
|
|
252
|
-
setText(content)
|
|
253
|
-
}
|
|
266
|
+
const content = await paste();
|
|
267
|
+
setText(content);
|
|
268
|
+
};
|
|
254
269
|
|
|
255
270
|
return (
|
|
256
271
|
<div>
|
|
257
272
|
<input value={text} onChange={(e) => setText(e.target.value)} />
|
|
258
273
|
<button onClick={handlePaste}>Paste</button>
|
|
259
274
|
</div>
|
|
260
|
-
)
|
|
275
|
+
);
|
|
261
276
|
}
|
|
262
277
|
```
|
|
263
278
|
|
|
@@ -268,26 +283,26 @@ function PasteInput() {
|
|
|
268
283
|
Detects rage clicks (multiple rapid clicks in the same area), e.g. when the UI is unresponsive. Report them to [Sentry](https://docs.sentry.io/product/issues/issue-details/replay-issues/rage-clicks/) or your error tracker to surface rage-click issues and lower rage-click-related support.
|
|
269
284
|
|
|
270
285
|
```tsx
|
|
271
|
-
import { useRef } from
|
|
272
|
-
import { useRageClick } from
|
|
286
|
+
import { useRef } from "preact/hooks";
|
|
287
|
+
import { useRageClick } from "preact-missing-hooks";
|
|
273
288
|
|
|
274
289
|
function SubmitButton() {
|
|
275
|
-
const ref = useRef<HTMLButtonElement>(null)
|
|
290
|
+
const ref = useRef<HTMLButtonElement>(null);
|
|
276
291
|
|
|
277
292
|
useRageClick(ref, {
|
|
278
293
|
onRageClick: ({ count, event }) => {
|
|
279
294
|
// Report to Sentry (or your error tracker) to create rage-click issues
|
|
280
|
-
Sentry.captureMessage(
|
|
281
|
-
level:
|
|
282
|
-
extra: { count, target: event.target, tag:
|
|
283
|
-
})
|
|
295
|
+
Sentry.captureMessage("Rage click detected", {
|
|
296
|
+
level: "warning",
|
|
297
|
+
extra: { count, target: event.target, tag: "rage_click" },
|
|
298
|
+
});
|
|
284
299
|
},
|
|
285
300
|
threshold: 5, // min clicks (default 5, Sentry-style)
|
|
286
301
|
timeWindow: 1000, // ms (default 1000)
|
|
287
302
|
distanceThreshold: 30, // px (default 30)
|
|
288
|
-
})
|
|
303
|
+
});
|
|
289
304
|
|
|
290
|
-
return <button ref={ref}>Submit</button
|
|
305
|
+
return <button ref={ref}>Submit</button>;
|
|
291
306
|
}
|
|
292
307
|
```
|
|
293
308
|
|
|
@@ -298,16 +313,16 @@ function SubmitButton() {
|
|
|
298
313
|
Runs async work in a queue with **sequential** (one task at a time, by priority) or **parallel** (worker pool) execution. Lower priority number = higher priority; same priority is FIFO.
|
|
299
314
|
|
|
300
315
|
```tsx
|
|
301
|
-
import { useThreadedWorker } from
|
|
316
|
+
import { useThreadedWorker } from "preact-missing-hooks";
|
|
302
317
|
|
|
303
318
|
// Sequential: one task at a time, sorted by priority
|
|
304
|
-
const sequential = useThreadedWorker(fetchUser, { mode:
|
|
319
|
+
const sequential = useThreadedWorker(fetchUser, { mode: "sequential" });
|
|
305
320
|
|
|
306
321
|
// Parallel: up to N tasks at once
|
|
307
322
|
const parallel = useThreadedWorker(processItem, {
|
|
308
|
-
mode:
|
|
323
|
+
mode: "parallel",
|
|
309
324
|
concurrency: 4,
|
|
310
|
-
})
|
|
325
|
+
});
|
|
311
326
|
|
|
312
327
|
// API (same for both modes)
|
|
313
328
|
const {
|
|
@@ -318,11 +333,11 @@ const {
|
|
|
318
333
|
queueSize, // tasks queued + running
|
|
319
334
|
clearQueue, // clear pending tasks (running continue)
|
|
320
335
|
terminate, // clear queue and reject new run()
|
|
321
|
-
} = sequential
|
|
336
|
+
} = sequential;
|
|
322
337
|
|
|
323
338
|
// Run with priority (1 = highest)
|
|
324
|
-
await run({ userId: 1 }, { priority: 1 })
|
|
325
|
-
await run({ userId: 2 }, { priority: 3 })
|
|
339
|
+
await run({ userId: 1 }, { priority: 1 });
|
|
340
|
+
await run({ userId: 2 }, { priority: 3 });
|
|
326
341
|
```
|
|
327
342
|
|
|
328
343
|
---
|
|
@@ -338,40 +353,40 @@ Production-ready IndexedDB hook: database initialization, table creation (with k
|
|
|
338
353
|
**Database API:** `db.table(name)`, `db.hasTable(name)`, `db.transaction(storeNames, mode, callback, options?)`.
|
|
339
354
|
|
|
340
355
|
```tsx
|
|
341
|
-
import { useIndexedDB } from
|
|
356
|
+
import { useIndexedDB } from "preact-missing-hooks";
|
|
342
357
|
|
|
343
358
|
function App() {
|
|
344
359
|
const { db, isReady, error } = useIndexedDB({
|
|
345
|
-
name:
|
|
360
|
+
name: "my-app-db",
|
|
346
361
|
version: 1,
|
|
347
362
|
tables: {
|
|
348
|
-
users: { keyPath:
|
|
349
|
-
settings: { keyPath:
|
|
363
|
+
users: { keyPath: "id", autoIncrement: true, indexes: ["email"] },
|
|
364
|
+
settings: { keyPath: "key" },
|
|
350
365
|
},
|
|
351
|
-
})
|
|
366
|
+
});
|
|
352
367
|
|
|
353
|
-
if (error) return <div>Failed to open database</div
|
|
354
|
-
if (!isReady || !db) return <div>Loading...</div
|
|
368
|
+
if (error) return <div>Failed to open database</div>;
|
|
369
|
+
if (!isReady || !db) return <div>Loading...</div>;
|
|
355
370
|
|
|
356
|
-
const users = db.table(
|
|
371
|
+
const users = db.table("users");
|
|
357
372
|
|
|
358
373
|
// All operations return Promises and accept optional { onSuccess, onError }
|
|
359
|
-
await users.insert({ email:
|
|
360
|
-
await users.update(1, { name:
|
|
361
|
-
const found = await users.query((u) => u.email.startsWith(
|
|
362
|
-
const n = await users.count()
|
|
363
|
-
await users.delete(1)
|
|
364
|
-
await users.upsert({ id: 2, email:
|
|
365
|
-
await users.bulkInsert([{ email:
|
|
366
|
-
await users.clear()
|
|
374
|
+
await users.insert({ email: "a@b.com", name: "Alice" });
|
|
375
|
+
await users.update(1, { name: "Alice Smith" });
|
|
376
|
+
const found = await users.query((u) => u.email.startsWith("a@"));
|
|
377
|
+
const n = await users.count();
|
|
378
|
+
await users.delete(1);
|
|
379
|
+
await users.upsert({ id: 2, email: "b@b.com" });
|
|
380
|
+
await users.bulkInsert([{ email: "c@b.com" }, { email: "d@b.com" }]);
|
|
381
|
+
await users.clear();
|
|
367
382
|
|
|
368
383
|
// Full transaction support
|
|
369
|
-
await db.transaction([
|
|
370
|
-
await tx.table(
|
|
371
|
-
await tx.table(
|
|
372
|
-
})
|
|
384
|
+
await db.transaction(["users", "settings"], "readwrite", async (tx) => {
|
|
385
|
+
await tx.table("users").insert({ email: "e@b.com" });
|
|
386
|
+
await tx.table("settings").upsert({ key: "theme", value: "dark" });
|
|
387
|
+
});
|
|
373
388
|
|
|
374
|
-
return <div>DB ready. Tables: {db.hasTable(
|
|
389
|
+
return <div>DB ready. Tables: {db.hasTable("users") ? "users" : ""}</div>;
|
|
375
390
|
}
|
|
376
391
|
```
|
|
377
392
|
|
|
@@ -384,8 +399,8 @@ Detects client IP addresses using WebRTC ICE candidates and a STUN server (**fro
|
|
|
384
399
|
Returns `{ ips: string[], loading: boolean, error: string | null }`. Options: `stunServers`, `timeout` (ms), `onDetect(ip)`.
|
|
385
400
|
|
|
386
401
|
```tsx
|
|
387
|
-
import { useWebRTCIP } from
|
|
388
|
-
import { useState, useEffect } from
|
|
402
|
+
import { useWebRTCIP } from "preact-missing-hooks";
|
|
403
|
+
import { useState, useEffect } from "preact/hooks";
|
|
389
404
|
|
|
390
405
|
function ClientIP() {
|
|
391
406
|
const { ips, loading, error } = useWebRTCIP({
|
|
@@ -393,25 +408,25 @@ function ClientIP() {
|
|
|
393
408
|
onDetect: (ip) => {
|
|
394
409
|
/* optional: e.g. analytics */
|
|
395
410
|
},
|
|
396
|
-
})
|
|
397
|
-
const [fallbackIP, setFallbackIP] = useState<string | null>(null)
|
|
411
|
+
});
|
|
412
|
+
const [fallbackIP, setFallbackIP] = useState<string | null>(null);
|
|
398
413
|
|
|
399
414
|
// Fallback to public IP API when WebRTC fails or returns empty
|
|
400
415
|
useEffect(() => {
|
|
401
|
-
if (loading || ips.length > 0) return
|
|
416
|
+
if (loading || ips.length > 0) return;
|
|
402
417
|
if (error) {
|
|
403
|
-
fetch(
|
|
418
|
+
fetch("https://api.ipify.org?format=json")
|
|
404
419
|
.then((r) => r.json())
|
|
405
420
|
.then((d) => setFallbackIP(d.ip))
|
|
406
|
-
.catch(() => {})
|
|
421
|
+
.catch(() => {});
|
|
407
422
|
}
|
|
408
|
-
}, [loading, ips.length, error])
|
|
423
|
+
}, [loading, ips.length, error]);
|
|
409
424
|
|
|
410
|
-
if (loading) return <p>Detecting IP…</p
|
|
411
|
-
if (ips.length > 0) return <p>IPs (WebRTC): {ips.join(
|
|
412
|
-
if (fallbackIP) return <p>IP (fallback API): {fallbackIP}</p
|
|
413
|
-
if (error) return <p>WebRTC failed. Try fallback API.</p
|
|
414
|
-
return null
|
|
425
|
+
if (loading) return <p>Detecting IP…</p>;
|
|
426
|
+
if (ips.length > 0) return <p>IPs (WebRTC): {ips.join(", ")}</p>;
|
|
427
|
+
if (fallbackIP) return <p>IP (fallback API): {fallbackIP}</p>;
|
|
428
|
+
if (error) return <p>WebRTC failed. Try fallback API.</p>;
|
|
429
|
+
return null;
|
|
415
430
|
}
|
|
416
431
|
```
|
|
417
432
|
|
|
@@ -424,23 +439,23 @@ Runs WebAssembly computation in a Web Worker so the main thread stays responsive
|
|
|
424
439
|
Returns `{ compute, result, loading, error, ready }`. Options: `wasmUrl` (required), `exportName` (default `'compute'`), optional `workerUrl` (custom worker script), optional `importObject` (must be serializable for the default worker).
|
|
425
440
|
|
|
426
441
|
```tsx
|
|
427
|
-
import { useWasmCompute } from
|
|
442
|
+
import { useWasmCompute } from "preact-missing-hooks";
|
|
428
443
|
|
|
429
444
|
function AddWithWasm() {
|
|
430
445
|
const { compute, result, loading, error, ready } = useWasmCompute<
|
|
431
446
|
number,
|
|
432
447
|
number
|
|
433
448
|
>({
|
|
434
|
-
wasmUrl:
|
|
435
|
-
exportName:
|
|
436
|
-
})
|
|
449
|
+
wasmUrl: "/add.wasm",
|
|
450
|
+
exportName: "add",
|
|
451
|
+
});
|
|
437
452
|
|
|
438
453
|
const handleClick = () => {
|
|
439
|
-
if (ready) compute(2).then(() => {})
|
|
440
|
-
}
|
|
454
|
+
if (ready) compute(2).then(() => {});
|
|
455
|
+
};
|
|
441
456
|
|
|
442
|
-
if (error) return <p>WASM unavailable: {error}</p
|
|
443
|
-
if (!ready) return <p>Loading WASM…</p
|
|
457
|
+
if (error) return <p>WASM unavailable: {error}</p>;
|
|
458
|
+
if (!ready) return <p>Loading WASM…</p>;
|
|
444
459
|
return (
|
|
445
460
|
<div>
|
|
446
461
|
<button onClick={handleClick} disabled={loading}>
|
|
@@ -448,7 +463,40 @@ function AddWithWasm() {
|
|
|
448
463
|
</button>
|
|
449
464
|
{result != null && <p>Result: {result}</p>}
|
|
450
465
|
</div>
|
|
451
|
-
)
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
### `useWorkerNotifications`
|
|
473
|
+
|
|
474
|
+
Listens to a Worker's `message` events and maintains state and derived stats. Your worker should `postMessage` with: `{ type: 'task_start', taskId? }`, `{ type: 'task_end', taskId?, duration? }`, `{ type: 'task_fail', taskId?, error? }`, and optionally `{ type: 'queue_size', size }`.
|
|
475
|
+
|
|
476
|
+
Returns `runningTasks`, `completedCount`, `failedCount`, `eventHistory`, `averageDurationMs`, `throughputPerSecond`, `currentQueueSize`, and **`progress`** — a single object with all active worker data (running, completed, failed, totalProcessed, avg duration, throughput/s, queue). Options: `maxHistory` (default 100), `throughputWindowMs` (default 1000).
|
|
477
|
+
|
|
478
|
+
```tsx
|
|
479
|
+
import { useWorkerNotifications } from "preact-missing-hooks";
|
|
480
|
+
|
|
481
|
+
function WorkerDashboard({ worker }) {
|
|
482
|
+
const { progress, eventHistory } = useWorkerNotifications(worker, {
|
|
483
|
+
maxHistory: 50,
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
return (
|
|
487
|
+
<div>
|
|
488
|
+
<p>
|
|
489
|
+
Running: {progress.runningTasks.length} | Done:{" "}
|
|
490
|
+
{progress.completedCount} | Failed: {progress.failedCount}
|
|
491
|
+
</p>
|
|
492
|
+
<p>
|
|
493
|
+
Avg: {progress.averageDurationMs.toFixed(0)}ms | Throughput:{" "}
|
|
494
|
+
{progress.throughputPerSecond.toFixed(2)}/s | Queue:{" "}
|
|
495
|
+
{progress.currentQueueSize}
|
|
496
|
+
</p>
|
|
497
|
+
<small>Events: {eventHistory.length}</small>
|
|
498
|
+
</div>
|
|
499
|
+
);
|
|
452
500
|
}
|
|
453
501
|
```
|
|
454
502
|
|
package/dist/entry.cjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Auto-detect Preact vs React and re-export the matching build.
|
|
4
|
+
* Used for require('preact-missing-hooks') in Node / CJS bundlers.
|
|
5
|
+
*/
|
|
6
|
+
function detect() {
|
|
7
|
+
try {
|
|
8
|
+
require.resolve("preact");
|
|
9
|
+
return require("./index.js");
|
|
10
|
+
} catch (_) {
|
|
11
|
+
try {
|
|
12
|
+
require.resolve("react");
|
|
13
|
+
return require("./react.js");
|
|
14
|
+
} catch (_) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"preact-missing-hooks: Install either \"preact\" or \"react\" in your project."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
module.exports = detect();
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=require("preact/hooks"),n=require("preact"),r=new Map;function t(e,n){(null==n||n>e.length)&&(n=e.length);for(var r=0,t=Array(n);r<n;r++)t[r]=e[r];return t}function o(e,n){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(r)return(r=r.call(e)).next.bind(r);if(Array.isArray(e)||(r=function(e,n){if(e){if("string"==typeof e)return t(e,n);var r={}.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?t(e,n):void 0}}(e))||n&&e&&"number"==typeof e.length){r&&(e=r);var o=0;return function(){return o>=e.length?{done:!0}:{done:!1,value:e[o++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function u(){return u=Object.assign?Object.assign.bind():function(e){for(var n=1;n<arguments.length;n++){var r=arguments[n];for(var t in r)({}).hasOwnProperty.call(r,t)&&(e[t]=r[t])}return e},u.apply(null,arguments)}function i(){if("undefined"==typeof navigator)return{online:!0};var e={online:navigator.onLine},n=navigator.connection;return n&&(void 0!==n.effectiveType&&(e.effectiveType=n.effectiveType),void 0!==n.downlink&&(e.downlink=n.downlink),void 0!==n.rtt&&(e.rtt=n.rtt),void 0!==n.saveData&&(e.saveData=n.saveData),void 0!==n.type&&(e.connectionType=n.type)),e}function a(e,n){try{var r=e()}catch(e){return n(e)}return r&&r.then?r.then(void 0,n):r}var c=new Map;function s(e){return new Promise(function(n,r){e.onsuccess=function(){return n(e.result)},e.onerror=function(){var n;return r(null!=(n=e.error)?n:new DOMException("Unknown IndexedDB error"))}})}function f(e,n){return n?e.then(function(e){return null==n.onSuccess||n.onSuccess(e),e}).catch(function(e){throw null==n.onError||n.onError(e),e}):e}var l=/\b(?:25[0-5]|2[0-4]\d|1?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|1?\d{1,2})){3}\b/g,d=["stun:stun.l.google.com:19302"];exports.useClipboard=function(n){void 0===n&&(n={});var r=n.resetDelay,t=void 0===r?2e3:r,o=e.useState(!1),u=o[0],i=o[1],c=e.useState(null),s=c[0],f=c[1],l=e.useCallback(function(){i(!1),f(null)},[]);return{copy:e.useCallback(function(e){try{if(f(null),"undefined"==typeof navigator||!navigator.clipboard){var n=new Error("Clipboard API is not available");return f(n),Promise.resolve(!1)}return Promise.resolve(a(function(){return Promise.resolve(navigator.clipboard.writeText(e)).then(function(){return i(!0),t>0&&setTimeout(function(){return i(!1)},t),!0})},function(e){var n=e instanceof Error?e:new Error(String(e));return f(n),!1}))}catch(e){return Promise.reject(e)}},[t]),paste:e.useCallback(function(){try{if(f(null),"undefined"==typeof navigator||!navigator.clipboard){var e=new Error("Clipboard API is not available");return f(e),Promise.resolve("")}return Promise.resolve(a(function(){return Promise.resolve(navigator.clipboard.readText())},function(e){var n=e instanceof Error?e:new Error(String(e));return f(n),""}))}catch(e){return Promise.reject(e)}},[]),copied:u,error:s,reset:l}},exports.useEventBus=function(){return{emit:e.useCallback(function(e){var n=arguments,t=r.get(e);t&&t.forEach(function(e){return e.apply(void 0,[].slice.call(n,1))})},[]),on:e.useCallback(function(e,n){var t=r.get(e);return t||(t=new Set,r.set(e,t)),t.add(n),function(){t.delete(n),0===t.size&&r.delete(e)}},[])}},exports.useIndexedDB=function(n){var r=e.useState(null),t=r[0],i=r[1],a=e.useState(null),l=a[0],d=a[1],v=e.useState(!1),p=v[0],m=v[1],h=e.useRef(n);return h.current=n,e.useEffect(function(){var e=!1;d(null),m(!1),i(null);var n=h.current;return function(e){var n=e.name+"_v"+e.version,r=c.get(n);return r||(r=function(e){return new Promise(function(n,r){var t=indexedDB.open(e.name,e.version);t.onerror=function(){var e;return r(null!=(e=t.error)?e:new DOMException("Failed to open database"))},t.onsuccess=function(){return n(t.result)},t.onupgradeneeded=function(n){for(var r=n.target.result,t=e.tables,u=0,i=Object.keys(t);u<i.length;u++){var a=i[u],c=t[a];if(!r.objectStoreNames.contains(a)){var s,f=r.createObjectStore(a,{keyPath:c.keyPath,autoIncrement:null!=(s=c.autoIncrement)&&s});if(c.indexes)for(var l,d=o(c.indexes);!(l=d()).done;){var v=l.value;f.createIndex(v,v,{unique:!1})}}}}})}(e),c.set(n,r),r)}({name:n.name,version:n.version,tables:n.tables}).then(function(n){if(e)n.close();else{var r=function(e){return{get db(){return e},hasTable:function(n){return e.objectStoreNames.contains(n)},table:function(n){return function(e,n){return function(e,n){function r(r){return e.transaction([n],r).objectStore(n)}return{insert:function(e,n){return f(s(r("readwrite").add(e)),n)},update:function(e,n,t){var o=r("readwrite");return f(s(o.get(e)).then(function(e){if(void 0===e)throw new DOMException("Key not found","NotFoundError");var r=u({},e,n);return s(o.put(r))}).then(function(){}),t)},delete:function(e,n){return f(s(r("readwrite").delete(e)).then(function(){}),n)},exists:function(e){return s(r("readonly").getKey(e)).then(function(e){return void 0!==e})},query:function(e,n){var t=r("readonly").openCursor(),o=[];return f(new Promise(function(n,r){t.onsuccess=function(){var r=t.result;r?(e(r.value)&&o.push(r.value),r.continue()):n(o)},t.onerror=function(){var e;return r(null!=(e=t.error)?e:new DOMException("Unknown error"))}}),n)},upsert:function(e,n){return f(s(r("readwrite").put(e)),n)},bulkInsert:function(e,n){var t=r("readwrite"),o=[];if(0===e.length)return f(Promise.resolve(o),n);var u=0;return f(new Promise(function(n,r){e.forEach(function(i,a){var c=t.add(i);c.onsuccess=function(){o[a]=c.result,++u===e.length&&n(o)},c.onerror=function(){var e;return r(null!=(e=c.error)?e:new DOMException("Unknown error"))}})}),n)},clear:function(e){return f(s(r("readwrite").clear()).then(function(){}),e)},count:function(e){return f(s(r("readonly").count()),null!=e?e:{})}}}(e,n)}(e,n)},transaction:function(n,r,t,o){var i=e.transaction(n,r),a={table:function(e){return function(e,n){return function(e,n){function r(){return e.objectStore(n)}return{insert:function(e,n){return f(s(r().add(e)),n)},update:function(e,n,t){var o=r();return f(s(o.get(e)).then(function(e){if(void 0===e)throw new DOMException("Key not found","NotFoundError");var r=u({},e,n);return s(o.put(r))}).then(function(){}),t)},delete:function(e,n){return f(s(r().delete(e)).then(function(){}),n)},exists:function(e){return s(r().getKey(e)).then(function(e){return void 0!==e})},query:function(e,n){var t=r().openCursor(),o=[];return f(new Promise(function(n,r){t.onsuccess=function(){var r=t.result;r?(e(r.value)&&o.push(r.value),r.continue()):n(o)},t.onerror=function(){var e;return r(null!=(e=t.error)?e:new DOMException("Unknown error"))}}),n)},upsert:function(e,n){return f(s(r().put(e)),n)},bulkInsert:function(e,n){var t=r(),o=[];if(0===e.length)return f(Promise.resolve(o),n);var u=0;return f(new Promise(function(n,r){e.forEach(function(i,a){var c=t.add(i);c.onsuccess=function(){o[a]=c.result,++u===e.length&&n(o)},c.onerror=function(){var e;return r(null!=(e=c.error)?e:new DOMException("Unknown error"))}})}),n)},clear:function(e){return f(s(r().clear()).then(function(){}),e)},count:function(e){return f(s(r().count()),null!=e?e:{})}}}(e,n)}(i,e)}},c=new Promise(function(e,n){i.oncomplete=function(){return e()},i.onerror=function(){var e;return n(null!=(e=i.error)?e:new DOMException("Transaction failed"))}}),l=t(a);return function(e,n){return n?e.then(function(){return null==n.onSuccess?void 0:n.onSuccess()}).catch(function(e){throw null==n.onError||n.onError(e),e}):e}(Promise.resolve(l).then(function(){return c}),o)}}}(n);i(r),m(!0)}}).catch(function(n){e||d(n)}),function(){e=!0}},[n.name,n.version]),{db:t,isReady:p,error:l}},exports.useMutationObserver=function(n,r,t){e.useEffect(function(){var e=n.current;if(e){var o=new MutationObserver(r);return o.observe(e,t),function(){return o.disconnect()}}},[n,r,t])},exports.useNetworkState=function(){var n=e.useState(i),r=n[0],t=n[1];return e.useEffect(function(){if("undefined"!=typeof window){var e=function(){return t(i())};window.addEventListener("online",e),window.addEventListener("offline",e);var n=navigator.connection;return null!=n&&n.addEventListener&&n.addEventListener("change",e),function(){window.removeEventListener("online",e),window.removeEventListener("offline",e),null!=n&&n.removeEventListener&&n.removeEventListener("change",e)}}},[]),r},exports.usePreferredTheme=function(){var n=e.useState(function(){if("undefined"==typeof window)return"no-preference";var e=window.matchMedia("(prefers-color-scheme: dark)"),n=window.matchMedia("(prefers-color-scheme: light)");return e.matches?"dark":n.matches?"light":"no-preference"}),r=n[0],t=n[1];return e.useEffect(function(){if("undefined"!=typeof window){var e=window.matchMedia("(prefers-color-scheme: dark)"),n=function(e){t(e.matches?"dark":"light")},r=function(){var e=window.matchMedia("(prefers-color-scheme: dark)"),n=window.matchMedia("(prefers-color-scheme: light)");t(e.matches?"dark":n.matches?"light":"no-preference")};e.addEventListener("change",n);var o=window.matchMedia("(prefers-color-scheme: light)");return o.addEventListener("change",r),function(){e.removeEventListener("change",n),o.removeEventListener("change",r)}}},[]),r},exports.useRageClick=function(n,r){var t=r.onRageClick,o=r.threshold,u=void 0===o?5:o,i=r.timeWindow,a=void 0===i?1e3:i,c=r.distanceThreshold,s=void 0===c?30:c,f=e.useRef(t);f.current=t;var l=e.useRef([]);e.useEffect(function(){var e=n.current;if(e){var r=function(e){var n=Date.now(),r={time:n,x:e.clientX,y:e.clientY},t=n-a,o=l.current.filter(function(e){return e.time>=t});if(o.push(r),Infinity!==s){var i=o.filter(function(e){return n=e,t=r,Math.hypot(t.x-n.x,t.y-n.y)<=s;var n,t});if(i.length>=u)return f.current({count:i.length,event:e}),void(l.current=[])}else if(o.length>=u)return f.current({count:o.length,event:e}),void(l.current=[]);l.current=o};return e.addEventListener("click",r),function(){return e.removeEventListener("click",r)}}},[n,u,a,s])},exports.useThreadedWorker=function(n,r){var t=r.concurrency,o="sequential"===r.mode?1:Math.max(1,void 0===t?4:t),u=e.useState(!1),i=u[0],a=u[1],c=e.useState(void 0),s=c[0],f=c[1],l=e.useState(void 0),d=l[0],v=l[1],p=e.useState(0),m=p[0],h=p[1],y=e.useRef([]),w=e.useRef(0),g=e.useRef(0),b=e.useRef(!1),k=e.useRef(n);k.current=n;var E=e.useCallback(function(){h(y.current.length+g.current)},[]),S=e.useCallback(function(){if(!(b.current||g.current>=o)){if(0===y.current.length)return 0===g.current&&a(!1),void E();y.current.sort(function(e,n){return e.priority!==n.priority?e.priority-n.priority:e.sequence-n.sequence});var e=y.current.shift();g.current+=1,a(!0),E(),(0,k.current)(e.data).then(function(n){f(n),v(void 0),e.resolve(n)}).catch(function(n){v(n),e.reject(n)}).finally(function(){g.current-=1,E(),S()}),y.current.length>0&&g.current<o&&S()}},[o,E]),x=e.useCallback(function(e,n){var r;if(b.current)return Promise.reject(new Error("Worker is terminated"));var t=null!=(r=null==n?void 0:n.priority)?r:1,o=++w.current,u=new Promise(function(n,r){y.current.push({data:e,priority:t,sequence:o,resolve:n,reject:r})});return E(),a(!0),queueMicrotask(S),u},[S,E]),C=e.useCallback(function(){var e=y.current;y.current=[],e.forEach(function(e){return e.reject(new Error("Task cleared from queue"))}),E(),0===g.current&&a(!1)},[E]),M=e.useCallback(function(){b.current=!0,C()},[C]);return e.useEffect(function(){return function(){b.current=!0}},[]),{run:x,loading:i,result:s,error:d,queueSize:m,clearQueue:C,terminate:M}},exports.useTransition=function(){var n=e.useState(!1),r=n[0],t=n[1];return[e.useCallback(function(e){t(!0),Promise.resolve().then(function(){e(),t(!1)})},[]),r]},exports.useWasmCompute=function(n){var r=n.wasmUrl,t=n.exportName,o=void 0===t?"compute":t,u=n.workerUrl,i=n.importObject,a=e.useState(void 0),c=a[0],s=a[1],f=e.useState(!0),l=f[0],d=f[1],v=e.useState(null),p=v[0],m=v[1],h=e.useState(!1),y=h[0],w=h[1],g=e.useRef(null),b=e.useRef(null),k=e.useRef(null);return e.useEffect(function(){if("undefined"==typeof window)return m("useWasmCompute is not available during SSR"),void d(!1);if("undefined"==typeof Worker)return m("Worker is not supported in this environment"),void d(!1);if("undefined"==typeof WebAssembly||"function"!=typeof WebAssembly.instantiate)return m("WebAssembly is not supported in this environment"),void d(!1);m(null),w(!1);var e=function(e){if(e)return new Worker(e);var n=new Blob(["\nself.onmessage = async (e) => {\n const d = e.data;\n if (d.type === 'init') {\n try {\n const res = await fetch(d.wasmUrl);\n const buf = await res.arrayBuffer();\n const mod = await WebAssembly.instantiate(buf, d.importObject || {});\n self.wasmInstance = mod.instance;\n self.exportName = d.exportName || 'compute';\n self.postMessage({ type: 'ready' });\n } catch (err) {\n self.postMessage({ type: 'error', error: (err && err.message) || String(err) });\n }\n return;\n }\n if (d.type === 'compute') {\n try {\n const fn = self.wasmInstance.exports[self.exportName];\n if (typeof fn !== 'function') {\n self.postMessage({ type: 'error', error: 'Export \"' + self.exportName + '\" is not a function' });\n return;\n }\n const result = fn(d.input);\n self.postMessage({ type: 'result', result: result });\n } catch (err) {\n self.postMessage({ type: 'error', error: (err && err.message) || String(err) });\n }\n }\n};\n"],{type:"application/javascript"}),r=URL.createObjectURL(n),t=new Worker(r);return URL.revokeObjectURL(r),t}(u);g.current=e;var n=function(e){var n,r=null!=(n=e.data)?n:{},t=r.type,o=r.result,u=r.error;return"ready"===t?(w(!0),void d(!1)):"error"===t?(m(null!=u?u:"Unknown error"),d(!1),void(k.current&&(k.current(new Error(u)),b.current=null,k.current=null))):void("result"===t&&(s(o),d(!1),b.current&&(b.current(o),b.current=null,k.current=null)))};return e.addEventListener("message",n),e.postMessage({type:"init",wasmUrl:r,exportName:o,importObject:null!=i?i:{}}),function(){e.removeEventListener("message",n),e.terminate(),g.current=null,k.current&&(k.current(new Error("Worker terminated")),b.current=null,k.current=null)}},[r,o,u,i]),{compute:e.useCallback(function(e){return new Promise(function(n,r){g.current&&y?p?r(new Error(p)):(b.current=n,k.current=r,d(!0),g.current.postMessage({type:"compute",input:e})):r(new Error("WASM not ready"))})},[y,p]),result:c,loading:l,error:p,ready:y}},exports.useWebRTCIP=function(n){void 0===n&&(n={});var r=n.stunServers,t=void 0===r?d:r,o=n.timeout,u=void 0===o?3e3:o,i=n.onDetect,a=e.useState([]),c=a[0],s=a[1],f=e.useState(!0),v=f[0],p=f[1],m=e.useState(null),h=m[0],y=m[1],w=e.useRef(null),g=e.useRef(null),b=e.useRef(new Set),k=e.useRef(i);return k.current=i,e.useEffect(function(){if("undefined"==typeof window)return p(!1),void y("WebRTC IP detection is not available during SSR");if("undefined"==typeof RTCPeerConnection)return p(!1),void y("RTCPeerConnection is not available");var e=new Set;b.current=e;var n=function(){g.current&&(clearTimeout(g.current),g.current=null),w.current&&(w.current.close(),w.current=null),p(!1)},r=function(n){e.has(n)||(e.add(n),s(function(e){return[].concat(e,[n])}),null==k.current||k.current(n))};try{var o=new RTCPeerConnection({iceServers:[{urls:t}]});w.current=o,o.onicecandidate=function(e){var n,t=e.candidate;t&&t.candidate&&((n=t.candidate.match(l))?[].concat(n):[]).forEach(r)},o.createDataChannel(""),o.createOffer().then(function(e){return o.setLocalDescription(e)}).catch(function(e){y(e instanceof Error?e.message:"Failed to create offer"),n()}),g.current=setTimeout(function(){return n()},u)}catch(e){y(e instanceof Error?e.message:"WebRTC setup failed"),n()}return function(){n()}},[t.join(","),u]),{ips:c,loading:v,error:h}},exports.useWorkerNotifications=function(n,r){void 0===r&&(r={});var t=r.maxHistory,o=void 0===t?100:t,u=r.throughputWindowMs,i=void 0===u?1e3:u,a=e.useState([]),c=a[0],s=a[1],f=e.useState(0),l=f[0],d=f[1],v=e.useState(0),p=v[0],m=v[1],h=e.useState([]),y=h[0],w=h[1],g=e.useState(0),b=g[0],k=g[1],E=e.useRef([]),S=e.useRef(0),x=e.useRef(0);e.useEffect(function(){if(n){var e=function(e){var n=function(e){if(null==e||"object"!=typeof e)return null;var n=e.type;return"task_start"!==n&&"task_end"!==n&&"task_fail"!==n&&"queue_size"!==n?null:{type:n,taskId:"string"==typeof e.taskId?e.taskId:void 0,duration:"number"==typeof e.duration?e.duration:void 0,error:"string"==typeof e.error?e.error:void 0,size:"number"==typeof e.size?e.size:void 0,timestamp:Date.now()}}(e.data);if(n)if(w(function(e){return[].concat(e,[n]).slice(-o)}),"task_start"===n.type&&n.taskId)s(function(e){return e.includes(n.taskId)?e:[].concat(e,[n.taskId])});else if("task_end"===n.type){n.taskId&&s(function(e){return e.filter(function(e){return e!==n.taskId})}),d(function(e){return e+1});var r=Date.now()-i;E.current=[].concat(E.current.filter(function(e){return e>=r}),[n.timestamp]),"number"==typeof n.duration&&(S.current+=n.duration,x.current+=1)}else"task_fail"===n.type?(n.taskId&&s(function(e){return e.filter(function(e){return e!==n.taskId})}),m(function(e){return e+1})):"queue_size"===n.type&&"number"==typeof n.size&&k(n.size)};return n.addEventListener("message",e),function(){return n.removeEventListener("message",e)}}},[n,o]);var C=e.useMemo(function(){var e=x.current;return e>0?S.current/e:0},[y]),M=e.useMemo(function(){var e=Date.now()-i;return E.current.filter(function(n){return n>=e}).length/(i/1e3)},[y,i]),P=e.useMemo(function(){return{runningTasks:c,completedCount:l,failedCount:p,averageDurationMs:C,throughputPerSecond:M,currentQueueSize:b,totalProcessed:l+p,recentEventCount:y.length}},[c,l,p,C,M,b,y.length]);return{runningTasks:c,completedCount:l,failedCount:p,eventHistory:y,averageDurationMs:C,throughputPerSecond:M,currentQueueSize:b,progress:P}},exports.useWrappedChildren=function(r,t,o){return void 0===o&&(o="preserve"),e.useMemo(function(){if(!r)return r;var e=function(e){if(!n.isValidElement(e))return e;var r,i=e.props||{};r="override"===o?u({},i,t):u({},t,i);var a=null==i?void 0:i.style,c=null==t?void 0:t.style;return a&&c&&"object"==typeof a&&"object"==typeof c&&(r.style="override"===o?u({},a,c):u({},c,a)),n.cloneElement(e,r)};return Array.isArray(r)?r.map(e):e(r)},[r,t,o])};
|
|
2
|
+
//# sourceMappingURL=entry.js.map
|