@evolu/common 8.0.0-next.3 → 8.0.0-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Assert.d.ts +10 -5
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +10 -5
- package/dist/src/Error.d.ts +5 -3
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +16 -0
- package/dist/src/Identicon.js +4 -4
- package/dist/src/LockManager.d.ts +74 -0
- package/dist/src/LockManager.d.ts.map +1 -0
- package/dist/src/LockManager.js +103 -0
- package/dist/src/Microtask.d.ts.map +1 -1
- package/dist/src/Microtask.js +90 -26
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/RefCount.d.ts.map +1 -1
- package/dist/src/RefCount.js +153 -68
- package/dist/src/Resource.d.ts +6 -6
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +211 -162
- package/dist/src/Schedule.d.ts +5 -5
- package/dist/src/Schedule.d.ts.map +1 -1
- package/dist/src/Schedule.js +53 -44
- package/dist/src/Sqlite.d.ts +2 -0
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +66 -41
- package/dist/src/Task.d.ts +99 -123
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +273 -256
- package/dist/src/Test.d.ts +14 -15
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Time.d.ts +5 -0
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +5 -0
- package/dist/src/Type.d.ts +7 -0
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +7 -0
- package/dist/src/Types.d.ts +0 -8
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +7 -6
- package/dist/src/Worker.d.ts +42 -8
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +292 -112
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/local-first/Db.d.ts +5 -4
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +43 -37
- package/dist/src/local-first/Evolu.d.ts +3 -2
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +90 -80
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Shared.d.ts +65 -54
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +390 -376
- package/package.json +1 -1
- package/src/Array.ts +8 -8
- package/src/Assert.ts +11 -6
- package/src/Error.ts +5 -4
- package/src/Identicon.ts +4 -4
- package/src/LockManager.ts +181 -0
- package/src/Microtask.ts +17 -11
- package/src/Object.ts +3 -3
- package/src/RefCount.ts +25 -21
- package/src/Resource.ts +63 -56
- package/src/Schedule.ts +70 -48
- package/src/Set.ts +4 -4
- package/src/Sqlite.ts +43 -31
- package/src/Task.ts +392 -430
- package/src/Test.ts +14 -15
- package/src/Time.ts +7 -0
- package/src/Type.ts +9 -0
- package/src/Types.ts +0 -9
- package/src/WebSocket.ts +7 -6
- package/src/Worker.ts +195 -53
- package/src/index.ts +1 -0
- package/src/local-first/Db.ts +86 -61
- package/src/local-first/Evolu.ts +74 -68
- package/src/local-first/Owner.ts +1 -1
- package/src/local-first/Query.ts +1 -1
- package/src/local-first/Shared.ts +485 -476
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,
|
|
@@ -62,7 +61,6 @@ import {
|
|
|
62
61
|
type CallbackWithTeardown,
|
|
63
62
|
type Int1To100,
|
|
64
63
|
type Mutable,
|
|
65
|
-
type NewKeys,
|
|
66
64
|
type Predicate,
|
|
67
65
|
} from "./Types.js";
|
|
68
66
|
|
|
@@ -271,12 +269,22 @@ import {
|
|
|
271
269
|
* await run(fetchUser(123));
|
|
272
270
|
* ```
|
|
273
271
|
*
|
|
274
|
-
*
|
|
272
|
+
* Provide runtime-created dependencies to a single Task with `run(task, deps)`:
|
|
275
273
|
*
|
|
276
|
-
*
|
|
274
|
+
* ```ts
|
|
275
|
+
* const dbResult = await run(createDb(config));
|
|
276
|
+
* if (!dbResult.ok) return dbResult;
|
|
277
|
+
*
|
|
278
|
+
* const userResult = await run(fetchUser(123), { db: dbResult.value });
|
|
279
|
+
* ```
|
|
277
280
|
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
281
|
+
* For reusable resources that own async work, use `run.create(deps)` and
|
|
282
|
+
* dispose the created Run with the resource.
|
|
283
|
+
*
|
|
284
|
+
* ### Default dependencies
|
|
285
|
+
*
|
|
286
|
+
* {@link createRun} provides default {@link RunDefaultDeps} available to all
|
|
287
|
+
* Tasks without declaring `D`:
|
|
280
288
|
*
|
|
281
289
|
* - {@link Console} — logging with hierarchical context via `child()`
|
|
282
290
|
* - {@link Time} — current time
|
|
@@ -313,7 +321,7 @@ import {
|
|
|
313
321
|
* ```
|
|
314
322
|
*
|
|
315
323
|
* For testing, use {@link testCreateRun} to get deterministic, controllable
|
|
316
|
-
* implementations of all
|
|
324
|
+
* implementations of all RunDefaultDeps.
|
|
317
325
|
*
|
|
318
326
|
* ## Resource management
|
|
319
327
|
*
|
|
@@ -327,13 +335,13 @@ import {
|
|
|
327
335
|
* ### Example
|
|
328
336
|
*
|
|
329
337
|
* ```ts
|
|
330
|
-
* await using
|
|
338
|
+
* await using disposer = new AsyncDisposableStack();
|
|
331
339
|
*
|
|
332
340
|
* const fooResult = await run(createFoo());
|
|
333
341
|
* if (!fooResult.ok) return fooResult;
|
|
334
|
-
* const foo =
|
|
342
|
+
* const foo = disposer.use(fooResult.value);
|
|
335
343
|
*
|
|
336
|
-
*
|
|
344
|
+
* disposer.defer(async () => {
|
|
337
345
|
* await foo.close();
|
|
338
346
|
* });
|
|
339
347
|
* stack.adopt(session, async (session) => {
|
|
@@ -417,6 +425,35 @@ import {
|
|
|
417
425
|
* use {@link assertNotAborted} to crash immediately instead of threading the
|
|
418
426
|
* impossible case through domain logic.
|
|
419
427
|
*
|
|
428
|
+
* ### What should code do with `AbortError`?
|
|
429
|
+
*
|
|
430
|
+
* Treat `AbortError` as structured-concurrency control flow. It usually means
|
|
431
|
+
* the current work should stop because its owning {@link Run} or {@link Fiber} is
|
|
432
|
+
* stopping.
|
|
433
|
+
*
|
|
434
|
+
* In ordinary Task code, return it unchanged:
|
|
435
|
+
*
|
|
436
|
+
* ```ts
|
|
437
|
+
* const result = await run(loadUser(id));
|
|
438
|
+
* if (!result.ok) return result;
|
|
439
|
+
* ```
|
|
440
|
+
*
|
|
441
|
+
* In fire-and-forget or cleanup code where nobody observes the result and the
|
|
442
|
+
* runtime already owns cleanup, returning early is enough:
|
|
443
|
+
*
|
|
444
|
+
* ```ts
|
|
445
|
+
* const result = await run(waitUntilClosed());
|
|
446
|
+
* if (!result.ok) return;
|
|
447
|
+
* ```
|
|
448
|
+
*
|
|
449
|
+
* Do not use {@link Run#orThrow} just to avoid thinking about abort. It turns
|
|
450
|
+
* normal cancellation into an exception. Use `orThrow` at composition
|
|
451
|
+
* boundaries where any error should fail the whole flow.
|
|
452
|
+
*
|
|
453
|
+
* If abort would violate a lifecycle invariant, make that invariant explicit:
|
|
454
|
+
* run the must-finish part with {@link unabortable}, then use
|
|
455
|
+
* {@link assertNotAborted} to fail fast if the Task could not even start.
|
|
456
|
+
*
|
|
420
457
|
* ### How do I type an anonymous Task callback?
|
|
421
458
|
*
|
|
422
459
|
* For one-off inline Tasks, put the type arguments on the {@link Run} call:
|
|
@@ -579,6 +616,17 @@ export interface Run<D = unknown> extends AsyncDisposable {
|
|
|
579
616
|
/** Runs a {@link Task} and returns a {@link Fiber} handle. */
|
|
580
617
|
<T, E>(task: Task<T, E, D>): Fiber<T, E, D>;
|
|
581
618
|
|
|
619
|
+
/**
|
|
620
|
+
* Runs a {@link Task} with custom dependencies.
|
|
621
|
+
*
|
|
622
|
+
* The provided dependencies replace the current custom dependency set for
|
|
623
|
+
* this Task. Default {@link RunDefaultDeps} are always available.
|
|
624
|
+
*/
|
|
625
|
+
<T, E, Deps>(
|
|
626
|
+
task: Task<T, E, Deps>,
|
|
627
|
+
deps: Deps,
|
|
628
|
+
): Fiber<T, E, RunDefaultDeps & Deps>;
|
|
629
|
+
|
|
582
630
|
/**
|
|
583
631
|
* Runs a {@link Task} and throws if the returned {@link Result} is an error.
|
|
584
632
|
*
|
|
@@ -602,13 +650,16 @@ export interface Run<D = unknown> extends AsyncDisposable {
|
|
|
602
650
|
*
|
|
603
651
|
* Throws: `Error` with the original Task error attached as `cause`.
|
|
604
652
|
*/
|
|
605
|
-
readonly orThrow:
|
|
653
|
+
readonly orThrow: {
|
|
654
|
+
<T, E>(task: Task<T, E, D>): Promise<T>;
|
|
655
|
+
<T, E, Deps>(task: Task<T, E, Deps>, deps: Deps): Promise<T>;
|
|
656
|
+
};
|
|
606
657
|
|
|
607
658
|
/** Unique {@link Id} for this Run. */
|
|
608
659
|
readonly id: Id;
|
|
609
660
|
|
|
610
661
|
/** The parent {@link Run}, if this Run was created as a child. */
|
|
611
|
-
readonly parent: Run
|
|
662
|
+
readonly parent: Run | null;
|
|
612
663
|
|
|
613
664
|
/** @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal */
|
|
614
665
|
readonly signal: AbortSignal;
|
|
@@ -642,7 +693,7 @@ export interface Run<D = unknown> extends AsyncDisposable {
|
|
|
642
693
|
readonly getState: () => RunState;
|
|
643
694
|
|
|
644
695
|
/** Returns the current child {@link Fiber}s. */
|
|
645
|
-
readonly getChildren: () => ReadonlySet<
|
|
696
|
+
readonly getChildren: () => ReadonlySet<AnyFiber>;
|
|
646
697
|
|
|
647
698
|
/**
|
|
648
699
|
* Creates a memoized {@link RunSnapshot} of this Run.
|
|
@@ -709,13 +760,15 @@ export interface Run<D = unknown> extends AsyncDisposable {
|
|
|
709
760
|
*
|
|
710
761
|
* For a long-lived reusable {@link Run}, use {@link Run.create}.
|
|
711
762
|
*/
|
|
712
|
-
readonly daemon: Run
|
|
763
|
+
readonly daemon: Run;
|
|
713
764
|
|
|
714
765
|
/**
|
|
715
766
|
* Creates a {@link Run} from this Run.
|
|
716
767
|
*
|
|
717
768
|
* Like {@link createRun}, the returned Run is daemon: it stays running until
|
|
718
|
-
* disposed.
|
|
769
|
+
* disposed. Without arguments, it shares this Run's dependencies. With deps,
|
|
770
|
+
* it uses those custom dependencies plus preserved default
|
|
771
|
+
* {@link RunDefaultDeps}.
|
|
719
772
|
*
|
|
720
773
|
* Use this for long-lived disposable resources that need to own async work.
|
|
721
774
|
* The resource creates one internal Run with `run.create()` and uses that Run
|
|
@@ -728,77 +781,19 @@ export interface Run<D = unknown> extends AsyncDisposable {
|
|
|
728
781
|
*
|
|
729
782
|
* To run a single Task as daemon, use {@link Run.daemon}.
|
|
730
783
|
*/
|
|
731
|
-
readonly create:
|
|
784
|
+
readonly create: {
|
|
785
|
+
(): Run<D>;
|
|
786
|
+
<Deps>(deps: Deps): Run<RunDefaultDeps & Deps>;
|
|
787
|
+
};
|
|
732
788
|
|
|
733
|
-
/** Returns
|
|
734
|
-
readonly deps:
|
|
789
|
+
/** Returns this Run's dependencies. */
|
|
790
|
+
readonly deps: RunDefaultDeps & D;
|
|
735
791
|
|
|
736
792
|
/**
|
|
737
793
|
* @see {@link Concurrency}
|
|
738
794
|
* @see {@link concurrently}
|
|
739
795
|
*/
|
|
740
796
|
readonly concurrency: Concurrency;
|
|
741
|
-
|
|
742
|
-
/**
|
|
743
|
-
* Adds additional dependencies to this Run and returns it.
|
|
744
|
-
*
|
|
745
|
-
* Use for runtime-created dependencies — dependencies that cannot be created
|
|
746
|
-
* in the composition root (e.g., app start).
|
|
747
|
-
*
|
|
748
|
-
* ### Example
|
|
749
|
-
*
|
|
750
|
-
* ```ts
|
|
751
|
-
* // One-shot
|
|
752
|
-
* await run.addDeps({ db })(getUser(123));
|
|
753
|
-
*
|
|
754
|
-
* // Multiple deps at once
|
|
755
|
-
* await run.addDeps({ db, cache })(task);
|
|
756
|
-
*
|
|
757
|
-
* // Reusable — config comes from outside (message, file, etc.)
|
|
758
|
-
* type DbWorkerDeps = DbDep; // or DbDep & CacheDep & ...
|
|
759
|
-
*
|
|
760
|
-
* const init =
|
|
761
|
-
* (config: Config): Task<void, InitError, CreateDbDep> =>
|
|
762
|
-
* async (run) => {
|
|
763
|
-
* const { createDb } = run.deps;
|
|
764
|
-
* await using stack = new AsyncDisposableStack();
|
|
765
|
-
*
|
|
766
|
-
* const db = stack.use(await run.orThrow(startApp()));
|
|
767
|
-
* if (!db.ok) return db;
|
|
768
|
-
*
|
|
769
|
-
* const runWithDb = run.addDeps({ db: db.value });
|
|
770
|
-
*
|
|
771
|
-
* await runWithDb(getUser(123));
|
|
772
|
-
* await runWithDb(insertUser(user));
|
|
773
|
-
* return ok();
|
|
774
|
-
* };
|
|
775
|
-
* ```
|
|
776
|
-
*
|
|
777
|
-
* ## FAQ
|
|
778
|
-
*
|
|
779
|
-
* ### How does it work?
|
|
780
|
-
*
|
|
781
|
-
* This is the whole implementation:
|
|
782
|
-
*
|
|
783
|
-
* ```ts
|
|
784
|
-
* run.addDeps = <E extends NewKeys<E, D>>(newDeps: E): Run<D & E> => {
|
|
785
|
-
* depsRef.modify((currentDeps) => {
|
|
786
|
-
* const duplicate = Object.keys(newDeps).find(
|
|
787
|
-
* (k) => k in currentDeps,
|
|
788
|
-
* );
|
|
789
|
-
* assert(!duplicate, `Dependency '${duplicate}' already added.`);
|
|
790
|
-
* return [undefined, { ...currentDeps, ...newDeps }];
|
|
791
|
-
* });
|
|
792
|
-
* return self as unknown as Run<D & E>;
|
|
793
|
-
* };
|
|
794
|
-
* ```
|
|
795
|
-
*
|
|
796
|
-
* Dependencies are stored in a shared {@link Ref}, so `addDeps` propagates to
|
|
797
|
-
* all runs. The runtime assertion ensures dependencies are created once —
|
|
798
|
-
* automatic deduplication would mask poor design (dependencies should have a
|
|
799
|
-
* single, clear point of creation).
|
|
800
|
-
*/
|
|
801
|
-
readonly addDeps: <E extends NewKeys<E, D>>(extraDeps: E) => Run<D & E>;
|
|
802
797
|
}
|
|
803
798
|
|
|
804
799
|
/**
|
|
@@ -897,12 +892,19 @@ export interface Fiber<T = unknown, E = unknown, D = unknown>
|
|
|
897
892
|
getState(): RunState<T, E>;
|
|
898
893
|
}
|
|
899
894
|
|
|
895
|
+
/**
|
|
896
|
+
* Shorthand for a {@link Fiber} with `any` type parameters.
|
|
897
|
+
*
|
|
898
|
+
* @group Type utilities
|
|
899
|
+
*/
|
|
900
|
+
export type AnyFiber = Fiber<any, any, any>;
|
|
901
|
+
|
|
900
902
|
/**
|
|
901
903
|
* Extracts the value type from a {@link Fiber}.
|
|
902
904
|
*
|
|
903
905
|
* @group Type utilities
|
|
904
906
|
*/
|
|
905
|
-
export type InferFiberOk<F extends
|
|
907
|
+
export type InferFiberOk<F extends AnyFiber> =
|
|
906
908
|
F extends Fiber<infer T, any, any> ? T : never;
|
|
907
909
|
|
|
908
910
|
/**
|
|
@@ -910,7 +912,7 @@ export type InferFiberOk<F extends Fiber<any, any, any>> =
|
|
|
910
912
|
*
|
|
911
913
|
* @group Type utilities
|
|
912
914
|
*/
|
|
913
|
-
export type InferFiberErr<F extends
|
|
915
|
+
export type InferFiberErr<F extends AnyFiber> =
|
|
914
916
|
F extends Fiber<any, infer E, any> ? E : never;
|
|
915
917
|
|
|
916
918
|
/**
|
|
@@ -918,7 +920,7 @@ export type InferFiberErr<F extends Fiber<any, any, any>> =
|
|
|
918
920
|
*
|
|
919
921
|
* @group Type utilities
|
|
920
922
|
*/
|
|
921
|
-
export type InferFiberDeps<F extends
|
|
923
|
+
export type InferFiberDeps<F extends AnyFiber> =
|
|
922
924
|
F extends Fiber<any, any, infer D> ? D : never;
|
|
923
925
|
|
|
924
926
|
/**
|
|
@@ -1024,7 +1026,7 @@ export type RunSnapshotState = typeof RunSnapshotState.Type;
|
|
|
1024
1026
|
* @see {@link Run.snapshot}
|
|
1025
1027
|
*/
|
|
1026
1028
|
export interface RunSnapshot {
|
|
1027
|
-
/** The
|
|
1029
|
+
/** The Run id this snapshot represents. */
|
|
1028
1030
|
readonly id: Id;
|
|
1029
1031
|
|
|
1030
1032
|
/** The current lifecycle state. */
|
|
@@ -1084,7 +1086,7 @@ export interface RunConfigDep {
|
|
|
1084
1086
|
}
|
|
1085
1087
|
|
|
1086
1088
|
/** Default deps provided by {@link createRun}. */
|
|
1087
|
-
export type
|
|
1089
|
+
export type RunDefaultDeps = ConsoleDep &
|
|
1088
1090
|
RandomBytesDep &
|
|
1089
1091
|
RandomDep &
|
|
1090
1092
|
TimeDep &
|
|
@@ -1094,7 +1096,7 @@ export type RunDeps = ConsoleDep &
|
|
|
1094
1096
|
// Partial<TracerConfigDep> & // TODO:
|
|
1095
1097
|
// Partial<TracerDep>; // TODO:
|
|
1096
1098
|
|
|
1097
|
-
const
|
|
1099
|
+
const runDefaultDeps: RunDefaultDeps = {
|
|
1098
1100
|
console: createConsole(),
|
|
1099
1101
|
randomBytes: createRandomBytes(),
|
|
1100
1102
|
random: createRandom(),
|
|
@@ -1126,7 +1128,7 @@ export interface CreateRun<BaseDeps> {
|
|
|
1126
1128
|
* Node.js `uncaughtException`, `unhandledRejection`, and graceful shutdown
|
|
1127
1129
|
* handling, and `@evolu/react-native` adds React Native global error handling.
|
|
1128
1130
|
*
|
|
1129
|
-
* {@link
|
|
1131
|
+
* {@link RunDefaultDeps} provides default dependencies:
|
|
1130
1132
|
*
|
|
1131
1133
|
* - {@link Time}
|
|
1132
1134
|
* - {@link Console}
|
|
@@ -1164,7 +1166,7 @@ export interface CreateRun<BaseDeps> {
|
|
|
1164
1166
|
* };
|
|
1165
1167
|
*
|
|
1166
1168
|
* // Composition root: create a Run with custom deps
|
|
1167
|
-
* type AppDeps =
|
|
1169
|
+
* type AppDeps = RunDefaultDeps & ConfigDep;
|
|
1168
1170
|
*
|
|
1169
1171
|
* const appDeps: AppDeps = {
|
|
1170
1172
|
* ...testCreateDeps(), // or spread individual deps
|
|
@@ -1177,24 +1179,24 @@ export interface CreateRun<BaseDeps> {
|
|
|
1177
1179
|
* const result = await run(fetchUser("123"));
|
|
1178
1180
|
*
|
|
1179
1181
|
* // TypeScript catches missing deps at compile time:
|
|
1180
|
-
* // await using run2 = createRun(); // Run<
|
|
1182
|
+
* // await using run2 = createRun(); // Run<RunDefaultDeps>
|
|
1181
1183
|
* // run2(fetchUser("123")); // Error: Property 'config' is missing
|
|
1182
1184
|
* ```
|
|
1183
1185
|
*
|
|
1184
1186
|
* @group Creating Run
|
|
1185
1187
|
*/
|
|
1186
|
-
export const createRun: CreateRun<
|
|
1188
|
+
export const createRun: CreateRun<RunDefaultDeps> = <D>(
|
|
1187
1189
|
deps?: D,
|
|
1188
|
-
): Run<
|
|
1189
|
-
|
|
1190
|
-
return createRunInternal(createRef(mergedDeps))();
|
|
1191
|
-
};
|
|
1190
|
+
): Run<RunDefaultDeps & D> =>
|
|
1191
|
+
createRunInternal({ ...runDefaultDeps, ...deps } as RunDefaultDeps & D);
|
|
1192
1192
|
|
|
1193
1193
|
/** Internal Run properties, hidden from public API via TypeScript types. */
|
|
1194
|
-
interface RunInternal<
|
|
1194
|
+
interface RunInternal<
|
|
1195
|
+
D extends RunDefaultDeps = RunDefaultDeps,
|
|
1196
|
+
> extends Run<D> {
|
|
1195
1197
|
ownTaskSettled: PromiseWithResolvers<void> | null;
|
|
1196
1198
|
|
|
1197
|
-
readonly requestAbort: (
|
|
1199
|
+
readonly requestAbort: (abortError: AbortError) => void;
|
|
1198
1200
|
readonly requestSignal: AbortSignal;
|
|
1199
1201
|
|
|
1200
1202
|
/**
|
|
@@ -1209,227 +1211,233 @@ interface RunInternal<D extends RunDeps = RunDeps> extends Run<D> {
|
|
|
1209
1211
|
readonly handleTaskSettled: () => void;
|
|
1210
1212
|
}
|
|
1211
1213
|
|
|
1212
|
-
const createRunInternal =
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
}
|
|
1214
|
+
const createRunInternal = <D extends RunDefaultDeps>(
|
|
1215
|
+
deps: D,
|
|
1216
|
+
parent?: RunInternal,
|
|
1217
|
+
daemon?: RunInternal,
|
|
1218
|
+
abortBehavior?: AbortBehavior,
|
|
1219
|
+
concurrencyBehavior?: Concurrency,
|
|
1220
|
+
): RunInternal<D> => {
|
|
1221
|
+
const parentMask = parent?.abortMask ?? isAbortable;
|
|
1222
|
+
|
|
1223
|
+
let abortMask: AbortMask;
|
|
1224
|
+
switch (abortBehavior) {
|
|
1225
|
+
case undefined:
|
|
1226
|
+
abortMask = parentMask;
|
|
1227
|
+
break;
|
|
1228
|
+
case "unabortable":
|
|
1229
|
+
abortMask = increment(parentMask) as AbortMask;
|
|
1230
|
+
break;
|
|
1231
|
+
default:
|
|
1232
|
+
assert(
|
|
1233
|
+
abortBehavior <= parentMask,
|
|
1234
|
+
"restore used outside its unabortableMask",
|
|
1235
|
+
);
|
|
1236
|
+
abortMask = abortBehavior;
|
|
1237
|
+
}
|
|
1237
1238
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1239
|
+
const requestController = new AbortController();
|
|
1240
|
+
const signalController = new AbortController();
|
|
1240
1241
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1242
|
+
let state: RunState = running;
|
|
1243
|
+
let result: UnknownResult | undefined;
|
|
1244
|
+
let outcome: UnknownResult | undefined;
|
|
1245
|
+
let children: ReadonlySet<AnyFiber> = emptySet;
|
|
1245
1246
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
};
|
|
1247
|
+
const requestAbort = (abortError: AbortError) => {
|
|
1248
|
+
if (abortMask === isAbortable) signalController.abort(abortError);
|
|
1249
|
+
requestController.abort(abortError);
|
|
1250
|
+
};
|
|
1251
1251
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1252
|
+
if (parent) {
|
|
1253
|
+
subscribeToAbort(
|
|
1254
|
+
parent.requestSignal,
|
|
1255
|
+
() => requestAbort(parent.requestSignal.reason as AbortError),
|
|
1256
|
+
{ signal: requestController.signal },
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
1259
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
};
|
|
1260
|
+
const emitEvent = (data: RunEventData) => {
|
|
1261
|
+
if (!deps.runConfig?.eventsEnabled.get()) return;
|
|
1262
|
+
const e: RunEvent = { id: self.id, timestamp: deps.time.now(), data };
|
|
1263
|
+
for (let node: Run | null = self; node; node = node.parent) {
|
|
1264
|
+
node.onEvent?.(e);
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1268
1267
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1268
|
+
const run = (task: AnyTask, taskDeps?: unknown): AnyFiber => {
|
|
1269
|
+
const childRun = createRunInternal(
|
|
1270
|
+
taskDeps === undefined
|
|
1271
|
+
? deps
|
|
1272
|
+
: {
|
|
1273
|
+
console: deps.console,
|
|
1274
|
+
randomBytes: deps.randomBytes,
|
|
1275
|
+
random: deps.random,
|
|
1276
|
+
time: deps.time,
|
|
1277
|
+
...(deps.runConfig && {
|
|
1278
|
+
runConfig: deps.runConfig,
|
|
1279
|
+
}),
|
|
1280
|
+
...taskDeps,
|
|
1281
|
+
},
|
|
1282
|
+
self,
|
|
1283
|
+
daemon ?? self,
|
|
1284
|
+
getAbortBehavior(task),
|
|
1285
|
+
getConcurrencyBehavior(task),
|
|
1286
|
+
);
|
|
1276
1287
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1288
|
+
if (state !== running) {
|
|
1289
|
+
childRun.requestAbort(runStoppedAbortError);
|
|
1290
|
+
task = () => err(runStoppedAbortError);
|
|
1291
|
+
} else if (
|
|
1292
|
+
signalController.signal.aborted &&
|
|
1293
|
+
childRun.abortMask === isAbortable
|
|
1294
|
+
) {
|
|
1295
|
+
const abortError = signalController.signal.reason as AbortError;
|
|
1296
|
+
childRun.requestAbort(abortError);
|
|
1297
|
+
task = () => err(abortError);
|
|
1298
|
+
}
|
|
1287
1299
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
},
|
|
1302
|
-
getState: () => childRun.getState() as RunState<T, E>,
|
|
1303
|
-
[Symbol.dispose]: () => {
|
|
1304
|
-
childFiber.abort();
|
|
1305
|
-
},
|
|
1300
|
+
const childFiber: Fiber = Object.assign(
|
|
1301
|
+
Promise.try(task, childRun)
|
|
1302
|
+
.then(childRun.handleTaskFulfilled)
|
|
1303
|
+
.finally(childRun.handleTaskSettled)
|
|
1304
|
+
.finally(childRun[Symbol.asyncDispose])
|
|
1305
|
+
.finally(() => {
|
|
1306
|
+
children = deleteFromSet(children, childFiber);
|
|
1307
|
+
emitEvent({ type: "ChildRemoved", childId: childRun.id });
|
|
1308
|
+
}),
|
|
1309
|
+
{
|
|
1310
|
+
run: childRun,
|
|
1311
|
+
abort: (reason?: unknown): void => {
|
|
1312
|
+
childRun.requestAbort(createAbortError(reason));
|
|
1306
1313
|
},
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1314
|
+
getState: () => childRun.getState(),
|
|
1315
|
+
[Symbol.dispose]: () => {
|
|
1316
|
+
childFiber.abort();
|
|
1317
|
+
},
|
|
1318
|
+
},
|
|
1319
|
+
);
|
|
1311
1320
|
|
|
1312
|
-
|
|
1313
|
-
};
|
|
1321
|
+
children = addToSet(children, childFiber);
|
|
1322
|
+
emitEvent({ type: "ChildAdded", childId: childRun.id });
|
|
1314
1323
|
|
|
1315
|
-
|
|
1324
|
+
return childFiber;
|
|
1325
|
+
};
|
|
1316
1326
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
const id = createId(depsRef.get());
|
|
1320
|
-
|
|
1321
|
-
let snapshot: RunSnapshot | null = null;
|
|
1322
|
-
let disposingPromise: Promise<void> | null = null;
|
|
1323
|
-
|
|
1324
|
-
run.orThrow = async (task) => getOrThrow(await self(task));
|
|
1325
|
-
run.id = id;
|
|
1326
|
-
run.parent = parent ?? null;
|
|
1327
|
-
|
|
1328
|
-
run.signal = signalController.signal;
|
|
1329
|
-
run.abortMask = abortMask;
|
|
1330
|
-
run.onAbort = (callback) => {
|
|
1331
|
-
if (abortMask !== isAbortable) return;
|
|
1332
|
-
subscribeToAbort(
|
|
1333
|
-
signalController.signal,
|
|
1334
|
-
() => callback((signalController.signal.reason as AbortError).reason),
|
|
1335
|
-
{ once: true, signal: requestController.signal },
|
|
1336
|
-
);
|
|
1337
|
-
};
|
|
1338
|
-
run.getState = () => state;
|
|
1339
|
-
run.getChildren = () => children;
|
|
1327
|
+
let snapshot: RunSnapshot | null = null;
|
|
1328
|
+
let disposingPromise: Promise<void> | null = null;
|
|
1340
1329
|
|
|
1341
|
-
|
|
1342
|
-
const childSnapshots = Array.from(children).map((fiber) =>
|
|
1343
|
-
fiber.run.snapshot(),
|
|
1344
|
-
);
|
|
1345
|
-
if (
|
|
1346
|
-
snapshot?.state !== state ||
|
|
1347
|
-
!eqArrayStrict(snapshot.children, childSnapshots)
|
|
1348
|
-
) {
|
|
1349
|
-
snapshot = {
|
|
1350
|
-
id,
|
|
1351
|
-
state: state as RunSnapshotState,
|
|
1352
|
-
children: childSnapshots,
|
|
1353
|
-
abortMask,
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
return snapshot;
|
|
1357
|
-
};
|
|
1330
|
+
const self = run as RunInternal<D>;
|
|
1358
1331
|
|
|
1359
|
-
|
|
1332
|
+
{
|
|
1333
|
+
const run: Mutable<RunInternal<D>> = self;
|
|
1334
|
+
const id = createId(deps);
|
|
1335
|
+
|
|
1336
|
+
function orThrow<T, E>(task: Task<T, E, D>): Promise<T>;
|
|
1337
|
+
function orThrow<T, E, Deps>(
|
|
1338
|
+
task: Task<T, E, Deps>,
|
|
1339
|
+
taskDeps: Deps,
|
|
1340
|
+
): Promise<T>;
|
|
1341
|
+
async function orThrow<T, E, Deps>(
|
|
1342
|
+
task: Task<T, E, D | Deps>,
|
|
1343
|
+
taskDeps?: Deps,
|
|
1344
|
+
): Promise<T> {
|
|
1345
|
+
const result =
|
|
1346
|
+
taskDeps === undefined ? await self(task) : await self(task, taskDeps);
|
|
1347
|
+
return getOrThrow(result);
|
|
1348
|
+
}
|
|
1360
1349
|
|
|
1361
|
-
|
|
1350
|
+
run.orThrow = orThrow;
|
|
1351
|
+
run.id = id;
|
|
1352
|
+
run.parent = parent ?? null;
|
|
1362
1353
|
|
|
1363
|
-
|
|
1354
|
+
run.signal = signalController.signal;
|
|
1355
|
+
run.abortMask = abortMask;
|
|
1356
|
+
run.onAbort = (callback) => {
|
|
1357
|
+
if (abortMask !== isAbortable) return;
|
|
1358
|
+
subscribeToAbort(
|
|
1359
|
+
signalController.signal,
|
|
1360
|
+
() => callback((signalController.signal.reason as AbortError).reason),
|
|
1361
|
+
{ once: true, signal: requestController.signal },
|
|
1362
|
+
);
|
|
1363
|
+
};
|
|
1364
|
+
run.getState = () => state;
|
|
1365
|
+
run.getChildren = () => children;
|
|
1364
1366
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
+
run.snapshot = () => {
|
|
1368
|
+
const childSnapshots = Array.from(children).map((fiber) =>
|
|
1369
|
+
fiber.run.snapshot(),
|
|
1370
|
+
);
|
|
1371
|
+
if (
|
|
1372
|
+
snapshot?.state !== state ||
|
|
1373
|
+
!eqArrayStrict(snapshot.children, childSnapshots)
|
|
1374
|
+
) {
|
|
1375
|
+
snapshot = {
|
|
1376
|
+
id,
|
|
1377
|
+
state,
|
|
1378
|
+
children: childSnapshots,
|
|
1379
|
+
abortMask,
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
return snapshot;
|
|
1383
|
+
};
|
|
1367
1384
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1385
|
+
run.onEvent = undefined;
|
|
1386
|
+
run.daemon = daemon ?? self;
|
|
1387
|
+
run.create = (runDeps: {} = deps): Run<any> => {
|
|
1388
|
+
const task = createDeferred().task;
|
|
1389
|
+
return run.daemon(task, runDeps).run;
|
|
1390
|
+
};
|
|
1391
|
+
run.deps = deps;
|
|
1392
|
+
run.concurrency =
|
|
1393
|
+
concurrencyBehavior ?? parent?.concurrency ?? defaultConcurrency;
|
|
1394
|
+
run[Symbol.asyncDispose] = () => {
|
|
1395
|
+
if (disposingPromise) return disposingPromise;
|
|
1396
|
+
|
|
1397
|
+
state = { type: "Disposing" };
|
|
1398
|
+
emitEvent({ type: "StateChanged", state });
|
|
1399
|
+
requestAbort(runStoppedAbortError);
|
|
1400
|
+
|
|
1401
|
+
disposingPromise = Promise.allSettled(
|
|
1402
|
+
(self.ownTaskSettled
|
|
1403
|
+
? [self.ownTaskSettled.promise, ...children]
|
|
1404
|
+
: children) as Iterable<PromiseLike<unknown>>,
|
|
1405
|
+
)
|
|
1406
|
+
.then(lazyVoid)
|
|
1407
|
+
.finally(() => {
|
|
1408
|
+
/**
|
|
1409
|
+
* Root and daemon Runs have no own Task, so `run.handleTaskFulfilled`
|
|
1410
|
+
* never populates their terminal values. In that case disposal
|
|
1411
|
+
* publishes `ok()` for both `result` and `outcome`. Task-backed Runs
|
|
1412
|
+
* normally reach this point with both values already set.
|
|
1413
|
+
*/
|
|
1414
|
+
[result, outcome] = [result ?? ok(), outcome ?? ok()];
|
|
1415
|
+
state = { type: "Settled", result, outcome };
|
|
1416
|
+
emitEvent({ type: "StateChanged", state });
|
|
1378
1417
|
});
|
|
1379
|
-
return self as unknown as Run<D & E>;
|
|
1380
|
-
};
|
|
1381
|
-
|
|
1382
|
-
run[Symbol.asyncDispose] = () => {
|
|
1383
|
-
if (disposingPromise) return disposingPromise;
|
|
1384
|
-
|
|
1385
|
-
state = { type: "Disposing" };
|
|
1386
|
-
emitEvent({ type: "StateChanged", state });
|
|
1387
|
-
requestAbort(runStoppedAbortError);
|
|
1388
|
-
|
|
1389
|
-
disposingPromise = Promise.allSettled(
|
|
1390
|
-
(run.ownTaskSettled
|
|
1391
|
-
? [run.ownTaskSettled.promise, ...children]
|
|
1392
|
-
: children) as Iterable<PromiseLike<unknown>>,
|
|
1393
|
-
)
|
|
1394
|
-
.then(lazyVoid)
|
|
1395
|
-
.finally(() => {
|
|
1396
|
-
/**
|
|
1397
|
-
* Root and daemon Runs have no own Task, so
|
|
1398
|
-
* `run.handleTaskFulfilled` never populates their terminal values.
|
|
1399
|
-
* In that case disposal publishes `ok()` for both `result` and
|
|
1400
|
-
* `outcome`. Task-backed Runs normally reach this point with both
|
|
1401
|
-
* values already set.
|
|
1402
|
-
*/
|
|
1403
|
-
[result, outcome] = [result ?? ok(), outcome ?? ok()];
|
|
1404
|
-
state = { type: "Settled", result, outcome };
|
|
1405
|
-
emitEvent({ type: "StateChanged", state });
|
|
1406
|
-
});
|
|
1407
|
-
|
|
1408
|
-
return disposingPromise;
|
|
1409
|
-
};
|
|
1410
|
-
|
|
1411
|
-
// Internal
|
|
1412
|
-
run.ownTaskSettled = parent ? Promise.withResolvers<void>() : null;
|
|
1413
1418
|
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
run.handleTaskFulfilled = (taskOutcome) => {
|
|
1418
|
-
const taskResult = run.signal.aborted
|
|
1419
|
-
? (err(run.signal.reason as AbortError) as typeof taskOutcome)
|
|
1420
|
-
: taskOutcome;
|
|
1421
|
-
result = taskResult;
|
|
1422
|
-
outcome = taskOutcome;
|
|
1423
|
-
return taskResult;
|
|
1424
|
-
};
|
|
1419
|
+
return disposingPromise;
|
|
1420
|
+
};
|
|
1425
1421
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1422
|
+
// Internal
|
|
1423
|
+
run.ownTaskSettled = parent ? Promise.withResolvers<void>() : null;
|
|
1424
|
+
run.requestAbort = requestAbort;
|
|
1425
|
+
run.requestSignal = requestController.signal;
|
|
1426
|
+
run.handleTaskFulfilled = (taskOutcome) => {
|
|
1427
|
+
const taskResult = self.signal.aborted
|
|
1428
|
+
? err(self.signal.reason as AbortError)
|
|
1429
|
+
: taskOutcome;
|
|
1430
|
+
result = taskResult;
|
|
1431
|
+
outcome = taskOutcome;
|
|
1432
|
+
return taskResult;
|
|
1433
|
+
};
|
|
1434
|
+
run.handleTaskSettled = () => {
|
|
1435
|
+
self.ownTaskSettled?.resolve();
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1430
1438
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1439
|
+
return self;
|
|
1440
|
+
};
|
|
1433
1441
|
|
|
1434
1442
|
const running: RunState = { type: "Running" };
|
|
1435
1443
|
|
|
@@ -1486,16 +1494,19 @@ const abortBehavior =
|
|
|
1486
1494
|
/**
|
|
1487
1495
|
* Makes a {@link Task} unabortable.
|
|
1488
1496
|
*
|
|
1489
|
-
* Once started, an unabortable Task always completes
|
|
1490
|
-
*
|
|
1497
|
+
* Once started, an unabortable Task always completes. Abort requests are masked
|
|
1498
|
+
* while it runs, and `signal.aborted` remains `false` inside the Task.
|
|
1491
1499
|
*
|
|
1492
|
-
*
|
|
1500
|
+
* `unabortable` controls abort signal propagation; it does not force work to
|
|
1501
|
+
* start. If the parent {@link Run} is already disposing or settled, `run(task)`
|
|
1493
1502
|
* short-circuits before task execution and returns `err(AbortError)` with
|
|
1494
1503
|
* {@link runStoppedError} as reason. So `unabortable` means “do not interrupt
|
|
1495
1504
|
* this Task once it has started”, not “remove AbortError from its type”.
|
|
1496
1505
|
*
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1506
|
+
* Most callers should still propagate or ignore {@link AbortError} according to
|
|
1507
|
+
* ordinary structured-concurrency ownership. When abort would violate a
|
|
1508
|
+
* lifecycle invariant, await the unabortable Task and use
|
|
1509
|
+
* {@link assertNotAborted} to fail fast if it could not even start.
|
|
1499
1510
|
*
|
|
1500
1511
|
* ### Example
|
|
1501
1512
|
*
|
|
@@ -1754,7 +1765,7 @@ export const callback =
|
|
|
1754
1765
|
readonly ok: Callback<T>;
|
|
1755
1766
|
readonly err: Callback<E>;
|
|
1756
1767
|
readonly signal: AbortSignal;
|
|
1757
|
-
readonly deps:
|
|
1768
|
+
readonly deps: RunDefaultDeps;
|
|
1758
1769
|
}>,
|
|
1759
1770
|
): Task<T, E> =>
|
|
1760
1771
|
(run) =>
|
|
@@ -2024,10 +2035,7 @@ export const retry =
|
|
|
2024
2035
|
<T, E, D = unknown, Output = unknown>(
|
|
2025
2036
|
task: Task<T, E, D>,
|
|
2026
2037
|
schedule: Schedule<Output, E>,
|
|
2027
|
-
{
|
|
2028
|
-
retryable = lazyTrue as Predicate<E>,
|
|
2029
|
-
onRetry,
|
|
2030
|
-
}: RetryOptions<E, Output> = {},
|
|
2038
|
+
{ retryable = lazyTrue, onRetry }: RetryOptions<E, Output> = {},
|
|
2031
2039
|
): Task<T, RetryError<E>, D> =>
|
|
2032
2040
|
async (run) => {
|
|
2033
2041
|
const step = schedule(run.deps);
|
|
@@ -2144,10 +2152,7 @@ export const repeat =
|
|
|
2144
2152
|
<T, E, D = unknown, Output = unknown>(
|
|
2145
2153
|
task: Task<T, E, D>,
|
|
2146
2154
|
schedule: Schedule<Output, T>,
|
|
2147
|
-
{
|
|
2148
|
-
repeatable = lazyTrue as Predicate<T>,
|
|
2149
|
-
onRepeat,
|
|
2150
|
-
}: RepeatOptions<T, Output> = {},
|
|
2155
|
+
{ repeatable = lazyTrue, onRepeat }: RepeatOptions<T, Output> = {},
|
|
2151
2156
|
): Task<T, E, D> =>
|
|
2152
2157
|
async (run) => {
|
|
2153
2158
|
const step = schedule(run.deps);
|
|
@@ -2186,7 +2191,7 @@ export const repeat =
|
|
|
2186
2191
|
*
|
|
2187
2192
|
* Similar to
|
|
2188
2193
|
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers | Promise.withResolvers},
|
|
2189
|
-
* but integrated with {@link Task} and {@link Run}
|
|
2194
|
+
* but integrated with {@link Task} and {@link Run}.
|
|
2190
2195
|
*
|
|
2191
2196
|
* Use for bridging callback-based APIs or coordinating between Tasks.
|
|
2192
2197
|
*
|
|
@@ -2340,36 +2345,43 @@ export interface Gate<D = unknown> extends Disposable {
|
|
|
2340
2345
|
* @group Concurrency primitives
|
|
2341
2346
|
*/
|
|
2342
2347
|
export const createGate = <D = unknown>(): Gate<D> => {
|
|
2348
|
+
using disposer = new DisposableStack();
|
|
2343
2349
|
let isOpen = false;
|
|
2344
|
-
let disposed = false;
|
|
2345
2350
|
let deferred = createDeferred<void>();
|
|
2346
2351
|
|
|
2352
|
+
disposer.defer(() => {
|
|
2353
|
+
deferred[Symbol.dispose]();
|
|
2354
|
+
});
|
|
2355
|
+
|
|
2356
|
+
const disposables = disposer.move();
|
|
2357
|
+
|
|
2347
2358
|
return {
|
|
2348
2359
|
wait: (run) => {
|
|
2349
|
-
if (disposed) return err(deferredDisposedError);
|
|
2360
|
+
if (disposables.disposed) return err(deferredDisposedError);
|
|
2350
2361
|
if (isOpen) return ok();
|
|
2351
2362
|
return run(deferred.task);
|
|
2352
2363
|
},
|
|
2353
2364
|
|
|
2354
2365
|
open: () => {
|
|
2355
|
-
|
|
2366
|
+
assertNotDisposed(disposables);
|
|
2367
|
+
if (isOpen) return;
|
|
2356
2368
|
isOpen = true;
|
|
2357
2369
|
deferred.resolve(ok());
|
|
2358
2370
|
},
|
|
2359
2371
|
|
|
2360
2372
|
close: () => {
|
|
2361
|
-
|
|
2373
|
+
assertNotDisposed(disposables);
|
|
2374
|
+
if (!isOpen) return;
|
|
2362
2375
|
isOpen = false;
|
|
2363
2376
|
deferred = createDeferred<void>();
|
|
2364
2377
|
},
|
|
2365
2378
|
|
|
2366
|
-
isOpen: () =>
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
if (disposed) return;
|
|
2370
|
-
disposed = true;
|
|
2371
|
-
deferred[Symbol.dispose]();
|
|
2379
|
+
isOpen: () => {
|
|
2380
|
+
assertNotDisposed(disposables);
|
|
2381
|
+
return isOpen;
|
|
2372
2382
|
},
|
|
2383
|
+
|
|
2384
|
+
[Symbol.dispose]: () => disposables.dispose(),
|
|
2373
2385
|
};
|
|
2374
2386
|
};
|
|
2375
2387
|
|
|
@@ -2483,10 +2495,26 @@ export const createSemaphore = (permits: Concurrency): Semaphore => {
|
|
|
2483
2495
|
readonly resolve: Callback<Result<void, AbortError>>;
|
|
2484
2496
|
}
|
|
2485
2497
|
|
|
2498
|
+
using disposer = new DisposableStack();
|
|
2486
2499
|
const fibers = new Set<Fiber>();
|
|
2487
2500
|
const waiters: Array<Waiter> = [];
|
|
2488
2501
|
let taken = zeroNonNegativeInt;
|
|
2489
|
-
|
|
2502
|
+
|
|
2503
|
+
disposer.defer(() => {
|
|
2504
|
+
using disposer = new DisposableStack();
|
|
2505
|
+
for (const fiber of fibers) {
|
|
2506
|
+
disposer.adopt(fiber, (fiber) => {
|
|
2507
|
+
fiber.abort(semaphoreDisposedError);
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
for (const waiter of waiters) {
|
|
2512
|
+
waiter.resolve(err(semaphoreDisposedAbortError));
|
|
2513
|
+
}
|
|
2514
|
+
waiters.length = 0;
|
|
2515
|
+
});
|
|
2516
|
+
|
|
2517
|
+
const disposables = disposer.move();
|
|
2490
2518
|
|
|
2491
2519
|
const withPermits =
|
|
2492
2520
|
<T, E, D>(requestedPermits: Concurrency) =>
|
|
@@ -2499,7 +2527,7 @@ export const createSemaphore = (permits: Concurrency): Semaphore => {
|
|
|
2499
2527
|
"Requested permits must not exceed semaphore capacity.",
|
|
2500
2528
|
);
|
|
2501
2529
|
|
|
2502
|
-
if (disposed) return err(semaphoreDisposedAbortError);
|
|
2530
|
+
if (disposables.disposed) return err(semaphoreDisposedAbortError);
|
|
2503
2531
|
|
|
2504
2532
|
if (waiters.length > 0 || taken + requested > permits) {
|
|
2505
2533
|
const waiter = Promise.withResolvers<Result<void, AbortError>>();
|
|
@@ -2548,31 +2576,19 @@ export const createSemaphore = (permits: Concurrency): Semaphore => {
|
|
|
2548
2576
|
withPermit: <T, E, D>(task: Task<T, E, D>): Task<T, E, D> =>
|
|
2549
2577
|
withPermits<T, E, D>(1)(task),
|
|
2550
2578
|
|
|
2551
|
-
snapshot: () =>
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
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;
|
|
2579
|
+
snapshot: () => {
|
|
2580
|
+
assertNotDisposed(disposables);
|
|
2581
|
+
return {
|
|
2582
|
+
permits,
|
|
2583
|
+
taken,
|
|
2584
|
+
waiting: NonNegativeInt.orThrow(waiters.length),
|
|
2585
|
+
available: NonNegativeInt.orThrow(permits - taken),
|
|
2586
|
+
isIdle: taken === 0 && waiters.length === 0,
|
|
2587
|
+
disposed: disposables.disposed,
|
|
2588
|
+
};
|
|
2575
2589
|
},
|
|
2590
|
+
|
|
2591
|
+
[Symbol.dispose]: () => disposables.dispose(),
|
|
2576
2592
|
};
|
|
2577
2593
|
};
|
|
2578
2594
|
|
|
@@ -2659,16 +2675,29 @@ export function createSemaphoreByKey<K, L = K>(
|
|
|
2659
2675
|
permits: Concurrency,
|
|
2660
2676
|
{ lookup = identity as Lookup<K, L> }: CreateSemaphoreByKeyOptions<K, L> = {},
|
|
2661
2677
|
): SemaphoreByKey<K> {
|
|
2678
|
+
using disposer = new DisposableStack();
|
|
2662
2679
|
const semaphoresByKey = createLookupMap<K, Semaphore, L>({
|
|
2663
2680
|
lookup,
|
|
2664
2681
|
});
|
|
2665
|
-
|
|
2682
|
+
|
|
2683
|
+
disposer.defer(() => {
|
|
2684
|
+
using disposer = new DisposableStack();
|
|
2685
|
+
disposer.defer(() => {
|
|
2686
|
+
semaphoresByKey.clear();
|
|
2687
|
+
});
|
|
2688
|
+
|
|
2689
|
+
for (const semaphore of semaphoresByKey.values()) {
|
|
2690
|
+
disposer.use(semaphore);
|
|
2691
|
+
}
|
|
2692
|
+
});
|
|
2693
|
+
|
|
2694
|
+
const disposables = disposer.move();
|
|
2666
2695
|
|
|
2667
2696
|
const withPermits =
|
|
2668
2697
|
<T, E, D>(key: K, requestedPermits: Concurrency) =>
|
|
2669
2698
|
(task: Task<T, E, D>): Task<T, E, D> =>
|
|
2670
2699
|
async (run: Run<D>) => {
|
|
2671
|
-
if (disposed) return err(semaphoreDisposedAbortError);
|
|
2700
|
+
if (disposables.disposed) return err(semaphoreDisposedAbortError);
|
|
2672
2701
|
|
|
2673
2702
|
let semaphore = semaphoresByKey.get(key);
|
|
2674
2703
|
if (!semaphore) {
|
|
@@ -2678,6 +2707,8 @@ export function createSemaphoreByKey<K, L = K>(
|
|
|
2678
2707
|
|
|
2679
2708
|
using _ = {
|
|
2680
2709
|
[Symbol.dispose]: () => {
|
|
2710
|
+
if (semaphoresByKey.get(key) !== semaphore) return;
|
|
2711
|
+
|
|
2681
2712
|
const snapshot = semaphore.snapshot();
|
|
2682
2713
|
if (snapshot.isIdle) {
|
|
2683
2714
|
semaphoresByKey.delete(key);
|
|
@@ -2695,18 +2726,12 @@ export function createSemaphoreByKey<K, L = K>(
|
|
|
2695
2726
|
|
|
2696
2727
|
withPermits,
|
|
2697
2728
|
|
|
2698
|
-
snapshot: (key) =>
|
|
2699
|
-
|
|
2700
|
-
|
|
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();
|
|
2729
|
+
snapshot: (key) => {
|
|
2730
|
+
assertNotDisposed(disposables);
|
|
2731
|
+
return semaphoresByKey.get(key)?.snapshot() ?? null;
|
|
2709
2732
|
},
|
|
2733
|
+
|
|
2734
|
+
[Symbol.dispose]: () => disposables.dispose(),
|
|
2710
2735
|
};
|
|
2711
2736
|
}
|
|
2712
2737
|
|
|
@@ -2931,68 +2956,6 @@ export const createMutexRef = <T>(initialValue: T): MutexRef<T> => {
|
|
|
2931
2956
|
};
|
|
2932
2957
|
};
|
|
2933
2958
|
|
|
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
2959
|
/**
|
|
2997
2960
|
* Options for {@link all}, {@link allSettled}, {@link map}, and {@link mapSettled}.
|
|
2998
2961
|
*
|
|
@@ -3244,7 +3207,7 @@ export function allSettled(
|
|
|
3244
3207
|
input: Iterable<AnyTask> | Readonly<Record<string, AnyTask>>,
|
|
3245
3208
|
options?: CollectOptions<boolean>,
|
|
3246
3209
|
): Task<unknown> {
|
|
3247
|
-
return collect("allSettled", input, options)
|
|
3210
|
+
return collect("allSettled", input, options);
|
|
3248
3211
|
}
|
|
3249
3212
|
|
|
3250
3213
|
/**
|
|
@@ -3546,9 +3509,7 @@ export interface AnyAbortError extends InferType<typeof AnyAbortError> {}
|
|
|
3546
3509
|
*/
|
|
3547
3510
|
export const anyAbortError: AnyAbortError = { type: "AnyAbortError" };
|
|
3548
3511
|
|
|
3549
|
-
type CollectInput =
|
|
3550
|
-
| Iterable<Task<unknown, unknown>>
|
|
3551
|
-
| Readonly<Record<string, AnyTask>>;
|
|
3512
|
+
type CollectInput = Iterable<AnyTask> | Readonly<Record<string, AnyTask>>;
|
|
3552
3513
|
|
|
3553
3514
|
/** Shared implementation for {@link all} and {@link allSettled}. */
|
|
3554
3515
|
const collect = (
|
|
@@ -3558,15 +3519,16 @@ const collect = (
|
|
|
3558
3519
|
collect = true,
|
|
3559
3520
|
abortReason = type === "all" ? allAbortError : allSettledAbortError,
|
|
3560
3521
|
}: CollectOptions<boolean> = {},
|
|
3561
|
-
):
|
|
3522
|
+
): AnyTask => {
|
|
3562
3523
|
const stopOn = type === "all" ? ("error" as const) : null;
|
|
3563
3524
|
|
|
3564
3525
|
if (isIterable(input)) {
|
|
3565
|
-
const
|
|
3526
|
+
const tasks: Iterable<AnyTask> = input;
|
|
3527
|
+
const array = arrayFrom(tasks);
|
|
3566
3528
|
if (!isNonEmptyArray(array))
|
|
3567
3529
|
return () => ok(collect ? emptyArray : undefined);
|
|
3568
3530
|
|
|
3569
|
-
return pool(array
|
|
3531
|
+
return pool(array, {
|
|
3570
3532
|
stopOn,
|
|
3571
3533
|
collect,
|
|
3572
3534
|
abortReason,
|
|
@@ -3577,7 +3539,7 @@ const collect = (
|
|
|
3577
3539
|
const taskArray: Array<AnyTask> = [];
|
|
3578
3540
|
for (const key in input) {
|
|
3579
3541
|
keys.push(key);
|
|
3580
|
-
taskArray.push(
|
|
3542
|
+
taskArray.push(input[key]);
|
|
3581
3543
|
}
|
|
3582
3544
|
if (keys.length === 0) return () => ok(collect ? emptyRecord : undefined);
|
|
3583
3545
|
|
|
@@ -3749,7 +3711,7 @@ function pool<T, E>(
|
|
|
3749
3711
|
};
|
|
3750
3712
|
|
|
3751
3713
|
const workerCount = Math.min(run.concurrency, length);
|
|
3752
|
-
const workers = arrayFrom(workerCount, () => run.daemon(worker));
|
|
3714
|
+
const workers = arrayFrom(workerCount, () => run.daemon(worker, run.deps));
|
|
3753
3715
|
|
|
3754
3716
|
using _ = new DisposableStack();
|
|
3755
3717
|
_.defer(() => {
|