Skip to content

Repository files navigation

W(eb)-ORM

codecov

Documentation

IndexedDB is in theory every developer's dream, having a full database built-in into your browser is amazing. However, if you have tried using it, it'll feel lacking compared to an actual SQL database, or even a document database.

This package's objective is to hide away this ugly truth and expose it with a nice ORM, so that you can forget this is far from being a fully fledged DB.

Overview

A table can be simply created as follows:

import{Field,Model}from'@d34d/w-orm'enumUserRole{Admin,Moderator,Guest,}classUserextendsModel{
@Field({primaryKey: true,default: ()=>crypto.randomUUID()})id!: string
@Field({unique: true})username!: string
@Field({default: UserRole.Guest})role!: UserRole
@Field({nullable: true})email?: boolean
@Field({default: 0})balance!: number}

And can be interacted with as follows:

// With typing support!constuser=awaitUser.create({username: 'Joe',balance: 42,})constallUsers=awaitUser.orderBy('id').all()constotherUser=awaitUser.filter({username: 'Carlos',}).first()constallAdmins=awaitUser.filter({role: UserRole.Admin,}).all()// Filters are more powerful than just checking valuesconsteveryoneInDebt=awaitUser.filter({balance: (b)=>b<-1}).all()awaitotherUser.remove()

Installation

  1. Install the npm package: npm install @d34d/w-orm reflect-metadata

  2. reflect-metadata needs to be imported somewhere in the global space of your app (eg. index.ts): import "reflect-metadata"

  3. Finally the following needs to be enabled in your tsconfig.json

{
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}

Table definition

Tables are defined as classes extending the Model class and using the Field decorator:

classUserextendsModel{// Primary key and generator function
@Field({primaryKey: true,default: ()=>crypto.randomUUID()})id!: string// Unique constraint
@Field({unique: true})username!: string// Static default (equivalent to `() => "guest"`)
@Field({default: "guest"})role!: string// nullable field, any other field will throw an error if not defined
@Field({nullable: true})email?: boolean}

A Field has the following parameters:

  • primaryKey: Whether the field is the primary key of the model, if multiple fields are marked as primary key, their combination will be the key.

    Important note: A limitation in IndexedDB makes it so the primary key can't be changed once the table is created and W-ORM will throw an error, a way to circumvent this is explained in migrations.

  • unique: Whether the field has an unique constraint. This will be enforced by the database.
  • nullable: Whether the field can be null/undefined, primary keys cannot be nullable.
  • default: The default value of the field, it can be a value or a function that returns the value.
  • index: Whether the field should be indexed, it is recommended to keep it unless the type isn't indexable (eg. a Blob).

More info in the API documentation.

Query system

All queries start from your Model class:

// Get with primary keyconsttable=awaitUser.get(1)// Get allconstallTables=awaitUser.all()// Get with filterconsttables=awaitUser.filter({name: 'John'}).first()// Get with advanced filter (user provided function)consttables2=awaitUser.filter({name: (n)=>n.includes('Ruiz')}).first()// Get with filter and orderconsttables3=awaitUser.filter({name: 'John'}).orderBy('-name').first()// Create a new entryconstnewUser=awaitUser.create({name: 'John'})// Update an entrynewUser.name='Jane'// or with typing supportnewUser.update({name: 'Jane'})// Commit changesawaitnewUser.save()// Delete an entryawaitnewUser.delete()

More info in the API documentation.

Transactions

Sometimes DB operations are meant to be executed as a "bundle", so that they either all pass or fail together.

Transactions allow us to implement this, with automatic rollbacks on error. And even if you don't need this, there are performance benefits to using transactions.

The tables that a transaction will interact with need to be explicitly defined, as write transactions will lock those until the transaction is over.

awaitTransaction('readwrite',[User],async(tx)=>{constnewUser=awaitUser.create({name: 'John Doe'},tx)constgetUser=awaitUser.get(newUser.id,tx)// Any error thrown in the callback will abort the transaction, this will rollback any changes madethrownewError('rollback')// If no error is thrown, the transaction will be committed})

Important note: Because of a limitation in the IndexedDB API, the transaction will be automatically committed if we wait for any non-transactional operation. (e.g. fetching some data from the network).

More info in the API documentation.

Migration system

Sometimes, changes to the way existing data is stored are required for an update, to cope with this W-ORM provides an intuitive migration system.

Migrations are defined as list of functions to be executed, depending on the current and target DB versions. The key is the target version number, and the value is the migration callback.

Eg. { 2: (migration) => { ... } } will execute the migration callback when the current database version is smaller than 2.

A migration callback receives a MigrationContext object as its only argument. This object contains a transaction to be used for the migration.

It is expected for the callback to create Model classes that represent the table's state in between these two versions. The fields aren't actually used by W-ORM in this scenario, and only serve to improve the typing within the migration.

The Model methods can be then used to manipulate the data. It is very important to use the transaction provided by the migration context, otherwise the migration will hang forever.

constmigrations: MigrationList={2: async(migration)=>{classUserextendsModel{id!: numbername!: string}constusers=awaitUser.all()for(constuserofusers){user.name=`${user.id} name`awaituser.save(migration.tx)}// Or with the `forEach method`awaitUser.forEach(async(instance,tx)=>{instance.name=`${instance.id} name`awaitinstance.save(tx)},migration.tx)constspecificUser=awaitUser.get(69,migration.tx)awaitspecificUser?.delete(migration.tx)},}

More info in the API documentation.

About

IndexedDB ORM

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages