@zipbul/result 0.0.1
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 +383 -0
- package/README.md +383 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +12 -0
- package/dist/src/constants.d.ts +21 -0
- package/dist/src/err.d.ts +14 -0
- package/dist/src/is-err.d.ts +14 -0
- package/dist/src/types.d.ts +20 -0
- package/package.json +33 -0
package/README.ko.md
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# @zipbul/result
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | **한국어**
|
|
4
|
+
|
|
5
|
+
예외(exception) 없이 에러를 처리하는 경량 Result 타입입니다.
|
|
6
|
+
클래스로 감싸지 않고 평범한 유니온 값(`T | Err<E>`)을 반환합니다 — 런타임 오버헤드 제로, 완전한 타입 안전성.
|
|
7
|
+
|
|
8
|
+
> throw 없음, try/catch 없음, 래퍼 클래스 없음. 값만 있습니다.
|
|
9
|
+
|
|
10
|
+
<br>
|
|
11
|
+
|
|
12
|
+
## 📦 설치
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun add @zipbul/result
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
<br>
|
|
19
|
+
|
|
20
|
+
## 💡 핵심 개념
|
|
21
|
+
|
|
22
|
+
`throw`를 사용하는 전통적 에러 처리는 제어 흐름을 끊고, 타입 정보를 잃으며, 호출자에게 `try/catch` 추측 게임을 강요합니다.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// ❌ Throw — 호출자는 뭐가 올지 전혀 모릅니다
|
|
26
|
+
function parseConfig(raw: string): Config {
|
|
27
|
+
if (!raw) throw new Error('empty input'); // 타입이 뭔가요? 알 수 없음.
|
|
28
|
+
if (!valid(raw)) throw new ValidationError(); // 조용히 상위로 전파됨.
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const config = parseConfig(input);
|
|
34
|
+
} catch (e) {
|
|
35
|
+
// `e`가 뭔가요? Error? ValidationError? JSON.parse의 SyntaxError?
|
|
36
|
+
// TypeScript는 여기서 도와줄 수 없습니다 — `e`는 `unknown`입니다.
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// ✅ Result — 타입 안전, 명시적, 놀라움 없음
|
|
42
|
+
import { err, isErr, type Result } from '@zipbul/result';
|
|
43
|
+
|
|
44
|
+
function parseConfig(raw: string): Result<Config, string> {
|
|
45
|
+
if (!raw) return err('empty input');
|
|
46
|
+
if (!valid(raw)) return err('validation failed');
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = parseConfig(input);
|
|
51
|
+
|
|
52
|
+
if (isErr(result)) {
|
|
53
|
+
console.error(result.data); // string — TypeScript가 타입을 압니다
|
|
54
|
+
} else {
|
|
55
|
+
console.log(result.host); // Config — 완전히 좁혀짐
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
<br>
|
|
60
|
+
|
|
61
|
+
## 🚀 빠른 시작
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { err, isErr, type Result } from '@zipbul/result';
|
|
65
|
+
|
|
66
|
+
interface User {
|
|
67
|
+
id: number;
|
|
68
|
+
name: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function findUser(id: number): Result<User, string> {
|
|
72
|
+
if (id <= 0) return err('Invalid ID');
|
|
73
|
+
|
|
74
|
+
const user = db.get(id);
|
|
75
|
+
if (!user) return err('User not found');
|
|
76
|
+
|
|
77
|
+
return user;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const result = findUser(42);
|
|
81
|
+
|
|
82
|
+
if (isErr(result)) {
|
|
83
|
+
// result는 Err<string>
|
|
84
|
+
console.error(`실패: ${result.data}`);
|
|
85
|
+
} else {
|
|
86
|
+
// result는 User
|
|
87
|
+
console.log(`안녕하세요, ${result.name}`);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
<br>
|
|
92
|
+
|
|
93
|
+
## 📚 API 레퍼런스
|
|
94
|
+
|
|
95
|
+
### `err()`
|
|
96
|
+
|
|
97
|
+
불변(immutable) `Err` 값을 생성합니다. 절대 throw하지 않습니다.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { err } from '@zipbul/result';
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| 오버로드 | 반환 | 설명 |
|
|
104
|
+
|:---------|:-----|:-----|
|
|
105
|
+
| `err()` | `Err<never>` | 데이터 없는 에러 |
|
|
106
|
+
| `err<E>(data: E)` | `Err<E>` | 데이터가 첨부된 에러 |
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// 데이터 없음 — 단순 신호
|
|
110
|
+
const e1 = err();
|
|
111
|
+
// e1.data → never (접근 불가)
|
|
112
|
+
// e1.stack → 캡처된 스택 트레이스
|
|
113
|
+
|
|
114
|
+
// 데이터 포함 — 에러 상세 정보 전달
|
|
115
|
+
const e2 = err('not found');
|
|
116
|
+
// e2.data → 'not found'
|
|
117
|
+
// e2.stack → 캡처된 스택 트레이스
|
|
118
|
+
|
|
119
|
+
// 풍부한 에러 객체
|
|
120
|
+
const e3 = err({ code: 'TIMEOUT', retryAfter: 3000 });
|
|
121
|
+
// e3.data.code → 'TIMEOUT'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
반환된 `Err`의 프로퍼티:
|
|
125
|
+
|
|
126
|
+
| 프로퍼티 | 타입 | 설명 |
|
|
127
|
+
|:---------|:-----|:-----|
|
|
128
|
+
| `data` | `E` | 첨부된 에러 데이터 |
|
|
129
|
+
| `stack` | `string` | `err()` 호출 지점에서 캡처된 스택 트레이스 |
|
|
130
|
+
|
|
131
|
+
> **불변성** — 모든 `Err`는 `Object.freeze()`됩니다. strict mode에서 프로퍼티를 수정하면 `TypeError`가 발생합니다.
|
|
132
|
+
|
|
133
|
+
<br>
|
|
134
|
+
|
|
135
|
+
### `isErr()`
|
|
136
|
+
|
|
137
|
+
값을 `Err<E>`로 좁히는 타입 가드입니다.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { isErr } from '@zipbul/result';
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
function isErr<E = unknown>(value: unknown): value is Err<E>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- `value`가 null이 아닌 객체이고, 마커 프로퍼티가 `true`인 경우에만 `true`를 반환합니다.
|
|
148
|
+
- **절대 throw하지 않습니다** — `null`, `undefined`, 원시값, 예외를 내부적으로 처리합니다.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const result: Result<number, string> = doSomething();
|
|
152
|
+
|
|
153
|
+
if (isErr(result)) {
|
|
154
|
+
// result: Err<string>
|
|
155
|
+
console.error(result.data);
|
|
156
|
+
} else {
|
|
157
|
+
// result: number
|
|
158
|
+
console.log(result + 1);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
> **제네릭 `E` 주의사항** — `isErr<E>()`는 타입 단언만 제공합니다. `data`의 형태를 런타임에서 검증하지 않습니다. 호출자가 제네릭이 실제 에러 타입과 일치하는지 보장해야 합니다.
|
|
163
|
+
|
|
164
|
+
<br>
|
|
165
|
+
|
|
166
|
+
### `Result<T, E>`
|
|
167
|
+
|
|
168
|
+
평범한 유니온 타입 — 래퍼 클래스가 아닙니다.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
type Result<T, E = never> = T | Err<E>;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
| 파라미터 | 기본값 | 설명 |
|
|
175
|
+
|:---------|:-------|:-----|
|
|
176
|
+
| `T` | — | 성공 값 타입 |
|
|
177
|
+
| `E` | `never` | 에러 데이터 타입 |
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// 단순 — 에러 데이터 없음
|
|
181
|
+
type MayFail = Result<Config>;
|
|
182
|
+
|
|
183
|
+
// 에러 데이터 포함
|
|
184
|
+
type ParseResult = Result<Config, string>;
|
|
185
|
+
|
|
186
|
+
// 풍부한 에러 타입
|
|
187
|
+
type ApiResult = Result<User, { code: string; message: string }>;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
<br>
|
|
191
|
+
|
|
192
|
+
### `Err<E>`
|
|
193
|
+
|
|
194
|
+
`err()`가 반환하는 에러 타입입니다.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
type Err<E = never> = {
|
|
198
|
+
stack: string;
|
|
199
|
+
data: E;
|
|
200
|
+
};
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
> 식별에 사용되는 마커 프로퍼티는 의도적으로 타입에서 제외됩니다. `err()`가 런타임에 내부적으로 추가하고, `isErr()`를 통해서만 판별합니다 — 이렇게 하면 공개 API 표면이 깔끔해지고, 소비자가 구현 세부사항에 의존하는 것을 방지합니다.
|
|
204
|
+
|
|
205
|
+
<br>
|
|
206
|
+
|
|
207
|
+
### 마커 키(Marker Key)
|
|
208
|
+
|
|
209
|
+
마커 키는 `Err` 객체를 식별하는 데 사용되는 숨겨진 고유 프로퍼티입니다. 충돌에 강한 문자열이 기본값입니다.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { DEFAULT_MARKER_KEY, getMarkerKey, setMarkerKey } from '@zipbul/result';
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
| 내보내기 | 타입 | 설명 |
|
|
216
|
+
|:---------|:-----|:-----|
|
|
217
|
+
| `DEFAULT_MARKER_KEY` | `string` | `'__$$e_9f4a1c7b__'` — 기본 키 |
|
|
218
|
+
| `getMarkerKey()` | `() => string` | 현재 마커 키 반환 |
|
|
219
|
+
| `setMarkerKey(key)` | `(key: string) => void` | 마커 키 변경 |
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
// 독립 모듈 간 감지 리셋
|
|
223
|
+
import { setMarkerKey, getMarkerKey } from '@zipbul/result';
|
|
224
|
+
|
|
225
|
+
setMarkerKey('__my_app_err__');
|
|
226
|
+
console.log(getMarkerKey()); // '__my_app_err__'
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
> **검증** — `setMarkerKey()`는 키가 빈 문자열이거나 공백만으로 이루어진 경우 `TypeError`를 던집니다.
|
|
230
|
+
>
|
|
231
|
+
> **주의** — 마커 키를 변경하면 `isErr()`가 이전 키로 생성된 `Err` 객체를 더 이상 인식하지 못합니다. 독립 모듈 간 에러 도메인을 분리해야 할 때만 변경하세요.
|
|
232
|
+
|
|
233
|
+
<br>
|
|
234
|
+
|
|
235
|
+
## 🔬 고급 사용법
|
|
236
|
+
|
|
237
|
+
### Result를 반환하는 함수
|
|
238
|
+
|
|
239
|
+
`Result`로 함수 시그니처를 정의하면 에러 경로가 타입 시스템에서 명시적으로 드러납니다.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import { err, isErr, type Result } from '@zipbul/result';
|
|
243
|
+
|
|
244
|
+
interface ValidationError {
|
|
245
|
+
field: string;
|
|
246
|
+
message: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function validate(input: unknown): Result<ValidData, ValidationError> {
|
|
250
|
+
if (!input || typeof input !== 'object') {
|
|
251
|
+
return err({ field: 'root', message: 'Expected an object' });
|
|
252
|
+
}
|
|
253
|
+
// ... 검증 로직
|
|
254
|
+
return input as ValidData;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const result = validate(body);
|
|
258
|
+
if (isErr(result)) {
|
|
259
|
+
return Response.json({ error: result.data }, { status: 400 });
|
|
260
|
+
}
|
|
261
|
+
// result는 여기서 ValidData
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 결과 체이닝
|
|
265
|
+
|
|
266
|
+
`Result`는 평범한 유니온이므로 `.map()`이나 `.flatMap()`이 없습니다. 표준 제어 흐름을 사용하세요:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
function processOrder(orderId: string): Result<Receipt, string> {
|
|
270
|
+
const order = findOrder(orderId);
|
|
271
|
+
if (isErr(order)) return order; // 전파
|
|
272
|
+
|
|
273
|
+
const payment = chargePayment(order);
|
|
274
|
+
if (isErr(payment)) return payment; // 전파
|
|
275
|
+
|
|
276
|
+
return generateReceipt(order, payment);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
> 이것은 의도된 설계입니다. `.map()` / `.flatMap()`이 있는 클래스는 런타임 비용을 추가하고 특정 합성 스타일을 강요합니다. 평범한 값 + `isErr()`는 표준 `if`, `switch`, early return 등 원하는 패턴을 자유롭게 사용할 수 있게 합니다.
|
|
281
|
+
|
|
282
|
+
### 비동기 결과
|
|
283
|
+
|
|
284
|
+
`Promise`와 자연스럽게 작동합니다:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
async function fetchUser(id: number): Promise<Result<User, ApiError>> {
|
|
288
|
+
try {
|
|
289
|
+
const res = await fetch(`/api/users/${id}`);
|
|
290
|
+
if (!res.ok) return err({ code: res.status, message: res.statusText });
|
|
291
|
+
return await res.json();
|
|
292
|
+
} catch {
|
|
293
|
+
return err({ code: 0, message: 'Network error' });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 스택 트레이스
|
|
299
|
+
|
|
300
|
+
모든 `Err`는 생성 시점에 스택 트레이스를 캡처하여, `throw` 없이 디버깅이 가능합니다:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
const e = err('something went wrong');
|
|
304
|
+
console.log(e.stack);
|
|
305
|
+
// Error
|
|
306
|
+
// at err (/.../err.ts:22:18)
|
|
307
|
+
// at validate (/.../validate.ts:15:12)
|
|
308
|
+
// at handleRequest (/.../server.ts:8:20)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
<br>
|
|
312
|
+
|
|
313
|
+
## 🔌 프레임워크 연동 예시
|
|
314
|
+
|
|
315
|
+
<details>
|
|
316
|
+
<summary><b>Bun.serve</b></summary>
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import { err, isErr, type Result } from '@zipbul/result';
|
|
320
|
+
|
|
321
|
+
interface AppError {
|
|
322
|
+
code: string;
|
|
323
|
+
message: string;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function parseBody(request: Request): Promise<Result<Payload, AppError>> {
|
|
327
|
+
// ... Result 반환
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
Bun.serve({
|
|
331
|
+
async fetch(request) {
|
|
332
|
+
const body = await parseBody(request);
|
|
333
|
+
|
|
334
|
+
if (isErr(body)) {
|
|
335
|
+
return Response.json(
|
|
336
|
+
{ error: body.data.code, message: body.data.message },
|
|
337
|
+
{ status: 400 },
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// body는 Payload
|
|
342
|
+
return Response.json({ ok: true, data: process(body) });
|
|
343
|
+
},
|
|
344
|
+
port: 3000,
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
</details>
|
|
349
|
+
|
|
350
|
+
<details>
|
|
351
|
+
<summary><b>@zipbul/cors와 함께</b></summary>
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { Cors, CorsAction } from '@zipbul/cors';
|
|
355
|
+
import { isErr } from '@zipbul/result';
|
|
356
|
+
|
|
357
|
+
const corsResult = Cors.create({
|
|
358
|
+
origin: 'https://app.example.com',
|
|
359
|
+
credentials: true,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Cors.create()는 Result<Cors, CorsError>를 반환합니다
|
|
363
|
+
if (isErr(corsResult)) {
|
|
364
|
+
throw new Error(`CORS 설정 에러: ${corsResult.data.message}`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const cors = corsResult;
|
|
368
|
+
|
|
369
|
+
// cors.handle()는 Promise<Result<CorsResult, CorsError>>를 반환합니다
|
|
370
|
+
const result = await cors.handle(request);
|
|
371
|
+
|
|
372
|
+
if (isErr(result)) {
|
|
373
|
+
return new Response('Internal Error', { status: 500 });
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
</details>
|
|
378
|
+
|
|
379
|
+
<br>
|
|
380
|
+
|
|
381
|
+
## 📄 라이선스
|
|
382
|
+
|
|
383
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# @zipbul/result
|
|
2
|
+
|
|
3
|
+
**English** | [한국어](./README.ko.md)
|
|
4
|
+
|
|
5
|
+
A lightweight Result type for error handling without exceptions.
|
|
6
|
+
Returns plain union values (`T | Err<E>`) instead of wrapping in classes — zero runtime overhead, full type safety.
|
|
7
|
+
|
|
8
|
+
> No throw, no try/catch, no wrapper class. Just values.
|
|
9
|
+
|
|
10
|
+
<br>
|
|
11
|
+
|
|
12
|
+
## 📦 Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun add @zipbul/result
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
<br>
|
|
19
|
+
|
|
20
|
+
## 💡 Core Concept
|
|
21
|
+
|
|
22
|
+
Traditional error handling with `throw` breaks control flow, loses type information, and forces callers into a `try/catch` guessing game.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// ❌ Throw — caller has no idea what to expect
|
|
26
|
+
function parseConfig(raw: string): Config {
|
|
27
|
+
if (!raw) throw new Error('empty input'); // What type? Unknown.
|
|
28
|
+
if (!valid(raw)) throw new ValidationError(); // Silently propagates up.
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const config = parseConfig(input);
|
|
34
|
+
} catch (e) {
|
|
35
|
+
// What is `e`? Error? ValidationError? SyntaxError from JSON.parse?
|
|
36
|
+
// TypeScript cannot help you here — `e` is `unknown`.
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// ✅ Result — type-safe, explicit, no surprises
|
|
42
|
+
import { err, isErr, type Result } from '@zipbul/result';
|
|
43
|
+
|
|
44
|
+
function parseConfig(raw: string): Result<Config, string> {
|
|
45
|
+
if (!raw) return err('empty input');
|
|
46
|
+
if (!valid(raw)) return err('validation failed');
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = parseConfig(input);
|
|
51
|
+
|
|
52
|
+
if (isErr(result)) {
|
|
53
|
+
console.error(result.data); // string — TypeScript knows the type
|
|
54
|
+
} else {
|
|
55
|
+
console.log(result.host); // Config — fully narrowed
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
<br>
|
|
60
|
+
|
|
61
|
+
## 🚀 Quick Start
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { err, isErr, type Result } from '@zipbul/result';
|
|
65
|
+
|
|
66
|
+
interface User {
|
|
67
|
+
id: number;
|
|
68
|
+
name: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function findUser(id: number): Result<User, string> {
|
|
72
|
+
if (id <= 0) return err('Invalid ID');
|
|
73
|
+
|
|
74
|
+
const user = db.get(id);
|
|
75
|
+
if (!user) return err('User not found');
|
|
76
|
+
|
|
77
|
+
return user;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const result = findUser(42);
|
|
81
|
+
|
|
82
|
+
if (isErr(result)) {
|
|
83
|
+
// result is Err<string>
|
|
84
|
+
console.error(`Failed: ${result.data}`);
|
|
85
|
+
} else {
|
|
86
|
+
// result is User
|
|
87
|
+
console.log(`Hello, ${result.name}`);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
<br>
|
|
92
|
+
|
|
93
|
+
## 📚 API Reference
|
|
94
|
+
|
|
95
|
+
### `err()`
|
|
96
|
+
|
|
97
|
+
Creates an immutable `Err` value. Never throws.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { err } from '@zipbul/result';
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Overload | Return | Description |
|
|
104
|
+
|:---------|:-------|:------------|
|
|
105
|
+
| `err()` | `Err<never>` | Error with no data |
|
|
106
|
+
| `err<E>(data: E)` | `Err<E>` | Error with attached data |
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// No data — simple signal
|
|
110
|
+
const e1 = err();
|
|
111
|
+
// e1.data → never (cannot access)
|
|
112
|
+
// e1.stack → captured stack trace
|
|
113
|
+
|
|
114
|
+
// With data — carry error details
|
|
115
|
+
const e2 = err('not found');
|
|
116
|
+
// e2.data → 'not found'
|
|
117
|
+
// e2.stack → captured stack trace
|
|
118
|
+
|
|
119
|
+
// Rich error objects
|
|
120
|
+
const e3 = err({ code: 'TIMEOUT', retryAfter: 3000 });
|
|
121
|
+
// e3.data.code → 'TIMEOUT'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Properties of the returned `Err`:
|
|
125
|
+
|
|
126
|
+
| Property | Type | Description |
|
|
127
|
+
|:---------|:-----|:------------|
|
|
128
|
+
| `data` | `E` | The attached error data |
|
|
129
|
+
| `stack` | `string` | Stack trace captured at `err()` call site |
|
|
130
|
+
|
|
131
|
+
> **Immutability** — every `Err` is `Object.freeze()`d. Attempting to modify properties in strict mode throws a `TypeError`.
|
|
132
|
+
|
|
133
|
+
<br>
|
|
134
|
+
|
|
135
|
+
### `isErr()`
|
|
136
|
+
|
|
137
|
+
Type guard that narrows a value to `Err<E>`.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { isErr } from '@zipbul/result';
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
function isErr<E = unknown>(value: unknown): value is Err<E>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- Returns `true` if `value` is a non-null object with the marker property set to `true`.
|
|
148
|
+
- **Never throws** — handles `null`, `undefined`, primitives, and exceptions internally.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const result: Result<number, string> = doSomething();
|
|
152
|
+
|
|
153
|
+
if (isErr(result)) {
|
|
154
|
+
// result: Err<string>
|
|
155
|
+
console.error(result.data);
|
|
156
|
+
} else {
|
|
157
|
+
// result: number
|
|
158
|
+
console.log(result + 1);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
> **Generic `E` caveat** — `isErr<E>()` provides a type assertion only. It does not validate the shape of `data` at runtime. Callers must ensure the generic matches the actual error type.
|
|
163
|
+
|
|
164
|
+
<br>
|
|
165
|
+
|
|
166
|
+
### `Result<T, E>`
|
|
167
|
+
|
|
168
|
+
A plain union type — not a wrapper class.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
type Result<T, E = never> = T | Err<E>;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
| Parameter | Default | Description |
|
|
175
|
+
|:----------|:--------|:------------|
|
|
176
|
+
| `T` | — | Success value type |
|
|
177
|
+
| `E` | `never` | Error data type |
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// Simple — no error data
|
|
181
|
+
type MayFail = Result<Config>;
|
|
182
|
+
|
|
183
|
+
// With error data
|
|
184
|
+
type ParseResult = Result<Config, string>;
|
|
185
|
+
|
|
186
|
+
// Rich error types
|
|
187
|
+
type ApiResult = Result<User, { code: string; message: string }>;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
<br>
|
|
191
|
+
|
|
192
|
+
### `Err<E>`
|
|
193
|
+
|
|
194
|
+
The error type returned by `err()`.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
type Err<E = never> = {
|
|
198
|
+
stack: string;
|
|
199
|
+
data: E;
|
|
200
|
+
};
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
> The marker property used for identification is deliberately excluded from the type. It is added internally by `err()` and checked by `isErr()` — this keeps the public API surface clean and prevents consumers from depending on implementation details.
|
|
204
|
+
|
|
205
|
+
<br>
|
|
206
|
+
|
|
207
|
+
### Marker Key
|
|
208
|
+
|
|
209
|
+
The marker key is a unique hidden property used to identify `Err` objects. It defaults to a collision-resistant string.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { DEFAULT_MARKER_KEY, getMarkerKey, setMarkerKey } from '@zipbul/result';
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
| Export | Type | Description |
|
|
216
|
+
|:-------|:-----|:------------|
|
|
217
|
+
| `DEFAULT_MARKER_KEY` | `string` | `'__$$e_9f4a1c7b__'` — the default key |
|
|
218
|
+
| `getMarkerKey()` | `() => string` | Returns the current marker key |
|
|
219
|
+
| `setMarkerKey(key)` | `(key: string) => void` | Changes the marker key |
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
// Reset detection across independent modules
|
|
223
|
+
import { setMarkerKey, getMarkerKey } from '@zipbul/result';
|
|
224
|
+
|
|
225
|
+
setMarkerKey('__my_app_err__');
|
|
226
|
+
console.log(getMarkerKey()); // '__my_app_err__'
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
> **Validation** — `setMarkerKey()` throws `TypeError` if the key is empty or whitespace-only.
|
|
230
|
+
>
|
|
231
|
+
> **Warning** — changing the marker key means `isErr()` will no longer recognize `Err` objects created with the previous key. Only change this if you need to isolate error domains across independent modules.
|
|
232
|
+
|
|
233
|
+
<br>
|
|
234
|
+
|
|
235
|
+
## 🔬 Advanced Usage
|
|
236
|
+
|
|
237
|
+
### Result-returning functions
|
|
238
|
+
|
|
239
|
+
Define function signatures with `Result` to make error paths explicit in the type system.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import { err, isErr, type Result } from '@zipbul/result';
|
|
243
|
+
|
|
244
|
+
interface ValidationError {
|
|
245
|
+
field: string;
|
|
246
|
+
message: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function validate(input: unknown): Result<ValidData, ValidationError> {
|
|
250
|
+
if (!input || typeof input !== 'object') {
|
|
251
|
+
return err({ field: 'root', message: 'Expected an object' });
|
|
252
|
+
}
|
|
253
|
+
// ... validation logic
|
|
254
|
+
return input as ValidData;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const result = validate(body);
|
|
258
|
+
if (isErr(result)) {
|
|
259
|
+
return Response.json({ error: result.data }, { status: 400 });
|
|
260
|
+
}
|
|
261
|
+
// result is ValidData here
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Chaining results
|
|
265
|
+
|
|
266
|
+
Since `Result` is a plain union, there's no `.map()` or `.flatMap()`. Use standard control flow:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
function processOrder(orderId: string): Result<Receipt, string> {
|
|
270
|
+
const order = findOrder(orderId);
|
|
271
|
+
if (isErr(order)) return order; // propagate
|
|
272
|
+
|
|
273
|
+
const payment = chargePayment(order);
|
|
274
|
+
if (isErr(payment)) return payment; // propagate
|
|
275
|
+
|
|
276
|
+
return generateReceipt(order, payment);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
> This is intentional. Classes with `.map()` / `.flatMap()` add runtime cost and force a specific composition style. Plain values + `isErr()` let you use standard `if`, `switch`, early return, and any other pattern you prefer.
|
|
281
|
+
|
|
282
|
+
### Async results
|
|
283
|
+
|
|
284
|
+
Works naturally with `Promise`:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
async function fetchUser(id: number): Promise<Result<User, ApiError>> {
|
|
288
|
+
try {
|
|
289
|
+
const res = await fetch(`/api/users/${id}`);
|
|
290
|
+
if (!res.ok) return err({ code: res.status, message: res.statusText });
|
|
291
|
+
return await res.json();
|
|
292
|
+
} catch {
|
|
293
|
+
return err({ code: 0, message: 'Network error' });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Stack traces
|
|
299
|
+
|
|
300
|
+
Every `Err` captures a stack trace at creation time, enabling debugging without `throw`:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
const e = err('something went wrong');
|
|
304
|
+
console.log(e.stack);
|
|
305
|
+
// Error
|
|
306
|
+
// at err (/.../err.ts:22:18)
|
|
307
|
+
// at validate (/.../validate.ts:15:12)
|
|
308
|
+
// at handleRequest (/.../server.ts:8:20)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
<br>
|
|
312
|
+
|
|
313
|
+
## 🔌 Framework Integration Examples
|
|
314
|
+
|
|
315
|
+
<details>
|
|
316
|
+
<summary><b>Bun.serve</b></summary>
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import { err, isErr, type Result } from '@zipbul/result';
|
|
320
|
+
|
|
321
|
+
interface AppError {
|
|
322
|
+
code: string;
|
|
323
|
+
message: string;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function parseBody(request: Request): Promise<Result<Payload, AppError>> {
|
|
327
|
+
// ... returns Result
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
Bun.serve({
|
|
331
|
+
async fetch(request) {
|
|
332
|
+
const body = await parseBody(request);
|
|
333
|
+
|
|
334
|
+
if (isErr(body)) {
|
|
335
|
+
return Response.json(
|
|
336
|
+
{ error: body.data.code, message: body.data.message },
|
|
337
|
+
{ status: 400 },
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// body is Payload
|
|
342
|
+
return Response.json({ ok: true, data: process(body) });
|
|
343
|
+
},
|
|
344
|
+
port: 3000,
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
</details>
|
|
349
|
+
|
|
350
|
+
<details>
|
|
351
|
+
<summary><b>With @zipbul/cors</b></summary>
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { Cors, CorsAction } from '@zipbul/cors';
|
|
355
|
+
import { isErr } from '@zipbul/result';
|
|
356
|
+
|
|
357
|
+
const corsResult = Cors.create({
|
|
358
|
+
origin: 'https://app.example.com',
|
|
359
|
+
credentials: true,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Cors.create() returns Result<Cors, CorsError>
|
|
363
|
+
if (isErr(corsResult)) {
|
|
364
|
+
throw new Error(`CORS config error: ${corsResult.data.message}`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const cors = corsResult;
|
|
368
|
+
|
|
369
|
+
// cors.handle() returns Promise<Result<CorsResult, CorsError>>
|
|
370
|
+
const result = await cors.handle(request);
|
|
371
|
+
|
|
372
|
+
if (isErr(result)) {
|
|
373
|
+
return new Response('Internal Error', { status: 500 });
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
</details>
|
|
378
|
+
|
|
379
|
+
<br>
|
|
380
|
+
|
|
381
|
+
## 📄 License
|
|
382
|
+
|
|
383
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/constants.ts
|
|
3
|
+
var DEFAULT_MARKER_KEY = "__$$e_9f4a1c7b__";
|
|
4
|
+
var currentMarkerKey = DEFAULT_MARKER_KEY;
|
|
5
|
+
function getMarkerKey() {
|
|
6
|
+
return currentMarkerKey;
|
|
7
|
+
}
|
|
8
|
+
function setMarkerKey(key) {
|
|
9
|
+
if (key.trim().length === 0) {
|
|
10
|
+
throw new TypeError("Marker key must be a non-empty string");
|
|
11
|
+
}
|
|
12
|
+
currentMarkerKey = key;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/err.ts
|
|
16
|
+
function err(data) {
|
|
17
|
+
let stack;
|
|
18
|
+
try {
|
|
19
|
+
stack = new Error().stack ?? "";
|
|
20
|
+
} catch {
|
|
21
|
+
stack = "";
|
|
22
|
+
}
|
|
23
|
+
const result = {
|
|
24
|
+
[getMarkerKey()]: true,
|
|
25
|
+
stack,
|
|
26
|
+
data
|
|
27
|
+
};
|
|
28
|
+
return Object.freeze(result);
|
|
29
|
+
}
|
|
30
|
+
// src/is-err.ts
|
|
31
|
+
function isErr(value) {
|
|
32
|
+
try {
|
|
33
|
+
return value !== null && value !== undefined && typeof value === "object" && value[getMarkerKey()] === true;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
setMarkerKey,
|
|
40
|
+
isErr,
|
|
41
|
+
getMarkerKey,
|
|
42
|
+
err,
|
|
43
|
+
DEFAULT_MARKER_KEY
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
//# debugId=5E29B525CDB4D96664756E2164756E21
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/constants.ts", "../src/err.ts", "../src/is-err.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * 기본 에러 마커 키.\n * 에러 객체의 판별 프로퍼티명으로 사용된다.\n * zipbul 프레임워크와 무관한 유니크한 값으로, 충돌 가능성을 최소화한다.\n */\nexport const DEFAULT_MARKER_KEY = '__$$e_9f4a1c7b__';\n\nlet currentMarkerKey: string = DEFAULT_MARKER_KEY;\n\n/**\n * 현재 설정된 마커 키를 반환한다.\n *\n * @returns 현재 마커 키 문자열\n */\nexport function getMarkerKey(): string {\n return currentMarkerKey;\n}\n\n/**\n * 마커 키를 변경한다.\n * error()와 isError()가 이 키를 참조하여 에러를 판별한다.\n * 빈 문자열 및 공백만으로 이루어진 문자열은 허용하지 않는다.\n *\n * @param key - 새 마커 키. 비어있지 않은(공백 제외) 문자열이어야 한다.\n * @throws {TypeError} key가 빈/공백 문자열인 경우\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 * Err를 생성한다.\n * 절대 throw하지 않는다.\n * 반환 전 Object.freeze를 적용한다.\n *\n * @returns 동결(frozen)된 Err (data 없음)\n */\nexport function err(): Err;\n/**\n * @param data - 에러에 첨부할 데이터. 타입 무관.\n * @returns 동결(frozen)된 Err<E>\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 * 값이 Err인지 판별하는 타입 가드.\n * 현재 설정된 마커 키(`getMarkerKey()`)에 해당하는 프로퍼티가\n * `=== true`인 경우에만 true를 반환한다.\n * 절대 throw하지 않는다.\n *\n * 주의: 제네릭 E는 런타임 검증 없이 타입 단언만 제공한다.\n * data의 구조는 호출자가 보장해야 한다.\n *\n * @param value - 판별 대상. 타입 무관.\n * @returns value가 Err이면 true\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
|
+
],
|
|
9
|
+
"mappings": ";;AAKO,IAAM,qBAAqB;AAElC,IAAI,mBAA2B;AAOxB,SAAS,YAAY,GAAW;AAAA,EACrC,OAAO;AAAA;AAWF,SAAS,YAAY,CAAC,KAAmB;AAAA,EAC9C,IAAI,IAAI,KAAK,EAAE,WAAW,GAAG;AAAA,IAC3B,MAAM,IAAI,UAAU,uCAAuC;AAAA,EAC7D;AAAA,EACA,mBAAmB;AAAA;;;ACdd,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;;ACftB,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;",
|
|
10
|
+
"debugId": "5E29B525CDB4D96664756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 기본 에러 마커 키.
|
|
3
|
+
* 에러 객체의 판별 프로퍼티명으로 사용된다.
|
|
4
|
+
* zipbul 프레임워크와 무관한 유니크한 값으로, 충돌 가능성을 최소화한다.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_MARKER_KEY = "__$$e_9f4a1c7b__";
|
|
7
|
+
/**
|
|
8
|
+
* 현재 설정된 마커 키를 반환한다.
|
|
9
|
+
*
|
|
10
|
+
* @returns 현재 마커 키 문자열
|
|
11
|
+
*/
|
|
12
|
+
export declare function getMarkerKey(): string;
|
|
13
|
+
/**
|
|
14
|
+
* 마커 키를 변경한다.
|
|
15
|
+
* error()와 isError()가 이 키를 참조하여 에러를 판별한다.
|
|
16
|
+
* 빈 문자열 및 공백만으로 이루어진 문자열은 허용하지 않는다.
|
|
17
|
+
*
|
|
18
|
+
* @param key - 새 마커 키. 비어있지 않은(공백 제외) 문자열이어야 한다.
|
|
19
|
+
* @throws {TypeError} key가 빈/공백 문자열인 경우
|
|
20
|
+
*/
|
|
21
|
+
export declare function setMarkerKey(key: string): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Err } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Err를 생성한다.
|
|
4
|
+
* 절대 throw하지 않는다.
|
|
5
|
+
* 반환 전 Object.freeze를 적용한다.
|
|
6
|
+
*
|
|
7
|
+
* @returns 동결(frozen)된 Err (data 없음)
|
|
8
|
+
*/
|
|
9
|
+
export declare function err(): Err;
|
|
10
|
+
/**
|
|
11
|
+
* @param data - 에러에 첨부할 데이터. 타입 무관.
|
|
12
|
+
* @returns 동결(frozen)된 Err<E>
|
|
13
|
+
*/
|
|
14
|
+
export declare function err<E>(data: E): Err<E>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Err } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 값이 Err인지 판별하는 타입 가드.
|
|
4
|
+
* 현재 설정된 마커 키(`getMarkerKey()`)에 해당하는 프로퍼티가
|
|
5
|
+
* `=== true`인 경우에만 true를 반환한다.
|
|
6
|
+
* 절대 throw하지 않는다.
|
|
7
|
+
*
|
|
8
|
+
* 주의: 제네릭 E는 런타임 검증 없이 타입 단언만 제공한다.
|
|
9
|
+
* data의 구조는 호출자가 보장해야 한다.
|
|
10
|
+
*
|
|
11
|
+
* @param value - 판별 대상. 타입 무관.
|
|
12
|
+
* @returns value가 Err이면 true
|
|
13
|
+
*/
|
|
14
|
+
export declare function isErr<E = unknown>(value: unknown): value is Err<E>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 에러 타입.
|
|
3
|
+
* 마커 프로퍼티는 타입에 포함하지 않는다.
|
|
4
|
+
* err() 함수가 런타임에 마커를 동적 추가하며,
|
|
5
|
+
* isErr() 타입 가드를 통해서만 판별한다.
|
|
6
|
+
*
|
|
7
|
+
* @template E - 에러에 첨부할 추가 데이터의 타입. 기본값은 never.
|
|
8
|
+
*/
|
|
9
|
+
export type Err<E = never> = {
|
|
10
|
+
stack: string;
|
|
11
|
+
data: E;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* 성공(T) 또는 에러(Err<E>)를 표현하는 유니온 타입.
|
|
15
|
+
* wrapper 클래스가 아닌 plain union으로, 런타임 오버헤드가 없다.
|
|
16
|
+
*
|
|
17
|
+
* @template T - 성공값 타입
|
|
18
|
+
* @template E - 에러 데이터 타입. 기본값은 never.
|
|
19
|
+
*/
|
|
20
|
+
export type Result<T, E = never> = T | Err<E>;
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zipbul/result",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Lightweight Result type for error handling without exceptions",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Junhyung Park (https://github.com/parkrevil)",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/zipbul/toolkit",
|
|
10
|
+
"directory": "packages/result"
|
|
11
|
+
},
|
|
12
|
+
"bugs": "https://github.com/zipbul/toolkit/issues",
|
|
13
|
+
"homepage": "https://github.com/zipbul/toolkit/tree/main/packages/result#readme",
|
|
14
|
+
"keywords": ["result", "error", "either", "typescript", "zipbul"],
|
|
15
|
+
"engines": {
|
|
16
|
+
"bun": ">=1.0.0"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"module": "dist/index.js",
|
|
20
|
+
"types": "dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": ["dist"],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "bun build index.ts --outdir dist --target bun --format esm --sourcemap=linked && tsc -p tsconfig.build.json",
|
|
30
|
+
"test": "bun test",
|
|
31
|
+
"coverage": "bun test --coverage"
|
|
32
|
+
}
|
|
33
|
+
}
|