|
| 1 | +# @cometloop/safe |
| 2 | + |
| 3 | +Type-safe error handling library for TypeScript using the Result pattern. Zero dependencies. |
| 4 | + |
| 5 | +## **Wrap any function** |
| 6 | + |
| 7 | +- Flexible wrapping — single function, domain scope, or app-wide |
| 8 | +- Type-safe results — tuples or objects |
| 9 | +- Flexible parsing — transform results and errors with full type inference |
| 10 | +- Built in hooks — run side effects automatically |
| 11 | +- Async utils included — retry, timeout, abort, all, allSettled |
| 12 | +- No try/catch clutter — clean, concise call sites |
| 13 | + |
| 14 | +## Documentation |
| 15 | + |
| 16 | +Full API reference, examples, and guides: |
| 17 | + |
| 18 | +[https://cometloop.github.io/safe/](https://cometloop.github.io/safe/) |
| 19 | + |
| 20 | +How we recommend using this library: |
| 21 | + |
| 22 | +[https://cometloop.github.io/safe/docs/recommended-patterns](https://cometloop.github.io/safe/docs/recommended-patterns) |
| 23 | + |
| 24 | +## Example usage: |
| 25 | + |
| 26 | +```typescript |
| 27 | +import { createSafe, safe } from '@cometloop/safe' |
| 28 | + |
| 29 | +// Wrap any function — zero config |
| 30 | +const safeParse = safe.wrap(JSON.parse) |
| 31 | +const [data, err] = safeParse(rawInput) |
| 32 | + |
| 33 | +// Shared config for a whole domain |
| 34 | +const apiSafe = createSafe({ |
| 35 | + parseError: errorParser, |
| 36 | + defaultError: fallbackError, |
| 37 | + onError: errorHook, |
| 38 | +}) |
| 39 | + |
| 40 | +const fetchUser = apiSafe.wrapAsync(fetchUserAsync) |
| 41 | +const fetchPosts = apiSafe.wrapAsync(fetchPostsAsync) |
| 42 | + |
| 43 | +// Same config. Full type narrowing. |
| 44 | +const [user, userErr] = await fetchUser('123') |
| 45 | +if (userErr) return |
| 46 | + |
| 47 | +const [posts, postsErr] = await fetchPosts(user.id) |
| 48 | +console.log(user.name, posts.length) |
| 49 | + |
| 50 | +// Prefer objects? One call to switch. |
| 51 | +const objSafe = withObjects(apiSafe) |
| 52 | +const fetchPostsObj = objSafe.wrapAsync(fetchPostsAsync) |
| 53 | +const { ok, data, error } = await fetchPostsObj('123') |
| 54 | +``` |
| 55 | + |
| 56 | +## Installation |
| 57 | + |
| 58 | +```bash |
| 59 | +pnpm add @cometloop/safe |
| 60 | +``` |
| 61 | + |
| 62 | +```bash |
| 63 | +bun add @cometloop/safe |
| 64 | +``` |
| 65 | + |
| 66 | +```bash |
| 67 | +yarn add @cometloop/safe |
| 68 | +``` |
| 69 | + |
| 70 | +```bash |
| 71 | +npm install @cometloop/safe |
| 72 | +``` |
| 73 | + |
| 74 | +## Quick Start |
| 75 | + |
| 76 | +The recommended way to use `@cometloop/safe` is with `createSafe`. Configure error handling once, then every call site stays clean — just a normal function call. |
| 77 | + |
| 78 | +```ts |
| 79 | +import { createSafe } from '@cometloop/safe' |
| 80 | + |
| 81 | +// Configure once |
| 82 | +const appSafe = createSafe({ |
| 83 | + parseError: (e) => ({ |
| 84 | + code: e instanceof Error ? e.name : 'UNKNOWN', |
| 85 | + message: e instanceof Error ? e.message : String(e), |
| 86 | + }), |
| 87 | + defaultError: { code: 'UNKNOWN', message: 'An unknown error occurred' }, |
| 88 | +}) |
| 89 | + |
| 90 | +// Wrap your functions |
| 91 | +const safeFetchUser = appSafe.wrapAsync(fetchUser) |
| 92 | +const safeJsonParse = appSafe.wrap(JSON.parse) |
| 93 | + |
| 94 | +// Call sites are clean — just like calling a normal function |
| 95 | +const [user, error] = await safeFetchUser(id) |
| 96 | +const [config, parseErr] = safeJsonParse(rawJson) |
| 97 | +``` |
| 98 | + |
| 99 | +No inline error mappers. No extra options objects. No lambda wrappers. Just call the function and handle the result. |
| 100 | + |
| 101 | +You can also use the standalone API for quick one-off operations: |
| 102 | + |
| 103 | +```ts |
| 104 | +import { safe } from '@cometloop/safe' |
| 105 | + |
| 106 | +const [data, error] = safe.sync(() => JSON.parse(jsonString)) |
| 107 | +const [user, error] = await safe.async(() => fetchUser(id)) |
| 108 | +``` |
| 109 | + |
| 110 | +## API |
| 111 | + |
| 112 | +| Method | Description | |
| 113 | +| ------------------------ | ------------------------------------------------------------------------------- | |
| 114 | +| `createSafe(config)` | Create a pre-configured instance — the recommended entry point | |
| 115 | +| `safe.wrap(fn)` | Wrap a sync function to return `SafeResult` | |
| 116 | +| `safe.wrapAsync(fn)` | Wrap an async function to return `Promise<SafeResult>` | |
| 117 | +| `safe.sync(fn)` | Execute a sync function, return `SafeResult` | |
| 118 | +| `safe.async(fn)` | Execute an async function, return `Promise<SafeResult>` | |
| 119 | +| `safe.all({...})` | Run multiple async operations in parallel, return all or first error | |
| 120 | +| `safe.allSettled({...})` | Run multiple async operations in parallel, return all individual results | |
| 121 | +| `withObjects(...)` | Convert any result, function, or instance to object-style `{ ok, data, error }` | |
| 122 | + |
| 123 | +All methods support optional `parseError` for custom error types, `parseResult` for result transformation, and lifecycle hooks (`onSuccess`, `onError`, `onSettled`, `onHookError`). Async methods additionally support `retry`, `abortAfter` (timeout), and `onRetry`. |
| 124 | + |
| 125 | +## Features |
| 126 | + |
| 127 | +- **Clean call sites** — configure once with `createSafe`, then call functions normally |
| 128 | +- **Result tuples** — `[value, null]` or `[null, error]` with TypeScript narrowing |
| 129 | +- **Object results** — prefer `{ ok, data, error }` over tuples? Use `withObjects` |
| 130 | +- **Custom error types** — `parseError` maps caught errors to your domain types |
| 131 | +- **Result transformation** — `parseResult` transforms successful values |
| 132 | +- **Lifecycle hooks** — `onSuccess`, `onError`, `onSettled`, `onRetry`, `onHookError` |
| 133 | +- **Retry with backoff** — configurable retry for async operations |
| 134 | +- **Timeout/abort** — `abortAfter` with `AbortSignal` integration |
| 135 | +- **Error normalization** — non-`Error` thrown values are automatically normalized |
| 136 | +- **Zero dependencies** |
| 137 | + |
| 138 | +## Documentation |
| 139 | + |
| 140 | +Full API reference, examples, and guides: |
| 141 | + |
| 142 | +**[https://cometloop.github.io/safe/](https://cometloop.github.io/safe/)** |
| 143 | + |
| 144 | +## License |
| 145 | + |
| 146 | +MIT |
0 commit comments