resultar 3.1.0-beta.1 → 3.2.0
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 +45 -25
- 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
|
}
|
|
@@ -1097,7 +1119,7 @@ var DisposableResultAsync = class {
|
|
|
1097
1119
|
*/
|
|
1098
1120
|
var ResultAsync = class ResultAsync extends Pipeable {
|
|
1099
1121
|
static okAsync(value) {
|
|
1100
|
-
return new ResultAsync(Promise.resolve(
|
|
1122
|
+
return new ResultAsync(Promise.resolve(ok(value)));
|
|
1101
1123
|
}
|
|
1102
1124
|
/**
|
|
1103
1125
|
* Returns a ResultAsync that is immediately resolved with a Result.ok(undefined) value.
|
|
@@ -1108,19 +1130,19 @@ var ResultAsync = class ResultAsync extends Pipeable {
|
|
|
1108
1130
|
return new ResultAsync(Promise.resolve(Result.unit()));
|
|
1109
1131
|
}
|
|
1110
1132
|
static errAsync(error) {
|
|
1111
|
-
return new ResultAsync(Promise.resolve(
|
|
1133
|
+
return new ResultAsync(Promise.resolve(err(error)));
|
|
1112
1134
|
}
|
|
1113
1135
|
static tryCatch(fn, errorFn) {
|
|
1114
|
-
return new ResultAsync((typeof fn === "function" ? Promise.try(fn) : fn).then((value) =>
|
|
1115
|
-
if (errorFn) return
|
|
1116
|
-
return
|
|
1136
|
+
return new ResultAsync((typeof fn === "function" ? Promise.try(fn) : fn).then((value) => ok(value)).catch((error) => {
|
|
1137
|
+
if (errorFn) return err(errorFn(error));
|
|
1138
|
+
return err(error);
|
|
1117
1139
|
}));
|
|
1118
1140
|
}
|
|
1119
1141
|
static fromSafePromise(promise) {
|
|
1120
|
-
return new ResultAsync(promise.then((value) =>
|
|
1142
|
+
return new ResultAsync(promise.then((value) => ok(value)));
|
|
1121
1143
|
}
|
|
1122
1144
|
static fromPromise(promise, errorFn) {
|
|
1123
|
-
return new ResultAsync(promise.then((value) =>
|
|
1145
|
+
return new ResultAsync(promise.then((value) => ok(value)).catch((error) => err(errorFn(error))));
|
|
1124
1146
|
}
|
|
1125
1147
|
static combine(input) {
|
|
1126
1148
|
if (Array.isArray(input)) return combineResultAsyncList(input);
|
|
@@ -1244,11 +1266,9 @@ var ResultAsync = class ResultAsync extends Pipeable {
|
|
|
1244
1266
|
static fromThrowable(fn, errorFn) {
|
|
1245
1267
|
return (...args) => new ResultAsync((async () => {
|
|
1246
1268
|
try {
|
|
1247
|
-
|
|
1248
|
-
return Result.ok(v);
|
|
1269
|
+
return ok(await fn(...args));
|
|
1249
1270
|
} catch (error) {
|
|
1250
|
-
|
|
1251
|
-
return Result.err(e);
|
|
1271
|
+
return err(errorFn ? errorFn(error) : error);
|
|
1252
1272
|
}
|
|
1253
1273
|
})());
|
|
1254
1274
|
}
|
|
@@ -1262,32 +1282,32 @@ var ResultAsync = class ResultAsync extends Pipeable {
|
|
|
1262
1282
|
}
|
|
1263
1283
|
mapErr(f) {
|
|
1264
1284
|
return new ResultAsync(this.innerPromise.then(async (res) => {
|
|
1265
|
-
if (res.isOk()) return
|
|
1266
|
-
return
|
|
1285
|
+
if (res.isOk()) return ok(res.value);
|
|
1286
|
+
return err(await f(res.error));
|
|
1267
1287
|
}));
|
|
1268
1288
|
}
|
|
1269
1289
|
map(f) {
|
|
1270
1290
|
return new ResultAsync(this.innerPromise.then(async (res) => {
|
|
1271
|
-
if (res.isErr()) return
|
|
1272
|
-
return
|
|
1291
|
+
if (res.isErr()) return err(res.error);
|
|
1292
|
+
return ok(await f(res.value));
|
|
1273
1293
|
}));
|
|
1274
1294
|
}
|
|
1275
1295
|
as(value) {
|
|
1276
1296
|
return new ResultAsync(this.innerPromise.then((res) => {
|
|
1277
|
-
if (res.isErr()) return
|
|
1278
|
-
return
|
|
1297
|
+
if (res.isErr()) return err(res.error);
|
|
1298
|
+
return ok(value);
|
|
1279
1299
|
}));
|
|
1280
1300
|
}
|
|
1281
1301
|
filterOrElse(predicate, onFalse) {
|
|
1282
1302
|
return new ResultAsync(this.innerPromise.then(async (res) => {
|
|
1283
|
-
if (res.isErr()) return
|
|
1284
|
-
if (await predicate(res.value)) return
|
|
1285
|
-
return
|
|
1303
|
+
if (res.isErr()) return err(res.error);
|
|
1304
|
+
if (await predicate(res.value)) return ok(res.value);
|
|
1305
|
+
return err(await onFalse(res.value));
|
|
1286
1306
|
}));
|
|
1287
1307
|
}
|
|
1288
1308
|
andThen(f) {
|
|
1289
1309
|
return new ResultAsync(this.innerPromise.then((res) => {
|
|
1290
|
-
if (res.isErr()) return
|
|
1310
|
+
if (res.isErr()) return err(res.error);
|
|
1291
1311
|
const next = f(res.value);
|
|
1292
1312
|
return next instanceof ResultAsync ? next.innerPromise : next;
|
|
1293
1313
|
}));
|
|
@@ -1304,7 +1324,7 @@ var ResultAsync = class ResultAsync extends Pipeable {
|
|
|
1304
1324
|
const next = f(res.error);
|
|
1305
1325
|
return next instanceof ResultAsync ? next.innerPromise : next;
|
|
1306
1326
|
}
|
|
1307
|
-
return
|
|
1327
|
+
return ok(res.value);
|
|
1308
1328
|
}));
|
|
1309
1329
|
}
|
|
1310
1330
|
catchTag(tag, f) {
|