Skip to content

🔥 Repository Pattern implementation for Firebase Realtime Database

License

Notifications You must be signed in to change notification settings

flex-development/dreepo

Repository files navigation

dreepo

Repository Pattern implementation for Firebase Realtime Database

TypeScript tested with jest

Overview

Getting Started
Installation
Usage
Built With
Contributing

Getting Started

Inspired by Fireorm, Dreepo exposes a Repository Pattern implementation for Firebase Realtime Database.

Alongside an abstract database access layer, repositories also support:

  • aggregation pipelines, searches, and url queries using mango
  • model validation using class-validator

Installation

  1. Create or edit an .npmrc file with the following information:

    @flex-development:registry=https://npm.pkg.github.com/
    
  2. Add project to dependencies

    yarn add @flex-development/dreepo # or npm i @flex-development/dreepo

Usage

Configuration
Database Connection
Modeling Entities
Repository Options
Creating a New Repository
Repository Cache
Repository Class API

Configuration

Firebase Service Account

Dreepo communicates with your Realtime Database using the Firebase Database REST API. Generated by service accounts, Google OAuth2 access tokens are used to authenticate requests.

  1. Navigate to the Service Accounts section of the Firebase console

  2. Click Generate New Private Key to generate a new service account key file

Environment Variables

  • DEBUG: Toggle debug logs from the dreepo namespace
  • DEBUG_COLORS: Toggle debug log namespace colors

Mango

The Repository class integrates with mango, a plugin for mingo and qs-to-mongo. This allows for aggregation pipelines and performing searches with query criteria and options, as well as URL query handling.

mingo operators loaded by mango can be viewed in the config file. If additional operators are needed, you'll need to load them on your own before creating a new repository.

TypeScript

For shorter import paths, TypeScript users can add the following aliases:

{
  "compilerOptions": {
    "paths": {
      "@dreepo": ["node_modules/@flex-development/dreepo/index"],
      "@dreepo/*": ["node_modules/@flex-development/dreepo/*"]
    }
  }
}

These aliases will be used in following code examples.

Database Connection

Before creating a new repository, initialize a RepoDBConnection provider to establish a connection between your database and repository.

import { RepoDBConnection } from '@dreepo'

const path = 'users'
const url = process.env.FIREBASE_DATABASE_URL || ''
const client_email = process.env.FIREBASE_CLIENT_EMAIL || ''
const private_key = process.env.FIREBASE_PRIVATE_KEY || ''

export const dbconn = new RepoDBConnection(path, url, client_email, private_key)

Note:

  • An Exception will be thrown if any options are invalid
  • Private keys will be formatted using private_key.replace(/\\n/g, '\n')

Modeling Entities

Before instantiating a new repository, a model needs to be created.

For the next set of examples, the model User will be used.

import { Entity } from '@dreepo'
import type { IEntity } from '@dreepo/interfaces'
import type { RepoParsedUrlQuery, RepoSearchParams } from '@dreepo/types'
import {
  IsEmail,
  IsNotEmpty,
  IsOptional,
  IsPhoneNumber,
  IsString
} from 'class-validator'

export interface IUser extends IEntity {
  email: string
  first_name: string
  last_name: string
  phone?: string
}

export type UserParams = RepoSearchParams<IUser>
export type UserQuery = RepoParsedUrlQuery<IUser>

export class User extends Entity implements IUser {
  @IsEmail()
  email: IUser['email']

  @IsString()
  @IsNotEmpty()
  first_name: IUser['first_name']

  @IsString()
  @IsNotEmpty()
  last_name: IUser['last_name']

  @IsOptional()
  @IsPhoneNumber()
  phone?: IUser['phone']
}

For more information about validation decorators, see the class-validator package.

Dreepo also exposes a set of custom decorators.

Repository Options

The Repository class accepts an options object that passes additional options to mango, and class-transformer-validator.

import type { RepoOptionsDTO } from '@dreepo/dto'

const options: RepoOptionsDTO<IUser> = {
  cache: { collection: [] },
  mingo: {},
  parser: {},
  validation: {
    enabled: true,
    transformer: {},
    validator: {}
  }
}

Note that all properties are optional.

For more information about the cache, mingo, and parser options, see Plugin from the mango documentation. options.mingo.idKey will be overridden and always have the value 'id'.

Validation options will be merged with the following object:

import type { TVODefaults } from '@dreepo/types'

/**
 * @property {TVODefaults} TVO_DEFAULTS - `class-transformer-validator` options
 * @see https://github.com/MichalLytek/class-transformer-validator
 */
export const TVO_DEFAULTS: TVODefaults = Object.freeze({
  transformer: {},
  validator: {
    enableDebugMessages: true,
    forbidNonWhitelisted: true,
    stopAtFirstError: false,
    validationError: { target: false, value: true },
    whitelist: true
  }
})

Creating a New Repository

import { Repository } from '@dreepo'

export const Users = new Repository<IUser, UserParams, UserQuery>(
  dbconn,
  User,
  options
)

Repository Cache

After instantiation, before calling any repository methods, the repository's cache must be refreshed to keep the database and repository cache in sync.

If the cache is empty before running an aggregation pipeline or executing a search, a warning will be logged to the console.

Not refreshing the cache before a write operation (create, patch, or save) could lead to accidental overwrites or other database inconsistencies.

await Users.refreshCache()

Repository Class API

The Repository class allows users to perform CRUD operations on their Realtime Database, as well as check values against the repository model schema.

Documentation can be viewed here.

/**
 * `Repository` class interface.
 *
 * - https://github.com/flex-development/mango
 * - https://github.com/fox1t/qs-to-mongo
 * - https://github.com/kofrasa/mingo
 *
 * @template E - Entity
 * @template P - Repository search parameters (query criteria and options)
 * @template Q - Parsed URL query object
 */
export interface IRepository<
  E extends IEntity = IEntity,
  P extends RepoSearchParams<E> = RepoSearchParams<E>,
  Q extends RepoParsedUrlQuery<E> = RepoParsedUrlQuery<E>
> extends IMango<E, EUID, P, Q> {
  readonly cache: RepoCache<E>
  readonly dbconn: IRepoDBConnection
  readonly model: EntityClass<E>
  readonly options: RepoOptions<E>
  readonly validator: IRepoValidator<E>

  clear(): Promise<boolean>
  create(dto: EntityDTO<E>): Promise<E>
  delete(id: OneOrMany<E['id']>, should_exist?: boolean): Promise<typeof id>
  patch(id: E['id'], dto: Partial<EntityDTO<E>>, rfields?: string[]): Promise<E>
  refreshCache(): Promise<RepoCache<E>>
  save(dto: OneOrMany<OrPartial<EntityDTO<E>>>): Promise<E[]>
}

Looking for the Mango plugin docs? See here.

Built With