Hibe - Immutable data without pain
tl;dr hibe is a library to create immutable data objects/graphs through a 'mutable' api. Changes can be observed and objects can be serialized/de-serialized to JSON data.
Key features
Core concept
Hibe has been primarily designed to work in uni-directional dataflow contexts (cf. flux or redux). In this architecture, User Interface updates are triggered by state changes - and state changes are triggered through actions (in other words UI elements never refresh themselves directly).
This model is composed of 2 main sequences:
- a read-only sequence that occurs after state changes to trigger UI view updates. In this sequence, data should be ideally immutable as it gives a very simple way to avoid recalculating pieces that haven't changed on the UI side.
- a (mostly)write-only sequence that occurs during the action processing. In this sequence, having mutable data is convenient as actions can be written through very straightforward and maintainable code.
Hibe allows exactly that: have immutable objects that provide a mutable api to create new versions of those objects. To be more precise, only the last version of a hibe object can be virtually mutated. In this respect, hibe objects behave as if they were eventually immutable.
Let's imagine a very simple example to concretely illustrate what it means.
@Data()
export class Todo {
@value() description = "";
@value() completed = false;
@value() editing = false;
}
let todo = new Todo();
The Todo class in the previous code snippet models an item in the todo mvc application. The todo instance is immutable - but it can still be virtually updated like this:
todo.description = "Call Marge";
todo.completed = true;
console.log(todo.description);
console.log(todo.completed);
When this code is run hibe implicitly creates a new version for the todo object and redirects all read/write operation to it - so that todo is unchanged, even though it seems mutable from the developer's perspective.
Of course this would be pointless if the new version remained hidden. In practice hibe triggers a micro-task (used by Promises ) to asynchronously spawn the new versions as soon as the 'mutation sequence' ends.
When the new version has spawn, the code will behave as follows:
console.log(todo.description);
console.log(todo.completed);
todo = latestVersion(todo);
console.log(todo.description);
console.log(todo.completed);
You may wonder how the application can get notified of the micro-task result. There are actually 2 ways:
- either by watching a data object
watch(todo, (newTodo: Todo) => {
todo = newTodo;
})
- or by explicitly waiting for the micro-task through a promise
todo = await mutationComplete(todo);
(note: for advanced cases a synchronous API is also available - cf. commitMutations())
Of course, more complex (directed acyclic) graphs can be created:
@Data()
export class TodoApp {
@value() newEntry = "";
@data(list(Todo)) list: Todo[];
@value() filter = "ALL";
}
Hibe objects (aka. datasets) also support @computed properties to expose values that are calculated from other properties (and that will not be recalculated if its dependencies don't change):
@Data()
export class TodoApp {
@value() newEntry = "";
@data(list(Todo)) list: Todo[];
@value() filter = "ALL";
@computed() get listView(): Todo[] {
if (this.filter === "ALL") {
return this.list;
} else {
let isComplete = (this.filter === "COMPLETED");
return this.list.filter(item => item.completed === isComplete);
}
}
@computed() get itemsLeft(): number {
let itemsLeft = 0;
this.list.forEach(item => {
itemsLeft += item.completed ? 0 : 1;
});
return itemsLeft;
}
}
Using hibe
Hibe can be installed from npm:
npm i hibe
Compiling hibe on your machine
Simply install yarn - then run
yarn install
yarn build-hive
This will generate a hibe.js in a dist folder.
To run tests:
yarn test
License
Apache V2.0