@restura/core 0.1.0-alpha.15 → 0.1.0-alpha.17

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.d.mts CHANGED
@@ -1392,10 +1392,10 @@ interface AsyncExpressApplication {
1392
1392
  }
1393
1393
 
1394
1394
  declare abstract class PsqlConnection {
1395
- constructor();
1395
+ protected constructor();
1396
1396
  protected abstract query<R extends QueryResultRow = QueryResultRow, T extends Array<unknown> = unknown[]>(query: string, values?: QueryConfigValues<T>): Promise<QueryResult<R>>;
1397
- queryOne(query: string, options: any[], requesterDetails: RequesterDetails): Promise<QueryResultRow>;
1398
- runQuery(query: string, options: any[], requesterDetails: RequesterDetails): Promise<QueryResultRow[]>;
1397
+ queryOne<T>(query: string, options: any[], requesterDetails: RequesterDetails): Promise<T>;
1398
+ runQuery<T>(query: string, options: any[], requesterDetails: RequesterDetails): Promise<T[]>;
1399
1399
  private logSqlStatement;
1400
1400
  }
1401
1401
 
@@ -1406,18 +1406,6 @@ declare class PsqlPool extends PsqlConnection {
1406
1406
  protected query<R extends QueryResultRow = QueryResultRow, T extends Array<unknown> = unknown[]>(query: string, values?: QueryConfigValues<T>): Promise<QueryResult<R>>;
1407
1407
  }
1408
1408
 
