@evolu/common 8.0.0-next.2 → 8.0.0-next.4

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 (73) hide show
  1. package/dist/src/Assert.d.ts +5 -5
  2. package/dist/src/Assert.d.ts.map +1 -1
  3. package/dist/src/Assert.js +4 -4
  4. package/dist/src/Identicon.js +4 -4
  5. package/dist/src/LockManager.d.ts +74 -0
  6. package/dist/src/LockManager.d.ts.map +1 -0
  7. package/dist/src/LockManager.js +103 -0
  8. package/dist/src/Microtask.d.ts.map +1 -1
  9. package/dist/src/Microtask.js +90 -26
  10. package/dist/src/Object.d.ts.map +1 -1
  11. package/dist/src/RefCount.d.ts.map +1 -1
  12. package/dist/src/RefCount.js +153 -68
  13. package/dist/src/Resource.d.ts +6 -6
  14. package/dist/src/Resource.d.ts.map +1 -1
  15. package/dist/src/Resource.js +211 -162
  16. package/dist/src/Schedule.d.ts +5 -5
  17. package/dist/src/Schedule.d.ts.map +1 -1
  18. package/dist/src/Schedule.js +53 -44
  19. package/dist/src/Sqlite.d.ts +2 -0
  20. package/dist/src/Sqlite.d.ts.map +1 -1
  21. package/dist/src/Sqlite.js +64 -40
  22. package/dist/src/Task.d.ts +6 -33
  23. package/dist/src/Task.d.ts.map +1 -1
  24. package/dist/src/Task.js +213 -204
  25. package/dist/src/Test.d.ts +4 -4
  26. package/dist/src/Time.d.ts +5 -0
  27. package/dist/src/Time.d.ts.map +1 -1
  28. package/dist/src/Time.js +5 -0
  29. package/dist/src/Type.d.ts +7 -0
  30. package/dist/src/Type.d.ts.map +1 -1
  31. package/dist/src/Type.js +7 -0
  32. package/dist/src/WebSocket.d.ts.map +1 -1
  33. package/dist/src/WebSocket.js +7 -6
  34. package/dist/src/Worker.d.ts +42 -8
  35. package/dist/src/Worker.d.ts.map +1 -1
  36. package/dist/src/Worker.js +298 -75
  37. package/dist/src/index.d.ts +1 -0
  38. package/dist/src/index.d.ts.map +1 -1
  39. package/dist/src/index.js +1 -0
  40. package/dist/src/local-first/Db.d.ts +5 -4
  41. package/dist/src/local-first/Db.d.ts.map +1 -1
  42. package/dist/src/local-first/Db.js +44 -38
  43. package/dist/src/local-first/Evolu.d.ts +3 -2
  44. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  45. package/dist/src/local-first/Evolu.js +90 -80
  46. package/dist/src/local-first/Owner.d.ts.map +1 -1
  47. package/dist/src/local-first/Shared.d.ts +65 -54
  48. package/dist/src/local-first/Shared.d.ts.map +1 -1
  49. package/dist/src/local-first/Shared.js +387 -373
  50. package/package.json +1 -1
  51. package/src/Array.ts +8 -8
  52. package/src/Assert.ts +5 -5
  53. package/src/Identicon.ts +4 -4
  54. package/src/LockManager.ts +181 -0
  55. package/src/Microtask.ts +17 -11
  56. package/src/Object.ts +3 -3
  57. package/src/RefCount.ts +25 -21
  58. package/src/Resource.ts +63 -56
  59. package/src/Schedule.ts +70 -48
  60. package/src/Set.ts +4 -4
  61. package/src/Sqlite.ts +41 -30
  62. package/src/Task.ts +77 -120
  63. package/src/Test.ts +4 -4
  64. package/src/Time.ts +7 -0
  65. package/src/Type.ts +9 -0
  66. package/src/WebSocket.ts +7 -6
  67. package/src/Worker.ts +240 -49
  68. package/src/index.ts +1 -0
  69. package/src/local-first/Db.ts +69 -46
  70. package/src/local-first/Evolu.ts +74 -68
  71. package/src/local-first/Owner.ts +1 -1
  72. package/src/local-first/Query.ts +1 -1
  73. package/src/local-first/Shared.ts +472 -464
package/src/Task.ts CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  type NonEmptyReadonlyArray,
13
13
  } from "./Array.js";
14
14
  import type { assertNotAborted } from "./Assert.js";
15
- import { assert } from "./Assert.js";
15
+ import { assert, assertNotDisposed } from "./Assert.js";
16
16
  import { type Console, type ConsoleDep, createConsole } from "./Console.js";
