beast-orm
TypeScript icon, indicating that this package has built-in type declarations

1.1.5 • Public • Published

Beast ORM

ORM for accessing indexedDB and localStorage.

DBMS Support

  • IndexedDB
  • Sqlite (Will be publish in version 2.0.0)
  • localstorage

Details

  • The indexedDB implementation runs multiple request in a single transaction

A promise base api implementation for accessing for accessing indexedDB

Create model

A model is a representation of a database table. Feel free to place your models anywhere that can be exported

import { models } from 'beast-orm';

class User extends models.Model {

  userId = models.AutoField({primaryKey:true})
  username = models.CharField({maxLength: 100})
  email = models.CharField({blank: true, maxLength: 100})
  age = models.IntegerField()
}

Register model

Once you’ve register your data models, automatically gives you a database-abstraction API for accessing indexedDB as a promise base api implementation that lets you create, retrieve, update and delete objects. Models that belongs to the same database should be register at the same time.

import { models } from 'beast-orm';
import { User } from './models/user.js';

models.register({
  databaseName: 'tutorial',
  version: 1,
  type: 'indexedDB',
  models: [User]
})

Creating objects

An instance of that class represents a particular record in the database table

const user = await User.create({username:'kobe', email:'kobe.bryant@lakers.com'})

To add multiple records in one go

const users = [
  {username:'kobe', email:'kobe.bryant@forever.com', age: 30},
  {username:'michael', email:'michael.jackson@forever.com', age: 30}
]

const users = await User.create(users)

Saving changes to objects

this example changes its name and updates its record in the database

const u1 = await User.get({userId:1})
u1.username = 'New name'
u1.save()

Retrieving objects

The query is not executed as soon as the function is called

Retrieving all objects

The simplest way to retrieve objects from a table is to get all of them. To do this, use the all()

User.all()

Retrieving specific objects with filters

Filter - returns objects that match the given lookup parameters.

get - return object that match the given lookup

parameters.

const user = await User.get({username:'kobe'})

console.log(user.username) // kobe

get only works with unique fields

Filter can have complex lookup parameters

const users = await User.filter({age:10}).execute()

Field lookup

  • gt - greater
  • gte - greater or equal
  • lt - less
  • lte - less or equal
  • not - different

Example:

const users = await User.filter({age__gt: 10}).execute()
// sql Select * from User Where age > 10

const users = await User.filter({age__gt: 10, age__lt: 50}).execute()
// sql Select * from User Where age > 10 AND  age < 50

const users = await User.filter({age: 10, age__lt: 50}, {username: 'kobe'}).execute()
// sql Select * from User Where age = 10 AND  age < 50 OR username = 'kobe'

const users = await User.filter({age__not: 10}, [{age: 20},{username:'james'}]).execute()
// sql Select * from User Where not age = 10  OR (age = 20 OR username = 'james')

Saving changes to objects

To save changes to an object that’s already in the database, use save().

  const user = await User.get({username:'kobe'})
  user.username = 'james'
  user.save()

Updating multiple objects at once

Sometimes you want to set a field to a particular value for all the objects You can do this with the update() method. For example:

  User.filter({age:10}).update({age:11})

Deleting objects

The delete method, conveniently, is named delete(). This method immediately deletes the object

  User.delete()

You can also delete objects in bulk. Every QuerySet has a delete() method, which deletes all members of that QuerySet.

For example, this deletes all User objects with a age 40, and returns the number of objects deleted.

  User.filter({age: 40}).delete()


ArrayField

A field for storing lists of data. Most field types can be used, and you pass another field instance. You may also specify a size. ArrayField can be nested to store multi-dimensional arrays.

If you give the field a default, ensure it’s a callable such as list (for an empty default) or a callable that returns a list (such as a function). Incorrectly using default:[] creates a mutable default that is shared between all instances of ArrayField.

const { ArrayField } = models.indexedDB.fields

class ChessBoardUser extends models.Model {
  board = ArrayField({
    field: ArrayField({
      field: models.CharField({maxLength:10, blank:true}),
      size:8,
    }),
    size:8,
  })
}

models.register({
  databaseName: 'tutorial-ArrayField',
  version: 1,
  type: 'indexedDB',
  models: [ChessBoardUser]
})

// valid
ChessBoardUser.create({
  board: [
    ['01','02','03','04','05','06','07','08'],
    ['21','22','23','24','25','26','27','28'],
    ['31','32','33','34','35','36','37','38'],
    ['41','42','43','44','45','46','47','48'],
    ['51','52','53','54','55','56','57','58'],
    ['61','62','63','64','65','66','67','68'],
    ['71','72','73','74','75','76','77','78'],
    ['81','82','83','84','85','86','87','88'],
  ]
})


Querying ArrayField

