@quillsql/node 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # Quill Node SDK
2
+
3
+ ## Installation
4
+
5
+ ```shell
6
+ $ npm install @quillsql/node
7
+
8
+ # Or, if you prefer yarn
9
+ $ yarn add @quillsql/node
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ Instantiate quill with your credentials and add the below POST endpoint.
15
+
16
+ Note that we assume you have an organization id on the user returned by your auth middleware. Queries will not work properly without the organization id.
17
+
18
+ ```js
19
+ const quill = require("@quillsql/node")({
20
+ privateKey: process.env.QULL_PRIVATE_KEY,
21
+ databaseConnectionString: process.env.POSTGRES_READ,
22
+ });
23
+
24
+ // "authenticateJWT" is your own pre-existing auth middleware
25
+ app.post("/quill", authenticateJWT, async (req, res) => {
26
+ // assuming user fetched via auth middleware has an organizationId
27
+ const { organizationId } = req.user;
28
+ const { metadata } = req.body;
29
+ const result = await quill.query({
30
+ orgId: organizationId,
31
+ metadata,
32
+ });
33
+ res.send(result);
34
+ });
35
+ ```
36
+
37
+ ## For local testing (dev purposes only)
38
+
39
+ Create an .env file with the following key-value pairs:
40
+
41
+ ```
42
+ QUILL_PRIVATE_KEY=
43
+ DB_URL=
44
+ ENV=development
45
+ BACKEND_URL=
46
+ ```
47
+
48
+ Use the following commands to start a locally hosted dev server.
49
+
50
+ ```
51
+ npm install
52
+ npm run dev-server
53
+ ```
54
+
55
+ You should be able to ping your local server at `http://localhost:3000`.
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const axios_1 = __importDefault(require("axios"));
16
+ /** This client is currently not used but is a good design pratice */
17
+ class QuillServerClient {
18
+ constructor(privateKey) {
19
+ this.baseUrl = "";
20
+ this.config = { headers: { Authorization: `Bearer ${privateKey}` } };
21
+ }
22
+ postQuill(route, payload) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ return axios_1.default.post(`${this.baseUrl}/${route}`, payload, this.config);
25
+ });
26
+ }
27
+ }
28
+ exports.default = QuillServerClient;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.CachedPool = void 0;
13
+ const pg_1 = require("pg");
14
+ const redis_1 = require("redis");
15
+ const Error_1 = require("../utils/Error");
16
+ class PgError extends Error {
17
+ // Add other properties if needed
18
+ constructor(detail, hint, position) {
19
+ super();
20
+ this.detail = detail;
21
+ this.hint = hint;
22
+ this.position = position;
23
+ }
24
+ }
25
+ /** The TTL for new cache entries (default: 1h) */
26
+ const DEFAULT_CACHE_TTL = 24 * 60 * 60;
27
+ class CachedPool {
28
+ constructor(config, cacheConfig = {}) {
29
+ var _a;
30
+ this.pool = new pg_1.Pool(config);
31
+ this.ttl = (_a = cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.ttl) !== null && _a !== void 0 ? _a : DEFAULT_CACHE_TTL;
32
+ this.cache = this.getCache(cacheConfig);
33
+ }
34
+ query(text, values) {
35
+ return __awaiter(this, void 0, void 0, function* () {
36
+ try {
37
+ if (!this.cache) {
38
+ const results = yield this.pool.query(text, values);
39
+ return {
40
+ fields: results.fields.map((field) => ({
41
+ name: field.name,
42
+ dataTypeID: field.dataTypeID,
43
+ })),
44
+ rows: results.rows,
45
+ };
46
+ }
47
+ const key = `${this.orgId}:${text}`;
48
+ const cachedResult = yield this.cache.get(key);
49
+ if (cachedResult) {
50
+ return JSON.parse(cachedResult);
51
+ }
52
+ else {
53
+ const newResult = yield this.pool.query(text, values);
54
+ const newResultString = JSON.stringify(newResult);
55
+ yield this.cache.set(key, newResultString, "EX", DEFAULT_CACHE_TTL);
56
+ return {
57
+ fields: newResult.fields.map((field) => ({
58
+ name: field.name,
59
+ dataTypeID: field.dataTypeID,
60
+ })),
61
+ rows: newResult.rows,
62
+ };
63
+ }
64
+ }
65
+ catch (err) {
66
+ if ((0, Error_1.isSuperset)(err, PgError)) {
67
+ throw new PgError(err.detail, err.hint, err.position);
68
+ }
69
+ else if (err instanceof Error) {
70
+ throw new Error(err.message);
71
+ }
72
+ }
73
+ });
74
+ }
75
+ /**
76
+ * Configures and returns a cache instance or null if none could be created.
77
+ */
78
+ getCache({ username, password, host, port, cacheType, }) {
79
+ if (cacheType === "redis" || cacheType === "rediss") {
80
+ const redisURL = `${cacheType}://${username}:${password}@${host}:${port}`;
81
+ return (0, redis_1.createClient)({ url: redisURL });
82
+ }
83
+ return null;
84
+ }
85
+ close() {
86
+ return __awaiter(this, void 0, void 0, function* () {
87
+ yield this.pool.end();
88
+ });
89
+ }
90
+ }
91
+ exports.CachedPool = CachedPool;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const _1 = __importDefault(require("."));
16
+ require("dotenv/config");
17
+ const HOST = process.env.ENV === "development"
18
+ ? "http://localhost:8080"
19
+ : "https://quill-344421.uc.r.appspot.com";
20
+ describe("Quill", () => {
21
+ let quill;
22
+ beforeEach(() => {
23
+ quill = new _1.default(process.env.QUILL_PRIVATE_KEY, process.env.DB_URL);
24
+ });
25
+ afterAll(() => {
26
+ jest.restoreAllMocks();
27
+ });
28
+ describe("query", () => {
29
+ it("org - should return ", () => __awaiter(void 0, void 0, void 0, function* () {
30
+ const metadata = {
31
+ task: "orgs",
32
+ clientId: "62cda15d7c9fcca7bc0a3689",
33
+ };
34
+ const result = yield quill.query({
35
+ orgId: "2",
36
+ metadata,
37
+ });
38
+ // TODO - add assertions
39
+ }));
40
+ });
41
+ });
package/dist/index.js ADDED
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const CachedPools_1 = require("./db/CachedPools");
16
+ const axios_1 = __importDefault(require("axios"));
17
+ require("dotenv/config");
18
+ const RunQueryProcesses_1 = require("./utils/RunQueryProcesses");
19
+ const HOST = process.env.ENV === "development"
20
+ ? "http://localhost:8080"
21
+ : "https://quill-344421.uc.r.appspot.com";
22
+ /**
23
+ * Quill - Fullstack API Platform for Dashboards and Reporting.
24
+ */
25
+ class Quill {
26
+ constructor(privateKey, databaseConnectionString, cache = {}) {
27
+ this.ssl = { rejectUnauthorized: false };
28
+ this.baseUrl = HOST;
29
+ this.config = { headers: { Authorization: `Bearer ${privateKey}` } };
30
+ this.connectionString = databaseConnectionString;
31
+ this.ssl = { rejectUnauthorized: false };
32
+ this.targetPool = new CachedPools_1.CachedPool({ connectionString: this.connectionString, ssl: this.ssl }, cache);
33
+ }
34
+ query({ orgId, metadata }) {
35
+ return __awaiter(this, void 0, void 0, function* () {
36
+ this.targetPool.orgId = orgId;
37
+ try {
38
+ // Initial Query Request
39
+ const preQueryResults = yield this.runQueries(metadata.preQueries);
40
+ const response = yield this.postQuill(metadata.task, Object.assign(Object.assign({}, metadata), { orgId,
41
+ preQueryResults }));
42
+ const results = yield this.runQueries(response.queries, response.runQueryConfig);
43
+ // if there is no metadata object in the response, create one
44
+ if (!response.metadata) {
45
+ response.metadata = {};
46
+ }
47
+ // if there is a single query array in queryResults and the rows and field objects to metadata
48
+ if ((results === null || results === void 0 ? void 0 : results.queryResults.length) === 1) {
49
+ const queryResults = results.queryResults[0];
50
+ if (queryResults.rows) {
51
+ response.metadata.rows = queryResults.rows;
52
+ }
53
+ if (queryResults.fields) {
54
+ response.metadata.fields = queryResults.fields;
55
+ }
56
+ }
57
+ return {
58
+ data: response.metadata || null,
59
+ queries: results,
60
+ status: "success",
61
+ };
62
+ }
63
+ catch (err) {
64
+ return { status: "error", error: err.message };
65
+ }
66
+ });
67
+ }
68
+ runQueries(queries, runQueryConfig) {
69
+ return __awaiter(this, void 0, void 0, function* () {
70
+ let results;
71
+ if (!queries)
72
+ return Object.assign(Object.assign({}, results), { queryResults: [] });
73
+ if (runQueryConfig === null || runQueryConfig === void 0 ? void 0 : runQueryConfig.arrayToMap) {
74
+ return Object.assign(Object.assign({}, results), { mappedQueries: yield (0, RunQueryProcesses_1.mapQueries)(queries, runQueryConfig.arrayToMap, this.targetPool) });
75
+ }
76
+ else {
77
+ const queryResults = yield Promise.all(queries.map((query) => __awaiter(this, void 0, void 0, function* () {
78
+ return yield this.targetPool.query(query);
79
+ })));
80
+ results = Object.assign(Object.assign({}, results), { queryResults });
81
+ if (runQueryConfig === null || runQueryConfig === void 0 ? void 0 : runQueryConfig.getSchema) {
82
+ results = Object.assign(Object.assign({}, results), { columns: yield (0, RunQueryProcesses_1.getTableSchema)(queryResults[0], this.targetPool) });
83
+ }
84
+ if (runQueryConfig === null || runQueryConfig === void 0 ? void 0 : runQueryConfig.removeFields) {
85
+ results = Object.assign(Object.assign({}, results), { queryResults: (0, RunQueryProcesses_1.removeFields)(queryResults, runQueryConfig.removeFields) });
86
+ }
87
+ }
88
+ return results;
89
+ });
90
+ }
91
+ postQuill(path, payload) {
92
+ return __awaiter(this, void 0, void 0, function* () {
93
+ const response = yield axios_1.default.post(`${this.baseUrl}/sdk/${path}`, payload, this.config);
94
+ return response.data;
95
+ });
96
+ }
97
+ close() {
98
+ return __awaiter(this, void 0, void 0, function* () {
99
+ yield this.targetPool.close();
100
+ });
101
+ }
102
+ }
103
+ exports.default = Quill;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const _1 = __importDefault(require("."));
7
+ jest.mock(".");
8
+ describe("Quill", () => {
9
+ let quill;
10
+ beforeEach(() => {
11
+ quill = new _1.default("dummy_private_key", "dummy_db_url");
12
+ quill.targetPool.query = jest.fn().mockResolvedValue([]);
13
+ });
14
+ describe("query", () => {
15
+ it("return nothing when suppling no queries", () => {
16
+ const metadata = {
17
+ task: "test",
18
+ queries: [],
19
+ };
20
+ const result = quill.query({
21
+ orgId: "dummy",
22
+ metadata,
23
+ });
24
+ expect(result).resolves.toEqual([]);
25
+ });
26
+ it("returns an error for the improper query", () => {
27
+ const metadata = {
28
+ task: "test",
29
+ queries: ["SELECT * FROM test"],
30
+ };
31
+ const result = quill.query({
32
+ orgId: "dummy",
33
+ metadata,
34
+ });
35
+ });
36
+ });
37
+ // Add more test cases as needed
38
+ });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isSuperset = exports.PgError = void 0;
4
+ class PgError extends Error {
5
+ }
6
+ exports.PgError = PgError;
7
+ function isSuperset(obj, baseClass) {
8
+ // Get the property names of the base class
9
+ const baseProps = Object.getOwnPropertyNames(baseClass.prototype);
10
+ // Check if the object has all the properties of the base class
11
+ for (const prop of baseProps) {
12
+ if (!obj.hasOwnProperty(prop)) {
13
+ return false;
14
+ }
15
+ }
16
+ return true;
17
+ }
18
+ exports.isSuperset = isSuperset;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.mapQueries = exports.removeFields = exports.getTableSchema = void 0;
13
+ function getTableSchema(queryResults, targetPool) {
14
+ return __awaiter(this, void 0, void 0, function* () {
15
+ const typesQuery = yield targetPool.query("select typname, oid, typarray from pg_type order by oid;");
16
+ const schema = queryResults[0].fields.map((field) => {
17
+ return {
18
+ fieldType: typesQuery.rows.filter((type) => field.dataTypeID === type.oid)[0].typname,
19
+ name: field.name,
20
+ displayName: field.name,
21
+ isVisible: true,
22
+ };
23
+ });
24
+ return schema;
25
+ });
26
+ }
27
+ exports.getTableSchema = getTableSchema;
28
+ function removeFields(queryResults, fieldsToRemove) {
29
+ const fields = queryResults.fields.filter((field) => fieldsToRemove.includes(field.name));
30
+ const rows = queryResults.map((row) => {
31
+ fieldsToRemove.forEach((field) => {
32
+ delete row[field];
33
+ });
34
+ return { fields, rows };
35
+ });
36
+ }
37
+ exports.removeFields = removeFields;
38
+ function mapQueries(queries, arrayToMap, targetPool) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ let arrayToMapResult = [];
41
+ for (let i = 0; i < queries.length; i++) {
42
+ const queryResult = yield targetPool.query(queries[i]);
43
+ arrayToMapResult.push(Object.assign(Object.assign({}, arrayToMap.arr[i]), { [arrayToMap.field]: queryResult.rows }));
44
+ }
45
+ return arrayToMapResult;
46
+ });
47
+ }
48
+ exports.mapQueries = mapQueries;
@@ -0,0 +1,35 @@
1
+ // Write a simple node server to test the SDK
2
+
3
+ import express from "express";
4
+ import Quill from "../../src/index";
5
+ import cors from "cors";
6
+
7
+ const app = express();
8
+ const port = 3000;
9
+
10
+ const quill = new Quill(
11
+ process.env.QUILL_PRIVATE_KEY as string,
12
+ process.env.DB_URL as string,
13
+ {}
14
+ );
15
+
16
+ app.use(cors());
17
+ app.use(express.json());
18
+
19
+ app.get("/", (req, res) => {
20
+ res.send("Hello World!");
21
+ });
22
+
23
+ app.post("/quill", async (req, res) => {
24
+ const organizationId = req.body.orgId;
25
+ const { metadata } = req.body;
26
+ const result = await quill.query({
27
+ orgId: organizationId,
28
+ metadata,
29
+ });
30
+ res.send(result);
31
+ });
32
+
33
+ app.listen(port, () => {
34
+ console.log(`Example app listening at http://localhost:${port}`);
35
+ });
package/jest.config.js ADDED
@@ -0,0 +1,19 @@
1
+ module.exports = {
2
+ "browser": false,
3
+ "automock": false,
4
+ "testEnvironment": "node",
5
+ "collectCoverage": false,
6
+ "transform": {
7
+ ".(ts|tsx)": "ts-jest"
8
+ },
9
+ "transformIgnorePatterns": ['^.+\\.(js|json)$'],
10
+ "testRegex": "(/__tests__/.*|src.*?\\.(ispec|uspec))\\.(ts)$",
11
+ "testPathIgnorePatterns": [
12
+ "//node_modules/"
13
+ ],
14
+ "moduleFileExtensions": [
15
+ "ts",
16
+ "js",
17
+ "json"
18
+ ]
19
+ };
package/package.json CHANGED
@@ -1,23 +1,34 @@
1
1
  {
2
2
  "name": "@quillsql/node",
3
- "version": "0.2.7",
4
- "description": "Quill SDK for Node.js",
5
- "main": "dist/index.js",
3
+ "version": "0.2.9",
4
+ "description": "Quill's client SDK for node backends.",
5
+ "main": "dist/index.ts",
6
6
  "scripts": {
7
- "build": "tsc",
8
- "test": "echo \"Error: no test specified\" && exit 1"
7
+ "build": "tsc --outDir dist",
8
+ "start:dev": "nodemon ./examples/node-server/app.ts",
9
+ "integration-tests": "jest '.*\\.ispec\\.ts$'",
10
+ "test": "tsc src && jest --detectOpenHandles"
11
+ },
12
+ "author": "lagambino",
13
+ "license": "ISC",
14
+ "devDependencies": {
15
+ "@types/cors": "^2.8.17",
16
+ "@types/express": "^4.17.21",
17
+ "@types/jest": "^29.5.11",
18
+ "@types/node": "^20.10.7",
19
+ "@types/pg": "^8.10.9",
20
+ "cors": "^2.8.5",
21
+ "express": "^4.18.2",
22
+ "jest": "^29.7.0",
23
+ "nodemon": "^3.0.2",
24
+ "ts-node": "^10.9.2",
25
+ "typescript": "^5.3.3"
9
26
  },
10
- "author": "Quill",
11
- "license": "MIT",
12
27
  "dependencies": {
13
- "axios": "^0.21.4",
28
+ "axios": "^1.6.5",
14
29
  "dotenv": "^16.3.1",
15
- "pg": "^8.11.1",
16
- "pg-error": "^1.1.0"
17
- },
18
- "devDependencies": {
19
- "@types/node": "^20.4.2",
20
- "ts-node": "^10.9.1",
21
- "typescript": "^5.1.6"
30
+ "pg": "^8.11.3",
31
+ "redis": "^4.6.12",
32
+ "ts-jest": "^29.1.1"
22
33
  }
23
34
  }
