netanchor 5.0.0
netanchor: ^5.0.0 copied to clipboard
A swappable, SOLID-compliant networking layer for Flutter & Dart. Adapter pattern over Dio with refresh-token, retry, cache, pagination, and DataKey unwrapping. Zero UI coupling.
Changelog #
All notable changes to netanchor are documented here.
The format follows Keep a Changelog, and this project adheres to Semantic Versioning.
4.1.0 — 2026-07-21 #
Added #
- Added
Result.mapAsync, accepting a synchronous or asynchronous success transform throughFutureOrwhile preserving failures unchanged. - Added
Result.flatMapAsyncfor composing synchronous or asynchronousResult-returning repository operations without rebuilding the failure branch. - Added
widenFailure()for safely reifyingResult<T, F>asResult<T, Failure>before a chain may introduce a different application failure subtype. It preserves the exact failure instance. - Added tests for synchronous and asynchronous callbacks, success/failure
composition, callback skipping, instance preservation, exception
propagation, and
NetworkFailure→Failurewidening before chaining an application-owned cache failure.
Documentation #
- Added a repository example that composes remote login and asynchronous session persistence without manual branching.
- Documented that transform exceptions propagate to the caller because a
generic
Result<S, F>cannot construct an arbitraryFsafely. - Documented why a covariant assignment alone does not change Dart's reified
runtime generic type before heterogeneous
flatMapAsynccomposition.
This release is additive. Existing map, flatMap, when, result variants,
and failure contracts are unchanged.
4.0.0 — 2026-07-21 #
This major release lets network and application-specific failures share one open typed contract without repository-level failure mappers.
Breaking changes #
- Renamed the failed result variant from
Failure<S, F>toFailureResult<S, F>so the clearerFailurename can represent the common failure contract. Pattern matches and direct constructor calls must use the new variant name. Result.failure(...)is unchanged and remains the recommended construction API.
Added #
- Added the open
Failureinterface with amessagecontract. Applications can implement it for cache, persistence, or business-rule failures without adding application types to netanchor. NetworkFailurenow implementsFailure, whileNetworkResult<T>remainsResult<T, NetworkFailure>.- Added covariance and repository tests proving that
Result<T, NetworkFailure>can widen toResult<T, Failure>without casts, mapping, branching, or loss of the original failure instance and metadata.
Migration #
// 3.x
case Failure(:final failure):
return Failure<User, NetworkFailure>(failure);
// 4.0
case FailureResult(:final failure):
return FailureResult<User, NetworkFailure>(failure);
// Unchanged
return Result<User, NetworkFailure>.failure(failure);
3.0.0 — 2026-07-21 #
This major release replaces ambiguous response parser callbacks with explicit, composable payload decoders.
Breaking changes #
- Replaced every request's
parser:parameter withdecoder:and theResponseParser<T>typedef withPayloadDecoder<T>. getPaginatednow acceptsitemDecoder:instead ofparser:and customPaginationParserimplementations receive aPayloadDecoder<T>.sendVoidandheadnow returnNetworkResult<void>;VoidResponseandvoidParserwere removed.
Added #
Decoders.jsonObject,jsonList,list,nullable,raw,empty, andcustomcover conventional JSON models, primitives, nullable responses, empty bodies, and application-specific wire formats.PayloadDecodeExceptionreports the expected shape, actual runtime type, and JSONPath-like failure location without storing or printing the raw payload.- List decoders preserve order, never skip invalid items, and report nested
item paths such as
$[2].
Changed #
- Successful live, cached, retried, and authentication-replayed responses now
share one decoding pipeline: optional
DataKeyunwrapping happens first, then the selected decoder runs exactly once for the returned response. - Decode errors remain inside
NetworkResultasParseFailure; callback exceptions are retained as diagnostic causes without leaking payload data.
2.0.1 — 2026-07-20 #
Fixed #
- Mixed multipart maps now detect and convert
List<UploadFile>values to DioFormData, matching the documented multi-file upload example. NetAnchorListener.onErrorandonNoInternetnow represent terminal request outcomes. Temporary failures followed by a successful retry no longer trigger misleading UI events.- A successful
networkFirstcache fallback no longer emits the offline failure that caused the fallback. Background stale-while-revalidate failures also stay internal while the caller receives cached data.
2.0.0 — 2026-07-20 #
This major release makes retry decisions request-aware, hardens the execution pipeline, and turns secure logging into the default.
Breaking changes #
- Request-aware retry API —
RetryPolicy.shouldRetrynow receives aRetryContextcontaining the request, failure, policy attempt, andRetryCause. Custom policies must update their method signature. - Explicit retry modes — requests use
RetryMode.automatic,.always, or.never. Automatic mode permits onlyGETandHEAD; mutations require.alwaysand should carry a server-supported idempotency key. - Canonical immutable headers — request and response header names are stored lower-case, and request maps / response header collections are unmodifiable snapshots.
- Opaque cache keys — automatic cache keys are now canonical SHA-256
fingerprints and include authentication identity. Existing 1.x automatic
entries are intentionally not reused after upgrade. Explicit
CacheConfig.keyvalues retain their exact-key behavior.
Added #
- Safe per-request retry control — automatic retries now default to
GET/HEADonly. Mutating requests requireRetryMode.always. - Complete retry context — policies can inspect request headers, metadata, method, failure origin, and an auth-independent attempt counter.
- Refresh token extractor —
AuthConfig.refreshTokenExtractorcan parse tokens from any successful refresh response shape while the existing root JSON keys remain the default. - Cache scope —
CacheConfig.scopepartitions tenant-, company-, and locale-specific responses while automatic authentication isolation remains active. - Per-request metadata — every facade helper now exposes the existing
immutable
HttpRequest.metadatachannel to interceptors and retry policies. - Pure Dart runtime — removed the unused Flutter SDK runtime dependency; Flutter apps remain fully supported by the same package.
Security #
- Traffic-log redaction —
LoggingInterceptornow recursively masks common credential fields in URIs, query parameters, headers, request bodies, and response bodies by default.LogRedactorsupports application-specific body formats and keys. - Failure logs redact URI credentials and omit server-controlled messages; refresh errors and cache diagnostics no longer print response bodies or raw cache keys.
Fixed #
- Connectivity failures now participate in the retry policy.
- Cancellation interrupts retry backoff immediately.
- Interceptor and connectivity-checker exceptions remain inside the typed
NetworkResultcontract. - Auth refresh retries no longer consume failure retry attempts.
- Post-refresh callbacks cannot invalidate already-persisted tokens, and terminal 401 responses still reach response interceptors.
- Cache backend errors degrade gracefully instead of hiding successful network responses.
CachePolicy.noCachenow performs neither reads nor writes, whilenetworkOnlyand successfulnetworkFirstrequests skip unnecessary reads.- Automatic cache entries are isolated across authenticated users and use canonical request encoding independent of map insertion order. Global default headers also participate in automatic key partitioning.
- Retry-policy exceptions remain inside the typed
NetworkResultcontract. - Reusing a
NetAnchorBuilderno longer duplicates logging interceptors or retains an automatically-managed interceptor from an earlier build. - Custom refresh extractors are validated before tokens reach storage.
DioAdapterno longer mutates a caller-owned Dio client's globalvalidateStatus, honorsUploadFile.contentType, and no longer importsdart:io, restoring Web compatibility.
1.3.0 — 2026-07-20 #
Adds per-request auth control and makes failure messages consistent. This release is backward compatible: existing requests keep their current auth behavior unless they explicitly opt out.
Added #
- Per-request
requiresAuthflag — every request helper now acceptsrequiresAuth: falsefor public endpoints. These requests skip automatic access-token attachment and do not enter the refresh / unauthorized flow on a401. The default remainstrue, preserving existing behavior.
Fixed #
- Consistent failure messages — terminal
401responses now preserve the server'smessageorerrorvalue inUnauthorizedFailure.message, just like other HTTP failures. Plain-text response bodies are also preserved.localizedKeyremains the stable, type-based localization contract. - Latest Dio compatibility — exception mapping remains exhaustive when
Dio adds a new
DioExceptionType; transformation timeouts map toHttpAdapterErrorKind.timeoutwithout forcing consumers onto a newer Dio minimum version.
1.2.0 — 2026-06-24 #
Adds a sixth cache strategy. Additive and backward compatible — existing policies and call sites are unchanged.
Added #
-
CachePolicy.networkFirst— the "online → fresh, offline → last known" strategy. Always tries the network first and returns the fresh response (writing it to the cache) when the server is reachable. Only a connectivity or timeout failure makes it fall back to the most recent cached entry — even if that entry is expired — so the caller still gets data offline. Any other failure (4xx/5xx, parse) is returned untouched and never masked by stale cache.await netAnchor.get<List<Ad>>( '/ads', parser: Ad.fromList, cache: const CacheConfig(policy: CachePolicy.networkFirst, ttl: Duration(days: 7)), );Previously this behaviour had to be hand-composed at the call site (
networkOnly, then a secondcacheFirstread on a no-internet failure). It is now a first-class policy.
Notes #
networkFirstnever proactively evicts an expired entry on the pre-read pass — the stale copy is deliberately retained as the offline fallback.- README caching section updated (six strategies; new row + example).
1.1.3 — Branding #
Docs / packaging patch. No code or behavior changed in lib/.
- README header now leads with the brand. An animated logo at the
top, followed by a row of pub-version / pub-points / MIT-license
badges in the brand palette (
#0B2D4Dbackground,#19B5FEforeground). Served from the GitHub raw URL so pub.dev renders them correctly — pub.dev does not resolve relative paths or render SVG in README. assets/added at the repo root with the four files the README references:netanchor-animated.gif,netanchor-logo-horizontal-clean.png,netanchor-mark.svg,netanchor-mark-mono.svg. Repo-only — they're served from GitHub, not bundled into consumer apps..pubignorelistsassets/,netanchor-logo/, andnetanchor-logo.zip. Brand source files stay in the GitHub repo but are kept out of the tarball uploaded to pub.dev.- Installation snippet bumped from
^1.1.2to^1.1.3.
1.1.2 — Republish #
Version-only bump; no source changes from 1.1.1. Published manually to refresh the pub.dev rendering after the README author section landed.
1.1.1 — README fixes #
Docs-only patch. Two README cleanups:
- Installation snippet bumped from
netanchor: ^0.4.0(a stale reference still showing the very first pub.dev release) to^1.1.0, so the example matches the actual minimum version that exposesPrettyNetAnchorLogger/LogSeverity/log(). - Contributing section closed for now. The project isn't taking external pull requests yet — the README now says so explicitly instead of inviting them. Bug reports as issues are still welcome.
No code or behavior changed.
1.1.0 — Pretty logger #
Backwards-compatible release. New traffic-log renderer that boxes every
request / response / failure with the visual style of package:logger,
plus a richer logger contract so loggers can opt in without breaking the
existing info / warn / error API.
Added #
PrettyNetAnchorLogger— bordered, color-coded box renderer matching the visual style ofpackage:logger'sPrettyPrinter. Each entry is wrapped in a┌─ TITLE ────┐ … └────┘box with ANSI colors, level emoji (💡 / ⚠️ / ⛔), optional timestamp row, indented JSON forMap/Iterablepayloads, and a filtered caller-stack section (at Symbol (package:foo/bar.dart:42:5)) whose paths are clickable in most IDE consoles.final netAnchor = NetAnchor.builder() .baseUrl('https://api.example.com') .logger(const PrettyNetAnchorLogger(printTime: true)) .enableTrafficLog() .build();LogSeverityenum (info/warn/error) — severity tag for the newNetAnchorLogger.logmethod. NamedLogSeverity(notLogLevel) to avoid colliding with theLogLeveltypes that consumer apps commonly define for their own UI log panels.NetAnchorLogger.log({level, title, message, error, stackTrace})— titled, leveled log entry that pretty loggers render on the box border. The abstract base provides a default implementation that falls back toinfo/warn/errorwith the title inlined into the message, so existing custom loggers keep working unchanged.LoggingInterceptor.pretty(...)factory — one-call constructor that wires aPrettyNetAnchorLoggerwith sensible defaults (colors: true,printTime: false,lineLength: 120).- Caller-stack section in pretty boxes — the
LoggingInterceptornow capturesStackTrace.currentat each hook and passes it tologger.log. Framework frames (package:netanchor/,package:dio/,package:flutter/,dart:async, …) are filtered out by default; the remaining user frames (capped atcallerMethodCount, default 3) are rendered inside the box. Customize via theexcludeFromCallerTraceknob.
Changed #
LoggingInterceptor.onResponseroutes by status code. 2xx still logs atinfo(cyan), but 4xx now logs atwarn(yellow) and 5xx aterror(red), so HTTP failures stand out visually instead of disappearing in the success stream. Pure behavior change for users who rely on log severity — output is otherwise unchanged.- Request / response / failure bodies are JSON-pretty-printed when
they're a
MaporIterable, with one key per line. Plain strings and other types still go throughtoString.
1.0.0 — Stable API #
First stable release. The public API is now considered settled; future breaking changes will follow semver with a deprecation path.
Added #
Result.success/Result.failureconst factory constructors — build a result without naming the variant class. Pairs nicely with theNetworkResult<T>alias:NetworkResult<User>.success(user); NetworkResult<User>.failure(const UnauthorizedFailure());Success<T, F>(...)/Failure<T, F>(...)still work — the factories are purely additive.NetAnchorBuilder.enableTrafficLog({logHeaders, logBody})— a one-call switch that wires aLoggingInterceptorusing the builder's configured logger. Previously you had to knowLoggingInterceptorexisted, construct it yourself, and hand it the logger viaaddInterceptor(...). The builder dartdoc now also spells out that.logger()is the sink and.enableTrafficLog()is the switch.
Changed #
LoggingInterceptor.onFailureno longer logsfailure.rawError. For an HTTP failurerawErroris the response body, whichonResponsealready printed on the←line — so the old behaviour logged the body twice.onFailurenow logs only the typed failure summary (status code + message), which is the new information that line carries. Transport-level failures (timeout, no-internet, parse) were never double-logged and are unaffected.
0.4.0 — Initial public release #
First public release on pub.dev. A swappable, SOLID-compliant networking layer for Flutter & Dart with zero UI coupling.
Added #
- Core —
NetAnchor,NetAnchorBuilder, andNetAnchorConfigfor configuring a network client with a fluent builder API. - Adapter pattern — pluggable
HttpAdapterabstraction with a built-inDioAdapter(overdio ^5.7.0) and aMockAdapterfor tests. - Result types —
Result<S, F>sealed type plusNetworkFailureandnetwork_failure_xextensions so callers branch on typed failures instead of catching exceptions. - Interceptors —
Interceptorinterface with built-inAuthInterceptor,HeaderInterceptor, andLoggingInterceptor. - Auth & refresh tokens —
TokenRefresher/HttpTokenRefresher,TokenStorage, andAuthConfigwith an internal single-flight refresh coordinator that queues concurrent 401s and replays them once. - Retry — configurable
RetryPolicywith exponential backoff and per-request opt-in/opt-out. - Cache —
CachePolicyand pluggableCacheStore(in-memory by default). - Pagination —
Paginated<T>envelope andPaginationParserfor cursor- and page-based APIs. - Response parsing —
ResponseParser,DataKeyunwrapping for APIs that wrap payloads underdata/result/custom keys, andVoidResponsefor 204-style endpoints. - File upload —
UploadFilewith progress callbacks. - Binary downloads — first-class support via
HttpResponseType. - Cancellation —
CancellationTokenfor cooperative request cancellation. - Listeners —
NetAnchorListener+NetworkEventfor decoupling cross-cutting UI concerns (toasts, navigation, analytics) from repositories. - Connectivity —
ConnectivityCheckerhook for offline-aware flows. - Logger —
NetAnchorLoggerabstraction. - Example app — runnable Flutter example under
example/showingNetAnchor.builder(), listeners, and an auth module wired through a Bloc/repository structure. - Tests — coverage for pagination, retry, cache, auth refresh,
cancellation,
DataKey,MockAdapter, and the core builder.
5.0.0 - 2026-07-22 #
This major release simplifies the normal request → repository → UI path and hardens caching without moving application presentation policy into core.
Breaking #
Result<S, F>is nowResult<T>and always carries the openFailurecontract.NetworkResult,widenFailure, and the second variant generic were removed.- Result composition is explicit:
mapSuccess,mapSuccessAsync,thenResult, andthenResultAsync. The same concise helpers are available directly onFuture<Result<T>>. - Normal request parsing uses
fromJson:;getListandgetJsoncover common collection/raw-object cases. NetAnchorListenerbecame diagnostics-onlyNetworkObserver;silentwas removed so transport callbacks cannot accidentally drive UI.Failureimplementations now expose stablecodeplus safemessage.UnknownFailurewas renamed toUnknownNetworkFailure; arbitrary HTTP statuses useHttpFailure.- Cache setup and policy were redesigned around
.cache(...), stable identity,CacheOptions, andCacheStrategy. MockAdaptermoved topackage:netanchor/netanchor_testing.dart.
Added #
ResponseFailureMapperfor application-specific non-2xx failures without a repository-wide network mapper.- Async model factories through
FutureOr. - Opt-in private/public response caching with namespace, schema, scope, explicitly varied headers, tag invalidation, and logout partition clearing.
- Cache-first, network-first, stale-while-revalidate, and refresh strategies.
- Cache race protection: newest-write wins, invalidation/logout generations, no-store ordering, corrupt-entry recovery, and single-flight background refresh.
TokenRefreshExceptionfor expected refresh protocol failures.- A complete app-owned
UnexpectedErrorDetailsrunner/presenter example.
Security and behavior #
- Private cache is bypassed when stable identity is unavailable; access-token rotation never changes cache identity.
Cache-Control: no-storeremoves current cached data and never allows an old response to delete a newer value.- Mutations remain non-retryable unless explicitly opted in with
RetryMode.always. - Sensitive traffic redaction remains enabled independently of build mode.