Server ORM to process Firestore operations.
Main goal of this library is to provide as simple as possible interface, good code design and batch of features đź’›
If you unable to find feature that you interested in, please report an issue and we'll discuss it!
- Configuration:
@google-cloud/firestore
orfirebase-admin
package - Usage example: Task management
- Document mapping
@Document(collectionName: string, idKey = 'id'')
- Collection
Collection
- Get collection
getCollection()
- Find first document
#first(): Promise<T>
- Find document by id
#find(id: string): Promise<T>
- Find one document where
#find(where: Record<string, any | any[]>): Promise<T>
- Count documents
#count(): Promise<number>
- Fetch document array
#fetch(): Promise<T[]>
- Stream documents
#stream(): Stream<T>
- Iterate documents
#[Symbol.asyncIterator](): AsyncIterable<T>
- Create document
#create(document: T): Promise<void>
- Update document
#update(document: T): Promise<void>
- Delete document
#delete(document: T): Promise<void>
- Get collection
- Query builder
QueryBuilder
- Version
#version: number
- Select
#select(keys: '*' | string | string[]): this
- Where
#where(key: string, value: any): this
- Where record
#where(record: WhereRecord): this
- Order by
#orderBy(key: string, direction = 'asc'): this
- Limit
#limit(limit: number): this
- Offset
#offset(offset: number): this
- To query
#toQuery(): Query
- Version
- Query object
- License
Install @imbyr/firestore
:
npm install @imbyr/firestore --save
Then you need to install @google-cloud/firestore
. You can use @google-cloud/firestore
or firebase-admin
.
Using @google-cloud/firestore
package:
npm install @google-cloud/firestore --save
Firestore configuration:
import { configureFirestore } from '@imbyr/firestore';
import { Firestore } from '@google-cloud/firestore';
const firestore = new Firestore({ keyFilename: 'path/to/serviceAccount.json' });
configureFirestore(firestore);
Using firebase-admin
package:
npm install firebase-admin --save
Firestore configuration:
import { configureFirestore } from '@imbyr/firestore';
import * as admin from 'firebase-admin';
admin.initializeApp({ credential: admin.credential.cert('path/to/serviceAccount.json') });
const firestore = admin.firestore();
configureFirestore(firestore);
Here the simple example how to manage tasks:
import { Document, getCollection } from '@imbyr/firestore';
// Collection name
const TASKS = 'tasks';
@Document(TASKS)
class Task {
id: string;
fulfilled = false;
constructor(public name: string) { }
}
(async () => {
// Instantiate collection.
const tasks = getCollection(Task);
// Prepare fulfilled query.
const fulfilled = tasks.where('fulfilled', true);
// Create task documents.
for (let i = 0; i < 5; i++) {
const task = new Task('Task #' + i);
await tasks.create(task);
}
await tasks.count(); // returns: 5
await fulfilled.count(); // returns: 0
// Fulfill 4 tasks.
for await (const task of tasks.limit(4)) {
task.fulfilled = true;
await tasks.update(task);
}
await fulfilled.count(); // returns: 4
// Delete all fulfilled tasks.
for await (const task of fulfilled) {
await tasks.delete(task);
}
await tasks.count(); // returns: 1
await fulfilled.count(); // returns: 0
// Find the remaining task.
const task = await tasks.first();
})();
Signature: @Document(collectionName: string, idKey = 'id');
Current decorator maps class as document of particular collection.
The document must have the id property. By default key is id
but you can use your own.
Example:
import { Document } from '@imbyr/firestore';
@Document('tasks')
class Task {
id: string;
name: string;
}
Example:
import { Document } from '@imbyr/firestore';
@Document('tasks', 'tid')
class Task {
tid: string;
name: string;
// ...
}
Signature: @Reference(referentType: Type)
References encapsulates Firestore logic to make relation between documents.
Important: when document with reference reads, normalizer under the hood gets every reference as particular document. Optimization ideas are welcome.
Example:
import { Document, Reference, getCollection } from '@imbyr/firestore';
@Document('employees')
class Employee {
id: string;
}
@Document('tasks')
class Task {
id: string;
@Reference(Employee)
assignee: Employee;
constructor(assignee: Employee) {
this.assignee = assignee;
}
}
(async () => {
const employees = getCollection(Employee);
// Create a single employee.
const employee = new Employee();
await employees.create(employee);
const tasks = getCollection(Task);
// Create a single taks for employee.
const task = new Task(employee);
await tasks.create(task);
// Find task where assignee
const status = await tasks.find({ assignee: employee });
console.log(status);
/*
* Result:
*
* Task {
* id: 'auto-generated id',
* assignee: Employee {
* id: 'auto-generated id',
* }
* }
*/
})();
Collection
extends QueryBuild
and provides CRUD API for particular collection.
Factory function that return instance of Collection<T>
;
Signature: getCollection<T extends object>(documentType: Type<T>): Collection<T>;
Example:
import { getCollection } from '@imbyr/firestore';
// Get tasks collection
const collection = getCollection(Task);
Signature: #first(): Promise<T>
If document not found DocumentNotFoundError
will be thrown.
Example:
const task = await collection.first();
Query example:
const task = await collection.where('active', true).first();
Signature: #find<T>(id: string): Promise<T>
If document not found DocumentNotFoundError
will be thrown.
Example:
const document = await collection.find('1');
Signature: #find<T>(where: Record<string, any | any[]>): Promise<T>
If record value is Array
then WhereOperator.In
will be used. In other cases WhereOperator.EqualTo
uses.
If document not found DocumentNotFoundError
will be thrown.
Example:
const document = await collection.find('1');
Signature: #count(): Promise<number>
Example:
const count = await collection.count();
Signature: #fetch(): Promise<T[]>
Example:
const documents = await collection.fetch();
Example with query:
const documents = await collection.limit(20).fetch();
Collection can be iterated using async for/of
.
Example:
for await (const document of collection) {
console.log(document);
}
for await (const document of collection.limit(20)) {
console.log(document);
}
If you familiar with NodeJS streams you can use it for different purposes.
Signature: #stream(): NodeJS.ReadableStream
Collection stream example:
const documents = [];
const stream = collection.stream();
stream.on('data', document => documents.push(document));
stream.on('end', () => {
console.log('Found ' + documents.length ' documents')
});
Query stream example:
const stream = collection
.select('id')
.where('active', 'true')
.orderBy('name')
.limit(10)
.offset(5)
.stream();
stream.on('data', document => documents.push(document));
stream.on('end', () => {
console.log('Found ' + documents.length ' documents')
});
Signature: #create(document: T): Promise<void>
Example:
const task = new Task();
await collection.create(task);
Signature: #update(document: T): Promise<void>
Example:
const document = await collection.first();
document.name = 'Another name';
await collection.update(document);
Signature: #delete(document: T): Promise<void>
Example:
const document = await collection.first();
await tasks.delete(document);
QueryBuilder
is an interface to prepare filtering/sorting/pagination expressions for read operation.
Example:
const builder = new QueryBuilder()
.select('id')
.where('active', true)
.whereIn('status', ['foo', 'bar'])
.orderBy('created')
.limit(20)
.offset(10);
Every operation clones instance of QueryBuilder
and increases #version
value.
Version specifies count of operations that was executed on current instance. This value uses in Collection
to identify that instance was not modified.
Example:
const version = builder.version;
Signature: #select(keys: '*' | string | string[]): this
Example:
builder = builder.select('*');
// or
builder = builder.select('id');
// or
builder = builder.select(['id', 'name']);
Signatures:
#where(key: string, value: any): this
shortcut for#where(key: string, WhereOperator.EqualTo, value: string): this
#where(key: string, operator: WhereOperator, value: any): this
Equal to (==
) example:
builder = builder.where('id', '1');
// or
builder = builder.where('id', WhereOperator.EqualTo, '1');
// or
builder = builder.whereEqualTo('id', '1');
Less than (<
) example:
builder = builder.where('id', WhereOperator.LessThan, '1');
// or
builder = builder.whereLessThan('id', '1');
Less than or equal to (<=
) example:
builder = builder.where('id', WhereOperator.LessThanOrEqualTo, '1');
// or
builder = builder.whereLessThanOrEqualTo('id', '1');
Greater than (>
) example:
builder = builder.where('id', WhereOperator.GreaterThan, '1');
// or
builder = builder.whereGreaterThan('id', '1');
Greater than or equal to (>=
) example:
builder = builder.where('id', WhereOperator.GreaterThanOrEqualTo, '1');
// or
builder = builder.whereGreaterThanOrEqualTo('id', '1');
Array contains (array-contains
) example:
builder = builder.where('id', WhereOperator.ArrayContains, '1');
// or
builder = builder.whereArrayContains('id', '1');
In (in
) example:
builder = builder.where('id', WhereOperator.In, ['1']);
// or
builder = builder.whereIn('id', ['1']);
Array contains any (array-contains-any
) example:
builder = builder.where('id', WhereOperator.ArrayContainsAny, ['1']);
// or
builder = builder.whereArrayContainsAny('id', ['1']);
WhereRecord
decomposes to Where[]
.
When value is type of Array
then WhereOperator.In
will be applied.
In other cases WhereOperator.EqualTo
used.
Signatures:
type WhereRecord = Partial<Record<string, any | any[]>>
#where(record: WhereRecord): this
Example:
builder = builder.where({
id: '1',
status: 'active',
author: ['author-a', 'author-b'],
});
Signatures: #orderBy(key: string, direction = OrderByDirection.Ascending): this
Example:
builder = builder.orderBy('id');
// or
builder = builder.orderBy('id', OrderByDirection.Ascending);
// or
builder = builder.orderBy('id', OrderByDirection.Descending);
Signatures: #limit(limit: number): this
Example:
builder = builder.limit(20);
Signatures: #offset(offset: number): this
Example:
builder = builder.offset(20);
Signatures: #toQuery(): Query
Example:
const query = builder.toQuery();
Query
object is the result or QueryBuilder
.
Signature:
import { Where, OrderBy } from '@imbyr/firestore';
interface Query {
select: string[];
where: Where[];
orderBy: OrderBy[];
limit: number | null;
offset: number | null;
}
See LICENSE for details.