{"id":15368090,"url":"https://github.com/iyobo/pouchorm","last_synced_at":"2026-03-06T23:32:35.059Z","repository":{"id":36712354,"uuid":"229822027","full_name":"iyobo/pouchorm","owner":"iyobo","description":"The definitive ORM for working with PouchDB. Native support for Typescript.","archived":false,"fork":false,"pushed_at":"2025-03-07T13:15:35.000Z","size":2089,"stargazers_count":50,"open_issues_count":13,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-28T14:17:22.747Z","etag":null,"topics":["class-validator","database","orm","pouchdb","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iyobo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-12-23T20:58:32.000Z","updated_at":"2025-08-04T09:51:58.000Z","dependencies_parsed_at":"2024-10-16T09:21:44.981Z","dependency_job_id":"ddc73904-f341-4474-bb3d-c8d608d15aa5","html_url":"https://github.com/iyobo/pouchorm","commit_stats":{"total_commits":139,"total_committers":5,"mean_commits":27.8,"dds":"0.16546762589928055","last_synced_commit":"0ad082a7c0bdee75647f64ef5742d90de1967ec9"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/iyobo/pouchorm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iyobo%2Fpouchorm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iyobo%2Fpouchorm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iyobo%2Fpouchorm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iyobo%2Fpouchorm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iyobo","download_url":"https://codeload.github.com/iyobo/pouchorm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iyobo%2Fpouchorm/sbom","scorecard":{"id":499127,"data":{"date":"2025-08-11","repo":{"name":"github.com/iyobo/pouchorm","commit":"983dc05e5ac2f026b69268cf96668d2e129cd10f"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.1,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/main.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/iyobo/pouchorm/main.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/main.yml:60: update your workflow using https://app.stepsecurity.io/secureworkflow/iyobo/pouchorm/main.yml/master?enable=pin","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":6,"reason":"4 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T21:21:01.068Z","repository_id":36712354,"created_at":"2025-08-19T21:21:01.068Z","updated_at":"2025-08-19T21:21:01.068Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30203353,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T19:07:06.838Z","status":"ssl_error","status_checked_at":"2026-03-06T18:57:34.882Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["class-validator","database","orm","pouchdb","typescript"],"created_at":"2024-10-01T13:28:15.407Z","updated_at":"2026-03-06T23:32:35.028Z","avatar_url":"https://github.com/iyobo.png","language":"TypeScript","readme":"# PouchORM\n\n[![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)\n\u003cimg src=\"https://raw.githubusercontent.com/iyobo/pouchorm/master/coverage.svg\"\u003e\n\n\n\nThe definitive ORM for working with PouchDB.\n\nThe Pouch/Couch database ecosystem is a great choice for client-side products that need the complex \n(and seemingly oxymoronic) sibling-features of Offline-First **and** Realtime collaboration.\n\nBut the base pouchDB interface is rather bare and oft-times painful to work with. That's where PouchORM comes in. \n\nPouchORM does a lot of the heavy lifting for you and makes it easy to get going with PouchDB so \nyou can focus on your data... not the database.\n\n## Highlights\n- Typescript is a first class citizen.\n  - Will work with raw javascript, but you'll be missing out on the cool Typescript dev perks.\n- Introduces the concept of *Collections* to pouchdb\n  - Multiple collections in a single Database\n  - Multiple collections in multiple Databases\n- Supports web, electron, react-native, and anything else pouchdb supports.\n- Supports optional class validation\n\n\n## To install\n`npm i pouchorm`\n\nor if you prefer yarn:\n`yarn add pouchorm`\n\nWhen using the optional class validation, also install `class-validator` as a dependency of your project using `npm` or `yarn`.\n\n## Changelog\n- v2.0.2\n  - Added optional ID generics i.e PouchCollection\u003cT,IDType\u003e, IModel\u003cIDType\u003e, and PouchModel\u003cIDType\u003e\n- v2.0.0\n  - feat: changed meta name `$updatedBy` to simply `$by` to conserve space.\n- v1.6.0\n  - feat: Added simplified audit trace, specified by `PouchORM.setUserId(...)`.\n- v1.5.0\n  - feat: Added ORM support for managing syncing between multiple databases\n- v1.3\n  - feat: Added Delta sync support\n\n## How to Use\n\nConsider this definition of a model and it's collection.\n```typescript\n// Person.ts\n\n    import {IModel, PouchCollection, PouchORM} from \"pouchorm\";\n    PouchORM.LOGGING = true; // enable diagnostic logging if desired\n    \n    export interface IPerson extends IModel {\n        name: string;\n        age: number;\n        otherInfo: Record\u003cstring, unknown\u003e;\n    }\n    \n    export class PersonCollection extends PouchCollection\u003cIPerson\u003e {\n        \n        // Optional. Override to define collection-specific indexes.\n        async beforeInit(): Promise\u003cvoid\u003e {\n            \n            await this.addIndex(['age']); // be sure to create an index for what you plan to filter by.\n        }\n\n        // Optional. Overide to perform actions after all the necessary indexes have been created.\n        async afterInit(): Promise\u003cvoid\u003e {\n           \n        }\n    \n    }\n    \n```\n\n`IModel` contains the meta fields needed by PouchDB and PouchORM to operate so every model interface definition \nneeds to extend it. Only supports the same field types as pouchDB does.\n\n`PouchCollection` is a generic abstract class that should be given your model type. \nThis helps it guide you later and give you suggestions of how to work with your model.\n\nIn the case that you want the syntactic sugar of classing your models, or you want to use class validation,\n`PouchModel` is a generic class implementation of `IModel` that can be extended.\n```typescript\nexport class Person extends PouchModel\u003cPerson\u003e {\n    @IsString()\n    name: string\n\n    @IsNumber()\n    age: number\n\n    otherInfo: { [key: string]: any };\n}\n\nexport class PersonCollection extends PouchCollection\u003cPerson\u003e {\n...\n```\n\nIf you need to do things before and after initialization, you can override the async hook functions: `beforeInit` \nor `afterInit`;\n\nNow that we have defined our **Model** and a **Collection** for that model, Here is how we instantiate collections.\nYou should probably define and export collection instances somewhere in your codebase that you can easily import \nanywhere in your app.\n       \n```typescript\n\n    // instantiate a collection by giving it the dbname it should use\n    export const personCollection: PersonCollection = new PersonCollection('db1');\n\n    // Another collection. Notice how it shares the same dbname we passed into the previous collection instance.\n    export const someOtherCollection: SomeOtherCollection = new SomeOtherCollection('db1'); \n    \n    // In case we needed the same model but for a different database\n    export const personCollection2: PersonCollection = new PersonCollection('db2');\n\n```\n\nFrom this point:\n - We have our definitions\n - We have our collection instances\n \nWe are ready to start CRUDing!\n\n```typescript\n    import {personCollection} from '...'\n\n    // Using collections\n    let somePerson: IPerson = {\n        name: 'Basket Mouth',\n        age: 99,\n    }\n    let anotherPerson: IPerson = {\n        name: 'Bovi',\n        age: 45,\n    }\n\n    somePerson = await personCollection.upsert(somePerson);\n    anotherPerson = await personCollection.upsert(anotherPerson);\n    \n    // somePerson has been persisted and will now also have some metafields like _id, _rev, etc.\n\n    somePerson.age = 45;\n    somePerson = await personCollection.upsert(somePerson);\n\n    // changes to somePerson has been persisted. _rev would have also changed.\n\n    const result: IPerson[] = await personCollection.find({age: 45})\n    \n    // result.length === 2\n\n```\n\n## PouchCollection instance API reference\nConsider that `T` is the provided type or class definition of your model.\n\n### Constructor\n`new Collection(dbname: string, opts?: PouchDB.Configuration.DatabaseConfiguration, validate: ClassValidate = ClassValidate.OFF)`\n\n### Methods\n- `find(criteria: Partial\u003cT\u003e): Promise\u003cT[]\u003e`\n- `findOrFail(criteria: Partial\u003cT\u003e): Promise\u003cT[]\u003e`\n- `findOne(criteria: Partial\u003cT\u003e): Promise\u003cT\u003e`\n- `findOneOrFail(criteria: Partial\u003cT\u003e): Promise\u003cT\u003e`\n- `findById(_id: string): Promise\u003cT\u003e`\n- `findByIdOrFail(_id: string): Promise\u003cT\u003e`\n\n- `removeById(id: string): Promise\u003cvoid\u003e`\n- `remove(item: T): Promise\u003cvoid\u003e`\n\n- `upsert(item: T, deltaFunc?: (existing: T) =\u003e T): Promise\u003cT\u003e`\n\n- `bulkUpsert(items: T[]): Promise\u003c(Response|Error)[]\u003e`\n- `bulkRemove(items: T[]): Promise\u003c(Response|Error)[]\u003e`\n\n## Class Validation\nClass validation brings the power of strong typing and data validation to PouchDB.\n\nThe validation uses the `class-validator` library, and should work anywhere that PouchDB works. This can\nbe turned on at the global PouchORM level using `PouchORM.VALIDATE` or at the collection level when creating\na new instance of PouchCollection.\n\nBy default, `upsert` calls `PouchORM.getClassValidator()` when validation is turned on. This dynamically\nimports to `PouchORM.ClassValidator` with the full instance of the required library. The method can also be\ncalled at any time so that class validation methods, decorators, and so on may used your application without\nthe need to statically import the library. **However**, if `class-validator` has not been installed to\n`node_modules`, this **will** crash PouchORM when `PouchORM.getClassValidator()` is called and/or you attempt\nto use `PouchORM.ClassValidator`.\n\nFor complete details and advanced usage of `class-validator`, see their [documentation](https://github.com/typestack/class-validator).\n\n## PouchORM metadata\n\nPouchORM adds some metadata fields to each documents to make certain features possible.\nKey of which are `$timestamp` and `$collectionType`.\n\n### $timestamp\n\nThis gets updated with a unix timestamp upon upserting a document. This is also auto-indexed for time-sensitive ordering\n(i.e so items don't show up in random locations in results each time, which can be disconcerting)\n\n### $collectionType\n\nThere is no concept of tables or collections in PouchDB. Only databases. This field helps us differentiate what\ncollection each document belongs to. This is also auto-indexed for your convenience.\n\n### $by (v1.6.x)\n\nPouchORM can help you append a userId to each originating change to specify who changed a document last.\nSimply use `PouchORM.setUserId(...)` to specify who the local/active user is, and PouchORM will put that id here.\nIf this is not set, this field will be `...`\n\nIf you need more stringent audit log capabilities, that's something you should implement for your application.\n\n## Custom ID generation\n\nYou can control the way IDs are generated for new items. Just define the `idGenerator` function property in a\ncollection object. This can be a normal or async function that returns a string.\n\n```typescript\nimport {personCollection} from '...'\n\n\npersonCollection.idGenerator = (item) =\u003e {\n  return 'randomIdString';\n};\n\nconst p = await personCollection.upsert({...})\np._id === 'randomIdString' // true\n\n```\n\nYou can also do:\n\n```typescript\npersonCollection.idGenerator = async (item) =\u003e {\n  const anotherString = await someAsyncIDStringBuilder()\n  return anotherString;\n};\n\n// or better yet, cleanly override the property in the class for consistency\n\nexport class PersonCollection extends PouchCollection\u003cIPerson\u003e {\n\n  // override\n  async idGenerator(){\n    return 'randomIdString';\n  }\n}\n\n```\n\n## Installing PouchDB plugins\n\nYou can access the base PouchDB module used by PouchORM with `PouchORM.PouchDB`. You can install plugins you need with\nthat e.g `PouchORM.PouchDB.plugin(...)`. PouchORM already comes with the plugin `pouchdb-find` which is essential for\nany useful querying of the database.\n\n## Accessing the raw pouchdb database\n\nEvery instance has a reference to the internally instantiated db `collectionInstance.db` that you can use to reference\nother methods of the raw pouch db instance e.g `personCollection.db.putAttachment(...)`.\n\nYou can use this for anything that does not directly involve accessing documents e.g adding an attachment is fine.\nBut caution must be followed when you want to use this to manipulate a document directly, as pouch orm marks documents with \nhelpful metadata it uses to enhance your development experience, particularly $timestamp and $collectionType. \n \nIt is generally better to rely on the exposed functions in your collection instance.\n\nIf you want more pouchdb feature support, feel free to open an issue. This library is also very simple \nto grok, so feel free to send in a PR! \n\n## Deleting the Database\n\n```\nimport {PouchORM} from 'pouchorm'\n...\nPouchORM.deleteDatabase(dbName: string)\n```\nIt goes without saying that this cannot be undone, so be careful with this!\nAlso, 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.\n\n## Realtime Sync!\n\nLast but not least, PouchDB is all about sync. \nYou could always access the native Pouch DB object and run sync operations.\n\nBut as of v1.5, some sugar has been added to make this a simplified PouchORM experience as well.\n\nIntroducing `PouchORM.startSync(fromPath, toPath, opts)` where either paths could \nbe local paths/names or a remote db url path. Within `opts`, you can specify callbacks that trigger upon specific events\nduring the realtime sync e.g `onChange`, `onError`,`onStart`, etc. Have a look at the reference.\n\nYou 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.\n\n## Supporting the Project\nIf you use PouchORM and it's helping you do awesome stuff, be a sport and  \u003ca href=\"https://www.buymeacoffee.com/iyobo\" target=\"_blank\"\u003e\u003cimg src=\"https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png\" alt=\"Buy Me A Coffee\" style=\"height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;\" \u003e\u003c/a\u003e or \u003ca href=\"https://www.patreon.com/bePatron?u=19661939\" data-patreon-widget-type=\"become-patron-button\"\u003eBecome a Patron!\u003c/a\u003e. PRs are also welcome.\nNOTE: Tests required for new PR acceptance. Those are easy to make as well.\n   \n# Contributors\n\n- Iyobo Eki\n- Aaron Huggins\n","funding_links":["https://www.buymeacoffee.com/iyobo","https://www.patreon.com/bePatron?u=19661939"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiyobo%2Fpouchorm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiyobo%2Fpouchorm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiyobo%2Fpouchorm/lists"}