@actuallab/core 13.0.25 → 13.0.167

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/index.cjs CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  AsyncContext: () => AsyncContext,
24
24
  AsyncContextKey: () => AsyncContextKey,
25
25
  AsyncLock: () => AsyncLock,
26
+ AsyncSignal: () => AsyncSignal,
26
27
  DisposableBag: () => DisposableBag,
27
28
  EventHandlerSet: () => EventHandlerSet,
28
29
  Log: () => Log,
@@ -41,6 +42,8 @@ __export(index_exports, {
41
42
  TimeoutError: () => TimeoutError,
42
43
  abortPromise: () => abortPromise,
43
44
  abortSignalKey: () => abortSignalKey,
45
+ awaitWithCleanup: () => awaitWithCleanup,
46
+ cancellationError: () => cancellationError,
44
47
  catchErrors: () => catchErrors,
45
48
  createLogProvider: () => createLogProvider,
46
49
  debounce: () => debounce,
@@ -49,6 +52,9 @@ __export(index_exports, {
49
52
  errorResult: () => errorResult,
50
53
  initLogging: () => initLogging,
51
54
  initWorkerLogging: () => initWorkerLogging,
55
+ isCancellation: () => isCancellation,
56
+ ownMetadata: () => ownMetadata,
57
+ resolveArgCount: () => resolveArgCount,
52
58
  resolvedVoidPromise: () => resolvedVoidPromise,
53
59
  result: () => result,
54
60
  resultFrom: () => resultFrom,
@@ -501,9 +507,9 @@ var Result = class _Result {
501
507
  hasError;
502
508
  _value;
503
509
  _error;
504
- constructor(value, error) {
505
- this.hasValue = error === void 0;
506
- this.hasError = !this.hasValue;
510
+ constructor(value, error, hasError = error !== void 0) {
511
+ this.hasError = hasError;
512
+ this.hasValue = !hasError;
507
513
  this._value = value;
508
514
  this._error = error;
509
515
  }
@@ -517,11 +523,17 @@ var Result = class _Result {
517
523
  get valueOrUndefined() {
518
524
  return this.hasValue ? this._value : void 0;
519
525
  }
526
+ // Values compared with valueComparer (Object.is by default); errors by reference — mirrors C# Result<T>.Equals.
527
+ equals(other, valueComparer = Object.is) {
528
+ if (this.hasError !== other.hasError || this._error !== other._error)
529
+ return false;
530
+ return valueComparer(this._value, other._value);
531
+ }
520
532
  static ok(value) {
521
- return new _Result(value);
533
+ return new _Result(value, void 0, false);
522
534
  }
523
535
  static error(error) {
524
- return new _Result(void 0, error);
536
+ return new _Result(void 0, error ?? new Error("Unspecified error"), true);
525
537
  }
526
538
  };
527
539
  function result(value, error) {
@@ -558,15 +570,35 @@ var abortSignalKey = new AsyncContextKey(
558
570
  "AbortSignal",
559
571
  void 0
560
572
  );
573
+ var _alsImpl = detectAsyncLocalStorage();
574
+ var _als = _alsImpl;
575
+ function detectAsyncLocalStorage() {
576
+ const proc = globalThis.process;
577
+ const asyncHooks = proc?.getBuiltinModule?.("node:async_hooks");
578
+ return asyncHooks === void 0 ? void 0 : new asyncHooks.AsyncLocalStorage();
579
+ }
561
580
  var AsyncContext = class _AsyncContext {
562
- static current = void 0;
563
581
  /** Immutable empty singleton — avoids allocating new AsyncContext(). */
564
582
  static empty = new _AsyncContext();
583
+ static _current = void 0;
565
584
  static _defaults = /* @__PURE__ */ new Map();
566
585
  _values;
567
586
  constructor(values) {
568
587
  this._values = values ?? /* @__PURE__ */ new Map();
569
588
  }
589
+ static get current() {
590
+ return _als?.getStore() ?? _AsyncContext._current;
591
+ }
592
+ static set current(value) {
593
+ if (_als !== void 0)
594
+ _als.enterWith(value);
595
+ else
596
+ _AsyncContext._current = value;
597
+ }
598
+ /** True when AsyncContext.current flows across awaits (Node AsyncLocalStorage is active). */
599
+ static get isAsyncLocalStorageActive() {
600
+ return _als !== void 0;
601
+ }
570
602
  get(key) {
571
603
  if (this._values.has(key.id)) return this._values.get(key.id);
572
604
  if (_AsyncContext._defaults.has(key.id))
@@ -580,20 +612,27 @@ var AsyncContext = class _AsyncContext {
580
612
  }
581
613
  activate() {
582
614
  const prev = _AsyncContext.current;
583
- _AsyncContext.current = this;
615
+ const als = _als;
616
+ if (als !== void 0) {
617
+ als.enterWith(this);
618
+ return { dispose: () => als.enterWith(prev) };
619
+ }
620
+ _AsyncContext._current = this;
584
621
  return {
585
622
  dispose: () => {
586
- _AsyncContext.current = prev;
623
+ _AsyncContext._current = prev;
587
624
  }
588
625
  };
589
626
  }
590
627
  run(fn) {
591
- const prev = _AsyncContext.current;
592
- _AsyncContext.current = this;
628
+ if (_als !== void 0)
629
+ return _als.run(this, fn);
630
+ const prev = _AsyncContext._current;
631
+ _AsyncContext._current = this;
593
632
  try {
594
633
  return fn();
595
634
  } finally {
596
- _AsyncContext.current = prev;
635
+ _AsyncContext._current = prev;
597
636
  }
598
637
  }
599
638
  /** Strip THIS exact instance from args if it's the last element (reference equality). */
@@ -615,6 +654,12 @@ var AsyncContext = class _AsyncContext {
615
654
  static getOrCreate() {
616
655
  return _AsyncContext.current ?? _AsyncContext.empty;
617
656
  }
657
+ // Toggles AsyncLocalStorage backing — for tests that exercise the browser fallback path.
658
+ // Clears the fallback static field so no stale context bleeds across the toggle.
659
+ static _setAsyncLocalStorageActive(active) {
660
+ _als = active ? _alsImpl : void 0;
661
+ _AsyncContext._current = void 0;
662
+ }
618
663
  };
619
664
 
620
665
  // src/async-lock.ts
@@ -624,13 +669,26 @@ var AsyncLock = class {
624
669
  get isLocked() {
625
670
  return this._locked;
626
671
  }
627
- async acquire() {
672
+ async acquire(signal) {
673
+ if (signal?.aborted)
674
+ throw signal.reason;
628
675
  if (!this._locked) {
629
676
  this._locked = true;
630
677
  return;
631
678
  }
632
- return new Promise((resolve) => {
633
- this._queue.push(resolve);
679
+ return new Promise((resolve, reject) => {
680
+ const onAbort = () => {
681
+ const i = this._queue.indexOf(waiter);
682
+ if (i >= 0)
683
+ this._queue.splice(i, 1);
684
+ reject(signal.reason);
685
+ };
686
+ const waiter = () => {
687
+ signal?.removeEventListener("abort", onAbort);
688
+ resolve();
689
+ };
690
+ this._queue.push(waiter);
691
+ signal?.addEventListener("abort", onAbort, { once: true });
634
692
  });
635
693
  }
636
694
  release() {
@@ -642,8 +700,8 @@ var AsyncLock = class {
642
700
  this._locked = false;
643
701
  }
644
702
  }
645
- async run(fn) {
646
- await this.acquire();
703
+ async run(fn, signal) {
704
+ await this.acquire(signal);
647
705
  try {
648
706
  return await fn();
649
707
  } finally {
@@ -664,6 +722,8 @@ var PromiseSource = class {
664
722
  this._resolve = resolve;
665
723
  this._reject = reject;
666
724
  });
725
+ this.promise.catch(() => {
726
+ });
667
727
  }
668
728
  get isCompleted() {
669
729
  return this._isCompleted;
@@ -705,7 +765,18 @@ var EventHandlerSet = class {
705
765
  this._handlers.delete(handler);
706
766
  }
707
767
  trigger(arg) {
708
- for (const handler of this._handlers) handler(arg);
768
+ for (const handler of [...this._handlers])
769
+ handler(arg);
770
+ }
771
+ // Fires every handler once, isolating each so one that throws can't stop the rest.
772
+ triggerSafe(arg, onError) {
773
+ for (const handler of [...this._handlers]) {
774
+ try {
775
+ handler(arg);
776
+ } catch (error) {
777
+ onError(error);
778
+ }
779
+ }
709
780
  }
710
781
  clear() {
711
782
  this._handlers.clear();
@@ -721,6 +792,19 @@ var EventHandlerSet = class {
721
792
  }
722
793
  };
723
794
 
795
+ // src/async-signal.ts
796
+ var AsyncSignal = class {
797
+ source = new PromiseSource();
798
+ wait() {
799
+ return this.source.promise;
800
+ }
801
+ notify() {
802
+ const source = this.source;
803
+ this.source = new PromiseSource();
804
+ source.resolve();
805
+ }
806
+ };
807
+
724
808
  // src/withTimeout.ts
725
809
  var TimeoutError = class extends Error {
726
810
  name = "TimeoutError";
@@ -795,9 +879,19 @@ var TimedOut = class _TimedOut {
795
879
  };
796
880
 
797
881
  // src/delay.ts
798
- function delayAsync(ms) {
799
- return new Promise((resolve) => {
800
- setTimeout(resolve, ms);
882
+ function delayAsync(ms, signal) {
883
+ if (signal?.aborted)
884
+ return Promise.reject(signal.reason);
885
+ return new Promise((resolve, reject) => {
886
+ const onAbort = () => {
887
+ clearTimeout(timer);
888
+ reject(signal.reason);
889
+ };
890
+ const timer = setTimeout(() => {
891
+ signal?.removeEventListener("abort", onAbort);
892
+ resolve();
893
+ }, ms);
894
+ signal?.addEventListener("abort", onAbort, { once: true });
801
895
  });
802
896
  }
803
897
  function delayAsyncWith(ms, value) {
@@ -809,10 +903,13 @@ function delayAsyncWith(ms, value) {
809
903
  // src/abort-promise.ts
810
904
  var abortPromiseCache = /* @__PURE__ */ new WeakMap();
811
905
  function abortPromise(signal) {
812
- if (signal.aborted) return Promise.reject(signal.reason);
813
906
  let cached = abortPromiseCache.get(signal);
814
907
  if (!cached) {
815
908
  cached = new Promise((_, reject) => {
909
+ if (signal.aborted) {
910
+ reject(signal.reason);
911
+ return;
912
+ }
816
913
  signal.addEventListener("abort", () => reject(signal.reason), { once: true });
817
914
  });
818
915
  cached.catch(() => {
@@ -822,6 +919,59 @@ function abortPromise(signal) {
822
919
  return cached;
823
920
  }
824
921
 
922
+ // src/cancellation.ts
923
+ function isCancellation(error) {
924
+ return error != null && typeof error === "object" && error.name === "AbortError";
925
+ }
926
+ function cancellationError(message = "The operation was cancelled.") {
927
+ const error = new Error(message);
928
+ error.name = "AbortError";
929
+ return error;
930
+ }
931
+
932
+ // src/await-with-cleanup.ts
933
+ function awaitWithCleanup(abortSignal, onAbort, wire) {
934
+ if (abortSignal?.aborted)
935
+ return onAbort === "reject" ? Promise.reject(abortSignal.reason ?? new Error("Operation cancelled.")) : resolvedVoidPromise;
936
+ const ps = new PromiseSource();
937
+ const cleanups = [];
938
+ let settled = false;
939
+ const runCleanups = () => {
940
+ for (const cleanup of cleanups)
941
+ cleanup();
942
+ cleanups.length = 0;
943
+ };
944
+ const complete = () => {
945
+ if (settled)
946
+ return;
947
+ settled = true;
948
+ runCleanups();
949
+ ps.resolve(void 0);
950
+ };
951
+ const addCleanup = (cleanup) => {
952
+ if (settled)
953
+ cleanup();
954
+ else
955
+ cleanups.push(cleanup);
956
+ };
957
+ if (abortSignal !== void 0) {
958
+ const onAbortFired = () => {
959
+ if (settled)
960
+ return;
961
+ settled = true;
962
+ runCleanups();
963
+ if (onAbort === "reject")
964
+ ps.reject(abortSignal.reason ?? new Error("Operation cancelled."));
965
+ else
966
+ ps.resolve(void 0);
967
+ };
968
+ abortSignal.addEventListener("abort", onAbortFired, { once: true });
969
+ cleanups.push(() => abortSignal.removeEventListener("abort", onAbortFired));
970
+ }
971
+ wire(complete, addCleanup);
972
+ return ps;
973
+ }
974
+
825
975
  // src/internal-logging.ts
826
976
  var scopeDefaults = {
827
977
  retry: 3 /* Warn */,
@@ -1103,29 +1253,10 @@ var RetryDelayer = class {
1103
1253
  const actualDelayMs = Math.max(1, delayMs);
1104
1254
  const endsAt = Date.now() + actualDelayMs;
1105
1255
  const cancelSignal = this._cancelController.signal;
1106
- const promise = new Promise((resolve, reject) => {
1107
- const timer = setTimeout(() => {
1108
- cleanup();
1109
- resolve();
1110
- }, actualDelayMs);
1111
- const onCancel = () => {
1112
- clearTimeout(timer);
1113
- cleanup();
1114
- resolve();
1115
- };
1116
- const onAbort = () => {
1117
- clearTimeout(timer);
1118
- cleanup();
1119
- reject(new Error("Retry delay aborted."));
1120
- };
1121
- const cleanup = () => {
1122
- cancelSignal.removeEventListener("abort", onCancel);
1123
- cancellationSignal?.removeEventListener("abort", onAbort);
1124
- };
1125
- cancelSignal.addEventListener("abort", onCancel, { once: true });
1126
- cancellationSignal?.addEventListener("abort", onAbort, {
1127
- once: true
1128
- });
1256
+ const combinedSignal = cancellationSignal ? AbortSignal.any([cancellationSignal, cancelSignal]) : cancelSignal;
1257
+ const promise = delayAsync(actualDelayMs, combinedSignal).catch((e) => {
1258
+ if (!cancelSignal.aborted)
1259
+ throw e;
1129
1260
  });
1130
1261
  return { promise, endsAt, isLimitExceeded: false };
1131
1262
  }
@@ -1140,7 +1271,7 @@ var RetryDelayer = class {
1140
1271
  // src/retry.ts
1141
1272
  var { warnLog } = getLogs("retry");
1142
1273
  var defaultRetryDelays = () => 50;
1143
- async function retry(tryCount, fn, retryDelays = defaultRetryDelays) {
1274
+ async function retry(tryCount, fn, retryDelays = defaultRetryDelays, signal) {
1144
1275
  if (tryCount <= 0)
1145
1276
  throw new Error("retry: tryCount must be positive.");
1146
1277
  let lastError;
@@ -1154,7 +1285,7 @@ async function retry(tryCount, fn, retryDelays = defaultRetryDelays) {
1154
1285
  ++tryIndex;
1155
1286
  if (tryIndex >= tryCount) throw lastError;
1156
1287
  warnLog?.log(`retry(${tryIndex}/${tryCount}): error:`, lastError);
1157
- await delayAsync(retryDelays(tryIndex));
1288
+ await delayAsync(retryDelays(tryIndex), signal);
1158
1289
  }
1159
1290
  }
1160
1291
  async function catchErrors(fn, onError) {
@@ -1164,11 +1295,52 @@ async function catchErrors(fn, onError) {
1164
1295
  return onError ? onError(e) : void 0;
1165
1296
  }
1166
1297
  }
1298
+
1299
+ // src/decorators.ts
1300
+ function ownMetadata(metadata, key) {
1301
+ const bag = metadata;
1302
+ if (!Object.hasOwn(metadata, key))
1303
+ bag[key] = { ...bag[key] };
1304
+ return bag[key];
1305
+ }
1306
+ function resolveArgCount(fn, explicit, methodName) {
1307
+ if (explicit !== void 0)
1308
+ return explicit;
1309
+ if (hasAmbiguousArity(fn))
1310
+ throw new Error(
1311
+ `Cannot infer the argument count of "${methodName}": its parameter list uses default and/or rest parameters, for which Function.length is unreliable. Pass an explicit argCount to the decorator.`
1312
+ );
1313
+ return fn.length;
1314
+ }
1315
+ function hasAmbiguousArity(fn) {
1316
+ const params = parameterListText(fn);
1317
+ return params !== void 0 && (params.includes("=") || params.includes("..."));
1318
+ }
1319
+ function parameterListText(fn) {
1320
+ const src = fn.toString();
1321
+ const open = src.indexOf("(");
1322
+ const arrow = src.indexOf("=>");
1323
+ if (open < 0 || arrow >= 0 && arrow < open)
1324
+ return void 0;
1325
+ let depth = 0;
1326
+ for (let i = open; i < src.length; i++) {
1327
+ const ch = src[i];
1328
+ if (ch === "(")
1329
+ depth++;
1330
+ else if (ch === ")") {
1331
+ depth--;
1332
+ if (depth === 0)
1333
+ return src.slice(open + 1, i);
1334
+ }
1335
+ }
1336
+ return void 0;
1337
+ }
1167
1338
  // Annotate the CommonJS export names for ESM import in node:
1168
1339
  0 && (module.exports = {
1169
1340
  AsyncContext,
1170
1341
  AsyncContextKey,
1171
1342
  AsyncLock,
1343
+ AsyncSignal,
1172
1344
  DisposableBag,
1173
1345
  EventHandlerSet,
1174
1346
  Log,
@@ -1187,6 +1359,8 @@ async function catchErrors(fn, onError) {
1187
1359
  TimeoutError,
1188
1360
  abortPromise,
1189
1361
  abortSignalKey,
1362
+ awaitWithCleanup,
1363
+ cancellationError,
1190
1364
  catchErrors,
1191
1365
  createLogProvider,
1192
1366
  debounce,
@@ -1195,6 +1369,9 @@ async function catchErrors(fn, onError) {
1195
1369
  errorResult,
1196
1370
  initLogging,
1197
1371
  initWorkerLogging,
1372
+ isCancellation,
1373
+ ownMetadata,
1374
+ resolveArgCount,
1198
1375
  resolvedVoidPromise,
1199
1376
  result,
1200
1377
  resultFrom,