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
|
@@ -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
|
@@ -36,7 +36,7 @@ A lightweight, extendable collection of React-like hooks for Preact, including u
|
|
|
36
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.
|
|
37
37
|
- Fully TypeScript compatible
|
|
38
38
|
- Bundled with Microbundle
|
|
39
|
-
- Zero dependencies (
|
|
39
|
+
- Zero dependencies (peer: `preact` or `react` — use `/react` for React)
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
@@ -46,22 +46,35 @@ A lightweight, extendable collection of React-like hooks for Preact, including u
|
|
|
46
46
|
npm install preact-missing-hooks
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
Ensure your app has either **preact** or **react** installed (the package uses whichever is present).
|
|
50
|
+
|
|
49
51
|
---
|
|
50
52
|
|
|
51
53
|
## Import options
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
+
|
|
57
68
|
- **Subpath exports (tree-shakeable)** — Import a single hook:
|
|
69
|
+
|
|
58
70
|
```ts
|
|
59
|
-
import { useThreadedWorker } from
|
|
60
|
-
import { useClipboard } from
|
|
61
|
-
import { useWebRTCIP } from
|
|
62
|
-
import { useWasmCompute } from
|
|
63
|
-
import { useWorkerNotifications } 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";
|
|
64
76
|
```
|
|
77
|
+
|
|
65
78
|
All hooks are available: `useTransition`, `useMutationObserver`, `useEventBus`, `useWrappedChildren`, `usePreferredTheme`, `useNetworkState`, `useClipboard`, `useRageClick`, `useThreadedWorker`, `useIndexedDB`, `useWebRTCIP`, `useWasmCompute`, `useWorkerNotifications`.
|
|
66
79
|
|
|
67
80
|
---
|
|
@@ -71,22 +84,22 @@ npm install preact-missing-hooks
|
|
|
71
84
|
### `useTransition`
|
|
72
85
|
|
|
73
86
|
```tsx
|
|
74
|
-
import { useTransition } from
|
|
87
|
+
import { useTransition } from "preact-missing-hooks";
|
|
75
88
|
|
|
76
89
|
function ExampleTransition() {
|
|
77
|
-
const [startTransition, isPending] = useTransition()
|
|
90
|
+
const [startTransition, isPending] = useTransition();
|
|
78
91
|
|
|
79
92
|
const handleClick = () => {
|
|
80
93
|
startTransition(() => {
|
|
81
94
|
// perform an expensive update here
|
|
82
|
-
})
|
|
83
|
-
}
|
|
95
|
+
});
|
|
96
|
+
};
|
|
84
97
|
|
|
85
98
|
return (
|
|
86
99
|
<button onClick={handleClick} disabled={isPending}>
|
|
87
|
-
{isPending ?
|
|
100
|
+
{isPending ? "Loading..." : "Click Me"}
|
|
88
101
|
</button>
|
|
89
|
-
)
|
|
102
|
+
);
|
|
90
103
|
}
|
|
91
104
|
```
|
|
92
105
|
|
|
@@ -95,21 +108,21 @@ function ExampleTransition() {
|
|
|
95
108
|
### `useMutationObserver`
|
|
96
109
|
|
|
97
110
|
```tsx
|
|
98
|
-
import { useRef } from
|
|
99
|
-
import { useMutationObserver } from
|
|
111
|
+
import { useRef } from "preact/hooks";
|
|
112
|
+
import { useMutationObserver } from "preact-missing-hooks";
|
|
100
113
|
|
|
101
114
|
function ExampleMutation() {
|
|
102
|
-
const ref = useRef<HTMLDivElement>(null)
|
|
115
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
103
116
|
|
|
104
117
|
useMutationObserver(
|
|
105
118
|
ref,
|
|
106
119
|
(mutations) => {
|
|
107
|
-
console.log(
|
|
120
|
+
console.log("Detected mutations:", mutations);
|
|
108
121
|
},
|
|
109
|
-
{ childList: true, subtree: true }
|
|
110
|
-
)
|
|
122
|
+
{ childList: true, subtree: true }
|
|
123
|
+
);
|
|
111
124
|
|
|
112
|
-
return <div ref={ref}>Observe this content</div
|
|
125
|
+
return <div ref={ref}>Observe this content</div>;
|
|
113
126
|
}
|
|
114
127
|
```
|
|
115
128
|
|
|
@@ -120,33 +133,33 @@ function ExampleMutation() {
|
|
|
120
133
|
```tsx
|
|
121
134
|
// types.ts
|
|
122
135
|
export type Events = {
|
|
123
|
-
notify: (message: string) => void
|
|
124
|
-
}
|
|
136
|
+
notify: (message: string) => void;
|
|
137
|
+
};
|
|
125
138
|
|
|
126
139
|
// Sender.tsx
|
|
127
|
-
import { useEventBus } from
|
|
128
|
-
import type { Events } from
|
|
140
|
+
import { useEventBus } from "preact-missing-hooks";
|
|
141
|
+
import type { Events } from "./types";
|
|
129
142
|
|
|
130
143
|
function Sender() {
|
|
131
|
-
const { emit } = useEventBus<Events>()
|
|
132
|
-
return <button onClick={() => emit(
|
|
144
|
+
const { emit } = useEventBus<Events>();
|
|
145
|
+
return <button onClick={() => emit("notify", "Hello World!")}>Send</button>;
|
|
133
146
|
}
|
|
134
147
|
|
|
135
148
|
// Receiver.tsx
|
|
136
|
-
import { useEventBus } from
|
|
137
|
-
import { useState, useEffect } from
|
|
138
|
-
import type { Events } from
|
|
149
|
+
import { useEventBus } from "preact-missing-hooks";
|
|
150
|
+
import { useState, useEffect } from "preact/hooks";
|
|
151
|
+
import type { Events } from "./types";
|
|
139
152
|
|
|
140
153
|
function Receiver() {
|
|
141
|
-
const [msg, setMsg] = useState<string>(
|
|
142
|
-
const { on } = useEventBus<Events>()
|
|
154
|
+
const [msg, setMsg] = useState<string>("");
|
|
155
|
+
const { on } = useEventBus<Events>();
|
|
143
156
|
|
|
144
157
|
useEffect(() => {
|
|
145
|
-
const unsubscribe = on(
|
|
146
|
-
return unsubscribe
|
|
147
|
-
}, [])
|
|
158
|
+
const unsubscribe = on("notify", setMsg);
|
|
159
|
+
return unsubscribe;
|
|
160
|
+
}, []);
|
|
148
161
|
|
|
149
|
-
return <div>Message: {msg}</div
|
|
162
|
+
return <div>Message: {msg}</div>;
|
|
150
163
|
}
|
|
151
164
|
```
|
|
152
165
|
|
|
@@ -155,19 +168,19 @@ function Receiver() {
|
|
|
155
168
|
### `useWrappedChildren`
|
|
156
169
|
|
|
157
170
|
```tsx
|
|
158
|
-
import { useWrappedChildren } from
|
|
171
|
+
import { useWrappedChildren } from "preact-missing-hooks";
|
|
159
172
|
|
|
160
173
|
function ParentComponent({ children }) {
|
|
161
174
|
// Inject common props into all children
|
|
162
175
|
const injectProps = {
|
|
163
|
-
className:
|
|
164
|
-
onClick: () => console.log(
|
|
165
|
-
style: { border:
|
|
166
|
-
}
|
|
176
|
+
className: "enhanced-child",
|
|
177
|
+
onClick: () => console.log("Child clicked!"),
|
|
178
|
+
style: { border: "1px solid #ccc" },
|
|
179
|
+
};
|
|
167
180
|
|
|
168
|
-
const wrappedChildren = useWrappedChildren(children, injectProps)
|
|
181
|
+
const wrappedChildren = useWrappedChildren(children, injectProps);
|
|
169
182
|
|
|
170
|
-
return <div className="parent">{wrappedChildren}</div
|
|
183
|
+
return <div className="parent">{wrappedChildren}</div>;
|
|
171
184
|
}
|
|
172
185
|
|
|
173
186
|
// Usage with preserve strategy (default - existing props are preserved)
|
|
@@ -175,21 +188,21 @@ function PreserveExample() {
|
|
|
175
188
|
return (
|
|
176
189
|
<ParentComponent>
|
|
177
190
|
<button className="btn">Existing class preserved</button>
|
|
178
|
-
<span style={{ color:
|
|
191
|
+
<span style={{ color: "red" }}>Both styles applied</span>
|
|
179
192
|
</ParentComponent>
|
|
180
|
-
)
|
|
193
|
+
);
|
|
181
194
|
}
|
|
182
195
|
|
|
183
196
|
// Usage with override strategy (injected props override existing ones)
|
|
184
197
|
function OverrideExample() {
|
|
185
|
-
const injectProps = { className:
|
|
198
|
+
const injectProps = { className: "new-class" };
|
|
186
199
|
const children = (
|
|
187
200
|
<button className="old-class">Class will be overridden</button>
|
|
188
|
-
)
|
|
201
|
+
);
|
|
189
202
|
|
|
190
|
-
const wrappedChildren = useWrappedChildren(children, injectProps,
|
|
203
|
+
const wrappedChildren = useWrappedChildren(children, injectProps, "override");
|
|
191
204
|
|
|
192
|
-
return <div>{wrappedChildren}</div
|
|
205
|
+
return <div>{wrappedChildren}</div>;
|
|
193
206
|
}
|
|
194
207
|
```
|
|
195
208
|
|
|
@@ -198,12 +211,12 @@ function OverrideExample() {
|
|
|
198
211
|
### `usePreferredTheme`
|
|
199
212
|
|
|
200
213
|
```tsx
|
|
201
|
-
import { usePreferredTheme } from
|
|
214
|
+
import { usePreferredTheme } from "preact-missing-hooks";
|
|
202
215
|
|
|
203
216
|
function ThemeAwareComponent() {
|
|
204
|
-
const theme = usePreferredTheme() // 'light' | 'dark' | 'no-preference'
|
|
217
|
+
const theme = usePreferredTheme(); // 'light' | 'dark' | 'no-preference'
|
|
205
218
|
|
|
206
|
-
return <div data-theme={theme}>Your system prefers: {theme}</div
|
|
219
|
+
return <div data-theme={theme}>Your system prefers: {theme}</div>;
|
|
207
220
|
}
|
|
208
221
|
```
|
|
209
222
|
|
|
@@ -212,18 +225,18 @@ function ThemeAwareComponent() {
|
|
|
212
225
|
### `useNetworkState`
|
|
213
226
|
|
|
214
227
|
```tsx
|
|
215
|
-
import { useNetworkState } from
|
|
228
|
+
import { useNetworkState } from "preact-missing-hooks";
|
|
216
229
|
|
|
217
230
|
function NetworkStatus() {
|
|
218
|
-
const { online, effectiveType, saveData } = useNetworkState()
|
|
231
|
+
const { online, effectiveType, saveData } = useNetworkState();
|
|
219
232
|
|
|
220
233
|
return (
|
|
221
234
|
<div>
|
|
222
|
-
Status: {online ?
|
|
235
|
+
Status: {online ? "Online" : "Offline"}
|
|
223
236
|
{effectiveType && ` (${effectiveType})`}
|
|
224
|
-
{saveData &&
|
|
237
|
+
{saveData && " — Reduced data mode enabled"}
|
|
225
238
|
</div>
|
|
226
|
-
)
|
|
239
|
+
);
|
|
227
240
|
}
|
|
228
241
|
```
|
|
229
242
|
|
|
@@ -232,34 +245,34 @@ function NetworkStatus() {
|
|
|
232
245
|
### `useClipboard`
|
|
233
246
|
|
|
234
247
|
```tsx
|
|
235
|
-
import { useState } from
|
|
236
|
-
import { useClipboard } from
|
|
248
|
+
import { useState } from "preact/hooks";
|
|
249
|
+
import { useClipboard } from "preact-missing-hooks";
|
|
237
250
|
|
|
238
251
|
function CopyButton() {
|
|
239
|
-
const { copy, copied, error } = useClipboard({ resetDelay: 2000 })
|
|
252
|
+
const { copy, copied, error } = useClipboard({ resetDelay: 2000 });
|
|
240
253
|
|
|
241
254
|
return (
|
|
242
|
-
<button onClick={() => copy(
|
|
243
|
-
{copied ?
|
|
255
|
+
<button onClick={() => copy("Hello, World!")}>
|
|
256
|
+
{copied ? "Copied!" : "Copy"}
|
|
244
257
|
</button>
|
|
245
|
-
)
|
|
258
|
+
);
|
|
246
259
|
}
|
|
247
260
|
|
|
248
261
|
function PasteInput() {
|
|
249
|
-
const [text, setText] = useState(
|
|
250
|
-
const { paste } = useClipboard()
|
|
262
|
+
const [text, setText] = useState("");
|
|
263
|
+
const { paste } = useClipboard();
|
|
251
264
|
|
|
252
265
|
const handlePaste = async () => {
|
|
253
|
-
const content = await paste()
|
|
254
|
-
setText(content)
|
|
255
|
-
}
|
|
266
|
+
const content = await paste();
|
|
267
|
+
setText(content);
|
|
268
|
+
};
|
|
256
269
|
|
|
257
270
|
return (
|
|
258
271
|
<div>
|
|
259
272
|
<input value={text} onChange={(e) => setText(e.target.value)} />
|
|
260
273
|
<button onClick={handlePaste}>Paste</button>
|
|
261
274
|
</div>
|
|
262
|
-
)
|
|
275
|
+
);
|
|
263
276
|
}
|
|
264
277
|
```
|
|
265
278
|
|
|
@@ -270,26 +283,26 @@ function PasteInput() {
|
|
|
270
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.
|
|
271
284
|
|
|
272
285
|
```tsx
|
|
273
|
-
import { useRef } from
|
|
274
|
-
import { useRageClick } from
|
|
286
|
+
import { useRef } from "preact/hooks";
|
|
287
|
+
import { useRageClick } from "preact-missing-hooks";
|
|
275
288
|
|
|
276
289
|
function SubmitButton() {
|
|
277
|
-
const ref = useRef<HTMLButtonElement>(null)
|
|
290
|
+
const ref = useRef<HTMLButtonElement>(null);
|
|
278
291
|
|
|
279
292
|
useRageClick(ref, {
|
|
280
293
|
onRageClick: ({ count, event }) => {
|
|
281
294
|
// Report to Sentry (or your error tracker) to create rage-click issues
|
|
282
|
-
Sentry.captureMessage(
|
|
283
|
-
level:
|
|
284
|
-
extra: { count, target: event.target, tag:
|
|
285
|
-
})
|
|
295
|
+
Sentry.captureMessage("Rage click detected", {
|
|
296
|
+
level: "warning",
|
|
297
|
+
extra: { count, target: event.target, tag: "rage_click" },
|
|
298
|
+
});
|
|
286
299
|
},
|
|
287
300
|
threshold: 5, // min clicks (default 5, Sentry-style)
|
|
288
301
|
timeWindow: 1000, // ms (default 1000)
|
|
289
302
|
distanceThreshold: 30, // px (default 30)
|
|
290
|
-
})
|
|
303
|
+
});
|
|
291
304
|
|
|
292
|
-
return <button ref={ref}>Submit</button
|
|
305
|
+
return <button ref={ref}>Submit</button>;
|
|
293
306
|
}
|
|
294
307
|
```
|
|
295
308
|
|
|
@@ -300,16 +313,16 @@ function SubmitButton() {
|
|
|
300
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.
|
|
301
314
|
|
|
302
315
|
```tsx
|
|
303
|
-
import { useThreadedWorker } from
|
|
316
|
+
import { useThreadedWorker } from "preact-missing-hooks";
|
|
304
317
|
|
|
305
318
|
// Sequential: one task at a time, sorted by priority
|
|
306
|
-
const sequential = useThreadedWorker(fetchUser, { mode:
|
|
319
|
+
const sequential = useThreadedWorker(fetchUser, { mode: "sequential" });
|
|
307
320
|
|
|
308
321
|
// Parallel: up to N tasks at once
|
|
309
322
|
const parallel = useThreadedWorker(processItem, {
|
|
310
|
-
mode:
|
|
323
|
+
mode: "parallel",
|
|
311
324
|
concurrency: 4,
|
|
312
|
-
})
|
|
325
|
+
});
|
|
313
326
|
|
|
314
327
|
// API (same for both modes)
|
|
315
328
|
const {
|
|
@@ -320,11 +333,11 @@ const {
|
|
|
320
333
|
queueSize, // tasks queued + running
|
|
321
334
|
clearQueue, // clear pending tasks (running continue)
|
|
322
335
|
terminate, // clear queue and reject new run()
|
|
323
|
-
} = sequential
|
|
336
|
+
} = sequential;
|
|
324
337
|
|
|
325
338
|
// Run with priority (1 = highest)
|
|
326
|
-
await run({ userId: 1 }, { priority: 1 })
|
|
327
|
-
await run({ userId: 2 }, { priority: 3 })
|
|
339
|
+
await run({ userId: 1 }, { priority: 1 });
|
|
340
|
+
await run({ userId: 2 }, { priority: 3 });
|
|
328
341
|
```
|
|
329
342
|
|
|
330
343
|
---
|
|
@@ -340,40 +353,40 @@ Production-ready IndexedDB hook: database initialization, table creation (with k
|
|
|
340
353
|
**Database API:** `db.table(name)`, `db.hasTable(name)`, `db.transaction(storeNames, mode, callback, options?)`.
|
|
341
354
|
|
|
342
355
|
```tsx
|
|
343
|
-
import { useIndexedDB } from
|
|
356
|
+
import { useIndexedDB } from "preact-missing-hooks";
|
|
344
357
|
|
|
345
358
|
function App() {
|
|
346
359
|
const { db, isReady, error } = useIndexedDB({
|
|
347
|
-
name:
|
|
360
|
+
name: "my-app-db",
|
|
348
361
|
version: 1,
|
|
349
362
|
tables: {
|
|
350
|
-
users: { keyPath:
|
|
351
|
-
settings: { keyPath:
|
|
363
|
+
users: { keyPath: "id", autoIncrement: true, indexes: ["email"] },
|
|
364
|
+
settings: { keyPath: "key" },
|
|
352
365
|
},
|
|
353
|
-
})
|
|
366
|
+
});
|
|
354
367
|
|
|
355
|
-
if (error) return <div>Failed to open database</div
|
|
356
|
-
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>;
|
|
357
370
|
|
|
358
|
-
const users = db.table(
|
|
371
|
+
const users = db.table("users");
|
|
359
372
|
|
|
360
373
|
// All operations return Promises and accept optional { onSuccess, onError }
|
|
361
|
-
await users.insert({ email:
|
|
362
|
-
await users.update(1, { name:
|
|
363
|
-
const found = await users.query((u) => u.email.startsWith(
|
|
364
|
-
const n = await users.count()
|
|
365
|
-
await users.delete(1)
|
|
366
|
-
await users.upsert({ id: 2, email:
|
|
367
|
-
await users.bulkInsert([{ email:
|
|
368
|
-
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();
|
|
369
382
|
|
|
370
383
|
// Full transaction support
|
|
371
|
-
await db.transaction([
|
|
372
|
-
await tx.table(
|
|
373
|
-
await tx.table(
|
|
374
|
-
})
|
|
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
|
+
});
|
|
375
388
|
|
|
376
|
-
return <div>DB ready. Tables: {db.hasTable(
|
|
389
|
+
return <div>DB ready. Tables: {db.hasTable("users") ? "users" : ""}</div>;
|
|
377
390
|
}
|
|
378
391
|
```
|
|
379
392
|
|
|
@@ -386,8 +399,8 @@ Detects client IP addresses using WebRTC ICE candidates and a STUN server (**fro
|
|
|
386
399
|
Returns `{ ips: string[], loading: boolean, error: string | null }`. Options: `stunServers`, `timeout` (ms), `onDetect(ip)`.
|
|
387
400
|
|
|
388
401
|
```tsx
|
|
389
|
-
import { useWebRTCIP } from
|
|
390
|
-
import { useState, useEffect } from
|
|
402
|
+
import { useWebRTCIP } from "preact-missing-hooks";
|
|
403
|
+
import { useState, useEffect } from "preact/hooks";
|
|
391
404
|
|
|
392
405
|
function ClientIP() {
|
|
393
406
|
const { ips, loading, error } = useWebRTCIP({
|
|
@@ -395,25 +408,25 @@ function ClientIP() {
|
|
|
395
408
|
onDetect: (ip) => {
|
|
396
409
|
/* optional: e.g. analytics */
|
|
397
410
|
},
|
|
398
|
-
})
|
|
399
|
-
const [fallbackIP, setFallbackIP] = useState<string | null>(null)
|
|
411
|
+
});
|
|
412
|
+
const [fallbackIP, setFallbackIP] = useState<string | null>(null);
|
|
400
413
|
|
|
401
414
|
// Fallback to public IP API when WebRTC fails or returns empty
|
|
402
415
|
useEffect(() => {
|
|
403
|
-
if (loading || ips.length > 0) return
|
|
416
|
+
if (loading || ips.length > 0) return;
|
|
404
417
|
if (error) {
|
|
405
|
-
fetch(
|
|
418
|
+
fetch("https://api.ipify.org?format=json")
|
|
406
419
|
.then((r) => r.json())
|
|
407
420
|
.then((d) => setFallbackIP(d.ip))
|
|
408
|
-
.catch(() => {})
|
|
421
|
+
.catch(() => {});
|
|
409
422
|
}
|
|
410
|
-
}, [loading, ips.length, error])
|
|
423
|
+
}, [loading, ips.length, error]);
|
|
411
424
|
|
|
412
|
-
if (loading) return <p>Detecting IP…</p
|
|
413
|
-
if (ips.length > 0) return <p>IPs (WebRTC): {ips.join(
|
|
414
|
-
if (fallbackIP) return <p>IP (fallback API): {fallbackIP}</p
|
|
415
|
-
if (error) return <p>WebRTC failed. Try fallback API.</p
|
|
416
|
-
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;
|
|
417
430
|
}
|
|
418
431
|
```
|
|
419
432
|
|
|
@@ -426,23 +439,23 @@ Runs WebAssembly computation in a Web Worker so the main thread stays responsive
|
|
|
426
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).
|
|
427
440
|
|
|
428
441
|
```tsx
|
|
429
|
-
import { useWasmCompute } from
|
|
442
|
+
import { useWasmCompute } from "preact-missing-hooks";
|
|
430
443
|
|
|
431
444
|
function AddWithWasm() {
|
|
432
445
|
const { compute, result, loading, error, ready } = useWasmCompute<
|
|
433
446
|
number,
|
|
434
447
|
number
|
|
435
448
|
>({
|
|
436
|
-
wasmUrl:
|
|
437
|
-
exportName:
|
|
438
|
-
})
|
|
449
|
+
wasmUrl: "/add.wasm",
|
|
450
|
+
exportName: "add",
|
|
451
|
+
});
|
|
439
452
|
|
|
440
453
|
const handleClick = () => {
|
|
441
|
-
if (ready) compute(2).then(() => {})
|
|
442
|
-
}
|
|
454
|
+
if (ready) compute(2).then(() => {});
|
|
455
|
+
};
|
|
443
456
|
|
|
444
|
-
if (error) return <p>WASM unavailable: {error}</p
|
|
445
|
-
if (!ready) return <p>Loading WASM…</p
|
|
457
|
+
if (error) return <p>WASM unavailable: {error}</p>;
|
|
458
|
+
if (!ready) return <p>Loading WASM…</p>;
|
|
446
459
|
return (
|
|
447
460
|
<div>
|
|
448
461
|
<button onClick={handleClick} disabled={loading}>
|
|
@@ -450,7 +463,7 @@ function AddWithWasm() {
|
|
|
450
463
|
</button>
|
|
451
464
|
{result != null && <p>Result: {result}</p>}
|
|
452
465
|
</div>
|
|
453
|
-
)
|
|
466
|
+
);
|
|
454
467
|
}
|
|
455
468
|
```
|
|
456
469
|
|
|
@@ -463,27 +476,27 @@ Listens to a Worker's `message` events and maintains state and derived stats. Yo
|
|
|
463
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).
|
|
464
477
|
|
|
465
478
|
```tsx
|
|
466
|
-
import { useWorkerNotifications } from
|
|
479
|
+
import { useWorkerNotifications } from "preact-missing-hooks";
|
|
467
480
|
|
|
468
481
|
function WorkerDashboard({ worker }) {
|
|
469
482
|
const { progress, eventHistory } = useWorkerNotifications(worker, {
|
|
470
483
|
maxHistory: 50,
|
|
471
|
-
})
|
|
484
|
+
});
|
|
472
485
|
|
|
473
486
|
return (
|
|
474
487
|
<div>
|
|
475
488
|
<p>
|
|
476
|
-
Running: {progress.runningTasks.length} | Done:{
|
|
489
|
+
Running: {progress.runningTasks.length} | Done:{" "}
|
|
477
490
|
{progress.completedCount} | Failed: {progress.failedCount}
|
|
478
491
|
</p>
|
|
479
492
|
<p>
|
|
480
|
-
Avg: {progress.averageDurationMs.toFixed(0)}ms | Throughput:{
|
|
481
|
-
{progress.throughputPerSecond.toFixed(2)}/s | Queue:{
|
|
493
|
+
Avg: {progress.averageDurationMs.toFixed(0)}ms | Throughput:{" "}
|
|
494
|
+
{progress.throughputPerSecond.toFixed(2)}/s | Queue:{" "}
|
|
482
495
|
{progress.currentQueueSize}
|
|
483
496
|
</p>
|
|
484
497
|
<small>Events: {eventHistory.length}</small>
|
|
485
498
|
</div>
|
|
486
|
-
)
|
|
499
|
+
);
|
|
487
500
|
}
|
|
488
501
|
```
|
|
489
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
|