@evolu/common 8.0.0-next.4 → 8.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.
Files changed (237) hide show
  1. package/dist/src/Array.d.ts +279 -173
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +105 -101
  4. package/dist/src/Assert.d.ts +44 -44
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +44 -32
  7. package/dist/src/BigInt.d.ts +6 -3
  8. package/dist/src/BigInt.d.ts.map +1 -1
  9. package/dist/src/BigInt.js +5 -2
  10. package/dist/src/Brand.d.ts +27 -40
  11. package/dist/src/Brand.d.ts.map +1 -1
  12. package/dist/src/Buffer.d.ts +27 -11
  13. package/dist/src/Buffer.d.ts.map +1 -1
  14. package/dist/src/Cache.d.ts +9 -4
  15. package/dist/src/Cache.d.ts.map +1 -1
  16. package/dist/src/Cache.js +8 -3
  17. package/dist/src/Callbacks.d.ts +30 -19
  18. package/dist/src/Callbacks.d.ts.map +1 -1
  19. package/dist/src/Console.d.ts +145 -66
  20. package/dist/src/Console.d.ts.map +1 -1
  21. package/dist/src/Console.js +91 -43
  22. package/dist/src/Crypto.d.ts +85 -42
  23. package/dist/src/Crypto.d.ts.map +1 -1
  24. package/dist/src/Crypto.js +44 -20
  25. package/dist/src/Eq.d.ts +87 -21
  26. package/dist/src/Eq.d.ts.map +1 -1
  27. package/dist/src/Eq.js +67 -19
  28. package/dist/src/Error.d.ts +5 -3
  29. package/dist/src/Error.d.ts.map +1 -1
  30. package/dist/src/Error.js +16 -0
  31. package/dist/src/Function.d.ts +119 -58
  32. package/dist/src/Function.d.ts.map +1 -1
  33. package/dist/src/Function.js +86 -43
  34. package/dist/src/Http.d.ts +394 -0
  35. package/dist/src/Http.d.ts.map +1 -0
  36. package/dist/src/Http.js +102 -0
  37. package/dist/src/Identicon.d.ts +16 -7
  38. package/dist/src/Identicon.d.ts.map +1 -1
  39. package/dist/src/Identicon.js +14 -5
  40. package/dist/src/LeakDetector.d.ts +88 -0
  41. package/dist/src/LeakDetector.d.ts.map +1 -0
  42. package/dist/src/LeakDetector.js +86 -0
  43. package/dist/src/LockManager.d.ts +18 -9
  44. package/dist/src/LockManager.d.ts.map +1 -1
  45. package/dist/src/LockManager.js +7 -8
  46. package/dist/src/Lookup.d.ts +43 -13
  47. package/dist/src/Lookup.d.ts.map +1 -1
  48. package/dist/src/Lookup.js +28 -8
  49. package/dist/src/Microtask.d.ts +1 -1
  50. package/dist/src/Microtask.d.ts.map +1 -1
  51. package/dist/src/Microtask.js +3 -7
  52. package/dist/src/Number.d.ts +49 -13
  53. package/dist/src/Number.d.ts.map +1 -1
  54. package/dist/src/Number.js +34 -5
  55. package/dist/src/Object.d.ts +160 -40
  56. package/dist/src/Object.d.ts.map +1 -1
  57. package/dist/src/Object.js +92 -46
  58. package/dist/src/Option.d.ts +10 -7
  59. package/dist/src/Option.d.ts.map +1 -1
  60. package/dist/src/Option.js +9 -6
  61. package/dist/src/Order.d.ts +21 -21
  62. package/dist/src/Order.d.ts.map +1 -1
  63. package/dist/src/Order.js +18 -21
  64. package/dist/src/Platform.d.ts +31 -0
  65. package/dist/src/Platform.d.ts.map +1 -1
  66. package/dist/src/Platform.js +98 -0
  67. package/dist/src/Random.d.ts +32 -17
  68. package/dist/src/Random.d.ts.map +1 -1
  69. package/dist/src/Random.js +12 -8
  70. package/dist/src/Redacted.d.ts +43 -29
  71. package/dist/src/Redacted.d.ts.map +1 -1
  72. package/dist/src/Redacted.js +14 -3
  73. package/dist/src/Ref.d.ts +11 -3
  74. package/dist/src/Ref.d.ts.map +1 -1
  75. package/dist/src/Ref.js +2 -2
  76. package/dist/src/RefCount.d.ts +2 -6
  77. package/dist/src/RefCount.d.ts.map +1 -1
  78. package/dist/src/RefCount.js +33 -90
  79. package/dist/src/Relation.d.ts +70 -5
  80. package/dist/src/Relation.d.ts.map +1 -1
  81. package/dist/src/Relation.js +77 -2
  82. package/dist/src/Resource.d.ts +590 -155
  83. package/dist/src/Resource.d.ts.map +1 -1
  84. package/dist/src/Resource.js +592 -343
  85. package/dist/src/Result.d.ts +838 -191
  86. package/dist/src/Result.d.ts.map +1 -1
  87. package/dist/src/Result.js +163 -102
  88. package/dist/src/Schedule.d.ts +684 -231
  89. package/dist/src/Schedule.d.ts.map +1 -1
  90. package/dist/src/Schedule.js +766 -295
  91. package/dist/src/Set.d.ts +89 -40
  92. package/dist/src/Set.d.ts.map +1 -1
  93. package/dist/src/Set.js +50 -29
  94. package/dist/src/Sqlite.d.ts +29 -72
  95. package/dist/src/Sqlite.d.ts.map +1 -1
  96. package/dist/src/Sqlite.js +41 -46
  97. package/dist/src/StackTrace.d.ts +40 -0
  98. package/dist/src/StackTrace.d.ts.map +1 -0
  99. package/dist/src/StackTrace.js +140 -0
  100. package/dist/src/Store.d.ts +5 -4
  101. package/dist/src/Store.d.ts.map +1 -1
  102. package/dist/src/Store.js +5 -4
  103. package/dist/src/String.d.ts +7 -1
  104. package/dist/src/String.d.ts.map +1 -1
  105. package/dist/src/String.js +20 -7
  106. package/dist/src/Task.d.ts +3572 -1770
  107. package/dist/src/Task.d.ts.map +1 -1
  108. package/dist/src/Task.js +1879 -1274
  109. package/dist/src/Test.d.ts +17 -125
  110. package/dist/src/Test.d.ts.map +1 -1
  111. package/dist/src/Test.js +17 -68
  112. package/dist/src/Time.d.ts +108 -49
  113. package/dist/src/Time.d.ts.map +1 -1
  114. package/dist/src/Time.js +172 -73
  115. package/dist/src/Type.d.ts +3542 -1926
  116. package/dist/src/Type.d.ts.map +1 -1
  117. package/dist/src/Type.js +3070 -1784
  118. package/dist/src/Types.d.ts +200 -108
  119. package/dist/src/Types.d.ts.map +1 -1
  120. package/dist/src/Types.js +39 -8
  121. package/dist/src/WebSocket.d.ts +48 -23
  122. package/dist/src/WebSocket.d.ts.map +1 -1
  123. package/dist/src/WebSocket.js +15 -6
  124. package/dist/src/Worker.d.ts +94 -36
  125. package/dist/src/Worker.d.ts.map +1 -1
  126. package/dist/src/Worker.js +58 -24
  127. package/dist/src/index.d.ts +55 -52
  128. package/dist/src/index.d.ts.map +1 -1
  129. package/dist/src/index.js +3 -1
  130. package/dist/src/intl/cs.d.ts +94 -0
  131. package/dist/src/intl/cs.d.ts.map +1 -0
  132. package/dist/src/intl/cs.js +220 -0
  133. package/dist/src/intl/index.d.ts +7 -0
  134. package/dist/src/intl/index.d.ts.map +1 -0
  135. package/dist/src/intl/index.js +6 -0
  136. package/dist/src/local-first/Db.d.ts +12 -8
  137. package/dist/src/local-first/Db.d.ts.map +1 -1
  138. package/dist/src/local-first/Db.js +109 -132
  139. package/dist/src/local-first/Error.d.ts +4 -4
  140. package/dist/src/local-first/Evolu.d.ts +338 -92
  141. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  142. package/dist/src/local-first/Evolu.js +47 -55
  143. package/dist/src/local-first/LocalAuth.d.ts +4 -4
  144. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.d.ts +61 -24
  146. package/dist/src/local-first/Owner.d.ts.map +1 -1
  147. package/dist/src/local-first/Owner.js +45 -8
  148. package/dist/src/local-first/Protocol.d.ts +44 -31
  149. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  150. package/dist/src/local-first/Protocol.js +36 -13
  151. package/dist/src/local-first/Query.d.ts +147 -62
  152. package/dist/src/local-first/Query.d.ts.map +1 -1
  153. package/dist/src/local-first/Query.js +112 -58
  154. package/dist/src/local-first/Relay.d.ts +44 -14
  155. package/dist/src/local-first/Relay.d.ts.map +1 -1
  156. package/dist/src/local-first/Schema.d.ts +92 -41
  157. package/dist/src/local-first/Schema.d.ts.map +1 -1
  158. package/dist/src/local-first/Schema.js +25 -8
  159. package/dist/src/local-first/Shared.d.ts +23 -21
  160. package/dist/src/local-first/Shared.d.ts.map +1 -1
  161. package/dist/src/local-first/Shared.js +100 -57
  162. package/dist/src/local-first/Storage.d.ts +53 -32
  163. package/dist/src/local-first/Storage.d.ts.map +1 -1
  164. package/dist/src/local-first/Storage.js +194 -234
  165. package/dist/src/local-first/Timestamp.d.ts +21 -21
  166. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  167. package/dist/src/local-first/Timestamp.js +2 -2
  168. package/dist/src/local-first/index.d.ts +11 -11
  169. package/package.json +14 -13
  170. package/src/Array.ts +306 -205
  171. package/src/Assert.ts +54 -69
  172. package/src/BigInt.ts +6 -3
  173. package/src/Brand.ts +27 -40
  174. package/src/Buffer.ts +28 -12
  175. package/src/Cache.ts +9 -4
  176. package/src/Callbacks.ts +30 -19
  177. package/src/Console.ts +156 -84
  178. package/src/Crypto.ts +83 -41
  179. package/src/Eq.ts +95 -22
  180. package/src/Error.ts +8 -4
  181. package/src/Function.ts +167 -59
  182. package/src/Http.ts +549 -0
  183. package/src/Identicon.ts +16 -7
  184. package/src/LeakDetector.ts +182 -0
  185. package/src/LockManager.ts +43 -39
  186. package/src/Lookup.ts +62 -28
  187. package/src/Microtask.ts +19 -23
  188. package/src/Number.ts +64 -12
  189. package/src/Object.ts +173 -49
  190. package/src/Option.ts +10 -7
  191. package/src/Order.ts +23 -21
  192. package/src/Platform.ts +136 -0
  193. package/src/Polyfills.ts +6 -12
  194. package/src/Random.ts +32 -19
  195. package/src/Redacted.ts +44 -30
  196. package/src/Ref.ts +14 -6
  197. package/src/RefCount.ts +38 -80
  198. package/src/Relation.ts +202 -10
  199. package/src/Resource.ts +1199 -591
  200. package/src/Result.ts +930 -271
  201. package/src/Schedule.ts +859 -343
  202. package/src/Set.ts +89 -40
  203. package/src/Sqlite.ts +144 -138
  204. package/src/StackTrace.ts +230 -0
  205. package/src/Store.ts +8 -7
  206. package/src/String.ts +22 -5
  207. package/src/Task.ts +5101 -2832
  208. package/src/Test.ts +20 -168
  209. package/src/Time.ts +293 -90
  210. package/src/Type.ts +10034 -4117
  211. package/src/Types.ts +227 -110
  212. package/src/WebSocket.ts +64 -36
  213. package/src/Worker.ts +180 -72
  214. package/src/index.ts +55 -52
  215. package/src/intl/cs.ts +388 -0
  216. package/src/intl/index.ts +7 -0
  217. package/src/local-first/Db.ts +152 -179
  218. package/src/local-first/Error.ts +4 -4
  219. package/src/local-first/Evolu.ts +495 -250
  220. package/src/local-first/LocalAuth.ts +6 -12
  221. package/src/local-first/Owner.ts +58 -21
  222. package/src/local-first/Protocol.ts +49 -31
  223. package/src/local-first/Query.ts +146 -61
  224. package/src/local-first/Relay.ts +51 -21
  225. package/src/local-first/Schema.ts +109 -51
  226. package/src/local-first/Shared.ts +266 -226
  227. package/src/local-first/Storage.ts +266 -264
  228. package/src/local-first/Timestamp.ts +25 -18
  229. package/src/local-first/index.ts +11 -11
  230. package/dist/src/Skiplist.d.ts +0 -28
  231. package/dist/src/Skiplist.d.ts.map +0 -1
  232. package/dist/src/Skiplist.js +0 -64
  233. package/dist/src/Tracer.d.ts +0 -48
  234. package/dist/src/Tracer.d.ts.map +0 -1
  235. package/dist/src/Tracer.js +0 -6
  236. package/src/Skiplist.ts +0 -108
  237. package/src/Tracer.ts +0 -54
