firebase-lib-db
Version:
Database Lib to access Firestore (CRUD) and Cache Data
478 lines (473 loc) • 15.4 kB
JavaScript
;
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