@outfitter/contracts 0.1.0-rc.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 +50 -0
- package/dist/actions.d.ts +388 -0
- package/dist/actions.js +32 -0
- package/dist/adapters.d.ts +182 -0
- package/dist/adapters.js +1 -0
- package/dist/assert/index.d.ts +63 -0
- package/dist/assert/index.js +36 -0
- package/dist/capabilities.d.ts +19 -0
- package/dist/capabilities.js +66 -0
- package/dist/context.d.ts +125 -0
- package/dist/context.js +36 -0
- package/dist/envelope.d.ts +328 -0
- package/dist/envelope.js +56 -0
- package/dist/errors.d.ts +261 -0
- package/dist/errors.js +171 -0
- package/dist/handler.d.ts +318 -0
- package/dist/handler.js +1 -0
- package/dist/index.d.ts +1354 -0
- package/dist/index.js +137 -0
- package/dist/recovery.d.ts +150 -0
- package/dist/recovery.js +56 -0
- package/dist/redactor.d.ts +100 -0
- package/dist/redactor.js +111 -0
- package/dist/resilience.d.ts +299 -0
- package/dist/resilience.js +82 -0
- package/dist/result/index.d.ts +103 -0
- package/dist/result/index.js +13 -0
- package/dist/result/utilities.d.ts +103 -0
- package/dist/result/utilities.js +31 -0
- package/dist/serialization.d.ts +313 -0
- package/dist/serialization.js +270 -0
- package/dist/validation.d.ts +59 -0
- package/dist/validation.js +42 -0
- package/package.json +143 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { TaggedErrorClass } from "better-result";
|
|
2
|
+
declare const ValidationErrorBase: TaggedErrorClass<"ValidationError", {
|
|
3
|
+
message: string;
|
|
4
|
+
field?: string;
|
|
5
|
+
}>;
|
|
6
|
+
declare const AssertionErrorBase: TaggedErrorClass<"AssertionError", {
|
|
7
|
+
message: string;
|
|
8
|
+
}>;
|
|
9
|
+
declare const NotFoundErrorBase: TaggedErrorClass<"NotFoundError", {
|
|
10
|
+
message: string;
|
|
11
|
+
resourceType: string;
|
|
12
|
+
resourceId: string;
|
|
13
|
+
}>;
|
|
14
|
+
declare const ConflictErrorBase: TaggedErrorClass<"ConflictError", {
|
|
15
|
+
message: string;
|
|
16
|
+
context?: Record<string, unknown>;
|
|
17
|
+
}>;
|
|
18
|
+
declare const PermissionErrorBase: TaggedErrorClass<"PermissionError", {
|
|
19
|
+
message: string;
|
|
20
|
+
context?: Record<string, unknown>;
|
|
21
|
+
}>;
|
|
22
|
+
declare const TimeoutErrorBase: TaggedErrorClass<"TimeoutError", {
|
|
23
|
+
message: string;
|
|
24
|
+
operation: string;
|
|
25
|
+
timeoutMs: number;
|
|
26
|
+
}>;
|
|
27
|
+
declare const RateLimitErrorBase: TaggedErrorClass<"RateLimitError", {
|
|
28
|
+
message: string;
|
|
29
|
+
retryAfterSeconds?: number;
|
|
30
|
+
}>;
|
|
31
|
+
declare const NetworkErrorBase: TaggedErrorClass<"NetworkError", {
|
|
32
|
+
message: string;
|
|
33
|
+
context?: Record<string, unknown>;
|
|
34
|
+
}>;
|
|
35
|
+
declare const InternalErrorBase: TaggedErrorClass<"InternalError", {
|
|
36
|
+
message: string;
|
|
37
|
+
context?: Record<string, unknown>;
|
|
38
|
+
}>;
|
|
39
|
+
declare const AuthErrorBase: TaggedErrorClass<"AuthError", {
|
|
40
|
+
message: string;
|
|
41
|
+
reason?: "missing" | "invalid" | "expired";
|
|
42
|
+
}>;
|
|
43
|
+
declare const CancelledErrorBase: TaggedErrorClass<"CancelledError", {
|
|
44
|
+
message: string;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Input validation failed.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* new ValidationError({ message: "Email format invalid", field: "email" });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare class ValidationError extends ValidationErrorBase {
|
|
55
|
+
readonly category: "validation";
|
|
56
|
+
exitCode(): number;
|
|
57
|
+
statusCode(): number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Assertion failed (invariant violation).
|
|
61
|
+
*
|
|
62
|
+
* Used by assertion utilities that return Result types instead of throwing.
|
|
63
|
+
* AssertionError indicates a programming bug — an invariant that should
|
|
64
|
+
* never be violated was broken. These are internal errors, not user input
|
|
65
|
+
* validation failures.
|
|
66
|
+
*
|
|
67
|
+
* **Category rationale**: Uses `internal` (not `validation`) because:
|
|
68
|
+
* - Assertions check **invariants** (programmer assumptions), not user input
|
|
69
|
+
* - A failed assertion means "this should be impossible if the code is correct"
|
|
70
|
+
* - User-facing validation uses {@link ValidationError} with helpful field info
|
|
71
|
+
* - HTTP 500 is correct: this is a server bug, not a client mistake
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* // In domain logic after validation has passed
|
|
76
|
+
* const result = assertDefined(cachedValue, "Cache should always have value after init");
|
|
77
|
+
* if (result.isErr()) {
|
|
78
|
+
* return result; // Propagate as internal error
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @see ValidationError - For user input validation failures (HTTP 400)
|
|
83
|
+
*/
|
|
84
|
+
declare class AssertionError extends AssertionErrorBase {
|
|
85
|
+
readonly category: "internal";
|
|
86
|
+
exitCode(): number;
|
|
87
|
+
statusCode(): number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Requested resource not found.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* new NotFoundError({ message: "note not found: abc123", resourceType: "note", resourceId: "abc123" });
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
declare class NotFoundError extends NotFoundErrorBase {
|
|
98
|
+
readonly category: "not_found";
|
|
99
|
+
exitCode(): number;
|
|
100
|
+
statusCode(): number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* State conflict (optimistic locking, concurrent modification).
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* new ConflictError({ message: "Resource was modified by another process" });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
declare class ConflictError extends ConflictErrorBase {
|
|
111
|
+
readonly category: "conflict";
|
|
112
|
+
exitCode(): number;
|
|
113
|
+
statusCode(): number;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Authorization denied.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* new PermissionError({ message: "Cannot delete read-only resource" });
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
declare class PermissionError extends PermissionErrorBase {
|
|
124
|
+
readonly category: "permission";
|
|
125
|
+
exitCode(): number;
|
|
126
|
+
statusCode(): number;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Operation timed out.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* new TimeoutError({ message: "Database query timed out after 5000ms", operation: "Database query", timeoutMs: 5000 });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
declare class TimeoutError extends TimeoutErrorBase {
|
|
137
|
+
readonly category: "timeout";
|
|
138
|
+
exitCode(): number;
|
|
139
|
+
statusCode(): number;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Rate limit exceeded.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* new RateLimitError({ message: "Rate limit exceeded, retry after 60s", retryAfterSeconds: 60 });
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
declare class RateLimitError extends RateLimitErrorBase {
|
|
150
|
+
readonly category: "rate_limit";
|
|
151
|
+
exitCode(): number;
|
|
152
|
+
statusCode(): number;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Network/transport failure.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* new NetworkError({ message: "Connection refused to api.example.com" });
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
declare class NetworkError extends NetworkErrorBase {
|
|
163
|
+
readonly category: "network";
|
|
164
|
+
exitCode(): number;
|
|
165
|
+
statusCode(): number;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Unexpected internal error.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* new InternalError({ message: "Unexpected state in processor" });
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
declare class InternalError extends InternalErrorBase {
|
|
176
|
+
readonly category: "internal";
|
|
177
|
+
exitCode(): number;
|
|
178
|
+
statusCode(): number;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Authentication failed (missing or invalid credentials).
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* new AuthError({ message: "Invalid API key", reason: "invalid" });
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
declare class AuthError extends AuthErrorBase {
|
|
189
|
+
readonly category: "auth";
|
|
190
|
+
exitCode(): number;
|
|
191
|
+
statusCode(): number;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Operation cancelled by user or signal.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```typescript
|
|
198
|
+
* new CancelledError({ message: "Operation cancelled by user" });
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
declare class CancelledError extends CancelledErrorBase {
|
|
202
|
+
readonly category: "cancelled";
|
|
203
|
+
exitCode(): number;
|
|
204
|
+
statusCode(): number;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Union type of all concrete error class instances.
|
|
208
|
+
*/
|
|
209
|
+
type AnyKitError = InstanceType<typeof ValidationError> | InstanceType<typeof AssertionError> | InstanceType<typeof NotFoundError> | InstanceType<typeof ConflictError> | InstanceType<typeof PermissionError> | InstanceType<typeof TimeoutError> | InstanceType<typeof RateLimitError> | InstanceType<typeof NetworkError> | InstanceType<typeof InternalError> | InstanceType<typeof AuthError> | InstanceType<typeof CancelledError>;
|
|
210
|
+
/**
|
|
211
|
+
* Type alias for backwards compatibility with handler signatures.
|
|
212
|
+
* Use AnyKitError for the union type.
|
|
213
|
+
*/
|
|
214
|
+
type OutfitterError = AnyKitError;
|
|
215
|
+
import { Result } from "better-result";
|
|
216
|
+
/**
|
|
217
|
+
* Options for retry behavior.
|
|
218
|
+
*/
|
|
219
|
+
interface RetryOptions {
|
|
220
|
+
/** Maximum number of retry attempts (default: 3) */
|
|
221
|
+
maxAttempts?: number;
|
|
222
|
+
/** Initial delay in milliseconds (default: 1000) */
|
|
223
|
+
initialDelayMs?: number;
|
|
224
|
+
/** Maximum delay in milliseconds (default: 30000) */
|
|
225
|
+
maxDelayMs?: number;
|
|
226
|
+
/** Exponential backoff multiplier (default: 2) */
|
|
227
|
+
backoffMultiplier?: number;
|
|
228
|
+
/** Whether to add jitter to delays (default: true) */
|
|
229
|
+
jitter?: boolean;
|
|
230
|
+
/** Predicate to determine if error is retryable */
|
|
231
|
+
isRetryable?: (error: OutfitterError) => boolean;
|
|
232
|
+
/** Abort signal for cancellation */
|
|
233
|
+
signal?: AbortSignal;
|
|
234
|
+
/** Callback invoked before each retry */
|
|
235
|
+
onRetry?: (attempt: number, error: OutfitterError, delayMs: number) => void;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Options for timeout behavior.
|
|
239
|
+
*/
|
|
240
|
+
interface TimeoutOptions {
|
|
241
|
+
/** Timeout duration in milliseconds */
|
|
242
|
+
timeoutMs: number;
|
|
243
|
+
/** Operation name for error context */
|
|
244
|
+
operation?: string;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Retry an async operation with exponential backoff.
|
|
248
|
+
*
|
|
249
|
+
* Automatically retries transient errors (network, timeout, rate_limit)
|
|
250
|
+
* unless overridden with `isRetryable`.
|
|
251
|
+
*
|
|
252
|
+
* @typeParam T - Success type
|
|
253
|
+
* @param fn - Async function returning Result
|
|
254
|
+
* @param options - Retry configuration
|
|
255
|
+
* @returns Result from final attempt
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* const result = await retry(
|
|
260
|
+
* () => fetchData(url),
|
|
261
|
+
* {
|
|
262
|
+
* maxAttempts: 5,
|
|
263
|
+
* initialDelayMs: 500,
|
|
264
|
+
* onRetry: (attempt, error) => {
|
|
265
|
+
* logger.warn(`Retry ${attempt}`, { error: error._tag });
|
|
266
|
+
* },
|
|
267
|
+
* }
|
|
268
|
+
* );
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
declare function retry<T>(fn: () => Promise<Result<T, OutfitterError>>, options?: RetryOptions): Promise<Result<T, OutfitterError>>;
|
|
272
|
+
/**
|
|
273
|
+
* Wrap an async operation with a timeout.
|
|
274
|
+
*
|
|
275
|
+
* Returns TimeoutError if operation doesn't complete within the specified duration.
|
|
276
|
+
*
|
|
277
|
+
* @typeParam T - Success type
|
|
278
|
+
* @typeParam E - Error type
|
|
279
|
+
* @param fn - Async function returning Result
|
|
280
|
+
* @param options - Timeout configuration
|
|
281
|
+
* @returns Result from operation or TimeoutError
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* const result = await withTimeout(
|
|
286
|
+
* () => slowOperation(),
|
|
287
|
+
* { timeoutMs: 5000, operation: "database query" }
|
|
288
|
+
* );
|
|
289
|
+
*
|
|
290
|
+
* if (result.isErr() && result.error._tag === "TimeoutError") {
|
|
291
|
+
* // Handle timeout
|
|
292
|
+
* }
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
declare function withTimeout<
|
|
296
|
+
T,
|
|
297
|
+
E extends OutfitterError
|
|
298
|
+
>(fn: () => Promise<Result<T, E>>, options: TimeoutOptions): Promise<Result<T, E | TimeoutError>>;
|
|
299
|
+
export { withTimeout, retry, TimeoutOptions, RetryOptions };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
TimeoutError
|
|
4
|
+
} from "./errors.js";
|
|
5
|
+
|
|
6
|
+
// packages/contracts/src/resilience.ts
|
|
7
|
+
import { Result } from "better-result";
|
|
8
|
+
function defaultIsRetryable(error) {
|
|
9
|
+
return error.category === "network" || error.category === "timeout" || error.category === "rate_limit";
|
|
10
|
+
}
|
|
11
|
+
function calculateDelay(attempt, initialDelayMs, maxDelayMs, backoffMultiplier, jitter) {
|
|
12
|
+
const baseDelay = initialDelayMs * backoffMultiplier ** (attempt - 1);
|
|
13
|
+
const cappedDelay = Math.min(baseDelay, maxDelayMs);
|
|
14
|
+
if (jitter) {
|
|
15
|
+
const jitterFactor = 0.5 + Math.random();
|
|
16
|
+
return Math.floor(cappedDelay * jitterFactor);
|
|
17
|
+
}
|
|
18
|
+
return cappedDelay;
|
|
19
|
+
}
|
|
20
|
+
function sleep(ms) {
|
|
21
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
|
+
}
|
|
23
|
+
async function retry(fn, options) {
|
|
24
|
+
const maxAttempts = options?.maxAttempts ?? 3;
|
|
25
|
+
const initialDelayMs = options?.initialDelayMs ?? 1000;
|
|
26
|
+
const maxDelayMs = options?.maxDelayMs ?? 30000;
|
|
27
|
+
const backoffMultiplier = options?.backoffMultiplier ?? 2;
|
|
28
|
+
const jitter = options?.jitter ?? true;
|
|
29
|
+
const isRetryable = options?.isRetryable ?? defaultIsRetryable;
|
|
30
|
+
const onRetry = options?.onRetry;
|
|
31
|
+
const signal = options?.signal;
|
|
32
|
+
let lastError;
|
|
33
|
+
let attempt = 0;
|
|
34
|
+
while (attempt < maxAttempts) {
|
|
35
|
+
attempt++;
|
|
36
|
+
if (signal?.aborted) {
|
|
37
|
+
return Result.err(lastError ?? new TimeoutError({
|
|
38
|
+
message: "Operation cancelled",
|
|
39
|
+
operation: "retry",
|
|
40
|
+
timeoutMs: 0
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
const result = await fn();
|
|
44
|
+
if (result.isOk()) {
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
lastError = result.error;
|
|
48
|
+
if (attempt >= maxAttempts || !isRetryable(lastError)) {
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
const delayMs = calculateDelay(attempt, initialDelayMs, maxDelayMs, backoffMultiplier, jitter);
|
|
52
|
+
if (onRetry) {
|
|
53
|
+
onRetry(attempt, lastError, delayMs);
|
|
54
|
+
}
|
|
55
|
+
await sleep(delayMs);
|
|
56
|
+
}
|
|
57
|
+
throw new Error("Unexpected: retry loop completed without returning a result");
|
|
58
|
+
}
|
|
59
|
+
async function withTimeout(fn, options) {
|
|
60
|
+
const { timeoutMs, operation = "operation" } = options;
|
|
61
|
+
let timeoutId;
|
|
62
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
63
|
+
timeoutId = setTimeout(() => {
|
|
64
|
+
resolve(Result.err(new TimeoutError({
|
|
65
|
+
message: `${operation} timed out after ${timeoutMs}ms`,
|
|
66
|
+
operation,
|
|
67
|
+
timeoutMs
|
|
68
|
+
})));
|
|
69
|
+
}, timeoutMs);
|
|
70
|
+
});
|
|
71
|
+
try {
|
|
72
|
+
return await Promise.race([fn(), timeoutPromise]);
|
|
73
|
+
} finally {
|
|
74
|
+
if (timeoutId !== undefined) {
|
|
75
|
+
clearTimeout(timeoutId);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
withTimeout,
|
|
81
|
+
retry
|
|
82
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Result } from "better-result";
|
|
2
|
+
/**
|
|
3
|
+
* Extract value from Ok, or compute default from error.
|
|
4
|
+
*
|
|
5
|
+
* Unlike `unwrapOr`, the default is computed lazily only on Err.
|
|
6
|
+
* This is useful when the default value is expensive to compute
|
|
7
|
+
* and should only be evaluated when needed.
|
|
8
|
+
*
|
|
9
|
+
* @param result - The Result to unwrap
|
|
10
|
+
* @param defaultFn - Function to compute default value from error
|
|
11
|
+
* @returns The success value or computed default
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const result = Result.err("not found");
|
|
16
|
+
* const value = unwrapOrElse(result, (error) => {
|
|
17
|
+
* console.log("Computing expensive default due to:", error);
|
|
18
|
+
* return expensiveComputation();
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare const unwrapOrElse: <
|
|
23
|
+
T,
|
|
24
|
+
E
|
|
25
|
+
>(result: Result<T, E>, defaultFn: (error: E) => T) => T;
|
|
26
|
+
/**
|
|
27
|
+
* Return first Ok, or fallback if first is Err.
|
|
28
|
+
*
|
|
29
|
+
* Useful for trying alternative operations - if the first fails,
|
|
30
|
+
* fall back to an alternative Result.
|
|
31
|
+
*
|
|
32
|
+
* @param result - The primary Result to try
|
|
33
|
+
* @param fallback - The fallback Result if primary is Err
|
|
34
|
+
* @returns First Ok result, or fallback
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const primary = parseFromCache(key);
|
|
39
|
+
* const fallback = parseFromNetwork(key);
|
|
40
|
+
* const result = orElse(primary, fallback);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare const orElse: <
|
|
44
|
+
T,
|
|
45
|
+
E,
|
|
46
|
+
F
|
|
47
|
+
>(result: Result<T, E>, fallback: Result<T, F>) => Result<T, F>;
|
|
48
|
+
/**
|
|
49
|
+
* Combine two Results into a tuple Result.
|
|
50
|
+
*
|
|
51
|
+
* Returns first error if either fails, evaluated left-to-right.
|
|
52
|
+
* Useful for combining independent operations that must all succeed.
|
|
53
|
+
*
|
|
54
|
+
* @param r1 - First Result
|
|
55
|
+
* @param r2 - Second Result
|
|
56
|
+
* @returns Result containing tuple of both values, or first error
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const user = fetchUser(id);
|
|
61
|
+
* const settings = fetchSettings(id);
|
|
62
|
+
* const combined = combine2(user, settings);
|
|
63
|
+
*
|
|
64
|
+
* if (combined.isOk()) {
|
|
65
|
+
* const [userData, userSettings] = combined.value;
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
declare const combine2: <
|
|
70
|
+
T1,
|
|
71
|
+
T2,
|
|
72
|
+
E
|
|
73
|
+
>(r1: Result<T1, E>, r2: Result<T2, E>) => Result<[T1, T2], E>;
|
|
74
|
+
/**
|
|
75
|
+
* Combine three Results into a tuple Result.
|
|
76
|
+
*
|
|
77
|
+
* Returns first error if any fails, evaluated left-to-right.
|
|
78
|
+
* Useful for combining independent operations that must all succeed.
|
|
79
|
+
*
|
|
80
|
+
* @param r1 - First Result
|
|
81
|
+
* @param r2 - Second Result
|
|
82
|
+
* @param r3 - Third Result
|
|
83
|
+
* @returns Result containing tuple of all values, or first error
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const user = fetchUser(id);
|
|
88
|
+
* const settings = fetchSettings(id);
|
|
89
|
+
* const permissions = fetchPermissions(id);
|
|
90
|
+
* const combined = combine3(user, settings, permissions);
|
|
91
|
+
*
|
|
92
|
+
* if (combined.isOk()) {
|
|
93
|
+
* const [userData, userSettings, userPermissions] = combined.value;
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
declare const combine3: <
|
|
98
|
+
T1,
|
|
99
|
+
T2,
|
|
100
|
+
T3,
|
|
101
|
+
E
|
|
102
|
+
>(r1: Result<T1, E>, r2: Result<T2, E>, r3: Result<T3, E>) => Result<[T1, T2, T3], E>;
|
|
103
|
+
export { unwrapOrElse, orElse, combine3, combine2 };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Result } from "better-result";
|
|
2
|
+
/**
|
|
3
|
+
* Extract value from Ok, or compute default from error.
|
|
4
|
+
*
|
|
5
|
+
* Unlike `unwrapOr`, the default is computed lazily only on Err.
|
|
6
|
+
* This is useful when the default value is expensive to compute
|
|
7
|
+
* and should only be evaluated when needed.
|
|
8
|
+
*
|
|
9
|
+
* @param result - The Result to unwrap
|
|
10
|
+
* @param defaultFn - Function to compute default value from error
|
|
11
|
+
* @returns The success value or computed default
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const result = Result.err("not found");
|
|
16
|
+
* const value = unwrapOrElse(result, (error) => {
|
|
17
|
+
* console.log("Computing expensive default due to:", error);
|
|
18
|
+
* return expensiveComputation();
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare const unwrapOrElse: <
|
|
23
|
+
T,
|
|
24
|
+
E
|
|
25
|
+
>(result: Result<T, E>, defaultFn: (error: E) => T) => T;
|
|
26
|
+
/**
|
|
27
|
+
* Return first Ok, or fallback if first is Err.
|
|
28
|
+
*
|
|
29
|
+
* Useful for trying alternative operations - if the first fails,
|
|
30
|
+
* fall back to an alternative Result.
|
|
31
|
+
*
|
|
32
|
+
* @param result - The primary Result to try
|
|
33
|
+
* @param fallback - The fallback Result if primary is Err
|
|
34
|
+
* @returns First Ok result, or fallback
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const primary = parseFromCache(key);
|
|
39
|
+
* const fallback = parseFromNetwork(key);
|
|
40
|
+
* const result = orElse(primary, fallback);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare const orElse: <
|
|
44
|
+
T,
|
|
45
|
+
E,
|
|
46
|
+
F
|
|
47
|
+
>(result: Result<T, E>, fallback: Result<T, F>) => Result<T, F>;
|
|
48
|
+
/**
|
|
49
|
+
* Combine two Results into a tuple Result.
|
|
50
|
+
*
|
|
51
|
+
* Returns first error if either fails, evaluated left-to-right.
|
|
52
|
+
* Useful for combining independent operations that must all succeed.
|
|
53
|
+
*
|
|
54
|
+
* @param r1 - First Result
|
|
55
|
+
* @param r2 - Second Result
|
|
56
|
+
* @returns Result containing tuple of both values, or first error
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const user = fetchUser(id);
|
|
61
|
+
* const settings = fetchSettings(id);
|
|
62
|
+
* const combined = combine2(user, settings);
|
|
63
|
+
*
|
|
64
|
+
* if (combined.isOk()) {
|
|
65
|
+
* const [userData, userSettings] = combined.value;
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
declare const combine2: <
|
|
70
|
+
T1,
|
|
71
|
+
T2,
|
|
72
|
+
E
|
|
73
|
+
>(r1: Result<T1, E>, r2: Result<T2, E>) => Result<[T1, T2], E>;
|
|
74
|
+
/**
|
|
75
|
+
* Combine three Results into a tuple Result.
|
|
76
|
+
*
|
|
77
|
+
* Returns first error if any fails, evaluated left-to-right.
|
|
78
|
+
* Useful for combining independent operations that must all succeed.
|
|
79
|
+
*
|
|
80
|
+
* @param r1 - First Result
|
|
81
|
+
* @param r2 - Second Result
|
|
82
|
+
* @param r3 - Third Result
|
|
83
|
+
* @returns Result containing tuple of all values, or first error
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const user = fetchUser(id);
|
|
88
|
+
* const settings = fetchSettings(id);
|
|
89
|
+
* const permissions = fetchPermissions(id);
|
|
90
|
+
* const combined = combine3(user, settings, permissions);
|
|
91
|
+
*
|
|
92
|
+
* if (combined.isOk()) {
|
|
93
|
+
* const [userData, userSettings, userPermissions] = combined.value;
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
declare const combine3: <
|
|
98
|
+
T1,
|
|
99
|
+
T2,
|
|
100
|
+
T3,
|
|
101
|
+
E
|
|
102
|
+
>(r1: Result<T1, E>, r2: Result<T2, E>, r3: Result<T3, E>) => Result<[T1, T2, T3], E>;
|
|
103
|
+
export { unwrapOrElse, orElse, combine3, combine2 };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/contracts/src/result/utilities.ts
|
|
3
|
+
import { Result } from "better-result";
|
|
4
|
+
var unwrapOrElse = (result, defaultFn) => {
|
|
5
|
+
return result.isOk() ? result.value : defaultFn(result.error);
|
|
6
|
+
};
|
|
7
|
+
var orElse = (result, fallback) => {
|
|
8
|
+
return result.isOk() ? result : fallback;
|
|
9
|
+
};
|
|
10
|
+
var combine2 = (r1, r2) => {
|
|
11
|
+
if (r1.isErr())
|
|
12
|
+
return r1;
|
|
13
|
+
if (r2.isErr())
|
|
14
|
+
return r2;
|
|
15
|
+
return Result.ok([r1.value, r2.value]);
|
|
16
|
+
};
|
|
17
|
+
var combine3 = (r1, r2, r3) => {
|
|
18
|
+
if (r1.isErr())
|
|
19
|
+
return r1;
|
|
20
|
+
if (r2.isErr())
|
|
21
|
+
return r2;
|
|
22
|
+
if (r3.isErr())
|
|
23
|
+
return r3;
|
|
24
|
+
return Result.ok([r1.value, r2.value, r3.value]);
|
|
25
|
+
};
|
|
26
|
+
export {
|
|
27
|
+
unwrapOrElse,
|
|
28
|
+
orElse,
|
|
29
|
+
combine3,
|
|
30
|
+
combine2
|
|
31
|
+
};
|