17
17
  import type { RandomBytes, RandomBytesDep } from "./Crypto.js";
18
18
  import { createRandomBytes } from "./Crypto.js";
@@ -43,7 +43,6 @@ import {
43
43
  Id,
44
44
  type InferType,
45
45
  maxPositiveInt,
46
- type Name,
47
46
  NonNegativeInt,
48
47
  object,
49
48
  onePositiveInt,
@@ -327,13 +326,13 @@ import {
327
326
  * ### Example
328
327
  *
329
328
  * ```ts
330
- * await using stack = new AsyncDisposableStack();
329
+ * await using disposer = new AsyncDisposableStack();
331
330
  *
332
331
  * const fooResult = await run(createFoo());
333
332
  * if (!fooResult.ok) return fooResult;
334
- * const foo = stack.use(fooResult.value);
333
+ * const foo = disposer.use(fooResult.value);
335
334
  *
336
- * stack.defer(async () => {
335
+ * disposer.defer(async () => {
337
336
  * await foo.close();
338
337
  * });
339
338
  * stack.adopt(session, async (session) => {
@@ -761,9 +760,9 @@ export interface Run<D = unknown> extends AsyncDisposable {
761
760
  * (config: Config): Task<void, InitError, CreateDbDep> =>
762
761
  * async (run) => {
763
762
  * const { createDb } = run.deps;
764
- * await using stack = new AsyncDisposableStack();
763
+ * await using disposer = new AsyncDisposableStack();
765
764
  *
766
- * const db = stack.use(await run.orThrow(startApp()));
765
+ * const db = disposer.use(await run.orThrow(startApp()));
767
766
  * if (!db.ok) return db;
768
767
  *
769
768
  * const runWithDb = run.addDeps({ db: db.value });
@@ -2025,7 +2024,7 @@ export const retry =
2025
2024
  task: Task<T, E, D>,
2026
2025
  schedule: Schedule<Output, E>,
2027
2026
  {
2028
- retryable = lazyTrue as Predicate<E>,
2027
+ retryable = lazyTrue,
2029
2028
  onRetry,
2030
2029
  }: RetryOptions<E, Output> = {},
2031
2030
  ): Task<T, RetryError<E>, D> =>
@@ -2145,7 +2144,7 @@ export const repeat =
2145
2144
  task: Task<T, E, D>,
2146
2145
  schedule: Schedule<Output, T>,
2147
2146
  {
2148
- repeatable = lazyTrue as Predicate<T>,
2147
+ repeatable = lazyTrue,
2149
2148
  onRepeat,
2150
2149
  }: RepeatOptions<T, Output> = {},
2151
2150
  ): Task<T, E, D> =>
@@ -2340,36 +2339,43 @@ export interface Gate<D = unknown> extends Disposable {
2340
2339
  * @group Concurrency primitives
2341
2340
  */
2342
2341
  export const createGate = <D = unknown>(): Gate<D> => {
2342
+ using disposer = new DisposableStack();
2343
2343
  let isOpen = false;
2344
- let disposed = false;
2345
2344
  let deferred = createDeferred<void>();
2346
2345
 
2346
+ disposer.defer(() => {
2347
+ deferred[Symbol.dispose]();
2348
+ });
2349
+
2350
+ const disposables = disposer.move();
2351
+
2347
2352
  return {
2348
2353
  wait: (run) => {
2349
- if (disposed) return err(deferredDisposedError);
2354
+ if (disposables.disposed) return err(deferredDisposedError);
2350
2355
  if (isOpen) return ok();
2351
2356
  return run(deferred.task);
2352
2357
  },
2353
2358
 
2354
2359
  open: () => {
2355
- if (disposed || isOpen) return;
2360
+ assertNotDisposed(disposables);
2361
+ if (isOpen) return;
2356
2362
  isOpen = true;
2357
2363
  deferred.resolve(ok());
2358
2364
  },
2359
2365
 
2360
2366
  close: () => {
2361
- if (disposed || !isOpen) return;
2367
+ assertNotDisposed(disposables);
2368
+ if (!isOpen) return;
2362
2369
  isOpen = false;
2363
2370
  deferred = createDeferred<void>();
2364
2371
  },
2365
2372
 
2366
- isOpen: () => isOpen,
2367
-
2368
- [Symbol.dispose]: () => {
2369
- if (disposed) return;
2370
- disposed = true;
2371
- deferred[Symbol.dispose]();
2373
+ isOpen: () => {
2374
+ assertNotDisposed(disposables);
2375
+ return isOpen;
2372
2376
  },
2377
+
2378
+ [Symbol.dispose]: () => disposables.dispose(),
2373
2379
  };
2374
2380
  };
2375
2381
 
@@ -2483,10 +2489,26 @@ export const createSemaphore = (permits: Concurrency): Semaphore => {
2483
2489
  readonly resolve: Callback<Result<void, AbortError>>;
2484
2490
  }
2485
2491
 
2492
+ using disposer = new DisposableStack();
2486
2493
  const fibers = new Set<Fiber>();
2487
2494
  const waiters: Array<Waiter> = [];
2488
2495
  let taken = zeroNonNegativeInt;
2489
- let disposed = false;
2496
+
2497
+ disposer.defer(() => {
2498
+ using disposer = new DisposableStack();
2499
+ for (const fiber of fibers) {
2500
+ disposer.adopt(fiber, (fiber) => {
2501
+ fiber.abort(semaphoreDisposedError);
2502
+ });
2503
+ }
2504
+
2505
+ for (const waiter of waiters) {
2506
+ waiter.resolve(err(semaphoreDisposedAbortError));
2507
+ }
2508
+ waiters.length = 0;
2509
+ });
2510
+
2511
+ const disposables = disposer.move();
2490
2512
 
2491
2513
  const withPermits =
2492
2514
  <T, E, D>(requestedPermits: Concurrency) =>
@@ -2499,7 +2521,7 @@ export const createSemaphore = (permits: Concurrency): Semaphore => {
2499
2521
  "Requested permits must not exceed semaphore capacity.",
2500
2522
  );
2501
2523
 
2502
- if (disposed) return err(semaphoreDisposedAbortError);
2524
+ if (disposables.disposed) return err(semaphoreDisposedAbortError);
2503
2525
 
2504
2526
  if (waiters.length > 0 || taken + requested > permits) {
2505
2527
  const waiter = Promise.withResolvers<Result<void, AbortError>>();
@@ -2548,31 +2570,19 @@ export const createSemaphore = (permits: Concurrency): Semaphore => {
2548
2570
  withPermit: <T, E, D>(task: Task<T, E, D>): Task<T, E, D> =>
2549
2571
  withPermits<T, E, D>(1)(task),
2550
2572
 
2551
- snapshot: () => ({
2552
- permits,
2553
- taken,
2554
- waiting: NonNegativeInt.orThrow(waiters.length),
2555
- available: NonNegativeInt.orThrow(permits - taken),
2556
- isIdle: taken === 0 && waiters.length === 0,
2557
- disposed,
2558
- }),
2559
-
2560
- [Symbol.dispose]: () => {
2561
- if (disposed) return;
2562
- disposed = true;
2563
-
2564
- using stack = new DisposableStack();
2565
- for (const fiber of fibers) {
2566
- stack.adopt(fiber, (fiber) => {
2567
- fiber.abort(semaphoreDisposedError);
2568
- });
2569
- }
2570
-
2571
- for (const waiter of waiters) {
2572
- waiter.resolve(err(semaphoreDisposedAbortError));
2573
- }
2574
- waiters.length = 0;
2573
+ snapshot: () => {
2574
+ assertNotDisposed(disposables);
2575
+ return {
2576
+ permits,
2577
+ taken,
2578
+ waiting: NonNegativeInt.orThrow(waiters.length),
2579
+ available: NonNegativeInt.orThrow(permits - taken),
2580
+ isIdle: taken === 0 && waiters.length === 0,
2581
+ disposed: disposables.disposed,
2582
+ };
2575
2583
  },
2584
+
2585
+ [Symbol.dispose]: () => disposables.dispose(),
2576
2586
  };
2577
2587
  };
2578
2588
 
@@ -2659,16 +2669,29 @@ export function createSemaphoreByKey<K, L = K>(
2659
2669
  permits: Concurrency,
2660
2670
  { lookup = identity as Lookup<K, L> }: CreateSemaphoreByKeyOptions<K, L> = {},
2661
2671
  ): SemaphoreByKey<K> {
2672
+ using disposer = new DisposableStack();
2662
2673
  const semaphoresByKey = createLookupMap<K, Semaphore, L>({
2663
2674
  lookup,
2664
2675
  });
2665
- let disposed = false;
2676
+
2677
+ disposer.defer(() => {
2678
+ using disposer = new DisposableStack();
2679
+ disposer.defer(() => {
2680
+ semaphoresByKey.clear();
2681
+ });
2682
+
2683
+ for (const semaphore of semaphoresByKey.values()) {
2684
+ disposer.use(semaphore);
2685
+ }
2686
+ });
2687
+
2688
+ const disposables = disposer.move();
2666
2689
 
2667
2690
  const withPermits =
2668
2691
  <T, E, D>(key: K, requestedPermits: Concurrency) =>
2669
2692
  (task: Task<T, E, D>): Task<T, E, D> =>
2670
2693
  async (run: Run<D>) => {
2671
- if (disposed) return err(semaphoreDisposedAbortError);
2694
+ if (disposables.disposed) return err(semaphoreDisposedAbortError);
2672
2695
 
2673
2696
  let semaphore = semaphoresByKey.get(key);
2674
2697
  if (!semaphore) {
@@ -2678,6 +2701,8 @@ export function createSemaphoreByKey<K, L = K>(
2678
2701
 
2679
2702
  using _ = {
2680
2703
  [Symbol.dispose]: () => {
2704
+ if (semaphoresByKey.get(key) !== semaphore) return;
2705
+
2681
2706
  const snapshot = semaphore.snapshot();
2682
2707
  if (snapshot.isIdle) {
2683
2708
  semaphoresByKey.delete(key);
@@ -2695,18 +2720,12 @@ export function createSemaphoreByKey<K, L = K>(
2695
2720
 
2696
2721
  withPermits,
2697
2722
 
2698
- snapshot: (key) => semaphoresByKey.get(key)?.snapshot() ?? null,
2699
-
2700
- [Symbol.dispose]: () => {
2701
- if (disposed) return;
2702
- disposed = true;
2703
-
2704
- using stack = new DisposableStack();
2705
- for (const semaphore of semaphoresByKey.values()) {
2706
- stack.use(semaphore);
2707
- }
2708
- semaphoresByKey.clear();
2723
+ snapshot: (key) => {
2724
+ assertNotDisposed(disposables);
2725
+ return semaphoresByKey.get(key)?.snapshot() ?? null;
2709
2726
  },
2727
+
2728
+ [Symbol.dispose]: () => disposables.dispose(),
2710
2729
  };
2711
2730
  }
2712
2731
 
@@ -2931,68 +2950,6 @@ export const createMutexRef = <T>(initialValue: T): MutexRef<T> => {
2931
2950
  };
2932
2951
  };
2933
2952
 
2934
- /**
2935
- * Cross-platform leader lock abstraction.
2936
- *
2937
- * `lock` returns a {@link Task} that waits until leadership is acquired and
2938
- * yields a lease.
2939
- *
2940
- * Returns {@link AsyncDisposable} lease. Dispose it to release leadership.
2941
- *
2942
- * @group Concurrency primitives
2943
- */
2944
- export interface LeaderLock {
2945
- readonly lock: (name: Name) => Task<AsyncDisposable>;
2946
- }
2947
-
2948
- /** @group Concurrency primitives */
2949
- export interface LeaderLockDep {
2950
- readonly leaderLock: LeaderLock;
2951
- }
2952
-
2953
- /**
2954
- * Creates an in-process {@link LeaderLock}.
2955
- *
2956
- * Uses one {@link Mutex} per {@link Name}. Suitable for runtimes without a
2957
- * cross-process lock manager (for example in-memory worker tests or React
2958
- * Native).
2959
- *
2960
- * @group Concurrency primitives
2961
- */
2962
- export const createInMemoryLeaderLock = (): LeaderLock => {
2963
- const mutexByName = createMutexByKey<Name>();
2964
-
2965
- return {
2966
- lock: (name) => async (run) => {
2967
- const leaseRun = run.create();
2968
- const released = createDeferred<void>();
2969
- const acquired = createDeferred<void>();
2970
-
2971
- void leaseRun(
2972
- mutexByName.withLock(name, async (run) => {
2973
- acquired.resolve(ok());
2974
- await run(released.task);
2975
- return ok();
2976
- }),
2977
- );
2978
-
2979
- const acquiredResult = await run(acquired.task);
2980
- if (!acquiredResult.ok) {
2981
- assert(
2982
- AbortError.is(acquiredResult.error),
2983
- "Leader lock acquisition deferred must not be disposed.",
2984
- );
2985
- void leaseRun[Symbol.asyncDispose]();
2986
- return err(acquiredResult.error);
2987
- }
2988
-
2989
- return ok({
2990
- [Symbol.asyncDispose]: leaseRun[Symbol.asyncDispose],
2991
- });
2992
- },
2993
- };
2994
- };
2995
-
2996
2953
  /**
2997
2954
  * Options for {@link all}, {@link allSettled}, {@link map}, and {@link mapSettled}.
2998
2955
  *
package/src/Test.ts CHANGED
@@ -119,10 +119,10 @@ export const testCreateDeps = (options?: {
119
119
  * // If multiple tests need the same setup, create a disposable helper.
120
120
  * // Then `await using setup` disposes everything the helper owns.
121
121
  * const setupFoo = async () => {
122
- * await using stack = new AsyncDisposableStack();
123
- * const run = stack.use(testCreateRun());
124
- * const foo = stack.use(await run.orThrow(createFoo()));
125
- * const moved = stack.move();
122
+ * await using disposer = new AsyncDisposableStack();
123
+ * const run = disposer.use(testCreateRun());
124
+ * const foo = disposer.use(await run.orThrow(createFoo()));
125
+ * const disposables = disposer.move();
126
126
  *
127
127
  * // Return whatever the tests need: a Run with the dependency,
128
128
  * // the dependency itself, or both.
package/src/Time.ts CHANGED
@@ -177,6 +177,13 @@ export const minMillis = 0 as Millis;
177
177
  /** Maximum {@link Millis} value. */
178
178
  export const maxMillis = (maxMillisWithInfinity - 1) as Millis;
179
179
 
180
+ /**
181
+ * Converts a number to {@link Millis}, rounding to the nearest millisecond and
182
+ * saturating overflow at {@link maxMillis}.
183
+ */
184
+ export const saturateMillis = (value: number): Millis =>
185
+ Millis.orNull(Math.max(0, Math.round(value))) ?? maxMillis;
186
+
180
187
  /**
181
188
  * Converts {@link Millis} to {@link DateIso}.
182
189
  *
package/src/Type.ts CHANGED
@@ -2334,6 +2334,14 @@ export const formatFiniteError =
2334
2334
  export const FiniteNumber = /*#__PURE__*/ finite(Number);
2335
2335
  export type FiniteNumber = typeof FiniteNumber.Type;
2336
2336
 
2337
+ /**
2338
+ * Non-negative finite number.
2339
+ *
2340
+ * @group Number
2341
+ */
2342
+ export const NonNegativeFiniteNumber = /*#__PURE__*/ nonNegative(FiniteNumber);
2343
+ export type NonNegativeFiniteNumber = typeof NonNegativeFiniteNumber.Type;
2344
+
2337
2345
  /**
2338
2346
  * Number that is a multiple of a divisor.
2339
2347
  *
@@ -4651,6 +4659,7 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
4651
4659
 
4652
4660
  if (extraMessage != null) return extraMessage;
4653
4661
 
4662
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
4654
4663
  error = error as TypeErrors;
4655
4664
 
4656
4665
  switch (error.type) {
package/src/WebSocket.ts CHANGED
@@ -200,7 +200,7 @@ export const createWebSocket: CreateWebSocket =
200
200
  } = {},
201
201
  ) =>
202
202
  async (run) => {
203
- await using stack = new AsyncDisposableStack();
203
+ await using disposer = new AsyncDisposableStack();
204
204
 
205
205
  let socket: globalThis.WebSocket | null = null;
206
206
 
@@ -268,7 +268,7 @@ export const createWebSocket: CreateWebSocket =
268
268
  return closeSocket;
269
269
  });
270
270
 
271
- const retryFiber = stack.use(run.daemon(retry(connect, schedule)));
271
+ const retryFiber = disposer.use(run.daemon(retry(connect, schedule)));
272
272
 
273
273
  // Report RetryError (schedule exhausted) via onError callback
274
274
  void retryFiber.then((result) => {
@@ -277,7 +277,7 @@ export const createWebSocket: CreateWebSocket =
277
277
  }
278
278
  });
279
279
 
280
- const moved = stack.move();
280
+ const disposables = disposer.move();
281
281
 
282
282
  return ok<WebSocket>({
283
283
  send: (data) => {
@@ -290,14 +290,15 @@ export const createWebSocket: CreateWebSocket =
290
290
  },
291
291
 
292
292
  getReadyState: () => {
293
- if (moved.disposed) return "closed";
293
+ if (disposables.disposed) return "closed";
294
294
  return socket ? nativeToStringState[socket.readyState] : "connecting";
295
295
  },
296
296
 
297
297
  isOpen: () =>
298
- !moved.disposed && socket?.readyState === globalThis.WebSocket.OPEN,
298
+ !disposables.disposed &&
299
+ socket?.readyState === globalThis.WebSocket.OPEN,
299
300
 
300
- [Symbol.asyncDispose]: () => moved.disposeAsync(),
301
+ [Symbol.asyncDispose]: () => disposables.disposeAsync(),
301
302
  });
302
303
  };
303
304