package/src/WebSocket.ts CHANGED
@@ -4,16 +4,16 @@
4
4
  * @module
5
5
  */
6
6
 
7
- import { assert } from "./Assert.js";
8
- import { lazyTrue } from "./Function.js";
9
- import type { Result } from "./Result.js";
10
- import { err, ok } from "./Result.js";
11
- import type { Schedule } from "./Schedule.js";
12
- import { exponential, jitter, maxDelay } from "./Schedule.js";
13
- import type { RetryError, Task } from "./Task.js";
14
- import { callback, retry } from "./Task.js";
15
- import type { Millis } from "./Time.js";
16
- import { ArrayBuffer, String, Uint8Array, type Typed } from "./Type.js";
7
+ import { assert } from "./Assert.ts";
8
+ import { constTrue } from "./Function.ts";
9
+ import type { Result } from "./Result.ts";
10
+ import { err, ok } from "./Result.ts";
11
+ import type { Schedule } from "./Schedule.ts";
12
+ import { exponential, jitter, maxDelay } from "./Schedule.ts";
13
+ import type { RetryError, Task } from "./Task.ts";
14
+ import { callback, retry } from "./Task.ts";
15
+ import type { Millis } from "./Time.ts";
16
+ import { ArrayBuffer, String, Uint8Array, type Typed } from "./Type.ts";
17
17
 
