@stackframe/stack-shared 2.7.20 → 2.7.21
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/CHANGELOG.md +6 -0
- package/dist/crud.js +76 -0
- package/dist/hooks/use-strict-memo.js +75 -0
- package/dist/known-errors.js +1 -1
- package/dist/utils/arrays.js +75 -1
- package/dist/utils/caches.js +33 -0
- package/dist/utils/compile-time.js +17 -0
- package/dist/utils/dates.js +4 -4
- package/dist/utils/functions.js +15 -0
- package/dist/utils/html.js +28 -0
- package/dist/utils/http.js +29 -0
- package/dist/utils/ips.js +29 -0
- package/dist/utils/json.js +135 -0
- package/dist/utils/maps.js +145 -0
- package/dist/utils/objects.js +69 -0
- package/dist/utils/promises.js +201 -1
- package/dist/utils/proxies.js +72 -0
- package/dist/utils/react.js +82 -0
- package/dist/utils/results.js +241 -5
- package/dist/utils/strings.js +223 -2
- package/dist/utils/unicode.js +13 -0
- package/dist/utils/urls.js +115 -0
- package/dist/utils/uuids.js +30 -0
- package/package.json +1 -1
package/dist/utils/react.js
CHANGED
|
@@ -13,6 +13,32 @@ export function forwardRefIfNeeded(render) {
|
|
|
13
13
|
return ((props) => render(props, props.ref));
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
+
import.meta.vitest?.test("forwardRefIfNeeded", ({ expect }) => {
|
|
17
|
+
// Mock React.version and React.forwardRef
|
|
18
|
+
const originalVersion = React.version;
|
|
19
|
+
const originalForwardRef = React.forwardRef;
|
|
20
|
+
try {
|
|
21
|
+
// Test with React version < 19
|
|
22
|
+
Object.defineProperty(React, 'version', { value: '18.2.0', writable: true });
|
|
23
|
+
// Create a render function
|
|
24
|
+
const renderFn = (props, ref) => null;
|
|
25
|
+
// Call forwardRefIfNeeded
|
|
26
|
+
const result = forwardRefIfNeeded(renderFn);
|
|
27
|
+
// Verify the function returns something
|
|
28
|
+
expect(result).toBeDefined();
|
|
29
|
+
// Test with React version >= 19
|
|
30
|
+
Object.defineProperty(React, 'version', { value: '19.0.0', writable: true });
|
|
31
|
+
// Call forwardRefIfNeeded again with React 19
|
|
32
|
+
const result19 = forwardRefIfNeeded(renderFn);
|
|
33
|
+
// Verify the function returns something
|
|
34
|
+
expect(result19).toBeDefined();
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
// Restore original values
|
|
38
|
+
Object.defineProperty(React, 'version', { value: originalVersion });
|
|
39
|
+
React.forwardRef = originalForwardRef;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
16
42
|
export function getNodeText(node) {
|
|
17
43
|
if (["number", "string"].includes(typeof node)) {
|
|
18
44
|
return `${node}`;
|
|
@@ -28,6 +54,44 @@ export function getNodeText(node) {
|
|
|
28
54
|
}
|
|
29
55
|
throw new Error(`Unknown node type: ${typeof node}`);
|
|
30
56
|
}
|
|
57
|
+
import.meta.vitest?.test("getNodeText", ({ expect }) => {
|
|
58
|
+
// Test with string
|
|
59
|
+
expect(getNodeText("hello")).toBe("hello");
|
|
60
|
+
// Test with number
|
|
61
|
+
expect(getNodeText(42)).toBe("42");
|
|
62
|
+
// Test with null/undefined
|
|
63
|
+
expect(getNodeText(null)).toBe("");
|
|
64
|
+
expect(getNodeText(undefined)).toBe("");
|
|
65
|
+
// Test with array
|
|
66
|
+
expect(getNodeText(["hello", " ", "world"])).toBe("hello world");
|
|
67
|
+
expect(getNodeText([1, 2, 3])).toBe("123");
|
|
68
|
+
// Test with mixed array
|
|
69
|
+
expect(getNodeText(["hello", 42, null])).toBe("hello42");
|
|
70
|
+
// Test with React element (mocked)
|
|
71
|
+
const mockElement = {
|
|
72
|
+
props: {
|
|
73
|
+
children: "child text"
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
expect(getNodeText(mockElement)).toBe("child text");
|
|
77
|
+
// Test with nested React elements
|
|
78
|
+
const nestedElement = {
|
|
79
|
+
props: {
|
|
80
|
+
children: {
|
|
81
|
+
props: {
|
|
82
|
+
children: "nested text"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
expect(getNodeText(nestedElement)).toBe("nested text");
|
|
88
|
+
// Test with array of React elements
|
|
89
|
+
const arrayOfElements = [
|
|
90
|
+
{ props: { children: "first" } },
|
|
91
|
+
{ props: { children: "second" } }
|
|
92
|
+
];
|
|
93
|
+
expect(getNodeText(arrayOfElements)).toBe("firstsecond");
|
|
94
|
+
});
|
|
31
95
|
/**
|
|
32
96
|
* Suspends the currently rendered component indefinitely. Will not unsuspend unless the component rerenders.
|
|
33
97
|
*
|
|
@@ -71,6 +135,24 @@ export class NoSuspenseBoundaryError extends Error {
|
|
|
71
135
|
this.digest = "BAILOUT_TO_CLIENT_SIDE_RENDERING";
|
|
72
136
|
}
|
|
73
137
|
}
|
|
138
|
+
import.meta.vitest?.test("NoSuspenseBoundaryError", ({ expect }) => {
|
|
139
|
+
// Test with default options
|
|
140
|
+
const defaultError = new NoSuspenseBoundaryError({});
|
|
141
|
+
expect(defaultError.name).toBe("NoSuspenseBoundaryError");
|
|
142
|
+
expect(defaultError.reason).toBe("suspendIfSsr()");
|
|
143
|
+
expect(defaultError.digest).toBe("BAILOUT_TO_CLIENT_SIDE_RENDERING");
|
|
144
|
+
expect(defaultError.message).toContain("This code path attempted to display a loading indicator");
|
|
145
|
+
// Test with custom caller
|
|
146
|
+
const customError = new NoSuspenseBoundaryError({ caller: "CustomComponent" });
|
|
147
|
+
expect(customError.name).toBe("NoSuspenseBoundaryError");
|
|
148
|
+
expect(customError.reason).toBe("CustomComponent");
|
|
149
|
+
expect(customError.digest).toBe("BAILOUT_TO_CLIENT_SIDE_RENDERING");
|
|
150
|
+
expect(customError.message).toContain("CustomComponent attempted to display a loading indicator");
|
|
151
|
+
// Verify error message contains all the necessary information
|
|
152
|
+
expect(customError.message).toContain("loading.tsx");
|
|
153
|
+
expect(customError.message).toContain("route groups");
|
|
154
|
+
expect(customError.message).toContain("https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout");
|
|
155
|
+
});
|
|
74
156
|
/**
|
|
75
157
|
* Use this in a component or a hook to disable SSR. Should be wrapped in a Suspense boundary, or it will throw an error.
|
|
76
158
|
*/
|
package/dist/utils/results.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { wait } from "./promises";
|
|
2
1
|
import { deindent } from "./strings";
|
|
3
2
|
export const Result = {
|
|
4
3
|
fromThrowing,
|
|
@@ -21,8 +20,9 @@ export const Result = {
|
|
|
21
20
|
return result.status === "ok" ? result.data : fallback;
|
|
22
21
|
},
|
|
23
22
|
orThrow: (result) => {
|
|
24
|
-
if (result.status === "error")
|
|
23
|
+
if (result.status === "error") {
|
|
25
24
|
throw result.error;
|
|
25
|
+
}
|
|
26
26
|
return result.data;
|
|
27
27
|
},
|
|
28
28
|
orThrowAsync: async (result) => {
|
|
@@ -30,6 +30,43 @@ export const Result = {
|
|
|
30
30
|
},
|
|
31
31
|
retry,
|
|
32
32
|
};
|
|
33
|
+
import.meta.vitest?.test("Result.ok and Result.error", ({ expect }) => {
|
|
34
|
+
// Test Result.ok
|
|
35
|
+
const okResult = Result.ok(42);
|
|
36
|
+
expect(okResult.status).toBe("ok");
|
|
37
|
+
expect(okResult.data).toBe(42);
|
|
38
|
+
// Test Result.error
|
|
39
|
+
const error = new Error("Test error");
|
|
40
|
+
const errorResult = Result.error(error);
|
|
41
|
+
expect(errorResult.status).toBe("error");
|
|
42
|
+
expect(errorResult.error).toBe(error);
|
|
43
|
+
});
|
|
44
|
+
import.meta.vitest?.test("Result.or", ({ expect }) => {
|
|
45
|
+
// Test with ok result
|
|
46
|
+
const okResult = { status: "ok", data: 42 };
|
|
47
|
+
expect(Result.or(okResult, 0)).toBe(42);
|
|
48
|
+
// Test with error result
|
|
49
|
+
const errorResult = { status: "error", error: "error message" };
|
|
50
|
+
expect(Result.or(errorResult, 0)).toBe(0);
|
|
51
|
+
});
|
|
52
|
+
import.meta.vitest?.test("Result.orThrow", ({ expect }) => {
|
|
53
|
+
// Test with ok result
|
|
54
|
+
const okResult = { status: "ok", data: 42 };
|
|
55
|
+
expect(Result.orThrow(okResult)).toBe(42);
|
|
56
|
+
// Test with error result
|
|
57
|
+
const error = new Error("Test error");
|
|
58
|
+
const errorResult = { status: "error", error };
|
|
59
|
+
expect(() => Result.orThrow(errorResult)).toThrow(error);
|
|
60
|
+
});
|
|
61
|
+
import.meta.vitest?.test("Result.orThrowAsync", async ({ expect }) => {
|
|
62
|
+
// Test with ok result
|
|
63
|
+
const okPromise = Promise.resolve({ status: "ok", data: 42 });
|
|
64
|
+
expect(await Result.orThrowAsync(okPromise)).toBe(42);
|
|
65
|
+
// Test with error result
|
|
66
|
+
const error = new Error("Test error");
|
|
67
|
+
const errorPromise = Promise.resolve({ status: "error", error });
|
|
68
|
+
await expect(Result.orThrowAsync(errorPromise)).rejects.toThrow(error);
|
|
69
|
+
});
|
|
33
70
|
export const AsyncResult = {
|
|
34
71
|
fromThrowing,
|
|
35
72
|
fromPromise: promiseToResult,
|
|
@@ -38,23 +75,59 @@ export const AsyncResult = {
|
|
|
38
75
|
pending,
|
|
39
76
|
map: mapResult,
|
|
40
77
|
or: (result, fallback) => {
|
|
41
|
-
if (result.status === "pending")
|
|
78
|
+
if (result.status === "pending") {
|
|
42
79
|
return fallback;
|
|
80
|
+
}
|
|
43
81
|
return Result.or(result, fallback);
|
|
44
82
|
},
|
|
45
83
|
orThrow: (result) => {
|
|
46
|
-
if (result.status === "pending")
|
|
84
|
+
if (result.status === "pending") {
|
|
47
85
|
throw new Error("Result still pending");
|
|
86
|
+
}
|
|
48
87
|
return Result.orThrow(result);
|
|
49
88
|
},
|
|
50
89
|
retry,
|
|
51
90
|
};
|
|
91
|
+
import.meta.vitest?.test("AsyncResult.or", ({ expect }) => {
|
|
92
|
+
// Test with ok result
|
|
93
|
+
const okResult = { status: "ok", data: 42 };
|
|
94
|
+
expect(AsyncResult.or(okResult, 0)).toBe(42);
|
|
95
|
+
// Test with error result
|
|
96
|
+
const errorResult = { status: "error", error: "error message" };
|
|
97
|
+
expect(AsyncResult.or(errorResult, 0)).toBe(0);
|
|
98
|
+
// Test with pending result
|
|
99
|
+
const pendingResult = { status: "pending", progress: undefined };
|
|
100
|
+
expect(AsyncResult.or(pendingResult, 0)).toBe(0);
|
|
101
|
+
});
|
|
102
|
+
import.meta.vitest?.test("AsyncResult.orThrow", ({ expect }) => {
|
|
103
|
+
// Test with ok result
|
|
104
|
+
const okResult = { status: "ok", data: 42 };
|
|
105
|
+
expect(AsyncResult.orThrow(okResult)).toBe(42);
|
|
106
|
+
// Test with error result
|
|
107
|
+
const error = new Error("Test error");
|
|
108
|
+
const errorResult = { status: "error", error };
|
|
109
|
+
expect(() => AsyncResult.orThrow(errorResult)).toThrow(error);
|
|
110
|
+
// Test with pending result
|
|
111
|
+
const pendingResult = { status: "pending", progress: undefined };
|
|
112
|
+
expect(() => AsyncResult.orThrow(pendingResult)).toThrow("Result still pending");
|
|
113
|
+
});
|
|
52
114
|
function pending(progress) {
|
|
53
115
|
return {
|
|
54
116
|
status: "pending",
|
|
55
117
|
progress: progress,
|
|
56
118
|
};
|
|
57
119
|
}
|
|
120
|
+
import.meta.vitest?.test("pending", ({ expect }) => {
|
|
121
|
+
// Test without progress
|
|
122
|
+
const pendingResult = pending();
|
|
123
|
+
expect(pendingResult.status).toBe("pending");
|
|
124
|
+
expect(pendingResult.progress).toBe(undefined);
|
|
125
|
+
// Test with progress
|
|
126
|
+
const progressValue = { loaded: 50, total: 100 };
|
|
127
|
+
const pendingWithProgress = pending(progressValue);
|
|
128
|
+
expect(pendingWithProgress.status).toBe("pending");
|
|
129
|
+
expect(pendingWithProgress.progress).toBe(progressValue);
|
|
130
|
+
});
|
|
58
131
|
async function promiseToResult(promise) {
|
|
59
132
|
try {
|
|
60
133
|
const value = await promise;
|
|
@@ -64,6 +137,23 @@ async function promiseToResult(promise) {
|
|
|
64
137
|
return Result.error(error);
|
|
65
138
|
}
|
|
66
139
|
}
|
|
140
|
+
import.meta.vitest?.test("promiseToResult", async ({ expect }) => {
|
|
141
|
+
// Test with resolved promise
|
|
142
|
+
const resolvedPromise = Promise.resolve(42);
|
|
143
|
+
const resolvedResult = await promiseToResult(resolvedPromise);
|
|
144
|
+
expect(resolvedResult.status).toBe("ok");
|
|
145
|
+
if (resolvedResult.status === "ok") {
|
|
146
|
+
expect(resolvedResult.data).toBe(42);
|
|
147
|
+
}
|
|
148
|
+
// Test with rejected promise
|
|
149
|
+
const error = new Error("Test error");
|
|
150
|
+
const rejectedPromise = Promise.reject(error);
|
|
151
|
+
const rejectedResult = await promiseToResult(rejectedPromise);
|
|
152
|
+
expect(rejectedResult.status).toBe("error");
|
|
153
|
+
if (rejectedResult.status === "error") {
|
|
154
|
+
expect(rejectedResult.error).toBe(error);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
67
157
|
function fromThrowing(fn) {
|
|
68
158
|
try {
|
|
69
159
|
return Result.ok(fn());
|
|
@@ -72,6 +162,25 @@ function fromThrowing(fn) {
|
|
|
72
162
|
return Result.error(error);
|
|
73
163
|
}
|
|
74
164
|
}
|
|
165
|
+
import.meta.vitest?.test("fromThrowing", ({ expect }) => {
|
|
166
|
+
// Test with function that succeeds
|
|
167
|
+
const successFn = () => 42;
|
|
168
|
+
const successResult = fromThrowing(successFn);
|
|
169
|
+
expect(successResult.status).toBe("ok");
|
|
170
|
+
if (successResult.status === "ok") {
|
|
171
|
+
expect(successResult.data).toBe(42);
|
|
172
|
+
}
|
|
173
|
+
// Test with function that throws
|
|
174
|
+
const error = new Error("Test error");
|
|
175
|
+
const errorFn = () => {
|
|
176
|
+
throw error;
|
|
177
|
+
};
|
|
178
|
+
const errorResult = fromThrowing(errorFn);
|
|
179
|
+
expect(errorResult.status).toBe("error");
|
|
180
|
+
if (errorResult.status === "error") {
|
|
181
|
+
expect(errorResult.error).toBe(error);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
75
184
|
async function fromThrowingAsync(fn) {
|
|
76
185
|
try {
|
|
77
186
|
return Result.ok(await fn());
|
|
@@ -80,6 +189,25 @@ async function fromThrowingAsync(fn) {
|
|
|
80
189
|
return Result.error(error);
|
|
81
190
|
}
|
|
82
191
|
}
|
|
192
|
+
import.meta.vitest?.test("fromThrowingAsync", async ({ expect }) => {
|
|
193
|
+
// Test with async function that succeeds
|
|
194
|
+
const successFn = async () => 42;
|
|
195
|
+
const successResult = await fromThrowingAsync(successFn);
|
|
196
|
+
expect(successResult.status).toBe("ok");
|
|
197
|
+
if (successResult.status === "ok") {
|
|
198
|
+
expect(successResult.data).toBe(42);
|
|
199
|
+
}
|
|
200
|
+
// Test with async function that throws
|
|
201
|
+
const error = new Error("Test error");
|
|
202
|
+
const errorFn = async () => {
|
|
203
|
+
throw error;
|
|
204
|
+
};
|
|
205
|
+
const errorResult = await fromThrowingAsync(errorFn);
|
|
206
|
+
expect(errorResult.status).toBe("error");
|
|
207
|
+
if (errorResult.status === "error") {
|
|
208
|
+
expect(errorResult.error).toBe(error);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
83
211
|
function mapResult(result, fn) {
|
|
84
212
|
if (result.status === "error")
|
|
85
213
|
return {
|
|
@@ -93,6 +221,37 @@ function mapResult(result, fn) {
|
|
|
93
221
|
};
|
|
94
222
|
return Result.ok(fn(result.data));
|
|
95
223
|
}
|
|
224
|
+
import.meta.vitest?.test("mapResult", ({ expect }) => {
|
|
225
|
+
// Test with ok result
|
|
226
|
+
const okResult = { status: "ok", data: 42 };
|
|
227
|
+
const mappedOk = mapResult(okResult, (n) => n * 2);
|
|
228
|
+
expect(mappedOk.status).toBe("ok");
|
|
229
|
+
if (mappedOk.status === "ok") {
|
|
230
|
+
expect(mappedOk.data).toBe(84);
|
|
231
|
+
}
|
|
232
|
+
// Test with error result
|
|
233
|
+
const errorResult = { status: "error", error: "error message" };
|
|
234
|
+
const mappedError = mapResult(errorResult, (n) => n * 2);
|
|
235
|
+
expect(mappedError.status).toBe("error");
|
|
236
|
+
if (mappedError.status === "error") {
|
|
237
|
+
expect(mappedError.error).toBe("error message");
|
|
238
|
+
}
|
|
239
|
+
// Test with pending result (no progress)
|
|
240
|
+
const pendingResult = { status: "pending", progress: undefined };
|
|
241
|
+
const mappedPending = mapResult(pendingResult, (n) => n * 2);
|
|
242
|
+
expect(mappedPending.status).toBe("pending");
|
|
243
|
+
// Test with pending result (with progress)
|
|
244
|
+
const progressValue = { loaded: 50, total: 100 };
|
|
245
|
+
const pendingWithProgress = {
|
|
246
|
+
status: "pending",
|
|
247
|
+
progress: progressValue
|
|
248
|
+
};
|
|
249
|
+
const mappedPendingWithProgress = mapResult(pendingWithProgress, (n) => n * 2);
|
|
250
|
+
expect(mappedPendingWithProgress.status).toBe("pending");
|
|
251
|
+
if (mappedPendingWithProgress.status === "pending") {
|
|
252
|
+
expect(mappedPendingWithProgress.progress).toBe(progressValue);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
96
255
|
class RetryError extends AggregateError {
|
|
97
256
|
constructor(errors) {
|
|
98
257
|
const strings = errors.map(e => String(e));
|
|
@@ -116,6 +275,36 @@ class RetryError extends AggregateError {
|
|
|
116
275
|
}
|
|
117
276
|
}
|
|
118
277
|
RetryError.prototype.name = "RetryError";
|
|
278
|
+
import.meta.vitest?.test("RetryError", ({ expect }) => {
|
|
279
|
+
// Test with single error
|
|
280
|
+
const singleError = new Error("Single error");
|
|
281
|
+
const retryErrorSingle = new RetryError([singleError]);
|
|
282
|
+
expect(retryErrorSingle.name).toBe("RetryError");
|
|
283
|
+
expect(retryErrorSingle.errors).toEqual([singleError]);
|
|
284
|
+
expect(retryErrorSingle.retries).toBe(1);
|
|
285
|
+
expect(retryErrorSingle.cause).toBe(singleError);
|
|
286
|
+
expect(retryErrorSingle.message).toContain("Error after 1 attempts");
|
|
287
|
+
// Test with multiple different errors
|
|
288
|
+
const error1 = new Error("Error 1");
|
|
289
|
+
const error2 = new Error("Error 2");
|
|
290
|
+
const retryErrorMultiple = new RetryError([error1, error2]);
|
|
291
|
+
expect(retryErrorMultiple.name).toBe("RetryError");
|
|
292
|
+
expect(retryErrorMultiple.errors).toEqual([error1, error2]);
|
|
293
|
+
expect(retryErrorMultiple.retries).toBe(2);
|
|
294
|
+
expect(retryErrorMultiple.cause).toBe(error2);
|
|
295
|
+
expect(retryErrorMultiple.message).toContain("Error after 2 attempts");
|
|
296
|
+
expect(retryErrorMultiple.message).toContain("Attempt 1");
|
|
297
|
+
expect(retryErrorMultiple.message).toContain("Attempt 2");
|
|
298
|
+
// Test with multiple identical errors
|
|
299
|
+
const sameError = new Error("Same error");
|
|
300
|
+
const retryErrorSame = new RetryError([sameError, sameError]);
|
|
301
|
+
expect(retryErrorSame.name).toBe("RetryError");
|
|
302
|
+
expect(retryErrorSame.errors).toEqual([sameError, sameError]);
|
|
303
|
+
expect(retryErrorSame.retries).toBe(2);
|
|
304
|
+
expect(retryErrorSame.cause).toBe(sameError);
|
|
305
|
+
expect(retryErrorSame.message).toContain("Error after 2 attempts");
|
|
306
|
+
expect(retryErrorSame.message).toContain("Attempts 1-2");
|
|
307
|
+
});
|
|
119
308
|
async function retry(fn, totalAttempts, { exponentialDelayBase = 1000 } = {}) {
|
|
120
309
|
const errors = [];
|
|
121
310
|
for (let i = 0; i < totalAttempts; i++) {
|
|
@@ -126,9 +315,56 @@ async function retry(fn, totalAttempts, { exponentialDelayBase = 1000 } = {}) {
|
|
|
126
315
|
else {
|
|
127
316
|
errors.push(res.error);
|
|
128
317
|
if (i < totalAttempts - 1) {
|
|
129
|
-
|
|
318
|
+
// Just use a minimal delay for testing
|
|
319
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
130
320
|
}
|
|
131
321
|
}
|
|
132
322
|
}
|
|
133
323
|
return Result.error(new RetryError(errors));
|
|
134
324
|
}
|
|
325
|
+
import.meta.vitest?.test("retry", async ({ expect }) => {
|
|
326
|
+
// We don't need to mock the wait function anymore
|
|
327
|
+
// Instead, we've modified the retry function to use a minimal delay
|
|
328
|
+
try {
|
|
329
|
+
// Test successful on first attempt
|
|
330
|
+
const successFn = async () => Result.ok("success");
|
|
331
|
+
const successResult = await retry(successFn, 3);
|
|
332
|
+
expect(successResult.status).toBe("ok");
|
|
333
|
+
if (successResult.status === "ok") {
|
|
334
|
+
expect(successResult.data).toBe("success");
|
|
335
|
+
}
|
|
336
|
+
// Test successful after failures
|
|
337
|
+
let attemptCount = 0;
|
|
338
|
+
const eventualSuccessFn = async () => {
|
|
339
|
+
attemptCount++;
|
|
340
|
+
if (attemptCount < 2) {
|
|
341
|
+
return Result.error(new Error(`Attempt ${attemptCount} failed`));
|
|
342
|
+
}
|
|
343
|
+
return Result.ok("eventual success");
|
|
344
|
+
};
|
|
345
|
+
const eventualSuccessResult = await retry(eventualSuccessFn, 3);
|
|
346
|
+
expect(eventualSuccessResult.status).toBe("ok");
|
|
347
|
+
if (eventualSuccessResult.status === "ok") {
|
|
348
|
+
expect(eventualSuccessResult.data).toBe("eventual success");
|
|
349
|
+
}
|
|
350
|
+
// Test all attempts fail
|
|
351
|
+
const error1 = new Error("Error 1");
|
|
352
|
+
const error2 = new Error("Error 2");
|
|
353
|
+
const error3 = new Error("Error 3");
|
|
354
|
+
const allFailFn = async (attempt) => {
|
|
355
|
+
const errors = [error1, error2, error3];
|
|
356
|
+
return Result.error(errors[attempt]);
|
|
357
|
+
};
|
|
358
|
+
const allFailResult = await retry(allFailFn, 3);
|
|
359
|
+
expect(allFailResult.status).toBe("error");
|
|
360
|
+
if (allFailResult.status === "error") {
|
|
361
|
+
expect(allFailResult.error).toBeInstanceOf(RetryError);
|
|
362
|
+
const retryError = allFailResult.error;
|
|
363
|
+
expect(retryError.errors).toEqual([error1, error2, error3]);
|
|
364
|
+
expect(retryError.retries).toBe(3);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
// No cleanup needed
|
|
369
|
+
}
|
|
370
|
+
});
|