@restura/core 0.1.0-alpha.11 → 0.1.0-alpha.13

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/dist/index.js CHANGED
@@ -1246,6 +1246,10 @@ function convertTable(table) {
1246
1246
  // src/restura/sql/PsqlEngine.ts
1247
1247
  var import_core_utils5 = require("@redskytech/core-utils");
1248
1248
 
1249
+ // src/restura/sql/PsqlPool.ts
1250
+ var import_pg = __toESM(require("pg"));
1251
+ var import_pg_format2 = __toESM(require("pg-format"));
1252
+
1249
1253
  // src/restura/sql/PsqlUtils.ts
1250
1254
  var import_pg_format = __toESM(require("pg-format"));
1251
1255
  function escapeColumnName(columnName) {
@@ -1281,7 +1285,9 @@ function isValueNumber2(value) {
1281
1285
  function SQL(strings, ...values) {
1282
1286
  let query = strings[0];
1283
1287
  values.forEach((value, index) => {
1284
- if (isValueNumber2(value)) {
1288
+ if (typeof value === "boolean") {
1289
+ query += value;
1290
+ } else if (typeof value === "number") {
1285
1291
  query += value;
1286
1292
  } else {
1287
1293
  query += import_pg_format.default.literal(value);
@@ -1291,6 +1297,76 @@ function SQL(strings, ...values) {
1291
1297
  return query;
1292
1298
  }
1293
1299
 
1300
+ // src/restura/sql/PsqlPool.ts
1301
+ var { Pool } = import_pg.default;
1302
+ var PsqlPool = class {
1303
+ constructor(poolConfig) {
1304
+ this.poolConfig = poolConfig;
1305
+ this.pool = new Pool(poolConfig);
1306
+ this.queryOne("SELECT NOW();", [], { isSystemUser: true, role: "", host: "localhost", ipAddress: "" }).then(() => {
1307
+ logger.info("Connected to PostgreSQL database");
1308
+ }).catch((error) => {
1309
+ logger.error("Error connecting to database", error);
1310
+ process.exit(1);
1311
+ });
1312
+ }
1313
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1314
+ async queryOne(query, options, requesterDetails) {
1315
+ const formattedQuery = questionMarksToOrderedParams(query);
1316
+ this.logSqlStatement(formattedQuery, options, requesterDetails);
1317
+ try {
1318
+ const response = await this.pool.query(formattedQuery, options);
1319
+ if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
1320
+ else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
1321
+ return response.rows[0];
1322
+ } catch (error) {
1323
+ console.error(error, query, options);
1324
+ if (RsError.isRsError(error)) throw error;
1325
+ if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
1326
+ throw new RsError("DUPLICATE", error.message);
1327
+ }
1328
+ throw new RsError("DATABASE_ERROR", `${error.message}`);
1329
+ }
1330
+ }
1331
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1332
+ async runQuery(query, options, requesterDetails) {
1333
+ const formattedQuery = questionMarksToOrderedParams(query);
1334
+ this.logSqlStatement(formattedQuery, options, requesterDetails);
1335
+ const queryUpdated = query.replace(/[\t\n]/g, " ");
1336
+ console.log(queryUpdated, options);
1337
+ try {
1338
+ const response = await this.pool.query(formattedQuery, options);
1339
+ return response.rows;
1340
+ } catch (error) {
1341
+ console.error(error, query, options);
1342
+ if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
1343
+ throw new RsError("DUPLICATE", error.message);
1344
+ }
1345
+ throw new RsError("DATABASE_ERROR", `${error.message}`);
1346
+ }
1347
+ }
1348
+ logSqlStatement(query, options, requesterDetails, prefix = "") {
1349
+ if (logger.level !== "silly") return;
1350
+ let sqlStatement = "";
1351
+ if (options.length === 0) {
1352
+ sqlStatement = query;
1353
+ } else {
1354
+ let stringIndex = 0;
1355
+ sqlStatement = query.replace(/\$\d+/g, () => {
1356
+ const value = options[stringIndex++];
1357
+ if (typeof value === "number") return value.toString();
1358
+ return import_pg_format2.default.literal(value);
1359
+ });
1360
+ }
1361
+ let initiator = "Anonymous";
1362
+ if ("userId" in requesterDetails && requesterDetails.userId)
1363
+ initiator = `User Id (${requesterDetails.userId.toString()})`;
1364
+ if ("isSystemUser" in requesterDetails && requesterDetails.isSystemUser) initiator = "SYSTEM";
1365
+ logger.silly(`${prefix}query by ${initiator}, Query ->
1366
+ ${sqlStatement}`);
1367
+ }
1368
+ };
1369
+
1294
1370
  // src/restura/sql/SqlEngine.ts
1295
1371
  var import_core_utils4 = require("@redskytech/core-utils");
1296
1372
  var SqlEngine = class {
@@ -1395,6 +1471,57 @@ var SqlEngine = class {
1395
1471
  // src/restura/sql/filterPsqlParser.ts
1396
1472
  var import_pegjs = __toESM(require("pegjs"));
1397
1473
  var filterSqlGrammar = `
1474
+ {
1475
+ // ported from pg-format but intentionally will add double quotes to every column
1476
+ function quoteSqlIdentity(value) {
1477
+ if (value === undefined || value === null) {
1478
+ throw new Error('SQL identifier cannot be null or undefined');
1479
+ } else if (value === false) {
1480
+ return '"f"';
1481
+ } else if (value === true) {
1482
+ return '"t"';
1483
+ } else if (value instanceof Date) {
1484
+ // return '"' + formatDate(value.toISOString()) + '"';
1485
+ } else if (value instanceof Buffer) {
1486
+ throw new Error('SQL identifier cannot be a buffer');
1487
+ } else if (Array.isArray(value) === true) {
1488
+ var temp = [];
1489
+ for (var i = 0; i < value.length; i++) {
1490
+ if (Array.isArray(value[i]) === true) {
1491
+ throw new Error('Nested array to grouped list conversion is not supported for SQL identifier');
1492
+ } else {
1493
+ // temp.push(quoteIdent(value[i]));
1494
+ }
1495
+ }
1496
+ return temp.toString();
1497
+ } else if (value === Object(value)) {
1498
+ throw new Error('SQL identifier cannot be an object');
1499
+ }
1500
+
1501
+ var ident = value.toString().slice(0); // create copy
1502
+
1503
+ // do not quote a valid, unquoted identifier
1504
+ // if (/^[a-z_][a-z0-9_$]*$/.test(ident) === true && isReserved(ident) === false) {
1505
+ // return ident;
1506
+ // }
1507
+
1508
+ var quoted = '"';
1509
+
1510
+ for (var i = 0; i < ident.length; i++) {
1511
+ var c = ident[i];
1512
+ if (c === '"') {
1513
+ quoted += c + c;
1514
+ } else {
1515
+ quoted += c;
1516
+ }
1517
+ }
1518
+
1519
+ quoted += '"';
1520
+
1521
+ return quoted;
1522
+ };
1523
+ }
1524
+
1398
1525
  start = expressionList
1399
1526
 
1400
1527
  expressionList =
@@ -1413,9 +1540,9 @@ negate = "!"
1413
1540
  operator = "and"i / "or"i
1414
1541
 
1415
1542
 
1416
- column = left:text "." right:text { return \`\${format.ident(left)}.\${format.ident(right)}\`; }
1543
+ column = left:text "." right:text { return \`\${quoteSqlIdentity(left)}.\${quoteSqlIdentity(right)}\`; }
1417
1544
  /
1418
- text:text { return format.ident(text); }
1545
+ text:text { return quoteSqlIdentity(text); }
1419
1546
 
1420
1547
 
1421
1548
  text = text:[a-z0-9-_:@]i+ { return text.join("");}
@@ -1441,18 +1568,155 @@ var filterPsqlParser = import_pegjs.default.generate(filterSqlGrammar, {
1441
1568
  var filterPsqlParser_default = filterPsqlParser;
1442
1569
 
1443
1570
  // src/restura/sql/PsqlEngine.ts
1571
+ var import_pg_diff_sync = __toESM(require("@wmfs/pg-diff-sync"));
1572
+ var import_pg_info = __toESM(require("@wmfs/pg-info"));
1573
+ var { Client } = "pg";
1574
+ var systemUser = {
1575
+ role: "",
1576
+ host: "",
1577
+ ipAddress: "",
1578
+ isSystemUser: true
1579
+ };
1444
1580
  var PsqlEngine = class extends SqlEngine {
1445
1581
  constructor(psqlConnectionPool) {
1446
1582
  super();
1447
1583
  this.psqlConnectionPool = psqlConnectionPool;
1448
1584
  }
1449
- async diffDatabaseToSchema(schema) {
1450
- console.log(schema);
1451
- return Promise.resolve("");
1585
+ async createDatabaseFromSchema(schema, connection) {
1586
+ const sqlFullStatement = this.generateDatabaseSchemaFromSchema(schema);
1587
+ await connection.runQuery(sqlFullStatement, [], systemUser);
1588
+ return sqlFullStatement;
1452
1589
  }
1453
1590
  generateDatabaseSchemaFromSchema(schema) {
1454
- console.log(schema);
1455
- return "";
1591
+ const sqlStatements = [];
1592
+ const enums = [];
1593
+ const indexes = [];
1594
+ for (const table of schema.database) {
1595
+ let sql = `CREATE TABLE "${table.name}"
1596
+ ( `;
1597
+ const tableColumns = [];
1598
+ for (const column of table.columns) {
1599
+ let columnSql = "";
1600
+ if (column.type === "ENUM") {
1601
+ enums.push(`CREATE TYPE ${schemaToPsqlType(column, table.name)} AS ENUM (${column.value});`);
1602
+ }
1603
+ columnSql += ` "${column.name}" ${schemaToPsqlType(column, table.name)}`;
1604
+ let value = column.value;
1605
+ if (column.type === "JSON") value = "";
1606
+ if (column.type === "JSONB") value = "";
1607
+ if (column.type === "DECIMAL" && value) {
1608
+ value = value.replace("-", ",").replace(/['"]/g, "");
1609
+ }
1610
+ if (value && column.type !== "ENUM") {
1611
+ columnSql += `(${value})`;
1612
+ } else if (column.length) columnSql += `(${column.length})`;
1613
+ if (column.isPrimary) {
1614
+ columnSql += " PRIMARY KEY ";
1615
+ }
1616
+ if (column.isUnique) {
1617
+ columnSql += ` CONSTRAINT "${table.name}_${column.name}_unique_index" UNIQUE `;
1618
+ }
1619
+ if (column.isNullable) columnSql += " NULL";
1620
+ else columnSql += " NOT NULL";
1621
+ if (column.default) columnSql += ` DEFAULT '${column.default}'`;
1622
+ tableColumns.push(columnSql);
1623
+ }
1624
+ sql += tableColumns.join(", \n");
1625
+ for (const index of table.indexes) {
1626
+ if (!index.isPrimaryKey) {
1627
+ let unique = " ";
1628
+ if (index.isUnique) unique = "UNIQUE ";
1629
+ indexes.push(
1630
+ ` CREATE ${unique}INDEX "${index.name}" ON "${table.name}" (${index.columns.map((item) => {
1631
+ return `"${item}" ${index.order}`;
1632
+ }).join(", ")})`
1633
+ );
1634
+ }
1635
+ }
1636
+ sql += "\n);";
1637
+ sqlStatements.push(sql);
1638
+ }
1639
+ for (const table of schema.database) {
1640
+ if (!table.foreignKeys.length) continue;
1641
+ const sql = `ALTER TABLE "${table.name}" `;
1642
+ const constraints = [];
1643
+ for (const foreignKey of table.foreignKeys) {
1644
+ let constraint = ` ADD CONSTRAINT "${foreignKey.name}"
1645
+ FOREIGN KEY ("${foreignKey.column}") REFERENCES "${foreignKey.refTable}" ("${foreignKey.refColumn}")`;
1646
+ constraint += ` ON DELETE ${foreignKey.onDelete}`;
1647
+ constraint += ` ON UPDATE ${foreignKey.onUpdate}`;
1648
+ constraints.push(constraint);
1649
+ }
1650
+ sqlStatements.push(sql + constraints.join(",\n") + ";");
1651
+ }
1652
+ for (const table of schema.database) {
1653
+ if (!table.checkConstraints.length) continue;
1654
+ const sql = `ALTER TABLE "${table.name}" `;
1655
+ const constraints = [];
1656
+ for (const check of table.checkConstraints) {
1657
+ const constraint = `ADD CONSTRAINT "${check.name}" CHECK (${check.check})`;
1658
+ constraints.push(constraint);
1659
+ }
1660
+ sqlStatements.push(sql + constraints.join(",\n") + ";");
1661
+ }
1662
+ sqlStatements.push(indexes.join(";\n"));
1663
+ return enums.join("\n") + "\n" + sqlStatements.join("\n\n");
1664
+ }
1665
+ async getScratchPool() {
1666
+ await this.psqlConnectionPool.runQuery(
1667
+ `DROP DATABASE IF EXISTS ${this.psqlConnectionPool.poolConfig.database}_scratch`,
1668
+ [],
1669
+ systemUser
1670
+ );
1671
+ await this.psqlConnectionPool.runQuery(
1672
+ `CREATE DATABASE ${this.psqlConnectionPool.poolConfig.database}_scratch;`,
1673
+ [],
1674
+ systemUser
1675
+ );
1676
+ const scratchPool = new PsqlPool({
1677
+ host: this.psqlConnectionPool.poolConfig.host,
1678
+ port: this.psqlConnectionPool.poolConfig.port,
1679
+ user: this.psqlConnectionPool.poolConfig.user,
1680
+ database: this.psqlConnectionPool.poolConfig.database + "_scratch",
1681
+ password: this.psqlConnectionPool.poolConfig.password,
1682
+ max: this.psqlConnectionPool.poolConfig.max,
1683
+ idleTimeoutMillis: this.psqlConnectionPool.poolConfig.idleTimeoutMillis,
1684
+ connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
1685
+ });
1686
+ return scratchPool;
1687
+ }
1688
+ async diffDatabaseToSchema(schema) {
1689
+ const scratchPool = await this.getScratchPool();
1690
+ await this.createDatabaseFromSchema(schema, scratchPool);
1691
+ const originalClient = new Client({
1692
+ database: this.psqlConnectionPool.poolConfig.database,
1693
+ user: this.psqlConnectionPool.poolConfig.user,
1694
+ password: this.psqlConnectionPool.poolConfig.password,
1695
+ host: this.psqlConnectionPool.poolConfig.host,
1696
+ port: this.psqlConnectionPool.poolConfig.port
1697
+ });
1698
+ const scratchClient = new Client({
1699
+ database: this.psqlConnectionPool.poolConfig.database + "_scratch",
1700
+ user: this.psqlConnectionPool.poolConfig.user,
1701
+ password: this.psqlConnectionPool.poolConfig.password,
1702
+ host: this.psqlConnectionPool.poolConfig.host,
1703
+ port: this.psqlConnectionPool.poolConfig.port
1704
+ });
1705
+ await originalClient.connect();
1706
+ await scratchClient.connect();
1707
+ const info1 = await (0, import_pg_info.default)({
1708
+ client: originalClient,
1709
+ schema: "public"
1710
+ });
1711
+ const info2 = await (0, import_pg_info.default)({
1712
+ client: scratchClient,
1713
+ schema: "public"
1714
+ });
1715
+ const diff = (0, import_pg_diff_sync.default)(info1, info2);
1716
+ console.log("Schema differences:", diff);
1717
+ await originalClient.end();
1718
+ await scratchClient.end();
1719
+ return diff.join("\n");
1456
1720
  }
1457
1721
  createNestedSelect(req, schema, item, routeData, userRole, sqlParams) {
1458
1722
  if (!item.subquery) return "";
@@ -1563,22 +1827,20 @@ var PsqlEngine = class extends SqlEngine {
1563
1827
  );
1564
1828
  } else if (routeData.type === "PAGED") {
1565
1829
  const data = req.data;
1566
- const pageResults = await this.psqlConnectionPool.runQuery(
1567
- `${selectStatement}${sqlStatement}${groupByOrderByStatement} LIMIT ? OFFSET ?;SELECT COUNT(${routeData.groupBy ? `DISTINCT ${routeData.groupBy.tableName}.${routeData.groupBy.columnName}` : "*"}) AS total
1568
- ${sqlStatement};`,
1569
- [
1570
- ...sqlParams,
1571
- data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER,
1572
- (data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER,
1573
- ...sqlParams
1574
- ],
1830
+ const pagePromise = this.psqlConnectionPool.runQuery(
1831
+ `${selectStatement}${sqlStatement}${groupByOrderByStatement}` + SQL`LIMIT ${data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER} OFFSET ${(data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER};`,
1832
+ sqlParams,
1575
1833
  req.requesterDetails
1576
1834
  );
1835
+ const totalQuery = `SELECT COUNT(${routeData.groupBy ? `DISTINCT ${routeData.groupBy.tableName}.${routeData.groupBy.columnName}` : "*"}) AS total
1836
+ ${sqlStatement};`;
1837
+ const totalPromise = await this.psqlConnectionPool.runQuery(totalQuery, sqlParams, req.requesterDetails);
1838
+ const [pageResults, totalResponse] = await Promise.all([pagePromise, totalPromise]);
1577
1839
  let total = 0;
1578
- if (import_core_utils5.ObjectUtils.isArrayWithData(pageResults)) {
1579
- total = pageResults[1][0].total;
1840
+ if (import_core_utils5.ObjectUtils.isArrayWithData(totalResponse)) {
1841
+ total = totalResponse[0].total;
1580
1842
  }
1581
- return { data: pageResults[0], total };
1843
+ return { data: pageResults, total };
1582
1844
  } else {
1583
1845
  throw new RsError("UNKNOWN_ERROR", "Unknown route type.");
1584
1846
  }
@@ -1728,77 +1990,12 @@ ${sqlStatement};`,
1728
1990
  return whereClause;
1729
1991
  }
1730
1992
  };
1731
-
1732
- // src/restura/sql/PsqlPool.ts
1733
- var import_pg = __toESM(require("pg"));
1734
- var import_pg_format2 = __toESM(require("pg-format"));
1735
- var { Pool } = import_pg.default;
1736
- var PsqlPool = class {
1737
- constructor(poolConfig) {
1738
- this.poolConfig = poolConfig;
1739
- this.pool = new Pool(poolConfig);
1740
- this.queryOne("SELECT NOW();", [], { isSystemUser: true, role: "", host: "localhost", ipAddress: "" }).then(() => {
1741
- logger.info("Connected to PostgreSQL database");
1742
- }).catch((error) => {
1743
- logger.error("Error connecting to database", error);
1744
- process.exit(1);
1745
- });
1746
- }
1747
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1748
- async queryOne(query, options, requesterDetails) {
1749
- const formattedQuery = questionMarksToOrderedParams(query);
1750
- this.logSqlStatement(formattedQuery, options, requesterDetails);
1751
- try {
1752
- const response = await this.pool.query(formattedQuery, options);
1753
- if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
1754
- else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
1755
- return response.rows[0];
1756
- } catch (error) {
1757
- console.error(error, query, options);
1758
- if (RsError.isRsError(error)) throw error;
1759
- if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
1760
- throw new RsError("DUPLICATE", error.message);
1761
- }
1762
- throw new RsError("DATABASE_ERROR", `${error.message}`);
1763
- }
1764
- }
1765
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1766
- async runQuery(query, options, requesterDetails) {
1767
- const formattedQuery = questionMarksToOrderedParams(query);
1768
- this.logSqlStatement(formattedQuery, options, requesterDetails);
1769
- const queryUpdated = query.replace(/[\t\n]/g, " ");
1770
- console.log(queryUpdated, options);
1771
- try {
1772
- const response = await this.pool.query(formattedQuery, options);
1773
- return response.rows;
1774
- } catch (error) {
1775
- console.error(error, query, options);
1776
- if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
1777
- throw new RsError("DUPLICATE", error.message);
1778
- }
1779
- throw new RsError("DATABASE_ERROR", `${error.message}`);
1780
- }
1781
- }
1782
- logSqlStatement(query, options, requesterDetails, prefix = "") {
1783
- if (logger.level !== "silly") return;
1784
- let sqlStatement = "";
1785
- if (options.length === 0) {
1786
- sqlStatement = query;
1787
- } else {
1788
- let stringIndex = 0;
1789
- sqlStatement = query.replace(/\$\d+/g, () => {
1790
- const value = options[stringIndex++];
1791
- if (typeof value === "number") return value.toString();
1792
- return import_pg_format2.default.literal(value);
1793
- });
1794
- }
1795
- let initiator = "Anonymous";
1796
- if ("userId" in requesterDetails && requesterDetails.userId)
1797
- initiator = `User Id (${requesterDetails.userId.toString()})`;
1798
- if ("isSystemUser" in requesterDetails && requesterDetails.isSystemUser) initiator = "SYSTEM";
1799
- logger.silly(`${prefix}query by ${initiator}, Query ->
1800
- ${sqlStatement}`);
1801
- }
1993
+ var schemaToPsqlType = (column, tableName) => {
1994
+ if (column.hasAutoIncrement) return "BIGSERIAL";
1995
+ if (column.type === "ENUM") return `"${tableName}_${column.name}_enum"`;
1996
+ if (column.type === "DATETIME") return "TIMESTAMPTZ";
1997
+ if (column.type === "MEDIUMINT") return "INT";
1998
+ return column.type;
1802
1999
  };
1803
2000
 
1804
2001
  // src/restura/restura.ts