Skip to content

Commit 2370898

Browse files
authored
feat: changesets setup (#5)
* feat: setup release ci action; first changeset, fix doc link * chore: add readme to safe lib folder * fix: formatting
1 parent 348eed8 commit 2370898

11 files changed

Lines changed: 699 additions & 14 deletions

File tree

.changeset/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changesets
2+
3+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4+
with multi-package repos, or single-package repos to help you version and publish your code. You can
5+
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6+
7+
We have a quick list of common questions to get you started engaging with this project in
8+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

.changeset/config.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
3+
"changelog": "@changesets/cli/changelog",
4+
"commit": false,
5+
"fixed": [],
6+
"linked": [],
7+
"access": "public",
8+
"baseBranch": "main",
9+
"updateInternalDependencies": "patch",
10+
"ignore": []
11+
}

.changeset/thick-buckets-give.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cometloop/safe': minor
3+
---
4+
5+
Initial build of @cometloop/safe lib

.github/workflows/release.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
concurrency:
8+
group: release-${{ github.ref }}
9+
cancel-in-progress: true
10+
11+
jobs:
12+
release:
13+
name: Release
14+
runs-on: ubuntu-latest
15+
permissions:
16+
contents: write
17+
pull-requests: write
18+
id-token: write
19+
steps:
20+
- uses: actions/checkout@v4
21+
22+
- uses: pnpm/action-setup@v4
23+
24+
- uses: actions/setup-node@v4
25+
with:
26+
node-version: 22
27+
cache: pnpm
28+
29+
- run: pnpm install --frozen-lockfile
30+
31+
- name: Create Release Pull Request or Publish
32+
id: changesets
33+
uses: changesets/action@v1
34+
with:
35+
publish: pnpm release
36+
version: pnpm version-packages
37+
env:
38+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39+
NPM_CONFIG_PROVENANCE: true
40+
41+
- name: Create GitHub Releases
42+
if: steps.changesets.outputs.published == 'true'
43+
run: |
44+
echo '${{ steps.changesets.outputs.publishedPackages }}' | jq -c '.[]' | while read -r pkg; do
45+
NAME=$(echo "$pkg" | jq -r '.name')
46+
VERSION=$(echo "$pkg" | jq -r '.version')
47+
TAG="${NAME}@${VERSION}"
48+
gh release create "$TAG" --generate-notes --title "${NAME} v${VERSION}"
49+
done
50+
env:
51+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

apps/documentation-website/src/components/Layout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ function Header() {
5858
</div>
5959
<div className="relative flex basis-0 justify-end gap-6 sm:gap-8 md:grow">
6060
<ThemeSelector className="relative z-10" />
61-
<Link href="https://github.com" className="group" aria-label="GitHub">
61+
<Link
62+
href="https://github.com/cometloop/safe"
63+
className="group"
64+
aria-label="GitHub"
65+
>
6266
<GitHubIcon className="h-6 w-6 fill-slate-400 group-hover:fill-slate-500 dark:group-hover:fill-slate-300" />
6367
</Link>
6468
</div>

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,20 @@
77
"test:coverage": "turbo run test:coverage",
88
"lint": "turbo run lint",
99
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
10-
"format:check": "prettier --check \"**/*.{ts,tsx,md}\""
10+
"format:check": "prettier --check \"**/*.{ts,tsx,md}\"",
11+
"changeset": "changeset",
12+
"version-packages": "changeset version",
13+
"release": "turbo run build && changeset publish"
1114
},
1215
"devDependencies": {
16+
"@changesets/cli": "^2.29.8",
1317
"concurrently": "^9.2.1",
1418
"eslint": "^8.57.0",
1519
"prettier": "^3.2.5",
1620
"turbo": "^2.7.3"
1721
},
1822
"packageManager": "pnpm@8.15.6",
19-
"name": "with-vite-react",
23+
"name": "@cometloop/safe-monorepo",
2024
"pnpm": {
2125
"overrides": {
2226
"vite": "^7.3.1",
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
{
22
"name": "@core/eslint-config",
33
"version": "0.0.0",
4+
"private": true,
45
"main": "index.js",
56
"license": "MIT",
67
"dependencies": {
78
"@typescript-eslint/eslint-plugin": "^7.1.0",
89
"@typescript-eslint/parser": "^7.1.0",
910
"eslint-config-prettier": "^9.1.0",
1011
"eslint-plugin-react-hooks": "^7.0.1"
11-
},
12-
"publishConfig": {
13-
"access": "public"
1412
}
1513
}

packages/safe/README.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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

packages/safe/package.json

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
{
22
"name": "@cometloop/safe",
33
"version": "0.0.1",
4-
"description": "Zero-dependency, type-safe error handling using a result tuple pattern for TypeScript",
4+
"description": "Zero-dependency, type-safe error handling using a result pattern for TypeScript",
55
"author": "cometloop",
6+
"publishConfig": {
7+
"access": "public"
8+
},
69
"license": "MIT",
710
"repository": {
811
"type": "git",
912
"url": "https://github.com/cometloop/safe.git",
1013
"directory": "packages/safe"
1114
},
12-
"homepage": "https://github.com/cometloop/safe#readme",
15+
"homepage": "https://cometloop.github.io/safe/",
1316
"bugs": {
1417
"url": "https://github.com/cometloop/safe/issues"
1518
},
@@ -20,7 +23,14 @@
2023
"safe",
2124
"tuple",
2225
"try-catch",
23-
"type-safe"
26+
"type-safe",
27+
"go-style",
28+
"golang",
29+
"async",
30+
"promise",
31+
"wrapper",
32+
"error",
33+
"no-throw"
2434
],
2535
"engines": {
2636
"node": ">=14"

packages/typescript-config/package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,5 @@
22
"name": "@core/typescript-config",
33
"version": "0.0.0",
44
"private": true,
5-
"license": "MIT",
6-
"publishConfig": {
7-
"access": "public"
8-
}
5+
"license": "MIT"
96
}

0 commit comments

Comments
 (0)