@evolu/web 3.0.0-next.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Sqlite.ts CHANGED
@@ -3,6 +3,7 @@ import { bytesToHex, createPreparedStatementsCache, ok } from "@evolu/common";
3
3
  import sqlite3InitModule, {
4
4
  type Database,
5
5
  type PreparedStatement,
6
+ type SAHPoolUtil,
6
7
  } from "@evolu/sqlite-wasm";
7
8
 
8
9
  // @ts-expect-error Missing types.
@@ -23,26 +24,51 @@ globalThis.sqlite3ApiConfig = {
23
24
  // Init ASAP.
24
25
  const sqlite3Promise = sqlite3InitModule();
25
26
 
27
+ const fileName = "evolu1.db";
28
+
26
29
  export const createWasmSqliteDriver: CreateSqliteDriver =
27
30
  (name, options) => async () => {
28
31
  const sqlite3 = await sqlite3Promise;
29
- // This is used to make OPFS default vfs for multipleciphers
30
- // @ts-expect-error Missing types (update @evolu/sqlite-wasm types)
31
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
32
- sqlite3.capi.sqlite3mc_vfs_create("opfs", 1);
33
32
 
34
- const stack = new globalThis.DisposableStack();
33
+ using disposer = new DisposableStack();
34
+ const useDatabase = (database: Database): Database =>
35
+ disposer.adopt(database, (database) => {
36
+ database.close();
37
+ });
38
+
39
+ let deleteDatabaseFile = false;
40
+ const createOpfsSAHPoolVfs = async (
41
+ options: Parameters<typeof sqlite3.installOpfsSAHPoolVfs>[0],
42
+ ): Promise<SAHPoolUtil> => {
43
+ const pool = await sqlite3.installOpfsSAHPoolVfs(options);
44
+ if (pool.isPaused()) await pool.unpauseVfs();
45
+ disposer.defer(() => {
46
+ if (deleteDatabaseFile) pool.unlink(`/${fileName}`);
47
+ pool.pauseVfs();
48
+ });
49
+ return pool;
50
+ };
51
+
35
52
  let db: Database;
53
+
36
54
  switch (options?.mode) {
37
55
  case "memory":
38
- db = new sqlite3.oo1.DB(":memory:");
56
+ db = useDatabase(new sqlite3.oo1.DB(":memory:"));
39
57
  break;
58
+
40
59
  case "encrypted": {
41
- const pool = await sqlite3.installOpfsSAHPoolVfs({
60
+ // MultipleCiphers encryption requires its VFS wrapper for OPFS SAH-pool.
61
+ // @ts-expect-error Missing types (update @evolu/sqlite-wasm types)
62
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
63
+ sqlite3.capi.sqlite3mc_vfs_create("opfs", 1);
64
+ const pool = await createOpfsSAHPoolVfs({
42
65
  directory: `.${name}`,
43
66
  });
44
- db = new pool.OpfsSAHPoolDb(
45
- "file:evolu1.db?vfs=multipleciphers-opfs-sahpool",
67
+ db = useDatabase(
68
+ new pool.OpfsSAHPoolDb(
69
+ // SQLite normalizes this URI filename to SAH-pool path "/evolu1.db".
70
+ `file:${fileName}?vfs=multipleciphers-opfs-sahpool`,
71
+ ),
46
72
  );
47
73
  db.exec(`
48
74
  PRAGMA cipher = 'sqlcipher';
@@ -50,17 +76,14 @@ export const createWasmSqliteDriver: CreateSqliteDriver =
50
76
  `);
51
77
  break;
52
78
  }
79
+
53
80
  default: {
54
- const pool = await sqlite3.installOpfsSAHPoolVfs({ name });
55
- db = new pool.OpfsSAHPoolDb("file:evolu1.db");
81
+ const pool = await createOpfsSAHPoolVfs({ name });
82
+ db = useDatabase(new pool.OpfsSAHPoolDb(`file:${fileName}`));
56
83
  }
57
84
  }
58
85
 
59
- db = stack.adopt(db, (db) => {
60
- db.close();
61
- });
62
-
63
- const cache = stack.use(
86
+ const cache = disposer.use(
64
87
  createPreparedStatementsCache<PreparedStatement>(
65
88
  (sql) => db.prepare(sql),
66
89
  (statement) => {
@@ -69,6 +92,8 @@ export const createWasmSqliteDriver: CreateSqliteDriver =
69
92
  ),
70
93
  );
71
94
 
95
+ const disposables = disposer.move();
96
+
72
97
  return ok({
73
98
  exec: (query) => {
74
99
  const prepared = cache.get(query);
@@ -101,9 +126,13 @@ export const createWasmSqliteDriver: CreateSqliteDriver =
101
126
 
102
127
  export: () => sqlite3.capi.sqlite3_js_db_export(db),
103
128
 
129
+ deleteDatabase: () => {
130
+ deleteDatabaseFile = true;
131
+ disposables.dispose();
132
+ },
133
+
104
134
  [Symbol.dispose]: () => {
105
- // poolUtil.unlink?
106
- stack.dispose();
135
+ disposables.dispose();
107
136
  },
108
137
  });
109
138
  };
package/src/Task.ts CHANGED
@@ -1,57 +1,21 @@
1
1
  /**
2
- * Web platform-specific Task utilities.
2
+ * Web-specific Task utilities.
3
3
  *
4
4
  * @module
5
5
  */
6
6
 
7
7
  import {
8
8
  createRun as createCommonRun,
9
- createUnknownError,
10
- ok,
11
- type CreateRun,
12
- type LeaderLock,
9
+ type DisposableRun,
13
10
  type Run,
14
- type RunDeps,
11
+ type RunCustomDeps,
15
12
  } from "@evolu/common";
16
13
 
17
14
  /**
18
- * Creates a {@link LeaderLock} backed by the Web Locks API.
15
+ * Creates a root {@link Run} for the browser.
19
16
  *
20
- * Waiting for the browser lock is intentionally unabortable. If a caller starts
21
- * waiting and its {@link Run} or fiber is later aborted, the underlying Web
22
- * Locks request keeps waiting until the browser grants the lock. Only the
23
- * returned lease releases it.
24
- */
25
- export const createLeaderLock = (): LeaderLock => ({
26
- lock: (name) => async () => {
27
- const acquired = Promise.withResolvers<void>();
28
- const release = Promise.withResolvers<void>();
29
-
30
- void globalThis.navigator.locks.request(
31
- `evolu-leaderlock-${name}`,
32
- { mode: "exclusive" },
33
- async () => {
34
- acquired.resolve();
35
- await release.promise;
36
- },
37
- );
38
-
39
- await acquired.promise;
40
-
41
- return ok({
42
- [Symbol.asyncDispose]: async () => {
43
- release.resolve();
44
- return Promise.resolve();
45
- },
46
- });
47
- },
48
- });
49
-
50
- /**
51
- * Creates {@link Run} for the browser with global error handling.
52
- *
53
- * Registers `error` and `unhandledrejection` handlers that log errors to the
54
- * console. Handlers are removed when the Run is disposed.
17
+ * Defects are reported with the browser's global `reportError`. A custom
18
+ * `reportDefect` dependency overrides the browser default.
55
19
  *
56
20
  * ### Example
57
21
  *
@@ -62,33 +26,22 @@ export const createLeaderLock = (): LeaderLock => ({
62
26
  * }),
63
27
  * });
64
28
  *
65
- * await using run = createRun({ console });
66
- * await using stack = new AsyncDisposableStack();
67
- *
68
- * stack.use(await run.orThrow(startApp()));
29
+ * const run = createRun({ console });
30
+ * const appPromise = run.ok(startApp());
69
31
  * ```
70
32
  */
71
- export const createRun: CreateRun<RunDeps> = <D>(
72
- deps?: D,
73
- ): Run<RunDeps & D> => {
74
- const run = createCommonRun(deps);
75
- const console = run.deps.console.child("global");
76
-
77
- globalThis.addEventListener(
78
- "error",
79
- (event) => {
80
- console.error("error", createUnknownError(event.error));
81
- },
82
- { signal: run.signal },
83
- );
84
-
85
- globalThis.addEventListener(
86
- "unhandledrejection",
87
- (event) => {
88
- console.error("unhandledrejection", createUnknownError(event.reason));
89
- },
90
- { signal: run.signal },
91
- );
92
-
93
- return run;
94
- };
33
+ export function createRun(): DisposableRun;
34
+ export function createRun<D extends object>(
35
+ deps: RunCustomDeps<D>,
36
+ ): DisposableRun<D>;
37
+ export function createRun<D extends object>(
38
+ deps?: RunCustomDeps<D>,
39
+ ): DisposableRun | DisposableRun<D> {
40
+ const reportDefect = (reported: unknown): void => {
41
+ globalThis.reportError(reported);
42
+ };
43
+
44
+ return deps === undefined
45
+ ? createCommonRun({ reportDefect })
46
+ : createCommonRun<D>({ reportDefect, ...deps });
47
+ }
package/src/Worker.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import type {
2
+ BroadcastChannel,
3
+ CreateBroadcastChannel,
4
+ CreateBroadcastChannelDep,
5
+ CreateMessageChannelDep,
2
6
  CreateMessagePort,
3
7
  MessageChannel,
4
8
  MessagePort,
@@ -10,14 +14,49 @@ import type {
10
14
  WorkerDeps,
11
15
  WorkerSelf,
12
16
  } from "@evolu/common";
13
- import { assert, createConsole, createConsoleStoreOutput } from "@evolu/common";
17
+ import {
18
+ assert,
19
+ createConsole,
20
+ createConsoleStoreOutput,
21
+ disposable,
22
+ } from "@evolu/common";
14
23
 
15
24
  /** Creates a {@link Worker} from a Web Worker. */
16
25
  export const createWorker = <Input, Output>(
17
26
  nativeWorker: globalThis.Worker,
18
27
  ): Worker<Input, Output> => wrap(nativeWorker);
19
28
 
20
- /** Creates an Evolu {@link SharedWorker} from a Web SharedWorker. */
29
+ /**
30
+ * Installs a one-tab SharedWorker polyfill backed by a Web Worker.
31
+ *
32
+ * Use it before constructing a SharedWorker in browsers without native
33
+ * SharedWorker support.
34
+ */
35
+ export const installOneTabSharedWorkerPolyfill = (): void => {
36
+ if (typeof globalThis.SharedWorker === "function") return;
37
+
38
+ Object.defineProperty(globalThis, "SharedWorker", {
39
+ configurable: true,
40
+ writable: true,
41
+ value: class {
42
+ readonly port: globalThis.MessagePort;
43
+
44
+ constructor(scriptURL: string | URL, options?: WorkerOptions) {
45
+ this.port = new globalThis.Worker(
46
+ scriptURL,
47
+ options,
48
+ ) as unknown as globalThis.MessagePort;
49
+ }
50
+ },
51
+ });
52
+ };
53
+
54
+ /**
55
+ * Creates a {@link SharedWorker} from a Web SharedWorker.
56
+ *
57
+ * Call {@link installOneTabSharedWorkerPolyfill} first to support browsers
58
+ * without native SharedWorker support.
59
+ */
21
60
  export const createSharedWorker = <Input, Output = never>(
22
61
  nativeSharedWorker: globalThis.SharedWorker,
23
62
  ): SharedWorker<Input, Output> => {
@@ -44,14 +83,16 @@ export const createMessageChannel = <Input, Output = never>(): MessageChannel<
44
83
  Output
45
84
  > => {
46
85
  const channel = new globalThis.MessageChannel();
47
- const stack = new DisposableStack();
86
+ using disposer = new DisposableStack();
87
+
88
+ const port1 = disposer.use(wrap<Input, Output>(channel.port1));
89
+ const port2 = disposer.use(wrap<Output, Input>(channel.port2));
90
+ const disposables = disposer.move();
48
91
 
49
92
  return {
50
- port1: stack.use(wrap<Input, Output>(channel.port1)),
51
- port2: stack.use(wrap<Output, Input>(channel.port2)),
52
- [Symbol.dispose]: () => {
53
- stack.dispose();
54
- },
93
+ port1,
94
+ port2,
95
+ [Symbol.dispose]: () => disposables.dispose(),
55
96
  };
56
97
  };
57
98
 
@@ -63,6 +104,47 @@ export const createMessageChannel = <Input, Output = never>(): MessageChannel<
63
104
  export const createMessagePort: CreateMessagePort = (nativePort) =>
64
105
  wrap(nativePort as unknown as globalThis.MessagePort);
65
106
 
107
+ /** Creates an Evolu {@link BroadcastChannel} from a Web BroadcastChannel. */
108
+ export const createBroadcastChannel: CreateBroadcastChannel = <
109
+ Input,
110
+ Output = Input,
111
+ >(
112
+ name: string,
113
+ ): BroadcastChannel<Input, Output> => {
114
+ const nativeBroadcastChannel = new globalThis.BroadcastChannel(name);
115
+ using disposer = new DisposableStack();
116
+ let disposed = false;
117
+
118
+ disposer.defer(() => {
119
+ disposed = true;
120
+ nativeBroadcastChannel.onmessage = null;
121
+ nativeBroadcastChannel.close();
122
+ });
123
+
124
+ let onMessageHandler: ((message: Output) => void) | null = null;
125
+
126
+ return disposable<BroadcastChannel<Input, Output>>(
127
+ {
128
+ postMessage: (message) => {
129
+ nativeBroadcastChannel.postMessage(message);
130
+ },
131
+ get onMessage() {
132
+ return disposed ? null : onMessageHandler;
133
+ },
134
+ set onMessage(fn) {
135
+ if (disposed) return;
136
+ onMessageHandler = fn;
137
+ nativeBroadcastChannel.onmessage = fn
138
+ ? (event: MessageEvent<Output>) => {
139
+ fn(event.data);
140
+ }
141
+ : null;
142
+ },
143
+ },
144
+ disposer,
145
+ );
146
+ };
147
+
66
148
  /**
67
149
  * Creates an Evolu {@link WorkerSelf} from a Web `DedicatedWorkerGlobalScope`
68
150
  * (`self` inside a dedicated worker).
@@ -99,23 +181,109 @@ export const createSharedWorkerSelf = <Input, Output = never>(
99
181
  return self;
100
182
  };
101
183
 
184
+ /**
185
+ * Creates a one-tab {@link SharedWorkerSelf} polyfill for browsers without
186
+ * native SharedWorker support.
187
+ *
188
+ * Delivers one synthetic `onConnect` port, queues startup messages until
189
+ * `onMessage` is set, and does not share state across tabs.
190
+ */
191
+ export const createOneTabSharedWorkerSelfPolyfill = <Input, Output = never>(
192
+ nativeSelf: globalThis.DedicatedWorkerGlobalScope,
193
+ ): SharedWorkerSelf<Input, Output> => {
194
+ using disposer = new DisposableStack();
195
+
196
+ let onConnectHandler: ((port: MessagePort<Output, Input>) => void) | null =
197
+ null;
198
+ let onMessageHandler: ((message: Input) => void) | null = null;
199
+ const messages: Array<Input> = [];
200
+ let connected = false;
201
+
202
+ disposer.defer(() => {
203
+ messages.length = 0;
204
+ onConnectHandler = null;
205
+ onMessageHandler = null;
206
+ nativeSelf.onmessage = null;
207
+ nativeSelf.close();
208
+ });
209
+
210
+ const disposables = disposer.move();
211
+
212
+ nativeSelf.onmessage = (event: MessageEvent<Input>) => {
213
+ if (disposables.disposed) return;
214
+
215
+ const handler = onMessageHandler;
216
+ if (handler) handler(event.data);
217
+ else messages.push(event.data);
218
+ };
219
+
220
+ const flushMessages = (): void => {
221
+ for (const message of messages.splice(0)) {
222
+ const handler = onMessageHandler;
223
+ if (!handler) return;
224
+ handler(message);
225
+ }
226
+ };
227
+
228
+ const port: MessagePort<Output, Input> = {
229
+ postMessage: (message, transfer) => {
230
+ if (disposables.disposed) return;
231
+ if (transfer == null) nativeSelf.postMessage(message);
232
+ else nativeSelf.postMessage(message, [...transfer]);
233
+ },
234
+
235
+ get onMessage() {
236
+ return disposables.disposed ? null : onMessageHandler;
237
+ },
238
+ set onMessage(fn) {
239
+ if (disposables.disposed) return;
240
+ onMessageHandler = fn;
241
+ if (fn) flushMessages();
242
+ },
243
+
244
+ native: nativeSelf as unknown as NativeMessagePort<Output, Input>,
245
+
246
+ [Symbol.dispose]: () => disposables.dispose(),
247
+ };
248
+
249
+ const connect = (): void => {
250
+ if (connected || !onConnectHandler) return;
251
+ connected = true;
252
+ onConnectHandler(port);
253
+ };
254
+
255
+ return {
256
+ get onConnect() {
257
+ return onConnectHandler;
258
+ },
259
+ set onConnect(fn) {
260
+ if (disposables.disposed) return;
261
+ onConnectHandler = fn;
262
+ connect();
263
+ },
264
+ [Symbol.dispose]: () => disposables.dispose(),
265
+ };
266
+ };
267
+
102
268
  /** Creates deps shared by web worker entry points. */
103
- export const createWorkerDeps = (): WorkerDeps => {
269
+ export const createWorkerDeps = (): WorkerDeps &
270
+ CreateBroadcastChannelDep &
271
+ CreateMessageChannelDep => {
104
272
  const consoleStoreOutput = createConsoleStoreOutput();
105
273
  const console = createConsole({ output: consoleStoreOutput });
106
274
 
107
275
  return {
108
276
  console,
109
277
  consoleStoreOutputEntry: consoleStoreOutput.entry,
278
+ createBroadcastChannel,
279
+ createMessageChannel,
110
280
  createMessagePort,
111
281
  };
112
282
  };
113
283
 
114
284
  const wrap = <Input, Output>(
115
285
  native:
116
- | DedicatedWorkerGlobalScope
117
- | globalThis.MessagePort
118
- | globalThis.Worker,
286
+ DedicatedWorkerGlobalScope | globalThis.MessagePort | globalThis.Worker,
119
287
  ): MessagePort<Input, Output> => {
120
288
  let onMessageHandler: ((message: Output) => void) | null = null;
121
289
 
@@ -144,7 +312,7 @@ const wrap = <Input, Output>(
144
312
 
145
313
  [Symbol.dispose]: () => {
146
314
  native.onmessage = null;
147
- if (native instanceof globalThis.Worker) native.terminate();
315
+ if ("terminate" in native) native.terminate();
148
316
  else native.close();
149
317
  },
150
318
  };
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
- export * from "./local-first/index.js";
2
- export * from "./Sqlite.js";
3
- export * from "./Task.js";
4
- export * from "./Worker.js";
1
+ export * from "./local-first/index.ts";
2
+ export { availableParallelism } from "./Platform.ts";
3
+ export * from "./Sqlite.ts";
4
+ export * from "./Task.ts";
5
+ export * from "./Worker.ts";
@@ -6,16 +6,15 @@ installPolyfills();
6
6
 
7
7
  import { createRandomBytes } from "@evolu/common";
8
8
  import { startDbWorker } from "@evolu/common/local-first";
9
- import { createWasmSqliteDriver } from "../Sqlite.js";
10
- import { createLeaderLock, createRun } from "../Task.js";
11
- import { createWorkerDeps, createWorkerSelf } from "../Worker.js";
9
+ import { createWasmSqliteDriver } from "../Sqlite.ts";
10
+ import { createRun } from "../Task.ts";
11
+ import { createWorkerDeps, createWorkerSelf } from "../Worker.ts";
12
12
 
13
- // TODO: Disposal.
14
13
  const run = createRun({
15
14
  ...createWorkerDeps(),
16
15
  createSqliteDriver: createWasmSqliteDriver,
17
- leaderLock: createLeaderLock(),
16
+ lockManager: globalThis.navigator.locks,
18
17
  randomBytes: createRandomBytes(),
19
18
  });
20
19
 
21
- run(startDbWorker(createWorkerSelf(self)));
20
+ void run(startDbWorker(createWorkerSelf(self)));
@@ -1,17 +1,21 @@
1
- import type { ConsoleDep } from "@evolu/common";
1
+ import { exhaustiveCheck, type ConsoleDep } from "@evolu/common";
2
2
  import type {
3
+ SharedWorker as CommonSharedWorker,
3
4
  CreateDbWorker,
4
5
  DbWorkerInit,
5
6
  EvoluDeps,
6
7
  SharedWorkerInput,
8
+ SharedWorkerOutput,
7
9
  } from "@evolu/common/local-first";
8
10
  import { createEvoluDeps as createCommonEvoluDeps } from "@evolu/common/local-first";
9
- import { reloadApp } from "../Platform.js";
11
+ import { reloadApp } from "../Platform.ts";
10
12
  import {
13
+ createBroadcastChannel,
11
14
  createMessageChannel,
12
15
  createSharedWorker,
13
16
  createWorker,
14
- } from "../Worker.js";
17
+ installOneTabSharedWorkerPolyfill,
18
+ } from "../Worker.ts";
15
19
 
16
20
  // // TODO: Redesign.
17
21
  // // eslint-disable-next-line evolu/require-pure-annotation
@@ -20,8 +24,20 @@ import {
20
24
  // secureStorage: createWebAuthnStore({ randomBytes: createRandomBytes() }),
21
25
  // });
22
26
 
27
+ export interface SharedWorkerUnsupported {
28
+ readonly type: "SharedWorkerUnsupported";
29
+ }
30
+
31
+ export interface SharedWorkerUnsupportedDep {
32
+ readonly onSharedWorkerUnsupported: () => void;
33
+ }
34
+
23
35
  /** Creates Evolu dependencies for the web platform. */
24
- export const createEvoluDeps = (deps: Partial<ConsoleDep> = {}): EvoluDeps => {
36
+ export const createEvoluDeps = (
37
+ deps: Partial<ConsoleDep> & Partial<SharedWorkerUnsupportedDep> = {},
38
+ ): EvoluDeps => {
39
+ installOneTabSharedWorkerPolyfill();
40
+
25
41
  const createDbWorker: CreateDbWorker = () =>
26
42
  createWorker<DbWorkerInit, never>(
27
43
  new Worker(new URL("Db.worker.js", import.meta.url), {
@@ -29,16 +45,59 @@ export const createEvoluDeps = (deps: Partial<ConsoleDep> = {}): EvoluDeps => {
29
45
  }),
30
46
  );
31
47
 
32
- const sharedWorker = createSharedWorker<SharedWorkerInput>(
48
+ const webSharedWorker = createSharedWorker<
49
+ SharedWorkerInput,
50
+ SharedWorkerOutput
51
+ >(
33
52
  new SharedWorker(new URL("Shared.worker.js", import.meta.url), {
34
53
  type: "module",
35
54
  }),
36
55
  );
56
+ let onSharedWorkerMessage: ((message: SharedWorkerOutput) => void) | null =
57
+ null;
58
+ const sharedWorker: CommonSharedWorker = {
59
+ ...webSharedWorker,
60
+ port: {
61
+ ...webSharedWorker.port,
62
+ get onMessage() {
63
+ return onSharedWorkerMessage;
64
+ },
65
+ set onMessage(fn) {
66
+ onSharedWorkerMessage = fn;
67
+ webSharedWorker.port.onMessage = fn
68
+ ? (message: SharedWorkerOutput | SharedWorkerUnsupported) => {
69
+ switch (message.type) {
70
+ case "DbWorkerInit": {
71
+ fn(message);
72
+ break;
73
+ }
74
+
75
+ case "SharedWorkerUnsupported": {
76
+ if (deps.onSharedWorkerUnsupported) {
77
+ deps.onSharedWorkerUnsupported();
78
+ } else {
79
+ globalThis.alert(
80
+ "This browser supports Evolu in one tab only. Close this tab and use the already open tab.",
81
+ );
82
+ }
83
+ break;
84
+ }
85
+
86
+ default:
87
+ exhaustiveCheck(message);
88
+ }
89
+ }
90
+ : null;
91
+ },
92
+ },
93
+ };
37
94
 
38
95
  return createCommonEvoluDeps({
39
96
  ...deps,
40
97
  createDbWorker,
98
+ createBroadcastChannel,
41
99
  createMessageChannel,
100
+ lockManager: globalThis.navigator.locks,
42
101
  reloadApp,
43
102
  sharedWorker,
44
103
  });
@@ -1,18 +1,58 @@
1
1
  /// <reference lib="webworker" />
2
- declare const self: SharedWorkerGlobalScope;
2
+ declare const self: DedicatedWorkerGlobalScope | SharedWorkerGlobalScope;
3
3
 
4
4
  import { installPolyfills } from "@evolu/common/polyfills";
5
5
  installPolyfills();
6
6
 
7
- import { createWebSocket } from "@evolu/common";
7
+ import { createWebSocket, ok, waitForAbort } from "@evolu/common";
8
+ import type {
9
+ SharedWorkerInput,
10
+ SharedWorkerOutput,
11
+ } from "@evolu/common/local-first";
8
12
  import { initSharedWorker } from "@evolu/common/local-first";
9
- import { createRun } from "../Task.js";
10
- import { createSharedWorkerSelf, createWorkerDeps } from "../Worker.js";
13
+ import type { SharedWorkerUnsupported } from "./Evolu.ts";
14
+ import { createRun } from "../Task.ts";
15
+ import {
16
+ createOneTabSharedWorkerSelfPolyfill,
17
+ createSharedWorkerSelf,
18
+ createWorkerDeps,
19
+ } from "../Worker.ts";
11
20
 
12
- // No disposal (`await using`) is needed — a SharedWorker lives forever.
13
21
  const run = createRun({
14
22
  ...createWorkerDeps(),
15
23
  createWebSocket,
24
+ lockManager: globalThis.navigator.locks,
16
25
  });
17
26
 
18
- run(initSharedWorker(createSharedWorkerSelf(self)));
27
+ void run(async (run) => {
28
+ if ("onconnect" in self) {
29
+ await using _ = await run.ok(
30
+ initSharedWorker(createSharedWorkerSelf(self)),
31
+ );
32
+ return await run(waitForAbort);
33
+ }
34
+
35
+ using workerSelf = createOneTabSharedWorkerSelfPolyfill<
36
+ SharedWorkerInput,
37
+ SharedWorkerOutput
38
+ >(self);
39
+
40
+ await globalThis.navigator.locks.request(
41
+ "evolu-one-tab-sharedworker-polyfill",
42
+ { ifAvailable: true, mode: "exclusive" },
43
+ async (lock) => {
44
+ if (!lock) {
45
+ const message: SharedWorkerUnsupported = {
46
+ type: "SharedWorkerUnsupported",
47
+ };
48
+ self.postMessage(message);
49
+ return;
50
+ }
51
+
52
+ await using _ = await run.ok(initSharedWorker(workerSelf));
53
+ await run(waitForAbort);
54
+ },
55
+ );
56
+
57
+ return ok();
58
+ });
@@ -1 +1 @@
1
- export * from "./Evolu.js";
1
+ export * from "./Evolu.ts";