@use-everywhere/core 0.10.0 → 0.11.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/dist/index.cjs +3 -1
- package/dist/index.js +3 -1
- package/dist/shared-worker.cjs +61 -15
- package/dist/shared-worker.d.cts +60 -7
- package/dist/shared-worker.d.ts +60 -7
- package/dist/shared-worker.js +60 -15
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1051,12 +1051,14 @@ function createWebLocksLeader(name, options, locks) {
|
|
|
1051
1051
|
if (closed || !eligible || releaseHeld || pending) return;
|
|
1052
1052
|
const controller = new AbortController();
|
|
1053
1053
|
pending = controller;
|
|
1054
|
+
let grant;
|
|
1054
1055
|
const held = new Promise((resolve) => {
|
|
1055
|
-
|
|
1056
|
+
grant = resolve;
|
|
1056
1057
|
});
|
|
1057
1058
|
void locks.request(name, { signal: controller.signal }, async () => {
|
|
1058
1059
|
pending = null;
|
|
1059
1060
|
if (closed || !eligible) return;
|
|
1061
|
+
releaseHeld = grant;
|
|
1060
1062
|
setLeader(clientId);
|
|
1061
1063
|
announce2();
|
|
1062
1064
|
await held;
|
package/dist/index.js
CHANGED
|
@@ -994,12 +994,14 @@ function createWebLocksLeader(name, options, locks) {
|
|
|
994
994
|
if (closed || !eligible || releaseHeld || pending) return;
|
|
995
995
|
const controller = new AbortController();
|
|
996
996
|
pending = controller;
|
|
997
|
+
let grant;
|
|
997
998
|
const held = new Promise((resolve) => {
|
|
998
|
-
|
|
999
|
+
grant = resolve;
|
|
999
1000
|
});
|
|
1000
1001
|
void locks.request(name, { signal: controller.signal }, async () => {
|
|
1001
1002
|
pending = null;
|
|
1002
1003
|
if (closed || !eligible) return;
|
|
1004
|
+
releaseHeld = grant;
|
|
1003
1005
|
setLeader(clientId);
|
|
1004
1006
|
announce2();
|
|
1005
1007
|
await held;
|
package/dist/shared-worker.cjs
CHANGED
|
@@ -20,34 +20,80 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/shared-worker.ts
|
|
21
21
|
var shared_worker_exports = {};
|
|
22
22
|
__export(shared_worker_exports, {
|
|
23
|
+
relay: () => relay,
|
|
23
24
|
startRelay: () => startRelay
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(shared_worker_exports);
|
|
26
27
|
function startRelay(scope) {
|
|
27
28
|
const ports = /* @__PURE__ */ new Set();
|
|
29
|
+
const local = /* @__PURE__ */ new Set();
|
|
30
|
+
const fanOut = (from, data) => {
|
|
31
|
+
const dead = [];
|
|
32
|
+
for (const peer of ports) {
|
|
33
|
+
if (peer === from) continue;
|
|
34
|
+
try {
|
|
35
|
+
peer.postMessage(data);
|
|
36
|
+
} catch {
|
|
37
|
+
dead.push(peer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const corpse of dead) {
|
|
41
|
+
ports.delete(corpse);
|
|
42
|
+
local.delete(corpse);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
28
45
|
scope.onconnect = (event) => {
|
|
29
46
|
const port = event.ports[0];
|
|
30
47
|
if (!port) return;
|
|
31
48
|
ports.add(port);
|
|
32
|
-
port.addEventListener("message", (message) =>
|
|
33
|
-
const dead = [];
|
|
34
|
-
for (const peer of ports) {
|
|
35
|
-
if (peer === port) continue;
|
|
36
|
-
try {
|
|
37
|
-
peer.postMessage(message.data);
|
|
38
|
-
} catch {
|
|
39
|
-
dead.push(peer);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
for (const corpse of dead) ports.delete(corpse);
|
|
43
|
-
});
|
|
49
|
+
port.addEventListener("message", (message) => fanOut(port, message.data));
|
|
44
50
|
port.start?.();
|
|
45
51
|
};
|
|
52
|
+
return {
|
|
53
|
+
broadcast: (data) => fanOut(null, data),
|
|
54
|
+
get size() {
|
|
55
|
+
return ports.size - local.size;
|
|
56
|
+
},
|
|
57
|
+
connect() {
|
|
58
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
59
|
+
let closed = false;
|
|
60
|
+
const seat = {
|
|
61
|
+
// Asynchronous on purpose. `fanOut` runs inside a port's message
|
|
62
|
+
// listener, and delivering synchronously from there produces the
|
|
63
|
+
// re-entrancy a real MessagePort never could. Nothing is cloned: every
|
|
64
|
+
// value reaching this seat already crossed a port boundary and was
|
|
65
|
+
// structured-cloned there, so there is no live reference to leak.
|
|
66
|
+
postMessage: (data) => {
|
|
67
|
+
queueMicrotask(() => {
|
|
68
|
+
for (const listener of listeners) listener(data);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
ports.add(seat);
|
|
73
|
+
local.add(seat);
|
|
74
|
+
return {
|
|
75
|
+
kind: "shared-worker",
|
|
76
|
+
post: (data) => {
|
|
77
|
+
if (!closed) fanOut(seat, data);
|
|
78
|
+
},
|
|
79
|
+
subscribe: (listener) => {
|
|
80
|
+
listeners.add(listener);
|
|
81
|
+
return () => listeners.delete(listener);
|
|
82
|
+
},
|
|
83
|
+
close: () => {
|
|
84
|
+
if (closed) return;
|
|
85
|
+
closed = true;
|
|
86
|
+
listeners.clear();
|
|
87
|
+
ports.delete(seat);
|
|
88
|
+
local.delete(seat);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
};
|
|
46
93
|
}
|
|
47
|
-
|
|
48
|
-
startRelay(self);
|
|
49
|
-
}
|
|
94
|
+
var relay = typeof self !== "undefined" && "onconnect" in self ? startRelay(self) : void 0;
|
|
50
95
|
// Annotate the CommonJS export names for ESM import in node:
|
|
51
96
|
0 && (module.exports = {
|
|
97
|
+
relay,
|
|
52
98
|
startRelay
|
|
53
99
|
});
|
package/dist/shared-worker.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { T as Transport } from './transport.types-CV1WZOhy.cjs';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The relay that runs *inside* a SharedWorker. Host a one-line module and point
|
|
3
5
|
* `SharedWorkerTransport` at it:
|
|
@@ -12,10 +14,25 @@
|
|
|
12
14
|
* has one job, and making the caller remember to invoke it adds a way to get a
|
|
13
15
|
* worker that accepts connections and forwards nothing.
|
|
14
16
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
17
|
+
* For a worker that also does other work — one that owns the WebSocket, say —
|
|
18
|
+
* import {@link relay} and join the bus through it:
|
|
19
|
+
*
|
|
20
|
+
* ```js
|
|
21
|
+
* // socket-worker.js
|
|
22
|
+
* import { relay } from '@use-everywhere/core/shared-worker';
|
|
23
|
+
* import { createSharedStore } from '@use-everywhere/core';
|
|
24
|
+
*
|
|
25
|
+
* const store = createSharedStore('feed', { tick: null }, { transport: () => relay.connect() });
|
|
26
|
+
* new WebSocket('wss://example.com/feed').onmessage = (e) => store.set('tick', JSON.parse(e.data));
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* `startRelay` is exported for tests, which cannot install a global `onconnect`,
|
|
30
|
+
* and for a scope this module cannot detect. Prefer {@link relay} inside a real
|
|
31
|
+
* worker: calling `startRelay(self)` there installs a *second* relay over the
|
|
32
|
+
* one this module already installed, and the first one's ports are then held by
|
|
33
|
+
* a handler nothing will ever call again.
|
|
18
34
|
*/
|
|
35
|
+
|
|
19
36
|
interface RelayPort {
|
|
20
37
|
postMessage(data: unknown): void;
|
|
21
38
|
start?: () => void;
|
|
@@ -28,10 +45,36 @@ interface RelayScope {
|
|
|
28
45
|
ports: readonly RelayPort[];
|
|
29
46
|
}) => void) | null;
|
|
30
47
|
}
|
|
48
|
+
/** A running relay: the ports it holds, and the worker's own seat on the bus. */
|
|
49
|
+
interface Relay {
|
|
50
|
+
/**
|
|
51
|
+
* Join the relay from inside the worker, as one more peer.
|
|
52
|
+
*
|
|
53
|
+
* Returns a `Transport`, so worker-side code uses `createSharedStore` and the
|
|
54
|
+
* rest of the library exactly as a tab does. That is the point: the wire
|
|
55
|
+
* format stays the engines' business, and a worker that wants to publish
|
|
56
|
+
* never has to hand-assemble an envelope the protocol might redefine.
|
|
57
|
+
*/
|
|
58
|
+
connect(): Transport;
|
|
59
|
+
/**
|
|
60
|
+
* Fan raw data out to every connected port.
|
|
61
|
+
*
|
|
62
|
+
* The escape hatch, for a worker speaking some protocol of its own. Anything
|
|
63
|
+
* the library's engines will read should go through {@link connect} instead —
|
|
64
|
+
* this bypasses them entirely.
|
|
65
|
+
*/
|
|
66
|
+
broadcast(data: unknown): void;
|
|
67
|
+
/**
|
|
68
|
+
* How many ports are attached. A live count of the contexts that opened this
|
|
69
|
+
* worker, which is what you want in order to idle a socket while nobody is
|
|
70
|
+
* looking. `connect()` seats are not counted: the worker is not its own tab.
|
|
71
|
+
*/
|
|
72
|
+
readonly size: number;
|
|
73
|
+
}
|
|
31
74
|
/**
|
|
32
|
-
* Fan every message out to the other connected
|
|
75
|
+
* Fan every message out to the other connected peers.
|
|
33
76
|
*
|
|
34
|
-
*
|
|
77
|
+
* Three properties the buses above this depend on:
|
|
35
78
|
*
|
|
36
79
|
* 1. **No self-echo.** A sender never receives its own message, matching
|
|
37
80
|
* BroadcastChannel exactly. Without it every store would apply its own
|
|
@@ -40,7 +83,17 @@ interface RelayScope {
|
|
|
40
83
|
* closing its port leaves an entangled port whose `postMessage` throws; one
|
|
41
84
|
* of those must not stop delivery to the tabs still listening, so the send
|
|
42
85
|
* loop is individually guarded and the corpse is pruned.
|
|
86
|
+
* 3. **The worker is a peer, not a special case.** A local seat from
|
|
87
|
+
* {@link Relay.connect} is held in the same set as the ports, so both rules
|
|
88
|
+
* above apply to it without a second code path to keep in agreement.
|
|
89
|
+
*/
|
|
90
|
+
declare function startRelay(scope: RelayScope): Relay;
|
|
91
|
+
/**
|
|
92
|
+
* The relay this module installed on import — present only when the module is
|
|
93
|
+
* running in a SharedWorker, and `undefined` anywhere else, so that importing
|
|
94
|
+
* it from a bundler tracing entry points, a test runner, or a server render is
|
|
95
|
+
* inert rather than a `ReferenceError`.
|
|
43
96
|
*/
|
|
44
|
-
declare
|
|
97
|
+
declare const relay: Relay | undefined;
|
|
45
98
|
|
|
46
|
-
export { type RelayPort, type RelayScope, startRelay };
|
|
99
|
+
export { type Relay, type RelayPort, type RelayScope, relay, startRelay };
|
package/dist/shared-worker.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { T as Transport } from './transport.types-CV1WZOhy.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The relay that runs *inside* a SharedWorker. Host a one-line module and point
|
|
3
5
|
* `SharedWorkerTransport` at it:
|
|
@@ -12,10 +14,25 @@
|
|
|
12
14
|
* has one job, and making the caller remember to invoke it adds a way to get a
|
|
13
15
|
* worker that accepts connections and forwards nothing.
|
|
14
16
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
17
|
+
* For a worker that also does other work — one that owns the WebSocket, say —
|
|
18
|
+
* import {@link relay} and join the bus through it:
|
|
19
|
+
*
|
|
20
|
+
* ```js
|
|
21
|
+
* // socket-worker.js
|
|
22
|
+
* import { relay } from '@use-everywhere/core/shared-worker';
|
|
23
|
+
* import { createSharedStore } from '@use-everywhere/core';
|
|
24
|
+
*
|
|
25
|
+
* const store = createSharedStore('feed', { tick: null }, { transport: () => relay.connect() });
|
|
26
|
+
* new WebSocket('wss://example.com/feed').onmessage = (e) => store.set('tick', JSON.parse(e.data));
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* `startRelay` is exported for tests, which cannot install a global `onconnect`,
|
|
30
|
+
* and for a scope this module cannot detect. Prefer {@link relay} inside a real
|
|
31
|
+
* worker: calling `startRelay(self)` there installs a *second* relay over the
|
|
32
|
+
* one this module already installed, and the first one's ports are then held by
|
|
33
|
+
* a handler nothing will ever call again.
|
|
18
34
|
*/
|
|
35
|
+
|
|
19
36
|
interface RelayPort {
|
|
20
37
|
postMessage(data: unknown): void;
|
|
21
38
|
start?: () => void;
|
|
@@ -28,10 +45,36 @@ interface RelayScope {
|
|
|
28
45
|
ports: readonly RelayPort[];
|
|
29
46
|
}) => void) | null;
|
|
30
47
|
}
|
|
48
|
+
/** A running relay: the ports it holds, and the worker's own seat on the bus. */
|
|
49
|
+
interface Relay {
|
|
50
|
+
/**
|
|
51
|
+
* Join the relay from inside the worker, as one more peer.
|
|
52
|
+
*
|
|
53
|
+
* Returns a `Transport`, so worker-side code uses `createSharedStore` and the
|
|
54
|
+
* rest of the library exactly as a tab does. That is the point: the wire
|
|
55
|
+
* format stays the engines' business, and a worker that wants to publish
|
|
56
|
+
* never has to hand-assemble an envelope the protocol might redefine.
|
|
57
|
+
*/
|
|
58
|
+
connect(): Transport;
|
|
59
|
+
/**
|
|
60
|
+
* Fan raw data out to every connected port.
|
|
61
|
+
*
|
|
62
|
+
* The escape hatch, for a worker speaking some protocol of its own. Anything
|
|
63
|
+
* the library's engines will read should go through {@link connect} instead —
|
|
64
|
+
* this bypasses them entirely.
|
|
65
|
+
*/
|
|
66
|
+
broadcast(data: unknown): void;
|
|
67
|
+
/**
|
|
68
|
+
* How many ports are attached. A live count of the contexts that opened this
|
|
69
|
+
* worker, which is what you want in order to idle a socket while nobody is
|
|
70
|
+
* looking. `connect()` seats are not counted: the worker is not its own tab.
|
|
71
|
+
*/
|
|
72
|
+
readonly size: number;
|
|
73
|
+
}
|
|
31
74
|
/**
|
|
32
|
-
* Fan every message out to the other connected
|
|
75
|
+
* Fan every message out to the other connected peers.
|
|
33
76
|
*
|
|
34
|
-
*
|
|
77
|
+
* Three properties the buses above this depend on:
|
|
35
78
|
*
|
|
36
79
|
* 1. **No self-echo.** A sender never receives its own message, matching
|
|
37
80
|
* BroadcastChannel exactly. Without it every store would apply its own
|
|
@@ -40,7 +83,17 @@ interface RelayScope {
|
|
|
40
83
|
* closing its port leaves an entangled port whose `postMessage` throws; one
|
|
41
84
|
* of those must not stop delivery to the tabs still listening, so the send
|
|
42
85
|
* loop is individually guarded and the corpse is pruned.
|
|
86
|
+
* 3. **The worker is a peer, not a special case.** A local seat from
|
|
87
|
+
* {@link Relay.connect} is held in the same set as the ports, so both rules
|
|
88
|
+
* above apply to it without a second code path to keep in agreement.
|
|
89
|
+
*/
|
|
90
|
+
declare function startRelay(scope: RelayScope): Relay;
|
|
91
|
+
/**
|
|
92
|
+
* The relay this module installed on import — present only when the module is
|
|
93
|
+
* running in a SharedWorker, and `undefined` anywhere else, so that importing
|
|
94
|
+
* it from a bundler tracing entry points, a test runner, or a server render is
|
|
95
|
+
* inert rather than a `ReferenceError`.
|
|
43
96
|
*/
|
|
44
|
-
declare
|
|
97
|
+
declare const relay: Relay | undefined;
|
|
45
98
|
|
|
46
|
-
export { type RelayPort, type RelayScope, startRelay };
|
|
99
|
+
export { type Relay, type RelayPort, type RelayScope, relay, startRelay };
|
package/dist/shared-worker.js
CHANGED
|
@@ -1,28 +1,73 @@
|
|
|
1
1
|
// src/shared-worker.ts
|
|
2
2
|
function startRelay(scope) {
|
|
3
3
|
const ports = /* @__PURE__ */ new Set();
|
|
4
|
+
const local = /* @__PURE__ */ new Set();
|
|
5
|
+
const fanOut = (from, data) => {
|
|
6
|
+
const dead = [];
|
|
7
|
+
for (const peer of ports) {
|
|
8
|
+
if (peer === from) continue;
|
|
9
|
+
try {
|
|
10
|
+
peer.postMessage(data);
|
|
11
|
+
} catch {
|
|
12
|
+
dead.push(peer);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
for (const corpse of dead) {
|
|
16
|
+
ports.delete(corpse);
|
|
17
|
+
local.delete(corpse);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
4
20
|
scope.onconnect = (event) => {
|
|
5
21
|
const port = event.ports[0];
|
|
6
22
|
if (!port) return;
|
|
7
23
|
ports.add(port);
|
|
8
|
-
port.addEventListener("message", (message) =>
|
|
9
|
-
const dead = [];
|
|
10
|
-
for (const peer of ports) {
|
|
11
|
-
if (peer === port) continue;
|
|
12
|
-
try {
|
|
13
|
-
peer.postMessage(message.data);
|
|
14
|
-
} catch {
|
|
15
|
-
dead.push(peer);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
for (const corpse of dead) ports.delete(corpse);
|
|
19
|
-
});
|
|
24
|
+
port.addEventListener("message", (message) => fanOut(port, message.data));
|
|
20
25
|
port.start?.();
|
|
21
26
|
};
|
|
27
|
+
return {
|
|
28
|
+
broadcast: (data) => fanOut(null, data),
|
|
29
|
+
get size() {
|
|
30
|
+
return ports.size - local.size;
|
|
31
|
+
},
|
|
32
|
+
connect() {
|
|
33
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
34
|
+
let closed = false;
|
|
35
|
+
const seat = {
|
|
36
|
+
// Asynchronous on purpose. `fanOut` runs inside a port's message
|
|
37
|
+
// listener, and delivering synchronously from there produces the
|
|
38
|
+
// re-entrancy a real MessagePort never could. Nothing is cloned: every
|
|
39
|
+
// value reaching this seat already crossed a port boundary and was
|
|
40
|
+
// structured-cloned there, so there is no live reference to leak.
|
|
41
|
+
postMessage: (data) => {
|
|
42
|
+
queueMicrotask(() => {
|
|
43
|
+
for (const listener of listeners) listener(data);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
ports.add(seat);
|
|
48
|
+
local.add(seat);
|
|
49
|
+
return {
|
|
50
|
+
kind: "shared-worker",
|
|
51
|
+
post: (data) => {
|
|
52
|
+
if (!closed) fanOut(seat, data);
|
|
53
|
+
},
|
|
54
|
+
subscribe: (listener) => {
|
|
55
|
+
listeners.add(listener);
|
|
56
|
+
return () => listeners.delete(listener);
|
|
57
|
+
},
|
|
58
|
+
close: () => {
|
|
59
|
+
if (closed) return;
|
|
60
|
+
closed = true;
|
|
61
|
+
listeners.clear();
|
|
62
|
+
ports.delete(seat);
|
|
63
|
+
local.delete(seat);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
22
68
|
}
|
|
23
|
-
|
|
24
|
-
startRelay(self);
|
|
25
|
-
}
|
|
69
|
+
var relay = typeof self !== "undefined" && "onconnect" in self ? startRelay(self) : void 0;
|
|
26
70
|
export {
|
|
71
|
+
relay,
|
|
27
72
|
startRelay
|
|
28
73
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@use-everywhere/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Cross-tab shared state, events, presence, and cross-origin window channels",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jonatan Kruszewski <jonakrusze@gmail.com>",
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
"name": "SharedWorker relay (shared-worker subpath)",
|
|
144
144
|
"path": "dist/shared-worker.js",
|
|
145
145
|
"import": "*",
|
|
146
|
-
"limit": "
|
|
146
|
+
"limit": "410 B"
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
149
|
"name": "MemoryHub (testing subpath)",
|