cacheman 0.5.0
cacheman: ^0.5.0 copied to clipboard
A tiny, type-safe wrapper over get_storage (persistent) with one unified API: TTL & absolute expiry, sliding renewal, namespaces, pluggable serialization, an optional codec hook, and a key-bound short [...]
cacheman #
简体中文: README.zh-CN.md
A tiny, type-safe wrapper over get_storage (persistent),
with one unified API: TTL & absolute expiry, sliding renewal, namespaces, pluggable
serialization, an optional codec hook, and a key-bound shortcut helper. The Dart/Flutter sibling of
@codejoo/storage (TypeScript).
Fully synchronous after a single await cache.ensureInitialized() call — see Cacheman's class
doc for why.
Install #
dependencies:
cacheman:
path: ../cacheman # or a git/pub dependency once published
Quick start #
import 'package:cacheman/cacheman.dart';
final cache = Cacheman();
await cache.ensureInitialized();
cache.write('token', 'abc'); // persists across restarts (get_storage)
cache.read<String>('token'); // 'abc' — synchronous
cache.write('session', 1, ttl: 60000); // expires in 60s
cache.remove('token');
cache.setNamespace('alice'); // per-account isolation, in place
API #
Cacheman({container, path, options}) #
Constructs a Cacheman (persistent, get_storage-backed) exposing all CRUD methods directly — no
.ls indirection. Synchronous — call and await ensureInitialized() once before any read/write.
cache.ensureInitialized() #
The only Future boundary in the API. Awaits get_storage's disk load for this instance's
container.
Subclassing: Cacheman's constructor and ensureInitialized() are both plain, non-factory members,
so a subclass just forwards constructor params via super(...) — no factory boilerplate needed:
class MyCacheman extends Cacheman {
MyCacheman({super.container, super.path, super.options});
int extra = 0;
}
final cache = MyCacheman();
await cache.ensureInitialized();
Cacheman methods #
| Method | Description |
|---|---|
read<T>(key, [default]) |
Read; missing/expired → default (or null). |
write<T>(key, value, {ttl, expireAt}) |
Write. ttl in ms. |
remove(key) |
Delete. |
readAll(keys, [defaults]) / writeAll(keys, values, {...}) / removeAll(keys) |
Batch, positional. |
keys() / key(index) / length |
Enumerate/count owned keys. |
purge() |
Proactively delete expired entries (otherwise lazy). |
erase() |
Erase owned keys (namespace/enckey-scoped) or everything. |
namespace / setNamespace([ns]) |
Current prefix / switch it in place. |
container |
Underlying get_storage GetStorage instance — for interop needing the raw container (e.g. listenKey for external change notifications). |
storageKey(key) |
The actual key key is persisted under (namespace-prefixed, enckey-encoded when enabled) — pass this to container.listenKey(...), not key itself. |
CachemanOptions #
serialize/deserialize, codeable/codec, sliding,
namespace, raw, force, readonly, enckey, onError — see each field's doc comment in
lib/src/engine.dart for exact semantics.
No codec implementation ships with this package. Codec is a plain encode/decode string
interface — bring your own (obfuscation, real encryption, compression, whatever fits).
fast<V>(cache, key) / lazy<V>(cache, key) / batchFast<V>(cache, keys) #
Key-bound shortcut accessors — see lib/src/fast.dart.
GetX reactive interop (container + storageKey) #
get is not a dependency of this package — container/storageKey are a plain escape
hatch, not a shipped GetX integration. If you do use GetX, they're enough to build a
VueUse-useStorage-style reactive wrapper yourself:
Rx<V?> reactive<V>(Cacheman cache, String key) {
final rx = Rx<V?>(cache.read<V>(key));
// External writes (another instance/isolate) update the Rx.
cache.container.listenKey(cache.storageKey(key), (dynamic _) => rx.value = cache.read<V>(key));
// Local Rx writes persist back through cacheman (ttl/serialize/etc. still apply).
ever(rx, (V? v) => v == null ? cache.remove(key) : cache.write<V>(key, v));
return rx;
}
final token = reactive<String>(cache, 'token');
Obx(() => Text(token.value ?? 'no token'));
token.value = 'abc123'; // updates the UI and persists
Use cache.storageKey(key), not cache.namespace + key, whenever enckey might be in play —
the codec's encoding is a private, pluggable detail storageKey already accounts for.
debug(cache) #
Decrypted snapshot of every owned entry, { "namespace:key": value } — see lib/src/debug.dart.
Jsonx #
jsonEncode/jsonDecode-compatible serializer that additionally round-trips DateTime / Duration /
Set / BigInt / Uri / RegExp. Pass Jsonx.encode/Jsonx.decode<T> (wrapped to the
CacheEntity <-> String shape) as CachemanOptions.serialize/deserialize — decode<T> casts the
result to T (e.g. Jsonx.decode<Map<String, dynamic>>(s)). Not round-trippable, by design: custom
Enums and Maps with non-String keys — see lib/src/jsonx.dart's doc comment.
Example #
A complete, runnable app exercising every feature above (persistent tier, ttl, sliding, namespace,
batch ops, fast/lazy/batchFast, debug(), codeable/enckey, Jsonx, raw/readonly)
is in example/:
flutter run example/lib/main.dart
Differences from @codejoo/storage (the TS sibling) #
- Fully synchronous after
create()—get_storageis sync-after-init, so there's nodb/async tier the way the TS version hasls/ss(sync) vsdb(async IndexedDB). - One tier, not three: only the persistent tier — no in-memory
sstier and no IndexedDB equivalent needed. - No built-in codec. The TS version ships obfuscation codecs; this package only exposes the
Codecinterface. force's retry only covers synchronous write failures (e.g. a customserializethrowing) —get_storage's actual disk-flush failures are asynchronous and reported viaonErrorseparately, not retried (seeCacheman's_gsdoc comment inlib/src/cacheman.dart).- No
crossTabequivalent (a browser-tab concept with no Flutter analogue).
License #
MIT