Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
11 changes: 11 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
5 changes: 5 additions & 0 deletions .changeset/thick-buckets-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cometloop/safe': minor
---

Initial build of @cometloop/safe lib
51 changes: 51 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Release

on:
push:
branches: [main]

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true

jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: Create Release Pull Request or Publish
id: changesets
uses: changesets/action@v1
with:
publish: pnpm release
version: pnpm version-packages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: true

- name: Create GitHub Releases
if: steps.changesets.outputs.published == 'true'
run: |
echo '${{ steps.changesets.outputs.publishedPackages }}' | jq -c '.[]' | while read -r pkg; do
NAME=$(echo "$pkg" | jq -r '.name')
VERSION=$(echo "$pkg" | jq -r '.version')
TAG="${NAME}@${VERSION}"
gh release create "$TAG" --generate-notes --title "${NAME} v${VERSION}"
done
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6 changes: 5 additions & 1 deletion apps/documentation-website/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ function Header() {
</div>
<div className="relative flex basis-0 justify-end gap-6 sm:gap-8 md:grow">
<ThemeSelector className="relative z-10" />
<Link href="https://github.com" className="group" aria-label="GitHub">
<Link
href="https://github.com/cometloop/safe"
className="group"
aria-label="GitHub"
>
<GitHubIcon className="h-6 w-6 fill-slate-400 group-hover:fill-slate-500 dark:group-hover:fill-slate-300" />
</Link>
</div>
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@
"test:coverage": "turbo run test:coverage",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,md}\""
"format:check": "prettier --check \"**/*.{ts,tsx,md}\"",
"changeset": "changeset",
"version-packages": "changeset version",
"release": "turbo run build && changeset publish"
},
"devDependencies": {
"@changesets/cli": "^2.29.8",
"concurrently": "^9.2.1",
"eslint": "^8.57.0",
"prettier": "^3.2.5",
"turbo": "^2.7.3"
},
"packageManager": "pnpm@8.15.6",
"name": "with-vite-react",
"name": "@cometloop/safe-monorepo",
"pnpm": {
"overrides": {
"vite": "^7.3.1",
Expand Down
4 changes: 1 addition & 3 deletions packages/eslint-config/package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
{
"name": "@core/eslint-config",
"version": "0.0.0",
"private": true,
"main": "index.js",
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react-hooks": "^7.0.1"
},
"publishConfig": {
"access": "public"
}
}
146 changes: 146 additions & 0 deletions packages/safe/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# @cometloop/safe

Type-safe error handling library for TypeScript using the Result pattern. Zero dependencies.

## **Wrap any function**

- Flexible wrapping — single function, domain scope, or app-wide
- Type-safe results — tuples or objects
- Flexible parsing — transform results and errors with full type inference
- Built in hooks — run side effects automatically
- Async utils included — retry, timeout, abort, all, allSettled
- No try/catch clutter — clean, concise call sites

## Documentation

Full API reference, examples, and guides:

[https://cometloop.github.io/safe/](https://cometloop.github.io/safe/)

How we recommend using this library:

[https://cometloop.github.io/safe/docs/recommended-patterns](https://cometloop.github.io/safe/docs/recommended-patterns)

## Example usage:

```typescript
import { createSafe, safe } from '@cometloop/safe'

// Wrap any function — zero config
const safeParse = safe.wrap(JSON.parse)
const [data, err] = safeParse(rawInput)

// Shared config for a whole domain
const apiSafe = createSafe({
parseError: errorParser,
defaultError: fallbackError,
onError: errorHook,
})

const fetchUser = apiSafe.wrapAsync(fetchUserAsync)
const fetchPosts = apiSafe.wrapAsync(fetchPostsAsync)

// Same config. Full type narrowing.
const [user, userErr] = await fetchUser('123')
if (userErr) return

const [posts, postsErr] = await fetchPosts(user.id)
console.log(user.name, posts.length)

// Prefer objects? One call to switch.
const objSafe = withObjects(apiSafe)
const fetchPostsObj = objSafe.wrapAsync(fetchPostsAsync)
const { ok, data, error } = await fetchPostsObj('123')
```

## Installation

```bash
pnpm add @cometloop/safe
```

```bash
bun add @cometloop/safe
```

```bash
yarn add @cometloop/safe
```

```bash
npm install @cometloop/safe
```

## Quick Start

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.

```ts
import { createSafe } from '@cometloop/safe'

// Configure once
const appSafe = createSafe({
parseError: (e) => ({
code: e instanceof Error ? e.name : 'UNKNOWN',
message: e instanceof Error ? e.message : String(e),
}),
defaultError: { code: 'UNKNOWN', message: 'An unknown error occurred' },
})

// Wrap your functions
const safeFetchUser = appSafe.wrapAsync(fetchUser)
const safeJsonParse = appSafe.wrap(JSON.parse)

// Call sites are clean — just like calling a normal function
const [user, error] = await safeFetchUser(id)
const [config, parseErr] = safeJsonParse(rawJson)
```

No inline error mappers. No extra options objects. No lambda wrappers. Just call the function and handle the result.

You can also use the standalone API for quick one-off operations:

```ts
import { safe } from '@cometloop/safe'

const [data, error] = safe.sync(() => JSON.parse(jsonString))
const [user, error] = await safe.async(() => fetchUser(id))
```

## API

| Method | Description |
| ------------------------ | ------------------------------------------------------------------------------- |
| `createSafe(config)` | Create a pre-configured instance — the recommended entry point |
| `safe.wrap(fn)` | Wrap a sync function to return `SafeResult` |
| `safe.wrapAsync(fn)` | Wrap an async function to return `Promise<SafeResult>` |
| `safe.sync(fn)` | Execute a sync function, return `SafeResult` |
| `safe.async(fn)` | Execute an async function, return `Promise<SafeResult>` |
| `safe.all({...})` | Run multiple async operations in parallel, return all or first error |
| `safe.allSettled({...})` | Run multiple async operations in parallel, return all individual results |
| `withObjects(...)` | Convert any result, function, or instance to object-style `{ ok, data, error }` |

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`.

## Features

- **Clean call sites** — configure once with `createSafe`, then call functions normally
- **Result tuples** — `[value, null]` or `[null, error]` with TypeScript narrowing
- **Object results** — prefer `{ ok, data, error }` over tuples? Use `withObjects`
- **Custom error types** — `parseError` maps caught errors to your domain types
- **Result transformation** — `parseResult` transforms successful values
- **Lifecycle hooks** — `onSuccess`, `onError`, `onSettled`, `onRetry`, `onHookError`
- **Retry with backoff** — configurable retry for async operations
- **Timeout/abort** — `abortAfter` with `AbortSignal` integration
- **Error normalization** — non-`Error` thrown values are automatically normalized
- **Zero dependencies**

## Documentation

Full API reference, examples, and guides:

**[https://cometloop.github.io/safe/](https://cometloop.github.io/safe/)**

## License

MIT
16 changes: 13 additions & 3 deletions packages/safe/package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
{
"name": "@cometloop/safe",
"version": "0.0.1",
"description": "Zero-dependency, type-safe error handling using a result tuple pattern for TypeScript",
"description": "Zero-dependency, type-safe error handling using a result pattern for TypeScript",
"author": "cometloop",
"publishConfig": {
"access": "public"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/cometloop/safe.git",
"directory": "packages/safe"
},
"homepage": "https://github.com/cometloop/safe#readme",
"homepage": "https://cometloop.github.io/safe/",
"bugs": {
"url": "https://github.com/cometloop/safe/issues"
},
Expand All @@ -20,7 +23,14 @@
"safe",
"tuple",
"try-catch",
"type-safe"
"type-safe",
"go-style",
"golang",
"async",
"promise",
"wrapper",
"error",
"no-throw"
],
"engines": {
"node": ">=14"
Expand Down
5 changes: 1 addition & 4 deletions packages/typescript-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,5 @@
"name": "@core/typescript-config",
"version": "0.0.0",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
}
"license": "MIT"
}
Loading