errore 0.2.0 → 0.4.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.
Files changed (2) hide show
  1. package/README.md +154 -0
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -125,6 +125,69 @@ const posts = andThen(user, u => fetchPosts(u.id))
125
125
  const logged = tap(user, u => console.log('Got user:', u.name))
126
126
  ```
127
127
 
128
+ ### Composing Operations
129
+
130
+ Chain multiple operations together:
131
+
132
+ ```ts
133
+ import { map, andThen, mapError, isError } from 'errore'
134
+
135
+ // Define operations that can fail
136
+ function parseNumber(s: string): ValidationError | number { ... }
137
+ function validatePositive(n: number): ValidationError | number { ... }
138
+ function divide(a: number, b: number): DivisionError | number { ... }
139
+
140
+ // Compose with nested calls
141
+ const result = andThen(
142
+ andThen(parseNumber(input), validatePositive),
143
+ n => divide(100, n)
144
+ )
145
+
146
+ // Or step by step (often clearer)
147
+ function calculate(input: string): ValidationError | DivisionError | number {
148
+ const parsed = parseNumber(input)
149
+ if (isError(parsed)) return parsed
150
+
151
+ const validated = validatePositive(parsed)
152
+ if (isError(validated)) return validated
153
+
154
+ return divide(100, validated)
155
+ }
156
+
157
+ // Transform errors at the end
158
+ const appResult = mapError(
159
+ calculate(userInput),
160
+ e => new AppError({ source: e._tag, message: e.message })
161
+ )
162
+ ```
163
+
164
+ Real-world async composition:
165
+
166
+ ```ts
167
+ async function processOrder(orderId: string): Promise<OrderError | Receipt> {
168
+ const order = await fetchOrder(orderId)
169
+ if (isError(order)) return order
170
+
171
+ const validated = validateOrder(order)
172
+ if (isError(validated)) return validated
173
+
174
+ const payment = await processPayment(validated)
175
+ if (isError(payment)) return payment
176
+
177
+ return generateReceipt(payment)
178
+ }
179
+
180
+ // Caller gets union of all possible errors
181
+ const receipt = await processOrder('123')
182
+ if (isError(receipt)) {
183
+ matchError(receipt, {
184
+ NotFoundError: e => `Order ${e.id} not found`,
185
+ ValidationError: e => `Invalid: ${e.field}`,
186
+ PaymentError: e => `Payment failed: ${e.reason}`,
187
+ })
188
+ }
189
+ ```
190
+
128
191
  ### Extraction
129
192
 
130
193
  ```ts
@@ -201,6 +264,96 @@ This works because:
201
264
  2. Custom error classes extend `Error`
202
265
  3. After an `instanceof Error` check, TS excludes all Error subtypes
203
266
 
267
+ ## Result + Option Combined: `Error | T | null`
268
+
269
+ One of errore's best features: you can naturally combine error handling with optional values. No wrapper nesting needed!
270
+
271
+ In Rust, you'd need `Result<Option<T>, E>` or `Option<Result<T, E>>` and worry about the order. Here it's just a union:
272
+
273
+ ```ts
274
+ // Result + Option in one natural type
275
+ function findUser(id: string): NotFoundError | User | null {
276
+ if (id === 'bad') return new NotFoundError({ id })
277
+ if (id === 'missing') return null
278
+ return { id, name: 'Alice' }
279
+ }
280
+
281
+ const user = findUser('123')
282
+
283
+ // Handle error first
284
+ if (isError(user)) {
285
+ return user.message // TypeScript: user is NotFoundError
286
+ }
287
+
288
+ // Handle null/missing case - use ?. and ?? naturally!
289
+ const name = user?.name ?? 'Anonymous'
290
+
291
+ // Or check explicitly
292
+ if (user === null) {
293
+ return 'User not found'
294
+ }
295
+
296
+ // TypeScript knows: user is User
297
+ console.log(user.name)
298
+ ```
299
+
300
+ ### Works with `undefined` too
301
+
302
+ ```ts
303
+ function lookup(key: string): NetworkError | string | undefined {
304
+ if (key === 'fail') return new NetworkError({ url: '/api', message: 'Failed' })
305
+ if (key === 'missing') return undefined
306
+ return 'found-value'
307
+ }
308
+
309
+ const value = lookup('key')
310
+
311
+ if (isError(value)) return value
312
+
313
+ // ?? works naturally with undefined
314
+ const result = value ?? 'default'
315
+ ```
316
+
317
+ ### Triple union: `Error | T | null | undefined`
318
+
319
+ Even this works with full type inference:
320
+
321
+ ```ts
322
+ function query(sql: string): ValidationError | { rows: string[] } | null | undefined {
323
+ if (sql === 'invalid') return new ValidationError({ field: 'sql', message: 'Bad' })
324
+ if (sql === 'empty') return null // explicitly no data
325
+ if (sql === 'no-table') return undefined // table doesn't exist
326
+ return { rows: ['a', 'b'] }
327
+ }
328
+
329
+ const result = query('SELECT *')
330
+
331
+ if (isError(result)) {
332
+ return result.field // TypeScript: ValidationError
333
+ }
334
+
335
+ if (result == null) {
336
+ return 'no data' // handles both null and undefined
337
+ }
338
+
339
+ // TypeScript: { rows: string[] }
340
+ console.log(result.rows)
341
+ ```
342
+
343
+ ### Why this is better than Rust/Zig
344
+
345
+ | Language | Result + Option | Order matters? |
346
+ |----------|-----------------|----------------|
347
+ | Rust | `Result<Option<T>, E>` or `Option<Result<T, E>>` | Yes, must unwrap in order |
348
+ | Zig | `!?T` (error union + optional) | Yes, specific syntax |
349
+ | **errore** | `Error \| T \| null` | **No!** Check in any order |
350
+
351
+ With errore:
352
+ - Use `?.` and `??` naturally
353
+ - Check `isError()` or `=== null` in any order
354
+ - No unwrapping ceremony
355
+ - TypeScript infers everything
356
+
204
357
  ## Comparison with Result Types
205
358
 
206
359
  | Result Pattern | errore |
@@ -210,6 +363,7 @@ This works because:
210
363
  | `result.value` | direct access after guard |
211
364
  | `result.map(fn)` | `map(result, fn)` |
212
365
  | `Result<User, Error>` | `Error \| User` |
366
+ | `Result<Option<T>, E>` | `Error \| T \| null` |
213
367
 
214
368
  ## License
215
369
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "errore",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Type-safe errors as values for TypeScript. Like Go, but with full type inference.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",