all_box 0.7.0
all_box: ^0.7.0 copied to clipboard
Synchronous, lightweight key-value storage with crash-safe writes (write-ahead + atomic rename). Pure Dart, no Flutter dependency, no reactive layer.
0.7.0 #
Reliability and release-infrastructure hardening, with a deliberately small public API surface.
- New lifecycle APIs:
AllBox.close()flushes pending data, releases the storage backend and unregisters the container instance;AllBox.destroy()closes the box and removes the persisted data for logout/reset scenarios. - New persistence-error reporting:
AllBox.init(..., onPersistenceError: ...)reports async persistence failures from debouncedwrite()calls, preserving the existing synchronous write ergonomics while making dropped disk/Web writes observable by logging/telemetry code. - New optional container-name validation:
validateContainerName: truerejects unsafe/non-portable container names. It remains opt-in to avoid breaking existing users that already persist names such asuser/cacheorcache:name. - Improved corruption diagnostics: when both
<container>.dband<container>.bakexist but are unreadable/corrupted, debug builds now log a diagnostic that names both files and explains that the in-memory container started empty.init()still does not throw on corrupted data. - Inspector snapshots are safer:
AllBoxInspectornow deep-freezes nestedMap/Listvalues in snapshots, so later mutations of stored values cannot mutate an already-created snapshot. - Serialization regression coverage: IO-backed non-JSON-encodable values
are covered for debounced
write(),writeAndSave()andwriteAndFlush(). NostrictSerializationpublic API was added. - CI added: GitHub Actions now runs formatting, analysis and VM tests
across Linux/Windows/macOS on Dart
stableand the package's minimum SDK (3.3.0, viapub downgrade), plus Chrome Web storage tests and adart2wasmcompile smoke test. - Web benchmark tooling: added a VM/fake-storage benchmark
(
tool/web_storage_benchmark.dart) and an optional real-Chromewindow.localStoragebenchmark (test/web/all_box_web_storage_browser_benchmark_test.dart). - Documentation: README and architecture docs now document lifecycle, persistence-error handling, safe container names, corruption diagnostics, Web/localStorage limitations, multi-tab limitations and benchmark commands.
- Compatibility note: the built-in Web backend is still
window.localStorage; IndexedDB, Workers/Service Workers and safe multi-tab writes remain future backend work, not promises of this release. - Breaking for custom storage implementers only:
AllBoxStoragenow includesclose(). Apps using the built-in IO/Web/memory storage do not need any migration, but customAllBoxStorageimplementations must add aFuture<void> close()method. A no-op implementation is fine when there are no resources to release.
0.6.0 #
Adds debug-only push notifications for external tooling, as a follow-up
to AllBoxInspector (0.5.0) — closes the "Fase 2" item from
all_box_devtool's roadmap.
- New:
write(),writeAndFlush(),writeAndSave(),remove()(when a key actually existed) anderase()now also post a VM Service extension event (AllBoxInspector.mutationEventKind,'all_box:mutation') right after mutating memory, in debug/profile builds only. Payload is intentionally minimal —{container, op, key?}— no value and no full snapshot; listeners are expected to re-fetch viaAllBoxInspector.snapshotOfAsJson/snapshotAsJson. - Not a Dart API change. There is still no callback list,
Stream, or anything Dart code (including this package's own) can subscribe to.developer.postEventonly ever reaches tooling attached over the VM Service protocol and is a no-op with nothing attached — the0.4.0promise ("write()/remove()/erase() never notify anything") was always about Dart-visible notification, which this does not add. SeeAllBox._debugPostMutationEvent's doc comment for the full reasoning. - No-op in release builds, same
allBoxDebugModeguard as everything else in this family of changes. - Enables a DevTools extension (or any other VM-Service-attached tool)
to react to writes in near real time instead of polling
AllBoxInspector.snapshot()on a timer — polling remains a fine and fully supported fallback; this is additive, not a replacement.
0.5.0 #
Adds AllBoxInspector: a read-only, debug/profile-only introspection
surface, built to support external tooling (e.g. a DevTools extension)
without reintroducing the listener/reactive API removed in 0.4.0.
- New:
AllBoxInspector.snapshot()/AllBoxInspector.snapshotOf(container)returnAllBoxContainerSnapshots: container name,isInitialized, storagebackend(AllBoxBackendKind:io/web/memory/unsupported/custom),pendingFlush(whether a debounced write is still waiting), a read-only copy ofentries, and anapproximateSizeBytesestimate. - New:
AllBoxContainerSnapshot.toJson(), and its JSON-string counterpartsAllBoxInspector.snapshotAsJson()/snapshotOfAsJson(container), meant for callers on the far side of a VM Serviceevalboundary (e.g. a DevTools extension usingEvalOnDartLibrary) — evaluating a single expression that returns aStringis far simpler than walking aList<AllBoxContainerSnapshot>field-by-field over the VM Service protocol. Non-JSON-encodable values insideentriesare replaced with a'<non-JSON-encodable: Type>'placeholder instead of making the whole snapshot fail to serialize. - No-op (returns an empty list /
null/'[]'/'null') in release builds, mirroring the existingallBoxDebugModeguard used byallBoxDebugLog. - Purely additive:
AllBox's existing public surface (read/getKeys/getValues/hasData/write/...) is unchanged. What was missing before this release was discovery — knowing which containers exist at all in the current isolate — and backend/flush metadata that no existing getter exposed; per-container reads already covered "I know the name and want its data" and remain regular (non-debug-only) public API. - Does not notify anything and does not add a
storage:interface member, so it cannot break existing customAllBoxStorageimplementations.
0.4.0 #
Web support, plus a breaking change: the reactive layer is gone.
The public core import stays exactly the same
(import 'package:all_box/all_box.dart';) — platform selection happens
internally via Dart's conditional imports (dart.library.io /
dart.library.js_interop), not via anything a consumer imports or
configures.
Breaking: reactivity removed. all_box no longer has any
listener/reactive API. Removed:
AllBox.listenKey/removeListenKey/listenAll(the core no longer notifies anything onwrite()/remove()/erase()).AllBoxListenable<T>andAllBoxBuilder<T>(the Flutter reactive layer), and thepackage:all_box/all_box_flutter.dartentrypoint entirely — there is now a single entrypoint,package:all_box/all_box.dart, for every platform including Flutter.- The
.val()extension onString(AllBoxValue/AllBoxValueExtension), which existed only to read/write through the now-removed reactive layer. test/all_box_flutter_test.dart(it only tested the removed reactive layer); theerase()/remove()/.val()tests in the remaining suites were updated to no longer assert on listener notifications.
Why: this keeps all_box a plain synchronous key-value store with no
opinion on state management, and makes the package pure Dart — it no
longer depends on the Flutter SDK at all (pubspec.yaml no longer
declares a flutter: dependency; flutter_test was replaced by
package:test in the package's own test suite).
Migration: call write()/writeAndFlush()/erase() as before, then
update your UI the same way you would for any other synchronous,
non-reactive storage — e.g. setState right after the call in a
StatefulWidget, or by wiring all_box into a ChangeNotifier you own,
all_observer, or whatever state-management approach your app already
uses. There is no drop-in replacement inside all_box itself for
AllBoxBuilder/AllBoxListenable/listenKey/listenAll/.val().
- Web storage, automatic and
path-free:AllBox.init('settings')(nopath) now works on Web, backed bywindow.localStoragevia puredart:js_interopstatic interop — neverdart:html(which blocksdart2wasmcompilation) and no new dependency (package:webwasn't needed). On IO platforms, behavior is unchanged:pathis still how you tellAllBoxwhere<container>.dblives. - New
AllBox.memory(container, {initialData}): promoted, non-deprecated replacement forinitWithMemoryBackendForTesting()(which still works, now as a thin@Deprecatedwrapper aroundmemory()). - New public types:
AllBoxStorage(the storage seam — IO, Web, in-memory, or your own),AllBoxPersistMode(save/flushdurability tiers),AllBoxStorageException(unsupported platform, missingpathon IO, JSON encoding failures, Web storage quota/availability errors). All advanced/optional — everyday code never touches them. - New test suites:
test/all_box_memory_storage_test.dart,test/all_box_platform_storage_test.dart,test/web/all_box_web_storage_test.dart(fake-backed, runs on the VM), andtest/web/all_box_web_storage_browser_test.dart(realwindow.localStorage,@TestOn('browser')-gated — run withdart test -p chrome test/web/all_box_web_storage_browser_test.dart). Added large-payload/large-volume coverage (5,000-key round-trips, ~200 KB single values) across IO, memory and Web storage.
Breaking changes — and why they're mostly not a problem in practice:
AllBox.init()return type changed fromFuture<void>toFuture<AllBox>(it now returns the initialized instance, sofinal box = await AllBox.init('settings');works directly, instead of needing a separateAllBox('settings')call afterwards). This is source-breaking only for code that explicitly typed the return value asFuture<void>or passedAllBox.inititself as aFuture<void> Function(...)callback/tear-off — a plainawait AllBox.init(...)call site (the documented, common usage) is unaffected.pathchanged from a required named parameter to an optional one (String? path). This is not breaking on its own — relaxing required → optional never breaks a call site that already passedpath. It does change the failure mode on IO whenpathis omitted: previously a compile-time "missing required argument" error, now a runtimeAllBoxStorageExceptionwith a clear message. Code that already compiled before is unaffected either way.- The minimum Dart SDK constraint moved from
>=3.0.0to>=3.3.0(required by thedart:js_interopextension types used in the Web storage backend). This is breaking for any consumer pinned to a Dart SDK older than 3.3.0 —pub get/flutter pub getwill fail to resolve this version for them. - No import statement changed for consumers:
dart:io,dart:js_interopand the conditional-import platform selection are entirely internal to the package (lib/src/core/storage/platform/);package:all_box/all_box.dartis still the only import needed, on every platform.
0.3.0 #
Performance release — additive API only, no new dependencies for the package itself, on-disk format unchanged.
-
New
writeAndSave(): intermediate durability tier. Completes once the OS write finishes (survives an app crash — the same guarantee class asHive.put), without the fsync cost ofwriteAndFlush()(the only tier that survives power loss). Keeps the full write-ahead + atomic-rename pipeline. Durability ladder:write()→writeAndSave()→writeAndFlush(). When both tiers coalesce into the same flush, the strongest requirement wins. -
Flush path ~2× less I/O: the pre-flush backup (
.db→.bak) is now a metadata-onlyrenameinstead of a full byte-for-bytecopy. Crash-safety guarantees are unchanged: at any instant either.dbor.bakholds a complete known-good version, andinit()already falls back to.bak. -
Flush coalescing: N concurrent
writeAndFlush()calls now collapse into at most 2 real disk writes (one in-flight + one queued) instead of N sequential full-file writes. Each caller'sFuturestill only completes once its value is durably on disk. -
write()hot path: the debounce timer is now armed once per burst instead of being cancelled and recreated on everywrite()(the timer churn used to cost more than the in-memory map update itself). Side benefit: continuous writes can no longer starve the flush — it now fires at mostflushDelayafter the first write of a burst. -
New test suite
test/flush_performance_test.dart(21 tests): db/bak invariants, simulated crash windows, coalescing contracts, timer semantics, durability round-trips, and randomized mutation stress. -
New on-device benchmark screen in the example app (
example/lib/benchmark/) comparing all_box vs Hive and SharedPreferences with the same loops in the same session (multiple rounds, median reported) — including honest per-lib durability-guarantee caveats. Competitor dependencies live only in the example app. GetStorage is not in the measured comparison: itswrite()Future resolves without waiting for any disk write (there is no API in it that does), so there is nothing comparable to measure; it remains covered in the qualitative comparison docs. -
Updated comparison docs and charts with numbers measured on a real device in profile mode.
-
benchmark/benchmark_test.dart: the package benchmark now runs viaflutter test benchmark/benchmark_test.dart(dart runnever worked — the package importsflutter/foundation).
0.2.1 #
Documentation-only release — no code changes to lib/, no breaking
changes, no new external dependencies.
- Restructured
README.md/README.pt-BR.mdinto the same landing-page format used byall_observer(table of contents, feature highlights, compact comparison table, "when to use it" section, "other packages by us" table, EN ⇄ PT-BR language switch links). - Added
documentation/en/comparison.mdanddocumentation/pt-BR/comparison.md: a detailed, factual comparison against GetStorage, Hive, Isar, and SharedPreferences, including a performance benchmark table (1,000 operations: in-memory write, sync read, durable disk write). - Added a new comparison benchmark chart
(
doc/comparison_benchmark_en.png/doc/comparison_benchmark_pt-BR.png) plotting all five solutions on a log scale. - Fixed the test-count badge (18 → 19) to match
test/all_box_test.dart.
0.2.0 #
AllBox.init()gains aninitialDataparameter, seeding default values on a genuine first run only (checked via the presence of<container>.db/<container>.bakon disk, not via in-memory state, so a container emptied by a previouserase()is correctly never reseeded). The seed is persisted immediately, bypassing the debounce window.write()/writeAndFlush()no longer throwArgumentErrorfor a non-JSON-encodable value. In debug builds only, they now log a louddebugPrintwarning (wrapped in ANSI red) instead, matchingGetStorage's permissive behavior — the write still goes through in memory either way.
0.1.0 #
Initial release.
- Synchronous in-memory reads (
read,readOrDefault,hasData,getKeys,getValues). - Optimistic, debounced writes (
write) and forced-flush writes (writeAndFlush);flushNow()to bypass the debounce window on demand (e.g.AppLifecycleState.paused). - One file per container, JSON-encoded via
dart:convert. - Write-ahead crash-safety: writes land on
<container>.tmpfirst, then an atomic rename replaces<container>.db; the previous good file is kept as<container>.bak. - Two-stage read error handling (UTF-8 decoding vs. JSON parsing), each
falling back to
<container>.bak, then to an empty container —init()never throws on a corrupted file. - Serialized flush queue: concurrent
writeAsStringcalls on the same file are never allowed to race. pathis a required parameter ofAllBox.init()— no internalpath_providerdependency, no plugin-pathMissingPluginExceptionrisk.listenKey/removeListenKey,listenAll(returns a dispose callback),erase()notifying every previously-existing key's listeners.AllBoxListenable<T>(ChangeNotifier+ValueListenable<T?>) andAllBoxBuilder<T>widget — pure-Flutter reactive layer.- Optional
.val()extension onString, DI-free. AllBox.initWithMemoryBackendForTesting()— pure in-memory backend for apps/packages that consumeall_boxto unit/widget-test their own code, with no real disk I/O and no realTimerscheduled onwrite().write()/writeAndFlush()validated that the value was JSON-encodable synchronously and threwArgumentErrorimmediately if not (superseded in 0.2.0 by a debug-only warning — see above).- No Web support in this release (documented limitation). Not isolate-safe (documented limitation).