@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/Resource.ts
CHANGED
|
@@ -113,21 +113,21 @@ export const createResourceRef = <T extends Resource, D>(
|
|
|
113
113
|
unabortable<ResourceRef<T, D>, never, D>(async (run) => {
|
|
114
114
|
const resourceRefRun = run.create();
|
|
115
115
|
|
|
116
|
-
await using
|
|
117
|
-
|
|
116
|
+
await using disposer = new AsyncDisposableStack();
|
|
117
|
+
disposer.use(resourceRefRun);
|
|
118
118
|
|
|
119
119
|
const initial = await resourceRefRun(create);
|
|
120
120
|
if (!initial.ok) return initial;
|
|
121
121
|
|
|
122
122
|
let current = createOwnedResource(initial.value);
|
|
123
123
|
|
|
124
|
-
const mutex =
|
|
125
|
-
|
|
124
|
+
const mutex = disposer.use(createMutex());
|
|
125
|
+
disposer.defer(() => current.stack.disposeAsync());
|
|
126
126
|
// Register as the last so disposal aborts further calls first.
|
|
127
127
|
// Repeated registration is safe because disposal is idempotent.
|
|
128
|
-
|
|
128
|
+
disposer.use(resourceRefRun);
|
|
129
129
|
|
|
130
|
-
const
|
|
130
|
+
const disposables = disposer.move();
|
|
131
131
|
|
|
132
132
|
return ok({
|
|
133
133
|
get: () => resourceRefRun(mutex.withLock(() => ok(current.resource))),
|
|
@@ -145,7 +145,7 @@ export const createResourceRef = <T extends Resource, D>(
|
|
|
145
145
|
),
|
|
146
146
|
),
|
|
147
147
|
|
|
148
|
-
[Symbol.asyncDispose]: () =>
|
|
148
|
+
[Symbol.asyncDispose]: () => disposables.disposeAsync(),
|
|
149
149
|
});
|
|
150
150
|
});
|
|
151
151
|
|
|
@@ -165,12 +165,6 @@ export interface SharedResource<
|
|
|
165
165
|
T extends Resource,
|
|
166
166
|
D = unknown,
|
|
167
167
|
> extends AsyncDisposable {
|
|
168
|
-
/** Returns the current shared-resource state for monitoring/debugging. */
|
|
169
|
-
readonly snapshot: () => SharedResourceSnapshot;
|
|
170
|
-
|
|
171
|
-
/** Returns the current resource, or `undefined` if absent. */
|
|
172
|
-
readonly get: () => BorrowedResource<T> | undefined;
|
|
173
|
-
|
|
174
168
|
/**
|
|
175
169
|
* Acquires a shared reference.
|
|
176
170
|
*
|
|
@@ -203,6 +197,12 @@ export interface SharedResource<
|
|
|
203
197
|
|
|
204
198
|
/** Returns the current acquire count. */
|
|
205
199
|
readonly getCount: Task<NonNegativeInt, never, D>;
|
|
200
|
+
|
|
201
|
+
/** Returns the current resource, or `undefined` if absent. */
|
|
202
|
+
readonly get: () => BorrowedResource<T> | undefined;
|
|
203
|
+
|
|
204
|
+
/** Returns the current shared-resource state for monitoring/debugging. */
|
|
205
|
+
readonly snapshot: () => SharedResourceSnapshot;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
/** Snapshot returned by {@link SharedResource.snapshot}. */
|
|
@@ -237,38 +237,31 @@ export const createSharedResource = <T extends Resource, D>(
|
|
|
237
237
|
create: Task<T, never, D>,
|
|
238
238
|
{ idleDisposeAfter, onDisposed }: SharedResourceOptions = {},
|
|
239
239
|
): Task<SharedResource<T, D>, never, D> =>
|
|
240
|
-
unabortable<SharedResource<T, D>, never, D>((run) => {
|
|
240
|
+
unabortable<SharedResource<T, D>, never, D>(async (run) => {
|
|
241
241
|
const sharedResourceRun = run.create();
|
|
242
242
|
let current: OwnedResource<T> | undefined;
|
|
243
243
|
let idleDisposeFiber: Fiber<void, AbortError, D> | undefined;
|
|
244
244
|
|
|
245
|
-
|
|
246
|
-
const refCount =
|
|
245
|
+
await using disposer = new AsyncDisposableStack();
|
|
246
|
+
const refCount = disposer.use(createRefCount());
|
|
247
247
|
|
|
248
|
-
const mutex =
|
|
248
|
+
const mutex = disposer.use(createMutex());
|
|
249
249
|
|
|
250
250
|
const disposeCurrent = async () => {
|
|
251
251
|
if (!current) return;
|
|
252
|
-
await using
|
|
253
|
-
if (onDisposed)
|
|
254
|
-
|
|
252
|
+
await using disposer = new AsyncDisposableStack();
|
|
253
|
+
if (onDisposed) disposer.defer(onDisposed);
|
|
254
|
+
disposer.use(current.stack);
|
|
255
255
|
current = undefined;
|
|
256
256
|
};
|
|
257
|
-
|
|
257
|
+
disposer.defer(disposeCurrent);
|
|
258
258
|
|
|
259
259
|
// Register as the last so disposal aborts further calls first.
|
|
260
|
-
|
|
260
|
+
disposer.use(sharedResourceRun);
|
|
261
261
|
|
|
262
|
-
const
|
|
262
|
+
const disposables = disposer.move();
|
|
263
263
|
|
|
264
264
|
return ok({
|
|
265
|
-
snapshot: () => ({
|
|
266
|
-
isIdle: refCount.getCount() === 0 && !current && !idleDisposeFiber,
|
|
267
|
-
mutex: mutex.snapshot(),
|
|
268
|
-
}),
|
|
269
|
-
|
|
270
|
-
get: () => current?.resource,
|
|
271
|
-
|
|
272
265
|
acquire: unabortable<BorrowedResource<T>, never, D>(() =>
|
|
273
266
|
sharedResourceRun(
|
|
274
267
|
mutex.withLock(async (run) => {
|
|
@@ -320,7 +313,14 @@ export const createSharedResource = <T extends Resource, D>(
|
|
|
320
313
|
getCount: () =>
|
|
321
314
|
sharedResourceRun(mutex.withLock(() => ok(refCount.getCount()))),
|
|
322
315
|
|
|
323
|
-
|
|
316
|
+
get: () => current?.resource,
|
|
317
|
+
|
|
318
|
+
snapshot: () => ({
|
|
319
|
+
isIdle: refCount.getCount() === 0 && !current && !idleDisposeFiber,
|
|
320
|
+
mutex: mutex.snapshot(),
|
|
321
|
+
}),
|
|
322
|
+
|
|
323
|
+
[Symbol.asyncDispose]: () => disposables.disposeAsync(),
|
|
324
324
|
});
|
|
325
325
|
});
|
|
326
326
|
|
|
@@ -356,9 +356,6 @@ export interface SharedResourceByKey<
|
|
|
356
356
|
T extends Resource,
|
|
357
357
|
D = unknown,
|
|
358
358
|
> extends AsyncDisposable {
|
|
359
|
-
/** Returns the current resource for `key`, or `undefined` if absent. */
|
|
360
|
-
readonly get: (key: K) => BorrowedResource<T> | undefined;
|
|
361
|
-
|
|
362
359
|
/**
|
|
363
360
|
* Acquires the shared resource for `key`, creating it on first use.
|
|
364
361
|
*
|
|
@@ -386,6 +383,9 @@ export interface SharedResourceByKey<
|
|
|
386
383
|
/** Returns the current acquire count for `key`. Missing keys return `0`. */
|
|
387
384
|
readonly getCount: (key: K) => Task<NonNegativeInt, never, D>;
|
|
388
385
|
|
|
386
|
+
/** Returns the current resource for `key`, or `undefined` if absent. */
|
|
387
|
+
readonly get: (key: K) => BorrowedResource<T> | undefined;
|
|
388
|
+
|
|
389
389
|
/** Returns current keyed resources and their per-key mutex state. */
|
|
390
390
|
readonly snapshot: () => SharedResourceByKeySnapshot<K, T>;
|
|
391
391
|
}
|
|
@@ -436,29 +436,28 @@ export function createSharedResourceByKey<
|
|
|
436
436
|
onDisposed,
|
|
437
437
|
}: SharedResourceByKeyOptions<K, L> = {},
|
|
438
438
|
): Task<SharedResourceByKey<K, T, D>, never, D> {
|
|
439
|
-
return unabortable<SharedResourceByKey<K, T, D>, never, D>((run) => {
|
|
439
|
+
return unabortable<SharedResourceByKey<K, T, D>, never, D>(async (run) => {
|
|
440
440
|
const sharedResourceByKeyRun = run.create();
|
|
441
441
|
const sharedResourcesByKey = createLookupMap<K, SharedResource<T, D>, L>({
|
|
442
442
|
lookup,
|
|
443
443
|
});
|
|
444
444
|
|
|
445
|
-
|
|
445
|
+
await using disposer = new AsyncDisposableStack();
|
|
446
446
|
|
|
447
|
-
const mutexByKey =
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
for (const resource of sharedResourcesByKey.values())
|
|
451
|
-
|
|
447
|
+
const mutexByKey = disposer.use(createMutexByKey<K, L>({ lookup }));
|
|
448
|
+
disposer.defer(async () => {
|
|
449
|
+
await using disposer = new AsyncDisposableStack();
|
|
450
|
+
for (const resource of sharedResourcesByKey.values())
|
|
451
|
+
disposer.use(resource);
|
|
452
|
+
await disposer.disposeAsync();
|
|
452
453
|
sharedResourcesByKey.clear();
|
|
453
454
|
});
|
|
454
455
|
// Register as the last so disposal aborts further calls first.
|
|
455
|
-
|
|
456
|
+
disposer.use(sharedResourceByKeyRun);
|
|
456
457
|
|
|
457
|
-
const
|
|
458
|
+
const disposables = disposer.move();
|
|
458
459
|
|
|
459
460
|
return ok({
|
|
460
|
-
get: (key) => sharedResourcesByKey.get(key)?.get(),
|
|
461
|
-
|
|
462
461
|
acquire: (key) =>
|
|
463
462
|
unabortable<BorrowedResource<T>, never, D>(() =>
|
|
464
463
|
sharedResourceByKeyRun(
|
|
@@ -521,6 +520,8 @@ export function createSharedResourceByKey<
|
|
|
521
520
|
}),
|
|
522
521
|
),
|
|
523
522
|
|
|
523
|
+
get: (key) => sharedResourcesByKey.get(key)?.get(),
|
|
524
|
+
|
|
524
525
|
snapshot: () => {
|
|
525
526
|
const resourcesByKey = new Map<K, BorrowedResource<T>>();
|
|
526
527
|
const mutexSnapshotsByKey = new Map<K, SemaphoreSnapshot | null>();
|
|
@@ -539,7 +540,7 @@ export function createSharedResourceByKey<
|
|
|
539
540
|
};
|
|
540
541
|
},
|
|
541
542
|
|
|
542
|
-
[Symbol.asyncDispose]: () =>
|
|
543
|
+
[Symbol.asyncDispose]: () => disposables.disposeAsync(),
|
|
543
544
|
});
|
|
544
545
|
});
|
|
545
546
|
}
|
|
@@ -661,27 +662,31 @@ export function createSharedResourceByKeyWithClaims<
|
|
|
661
662
|
return unabortable<SharedResourceByKeyWithClaims<K, C, T, D>, never, D>(
|
|
662
663
|
async (run) => {
|
|
663
664
|
const sharedResourceClaimsRun = run.create();
|
|
664
|
-
await using
|
|
665
|
+
await using disposer = new AsyncDisposableStack();
|
|
665
666
|
|
|
666
667
|
const keyByClaim = createRelation<C, K, LC, LK>({
|
|
667
668
|
lookupA: claimLookup,
|
|
668
669
|
lookupB: resourceLookup,
|
|
669
670
|
});
|
|
670
|
-
const pairRefCountsByClaim =
|
|
671
|
+
const pairRefCountsByClaim = disposer.adopt(
|
|
671
672
|
createLookupMap<C, RefCountByKey<K>, LC>({ lookup: claimLookup }),
|
|
672
673
|
(pairRefCountsByClaim) => {
|
|
674
|
+
using disposer = new DisposableStack();
|
|
675
|
+
disposer.defer(() => {
|
|
676
|
+
pairRefCountsByClaim.clear();
|
|
677
|
+
});
|
|
678
|
+
|
|
673
679
|
for (const pairRefCountByKey of pairRefCountsByClaim.values()) {
|
|
674
|
-
|
|
680
|
+
disposer.use(pairRefCountByKey);
|
|
675
681
|
}
|
|
676
|
-
pairRefCountsByClaim.clear();
|
|
677
682
|
},
|
|
678
683
|
);
|
|
679
684
|
|
|
680
|
-
const mutexByKey =
|
|
685
|
+
const mutexByKey = disposer.use(
|
|
681
686
|
createMutexByKey<K, LK>({ lookup: resourceLookup }),
|
|
682
687
|
);
|
|
683
688
|
|
|
684
|
-
|
|
689
|
+
disposer.defer(() => {
|
|
685
690
|
keyByClaim.clear();
|
|
686
691
|
});
|
|
687
692
|
|
|
@@ -692,10 +697,12 @@ export function createSharedResourceByKeyWithClaims<
|
|
|
692
697
|
}),
|
|
693
698
|
);
|
|
694
699
|
assertNotAborted(sharedResourcesByKeyResult);
|
|
695
|
-
const sharedResourcesByKey =
|
|
700
|
+
const sharedResourcesByKey = disposer.use(
|
|
701
|
+
sharedResourcesByKeyResult.value,
|
|
702
|
+
);
|
|
696
703
|
|
|
697
704
|
// Register as the last so disposal aborts further calls first.
|
|
698
|
-
|
|
705
|
+
disposer.use(sharedResourceClaimsRun);
|
|
699
706
|
|
|
700
707
|
/** Asserts that one call does not contain the same logical key twice. */
|
|
701
708
|
const assertNoDuplicateResourceKeys = (
|
|
@@ -710,7 +717,7 @@ export function createSharedResourceByKeyWithClaims<
|
|
|
710
717
|
);
|
|
711
718
|
};
|
|
712
719
|
|
|
713
|
-
const
|
|
720
|
+
const disposables = disposer.move();
|
|
714
721
|
|
|
715
722
|
return ok({
|
|
716
723
|
addClaim: (claim, resourceKeys) =>
|
|
@@ -840,7 +847,7 @@ export function createSharedResourceByKeyWithClaims<
|
|
|
840
847
|
return resources;
|
|
841
848
|
},
|
|
842
849
|
|
|
843
|
-
[Symbol.asyncDispose]: () =>
|
|
850
|
+
[Symbol.asyncDispose]: () => disposables.disposeAsync(),
|
|
844
851
|
});
|
|
845
852
|
},
|
|
846
853
|
);
|
package/src/Schedule.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { assertType } from "./Assert.js";
|
|
7
8
|
import { fibonacciAt, FibonacciIndex, increment } from "./Number.js";
|
|
8
9
|
import type { RandomDep } from "./Random.js";
|
|
9
10
|
import { done, err, type NextResult, ok } from "./Result.js";
|
|
@@ -13,9 +14,15 @@ import {
|
|
|
13
14
|
durationToMillis,
|
|
14
15
|
Millis,
|
|
15
16
|
minMillis,
|
|
17
|
+
saturateMillis,
|
|
16
18
|
type TimeDep,
|
|
17
19
|
} from "./Time.js";
|
|
18
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
NonNegativeFiniteNumber,
|
|
22
|
+
NonNegativeInt,
|
|
23
|
+
onePositiveInt,
|
|
24
|
+
PositiveInt,
|
|
25
|
+
} from "./Type.js";
|
|
19
26
|
import type { Predicate } from "./Types.js";
|
|
20
27
|
|
|
21
28
|
/**
|
|
@@ -113,11 +120,11 @@ const createScheduleStepMetrics = (
|
|
|
113
120
|
return () => {
|
|
114
121
|
const now = deps.time.now();
|
|
115
122
|
const currentAttempt = attempt;
|
|
116
|
-
attempt = increment(attempt)
|
|
123
|
+
attempt = PositiveInt.orThrow(increment(attempt));
|
|
117
124
|
start ??= now;
|
|
118
|
-
const elapsed = (now - start)
|
|
125
|
+
const elapsed = saturateMillis(now - start);
|
|
119
126
|
const elapsedSincePrevious =
|
|
120
|
-
previous === null ?
|
|
127
|
+
previous === null ? minMillis : saturateMillis(now - previous);
|
|
121
128
|
previous = now;
|
|
122
129
|
return { attempt: currentAttempt, elapsed, elapsedSincePrevious };
|
|
123
130
|
};
|
|
@@ -235,18 +242,24 @@ export const spaced =
|
|
|
235
242
|
*
|
|
236
243
|
* @group Constructors
|
|
237
244
|
*/
|
|
238
|
-
export const exponential =
|
|
239
|
-
(
|
|
240
|
-
|
|
245
|
+
export const exponential = (base: Duration, factor = 2): Schedule<Millis> => {
|
|
246
|
+
assertType(
|
|
247
|
+
NonNegativeFiniteNumber,
|
|
248
|
+
factor,
|
|
249
|
+
"Expected factor to be a non-negative finite number.",
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return () => {
|
|
241
253
|
const baseMs = durationToMillis(base);
|
|
242
254
|
let attempt = 0;
|
|
243
255
|
return () => {
|
|
244
256
|
attempt++;
|
|
245
257
|
const rawDelay = baseMs * Math.pow(factor, attempt - 1);
|
|
246
|
-
const delay =
|
|
258
|
+
const delay = saturateMillis(rawDelay);
|
|
247
259
|
return ok([delay, delay]);
|
|
248
260
|
};
|
|
249
261
|
};
|
|
262
|
+
};
|
|
250
263
|
|
|
251
264
|
/**
|
|
252
265
|
* Linear backoff schedule.
|
|
@@ -276,7 +289,7 @@ export const linear =
|
|
|
276
289
|
let attempt = 0;
|
|
277
290
|
return () => {
|
|
278
291
|
attempt++;
|
|
279
|
-
const delay =
|
|
292
|
+
const delay = saturateMillis(ms * attempt);
|
|
280
293
|
return ok([delay, delay]);
|
|
281
294
|
};
|
|
282
295
|
};
|
|
@@ -308,12 +321,10 @@ export const fibonacci =
|
|
|
308
321
|
(initial: Duration): Schedule<Millis> =>
|
|
309
322
|
() => {
|
|
310
323
|
const ms = durationToMillis(initial);
|
|
311
|
-
let index = 1;
|
|
324
|
+
let index = FibonacciIndex.orThrow(1);
|
|
312
325
|
return () => {
|
|
313
|
-
const delay =
|
|
314
|
-
|
|
315
|
-
);
|
|
316
|
-
index++;
|
|
326
|
+
const delay = saturateMillis(ms * fibonacciAt(index));
|
|
327
|
+
index = FibonacciIndex.orNull(increment(index)) ?? index;
|
|
317
328
|
return ok([delay, delay]);
|
|
318
329
|
};
|
|
319
330
|
};
|
|
@@ -356,7 +367,7 @@ export const fixed =
|
|
|
356
367
|
const remainder = intervalMs === 0 ? 0 : elapsed % intervalMs;
|
|
357
368
|
const boundary = intervalMs - remainder;
|
|
358
369
|
const delay = runningBehind ? 0 : boundary;
|
|
359
|
-
return ok([count++, delay
|
|
370
|
+
return ok([count++, saturateMillis(delay)]);
|
|
360
371
|
};
|
|
361
372
|
};
|
|
362
373
|
|
|
@@ -388,7 +399,7 @@ export const windowed =
|
|
|
388
399
|
const { elapsed } = metrics();
|
|
389
400
|
const remainder = intervalMs === 0 ? 0 : elapsed % intervalMs;
|
|
390
401
|
const delay = intervalMs === 0 ? 0 : intervalMs - remainder;
|
|
391
|
-
return ok([count++, delay
|
|
402
|
+
return ok([count++, saturateMillis(delay)]);
|
|
392
403
|
};
|
|
393
404
|
};
|
|
394
405
|
|
|
@@ -563,18 +574,22 @@ export const unfoldSchedule =
|
|
|
563
574
|
*
|
|
564
575
|
* @group Limiting
|
|
565
576
|
*/
|
|
566
|
-
export const take =
|
|
567
|
-
(n
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
return
|
|
577
|
+
export const take = (n: number) => {
|
|
578
|
+
assertType(NonNegativeInt, n, "Expected n to be a non-negative integer.");
|
|
579
|
+
|
|
580
|
+
return <Output, Input>(
|
|
581
|
+
schedule: Schedule<Output, Input>,
|
|
582
|
+
): Schedule<Output, Input> =>
|
|
583
|
+
(deps) => {
|
|
584
|
+
const step = schedule(deps);
|
|
585
|
+
let attempt = 0;
|
|
586
|
+
return (input) => {
|
|
587
|
+
attempt++;
|
|
588
|
+
if (attempt > n) return err(done());
|
|
589
|
+
return step(input);
|
|
590
|
+
};
|
|
576
591
|
};
|
|
577
|
-
|
|
592
|
+
};
|
|
578
593
|
|
|
579
594
|
/**
|
|
580
595
|
* Limits schedule execution to a maximum elapsed time.
|
|
@@ -659,19 +674,27 @@ export const maxDelay = (max: Duration) => {
|
|
|
659
674
|
*
|
|
660
675
|
* @group Delay
|
|
661
676
|
*/
|
|
662
|
-
export const jitter =
|
|
663
|
-
(
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
677
|
+
export const jitter = (factor = 0.5) => {
|
|
678
|
+
assertType(
|
|
679
|
+
NonNegativeFiniteNumber,
|
|
680
|
+
factor,
|
|
681
|
+
"Expected factor to be a non-negative finite number.",
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
return <Output, Input>(
|
|
685
|
+
schedule: Schedule<Output, Input>,
|
|
686
|
+
): Schedule<Output, Input> =>
|
|
687
|
+
(deps) => {
|
|
688
|
+
const step = schedule(deps);
|
|
689
|
+
return (input) => {
|
|
690
|
+
const result = step(input);
|
|
691
|
+
if (!result.ok) return result;
|
|
692
|
+
const [output, delay] = result.value;
|
|
693
|
+
const jittered = delay * (1 - factor + deps.random.next() * 2 * factor);
|
|
694
|
+
return ok([output, saturateMillis(jittered)]);
|
|
695
|
+
};
|
|
673
696
|
};
|
|
674
|
-
|
|
697
|
+
};
|
|
675
698
|
|
|
676
699
|
/**
|
|
677
700
|
* Adds an initial delay before the first attempt.
|
|
@@ -755,7 +778,7 @@ export const modifyDelay =
|
|
|
755
778
|
const result = step(input);
|
|
756
779
|
if (!result.ok) return result;
|
|
757
780
|
const [output, delay] = result.value;
|
|
758
|
-
return ok([output,
|
|
781
|
+
return ok([output, saturateMillis(f(delay))]);
|
|
759
782
|
};
|
|
760
783
|
};
|
|
761
784
|
|
|
@@ -787,8 +810,7 @@ export const compensate =
|
|
|
787
810
|
const result = step(input);
|
|
788
811
|
if (!result.ok) return result;
|
|
789
812
|
const [output, delay] = result.value;
|
|
790
|
-
|
|
791
|
-
return ok([output, adjusted as Millis]);
|
|
813
|
+
return ok([output, saturateMillis(delay - elapsedSincePrevious)]);
|
|
792
814
|
};
|
|
793
815
|
};
|
|
794
816
|
|
|
@@ -1042,15 +1064,15 @@ export function passthrough<Output, Input>(
|
|
|
1042
1064
|
* // Outputs: 100, 300, 700, 1500, ... (cumulative)
|
|
1043
1065
|
*
|
|
1044
1066
|
* // Collect all outputs
|
|
1045
|
-
* const collected = foldSchedule
|
|
1046
|
-
*
|
|
1047
|
-
* delay,
|
|
1048
|
-
*
|
|
1067
|
+
* const collected = foldSchedule<ReadonlyArray<Millis>, Millis>(
|
|
1068
|
+
* [],
|
|
1069
|
+
* (acc, delay) => [...acc, delay],
|
|
1070
|
+
* )(take(3)(spaced("1s")));
|
|
1049
1071
|
* // Outputs: [1000], [1000, 1000], [1000, 1000, 1000]
|
|
1050
1072
|
*
|
|
1051
1073
|
* // Count attempts with custom output
|
|
1052
1074
|
* const counted = foldSchedule(
|
|
1053
|
-
* { attempts: 0, lastDelay:
|
|
1075
|
+
* { attempts: 0, lastDelay: minMillis },
|
|
1054
1076
|
* (acc, delay: Millis) => ({
|
|
1055
1077
|
* attempts: acc.attempts + 1,
|
|
1056
1078
|
* lastDelay: delay,
|
|
@@ -1338,7 +1360,7 @@ export const unionSchedules =
|
|
|
1338
1360
|
const [outputA, delayA] = resultA.value;
|
|
1339
1361
|
const [outputB, delayB] = resultB.value;
|
|
1340
1362
|
// Use minimum delay, output from the one with smaller delay
|
|
1341
|
-
const minDelay = Math.min(delayA, delayB)
|
|
1363
|
+
const minDelay = Millis.orThrow(Math.min(delayA, delayB));
|
|
1342
1364
|
return delayA <= delayB
|
|
1343
1365
|
? ok([outputA, minDelay])
|
|
1344
1366
|
: ok([outputB, minDelay]);
|
package/src/Set.ts
CHANGED
|
@@ -65,7 +65,7 @@ export function createSet<T>(
|
|
|
65
65
|
/** Possibly empty set. */
|
|
66
66
|
export function createSet<T>(items: ReadonlyArray<T>): ReadonlySet<T>;
|
|
67
67
|
export function createSet<T>(items: ReadonlyArray<T>): ReadonlySet<T> {
|
|
68
|
-
return new Set(items)
|
|
68
|
+
return new Set(items);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
@@ -154,7 +154,7 @@ export const deleteFromSet = <T>(
|
|
|
154
154
|
): ReadonlySet<T> => {
|
|
155
155
|
const next = new Set(set);
|
|
156
156
|
next.delete(item);
|
|
157
|
-
return next
|
|
157
|
+
return next;
|
|
158
158
|
};
|
|
159
159
|
|
|
160
160
|
/**
|
|
@@ -191,7 +191,7 @@ export function mapSet<T, U>(
|
|
|
191
191
|
for (const item of set) {
|
|
192
192
|
next.add(mapper(item));
|
|
193
193
|
}
|
|
194
|
-
return next
|
|
194
|
+
return next;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
/**
|
|
@@ -229,7 +229,7 @@ export function filterSet<T>(
|
|
|
229
229
|
next.add(item);
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
|
-
return next
|
|
232
|
+
return next;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
package/src/Sqlite.ts
CHANGED
|
@@ -191,6 +191,9 @@ export type SqliteRow = Record<string, SqliteValue>;
|
|
|
191
191
|
export interface SqliteDriver extends Disposable {
|
|
192
192
|
readonly exec: (query: SqliteQuery) => SqliteExecResult;
|
|
193
193
|
|
|
194
|
+
/** Disposes this driver, then deletes its persistent database file. */
|
|
195
|
+
readonly deleteDatabase: () => void;
|
|
196
|
+
|
|
194
197
|
/**
|
|
195
198
|
* Returns bytes backed by {@link ArrayBuffer}.
|
|
196
199
|
*
|
|
@@ -234,18 +237,18 @@ export const createSqlite =
|
|
|
234
237
|
async (run) => {
|
|
235
238
|
const { createSqliteDriver } = run.deps;
|
|
236
239
|
const console = run.deps.console.child("sql");
|
|
237
|
-
await using
|
|
240
|
+
await using disposer = new AsyncDisposableStack();
|
|
238
241
|
|
|
239
242
|
const driverResult = await run(createSqliteDriver(name, options));
|
|
240
243
|
if (!driverResult.ok) return driverResult;
|
|
241
|
-
const driver =
|
|
244
|
+
const driver = disposer.use(driverResult.value);
|
|
242
245
|
console.debug("SQLite driver created");
|
|
243
246
|
|
|
244
|
-
const
|
|
247
|
+
const disposables = disposer.move();
|
|
245
248
|
|
|
246
249
|
const sqlite: Sqlite = {
|
|
247
250
|
exec: <R extends SqliteRow = SqliteRow>(query: SqliteQuery) => {
|
|
248
|
-
assertNotDisposed(
|
|
251
|
+
assertNotDisposed(disposables);
|
|
249
252
|
console.debug({ query });
|
|
250
253
|
|
|
251
254
|
const label =
|
|
@@ -274,18 +277,17 @@ export const createSqlite =
|
|
|
274
277
|
},
|
|
275
278
|
|
|
276
279
|
transaction: ((callback: () => Result<unknown, unknown> | void) => {
|
|
277
|
-
assertNotDisposed(
|
|
280
|
+
assertNotDisposed(disposables);
|
|
278
281
|
console.debug("begin");
|
|
279
282
|
driver.exec(sql`begin;`);
|
|
280
283
|
|
|
284
|
+
using rollback = new DisposableStack();
|
|
281
285
|
let shouldRollback = true;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
},
|
|
288
|
-
};
|
|
286
|
+
rollback.defer(() => {
|
|
287
|
+
if (!shouldRollback) return;
|
|
288
|
+
console.debug("rollback");
|
|
289
|
+
driver.exec(sql`rollback;`);
|
|
290
|
+
});
|
|
289
291
|
|
|
290
292
|
const result = callback();
|
|
291
293
|
if (result != null && !result.ok) return result;
|
|
@@ -298,11 +300,11 @@ export const createSqlite =
|
|
|
298
300
|
}) as SqliteTransaction,
|
|
299
301
|
|
|
300
302
|
export: () => {
|
|
301
|
-
assertNotDisposed(
|
|
303
|
+
assertNotDisposed(disposables);
|
|
302
304
|
return driver.export();
|
|
303
305
|
},
|
|
304
306
|
|
|
305
|
-
[Symbol.asyncDispose]: () =>
|
|
307
|
+
[Symbol.asyncDispose]: () => disposables.disposeAsync(),
|
|
306
308
|
};
|
|
307
309
|
|
|
308
310
|
return ok(sqlite);
|
|
@@ -318,17 +320,18 @@ export const testSetupSqlite = async (
|
|
|
318
320
|
readonly sqlite: Sqlite;
|
|
319
321
|
}
|
|
320
322
|
> => {
|
|
321
|
-
await using
|
|
322
|
-
const run =
|
|
323
|
-
const sqlite =
|
|
323
|
+
await using disposer = new AsyncDisposableStack();
|
|
324
|
+
const run = disposer.use(testCreateRun(deps));
|
|
325
|
+
const sqlite = disposer.use(
|
|
324
326
|
await run.orThrow(createSqlite(testName, { mode: "memory" })),
|
|
325
327
|
);
|
|
326
|
-
const
|
|
328
|
+
const runWithSqlite = disposer.use(run.create({ ...run.deps, sqlite }));
|
|
329
|
+
const disposables = disposer.move();
|
|
327
330
|
|
|
328
331
|
return {
|
|
329
|
-
run:
|
|
332
|
+
run: runWithSqlite,
|
|
330
333
|
sqlite,
|
|
331
|
-
[Symbol.asyncDispose]: () =>
|
|
334
|
+
[Symbol.asyncDispose]: () => disposables.disposeAsync(),
|
|
332
335
|
};
|
|
333
336
|
};
|
|
334
337
|
|
|
@@ -377,27 +380,36 @@ export const createPreparedStatementsCache = <P>(
|
|
|
377
380
|
factory: (sql: SafeSql) => P,
|
|
378
381
|
disposeFn: (statement: P) => void,
|
|
379
382
|
): PreparedStatements<P> => {
|
|
380
|
-
|
|
381
|
-
const
|
|
383
|
+
using disposer = new DisposableStack();
|
|
384
|
+
const statementsBySql = new Map<SafeSql, P>();
|
|
385
|
+
|
|
386
|
+
disposer.defer(() => {
|
|
387
|
+
using disposer = new DisposableStack();
|
|
388
|
+
disposer.defer(() => {
|
|
389
|
+
statementsBySql.clear();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
for (const statement of statementsBySql.values()) {
|
|
393
|
+
disposer.adopt(statement, disposeFn);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const disposables = disposer.move();
|
|
382
398
|
|
|
383
399
|
return {
|
|
384
400
|
get: (query, alwaysPrepare) => {
|
|
401
|
+
assertNotDisposed(disposables);
|
|
385
402
|
if (alwaysPrepare !== true && !query.options?.prepare)
|
|
386
403
|
return null as never;
|
|
387
|
-
let statement =
|
|
404
|
+
let statement = statementsBySql.get(query.sql);
|
|
388
405
|
if (!statement) {
|
|
389
406
|
statement = factory(query.sql);
|
|
390
|
-
|
|
407
|
+
statementsBySql.set(query.sql, statement);
|
|
391
408
|
}
|
|
392
|
-
return statement
|
|
409
|
+
return statement;
|
|
393
410
|
},
|
|
394
411
|
|
|
395
|
-
[Symbol.dispose]: () =>
|
|
396
|
-
if (isDisposed) return;
|
|
397
|
-
isDisposed = true;
|
|
398
|
-
cache.forEach(disposeFn);
|
|
399
|
-
cache.clear();
|
|
400
|
-
},
|
|
412
|
+
[Symbol.dispose]: () => disposables.dispose(),
|
|
401
413
|
};
|
|
402
414
|
};
|
|
403
415
|
|