18
18
  /**
19
19
  * WebSocket with auto-reconnect.
@@ -49,20 +49,42 @@ import { ArrayBuffer, String, Uint8Array, type Typed } from "./Type.js";
49
49
  * - A Blob (default: "blob")
50
50
  * - An ArrayBuffer ("arraybuffer")
51
51
  *
52
- * ### Example
52
+ * ### Connecting and sending
53
53
  *
54
54
  * ```ts
55
- * const ws = await run(
56
- * createWebSocket("wss://example.com", {
57
- * onMessage: (data) => console.log("Received:", data),
58
- * onOpen: () => console.log("Connected"),
59
- * onClose: () => console.log("Disconnected"),
60
- * }),
61
- * );
62
- * if (ws.ok) {
63
- * ws.value.send("Hello");
64
- * // Later: await ws.value[Symbol.asyncDispose]();
65
- * }
55
+ * import {
56
+ * createRun,
57
+ * createWebSocket,
58
+ * testCreateWebSocket,
59
+ * type CreateWebSocket,
60
+ * type Task,
61
+ * type WebSocketSendError,
62
+ * } from "@evolu/common";
63
+ *
64
+ * const connectAndSend =
65
+ * (
66
+ * createSocket: CreateWebSocket = createWebSocket,
67
+ * ): Task<void, WebSocketSendError> =>
68
+ * async (run) => {
69
+ * await using socket = await run.ok(
70
+ * createSocket("wss://example.com", {
71
+ * protocols: ["evolu"],
72
+ * binaryType: "arraybuffer",
73
+ * onOpen: () => console.log("Connected"),
74
+ * onMessage: (data) => console.log("Received:", data),
75
+ * onClose: () => console.log("Disconnected"),
76
+ * }),
77
+ * );
78
+ * return socket.send("Hello");
79
+ * };
80
+ *
81
+ * const socketFactory = testCreateWebSocket();
82
+ * await using run = createRun();
83
+ *
84
+ * expectOk(await run(connectAndSend(socketFactory)), undefined);
85
+ * expect(socketFactory.sentMessages).toEqual([
86
+ * { url: "wss://example.com", data: "Hello" },
87
+ * ]);
66
88
  * ```
67
89
  */
