Repository navigation
Releases: yangshun/ultrastorage
Release list
v0.9.0
- Breaking: Removed automatic JSON fallback after a serializer parser throws. Reads now use only the configured parser unless
fallbackParseris explicitly provided, including when using the defaultdevalueserializer. (#4) - Added
fallbackParserto bothcreateStorage()entry points. It runs only after a synchronous primary parser exception; if both parsers throw,getItemResult()retains the primary error. Promise or thenable output from either parser throws a synchronousTypeError. - Added opt-in
onQuotaExceeded: 'clear-expired'recovery. A backendQuotaExceededErrortriggers cleanup of recognized expired entries in the current namespace, followed by one retry of the same serialized write. Cleanup and retry failures propagate. - Fixed asynchronous custom parsers being silently treated as unsupported data and leaking rejected promises. Promise-like parser output now throws a synchronous
TypeError, with any rejection consumed. (#3) - Reorganized internal source into core and React modules while preserving the
ultrastorage,ultrastorage/core, andultrastorage/reactentry points. - Expanded migration, parser error, and quota recovery documentation, and refreshed the documentation site's branding and navigation.
Upgrade notes
Add fallbackParser: JSON.parse to preserve the previous JSON compatibility behavior. Without it, JSON envelopes rejected by the configured parser remain stored but are unreadable, excluded from enumeration, and skipped by clear() and clearExpired(). Use a compatible parser to migrate them, or removeItem(key) to remove known obsolete entries. Writes continue to use serializer.stringify().
const appStorage = createStorage({
prefix: 'app',
fallbackParser: JSON.parse,
});Keep your existing serializer, prefix, separator, and backend settings when adding the fallback. React hooks and reactive expiration follow the storage instance's parsing policy. See the migration guide for details.
Custom parsers must be synchronous. A promise or thenable now throws instead of returning an unsupported result; changing the fallback configuration does not enable async parsing.
Quota recovery is disabled by default. Enabling it does not evict live entries or fall back to memory, and a write can still fail after cleanup and the single retry.
Contributors
Thanks to @rickvian for reporting the asynchronous-parser issue (#2), contributing the fix (#3), and providing the reproduction and proposal for explicit fallback parsing (#4).
v0.8.0
- Added
getItem()legacy imports with a custom deserializer and optional schema validation. Imports run only when the destination is physically missing, persist the validated value without expiration, and remove the legacy key after a successful write. - Added
getExpiration()to inspect stored deadlines without cleanup andsetExpiration()to change or remove expiration without supplying the value. - Changed writes without expiration options to preserve the current unexpired deadline, including
updateItem()and React setters. Pass{ expiresAt: null }to remove expiration explicitly. - Added
getItemResult()with distinct outcomes for successful reads, missing and expired entries, unsupported data, parse errors, and validation errors. Successful results distinguish storednullfrom a missing value. - Added opt-in
{ reactiveExpiration: true }for subscriptions and React hooks. Expiration timers notify listeners and update React values without deleting entries, with rechecks on window focus and page visibility changes. - Expanded documentation for migration, expiration controls, detailed reads, and reactive expiration, and added a changelog page.
Upgrade notes
- Previously, writing without expiration options removed an existing deadline. To retain that behavior, pass
{ expiresAt: null }tosetItem(),updateItem(), or a React setter. Pass a newttlto restart the lifetime instead of preserving the original deadline. - Reactive expiration remains opt-in. Timer notifications may be delayed, do not remove stored entries, and can be followed by another
expireevent during lazy cleanup. - Legacy imports are available through
getItem()only.getItemResult()does not import legacy data; backend failures, thrown schema errors, and unsupported asynchronous validation still throw.
v0.7.0
- Added array storage keys through the exported
StorageKeytype across storage methods, subscriptions, and React hooks. Equivalent arrays address the same entry without depending on array identity. - Added
keys()to enumerate non-expired keys in one scan. Bothkey()andkeys()restore array keys as fresh arrays of their original string segments. - Added development warnings when a namespace prefix contains its separator, indicating possible overlap with broader namespaces.
- Fixed package imports in browser environments without a global
process, with smoke checks for both ESM and CommonJS builds. - Hardened stored-entry validation and rejected non-finite expiration timestamps, including invalid dates and overflowing TTLs.
- Fixed Standard Schema handling for successful results with
issues: undefinedand asynchronous validators returning promises or thenables. Unsupported asynchronous validation now consumes rejections before throwing a synchronous error. - Improved in-memory key enumeration by caching ordered keys until insertions or deletions change them.
- Expanded documentation for React usage, namespace overlap, storage durability, migration, and synchronous helper behavior.
Upgrade notes
key()now returnsStorageKey | nullinstead ofstring | null. Update callers that assume every enumerated key is a string.UltraStorageno longer intersects the nativeStoragetype; useUltraStoragefor application instances.- Existing string keys and
__us/__gsentries remain supported. Avoid authoring string keys beginning with the reserved array-key prefix\u0000us:a:; canonical encoded strings address the same entries as their corresponding arrays. - Subscription events retain a string
change.key; array keys use their opaque serialized form. - Invalid or non-finite
ttlandexpiresAtvalues now throwTypeError. Stored envelopes must have a value, version1, and an expiry ofnullor a finite number to be recognized.
v0.6.0
-
Renamed the package from
greatstoragetoultrastorageand the exportedGreatStoragetype toUltraStorage. -
Added key-level subscriptions through
subscribe(key, listener), including same-page updates across instances sharing a backend and native cross-tab storage events. -
Made the
UltraStorageinterface a strict superset of the nativeStorageinterface. -
New entries use the internal storage marker
__us. Existing__gsentries remain supported without migration. -
Added optional React 18/19 bindings through
ultrastorage/react:useStorageandcreateStorageHook, with typed fallbacks, schema reads, functional updates, and expiration options. -
Added cached, side-effect-free internal snapshots and SSR/hydration support. Core reads retain their existing fresh-object and lazy-cleanup behavior.
-
Deferred default
localStorageaccess until the first operation, making instance construction safe on the server. Backend failures now occur on use rather than construction; no memory fallback is introduced. -
Added adapter rendering, hydration, subscription, failure, and type coverage.
vp run test:coverageenforces 100% runtime coverage. -
Added documentation at https://ultrastorage.dev.
Upgrade notes
- Replace the
greatstoragedependency and import paths withultrastorage, includinggreatstorage/corewithultrastorage/core. Rename type imports fromGreatStoragetoUltraStorage. - Existing
__gsentries remain readable. Older versions cannot read newly written__usentries, so update applications that share the same storage together. - React remains optional. Install React 18 or 19 only when using
ultrastorage/react. - Default storage is resolved on first use, so constructing an instance on the server is safe; operations still require an available storage backend.
v0.5.0
0.5.0 - 2026-03-10
greatstorage 0.5.0 focuses on better bundle control for custom serializers and safer development ergonomics.
Highlights
- Added a new
greatstorage/coreentry point for applications that provide their own serializer and want to avoid bundlingdevalue. - Added development-only warnings for two easy-to-miss cases:
- writing
null, which reads back the same as a missing key viagetItem() - writing values with an expiry that is already in the past
- writing
- Expanded test coverage for the new core entry point and the warning behavior.
- Improved the README to document the new entry point and clarify the public API.
Why greatstorage/core exists
The default greatstorage entry point still includes devalue so rich JavaScript values work out of the box. If you already use a custom serializer such as superjson, greatstorage/core lets you keep the same storage API while making the serializer explicit and avoiding the extra default serialization dependency in your bundle.
Upgrade notes
- No breaking changes in the default
greatstorageentry point. greatstorage/coreis new in this release. ItscreateStorage()requires aserializer.- The new warnings only run outside production builds and do not change runtime behavior.