@use-everywhere/core 0.9.0 → 0.10.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 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,
@@ -1890,6 +1892,42 @@ function createNamespace(namespace) {
1890
1892
  createLeader: (name, options) => createLeader(busName(name), options)
1891
1893
  };
1892
1894
  }
1895
+
1896
+ // src/transport/shared-worker-transport.ts
1897
+ var SharedWorkerTransport = class {
1898
+ constructor(options) {
1899
+ this.kind = "shared-worker";
1900
+ this.listeners = /* @__PURE__ */ new Set();
1901
+ this.closed = false;
1902
+ const name = options.name ?? "use-everywhere";
1903
+ const factory = options.factory ?? ((url, workerName) => new SharedWorker(url, { name: workerName }));
1904
+ const worker = factory(String(options.url), name);
1905
+ this.port = worker.port;
1906
+ this.onMessage = (event) => {
1907
+ for (const listener of this.listeners) listener(event.data);
1908
+ };
1909
+ this.port.addEventListener("message", this.onMessage);
1910
+ this.port.start?.();
1911
+ }
1912
+ post(data) {
1913
+ if (this.closed) return;
1914
+ this.port.postMessage(data);
1915
+ }
1916
+ subscribe(listener) {
1917
+ this.listeners.add(listener);
1918
+ return () => this.listeners.delete(listener);
1919
+ }
1920
+ close() {
1921
+ if (this.closed) return;
1922
+ this.closed = true;
1923
+ this.listeners.clear();
1924
+ this.port.removeEventListener("message", this.onMessage);
1925
+ this.port.close();
1926
+ }
1927
+ };
1928
+ function isSharedWorkerAvailable() {
1929
+ return typeof SharedWorker !== "undefined";
1930
+ }
1893
1931
  // Annotate the CommonJS export names for ESM import in node:
1894
1932
  0 && (module.exports = {
1895
1933
  BroadcastChannelTransport,
@@ -1897,6 +1935,7 @@ function createNamespace(namespace) {
1897
1935
  DEFAULT_NAME,
1898
1936
  HandshakeTimeoutError,
1899
1937
  NoopTransport,
1938
+ SharedWorkerTransport,
1900
1939
  StorageTransport,
1901
1940
  WIRE_VERSION,
1902
1941
  WindowClosedError,
@@ -1914,6 +1953,7 @@ function createNamespace(namespace) {
1914
1953
  getWireSkew,
1915
1954
  indexedDbAdapter,
1916
1955
  isBroadcastChannelAvailable,
1956
+ isSharedWorkerAvailable,
1917
1957
  isStorageEventAvailable,
1918
1958
  jsonSerializer,
1919
1959
  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
@@ -1835,12 +1835,49 @@ function createNamespace(namespace) {
1835
1835
  createLeader: (name, options) => createLeader(busName(name), options)
1836
1836
  };
1837
1837
  }
1838
+
1839
+ // src/transport/shared-worker-transport.ts
1840
+ var SharedWorkerTransport = class {
1841
+ constructor(options) {
1842
+ this.kind = "shared-worker";
1843
+ this.listeners = /* @__PURE__ */ new Set();
1844
+ this.closed = false;
1845
+ const name = options.name ?? "use-everywhere";
1846
+ const factory = options.factory ?? ((url, workerName) => new SharedWorker(url, { name: workerName }));
1847
+ const worker = factory(String(options.url), name);
1848
+ this.port = worker.port;
1849
+ this.onMessage = (event) => {
1850
+ for (const listener of this.listeners) listener(event.data);
1851
+ };
1852
+ this.port.addEventListener("message", this.onMessage);
1853
+ this.port.start?.();
1854
+ }
1855
+ post(data) {
1856
+ if (this.closed) return;
1857
+ this.port.postMessage(data);
1858
+ }
1859
+ subscribe(listener) {
1860
+ this.listeners.add(listener);
1861
+ return () => this.listeners.delete(listener);
1862
+ }
1863
+ close() {
1864
+ if (this.closed) return;
1865
+ this.closed = true;
1866
+ this.listeners.clear();
1867
+ this.port.removeEventListener("message", this.onMessage);
1868
+ this.port.close();
1869
+ }
1870
+ };
1871
+ function isSharedWorkerAvailable() {
1872
+ return typeof SharedWorker !== "undefined";
1873
+ }
1838
1874
  export {
1839
1875
  BroadcastChannelTransport,
1840
1876
  CID_PARAM,
1841
1877
  DEFAULT_NAME,
1842
1878
  HandshakeTimeoutError,
1843
1879
  NoopTransport,
1880
+ SharedWorkerTransport,
1844
1881
  StorageTransport,
1845
1882
  WIRE_VERSION,
1846
1883
  WindowClosedError,
@@ -1858,6 +1895,7 @@ export {
1858
1895
  getWireSkew,
1859
1896
  indexedDbAdapter,
1860
1897
  isBroadcastChannelAvailable,
1898
+ isSharedWorkerAvailable,
1861
1899
  isStorageEventAvailable,
1862
1900
  jsonSerializer,
1863
1901
  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.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>",
@@ -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
  ]