68
90
  export interface WebSocket extends AsyncDisposable {
@@ -129,12 +151,8 @@ export interface WebSocketOptions {
129
151
  readonly onMessage?: (data: string | ArrayBuffer | Blob) => void;
130
152
 
131
153
  /**
132
- * Retry schedule for reconnection. Defaults to:
133
- *
134
- * ```ts
135
- * // A jittered, capped, unlimited exponential backoff.
136
- * jitter(1)(maxDelay("30s")(exponential("100ms")));
137
- * ```
154
+ * Retry schedule for reconnection. Defaults to
155
+ * {@link webSocketReconnectSchedule}.
138
156
  */
139
157
  readonly schedule?: Schedule<Millis, WebSocketRetryError>;
140
158
 
@@ -175,14 +193,24 @@ export interface WebSocketConnectionError extends Typed<"WebSocketConnectionErro
175
193
  }
176
194
 
177
195
  export type WebSocketRetryError =
178
- | WebSocketConnectError
179
- | WebSocketConnectionCloseError;
196
+ WebSocketConnectError | WebSocketConnectionCloseError;
180
197
 
181
198
  /** An error that occurs when the connection is closed by the server. */
182
199
  export interface WebSocketConnectionCloseError extends Typed<"WebSocketConnectionCloseError"> {
183
200
  readonly event: CloseEvent;
184
201
  }
185
202
 
203
+ /**
204
+ * Default WebSocket reconnect schedule.
205
+ *
206
+ * Uses unlimited exponential backoff with a 100ms base, 30s cap, and full
207
+ * jitter.
208
+ */
209
+ export const webSocketReconnectSchedule: Schedule<Millis, WebSocketRetryError> =
210
+ /*#__PURE__*/ jitter("100%")(
211
+ /*#__PURE__*/ maxDelay("30s")(/*#__PURE__*/ exponential("100ms")),
212
+ );
213
+
186
214
  /** Create a new {@link WebSocket}. */
187
215
  export const createWebSocket: CreateWebSocket =
188
216
  (
@@ -192,10 +220,10 @@ export const createWebSocket: CreateWebSocket =
192
220
  binaryType,
193
221
  onOpen,
194
222
  onClose,
195
- shouldRetryOnClose = lazyTrue,
223
+ shouldRetryOnClose = constTrue,
196
224
  onMessage,
197
225
  onError,
198
- schedule = jitter(1)(maxDelay("30s")(exponential("100ms"))),
226
+ schedule = webSocketReconnectSchedule,
199
227
  WebSocketConstructor = globalThis.WebSocket,
200
228
  } = {},
201
229
  ) =>
@@ -226,7 +254,7 @@ export const createWebSocket: CreateWebSocket =
226
254
  * A task that connects and stays connected until the connection closes or
227
255
  * errors. Returns error to trigger retry.
228
256
  */
