hibe
v0.0.6
Published
Lightweight data framework implementing eventual immutability paradigm
Downloads
2
Maintainers
Readme
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
- immutability through mutable APIs (aka. eventual immutability - cf. below)
- easily observable data (cf. watch())
- to / from JSON support (cf. load() and convert())
- support of Directed Acyclic Graphs
- support of data object inheritance
- (mostly) tree-shakeable: what you don't use will be stripped-out from your code - cf. rollup or webpack
- easily testable (cf. mutationComplete())
- memoized computed properties (cf. @computed)
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.
// Todo data for http://todomvc.com/examples/vanillajs/
@Data()
export class Todo {
@value() description = ""; // description of the todo task
@value() completed = false; // is the task completed?
@value() editing = false; // is the task being edited?
}
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); // print "Call Marge"
console.log(todo.completed); // print true
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); // print "" -> value before mutation
console.log(todo.completed); // print false
todo = latestVersion(todo); // latestVersion is a hibe utility function that return the latest version of a dataset
console.log(todo.description); // print "Call Marge" -> value after mutation
console.log(todo.completed); // print true
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) => {
// a new version of todo has been spawn
todo = newTodo;
// let's refresh the UI...
})
- or by explicitly waiting for the micro-task through a promise
todo = await mutationComplete(todo);
// the new todo version is now accessible
(note: for advanced cases a synchronous API is also available - cf. commitMutations())
Of course, more complex (directed acyclic) graphs can be created:
// TodoApp structure for http://todomvc.com/examples/vanillajs/
@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):
// TodoApp structure for http://todomvc.com/examples/vanillajs/
@Data()
export class TodoApp {
@value() newEntry = "";
@data(list(Todo)) list: Todo[];
@value() filter = "ALL";
// return an array of Todo sorted according to the filter property
@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);
}
}
// return the number of items that are not completed
@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