{"id":19273989,"url":"https://github.com/rintoj/hypergraph-storage","last_synced_at":"2025-04-17T05:22:52.099Z","repository":{"id":176814922,"uuid":"659163515","full_name":"rintoj/hypergraph-storage","owner":"rintoj","description":"TypeORM based repository implementation for accessing data storage.","archived":false,"fork":false,"pushed_at":"2025-02-27T11:45:28.000Z","size":791,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-29T06:01:42.593Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/rintoj.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":"2023-06-27T09:05:23.000Z","updated_at":"2025-02-27T11:44:43.000Z","dependencies_parsed_at":"2024-11-09T20:44:53.186Z","dependency_job_id":"29b75bf6-1c4c-4a75-829d-111dea54e7ae","html_url":"https://github.com/rintoj/hypergraph-storage","commit_stats":null,"previous_names":["rintoj/hypergraph-storage"],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rintoj%2Fhypergraph-storage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rintoj%2Fhypergraph-storage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rintoj%2Fhypergraph-storage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rintoj%2Fhypergraph-storage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rintoj","download_url":"https://codeload.github.com/rintoj/hypergraph-storage/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249318895,"owners_count":21250433,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-11-09T20:44:45.423Z","updated_at":"2025-04-17T05:22:52.076Z","avatar_url":"https://github.com/rintoj.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hypergraph Storage\n\n- [Hypergraph Storage](#hypergraph-storage)\n  - [Install](#install)\n  - [Usage](#usage)\n  - [Usage with NestJS (Recommended)](#usage-with-nestjs-recommended)\n    - [Step 1: Configure the `StorageModule` as a Root Module](#step-1-configure-the-storagemodule-as-a-root-module)\n    - [Step 2: Configure Entities in Feature Modules](#step-2-configure-entities-in-feature-modules)\n    - [Step 3: Use Repositories in Your Services](#step-3-use-repositories-in-your-services)\n  - [Usage without NestJS](#usage-without-nestjs)\n    - [Step 1: Initialize the Data Source](#step-1-initialize-the-data-source)\n    - [Step 2: Define a Repository Class](#step-2-define-a-repository-class)\n    - [Step 3: Get an Instance of the Repository](#step-3-get-an-instance-of-the-repository)\n    - [Step 4: Use Dependency Injection with Libraries like `tsyringe`](#step-4-use-dependency-injection-with-libraries-like-tsyringe)\n  - [Fetch Records](#fetch-records)\n    - [find](#find)\n    - [findById](#findbyid)\n    - [findByIds](#findbyids)\n    - [findAll](#findall)\n    - [findOne](#findone)\n  - [Query Builder](#query-builder)\n    - [Query](#query)\n    - [PaginatedQuery](#paginatedquery)\n  - [Insert \\\u0026 Update](#insert--update)\n    - [save](#save)\n    - [saveMany](#savemany)\n    - [insert](#insert)\n    - [insertMany](#insertmany)\n    - [update](#update)\n    - [updateMany](#updatemany)\n  - [Count](#count)\n  - [Increment](#increment)\n  - [Delete \\\u0026 Restore](#delete--restore)\n  - [Using with Cloud Firestore](#using-with-cloud-firestore)\n    - [Using with NestJS](#using-with-nestjs)\n      - [Key Points:](#key-points)\n    - [Using without NestJS](#using-without-nestjs)\n      - [Key Points:](#key-points-1)\n    - [Additional Notes](#additional-notes)\n    - [Queries](#queries)\n    - [Modification API](#modification-api)\n  - [Using Cache](#using-cache)\n  - [TypeORM DataSource](#typeorm-datasource)\n  - [Testing](#testing)\n    - [Testing with firestore](#testing-with-firestore)\n\nThis is a package for accessing databases using TypeORM, that comes with the following benefits:\n\n- Built for TypeScript and typing support\n- Works best with GraphQL especially libraries like [TypeGraphQL](https://typegraphql.com/)\n- Comes with easy to use [Query](#query-builder) builder with elegant and convenient syntax with\n  typing support\n- Supports pagination through [PaginatedQuery](#paginatedquery) builder\n- Built on top of [TypeORM](https://typeorm.io/), hence comes with all the benefits that it\n  provides:\n  - Supports MySQL / MariaDB / Postgres / CockroachDB / SQLite / Microsoft SQL Server / Oracle / SAP\n    Hana / sql.js.\n  - Works in NodeJS / Browser / Ionic / Cordova / React Native / NativeScript / Expo / Electron\n    platforms.\n  - Entities and columns.\n  - Database-specific column types.\n  - Entity manager.\n  - Clean object relational model.\n  - Associations (relations).\n  - Eager and lazy relations.\n  - Uni-directional, bi-directional and self-referenced relations.\n  - Supports multiple inheritance patterns.\n  - Cascades.\n  - Indices.\n  - Transactions.\n  - Migrations and automatic migrations generation.\n  - Connection pooling.\n  - Replication.\n  - Using multiple database instances.\n  - Working with multiple databases types.\n  - Cross-database and cross-schema queries.\n  - Left and inner joins.\n  - Proper pagination for queries using joins.\n  - Query caching.\n  - Streaming raw results.\n  - Logging.\n  - Listeners and subscribers (hooks).\n  - Supports MongoDB NoSQL database.\n  - TypeScript and JavaScript support.\n  - ESM and CommonJS support.\n  - Produced code is performant, flexible, clean and maintainable.\n\n## Install\n\nUsing npm:\n\n```sh\nnpm install @hgraph/storage\n```\n\nUsing yarn:\n\n```sh\nyarn add @hgraph/storage\n```\n\n## Usage\n\nDefine entity class. See [this](docs/entities.md) for more examples.\n\n```ts\nimport { Repository } from '@hgraph/storage'\nimport { Column, PrimaryColumn } from 'typeorm'\n\n@Entity()\nclass User {\n  @PrimaryColumn()\n  id!: string\n\n  @Column()\n  name!: string\n\n  @Column()\n  username!: string\n\n  @Column({ nullable: true })\n  bio?: string\n\n  @Column({ nullable: true })\n  verified?: boolean\n\n  @Column({ nullable: true })\n  followers?: number\n}\n```\n\n## Usage with NestJS (Recommended)\n\nTo integrate the `StorageModule` into your NestJS application effectively, follow the steps below.\nThe `StorageModule` provides a convenient way to manage repositories and entities within your NestJS\necosystem. For more information on NestJS modules, refer to the\n[NestJS Module Documentation](https://docs.nestjs.com/modules).\n\n### Step 1: Configure the `StorageModule` as a Root Module\n\nBegin by setting up the `StorageModule` in your root module (commonly `AppModule`). This\nconfiguration initializes the storage layer and connects to the database using parameters from your\nenvironment configuration:\n\n```ts\nimport { Module } from '@nestjs/common'\nimport { StorageModule, RepositoryType } from '@hgraph/storage/nestjs'\nimport { AppController } from './app.controller'\nimport { UserModule } from './user/user.module'\nimport { AuthModule } from './auth/auth.module'\nimport config from './config'\n\n@Module({\n  imports: [\n    StorageModule.forRoot({\n      repositoryType: RepositoryType.TypeORM, // Specify the repository type (e.g., TypeORM).\n      url: config.DATABASE_URL, // Database connection URL.\n      type: config.DATABASE_TYPE as any, // Database type (e.g., PostgreSQL, MySQL).\n      synchronize: config.DB_SYNCHRONIZE, // Synchronize schema with the database.\n    }),\n    UserModule, // Import your feature modules.\n    AuthModule,\n  ],\n  controllers: [AppController],\n})\nexport class AppModule {}\n```\n\n### Step 2: Configure Entities in Feature Modules\n\nEach feature module should declare its entities to be managed by the `StorageModule`. This ensures\nthat the necessary database schema and repository are available within the scope of the feature\nmodule:\n\n```ts\nimport { Module } from '@nestjs/common'\nimport { StorageModule } from '@hgraph/storage/nestjs'\nimport { User } from './user.entity'\n\n@Module({\n  imports: [StorageModule.forFeature([User])], // Declare entities specific to this module.\n})\nexport class CustomModule {}\n```\n\n### Step 3: Use Repositories in Your Services\n\nOnce the `StorageModule` is configured, you can inject repositories into your services using the\n`@InjectRepo` decorator. This simplifies access to your database operations:\n\n```ts\nimport { Injectable } from '@nestjs/common'\nimport { InjectRepo, Repository } from '@hgraph/storage/nestjs'\nimport { User } from './user.entity'\n\n@Injectable()\nexport class UserService {\n  constructor(\n    @InjectRepo(User) // Inject the repository for the User entity.\n    private readonly userRepository: Repository\u003cUser\u003e,\n  ) {}\n\n  // Example method for retrieving all users.\n  async findAll(): Promise\u003cUser[]\u003e {\n    return this.userRepository.find()\n  }\n}\n```\n\nBy following these steps, you can seamlessly manage your application’s data layer using the\n`StorageModule` while adhering to NestJS’s modular architecture.\n\n## Usage without NestJS\n\nTo use this library independently of NestJS, follow these steps to initialize your data source,\nconfigure repositories, and integrate dependency injection if required.\n\n### Step 1: Initialize the Data Source\n\nYou can initialize the data source programmatically using the `initializeDataSource` function.\nSpecify the database type, connection URL, and synchronization settings:\n\n```ts\nimport { initializeDataSource } from '@hgraph/storage'\n\nawait initializeDataSource({\n  type: process.env.DATABASE_TYPE as any, // Specify the database type (e.g., postgres, mysql).\n  url: process.env.DATABASE_URL, // Database connection URL.\n  synchronize: process.env.DB_SYNCHRONIZE, // Synchronize schema with the database.\n})\n```\n\nAlternatively, use environment variables to configure the database connection. This approach\nsimplifies deployment and avoids hardcoding sensitive information:\n\n```sh\nDATABASE_TYPE=postgres\nDATABASE_URL=\u003cdatabase_type\u003e://\u003cusername\u003e:\u003cpassword\u003e@\u003chost\u003e:\u003cport\u003e/\u003cdatabase_name\u003e\nDATABASE_SYNCHRONIZE=\"true\"\n```\n\nThen initialize the data source with the specified entities:\n\n```ts\nawait initializeDataSource({\n  entities: [User], // Declare your application entities.\n})\n```\n\n### Step 2: Define a Repository Class\n\nCreate a custom repository class for managing your entities. This class extends the base\n`Repository` class and specifies the entity type:\n\n```ts\nimport { Repository } from '@hgraph/storage'\nimport { User } from './user.entity'\n\nclass UserRepository extends Repository\u003cUser\u003e {\n  constructor() {\n    super(User) // Initialize the repository with the User entity.\n  }\n}\n```\n\n### Step 3: Get an Instance of the Repository\n\nTo interact with the `UserRepository`, create an instance of the repository. This allows you to\nperform database operations:\n\n```ts\nconst userRepository = new UserRepository()\n```\n\n### Step 4: Use Dependency Injection with Libraries like `tsyringe`\n\nIf you’re using a dependency injection library such as\n[`tsyringe`](https://www.npmjs.com/package/tsyringe), you can manage repository instances\nefficiently. This approach is especially useful for caching and GraphQL integrations:\n\n```ts\nimport { container } from 'tsyringe'\n\nconst userRepository = container.resolve(UserRepository) // Resolve the repository from the DI container.\n```\n\nBy following these steps, you can configure and use the this library outside of a NestJS application\nwhile maintaining flexibility and scalability.\n\n## Fetch Records\n\nThe repository class comes with `.find*` methods that you can use to query data using\n[`Query`](#query-builder) builder:\n\n- [find](#find)\n- [findById](#findbyid)\n- [findByIds](#findbyids)\n- [findAll](#findall)\n- [findOne](#findone)\n\n### find\n\nYou can fetch multiple records from a table using `find` method. This method supports pagination.\n\n```ts\n// find using a query, but paginate\nconst { next, items } = await userRepository.find(query =\u003e\n  query.whereEqualTo('name', 'John Doe').next(nextTokenFromBefore).limit(200),\n)\n```\n\nwill execute the following sql query and return first `200` records and a `next` token that you can\nuse for next page. `OFFSET` will be calculated from `nextTokenFromBefore`\n\n```sql\nSELECT * FROM \"user\"\nWHERE \"name\" = 'John Doe'\nOFFSET \u003cOFFSET\u003e\nLIMIT 200\n```\n\n### findById\n\nYou can query a record by id directly using `findById` method.\n\n```ts\nconst user = await userRepository.findById('user1')\n```\n\nwill execute a query\n\n```sql\nSELECT * FROM \"user\"\nWHERE \"id\" = 'user1'\n```\n\n### findByIds\n\nYou can find more than one record by its ids by using `findByIds`.\n\n```ts\n// find many by ids\nconst users = await userRepository.findByIds(['user1', 'user2'])\n```\n\nwill execute a query\n\n```sql\nSELECT * FROM \"user\"\nWHERE \"id\" IN ('user1', 'user2')\n```\n\n### findAll\n\nYou can use `findAll` to get all records without pagination. This method provides support for in\nmemory filter and pagination callback.\n\n```ts\n// find all from the entity table\nconst users = await userRepository.findAll()\n\n// find all using a query\nconst users = await userRepository.findAll(query =\u003e query.whereEqualTo('name', 'John Doe'))\n\n// find all using a query, filter and a pagination callback\nconst users = await userRepository.findAll(\n  query =\u003e query.whereEqualTo('name', 'John Doe'),\n  item =\u003e someLogicToFilter(item),\n  (items, next) =\u003e console.log('fetched a page', items, next),\n)\n```\n\n### findOne\n\nThis method works just like `findAll` but returns only the first record.\n\n```ts\n// find one from the top\nconst user = await userRepository.findOne()\n\n// find one using a query\nconst user = await userRepository.findOne(query =\u003e query.whereEqualTo('name', 'John Doe'))\n```\n\n## Query Builder\n\nQuery class provides an easy to use implementation for constructing complex SQL query. It allows you\nto build SQL queries using elegant and convenient syntax with typing support. Here is the\n[entity setup](docs/entities.md) for this example.\n\n### Query\n\n```ts\nimport { Query } from '@hgraph/storage'\n\nconst repo = new UserRepository()\nconst query = new Query(repo)\n\n  // select columns\n  .select('bio')\n  .select('id')\n  .select('email')\n\n  // where conditions\n  .whereEqualTo('id', 'id1')\n  .whereNotEqualTo('id', 'id1')\n\n  // numeric checks\n  .whereMoreThan('version', 1)\n  .whereMoreThanOrEqual('version', 1)\n  .whereLessThan('version', 1)\n  .whereLessThanOrEqual('version', 1)\n  .whereBetween('version', 1, 2)\n\n  // numeric \"NOT\" operators\n  .whereNotMoreThan('version', 1)\n  .whereNotMoreThanOrEqual('version', 1)\n  .whereNotLessThan('version', 0)\n  .whereNotLessThanOrEqual('version', 1)\n\n  // search\n  .whereTextContains('bio', 'true')\n  .whereTextStartsWith('bio', 'any')\n  .whereTextEndsWith('bio', 'any')\n\n  // case insensitive search\n  .whereTextInAnyCaseContains('bio', 'any')\n  .whereTextInAnyCaseStartsWith('bio', 'any')\n  .whereTextInAnyCaseEndsWith('bio', 'any')\n\n  // \"IN\" operator\n  .whereIn('role', [UserRole.ADMIN, UserRole.USER])\n\n  // null checks\n  .whereIsNull('name')\n  .whereIsNotNull('name')\n\n  // array operations\n  .whereArrayContains('tags', 'new')\n  .whereArrayContainsAny('tags', ['new', 'trending'])\n\n  // search on related tables\n  .whereJoin('photos', q =\u003e q.whereIsNotNull('url'))\n\n  // build \"OR\" condition\n  .whereOr(\n    query =\u003e query.whereEqualTo('id', '10'),\n    query =\u003e query.whereEqualTo('id', '10'),\n  )\n\n  // sort\n  .orderByAscending('version')\n  .orderByDescending('createdAt')\n\n  // fetch related entities\n  .fetchRelation('photos', 'album')\n  .loadRelationIds() // load only 'id', not required with `fetchRelation`\n\n  // enable or set timeout for `cache`\n  .cache(5000 ?? true)\n```\n\n### PaginatedQuery\n\n`PaginatedQuery`, in addition to the following, supports all the methods in `Query`.\n\n```ts\nimport { PaginatedQuery } from '@hgraph/storage'\n\nconst repo = new UserRepository()\nconst query = new PaginatedQuery(repo)\n\n  // use individual methods\n  .next('token')\n  .limit(10)\n\n  // or use this method\n  .pagination({ next: 'token', limit: 10 })\n```\n\n## Insert \u0026 Update\n\nYou can insert and update records using the following methods:\n\n- [save](#save)\n- [saveMany](#savemany)\n- [insert](#insert)\n- [insertMany](#insertmany)\n- [update](#update)\n- [updateMany](#updatemany)\n\n### save\n\nThis method will insert if the `id` (if provided) does not exist in the database, or will update the\nexisting record.\n\n```ts\nconst user = await userRepository.save({ id: 'user1', name: 'John Doe', username: 'johnd' })\n```\n\n### saveMany\n\nYou can insert or update more than one record using in a step using `saveMany`. Just like `save` the\nrecord will inserted if `id` does not record.\n\n```ts\nconst users = await userRepository.saveMany([\n  { id: 'user1', name: 'John Doe', username: 'johndoe' },\n  { id: 'user2', name: 'Mejia Henderso', username: 'mh' },\n])\n```\n\n### insert\n\nYou can insert a record using `insert` method. \"id\" will be auto populated, if omitted.\n\n```ts\nconst user = await userRepository.insert({ name: 'John Done', username: 'johndoe' })\n```\n\n### insertMany\n\nYou can insert multiple users at once using `insertMany`.\n\n```ts\nconst users = await userRepository.insertMany([\n  { name: 'John Doe', username: 'johndoe' },\n  { name: 'Mejia Henderso', username: 'mh' },\n])\n```\n\n### update\n\nUse this method to update a record, \"id\" is mandatory input.\n\n```ts\nconst user = await userRepository.update({ id: 'user1', username: 'john' })\n```\n\n### updateMany\n\nYou can update more than one record using a query using `updateMany`.\n\n```ts\n// update multiple records at once using a query\nconst users = await userRepository.updateMany(query =\u003e query.whereEqualTo('username', 'johndoe'), {\n  verified: true,\n})\n```\n\n## Count\n\nThis method counts entities that match query (if provided) and returns a numeric value.\n\n```ts\n// count all users\nconst count = await userRepository.count()\n\n// count all users with a query\nconst count = await userRepository.count(query =\u003e query.whereEqualTo('name', 'John Doe'))\n```\n\n## Increment\n\nYou can increment or decrement the value of a numeric column using an id or a query using this\nmethod.\n\n```ts\n// increment by id\nconst user = await userRepository.increment('user1', 'followers', 1)\n\n// decrement by id\nconst user = await userRepository.increment('user1', 'followers', -1)\n\n// increment by query\nconst user = await userRepository.increment(\n  query =\u003e query.whereEqualTo('name', 'John Doe'),\n  'followers',\n  1,\n)\n```\n\n## Delete \u0026 Restore\n\nYou can permanently delete a record from the table using `delete`. Alternatively you may choose to\nuse soft delete by passing `{ softDelete: true }` option. This will keep the record in the table,\nhowever will populate `deletedAt` column with the current time stamp. TypeORM will inject\n`\"deletedAt\" IS NULL` to all queries by default, thus eliminating any records that were soft\ndeleted. `restore` will remove `deletedAt` value.\n\n```ts\n// delete a user\nawait userRepository.delete('user1') // delete by id\nawait userRepository.delete(query =\u003e query.whereEqualTo('verified', false)) // delete by query\n\n// soft delete a user\nawait userRepository.delete('user1', { softDelete: true }) // soft delete by id\nawait userRepository.delete(query =\u003e query.whereEqualTo('verified', false), { softDelete: true }) // soft delete by query\n\n// restore a user if soft deleted\nawait userRepository.restore('user1') // restore by id\nawait userRepository.restore(query =\u003e query.whereEqualTo('verified', false)) // restore by query\n```\n\n---\n\n## Using with Cloud Firestore\n\nThis library offers robust support for\n[Cloud Firestore](https://firebase.google.com/docs/firestore), making it easier to integrate with\nyour applications. Whether you are using NestJS or a standalone setup, this guide will walk you\nthrough the initialization process and demonstrate how to configure the data source and repositories\nfor seamless integration. Most APIs provided by this library are designed to be compatible with each\nother, ensuring a consistent development experience.\n\n### Using with NestJS\n\nIf you're working with NestJS, integrating this library is straightforward. The following example\ndemonstrates how to set up the `StorageModule` with Firestore as the repository type. Ensure you\nhave your Firebase service account configuration and storage bucket details ready:\n\n```typescript\nimport { Module } from '@nestjs/common'\nimport { StorageModule, RepositoryType } from '@hgraph/storage'\nimport { UserModule } from './user/user.module'\nimport { AuthModule } from './auth/auth.module'\nimport { AppController } from './app.controller'\n\n@Module({\n  imports: [\n    StorageModule.forRoot({\n      repositoryType: RepositoryType.Firestore, // Specify Firestore as the repository type\n      serviceAccountConfig: process.env.FIREBASE_SERVICE_ACCOUNT, // Path or JSON object containing your service account details\n      storageBucket: process.env.FIREBASE_STORAGE_BUCKET, // Optional: Specify your Firebase Storage bucket\n    }),\n    UserModule, // Import additional modules as needed\n    AuthModule,\n  ],\n  controllers: [AppController], // Define application controllers\n})\nexport class AppModule {}\n```\n\n#### Key Points:\n\n- Replace `process.env.FIREBASE_SERVICE_ACCOUNT` with the appropriate path or JSON content for your\n  Firebase service account.\n- The `storageBucket` parameter is optional but can be included if your application uses Firebase\n  Storage.\n\n### Using without NestJS\n\nFor non-NestJS applications, you can initialize the Firestore data source and define repositories\ndirectly. This provides flexibility for various use cases:\n\n```typescript\nimport { initializeFirestore, FirestoreRepository } from '@hgraph/storage'\n\n// Initialize Firestore with service account configuration\nawait initializeFirestore({\n  serviceAccountConfig: 'string', // Path to the JSON file containing your service account configuration\n})\n\n// Define a repository for a specific entity\nexport class UserRepository extends FirestoreRepository\u003cUserEntity\u003e {\n  constructor() {\n    super(UserEntity) // Pass the entity class to the repository\n  }\n}\n```\n\n#### Key Points:\n\n- Ensure the `serviceAccountConfig` points to a valid Firebase service account JSON file.\n- Extend `FirestoreRepository` to create custom repositories for your entities, making it easier to\n  manage Firestore collections.\n\n### Additional Notes\n\n- Ensure you have installed the necessary Firebase SDK and dependencies before proceeding.\n- Always validate your service account credentials and configurations to avoid runtime errors.\n- For more advanced usage, refer to the library’s API documentation and Firestore’s official\n  guidelines.\n\n```sh\nnpm install firebase-admin\n```\n\n---\n\n### Queries\n\nThe following apis are supported\n\n```ts\nimport { FirestoreQuery } from '@hgraph/storage'\n\nconst repo = new UserRepository()\nconst query = new FirestoreQuery(repo)\n\n  // select columns\n  .select('bio')\n  .select('id')\n  .select('email')\n\n  // where conditions\n  .whereEqualTo('id', 'id1')\n  .whereNotEqualTo('id', 'id1')\n\n  // numeric checks\n  .whereMoreThan('version', 1)\n  .whereMoreThanOrEqual('version', 1)\n  .whereLessThan('version', 1)\n  .whereLessThanOrEqual('version', 1)\n  .whereBetween('version', 1, 2)\n\n  // numeric \"NOT\" operators\n  .whereNotMoreThan('version', 1)\n  .whereNotMoreThanOrEqual('version', 1)\n  .whereNotLessThan('version', 0)\n  .whereNotLessThanOrEqual('version', 1)\n\n  // search\n  // .whereTextContains('bio', 'true')                    // NOT SUPPORTED\n  .whereTextStartsWith('bio', 'any')\n  // .whereTextEndsWith('bio', 'any')                     // NOT SUPPORTED\n\n  // case insensitive search\n  // .whereTextInAnyCaseContains('bio', 'any')            // NOT SUPPORTED\n  // .whereTextInAnyCaseStartsWith('bio', 'any')          // NOT SUPPORTED\n  // .whereTextInAnyCaseEndsWith('bio', 'any')            // NOT SUPPORTED\n\n  // \"IN\" operator\n  .whereIn('role', [UserRole.ADMIN, UserRole.USER])\n\n  // null checks\n  .whereIsNull('name')\n  .whereIsNotNull('name')\n\n  // array operations\n  .whereArrayContains('tags', 'new')\n  .whereArrayContainsAny('tags', ['new', 'trending'])\n\n  // search on related tables\n  // .whereJoin('photos', q =\u003e q.whereIsNotNull('url'))   // NOT SUPPORTED YET\n\n  // build \"OR\" condition\n  .whereOr(\n    query =\u003e query.whereEqualTo('id', '10'),\n    query =\u003e query.whereEqualTo('id', '10'),\n  )\n\n  // sort\n  .orderByAscending('version')\n  .orderByDescending('createdAt')\n\n  // fetch related entities\n  // .fetchRelation('photos', 'album')                 // NOT SUPPORTED YET\n  .loadRelationIds()\n\n  // enable or set timeout for `cache`\n  .cache(5000 ?? true) // NOT EFFECT\n```\n\n### Modification API\n\n```ts\n// save a user\nconst user = await userRepository.save({ id: 'user1', name: 'John Doe', username: 'johnd' })\n\n// save multiple users\nconst users = await userRepository.saveMany([\n  { id: 'user1', name: 'John Doe', username: 'johndoe' },\n  { id: 'user2', name: 'Mejia Henderso', username: 'mh' },\n])\n\n// update a user\nconst user = await userRepository.update({ id: 'user1', username: 'john' })\n\n// update multiple records at once using a query\nconst users = await userRepository.updateMany(query =\u003e query.whereEqualTo('username', 'johndoe'), {\n  verified: true,\n})\n\n// count all users\nconst count = await userRepository.count()\n\n// count all users with a query\nconst count = await userRepository.count(query =\u003e query.whereEqualTo('name', 'John Doe'))\n\n// increment by id\nconst user = await userRepository.increment('user1', 'followers', 1)\n\n// decrement by id\nconst user = await userRepository.increment('user1', 'followers', -1)\n\n// increment by query\nconst user = await userRepository.increment(\n  query =\u003e query.whereEqualTo('name', 'John Doe'),\n  'followers',\n  1,\n)\n\n// safest way to add an entity to an array \"following\" is as below\nconst user = await userRepository.addToArray('following', {\n  id: 'user2',\n  name: 'Mejia Henderso',\n  username: 'mh',\n})\n\n// safest way to remove an entity from an array \"following\" is as below\nconst user = await userRepository.removeFromArray('following', {\n  id: 'user2',\n  name: 'Mejia Henderso',\n  username: 'mh',\n})\n\n// delete a user\nawait userRepository.delete('user1') // delete by id\nawait userRepository.delete(query =\u003e query.whereEqualTo('verified', false)) // delete by query\n\n// soft delete a user - NOT SUPPORTED\n// await userRepository.delete('user1', { softDelete: true }) // soft delete by id\n// await userRepository.delete(query =\u003e query.whereEqualTo('verified', false), { softDelete: true }) // soft delete by query\n\n// restore a user if soft deleted - NOT SUPPORTED YET\n// await userRepository.restore('user1') // restore by id\n// await userRepository.restore(query =\u003e query.whereEqualTo('verified', false)) // restore by query\n```\n\n## Using Cache\n\nId cache is very important for the performance of queries especially when using it with GraphQL.\nTherefor Hypergraph uses officially recommended library\n[dataloader](https://github.com/graphql/dataloader) to gain performance via batching and caching.\n\n```ts\nimport { RepositoryWithIdCache } from '@hgraph/storage'\n\nclass UserRepository extends RepositoryWithIdCache\u003cUser\u003e {\n  constructor() {\n    super(User)\n  }\n}\n```\n\nor if you are using firestore do the following\n\n```ts\nimport { FirestoreRepositoryWithIdCache } from '@hgraph/storage'\n\nclass UserRepository extends FirestoreRepositoryWithIdCache\u003cUser\u003e {\n  constructor() {\n    super(User)\n  }\n}\n```\n\nAlternatively you can build your own cache-by-a-property using the following code.\n\n```ts\nimport { Repository, RepositoryOptions, WithCache } from '@hgraph/storage'\nimport { ObjectLiteral } from 'typeorm'\nimport { ClassType } from 'tsds-tools'\n\n@WithCache('name')\nclass RepositoryWithNameCache\u003cEntity extends ObjectLiteral\u003e extends Repository\u003cEntity\u003e {\n  constructor(\n    public readonly entity: ClassType\u003cEntity\u003e,\n    public readonly options?: RepositoryOptions,\n  ) {\n    super(entity, options)\n  }\n}\n\nclass UserRepository extends RepositoryWithNameCache\u003cUser\u003e {\n  constructor() {\n    super(User)\n  }\n}\n```\n\nFor firestore:\n\n```ts\nimport {\n  FirestoreRepository,\n  FirestoreRepositoryOptions,\n  WithFirestoreCache,\n} from '@hgraph/storage'\nimport { ObjectLiteral } from 'typeorm'\nimport { ClassType } from 'tsds-tools'\n\n@WithFirestoreCache('name')\nclass FirestoreRepositoryWithNameCache\u003c\n  Entity extends ObjectLiteral,\n\u003e extends FirestoreRepository\u003cEntity\u003e {\n  constructor(\n    public readonly entity: ClassType\u003cEntity\u003e,\n    public readonly options?: FirestoreRepositoryOptions,\n  ) {\n    super(entity, options)\n  }\n}\n\nclass UserRepository extends FirestoreRepositoryWithNameCache\u003cUser\u003e {\n  constructor() {\n    super(User)\n  }\n}\n```\n\n## TypeORM DataSource\n\nYou can access TypeORM DataSource directly, to tap on to any TypeORM feature that is not covered by\nthis library by using the following code:\n\n```ts\nimport { initializeDataSource } from '@hgraph/storage'\nimport { container } from 'tsyringe'\nimport { DataSource } from 'typeorm'\n\nasync function run() {\n  await initializeDataSource({\n    type: 'postgres',\n    ...\n  })\n\n  const dataSource = container.resolve(DataSource)\n}\n```\n\n## Testing\n\nThis package comes with an in-memory implementation of the database based on\n[pg-mem](https://github.com/oguimbal/pg-mem) to support testing. Use `initializeMockDataSource` to\ninitialize in-memory database.\n\n```ts\nimport { initializeMockDataSource } from '@hgraph/storage/dist/typeorm-mock'\n\ndescribe('Test suite', () =\u003e {\n  let dataSource: MockTypeORMDataSource\n\n  class UserRepository extends Repository\u003cUserEntity\u003e {\n    constructor() {\n      super(UserEntity)\n    }\n  }\n\n  class PhotoRepository extends Repository\u003cPhotoEntity\u003e {\n    constructor() {\n      super(PhotoEntity)\n    }\n  }\n\n  beforeEach(async () =\u003e {\n    dataSource = await initializeMockDataSource({\n      type: 'postgres',\n      database: 'test',\n      entities: [UserEntity, PhotoEntity],\n      synchronize: false,\n      retry: 0,\n    })\n    await container.resolve(UserRepository).saveMany(data.users as any)\n    await container.resolve(PhotoRepository).saveMany(data.photos)\n  })\n\n  afterEach(async () =\u003e {\n    dataSource?.destroy()\n  })\n\n  test('should pass sanity test', async () =\u003e {\n    const repository = container.resolve(PhotoRepository)\n    const result = await repository.count()\n    expect(result).toEqual(data.photos.length)\n  })\n})\n```\n\n### Testing with firestore\n\n```ts\nimport { initializeMockFirestore } from '@hgraph/storage/dist/firestore-repository/firestore-mock'\n\ndescribe('Test suite', () =\u003e {\n  let dataSource: MockTypeORMDataSource\n\n  class UserRepository extends Repository\u003cUserEntity\u003e {\n    constructor() {\n      super(UserEntity)\n    }\n  }\n\n  class PhotoRepository extends Repository\u003cPhotoEntity\u003e {\n    constructor() {\n      super(PhotoEntity)\n    }\n  }\n\n  async function saveAll() {\n    await Promise.all([\n      container.resolve(UserRepository).saveMany(data.users as any),\n      container.resolve(PhotoRepository).saveMany(data.photos),\n    ])\n  }\n\n  async function deleteAll() {\n    await Promise.all([\n      container.resolve(UserRepository).delete(query =\u003e query),\n      container.resolve(PhotoRepository).delete(query =\u003e query),\n    ])\n  }\n\n  beforeAll(async () =\u003e {\n    // OPTION 1: RUN WITH EMULATOR\n    // const firestore = admin.initializeApp({ projectId: 'test-e9d5b' }).firestore()\n    // firestore.settings({ host: 'localhost:8080', ssl: false })\n    // container.registerInstance(FIRESTORE_INSTANCE, firestore)\n\n    // OPTION 2: RUN WITH MOCK\n    initializeMockFirestore()\n  })\n\n  beforeEach(async () =\u003e {\n    await deleteAll()\n    await saveAll()\n  })\n\n  afterAll(async () =\u003e {\n    await deleteAll()\n  })\n\n  test('should pass sanity test', async () =\u003e {\n    const repository = container.resolve(PhotoRepository)\n    const result = await repository.count()\n    expect(result).toEqual(data.photos.length)\n  })\n})\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frintoj%2Fhypergraph-storage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frintoj%2Fhypergraph-storage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frintoj%2Fhypergraph-storage/lists"}