@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.
Files changed (81) hide show
  1. package/dist/src/Assert.d.ts +10 -5
  2. package/dist/src/Assert.d.ts.map +1 -1
  3. package/dist/src/Assert.js +10 -5
  4. package/dist/src/Error.d.ts +5 -3
  5. package/dist/src/Error.d.ts.map +1 -1
  6. package/dist/src/Error.js +16 -0
  7. package/dist/src/Identicon.js +4 -4
  8. package/dist/src/LockManager.d.ts +74 -0
  9. package/dist/src/LockManager.d.ts.map +1 -0
  10. package/dist/src/LockManager.js +103 -0
  11. package/dist/src/Microtask.d.ts.map +1 -1
  12. package/dist/src/Microtask.js +90 -26
  13. package/dist/src/Object.d.ts.map +1 -1
  14. package/dist/src/RefCount.d.ts.map +1 -1
  15. package/dist/src/RefCount.js +153 -68
  16. package/dist/src/Resource.d.ts +6 -6
  17. package/dist/src/Resource.d.ts.map +1 -1
  18. package/dist/src/Resource.js +211 -162
  19. package/dist/src/Schedule.d.ts +5 -5
  20. package/dist/src/Schedule.d.ts.map +1 -1
  21. package/dist/src/Schedule.js +53 -44
  22. package/dist/src/Sqlite.d.ts +2 -0
  23. package/dist/src/Sqlite.d.ts.map +1 -1
  24. package/dist/src/Sqlite.js +66 -41
  25. package/dist/src/Task.d.ts +99 -123
  26. package/dist/src/Task.d.ts.map +1 -1
  27. package/dist/src/Task.js +273 -256
  28. package/dist/src/Test.d.ts +14 -15
  29. package/dist/src/Test.d.ts.map +1 -1
  30. package/dist/src/Time.d.ts +5 -0
  31. package/dist/src/Time.d.ts.map +1 -1
  32. package/dist/src/Time.js +5 -0
  33. package/dist/src/Type.d.ts +7 -0
  34. package/dist/src/Type.d.ts.map +1 -1
  35. package/dist/src/Type.js +7 -0
  36. package/dist/src/Types.d.ts +0 -8
  37. package/dist/src/Types.d.ts.map +1 -1
  38. package/dist/src/WebSocket.d.ts.map +1 -1
  39. package/dist/src/WebSocket.js +7 -6
  40. package/dist/src/Worker.d.ts +42 -8
  41. package/dist/src/Worker.d.ts.map +1 -1
  42. package/dist/src/Worker.js +292 -112
  43. package/dist/src/index.d.ts +1 -0
  44. package/dist/src/index.d.ts.map +1 -1
  45. package/dist/src/index.js +1 -0
  46. package/dist/src/local-first/Db.d.ts +5 -4
  47. package/dist/src/local-first/Db.d.ts.map +1 -1
  48. package/dist/src/local-first/Db.js +43 -37
  49. package/dist/src/local-first/Evolu.d.ts +3 -2
  50. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  51. package/dist/src/local-first/Evolu.js +90 -80
  52. package/dist/src/local-first/Owner.d.ts.map +1 -1
  53. package/dist/src/local-first/Shared.d.ts +65 -54
  54. package/dist/src/local-first/Shared.d.ts.map +1 -1
  55. package/dist/src/local-first/Shared.js +390 -376
  56. package/package.json +1 -1
  57. package/src/Array.ts +8 -8
  58. package/src/Assert.ts +11 -6
  59. package/src/Error.ts +5 -4
  60. package/src/Identicon.ts +4 -4
  61. package/src/LockManager.ts +181 -0
  62. package/src/Microtask.ts +17 -11
  63. package/src/Object.ts +3 -3
  64. package/src/RefCount.ts +25 -21
  65. package/src/Resource.ts +63 -56
  66. package/src/Schedule.ts +70 -48
  67. package/src/Set.ts +4 -4
  68. package/src/Sqlite.ts +43 -31
  69. package/src/Task.ts +392 -430
  70. package/src/Test.ts +14 -15
  71. package/src/Time.ts +7 -0
  72. package/src/Type.ts +9 -0
  73. package/src/Types.ts +0 -9
  74. package/src/WebSocket.ts +7 -6
  75. package/src/Worker.ts +195 -53
  76. package/src/index.ts +1 -0
  77. package/src/local-first/Db.ts +86 -61
  78. package/src/local-first/Evolu.ts +74 -68
  79. package/src/local-first/Owner.ts +1 -1
  80. package/src/local-first/Query.ts +1 -1
  81. package/src/local-first/Shared.ts +485 -476
package/dist/src/Task.js CHANGED
@@ -56,7 +56,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
56
56
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
57
57
  });