There are a number of custom lookups and transforms for ArrayField. We will use the following example model:

const { ArrayField } = models.indexedDB.fields

class Post extends models.Model {
  name = models.CharField({maxLength=200})
  tags = ArrayField({field:models.CharField({maxLength:200}), blank:true})
}


contains

The contains lookup is overridden on ArrayField. The returned objects will be those where the values passed are a subset of the data. It uses the SQL operator @>. For example:

Post.create({name:'First post', tags:['thoughts', 'django']})
Post.create({name:'Second post', tags:['thoughts']})
Post.create({name:'Third post', tags:['tutorial', 'django']})

Post.filter({tags__contains:['thoughts']}).execute()
// [<Post: First post>, <Post: Second post>]

Post.filter({tags__contains:['django']}).execute()
// [<Post: First post>, <Post: Third post>]

Post.filter({tags__contains:['django', 'thoughts']}).execute()
// [<Post: First post>]


contained_by

This is the inverse of the contains lookup - the objects returned will be those where the data is a subset of the values passed. It uses the SQL operator <@. For example:

Post.create({name:'First post', tags:['thoughts', 'django']}).execute()
Post.create({name:'Second post', tags:['thoughts']}).execute()
Post.create({name:'Third post', tags:['tutorial', 'django']}).execute()

Post.filter({tags__contained_by:['thoughts', 'django']}).execute()
// <Post: First post>, <Post: Second post>]

Post.filter({tags__contained_by:['thoughts', 'django', 'tutorial']}).execute()
// [<Post: First post>, <Post: Second post>, <Post: Third post>]

overlap

Returns objects where the data shares any results with the values passed. Uses the SQL operator &&. For example:s the SQL operator <@. For example:

Post.create({name:'First post', tags:['thoughts', 'django']}).execute()
Post.create({name:'Second post', tags:['thoughts']}).execute()
Post.create({name:'Third post', tags:['tutorial', 'django']}).execute()

Post.filter({tags__overlap:['thoughts']}).execute()
// [<Post: First post>, <Post: Second post>]

Post.filter({tags__overlap:['thoughts', 'tutorial']}).execute()
// [<Post: First post>, <Post: Second post>, <Post: Third post>]


len

Returns the length of the array. The lookups available afterward are those available for IntegerField. For example:

Post.create({name:'First post', tags:['thoughts', 'django']})
Post.create({name:'Second post', tags:['thoughts']})

Post.filter(tags__len=1).execute()
// [<Post: Second post>]


Index transforms

Index transforms index into the array. Any non-negative integer can be used. There are no errors if it exceeds the size of the array. The lookups available after the transform are those from the base_field. For example:

Post.create({name:'First post', tags:['thoughts', 'django']})
Post.create({name:'Second post', tags:['thoughts']})

Post.filter({tags__0:'thoughts'}).execute()
// [<Post: First post>, <Post: Second post>]

Post.filter({tags__1__iexact:'Django'}).execute()
// [<Post: First post>]

Post.filter({tags__276:'javascript'}).execute()
// []


JSONField

Lookups implementation is different in JSONField, mainly due to the existence of key transformations. To demonstrate, we will use the following example model:

const { JsonField } = models.indexedDB.fields

class Dog extends models.Model {
  name = models.CharField({maxLength:200})
  data = JsonField({null: false})
}

models.register({
  databaseName: 'tutorial-JsonField',
  version: 1,
  type: 'indexedDB',
  models: [Dog]
})


Storing and querying for None

As with other fields, storing None as the field’s value will store it as SQL NULL. While not recommended, it is possible to store JSON scalar null instead of SQL NULL by using Value('null').

Whichever of the values is stored, when retrieved from the database, the Python representation of the JSON scalar null is the same as SQL NULL, i.e. None. Therefore, it can be hard to distinguish between them.

This only applies to None as the top-level value of the field. If None is inside a list or dict, it will always be interpreted as JSON null.

When querying, None value will always be interpreted as JSON null. To query for SQL NULL, use isnull:

Dog.create({name:'Max', data: null})  # SQL NULL.
// <Dog: Max>
Dog.create({name:'Archie', data:Value('null')})  # JSON null.
// <Dog: Archie>
Dog.filter({data:null}).execute()
//  [<Dog: Archie>]
Dog.filter({data=Value('null')}).execute()
//  [<Dog: Archie>]
Dog.filter({data__isnull:true}).execute()
//  [<Dog: Max>]
Dog.filter({data__isnull:false}).execute()
//  [<Dog: Archie>]

Key, index, and path transforms

To query based on a given dictionary key, use that key as the lookup name:

Dog.create({name:'Rufus', data: {
  'breed': 'labrador',
  'owner': {
    'name': 'Bob',
    'other_pets': [{
      'name': 'Fishy',
    }],
  },
}})