229
- const connect: Task<void, WebSocketRetryError> = callback(({ err, ok }) => {
257
+ const connect: Task<void, WebSocketRetryError> = callback(({ resolve }) => {
230
258
  closeSocket();
231
259
 
232
260
  socket = new WebSocketConstructor(
@@ -246,9 +274,9 @@ export const createWebSocket: CreateWebSocket =
246
274
  socket.onclose = (event) => {
247
275
  onClose?.(event);
248
276
  if (shouldRetryOnClose(event)) {
249
- err({ type: "WebSocketConnectionCloseError", event });
277
+ resolve(err({ type: "WebSocketConnectionCloseError", event }));
250
278
  } else {
251
- ok();
279
+ resolve(ok());
252
280
  }
253
281
  };
254
282
 
@@ -262,7 +290,7 @@ export const createWebSocket: CreateWebSocket =
262
290
  : { type: "WebSocketConnectError", event };
263
291
  onError?.(error);
264
292
 
265
- if (error.type === "WebSocketConnectError") err(error);
293
+ if (error.type === "WebSocketConnectError") resolve(err(error));
266
294
  };
267
295
 
268
296
  return closeSocket;
package/src/Worker.ts CHANGED
@@ -4,10 +4,10 @@
4
4
  * @module
5
5
  */
6
6
 
7
- import { assert, assertNotDisposed } from "./Assert.js";
8
- import type { Brand } from "./Brand.js";
9
- import type { ConsoleDep, ConsoleStoreOutputEntryDep } from "./Console.js";
10
- import { testWaitForMacrotask } from "./Test.js";
7
+ import { assert } from "./Assert.ts";
8
+ import type { Brand } from "./Brand.ts";
9
+ import type { ConsoleDep, ConsoleStoreOutputEntryDep } from "./Console.ts";
10
+ import { disposable } from "./Function.ts";
11
11
 
12
12
  /**
13
13
  * Typed, disposable Worker.
@@ -60,13 +60,28 @@ export interface MessagePort<Input, Output = never> extends Disposable {
60
60
  /**
61
61
  * The native underlying port for transferring via `postMessage`.
62
62
  *
63
- * ### Example
63
+ * Prepare a port for transfer:
64
64
  *
65
65
  * ```ts
66
- * sharedWorker.port.postMessage(
67
- * { type: "InitConsole", port: consoleChannel.port1.native },
68
- * [consoleChannel.port1.native],
69
- * );
66
+ * import {
67
+ * createMessageChannel,
68
+ * type NativeMessagePort,
69
+ * } from "@evolu/common";
70
+ *
71
+ * interface ConsoleMessage {
72
+ * readonly type: "ConsoleEntry";
73
+ * readonly text: string;
74
+ * }
75
+ *
76
+ * using consoleChannel = createMessageChannel<ConsoleMessage>();
77
+ * const init = {
78
+ * type: "InitConsole",
79
+ * port: consoleChannel.port1.native,
80
+ * };
81
+ *
82
+ * expectTypeOf(init.port).toEqualTypeOf<
83
+ * NativeMessagePort<ConsoleMessage>
84
+ * >();
70
85
  * ```
71
86
  */
72
87
  readonly native: NativeMessagePort<Input, Output>;
@@ -126,36 +141,80 @@ export type WorkerDeps = ConsoleDep &
126
141
  *
127
142
  * For one-way communication, omit `Output` (defaults to `never`).
128
143
  *
129
- * ### Example
130
- *
131
- * Transfer a channel port to a SharedWorker for async initialization:
144
+ * Transfer a channel port to a SharedWorker and use the retained port
145
+ * immediately:
132
146
  *
133
147
  * ```ts
134
- * // Main thread: create channel, transfer port1, use port2 immediately.
135
- * const channel = createMessageChannel<EvoluRequest, EvoluResponse>();
136
- *
137
- * sharedWorker.port.postMessage(
138
- * { type: "CreateEvolu", port: channel.port1.native },
139
- * [channel.port1.native],
140
- * );
141
- *
142
- * // Safe to send immediately — messages queue until worker is ready.
143
- * channel.port2.postMessage({ type: "Query", query });
144
- * channel.port2.onMessage = (response) => {
145
- * handleResponse(response);
146
- * };
148
+ * import {
149
+ * createMessageChannel,
150
+ * createMessagePort,
151
+ * createSharedWorker,
152
+ * type NativeMessagePort,
153
+ * } from "@evolu/common";
154
+ *
155
+ * interface Query {
156
+ * readonly text: string;
157
+ * }
158
+ * interface QueryResult {
159
+ * readonly rows: ReadonlyArray<string>;
160
+ * }
161
+ * interface ConnectToEvolu {
162
+ * readonly port: NativeMessagePort<QueryResult, Query>;
163
+ * }
164
+ *
165
+ * const response = Promise.withResolvers<QueryResult>();
166
+ * using sharedWorker = createSharedWorker<ConnectToEvolu>((self) => {
167
+ * self.onConnect = (controlPort) => {
168
+ * controlPort.onMessage = ({ port }) => {
169
+ * const workerQueries = createMessagePort<QueryResult, Query>(port);
170
+ * workerQueries.onMessage = (query) => {
171
+ * workerQueries.postMessage({ rows: [query.text] });
172
+ * workerQueries[Symbol.dispose]();
173
+ * };
174
+ * };
175
+ * };
176
+ * });
177
+ * using queryChannel = createMessageChannel<Query, QueryResult>();
178
+ * queryChannel.port1.onMessage = response.resolve;
179
+ *
180
+ * // The main thread retains port1 and transfers the worker-facing port2.
181
+ * sharedWorker.port.postMessage({ port: queryChannel.port2.native }, [
182
+ * queryChannel.port2.native,
183
+ * ]);
184
+ * queryChannel.port1.postMessage({ text: "all todos" });
185
+ *
186
+ * expect(await response.promise).toEqual({ rows: ["all todos"] });
147
187
  * ```
148
188
  *
189
+ * On the worker side, attach the handler after asynchronous initialization;
190
+ * messages sent earlier remain queued:
191
+ *
149
192
  * ```ts
150
- * // Worker: receive the port, do async init, then start listening.
151
- * const evoluPort = createMessagePort<EvoluResponse, EvoluRequest>(
152
- * message.port,
153
- * );
154
- * await openDatabase(name);
155
- * evoluPort.onMessage = (request) => {
156
- * handleRequest(request);
193
+ * import { createMessageChannel, createMessagePort } from "@evolu/common";
194
+ *
195
+ * interface Query {
196
+ * readonly text: string;
197
+ * }
198
+ *
199
+ * using channel = createMessageChannel<Query>();
200
+ * using workerPort = createMessagePort<never, Query>(channel.port2.native);
201
+ * const received: Array<Query> = [];
202
+ * const allReceived = Promise.withResolvers<void>();
203
+ * // The main thread can send several messages while the worker initializes.
204
+ * channel.port1.postMessage({ text: "all todos" });
205
+ * channel.port1.postMessage({ text: "completed todos" });
206
+ *
207
+ * await Promise.resolve();
208
+ * workerPort.onMessage = (query) => {
209
+ * received.push(query);
210
+ * if (received.length === 2) allReceived.resolve();
157
211
  * };
158
- * // Queued messages are now delivered in order.
212
+ * await allReceived.promise;
213
+ *
214
+ * expect(received).toEqual([
215
+ * { text: "all todos" },
216
+ * { text: "completed todos" },
217
+ * ]);
159
218
  * ```
160
219
  *
161
220
  * @see https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
@@ -357,41 +416,41 @@ export const createBroadcastChannel: CreateBroadcastChannel = <
357
416
  dispatches.add(dispatch as BroadcastChannelDispatch<any>);
358
417
 
359
418
  using disposer = new DisposableStack();
419
+ let disposed = false;
360
420
 
361
421
  disposer.defer(() => {
422
+ disposed = true;
362
423
  onMessageHandler = null;
363
424
 
364
425
  dispatches.delete(dispatch as BroadcastChannelDispatch<any>);
365
426
  if (dispatches.size === 0) broadcastChannelDispatchesByName.delete(name);
366
427
  });
367
428
 
368
- const disposables = disposer.move();
369
-
370
- return {
371
- postMessage: (message) => {
372
- assertNotDisposed(disposables);
373
-
374
- const dispatches = broadcastChannelDispatchesByName.get(name);
375
- assert(dispatches, "Expected broadcast channel dispatches");
376
-
377
- for (const otherDispatch of [...dispatches]) {
378
- if (otherDispatch === dispatch) continue;
379
-
380
- globalThis.setTimeout(() => {
381
- if (!dispatches.has(otherDispatch)) return;
382
- otherDispatch(message);
383
- }, 0);
384
- }
385
- },
386
- get onMessage() {
387
- return disposables.disposed ? null : onMessageHandler;
429
+ return disposable<BroadcastChannel<Input, Output>>(
430
+ {
431
+ postMessage: (message) => {
432
+ const dispatches = broadcastChannelDispatchesByName.get(name);
433
+ assert(dispatches, "Expected broadcast channel dispatches");
434
+
435
+ for (const otherDispatch of [...dispatches]) {
436
+ if (otherDispatch === dispatch) continue;
437
+
438
+ scheduleWorkerTask(() => {
439
+ if (!dispatches.has(otherDispatch)) return;
440
+ otherDispatch(message);
441
+ });
442
+ }
443
+ },
444
+ get onMessage() {
445
+ return disposed ? null : onMessageHandler;
446
+ },
447
+ set onMessage(fn) {
448
+ if (disposed) return;
449
+ onMessageHandler = fn;
450
+ },
388
451
  },
389
- set onMessage(fn) {
390
- if (disposables.disposed) return;
391
- onMessageHandler = fn;
392
- },
393
- [Symbol.dispose]: () => disposables.dispose(),
394
- };
452
+ disposer,
453
+ );
395
454
  };
396
455
 
397
456
  type BroadcastChannelDispatch<T> = (message: T) => void;
@@ -537,17 +596,17 @@ export const testCreateBroadcastChannel = <Input, Output = Input>(
537
596
  };
538
597
 
539
598
  /**
540
- * Waits the minimal time necessary for in-memory worker message delivery in
541
- * tests.
599
+ * Waits until scheduled in-memory worker message delivery becomes idle.
542
600
  *
543
- * This is better than `vi.waitFor` when a test only needs to flush the known
544
- * in-memory worker/message-port pipeline. It encodes that transport boundary
545
- * once, avoids polling, and keeps worker-focused tests fast and explicit.
601
+ * This observes actual transport tasks instead of waiting for an arbitrary
602
+ * duration. It also waits through message handlers that schedule more worker
603
+ * tasks before the current microtask checkpoint completes.
546
604
  */
547
- export const testWaitForWorkerMessage = async (): Promise<void> => {
548
- await testWaitForMacrotask();
549
- await testWaitForMacrotask();
550
- };
605
+ export const testWaitForWorkerMessage = (): Promise<void> =>
606
+ new Promise((resolve) => {
607
+ workerTaskIdleWaiters.add(resolve);
608
+ notifyWorkerTaskIdle();
609
+ });
551
610
 
552
611
  const createMemoryWorkerPair = <Input, Output = never>(): {
553
612
  readonly worker: Worker<Input, Output>;
@@ -650,7 +709,7 @@ const createPort = <Input, Output>({
650
709
  nativePortRegistry.delete(native);
651
710
 
652
711
  if (receive.flushTimeoutId != null) {
653
- globalThis.clearTimeout(receive.flushTimeoutId);
712
+ cancelWorkerTask(receive.flushTimeoutId);
654
713
  receive.flushTimeoutId = null;
655
714
  }
656
715
 
@@ -665,8 +724,7 @@ const createPort = <Input, Output>({
665
724
  if (state.flushScheduled || state.ownerCount === 0) return;
666
725
  state.flushScheduled = true;
667
726
 
668
- // Native worker messages are task-queued; use macrotask timing.
669
- state.flushTimeoutId = globalThis.setTimeout(() => {
727
+ state.flushTimeoutId = scheduleWorkerTask(() => {
670
728
  state.flushTimeoutId = null;
671
729
  state.flushScheduled = false;
672
730
  if (state.ownerCount === 0) return;
@@ -680,7 +738,7 @@ const createPort = <Input, Output>({
680
738
 
681
739
  handler(message);
682
740
  }
683
- }, 0);
741
+ });
684
742
  };
685
743
 
686
744
  return {
@@ -718,6 +776,56 @@ const createPort = <Input, Output>({
718
776
  };
719
777
  };
720
778
 
779
+ const pendingWorkerTaskIds = new Set<
780
+ ReturnType<typeof globalThis.setTimeout>
781
+ >();
782
+ const workerTaskIdleWaiters = new Set<() => void>();
783
+ let workerTaskIdleCheckTimeoutId:
784
+ ReturnType<typeof globalThis.setTimeout> | undefined;
785
+
786
+ const scheduleWorkerTask = (
787
+ callback: () => void,
788
+ ): ReturnType<typeof globalThis.setTimeout> => {
789
+ if (workerTaskIdleCheckTimeoutId != null) {
790
+ globalThis.clearTimeout(workerTaskIdleCheckTimeoutId);
791
+ workerTaskIdleCheckTimeoutId = undefined;
792
+ }
793
+
794
+ const timeoutId = globalThis.setTimeout(() => {
795
+ pendingWorkerTaskIds.delete(timeoutId);
796
+ try {
797
+ callback();
798
+ } finally {
799
+ queueMicrotask(notifyWorkerTaskIdle);
800
+ }
801
+ }, 0);
802
+ pendingWorkerTaskIds.add(timeoutId);
803
+ return timeoutId;
804
+ };
805
+
806
+ const cancelWorkerTask = (
807
+ timeoutId: ReturnType<typeof globalThis.setTimeout>,
808
+ ): void => {
809
+ globalThis.clearTimeout(timeoutId);
810
+ pendingWorkerTaskIds.delete(timeoutId);
811
+ queueMicrotask(notifyWorkerTaskIdle);
812
+ };
813
+
814
+ const notifyWorkerTaskIdle = (): void => {
815
+ if (
816
+ pendingWorkerTaskIds.size > 0 ||
817
+ workerTaskIdleWaiters.size === 0 ||
818
+ workerTaskIdleCheckTimeoutId != null
819
+ )
820
+ return;
821
+
822
+ workerTaskIdleCheckTimeoutId = globalThis.setTimeout(() => {
823
+ workerTaskIdleCheckTimeoutId = undefined;
824
+ for (const resolve of workerTaskIdleWaiters) resolve();
825
+ workerTaskIdleWaiters.clear();
826
+ }, 0);
827
+ };
828
+
721
829
  const createTestPort = <Input, Output>(
722
830
  registration: PortRegistration<Input, Output>,
723
831
  ): MessagePort<Input, Output> => createPort(registration);
package/src/index.ts CHANGED
@@ -4,60 +4,63 @@
4
4
  * @module
5
5
  */
6
6
 
7
- export * from "./Array.js";
8
- export * from "./Assert.js";
9
- export * from "./BigInt.js";
10
- export * from "./Brand.js";
11
- export * from "./Buffer.js";
12
- export * from "./Cache.js";
13
- export * from "./Callbacks.js";
14
- export * from "./Console.js";
15
- export * from "./Crypto.js";
16
- export * from "./Eq.js";
17
- export * from "./Error.js";
18
- export * from "./Function.js";
19
- export * from "./Identicon.js";
20
- export * from "./LockManager.js";
21
- export * from "./Lookup.js";
22
- export * from "./Microtask.js";
23
- export * from "./Number.js";
24
- export * from "./Object.js";
25
- export * from "./Option.js";
26
- export * from "./Order.js";
27
- export * from "./Platform.js";
28
- export * from "./Random.js";
29
- export * from "./Redacted.js";
30
- export * from "./Ref.js";
31
- export * from "./RefCount.js";
32
- export * from "./Relation.js";
33
- export * from "./Resource.js";
34
- export * from "./Result.js";
35
- export * from "./Set.js";
36
- export * from "./Sqlite.js";
37
- export * from "./Store.js";
38
- export * from "./String.js";
39
- export * from "./Task.js";
40
- export * from "./Test.js";
41
- export * from "./Time.js";
42
- export * from "./Tracer.js";
43
- export * from "./Type.js";
44
- export * from "./Types.js";
45
- export * from "./WebSocket.js";
46
- export * from "./Worker.js";
7
+ export * from "./Array.ts";
8
+ export * from "./Assert.ts";
9
+ export * from "./BigInt.ts";
10
+ export * from "./Brand.ts";
11
+ export * from "./Buffer.ts";
12
+ export * from "./Cache.ts";
13
+ export * from "./Callbacks.ts";
14
+ export * from "./Console.ts";
15
+ export * from "./Crypto.ts";
16
+ export * from "./Eq.ts";
17
+ export * from "./Error.ts";
18
+ export * from "./Function.ts";
19
+ export * from "./Http.ts";
20
+ export * from "./Identicon.ts";
21
+ export * from "./LeakDetector.ts";
22
+ export * from "./LockManager.ts";
23
+ export * from "./Lookup.ts";
24
+ export * from "./Microtask.ts";
25
+ export * from "./Number.ts";
26
+ export * from "./Object.ts";
27
+ export * from "./Option.ts";
28
+ export * from "./Order.ts";
29
+ export * from "./Platform.ts";
30
+ export * from "./Random.ts";
31
+ export * from "./Redacted.ts";
32
+ export * from "./Ref.ts";
33
+ export * from "./RefCount.ts";
34
+ export * from "./Relation.ts";
35
+ export * from "./Resource.ts";
36
+ export * from "./Result.ts";
37
+ export * from "./Schedule.ts";
38
+ export * from "./Set.ts";
39
+ export * from "./Sqlite.ts";
40
+ export type { StandardSchemaV1 } from "@standard-schema/spec";
41
+ export * from "./Store.ts";
42
+ export * from "./String.ts";
43
+ export * from "./Task.ts";
44
+ export * from "./Test.ts";
45
+ export * from "./Time.ts";
46
+ export * from "./Type.ts";
47
+ export * from "./Types.ts";
48
+ export * from "./WebSocket.ts";
49
+ export * from "./Worker.ts";
47
50
 
48
51
  // Local-first essentials.
49
- export type { EvoluError } from "./local-first/Error.js";
50
- export { AppName, createEvolu } from "./local-first/Evolu.js";
52
+ export type { EvoluError } from "./local-first/Error.ts";
53
+ export { AppName, createEvolu } from "./local-first/Evolu.ts";
51
54
  export type {
52
55
  AppNameError,
53
56
  Evolu,
54
57
  EvoluConfig,
55
58
  EvoluDeps,
56
59
  UnuseOwner,
57
- } from "./local-first/Evolu.js";
58
- export * from "./local-first/LocalAuth.js";
59
- export * from "./local-first/Owner.js";
60
- export type { SyncOwner } from "./local-first/Owner.js";
60
+ } from "./local-first/Evolu.ts";
61
+ export * from "./local-first/LocalAuth.ts";
62
+ export * from "./local-first/Owner.ts";
63
+ export type { SyncOwner } from "./local-first/Owner.ts";
61
64
  export {
62
65
  evoluJsonArrayFrom,
63
66
  evoluJsonBuildObject,
@@ -69,8 +72,8 @@ export {
69
72
  type Query,
70
73
  type QueryRows,
71
74
  type Row,
72
- } from "./local-first/Query.js";
73
- export { createQueryBuilder } from "./local-first/Schema.js";
75
+ } from "./local-first/Query.ts";
76
+ export { createQueryBuilder } from "./local-first/Schema.ts";
74
77
  export type {
75
78
  AnyStandardSchemaV1,
76
79
  EvoluSchema,
@@ -85,22 +88,22 @@ export type {
85
88
  TableSchema,
86
89
  UpdateValues,
87
90
  UpsertValues,
88
- } from "./local-first/Schema.js";
91
+ } from "./local-first/Schema.ts";
89
92
  export type {
90
93
  // NetworkError,
91
94
  // PaymentRequiredError,
92
95
  // ServerError,
93
96
  SyncState,
94
- } from "./local-first/Shared.js";
97
+ } from "./local-first/Shared.ts";
95
98
  export {
96
99
  Timestamp,
97
100
  timestampBytesToTimestamp,
98
101
  timestampToTimestampBytes,
99
- } from "./local-first/Timestamp.js";
102
+ } from "./local-first/Timestamp.ts";
100
103
  export type {
101
104
  TimestampBytes,
102
105
  TimestampCounterOverflowError,
103
106
  TimestampDriftError,
104
107
  TimestampError,
105
108
  TimestampTimeOutOfRangeError,
106
- } from "./local-first/Timestamp.js";
109
+ } from "./local-first/Timestamp.ts";