@zipbul/result 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +76 -0
- package/README.md +76 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +7 -6
- package/dist/src/constants.d.ts +36 -10
- package/dist/src/err.d.ts +24 -6
- package/dist/src/is-err.d.ts +22 -8
- package/dist/src/safe.d.ts +73 -0
- package/dist/src/types.d.ts +47 -9
- package/package.json +2 -1
package/README.ko.md
CHANGED
|
@@ -207,6 +207,82 @@ type Err<E = never> = {
|
|
|
207
207
|
|
|
208
208
|
<br>
|
|
209
209
|
|
|
210
|
+
### `safe()`
|
|
211
|
+
|
|
212
|
+
동기 함수 또는 Promise를 `Result` / `ResultAsync`로 감쌉니다. throw와 rejection을 캐치하여 `Err`로 변환합니다.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { safe } from '@zipbul/result';
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
| 오버로드 | 반환 | 설명 |
|
|
219
|
+
|:---------|:-----|:-----|
|
|
220
|
+
| `safe(fn)` | `Result<T, unknown>` | 동기 — `fn()` 호출, throw 캐치 |
|
|
221
|
+
| `safe(fn, mapErr)` | `Result<T, E>` | 동기 — throw 캐치, `mapErr`로 변환 |
|
|
222
|
+
| `safe(promise)` | `ResultAsync<T, unknown>` | 비동기 — rejection 래핑 |
|
|
223
|
+
| `safe(promise, mapErr)` | `ResultAsync<T, E>` | 비동기 — rejection 래핑, `mapErr`로 변환 |
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// 동기 — throw할 수 있는 함수 래핑
|
|
227
|
+
const result = safe(() => JSON.parse(rawJson));
|
|
228
|
+
if (isErr(result)) {
|
|
229
|
+
console.error('파싱 실패:', result.data);
|
|
230
|
+
} else {
|
|
231
|
+
console.log(result); // 파싱된 객체
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 동기 + mapErr — unknown throw를 타입이 있는 에러로 변환
|
|
235
|
+
const typed = safe(
|
|
236
|
+
() => JSON.parse(rawJson),
|
|
237
|
+
(e) => ({ code: 'PARSE_ERROR', message: String(e) }),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// 비동기 — reject될 수 있는 Promise 래핑
|
|
241
|
+
const asyncResult = await safe(fetch('/api/data'));
|
|
242
|
+
|
|
243
|
+
// 비동기 + mapErr
|
|
244
|
+
const apiResult = await safe(
|
|
245
|
+
fetch('/api/users/1'),
|
|
246
|
+
(e) => ({ code: 'NETWORK', message: String(e) }),
|
|
247
|
+
);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
> **동기 경로** — `safe(fn)`은 `!(fn instanceof Promise)`로 함수를 감지합니다. Promise를 _반환하는_ 함수는 동기로 처리되며, Promise 객체가 성공값 `T`가 됩니다.
|
|
251
|
+
>
|
|
252
|
+
> **mapErr 패닉** — `mapErr` 자체가 throw하면, 동기의 경우 throw가 전파되고 비동기의 경우 반환된 promise가 reject됩니다. 이는 의도된 설계입니다 — `mapErr`는 사용자 코드이며, 그 실패는 패닉(panic)이지 `Err`가 아닙니다.
|
|
253
|
+
|
|
254
|
+
<br>
|
|
255
|
+
|
|
256
|
+
### `ResultAsync<T, E>`
|
|
257
|
+
|
|
258
|
+
비동기 결과를 위한 타입 별칭 — 래퍼 클래스가 아닙니다.
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
type ResultAsync<T, E = never> = Promise<Result<T, E>>;
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
| 파라미터 | 기본값 | 설명 |
|
|
265
|
+
|:---------|:-------|:-----|
|
|
266
|
+
| `T` | — | 성공 값 타입 |
|
|
267
|
+
| `E` | `never` | 에러 데이터 타입 |
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// 비동기 Result 반환 함수의 반환 타입으로 사용
|
|
271
|
+
async function fetchUser(id: number): ResultAsync<User, string> {
|
|
272
|
+
const res = await fetch(`/api/users/${id}`);
|
|
273
|
+
if (!res.ok) return err(res.statusText);
|
|
274
|
+
return await res.json();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 또는 기존 Promise를 safe()로 래핑
|
|
278
|
+
const result: ResultAsync<Response, string> = safe(
|
|
279
|
+
fetch('/api/data'),
|
|
280
|
+
(e) => String(e),
|
|
281
|
+
);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
<br>
|
|
285
|
+
|
|
210
286
|
### 마커 키(Marker Key)
|
|
211
287
|
|
|
212
288
|
마커 키는 `Err` 객체를 식별하는 데 사용되는 숨겨진 고유 프로퍼티입니다. 충돌에 강한 문자열이 기본값입니다.
|
package/README.md
CHANGED
|
@@ -207,6 +207,82 @@ type Err<E = never> = {
|
|
|
207
207
|
|
|
208
208
|
<br>
|
|
209
209
|
|
|
210
|
+
### `safe()`
|
|
211
|
+
|
|
212
|
+
Wraps a sync function or Promise into a `Result` / `ResultAsync`. Catches throws and rejections, converting them to `Err`.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { safe } from '@zipbul/result';
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
| Overload | Return | Description |
|
|
219
|
+
|:---------|:-------|:------------|
|
|
220
|
+
| `safe(fn)` | `Result<T, unknown>` | Sync — calls `fn()`, catches throws |
|
|
221
|
+
| `safe(fn, mapErr)` | `Result<T, E>` | Sync — catches throws, maps via `mapErr` |
|
|
222
|
+
| `safe(promise)` | `ResultAsync<T, unknown>` | Async — wraps rejection |
|
|
223
|
+
| `safe(promise, mapErr)` | `ResultAsync<T, E>` | Async — wraps rejection, maps via `mapErr` |
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// Sync — wrap a function that might throw
|
|
227
|
+
const result = safe(() => JSON.parse(rawJson));
|
|
228
|
+
if (isErr(result)) {
|
|
229
|
+
console.error('Parse failed:', result.data);
|
|
230
|
+
} else {
|
|
231
|
+
console.log(result); // parsed object
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Sync with mapErr — convert unknown throw to typed error
|
|
235
|
+
const typed = safe(
|
|
236
|
+
() => JSON.parse(rawJson),
|
|
237
|
+
(e) => ({ code: 'PARSE_ERROR', message: String(e) }),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// Async — wrap a Promise that might reject
|
|
241
|
+
const asyncResult = await safe(fetch('/api/data'));
|
|
242
|
+
|
|
243
|
+
// Async with mapErr
|
|
244
|
+
const apiResult = await safe(
|
|
245
|
+
fetch('/api/users/1'),
|
|
246
|
+
(e) => ({ code: 'NETWORK', message: String(e) }),
|
|
247
|
+
);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
> **Sync path** — `safe(fn)` detects a function via `!(fn instanceof Promise)`. A function that _returns_ a Promise is treated as sync — the Promise object becomes the success value `T`.
|
|
251
|
+
>
|
|
252
|
+
> **mapErr panic** — if `mapErr` itself throws, the throw propagates (sync) or the returned promise rejects (async). This is by design — `mapErr` is user code, and its failure is a panic, not an `Err`.
|
|
253
|
+
|
|
254
|
+
<br>
|
|
255
|
+
|
|
256
|
+
### `ResultAsync<T, E>`
|
|
257
|
+
|
|
258
|
+
A type alias for async results — not a wrapper class.
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
type ResultAsync<T, E = never> = Promise<Result<T, E>>;
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
| Parameter | Default | Description |
|
|
265
|
+
|:----------|:--------|:------------|
|
|
266
|
+
| `T` | — | Success value type |
|
|
267
|
+
| `E` | `never` | Error data type |
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// Use as return type for async Result-returning functions
|
|
271
|
+
async function fetchUser(id: number): ResultAsync<User, string> {
|
|
272
|
+
const res = await fetch(`/api/users/${id}`);
|
|
273
|
+
if (!res.ok) return err(res.statusText);
|
|
274
|
+
return await res.json();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Or wrap an existing Promise with safe()
|
|
278
|
+
const result: ResultAsync<Response, string> = safe(
|
|
279
|
+
fetch('/api/data'),
|
|
280
|
+
(e) => String(e),
|
|
281
|
+
);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
<br>
|
|
285
|
+
|
|
210
286
|
### Marker Key
|
|
211
287
|
|
|
212
288
|
The marker key is a unique hidden property used to identify `Err` objects. It defaults to a collision-resistant string.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { err } from './src/err';
|
|
2
2
|
export { isErr } from './src/is-err';
|
|
3
|
+
export { safe } from './src/safe';
|
|
3
4
|
export { DEFAULT_MARKER_KEY, getMarkerKey, setMarkerKey } from './src/constants';
|
|
4
|
-
export type { Err, Result } from './src/types';
|
|
5
|
+
export type { Err, Result, ResultAsync } from './src/types';
|
package/dist/index.js
CHANGED
|
@@ -35,13 +35,25 @@ function isErr(value) {
|
|
|
35
35
|
return false;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
// src/safe.ts
|
|
39
|
+
function safe(fnOrPromise, mapErr) {
|
|
40
|
+
if (fnOrPromise instanceof Promise) {
|
|
41
|
+
return fnOrPromise.then((value) => value, (thrown) => mapErr ? err(mapErr(thrown)) : err(thrown));
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
return fnOrPromise();
|
|
45
|
+
} catch (thrown) {
|
|
46
|
+
return mapErr ? err(mapErr(thrown)) : err(thrown);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
38
49
|
export {
|
|
39
50
|
setMarkerKey,
|
|
51
|
+
safe,
|
|
40
52
|
isErr,
|
|
41
53
|
getMarkerKey,
|
|
42
54
|
err,
|
|
43
55
|
DEFAULT_MARKER_KEY
|
|
44
56
|
};
|
|
45
57
|
|
|
46
|
-
//# debugId=
|
|
58
|
+
//# debugId=6EA5CE9688A72DB264756E2164756E21
|
|
47
59
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/constants.ts", "../src/err.ts", "../src/is-err.ts"],
|
|
3
|
+
"sources": ["../src/constants.ts", "../src/err.ts", "../src/is-err.ts", "../src/safe.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n *
|
|
6
|
-
"import type { Err } from './types';\nimport { getMarkerKey } from './constants';\n\n/**\n * Err
|
|
7
|
-
"import type { Err } from './types';\nimport { getMarkerKey } from './constants';\n\n/**\n *
|
|
5
|
+
"/**\n * The default marker key used to identify {@link Err} objects.\n *\n * This collision-resistant string is set as a hidden property on every `Err`\n * created by `err()`. You rarely need to reference it directly — it is\n * provided for advanced use cases like cross-module error domain isolation.\n *\n * @example\n * ```ts\n * import { DEFAULT_MARKER_KEY } from '@zipbul/result';\n * console.log(DEFAULT_MARKER_KEY); // '__$$e_9f4a1c7b__'\n * ```\n */\nexport const DEFAULT_MARKER_KEY = '__$$e_9f4a1c7b__';\n\nlet currentMarkerKey: string = DEFAULT_MARKER_KEY;\n\n/**\n * Returns the marker key currently in use.\n *\n * Both `err()` and `isErr()` rely on this key to tag and detect error objects.\n *\n * @returns The current marker key string.\n *\n * @example\n * ```ts\n * console.log(getMarkerKey()); // '__$$e_9f4a1c7b__'\n * ```\n */\nexport function getMarkerKey(): string {\n return currentMarkerKey;\n}\n\n/**\n * Replaces the marker key used by `err()` and `isErr()`.\n *\n * After calling this, newly-created `Err` objects will use the new key, and\n * `isErr()` will only recognise objects carrying the new key. Previously\n * created `Err` objects will **no longer** be detected.\n *\n * Only change this if you need to isolate error domains across independent\n * modules — in most applications the default key is perfectly fine.\n *\n * @param key - A non-empty, non-whitespace-only string to use as the new key.\n * @throws {TypeError} If `key` is empty or contains only whitespace.\n *\n * @example\n * ```ts\n * setMarkerKey('__my_app_err__');\n * console.log(getMarkerKey()); // '__my_app_err__'\n * ```\n */\nexport function setMarkerKey(key: string): void {\n if (key.trim().length === 0) {\n throw new TypeError('Marker key must be a non-empty string');\n }\n currentMarkerKey = key;\n}\n",
|
|
6
|
+
"import type { Err } from './types';\nimport { getMarkerKey } from './constants';\n\n/**\n * Creates an immutable {@link Err} value with no attached data.\n *\n * The returned object is `Object.freeze()`-d and includes a stack trace\n * captured at the call site. This function **never throws**.\n *\n * @returns A frozen `Err` with `data` typed as `never`.\n *\n * @example\n * ```ts\n * const e = err();\n * console.log(e.stack); // stack trace\n * ```\n */\nexport function err(): Err;\n/**\n * Creates an immutable {@link Err} value carrying the given data.\n *\n * The returned object is `Object.freeze()`-d and includes a stack trace\n * captured at the call site. This function **never throws**.\n *\n * @param data - Any value describing the error (string, object, number, etc.).\n * @returns A frozen `Err<E>` with `data` set to the provided value.\n *\n * @example\n * ```ts\n * const e = err({ code: 'TIMEOUT', retryAfter: 3000 });\n * console.log(e.data.code); // 'TIMEOUT'\n * ```\n */\nexport function err<E>(data: E): Err<E>;\nexport function err<E = never>(data?: E): Err<E> {\n let stack: string;\n try {\n stack = new Error().stack ?? '';\n } catch {\n stack = '';\n }\n\n const result = {\n [getMarkerKey()]: true,\n stack,\n data: data as E,\n };\n\n return Object.freeze(result) as Err<E>;\n}\n",
|
|
7
|
+
"import type { Err } from './types';\nimport { getMarkerKey } from './constants';\n\n/**\n * Type guard that checks whether a value is an {@link Err}.\n *\n * Returns `true` when `value` is a non-null object whose current marker\n * property is strictly `true`. This function **never throws** — it safely\n * handles `null`, `undefined`, primitives, and even hostile Proxy objects.\n *\n * **Note:** The generic `E` is a compile-time assertion only. It does **not**\n * validate the shape of `data` at runtime — callers must ensure `E` matches\n * the actual error type.\n *\n * @typeParam E - Expected error data type (default: `unknown`).\n * @param value - The value to check. Can be anything.\n * @returns `true` if `value` is an `Err`, allowing TypeScript to narrow the type.\n *\n * @example\n * ```ts\n * const result: Result<number, string> = doSomething();\n *\n * if (isErr(result)) {\n * console.error(result.data); // string\n * } else {\n * console.log(result + 1); // number\n * }\n * ```\n */\nexport function isErr<E = unknown>(\n value: unknown,\n): value is Err<E> {\n try {\n return (\n value !== null &&\n value !== undefined &&\n typeof value === 'object' &&\n (value as Record<string, unknown>)[getMarkerKey()] === true\n );\n } catch {\n return false;\n }\n}\n",
|
|
8
|
+
"import type { Result, ResultAsync } from './types';\nimport { err } from './err';\n\n/**\n * Executes a synchronous function and catches any thrown value into an {@link Err}.\n *\n * If `fn` returns normally, its return value is passed through as the success\n * value. If `fn` throws, the thrown value is wrapped with `err()` and returned.\n *\n * @param fn - A synchronous function to execute safely.\n * @returns The function's return value on success, or `Err<unknown>` on throw.\n *\n * @example\n * ```ts\n * const result = safe(() => JSON.parse(raw));\n * if (isErr(result)) console.error(result.data);\n * ```\n */\nexport function safe<T>(fn: () => T): Result<T, unknown>;\n/**\n * Executes a synchronous function and maps any thrown value through `mapErr`\n * before wrapping it in an {@link Err}.\n *\n * This overload lets you convert the raw `unknown` throw into a well-typed\n * error value, keeping your error types precise.\n *\n * @param fn - A synchronous function to execute safely.\n * @param mapErr - Transforms the thrown value into a typed error `E`.\n * @returns The function's return value on success, or `Err<E>` on throw.\n *\n * @example\n * ```ts\n * const result = safe(\n * () => JSON.parse(raw),\n * (e) => ({ code: 'PARSE', message: String(e) }),\n * );\n * ```\n */\nexport function safe<T, E>(fn: () => T, mapErr: (thrown: unknown) => E): Result<T, E>;\n/**\n * Wraps a Promise so that rejections become {@link Err} values instead of\n * thrown exceptions.\n *\n * If the promise resolves, the resolved value is passed through. If it\n * rejects, the rejection reason is wrapped with `err()` and the returned\n * promise resolves (never rejects) to `Err<unknown>`.\n *\n * @param promise - The promise to wrap.\n * @returns A `ResultAsync` that always resolves — either to `T` or to `Err<unknown>`.\n *\n * @example\n * ```ts\n * const result = await safe(fetch('/api/data'));\n * ```\n */\nexport function safe<T>(promise: Promise<T>): ResultAsync<T, unknown>;\n/**\n * Wraps a Promise so that rejections are mapped through `mapErr` and returned\n * as typed {@link Err} values.\n *\n * Combines the safety of promise wrapping with precise error typing.\n *\n * @param promise - The promise to wrap.\n * @param mapErr - Transforms the rejection reason into a typed error `E`.\n * @returns A `ResultAsync` that always resolves — either to `T` or to `Err<E>`.\n *\n * @example\n * ```ts\n * const result = await safe(\n * fetch('/api/users/1'),\n * (e) => ({ code: 'NETWORK', message: String(e) }),\n * );\n * ```\n */\nexport function safe<T, E>(promise: Promise<T>, mapErr: (thrown: unknown) => E): ResultAsync<T, E>;\nexport function safe<T, E = unknown>(\n fnOrPromise: (() => T) | Promise<T>,\n mapErr?: (thrown: unknown) => E,\n): Result<T, E> | ResultAsync<T, E> {\n if (fnOrPromise instanceof Promise) {\n return fnOrPromise.then(\n (value) => value as Result<T, E>,\n (thrown: unknown) => (mapErr ? err(mapErr(thrown)) : err(thrown)) as Result<T, E>,\n );\n }\n\n try {\n return fnOrPromise();\n } catch (thrown: unknown) {\n return mapErr ? err(mapErr(thrown)) : err(thrown as E);\n }\n}\n"
|
|
8
9
|
],
|
|
9
|
-
"mappings": ";;
|
|
10
|
-
"debugId": "
|
|
10
|
+
"mappings": ";;AAaO,IAAM,qBAAqB;AAElC,IAAI,mBAA2B;AAcxB,SAAS,YAAY,GAAW;AAAA,EACrC,OAAO;AAAA;AAsBF,SAAS,YAAY,CAAC,KAAmB;AAAA,EAC9C,IAAI,IAAI,KAAK,EAAE,WAAW,GAAG;AAAA,IAC3B,MAAM,IAAI,UAAU,uCAAuC;AAAA,EAC7D;AAAA,EACA,mBAAmB;AAAA;;;ACtBd,SAAS,GAAc,CAAC,MAAkB;AAAA,EAC/C,IAAI;AAAA,EACJ,IAAI;AAAA,IACF,QAAQ,IAAI,MAAM,EAAE,SAAS;AAAA,IAC7B,MAAM;AAAA,IACN,QAAQ;AAAA;AAAA,EAGV,MAAM,SAAS;AAAA,KACZ,aAAa,IAAI;AAAA,IAClB;AAAA,IACA;AAAA,EACF;AAAA,EAEA,OAAO,OAAO,OAAO,MAAM;AAAA;;ACnBtB,SAAS,KAAkB,CAChC,OACiB;AAAA,EACjB,IAAI;AAAA,IACF,OACE,UAAU,QACV,UAAU,aACV,OAAO,UAAU,YAChB,MAAkC,aAAa,OAAO;AAAA,IAEzD,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;;ACmCJ,SAAS,IAAoB,CAClC,aACA,QACkC;AAAA,EAClC,IAAI,uBAAuB,SAAS;AAAA,IAClC,OAAO,YAAY,KACjB,CAAC,UAAU,OACX,CAAC,WAAqB,SAAS,IAAI,OAAO,MAAM,CAAC,IAAI,IAAI,MAAM,CACjE;AAAA,EACF;AAAA,EAEA,IAAI;AAAA,IACF,OAAO,YAAY;AAAA,IACnB,OAAO,QAAiB;AAAA,IACxB,OAAO,SAAS,IAAI,OAAO,MAAM,CAAC,IAAI,IAAI,MAAW;AAAA;AAAA;",
|
|
11
|
+
"debugId": "6EA5CE9688A72DB264756E2164756E21",
|
|
11
12
|
"names": []
|
|
12
13
|
}
|
package/dist/src/constants.d.ts
CHANGED
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The default marker key used to identify {@link Err} objects.
|
|
3
|
+
*
|
|
4
|
+
* This collision-resistant string is set as a hidden property on every `Err`
|
|
5
|
+
* created by `err()`. You rarely need to reference it directly — it is
|
|
6
|
+
* provided for advanced use cases like cross-module error domain isolation.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { DEFAULT_MARKER_KEY } from '@zipbul/result';
|
|
11
|
+
* console.log(DEFAULT_MARKER_KEY); // '__$$e_9f4a1c7b__'
|
|
12
|
+
* ```
|
|
5
13
|
*/
|
|
6
14
|
export declare const DEFAULT_MARKER_KEY = "__$$e_9f4a1c7b__";
|
|
7
15
|
/**
|
|
8
|
-
*
|
|
16
|
+
* Returns the marker key currently in use.
|
|
17
|
+
*
|
|
18
|
+
* Both `err()` and `isErr()` rely on this key to tag and detect error objects.
|
|
19
|
+
*
|
|
20
|
+
* @returns The current marker key string.
|
|
9
21
|
*
|
|
10
|
-
* @
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* console.log(getMarkerKey()); // '__$$e_9f4a1c7b__'
|
|
25
|
+
* ```
|
|
11
26
|
*/
|
|
12
27
|
export declare function getMarkerKey(): string;
|
|
13
28
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
29
|
+
* Replaces the marker key used by `err()` and `isErr()`.
|
|
30
|
+
*
|
|
31
|
+
* After calling this, newly-created `Err` objects will use the new key, and
|
|
32
|
+
* `isErr()` will only recognise objects carrying the new key. Previously
|
|
33
|
+
* created `Err` objects will **no longer** be detected.
|
|
34
|
+
*
|
|
35
|
+
* Only change this if you need to isolate error domains across independent
|
|
36
|
+
* modules — in most applications the default key is perfectly fine.
|
|
37
|
+
*
|
|
38
|
+
* @param key - A non-empty, non-whitespace-only string to use as the new key.
|
|
39
|
+
* @throws {TypeError} If `key` is empty or contains only whitespace.
|
|
17
40
|
*
|
|
18
|
-
* @
|
|
19
|
-
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* setMarkerKey('__my_app_err__');
|
|
44
|
+
* console.log(getMarkerKey()); // '__my_app_err__'
|
|
45
|
+
* ```
|
|
20
46
|
*/
|
|
21
47
|
export declare function setMarkerKey(key: string): void;
|
package/dist/src/err.d.ts
CHANGED
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import type { Err } from './types';
|
|
2
2
|
/**
|
|
3
|
-
* Err
|
|
4
|
-
* 절대 throw하지 않는다.
|
|
5
|
-
* 반환 전 Object.freeze를 적용한다.
|
|
3
|
+
* Creates an immutable {@link Err} value with no attached data.
|
|
6
4
|
*
|
|
7
|
-
*
|
|
5
|
+
* The returned object is `Object.freeze()`-d and includes a stack trace
|
|
6
|
+
* captured at the call site. This function **never throws**.
|
|
7
|
+
*
|
|
8
|
+
* @returns A frozen `Err` with `data` typed as `never`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const e = err();
|
|
13
|
+
* console.log(e.stack); // stack trace
|
|
14
|
+
* ```
|
|
8
15
|
*/
|
|
9
16
|
export declare function err(): Err;
|
|
10
17
|
/**
|
|
11
|
-
* @
|
|
12
|
-
*
|
|
18
|
+
* Creates an immutable {@link Err} value carrying the given data.
|
|
19
|
+
*
|
|
20
|
+
* The returned object is `Object.freeze()`-d and includes a stack trace
|
|
21
|
+
* captured at the call site. This function **never throws**.
|
|
22
|
+
*
|
|
23
|
+
* @param data - Any value describing the error (string, object, number, etc.).
|
|
24
|
+
* @returns A frozen `Err<E>` with `data` set to the provided value.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const e = err({ code: 'TIMEOUT', retryAfter: 3000 });
|
|
29
|
+
* console.log(e.data.code); // 'TIMEOUT'
|
|
30
|
+
* ```
|
|
13
31
|
*/
|
|
14
32
|
export declare function err<E>(data: E): Err<E>;
|
package/dist/src/is-err.d.ts
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
import type { Err } from './types';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* 현재 설정된 마커 키(`getMarkerKey()`)에 해당하는 프로퍼티가
|
|
5
|
-
* `=== true`인 경우에만 true를 반환한다.
|
|
6
|
-
* 절대 throw하지 않는다.
|
|
3
|
+
* Type guard that checks whether a value is an {@link Err}.
|
|
7
4
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Returns `true` when `value` is a non-null object whose current marker
|
|
6
|
+
* property is strictly `true`. This function **never throws** — it safely
|
|
7
|
+
* handles `null`, `undefined`, primitives, and even hostile Proxy objects.
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* **Note:** The generic `E` is a compile-time assertion only. It does **not**
|
|
10
|
+
* validate the shape of `data` at runtime — callers must ensure `E` matches
|
|
11
|
+
* the actual error type.
|
|
12
|
+
*
|
|
13
|
+
* @typeParam E - Expected error data type (default: `unknown`).
|
|
14
|
+
* @param value - The value to check. Can be anything.
|
|
15
|
+
* @returns `true` if `value` is an `Err`, allowing TypeScript to narrow the type.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const result: Result<number, string> = doSomething();
|
|
20
|
+
*
|
|
21
|
+
* if (isErr(result)) {
|
|
22
|
+
* console.error(result.data); // string
|
|
23
|
+
* } else {
|
|
24
|
+
* console.log(result + 1); // number
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
13
27
|
*/
|
|
14
28
|
export declare function isErr<E = unknown>(value: unknown): value is Err<E>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Result, ResultAsync } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Executes a synchronous function and catches any thrown value into an {@link Err}.
|
|
4
|
+
*
|
|
5
|
+
* If `fn` returns normally, its return value is passed through as the success
|
|
6
|
+
* value. If `fn` throws, the thrown value is wrapped with `err()` and returned.
|
|
7
|
+
*
|
|
8
|
+
* @param fn - A synchronous function to execute safely.
|
|
9
|
+
* @returns The function's return value on success, or `Err<unknown>` on throw.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const result = safe(() => JSON.parse(raw));
|
|
14
|
+
* if (isErr(result)) console.error(result.data);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function safe<T>(fn: () => T): Result<T, unknown>;
|
|
18
|
+
/**
|
|
19
|
+
* Executes a synchronous function and maps any thrown value through `mapErr`
|
|
20
|
+
* before wrapping it in an {@link Err}.
|
|
21
|
+
*
|
|
22
|
+
* This overload lets you convert the raw `unknown` throw into a well-typed
|
|
23
|
+
* error value, keeping your error types precise.
|
|
24
|
+
*
|
|
25
|
+
* @param fn - A synchronous function to execute safely.
|
|
26
|
+
* @param mapErr - Transforms the thrown value into a typed error `E`.
|
|
27
|
+
* @returns The function's return value on success, or `Err<E>` on throw.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const result = safe(
|
|
32
|
+
* () => JSON.parse(raw),
|
|
33
|
+
* (e) => ({ code: 'PARSE', message: String(e) }),
|
|
34
|
+
* );
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function safe<T, E>(fn: () => T, mapErr: (thrown: unknown) => E): Result<T, E>;
|
|
38
|
+
/**
|
|
39
|
+
* Wraps a Promise so that rejections become {@link Err} values instead of
|
|
40
|
+
* thrown exceptions.
|
|
41
|
+
*
|
|
42
|
+
* If the promise resolves, the resolved value is passed through. If it
|
|
43
|
+
* rejects, the rejection reason is wrapped with `err()` and the returned
|
|
44
|
+
* promise resolves (never rejects) to `Err<unknown>`.
|
|
45
|
+
*
|
|
46
|
+
* @param promise - The promise to wrap.
|
|
47
|
+
* @returns A `ResultAsync` that always resolves — either to `T` or to `Err<unknown>`.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const result = await safe(fetch('/api/data'));
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function safe<T>(promise: Promise<T>): ResultAsync<T, unknown>;
|
|
55
|
+
/**
|
|
56
|
+
* Wraps a Promise so that rejections are mapped through `mapErr` and returned
|
|
57
|
+
* as typed {@link Err} values.
|
|
58
|
+
*
|
|
59
|
+
* Combines the safety of promise wrapping with precise error typing.
|
|
60
|
+
*
|
|
61
|
+
* @param promise - The promise to wrap.
|
|
62
|
+
* @param mapErr - Transforms the rejection reason into a typed error `E`.
|
|
63
|
+
* @returns A `ResultAsync` that always resolves — either to `T` or to `Err<E>`.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* const result = await safe(
|
|
68
|
+
* fetch('/api/users/1'),
|
|
69
|
+
* (e) => ({ code: 'NETWORK', message: String(e) }),
|
|
70
|
+
* );
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function safe<T, E>(promise: Promise<T>, mapErr: (thrown: unknown) => E): ResultAsync<T, E>;
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,20 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* 마커 프로퍼티는 타입에 포함하지 않는다.
|
|
4
|
-
* err() 함수가 런타임에 마커를 동적 추가하며,
|
|
5
|
-
* isErr() 타입 가드를 통해서만 판별한다.
|
|
2
|
+
* The error type returned by {@link err}.
|
|
6
3
|
*
|
|
7
|
-
*
|
|
4
|
+
* Every `Err` carries a `stack` trace captured at creation and an optional
|
|
5
|
+
* `data` payload describing what went wrong. The hidden marker property used
|
|
6
|
+
* for detection is intentionally excluded from the type — it is managed
|
|
7
|
+
* internally by `err()` and checked only through `isErr()`.
|
|
8
|
+
*
|
|
9
|
+
* @template E - The type of the attached error data. Defaults to `never`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const e: Err<string> = err('not found');
|
|
14
|
+
* console.log(e.data); // 'not found'
|
|
15
|
+
* console.log(e.stack); // stack trace string
|
|
16
|
+
* ```
|
|
8
17
|
*/
|
|
9
18
|
export type Err<E = never> = {
|
|
10
19
|
stack: string;
|
|
11
20
|
data: E;
|
|
12
21
|
};
|
|
13
22
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
23
|
+
* A plain union representing either a success value (`T`) or an error (`Err<E>`).
|
|
24
|
+
*
|
|
25
|
+
* Unlike wrapper-class approaches, `Result` is just `T | Err<E>` — zero
|
|
26
|
+
* runtime overhead, full type safety. Use {@link isErr} to narrow the type.
|
|
16
27
|
*
|
|
17
|
-
* @template T -
|
|
18
|
-
* @template E -
|
|
28
|
+
* @template T - The success value type.
|
|
29
|
+
* @template E - The error data type. Defaults to `never`.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* function divide(a: number, b: number): Result<number, string> {
|
|
34
|
+
* if (b === 0) return err('division by zero');
|
|
35
|
+
* return a / b;
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
19
38
|
*/
|
|
20
39
|
export type Result<T, E = never> = T | Err<E>;
|
|
40
|
+
/**
|
|
41
|
+
* A convenient type alias for asynchronous results.
|
|
42
|
+
*
|
|
43
|
+
* This is simply `Promise<Result<T, E>>` — no wrapper class, no extra
|
|
44
|
+
* abstraction. Use it as a return type for async functions that may fail.
|
|
45
|
+
*
|
|
46
|
+
* @template T - The success value type.
|
|
47
|
+
* @template E - The error data type. Defaults to `never`.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* async function fetchUser(id: number): ResultAsync<User, string> {
|
|
52
|
+
* const res = await fetch(`/api/users/${id}`);
|
|
53
|
+
* if (!res.ok) return err(res.statusText);
|
|
54
|
+
* return await res.json();
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export type ResultAsync<T, E = never> = Promise<Result<T, E>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zipbul/result",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Lightweight Result type for error handling without exceptions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Junhyung Park (https://github.com/parkrevil)",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "bun build index.ts --outdir dist --target bun --format esm --sourcemap=linked && tsc -p tsconfig.build.json",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
38
39
|
"test": "bun test",
|
|
39
40
|
"coverage": "bun test --coverage"
|
|
40
41
|
}
|