1409
- declare class PsqlTransaction extends PsqlConnection {
1410
- clientConfig: ClientConfig;
1411
- client: Client;
1412
- private beginTransactionPromise;
1413
- constructor(clientConfig: ClientConfig);
1414
- private beginTransaction;
1415
- rollback(): Promise<QueryResult<QueryResultRow>>;
1416
- commit(): Promise<QueryResult<QueryResultRow>>;
1417
- release(): Promise<void>;
1418
- protected query<R extends QueryResultRow = QueryResultRow, T extends Array<unknown> = unknown[]>(query: string, values?: QueryConfigValues<T>): Promise<QueryResult<R>>;
1419
- }
1420
-
1421
1409
  declare class ResturaEngine {
1422
1410
  resturaConfig: ResturaConfigSchema;
1423
1411
  private resturaRouter;
@@ -1516,4 +1504,69 @@ declare function updateObjectQuery(table: string, obj: DynamicObject, whereState
1516
1504
  declare function isValueNumber(value: unknown): value is number;
1517
1505
  declare function SQL(strings: any, ...values: any): any;
1518
1506
 
1519
- export { type ApiMethod, type AsyncExpressApplication, type AuthenticateHandler, type AuthenticationUserDetails, type ConjunctionTypes, type DynamicObject, type ErrorCode, HtmlStatusCodes, type LoginDetails, type MatchTypes, type PageQuery, PsqlConnection, PsqlPool, PsqlTransaction, type RequesterDetails, type ResponseType, type ResponseTypeMap, RsError, type RsErrorData, type RsErrorInternalData, type RsHeaders, type RsPagedResponseData, type RsRequest, type RsResponse, type RsResponseData, type RsRouteHandler, SQL, type SchemaChangeValue, type SchemaPreview, type StandardOrderTypes, type ValidAuthenticationCallback, type ValidatorString, escapeColumnName, insertObjectQuery, isValueNumber, logger, questionMarksToOrderedParams, restura, updateObjectQuery };
1507
+ declare class PsqlTransaction extends PsqlConnection {
1508
+ clientConfig: ClientConfig;
1509
+ client: Client;
1510
+ private beginTransactionPromise;
1511
+ constructor(clientConfig: ClientConfig);
1512
+ private beginTransaction;
1513
+ rollback(): Promise<QueryResult<QueryResultRow>>;
1514
+ commit(): Promise<QueryResult<QueryResultRow>>;
1515
+ release(): Promise<void>;
1516
+ protected query<R extends QueryResultRow = QueryResultRow, T extends Array<unknown> = unknown[]>(query: string, values?: QueryConfigValues<T>): Promise<QueryResult<R>>;
1517
+ }
1518
+
1519
+ type EventType = 'DATABASE_ROW_DELETE' | 'DATABASE_ROW_INSERT' | 'DATABASE_COLUMN_UPDATE' | 'WEBHOOK';
1520
+ type MutationType = 'INSERT' | 'UPDATE' | 'DELETE';
1521
+ interface SqlMutationData {
1522
+ mutationType: MutationType;
1523
+ requesterDetails: RequesterDetails;
1524
+ }
1525
+ interface DatabaseActionData {
1526
+ tableName: string;
1527
+ requesterDetails: RequesterDetails;
1528
+ }
1529
+ interface ActionRowInsertData<T = DynamicObject> extends DatabaseActionData {
1530
+ insertId: number;
1531
+ insertObject: T;
1532
+ }
1533
+ interface ActionRowDeleteData extends DatabaseActionData {
1534
+ deletedRow: DynamicObject;
1535
+ }
1536
+ interface ActionColumnChangeData extends DatabaseActionData {
1537
+ tableName: string;
1538
+ rowId: number;
1539
+ newData: DynamicObject;
1540
+ oldData: DynamicObject;
1541
+ }
1542
+ interface ActionRowInsertFilter {
1543
+ tableName: string;
1544
+ }
1545
+ interface ActionRowDeleteFilter {
1546
+ tableName: string;
1547
+ }
1548
+ interface ActionColumnChangeFilter {
1549
+ tableName: string;
1550
+ }
1551
+ type TriggerResult = {
1552
+ table: string;
1553
+ insertId?: number;
1554
+ query: string;
1555
+ record: DynamicObject;
1556
+ previousRecord: DynamicObject;
1557
+ requesterId: number;
1558
+ };
1559
+ declare class EventManager {
1560
+ private actionHandlers;
1561
+ addRowInsertHandler(onInsert: (data: ActionRowInsertData<unknown>) => Promise<void>, filter?: ActionRowInsertFilter): void;
1562
+ addColumnChangeHandler(onUpdate: (data: ActionColumnChangeData) => Promise<void>, filter: ActionColumnChangeFilter): void;
1563
+ addRowDeleteHandler(onDelete: (data: ActionRowDeleteData) => Promise<void>, filter?: ActionRowDeleteFilter): void;
1564
+ fireActionFromDbTrigger(sqlMutationData: SqlMutationData, result: TriggerResult): Promise<void>;
1565
+ private fireInsertActions;
1566
+ private fireDeleteActions;
1567
+ private fireUpdateActions;
1568
+ private hasHandlersForEventType;
1569
+ }
1570
+ declare const eventManager: EventManager;
1571
+
1572
+ export { type ActionColumnChangeData, type ActionColumnChangeFilter, type ActionRowDeleteData, type ActionRowDeleteFilter, type ActionRowInsertData, type ActionRowInsertFilter, type ApiMethod, type AsyncExpressApplication, type AuthenticateHandler, type AuthenticationUserDetails, type ConjunctionTypes, type DatabaseActionData, type DynamicObject, type ErrorCode, type EventType, HtmlStatusCodes, type LoginDetails, type MatchTypes, type MutationType, type PageQuery, PsqlConnection, PsqlPool, PsqlTransaction, type RequesterDetails, type ResponseType, type ResponseTypeMap, RsError, type RsErrorData, type RsErrorInternalData, type RsHeaders, type RsPagedResponseData, type RsRequest, type RsResponse, type RsResponseData, type RsRouteHandler, SQL, type SchemaChangeValue, type SchemaPreview, type SqlMutationData, type StandardOrderTypes, type TriggerResult, type ValidAuthenticationCallback, type ValidatorString, escapeColumnName, eventManager, insertObjectQuery, isValueNumber, logger, questionMarksToOrderedParams, restura, updateObjectQuery };
package/dist/index.d.ts CHANGED
@@ -1392,10 +1392,10 @@ interface AsyncExpressApplication {
1392
1392
  }
1393
1393
 
1394
1394
  declare abstract class PsqlConnection {
1395
- constructor();
1395
+ protected constructor();
1396
1396
  protected abstract query<R extends QueryResultRow = QueryResultRow, T extends Array<unknown> = unknown[]>(query: string, values?: QueryConfigValues<T>): Promise<QueryResult<R>>;
1397
- queryOne(query: string, options: any[], requesterDetails: RequesterDetails): Promise<QueryResultRow>;
1398
- runQuery(query: string, options: any[], requesterDetails: RequesterDetails): Promise<QueryResultRow[]>;
1397
+ queryOne<T>(query: string, options: any[], requesterDetails: RequesterDetails): Promise<T>;
1398
+ runQuery<T>(query: string, options: any[], requesterDetails: RequesterDetails): Promise<T[]>;
1399
1399
  private logSqlStatement;
1400
1400
  }
1401
1401
 
@@ -1406,18 +1406,6 @@ declare class PsqlPool extends PsqlConnection {
1406
1406
  protected query<R extends QueryResultRow = QueryResultRow, T extends Array<unknown> = unknown[]>(query: string, values?: QueryConfigValues<T>): Promise<QueryResult<R>>;
1407
1407
  }
1408
1408
 
1409
- declare class PsqlTransaction extends PsqlConnection {
1410
- clientConfig: ClientConfig;
1411
- client: Client;
1412
- private beginTransactionPromise;
1413
- constructor(clientConfig: ClientConfig);
1414
- private beginTransaction;
1415
- rollback(): Promise<QueryResult<QueryResultRow>>;
1416
- commit(): Promise<QueryResult<QueryResultRow>>;
1417
- release(): Promise<void>;
1418
- protected query<R extends QueryResultRow = QueryResultRow, T extends Array<unknown> = unknown[]>(query: string, values?: QueryConfigValues<T>): Promise<QueryResult<R>>;
1419
- }
1420
-
1421
1409
  declare class ResturaEngine {
1422
1410
  resturaConfig: ResturaConfigSchema;
1423
1411
  private resturaRouter;
@@ -1516,4 +1504,69 @@ declare function updateObjectQuery(table: string, obj: DynamicObject, whereState
1516
1504
  declare function isValueNumber(value: unknown): value is number;
1517
1505
  declare function SQL(strings: any, ...values: any): any;
1518
1506
 
1519
- export { type ApiMethod, type AsyncExpressApplication, type AuthenticateHandler, type AuthenticationUserDetails, type ConjunctionTypes, type DynamicObject, type ErrorCode, HtmlStatusCodes, type LoginDetails, type MatchTypes, type PageQuery, PsqlConnection, PsqlPool, PsqlTransaction, type RequesterDetails, type ResponseType, type ResponseTypeMap, RsError, type RsErrorData, type RsErrorInternalData, type RsHeaders, type RsPagedResponseData, type RsRequest, type RsResponse, type RsResponseData, type RsRouteHandler, SQL, type SchemaChangeValue, type SchemaPreview, type StandardOrderTypes, type ValidAuthenticationCallback, type ValidatorString, escapeColumnName, insertObjectQuery, isValueNumber, logger, questionMarksToOrderedParams, restura, updateObjectQuery };
1507
+ declare class PsqlTransaction extends PsqlConnection {
1508
+ clientConfig: ClientConfig;
1509
+ client: Client;
1510
+ private beginTransactionPromise;
1511
+ constructor(clientConfig: ClientConfig);
1512
+ private beginTransaction;
1513
+ rollback(): Promise<QueryResult<QueryResultRow>>;
1514
+ commit(): Promise<QueryResult<QueryResultRow>>;
1515
+ release(): Promise<void>;
1516
+ protected query<R extends QueryResultRow = QueryResultRow, T extends Array<unknown> = unknown[]>(query: string, values?: QueryConfigValues<T>): Promise<QueryResult<R>>;
1517
+ }
1518
+
1519
+ type EventType = 'DATABASE_ROW_DELETE' | 'DATABASE_ROW_INSERT' | 'DATABASE_COLUMN_UPDATE' | 'WEBHOOK';
1520
+ type MutationType = 'INSERT' | 'UPDATE' | 'DELETE';
1521
+ interface SqlMutationData {
1522
+ mutationType: MutationType;
1523
+ requesterDetails: RequesterDetails;
1524
+ }
1525
+ interface DatabaseActionData {
1526
+ tableName: string;
1527
+ requesterDetails: RequesterDetails;
1528
+ }
1529
+ interface ActionRowInsertData<T = DynamicObject> extends DatabaseActionData {
1530
+ insertId: number;
1531
+ insertObject: T;
1532
+ }
1533
+ interface ActionRowDeleteData extends DatabaseActionData {
1534
+ deletedRow: DynamicObject;
1535
+ }
1536
+ interface ActionColumnChangeData extends DatabaseActionData {
1537
+ tableName: string;
1538
+ rowId: number;
1539
+ newData: DynamicObject;
1540
+ oldData: DynamicObject;
1541
+ }
1542
+ interface ActionRowInsertFilter {
1543
+ tableName: string;
1544
+ }
1545
+ interface ActionRowDeleteFilter {
1546
+ tableName: string;
1547
+ }
1548
+ interface ActionColumnChangeFilter {
1549
+ tableName: string;
1550
+ }
1551
+ type TriggerResult = {
1552
+ table: string;
1553
+ insertId?: number;
1554
+ query: string;
1555
+ record: DynamicObject;
1556
+ previousRecord: DynamicObject;
1557
+ requesterId: number;
1558
+ };
1559
+ declare class EventManager {
1560
+ private actionHandlers;
1561
+ addRowInsertHandler(onInsert: (data: ActionRowInsertData<unknown>) => Promise<void>, filter?: ActionRowInsertFilter): void;
1562
+ addColumnChangeHandler(onUpdate: (data: ActionColumnChangeData) => Promise<void>, filter: ActionColumnChangeFilter): void;
1563
+ addRowDeleteHandler(onDelete: (data: ActionRowDeleteData) => Promise<void>, filter?: ActionRowDeleteFilter): void;
1564
+ fireActionFromDbTrigger(sqlMutationData: SqlMutationData, result: TriggerResult): Promise<void>;
1565
+ private fireInsertActions;
1566
+ private fireDeleteActions;
1567
+ private fireUpdateActions;
1568
+ private hasHandlersForEventType;
1569
+ }
1570
+ declare const eventManager: EventManager;
1571
+
1572
+ export { type ActionColumnChangeData, type ActionColumnChangeFilter, type ActionRowDeleteData, type ActionRowDeleteFilter, type ActionRowInsertData, type ActionRowInsertFilter, type ApiMethod, type AsyncExpressApplication, type AuthenticateHandler, type AuthenticationUserDetails, type ConjunctionTypes, type DatabaseActionData, type DynamicObject, type ErrorCode, type EventType, HtmlStatusCodes, type LoginDetails, type MatchTypes, type MutationType, type PageQuery, PsqlConnection, PsqlPool, PsqlTransaction, type RequesterDetails, type ResponseType, type ResponseTypeMap, RsError, type RsErrorData, type RsErrorInternalData, type RsHeaders, type RsPagedResponseData, type RsRequest, type RsResponse, type RsResponseData, type RsRouteHandler, SQL, type SchemaChangeValue, type SchemaPreview, type SqlMutationData, type StandardOrderTypes, type TriggerResult, type ValidAuthenticationCallback, type ValidatorString, escapeColumnName, eventManager, insertObjectQuery, isValueNumber, logger, questionMarksToOrderedParams, restura, updateObjectQuery };
package/dist/index.js CHANGED
@@ -74,6 +74,7 @@ __export(src_exports, {
74
74
  RsError: () => RsError,
75
75
  SQL: () => SQL,
76
76
  escapeColumnName: () => escapeColumnName,
77
+ eventManager: () => eventManager_default,
77
78
  insertObjectQuery: () => insertObjectQuery,
78
79
  isValueNumber: () => isValueNumber2,
79
80
  logger: () => logger,
@@ -260,7 +261,7 @@ var import_crypto = require("crypto");
260
261
  var express = __toESM(require("express"));
261
262
  var import_fs3 = __toESM(require("fs"));
262
263
  var import_path3 = __toESM(require("path"));
263
- var import_pg4 = __toESM(require("pg"));
264
+ var import_pg3 = __toESM(require("pg"));
264
265
  var prettier3 = __toESM(require("prettier"));
265
266
 
266
267
  // src/restura/sql/SqlUtils.ts
@@ -1279,7 +1280,8 @@ function insertObjectQuery(table, obj) {
1279
1280
  const params = Object.values(obj);
1280
1281
  const columns = keys.map((column) => escapeColumnName(column)).join(", ");
1281
1282
  const values = params.map((value) => SQL`${value}`).join(", ");
1282
- const query = `INSERT INTO "${table}" (${columns})
1283
+ const query = `
1284
+ INSERT INTO "${table}" (${columns})
1283
1285
  VALUES (${values})
1284
1286
  RETURNING *`;
1285
1287
  return query;
@@ -1289,7 +1291,8 @@ function updateObjectQuery(table, obj, whereStatement) {
1289
1291
  for (const i in obj) {
1290
1292
  setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
1291
1293
  }
1292
- return `UPDATE ${escapeColumnName(table)}
1294
+ return `
1295
+ UPDATE ${escapeColumnName(table)}
1293
1296
  SET ${setArray.join(", ")} ${whereStatement}
1294
1297
  RETURNING *`;
1295
1298
  }
@@ -1319,8 +1322,10 @@ var PsqlConnection = class {
1319
1322
  async queryOne(query, options, requesterDetails) {
1320
1323
  const formattedQuery = questionMarksToOrderedParams(query);
1321
1324
  this.logSqlStatement(formattedQuery, options, requesterDetails);
1325
+ const queryMetadata = `--QUERY_METADATA(${JSON.stringify(requesterDetails)})
1326
+ `;
1322
1327
  try {
1323
- const response = await this.query(formattedQuery, options);
1328
+ const response = await this.query(queryMetadata + formattedQuery, options);
1324
1329
  if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
1325
1330
  else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
1326
1331
  return response.rows[0];
@@ -1337,8 +1342,10 @@ var PsqlConnection = class {
1337
1342
  async runQuery(query, options, requesterDetails) {
1338
1343
  const formattedQuery = questionMarksToOrderedParams(query);
1339
1344
  this.logSqlStatement(formattedQuery, options, requesterDetails);
1345
+ const queryMetadata = `--QUERY_METADATA(${JSON.stringify(requesterDetails)})
1346
+ `;
1340
1347
  try {
1341
- const response = await this.query(formattedQuery, options);
1348
+ const response = await this.query(queryMetadata + formattedQuery, options);
1342
1349
  return response.rows;
1343
1350
  } catch (error) {
1344
1351
  console.error(error, query, options);
@@ -1589,6 +1596,110 @@ var filterPsqlParser = import_pegjs.default.generate(filterSqlGrammar, {
1589
1596
  });
1590
1597
  var filterPsqlParser_default = filterPsqlParser;
1591
1598
 
1599
+ // src/restura/eventManager.ts
1600
+ var import_bluebird = __toESM(require("bluebird"));
1601
+ var EventManager = class {
1602
+ constructor() {
1603
+ this.actionHandlers = {
1604
+ DATABASE_ROW_DELETE: [],
1605
+ DATABASE_ROW_INSERT: [],
1606
+ DATABASE_COLUMN_UPDATE: []
1607
+ };
1608
+ }
1609
+ addRowInsertHandler(onInsert, filter) {
1610
+ this.actionHandlers.DATABASE_ROW_INSERT.push({
1611
+ callback: onInsert,
1612
+ filter
1613
+ });
1614
+ }
1615
+ addColumnChangeHandler(onUpdate, filter) {
1616
+ this.actionHandlers.DATABASE_COLUMN_UPDATE.push({
1617
+ callback: onUpdate,
1618
+ filter
1619
+ });
1620
+ }
1621
+ addRowDeleteHandler(onDelete, filter) {
1622
+ this.actionHandlers.DATABASE_ROW_DELETE.push({
1623
+ callback: onDelete,
1624
+ filter
1625
+ });
1626
+ }
1627
+ async fireActionFromDbTrigger(sqlMutationData, result) {
1628
+ if (sqlMutationData.mutationType === "INSERT") {
1629
+ await this.fireInsertActions(sqlMutationData, result);
1630
+ } else if (sqlMutationData.mutationType === "UPDATE") {
1631
+ await this.fireUpdateActions(sqlMutationData, result);
1632
+ } else if (sqlMutationData.mutationType === "DELETE") {
1633
+ await this.fireDeleteActions(sqlMutationData, result);
1634
+ }
1635
+ }
1636
+ async fireInsertActions(data, triggerResult) {
1637
+ await import_bluebird.default.map(
1638
+ this.actionHandlers.DATABASE_ROW_INSERT,
1639
+ ({ callback, filter }) => {
1640
+ if (!this.hasHandlersForEventType("DATABASE_ROW_INSERT", filter, triggerResult)) return;
1641
+ const insertData = {
1642
+ tableName: triggerResult.table,
1643
+ insertId: triggerResult.record.id,
1644
+ insertObject: triggerResult.record,
1645
+ requesterDetails: data.requesterDetails
1646
+ };
1647
+ callback(insertData, data.requesterDetails);
1648
+ },
1649
+ { concurrency: 10 }
1650
+ );
1651
+ }
1652
+ async fireDeleteActions(data, triggerResult) {
1653
+ await import_bluebird.default.map(
1654
+ this.actionHandlers.DATABASE_ROW_DELETE,
1655
+ ({ callback, filter }) => {
1656
+ if (!this.hasHandlersForEventType("DATABASE_ROW_DELETE", filter, triggerResult)) return;
1657
+ const deleteData = {
1658
+ tableName: triggerResult.table,
1659
+ deletedRow: triggerResult.previousRecord,
1660
+ requesterDetails: data.requesterDetails
1661
+ };
1662
+ callback(deleteData, data.requesterDetails);
1663
+ },
1664
+ { concurrency: 10 }
1665
+ );
1666
+ }
1667
+ async fireUpdateActions(data, triggerResult) {
1668
+ await import_bluebird.default.map(
1669
+ this.actionHandlers.DATABASE_COLUMN_UPDATE,
1670
+ ({ callback, filter }) => {
1671
+ if (!this.hasHandlersForEventType("DATABASE_COLUMN_UPDATE", filter, triggerResult)) return;
1672
+ const columnChangeData = {
1673
+ tableName: triggerResult.table,
1674
+ rowId: triggerResult.record.id,
1675
+ newData: triggerResult.record,
1676
+ oldData: triggerResult.previousRecord,
1677
+ requesterDetails: data.requesterDetails
1678
+ };
1679
+ callback(columnChangeData, data.requesterDetails);
1680
+ },
1681
+ { concurrency: 10 }
1682
+ );
1683
+ }
1684
+ hasHandlersForEventType(eventType, filter, triggerResult) {
1685
+ if (filter) {
1686
+ switch (eventType) {
1687
+ case "DATABASE_ROW_INSERT":
1688
+ case "DATABASE_ROW_DELETE":
1689
+ if (filter.tableName && filter.tableName !== triggerResult.table) return false;
1690
+ break;
1691
+ case "DATABASE_COLUMN_UPDATE":
1692
+ const filterColumnChange = filter;
1693
+ if (filterColumnChange.tableName !== filter.tableName) return false;
1694
+ break;
1695
+ }
1696
+ }
1697
+ return true;
1698
+ }
1699
+ };
1700
+ var eventManager = new EventManager();
1701
+ var eventManager_default = eventManager;
1702
+
1592
1703
  // src/restura/sql/PsqlEngine.ts
1593
1704
  var { Client } = import_pg2.default;
1594
1705
  var systemUser = {
@@ -1598,9 +1709,49 @@ var systemUser = {
1598
1709
  isSystemUser: true
1599
1710
  };
1600
1711
  var PsqlEngine = class extends SqlEngine {
1601
- constructor(psqlConnectionPool) {
1712
+ constructor(psqlConnectionPool, shouldListenForDbTriggers = false) {
1602
1713
  super();
1603
1714
  this.psqlConnectionPool = psqlConnectionPool;
1715
+ if (shouldListenForDbTriggers) {
1716
+ this.setupTriggerListeners = this.listenForDbTriggers();
1717
+ }
1718
+ }
1719
+ async close() {
1720
+ if (this.triggerClient) {
1721
+ await this.triggerClient.end();
1722
+ }
1723
+ }
1724
+ async listenForDbTriggers() {
1725
+ this.triggerClient = new Client({
1726
+ user: this.psqlConnectionPool.poolConfig.user,
1727
+ host: this.psqlConnectionPool.poolConfig.host,
1728
+ database: this.psqlConnectionPool.poolConfig.database,
1729
+ password: this.psqlConnectionPool.poolConfig.password,
1730
+ port: this.psqlConnectionPool.poolConfig.port,
1731
+ connectionTimeoutMillis: 2e3
1732
+ });
1733
+ await this.triggerClient.connect();
1734
+ const promises = [];
1735
+ promises.push(this.triggerClient.query("LISTEN insert"));
1736
+ promises.push(this.triggerClient.query("LISTEN update"));
1737
+ promises.push(this.triggerClient.query("LISTEN delete"));
1738
+ await Promise.all(promises);
1739
+ this.triggerClient.on("notification", async (msg) => {
1740
+ if (msg.channel === "insert" || msg.channel === "update" || msg.channel === "delete") {
1741
+ const payload = JSON.parse(msg.payload);
1742
+ await this.handleTrigger(payload, msg.channel.toUpperCase());
1743
+ }
1744
+ });
1745
+ }
1746
+ async handleTrigger(payload, mutationType) {
1747
+ const findRequesterDetailsRegex = /^--QUERY_METADATA\(\{.*\}\)/;
1748
+ let requesterDetails = {};
1749
+ const match = payload.query.match(findRequesterDetailsRegex);
1750
+ if (match) {
1751
+ const jsonString = match[0].slice(match[0].indexOf("{"), match[0].lastIndexOf("}") + 1);
1752
+ requesterDetails = import_core_utils5.ObjectUtils.safeParse(jsonString);
1753
+ await eventManager_default.fireActionFromDbTrigger({ requesterDetails, mutationType }, payload);
1754
+ }
1604
1755
  }
1605
1756
  async createDatabaseFromSchema(schema, connection) {
1606
1757
  const sqlFullStatement = this.generateDatabaseSchemaFromSchema(schema);
@@ -1611,7 +1762,11 @@ var PsqlEngine = class extends SqlEngine {
1611
1762
  const sqlStatements = [];
1612
1763
  const enums = [];
1613
1764
  const indexes = [];
1765
+ const triggers = [];
1614
1766
  for (const table of schema.database) {
1767
+ triggers.push(this.createInsertTriggers(table.name));
1768
+ triggers.push(this.createUpdateTrigger(table.name));
1769
+ triggers.push(this.createDeleteTrigger(table.name));
1615
1770
  let sql = `CREATE TABLE "${table.name}"
1616
1771
  ( `;
1617
1772
  const tableColumns = [];
@@ -1649,7 +1804,7 @@ var PsqlEngine = class extends SqlEngine {
1649
1804
  indexes.push(
1650
1805
  ` CREATE ${unique}INDEX "${index.name}" ON "${table.name}" (${index.columns.map((item) => {
1651
1806
  return `"${item}" ${index.order}`;
1652
- }).join(", ")})`
1807
+ }).join(", ")});`
1653
1808
  );
1654
1809
  }
1655
1810
  }
@@ -1679,7 +1834,8 @@ var PsqlEngine = class extends SqlEngine {
1679
1834
  }
1680
1835
  sqlStatements.push(sql + constraints.join(",\n") + ";");
1681
1836
  }
1682
- sqlStatements.push(indexes.join(";\n"));
1837
+ sqlStatements.push(indexes.join("\n"));
1838
+ sqlStatements.push(triggers.join("\n"));
1683
1839
  return enums.join("\n") + "\n" + sqlStatements.join("\n\n");
1684
1840
  }
1685
1841
  async getScratchPool() {
@@ -1747,8 +1903,7 @@ var PsqlEngine = class extends SqlEngine {
1747
1903
  )) {
1748
1904
  return "'[]'";
1749
1905
  }
1750
- return `COALESCE((
1751
- SELECT JSON_AGG(JSON_BUILD_OBJECT(
1906
+ return `COALESCE((SELECT JSON_AGG(JSON_BUILD_OBJECT(
1752
1907
  ${item.subquery.properties.map((nestedItem) => {
1753
1908
  if (!this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
1754
1909
  ...routeData.joins,
@@ -1757,7 +1912,7 @@ var PsqlEngine = class extends SqlEngine {
1757
1912
  return;
1758
1913
  }
1759
1914
  if (nestedItem.subquery) {
1760
- return `"${nestedItem.name}", ${this.createNestedSelect(
1915
+ return `'${nestedItem.name}', ${this.createNestedSelect(
1761
1916
  // recursion
1762
1917
  req,
1763
1918
  schema,
@@ -1768,7 +1923,7 @@ var PsqlEngine = class extends SqlEngine {
1768
1923
  )}`;
1769
1924
  }
1770
1925
  return `'${nestedItem.name}', ${escapeColumnName(nestedItem.selector)}`;
1771
- }).filter(Boolean).join(",")}
1926
+ }).filter(Boolean).join(", ")}
1772
1927
  ))
1773
1928
  FROM
1774
1929
  "${item.subquery.table}"
@@ -1896,10 +2051,12 @@ var PsqlEngine = class extends SqlEngine {
1896
2051
  req.requesterDetails.role,
1897
2052
  sqlParams
1898
2053
  );
1899
- let deleteStatement = `DELETE
1900
- FROM "${routeData.table}" ${joinStatement}`;
1901
- deleteStatement += this.generateWhereClause(req, routeData.where, routeData, sqlParams);
1902
- deleteStatement += ";";
2054
+ const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
2055
+ if (whereClause.replace(/\s/g, "") === "") {
2056
+ throw new RsError("DELETE_FORBIDDEN", "Deletes need a where clause");
2057
+ }
2058
+ const deleteStatement = `
2059
+ DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
1903
2060
  await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams, req.requesterDetails);
1904
2061
  return true;
1905
2062
  }
@@ -1962,17 +2119,17 @@ var PsqlEngine = class extends SqlEngine {
1962
2119
  );
1963
2120
  let operator = item.operator;
1964
2121
  if (operator === "LIKE") {
1965
- sqlParams[sqlParams.length - 1] = `%${sqlParams[sqlParams.length - 1]}%`;
2122
+ item.value = `%${item.value}%`;
1966
2123
  } else if (operator === "STARTS WITH") {
1967
2124
  operator = "LIKE";
1968
- sqlParams[sqlParams.length - 1] = `${sqlParams[sqlParams.length - 1]}%`;
2125
+ item.value = `${item.value}%`;
1969
2126
  } else if (operator === "ENDS WITH") {
1970
2127
  operator = "LIKE";
1971
- sqlParams[sqlParams.length - 1] = `%${sqlParams[sqlParams.length - 1]}`;
2128
+ item.value = `%${item.value}`;
1972
2129
  }
1973
2130
  const replacedValue = this.replaceParamKeywords(item.value, routeData, req, sqlParams);
1974
2131
  const escapedValue = SQL`${replacedValue}`;
1975
- whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator} ${["IN", "NOT IN"].includes(operator) ? `(${escapedValue})` : escapedValue}
2132
+ whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator.replace("LIKE", "ILIKE")} ${["IN", "NOT IN"].includes(operator) ? `(${escapedValue})` : escapedValue}
1976
2133
  `;
1977
2134
  });
1978
2135
  const data = req.data;
@@ -2006,7 +2163,67 @@ var PsqlEngine = class extends SqlEngine {
2006
2163
  }
2007
2164
  return whereClause;
2008
2165
  }
2166
+ createUpdateTrigger(tableName) {
2167
+ return `
2168
+ CREATE OR REPLACE FUNCTION notify_${tableName}_update()
2169
+ RETURNS TRIGGER AS $$
2170
+ BEGIN
2171
+ PERFORM pg_notify('update', JSON_BUILD_OBJECT('table', '${tableName}', 'query', current_query(), 'record', NEW, 'previousRecord', OLD)::text);
2172
+ RETURN NEW;
2173
+ END;
2174
+ $$ LANGUAGE plpgsql;
2175
+
2176
+ CREATE OR REPLACE TRIGGER ${tableName}_update
2177
+ AFTER UPDATE ON "${tableName}"
2178
+ FOR EACH ROW
2179
+ EXECUTE FUNCTION notify_${tableName}_update();
2180
+ `;
2181
+ }
2182
+ createDeleteTrigger(tableName) {
2183
+ return `
2184
+ CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
2185
+ RETURNS TRIGGER AS $$
2186
+ BEGIN
2187
+ PERFORM pg_notify('delete', JSON_BUILD_OBJECT('table', '${tableName}', 'query', current_query(), 'record', NEW, 'previousRecord', OLD)::text);
2188
+ RETURN NEW;
2189
+ END;
2190
+ $$ LANGUAGE plpgsql;
2191
+
2192
+ CREATE OR REPLACE TRIGGER "${tableName}_delete"
2193
+ AFTER DELETE ON "${tableName}"
2194
+ FOR EACH ROW
2195
+ EXECUTE FUNCTION notify_${tableName}_delete();
2196
+ `;
2197
+ }
2198
+ createInsertTriggers(tableName) {
2199
+ return `
2200
+ CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
2201
+ RETURNS TRIGGER AS $$
2202
+ BEGIN
2203
+ PERFORM pg_notify('insert', JSON_BUILD_OBJECT('table', '${tableName}', 'query', current_query(), 'record', NEW, 'previousRecord', OLD)::text);
2204
+ RETURN NEW;
2205
+ END;
2206
+ $$ LANGUAGE plpgsql;
2207
+
2208
+ CREATE TRIGGER "${tableName}_insert"
2209
+ AFTER INSERT ON "${tableName}"
2210
+ FOR EACH ROW
2211
+ EXECUTE FUNCTION notify_${tableName}_insert();
2212
+ `;
2213
+ }
2009
2214
  };
2215
+ __decorateClass([
2216
+ boundMethod
2217
+ ], PsqlEngine.prototype, "handleTrigger", 1);
2218
+ __decorateClass([
2219
+ boundMethod
2220
+ ], PsqlEngine.prototype, "createUpdateTrigger", 1);
2221
+ __decorateClass([
2222
+ boundMethod
2223
+ ], PsqlEngine.prototype, "createDeleteTrigger", 1);
2224
+ __decorateClass([
2225
+ boundMethod
2226
+ ], PsqlEngine.prototype, "createInsertTriggers", 1);
2010
2227
  function schemaToPsqlType(column, tableName) {
2011
2228
  if (column.hasAutoIncrement) return "BIGSERIAL";
2012
2229
  if (column.type === "ENUM") return `"${tableName}_${column.name}_enum"`;
@@ -2015,35 +2232,6 @@ function schemaToPsqlType(column, tableName) {
2015
2232
  return column.type;
2016
2233
  }
2017
2234
 
2018
- // src/restura/sql/PsqlTransaction.ts
2019
- var import_pg3 = __toESM(require("pg"));
2020
- var { Client: Client2 } = import_pg3.default;
2021
- var PsqlTransaction = class extends PsqlConnection {
2022
- constructor(clientConfig) {
2023
- super();
2024
- this.clientConfig = clientConfig;
2025
- this.client = new Client2(clientConfig);
2026
- this.beginTransactionPromise = this.beginTransaction();
2027
- }
2028
- async beginTransaction() {
2029
- return this.query("BEGIN");
2030
- }
2031
- async rollback() {
2032
- return this.query("ROLLBACK");
2033
- }
2034
- async commit() {
2035
- return this.query("COMMIT");
2036
- }
2037
- async release() {
2038
- return this.client.end();
2039
- }
2040
- async query(query, values) {
2041
- await this.client.connect();
2042
- await this.beginTransactionPromise;
2043
- return this.client.query(query, values);
2044
- }
2045
- };
2046
-
2047
2235
  // src/restura/compareSchema.ts
2048
2236
  var import_lodash = __toESM(require("lodash.clonedeep"));
2049
2237
  var CompareSchema = class {
@@ -2135,7 +2323,7 @@ var compareSchema = new CompareSchema();
2135
2323
  var compareSchema_default = compareSchema;
2136
2324
 
2137
2325
  // src/restura/restura.ts
2138
- var { types } = import_pg4.default;
2326
+ var { types } = import_pg3.default;
2139
2327
  var ResturaEngine = class {
2140
2328
  constructor() {
2141
2329
  this.publicEndpoints = {
@@ -2510,6 +2698,35 @@ var setupPgReturnTypes = () => {
2510
2698
  };
2511
2699
  setupPgReturnTypes();
2512
2700
  var restura = new ResturaEngine();
2701
+
2702
+ // src/restura/sql/PsqlTransaction.ts
2703
+ var import_pg4 = __toESM(require("pg"));
2704
+ var { Client: Client2 } = import_pg4.default;
2705
+ var PsqlTransaction = class extends PsqlConnection {
2706
+ constructor(clientConfig) {
2707
+ super();
2708
+ this.clientConfig = clientConfig;
2709
+ this.client = new Client2(clientConfig);
2710
+ this.beginTransactionPromise = this.beginTransaction();
2711
+ }
2712
+ async beginTransaction() {
2713
+ return this.query("BEGIN");
2714
+ }
2715
+ async rollback() {
2716
+ return this.query("ROLLBACK");
2717
+ }
2718
+ async commit() {
2719
+ return this.query("COMMIT");
2720
+ }
2721
+ async release() {
2722
+ return this.client.end();
2723
+ }
2724
+ async query(query, values) {
2725
+ await this.client.connect();
2726
+ await this.beginTransactionPromise;
2727
+ return this.client.query(query, values);
2728
+ }
2729
+ };
2513
2730
  // Annotate the CommonJS export names for ESM import in node:
2514
2731
  0 && (module.exports = {
2515
2732
  HtmlStatusCodes,
@@ -2519,6 +2736,7 @@ var restura = new ResturaEngine();
2519
2736
  RsError,
2520
2737
  SQL,
2521
2738
  escapeColumnName,
2739
+ eventManager,
2522
2740
  insertObjectQuery,
2523
2741
  isValueNumber,
2524
2742
  logger,