@evolu/common 8.3.3 → 8.5.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 (76) hide show
  1. package/dist/src/Array.d.ts +24 -0
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +27 -1
  4. package/dist/src/Assert.d.ts +62 -19
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +64 -17
  7. package/dist/src/LeakDetector.d.ts.map +1 -1
  8. package/dist/src/LeakDetector.js +4 -1
  9. package/dist/src/Lookup.js +1 -0
  10. package/dist/src/Platform.js +1 -0
  11. package/dist/src/Relation.js +3 -3
  12. package/dist/src/Resource.d.ts.map +1 -1
  13. package/dist/src/Resource.js +9 -3
  14. package/dist/src/Result.d.ts.map +1 -1
  15. package/dist/src/Result.js +3 -5
  16. package/dist/src/Sqlite.d.ts.map +1 -1
  17. package/dist/src/Sqlite.js +9 -7
  18. package/dist/src/Store.d.ts.map +1 -1
  19. package/dist/src/Store.js +3 -1
  20. package/dist/src/String.d.ts.map +1 -1
  21. package/dist/src/String.js +1 -1
  22. package/dist/src/Task.d.ts +122 -60
  23. package/dist/src/Task.d.ts.map +1 -1
  24. package/dist/src/Task.js +40 -53
  25. package/dist/src/Test.d.ts.map +1 -1
  26. package/dist/src/Test.js +1 -0
  27. package/dist/src/Time.d.ts.map +1 -1
  28. package/dist/src/Time.js +9 -2
  29. package/dist/src/Type.d.ts +146 -9
  30. package/dist/src/Type.d.ts.map +1 -1
  31. package/dist/src/Type.js +10 -7
  32. package/dist/src/WebSocket.d.ts.map +1 -1
  33. package/dist/src/WebSocket.js +8 -0
  34. package/dist/src/Worker.js +1 -1
  35. package/dist/src/local-first/Db.d.ts.map +1 -1
  36. package/dist/src/local-first/Db.js +7 -6
  37. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  38. package/dist/src/local-first/Evolu.js +1 -0
  39. package/dist/src/local-first/Owner.d.ts +8 -7
  40. package/dist/src/local-first/Owner.d.ts.map +1 -1
  41. package/dist/src/local-first/Owner.js +4 -4
  42. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  43. package/dist/src/local-first/Protocol.js +31 -25
  44. package/dist/src/local-first/Query.d.ts.map +1 -1
  45. package/dist/src/local-first/Query.js +2 -1
  46. package/dist/src/local-first/Relay.js +1 -1
  47. package/dist/src/local-first/Shared.d.ts.map +1 -1
  48. package/dist/src/local-first/Shared.js +4 -2
  49. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  50. package/dist/src/local-first/Timestamp.js +1 -1
  51. package/package.json +1 -1
  52. package/src/Array.ts +28 -1
  53. package/src/Assert.ts +81 -19
  54. package/src/LeakDetector.ts +4 -3
  55. package/src/Lookup.ts +1 -0
  56. package/src/Platform.ts +1 -0
  57. package/src/Relation.ts +3 -3
  58. package/src/Resource.ts +9 -3
  59. package/src/Result.ts +5 -5
  60. package/src/Sqlite.ts +9 -7
  61. package/src/Store.ts +3 -1
  62. package/src/String.ts +1 -1
  63. package/src/Task.ts +133 -82
  64. package/src/Test.ts +1 -0
  65. package/src/Time.ts +9 -2
  66. package/src/Type.ts +173 -29
  67. package/src/WebSocket.ts +8 -0
  68. package/src/Worker.ts +1 -1
  69. package/src/local-first/Db.ts +11 -7
  70. package/src/local-first/Evolu.ts +1 -0
  71. package/src/local-first/Owner.ts +8 -7
  72. package/src/local-first/Protocol.ts +41 -31
  73. package/src/local-first/Query.ts +6 -3
  74. package/src/local-first/Relay.ts +1 -1
  75. package/src/local-first/Shared.ts +4 -2
  76. package/src/local-first/Timestamp.ts +1 -1
package/src/Task.ts CHANGED
@@ -3,36 +3,34 @@
3
3
  *
