yield-result 0.1.2 → 0.1.3
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 +121 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.js +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> Rust-style `?` operator for TypeScript using generators (`yield*`). Zero dependencies, lightweight, type-safe error handling without runtime exceptions.
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/yield-result)
|
|
6
|
+
[](https://bundlephobia.com/package/yield-result)
|
|
5
7
|
[](https://www.typescriptlang.org/)
|
|
6
8
|
[](https://vitest.dev/)
|
|
7
9
|
[](LICENSE)
|
|
@@ -129,10 +131,129 @@ const fetchRes = await fromPromise(
|
|
|
129
131
|
const asyncRes = await fromAsyncFn(() => loadData());
|
|
130
132
|
```
|
|
131
133
|
|
|
134
|
+
### 4. Strongly-Typed Errors with `taggedError`
|
|
135
|
+
|
|
136
|
+
Use `taggedError` to create clean, discriminated error unions with custom payloads:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { taggedError, err, safe, match } from "yield-result";
|
|
140
|
+
|
|
141
|
+
// Define domain errors
|
|
142
|
+
const HttpError = (status: number) => taggedError("HttpError", { status });
|
|
143
|
+
const ValidationError = (field: string) => taggedError("ValidationError", { field });
|
|
144
|
+
|
|
145
|
+
// Functions returning different error types
|
|
146
|
+
const fetchUser = (id: string) => err(HttpError(404));
|
|
147
|
+
const validateAge = (age: number) => err(ValidationError("age"));
|
|
148
|
+
|
|
149
|
+
// safe.sync automatically infers the error union: Result<User, TaggedError<"HttpError"> | TaggedError<"ValidationError">>
|
|
150
|
+
const result = safe.sync(function* () {
|
|
151
|
+
const user = yield* fetchUser("123");
|
|
152
|
+
yield* validateAge(user.age);
|
|
153
|
+
return user;
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 🏷️ Automatic Error Union Inference (`InferYieldErr`)
|
|
160
|
+
|
|
161
|
+
`yield-result` features automatic error type extraction and union distribution (`InferYieldErr<G>`). When yielding different error types across pipeline steps, TypeScript automatically unifies them without requiring manual type annotations!
|
|
162
|
+
|
|
163
|
+
### Before vs After
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
type DbError = { _tag: "DbError"; code: string };
|
|
167
|
+
type ValidationError = { _tag: "ValidationError"; field: string };
|
|
168
|
+
|
|
169
|
+
const queryUser = (): Result<User, DbError> => ...;
|
|
170
|
+
const validateUser = (u: User): Result<boolean, ValidationError> => ...;
|
|
171
|
+
|
|
172
|
+
// ❌ BEFORE: Manual generic type parameters required:
|
|
173
|
+
const res = safe.sync<User, DbError | ValidationError>(function* () {
|
|
174
|
+
const user = yield* queryUser();
|
|
175
|
+
const valid = yield* validateUser(user);
|
|
176
|
+
return user;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ✅ AFTER: 100% AUTOMATIC INFERENCE (Zero type arguments needed!):
|
|
180
|
+
// Automatically inferred as: Result<User, DbError | ValidationError>
|
|
181
|
+
const res = safe.sync(function* () {
|
|
182
|
+
const user = yield* queryUser(); // Yields Err<DbError>
|
|
183
|
+
const valid = yield* validateUser(user); // Yields Err<ValidationError>
|
|
184
|
+
return user;
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 🌐 Complete Real-World API Workflow Example
|
|
191
|
+
|
|
192
|
+
Here is a full end-to-end production API endpoint handler (**Fetch → Validation → DB → HTTP Response**):
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { safe, ok, err, fromPromise, taggedError, match, Result } from "yield-result";
|
|
196
|
+
|
|
197
|
+
// 1. Define Discriminated Domain Errors
|
|
198
|
+
const ApiError = (status: number, message: string) => taggedError("ApiError", { status, message });
|
|
199
|
+
const DbError = (query: string) => taggedError("DbError", { query });
|
|
200
|
+
const ValidationError = (field: string, reason: string) => taggedError("ValidationError", { field, reason });
|
|
201
|
+
|
|
202
|
+
type DomainError =
|
|
203
|
+
| ReturnType<typeof ApiError>
|
|
204
|
+
| ReturnType<typeof DbError>
|
|
205
|
+
| ReturnType<typeof ValidationError>;
|
|
206
|
+
|
|
207
|
+
// 2. Domain Services
|
|
208
|
+
async function fetchExternalOrder(orderId: string): Promise<Result<{ id: string; amount: number }, DomainError>> {
|
|
209
|
+
return fromPromise(
|
|
210
|
+
fetch(`https://api.payments.com/orders/${orderId}`).then(r => r.json()),
|
|
211
|
+
() => ApiError(502, "External Payment Gateway unreachable")
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function validateOrderAmount(order: { id: string; amount: number }): Result<{ id: string; amount: number }, DomainError> {
|
|
216
|
+
if (order.amount <= 0) return err(ValidationError("amount", "Order amount must be positive"));
|
|
217
|
+
return ok(order);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function saveOrderToDb(order: { id: string; amount: number }): Promise<Result<{ saved: boolean }, DomainError>> {
|
|
221
|
+
// Simulating DB write
|
|
222
|
+
return ok({ saved: true });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 3. Complete Business Pipeline with safe.async
|
|
226
|
+
export async function handleOrderCheckout(orderId: string) {
|
|
227
|
+
const pipelineResult = await safe.async(async function* () {
|
|
228
|
+
const rawOrder = yield* await fetchExternalOrder(orderId); // Step 1: External API
|
|
229
|
+
const validOrder = yield* validateOrderAmount(rawOrder); // Step 2: Domain Validation
|
|
230
|
+
const dbRecord = yield* await saveOrderToDb(validOrder); // Step 3: Database Save
|
|
231
|
+
|
|
232
|
+
return { status: 200, data: { orderId: validOrder.id, dbRecord } };
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// 4. Exhaustive Pattern Matching for HTTP Response
|
|
236
|
+
return match(pipelineResult, {
|
|
237
|
+
ok: (res) => ({ statusCode: 200, body: JSON.stringify(res) }),
|
|
238
|
+
err: (error) => {
|
|
239
|
+
switch (error._tag) {
|
|
240
|
+
case "ApiError":
|
|
241
|
+
return { statusCode: error.status, body: JSON.stringify({ error: error.message }) };
|
|
242
|
+
case "ValidationError":
|
|
243
|
+
return { statusCode: 400, body: JSON.stringify({ error: `Invalid ${error.field}: ${error.reason}` }) };
|
|
244
|
+
case "DbError":
|
|
245
|
+
return { statusCode: 500, body: JSON.stringify({ error: "Database transaction failed" }) };
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
132
252
|
---
|
|
133
253
|
|
|
134
254
|
## 🛡️ Built-in Safety Features
|
|
135
255
|
|
|
256
|
+
|
|
136
257
|
### Runtime Protection against Missing Asterisk (`*`)
|
|
137
258
|
|
|
138
259
|
Forgetting the asterisk (`yield` instead of `yield*`) is automatically intercepted at runtime and in TypeScript:
|
package/dist/types.d.ts
CHANGED
|
@@ -87,6 +87,16 @@ export declare const partition: <T, E>(results: Result<T, E>[]) => {
|
|
|
87
87
|
values: T[];
|
|
88
88
|
errors: E[];
|
|
89
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* Interface representing a discriminated error object with a `_tag` property.
|
|
92
|
+
*/
|
|
93
|
+
export interface TaggedError<T extends string> {
|
|
94
|
+
readonly _tag: T;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Creates an immutable TaggedError object with a `_tag` property and optional payload properties.
|
|
98
|
+
*/
|
|
99
|
+
export declare const taggedError: <T extends string, P extends Record<string, unknown> = {}>(tag: T, props?: P) => Readonly<TaggedError<T> & P>;
|
|
90
100
|
/**
|
|
91
101
|
* Namespace object grouping all Result constructors, guards, and combinators.
|
|
92
102
|
*/
|
|
@@ -112,4 +122,5 @@ export declare const Result: {
|
|
|
112
122
|
values: T[];
|
|
113
123
|
errors: E[];
|
|
114
124
|
};
|
|
125
|
+
readonly taggedError: <T extends string, P extends Record<string, unknown> = {}>(tag: T, props?: P) => Readonly<TaggedError<T> & P>;
|
|
115
126
|
};
|
package/dist/types.js
CHANGED
|
@@ -133,6 +133,13 @@ export const partition = (results) => {
|
|
|
133
133
|
}
|
|
134
134
|
return { values, errors };
|
|
135
135
|
};
|
|
136
|
+
/**
|
|
137
|
+
* Creates an immutable TaggedError object with a `_tag` property and optional payload properties.
|
|
138
|
+
*/
|
|
139
|
+
export const taggedError = (tag, props) => Object.freeze({
|
|
140
|
+
_tag: tag,
|
|
141
|
+
...(props ?? {}),
|
|
142
|
+
});
|
|
136
143
|
/**
|
|
137
144
|
* Namespace object grouping all Result constructors, guards, and combinators.
|
|
138
145
|
*/
|
|
@@ -152,4 +159,5 @@ export const Result = {
|
|
|
152
159
|
match,
|
|
153
160
|
all,
|
|
154
161
|
partition,
|
|
162
|
+
taggedError,
|
|
155
163
|
};
|
package/package.json
CHANGED