@@ -0,0 +1,23 @@
1
+ import axios from "axios";
2
+
3
+ /** This client is currently not used but is a good design pratice */
4
+
5
+ class QuillServerClient {
6
+ private baseUrl: string;
7
+ private config: {
8
+ headers: {
9
+ Authorization: string;
10
+ };
11
+ };
12
+
13
+ constructor(privateKey: string) {
14
+ this.baseUrl = "";
15
+ this.config = { headers: { Authorization: `Bearer ${privateKey}` } };
16
+ }
17
+
18
+ public async postQuill(route: string, payload: any): Promise<any> {
19
+ return axios.post(`${this.baseUrl}/${route}`, payload, this.config);
20
+ }
21
+ }
22
+
23
+ export default QuillServerClient;
@@ -0,0 +1,97 @@
1
+ import { Pool } from "pg";
2
+ import { Mapable, CacheCredentials } from "../models/Cache";
3
+ import { QuillConfig } from "../models/Quill";
4
+ import { createClient } from "redis";
5
+ import { isSuperset } from "../utils/Error";
6
+
7
+ class PgError extends Error {
8
+ code?: string;
9
+ detail?: string;
10
+ hint?: string;
11
+ position?: string;
12
+ // Add other properties if needed
13
+ constructor(detail?: string, hint?: string, position?: string) {
14
+ super();
15
+ this.detail = detail;
16
+ this.hint = hint;
17
+ this.position = position;
18
+ }
19
+ }
20
+
21
+ /** The TTL for new cache entries (default: 1h) */
22
+ const DEFAULT_CACHE_TTL = 24 * 60 * 60;
23
+
24
+ export class CachedPool {
25
+ public pool: Pool;
26
+ public orgId: any;
27
+ public ttl: number;
28
+ public cache: Mapable | null;
29
+
30
+ constructor(config: any, cacheConfig: Partial<CacheCredentials> = {}) {
31
+ this.pool = new Pool(config);
32
+ this.ttl = cacheConfig?.ttl ?? DEFAULT_CACHE_TTL;
33
+ this.cache = this.getCache(cacheConfig);
34
+ }
35
+
36
+ public async query(text: string, values?: any[]): Promise<any> {
37
+ try {
38
+ if (!this.cache) {
39
+ const results = await this.pool.query(text, values);
40
+ return {
41
+ fields: results.fields.map((field: any) => ({
42
+ name: field.name,
43
+ dataTypeID: field.dataTypeID,
44
+ })),
45
+ rows: results.rows,
46
+ };
47
+ }
48
+ const key: string = `${this.orgId}:${text}`;
49
+ const cachedResult: string | null = await this.cache.get(key);
50
+ if (cachedResult) {
51
+ return JSON.parse(cachedResult);
52
+ } else {
53
+ const newResult: any = await this.pool.query(text, values);
54
+ const newResultString: string = JSON.stringify(newResult);
55
+ await this.cache.set(key, newResultString, "EX", DEFAULT_CACHE_TTL);
56
+ return {
57
+ fields: newResult.fields.map((field: any) => ({
58
+ name: field.name,
59
+ dataTypeID: field.dataTypeID,
60
+ })),
61
+ rows: newResult.rows,
62
+ };
63
+ }
64
+ } catch (err) {
65
+ if (isSuperset(err, PgError)) {
66
+ throw new PgError(
67
+ (err as any).detail,
68
+ (err as any).hint,
69
+ (err as any).position
70
+ );
71
+ } else if (err instanceof Error) {
72
+ throw new Error(err.message);
73
+ }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Configures and returns a cache instance or null if none could be created.
79
+ */
80
+ private getCache({
81
+ username,
82
+ password,
83
+ host,
84
+ port,
85
+ cacheType,
86
+ }: QuillConfig["cache"]): Mapable | null {
87
+ if (cacheType === "redis" || cacheType === "rediss") {
88
+ const redisURL = `${cacheType}://${username}:${password}@${host}:${port}`;
89
+ return createClient({ url: redisURL }) as Mapable;
90
+ }
91
+ return null;
92
+ }
93
+
94
+ async close() {
95
+ await this.pool.end();
96
+ }
97
+ }