Package Exports
- @voiys/tagged-result
Readme
ð·ïļ Tagged Result
A lightweight TypeScript utility for creating tagged result unions with excellent type inference. Handle success/error outcomes with descriptive types that TypeScript can narrow automatically. ð
âĻ Features
- ð§ Smart inference: TypeScript automatically infers and narrows types
- ð·ïļ Automatic prefixing: Custom tags are automatically prefixed with
SUCCESS_
orERROR_
- ðŠķ Lightweight: Zero dependencies, minimal footprint
ðĶ Installation
npm install @voiys/tagged-result
yarn add @voiys/tagged-result
pnpm add @voiys/tagged-result
bun add @voiys/tagged-result
ð Quick Start
import { Result } from '@voiys/tagged-result';
// Default success/error types
const success = Result.ok({ id: 123, name: "Alice" });
// Type: { type: "SUCCESS", data: { id: number, name: string } }
const error = Result.err({ message: "Something went wrong" });
// Type: { type: "ERROR", data: { message: string } }
// Custom tagged variants (automatically prefixed)
const tagged = Result.ok("USER_CREATED", { id: 123, name: "Alice" });
// Type: { type: "SUCCESS_USER_CREATED", data: { id: number, name: string } }
const failure = Result.err("VALIDATION", { field: "email" });
// Type: { type: "ERROR_VALIDATION", data: { field: string } }
// TypeScript narrows automatically
if (tagged.type === "SUCCESS_USER_CREATED") {
console.log(tagged.data.name); // TypeScript knows this is string
}
ð API Reference
Result.ok(data)
& Result.ok(type, data)
Creates a successful result with optional custom type tag following the pattern "SUCCESS"
or "SUCCESS_${Uppercase<string>}"
.
// Using default "SUCCESS" type
const result1 = Result.ok({ value: 42 });
// Type: DefaultSuccessResultType<{ value: number }>
// Using custom type
const result2 = Result.ok("DATA_LOADED", { items: [] });
// Type: SuccessResultType<"DATA_LOADED", { items: any[] }>
Result.err(data)
& Result.err(type, data)
Creates an error result with optional custom type tag following the pattern "ERROR"
or "ERROR_${Uppercase<string>}"
.
// Using default "ERROR" type
const result1 = Result.err({ message: "Something went wrong" });
// Type: DefaultErrorResultType<{ message: string }>
// Using custom type
const result2 = Result.err("NOT_FOUND", { resourceId: "user-123" });
// Type: ErrorResultType<"NOT_FOUND", { resourceId: string }>
Type Definitions
SuccessResultType<T, D>
& ErrorResultType<T, D>
The core types for tagged results with SUCCESS_* and ERROR_* variants.
type MySuccessResult = SuccessResultType<"PROCESSED", { message: string }>;
// Results in: { type: "SUCCESS_PROCESSED", data: { message: string } }
type MyErrorResult = ErrorResultType<"FAILED", { message: string }>;
// Results in: { type: "ERROR_FAILED", data: { message: string } }
DefaultSuccessResultType<D>
& DefaultErrorResultType<D>
Simplified types for default SUCCESS and ERROR results.
type MyDefaultSuccess = DefaultSuccessResultType<{ message: string }>;
// Results in: { type: "SUCCESS", data: { message: string } }
type MyDefaultError = DefaultErrorResultType<{ message: string }>;
// Results in: { type: "ERROR", data: { message: string } }
ð Synchronous Example
import { Result, SuccessResultType, ErrorResultType } from '@voiys/tagged-result';
// Let TypeScript infer the return type
function parseNumber(input: string) {
const num = parseInt(input);
if (isNaN(num)) {
return Result.err("INVALID_NUMBER", { input });
}
return Result.ok("PARSED", { value: num });
}
// Or force a specific return type
function validateUser(data: any): SuccessResultType<"VALID", { id: number }> | ErrorResultType<"INVALID", { error: string }> {
if (!data.id || typeof data.id !== 'number') {
return Result.err("INVALID", { error: "ID must be a number" });
}
return Result.ok("VALID", { id: data.id });
}
// Usage
const result = parseNumber("42");
if (result.type === "SUCCESS_PARSED") {
console.log(result.data.value); // TypeScript knows this is number
}
⥠Asynchronous Example
// Let TypeScript infer the return type
async function fetchData(url: string) {
try {
const response = await fetch(url);
if (!response.ok) {
return Result.err("HTTP_ERROR", { status: response.status });
}
const data = await response.json();
return Result.ok(data);
} catch (error) {
return Result.err("NETWORK_ERROR", { message: error.message });
}
}
// Or force a specific return type
async function getUser(id: number): Promise<SuccessResultType<"USER_FOUND", User> | ErrorResultType<"NOT_FOUND", { message: string }> | DefaultErrorResultType<{ message: string }>> {
try {
const response = await fetch(`/users/${id}`);
if (response.status === 404) {
return Result.err("NOT_FOUND", { message: "User not found" });
}
if (!response.ok) {
return Result.err({ message: "Failed to fetch user" });
}
const user = await response.json();
return Result.ok("USER_FOUND", user);
} catch (error) {
return Result.err({ message: error.message });
}
}
// Usage
const userResult = await getUser(123);
switch (userResult.type) {
case "SUCCESS_USER_FOUND":
console.log('User:', userResult.data); // TypeScript knows this is User
break;
case "ERROR_NOT_FOUND":
case "ERROR":
console.error(userResult.data.message);
break;
}
ðŊ Best Practices
1. Use Descriptive Tags ð·ïļ
// â Not descriptive
Result.err("ERROR", { message: "Failed" });
// â
Descriptive and actionable
Result.err("VALIDATION_FAILED", { field: "email", message: "Invalid email format" });
2. Group Related Result Types ðĶ
type UserOperationResult =
| SuccessResultType<"USER_CREATED" | "USER_UPDATED", User>
| ErrorResultType<"USER_NOT_FOUND" | "VALIDATION" | "PERMISSION_DENIED", { message: string }>
| DefaultErrorResultType<{ message: string }>;
3. Use Switch Statements for Exhaustive Checking â
function handleResult(result: UserOperationResult) {
switch (result.type) {
case "SUCCESS_USER_CREATED":
case "SUCCESS_USER_UPDATED":
return result.data; // TypeScript knows this is User
case "ERROR_USER_NOT_FOUND":
case "ERROR_VALIDATION":
case "ERROR_PERMISSION_DENIED":
case "ERROR":
throw new Error(result.data.message);
default:
// TypeScript will error if we miss a case
const exhaustive: never = result;
throw new Error('Unhandled result type');
}
}
4. Chain Operations Safely ð
async function processUserWorkflow(userId: number) {
const userResult = await fetchUser(userId);
if (userResult.type !== "SUCCESS") {
return userResult; // Propagate error
}
const validationResult = validateUser(userResult.data.data);
if (validationResult.type !== "SUCCESS_VALID") {
return validationResult; // Propagate validation error
}
// Continue with valid user...
return Result.ok("WORKFLOW_COMPLETE", validationResult.data);
}
ðĪ Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
ð License
This project is licensed under the Unlicense - see the LICENSE file for details.
ð Acknowledgments
- Inspired by Rust's
Result<T, E>
type - Built with TypeScript's powerful type system
- Designed for modern JavaScript/TypeScript applications
Made with âĪïļ by voiys