resultar 3.1.0 → 3.2.1
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/README.md +13 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +25 -3
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -170,12 +170,17 @@ const user = ResultAsync.retryOrElse(
|
|
|
170
170
|
{
|
|
171
171
|
times: 2,
|
|
172
172
|
delayMs: ({ nextAttempt }) => nextAttempt * 100,
|
|
173
|
+
jittered: 0.5,
|
|
173
174
|
while: (error) => error._tag === 'RateLimitError' || error._tag === 'ServiceUnavailableError',
|
|
174
175
|
orElse: () => readCachedUser(id),
|
|
175
176
|
},
|
|
176
177
|
)
|
|
177
178
|
```
|
|
178
179
|
|
|
180
|
+
`jittered` randomizes the computed retry delay by a percentage factor to avoid synchronized retries.
|
|
181
|
+
For example, `delayMs: 100` with `jittered: 0.5` waits between `50ms` and `150ms`; omit it or use
|
|
182
|
+
`0` to keep the exact delay.
|
|
183
|
+
|
|
179
184
|
Race a primary and replica with cooperative loser abort:
|
|
180
185
|
|
|
181
186
|
```ts
|
|
@@ -640,6 +645,7 @@ const user = ResultAsync.retry(
|
|
|
640
645
|
{
|
|
641
646
|
times: 2,
|
|
642
647
|
delayMs: ({ nextAttempt }) => nextAttempt * 100,
|
|
648
|
+
jittered: 0.2,
|
|
643
649
|
while: (error) => error._tag === 'RateLimitError',
|
|
644
650
|
},
|
|
645
651
|
)
|
|
@@ -653,6 +659,9 @@ const withFallback = ResultAsync.retryOrElse(
|
|
|
653
659
|
)
|
|
654
660
|
```
|
|
655
661
|
|
|
662
|
+
Use `jittered` when many callers may retry at the same time. The value is a delay spread factor:
|
|
663
|
+
`jittered: 0.2` applies a random delay from `80%` to `120%` of the computed `delayMs`.
|
|
664
|
+
|
|
656
665
|
For resourceful async work, use `ResultAsync.withResource` when acquisition and cleanup must stay
|
|
657
666
|
paired. Cleanup is best-effort and always runs after successful acquisition, including when the use
|
|
658
667
|
step returns `Err` or rejects.
|
|
@@ -932,8 +941,8 @@ pnpm exec resultar-lint doctor
|
|
|
932
941
|
pnpm exec resultar-lint unpatch
|
|
933
942
|
```
|
|
934
943
|
|
|
935
|
-
For TypeScript 7
|
|
936
|
-
TypeScript and then Resultar lint validation.
|
|
944
|
+
For TypeScript 7 projects using `typescript@rc`, `resultar-tsgo` exposes a `tsgo` wrapper that runs
|
|
945
|
+
native TypeScript and then Resultar lint validation.
|
|
937
946
|
|
|
938
947
|
## API Decision Guide
|
|
939
948
|
|
|
@@ -1021,12 +1030,11 @@ This repository is a pnpm workspace:
|
|
|
1021
1030
|
|
|
1022
1031
|
- `packages/resultar`: Resultar runtime package.
|
|
1023
1032
|
- `packages/resultar-lint`: TypeScript language-service diagnostics and no-discard CLI.
|
|
1024
|
-
- `packages/resultar-tsgo`: TypeScript 7
|
|
1025
|
-
validation.
|
|
1033
|
+
- `packages/resultar-tsgo`: TypeScript 7 `typescript@rc` wrapper plus Resultar lint validation.
|
|
1026
1034
|
- `benchmarks`: benchmark package.
|
|
1027
1035
|
- `examples/resultar`: runnable core Resultar cookbook.
|
|
1028
1036
|
- `examples/lint`: TypeScript 6 Resultar lint, patched `tsc`, and Vite+ / Oxlint smoke example.
|
|
1029
|
-
- `examples/tsgo-lint`: TypeScript 7
|
|
1037
|
+
- `examples/tsgo-lint`: TypeScript 7 `typescript@rc` Resultar lint smoke example.
|
|
1030
1038
|
|
|
1031
1039
|
Common commands:
|
|
1032
1040
|
|
package/dist/index.d.ts
CHANGED
|
@@ -432,6 +432,7 @@ type ResultAsyncRetryTaskErr<Task> = Task extends ResultAsyncRetryTask<unknown,
|
|
|
432
432
|
type ResultAsyncRetryDelay = number | ((context: ResultAsyncRetryContext) => number);
|
|
433
433
|
interface ResultAsyncRetryOptions<E> {
|
|
434
434
|
readonly delayMs?: ResultAsyncRetryDelay;
|
|
435
|
+
readonly jittered?: number;
|
|
435
436
|
readonly onRetry?: (error: E, context: ResultAsyncRetryContext) => void | Promise<void>;
|
|
436
437
|
readonly signal?: ResultAsyncAbortSignal;
|
|
437
438
|
readonly times: number;
|
package/dist/index.js
CHANGED
|
@@ -743,13 +743,34 @@ const validateResultAsyncRetryTimes = (times) => {
|
|
|
743
743
|
const validateResultAsyncRetryDelayValue = (delayMs) => {
|
|
744
744
|
if (!Number.isFinite(delayMs) || delayMs < 0) throw createIllegalArgumentException("ResultAsync retry delayMs must be a non-negative finite number");
|
|
745
745
|
};
|
|
746
|
+
const validateResultAsyncRetryJittered = (jittered) => {
|
|
747
|
+
if (!Number.isFinite(jittered) || jittered < 0) throw createIllegalArgumentException("ResultAsync retry jittered must be a non-negative finite number");
|
|
748
|
+
};
|
|
749
|
+
const validateResultAsyncRetryJitterRandomValue = (value) => {
|
|
750
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) throw createIllegalArgumentException("ResultAsync retry jitter random value must be a finite number between 0 and 1");
|
|
751
|
+
};
|
|
752
|
+
const validateResultAsyncRetryStaticJittered = (jittered) => {
|
|
753
|
+
if (jittered !== void 0) validateResultAsyncRetryJittered(jittered);
|
|
754
|
+
};
|
|
746
755
|
const validateResultAsyncRetryStaticDelay = (delay) => {
|
|
747
756
|
if (typeof delay === "number") validateResultAsyncRetryDelayValue(delay);
|
|
748
757
|
};
|
|
749
|
-
const
|
|
758
|
+
const getResultAsyncRetryJitterRandom = () => {
|
|
759
|
+
const value = Math.random();
|
|
760
|
+
validateResultAsyncRetryJitterRandomValue(value);
|
|
761
|
+
return value;
|
|
762
|
+
};
|
|
763
|
+
const applyResultAsyncRetryJitter = (delayMs, jittered) => {
|
|
764
|
+
if (jittered === void 0 || jittered === 0 || delayMs === 0) return delayMs;
|
|
765
|
+
const random = getResultAsyncRetryJitterRandom();
|
|
766
|
+
const range = delayMs * jittered;
|
|
767
|
+
const min = Math.max(0, delayMs - range);
|
|
768
|
+
return min + (delayMs + range - min) * random;
|
|
769
|
+
};
|
|
770
|
+
const normalizeResultAsyncRetryDelay = (delay, jittered, context) => {
|
|
750
771
|
const delayMs = typeof delay === "function" ? delay(context) : delay ?? 0;
|
|
751
772
|
validateResultAsyncRetryDelayValue(delayMs);
|
|
752
|
-
return delayMs;
|
|
773
|
+
return applyResultAsyncRetryJitter(delayMs, jittered);
|
|
753
774
|
};
|
|
754
775
|
const waitResultAsyncRetryDelay = async (delayMs, signal) => {
|
|
755
776
|
if (signal.aborted) return Result.err(createResultAsyncRetryAbortError(signal));
|
|
@@ -776,6 +797,7 @@ const resolveResultAsyncRetryRecovery = async (recovered) => recovered instanceo
|
|
|
776
797
|
const runResultAsyncRetryAttempts = async (task, options, recover) => {
|
|
777
798
|
validateResultAsyncRetryTimes(options.times);
|
|
778
799
|
validateResultAsyncRetryStaticDelay(options.delayMs);
|
|
800
|
+
validateResultAsyncRetryStaticJittered(options.jittered);
|
|
779
801
|
const signal = options.signal ?? new AbortController().signal;
|
|
780
802
|
let attempt = 0;
|
|
781
803
|
while (true) {
|
|
@@ -785,7 +807,7 @@ const runResultAsyncRetryAttempts = async (task, options, recover) => {
|
|
|
785
807
|
const context = createResultAsyncRetryContext(attempt, options.times);
|
|
786
808
|
if (!await shouldRetryResultAsyncError(options, result.error, context) || attempt >= options.times) return resolveResultAsyncRetryRecovery(recover(result.error, context));
|
|
787
809
|
await callResultAsyncRetryOnRetry(options, result.error, context);
|
|
788
|
-
const delayResult = await waitResultAsyncRetryDelay(normalizeResultAsyncRetryDelay(options.delayMs, context), signal);
|
|
810
|
+
const delayResult = await waitResultAsyncRetryDelay(normalizeResultAsyncRetryDelay(options.delayMs, options.jittered, context), signal);
|
|
789
811
|
if (delayResult.isErr()) return Result.err(delayResult.error);
|
|
790
812
|
attempt += 1;
|
|
791
813
|
}
|