58
58
  import { arrayFrom, emptyArray, isNonEmptyArray, mapArray, } from "./Array.js";
59
- import { assert } from "./Assert.js";
59
+ import { assert, assertNotDisposed } from "./Assert.js";
60
60
  import { createConsole } from "./Console.js";
61
61
  import { createRandomBytes } from "./Crypto.js";
62
62
  import { eqArrayStrict } from "./Eq.js";
@@ -154,7 +154,7 @@ export const RunEvent = /*#__PURE__*/ object({
154
154
  // TODO:
155
155
  // Partial<TracerConfigDep> & // TODO:
156
156
  // Partial<TracerDep>; // TODO:
157
- const runDeps = {
157
+ const runDefaultDeps = {
158
158
  console: createConsole(),
159
159
  randomBytes: createRandomBytes(),
160
160
  random: createRandom(),
@@ -175,7 +175,7 @@ const runDeps = {
175
175
  * Node.js `uncaughtException`, `unhandledRejection`, and graceful shutdown
176
176
  * handling, and `@evolu/react-native` adds React Native global error handling.
177
177
  *
178
- * {@link RunDeps} provides default dependencies:
178
+ * {@link RunDefaultDeps} provides default dependencies:
179
179
  *
180
180
  * - {@link Time}
181
181
  * - {@link Console}
@@ -213,7 +213,7 @@ const runDeps = {
213
213
  * };
214
214
  *
215
215
  * // Composition root: create a Run with custom deps
216
- * type AppDeps = RunDeps & ConfigDep;
216
+ * type AppDeps = RunDefaultDeps & ConfigDep;
217
217
  *
218
218
  * const appDeps: AppDeps = {
219
219
  * ...testCreateDeps(), // or spread individual deps
@@ -226,17 +226,14 @@ const runDeps = {
226
226
  * const result = await run(fetchUser("123"));
227
227
  *
228
228
  * // TypeScript catches missing deps at compile time:
229
- * // await using run2 = createRun(); // Run<RunDeps>
229
+ * // await using run2 = createRun(); // Run<RunDefaultDeps>
230
230
  * // run2(fetchUser("123")); // Error: Property 'config' is missing
231
231
  * ```
232
232
  *
233
233
  * @group Creating Run
234
234
  */
235
- export const createRun = (deps) => {
236
- const mergedDeps = { ...runDeps, ...deps };
237
- return createRunInternal(createRef(mergedDeps))();
238
- };
239
- const createRunInternal = (depsRef) => (parent, daemon, abortBehavior, concurrencyBehavior) => {
235
+ export const createRun = (deps) => createRunInternal({ ...runDefaultDeps, ...deps });
236
+ const createRunInternal = (deps, parent, daemon, abortBehavior, concurrencyBehavior) => {
240
237
  const parentMask = parent?.abortMask ?? isAbortable;
241
238
  let abortMask;
242
239
  switch (abortBehavior) {
@@ -256,8 +253,7 @@ const createRunInternal = (depsRef) => (parent, daemon, abortBehavior, concurren
256
253
  let result;
257
254
  let outcome;
258
255
  let children = emptySet;
259
- const requestAbort = (reason) => {
260
- const abortError = reason;
256
+ const requestAbort = (abortError) => {
261
257
  if (abortMask === isAbortable)
262
258
  signalController.abort(abortError);
263
259
  requestController.abort(abortError);
@@ -266,7 +262,6 @@ const createRunInternal = (depsRef) => (parent, daemon, abortBehavior, concurren
266
262
  subscribeToAbort(parent.requestSignal, () => requestAbort(parent.requestSignal.reason), { signal: requestController.signal });
267
263
  }
268
264
  const emitEvent = (data) => {
269
- const deps = depsRef.get();
270
265
  if (!deps.runConfig?.eventsEnabled.get())
271
266
  return;
272
267
  const e = { id: self.id, timestamp: deps.time.now(), data };
@@ -274,16 +269,28 @@ const createRunInternal = (depsRef) => (parent, daemon, abortBehavior, concurren
274
269
  node.onEvent?.(e);
275
270
  }
276
271
  };
277
- const run = (task) => {
278
- const childRun = createRunInternal(depsRef)(self, daemon ?? self, getAbortBehavior(task), getConcurrencyBehavior(task));
272
+ const run = (task, taskDeps) => {
273
+ const childRun = createRunInternal(taskDeps === undefined
274
+ ? deps
275
+ : {
276
+ console: deps.console,
277
+ randomBytes: deps.randomBytes,
278
+ random: deps.random,
279
+ time: deps.time,
280
+ ...(deps.runConfig && {
281
+ runConfig: deps.runConfig,
282
+ }),
283
+ ...taskDeps,
284
+ }, self, daemon ?? self, getAbortBehavior(task), getConcurrencyBehavior(task));
279
285
  if (state !== running) {
280
286
  childRun.requestAbort(runStoppedAbortError);
281
287
  task = () => err(runStoppedAbortError);
282
288
  }
283
289
  else if (signalController.signal.aborted &&
284
290
  childRun.abortMask === isAbortable) {
285
- childRun.requestAbort(signalController.signal.reason);
286
- task = () => err(signalController.signal.reason);
291
+ const abortError = signalController.signal.reason;
292
+ childRun.requestAbort(abortError);
293
+ task = () => err(abortError);
287
294
  }
288
295
  const childFiber = Object.assign(Promise.try(task, childRun)
289
296
  .then(childRun.handleTaskFulfilled)
@@ -306,13 +313,17 @@ const createRunInternal = (depsRef) => (parent, daemon, abortBehavior, concurren
306
313
  emitEvent({ type: "ChildAdded", childId: childRun.id });
307
314
  return childFiber;
308
315
  };
316
+ let snapshot = null;
317
+ let disposingPromise = null;
309
318
  const self = run;
310
319
  {
311
320
  const run = self;
312
- const id = createId(depsRef.get());
313
- let snapshot = null;
314
- let disposingPromise = null;
315
- run.orThrow = async (task) => getOrThrow(await self(task));
321
+ const id = createId(deps);
322
+ async function orThrow(task, taskDeps) {
323
+ const result = taskDeps === undefined ? await self(task) : await self(task, taskDeps);
324
+ return getOrThrow(result);
325
+ }
326
+ run.orThrow = orThrow;
316
327
  run.id = id;
317
328
  run.parent = parent ?? null;
318
329
  run.signal = signalController.signal;
@@ -330,45 +341,38 @@ const createRunInternal = (depsRef) => (parent, daemon, abortBehavior, concurren
330
341
  !eqArrayStrict(snapshot.children, childSnapshots)) {
331
342
  snapshot = {
332
343
  id,
333
- state: state,
344
+ state,
334
345
  children: childSnapshots,
335
346
  abortMask,
336
347
  };
337
348
  }
338
349
  return snapshot;
339
350
  };
351
+ run.onEvent = undefined;
340
352
  run.daemon = daemon ?? self;
341
- run.create = () => run.daemon(createDeferred().task).run;
342
- Object.defineProperty(run, "deps", { get: depsRef.get });
353
+ run.create = (runDeps = deps) => {
354
+ const task = createDeferred().task;
355
+ return run.daemon(task, runDeps).run;
356
+ };
357
+ run.deps = deps;
343
358
  run.concurrency =
344
359
  concurrencyBehavior ?? parent?.concurrency ?? defaultConcurrency;
345
- run.addDeps = (newDeps) => {
346
- depsRef.modify((currentDeps) => {
347
- const duplicate = Object.keys(newDeps).find((k) => k in currentDeps);
348
- assert(!duplicate, `Dependency '${duplicate}' already added. ` +
349
- `This assert ensures dependencies are created once. ` +
350
- `Automatic deduplication would mask bugs.`);
351
- return [undefined, { ...currentDeps, ...newDeps }];
352
- });
353
- return self;
354
- };
355
360
  run[Symbol.asyncDispose] = () => {
356
361
  if (disposingPromise)
357
362
  return disposingPromise;
358
363
  state = { type: "Disposing" };
359
364
  emitEvent({ type: "StateChanged", state });
360
365
  requestAbort(runStoppedAbortError);
361
- disposingPromise = Promise.allSettled((run.ownTaskSettled
362
- ? [run.ownTaskSettled.promise, ...children]
366
+ disposingPromise = Promise.allSettled((self.ownTaskSettled
367
+ ? [self.ownTaskSettled.promise, ...children]
363
368
  : children))
364
369
  .then(lazyVoid)
365
370
  .finally(() => {
366
371
  /**
367
- * Root and daemon Runs have no own Task, so
368
- * `run.handleTaskFulfilled` never populates their terminal values.
369
- * In that case disposal publishes `ok()` for both `result` and
370
- * `outcome`. Task-backed Runs normally reach this point with both
371
- * values already set.
372
+ * Root and daemon Runs have no own Task, so `run.handleTaskFulfilled`
373
+ * never populates their terminal values. In that case disposal
374
+ * publishes `ok()` for both `result` and `outcome`. Task-backed Runs
375
+ * normally reach this point with both values already set.
372
376
  */
373
377
  [result, outcome] = [result ?? ok(), outcome ?? ok()];
374
378
  state = { type: "Settled", result, outcome };
@@ -381,15 +385,15 @@ const createRunInternal = (depsRef) => (parent, daemon, abortBehavior, concurren
381
385
  run.requestAbort = requestAbort;
382
386
  run.requestSignal = requestController.signal;
383
387
  run.handleTaskFulfilled = (taskOutcome) => {
384
- const taskResult = run.signal.aborted
385
- ? err(run.signal.reason)
388
+ const taskResult = self.signal.aborted
389
+ ? err(self.signal.reason)
386
390
  : taskOutcome;
387
391
  result = taskResult;
388
392
  outcome = taskOutcome;
389
393
  return taskResult;
390
394
  };
391
395
  run.handleTaskSettled = () => {
392
- run.ownTaskSettled?.resolve();
396
+ self.ownTaskSettled?.resolve();
393
397
  };
394
398
  }
395
399
  return self;
@@ -432,16 +436,19 @@ const abortBehavior = (behavior) => (task) => Object.assign((run) => run(task),
432
436
  /**
433
437
  * Makes a {@link Task} unabortable.
434
438
  *
435
- * Once started, an unabortable Task always completes abort requests are
436
- * ignored and `signal.aborted` remains `false`.
439
+ * Once started, an unabortable Task always completes. Abort requests are masked
440
+ * while it runs, and `signal.aborted` remains `false` inside the Task.
437
441
  *
438
- * If the parent {@link Run} is already disposing or settled, `run(task)`
442
+ * `unabortable` controls abort signal propagation; it does not force work to
443
+ * start. If the parent {@link Run} is already disposing or settled, `run(task)`
439
444
  * short-circuits before task execution and returns `err(AbortError)` with
440
445
  * {@link runStoppedError} as reason. So `unabortable` means “do not interrupt
441
446
  * this Task once it has started”, not “remove AbortError from its type”.
442
447
  *
443
- * When that pre-start abort would be a programmer error, assert it explicitly
444
- * with `assertNotAborted` after awaiting the result.
448
+ * Most callers should still propagate or ignore {@link AbortError} according to
449
+ * ordinary structured-concurrency ownership. When abort would violate a
450
+ * lifecycle invariant, await the unabortable Task and use
451
+ * {@link assertNotAborted} to fail fast if it could not even start.
445
452
  *
446
453
  * ### Example
447
454
  *
@@ -804,7 +811,7 @@ export const timeoutError = { type: "TimeoutError" };
804
811
  * @group Composition
805
812
  * @see {@link RetryOptions}
806
813
  */
807
- export const retry = (task, schedule, { retryable = lazyTrue, onRetry, } = {}) => async (run) => {
814
+ export const retry = (task, schedule, { retryable = lazyTrue, onRetry } = {}) => async (run) => {
808
815
  const step = schedule(run.deps);
809
816
  let attempt = onePositiveInt;
810
817
  let error;
@@ -887,7 +894,7 @@ export const retry = (task, schedule, { retryable = lazyTrue, onRetry, } = {}) =
887
894
  *
888
895
  * @group Composition
889
896
  */
890
- export const repeat = (task, schedule, { repeatable = lazyTrue, onRepeat, } = {}) => async (run) => {
897
+ export const repeat = (task, schedule, { repeatable = lazyTrue, onRepeat } = {}) => async (run) => {
891
898
  const step = schedule(run.deps);
892
899
  let lastResult;
893
900
  let attempt = onePositiveInt;
@@ -998,37 +1005,51 @@ export const deferredDisposedError = {
998
1005
  * @group Concurrency primitives
999
1006
  */
1000
1007
  export const createGate = () => {
1001
- let isOpen = false;
1002
- let disposed = false;
1003
- let deferred = createDeferred();
1004
- return {
1005
- wait: (run) => {
1006
- if (disposed)
1007
- return err(deferredDisposedError);
1008
- if (isOpen)
1009
- return ok();
1010
- return run(deferred.task);
1011
- },
1012
- open: () => {
1013
- if (disposed || isOpen)
1014
- return;
1015
- isOpen = true;
1016
- deferred.resolve(ok());
1017
- },
1018
- close: () => {
1019
- if (disposed || !isOpen)
1020
- return;
1021
- isOpen = false;
1022
- deferred = createDeferred();
1023
- },
1024
- isOpen: () => isOpen,
1025
- [Symbol.dispose]: () => {
1026
- if (disposed)
1027
- return;
1028
- disposed = true;
1008
+ const env_1 = { stack: [], error: void 0, hasError: false };
1009
+ try {
1010
+ const disposer = __addDisposableResource(env_1, new DisposableStack(), false);
1011
+ let isOpen = false;
1012
+ let deferred = createDeferred();
1013
+ disposer.defer(() => {
1029
1014
  deferred[Symbol.dispose]();
1030
- },
1031
- };
1015
+ });
1016
+ const disposables = disposer.move();
1017
+ return {
1018
+ wait: (run) => {
1019
+ if (disposables.disposed)
1020
+ return err(deferredDisposedError);
1021
+ if (isOpen)
1022
+ return ok();
1023
+ return run(deferred.task);
1024
+ },
1025
+ open: () => {
1026
+ assertNotDisposed(disposables);
1027
+ if (isOpen)
1028
+ return;
1029
+ isOpen = true;
1030
+ deferred.resolve(ok());
1031
+ },
1032
+ close: () => {
1033
+ assertNotDisposed(disposables);
1034
+ if (!isOpen)
1035
+ return;
1036
+ isOpen = false;
1037
+ deferred = createDeferred();
1038
+ },
1039
+ isOpen: () => {
1040
+ assertNotDisposed(disposables);
1041
+ return isOpen;
1042
+ },
1043
+ [Symbol.dispose]: () => disposables.dispose(),
1044
+ };
1045
+ }
1046
+ catch (e_1) {
1047
+ env_1.error = e_1;
1048
+ env_1.hasError = true;
1049
+ }
1050
+ finally {
1051
+ __disposeResources(env_1);
1052
+ }
1032
1053
  };
1033
1054
  /**
1034
1055
  * Creates a {@link Semaphore} that limits concurrent {@link Task}s.
@@ -1071,85 +1092,18 @@ export const createGate = () => {
1071
1092
  * @group Concurrency primitives
1072
1093
  */
1073
1094
  export const createSemaphore = (permits) => {
1074
- const fibers = new Set();
1075
- const waiters = [];
1076
- let taken = zeroNonNegativeInt;
1077
- let disposed = false;
1078
- const withPermits = (requestedPermits) => (task) => async (run) => {
1079
- const env_1 = { stack: [], error: void 0, hasError: false };
1080
- try {
1081
- const requested = PositiveInt.orThrow(requestedPermits);
1082
- assert(requested <= permits, "Requested permits must not exceed semaphore capacity.");
1083
- if (disposed)
1084
- return err(semaphoreDisposedAbortError);
1085
- if (waiters.length > 0 || taken + requested > permits) {
1086
- const waiter = Promise.withResolvers();
1087
- const waiting = {
1088
- permits: requested,
1089
- resolve: waiter.resolve,
1090
- };
1091
- waiters.push(waiting);
1092
- run.onAbort((reason) => {
1093
- const i = waiters.indexOf(waiting);
1094
- if (i >= 0)
1095
- waiters.splice(i, 1);
1096
- waiter.resolve(err(createAbortError(reason)));
1097
- });
1098
- const permit = await waiter.promise;
1099
- if (!permit.ok)
1100
- return permit;
1101
- }
1102
- else {
1103
- taken = NonNegativeInt.orThrow(taken + requested);
1104
- }
1105
- let fiber = null;
1106
- const _ = __addDisposableResource(env_1, {
1107
- [Symbol.dispose]: () => {
1108
- if (fiber)
1109
- fibers.delete(fiber);
1110
- taken = NonNegativeInt.orThrow(taken - requested);
1111
- while (waiters.length > 0) {
1112
- const waiter = waiters[0];
1113
- if (taken + waiter.permits > permits)
1114
- break;
1115
- waiters.shift();
1116
- taken = NonNegativeInt.orThrow(taken + waiter.permits);
1117
- waiter.resolve(ok());
1118
- }
1119
- },
1120
- }, false);
1121
- fiber = run(task);
1122
- fibers.add(fiber);
1123
- return await fiber;
1124
- }
1125
- catch (e_1) {
1126
- env_1.error = e_1;
1127
- env_1.hasError = true;
1128
- }
1129
- finally {
1130
- __disposeResources(env_1);
1131
- }
1132
- };
1133
- return {
1134
- withPermits,
1135
- withPermit: (task) => withPermits(1)(task),
1136
- snapshot: () => ({
1137
- permits,
1138
- taken,
1139
- waiting: NonNegativeInt.orThrow(waiters.length),
1140
- available: NonNegativeInt.orThrow(permits - taken),
1141
- isIdle: taken === 0 && waiters.length === 0,
1142
- disposed,
1143
- }),
1144
- [Symbol.dispose]: () => {
1145
- const env_2 = { stack: [], error: void 0, hasError: false };
1095
+ const env_2 = { stack: [], error: void 0, hasError: false };
1096
+ try {
1097
+ const disposer = __addDisposableResource(env_2, new DisposableStack(), false);
1098
+ const fibers = new Set();
1099
+ const waiters = [];
1100
+ let taken = zeroNonNegativeInt;
1101
+ disposer.defer(() => {
1102
+ const env_3 = { stack: [], error: void 0, hasError: false };
1146
1103
  try {
1147
- if (disposed)
1148
- return;
1149
- disposed = true;
1150
- const stack = __addDisposableResource(env_2, new DisposableStack(), false);
1104
+ const disposer = __addDisposableResource(env_3, new DisposableStack(), false);
1151
1105
  for (const fiber of fibers) {
1152
- stack.adopt(fiber, (fiber) => {
1106
+ disposer.adopt(fiber, (fiber) => {
1153
1107
  fiber.abort(semaphoreDisposedError);
1154
1108
  });
1155
1109
  }
@@ -1158,15 +1112,94 @@ export const createSemaphore = (permits) => {
1158
1112
  }
1159
1113
  waiters.length = 0;
1160
1114
  }
1161
- catch (e_2) {
1162
- env_2.error = e_2;
1163
- env_2.hasError = true;
1115
+ catch (e_3) {
1116
+ env_3.error = e_3;
1117
+ env_3.hasError = true;
1164
1118
  }
1165
1119
  finally {
1166
- __disposeResources(env_2);
1120
+ __disposeResources(env_3);
1167
1121
  }
1168
- },
1169
- };
1122
+ });
1123
+ const disposables = disposer.move();
1124
+ const withPermits = (requestedPermits) => (task) => async (run) => {
1125
+ const env_4 = { stack: [], error: void 0, hasError: false };
1126
+ try {
1127
+ const requested = PositiveInt.orThrow(requestedPermits);
1128
+ assert(requested <= permits, "Requested permits must not exceed semaphore capacity.");
1129
+ if (disposables.disposed)
1130
+ return err(semaphoreDisposedAbortError);
1131
+ if (waiters.length > 0 || taken + requested > permits) {
1132
+ const waiter = Promise.withResolvers();
1133
+ const waiting = {
1134
+ permits: requested,
1135
+ resolve: waiter.resolve,
1136
+ };
1137
+ waiters.push(waiting);
1138
+ run.onAbort((reason) => {
1139
+ const i = waiters.indexOf(waiting);
1140
+ if (i >= 0)
1141
+ waiters.splice(i, 1);
1142
+ waiter.resolve(err(createAbortError(reason)));
1143
+ });
1144
+ const permit = await waiter.promise;
1145
+ if (!permit.ok)
1146
+ return permit;
1147
+ }
1148
+ else {
1149
+ taken = NonNegativeInt.orThrow(taken + requested);
1150
+ }
1151
+ let fiber = null;
1152
+ const _ = __addDisposableResource(env_4, {
1153
+ [Symbol.dispose]: () => {
1154
+ if (fiber)
1155
+ fibers.delete(fiber);
1156
+ taken = NonNegativeInt.orThrow(taken - requested);
1157
+ while (waiters.length > 0) {
1158
+ const waiter = waiters[0];
1159
+ if (taken + waiter.permits > permits)
1160
+ break;
1161
+ waiters.shift();
1162
+ taken = NonNegativeInt.orThrow(taken + waiter.permits);
1163
+ waiter.resolve(ok());
1164
+ }
1165
+ },
1166
+ }, false);
1167
+ fiber = run(task);
1168
+ fibers.add(fiber);
1169
+ return await fiber;
1170
+ }
1171
+ catch (e_4) {
1172
+ env_4.error = e_4;
1173
+ env_4.hasError = true;
1174
+ }
1175
+ finally {
1176
+ __disposeResources(env_4);
1177
+ }
1178
+ };
1179
+ return {
1180
+ withPermits,
1181
+ withPermit: (task) => withPermits(1)(task),
1182
+ snapshot: () => {
1183
+ assertNotDisposed(disposables);
1184
+ return {
1185
+ permits,
1186
+ taken,
1187
+ waiting: NonNegativeInt.orThrow(waiters.length),
1188
+ available: NonNegativeInt.orThrow(permits - taken),
1189
+ isIdle: taken === 0 && waiters.length === 0,
1190
+ disposed: disposables.disposed,
1191
+ };
1192
+ },
1193
+ [Symbol.dispose]: () => disposables.dispose(),
1194
+ };
1195
+ }
1196
+ catch (e_2) {
1197
+ env_2.error = e_2;
1198
+ env_2.hasError = true;
1199
+ }
1200
+ finally {
1201
+ __disposeResources(env_2);
1202
+ }
1170
1203
  };
1171
1204
  /**
1172
1205
  * Abort reason used when a {@link Semaphore} is disposed.
@@ -1184,64 +1217,80 @@ export const semaphoreDisposedError = {
1184
1217
  };
1185
1218
  const semaphoreDisposedAbortError = createAbortError(semaphoreDisposedError);
1186
1219
  export function createSemaphoreByKey(permits, { lookup = identity } = {}) {
1187
- const semaphoresByKey = createLookupMap({
1188
- lookup,
1189
- });
1190
- let disposed = false;
1191
- const withPermits = (key, requestedPermits) => (task) => async (run) => {
1192
- const env_3 = { stack: [], error: void 0, hasError: false };
1193
- try {
1194
- if (disposed)
1195
- return err(semaphoreDisposedAbortError);
1196
- let semaphore = semaphoresByKey.get(key);
1197
- if (!semaphore) {
1198
- semaphore = createSemaphore(permits);
1199
- semaphoresByKey.set(key, semaphore);
1200
- }
1201
- const _ = __addDisposableResource(env_3, {
1202
- [Symbol.dispose]: () => {
1203
- const snapshot = semaphore.snapshot();
1204
- if (snapshot.isIdle) {
1205
- semaphoresByKey.delete(key);
1206
- semaphore[Symbol.dispose]();
1207
- }
1208
- },
1209
- }, false);
1210
- return await run(semaphore.withPermits(requestedPermits)(task));
1211
- }
1212
- catch (e_3) {
1213
- env_3.error = e_3;
1214
- env_3.hasError = true;
1215
- }
1216
- finally {
1217
- __disposeResources(env_3);
1218
- }
1219
- };
1220
- return {
1221
- withPermit: (key, task) => withPermits(key, 1)(task),
1222
- withPermits,
1223
- snapshot: (key) => semaphoresByKey.get(key)?.snapshot() ?? null,
1224
- [Symbol.dispose]: () => {
1225
- const env_4 = { stack: [], error: void 0, hasError: false };
1220
+ const env_5 = { stack: [], error: void 0, hasError: false };
1221
+ try {
1222
+ const disposer = __addDisposableResource(env_5, new DisposableStack(), false);
1223
+ const semaphoresByKey = createLookupMap({
1224
+ lookup,
1225
+ });
1226
+ disposer.defer(() => {
1227
+ const env_6 = { stack: [], error: void 0, hasError: false };
1226
1228
  try {
1227
- if (disposed)
1228
- return;
1229
- disposed = true;
1230
- const stack = __addDisposableResource(env_4, new DisposableStack(), false);
1229
+ const disposer = __addDisposableResource(env_6, new DisposableStack(), false);
1230
+ disposer.defer(() => {
1231
+ semaphoresByKey.clear();
1232
+ });
1231
1233
  for (const semaphore of semaphoresByKey.values()) {
1232
- stack.use(semaphore);
1234
+ disposer.use(semaphore);
1233
1235
  }
1234
- semaphoresByKey.clear();
1235
1236
  }
1236
- catch (e_4) {
1237
- env_4.error = e_4;
1238
- env_4.hasError = true;
1237
+ catch (e_6) {
1238
+ env_6.error = e_6;
1239
+ env_6.hasError = true;
1239
1240
  }
1240
1241
  finally {
1241
- __disposeResources(env_4);
1242
+ __disposeResources(env_6);
1242
1243
  }
1243
- },
1244
- };
1244
+ });
1245
+ const disposables = disposer.move();
1246
+ const withPermits = (key, requestedPermits) => (task) => async (run) => {
1247
+ const env_7 = { stack: [], error: void 0, hasError: false };
1248
+ try {
1249
+ if (disposables.disposed)
1250
+ return err(semaphoreDisposedAbortError);
1251
+ let semaphore = semaphoresByKey.get(key);
1252
+ if (!semaphore) {
1253
+ semaphore = createSemaphore(permits);
1254
+ semaphoresByKey.set(key, semaphore);
1255
+ }
1256
+ const _ = __addDisposableResource(env_7, {
1257
+ [Symbol.dispose]: () => {
1258
+ if (semaphoresByKey.get(key) !== semaphore)
1259
+ return;
1260
+ const snapshot = semaphore.snapshot();
1261
+ if (snapshot.isIdle) {
1262
+ semaphoresByKey.delete(key);
1263
+ semaphore[Symbol.dispose]();
1264
+ }
1265
+ },
1266
+ }, false);
1267
+ return await run(semaphore.withPermits(requestedPermits)(task));
1268
+ }
1269
+ catch (e_7) {
1270
+ env_7.error = e_7;
1271
+ env_7.hasError = true;
1272
+ }
1273
+ finally {
1274
+ __disposeResources(env_7);
1275
+ }
1276
+ };
1277
+ return {
1278
+ withPermit: (key, task) => withPermits(key, 1)(task),
1279
+ withPermits,
1280
+ snapshot: (key) => {
1281
+ assertNotDisposed(disposables);
1282
+ return semaphoresByKey.get(key)?.snapshot() ?? null;
1283
+ },
1284
+ [Symbol.dispose]: () => disposables.dispose(),
1285
+ };
1286
+ }
1287
+ catch (e_5) {
1288
+ env_5.error = e_5;
1289
+ env_5.hasError = true;
1290
+ }
1291
+ finally {
1292
+ __disposeResources(env_5);
1293
+ }
1245
1294
  }
1246
1295
  /**
1247
1296
  * Creates a {@link Mutex}.
@@ -1316,39 +1365,6 @@ export const createMutexRef = (initialValue) => {
1316
1365
  [Symbol.dispose]: mutex[Symbol.dispose],
1317
1366
  };
1318
1367
  };
1319
- /**
1320
- * Creates an in-process {@link LeaderLock}.
1321
- *
1322
- * Uses one {@link Mutex} per {@link Name}. Suitable for runtimes without a
1323
- * cross-process lock manager (for example in-memory worker tests or React
1324
- * Native).
1325
- *
1326
- * @group Concurrency primitives
1327
- */
1328
- export const createInMemoryLeaderLock = () => {
1329
- const mutexByName = createMutexByKey();
1330
- return {
1331
- lock: (name) => async (run) => {
1332
- const leaseRun = run.create();
1333
- const released = createDeferred();
1334
- const acquired = createDeferred();
1335
- void leaseRun(mutexByName.withLock(name, async (run) => {
1336
- acquired.resolve(ok());
1337
- await run(released.task);
1338
- return ok();
1339
- }));
1340
- const acquiredResult = await run(acquired.task);
1341
- if (!acquiredResult.ok) {
1342
- assert(AbortError.is(acquiredResult.error), "Leader lock acquisition deferred must not be disposed.");
1343
- void leaseRun[Symbol.asyncDispose]();
1344
- return err(acquiredResult.error);
1345
- }
1346
- return ok({
1347
- [Symbol.asyncDispose]: leaseRun[Symbol.asyncDispose],
1348
- });
1349
- },
1350
- };
1351
- };
1352
1368
  export function all(input, options) {
1353
1369
  return collect("all", input, options);
1354
1370
  }
@@ -1433,7 +1449,8 @@ export const anyAbortError = { type: "AnyAbortError" };
1433
1449
  const collect = (type, input, { collect = true, abortReason = type === "all" ? allAbortError : allSettledAbortError, } = {}) => {
1434
1450
  const stopOn = type === "all" ? "error" : null;
1435
1451
  if (isIterable(input)) {
1436
- const array = arrayFrom(input);
1452
+ const tasks = input;
1453
+ const array = arrayFrom(tasks);
1437
1454
  if (!isNonEmptyArray(array))
1438
1455
  return () => ok(collect ? emptyArray : undefined);
1439
1456
  return pool(array, {
@@ -1470,7 +1487,7 @@ function pool(tasksIterable, { stopOn = null, collect, abortReason, allFailed, }
1470
1487
  if (length === 0)
1471
1488
  return () => ok(emptyArray);
1472
1489
  return async (run) => {
1473
- const env_5 = { stack: [], error: void 0, hasError: false };
1490
+ const env_8 = { stack: [], error: void 0, hasError: false };
1474
1491
  try {
1475
1492
  const results = collect ? new Array(length) : null;
1476
1493
  const aborted = Promise.withResolvers();
@@ -1519,8 +1536,8 @@ function pool(tasksIterable, { stopOn = null, collect, abortReason, allFailed, }
1519
1536
  worker.abort(reason);
1520
1537
  };
1521
1538
  const workerCount = Math.min(run.concurrency, length);
1522
- const workers = arrayFrom(workerCount, () => run.daemon(worker));
1523
- const _ = __addDisposableResource(env_5, new DisposableStack(), false);
1539
+ const workers = arrayFrom(workerCount, () => run.daemon(worker, run.deps));
1540
+ const _ = __addDisposableResource(env_8, new DisposableStack(), false);
1524
1541
  _.defer(() => {
1525
1542
  abortWorkers(abortReason);
1526
1543
  });
@@ -1546,12 +1563,12 @@ function pool(tasksIterable, { stopOn = null, collect, abortReason, allFailed, }
1546
1563
  return ok();
1547
1564
  return allFailed === "completion" ? lastResult : lastIndexResult;
1548
1565
  }
1549
- catch (e_5) {
1550
- env_5.error = e_5;
1551
- env_5.hasError = true;
1566
+ catch (e_8) {
1567
+ env_8.error = e_8;
1568
+ env_8.hasError = true;
1552
1569
  }
1553
1570
  finally {
1554
- __disposeResources(env_5);
1571
+ __disposeResources(env_8);
1555
1572
  }
1556
1573
  };
1557
1574
  }