yield-result 0.1.1 → 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 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
+ [![npm version](https://img.shields.io/npm/v/yield-result.svg?style=flat)](https://www.npmjs.com/package/yield-result)
6
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/yield-result.svg)](https://bundlephobia.com/package/yield-result)
5
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.5+-blue.svg)](https://www.typescriptlang.org/)
6
8
  [![Vitest](https://img.shields.io/badge/tested%20with-vitest-blueviolet.svg)](https://vitest.dev/)
7
9
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](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/flow.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { Result, Err } from "./types.js";
2
+ type InferYieldErr<G> = G extends Generator<infer Y, unknown, unknown> ? Y extends Err<infer E> ? E : never : G extends AsyncGenerator<infer Y, unknown, unknown> ? Y extends Err<infer E> ? E : never : never;
3
+ type InferReturn<G> = G extends Generator<unknown, infer R, unknown> ? R : G extends AsyncGenerator<unknown, infer R, unknown> ? R : never;
2
4
  /**
3
5
  * Control flow runners for evaluating generator functions with short-circuiting on Err.
4
6
  */
@@ -6,11 +8,14 @@ export declare const safe: {
6
8
  /**
7
9
  * Evaluates a synchronous generator function, returning Ok(value) on completion,
8
10
  * or short-circuiting on the first yielded Err(error).
11
+ * Automatically infers and unifies error types yielded across all steps.
9
12
  */
10
- sync: <T, E = unknown>(fn: () => Generator<Err<E>, T, unknown>) => Result<T, E>;
13
+ sync: <G extends Generator<Err<unknown>, unknown, unknown>>(fn: () => G) => Result<InferReturn<G>, InferYieldErr<G>>;
11
14
  /**
12
15
  * Evaluates an async generator function, returning Ok(value) on completion,
13
16
  * or short-circuiting on the first yielded Err(error).
17
+ * Automatically infers and unifies error types yielded across all steps.
14
18
  */
15
- async: <T, E = unknown>(fn: () => AsyncGenerator<Err<E>, T, unknown>) => Promise<Result<T, E>>;
19
+ async: <G extends AsyncGenerator<Err<unknown>, unknown, unknown>>(fn: () => G) => Promise<Result<InferReturn<G>, InferYieldErr<G>>>;
16
20
  };
21
+ export {};
package/dist/flow.js CHANGED
@@ -6,6 +6,7 @@ export const safe = {
6
6
  /**
7
7
  * Evaluates a synchronous generator function, returning Ok(value) on completion,
8
8
  * or short-circuiting on the first yielded Err(error).
9
+ * Automatically infers and unifies error types yielded across all steps.
9
10
  */
10
11
  sync: (fn) => {
11
12
  let gen;
@@ -56,6 +57,7 @@ export const safe = {
56
57
  /**
57
58
  * Evaluates an async generator function, returning Ok(value) on completion,
58
59
  * or short-circuiting on the first yielded Err(error).
60
+ * Automatically infers and unifies error types yielded across all steps.
59
61
  */
60
62
  async: async (fn) => {
61
63
  let gen;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yield-result",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Rust-style ? operator for TypeScript using generators (yield*). Zero dependencies, type-safe error handling.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",