4
4
  * JavaScript-native structured concurrency.
5
5
  *
6
- * Structured concurrency makes ownership of asynchronous work explicit.
7
- * Operations form a tree where every child belongs to a parent. A parent waits
8
- * for its children before it completes, and abort follows the tree: aborting a
9
- * parent requests abort of all its descendants. Races and fail-fast operations
10
- * also abort their remaining sibling branches.
6
+ * Structured concurrency organizes running tasks into a tree. Every child
7
+ * belongs to a parent, a parent waits for its children before it completes, and
8
+ * abort propagates from parents to descendants. Races and fail-fast control
9
+ * flow abort siblings that are no longer needed.
11
10
  *
12
11
  * With plain {@link AbortController} code, these guarantees depend on call-site
13
- * discipline: someone must remember the `finally` that aborts started work and
14
- * the await that waits for cleanup. {@link Run} makes both structural:
15
- * `run(task)` registers every child before it starts, and the parent settles
12
+ * discipline: someone must remember the `finally` that aborts started tasks and
13
+ * the await that waits for cleanup. Evolu makes both structural: `run(task)`
14
+ * registers every child before it starts, and the parent {@link Run} settles
16
15
  * only after child cleanup finishes.
17
16
  *
18
- * Evolu models structured concurrency with ordinary JavaScript:
17
+ * Evolu implements structured concurrency with:
19
18
  *
20
- * - A {@link Task} describes an asynchronous operation and its dependencies.
19
+ * - A {@link Task} is a function passed to Run that returns an {@link Awaitable}
20
+ * {@link Result} and declares its dependencies.
21
21
  * - A {@link Run} starts Tasks and owns their lifetimes.
22
22
  * - A {@link Fiber} is the Promise-backed handle returned when a Run starts a
23
23
  * Task.
24
24
  * - An {@link AbortableFiber} adds explicit abort and async disposal.
25
25
  *
26
- * The runtime core is deliberately small: ordinary functions, a callable Run
27
- * with closed-over state, Promise-backed Fibers, {@link AbortSignal}
28
- * propagation, and JavaScript resource management. Together, these primitives
29
- * provide abort, cleanup, defect handling, dependency injection, monitoring,
30
- * concurrency, and resource bracketing.
26
+ * Together, these APIs provide abort, cleanup, defect handling, dependency
27
+ * injection, monitoring, and resource management.
31
28
  *
32
- * Tasks return domain success or failure as {@link Result}. Abort is control
33
- * flow represented by {@link AbortError}. If a Task throws or rejects with
34
- * anything else, that is a defect: the root Run reports it and shuts down its
35
- * tree so code does not continue in a potentially invalid state.
29
+ * Tasks return a {@link Result} containing either success or a domain error.
30
+ * Abort is control flow represented by {@link AbortError}. If a Task throws or
31
+ * rejects with anything else, that is a defect: the root Run reports it and
32
+ * shuts down its tree so code does not continue in a potentially invalid
33
+ * state.
36
34
  *
37
35
  * ```ts
38
36
  * import {
@@ -201,7 +199,7 @@
201
199
  *
202
200
  * {@link fetch} with a body mode already returns a plain value, so resilience is
203
201
  * ordinary Task composition. Combine {@link timeout} and {@link retry} to bound
204
- * each attempt and retry recoverable domain failures:
202
+ * each attempt and retry recoverable domain errors:
205
203
  *
206
204
  * ```ts
