errore 0.10.0 → 0.12.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.md CHANGED
@@ -225,6 +225,8 @@ if (result instanceof Error) {
225
225
  The error definitions:
226
226
 
227
227
  ```ts
228
+ import * as errore from 'errore'
229
+
228
230
  class NotFoundError extends errore.createTaggedError({
229
231
  name: 'NotFoundError',
230
232
  message: 'User $id not found'
@@ -299,6 +301,8 @@ Returns `undefined` if no matching ancestor is found. Safe against circular `.ca
299
301
  Use `extends` to inherit from a custom base class. The error will pass `instanceof` for both the base class and the specific error class:
300
302
 
301
303
  ```ts
304
+ import * as errore from 'errore'
305
+
302
306
  class AppError extends Error {
303
307
  statusCode = 500
304
308
  toResponse() { return { error: this.message, code: this.statusCode } }
@@ -361,6 +365,11 @@ const response = await errore.tryAsync({
361
365
  })
362
366
  ```
363
367
 
368
+ > **Best practices for `try` / `tryAsync`:**
369
+ > - **Use as low as possible in the call stack** — only at boundaries with uncontrolled dependencies (third-party libs, `JSON.parse`, `fetch`, file I/O). Your own functions should return errors as values, never throw.
370
+ > - **Keep the callback minimal** — wrap only the single throwing call, not your business logic. The `try` callback should be a one-liner.
371
+ > - **Always prefer `errore.try` over `errore.tryFn`** — they are the same function, but `try` is the canonical name.
372
+
364
373
  ### Transformations
365
374
 
366
375
  **Transform and chain** operations:
@@ -381,6 +390,54 @@ const posts = errore.andThen(user, u => fetchPosts(u.id))
381
390
  const logged = errore.tap(user, u => console.log('Got user:', u.name))
382
391
  ```
383
392
 
393
+ ### Resource Cleanup (defer)
394
+
395
+ errore ships `DisposableStack` and `AsyncDisposableStack` polyfills for Go-like `defer` cleanup. Works in every runtime — no native `DisposableStack` support needed:
396
+
397
+ ```ts
398
+ import * as errore from 'errore'
399
+
400
+ async function processRequest(id: string): Promise<DbError | Result> {
401
+ // await using = cleanup runs automatically when scope exits
402
+ await using cleanup = new errore.AsyncDisposableStack()
403
+
404
+ const db = await connectDb()
405
+ cleanup.defer(() => db.close())
406
+
407
+ const cache = await openCache()
408
+ cleanup.defer(() => cache.flush())
409
+
410
+ // ... use db and cache ...
411
+ return result
412
+ // cleanup runs in LIFO order: cache.flush() → db.close()
413
+ }
414
+ ```
415
+
416
+ Resources are released in **reverse order** (last deferred = first cleaned up), just like Go's `defer`. Cleanup runs on normal return, early error return, or thrown exception.
417
+
418
+ ```ts
419
+ // Sync version with using
420
+ function readConfig(path: string): ParseError | Config {
421
+ using cleanup = new errore.DisposableStack()
422
+
423
+ const file = openFileSync(path)
424
+ cleanup.defer(() => file.closeSync())
425
+
426
+ const lock = acquireLock(path)
427
+ cleanup.defer(() => lock.release())
428
+
429
+ return parseConfig(file.readSync())
430
+ }
431
+ ```
432
+
433
+ You can also register existing `Disposable` objects directly:
434
+
435
+ ```ts
436
+ await using cleanup = new errore.AsyncDisposableStack()
437
+ cleanup.use(dbConnection) // calls dbConnection[Symbol.dispose]() on exit
438
+ cleanup.adopt(handle, (h) => h.close()) // custom cleanup for non-disposable values
439
+ ```
440
+
384
441
  ### Extraction
385
442
 
386
443
  **Extract values** or throw, **split arrays** by success/error:
@@ -675,6 +732,8 @@ Effect is powerful if you need its full feature set. But if you just want type-s
675
732
  | Learning curve | Steep (new paradigm) | Minimal (just `instanceof`) |
676
733
  | Codebase impact | Pervasive (everything becomes an Effect) | Surgical (adopt incrementally) |
677
734
  | Bundle size | ~50KB+ | **~0 bytes** |
735
+ | Resource cleanup | `Scope` + `addFinalizer` + `acquireRelease` | `using` + `DisposableStack.defer()` |
736
+ | Cancellation | Fiber interruption model | Native `AbortController` |
678
737
  | Use case | Full FP framework | Just error handling |
679
738
 
680
739
  **Use Effect** when you want dependency injection, structured concurrency, and the full functional programming experience.