@evolu/common 8.0.0-next.4 → 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 +5 -0
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +6 -1
- 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/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +2 -1
- package/dist/src/Task.d.ts +95 -92
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +60 -52
- package/dist/src/Test.d.ts +10 -11
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Types.d.ts +0 -8
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +10 -10
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +26 -26
- package/package.json +1 -1
- package/src/Assert.ts +6 -1
- package/src/Error.ts +5 -4
- package/src/Sqlite.ts +2 -1
- package/src/Task.ts +319 -314
- package/src/Test.ts +10 -11
- package/src/Types.ts +0 -9
- package/src/local-first/Db.ts +31 -29
- package/src/local-first/Shared.ts +33 -32
package/src/Task.ts
CHANGED
|
@@ -61,7 +61,6 @@ import {
|
|
|
61
61
|
type CallbackWithTeardown,
|
|
62
62
|
type Int1To100,
|
|
63
63
|
type Mutable,
|
|
64
|
-
type NewKeys,
|
|
65
64
|
type Predicate,
|
|
66
65
|
} from "./Types.js";
|
|
67
66
|
|
|
@@ -270,12 +269,22 @@ import {
|
|
|
270
269
|
* await run(fetchUser(123));
|
|
271
270
|
* ```
|
|
272
271
|
*
|
|
273
|
-
*
|
|
272
|
+
* Provide runtime-created dependencies to a single Task with `run(task, deps)`:
|
|
274
273
|
*
|
|
275
|
-
*
|
|
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
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* For reusable resources that own async work, use `run.create(deps)` and
|
|
282
|
+
* dispose the created Run with the resource.
|
|
276
283
|
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
284
|
+
* ### Default dependencies
|
|
285
|
+
*
|
|
286
|
+
* {@link createRun} provides default {@link RunDefaultDeps} available to all
|
|
287
|
+
* Tasks without declaring `D`:
|
|
279
288
|
*
|
|
280
289
|
* - {@link Console} — logging with hierarchical context via `child()`
|
|
281
290
|
* - {@link Time} — current time
|
|
@@ -312,7 +321,7 @@ import {
|
|
|
312
321
|
* ```
|
|
313
322
|
*
|
|
314
323
|
* For testing, use {@link testCreateRun} to get deterministic, controllable
|
|
315
|
-
* implementations of all
|
|
324
|
+
* implementations of all RunDefaultDeps.
|
|
316
325
|
*
|
|
317
326
|
* ## Resource management
|
|
318
327
|
*
|
|
@@ -416,6 +425,35 @@ import {
|
|
|
416
425
|
* use {@link assertNotAborted} to crash immediately instead of threading the
|
|
417
426
|
* impossible case through domain logic.
|
|
418
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
|
+
*
|
|
419
457
|
* ### How do I type an anonymous Task callback?
|
|
420
458
|
*
|
|
421
459
|
* For one-off inline Tasks, put the type arguments on the {@link Run} call:
|
|
@@ -578,6 +616,17 @@ export interface Run<D = unknown> extends AsyncDisposable {
|
|
|
578
616
|
/** Runs a {@link Task} and returns a {@link Fiber} handle. */
|
|
579
617
|
<T, E>(task: Task<T, E, D>): Fiber<T, E, D>;
|
|
580
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
|
+
|
|
581
630
|
/**
|
|
582
631
|
* Runs a {@link Task} and throws if the returned {@link Result} is an error.
|
|
583
632
|
*
|
|
@@ -601,13 +650,16 @@ export interface Run<D = unknown> extends AsyncDisposable {
|
|
|
601
650
|
*
|
|
602
651
|
* Throws: `Error` with the original Task error attached as `cause`.
|
|
603
652
|
*/
|
|
604
|
-
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
|
+
};
|
|
605
657
|
|
|
606
658
|
/** Unique {@link Id} for this Run. */
|
|
607
659
|
readonly id: Id;
|
|
608
660
|
|
|
609
661
|
/** The parent {@link Run}, if this Run was created as a child. */
|
|
610
|
-
readonly parent: Run
|
|
662
|
+
readonly parent: Run | null;
|
|
611
663
|
|
|
612
664
|
/** @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal */
|
|
613
665
|
readonly signal: AbortSignal;
|
|
@@ -641,7 +693,7 @@ export interface Run<D = unknown> extends AsyncDisposable {
|
|
|
641
693
|
readonly getState: () => RunState;
|
|
642
694
|
|
|
643
695
|
/** Returns the current child {@link Fiber}s. */
|
|
644
|
-
readonly getChildren: () => ReadonlySet<
|
|
696
|
+
readonly getChildren: () => ReadonlySet<AnyFiber>;
|
|
645
697
|
|
|
646
698
|
/**
|
|
647
699
|
* Creates a memoized {@link RunSnapshot} of this Run.
|
|
@@ -708,13 +760,15 @@ export interface Run<D = unknown> extends AsyncDisposable {
|
|
|
708
760
|
*
|
|
709
761
|
* For a long-lived reusable {@link Run}, use {@link Run.create}.
|
|
710
762
|
*/
|
|
711
|
-
readonly daemon: Run
|
|
763
|
+
readonly daemon: Run;
|
|
712
764
|
|
|
713
765
|
/**
|
|
714
766
|
* Creates a {@link Run} from this Run.
|
|
715
767
|
*
|
|
716
768
|
* Like {@link createRun}, the returned Run is daemon: it stays running until
|
|
717
|
-
* 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}.
|
|
718
772
|
*
|
|
719
773
|
* Use this for long-lived disposable resources that need to own async work.
|
|
720
774
|
* The resource creates one internal Run with `run.create()` and uses that Run
|
|
@@ -727,77 +781,19 @@ export interface Run<D = unknown> extends AsyncDisposable {
|
|
|
727
781
|
*
|
|
728
782
|
* To run a single Task as daemon, use {@link Run.daemon}.
|
|
729
783
|
*/
|
|
730
|
-
readonly create:
|
|
784
|
+
readonly create: {
|
|
785
|
+
(): Run<D>;
|
|
786
|
+
<Deps>(deps: Deps): Run<RunDefaultDeps & Deps>;
|
|
787
|
+
};
|
|
731
788
|
|
|
732
|
-
/** Returns
|
|
733
|
-
readonly deps:
|
|
789
|
+
/** Returns this Run's dependencies. */
|
|
790
|
+
readonly deps: RunDefaultDeps & D;
|
|
734
791
|
|
|
735
792
|
/**
|
|
736
793
|
* @see {@link Concurrency}
|
|
737
794
|
* @see {@link concurrently}
|
|
738
795
|
*/
|
|
739
796
|
readonly concurrency: Concurrency;
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* Adds additional dependencies to this Run and returns it.
|
|
743
|
-
*
|
|
744
|
-
* Use for runtime-created dependencies — dependencies that cannot be created
|
|
745
|
-
* in the composition root (e.g., app start).
|
|
746
|
-
*
|
|
747
|
-
* ### Example
|
|
748
|
-
*
|
|
749
|
-
* ```ts
|
|
750
|
-
* // One-shot
|
|
751
|
-
* await run.addDeps({ db })(getUser(123));
|
|
752
|
-
*
|
|
753
|
-
* // Multiple deps at once
|
|
754
|
-
* await run.addDeps({ db, cache })(task);
|
|
755
|
-
*
|
|
756
|
-
* // Reusable — config comes from outside (message, file, etc.)
|
|
757
|
-
* type DbWorkerDeps = DbDep; // or DbDep & CacheDep & ...
|
|
758
|
-
*
|
|
759
|
-
* const init =
|
|
760
|
-
* (config: Config): Task<void, InitError, CreateDbDep> =>
|
|
761
|
-
* async (run) => {
|
|
762
|
-
* const { createDb } = run.deps;
|
|
763
|
-
* await using disposer = new AsyncDisposableStack();
|
|
764
|
-
*
|
|
765
|
-
* const db = disposer.use(await run.orThrow(startApp()));
|
|
766
|
-
* if (!db.ok) return db;
|
|
767
|
-
*
|
|
768
|
-
* const runWithDb = run.addDeps({ db: db.value });
|
|
769
|
-
*
|
|
770
|
-
* await runWithDb(getUser(123));
|
|
771
|
-
* await runWithDb(insertUser(user));
|
|
772
|
-
* return ok();
|
|
773
|
-
* };
|
|
774
|
-
* ```
|
|
775
|
-
*
|
|
776
|
-
* ## FAQ
|
|
777
|
-
*
|
|
778
|
-
* ### How does it work?
|
|
779
|
-
*
|
|
780
|
-
* This is the whole implementation:
|
|
781
|
-
*
|
|
782
|
-
* ```ts
|
|
783
|
-
* run.addDeps = <E extends NewKeys<E, D>>(newDeps: E): Run<D & E> => {
|
|
784
|
-
* depsRef.modify((currentDeps) => {
|
|
785
|
-
* const duplicate = Object.keys(newDeps).find(
|
|
786
|
-
* (k) => k in currentDeps,
|
|
787
|
-
* );
|
|
788
|
-
* assert(!duplicate, `Dependency '${duplicate}' already added.`);
|
|
789
|
-
* return [undefined, { ...currentDeps, ...newDeps }];
|
|
790
|
-
* });
|
|
791
|
-
* return self as unknown as Run<D & E>;
|
|
792
|
-
* };
|
|
793
|
-
* ```
|
|
794
|
-
*
|
|
795
|
-
* Dependencies are stored in a shared {@link Ref}, so `addDeps` propagates to
|
|
796
|
-
* all runs. The runtime assertion ensures dependencies are created once —
|
|
797
|
-
* automatic deduplication would mask poor design (dependencies should have a
|
|
798
|
-
* single, clear point of creation).
|
|
799
|
-
*/
|
|
800
|
-
readonly addDeps: <E extends NewKeys<E, D>>(extraDeps: E) => Run<D & E>;
|
|
801
797
|
}
|
|
802
798
|
|
|
803
799
|
/**
|
|
@@ -896,12 +892,19 @@ export interface Fiber<T = unknown, E = unknown, D = unknown>
|
|
|
896
892
|
getState(): RunState<T, E>;
|
|
897
893
|
}
|
|
898
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
|
+
|
|
899
902
|
/**
|
|
900
903
|
* Extracts the value type from a {@link Fiber}.
|
|
901
904
|
*
|
|
902
905
|
* @group Type utilities
|
|
903
906
|
*/
|
|
904
|
-
export type InferFiberOk<F extends
|
|
907
|
+
export type InferFiberOk<F extends AnyFiber> =
|
|
905
908
|
F extends Fiber<infer T, any, any> ? T : never;
|
|
906
909
|
|
|
907
910
|
/**
|
|
@@ -909,7 +912,7 @@ export type InferFiberOk<F extends Fiber<any, any, any>> =
|
|
|
909
912
|
*
|
|
910
913
|
* @group Type utilities
|
|
911
914
|
*/
|
|
912
|
-
export type InferFiberErr<F extends
|
|
915
|
+
export type InferFiberErr<F extends AnyFiber> =
|
|
913
916
|
F extends Fiber<any, infer E, any> ? E : never;
|
|
914
917
|
|
|
915
918
|
/**
|
|
@@ -917,7 +920,7 @@ export type InferFiberErr<F extends Fiber<any, any, any>> =
|
|
|
917
920
|
*
|
|
918
921
|
* @group Type utilities
|
|
919
922
|
*/
|
|
920
|
-
export type InferFiberDeps<F extends
|
|
923
|
+
export type InferFiberDeps<F extends AnyFiber> =
|
|
921
924
|
F extends Fiber<any, any, infer D> ? D : never;
|
|
922
925
|
|
|
923
926
|
/**
|
|
@@ -1023,7 +1026,7 @@ export type RunSnapshotState = typeof RunSnapshotState.Type;
|
|
|
1023
1026
|
* @see {@link Run.snapshot}
|
|
1024
1027
|
*/
|
|
1025
1028
|
export interface RunSnapshot {
|
|
1026
|
-
/** The
|
|
1029
|
+
/** The Run id this snapshot represents. */
|
|
1027
1030
|
readonly id: Id;
|
|
1028
1031
|
|
|
1029
1032
|
/** The current lifecycle state. */
|
|
@@ -1083,7 +1086,7 @@ export interface RunConfigDep {
|
|
|
1083
1086
|
}
|
|
1084
1087
|
|
|
1085
1088
|
/** Default deps provided by {@link createRun}. */
|
|
1086
|
-
export type
|
|
1089
|
+
export type RunDefaultDeps = ConsoleDep &
|
|
1087
1090
|
RandomBytesDep &
|
|
1088
1091
|
RandomDep &
|
|
1089
1092
|
TimeDep &
|
|
@@ -1093,7 +1096,7 @@ export type RunDeps = ConsoleDep &
|
|
|
1093
1096
|
// Partial<TracerConfigDep> & // TODO:
|
|
1094
1097
|
// Partial<TracerDep>; // TODO:
|
|
1095
1098
|
|
|
1096
|
-
const
|
|
1099
|
+
const runDefaultDeps: RunDefaultDeps = {
|
|
1097
1100
|
console: createConsole(),
|
|
1098
1101
|
randomBytes: createRandomBytes(),
|
|
1099
1102
|
random: createRandom(),
|
|
@@ -1125,7 +1128,7 @@ export interface CreateRun<BaseDeps> {
|
|
|
1125
1128
|
* Node.js `uncaughtException`, `unhandledRejection`, and graceful shutdown
|
|
1126
1129
|
* handling, and `@evolu/react-native` adds React Native global error handling.
|
|
1127
1130
|
*
|
|
1128
|
-
* {@link
|
|
1131
|
+
* {@link RunDefaultDeps} provides default dependencies:
|
|
1129
1132
|
*
|
|
1130
1133
|
* - {@link Time}
|
|
1131
1134
|
* - {@link Console}
|
|
@@ -1163,7 +1166,7 @@ export interface CreateRun<BaseDeps> {
|
|
|
1163
1166
|
* };
|
|
1164
1167
|
*
|
|
1165
1168
|
* // Composition root: create a Run with custom deps
|
|
1166
|
-
* type AppDeps =
|
|
1169
|
+
* type AppDeps = RunDefaultDeps & ConfigDep;
|
|
1167
1170
|
*
|
|
1168
1171
|
* const appDeps: AppDeps = {
|
|
1169
1172
|
* ...testCreateDeps(), // or spread individual deps
|
|
@@ -1176,24 +1179,24 @@ export interface CreateRun<BaseDeps> {
|
|
|
1176
1179
|
* const result = await run(fetchUser("123"));
|
|
1177
1180
|
*
|
|
1178
1181
|
* // TypeScript catches missing deps at compile time:
|
|
1179
|
-
* // await using run2 = createRun(); // Run<
|
|
1182
|
+
* // await using run2 = createRun(); // Run<RunDefaultDeps>
|
|
1180
1183
|
* // run2(fetchUser("123")); // Error: Property 'config' is missing
|
|
1181
1184
|
* ```
|
|
1182
1185
|
*
|
|
1183
1186
|
* @group Creating Run
|
|
1184
1187
|
*/
|
|
1185
|
-
export const createRun: CreateRun<
|
|
1188
|
+
export const createRun: CreateRun<RunDefaultDeps> = <D>(
|
|
1186
1189
|
deps?: D,
|
|
1187
|
-
): Run<
|
|
1188
|
-
|
|
1189
|
-
return createRunInternal(createRef(mergedDeps))();
|
|
1190
|
-
};
|
|
1190
|
+
): Run<RunDefaultDeps & D> =>
|
|
1191
|
+
createRunInternal({ ...runDefaultDeps, ...deps } as RunDefaultDeps & D);
|
|
1191
1192
|
|
|
1192
1193
|
/** Internal Run properties, hidden from public API via TypeScript types. */
|
|
1193
|
-
interface RunInternal<
|
|
1194
|
+
interface RunInternal<
|
|
1195
|
+
D extends RunDefaultDeps = RunDefaultDeps,
|
|
1196
|
+
> extends Run<D> {
|
|
1194
1197
|
ownTaskSettled: PromiseWithResolvers<void> | null;
|
|
1195
1198
|
|
|
1196
|
-
readonly requestAbort: (
|
|
1199
|
+
readonly requestAbort: (abortError: AbortError) => void;
|
|
1197
1200
|
readonly requestSignal: AbortSignal;
|
|
1198
1201
|
|
|
1199
1202
|
/**
|
|
@@ -1208,227 +1211,233 @@ interface RunInternal<D extends RunDeps = RunDeps> extends Run<D> {
|
|
|
1208
1211
|
readonly handleTaskSettled: () => void;
|
|
1209
1212
|
}
|
|
1210
1213
|
|
|
1211
|
-
const createRunInternal =
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
}
|
|
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
|
+
}
|
|
1236
1238
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
+
const requestController = new AbortController();
|
|
1240
|
+
const signalController = new AbortController();
|
|
1239
1241
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1242
|
+
let state: RunState = running;
|
|
1243
|
+
let result: UnknownResult | undefined;
|
|
1244
|
+
let outcome: UnknownResult | undefined;
|
|
1245
|
+
let children: ReadonlySet<AnyFiber> = emptySet;
|
|
1244
1246
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
};
|
|
1247
|
+
const requestAbort = (abortError: AbortError) => {
|
|
1248
|
+
if (abortMask === isAbortable) signalController.abort(abortError);
|
|
1249
|
+
requestController.abort(abortError);
|
|
1250
|
+
};
|
|
1250
1251
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1252
|
+
if (parent) {
|
|
1253
|
+
subscribeToAbort(
|
|
1254
|
+
parent.requestSignal,
|
|
1255
|
+
() => requestAbort(parent.requestSignal.reason as AbortError),
|
|
1256
|
+
{ signal: requestController.signal },
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1258
1259
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
};
|
|
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
|
+
};
|
|
1267
1267
|
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
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
|
+
);
|
|
1275
1287
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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
|
+
}
|
|
1286
1299
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
},
|
|
1301
|
-
getState: () => childRun.getState() as RunState<T, E>,
|
|
1302
|
-
[Symbol.dispose]: () => {
|
|
1303
|
-
childFiber.abort();
|
|
1304
|
-
},
|
|
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));
|
|
1305
1313
|
},
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1314
|
+
getState: () => childRun.getState(),
|
|
1315
|
+
[Symbol.dispose]: () => {
|
|
1316
|
+
childFiber.abort();
|
|
1317
|
+
},
|
|
1318
|
+
},
|
|
1319
|
+
);
|
|
1310
1320
|
|
|
1311
|
-
|
|
1312
|
-
};
|
|
1321
|
+
children = addToSet(children, childFiber);
|
|
1322
|
+
emitEvent({ type: "ChildAdded", childId: childRun.id });
|
|
1313
1323
|
|
|
1314
|
-
|
|
1324
|
+
return childFiber;
|
|
1325
|
+
};
|
|
1315
1326
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
const id = createId(depsRef.get());
|
|
1319
|
-
|
|
1320
|
-
let snapshot: RunSnapshot | null = null;
|
|
1321
|
-
let disposingPromise: Promise<void> | null = null;
|
|
1322
|
-
|
|
1323
|
-
run.orThrow = async (task) => getOrThrow(await self(task));
|
|
1324
|
-
run.id = id;
|
|
1325
|
-
run.parent = parent ?? null;
|
|
1326
|
-
|
|
1327
|
-
run.signal = signalController.signal;
|
|
1328
|
-
run.abortMask = abortMask;
|
|
1329
|
-
run.onAbort = (callback) => {
|
|
1330
|
-
if (abortMask !== isAbortable) return;
|
|
1331
|
-
subscribeToAbort(
|
|
1332
|
-
signalController.signal,
|
|
1333
|
-
() => callback((signalController.signal.reason as AbortError).reason),
|
|
1334
|
-
{ once: true, signal: requestController.signal },
|
|
1335
|
-
);
|
|
1336
|
-
};
|
|
1337
|
-
run.getState = () => state;
|
|
1338
|
-
run.getChildren = () => children;
|
|
1327
|
+
let snapshot: RunSnapshot | null = null;
|
|
1328
|
+
let disposingPromise: Promise<void> | null = null;
|
|
1339
1329
|
|
|
1340
|
-
|
|
1341
|
-
const childSnapshots = Array.from(children).map((fiber) =>
|
|
1342
|
-
fiber.run.snapshot(),
|
|
1343
|
-
);
|
|
1344
|
-
if (
|
|
1345
|
-
snapshot?.state !== state ||
|
|
1346
|
-
!eqArrayStrict(snapshot.children, childSnapshots)
|
|
1347
|
-
) {
|
|
1348
|
-
snapshot = {
|
|
1349
|
-
id,
|
|
1350
|
-
state: state as RunSnapshotState,
|
|
1351
|
-
children: childSnapshots,
|
|
1352
|
-
abortMask,
|
|
1353
|
-
};
|
|
1354
|
-
}
|
|
1355
|
-
return snapshot;
|
|
1356
|
-
};
|
|
1330
|
+
const self = run as RunInternal<D>;
|
|
1357
1331
|
|
|
1358
|
-
|
|
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
|
+
}
|
|
1359
1349
|
|
|
1360
|
-
|
|
1350
|
+
run.orThrow = orThrow;
|
|
1351
|
+
run.id = id;
|
|
1352
|
+
run.parent = parent ?? null;
|
|
1361
1353
|
|
|
1362
|
-
|
|
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;
|
|
1363
1366
|
|
|
1364
|
-
|
|
1365
|
-
|
|
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
|
+
};
|
|
1366
1384
|
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
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 });
|
|
1377
1417
|
});
|
|
1378
|
-
return self as unknown as Run<D & E>;
|
|
1379
|
-
};
|
|
1380
|
-
|
|
1381
|
-
run[Symbol.asyncDispose] = () => {
|
|
1382
|
-
if (disposingPromise) return disposingPromise;
|
|
1383
|
-
|
|
1384
|
-
state = { type: "Disposing" };
|
|
1385
|
-
emitEvent({ type: "StateChanged", state });
|
|
1386
|
-
requestAbort(runStoppedAbortError);
|
|
1387
|
-
|
|
1388
|
-
disposingPromise = Promise.allSettled(
|
|
1389
|
-
(run.ownTaskSettled
|
|
1390
|
-
? [run.ownTaskSettled.promise, ...children]
|
|
1391
|
-
: children) as Iterable<PromiseLike<unknown>>,
|
|
1392
|
-
)
|
|
1393
|
-
.then(lazyVoid)
|
|
1394
|
-
.finally(() => {
|
|
1395
|
-
/**
|
|
1396
|
-
* Root and daemon Runs have no own Task, so
|
|
1397
|
-
* `run.handleTaskFulfilled` never populates their terminal values.
|
|
1398
|
-
* In that case disposal publishes `ok()` for both `result` and
|
|
1399
|
-
* `outcome`. Task-backed Runs normally reach this point with both
|
|
1400
|
-
* values already set.
|
|
1401
|
-
*/
|
|
1402
|
-
[result, outcome] = [result ?? ok(), outcome ?? ok()];
|
|
1403
|
-
state = { type: "Settled", result, outcome };
|
|
1404
|
-
emitEvent({ type: "StateChanged", state });
|
|
1405
|
-
});
|
|
1406
1418
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
// Internal
|
|
1411
|
-
run.ownTaskSettled = parent ? Promise.withResolvers<void>() : null;
|
|
1412
|
-
|
|
1413
|
-
run.requestAbort = requestAbort;
|
|
1414
|
-
run.requestSignal = requestController.signal;
|
|
1415
|
-
|
|
1416
|
-
run.handleTaskFulfilled = (taskOutcome) => {
|
|
1417
|
-
const taskResult = run.signal.aborted
|
|
1418
|
-
? (err(run.signal.reason as AbortError) as typeof taskOutcome)
|
|
1419
|
-
: taskOutcome;
|
|
1420
|
-
result = taskResult;
|
|
1421
|
-
outcome = taskOutcome;
|
|
1422
|
-
return taskResult;
|
|
1423
|
-
};
|
|
1419
|
+
return disposingPromise;
|
|
1420
|
+
};
|
|
1424
1421
|
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
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
|
+
}
|
|
1429
1438
|
|
|
1430
|
-
|
|
1431
|
-
|
|
1439
|
+
return self;
|
|
1440
|
+
};
|
|
1432
1441
|
|
|
1433
1442
|
const running: RunState = { type: "Running" };
|
|
1434
1443
|
|
|
@@ -1485,16 +1494,19 @@ const abortBehavior =
|
|
|
1485
1494
|
/**
|
|
1486
1495
|
* Makes a {@link Task} unabortable.
|
|
1487
1496
|
*
|
|
1488
|
-
* Once started, an unabortable Task always completes
|
|
1489
|
-
*
|
|
1497
|
+
* Once started, an unabortable Task always completes. Abort requests are masked
|
|
1498
|
+
* while it runs, and `signal.aborted` remains `false` inside the Task.
|
|
1490
1499
|
*
|
|
1491
|
-
*
|
|
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)`
|
|
1492
1502
|
* short-circuits before task execution and returns `err(AbortError)` with
|
|
1493
1503
|
* {@link runStoppedError} as reason. So `unabortable` means “do not interrupt
|
|
1494
1504
|
* this Task once it has started”, not “remove AbortError from its type”.
|
|
1495
1505
|
*
|
|
1496
|
-
*
|
|
1497
|
-
*
|
|
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.
|
|
1498
1510
|
*
|
|
1499
1511
|
* ### Example
|
|
1500
1512
|
*
|
|
@@ -1753,7 +1765,7 @@ export const callback =
|
|
|
1753
1765
|
readonly ok: Callback<T>;
|
|
1754
1766
|
readonly err: Callback<E>;
|
|
1755
1767
|
readonly signal: AbortSignal;
|
|
1756
|
-
readonly deps:
|
|
1768
|
+
readonly deps: RunDefaultDeps;
|
|
1757
1769
|
}>,
|
|
1758
1770
|
): Task<T, E> =>
|
|
1759
1771
|
(run) =>
|
|
@@ -2023,10 +2035,7 @@ export const retry =
|
|
|
2023
2035
|
<T, E, D = unknown, Output = unknown>(
|
|
2024
2036
|
task: Task<T, E, D>,
|
|
2025
2037
|
schedule: Schedule<Output, E>,
|
|
2026
|
-
{
|
|
2027
|
-
retryable = lazyTrue,
|
|
2028
|
-
onRetry,
|
|
2029
|
-
}: RetryOptions<E, Output> = {},
|
|
2038
|
+
{ retryable = lazyTrue, onRetry }: RetryOptions<E, Output> = {},
|
|
2030
2039
|
): Task<T, RetryError<E>, D> =>
|
|
2031
2040
|
async (run) => {
|
|
2032
2041
|
const step = schedule(run.deps);
|
|
@@ -2143,10 +2152,7 @@ export const repeat =
|
|
|
2143
2152
|
<T, E, D = unknown, Output = unknown>(
|
|
2144
2153
|
task: Task<T, E, D>,
|
|
2145
2154
|
schedule: Schedule<Output, T>,
|
|
2146
|
-
{
|
|
2147
|
-
repeatable = lazyTrue,
|
|
2148
|
-
onRepeat,
|
|
2149
|
-
}: RepeatOptions<T, Output> = {},
|
|
2155
|
+
{ repeatable = lazyTrue, onRepeat }: RepeatOptions<T, Output> = {},
|
|
2150
2156
|
): Task<T, E, D> =>
|
|
2151
2157
|
async (run) => {
|
|
2152
2158
|
const step = schedule(run.deps);
|
|
@@ -2185,7 +2191,7 @@ export const repeat =
|
|
|
2185
2191
|
*
|
|
2186
2192
|
* Similar to
|
|
2187
2193
|
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers | Promise.withResolvers},
|
|
2188
|
-
* but integrated with {@link Task} and {@link Run}
|
|
2194
|
+
* but integrated with {@link Task} and {@link Run}.
|
|
2189
2195
|
*
|
|
2190
2196
|
* Use for bridging callback-based APIs or coordinating between Tasks.
|
|
2191
2197
|
*
|
|
@@ -3201,7 +3207,7 @@ export function allSettled(
|
|
|
3201
3207
|
input: Iterable<AnyTask> | Readonly<Record<string, AnyTask>>,
|
|
3202
3208
|
options?: CollectOptions<boolean>,
|
|
3203
3209
|
): Task<unknown> {
|
|
3204
|
-
return collect("allSettled", input, options)
|
|
3210
|
+
return collect("allSettled", input, options);
|
|
3205
3211
|
}
|
|
3206
3212
|
|
|
3207
3213
|
/**
|
|
@@ -3503,9 +3509,7 @@ export interface AnyAbortError extends InferType<typeof AnyAbortError> {}
|
|
|
3503
3509
|
*/
|
|
3504
3510
|
export const anyAbortError: AnyAbortError = { type: "AnyAbortError" };
|
|
3505
3511
|
|
|
3506
|
-
type CollectInput =
|
|
3507
|
-
| Iterable<Task<unknown, unknown>>
|
|
3508
|
-
| Readonly<Record<string, AnyTask>>;
|
|
3512
|
+
type CollectInput = Iterable<AnyTask> | Readonly<Record<string, AnyTask>>;
|
|
3509
3513
|
|
|
3510
3514
|
/** Shared implementation for {@link all} and {@link allSettled}. */
|
|
3511
3515
|
const collect = (
|
|
@@ -3515,15 +3519,16 @@ const collect = (
|
|
|
3515
3519
|
collect = true,
|
|
3516
3520
|
abortReason = type === "all" ? allAbortError : allSettledAbortError,
|
|
3517
3521
|
}: CollectOptions<boolean> = {},
|
|
3518
|
-
):
|
|
3522
|
+
): AnyTask => {
|
|
3519
3523
|
const stopOn = type === "all" ? ("error" as const) : null;
|
|
3520
3524
|
|
|
3521
3525
|
if (isIterable(input)) {
|
|
3522
|
-
const
|
|
3526
|
+
const tasks: Iterable<AnyTask> = input;
|
|
3527
|
+
const array = arrayFrom(tasks);
|
|
3523
3528
|
if (!isNonEmptyArray(array))
|
|
3524
3529
|
return () => ok(collect ? emptyArray : undefined);
|
|
3525
3530
|
|
|
3526
|
-
return pool(array
|
|
3531
|
+
return pool(array, {
|
|
3527
3532
|
stopOn,
|
|
3528
3533
|
collect,
|
|
3529
3534
|
abortReason,
|
|
@@ -3534,7 +3539,7 @@ const collect = (
|
|
|
3534
3539
|
const taskArray: Array<AnyTask> = [];
|
|
3535
3540
|
for (const key in input) {
|
|
3536
3541
|
keys.push(key);
|
|
3537
|
-
taskArray.push(
|
|
3542
|
+
taskArray.push(input[key]);
|
|
3538
3543
|
}
|
|
3539
3544
|
if (keys.length === 0) return () => ok(collect ? emptyRecord : undefined);
|
|
3540
3545
|
|
|
@@ -3706,7 +3711,7 @@ function pool<T, E>(
|
|
|
3706
3711
|
};
|
|
3707
3712
|
|
|
3708
3713
|
const workerCount = Math.min(run.concurrency, length);
|
|
3709
|
-
const workers = arrayFrom(workerCount, () => run.daemon(worker));
|
|
3714
|
+
const workers = arrayFrom(workerCount, () => run.daemon(worker, run.deps));
|
|
3710
3715
|
|
|
3711
3716
|
using _ = new DisposableStack();
|
|
3712
3717
|
_.defer(() => {
|