simplyfire
Version:
A lightweight firestore api for firebase cloud functions & Angular.
294 lines (285 loc) • 10.8 kB
JavaScript
import { __awaiter, __rest } from 'tslib';
class AbstractFirestoreApi {
constructor() {
// Maximum number of writes that can be passed to a Commit operation
// or performed in a transaction
// https://cloud.google.com/firestore/quotas#writes_and_transactions
this.BATCH_MAX_WRITES = 500;
}
getValueFromSnapshot(snapshot) {
return (snapshot.exists ? snapshot.data() : null);
}
}
class QueryBuilder {
constructor() {
this._where = [];
this._orderBy = [];
this._leftJoins = [];
}
get joins() {
return this._leftJoins;
}
where(...where) {
this._where.push(where);
return this;
}
orderBy(...orderBy) {
this._orderBy.push(orderBy);
return this;
}
leftJoin(...leftJoin) {
this._leftJoins.push(leftJoin);
}
limit(limit) {
this._limit = limit;
return this;
}
limitToLast(limitToLast) {
this._limitToLast = limitToLast;
return this;
}
startAt(...startAt) {
this._startAt = startAt;
return this;
}
startAfter(...startAfter) {
this._startAfter = startAfter;
return this;
}
endAt(...endAt) {
this._endAt = endAt;
return this;
}
endBefore(...endBefore) {
this._endBefore = endBefore;
return this;
}
// Still have to use <any> type due to most interfaces of @google-cloud/firestore
// are not compatible with @firebase/firestore's interfaces.
exec(ref, queryOps) {
var _a, _b, _c, _d;
if (typeof window === 'undefined') {
return this.execQueryForCloud(ref);
}
if (!queryOps) {
throw Error('invalid arguments');
}
const { query, where, orderBy, limit, limitToLast, startAt, startAfter, endAt, endBefore } = queryOps;
const queryConstraints = [
...this._where.map((w) => where(...w)),
...this._orderBy.map((o) => orderBy(...o)),
...(this._limit ? [limit(this._limit)] : []),
...(this._limitToLast ? [limitToLast(this._limitToLast)] : []),
...(((_a = this._startAt) === null || _a === void 0 ? void 0 : _a.every((i) => !!i)) ? [startAt(...this._startAt)] : []),
...(((_b = this._startAfter) === null || _b === void 0 ? void 0 : _b.every((i) => !!i)) ? [startAfter(...this._startAfter)] : []),
...(((_c = this._endAt) === null || _c === void 0 ? void 0 : _c.every((i) => !!i)) ? [endAt(...this._endAt)] : []),
...(((_d = this._endBefore) === null || _d === void 0 ? void 0 : _d.every((i) => !!i)) ? [endBefore(...this._endBefore)] : [])
];
return query(ref, ...queryConstraints);
}
execQueryForCloud(ref) {
let query = this._where.reduce((q, wh) => q.where(...wh), ref);
query = this._orderBy.reduce((q, ob) => q.orderBy(...ob), query);
if (this._limit) {
query = query.limit(this._limit);
}
if (this._limitToLast) {
query = query.limitToLast(this._limitToLast);
}
if (this._startAt) {
query = query.startAt(this._startAt);
}
if (this._startAfter) {
query = query.startAfter(this._startAfter);
}
if (this._endAt) {
query = query.endAt(this._endAt);
}
if (this._endBefore) {
query = query.endBefore(this._endBefore);
}
return query;
}
}
// chunk array to a certain size
const arrayToChunks = (list, size) => {
list = [...list];
return [...Array(Math.ceil(list.length / size))].map((_) => list.splice(0, size));
};
class FirestoreCloudService extends AbstractFirestoreApi {
static getInstance(admin, settings = {}) {
var _a;
(_a = this.instance) !== null && _a !== void 0 ? _a : (this.instance = new this());
this.instance.initialize(admin, settings);
return this.instance;
}
initialize(admin, settings) {
admin.initializeApp();
this.db = admin.firestore();
this.db.settings(settings);
this.admin = admin;
}
// -----------------------------------------------------------------------------------------------------
// @ Abstract members
// -----------------------------------------------------------------------------------------------------
collection(collection, qb) {
return __awaiter(this, void 0, void 0, function* () {
return (yield this.collectionSnapshot(collection, qb)).docs.map((doc) => (Object.assign({ id: doc.id }, doc.data())));
});
}
collectionGroup(collectionId, qb) {
return __awaiter(this, void 0, void 0, function* () {
return (yield this.collectionGroupSnapshot(collectionId, qb)).docs.map((doc) => (Object.assign({ id: doc.id }, doc.data())));
});
}
doc(path) {
return __awaiter(this, void 0, void 0, function* () {
const snapshot = yield this.docRef(path).get();
return (snapshot.exists && Object.assign({ id: snapshot.id }, snapshot.data())) || null;
});
}
upsert(collection, data, opts = { merge: true }) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const timestamp = this.serverTimestamp;
// eslint-disable-next-line prefer-const
let { id } = data, updata = __rest(data, ["id"]);
(_a = updata.createdTs) !== null && _a !== void 0 ? _a : (updata.createdTs = timestamp);
if (!id) {
id = this.db.collection(collection).doc().id;
}
updata.updatedTs = timestamp;
yield this.docRef(`${collection}/${id}`).set(Object.assign({}, updata), opts);
return id;
});
}
update(path, data) {
return __awaiter(this, void 0, void 0, function* () {
yield this.docRef(path).update(data);
});
}
delete(path) {
return __awaiter(this, void 0, void 0, function* () {
yield this.docRef(path).delete();
});
}
/**
* Bulk update data
*/
bulkUpsert(path, data, opts = { merge: true }) {
return __awaiter(this, void 0, void 0, function* () {
const bulkIds = [];
const promises = [];
const timestamp = this.serverTimestamp;
if (Array.isArray(data)) {
// Due to a batch limitation, need to split docs array into chunks
for (const chunks of arrayToChunks(data, this.BATCH_MAX_WRITES)) {
const batch = this.batch;
chunks.forEach((d) => {
var _a;
let { id } = d, updata = __rest(d, ["id"]);
id !== null && id !== void 0 ? id : (id = this.db.collection(path).doc().id);
(_a = updata.createdTs) !== null && _a !== void 0 ? _a : (updata.createdTs = timestamp);
updata.updatedTs = timestamp;
batch.set(this.docRef(`${path}/${id}`), updata, opts);
bulkIds.push(id);
});
const p = batch.commit();
promises.push(p);
}
}
else {
const snapshot = yield this.collectionSnapshot(path, data.qb);
// Due to a batch limitation, need to split docs array into chunks
for (const chunks of arrayToChunks(snapshot.docs, this.BATCH_MAX_WRITES)) {
const batch = this.batch;
chunks.forEach((d) => batch.set(d.ref, Object.assign({ updatedTs: timestamp }, data.data), opts) && bulkIds.push(d.id));
const p = batch.commit();
promises.push(p);
}
}
yield Promise.all(promises);
return bulkIds;
});
}
/**
* Bulk delete data
*/
bulkDelete(collection, qb, maxSize = 1000) {
return __awaiter(this, void 0, void 0, function* () {
if (!qb) {
qb = new QueryBuilder();
qb.limit(maxSize);
}
const bulkIds = [];
const promises = [];
const snapshot = yield this.collectionSnapshot(collection, qb);
// Due to a batch limitation, need to split docs array into chunks
for (const chunks of arrayToChunks(snapshot.docs, this.BATCH_MAX_WRITES)) {
const batch = this.batch;
chunks.forEach((doc) => batch.delete(doc.ref) && bulkIds.push(doc.id));
const p = batch.commit();
promises.push(p);
}
yield Promise.all(promises);
return bulkIds;
});
}
get batch() {
return this.db.batch();
}
get serverTimestamp() {
return this.admin.firestore.FieldValue.serverTimestamp();
}
increment(n = 1) {
return this.admin.firestore.FieldValue.increment(n);
}
/**
* Returns a generated Firestore Document Id.
*/
createId(colPath) {
return this.db.collection(colPath !== null && colPath !== void 0 ? colPath : '_').doc().id;
}
runTransaction(updateFunction) {
return this.db.runTransaction(updateFunction);
}
// Recursively delete a reference and log the references of failures.
// https://github.com/googleapis/nodejs-firestore/pull/1494
recursiveDelete(ref, bulkWriter) {
return this.db.recursiveDelete(ref, bulkWriter);
}
// -----------------------------------------------------------------------------------------------------
// @ Custom methods
// -----------------------------------------------------------------------------------------------------
/**
* Create a Firestore Timestamp
*
* @param date
*/
createTimestamp(date = new Date()) {
return this.admin.firestore.Timestamp.fromDate(date);
}
collectionSnapshot(path, qb) {
const collectionRef = this.db.collection(path);
return (qb ? qb.exec(collectionRef) : collectionRef).get();
}
collectionGroupSnapshot(collectionId, qb) {
const groupRef = this.db.collectionGroup(collectionId);
return (qb ? qb.exec(groupRef) : groupRef).get();
}
docRef(path) {
return this.db.doc(path);
}
}
FirestoreCloudService.instance = null;
/*
* Public API Surface of common
*/
/*
* Public API Surface of common
*/
/**
* Generated bundle index. Do not edit.
*/
export { AbstractFirestoreApi, FirestoreCloudService, QueryBuilder, arrayToChunks };
//# sourceMappingURL=simplyfire.mjs.map