@restura/core 1.0.3 → 1.0.5

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.ts CHANGED
@@ -934,8 +934,10 @@ declare abstract class PsqlConnection {
934
934
  readonly instanceId: UUID;
935
935
  protected constructor(instanceId?: UUID);
936
936
  protected abstract query<R extends QueryResultRow = QueryResultRow, T extends Array<unknown> = unknown[]>(query: string, values?: QueryConfigValues<T>): Promise<QueryResult<R>>;
937
- queryOne<T>(query: string, options: any[], requesterDetails: RequesterDetails): Promise<T>;
938
- runQuery<T>(query: string, options: any[], requesterDetails: RequesterDetails): Promise<T[]>;
937
+ queryOne<T>(query: string, options: unknown[], requesterDetails: RequesterDetails): Promise<T>;
938
+ queryOneSchema<T>(query: string, params: unknown[], requesterDetails: RequesterDetails, zodSchema: z.ZodSchema<T>): Promise<T>;
939
+ runQuery<T>(query: string, options: unknown[], requesterDetails: RequesterDetails): Promise<T[]>;
940
+ runQuerySchema<T>(query: string, params: unknown[], requesterDetails: RequesterDetails, zodSchema: z.ZodSchema<T>): Promise<T[]>;
939
941
  private logQueryDuration;
940
942
  private logSqlStatement;
941
943
  }
