@use-everywhere/core 0.9.0 → 0.10.1

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 CHANGED
@@ -25,6 +25,7 @@ __export(src_exports, {
25
25
  DEFAULT_NAME: () => DEFAULT_NAME,
26
26
  HandshakeTimeoutError: () => HandshakeTimeoutError,
27
27
  NoopTransport: () => NoopTransport,
28
+ SharedWorkerTransport: () => SharedWorkerTransport,
28
29
  StorageTransport: () => StorageTransport,
29
30
  WIRE_VERSION: () => WIRE_VERSION,
30
31
  WindowClosedError: () => WindowClosedError,
@@ -42,6 +43,7 @@ __export(src_exports, {
42
43
  getWireSkew: () => getWireSkew,
43
44
  indexedDbAdapter: () => indexedDbAdapter,
44
45
  isBroadcastChannelAvailable: () => isBroadcastChannelAvailable,
46
+ isSharedWorkerAvailable: () => isSharedWorkerAvailable,
45
47
  isStorageEventAvailable: () => isStorageEventAvailable,
46
48
  jsonSerializer: () => jsonSerializer,
47
49
  localStorageAdapter: () => localStorageAdapter,
@@ -1049,12 +1051,14 @@ function createWebLocksLeader(name, options, locks) {
1049
1051
  if (closed || !eligible || releaseHeld || pending) return;
1050
1052
  const controller = new AbortController();
1051
1053
  pending = controller;
1054
+ let grant;
1052
1055
  const held = new Promise((resolve) => {
1053
- releaseHeld = resolve;
1056
+ grant = resolve;
1054
1057
  });
1055
1058
  void locks.request(name, { signal: controller.signal }, async () => {
1056
1059
  pending = null;
1057
1060
  if (closed || !eligible) return;
1061
+ releaseHeld = grant;
1058
1062
  setLeader(clientId);
1059
1063
  announce2();
1060
1064
  await held;
@@ -1890,6 +1894,42 @@ function createNamespace(namespace) {
1890
1894
  createLeader: (name, options) => createLeader(busName(name), options)
1891
1895
  };
1892
1896
  }
1897
+
1898
+ // src/transport/shared-worker-transport.ts
1899
+ var SharedWorkerTransport = class {
1900
+ constructor(options) {
1901
+ this.kind = "shared-worker";
1902
+ this.listeners = /* @__PURE__ */ new Set();
1903
+ this.closed = false;
1904
+ const name = options.name ?? "use-everywhere";
1905
+ const factory = options.factory ?? ((url, workerName) => new SharedWorker(url, { name: workerName }));
1906
+ const worker = factory(String(options.url), name);
1907
+ this.port = worker.port;
1908
+ this.onMessage = (event) => {
1909
+ for (const listener of this.listeners) listener(event.data);
1910
+ };
1911
+ this.port.addEventListener("message", this.onMessage);
1912
+ this.port.start?.();
1913
+ }
1914
+ post(data) {
1915
+ if (this.closed) return;
1916
+ this.port.postMessage(data);
1917
+ }
1918
+ subscribe(listener) {
1919
+ this.listeners.add(listener);
1920
+ return () => this.listeners.delete(listener);
1921
+ }
1922
+ close() {
1923
+ if (this.closed) return;
1924
+ this.closed = true;
1925
+ this.listeners.clear();
1926
+ this.port.removeEventListener("message", this.onMessage);
1927
+ this.port.close();
1928
+ }
1929
+ };
1930
+ function isSharedWorkerAvailable() {
1931
+ return typeof SharedWorker !== "undefined";
1932
+ }
1893
1933
  // Annotate the CommonJS export names for ESM import in node:
1894
1934
  0 && (module.exports = {
1895
1935
  BroadcastChannelTransport,
@@ -1897,6 +1937,7 @@ function createNamespace(namespace) {
1897
1937
  DEFAULT_NAME,
1898
1938
  HandshakeTimeoutError,
1899
1939
  NoopTransport,
1940
+ SharedWorkerTransport,
1900
1941
  StorageTransport,
1901
1942
  WIRE_VERSION,
1902
1943
  WindowClosedError,
@@ -1914,6 +1955,7 @@ function createNamespace(namespace) {
1914
1955
  getWireSkew,
1915
1956
  indexedDbAdapter,
1916
1957
  isBroadcastChannelAvailable,
1958
+ isSharedWorkerAvailable,
1917
1959
  isStorageEventAvailable,
1918
1960
  jsonSerializer,
1919
1961
  localStorageAdapter,
package/dist/index.d.cts CHANGED
@@ -1090,6 +1090,98 @@ declare class StorageTransport implements Transport {
1090
1090
  close(): void;
1091
1091
  }
1092
1092
 
1093
+ /**
1094
+ * The subset of `SharedWorker` this transport uses. Narrower than the DOM type
1095
+ * on purpose: tests supply a fake, and a fake that has to implement
1096
+ * `EventTarget` to be accepted is a fake nobody writes.
1097
+ */
1098
+ interface SharedWorkerLike {
1099
+ readonly port: MessagePortLike;
1100
+ }
1101
+ interface MessagePortLike {
1102
+ postMessage(data: unknown): void;
1103
+ start?: () => void;
1104
+ close(): void;
1105
+ addEventListener(type: 'message', listener: (event: {
1106
+ data: unknown;
1107
+ }) => void): void;
1108
+ removeEventListener(type: 'message', listener: (event: {
1109
+ data: unknown;
1110
+ }) => void): void;
1111
+ }
1112
+ interface SharedWorkerTransportOptions {
1113
+ /**
1114
+ * The relay script. A `URL` rather than a string in the common case, because
1115
+ * `new URL('./relay.js', import.meta.url)` is what survives a bundler.
1116
+ *
1117
+ * It must be a *stable* URL, and that is the whole reason this option exists
1118
+ * rather than the library inlining a worker from a Blob the way it could for
1119
+ * a dedicated one: a SharedWorker's identity is its script URL plus its name,
1120
+ * and every tab that builds its own Blob URL gets its own worker. Inlining
1121
+ * would produce N private workers that share nothing — the failure this
1122
+ * transport exists to prevent, wearing the costume of a fix.
1123
+ */
1124
+ url: string | URL;
1125
+ /**
1126
+ * Distinguishes workers loaded from the same script. Defaults to the bus
1127
+ * name, which is what makes two buses on one origin independent.
1128
+ */
1129
+ name?: string;
1130
+ /**
1131
+ * Constructs the worker. Only tests should pass this; the default is
1132
+ * `globalThis.SharedWorker`.
1133
+ */
1134
+ factory?: (url: string, name: string) => SharedWorkerLike;
1135
+ }
1136
+ /**
1137
+ * Cross-tab delivery through one SharedWorker per origin.
1138
+ *
1139
+ * BroadcastChannel is the better default for pure fan-out and stays the
1140
+ * default. This transport buys something else: a single place that is not a
1141
+ * tab. The relay outlives any individual tab, so the thing every "the leader
1142
+ * owns the socket" design actually wants — one connection, held somewhere no
1143
+ * user can close mid-flight — becomes possible without electing a leader at
1144
+ * all.
1145
+ *
1146
+ * What it does not buy: durability. The worker is torn down when the last port
1147
+ * closes, exactly like a BroadcastChannel with no listeners. State still lives
1148
+ * in the tabs; this moves the *wire*, not the source of truth.
1149
+ *
1150
+ * The relay script is three lines and ships with the library:
1151
+ *
1152
+ * ```js
1153
+ * // sw-relay.js — your app hosts this file
1154
+ * import 'use-everywhere/shared-worker';
1155
+ * ```
1156
+ *
1157
+ * ```ts
1158
+ * createSharedStore('cart', {
1159
+ * transport: new SharedWorkerTransport({
1160
+ * url: new URL('./sw-relay.js', import.meta.url),
1161
+ * }),
1162
+ * });
1163
+ * ```
1164
+ */
1165
+ declare class SharedWorkerTransport implements Transport {
1166
+ readonly kind: TransportKind;
1167
+ private port;
1168
+ private listeners;
1169
+ private onMessage;
1170
+ private closed;
1171
+ constructor(options: SharedWorkerTransportOptions);
1172
+ post(data: unknown): void;
1173
+ subscribe(listener: (data: unknown) => void): () => void;
1174
+ close(): void;
1175
+ }
1176
+ /**
1177
+ * Whether this context can construct a SharedWorker at all.
1178
+ *
1179
+ * Notably false in every dedicated worker (they cannot nest a SharedWorker) and
1180
+ * in Chrome on Android. Worth checking before choosing this transport, since
1181
+ * the constructor throws rather than degrading.
1182
+ */
1183
+ declare function isSharedWorkerAvailable(): boolean;
1184
+
1093
1185
  declare function isBroadcastChannelAvailable(): boolean;
1094
1186
  /**
1095
1187
  * Can we hear other tabs through the `storage` event?
@@ -1111,4 +1203,4 @@ declare function isStorageEventAvailable(): boolean;
1111
1203
  */
1112
1204
  declare function defaultTransport(name: string): Transport;
1113
1205
 
1114
- export { type AskOptions, BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type ChannelOptions, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type IndexedDbAdapterOptions, type InvalidPayload, type Leader, type LeaderOptions, type LeaderSnapshot, type LeaderStrategy, type LockManagerLike, type MessageEventLike, type MessageMap, type MessageMeta, type Namespace, NoopTransport, type OnInvalid, type OnOptions, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type PostOptions, type Presence, type PresenceOptions, type ReplyMap, type RestoreError, type SchemaMap, type SchemaOptions, type Serializer, type SharedReducer, type SharedReducerOptions, type SharedStore, type SharedStoreOptions, type StandardSchemaV1, type StorageLike, StorageTransport, Transport, TransportKind, type Version, WIRE_VERSION, type WebStorageAdapterOptions, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createNamespace, createPresence, createSharedReducer, createSharedStore, defaultTransport, enableDebug, getBusNames, getTransportKind, getWireSkew, indexedDbAdapter, isBroadcastChannelAvailable, isStorageEventAvailable, jsonSerializer, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
1206
+ export { type AskOptions, BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type ChannelOptions, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type IndexedDbAdapterOptions, type InvalidPayload, type Leader, type LeaderOptions, type LeaderSnapshot, type LeaderStrategy, type LockManagerLike, type MessageEventLike, type MessageMap, type MessageMeta, type MessagePortLike, type Namespace, NoopTransport, type OnInvalid, type OnOptions, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type PostOptions, type Presence, type PresenceOptions, type ReplyMap, type RestoreError, type SchemaMap, type SchemaOptions, type Serializer, type SharedReducer, type SharedReducerOptions, type SharedStore, type SharedStoreOptions, type SharedWorkerLike, SharedWorkerTransport, type SharedWorkerTransportOptions, type StandardSchemaV1, type StorageLike, StorageTransport, Transport, TransportKind, type Version, WIRE_VERSION, type WebStorageAdapterOptions, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createNamespace, createPresence, createSharedReducer, createSharedStore, defaultTransport, enableDebug, getBusNames, getTransportKind, getWireSkew, indexedDbAdapter, isBroadcastChannelAvailable, isSharedWorkerAvailable, isStorageEventAvailable, jsonSerializer, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
package/dist/index.d.ts CHANGED
@@ -1090,6 +1090,98 @@ declare class StorageTransport implements Transport {
1090
1090
  close(): void;
1091
1091
  }
1092
1092
 
1093
+ /**
1094
+ * The subset of `SharedWorker` this transport uses. Narrower than the DOM type
1095
+ * on purpose: tests supply a fake, and a fake that has to implement
1096
+ * `EventTarget` to be accepted is a fake nobody writes.
1097
+ */
1098
+ interface SharedWorkerLike {
1099
+ readonly port: MessagePortLike;
1100
+ }
1101
+ interface MessagePortLike {
1102
+ postMessage(data: unknown): void;
1103
+ start?: () => void;
1104
+ close(): void;
1105
+ addEventListener(type: 'message', listener: (event: {
1106
+ data: unknown;
1107
+ }) => void): void;
1108
+ removeEventListener(type: 'message', listener: (event: {
1109
+ data: unknown;
1110
+ }) => void): void;
1111
+ }
1112
+ interface SharedWorkerTransportOptions {
1113
+ /**
1114
+ * The relay script. A `URL` rather than a string in the common case, because
1115
+ * `new URL('./relay.js', import.meta.url)` is what survives a bundler.
1116
+ *
1117
+ * It must be a *stable* URL, and that is the whole reason this option exists
1118
+ * rather than the library inlining a worker from a Blob the way it could for
1119
+ * a dedicated one: a SharedWorker's identity is its script URL plus its name,
1120
+ * and every tab that builds its own Blob URL gets its own worker. Inlining
1121
+ * would produce N private workers that share nothing — the failure this
1122
+ * transport exists to prevent, wearing the costume of a fix.
1123
+ */
1124
+ url: string | URL;
1125
+ /**
1126
+ * Distinguishes workers loaded from the same script. Defaults to the bus
1127
+ * name, which is what makes two buses on one origin independent.
1128
+ */
1129
+ name?: string;
1130
+ /**
1131
+ * Constructs the worker. Only tests should pass this; the default is
1132
+ * `globalThis.SharedWorker`.
1133
+ */
1134
+ factory?: (url: string, name: string) => SharedWorkerLike;
1135
+ }
1136
+ /**
1137
+ * Cross-tab delivery through one SharedWorker per origin.
1138
+ *
1139
+ * BroadcastChannel is the better default for pure fan-out and stays the
1140
+ * default. This transport buys something else: a single place that is not a
1141
+ * tab. The relay outlives any individual tab, so the thing every "the leader
1142
+ * owns the socket" design actually wants — one connection, held somewhere no
1143
+ * user can close mid-flight — becomes possible without electing a leader at
1144
+ * all.
1145
+ *
1146
+ * What it does not buy: durability. The worker is torn down when the last port
1147
+ * closes, exactly like a BroadcastChannel with no listeners. State still lives
1148
+ * in the tabs; this moves the *wire*, not the source of truth.
1149
+ *
1150
+ * The relay script is three lines and ships with the library:
1151
+ *
1152
+ * ```js
1153
+ * // sw-relay.js — your app hosts this file
1154
+ * import 'use-everywhere/shared-worker';
1155
+ * ```
1156
+ *
1157
+ * ```ts
1158
+ * createSharedStore('cart', {
1159
+ * transport: new SharedWorkerTransport({
1160
+ * url: new URL('./sw-relay.js', import.meta.url),
1161
+ * }),
1162
+ * });
1163
+ * ```
1164
+ */
1165
+ declare class SharedWorkerTransport implements Transport {
1166
+ readonly kind: TransportKind;
1167
+ private port;
1168
+ private listeners;
1169
+ private onMessage;
1170
+ private closed;
1171
+ constructor(options: SharedWorkerTransportOptions);
1172
+ post(data: unknown): void;
1173
+ subscribe(listener: (data: unknown) => void): () => void;
1174
+ close(): void;
1175
+ }
1176
+ /**
1177
+ * Whether this context can construct a SharedWorker at all.
1178
+ *
1179
+ * Notably false in every dedicated worker (they cannot nest a SharedWorker) and
1180
+ * in Chrome on Android. Worth checking before choosing this transport, since
1181
+ * the constructor throws rather than degrading.
1182
+ */
1183
+ declare function isSharedWorkerAvailable(): boolean;
1184
+
1093
1185
  declare function isBroadcastChannelAvailable(): boolean;
1094
1186
  /**
1095
1187
  * Can we hear other tabs through the `storage` event?
@@ -1111,4 +1203,4 @@ declare function isStorageEventAvailable(): boolean;
1111
1203
  */
1112
1204
  declare function defaultTransport(name: string): Transport;
1113
1205
 
1114
- export { type AskOptions, BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type ChannelOptions, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type IndexedDbAdapterOptions, type InvalidPayload, type Leader, type LeaderOptions, type LeaderSnapshot, type LeaderStrategy, type LockManagerLike, type MessageEventLike, type MessageMap, type MessageMeta, type Namespace, NoopTransport, type OnInvalid, type OnOptions, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type PostOptions, type Presence, type PresenceOptions, type ReplyMap, type RestoreError, type SchemaMap, type SchemaOptions, type Serializer, type SharedReducer, type SharedReducerOptions, type SharedStore, type SharedStoreOptions, type StandardSchemaV1, type StorageLike, StorageTransport, Transport, TransportKind, type Version, WIRE_VERSION, type WebStorageAdapterOptions, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createNamespace, createPresence, createSharedReducer, createSharedStore, defaultTransport, enableDebug, getBusNames, getTransportKind, getWireSkew, indexedDbAdapter, isBroadcastChannelAvailable, isStorageEventAvailable, jsonSerializer, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
1206
+ export { type AskOptions, BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type ChannelOptions, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type IndexedDbAdapterOptions, type InvalidPayload, type Leader, type LeaderOptions, type LeaderSnapshot, type LeaderStrategy, type LockManagerLike, type MessageEventLike, type MessageMap, type MessageMeta, type MessagePortLike, type Namespace, NoopTransport, type OnInvalid, type OnOptions, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type PostOptions, type Presence, type PresenceOptions, type ReplyMap, type RestoreError, type SchemaMap, type SchemaOptions, type Serializer, type SharedReducer, type SharedReducerOptions, type SharedStore, type SharedStoreOptions, type SharedWorkerLike, SharedWorkerTransport, type SharedWorkerTransportOptions, type StandardSchemaV1, type StorageLike, StorageTransport, Transport, TransportKind, type Version, WIRE_VERSION, type WebStorageAdapterOptions, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createNamespace, createPresence, createSharedReducer, createSharedStore, defaultTransport, enableDebug, getBusNames, getTransportKind, getWireSkew, indexedDbAdapter, isBroadcastChannelAvailable, isSharedWorkerAvailable, isStorageEventAvailable, jsonSerializer, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
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
- releaseHeld = resolve;
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;
@@ -1835,12 +1837,49 @@ function createNamespace(namespace) {
1835
1837
  createLeader: (name, options) => createLeader(busName(name), options)
1836
1838
  };
1837
1839
  }
1840
+
1841
+ // src/transport/shared-worker-transport.ts
1842
+ var SharedWorkerTransport = class {
1843
+ constructor(options) {
1844
+ this.kind = "shared-worker";
1845
+ this.listeners = /* @__PURE__ */ new Set();
1846
+ this.closed = false;
1847
+ const name = options.name ?? "use-everywhere";
1848
+ const factory = options.factory ?? ((url, workerName) => new SharedWorker(url, { name: workerName }));
1849
+ const worker = factory(String(options.url), name);
1850
+ this.port = worker.port;
1851
+ this.onMessage = (event) => {
1852
+ for (const listener of this.listeners) listener(event.data);
1853
+ };
1854
+ this.port.addEventListener("message", this.onMessage);
1855
+ this.port.start?.();
1856
+ }
1857
+ post(data) {
1858
+ if (this.closed) return;
1859
+ this.port.postMessage(data);
1860
+ }
1861
+ subscribe(listener) {
1862
+ this.listeners.add(listener);
1863
+ return () => this.listeners.delete(listener);
1864
+ }
1865
+ close() {
1866
+ if (this.closed) return;
1867
+ this.closed = true;
1868
+ this.listeners.clear();
1869
+ this.port.removeEventListener("message", this.onMessage);
1870
+ this.port.close();
1871
+ }
1872
+ };
1873
+ function isSharedWorkerAvailable() {
1874
+ return typeof SharedWorker !== "undefined";
1875
+ }
1838
1876
  export {
1839
1877
  BroadcastChannelTransport,
1840
1878
  CID_PARAM,
1841
1879
  DEFAULT_NAME,
1842
1880
  HandshakeTimeoutError,
1843
1881
  NoopTransport,
1882
+ SharedWorkerTransport,
1844
1883
  StorageTransport,
1845
1884
  WIRE_VERSION,
1846
1885
  WindowClosedError,
@@ -1858,6 +1897,7 @@ export {
1858
1897
  getWireSkew,
1859
1898
  indexedDbAdapter,
1860
1899
  isBroadcastChannelAvailable,
1900
+ isSharedWorkerAvailable,
1861
1901
  isStorageEventAvailable,
1862
1902
  jsonSerializer,
1863
1903
  localStorageAdapter,
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/shared-worker.ts
21
+ var shared_worker_exports = {};
22
+ __export(shared_worker_exports, {
23
+ startRelay: () => startRelay
24
+ });
25
+ module.exports = __toCommonJS(shared_worker_exports);
26
+ function startRelay(scope) {
27
+ const ports = /* @__PURE__ */ new Set();
28
+ scope.onconnect = (event) => {
29
+ const port = event.ports[0];
30
+ if (!port) return;
31
+ 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
+ });
44
+ port.start?.();
45
+ };
46
+ }
47
+ if (typeof self !== "undefined" && "onconnect" in self) {
48
+ startRelay(self);
49
+ }
50
+ // Annotate the CommonJS export names for ESM import in node:
51
+ 0 && (module.exports = {
52
+ startRelay
53
+ });
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The relay that runs *inside* a SharedWorker. Host a one-line module and point
3
+ * `SharedWorkerTransport` at it:
4
+ *
5
+ * ```js
6
+ * // sw-relay.js
7
+ * import '@use-everywhere/core/shared-worker';
8
+ * ```
9
+ *
10
+ * Importing this module installs the `connect` handler, which is why the entry
11
+ * point is a side effect rather than a function you call: a SharedWorker script
12
+ * has one job, and making the caller remember to invoke it adds a way to get a
13
+ * worker that accepts connections and forwards nothing.
14
+ *
15
+ * `startRelay` is exported anyway, for a worker that also does other work — an
16
+ * app whose relay owns the WebSocket, say — and for tests, which cannot install
17
+ * a global `onconnect`.
18
+ */
19
+ interface RelayPort {
20
+ postMessage(data: unknown): void;
21
+ start?: () => void;
22
+ addEventListener(type: 'message', listener: (event: {
23
+ data: unknown;
24
+ }) => void): void;
25
+ }
26
+ interface RelayScope {
27
+ onconnect: ((event: {
28
+ ports: readonly RelayPort[];
29
+ }) => void) | null;
30
+ }
31
+ /**
32
+ * Fan every message out to the other connected ports.
33
+ *
34
+ * Two properties the buses above this depend on:
35
+ *
36
+ * 1. **No self-echo.** A sender never receives its own message, matching
37
+ * BroadcastChannel exactly. Without it every store would apply its own
38
+ * writes twice and every presence roster would count each tab as two.
39
+ * 2. **A dead port is dropped, not thrown over.** A tab that goes away without
40
+ * closing its port leaves an entangled port whose `postMessage` throws; one
41
+ * of those must not stop delivery to the tabs still listening, so the send
42
+ * loop is individually guarded and the corpse is pruned.
43
+ */
44
+ declare function startRelay(scope: RelayScope): void;
45
+
46
+ export { type RelayPort, type RelayScope, startRelay };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The relay that runs *inside* a SharedWorker. Host a one-line module and point
3
+ * `SharedWorkerTransport` at it:
4
+ *
5
+ * ```js
6
+ * // sw-relay.js
7
+ * import '@use-everywhere/core/shared-worker';
8
+ * ```
9
+ *
10
+ * Importing this module installs the `connect` handler, which is why the entry
11
+ * point is a side effect rather than a function you call: a SharedWorker script
12
+ * has one job, and making the caller remember to invoke it adds a way to get a
13
+ * worker that accepts connections and forwards nothing.
14
+ *
15
+ * `startRelay` is exported anyway, for a worker that also does other work — an
16
+ * app whose relay owns the WebSocket, say — and for tests, which cannot install
17
+ * a global `onconnect`.
18
+ */
19
+ interface RelayPort {
20
+ postMessage(data: unknown): void;
21
+ start?: () => void;
22
+ addEventListener(type: 'message', listener: (event: {
23
+ data: unknown;
24
+ }) => void): void;
25
+ }
26
+ interface RelayScope {
27
+ onconnect: ((event: {
28
+ ports: readonly RelayPort[];
29
+ }) => void) | null;
30
+ }
31
+ /**
32
+ * Fan every message out to the other connected ports.
33
+ *
34
+ * Two properties the buses above this depend on:
35
+ *
36
+ * 1. **No self-echo.** A sender never receives its own message, matching
37
+ * BroadcastChannel exactly. Without it every store would apply its own
38
+ * writes twice and every presence roster would count each tab as two.
39
+ * 2. **A dead port is dropped, not thrown over.** A tab that goes away without
40
+ * closing its port leaves an entangled port whose `postMessage` throws; one
41
+ * of those must not stop delivery to the tabs still listening, so the send
42
+ * loop is individually guarded and the corpse is pruned.
43
+ */
44
+ declare function startRelay(scope: RelayScope): void;
45
+
46
+ export { type RelayPort, type RelayScope, startRelay };
@@ -0,0 +1,28 @@
1
+ // src/shared-worker.ts
2
+ function startRelay(scope) {
3
+ const ports = /* @__PURE__ */ new Set();
4
+ scope.onconnect = (event) => {
5
+ const port = event.ports[0];
6
+ if (!port) return;
7
+ 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
+ });
20
+ port.start?.();
21
+ };
22
+ }
23
+ if (typeof self !== "undefined" && "onconnect" in self) {
24
+ startRelay(self);
25
+ }
26
+ export {
27
+ startRelay
28
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@use-everywhere/core",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
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>",
@@ -24,7 +24,10 @@
24
24
  "access": "public"
25
25
  },
26
26
  "type": "module",
27
- "sideEffects": false,
27
+ "sideEffects": [
28
+ "./dist/shared-worker.js",
29
+ "./dist/shared-worker.cjs"
30
+ ],
28
31
  "main": "./dist/index.cjs",
29
32
  "module": "./dist/index.js",
30
33
  "types": "./dist/index.d.ts",
@@ -39,6 +42,16 @@
39
42
  "default": "./dist/index.cjs"
40
43
  }
41
44
  },
45
+ "./shared-worker": {
46
+ "import": {
47
+ "types": "./dist/shared-worker.d.ts",
48
+ "default": "./dist/shared-worker.js"
49
+ },
50
+ "require": {
51
+ "types": "./dist/shared-worker.d.cts",
52
+ "default": "./dist/shared-worker.cjs"
53
+ }
54
+ },
42
55
  "./testing": {
43
56
  "import": {
44
57
  "types": "./dist/testing.d.ts",
@@ -120,6 +133,18 @@
120
133
  "import": "{ indexedDbAdapter }",
121
134
  "limit": "447 B"
122
135
  },
136
+ {
137
+ "name": "SharedWorkerTransport",
138
+ "path": "dist/index.js",
139
+ "import": "{ SharedWorkerTransport }",
140
+ "limit": "380 B"
141
+ },
142
+ {
143
+ "name": "SharedWorker relay (shared-worker subpath)",
144
+ "path": "dist/shared-worker.js",
145
+ "import": "*",
146
+ "limit": "280 B"
147
+ },
123
148
  {
124
149
  "name": "MemoryHub (testing subpath)",
125
150
  "path": "dist/testing.js",
@@ -145,6 +170,9 @@
145
170
  },
146
171
  "typesVersions": {
147
172
  "*": {
173
+ "shared-worker": [
174
+ "./dist/shared-worker.d.ts"
175
+ ],
148
176
  "testing": [
149
177
  "./dist/testing.d.ts"
150
178
  ]