207
205
  * import {
@@ -424,9 +422,9 @@
424
422
  * expect(socketDisposed).toBe(true);
425
423
  * ```
426
424
  *
427
- * Use {@link Run.ok} with `await using` when an infallible Task returns a
428
- * disposable value. Use {@link acquireUseRelease} when acquisition and release
429
- * are separate operations rather than a disposable value.
425
+ * Use {@link Run.ok} with `await using` when a Task whose error type is `never`
426
+ * returns a disposable value. Use {@link acquireUseRelease} when acquisition and
427
+ * release are separate steps rather than a disposable value.
430
428
  *
431
429
  * ## Awaitable
432
430
  *
@@ -439,7 +437,7 @@
439
437
  *
440
438
  * A Task is an async ownership boundary, not a general unit of program
441
439
  * decomposition. Calling `run(task)` always creates a child Run by design. Use
442
- * ordinary promises when an async operation does not need its own Run.
440
+ * a plain async function when it does not need its own Run.
443
441
  *
444
442
  * A unified sync/async effect API is technically possible. It can detect
445
443
  * Promise-like values with {@link isPromiseLike}, dispose synchronous resources
@@ -457,7 +455,7 @@
457
455
  * code performs effects with the result. For example, a pure function can
458
456
  * accept a {@link RandomNumber} value instead of depending on {@link Random}.
459
457
  *
460
- * Large CPU-bound operations, such as parsing large JSON, sorting millions of
458
+ * Large CPU-bound computations, such as parsing large JSON, sorting millions of
461
459
  * items, or complex cryptography, belong in a worker. Model the asynchronous
462
460
  * call to that worker as a Task so Run can provide timeout, abort, cleanup, and
463
461
  * monitoring.
@@ -517,10 +515,10 @@
517
515
  * ### What should Task code do with defects?
518
516
  *
519
517
  * Nothing. Once a defect reaches the {@link Run}, it is too late: the root Run
520
- * panics, running Tasks are aborted, and the Run tree shuts down. If an
521
- * operation can throw or reject for a recoverable reason, wrap that operation
522
- * with {@link trySync} or {@link tryAsync} so the failure becomes a typed
523
- * {@link Result} error. Let unrecoverable failures propagate as defects.
518
+ * panics, running Tasks are aborted, and the Run tree shuts down. Use
519
+ * {@link trySync} or {@link tryAsync} to turn recoverable exceptions and Promise
520
+ * rejections into typed {@link Result} errors. Let unrecoverable failures
521
+ * propagate as defects.
524
522
  *
525
523
  * ### Why does a defect panic the whole Run tree?
526
524
  *
@@ -619,13 +617,6 @@
619
617
  * periodically await {@link yieldNow} for cooperative scheduling, and move
620
618
  * CPU-bound work to a worker.
621
619
  *
622
- * ### Should a Task be called directly?
623
- *
624
- * Only inside Task internals that explicitly require same-Run execution. A
625
- * direct call, `task(run)`, uses the current Run instead of creating a child
626
- * Run, so it bypasses child lifetime tracking, scheduling metadata, and child
627
- * disposal boundaries. Application code should use `run(task)`.
628
- *
629
620
  * ### Where are fork and join?
630
621
  *
631
622
  * Calling `run(task)` is fork: it starts a child Task and returns a
@@ -756,16 +747,78 @@ import type {
756
747
  Predicate,
757
748
  } from "./Types.ts";
758
749
 
759
- // Core
760
-
761
750
  /**
762
- * An operation run by {@link Run} that returns a {@link Result} synchronously or
763
- * asynchronously and declares its dependencies through `D`.
764
- *
765
- * Its return type is {@link Awaitable}.
751
+ * A function passed to {@link Run} that returns an {@link Awaitable}
752
+ * {@link Result} and declares its dependencies through `D`.
766
753
  *
767
754
  * See the {@link @evolu/common!Task | Task overview}.
768
755
  *
756
+ * ### Example
757
+ *
758
+ * A Task that can't fail with a domain error:
759
+ *
760
+ * ```ts
761
+ * import { createRun, ok, type Task } from "@evolu/common";
762
+ *
763
+ * const greet: Task<string> = () => ok("Hello!");
764
+ * const main: Task<string> = async (run) => await run(greet);
765
+ *
766
+ * await using run = createRun();
767
+ * expectOk(await run(main), "Hello!");
768
+ *
769
+ * // Without domain errors, `run.ok` returns the Ok value.
770
+ * expect(await run.ok(main)).toBe("Hello!");
771
+ * ```
772
+ *
773
+ * A Task that can fail with a domain error:
774
+ *
775
+ * ```ts
776
+ * import { createRun, err, type Task, type Typed } from "@evolu/common";
777
+ *
778
+ * const findUser =
779
+ * (id: string): Task<string, UserNotFoundError> =>
780
+ * () =>
781
+ * err({ type: "UserNotFound", id });
782
+ *
783
+ * interface UserNotFoundError extends Typed<"UserNotFound"> {
784
+ * readonly id: string;
785
+ * }
786
+ *
787
+ * await using run = createRun();
788
+ * expectErr(await run(findUser("user-1")), {
789
+ * type: "UserNotFound",
790
+ * id: "user-1",
791
+ * });
792
+ * ```
793
+ *
794
+ * A Task with dependencies:
795
+ *
796
+ * ```ts
797
+ * import { createRun, ok, type Task } from "@evolu/common";
798
+ *
799
+ * interface Config {
800
+ * readonly greeting: string;
801
+ * }
802
+ *
803
+ * interface ConfigDep {
804
+ * readonly config: Config;
805
+ * }
806
+ *
807
+ * const greet: Task<string, never, ConfigDep> = (run) =>
808
+ * ok(`${run.deps.config.greeting}!`);
809
+ *
810
+ * const config: Config = { greeting: "Hello" };
811
+ * await using run = createRun({ config });
812
+ * expectOk(await run(greet), "Hello!");
813
+ * ```
814
+ *
815
+ * Start Tasks with `run(task)`, as shown above.
816
+ *
817
+ * A Task can also be called directly as `task(run)`, but this is rarely needed.
818
+ * The call executes the Task inline in the current Run, as if its body were
819
+ * part of the parent Task, so it does not create a child Run. This is mainly
820
+ * useful for Task composition helpers.
821
+ *
769
822
  * @group Core
770
823
  */
771
824
  export type Task<T, E = never, D = unknown> = (
@@ -937,6 +990,13 @@ export interface Run<D = unknown> {
937
990
  * expectOk(userResult, "Ada");
938
991
  * expectOk(savedResult, undefined);
939
992
  * ```
993
+ *
994
+ * Start Tasks with `run(task)`, as shown above.
995
+ *
996
+ * A Task can also be called directly as `task(run)`, but this is rarely
997
+ * needed. The call executes the Task inline in the current Run, as if its
998
+ * body were part of the parent Task, so it does not create a child Run. This
999
+ * is mainly useful for Task composition helpers.
940
1000
  */
941
1001
  <T, E>(task: Task<T, E, D>): Fiber<T, E, D>;
942
1002
 
@@ -1196,8 +1256,8 @@ export interface Run<D = unknown> {
1196
1256
  * Creates a {@link DisposableRun} attached to the root {@link Run} with this
1197
1257
  * Run's deps.
1198
1258
  *
1199
- * Use it when you need a Run that can be reused across multiple operations.
1200
- * For a single long-lived {@link Task}, use {@link Run.daemon}.
1259
+ * Use it to give multiple related Tasks a shared lifetime. For a single
1260
+ * long-lived {@link Task}, use {@link Run.daemon}.
1201
1261
  *
1202
1262
  * Use deps to replace the created Run's custom deps. Default deps
1203
1263
  * ({@link RunDefaultDeps}) are inherited unless explicitly replaced with
@@ -2520,7 +2580,6 @@ const createRunInternal = <D extends object>(
2520
2580
  result = await scheduler.postTask(
2521
2581
  () => {
2522
2582
  startSignal.throwIfAborted();
2523
- // eslint-disable-next-line evolu/no-direct-task-call -- The executor invokes the Task with its child Run.
2524
2583
  return task(taskRun);
2525
2584
  },
2526
2585
  {
@@ -2530,7 +2589,6 @@ const createRunInternal = <D extends object>(
2530
2589
  );
2531
2590
  } else {
2532
2591
  startSignal.throwIfAborted();
2533
- // eslint-disable-next-line evolu/no-direct-task-call -- The executor invokes the Task with its child Run.
2534
2592
  result = await task(taskRun);
2535
2593
  }
2536
2594
 
@@ -2595,7 +2653,7 @@ const createRunInternal = <D extends object>(
2595
2653
  getOrThrow(await run(task, taskDeps))) as Run<D>["orThrow"];
2596
2654
 
2597
2655
  run.ok = (async (task: TaskInternal, taskDeps?: object) =>
2598
- getOk((await run(task, taskDeps)) as Result<any, never>)) as Run<D>["ok"];
2656
+ getOk((await run(task, taskDeps)) as Result<any>)) as Run<D>["ok"];
2599
2657
  /* eslint-enable @typescript-eslint/no-unsafe-return */
2600
2658
 
2601
2659
  run.abortable = ((task: TaskInternal, deps?: object) =>
@@ -2724,15 +2782,12 @@ const withTaskMeta =
2724
2782
  taskInternal[taskMetaSymbol]?.abortBehavior === undefined,
2725
2783
  "abort behavior helpers cannot wrap the same Task",
2726
2784
  );
2727
- // eslint-disable-next-line evolu/no-direct-task-call -- Preserve the wrapped Task's child Run.
2728
2785
  const wrapped: TaskInternal<T, E, D> = (run) => task(run);
2729
2786
  const taskMeta = taskInternal[taskMetaSymbol];
2730
2787
  wrapped[taskMetaSymbol] = taskMeta ? { ...taskMeta, ...meta } : meta;
2731
2788
  return wrapped;
2732
2789
  };
2733
2790
 
2734
- // Task helpers
2735
-
2736
2791
  /**
2737
2792
  * A readonly record whose values are {@link Task}s.
2738
2793
  *
@@ -2768,7 +2823,7 @@ export type InferTaskRecordDeps<TTasks extends TaskRecord> = InferTasksDeps<
2768
2823
  * Options shared by {@link Task} collection helpers.
2769
2824
  *
2770
2825
  * `concurrency` controls how many Tasks run at once. It defaults to `1`. For
2771
- * CPU-bound Tasks backed by workers or parallel native operations, a platform
2826
+ * CPU-bound Tasks backed by workers or native parallelism, a platform
2772
2827
  * `availableParallelism()` result is often a good limit. For network or
2773
2828
  * database Tasks, choose a limit based on the transport, server, connection
2774
2829
  * pool, and rate limits.
@@ -2993,7 +3048,7 @@ export function all<
2993
3048
  TTask extends AnyTask,
2994
3049
  >(
2995
3050
  values: TValues,
2996
- // eslint-disable-next-line @typescript-eslint/unified-signatures -- Separate array and record overloads keep callback parameter inference precise.
3051
+ // Separate array and record overloads keep callback parameter inference precise.
2997
3052
  fn: (value: TValues[keyof TValues], key: keyof TValues) => TTask,
2998
3053
  options: AllOptions,
2999
3054
  ): Task<void, InferTaskErr<TTask>, InferTasksDeps<ReadonlyArray<TTask>>>;
@@ -3103,7 +3158,7 @@ export function all<
3103
3158
  TTask extends AnyTask,
3104
3159
  >(
3105
3160
  values: TValues,
3106
- // eslint-disable-next-line @typescript-eslint/unified-signatures -- Separate array and record overloads keep callback parameter inference precise.
3161
+ // Separate array and record overloads keep callback parameter inference precise.
3107
3162
  fn: (value: TValues[keyof TValues], key: keyof TValues) => TTask,
3108
3163
  options?: TaskCollectionOptions,
3109
3164
  ): Task<
@@ -3392,7 +3447,7 @@ export function allSettled<
3392
3447
  TTask extends AnyTask,
3393
3448
  >(
3394
3449
  values: TValues,
3395
- // eslint-disable-next-line @typescript-eslint/unified-signatures -- Separate array and record overloads keep callback parameter inference precise.
3450
+ // Separate array and record overloads keep callback parameter inference precise.
3396
3451
  fn: (value: TValues[keyof TValues], key: keyof TValues) => TTask,
3397
3452
  options?: TaskCollectionOptions,
3398
3453
  ): Task<
@@ -3499,7 +3554,7 @@ const mapInput = (
3499
3554
  * This helper is a callback bridge. If `reject` forwards an Error created in a
3500
3555
  * separate async chain, V8 cannot reconstruct the caller's zero-cost async
3501
3556
  * stack through this bridge. Prefer native promise APIs and `await` when the
3502
- * wrapped operation already has a promise-shaped API.
3557
+ * wrapped API already returns a Promise.
3503
3558
  *
3504
3559
  * One-shot settlement applies only to `resolve` and `reject`. A synchronous
3505
3560
  * throw from the setup function is a defect that panics the Run tree even after
@@ -4484,7 +4539,7 @@ export const each =
4484
4539
  nextIndex += 1;
4485
4540
 
4486
4541
  const result = await run(tasks[index]);
4487
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- stopped can flip across the await via sibling workers
4542
+ // stopped can flip across the await via sibling workers.
4488
4543
  if (stopped) break;
4489
4544
  run.signal.throwIfAborted();
4490
4545
  const decision = onResult(result, index);
@@ -4502,6 +4557,7 @@ export const each =
4502
4557
 
4503
4558
  active -= 1;
4504
4559
  if (active === 0) wake.resolve();
4560
+ // oxlint-disable-next-line typescript/return-await -- StackTrace.test.ts measures this direct await edge in the worker topology.
4505
4561
  return await parked;
4506
4562
  };
4507
4563
 
@@ -4609,8 +4665,6 @@ export const yieldNow: Task<void> = async (run) => {
4609
4665
  return ok();
4610
4666
  };
4611
4667
 
4612
- // Abortability
4613
-
4614
4668
  /**
4615
4669
  * Waits until the current {@link Run} aborts, then rejects with its
4616
4670
  * {@link AbortError}.
@@ -4685,8 +4739,8 @@ export const waitForAbort: Task<never> = async (run) => {
4685
4739
  * execution. The daemon Task continues under root Run ownership until it
4686
4740
  * settles, observes abort, or the root Run is disposed.
4687
4741
  *
4688
- * This is not a replacement for direct {@link AbortSignal} support in operations
4689
- * that can observe abort, such as {@link fetch}, timers that accept a signal, or
4742
+ * This is not a replacement for direct {@link AbortSignal} support in APIs that
4743
+ * can observe abort, such as {@link fetch}, timers that accept a signal, or
4690
4744
  * callback APIs that accept a signal. Use it as an escape hatch for Tasks that
4691
4745
  * ignore abort when an abort request must stop waiting immediately.
4692
4746
  *
@@ -4747,7 +4801,7 @@ export const waitForAbort: Task<never> = async (run) => {
4747
4801
  * expect(finished).toBe(true);
4748
4802
  * ```
4749
4803
  *
4750
- * Promise-producing operations should start inside the Task, not before it.
4804
+ * Promise-returning functions should be called inside the Task, not before it.
4751
4805
  *
4752
4806
  * ```ts
4753
4807
  * import { createRun, ok, type Result, type Task } from "@evolu/common";
@@ -4856,7 +4910,7 @@ export const unabortable = /*#__PURE__*/ withTaskMeta({
4856
4910
  *
4857
4911
  * An abort request before the mask Task starts prevents entering the mask. Once
4858
4912
  * the body starts, plain child Tasks inherit the mask, so acquire and release
4859
- * can run after abort. Put release operations directly in the original mask's
4913
+ * can run after abort. Start release Tasks directly in the original mask's
4860
4914
  * `finally`; do not wrap release in a nested `unabortableMask`, which is a new
4861
4915
  * critical-section entry and may not start after abort.
4862
4916
  *
@@ -4938,7 +4992,7 @@ export const unabortableMask = <T, E, D = unknown>(
4938
4992
  runInternal.abortMask > abortableMask,
4939
4993
  "unabortableMask requires a masked Run; use run(task), not a direct call",
4940
4994
  );
4941
- const restoreToken = Symbol() as RestoreToken;
4995
+ const restoreToken = Symbol("restore") as RestoreToken;
4942
4996
 
4943
4997
  // The token is local to this Task Run; descendant Runs inherit the token
4944
4998
  // set so helpers can receive restore while the mask Task is alive.
@@ -4973,7 +5027,7 @@ export const unabortableMask = <T, E, D = unknown>(
4973
5027
  * Prefer native `using`, `await using`, or {@link AsyncDisposableStack} for
4974
5028
  * owned values that implement {@link Disposable} or {@link AsyncDisposable}. Use
4975
5029
  * `acquireUseRelease` when acquisition must be balanced with a separate release
4976
- * operation, such as unlocking, returning a pooled value, releasing a lease, or
5030
+ * step, such as unlocking, returning a pooled value, releasing a lease, or
4977
5031
  * logging out of a session.
4978
5032
  *
4979
5033
  * ### Example
@@ -5045,7 +5099,7 @@ export const acquireUseRelease = <
5045
5099
  if (!resourceResult.ok) return resourceResult;
5046
5100
 
5047
5101
  try {
5048
- // eslint-disable-next-line react-hooks/rules-of-hooks -- `use` is an acquireUseRelease callback, not a React Hook.
5102
+ // oxlint-disable-next-line react/rules-of-hooks -- `use` is an acquireUseRelease callback, not a React Hook.
5049
5103
  return await run(restore(use(resourceResult.value)));
5050
5104
  } finally {
5051
5105
  await run.ok(release(resourceResult.value));
@@ -5053,8 +5107,6 @@ export const acquireUseRelease = <
5053
5107
  },
5054
5108
  );
5055
5109
 
5056
- // Concurrency primitives
5057
-
5058
5110
  /**
5059
5111
  * A one-shot value resolved from outside the waiting {@link Task}.
5060
5112
  *
@@ -5230,7 +5282,6 @@ export const createGate = ({
5230
5282
 
5231
5283
  return {
5232
5284
  // Direct same-Run delegation is intentional so wait observes the current deferred.
5233
- // eslint-disable-next-line evolu/no-direct-task-call
5234
5285
  wait: (run) => deferred.task(run),
5235
5286
  open: () => {
5236
5287
  if (isOpen) return false;
@@ -5259,8 +5310,8 @@ export const createGate = ({
5259
5310
  *
5260
5311
  * Use {@link Semaphore.withPermit} or {@link Semaphore.withPermits} to acquire
5261
5312
  * permits for one Task and release them when it settles. Use
5262
- * {@link Semaphore.take} when permits must be held across multiple operations;
5263
- * the returned {@link SemaphorePermit} owns release and is disposable.
5313
+ * {@link Semaphore.take} when one permit must cover several child Tasks; the
5314
+ * returned {@link SemaphorePermit} owns release and is disposable.
5264
5315
  *
5265
5316
  * Requests are not capped by the current permit count because
5266
5317
  * {@link Semaphore.resize} can increase it later.
@@ -5362,8 +5413,8 @@ export interface SemaphorePermit extends Disposable {
5362
5413
  * semaphore does not reorder requests to maximize utilization.
5363
5414
  *
5364
5415
  * Use `"fifo"` when fairness and predictable progress matter, such as tenant
5365
- * sync, API quota, or database operations where large requests must not be
5366
- * starved by a stream of smaller requests.
5416
+ * sync, API quota, or database pools where large requests must not be starved
5417
+ * by a stream of smaller requests.
5367
5418
  *
5368
5419
  * Use `"greedy"` when permits represent a shared budget and smaller or
5369
5420
  * latency-sensitive requests should proceed around larger queued requests. For
@@ -5666,8 +5717,8 @@ export const createMutex = (): Mutex => {
5666
5717
  * resources, making idle-key cleanup less predictable and making accidental key
5667
5718
  * retention easier.
5668
5719
  *
5669
- * Use Semaphore directly when callers need to hold permits across multiple
5670
- * operations or resize a permit pool. Use `SemaphoreByKey` when permit
5720
+ * Use Semaphore directly when callers need to hold permits while starting
5721
+ * several child Tasks or resize a permit pool. Use `SemaphoreByKey` when permit
5671
5722
  * ownership should be tied to one Task lifetime and idle keys can be forgotten
5672
5723
  * automatically.
5673
5724
  *
@@ -5847,9 +5898,9 @@ export function createMutexByKey<K, L = K>({
5847
5898
  /**
5848
5899
  * {@link Ref} protected by a {@link Mutex}.
5849
5900
  *
5850
- * `MutexRef` serializes reads, writes, and updates through an internal Mutex,
5851
- * so every operation observes one consistent value transition at a time. When
5852
- * an update fails or is aborted, the previous value is preserved.
5901
+ * `MutexRef` serializes reads, writes, and updates through an internal Mutex.
5902
+ * Reads see a stable value, while writes and updates commit one transition at a
5903
+ * time. When an update fails or is aborted, the previous value is preserved.
5853
5904
  *
5854
5905
  * `MutexRef` is non-reentrant. Updaters and modifiers run while holding the
5855
5906
  * internal Mutex, so calling another method on the same MutexRef from inside
@@ -5859,8 +5910,8 @@ export function createMutexByKey<K, L = K>({
5859
5910
  * read-modify-write. Plain Ref cannot express that — between a sync read and a
5860
5911
  * later write, a concurrent transition can interleave and get lost.
5861
5912
  *
5862
- * `MutexRef` operations are Tasks and incur normal {@link Run} lifecycle
5863
- * overhead. Use {@link Ref} instead for synchronous state transitions,
5913
+ * `MutexRef` reads, writes, and updates are Tasks and incur normal {@link Run}
5914
+ * lifecycle overhead. Use {@link Ref} instead for synchronous state transitions,
5864
5915
  * especially on allocation-sensitive hot paths.
5865
5916
  *
5866
5917
  * @group Concurrency primitives
@@ -5981,7 +6032,7 @@ export const createMutexRef = <T>(initialValue: T): MutexRef<T> => {
5981
6032
  // filtering, and pluggable log sinks.
5982
6033
  // - Tracing spans with names, timing, parent-child relationships, attributes,
5983
6034
  // error status, and helpers for annotating the current or child spans.
5984
- // - Metrics for counters, gauges, histograms, and operation durations.
6035
+ // - Metrics for counters, gauges, histograms, and Task execution durations.
5985
6036
  // - Resource metadata for service name, service version, deployment
5986
6037
  // environment, and user-provided attributes.
5987
6038
  // - Exporters for production telemetry backends, including OTLP-compatible
@@ -5994,5 +6045,5 @@ export const createMutexRef = <T>(initialValue: T): MutexRef<T> => {
5994
6045
  // - Run labels and structured annotations for rendering useful snapshot trees
5995
6046
  // instead of anonymous ids.
5996
6047
  // - Snapshot and trace views should preserve ownership boundaries, so reusable
5997
- // resources and long-lived operations appear as labeled subtrees instead of
5998
- // unrelated child operations.
6048
+ // resources and long-lived Runs appear as labeled subtrees instead of
6049
+ // unrelated child Runs.
package/src/Test.ts CHANGED
@@ -50,5 +50,6 @@ export const testCreateId = (): TestCreateId => {
50
50
  randomLib: testCreateRandomLib(),
51
51
  });
52
52
 
53
+ // oxlint-disable-next-line typescript/no-unnecessary-type-arguments -- Explicit never resolves createId's conditional brand-validation rest parameter; inference rejects the argument without it.
53
54
  return (() => createId<never>({ randomBytes })) as TestCreateId;
54
55
  };
package/src/Time.ts CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { assert } from "./Assert.ts";
8
8
  import type { Brand } from "./Brand.ts";
9
+ import { exhaustiveCheck } from "./Function.ts";
9
10
  import type { yieldNow } from "./Task.ts";
10
11
  import {
11
12
  brand,
@@ -239,6 +240,10 @@ export const testCreateTime = (options?: {
239
240
  case "microtask":
240
241
  queueMicrotask(incrementNow);
241
242
  break;
243
+ case undefined:
244
+ break;
245
+ default:
246
+ exhaustiveCheck(autoIncrement);
242
247
  }
243
248
  return result;
244
249
  };
@@ -564,8 +569,10 @@ const durationUnits = {
564
569
  m: 60000,
565
570
  h: 3600000,
566
571
  d: 86400000,
567
- w: 604800000, // 7 days
568
- y: 31536000000, // 365 days
572
+ // 7 days
573
+ w: 604800000,
574
+ // 365 days
575
+ y: 31536000000,
569
576
  } as const;
570
577
 
571
578
  /**