@@ -1059,9 +1061,13 @@ declare class PsqlEngine extends SqlEngine {
1059
1061
  setupTriggerListeners: Promise<void> | undefined;
1060
1062
  private triggerClient;
1061
1063
  private scratchDbName;
1064
+ private reconnectAttempts;
1065
+ private readonly MAX_RECONNECT_ATTEMPTS;
1066
+ private readonly INITIAL_RECONNECT_DELAY;
1062
1067
  constructor(psqlConnectionPool: PsqlPool, shouldListenForDbTriggers?: boolean, scratchDatabaseSuffix?: string);
1063
1068
  close(): Promise<void>;
1064
1069
  private setupPgReturnTypes;
1070
+ private reconnectTriggerClient;
1065
1071
  private listenForDbTriggers;
1066
1072
  private handleTrigger;
1067
1073
  createDatabaseFromSchema(schema: ResturaSchema, connection: PsqlPool): Promise<string>;
@@ -1140,4 +1146,4 @@ declare function isValueNumber(value: unknown): value is number;
1140
1146
  */
1141
1147
  declare function SQL(strings: TemplateStringsArray, ...values: unknown[]): string;
1142
1148
 
1143
- export { type ActionColumnChangeData, type ActionColumnChangeFilter, type ActionRowDeleteData, type ActionRowDeleteFilter, type ActionRowInsertData, type ActionRowInsertFilter, type ApiMethod, type AsyncExpressApplication, type AuthenticateHandler, type AuthenticatedRequesterDetails, type ConjunctionTypes, type DatabaseActionData, type DynamicObject, type ErrorCode, type EventType, HtmlStatusCodes, type LoggerConfigSchema, type MatchTypes, type MutationType, type OnValidAuthenticationCallback, type PageQuery, PsqlConnection, PsqlEngine, PsqlPool, PsqlTransaction, type QueryMetadata, type RequesterDetails, type ResturaConfigSchema, 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, apiGenerator, escapeColumnName, eventManager, filterPsqlParser, insertObjectQuery, isSchemaValid, isValueNumber, logger, modelGenerator, questionMarksToOrderedParams, restura, resturaGlobalTypesGenerator, updateObjectQuery };
1149
+ export { type ActionColumnChangeData, type ActionColumnChangeFilter, type ActionRowDeleteData, type ActionRowDeleteFilter, type ActionRowInsertData, type ActionRowInsertFilter, type ApiMethod, type AsyncExpressApplication, type AuthenticateHandler, type AuthenticatedRequesterDetails, type ConjunctionTypes, type DatabaseActionData, type DynamicObject, type ErrorCode, type EventType, HtmlStatusCodes, type LoggerConfigSchema, type MatchTypes, type MutationType, type OnValidAuthenticationCallback, type PageQuery, PsqlConnection, PsqlEngine, PsqlPool, PsqlTransaction, type QueryMetadata, type RequesterDetails, type ResturaConfigSchema, type ResturaSchema, 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, apiGenerator, escapeColumnName, eventManager, filterPsqlParser, insertObjectQuery, isSchemaValid, isValueNumber, logger, modelGenerator, questionMarksToOrderedParams, restura, resturaGlobalTypesGenerator, resturaSchema, updateObjectQuery };
package/dist/index.js CHANGED
@@ -1617,6 +1617,7 @@ import pg from "pg";
1617
1617
  import crypto from "crypto";
1618
1618
  import format3 from "pg-format";
1619
1619
  import { format as sqlFormat } from "sql-formatter";
1620
+ import { z as z6 } from "zod/v4";
1620
1621
 
1621
1622
  // src/restura/sql/PsqlUtils.ts
1622
1623
  import format2 from "pg-format";
@@ -1691,7 +1692,6 @@ var PsqlConnection = class {
1691
1692
  constructor(instanceId) {
1692
1693
  this.instanceId = instanceId || crypto.randomUUID();
1693
1694
  }
1694
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1695
1695
  async queryOne(query, options, requesterDetails) {
1696
1696
  const formattedQuery = questionMarksToOrderedParams(query);
1697
1697
  const meta = { connectionInstanceId: this.instanceId, ...requesterDetails };
@@ -1713,7 +1713,21 @@ var PsqlConnection = class {
1713
1713
  throw new RsError("DATABASE_ERROR", `${error.message}`);
1714
1714
  }
1715
1715
  }
1716
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1716
+ async queryOneSchema(query, params, requesterDetails, zodSchema) {
1717
+ const result = await this.queryOne(query, params, requesterDetails);
1718
+ try {
1719
+ return zodSchema.parse(result);
1720
+ } catch (error) {
1721
+ if (error instanceof z6.ZodError) {
1722
+ logger.error("Invalid data returned from database:");
1723
+ logger.silly("\n" + JSON.stringify(result, null, 2));
1724
+ logger.error("\n" + z6.prettifyError(error));
1725
+ } else {
1726
+ logger.error(error);
1727
+ }
1728
+ throw new RsError("DATABASE_ERROR", `Invalid data returned from database`);
1729
+ }
1730
+ }
1717
1731
  async runQuery(query, options, requesterDetails) {
1718
1732
  const formattedQuery = questionMarksToOrderedParams(query);
1719
1733
  const meta = { connectionInstanceId: this.instanceId, ...requesterDetails };
@@ -1732,6 +1746,21 @@ var PsqlConnection = class {
1732
1746
  throw new RsError("DATABASE_ERROR", `${error.message}`);
1733
1747
  }
1734
1748
  }
1749
+ async runQuerySchema(query, params, requesterDetails, zodSchema) {
1750
+ const result = await this.runQuery(query, params, requesterDetails);
1751
+ try {
1752
+ return z6.array(zodSchema).parse(result);
1753
+ } catch (error) {
1754
+ if (error instanceof z6.ZodError) {
1755
+ logger.error("Invalid data returned from database:");
1756
+ logger.silly("\n" + JSON.stringify(result, null, 2));
1757
+ logger.error("\n" + z6.prettifyError(error));
1758
+ } else {
1759
+ logger.error(error);
1760
+ }
1761
+ throw new RsError("DATABASE_ERROR", `Invalid data returned from database`);
1762
+ }
1763
+ }
1735
1764
  logQueryDuration(startTime) {
1736
1765
  if (logger.level === "silly") {
1737
1766
  const [seconds, nanoseconds] = process.hrtime(startTime);
@@ -2045,6 +2074,7 @@ var systemUser = {
2045
2074
  isSystemUser: true
2046
2075
  };
2047
2076
  var PsqlEngine = class extends SqlEngine {
2077
+ // 5 seconds
2048
2078
  constructor(psqlConnectionPool, shouldListenForDbTriggers = false, scratchDatabaseSuffix = "") {
2049
2079
  super();
2050
2080
  this.psqlConnectionPool = psqlConnectionPool;
@@ -2057,6 +2087,9 @@ var PsqlEngine = class extends SqlEngine {
2057
2087
  setupTriggerListeners;
2058
2088
  triggerClient;
2059
2089
  scratchDbName = "";
2090
+ reconnectAttempts = 0;
2091
+ MAX_RECONNECT_ATTEMPTS = 5;
2092
+ INITIAL_RECONNECT_DELAY = 5e3;
2060
2093
  async close() {
2061
2094
  if (this.triggerClient) {
2062
2095
  await this.triggerClient.end();
@@ -2072,6 +2105,34 @@ var PsqlEngine = class extends SqlEngine {
2072
2105
  return val === null ? null : Number(val);
2073
2106
  });
2074
2107
  }
2108
+ async reconnectTriggerClient() {
2109
+ if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
2110
+ logger.error("Max reconnection attempts reached for trigger client. Stopping reconnection attempts.");
2111
+ return;
2112
+ }
2113
+ if (this.triggerClient) {
2114
+ try {
2115
+ await this.triggerClient.end();
2116
+ } catch (error) {
2117
+ logger.error(`Error closing trigger client: ${error}`);
2118
+ }
2119
+ }
2120
+ const delay = this.INITIAL_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts);
2121
+ logger.info(
2122
+ `Attempting to reconnect trigger client in ${delay / 1e3} seconds... (Attempt ${this.reconnectAttempts + 1}/${this.MAX_RECONNECT_ATTEMPTS})`
2123
+ );
2124
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
2125
+ this.reconnectAttempts++;
2126
+ try {
2127
+ await this.listenForDbTriggers();
2128
+ this.reconnectAttempts = 0;
2129
+ } catch (error) {
2130
+ logger.error(`Reconnection attempt ${this.reconnectAttempts} failed: ${error}`);
2131
+ if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
2132
+ await this.reconnectTriggerClient();
2133
+ }
2134
+ }
2135
+ }
2075
2136
  async listenForDbTriggers() {
2076
2137
  this.triggerClient = new Client({
2077
2138
  user: this.psqlConnectionPool.poolConfig.user,
@@ -2081,18 +2142,28 @@ var PsqlEngine = class extends SqlEngine {
2081
2142
  port: this.psqlConnectionPool.poolConfig.port,
2082
2143
  connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
2083
2144
  });
2084
- await this.triggerClient.connect();
2085
- const promises = [];
2086
- promises.push(this.triggerClient.query("LISTEN insert"));
2087
- promises.push(this.triggerClient.query("LISTEN update"));
2088
- promises.push(this.triggerClient.query("LISTEN delete"));
2089
- await Promise.all(promises);
2090
- this.triggerClient.on("notification", async (msg) => {
2091
- if (msg.channel === "insert" || msg.channel === "update" || msg.channel === "delete") {
2092
- const payload = ObjectUtils4.safeParse(msg.payload);
2093
- await this.handleTrigger(payload, msg.channel.toUpperCase());
2094
- }
2095
- });
2145
+ try {
2146
+ await this.triggerClient.connect();
2147
+ const promises = [];
2148
+ promises.push(this.triggerClient.query("LISTEN insert"));
2149
+ promises.push(this.triggerClient.query("LISTEN update"));
2150
+ promises.push(this.triggerClient.query("LISTEN delete"));
2151
+ await Promise.all(promises);
2152
+ this.triggerClient.on("error", async (error) => {
2153
+ logger.error(`Trigger client error: ${error}`);
2154
+ await this.reconnectTriggerClient();
2155
+ });
2156
+ this.triggerClient.on("notification", async (msg) => {
2157
+ if (msg.channel === "insert" || msg.channel === "update" || msg.channel === "delete") {
2158
+ const payload = ObjectUtils4.safeParse(msg.payload);
2159
+ await this.handleTrigger(payload, msg.channel.toUpperCase());
2160
+ }
2161
+ });
2162
+ logger.info("Successfully connected to database triggers");
2163
+ } catch (error) {
2164
+ logger.error(`Failed to setup trigger listeners: ${error}`);
2165
+ await this.reconnectTriggerClient();
2166
+ }
2096
2167
  }
2097
2168
  async handleTrigger(payload, mutationType) {
2098
2169
  if (payload.queryMetadata && payload.queryMetadata.connectionInstanceId === this.psqlConnectionPool.instanceId) {
@@ -3232,6 +3303,7 @@ export {
3232
3303
  questionMarksToOrderedParams,
3233
3304
  restura,
3234
3305
  resturaGlobalTypesGenerator,
3306
+ resturaSchema,
3235
3307
  updateObjectQuery
3236
3308
  };
3237
3309
  //# sourceMappingURL=index.js.map