IN-MEMORY • NAMESPACE-AWARE • TYPESCRIPT Transient state. Properly organized.
MemoryX is a lightweight, zero-dependency in-memory key-value store for JavaScript and TypeScript. Keep transient browser data isolated in namespaces, address any nested value with dot-path access, and observe changes synchronously — without bringing a full state-management stack. Ships as a single ESM module.
- Namespace Support: Store data in separate namespaces to avoid collisions.
- CRUD Operations: Easily get, set, delete, and check the existence of data.
- Global Storage: Uses the
windowobject to persist data globally within the browser session. - Memory Management: Clear and manage data for each namespace individually.
To install MemoryX in your project, run the following npm command:
npm install @darcas/memoryxOr, if you're using yarn:
yarn add @darcas/memoryximport { MemoryX } from '@darcas/memoryx';
// Create a new instance with a custom namespace (default is '_global')
const memory = new MemoryX('myNamespace');
// Store data
memory.set('user.name', 'John Doe');
memory.set('user.age', 30);
// Retrieve data
const name = memory.get('user.name'); // 'John Doe'
const age = memory.get('user.age'); // 30
// Check if a key exists
const hasName = memory.has('user.name'); // true
const hasEmail = memory.has('user.email'); // false
// Delete data
memory.del('user.age');
// Clear the namespace
memory.destroy();The constructor takes an optional namespace argument. If not provided, the default namespace _global will be used.
- Parameters:
namespace(optional): A string that defines the namespace under which the data will be stored. Default is_global.
Clears all the data stored under the current namespace.
memory.destroy();Retrieves the value stored at the specified path. If the path doesn't exist, it returns the provided default value (or null if no default is provided).
-
Parameters:
path: A string or array representing the key path.def: The default value to return if the key doesn't exist (optional).
-
Returns: The value stored at the specified
path.
const name = memory.get('user.name', 'Default Name'); // 'John Doe' or 'Default Name'Stores a value at the specified path.
- Parameters:
path: The key path where the value should be stored.value: The value to be stored.
memory.set('user.email', 'john@example.com');Deletes the value stored at the specified path.
- Parameters:
path: The key path to be deleted.
memory.del('user.email');Checks if a value exists at the specified path.
-
Parameters:
path: The key path to check.
-
Returns:
trueif the key exists,falseotherwise.
const exists = memory.has('user.name'); // true or falseStatic getter that returns an array of all namespaces currently stored in memory.
const namespaces = MemoryX.namespaces; // ['_global', 'myNamespace']Subscribes a listener to changes at the specified path. The listener is also notified when any descendant path changes. Use the '*' wildcard to listen to every change in the namespace. Returns an unsubscribe function.
const unsubscribe = memory.subscribe('user', (value, previous) => {
console.log('changed:', value, 'was:', previous);
});
memory.set('user.name', 'Bob'); // listener fires
unsubscribe(); // stop listeningReturns a shallow copy of all data in the current namespace.
Returns the top-level keys of the current namespace.
Deep-merges a plain object into the value stored at path.
memory.merge('user', { age: 31 });Appends a value to the array stored at path (creates the array if missing).
Increments/decrements the numeric value at path by by (default 1). Missing or non-numeric values are treated as 0.
Exports the namespace as a JSON string and restores it back. restore replaces the entire namespace content and throws a TypeError on invalid input.
MemoryX resolves its global root from window when available and falls back to globalThis, so it can be used safely in Node/SSR environments.
npm testThe test suite (Vitest) includes differential tests against lodash to guarantee that path resolution behaves exactly like the lodash functions it replaced.
Here is a full example:
import { MemoryX } from '@darcas/memoryx';
// Create a new instance
const memory = new MemoryX('myNamespace');
// Set values
memory.set('user.name', 'Alice');
memory.set('user.email', 'alice@example.com');
// Get values
console.log(memory.get('user.name')); // 'Alice'
console.log(memory.get('user.email')); // 'alice@example.com'
// Check existence
console.log(memory.has('user.name')); // true
console.log(memory.has('user.phone')); // false
// Delete a value
memory.del('user.email');
// Clear the namespace
memory.destroy();Things worth knowing before relying on MemoryX in production:
- Notifications are synchronous and fire once per mutation: three consecutive
set()calls trigger three notifications. There is no built-in batching, debounce, or async mode — wrap multi-write sequences in your own logic if you need coalescing. - Listeners run after the mutation is applied;
previousis the value held immediately before that single mutation. - Subscriptions are per instance (per namespace): an instance does not observe writes made through a different instance of another namespace.
- A listener that throws propagates the exception to the caller of
set/del/etc. Do not let listeners throw.
- The store holds values by reference:
all()returns a shallow copy, so mutating a nested object obtained fromget()mutates the store. Copy explicitly if you need isolation. snapshot()/restore()use JSON:Map,Set, functions,undefined, and class instances are lost or degraded (NaN→null,Date→ ISO string). Round-trip only JSON-safe data.- Storing
undefinedviaset(path, undefined)creates the path butget()cannot distinguish it from a missing path (both return the default). Usehas()for existence checks.
- Instances of the same namespace share state, including across separate bundles on the same page.
destroy()removes the namespace from the store. Every instance of that namespace is affected (they all read the same store), and each one lazily recreates it on its next access.restore()replaces the whole namespace content and notifies every subscriber.
- Path resolution is a verified drop-in replacement for lodash's
get/set/has/unset(differential-tested against real lodash), including array-vs-object intermediate creation and sparse-arrayhas(). - One intentional divergence: writing to any path containing
__proto__,constructor, orprototypeaborts the entire operation (lodash would skip only the offending segment). This guards against prototype pollution.
- ESM-only package (
"type": "module"). CommonJS consumers must use dynamicimport(). - Output targets ES2015: no IE11 support.
- In SSR/Node the shared store lives on
globalThis: state is per-process and not shared across workers or server instances.
If you'd like to contribute to the project, feel free to fork it and create a pull request. Please ensure that your changes are well-tested and properly documented.
This project is licensed under the MIT License. See the LICENSE file for details.
Made with ❤️ by Dario Casertano (DarCas).