A typescript ORM that uses annotation and classes to describe the database
Simply use an interface to describe the document schema, with at least an _id
string field
The entity class needs to extend BaseEntity
and requires the SlothEntity annotation (passing the database name).
@SlothEntity('students')
class StudentEnt extends BaseEntity<IStudent> {
Add your document fields to the entity class and decorate them using SlothField
. The assigned value will be used as a default value.
@SlothField()
age: number = 18
It is common practice to generate string URIs from the other document values and use it as an _id
or on other indices for easier sorting and relationship description (especially oneToMany). The SlothURI decorator takes at least two arguments: the first one is the root, which is a constant string value. Using the database name when not describing relation is recommended. For example students/john-doe
has the students
root, but does not describe any relationship. If your document belongs to a parent document then a root that includes all documents types would be recommended, for example university
would cover students, marks and courses. The other values are field names, included in you document, to be used to build the URI in the following order. Each specified field will then be stringified and slugified using toString()
and limax
.
For example:
@SlothURI('students', 'surname', 'name')
_id: string = ''
Please note we are assigning a default value to the _id
field that will get ignored.
This is the equivalent of a students/:surname/:name
DocURI.
A PouchFactory is a simple function that returns a PouchDB instance for a given database name. Every function in SlothDatabase
except withRoot
requires as an argument a PouchFactory. Entities are attached a PouchFactory in the constructor, so the entity functions (save()
, remove()
, etc) does not require a factory. A simple factory would be (name: string) => new PouchDB(name)
const author1 = Author.create(factory, {...})
await author.save()
author.age = 42
await author.save()
await author.remove()
SlothDB supports for now one type of relationship: belongsTo/oneToMany (which is the same relationship, but with a different perspective).
The annotation SlothRel
can be used on the field that describes a belongsTo relationship, that-is-to-say the field value is a string representing the parent document _id
field. The SlothField decorator is not usable with this annotation. If the target field is included in SlothURI, then the string value of this field (which is the _id
of the parent document) will have its root removed in order to include it in the URI. The value is not slugified using limax, so /
are not escaped. For example students/mit/john-doe
will become mit/john-doe
and a mark URI for this student would become marks/mit/john-doe/chemistry/2018-04-20
whereas the original URI has only 3 parts (student, course, date).
To describe a belongsTo relationship you can use SlothRel with a belongsTo
object:
@SlothRel({belongsTo: () => Student})
student_id: string = ''
The belongsTo
value is just a simple function that returns the parent SlothDatabase instance, to avoid circular dependency conflicts.
If the cascade
option is not present or true
, removing all child document of a single parent will also remove the parent.
The annotation SlothRel can also be used on a non-document field, with the hasMany
function, which returns the SlothDatabase instance of the child entity. The target field is a function that returns a child instance. This function should null, the annotation will replace it with an impl:
@SlothRel({ hasMany: () => Album })
albums: () => Album
The SlothRel uses the withRoot
function of SlothDatabase
which return a SlothDatabase that prefixes the startkey argument of the allDocs calls with the current document _id
hence the id needs to be described using the same root and the first key of the child's _id
must be the parent id field.
The SlothView
annotation describes a CouchDB map function. It takes as an argument a function (doc, emit) => void
, the view name (default to by_<field name>
) and the optional design document identifier (default to views
). Please note that this function does not modify any behavior of the target, so the decorated field requires another decorator (like SlothField
or SlothURI
) and the choice of the decorated field is purely semantic and decorating another field will only change the view name. Depending on the typescript target, you might want to use es5 functions (avoid fat-arrow functions).
The SlothIndex
is a function that applies the SlothView decorator with emit(doc['${key}'].toString())
as a function to create a basic index on the decorated field.
The SlothDatabase
class takes as a third generic argument extending a string that describes the possible view values. The queryDocs
function then takes as an argument the string constrained by the generic parameter. It is then recommended to use an enum to identify views:
enum AuthorView {
byName = 'views/by_name'
byAge = 'views/by_age'
}
...
const Author = new SlothDatabase<IAuthor, AuthorEntity, AuthorView>(AuthorEntity)
const seniorAuthors = await Author.queryDocs(factory, AuthorView.byAge, 60, 130)
interface IAuthor {
_id: string,
name: string
}
@SlothEntity('authors')
class AuthorEntity extends BaseEntity<IAuthor> {
@SlothURI('library', 'author')
_id: string = ''
@SlothField()
name: string = 'Unknown'
}
export const Author = new SlothDatabase<IAuthor, AuthorEntity>(AuthorEntity)
interface IBook {
_id: string,
name: string,
author: string
}
export enum BookViews {
ByName = 'views/by_name'
}
@SlothEntity('books')
class BookEntity extend BaseEntity<IBook> {
@SlothURI('library', 'author', 'name')
_id: string = ''
@SlothIndex()
@SlothField()
name: string = 'Unknown'
@SlothRel({belongsTo: Author})
author: string = 'library/unknown'
}
export const Book = new SlothDatabase<IBook, BookEntity, BookViews>(BookEntity)
Then to use
const jrrTolkien = Author.create(factory, {name: 'JRR Tolkien'})
jrrTolkien._id === 'library/jrr-tolkien'
jrrTolkien.name === 'JRR Tolkien'
await jrrTolkien.exists() === false
await jrrTolkien.save()
await jrrTolkien.exists() === true
const lotr = Book.create(factory, {name: 'The Lord Of The Rings', author: jrrTolkien._id})
lotr._id === 'library/jrr-tolkien/the-lord-of-the-rings'
const golding = await Author.put(factory, {name: 'William Golding'})
await golding.exists() === true
await Book.put(factory, {name: 'The Lord of The Flies', author: golding._id})
const booksStartingWithLord = await Author.queryDocs(factory, BookViews.ByName, 'The Lord of The')
booksStartingWithLord.length === 2
npm t
: Run test suitenpm start
: Run npm run build
in watch modenpm run test:watch
: Run test suite in interactive watch modenpm run test:prod
: Run linting and generate coveragenpm run build
: Generate bundles and typings, create docsnpm run lint
: Lints codenpm run commit
: Commit using conventional commit style (husky will tell you to use it if you haven't :wink:)Specify a database factory, a simple function that returns a database This is useful for circular dependency
Specify that no parent should have no children, so that whenever deleting a child entity the parent is automatically deleted as well if it's an only child
Describes a oneToMany relation where the entity specified here is the child entity, and the entity owner is the parent
Specifies that whenever removing the parent entity, the children should get removed as well
Defaults to true, and recommend keeping it this way
Specify a database factory, a simple function that returns a database This is useful for circular dependency
An relation descriptor, either hasMany or belongsTo
A function that listens for changes actions, can be a redux dispatch
This decorator is used to mark classes that will be an entity, a document This function, by extending the constructor and defining this.sloth property effectively allows the usage of other property decorators
The database schema
The database name for this entity
SlothField decorator is used to mark a specific class property as a document key. This introduces a few behaviors:
the value type
specifies the document key name, default to prop key name
Creates an index for a field. It's a view function that simply emits the document key
the decorator to apply on the field
SlothRel is used to indicate that a specific string property corresponds to another entity identifier. The possible relations are:
the relation description
The SlothURI decorator describes a class parameter that has no intrisic value but which value depends on the other properties described as a path.
The path has a root value, which is a constant and should be either the database name
pluralized, or a namespace. For example in library management system, the root could be
either library
or books
. It is recommended to use a namespace for relational
databases and the database name for orphans.
The path components, following the root, should be slugified properties of the entity. If the path needs to include another entity identifier, as often it needs to be in relational database, then the root of the other entity id should be omitted, but path separator (/) should NOT be escaped;, even in URLs. The path components are described using their property names.
the document schema
the URI root
key names to pick from the document
Creates a view for a field. This function does not modify the behavior of the current field, hence requires another decorator such as SlothURI or SlothField. The view will be created by the SlothDatabase
the view function, as arrow or es5 function
the decorator to apply on the field
Extract the ProtoData from a class prototype, possibly creating it if needed
the object to extract data from
create the protoData if it is undefined
Extract sloth data from an entity instance, throwing an exception if it can't be found
the entity schema
the class instance to extract from
Generated using TypeDoc
Describes a manyToOne or oneToOne relation where the entity specified here is the parent and the entity owner is the child