@actuallab/core 13.0.20 → 13.0.163

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
@@ -41,6 +41,8 @@ __export(index_exports, {
41
41
  TimeoutError: () => TimeoutError,
42
42
  abortPromise: () => abortPromise,
43
43
  abortSignalKey: () => abortSignalKey,
44
+ awaitWithCleanup: () => awaitWithCleanup,
45
+ cancellationError: () => cancellationError,
44
46
  catchErrors: () => catchErrors,
45
47
  createLogProvider: () => createLogProvider,
46
48
  debounce: () => debounce,
@@ -49,6 +51,9 @@ __export(index_exports, {
49
51
  errorResult: () => errorResult,
50
52
  initLogging: () => initLogging,
51
53
  initWorkerLogging: () => initWorkerLogging,
54
+ isCancellation: () => isCancellation,
55
+ ownMetadata: () => ownMetadata,
56
+ resolveArgCount: () => resolveArgCount,
52
57
  resolvedVoidPromise: () => resolvedVoidPromise,
53
58
  result: () => result,
54
59
  resultFrom: () => resultFrom,
@@ -501,9 +506,9 @@ var Result = class _Result {
501
506
  hasError;
502
507
  _value;
503
508
  _error;
504
- constructor(value, error) {
505
- this.hasValue = error === void 0;
506
- this.hasError = !this.hasValue;
509
+ constructor(value, error, hasError = error !== void 0) {
510
+ this.hasError = hasError;
511
+ this.hasValue = !hasError;
507
512
  this._value = value;
508
513
  this._error = error;
509
514
  }
@@ -517,11 +522,17 @@ var Result = class _Result {
517
522
  get valueOrUndefined() {
518
523
  return this.hasValue ? this._value : void 0;
519
524
  }
525
+ // Values compared with valueComparer (Object.is by default); errors by reference — mirrors C# Result<T>.Equals.
526
+ equals(other, valueComparer = Object.is) {
527
+ if (this.hasError !== other.hasError || this._error !== other._error)
528
+ return false;
529
+ return valueComparer(this._value, other._value);
530
+ }
520
531
  static ok(value) {
521
- return new _Result(value);
532
+ return new _Result(value, void 0, false);
522
533
  }
523
534
  static error(error) {
524
- return new _Result(void 0, error);
535
+ return new _Result(void 0, error ?? new Error("Unspecified error"), true);
525
536
  }
526
537
  };
527
538
  function result(value, error) {
@@ -558,15 +569,35 @@ var abortSignalKey = new AsyncContextKey(
558
569
  "AbortSignal",
559
570
  void 0
560
571
  );
572
+ var _alsImpl = detectAsyncLocalStorage();
573
+ var _als = _alsImpl;
574
+ function detectAsyncLocalStorage() {
575
+ const proc = globalThis.process;
576
+ const asyncHooks = proc?.getBuiltinModule?.("node:async_hooks");
577
+ return asyncHooks === void 0 ? void 0 : new asyncHooks.AsyncLocalStorage();
578
+ }
561
579
  var AsyncContext = class _AsyncContext {
562
- static current = void 0;
563
580
  /** Immutable empty singleton — avoids allocating new AsyncContext(). */
564
581
  static empty = new _AsyncContext();
582
+ static _current = void 0;
565
583
  static _defaults = /* @__PURE__ */ new Map();
566
584
  _values;
567
585
  constructor(values) {
568
586
  this._values = values ?? /* @__PURE__ */ new Map();
569
587
  }
588
+ static get current() {
589
+ return _als?.getStore() ?? _AsyncContext._current;
590
+ }
591
+ static set current(value) {
592
+ if (_als !== void 0)
593
+ _als.enterWith(value);
594
+ else
595
+ _AsyncContext._current = value;
596
+ }
597
+ /** True when AsyncContext.current flows across awaits (Node AsyncLocalStorage is active). */
598
+ static get isAsyncLocalStorageActive() {
599
+ return _als !== void 0;
600
+ }
570
601
  get(key) {
571
602
  if (this._values.has(key.id)) return this._values.get(key.id);
572
603
  if (_AsyncContext._defaults.has(key.id))
@@ -580,20 +611,27 @@ var AsyncContext = class _AsyncContext {
580
611
  }
581
612
  activate() {
582
613
  const prev = _AsyncContext.current;
583
- _AsyncContext.current = this;
614
+ const als = _als;
615
+ if (als !== void 0) {
616
+ als.enterWith(this);
617
+ return { dispose: () => als.enterWith(prev) };
618
+ }
619
+ _AsyncContext._current = this;
584
620
  return {
585
621
  dispose: () => {
586
- _AsyncContext.current = prev;
622
+ _AsyncContext._current = prev;
587
623
  }
588
624
  };
589
625
  }
590
626
  run(fn) {
591
- const prev = _AsyncContext.current;
592
- _AsyncContext.current = this;
627
+ if (_als !== void 0)
628
+ return _als.run(this, fn);
629
+ const prev = _AsyncContext._current;
630
+ _AsyncContext._current = this;
593
631
  try {
594
632
  return fn();
595
633
  } finally {
596
- _AsyncContext.current = prev;
634
+ _AsyncContext._current = prev;
597
635
  }
598
636
  }
599
637
  /** Strip THIS exact instance from args if it's the last element (reference equality). */
@@ -615,6 +653,12 @@ var AsyncContext = class _AsyncContext {
615
653
  static getOrCreate() {
616
654
  return _AsyncContext.current ?? _AsyncContext.empty;
617
655
  }
656
+ // Toggles AsyncLocalStorage backing — for tests that exercise the browser fallback path.
657
+ // Clears the fallback static field so no stale context bleeds across the toggle.
658
+ static _setAsyncLocalStorageActive(active) {
659
+ _als = active ? _alsImpl : void 0;
660
+ _AsyncContext._current = void 0;
661
+ }
618
662
  };
619
663
 
620
664
  // src/async-lock.ts
@@ -624,13 +668,26 @@ var AsyncLock = class {
624
668
  get isLocked() {
625
669
  return this._locked;
626
670
  }
627
- async acquire() {
671
+ async acquire(signal) {
672
+ if (signal?.aborted)
673
+ throw signal.reason;
628
674
  if (!this._locked) {
629
675
  this._locked = true;
630
676
  return;
631
677
  }
632
- return new Promise((resolve) => {
633
- this._queue.push(resolve);
678
+ return new Promise((resolve, reject) => {
679
+ const onAbort = () => {
680
+ const i = this._queue.indexOf(waiter);
681
+ if (i >= 0)
682
+ this._queue.splice(i, 1);
683
+ reject(signal.reason);
684
+ };
685
+ const waiter = () => {
686
+ signal?.removeEventListener("abort", onAbort);
687
+ resolve();
688
+ };
689
+ this._queue.push(waiter);
690
+ signal?.addEventListener("abort", onAbort, { once: true });
634
691
  });
635
692
  }
636
693
  release() {
@@ -642,8 +699,8 @@ var AsyncLock = class {
642
699
  this._locked = false;
643
700
  }
644
701
  }
645
- async run(fn) {
646
- await this.acquire();
702
+ async run(fn, signal) {
703
+ await this.acquire(signal);
647
704
  try {
648
705
  return await fn();
649
706
  } finally {
@@ -664,6 +721,8 @@ var PromiseSource = class {
664
721
  this._resolve = resolve;
665
722
  this._reject = reject;
666
723
  });
724
+ this.promise.catch(() => {
725
+ });
667
726
  }
668
727
  get isCompleted() {
669
728
  return this._isCompleted;
@@ -705,7 +764,18 @@ var EventHandlerSet = class {
705
764
  this._handlers.delete(handler);
706
765
  }
707
766
  trigger(arg) {
708
- for (const handler of this._handlers) handler(arg);
767
+ for (const handler of [...this._handlers])
768
+ handler(arg);
769
+ }
770
+ // Fires every handler once, isolating each so one that throws can't stop the rest.
771
+ triggerSafe(arg, onError) {
772
+ for (const handler of [...this._handlers]) {
773
+ try {
774
+ handler(arg);
775
+ } catch (error) {
776
+ onError(error);
777
+ }
778
+ }
709
779
  }
710
780
  clear() {
711
781
  this._handlers.clear();
@@ -795,9 +865,19 @@ var TimedOut = class _TimedOut {
795
865
  };
796
866
 
797
867
  // src/delay.ts
798
- function delayAsync(ms) {
799
- return new Promise((resolve) => {
800
- setTimeout(resolve, ms);
868
+ function delayAsync(ms, signal) {
869
+ if (signal?.aborted)
870
+ return Promise.reject(signal.reason);
871
+ return new Promise((resolve, reject) => {
872
+ const onAbort = () => {
873
+ clearTimeout(timer);
874
+ reject(signal.reason);
875
+ };
876
+ const timer = setTimeout(() => {
877
+ signal?.removeEventListener("abort", onAbort);
878
+ resolve();
879
+ }, ms);
880
+ signal?.addEventListener("abort", onAbort, { once: true });
801
881
  });
802
882
  }
803
883
  function delayAsyncWith(ms, value) {
@@ -809,10 +889,13 @@ function delayAsyncWith(ms, value) {
809
889
  // src/abort-promise.ts
810
890
  var abortPromiseCache = /* @__PURE__ */ new WeakMap();
811
891
  function abortPromise(signal) {
812
- if (signal.aborted) return Promise.reject(signal.reason);
813
892
  let cached = abortPromiseCache.get(signal);
814
893
  if (!cached) {
815
894
  cached = new Promise((_, reject) => {
895
+ if (signal.aborted) {
896
+ reject(signal.reason);
897
+ return;
898
+ }
816
899
  signal.addEventListener("abort", () => reject(signal.reason), { once: true });
817
900
  });
818
901
  cached.catch(() => {
@@ -822,6 +905,59 @@ function abortPromise(signal) {
822
905
  return cached;
823
906
  }
824
907
 
908
+ // src/cancellation.ts
909
+ function isCancellation(error) {
910
+ return error != null && typeof error === "object" && error.name === "AbortError";
911
+ }
912
+ function cancellationError(message = "The operation was cancelled.") {
913
+ const error = new Error(message);
914
+ error.name = "AbortError";
915
+ return error;
916
+ }
917
+
918
+ // src/await-with-cleanup.ts
919
+ function awaitWithCleanup(abortSignal, onAbort, wire) {
920
+ if (abortSignal?.aborted)
921
+ return onAbort === "reject" ? Promise.reject(abortSignal.reason ?? new Error("Operation cancelled.")) : resolvedVoidPromise;
922
+ const ps = new PromiseSource();
923
+ const cleanups = [];
924
+ let settled = false;
925
+ const runCleanups = () => {
926
+ for (const cleanup of cleanups)
927
+ cleanup();
928
+ cleanups.length = 0;
929
+ };
930
+ const complete = () => {
931
+ if (settled)
932
+ return;
933
+ settled = true;
934
+ runCleanups();
935
+ ps.resolve(void 0);
936
+ };
937
+ const addCleanup = (cleanup) => {
938
+ if (settled)
939
+ cleanup();
940
+ else
941
+ cleanups.push(cleanup);
942
+ };
943
+ if (abortSignal !== void 0) {
944
+ const onAbortFired = () => {
945
+ if (settled)
946
+ return;
947
+ settled = true;
948
+ runCleanups();
949
+ if (onAbort === "reject")
950
+ ps.reject(abortSignal.reason ?? new Error("Operation cancelled."));
951
+ else
952
+ ps.resolve(void 0);
953
+ };
954
+ abortSignal.addEventListener("abort", onAbortFired, { once: true });
955
+ cleanups.push(() => abortSignal.removeEventListener("abort", onAbortFired));
956
+ }
957
+ wire(complete, addCleanup);
958
+ return ps;
959
+ }
960
+
825
961
  // src/internal-logging.ts
826
962
  var scopeDefaults = {
827
963
  retry: 3 /* Warn */,
@@ -1103,29 +1239,10 @@ var RetryDelayer = class {
1103
1239
  const actualDelayMs = Math.max(1, delayMs);
1104
1240
  const endsAt = Date.now() + actualDelayMs;
1105
1241
  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
- });
1242
+ const combinedSignal = cancellationSignal ? AbortSignal.any([cancellationSignal, cancelSignal]) : cancelSignal;
1243
+ const promise = delayAsync(actualDelayMs, combinedSignal).catch((e) => {
1244
+ if (!cancelSignal.aborted)
1245
+ throw e;
1129
1246
  });
1130
1247
  return { promise, endsAt, isLimitExceeded: false };
1131
1248
  }
@@ -1140,7 +1257,7 @@ var RetryDelayer = class {
1140
1257
  // src/retry.ts
1141
1258
  var { warnLog } = getLogs("retry");
1142
1259
  var defaultRetryDelays = () => 50;
1143
- async function retry(tryCount, fn, retryDelays = defaultRetryDelays) {
1260
+ async function retry(tryCount, fn, retryDelays = defaultRetryDelays, signal) {
1144
1261
  if (tryCount <= 0)
1145
1262
  throw new Error("retry: tryCount must be positive.");
1146
1263
  let lastError;
@@ -1154,7 +1271,7 @@ async function retry(tryCount, fn, retryDelays = defaultRetryDelays) {
1154
1271
  ++tryIndex;
1155
1272
  if (tryIndex >= tryCount) throw lastError;
1156
1273
  warnLog?.log(`retry(${tryIndex}/${tryCount}): error:`, lastError);
1157
- await delayAsync(retryDelays(tryIndex));
1274
+ await delayAsync(retryDelays(tryIndex), signal);
1158
1275
  }
1159
1276
  }
1160
1277
  async function catchErrors(fn, onError) {
@@ -1164,6 +1281,46 @@ async function catchErrors(fn, onError) {
1164
1281
  return onError ? onError(e) : void 0;
1165
1282
  }
1166
1283
  }
1284
+
1285
+ // src/decorators.ts
1286
+ function ownMetadata(metadata, key) {
1287
+ const bag = metadata;
1288
+ if (!Object.hasOwn(metadata, key))
1289
+ bag[key] = { ...bag[key] };
1290
+ return bag[key];
1291
+ }
1292
+ function resolveArgCount(fn, explicit, methodName) {
1293
+ if (explicit !== void 0)
1294
+ return explicit;
1295
+ if (hasAmbiguousArity(fn))
1296
+ throw new Error(
1297
+ `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.`
1298
+ );
1299
+ return fn.length;
1300
+ }
1301
+ function hasAmbiguousArity(fn) {
1302
+ const params = parameterListText(fn);
1303
+ return params !== void 0 && (params.includes("=") || params.includes("..."));
1304
+ }
1305
+ function parameterListText(fn) {
1306
+ const src = Function.prototype.toString.call(fn);
1307
+ const open = src.indexOf("(");
1308
+ const arrow = src.indexOf("=>");
1309
+ if (open < 0 || arrow >= 0 && arrow < open)
1310
+ return void 0;
1311
+ let depth = 0;
1312
+ for (let i = open; i < src.length; i++) {
1313
+ const ch = src[i];
1314
+ if (ch === "(")
1315
+ depth++;
1316
+ else if (ch === ")") {
1317
+ depth--;
1318
+ if (depth === 0)
1319
+ return src.slice(open + 1, i);
1320
+ }
1321
+ }
1322
+ return void 0;
1323
+ }
1167
1324
  // Annotate the CommonJS export names for ESM import in node:
1168
1325
  0 && (module.exports = {
1169
1326
  AsyncContext,
@@ -1187,6 +1344,8 @@ async function catchErrors(fn, onError) {
1187
1344
  TimeoutError,
1188
1345
  abortPromise,
1189
1346
  abortSignalKey,
1347
+ awaitWithCleanup,
1348
+ cancellationError,
1190
1349
  catchErrors,
1191
1350
  createLogProvider,
1192
1351
  debounce,
@@ -1195,6 +1354,9 @@ async function catchErrors(fn, onError) {
1195
1354
  errorResult,
1196
1355
  initLogging,
1197
1356
  initWorkerLogging,
1357
+ isCancellation,
1358
+ ownMetadata,
1359
+ resolveArgCount,
1198
1360
  resolvedVoidPromise,
1199
1361
  result,
1200
1362
  resultFrom,