@restura/core 0.1.0-alpha.20 → 0.1.0-alpha.22

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.mjs CHANGED
@@ -711,6 +711,8 @@ function addApiResponseFunctions(req, res, next) {
711
711
  function authenticateUser(applicationAuthenticateHandler) {
712
712
  return (req, res, next) => {
713
713
  applicationAuthenticateHandler(req, res, (userDetails) => {
714
+ req.requesterDetails.host = req.hostname;
715
+ req.requesterDetails.ipAddress = req.ip || "";
714
716
  req.requesterDetails = __spreadValues(__spreadValues({}, req.requesterDetails), userDetails);
715
717
  next();
716
718
  });
@@ -1283,14 +1285,17 @@ function SQL(strings, ...values) {
1283
1285
  }
1284
1286
 
1285
1287
  // src/restura/sql/PsqlConnection.ts
1288
+ import crypto from "crypto";
1286
1289
  var PsqlConnection = class {
1287
1290
  constructor() {
1291
+ this.instanceId = crypto.randomUUID();
1288
1292
  }
1289
1293
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1290
1294
  async queryOne(query, options, requesterDetails) {
1291
1295
  const formattedQuery = questionMarksToOrderedParams(query);
1292
- this.logSqlStatement(formattedQuery, options, requesterDetails);
1293
- const queryMetadata = `--QUERY_METADATA(${JSON.stringify(requesterDetails)})
1296
+ const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
1297
+ this.logSqlStatement(formattedQuery, options, meta);
1298
+ const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
1294
1299
  `;
1295
1300
  try {
1296
1301
  const response = await this.query(queryMetadata + formattedQuery, options);
@@ -1309,8 +1314,9 @@ var PsqlConnection = class {
1309
1314
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1310
1315
  async runQuery(query, options, requesterDetails) {
1311
1316
  const formattedQuery = questionMarksToOrderedParams(query);
1312
- this.logSqlStatement(formattedQuery, options, requesterDetails);
1313
- const queryMetadata = `--QUERY_METADATA(${JSON.stringify(requesterDetails)})
1317
+ const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
1318
+ this.logSqlStatement(formattedQuery, options, meta);
1319
+ const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
1314
1320
  `;
1315
1321
  try {
1316
1322
  const response = await this.query(queryMetadata + formattedQuery, options);
@@ -1323,7 +1329,7 @@ var PsqlConnection = class {
1323
1329
  throw new RsError("DATABASE_ERROR", `${error.message}`);
1324
1330
  }
1325
1331
  }
1326
- logSqlStatement(query, options, requesterDetails, prefix = "") {
1332
+ logSqlStatement(query, options, queryMetadata, prefix = "") {
1327
1333
  if (logger.level !== "silly") return;
1328
1334
  let sqlStatement = "";
1329
1335
  if (options.length === 0) {
@@ -1337,9 +1343,9 @@ var PsqlConnection = class {
1337
1343
  });
1338
1344
  }
1339
1345
  let initiator = "Anonymous";
1340
- if ("userId" in requesterDetails && requesterDetails.userId)
1341
- initiator = `User Id (${requesterDetails.userId.toString()})`;
1342
- if ("isSystemUser" in requesterDetails && requesterDetails.isSystemUser) initiator = "SYSTEM";
1346
+ if ("userId" in queryMetadata && queryMetadata.userId)
1347
+ initiator = `User Id (${queryMetadata.userId.toString()})`;
1348
+ if ("isSystemUser" in queryMetadata && queryMetadata.isSystemUser) initiator = "SYSTEM";
1343
1349
  logger.silly(`${prefix}query by ${initiator}, Query ->
1344
1350
  ${sqlStatement}`);
1345
1351
  }
@@ -1521,13 +1527,15 @@ var filterSqlGrammar = `
1521
1527
 
1522
1528
  start = expressionList
1523
1529
 
1530
+ _ = [ \\t\\r\\n]* // Matches spaces, tabs, and line breaks
1531
+
1524
1532
  expressionList =
1525
- leftExpression:expression operator:operator rightExpression:expressionList
1533
+ leftExpression:expression _ operator:operator _ rightExpression:expressionList
1526
1534
  { return \`\${leftExpression} \${operator} \${rightExpression}\`;}
1527
1535
  / expression
1528
1536
 
1529
1537
  expression =
1530
- negate:negate?"(" "column:" column:column ","? value:value? ","? type:type? ")"
1538
+ negate:negate? _ "(" _ "column" _ ":" column:column _ ","? _ value:value? ","? _ type:type? _ ")"_
1531
1539
  {return \`\${negate? " NOT " : ""}(\${type? type(column, value) : \`\${column} = \${format.literal(value)}\`})\`;}
1532
1540
  /
1533
1541
  negate:negate?"("expression:expressionList")" { return \`\${negate? " NOT " : ""}(\${expression})\`; }
@@ -1542,9 +1550,9 @@ column = left:text "." right:text { return \`\${quoteSqlIdentity(left)}.\${quot
1542
1550
  text:text { return quoteSqlIdentity(text); }
1543
1551
 
1544
1552
 
1545
- text = text:[a-z0-9-_:@]i+ { return text.join("");}
1553
+ text = text:[a-z0-9 \\t\\r\\n\\-_:@]i+ { return text.join(""); }
1546
1554
 
1547
- type = "type:" type:typeString { return type; }
1555
+ type = "type" _ ":" _ type:typeString { return type; }
1548
1556
  typeString = text:"startsWith" { return function(column, value) { return \`\${column} ILIKE '\${format.literal(value).slice(1,-1)}%'\`; } } /
1549
1557
  text:"endsWith" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}'\`; } } /
1550
1558
  text:"contains" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}%'\`; } } /
@@ -1554,8 +1562,9 @@ typeString = text:"startsWith" { return function(column, value) { return \`\${co
1554
1562
  text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${format.literal(value).slice(1,-1)}'\`; } } /
1555
1563
  text:"lessThan" { return function(column, value) { return \`\${column} < '\${format.literal(value).slice(1,-1)}'\`; } } /
1556
1564
  text:"isNull" { return function(column, value) { return \`isNull(\${column})\`; } }
1557
-
1558
- value = "value:" value:text { return value; }
1565
+
1566
+ value = "value" _ ":" value:text { return value; }
1567
+
1559
1568
 
1560
1569
  `;
1561
1570
  var filterPsqlParser = peg.generate(filterSqlGrammar, {
@@ -1610,9 +1619,9 @@ var EventManager = class {
1610
1619
  tableName: triggerResult.table,
1611
1620
  insertId: triggerResult.record.id,
1612
1621
  insertObject: triggerResult.record,
1613
- requesterDetails: data.requesterDetails
1622
+ queryMetadata: data.queryMetadata
1614
1623
  };
1615
- callback(insertData, data.requesterDetails);
1624
+ callback(insertData, data.queryMetadata);
1616
1625
  },
1617
1626
  { concurrency: 10 }
1618
1627
  );
@@ -1625,9 +1634,9 @@ var EventManager = class {
1625
1634
  const deleteData = {
1626
1635
  tableName: triggerResult.table,
1627
1636
  deletedRow: triggerResult.previousRecord,
1628
- requesterDetails: data.requesterDetails
1637
+ queryMetadata: data.queryMetadata
1629
1638
  };
1630
- callback(deleteData, data.requesterDetails);
1639
+ callback(deleteData, data.queryMetadata);
1631
1640
  },
1632
1641
  { concurrency: 10 }
1633
1642
  );
@@ -1642,9 +1651,9 @@ var EventManager = class {
1642
1651
  rowId: triggerResult.record.id,
1643
1652
  newData: triggerResult.record,
1644
1653
  oldData: triggerResult.previousRecord,
1645
- requesterDetails: data.requesterDetails
1654
+ queryMetadata: data.queryMetadata
1646
1655
  };
1647
- callback(columnChangeData, data.requesterDetails);
1656
+ callback(columnChangeData, data.queryMetadata);
1648
1657
  },
1649
1658
  { concurrency: 10 }
1650
1659
  );
@@ -1659,6 +1668,13 @@ var EventManager = class {
1659
1668
  case "DATABASE_COLUMN_UPDATE":
1660
1669
  const filterColumnChange = filter;
1661
1670
  if (filterColumnChange.tableName !== filter.tableName) return false;
1671
+ if (!filterColumnChange.columns.some((item) => {
1672
+ const updatedColumns = Object.keys(
1673
+ changedValues(triggerResult.record, triggerResult.previousRecord)
1674
+ );
1675
+ return updatedColumns.includes(item);
1676
+ }))
1677
+ return false;
1662
1678
  break;
1663
1679
  }
1664
1680
  }
@@ -1667,6 +1683,22 @@ var EventManager = class {
1667
1683
  };
1668
1684
  var eventManager = new EventManager();
1669
1685
  var eventManager_default = eventManager;
1686
+ function changedValues(record, previousRecord) {
1687
+ const changed = {};
1688
+ for (const i in previousRecord) {
1689
+ if (previousRecord[i] !== record[i]) {
1690
+ if (typeof previousRecord[i] === "object" && typeof record[i] === "object") {
1691
+ const nestedChanged = changedValues(record[i], previousRecord[i]);
1692
+ if (Object.keys(nestedChanged).length > 0) {
1693
+ changed[i] = record[i];
1694
+ }
1695
+ } else {
1696
+ changed[i] = record[i];
1697
+ }
1698
+ }
1699
+ }
1700
+ return changed;
1701
+ }
1670
1702
 
1671
1703
  // src/restura/sql/PsqlEngine.ts
1672
1704
  var { Client } = pg2;
@@ -1713,12 +1745,15 @@ var PsqlEngine = class extends SqlEngine {
1713
1745
  }
1714
1746
  async handleTrigger(payload, mutationType) {
1715
1747
  const findRequesterDetailsRegex = /^--QUERY_METADATA\(\{.*\}\)/;
1716
- let requesterDetails = {};
1717
1748
  const match = payload.query.match(findRequesterDetailsRegex);
1718
1749
  if (match) {
1719
1750
  const jsonString = match[0].slice(match[0].indexOf("{"), match[0].lastIndexOf("}") + 1);
1720
- requesterDetails = ObjectUtils4.safeParse(jsonString);
1721
- await eventManager_default.fireActionFromDbTrigger({ requesterDetails, mutationType }, payload);
1751
+ const queryMetadata = ObjectUtils4.safeParse(jsonString);
1752
+ const triggerFromThisInstance = queryMetadata.connectionInstanceId === this.psqlConnectionPool.instanceId;
1753
+ if (!triggerFromThisInstance) {
1754
+ return;
1755
+ }
1756
+ await eventManager_default.fireActionFromDbTrigger({ queryMetadata, mutationType }, payload);
1722
1757
  }
1723
1758
  }
1724
1759
  async createDatabaseFromSchema(schema, connection) {
@@ -1728,7 +1763,6 @@ var PsqlEngine = class extends SqlEngine {
1728
1763
  }
1729
1764
  generateDatabaseSchemaFromSchema(schema) {
1730
1765
  const sqlStatements = [];
1731
- const enums = [];
1732
1766
  const indexes = [];
1733
1767
  const triggers = [];
1734
1768
  for (const table of schema.database) {
@@ -1740,10 +1774,7 @@ var PsqlEngine = class extends SqlEngine {
1740
1774
  const tableColumns = [];
1741
1775
  for (const column of table.columns) {
1742
1776
  let columnSql = "";
1743
- if (column.type === "ENUM") {
1744
- enums.push(`CREATE TYPE ${schemaToPsqlType(column, table.name)} AS ENUM (${column.value});`);
1745
- }
1746
- columnSql += ` "${column.name}" ${schemaToPsqlType(column, table.name)}`;
1777
+ columnSql += ` "${column.name}" ${schemaToPsqlType(column)}`;
1747
1778
  let value = column.value;
1748
1779
  if (column.type === "JSON") value = "";
1749
1780
  if (column.type === "JSONB") value = "";
@@ -1762,6 +1793,9 @@ var PsqlEngine = class extends SqlEngine {
1762
1793
  if (column.isNullable) columnSql += " NULL";
1763
1794
  else columnSql += " NOT NULL";
1764
1795
  if (column.default) columnSql += ` DEFAULT ${column.default}`;
1796
+ if (value && column.type === "ENUM") {
1797
+ columnSql += ` CHECK ("${column.name}" IN (${value}))`;
1798
+ }
1765
1799
  tableColumns.push(columnSql);
1766
1800
  }
1767
1801
  sql += tableColumns.join(", \n");
@@ -1804,16 +1838,18 @@ var PsqlEngine = class extends SqlEngine {
1804
1838
  }
1805
1839
  sqlStatements.push(indexes.join("\n"));
1806
1840
  sqlStatements.push(triggers.join("\n"));
1807
- return enums.join("\n") + "\n" + sqlStatements.join("\n\n");
1841
+ return sqlStatements.join("\n\n");
1808
1842
  }
1809
1843
  async getScratchPool() {
1810
- const response = await this.psqlConnectionPool.runQuery(
1811
- `SELECT * FROM pg_database
1812
- WHERE datname = '${this.psqlConnectionPool.poolConfig.database}_scratch'`,
1844
+ var _a2, _b;
1845
+ const scratchDbExists = await this.psqlConnectionPool.runQuery(
1846
+ `SELECT *
1847
+ FROM pg_database
1848
+ WHERE datname = '${this.psqlConnectionPool.poolConfig.database}_scratch';`,
1813
1849
  [],
1814
1850
  systemUser
1815
1851
  );
1816
- if (response.length === 0) {
1852
+ if (scratchDbExists.length === 0) {
1817
1853
  await this.psqlConnectionPool.runQuery(
1818
1854
  `CREATE DATABASE ${this.psqlConnectionPool.poolConfig.database}_scratch;`,
1819
1855
  [],
@@ -1830,12 +1866,27 @@ var PsqlEngine = class extends SqlEngine {
1830
1866
  idleTimeoutMillis: this.psqlConnectionPool.poolConfig.idleTimeoutMillis,
1831
1867
  connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
1832
1868
  });
1833
- await scratchPool.runQuery(`drop schema public cascade;`, [], systemUser);
1869
+ await scratchPool.runQuery(`DROP SCHEMA public CASCADE;`, [], systemUser);
1834
1870
  await scratchPool.runQuery(
1835
- `create schema public authorization ${this.psqlConnectionPool.poolConfig.user};`,
1871
+ `CREATE SCHEMA public AUTHORIZATION ${this.psqlConnectionPool.poolConfig.user};`,
1872
+ [],
1873
+ systemUser
1874
+ );
1875
+ const schemaComment = await this.psqlConnectionPool.runQuery(
1876
+ `SELECT pg_description.description
1877
+ FROM pg_description
1878
+ JOIN pg_namespace ON pg_namespace.oid = pg_description.objoid
1879
+ WHERE pg_namespace.nspname = 'public';`,
1836
1880
  [],
1837
1881
  systemUser
1838
1882
  );
1883
+ if ((_a2 = schemaComment[0]) == null ? void 0 : _a2.description) {
1884
+ await scratchPool.runQuery(
1885
+ `COMMENT ON SCHEMA public IS '${(_b = schemaComment[0]) == null ? void 0 : _b.description}';`,
1886
+ [],
1887
+ systemUser
1888
+ );
1889
+ }
1839
1890
  return scratchPool;
1840
1891
  }
1841
1892
  async diffDatabaseToSchema(schema) {
@@ -2205,9 +2256,9 @@ __decorateClass([
2205
2256
  __decorateClass([
2206
2257
  boundMethod
2207
2258
  ], PsqlEngine.prototype, "createInsertTriggers", 1);
2208
- function schemaToPsqlType(column, tableName) {
2259
+ function schemaToPsqlType(column) {
2209
2260
  if (column.hasAutoIncrement) return "BIGSERIAL";
2210
- if (column.type === "ENUM") return `"${tableName}_${column.name}_enum"`;
2261
+ if (column.type === "ENUM") return `TEXT`;
2211
2262
  if (column.type === "DATETIME") return "TIMESTAMPTZ";
2212
2263
  if (column.type === "MEDIUMINT") return "INT";
2213
2264
  return column.type;
@@ -2750,7 +2801,7 @@ __decorateClass([
2750
2801
  __decorateClass([
2751
2802
  boundMethod
2752
2803
  ], ResturaEngine.prototype, "runCustomRouteLogic", 1);
2753
- var setupPgReturnTypes = () => {
2804
+ function setupPgReturnTypes() {
2754
2805
  const TIMESTAMPTZ_OID = 1184;
2755
2806
  types.setTypeParser(TIMESTAMPTZ_OID, (val) => {
2756
2807
  return val === null ? null : new Date(val).toISOString();
@@ -2759,7 +2810,7 @@ var setupPgReturnTypes = () => {
2759
2810
  types.setTypeParser(BIGINT_OID, (val) => {
2760
2811
  return val === null ? null : Number(val);
2761
2812
  });
2762
- };
2813
+ }
2763
2814
  setupPgReturnTypes();
2764
2815
  var restura = new ResturaEngine();
2765
2816