resilify 1.2.0
resilify: ^1.2.0 copied to clipboard
Unified Result-based API handling for Dart & Flutter — works with http, Dio, Retrofit, Chopper, and WebSocket. No exceptions. Just results.
Changelog #
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.2.0 #
Added #
-
Bulkhead— concurrency limiter forResult-returning operations. Caps the number of simultaneously in-flight calls atmaxConcurrentand queues the overflow up tomaxQueueSize(FIFO). When both the in-flight slots and the queue are full, additional callers fast-fail withError(Failure.bulkheadRejected())instead of being kept waiting. Composes naturally withCircuitBreaker,RetryHelper, andResultCache.final bulkhead = Bulkhead(maxConcurrent: 8, maxQueueSize: 16); final result = await bulkhead.execute(() => api.fetchUser(id)); -
FailureKind.bulkheadRejected+FailureType.bulkheadRejected+Failure.bulkheadRejected()— categorical tag and named constructor for bulkhead overflow. Treated as transient byFailure.isRetryable, so retry helpers will back off and try again rather than surfacing it as a permanent failure. -
Result.partition()— splits anIterable<Result<T>>into two parallel unmodifiable lists(List<T> successes, List<Failure> failures), preserving order. Non-short-circuiting counterpart toResult.collect()— useful for batch operations where you want to surface partial progress alongside the failures.final (saved, errors) = Result.partition( await Future.wait(orders.map(api.save)), ); -
ResultCache.putIfAbsent()— synchronous, atomic insert-only-if- missing. MirrorsMap.putIfAbsentsemantics: also cachesErrorresults (unlikegetOrFetch, which only memoizes successes), making it useful for memoizing pure computations or pre-resolvedResults. -
FutureResultX.onFinallyAsync()— async finally-style hook that runs anasynccallback regardless of variant (and even if the underlying future throws), then returns the original result. Useful for releasing tokens, closing transient resources, or dismissing UI state without coupling to success/error handling.
1.1.1 #
Added #
-
ResultX.recover()/ResultX.recoverWith()(sync) — synchronous counterparts to the existingFutureResultX.recover/recoverWith.recoversubstitutes a fallback success value computed from the [Failure];recoverWithdelegates to a callback that returns its own [Result] (which may itself be an [Error]).final user = cachedResult.recover((f) => User.guest()); -
ResultX.ensure()— post-success validation that turns a [Success] into an [Error] when a predicate fails, leaving an existing [Error] untouched. Useful when an HTTP 200 envelope still encodes a logical failure (empty payload, server-side flag, etc.).final valid = orderResult.ensure( (o) => o.items.isNotEmpty, (o) => const Failure.badResponse(message: 'Empty order'), ); -
ResultCache.keys— unmodifiable snapshot of the cache key set for diagnostics and ad-hoc inspection. -
ResultCache.invalidateWhere()— bulk invalidation by predicate, returning the number of entries removed. Enables tag-style eviction when keys encode resource type or user scope:cache.invalidateWhere((k) => k.startsWith('user:'));
1.1.0 #
Added #
-
Circuit breaker (
CircuitBreaker) — a closed/open/half-open state machine forResult-returning operations. AfterfailureThresholdconsecutive trip-eligible failures the breaker opens and fast-fails calls forresetTimeout, then admits a single probe. ConfigurableshouldTrippredicate (defaults tofailure.isRetryable) andonStateChangeobserver. Pluggable clock for tests. -
FailureKindenum +Failure.kindfield — categorical tag that discriminates failures independently ofcode/message. Enables accurate retry decisions for code-less failures (anetworkfailure is retryable; aparsingfailure is not). NewFailureKind.circuitOpenmarks fast-fail responses fromCircuitBreaker.execute. -
FailureTypeenum — everyFailurenow carries atypefield set automatically by its named constructor. Switch onfailure.typeinstead of inspectingfailure.codeto route failures to the right UI state or analytics bucket without fragile integer comparisons:switch (failure.type) { FailureType.network => showOfflineBanner(), FailureType.unauthorized => pushLoginScreen(), _ => showGenericError(failure.message), } -
Failure.validation()— new named constructor for HTTP 422 (Unprocessable Entity) withtype = FailureType.validation. Also handled byFailure.fromStatusCode(422). -
RetryHelper.withTimeout— standalone helper that wraps a single attempt in a timeout, returningError(Failure.timeout())instead of throwing. -
Future<T>.asResult()extension — terser bridge from a throwingFuture<T>into aFuture<Result<T>>(vs the longerResult.tryRunAsync(() => future)). NamedasResultrather thantoResultto avoid colliding with the retrofit / chopper integrations' transport-specific.toResult()methods. -
Result.collectAsync()— async counterpart toResult.collect(). Awaits anIterable<Future<Result<T>>>in order and short-circuits on the first error, returning aResult<List<T>>:final r = await Result.collectAsync([ api.fetchUser('u1'), api.fetchUser('u2'), ]); -
Result.asyncMap()/Result.asyncFlatMap()— async transform methods on plainResult<T>(not a future). ComplementFutureResultX.mapAsync/flatMapAsyncfor cases where you already hold a resolved result and want to start an async pipeline:final result = await cachedResult.asyncMap((user) => enrichUser(user)); -
FutureResultX.onSuccessAsync()/onErrorAsync()— async side-effect hooks onFuture<Result<T>>. Mirror the synchronousonSuccess/onErrorbut acceptasynccallbacks, useful for async logging, cache writes, or analytics calls:await api.fetchUser(id) .onSuccessAsync((u) => cache.put(id, u)) .onErrorAsync((f) => analytics.logError(f)); -
ResultCache<K, V>— in-memory cache forResultvalues with optional TTL.getOrFetchserves cached successes instantly and fetches on miss; errors are never cached so callers can retry immediately:final cache = ResultCache<String, User>(ttl: const Duration(minutes: 5)); final user = await cache.getOrFetch('u1', () => api.fetchUser('u1')); -
ResultDeduplicator<K, V>— collapses concurrent calls for the same key into a single in-flight future. All concurrent callers receive the same result; once the future completes the key is evicted so the next call triggers a fresh fetch:final dedup = ResultDeduplicator<String, User>(); // Three concurrent callers → one network request. final results = await Future.wait([ dedup.run('u1', () => api.fetchUser('u1')), dedup.run('u1', () => api.fetchUser('u1')), dedup.run('u1', () => api.fetchUser('u1')), ]); -
LogCallback— thetypedef LogCallback = void Function(String line)is now exported from the coreresilify.dartbarrel so you can type callback parameters without importing the Dio barrel.
Changed #
Failure.isRetryableis now decided primarily byFailureKind, soFailure.network()and similar code-less transient failures are now reported as retryable. Failures built with the unstructured generic constructor still fall back tocode-based heuristics. Existingcode-based retry decisions are unchanged.Failure.toString()now includes bothkind:andtype:for easier diagnostics. The==/hashCodecontract is unchanged (stilltype+code+message+cause+retryAfter) so existing equality-based assertions keep working.
1.0.6 #
Fixed #
- Resolved pub.dev analysis failure caused by
dart pub outdated --jsoncrashing during dependency advisory parsing. - Stabilized dependency resolution to ensure compatibility with pub.dev's pana analysis pipeline.
- Fixed documentation generation issues that prevented dartdoc from running successfully.
- Corrected
documentationURL inpubspec.yamlto point to a valid and accessible resource.
Changed #
- Adjusted dependency constraints (notably
dio) to avoid triggering advisory parsing issues on pub.dev. - Simplified dev dependency graph to improve analysis reliability and prevent toolchain conflicts.
- Improved overall package compatibility with pub.dev scoring system and analysis environment.
Documentation #
- Added missing dartdoc comments across public APIs to improve documentation coverage and pub score.
1.0.5 #
Added #
-
Result.tap()— execute side effects (logging, debugging) on successful results without transforming the value. Great for inspecting data in the middle of a result chain:result.tap(print).tap(logToAnalytics).map(transformData); -
Enhanced code quality standards with stricter linting rules (upgraded
lintsto ^6.1.0) — the package now adheres to the latest Dart conventions and best practices for code style and correctness.
Fixed #
- Trailing-comma lint warnings in
lib/src/integrations/chopper_result.dartandlib/src/result.dart— package now passesdart analyze --fatal-warningswith zero issues. - Removed deprecated transitive dependencies (
build_resolvers,build_runner_core) that were flagged by pub.dev.
Changed #
- Updated dev dependencies to latest versions:
lints: ^4.0.0 → ^6.1.0 for stricter code quality checksretrofit_generator: ^8.0.0 → ^10.2.5build_runner: ^2.4.0 → ^2.15.0chopper_generator: ^8.0.0 → ^8.6.1json_serializable: ^6.7.0 → ^6.13.2test: ^1.25.0 → ^1.31.1- All transitive dependencies updated to latest compatible versions.
1.0.4 #
Added #
-
Failure.retryAfter— aDuration?field onFailurethat surfaces the server's back-off hint from aRetry-AfterHTTP header. Populated automatically for 429 / 5xx responses by bothHttpResultHandlerand the Dio integration'smapDioException. Pair withRetryHelper.retry'sdelayparameter to honor the server's wait time exactly:result.errorOrNull?.retryAfter // Duration(seconds: 30) when present -
Failure.parseRetryAfter(String?)— static helper that converts the seconds form of an HTTPRetry-Afterheader into aDuration. Returnsnullfor null / blank / non-numeric input; clamps negatives to zero. The HTTP-date form is intentionally left to callers so the core library stays free ofdart:io. -
platforms:declaration inpubspec.yaml(Android, iOS, Linux, macOS, Windows) so pub.dev surfaces verified platform support on the package page. -
documentation:link inpubspec.yamlpointing at the published dartdoc. -
Smoke tests for
HttpResultHandlercovering JSON GET/POST round-trips, query parameter merging, default-header propagation, 404 / 429 / 5xx mapping,Retry-Afterextraction, and parsing failures — closing the integration test gap flagged in the 1.0.3 audit.
Fixed #
- Trailing-comma lint warnings in
dio_result.dartandlogger.dartso the package now ships with a cleandart analyze.
1.0.3 #
Added #
Result.fromNullable<T>(T?, {Failure Function()? onNull})— bridge nullable APIs (cache lookups,firstWhereOrNull, etc.) into aResult<T>in one call.Result.zip2andResult.zip3— combine multiple results into a record (Result<(A, B)>/Result<(A, B, C)>), short-circuiting on the first failure left-to-right. Handy for parallel fetches withFuture.wait.Result.collect<T>(Iterable<Result<T>>)— fold a list of results into a singleResult<List<T>>, returning the first failure encountered.recoverWithextension onFuture<Result<T>>— likerecover, but the fallback callback may itself return aResult(so a fallback network call that also fails is surfaced as the final error).mapErrorAsyncextension onFuture<Result<T>>— async counterpart to the synchronousResult.mapError.
Changed #
RetryHelper.retrynow acceptsattemptTimeout. When set, each attempt is wrapped inFuture.timeout; an exceeded timeout is converted into anError(Failure.timeout())and goes through the normalretryIf/maxAttemptsmachinery.
1.0.2 #
Added #
- New
Failurenamed constructors:Failure.forbidden(403),Failure.conflict(409), andFailure.rateLimit(429). Failure.fromStatusCode(int)— picks the most specific named constructor for a given HTTP status, falling back tobadResponsefor other 4xx andserverErrorfor other 5xx.Failure.is4xx,Failure.is5xx, andFailure.isRetryablegetters — drop-in predicates forRetryHelper.retryIf.onCompleteextension onResult<T>— finally-style hook that fires for bothSuccessandError.flatten()extension onResult<Result<T>>— collapses one layer of nesting thatflatMapchains often produce.
Changed #
RetryHelper.retrynow acceptsmaxDelayto cap the wait between attempts andjitter(with an optionalRandom) to spread retries and prevent thundering-herd retry storms. Both default to no-op behavior, so existing call sites are unaffected.
1.0.1 #
Added #
Result.tryRunandResult.tryRunAsync— bridge throwing code into aResult<T>without writing try/catch at call sites. Both accept an optionalonErrorto translate the caught object into a domain-specificFailure.mapErroronResult<T>— transform the wrappedFailurewithout touching the success path. Useful for translating low-level transport failures into domain failures.errorOrThrowextension — symmetric counterpart togetOrThrow, returning the wrappedFailureor throwing aStateErroronSuccess.
1.0.0 — Initial release #
Added #
-
Core
- Sealed
Result<T>withSuccess<T>andError<T>variants (Dart 3). - Structured
Failurevalue type with named constructors:network,timeout,badResponse,parsing,unauthorized,notFound,serverError,cancelled,unknown. - Synchronous extensions:
isSuccess,isError,dataOrNull,errorOrNull,getOrElse,getOrThrow,onSuccess,onError. when,fold,map,flatMaponResult<T>.- Async helpers on
Future<Result<T>>:mapAsync,flatMapAsync,recover. - Stream helpers on
Stream<Result<T>>:mapStream,whereSuccess,whereError,dataStream,listenResult. - List helpers on
Result<List<T>>:mapList,filter,whereResult,firstOrError. RetryHelper.retrywith exponential backoff, predicate-based retry, and per-attempt observer.
- Sealed
-
Integrations (each opt-in via its own barrel file)
resilify_http.dart—HttpResultHandlerforpackage:http.resilify_dio.dart—DioResultHandler(incl.upload/downloadwith progress) andResultLoggerInterceptor.resilify_retrofit.dart—.toResult()on Retrofit-generated futures.resilify_chopper.dart—.toResult()on ChopperResponse<T>futures with pluggable failure mappers.resilify_websocket.dart—WebSocketResultHandler<T>with auto-reconnect and exponential backoff.