Skip to content

πŸš€ A minimum and universal Firestore client (Repository Pattern) for TypeScript

License

Notifications You must be signed in to change notification settings

ikenox/firestore-repository

Repository files navigation

npm version CI License: MIT

firestore-repository

A minimum and universal Firestore client (Repository Pattern) for TypeScript

Features

  • πŸš€ Minimum: Only a few straightforward interfaces and classes. You can easily start to use it immediately without learning a lot of things.
  • 🌐 Universal: You can share most code, including schema and query definitions, between backend and frontend.
  • 🀝 Unopinionated: This library does not introduce any additional concepts, and respects vocabulary of the official Firestore client library.
  • βœ… Type-safe: This library provides the type-safe interface. It also covers the untyped parts of the official Firestore library.
  • πŸ—„οΈ Repository Pattern: A simple and consistent way to access Firestore data.

Installation

For backend (with @google-cloud/firestore)

npm install firestore-repository @firestore-repository/google-cloud-firestore 

For web frontend (with @firebase/firestore)

npm install firestore-repository @firestore-repository/firebase-js-sdk

Usage

Define a collection and its repository

import { mapTo, data, rootCollection } from 'firestore-repository/schema';

// For backend
import { Firestore } from '@google-cloud/firestore';
import { Repository } from '@firestore-repository/google-cloud-firestore';
const db = new Firestore();

// For web frontend
import { getFirestore } from '@firebase/firestore';
import { Repository } from '@firestore-repository/firebase-js-sdk';
const db = getFirestore();

// define a collection
const users = rootCollection({
  name: 'Users',
  id: mapTo('userId'),
  data: data<{
    name: string;
    profile: {
      age: number;
      gender?: 'male' | 'female';
    };
    tag: string[];
  }>(),
});

const repository = new Repository(users, db);

Basic operations for a single document

// Set a document
await repository.set({
  userId: 'user1',
  name: 'John Doe',
  profile: {
    age: 42,
    gender: 'male',
  },
  tag: ['new'],
});

// Get a document
const doc = await repository.get({ userId: 'user1' });

// Listen a document
repository.getOnSnapshot({ userId: 'user1' }, (doc) => {
  console.log(doc);
});

// Delete a document
await repository.delete({ userId: 'user2' });

Query

import { condition as $, limit, query } from 'firestore-repository/query';

// Define a query
const q = query(
    users,
    $('profile.age', '>=', 20),
    $('profile.gender', '==', 'male'),
    limit(10),
);

// List documents
const docs = await repository.list(q);
console.log(docs);

// Listen documents
repository.listOnSnapshot(q, (docs) => {
  console.log(docs);
});

// Aggregate
const result = await repository.aggregate(q, {
  avgAge: average('profile.age'),
  sumAge: sum('profile.age'),
  count: count(),
});
console.log(`avg:${result.avgAge} sum:${result.sumAge} count:${result.count}`);

Batch operations

// Get multiple documents (backend only)
const users = await repository.batchGet([{ userId: 'user1' }, { userId: 'user2' }]);

// Set multiple documents
await repository.batchSet([
  {
    userId: 'user1',
    name: 'Alice',
    profile: { age: 30, gender: 'female' },
    tag: ['new'],
  },
  {
    userId: 'user2',
    name: 'Bob',
    profile: { age: 20, gender: 'male' },
    tag: [],
  },
]);

// Delete multiple documents
await repository.batchDelete([{ userId: 'user1' }, { userId: 'user2' }]);

Include multiple different operations in a batch

// For backend
const batch = db.writeBatch();
// For web frontend
import { writeBatch } from '@firebase/firestore';
const batch = writeBatch();

await repository.set(
    {
      userId: 'user3',
      name: 'Bob',
      profile: { age: 20, gender: 'male' },
      tag: [],
    },
    { tx: batch },
);
await repository.batchSet([ /* ... */ ], { tx: batch });
await repository.delete({ userId: 'user4' }, { tx: batch });
await repository.batchDelete([{ userId: 'user5' }, { userId: 'user6' }], {
  tx: batch,
});

await batch.commit();

Transaction

// For web frontend
import { runTransaction } from '@firebase/firestore';

// Or, please use db.runTransaction for backend
await runTransaction(async (tx) => {
  // Get
  const doc = await repository.get({ userId: 'user1' }, { tx });
  
  if (doc) {
    doc.tag = [...doc.tag, 'new-tag'];
    // Set
    await repository.set(doc, { tx });
    await repository.batchSet([
      { ...doc, userId: 'user2' },
      { ...doc, userId: 'user3' },
    ], { tx });
  }

  // Delete
  await repository.delete({ userId: 'user4' }, { tx });
  await repository.batchDelete([{ userId: 'user5' }, { userId: 'user6' }], { tx });
});

About

πŸš€ A minimum and universal Firestore client (Repository Pattern) for TypeScript

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published