Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/iyobo/pouchorm
The definitive ORM for working with PouchDB. Native support for Typescript.
https://github.com/iyobo/pouchorm
class-validator database orm pouchdb typescript
Last synced: 14 days ago
JSON representation
The definitive ORM for working with PouchDB. Native support for Typescript.
- Host: GitHub
- URL: https://github.com/iyobo/pouchorm
- Owner: iyobo
- License: mit
- Created: 2019-12-23T20:58:32.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2024-10-26T21:10:12.000Z (18 days ago)
- Last Synced: 2024-10-29T15:45:04.065Z (16 days ago)
- Topics: class-validator, database, orm, pouchdb, typescript
- Language: TypeScript
- Homepage:
- Size: 1.31 MB
- Stars: 42
- Watchers: 3
- Forks: 4
- Open Issues: 14
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# PouchORM
[![CI](https://github.com/iyobo/pouchorm/actions/workflows/main.yml/badge.svg?cacheBuster=1)](https://github.com/iyobo/pouchorm/actions/workflows/main.yml?cacheBuster=1)
The definitive ORM for working with PouchDB.
The Pouch/Couch database ecosystem is a great choice for client-side products that need the complex
(and seemingly oxymoronic) sibling-features of Offline-First **and** Realtime collaboration.But the base pouchDB interface is rather bare and oft-times painful to work with. That's where PouchORM comes in.
PouchORM does a lot of the heavy lifting for you and makes it easy to get going with PouchDB so
you can focus on your data... not the database.## Highlights
- Typescript is a first class citizen.
- Will work with raw javascript, but you'll be missing out on the cool Typescript dev perks.
- Introduces the concept of *Collections* to pouchdb
- Multiple collections in a single Database
- Multiple collections in multiple Databases
- Supports web, electron, react-native, and anything else pouchdb supports.
- Supports optional class validation## To install
`npm i pouchorm`or if you prefer yarn:
`yarn add pouchorm`When using the optional class validation, also install `class-validator` as a dependency of your project using `npm` or `yarn`.
## Changelog
- v2.0.2
- Added optional ID generics i.e PouchCollection, IModel, and PouchModel
- v2.0.0
- feat: changed meta name `$updatedBy` to simply `$by` to conserve space.
- v1.6.0
- feat: Added simplified audit trace, specified by `PouchORM.setUserId(...)`.
- v1.5.0
- feat: Added ORM support for managing syncing between multiple databases
- v1.3
- feat: Added Delta sync support## How to Use
Consider this definition of a model and it's collection.
```typescript
// Person.tsimport {IModel, PouchCollection, PouchORM} from "pouchorm";
PouchORM.LOGGING = true; // enable diagnostic logging if desired
export interface IPerson extends IModel {
name: string;
age: number;
otherInfo: { [key: string]: any };
}
export class PersonCollection extends PouchCollection {
// Optional. Override to define collection-specific indexes.
async beforeInit(): Promise {
await this.addIndex(['age']); // be sure to create an index for what you plan to filter by.
}// Optional. Overide to perform actions after all the necessary indexes have been created.
async afterInit(): Promise {
}
}
````IModel` contains the meta fields needed by PouchDB and PouchORM to operate so every model interface definition
needs to extend it. Only supports the same field types as pouchDB does.`PouchCollection` is a generic abstract class that should be given your model type.
This helps it guide you later and give you suggestions of how to work with your model.In the case that you want the syntactic sugar of classing your models, or you want to use class validation,
`PouchModel` is a generic class implementation of `IModel` that can be extended.
```typescript
export class Person extends PouchModel {
@IsString()
name: string@IsNumber()
age: numberotherInfo: { [key: string]: any };
}export class PersonCollection extends PouchCollection {
...
```If you need to do things before and after initialization, you can override the async hook functions: `beforeInit`
or `afterInit`;Now that we have defined our **Model** and a **Collection** for that model, Here is how we instantiate collections.
You should probably define and export collection instances somewhere in your codebase that you can easily import
anywhere in your app.
```typescript// instantiate a collection by giving it the dbname it should use
export const personCollection: PersonCollection = new PersonCollection('db1');// Another collection. Notice how it shares the same dbname we passed into the previous collection instance.
export const someOtherCollection: SomeOtherCollection = new SomeOtherCollection('db1');
// In case we needed the same model but for a different database
export const personCollection2: PersonCollection = new PersonCollection('db2');```
From this point:
- We have our definitions
- We have our collection instances
We are ready to start CRUDing!```typescript
import {personCollection} from '...'// Using collections
let somePerson: IPerson = {
name: 'Basket Mouth',
age: 99,
}
let anotherPerson: IPerson = {
name: 'Bovi',
age: 45,
}somePerson = await personCollection.upsert(somePerson);
anotherPerson = await personCollection.upsert(anotherPerson);
// somePerson has been persisted and will now also have some metafields like _id, _rev, etc.somePerson.age = 45;
somePerson = await personCollection.upsert(somePerson);// changes to somePerson has been persisted. _rev would have also changed.
const result: IPerson[] = await personCollection.find({age: 45})
// result.length === 2```
## PouchCollection instance API reference
Consider that `T` is the provided type or class definition of your model.### Constructor
`new Collection(dbname: string, opts?: PouchDB.Configuration.DatabaseConfiguration, validate: ClassValidate = ClassValidate.OFF)`### Methods
- `find(criteria: Partial): Promise`
- `findOrFail(criteria: Partial): Promise`
- `findOne(criteria: Partial): Promise`
- `findOneOrFail(criteria: Partial): Promise`
- `findById(_id: string): Promise`
- `findByIdOrFail(_id: string): Promise`- `removeById(id: string): Promise`
- `remove(item: T): Promise`- `upsert(item: T, deltaFunc?: (existing: T) => T): Promise`
- `bulkUpsert(items: T[]): Promise<(Response|Error)[]>`
- `bulkRemove(items: T[]): Promise<(Response|Error)[]>`## Class Validation
Class validation brings the power of strong typing and data validation to PouchDB.The validation uses the `class-validator` library, and should work anywhere that PouchDB works. This can
be turned on at the global PouchORM level using `PouchORM.VALIDATE` or at the collection level when creating
a new instance of PouchCollection.By default, `upsert` calls `PouchORM.getClassValidator()` when validation is turned on. This dynamically
imports to `PouchORM.ClassValidator` with the full instance of the required library. The method can also be
called at any time so that class validation methods, decorators, and so on may used your application without
the need to statically import the library. **However**, if `class-validator` has not been installed to
`node_modules`, this **will** crash PouchORM when `PouchORM.getClassValidator()` is called and/or you attempt
to use `PouchORM.ClassValidator`.For complete details and advanced usage of `class-validator`, see their [documentation](https://github.com/typestack/class-validator).
## PouchORM metadata
PouchORM adds some metadata fields to each documents to make certain features possible.
Key of which are `$timestamp` and `$collectionType`.### $timestamp
This gets updated with a unix timestamp upon upserting a document. This is also auto-indexed for time-sensitive ordering
(i.e so items don't show up in random locations in results each time, which can be disconcerting)### $collectionType
There is no concept of tables or collections in PouchDB. Only databases. This field helps us differentiate what
collection each document belongs to. This is also auto-indexed for your convenience.### $by (v1.6.x)
PouchORM can help you append a userId to each originating change to specify who changed a document last.
Simply use `PouchORM.setUserId(...)` to specify who the local/active user is, and PouchORM will put that id here.
If this is not set, this field will be `...`If you need more stringent audit log capabilities, that's something you should implement for your application.
## Custom ID generation
You can control the way IDs are generated for new items. Just define the `idGenerator` function property in a
collection object. This can be a normal or async function that returns a string.```typescript
import {personCollection} from '...'personCollection.idGenerator = (item) => {
return 'randomIdString';
};const p = await personCollection.upsert({...})
p._id === 'randomIdString' // true```
You can also do:
```typescript
personCollection.idGenerator = async (item) => {
const anotherString = await someAsyncIDStringBuilder()
return anotherString;
};// or better yet, cleanly override the property in the class for consistency
export class PersonCollection extends PouchCollection {
// override
async idGenerator(){
return 'randomIdString';
}
}```
## Installing PouchDB plugins
You can access the base PouchDB module used by PouchORM with `PouchORM.PouchDB`. You can install plugins you need with
that e.g `PouchORM.PouchDB.plugin(...)`. PouchORM already comes with the plugin `pouchdb-find` which is essential for
any useful querying of the database.## Accessing the raw pouchdb database
Every instance has a reference to the internally instantiated db `collectionInstance.db` that you can use to reference
other methods of the raw pouch db instance e.g `personCollection.db.putAttachment(...)`.You can use this for anything that does not directly involve accessing documents e.g adding an attachment is fine.
But caution must be followed when you want to use this to manipulate a document directly, as pouch orm marks documents with
helpful metadata it uses to enhance your development experience, particularly $timestamp and $collectionType.
It is generally better to rely on the exposed functions in your collection instance.If you want more pouchdb feature support, feel free to open an issue. This library is also very simple
to grok, so feel free to send in a PR!## Deleting the Database
```
import {PouchORM} from 'pouchorm'
...
PouchORM.deleteDatabase(dbName: string)
```
It goes without saying that this cannot be undone, so be careful with this!
Also, any loaded `PouchCollection` instances you still have will now throw the error "database is destroyed" if you try to run any DB access operations on them.## Realtime Sync!
Last but not least, PouchDB is all about sync.
You could always access the native Pouch DB object and run sync operations.But as of v1.5, some sugar has been added to make this a simplified PouchORM experience as well.
Introducing `PouchORM.startSync(fromPath, toPath, opts)` where either paths could
be local paths/names or a remote db url path. Within `opts`, you can specify callbacks that trigger upon specific events
during the realtime sync e.g `onChange`, `onError`,`onStart`, etc. Have a look at the reference.You can also cancel real-time sync by `PouchORM.stopSync(fromPath, toPath?)`. If the second parameter is null, it will stop all sync ops for that db regardless of destination.
## Supporting the Project
If you use PouchORM and it's helping you do awesome stuff, be a sport and or Become a Patron!. PRs are also welcome.
NOTE: Tests required for new PR acceptance. Those are easy to make as well.
# Contributors- Iyobo Eki
- Aaron Huggins