Skip to content

Commit 7d2c455

Browse files
committed
feat(core): add neverthrow integration and ThatError type refinement
Added integration with the `neverthrow` library to enhance error-handling scenarios. Implemented new tests under `neverthrow.test.ts` to validate error inference using `neverthrow` APIs. Refined the `ThatError` type in `types.ts` to support better nominal typing and added detailed use-case examples for clarity. Updated `package.json` to include `neverthrow` as a development dependency and added a `install:clean` script for workspace maintenance.
1 parent d205ae5 commit 7d2c455

7 files changed

Lines changed: 92 additions & 6 deletions

File tree

bun.lock

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@
1515
},
1616
"engines": {
1717
"bun": ">=1.3"
18+
},
19+
"scripts": {
20+
"install:clean": "rm -rf node_modules bun.lockb && bun install"
1821
}
1922
}

packages/core/__tests__/define-error.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
PayloadField,
88
ScopeField,
99
scopeOf,
10-
That
10+
That,
1111
} from "@thaterror/core"
1212

1313
export const AppError = That({

packages/core/__tests__/from-error.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,5 @@ describe('ErrorFamily from native error testing', () => {
5959

6060
const not_found = new HTTPException(404);
6161
expect(HttpBridgedError.from(not_found).cause).toBe(not_found);
62-
6362
})
6463
})
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { PayloadField, type ThatError } from "@thaterror/core";
3+
import { err, fromPromise } from 'neverthrow'
4+
import { AppError } from './define-error.test'
5+
6+
describe('neverthrow test', () => {
7+
test('should infer Error type', () => {
8+
const getNotFoundResult = () => {
9+
const e = AppError.NotFound(404);
10+
return err<string, ThatError<typeof AppError, "NotFound">>(e);
11+
};
12+
13+
const result = getNotFoundResult();
14+
15+
expect(result.isErr()).toBe(true);
16+
17+
if (result.isErr()) {
18+
const error = result.error;
19+
expect(error.is(AppError.NotFound)).toBe(true);
20+
expect(error[PayloadField]).toEqual([404]);
21+
}
22+
})
23+
24+
test('should infer Error type from async call', async () => {
25+
const fetchData = () => {
26+
const promise = Promise.reject(AppError.NotFound(404));
27+
28+
return fromPromise(
29+
promise,
30+
(e) => e as ThatError<typeof AppError, "NotFound">
31+
);
32+
};
33+
34+
const result = await fetchData();
35+
36+
expect(result.isErr()).toBe(true);
37+
38+
if (result.isErr()) {
39+
expect(result.error.is(AppError.NotFound)).toBe(true);
40+
expect(result.error[PayloadField][0]).toBe(404);
41+
}
42+
})
43+
})

packages/core/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"package.json"
2727
],
2828
"devDependencies": {
29-
"hono": "^4.11.3"
29+
"hono": "^4.11.3",
30+
"neverthrow": "^8.2.0"
3031
},
3132
"publishConfig": {
3233
"access": "public"

packages/core/src/types.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,38 @@ export type ErrorMapOf<F> =
173173
export type ErrorUnionOf<F> =
174174
F extends ErrorFamily<infer M, infer _Es>
175175
? ErrorUnionOfMap<M> : never
176+
177+
178+
/**
179+
* Extracts a specific subset of DefinedError instances from an ErrorFamily.
180+
* Strictly prohibits `any` to maintain nominal type integrity.
181+
*
182+
* @template F - The ErrorFamily instance (e.g., `typeof AppError`).
183+
* @template K - Keys to extract from the ErrorMap. Defaults to all keys.
184+
*
185+
* @example
186+
* ### USE CASE: Basic Usage
187+
* ```ts
188+
* type ErrType = ThatError<typeof AppError>;
189+
* ```
190+
*
191+
* @example
192+
* ### USE CASE: Integration with neverthrow
193+
* ```ts
194+
* function fetchData(): ResultAsync<string, ThatError<typeof AppError, 'Timeout' | 'Network'>> {
195+
* return fromPromise(
196+
* api.get('/data'),
197+
* (e: unknown) => AppError.bridge(e as Error, (err, cases) => {
198+
* if (err.message.includes('timeout')) return cases.Timeout();
199+
* return cases.Network();
200+
* }) as ThatError<typeof AppError, 'Timeout' | 'Network'>
201+
* );
202+
* }
203+
* ```
204+
*/
205+
export type ThatError<
206+
F,
207+
K extends keyof ErrorMapOf<F> = keyof ErrorMapOf<F>
208+
> = ErrorUnionOfMap<{
209+
[P in K & string]: ErrorMapOf<F>[P]
210+
}>

0 commit comments

Comments
 (0)