Dog.create({name:'Meg', data:{'breed': 'collie', 'owner': null}})
// <Dog: Meg>
Dog.filter({data__breed:'collie'}).execute()
// [<Dog: Meg>]

Multiple keys can be chained together to form a path lookup:

Dog.objects.filter({data__owner__name:'Bob'}).execute()
// [<Dog: Rufus>]

If the key is an integer, it will be interpreted as an index transform in an array:

Dog.objects.filter({data__owner__other_pets__0__name:'Fishy'}).execute()
// [<Dog: Rufus>]

If the key you wish to query by clashes with the name of another lookup, use the contains lookup instead.

To query for missing keys, use the isnull lookup:

Dog.objects.create({name:'Shep', data:{'breed': 'collie'}})

Dog.objects.filter({data__owner__isnull:true}).execute()
// [<Dog: Shep>]

Note

The lookup examples given above implicitly use the exact lookup. Key, index, and path transforms can also be chained with: icontains, endswith, iendswith, iexact, regex, iregex, startswith, istartswith, lt, lte, gt, and gte, as well as with Containment and key lookups.

contains

The contains lookup is overridden on JSONField. The returned objects are those where the given dict of key-value pairs are all contained in the top-level of the field. For example:

Dog.create({name:'Rufus', data:{'breed': 'labrador', 'owner': 'Bob'}})
// <Dog: Rufus>
Dog.create({name:'Meg', data:{'breed': 'collie', 'owner': 'Bob'}})
// <Dog: Meg>
Dog.create({name:'Fred', data:{}})
// <Dog: Fred>
Dog.filter({data__contains:{'owner': 'Bob'}}).execute()
// [<Dog: Rufus>, <Dog: Meg>]
Dog.filter({data__contains:{'breed': 'collie'}}).execute()
// [<Dog: Meg>]

contained_by

This is the inverse of the contains lookup - the objects returned will be those where the key-value pairs on the object are a subset of those in the value passed. For example:

Dog.create({name:'Rufus', data:{'breed': 'labrador', 'owner': 'Bob'}})
Dog.create({name:'Meg', data:{'breed': 'collie', 'owner': 'Bob'}})
Dog.create({name:'Fred', data:{}})

Dog.filter({data__contained_by:{'breed': 'collie', 'owner': 'Bob'}}).execute()
// [<Dog: Meg>, <Dog: Fred>]
Dog.filter({data__contained_by:{'breed': 'collie'}}).execute()
// [<Dog: Fred>]

has_key

Returns objects where the given key is in the top-level of the data. For example:

Dog.create({name:'Rufus', data:{'breed': 'labrador'}})
// [<Dog: Rufus>]
Dog.create({name:'Meg', data:{'breed': 'collie', 'owner': 'Bob'}})
// [<Dog: Meg>]
Dog.filter({data__has_key:'owner'}).execute()
// [<Dog: Meg>]

has_keys

Returns objects where all of the given keys are in the top-level of the data. For example:

Dog.create(name:'Rufus', data:{'breed': 'labrador'})
// [<Dog: Rufus>]
Dog.create({name:'Meg', data:{'breed': 'collie', 'owner': 'Bob'}})
// [<Dog: Meg>]
Dog.filter({data__has_keys:['breed', 'owner']})
// [<Dog: Meg>]

has_any_keys

Returns objects where any of the given keys are in the top-level of the data. For example:

Dog.create({name:'Rufus', data:{'breed': 'labrador'}})
// [<Dog: Rufus>]
Dog.create({name:'Meg', data:{'owner': 'Bob'}})
// [<Dog: Meg>]
Dog.filter({data__has_any_keys:['owner', 'breed']})
// [<Dog: Rufus>, <Dog: Meg>]

Many-to-many relationships

In this example, an Article can be published in multiple Publication objects, and a Publication has multiple Article objects:

class Publication extends models.Model {
  title = models.CharField({maxLength: 50})
}


class Article extends models.Model {
  headline = models.CharField({maxLength: 100})
  publication = models.ManyToManyField({model:Publication})
}

models.register({
  databaseName:'One-to-one-relationships',
  type: 'indexedDB',
  version: 1,
  models: [Publication, Article]
})

What follows are examples of operations that can be performed using the Python API facilities.

Create a few Publications:

  const  p1 = await Publication.create({title:'The Python Journal'})
  const  p2 = await Publication.create({title:'Science News'})
  const  p3 = await Publication.create({title:'Science Weekly'})

Create an Article:

  const a1 = await Article.create({headline:'lets you build web apps easily'})
  const a2 = await Article.create({headline:'NASA uses Python'})

Associate the Article with a Publication:

  await a1.publication_add([p1, p2])

Article objects have access to their related Publication objects:

  await a1.publication_all()

