UNPKG

firebase-lib-db

Version:

Database Lib to access Firestore (CRUD) and Cache Data

478 lines (473 loc) 15.4 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/v2/index.ts var v2_exports = {}; __export(v2_exports, { Firestore: () => Firestore, Redis: () => Redis }); module.exports = __toCommonJS(v2_exports); // src/logger/index.ts var import_winston = __toESM(require("winston")); var loggerInstance = null; function GetLogger() { if (!loggerInstance) { loggerInstance = import_winston.default.createLogger({ level: process.env.LOG_LEVEL || "info", format: import_winston.default.format.json(), transports: [new import_winston.default.transports.Console()] }); } return loggerInstance; } var info = (message, rest) => { log("info", message, rest); }; var error = (message, rest) => { log("error", message, rest); }; var log = (severity, message, rest) => { const logger = GetLogger()[severity]; if (rest.error instanceof Error) { rest.error = { message: rest.error.message, stack: rest.error.stack }; } const metadata = __spreadValues({}, rest); if (rest.operation) { logger( `[Database] [${severity.toUpperCase()}] [${rest.operation}] - ${message}`, { metadata } ); } else { logger(`[Database] [${severity.toUpperCase()}] - ${message}`, { metadata }); } }; // src/util/ConnectionPool.ts var import_redis = require("redis"); var endpoint = process.env.REDISCLOUD_URL; var _RedisPool = class _RedisPool { static GetConnection() { return __async(this, null, function* () { if (_RedisPool.pool.length > 0) { const connection = _RedisPool.pool.pop(); if (connection) return connection; } const newConnection = (0, import_redis.createClient)({ url: endpoint }); try { yield newConnection.connect(); } catch (err) { throw new Error(`Erro ao conectar no Redis - ${err}`); } return newConnection; }); } static releaseConnection(connection) { if (!endpoint || !connection.isOpen) return; if (_RedisPool.pool.length < _RedisPool.MAX_CONNECTION) { _RedisPool.pool.push(connection); } else { connection.quit(); } } }; _RedisPool.MAX_CONNECTION = 10; _RedisPool.pool = []; var RedisPool = _RedisPool; // src/v2/cache/Redis.ts var Redis = class { logError(method, keyOrPattern, err) { error(`Error in ${method} - Key/Pattern: ${keyOrPattern} `, { operation: method, err }); } del(key) { return __async(this, null, function* () { const conn = yield RedisPool.GetConnection(); try { yield conn.del(key); } catch (error2) { this.logError("del", key, error2); throw error2; } finally { RedisPool.releaseConnection(conn); } }); } get(key) { return __async(this, null, function* () { const conn = yield RedisPool.GetConnection(); try { const data = yield conn.get(key); return data ? JSON.parse(data) : null; } catch (err) { this.logError("get", key, error); throw error; } finally { RedisPool.releaseConnection(conn); } }); } set(key, obj, ttl = 86400) { return __async(this, null, function* () { if (!obj) return; const conn = yield RedisPool.GetConnection(); try { yield conn.setEx(key, ttl, JSON.stringify(obj)); } catch (error2) { this.logError("set", key, error2); throw error2; } finally { RedisPool.releaseConnection(conn); } }); } delPattern(pattern) { return __async(this, null, function* () { const conn = yield RedisPool.GetConnection(); try { const keys = yield conn.keys(pattern); if (keys.length) yield conn.del(keys); } catch (error2) { this.logError("delPattern", pattern, error2); throw error2; } finally { RedisPool.releaseConnection(conn); } }); } getByPattern(pattern) { return __async(this, null, function* () { const conn = yield RedisPool.GetConnection(); try { let cursor = 0; const keys = []; const conn2 = yield RedisPool.GetConnection(); do { const reply = yield conn2.scan(cursor, { MATCH: pattern, COUNT: 100 }); cursor = reply.cursor; keys.push(...reply.keys); } while (cursor !== 0); const values = []; for (const key of keys) { const value = yield conn2.get(key); values.push(value); } RedisPool.releaseConnection(conn2); return values; } catch (error2) { this.logError("getByPattern", pattern, error2); return []; } finally { RedisPool.releaseConnection(conn); } }); } }; // src/v2/database/Firestore.ts var import_crypto = __toESM(require("crypto")); var Firestore = class { constructor(firestore) { this.__getCacheKey = (collectionName, data) => { const hashStr = import_crypto.default.createHash("md5").update(JSON.stringify(data)).digest("hex"); return `${collectionName}::${hashStr}`; }; this._cache = new Redis(); this._db = firestore; } SaveBatch(collectionName, data) { return __async(this, null, function* () { const operation = "SaveBatch"; const timestamp = Date.now(); try { const batch = this._db.batch(); const result = []; data.forEach((item) => { const id = item.id || this._db.collection(collectionName).doc().id; const docRef = this._db.collection(collectionName).doc(id); batch.set(docRef, __spreadProps(__spreadValues({}, item), { modified: timestamp })); result.push(__spreadProps(__spreadValues({}, item), { id })); }); yield batch.commit(); info(`Batch saved ${result.length} documents to ${collectionName}`, { collectionName, operation }); yield this._cache.delPattern(`${collectionName}::*`); } catch (error2) { error("Could not SaveBatch", { collectionName, data, operation, error: error2 }); throw new Error("Could not batch save documents. Try again later."); } }); } Save(collectionName, data) { return __async(this, null, function* () { const operation = "SaveDocument"; const timestamp = Date.now(); try { let docId; if (data == null ? void 0 : data.id) { yield this._db.collection(collectionName).doc(data.id).set(__spreadProps(__spreadValues({}, data), { modified: timestamp })); docId = data.id; info(`Updating document [${docId}] in collection ${collectionName}.`, { operation, collectionName, documentId: docId }); } else { const docRef = yield this._db.collection(collectionName).add(__spreadProps(__spreadValues({}, data), { modified: timestamp })); docId = docRef.id; info( `Created new document with id ${docId} in collection ${collectionName}.`, { operation, collectionName, documentId: docId } ); } yield this._cache.delPattern(`${collectionName}::*`); return __spreadProps(__spreadValues({}, data), { id: docId }); } catch (error2) { error(`Could not Save Data in ${collectionName}`, { collectionName, data, operation, error: error2 }); throw new Error(`Could not save item in Database [${collectionName}]`); } }); } FindOne(collectionName, id) { return __async(this, null, function* () { const operation = "FindOne"; try { const cacheKey = this.__getCacheKey(collectionName, id); const cacheData = yield this._cache.get(cacheKey); if (cacheData) return cacheData; const docRef = this._db.collection(collectionName).doc(id); const docSnapshot = yield docRef.get(); if (docSnapshot.exists) { const data = docSnapshot.data(); this._cache.set(cacheKey, data); return data; } return null; } catch (error2) { error(`Could not FindOne Data in ${collectionName}`, { collectionName, id, operation, error: error2 }); throw new Error("Could not fetch data. Try again later"); } }); } QueryData(collectionName, query, noCache) { return __async(this, null, function* () { var _a; const operation = "QueryData"; try { let queryRef = this._db.collection(collectionName); if (query == null ? void 0 : query.orderBy) { queryRef = queryRef.orderBy( query.orderBy.field, query.orderBy.direction ); } (_a = query == null ? void 0 : query.wheres) == null ? void 0 : _a.forEach((where) => { queryRef = queryRef.where( where.field, where.operation, where.value ); }); if (query == null ? void 0 : query.cursor) { if (!query.orderBy) { throw new Error("An orderBy field is required when using cursors"); } const cursorValue = query.cursor || "modified"; queryRef = queryRef.startAfter(cursorValue); } const cacheKey = this.__getCacheKey(collectionName, queryRef); const cached = yield this._cache.get(cacheKey); if (cached && !noCache) return cached; const q0 = yield queryRef.count().get(); const count = q0.data().count; if (query == null ? void 0 : query.perPage) { queryRef = queryRef.limit(query.perPage); } const querySnapshot = yield queryRef.get(); const res = querySnapshot.docs.map((doc) => doc.data()); const lastDoc = querySnapshot.docs[querySnapshot.docs.length - 1]; let cursor = null; if (lastDoc && (query == null ? void 0 : query.orderBy)) { cursor = lastDoc.get(query.orderBy.field); } const result = { count, data: res, cursor, perPage: query == null ? void 0 : query.perPage }; if (!noCache) this._cache.set(cacheKey, result); return result; } catch (error2) { error("Could not Query Data", { collectionName, error: error2, operation, query }); throw new Error("Could not Query your Data. Try again later."); } }); } Delete(collectionName, query) { return __async(this, null, function* () { const operation = "DeleteData"; try { const idsDeleted = []; const queryResult = this._db.collection(collectionName).where(query.field, query.operation, query.value); const snapshot = yield queryResult.get(); const batch = this._db.batch(); snapshot.forEach((doc) => { batch.delete(doc.ref); idsDeleted.push(doc.id); }); yield batch.commit(); info( `Deleting data in collection ${collectionName}. Ids deleted: ${idsDeleted}`, { collectionName, operation } ); yield this._cache.delPattern(`${collectionName}::*`); return true; } catch (error2) { error(`Could not Delete Data`, { collectionName, query, error: error2, operation }); throw new Error("Error while delete. Try again later"); } }); } Update(collectionName, id, data) { return __async(this, null, function* () { const operation = "UpdateDocument"; try { const docRef = this._db.collection(collectionName).doc(id); yield docRef.update(__spreadProps(__spreadValues({}, data), { modified: Date.now() })); info(`Updating document ${id} in collection ${collectionName}`, { operation, documentId: id, collectionName }); yield this._cache.delPattern(`${collectionName}::*`); const ref = yield docRef.get(); return ref.data(); } catch (error2) { error(`Could not Update Data`, { collectionName, documentId: id, data, operation, error: error2 }); throw new Error("Error while Update data. Try again later."); } }); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Firestore, Redis }); //# sourceMappingURL=index.js.map