A schema-driven JSON Database with immutable snapshots capabilities. Built on top of Immutable, Superstruct and lowdb.
- Schema Validation: Ensure data integrity with
Superstruct
schemas - Immutable State: Use
Immutable
to manage state in a predictable way. - Flexible Adapter: Compatible with
lowdb
adapters (filesystem, localStorage, memory, etc), or extend functionality with custom adapters for advanced use cases. - Sync/Async Supports: Supports seamlessly both synchronous and asynchronous adapters.
- Cross-Platform: Works in both Node.js and Browser environments.
- Snapshot History: Automatically takes snapshots of the state, providing historical views of your data.
- Explicit Writes: Mutate state without immediate persistence, and write changes explicitly when needed.
Install the library using npm:
npm install immutable superstruct struma --save
Or with yarn:
yarn add immutable superstruct struma
Use superstruct
to define a schema for your data:
import { number, object, string } from 'superstruct';
const UserSchema = object({
name: string(),
age: number(),
preferences: object({
theme: string(),
}),
});
import { Struma } from 'struma';
import { JSONFile } from 'struma/adapters/node';
const adapter = new JSONFile('db.json');
const db = new Struma(UserSchema, adapter);
import { Map } from 'immutable';
// Update state
const newState = Map({
name: 'Alice',
age: 30,
preferences: Map({
theme: 'dark',
}),
});
// Either you can use plain js object too, it will
// auto resolves using `fromJS` method from `immutable`
db.state = newState;
// Save state to write into db.json
await db.write();
// db.json
{
"name": "Alice",
"age": 30,
"preferences": {
"theme": "dark"
}
}
console.log((await db.state).toJS()); // { name: 'Alice', age: 30, preferences: { theme: 'dark'} }
console.log((await db.snapshots).toJS()); // Array of historical states
Creates a new Struma
instance.
schema
: Asuperstruct
schema for data validation.adapter
: An adapter that conforms to theAdapter
interface.
- Getter: Returns the current state as an immutable object.
- Setter: Updates the state. Throws an error if the data is invalid.
Write the current state to the adapter. Returns a promise that resolves when the write operation is complete.
Returns a Promise that resolves a list of historical states (snapshots) as an immutable List. Each snapshot is an immutable representation of the state at a specific point in time.
You can create your own adapter as long as it conforms to the Adapter
interface
import type { Adapter as AsyncAdapter, SyncAdapter } from 'lowdb';
export interface Adapter<T> {
read: () => ReturnType<AsyncAdapter<T>['read'] | SyncAdapter<T>['read']>;
write: (data: T) => ReturnType<AsyncAdapter<T>['write'] | SyncAdapter<T>['write']>;
}
Tip
For common case, you can go to lowdb
Documentation
import { Struma } from 'struma';
import { JSONFile } from 'struma/adapters/node';
import { number, object, string } from 'superstruct';
const UserSchema = object({
name: string(),
age: number(),
});
const adapter = new JSONFile('db.json');
const db = new Struma(UserSchema, adapter);
(async () => {
// Update state
db.state = { name: 'Alice', age: 25 };
// Save state
await db.write();
// Read state
console.log((await db.state).toJS()); // { name: 'Alice', age: 25 }
// Access snapshot history
console.log((await db.snapshots).toJS()); // [null, { name: 'Alice', age: 25 }]
})();
As for UMD build, it will exposes Struma
class,
and Struma.adapters
compatible adapters for browser as global.
<script src="https://unpkg.com/immutable"></script>
<script src="https://unpkg.com/superstruct"></script>
<script src="https://unpkg.com/struma"></script>
<script>
// Define a schema
const UserSchema = Superstruct.object({
name: Superstruct.string(),
age: Superstruct.string(),
});
const adapter = new Struma.adapters.LocalStorage('db');
const db = new Struma(UserSchema, adapter);
(async () => {
// Update state
const newState = { name: 'John', age: '30' };
db.state = newState;
// Save state to write into LocalStorage
await db.write();
// Read state
const state = await db.state;
console.log('Current State:', state.toJS()); // { name: 'John', age: '30' }
// Access snapshot history
const snapshots = await db.snapshots;
console.log('Snapshot History:', snapshots.toJS()); // [null, { name: 'John', age: '30' }]
})();
</script>
For feedbacks or issues, check out the Issues.