Xorma In-memory Reactive Database
Xorma is a synchronous, reactive, in-memory database at an early stage of development (version 0.0.1). It is specifically designed for building complex frontend applications such as video editors, design tools, IDEs, and games, where maintaining complex object graphs with frequent write operations is common. Developed initially in 2022 for a 3D circuit simulator, Xorma leverages MobX for its core reactivity, allowing developers to define observable models (`Model.withType(DataType)`) and manage application state in a centralized `Store`. A key differentiator is its synchronous API for data manipulation, coupled with a guarantee that only one instance of a model will ever exist for a given ID within the store, ensuring data consistency and simplifying reactivity patterns. While actively developed, its 0.0.1 version indicates a rapidly evolving API and no established release cadence.
Common errors
-
Error: [mobx] Property 'fieldName' is not observable. Please ensure it is annotated with @observable, or added to an object passed to 'makeObservable'.
cause A property on a Xorma `Model` instance was accessed or modified in a reactive context but was not marked as observable using `makeObservable` in the constructor.fixIn your custom `Model` class's constructor, ensure all properties intended to be reactive are listed in the `makeObservable(this, { ... })` call with `observable` or `computed`. -
Component is not re-rendering after data changes.
cause A React/Vue component that consumes data from the Xorma store is not properly wrapped as an observer.fixEnsure your functional React components are wrapped with `observer` from `mobx-react` (or `mobx-vue` for Vue) to subscribe them to observable changes from the Xorma store. -
My `create` call is not returning a new instance, but modifying an existing one.
cause You attempted to `create` a new model instance with an ID that already exists in the Xorma store. Xorma guarantees a single instance per ID.fixThis is expected behavior. If you need a truly new, separate instance, provide a unique ID. If you intend to update, this is the correct method. Otherwise, consider cloning an existing instance if the API provides such functionality.
Warnings
- breaking Xorma is currently at version 0.0.1. The API is in a very early stage of development and is subject to frequent and significant breaking changes without prior notice. Use in production at your own risk.
- gotcha Xorma enforces a 'single instance per ID' guarantee. Attempting to `create` a model with an ID that already exists in the store will not create a new instance, but instead update the existing instance's data via its `loadJSON` method.
- gotcha The project repository indicates no security policy (`SECURITY.md`) has been defined. This means there's no formal process for reporting or addressing security vulnerabilities.
Install
-
npm install xorma -
yarn add xorma -
pnpm add xorma
Imports
- Store
const { Store } = require('xorma');import { Store } from 'xorma'; - Model
import Model from 'xorma/Model';
import { Model } from 'xorma'; - DataType
import { Data } from 'xorma';import { DataType } from 'xorma'; - observable, makeObservable
import { observable, makeObservable } from 'mobx';
Quickstart
import { DataType, Model, Store } from 'xorma';
import { observable, makeObservable, autorun } from 'mobx';
interface TaskData {
id: string;
name: string;
done: boolean;
}
// 1. Define your base model
class BaseModel extends Model.withType(DataType<TaskData>()) {
static idSelector(data: TaskData) {
return data.id;
}
}
// 2. Define your specific data model extending BaseModel
class TaskModel extends BaseModel.withType(DataType<TaskData>()) {
name!: string;
done!: boolean;
constructor(data: TaskData) {
super(data);
makeObservable(this, {
name: observable,
done: observable
});
this.loadJSON(data);
}
// Method to update instance data from JSON
loadJSON(data: TaskData) {
this.name = data.name;
this.done = data.done;
}
toJSON(): TaskData {
return { id: this.id, name: this.name, done: this.done };
}
}
// 3. Create a store and register your models
const store = new Store({
schemaVersion: 1,
models: {
Task: TaskModel // Register TaskModel under the key 'Task'
}
});
// Access the collection for TaskModel
const taskCollection = store.getCollection(TaskModel);
// 4. Add data to the store (synchronous)
console.log('Adding tasks...');
const task1 = taskCollection.create({
id: 'task-1',
name: 'Learn Xorma',
done: false
});
const task2 = taskCollection.create({
id: 'task-2',
name: 'Build something great',
done: false
});
// 5. Demonstrate reactivity with MobX autorun
autorun(() => {
const allTasks = taskCollection.getAll();
console.log('\n--- Reactive Task List ---');
allTasks.forEach(task => console.log(`[${task.id}] ${task.name} (Done: ${task.done})`));
console.log('--------------------------');
});
// 6. Update data and observe reactivity (synchronous)
setTimeout(() => {
console.log('\nUpdating task-1...');
task1.name = 'Master Xorma';
task1.done = true;
// Trying to create a task with an existing ID will update it
taskCollection.create({
id: 'task-2',
name: 'Deploy something awesome', // Name is updated
done: true // Done status is updated
});
// Add a new task
taskCollection.create({
id: 'task-3',
name: 'Celebrate success',
done: false
});
}, 1000);
// Output after all operations (will be reactive, showing changes from setTimeout)