Article objects have access to their related Publication objects:

  await p1.article_set_all()

One-to-one relationships

In this example, a Place optionally can be a Restaurant:

class Place extends models.Model {
  name = models.CharField({maxLength: 50})
  address = models.CharField({maxLength: 50})
} 

class Restaurant extends models.Model  {
  place = models.OneToOneField({model:Place})
  servesHotDogs = models.BooleanField({default: false})
  servesPizza = models.BooleanField({default: false})
}


await models.register({
  databaseName:'jest-test'+ new Date().getTime(),
  type: 'indexedDB',
  version: 1,
  models: [Place, Restaurant]
})

What follows are examples of operations that can be performed using the Python API facilities.

Create a couple of Places:

  const p1 = await Place.create({name:'Demon Dogs', address:'944 W. Fullerton'})

Create a Restaurant. Pass the “parent” object as this object’s primary key:

  const r = await Restaurant.create({place:p1, servesHotDogs: false, servesPizza:false})

A Restaurant can access its place:

  const r = await p1.Restaurant()

A Place can access its restaurant, if available:

  const p = await await r.Place()

many-to-one relationships

class Reporter extends models.Model {
  firstName = models.CharField({maxLength: 50})
  lastName = models.CharField({maxLength: 50})
  email = models.CharField()
}

class Article extends models.Model {
  headline = models.CharField({maxLength: 50})
  pubDate = models.DateField()
  reporter = models.ForeignKey({model:Reporter})
}


await models.register({
  databaseName:'jest-test'+ new Date().getTime(),
  type: 'indexedDB',
  version: 1,
  models: [Reporter, Article]
})

What follows are examples of operations that can be performed using the Python API facilities.

Create a few Reporters:

const r1 = await Reporter.create({firstName: 'asdfsadf', lastName: 'asdfsd', email:'teste'})
const r2 = await Reporter.create({firstName: 'Peter', lastName: 'Maquiran', email:'teste'})

Create an Article:

  const a = await Article.create({headline:"This is a test", pubDate:'', reporter:r1})

Article objects have access to their related Reporter objects:

  const r1 = await a.Reporter()

Reporter objects have access to their related Article objects:

  const a = await await r1.article_setAll()

Add the same article to a different article set

  const a = await await r1.article_setAdd({headline:"This is a test", pubDate:''})

Reactive List

  class Person extends models.Model {
    username = models.CharField({})  
    age = models.IntegerField({blank:true})
  }
 
  models.migrate({
    databaseName:'jest-test',
    type: 'indexedDB',
    version: 1,
    models: [Person]
  })

Create a reactive List that update when a transaction is committed on the database.

  const PersonList = Person.ReactiveList((model)=> model.all())
  const PersonAge5List = Person.ReactiveList((model)=> model.filter({age: 5}).execute())

Get the value

  PersonList.value
// [<Person: Rufus>, <Person: Meg>]

unsubscribe the reactive list

  PersonList.unsubscribe()

Trigger transaction

  class Person extends models.Model {
    username = models.CharField({})  
  }
 
  models.migrate({
    databaseName:'jest-test',
    type: 'indexedDB',
    version: 1,
    models: [Person]
  })

Create a callback function that fire every time a commit is made in the Person

  let subscription = Person.transactionOnCommit( async () => {
    console.log('commit')
  })

unsubscribe

  subscription.unsubscribe()

Trigger { BEFORE | AFTER } { INSERT | UPDATE | DELETE}

coming soon




LocalStorage base api implementation.

Create model

A model is a representation of a database table. Feel free to place your models anywhere that can be exported

import { models } from 'beast-orm';

class Session extends models.LocalStorage {
  static username = ''  
  static userId = 55
  static token = ''
} 

Register model

Once you’ve register your data models, automatically gives you a abstraction API for accessing local storage base api implementation that lets you create, retrieve, update and delete object.

import { models } from 'beast-orm';
import { Session } from './models/user.js';

models.migrate({
  databaseName: 'tutorial',
  version: 1,
  type: 'indexedDB',
  models: [Session]
})

Creating objects or Update

Change Session and save the changes, it will create the key value to the local storage in case it doesn't exist or simple update the value

Session.username = 'kobe'
Session.userId = '1'
Session.token = 'fjif8382'
Session.save()

Deleting objects

The delete method, conveniently, is named delete(). This method immediately deletes the object and returns the number of objects deleted and a dictionary with the number of deletions per object type. Example:

  Session.delete()

Languages and Tools

git jest puppeteer typescript

Credits

📜 License

MIT © BackdoorTech

Package Sidebar

Install

npm i beast-orm

Weekly Downloads

25

Version

1.1.5

License

ISC

Unpacked Size

290 kB

Total Files

103

Last publish

Collaborators

  • backdoortech