zero-query 1.1.1 → 1.2.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/LICENSE +21 -21
- package/README.md +2 -0
- package/cli/args.js +33 -33
- package/cli/commands/build-api.js +443 -442
- package/cli/commands/build.js +254 -247
- package/cli/commands/bundle.js +1228 -1224
- package/cli/commands/create.js +137 -121
- package/cli/commands/dev/devtools/index.js +56 -56
- package/cli/commands/dev/devtools/js/components.js +49 -49
- package/cli/commands/dev/devtools/js/core.js +423 -423
- package/cli/commands/dev/devtools/js/elements.js +421 -421
- package/cli/commands/dev/devtools/js/network.js +166 -166
- package/cli/commands/dev/devtools/js/performance.js +73 -73
- package/cli/commands/dev/devtools/js/router.js +105 -105
- package/cli/commands/dev/devtools/js/source.js +132 -132
- package/cli/commands/dev/devtools/js/stats.js +35 -35
- package/cli/commands/dev/devtools/js/tabs.js +79 -79
- package/cli/commands/dev/devtools/panel.html +95 -95
- package/cli/commands/dev/devtools/styles.css +244 -244
- package/cli/commands/dev/index.js +107 -107
- package/cli/commands/dev/logger.js +75 -75
- package/cli/commands/dev/overlay.js +858 -858
- package/cli/commands/dev/server.js +220 -220
- package/cli/commands/dev/validator.js +94 -94
- package/cli/commands/dev/watcher.js +172 -172
- package/cli/help.js +114 -112
- package/cli/index.js +52 -52
- package/cli/scaffold/default/LICENSE +21 -21
- package/cli/scaffold/default/app/app.js +207 -207
- package/cli/scaffold/default/app/components/about.js +201 -201
- package/cli/scaffold/default/app/components/api-demo.js +143 -143
- package/cli/scaffold/default/app/components/contact-card.js +231 -231
- package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
- package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
- package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
- package/cli/scaffold/default/app/components/counter.js +127 -127
- package/cli/scaffold/default/app/components/home.js +249 -249
- package/cli/scaffold/default/app/components/not-found.js +16 -16
- package/cli/scaffold/default/app/components/playground/playground.css +115 -115
- package/cli/scaffold/default/app/components/playground/playground.html +161 -161
- package/cli/scaffold/default/app/components/playground/playground.js +116 -116
- package/cli/scaffold/default/app/components/todos.js +225 -225
- package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
- package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
- package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
- package/cli/scaffold/default/app/routes.js +15 -15
- package/cli/scaffold/default/app/store.js +101 -101
- package/cli/scaffold/default/global.css +552 -552
- package/cli/scaffold/default/index.html +99 -99
- package/cli/scaffold/minimal/app/app.js +85 -85
- package/cli/scaffold/minimal/app/components/about.js +68 -68
- package/cli/scaffold/minimal/app/components/counter.js +122 -122
- package/cli/scaffold/minimal/app/components/home.js +68 -68
- package/cli/scaffold/minimal/app/components/not-found.js +16 -16
- package/cli/scaffold/minimal/app/routes.js +9 -9
- package/cli/scaffold/minimal/app/store.js +36 -36
- package/cli/scaffold/minimal/global.css +300 -300
- package/cli/scaffold/minimal/index.html +44 -44
- package/cli/scaffold/ssr/app/app.js +41 -41
- package/cli/scaffold/ssr/app/components/about.js +55 -55
- package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
- package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
- package/cli/scaffold/ssr/app/components/home.js +37 -37
- package/cli/scaffold/ssr/app/components/not-found.js +15 -15
- package/cli/scaffold/ssr/app/routes.js +8 -8
- package/cli/scaffold/ssr/global.css +228 -228
- package/cli/scaffold/ssr/index.html +37 -37
- package/cli/scaffold/ssr/package.json +8 -8
- package/cli/scaffold/ssr/server/data/posts.js +144 -144
- package/cli/scaffold/ssr/server/index.js +213 -213
- package/cli/scaffold/webrtc/app/app.js +11 -0
- package/cli/scaffold/webrtc/app/components/video-room.js +295 -0
- package/cli/scaffold/webrtc/app/lib/room.js +252 -0
- package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
- package/cli/scaffold/webrtc/global.css +250 -0
- package/cli/scaffold/webrtc/index.html +21 -0
- package/cli/utils.js +305 -287
- package/dist/API.md +661 -0
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +10313 -6614
- package/dist/zquery.min.js +8 -631
- package/index.d.ts +570 -371
- package/index.js +311 -240
- package/package.json +76 -70
- package/src/component.js +1709 -1691
- package/src/core.js +921 -921
- package/src/diff.js +497 -497
- package/src/errors.js +209 -209
- package/src/expression.js +922 -922
- package/src/http.js +242 -242
- package/src/package.json +1 -1
- package/src/reactive.js +255 -255
- package/src/router.js +843 -843
- package/src/ssr.js +418 -418
- package/src/store.js +318 -318
- package/src/utils.js +515 -515
- package/src/webrtc/e2ee.js +351 -0
- package/src/webrtc/errors.js +116 -0
- package/src/webrtc/ice.js +301 -0
- package/src/webrtc/index.js +131 -0
- package/src/webrtc/joinToken.js +119 -0
- package/src/webrtc/observe.js +172 -0
- package/src/webrtc/peer.js +351 -0
- package/src/webrtc/reactive.js +268 -0
- package/src/webrtc/room.js +625 -0
- package/src/webrtc/sdp.js +302 -0
- package/src/webrtc/sfu/index.js +43 -0
- package/src/webrtc/sfu/livekit.js +131 -0
- package/src/webrtc/sfu/mediasoup.js +150 -0
- package/src/webrtc/signaling.js +373 -0
- package/src/webrtc/turn.js +237 -0
- package/tests/_helpers/webrtcFakes.js +289 -0
- package/tests/audit.test.js +4158 -4158
- package/tests/cli.test.js +1136 -1103
- package/tests/compare.test.js +497 -486
- package/tests/component.test.js +3969 -3938
- package/tests/core.test.js +1910 -1910
- package/tests/dev-server.test.js +489 -489
- package/tests/diff.test.js +1416 -1416
- package/tests/docs.test.js +1664 -1650
- package/tests/electron-features.test.js +864 -864
- package/tests/errors.test.js +619 -619
- package/tests/expression.test.js +1056 -1056
- package/tests/http.test.js +648 -648
- package/tests/reactive.test.js +819 -819
- package/tests/router.test.js +2327 -2327
- package/tests/ssr.test.js +870 -870
- package/tests/store.test.js +830 -830
- package/tests/test-minifier.js +153 -153
- package/tests/test-ssr.js +27 -27
- package/tests/utils.test.js +1377 -1377
- package/tests/webrtc/e2ee.test.js +283 -0
- package/tests/webrtc/ice.test.js +202 -0
- package/tests/webrtc/joinToken.test.js +89 -0
- package/tests/webrtc/observe.test.js +111 -0
- package/tests/webrtc/peer.test.js +373 -0
- package/tests/webrtc/reactive.test.js +235 -0
- package/tests/webrtc/room.test.js +406 -0
- package/tests/webrtc/sdp.test.js +151 -0
- package/tests/webrtc/sfu-livekit.test.js +119 -0
- package/tests/webrtc/sfu.test.js +160 -0
- package/tests/webrtc/signaling.test.js +251 -0
- package/tests/webrtc/turn.test.js +256 -0
- package/types/collection.d.ts +383 -383
- package/types/component.d.ts +186 -186
- package/types/errors.d.ts +135 -135
- package/types/http.d.ts +92 -92
- package/types/misc.d.ts +201 -201
- package/types/reactive.d.ts +98 -98
- package/types/router.d.ts +190 -190
- package/types/ssr.d.ts +102 -102
- package/types/store.d.ts +146 -146
- package/types/utils.d.ts +245 -245
- package/types/webrtc.d.ts +653 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/webrtc/reactive.js - reactive composables on top of `Room`
|
|
3
|
+
*
|
|
4
|
+
* Thin wrappers that adapt the Room / Peer surface into the project's
|
|
5
|
+
* `signal()`/`effect()` primitives. They mirror the surface described in
|
|
6
|
+
* the roadmap: `useRoom`, `usePeer`, `useTracks`, `useDataChannel`, and
|
|
7
|
+
* `useConnectionQuality`. Each returns either a `Signal` (cleanup via the
|
|
8
|
+
* room's `leave()`) or a small object with a `.close()` / `.dispose()`
|
|
9
|
+
* method - callers manage lifetime explicitly since the component runtime
|
|
10
|
+
* does not currently expose `onCleanup`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { signal } from '../reactive.js';
|
|
14
|
+
import { Room, join } from './room.js';
|
|
15
|
+
import { WebRtcError } from './errors.js';
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Join a room and return a Promise that resolves to a `Room` whose
|
|
20
|
+
* `peers` / `localTracks` signals can be consumed directly. The returned
|
|
21
|
+
* Promise also exposes a `.dispose()` shortcut (`room.leave()`); when the
|
|
22
|
+
* room is closed, signals stop updating.
|
|
23
|
+
*
|
|
24
|
+
* Two call shapes:
|
|
25
|
+
* useRoom(url, opts) → join via signaling
|
|
26
|
+
* useRoom(roomInstance) → wrap an existing Room (composability)
|
|
27
|
+
*
|
|
28
|
+
* @param {string|Room} urlOrRoom
|
|
29
|
+
* @param {object} [opts]
|
|
30
|
+
* @returns {Promise<Room>}
|
|
31
|
+
*/
|
|
32
|
+
export function useRoom(urlOrRoom, opts) {
|
|
33
|
+
if (urlOrRoom instanceof Room) {
|
|
34
|
+
return Promise.resolve(urlOrRoom);
|
|
35
|
+
}
|
|
36
|
+
if (typeof urlOrRoom !== 'string') {
|
|
37
|
+
return Promise.reject(new WebRtcError(
|
|
38
|
+
'useRoom: first argument must be a signaling URL or a Room',
|
|
39
|
+
{ code: 'ZQ_WEBRTC_USE_ROOM_BAD_ARG' }
|
|
40
|
+
));
|
|
41
|
+
}
|
|
42
|
+
return join(urlOrRoom, opts || {});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Reactive view of a single remote peer.
|
|
48
|
+
*
|
|
49
|
+
* @param {Room} room
|
|
50
|
+
* @param {string} peerId
|
|
51
|
+
* @returns {{ readonly value: object | null, dispose: () => void }}
|
|
52
|
+
* A getter-only signal-like with `.value` (the PeerInfo or `null` if absent)
|
|
53
|
+
* and `.dispose()` to stop listening.
|
|
54
|
+
*/
|
|
55
|
+
export function usePeer(room, peerId) {
|
|
56
|
+
if (!(room instanceof Room)) {
|
|
57
|
+
throw new WebRtcError('usePeer: room must be a Room instance', { code: 'ZQ_WEBRTC_USE_PEER_BAD_ROOM' });
|
|
58
|
+
}
|
|
59
|
+
if (typeof peerId !== 'string' || peerId.length === 0) {
|
|
60
|
+
throw new WebRtcError('usePeer: peerId must be a non-empty string', { code: 'ZQ_WEBRTC_USE_PEER_BAD_ID' });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const out = signal(null);
|
|
64
|
+
const refresh = () => {
|
|
65
|
+
const map = room.peers.peek();
|
|
66
|
+
const info = map.get(peerId) || null;
|
|
67
|
+
if (info !== out.peek()) out.value = info;
|
|
68
|
+
};
|
|
69
|
+
refresh();
|
|
70
|
+
const unsub = room.peers.subscribe(refresh);
|
|
71
|
+
return {
|
|
72
|
+
get value() { return out.value; },
|
|
73
|
+
peek() { return out.peek(); },
|
|
74
|
+
subscribe(cb) { return out.subscribe(cb); },
|
|
75
|
+
dispose() { try { unsub(); } catch (_) {} },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Reactive list of a peer's currently-attached `MediaStreamTrack`s.
|
|
82
|
+
*
|
|
83
|
+
* @param {object} peerInfo - The PeerInfo object yielded by `room.peers`.
|
|
84
|
+
* @returns {{ readonly value: MediaStreamTrack[], dispose: () => void }}
|
|
85
|
+
*/
|
|
86
|
+
export function useTracks(peerInfo) {
|
|
87
|
+
if (!peerInfo || !peerInfo.stream) {
|
|
88
|
+
throw new WebRtcError('useTracks: peerInfo.stream is required', { code: 'ZQ_WEBRTC_USE_TRACKS_BAD_PEER' });
|
|
89
|
+
}
|
|
90
|
+
const sig = signal(_safeGetTracks(peerInfo.stream));
|
|
91
|
+
|
|
92
|
+
const refresh = () => {
|
|
93
|
+
const next = _safeGetTracks(peerInfo.stream);
|
|
94
|
+
// Replace the array reference so subscribers always notify.
|
|
95
|
+
sig.value = next;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
let unsub = null;
|
|
99
|
+
const stream = peerInfo.stream;
|
|
100
|
+
if (typeof stream.addEventListener === 'function') {
|
|
101
|
+
stream.addEventListener('addtrack', refresh);
|
|
102
|
+
stream.addEventListener('removetrack', refresh);
|
|
103
|
+
unsub = () => {
|
|
104
|
+
try { stream.removeEventListener('addtrack', refresh); } catch (_) {}
|
|
105
|
+
try { stream.removeEventListener('removetrack', refresh); } catch (_) {}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
get value() { return sig.value; },
|
|
111
|
+
peek() { return sig.peek(); },
|
|
112
|
+
subscribe(cb) { return sig.subscribe(cb); },
|
|
113
|
+
/** Manually re-sample (useful in tests / environments without addtrack events). */
|
|
114
|
+
refresh,
|
|
115
|
+
dispose() { if (unsub) unsub(); },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Reactive data-channel wrapper backed by `room.dataChannel(label)`.
|
|
122
|
+
*
|
|
123
|
+
* @param {Room} room
|
|
124
|
+
* @param {string} label
|
|
125
|
+
* @param {{ history?: number, opts?: RTCDataChannelInit }} [opts]
|
|
126
|
+
* @returns {{
|
|
127
|
+
* messages: { readonly value: Array<{ data: any, from: string, at: number }>, peek: () => any[], subscribe: (cb: Function) => () => void },
|
|
128
|
+
* send: (data: any) => void,
|
|
129
|
+
* close: () => void,
|
|
130
|
+
* dispose: () => void,
|
|
131
|
+
* }}
|
|
132
|
+
*/
|
|
133
|
+
export function useDataChannel(room, label, opts) {
|
|
134
|
+
if (!(room instanceof Room)) {
|
|
135
|
+
throw new WebRtcError('useDataChannel: room must be a Room instance', { code: 'ZQ_WEBRTC_USE_DC_BAD_ROOM' });
|
|
136
|
+
}
|
|
137
|
+
if (typeof label !== 'string' || label.length === 0) {
|
|
138
|
+
throw new WebRtcError('useDataChannel: label must be a non-empty string', { code: 'ZQ_WEBRTC_USE_DC_BAD_LABEL' });
|
|
139
|
+
}
|
|
140
|
+
const history = opts && typeof opts.history === 'number' ? opts.history : 100;
|
|
141
|
+
const wrap = room.dataChannel(label, opts && opts.opts);
|
|
142
|
+
|
|
143
|
+
const messages = signal([]);
|
|
144
|
+
const off = wrap.on('message', (data, from) => {
|
|
145
|
+
const entry = { data, from, at: Date.now() };
|
|
146
|
+
const next = messages.peek().slice();
|
|
147
|
+
next.push(entry);
|
|
148
|
+
if (next.length > history) next.splice(0, next.length - history);
|
|
149
|
+
messages.value = next;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
messages: {
|
|
154
|
+
get value() { return messages.value; },
|
|
155
|
+
peek() { return messages.peek(); },
|
|
156
|
+
subscribe(cb) { return messages.subscribe(cb); },
|
|
157
|
+
},
|
|
158
|
+
send(data) { wrap.send(data); },
|
|
159
|
+
close() {
|
|
160
|
+
try { off(); } catch (_) {}
|
|
161
|
+
try { wrap.close(); } catch (_) {}
|
|
162
|
+
},
|
|
163
|
+
dispose() {
|
|
164
|
+
try { off(); } catch (_) {}
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Periodically sample `peer.pc.getStats()` and map the result to a
|
|
172
|
+
* three-bucket connection quality classifier. Returns a signal-like with
|
|
173
|
+
* `.value` and `.dispose()` for cleanup.
|
|
174
|
+
*
|
|
175
|
+
* Heuristic (intentionally simple - more nuance lands with the
|
|
176
|
+
* observability pass):
|
|
177
|
+
* - 'good' if packetLossPct < 2 AND rtt < 200
|
|
178
|
+
* - 'fair' if packetLossPct < 10 AND rtt < 500
|
|
179
|
+
* - else 'poor'
|
|
180
|
+
*
|
|
181
|
+
* @param {object} peerInfo - PeerInfo from `room.peers`.
|
|
182
|
+
* @param {{ intervalMs?: number, getStats?: () => Promise<any> }} [opts]
|
|
183
|
+
* @returns {{ readonly value: 'good'|'fair'|'poor', dispose: () => void }}
|
|
184
|
+
*/
|
|
185
|
+
export function useConnectionQuality(peerInfo, opts) {
|
|
186
|
+
if (!peerInfo || !peerInfo.pc) {
|
|
187
|
+
throw new WebRtcError('useConnectionQuality: peerInfo.pc is required', { code: 'ZQ_WEBRTC_USE_CQ_BAD_PEER' });
|
|
188
|
+
}
|
|
189
|
+
const intervalMs = opts && typeof opts.intervalMs === 'number' ? opts.intervalMs : 2000;
|
|
190
|
+
const sampler = opts && typeof opts.getStats === 'function'
|
|
191
|
+
? opts.getStats
|
|
192
|
+
: () => peerInfo.pc.getStats();
|
|
193
|
+
|
|
194
|
+
const sig = signal('good');
|
|
195
|
+
let stopped = false;
|
|
196
|
+
|
|
197
|
+
const sample = async () => {
|
|
198
|
+
if (stopped) return;
|
|
199
|
+
try {
|
|
200
|
+
const report = await sampler();
|
|
201
|
+
const q = _classifyStats(report);
|
|
202
|
+
if (q !== sig.peek()) sig.value = q;
|
|
203
|
+
} catch (_) {
|
|
204
|
+
// sampling failures don't change the bucket
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
sample();
|
|
209
|
+
const timer = setInterval(sample, intervalMs);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
get value() { return sig.value; },
|
|
213
|
+
peek() { return sig.peek(); },
|
|
214
|
+
subscribe(cb) { return sig.subscribe(cb); },
|
|
215
|
+
dispose() {
|
|
216
|
+
stopped = true;
|
|
217
|
+
clearInterval(timer);
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// Internals
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
function _safeGetTracks(stream) {
|
|
228
|
+
if (stream && typeof stream.getTracks === 'function') {
|
|
229
|
+
try { return stream.getTracks(); }
|
|
230
|
+
catch (_) { return []; }
|
|
231
|
+
}
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Convert a `getStats()` report to a `'good' | 'fair' | 'poor'` bucket.
|
|
237
|
+
* The report can be a `RTCStatsReport`-like (Map / iterable of stat objects)
|
|
238
|
+
* or a plain object map.
|
|
239
|
+
*/
|
|
240
|
+
function _classifyStats(report) {
|
|
241
|
+
const stats = [];
|
|
242
|
+
if (report && typeof report.forEach === 'function') {
|
|
243
|
+
report.forEach((v) => stats.push(v));
|
|
244
|
+
} else if (report && typeof report === 'object') {
|
|
245
|
+
for (const k of Object.keys(report)) stats.push(report[k]);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
let inbound = null;
|
|
249
|
+
let pair = null;
|
|
250
|
+
for (const s of stats) {
|
|
251
|
+
if (!s || typeof s !== 'object') continue;
|
|
252
|
+
if (s.type === 'inbound-rtp' && !inbound) inbound = s;
|
|
253
|
+
if (s.type === 'candidate-pair' && (s.state === 'succeeded' || s.nominated)) pair = s;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let lossPct = 0;
|
|
257
|
+
if (inbound && typeof inbound.packetsLost === 'number' && typeof inbound.packetsReceived === 'number') {
|
|
258
|
+
const total = inbound.packetsLost + inbound.packetsReceived;
|
|
259
|
+
lossPct = total > 0 ? (inbound.packetsLost / total) * 100 : 0;
|
|
260
|
+
}
|
|
261
|
+
const rttMs = pair && typeof pair.currentRoundTripTime === 'number'
|
|
262
|
+
? pair.currentRoundTripTime * 1000
|
|
263
|
+
: 0;
|
|
264
|
+
|
|
265
|
+
if (lossPct < 2 && rttMs < 200) return 'good';
|
|
266
|
+
if (lossPct < 10 && rttMs < 500) return 'fair';
|
|
267
|
+
return 'poor';
|
|
268
|
+
}
|