@onehat/data A robust ORM for Javascript. Can CRUD, search, sort, filter, paginate your data. Integrates with many front- and back-end storage mediums.
- Repositories. A Repository stores many Entities in a storage medium. Corresponds to a database table. Repositories can sort, search/filter, and add/edit/delete their constituent Entities.
- Storage Mediums. Repositories are specialized to store their data in a single type of storage medium. Available types of Repositories include: Memory, Ajax, Rest, LocalStorage (Browser), SessionStorage (Browser), IndexedDB (Browser), AsyncStorage (React Native/Expo), SecureStore (React Native/Expo). One special type of Repository—LocalFromRemote—combines two different Repository types (one local and one remote) into a single Repository, thereby allowing autosyncing between the local and remote repositories, enabling true offline-first capability.
- Entities. An Entity is a single record of data, organized into properties. Corresponds to a database row. Entity data can be accessed directly (entity.username), via specific properties and their formatted values (entity.properties.username.displayValue), or by obtaining a JS object of the whole Entity (entity.getDisplayValues(), or entity.getSubmitValues()).
- Properties. A Property is a single unit of data. Corresponds to a database field. Properties are differentiated into different Property types (e.g. Integer, String, Boolean, etc), and thereby allow for easy formatting of "display" or "submit" values. For example, a date might be set to display as "Wed, Feb 5, 2020" but submit as "2020-02-05".
- Schemas. A Schema defines the configuration of a Repository. Corresponds roughly to the database table schema. The Schema defines the name and type of Repository, the Properties that exist, and which are "id" and "display" Properties.
npm i @onehat/data
Comprehensive unit tests can be found in ./cypress/integration. These are an excellent source of code examples. Comprehensive API documentation can be found in ./docs.
For every type of Entity you will use (e.g. Users or Animals or Invoices), define a Schema. A Schema determines the various Properties that each Entity will have, as well as the medium where the Entities will be stored.
const Users = {
name: 'Users',
model: {
idProperty: 'id',
displayProperty: 'username',
properties: [
{ name: 'id', type: 'int', },
{ name: 'username', type: 'string', }, // explicitly set property type
{ name: 'password', }, // type: 'string' is assumed, if not explicitly set
{ name: 'first_name', },
{ name: 'last_name', },
{ name: 'email', allowNull: false, }, // make it a required field
{ name: 'last_login', type: 'datetime', defaultValue: 'now', }, // give it a default value.
],
sorters: [
{
name: 'last_name',
direction: 'ASC',
},
{
name: 'first_name',
direction: 'ASC',
},
],
},
repository: 'memory', // Repository type. Can be string name or config object
};
export default Users;
Every Property must have a unique name. All other attributes are optional. Common Property attributes include:
- name - The name of the Property
- type - The type of the Property (e.g. 'string', 'bool', 'int', etc)
- allowNull - Is this Property required to have a value?
- defaultValue - Default value for this Property if none is supplied
- isSortable - Whether this Property type is sortable
Other Property attributes exist and can be found in the API.
The easiest way to create one or more Repositories is to use the global oneHatData singleton object. Each schema will have a bound repository of the same name (e.g. "Users", or "Groups").
import oneHatData from '@onehat/data';
import Groups from './Groups';
import Users from './Users';
oneHatData
.createSchemas([
Groups,
Users,
])
.createBoundRepositories()
.then(() => {
setIsReady(true);
const UsersRepository = oneHatData.getRepository('Users');
// Do something with your data
});
Once you have a Repository initialized, you can start adding data to it. Data is manipulated asynchronously, so you may optionally wait for it to complete.
const UsersRepository = oneHatData.getRepository('Users');
// 1. Add an Entity
const userEntity = await UsersRepository.add({
username: 'ajones',
password: '12345',
first_name: 'Alice',
last_name: 'Jones',
email: '[email protected]',
});
// 2. Edit an Entity
// Use assignment to change the value of a particular Property.
userEntity.password = 'mypass';
// Or you can be more verbose about it
userEntity.getProperty('password').setValue('mypass');
// 3. Delete an Entity
userEntity.delete();
// Or delete it from the Repository
await UsersRepository.delete(userEntity);
There are lots of filtering and sorting methods available on Repositories.
// Add a single filter
UsersRepository.filter('first_name', 'Alice');
const foundEntities = UsersRepository.entities;
// Or search by an id or function
const myEntity = UsersRepository.getById(1);
const results = UsersRepository.getBy((entity) => {
return entity.id > 2;
});
// Sort the entities by a particular Property
UsersRepository.sort('last_name', 'DESC');
const sortedEntities = UsersRepository.entities;
Repositories, Entities, and Properties emit many different kinds of events.
// The 'change' event, emitted from an Entity, is relayed through the Repository and becomes 'entity_change'
UsersRepository.on('entity_change', (entity) => {
console.log('changed entity');
});
userEntity.first_name = 'Joe';
// prints 'changed entity' to console
// The 'changeData' event is fired from the Repository after multiple Entities are loaded at once
UsersRepository.on('changeData', (entities) => {
console.log('entities changed');
});
UsersRepository.load([
{ email: '[email protected]' },
{ email: '[email protected]' },
{ email: '[email protected]' },
]);
// prints 'entities changed' to console