errore 0.11.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
@@ -365,6 +365,11 @@ const response = await errore.tryAsync({
365
365
  })
366
366
  ```
367
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
+
368
373
  ### Transformations
369
374
 
370
375
  **Transform and chain** operations:
@@ -385,6 +390,54 @@ const posts = errore.andThen(user, u => fetchPosts(u.id))
385
390
  const logged = errore.tap(user, u => console.log('Got user:', u.name))
386
391
  ```
387
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
+
388
441
  ### Extraction
389
442
 
390
443
  **Extract values** or throw, **split arrays** by success/error:
@@ -679,6 +732,8 @@ Effect is powerful if you need its full feature set. But if you just want type-s
679
732
  | Learning curve | Steep (new paradigm) | Minimal (just `instanceof`) |
680
733
  | Codebase impact | Pervasive (everything becomes an Effect) | Surgical (adopt incrementally) |
681
734
  | Bundle size | ~50KB+ | **~0 bytes** |
735
+ | Resource cleanup | `Scope` + `addFinalizer` + `acquireRelease` | `using` + `DisposableStack.defer()` |
736
+ | Cancellation | Fiber interruption model | Native `AbortController` |
682
737
  | Use case | Full FP framework | Just error handling |
683
738
 
684
739
  **Use Effect** when you want dependency injection, structured concurrency, and the full functional programming experience.