@restura/core 1.9.2 → 2.0.0

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
@@ -10,245 +10,52 @@ var __decorateClass = (decorators, target, key, kind) => {
10
10
  };
11
11
 
12
12
  // src/logger/logger.ts
13
- import { config } from "@restura/internal";
14
- import pino from "pino";
15
- import pinoPretty from "pino-pretty";
16
-
17
- // src/restura/RsError.ts
18
- var HtmlStatusCodes = /* @__PURE__ */ ((HtmlStatusCodes2) => {
19
- HtmlStatusCodes2[HtmlStatusCodes2["BAD_REQUEST"] = 400] = "BAD_REQUEST";
20
- HtmlStatusCodes2[HtmlStatusCodes2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
21
- HtmlStatusCodes2[HtmlStatusCodes2["PAYMENT_REQUIRED"] = 402] = "PAYMENT_REQUIRED";
22
- HtmlStatusCodes2[HtmlStatusCodes2["FORBIDDEN"] = 403] = "FORBIDDEN";
23
- HtmlStatusCodes2[HtmlStatusCodes2["NOT_FOUND"] = 404] = "NOT_FOUND";
24
- HtmlStatusCodes2[HtmlStatusCodes2["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
25
- HtmlStatusCodes2[HtmlStatusCodes2["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT";
26
- HtmlStatusCodes2[HtmlStatusCodes2["CONFLICT"] = 409] = "CONFLICT";
27
- HtmlStatusCodes2[HtmlStatusCodes2["GONE"] = 410] = "GONE";
28
- HtmlStatusCodes2[HtmlStatusCodes2["PAYLOAD_TOO_LARGE"] = 413] = "PAYLOAD_TOO_LARGE";
29
- HtmlStatusCodes2[HtmlStatusCodes2["UNSUPPORTED_MEDIA_TYPE"] = 415] = "UNSUPPORTED_MEDIA_TYPE";
30
- HtmlStatusCodes2[HtmlStatusCodes2["UPGRADE_REQUIRED"] = 426] = "UPGRADE_REQUIRED";
31
- HtmlStatusCodes2[HtmlStatusCodes2["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
32
- HtmlStatusCodes2[HtmlStatusCodes2["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
33
- HtmlStatusCodes2[HtmlStatusCodes2["SERVER_ERROR"] = 500] = "SERVER_ERROR";
34
- HtmlStatusCodes2[HtmlStatusCodes2["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED";
35
- HtmlStatusCodes2[HtmlStatusCodes2["BAD_GATEWAY"] = 502] = "BAD_GATEWAY";
36
- HtmlStatusCodes2[HtmlStatusCodes2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
37
- HtmlStatusCodes2[HtmlStatusCodes2["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT";
38
- HtmlStatusCodes2[HtmlStatusCodes2["NETWORK_CONNECT_TIMEOUT"] = 599] = "NETWORK_CONNECT_TIMEOUT";
39
- return HtmlStatusCodes2;
40
- })(HtmlStatusCodes || {});
41
- var RsError = class _RsError extends Error {
42
- err;
43
- msg;
44
- options;
45
- status;
46
- constructor(errCode, message, options) {
47
- super(message);
48
- this.name = "RsError";
49
- this.err = errCode;
50
- this.msg = message || "";
51
- this.status = _RsError.htmlStatus(errCode);
52
- this.options = options;
53
- }
54
- toJSON() {
55
- return {
56
- type: this.name,
57
- err: this.err,
58
- message: this.message,
59
- msg: this.msg,
60
- status: this.status ?? 500,
61
- stack: this.stack ?? "",
62
- options: this.options
63
- };
64
- }
65
- static htmlStatus(code) {
66
- return htmlStatusMap[code];
67
- }
68
- static isRsError(error) {
69
- return error instanceof _RsError;
70
- }
71
- };
72
- var htmlStatusMap = {
73
- // 1:1 mappings to HTTP status codes
74
- BAD_REQUEST: 400 /* BAD_REQUEST */,
75
- UNAUTHORIZED: 401 /* UNAUTHORIZED */,
76
- PAYMENT_REQUIRED: 402 /* PAYMENT_REQUIRED */,
77
- FORBIDDEN: 403 /* FORBIDDEN */,
78
- NOT_FOUND: 404 /* NOT_FOUND */,
79
- METHOD_NOT_ALLOWED: 405 /* METHOD_NOT_ALLOWED */,
80
- REQUEST_TIMEOUT: 408 /* REQUEST_TIMEOUT */,
81
- CONFLICT: 409 /* CONFLICT */,
82
- GONE: 410 /* GONE */,
83
- PAYLOAD_TOO_LARGE: 413 /* PAYLOAD_TOO_LARGE */,
84
- UNSUPPORTED_MEDIA_TYPE: 415 /* UNSUPPORTED_MEDIA_TYPE */,
85
- UPGRADE_REQUIRED: 426 /* UPGRADE_REQUIRED */,
86
- UNPROCESSABLE_ENTITY: 422 /* UNPROCESSABLE_ENTITY */,
87
- TOO_MANY_REQUESTS: 429 /* TOO_MANY_REQUESTS */,
88
- SERVER_ERROR: 500 /* SERVER_ERROR */,
89
- NOT_IMPLEMENTED: 501 /* NOT_IMPLEMENTED */,
90
- BAD_GATEWAY: 502 /* BAD_GATEWAY */,
91
- SERVICE_UNAVAILABLE: 503 /* SERVICE_UNAVAILABLE */,
92
- GATEWAY_TIMEOUT: 504 /* GATEWAY_TIMEOUT */,
93
- NETWORK_CONNECT_TIMEOUT: 599 /* NETWORK_CONNECT_TIMEOUT */,
94
- // Specific business errors mapped to appropriate HTTP codes
95
- UNKNOWN_ERROR: 500 /* SERVER_ERROR */,
96
- RATE_LIMIT_EXCEEDED: 429 /* TOO_MANY_REQUESTS */,
97
- INVALID_TOKEN: 401 /* UNAUTHORIZED */,
98
- INCORRECT_EMAIL_OR_PASSWORD: 401 /* UNAUTHORIZED */,
99
- DUPLICATE: 409 /* CONFLICT */,
100
- CONNECTION_ERROR: 504 /* GATEWAY_TIMEOUT */,
101
- SCHEMA_ERROR: 500 /* SERVER_ERROR */,
102
- DATABASE_ERROR: 500 /* SERVER_ERROR */
103
- };
104
-
105
- // src/logger/loggerConfigSchema.ts
106
- import { z } from "zod";
107
- var loggerConfigSchema = z.object({
108
- level: z.enum(["fatal", "error", "warn", "info", "debug", "silly", "trace"]).default("info"),
109
- transports: z.array(z.custom()).optional(),
110
- serializers: z.object({
111
- err: z.custom().optional()
112
- }).optional(),
113
- stream: z.custom().optional()
114
- }).refine((data) => !(data.transports && data.stream), {
115
- message: "You must provide either a transports array or a stream object, but not both",
116
- path: ["transports"]
117
- });
118
-
119
- // src/logger/logger.ts
120
- var loggerConfig = await config.validate("logger", loggerConfigSchema);
121
- var logLevelMap = {
122
- fatal: "fatal",
123
- error: "error",
124
- warn: "warn",
125
- info: "info",
126
- debug: "debug",
127
- silly: "trace",
128
- trace: "trace"
129
- };
130
- var currentLogLevel = logLevelMap[loggerConfig.level];
131
- var defaultStream = pinoPretty({
132
- colorize: true,
133
- translateTime: "yyyy-mm-dd HH:MM:ss.l",
134
- ignore: "pid,hostname,_meta",
135
- // _meta allows a user to pass in metadata for JSON but not print it to the console
136
- messageFormat: "{msg}",
137
- levelFirst: true,
138
- customColors: "error:red,warn:yellow,info:green,debug:blue,trace:magenta",
139
- destination: process.stdout
140
- });
141
- function isAxiosError(error) {
142
- const isObject = (error2) => error2 !== null && typeof error2 === "object";
143
- return isObject(error) && "isAxiosError" in error && error.isAxiosError === true;
144
- }
145
- var baseSerializer = pino.stdSerializers.err;
146
- var defaultSerializer = (error) => {
147
- if (isAxiosError(error)) {
148
- const err = error;
149
- return {
150
- type: "AxiosError",
151
- message: err.message,
152
- stack: err.stack,
153
- url: err.config?.url,
154
- method: err.config?.method?.toUpperCase(),
155
- status: err.response?.status,
156
- responseData: err.response?.data
157
- };
158
- }
159
- if (RsError.isRsError(error)) {
160
- return error.toJSON();
13
+ var LEVEL_ORDER = ["fatal", "error", "warn", "info", "debug", "trace"];
14
+ function isLevelEnabled(configured, requested) {
15
+ return LEVEL_ORDER.indexOf(requested) <= LEVEL_ORDER.indexOf(configured);
16
+ }
17
+ var envLevel = process.env.RESTURA_LOG_LEVEL;
18
+ var consoleLoggerLevel = LEVEL_ORDER.includes(envLevel) ? envLevel : "info";
19
+ var consoleLogger = {
20
+ level: consoleLoggerLevel,
21
+ fatal: (msg, ...args) => {
22
+ if (isLevelEnabled(consoleLoggerLevel, "fatal")) console.error(msg, ...args);
23
+ },
24
+ error: (msg, ...args) => {
25
+ if (isLevelEnabled(consoleLoggerLevel, "error")) console.error(msg, ...args);
26
+ },
27
+ warn: (msg, ...args) => {
28
+ if (isLevelEnabled(consoleLoggerLevel, "warn")) console.warn(msg, ...args);
29
+ },
30
+ info: (msg, ...args) => {
31
+ if (isLevelEnabled(consoleLoggerLevel, "info")) console.log(msg, ...args);
32
+ },
33
+ debug: (msg, ...args) => {
34
+ if (isLevelEnabled(consoleLoggerLevel, "debug")) console.debug(msg, ...args);
35
+ },
36
+ trace: (msg, ...args) => {
37
+ if (isLevelEnabled(consoleLoggerLevel, "trace")) console.debug(msg, ...args);
161
38
  }
162
- return baseSerializer(error);
163
39
  };
164
- var errorSerializer = (() => {
165
- try {
166
- return loggerConfig.serializers?.err ? loggerConfig.serializers.err(baseSerializer) : defaultSerializer;
167
- } catch (error) {
168
- console.error("Failed to initialize custom error serializer, falling back to default", error);
169
- return defaultSerializer;
170
- }
171
- })();
172
- var pinoLogger = pino(
173
- {
174
- level: currentLogLevel,
175
- ...loggerConfig.transports ? { transport: { targets: loggerConfig.transports } } : {},
176
- serializers: {
177
- err: errorSerializer
178
- }
40
+ var _impl = consoleLogger;
41
+ var logger = {
42
+ get level() {
43
+ return _impl.level;
179
44
  },
180
- loggerConfig.stream ? loggerConfig.stream : defaultStream
181
- );
182
- function buildContext(args) {
183
- const ctx = {};
184
- const prims = [];
185
- for (const arg of args) {
186
- if (arg instanceof Error && !ctx.err) {
187
- ctx.err = arg;
188
- } else if (arg && typeof arg === "object" && !Array.isArray(arg)) {
189
- Object.assign(ctx, arg);
190
- } else {
191
- prims.push(arg);
192
- }
45
+ fatal: (msg, ...args) => _impl.fatal(msg, ...args),
46
+ error: (msg, ...args) => _impl.error(msg, ...args),
47
+ warn: (msg, ...args) => _impl.warn(msg, ...args),
48
+ info: (msg, ...args) => _impl.info(msg, ...args),
49
+ debug: (msg, ...args) => _impl.debug(msg, ...args),
50
+ trace: (msg, ...args) => _impl.trace(msg, ...args)
51
+ };
52
+ function setLogger(impl) {
53
+ if (impl === logger) {
54
+ throw new Error("setLogger: cannot install the proxy logger as its own implementation");
193
55
  }
194
- if (prims.length) ctx.args = prims;
195
- return ctx;
56
+ if (impl === _impl) return;
57
+ _impl = impl;
196
58
  }
197
- function log(level, message, ...args) {
198
- pinoLogger[level](buildContext(args), message);
199
- }
200
- var logger = {
201
- level: loggerConfig.level,
202
- fatal: ((msg, ...args) => {
203
- if (typeof msg === "string") {
204
- log("fatal", msg, ...args);
205
- } else {
206
- pinoLogger.fatal(msg);
207
- }
208
- }),
209
- error: ((msg, ...args) => {
210
- if (typeof msg === "string") {
211
- log("error", msg, ...args);
212
- } else {
213
- pinoLogger.error(msg);
214
- }
215
- }),
216
- warn: ((msg, ...args) => {
217
- if (typeof msg === "string") {
218
- log("warn", msg, ...args);
219
- } else {
220
- pinoLogger.warn(msg);
221
- }
222
- }),
223
- info: ((msg, ...args) => {
224
- if (typeof msg === "string") {
225
- log("info", msg, ...args);
226
- } else {
227
- pinoLogger.info(msg);
228
- }
229
- }),
230
- debug: ((msg, ...args) => {
231
- if (typeof msg === "string") {
232
- log("debug", msg, ...args);
233
- } else {
234
- pinoLogger.debug(msg);
235
- }
236
- }),
237
- silly: ((msg, ...args) => {
238
- if (typeof msg === "string") {
239
- log("trace", msg, ...args);
240
- } else {
241
- pinoLogger.trace(msg);
242
- }
243
- }),
244
- trace: ((msg, ...args) => {
245
- if (typeof msg === "string") {
246
- log("trace", msg, ...args);
247
- } else {
248
- pinoLogger.trace(msg);
249
- }
250
- })
251
- };
252
59
 
253
60
  // src/restura/eventManager.ts
254
61
  import Bluebird from "bluebird";
@@ -423,6 +230,94 @@ var SqlUtils = class _SqlUtils {
423
230
  }
424
231
  };
425
232
 
233
+ // src/restura/RsError.ts
234
+ var HtmlStatusCodes = /* @__PURE__ */ ((HtmlStatusCodes2) => {
235
+ HtmlStatusCodes2[HtmlStatusCodes2["BAD_REQUEST"] = 400] = "BAD_REQUEST";
236
+ HtmlStatusCodes2[HtmlStatusCodes2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
237
+ HtmlStatusCodes2[HtmlStatusCodes2["PAYMENT_REQUIRED"] = 402] = "PAYMENT_REQUIRED";
238
+ HtmlStatusCodes2[HtmlStatusCodes2["FORBIDDEN"] = 403] = "FORBIDDEN";
239
+ HtmlStatusCodes2[HtmlStatusCodes2["NOT_FOUND"] = 404] = "NOT_FOUND";
240
+ HtmlStatusCodes2[HtmlStatusCodes2["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
241
+ HtmlStatusCodes2[HtmlStatusCodes2["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT";
242
+ HtmlStatusCodes2[HtmlStatusCodes2["CONFLICT"] = 409] = "CONFLICT";
243
+ HtmlStatusCodes2[HtmlStatusCodes2["GONE"] = 410] = "GONE";
244
+ HtmlStatusCodes2[HtmlStatusCodes2["PAYLOAD_TOO_LARGE"] = 413] = "PAYLOAD_TOO_LARGE";
245
+ HtmlStatusCodes2[HtmlStatusCodes2["UNSUPPORTED_MEDIA_TYPE"] = 415] = "UNSUPPORTED_MEDIA_TYPE";
246
+ HtmlStatusCodes2[HtmlStatusCodes2["UPGRADE_REQUIRED"] = 426] = "UPGRADE_REQUIRED";
247
+ HtmlStatusCodes2[HtmlStatusCodes2["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
248
+ HtmlStatusCodes2[HtmlStatusCodes2["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
249
+ HtmlStatusCodes2[HtmlStatusCodes2["SERVER_ERROR"] = 500] = "SERVER_ERROR";
250
+ HtmlStatusCodes2[HtmlStatusCodes2["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED";
251
+ HtmlStatusCodes2[HtmlStatusCodes2["BAD_GATEWAY"] = 502] = "BAD_GATEWAY";
252
+ HtmlStatusCodes2[HtmlStatusCodes2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
253
+ HtmlStatusCodes2[HtmlStatusCodes2["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT";
254
+ HtmlStatusCodes2[HtmlStatusCodes2["NETWORK_CONNECT_TIMEOUT"] = 599] = "NETWORK_CONNECT_TIMEOUT";
255
+ return HtmlStatusCodes2;
256
+ })(HtmlStatusCodes || {});
257
+ var RsError = class _RsError extends Error {
258
+ err;
259
+ msg;
260
+ options;
261
+ status;
262
+ constructor(errCode, message, options) {
263
+ super(message);
264
+ this.name = "RsError";
265
+ this.err = errCode;
266
+ this.msg = message || "";
267
+ this.status = _RsError.htmlStatus(errCode);
268
+ this.options = options;
269
+ }
270
+ toJSON() {
271
+ return {
272
+ type: this.name,
273
+ err: this.err,
274
+ message: this.message,
275
+ msg: this.msg,
276
+ status: this.status ?? 500,
277
+ stack: this.stack ?? "",
278
+ options: this.options
279
+ };
280
+ }
281
+ static htmlStatus(code) {
282
+ return htmlStatusMap[code];
283
+ }
284
+ static isRsError(error) {
285
+ return error instanceof _RsError;
286
+ }
287
+ };
288
+ var htmlStatusMap = {
289
+ // 1:1 mappings to HTTP status codes
290
+ BAD_REQUEST: 400 /* BAD_REQUEST */,
291
+ UNAUTHORIZED: 401 /* UNAUTHORIZED */,
292
+ PAYMENT_REQUIRED: 402 /* PAYMENT_REQUIRED */,
293
+ FORBIDDEN: 403 /* FORBIDDEN */,
294
+ NOT_FOUND: 404 /* NOT_FOUND */,
295
+ METHOD_NOT_ALLOWED: 405 /* METHOD_NOT_ALLOWED */,
296
+ REQUEST_TIMEOUT: 408 /* REQUEST_TIMEOUT */,
297
+ CONFLICT: 409 /* CONFLICT */,
298
+ GONE: 410 /* GONE */,
299
+ PAYLOAD_TOO_LARGE: 413 /* PAYLOAD_TOO_LARGE */,
300
+ UNSUPPORTED_MEDIA_TYPE: 415 /* UNSUPPORTED_MEDIA_TYPE */,
301
+ UPGRADE_REQUIRED: 426 /* UPGRADE_REQUIRED */,
302
+ UNPROCESSABLE_ENTITY: 422 /* UNPROCESSABLE_ENTITY */,
303
+ TOO_MANY_REQUESTS: 429 /* TOO_MANY_REQUESTS */,
304
+ SERVER_ERROR: 500 /* SERVER_ERROR */,
305
+ NOT_IMPLEMENTED: 501 /* NOT_IMPLEMENTED */,
306
+ BAD_GATEWAY: 502 /* BAD_GATEWAY */,
307
+ SERVICE_UNAVAILABLE: 503 /* SERVICE_UNAVAILABLE */,
308
+ GATEWAY_TIMEOUT: 504 /* GATEWAY_TIMEOUT */,
309
+ NETWORK_CONNECT_TIMEOUT: 599 /* NETWORK_CONNECT_TIMEOUT */,
310
+ // Specific business errors mapped to appropriate HTTP codes
311
+ UNKNOWN_ERROR: 500 /* SERVER_ERROR */,
312
+ RATE_LIMIT_EXCEEDED: 429 /* TOO_MANY_REQUESTS */,
313
+ INVALID_TOKEN: 401 /* UNAUTHORIZED */,
314
+ INCORRECT_EMAIL_OR_PASSWORD: 401 /* UNAUTHORIZED */,
315
+ DUPLICATE: 409 /* CONFLICT */,
316
+ CONNECTION_ERROR: 504 /* GATEWAY_TIMEOUT */,
317
+ SCHEMA_ERROR: 500 /* SERVER_ERROR */,
318
+ DATABASE_ERROR: 500 /* SERVER_ERROR */
319
+ };
320
+
426
321
  // src/restura/validators/ResponseValidator.ts
427
322
  var ResponseValidator = class _ResponseValidator {
428
323
  rootMap;
@@ -905,7 +800,7 @@ declare namespace Restura {
905
800
 
906
801
  // src/restura/restura.ts
907
802
  import { ObjectUtils as ObjectUtils4, StringUtils as StringUtils3 } from "@redskytech/core-utils";
908
- import { config as config2 } from "@restura/internal";
803
+ import { config } from "@restura/internal";
909
804
 
910
805
  // ../../node_modules/.pnpm/autobind-decorator@2.4.0/node_modules/autobind-decorator/lib/esm/index.js
911
806
  function _typeof(obj) {
@@ -1259,12 +1154,12 @@ function customTypeValidationGenerator(currentSchema, ignoreGeneratedTypes = fal
1259
1154
  return type;
1260
1155
  });
1261
1156
  fs2.writeFileSync(temporaryFile.name, additionalImports + typesWithExport.join("\n"));
1262
- const config3 = {
1157
+ const config2 = {
1263
1158
  path: resolve(temporaryFile.name),
1264
1159
  tsconfig: path2.join(process.cwd(), "tsconfig.json"),
1265
1160
  skipTypeCheck: true
1266
1161
  };
1267
- const generator = createGenerator(config3);
1162
+ const generator = createGenerator(config2);
1268
1163
  customInterfaceNames.forEach((item) => {
1269
1164
  try {
1270
1165
  const ddlSchema = generator.createSchema(item);
@@ -1392,30 +1287,30 @@ var getMulterUpload = (directory) => {
1392
1287
  };
1393
1288
 
1394
1289
  // src/restura/schemas/resturaSchema.ts
1395
- import { z as z3 } from "zod";
1290
+ import { z as z2 } from "zod";
1396
1291
 
1397
1292
  // src/restura/schemas/validatorDataSchema.ts
1398
- import { z as z2 } from "zod";
1399
- var validatorDataSchemeValue = z2.union([z2.string(), z2.array(z2.string()), z2.number(), z2.array(z2.number())]);
1400
- var validatorDataSchema = z2.object({
1401
- type: z2.enum(["TYPE_CHECK", "MIN", "MAX", "ONE_OF"]),
1293
+ import { z } from "zod";
1294
+ var validatorDataSchemeValue = z.union([z.string(), z.array(z.string()), z.number(), z.array(z.number())]);
1295
+ var validatorDataSchema = z.object({
1296
+ type: z.enum(["TYPE_CHECK", "MIN", "MAX", "ONE_OF"]),
1402
1297
  value: validatorDataSchemeValue
1403
1298
  }).strict();
1404
1299
 
1405
1300
  // src/restura/schemas/resturaSchema.ts
1406
- var orderBySchema = z3.object({
1407
- columnName: z3.string(),
1408
- order: z3.enum(["ASC", "DESC"]),
1409
- tableName: z3.string()
1301
+ var orderBySchema = z2.object({
1302
+ columnName: z2.string(),
1303
+ order: z2.enum(["ASC", "DESC"]),
1304
+ tableName: z2.string()
1410
1305
  }).strict();
1411
- var groupBySchema = z3.object({
1412
- columnName: z3.string(),
1413
- tableName: z3.string()
1306
+ var groupBySchema = z2.object({
1307
+ columnName: z2.string(),
1308
+ tableName: z2.string()
1414
1309
  }).strict();
1415
- var whereDataSchema = z3.object({
1416
- tableName: z3.string().optional(),
1417
- columnName: z3.string().optional(),
1418
- operator: z3.enum([
1310
+ var whereDataSchema = z2.object({
1311
+ tableName: z2.string().optional(),
1312
+ columnName: z2.string().optional(),
1313
+ operator: z2.enum([
1419
1314
  "=",
1420
1315
  "<",
1421
1316
  ">",
@@ -1431,79 +1326,79 @@ var whereDataSchema = z3.object({
1431
1326
  "IS",
1432
1327
  "IS NOT"
1433
1328
  ]).optional(),
1434
- value: z3.string().or(z3.number()).optional(),
1435
- custom: z3.string().optional(),
1436
- conjunction: z3.enum(["AND", "OR"]).optional()
1329
+ value: z2.string().or(z2.number()).optional(),
1330
+ custom: z2.string().optional(),
1331
+ conjunction: z2.enum(["AND", "OR"]).optional()
1437
1332
  }).strict();
1438
- var assignmentDataSchema = z3.object({
1439
- name: z3.string(),
1440
- value: z3.string()
1333
+ var assignmentDataSchema = z2.object({
1334
+ name: z2.string(),
1335
+ value: z2.string()
1441
1336
  }).strict();
1442
- var joinDataSchema = z3.object({
1443
- table: z3.string(),
1444
- localTable: z3.string().optional(),
1337
+ var joinDataSchema = z2.object({
1338
+ table: z2.string(),
1339
+ localTable: z2.string().optional(),
1445
1340
  // Defaults to base table if not specificed
1446
- localTableAlias: z3.string().optional(),
1341
+ localTableAlias: z2.string().optional(),
1447
1342
  // If we are joining a table off of a previous join, this is the alias of the previous join
1448
- localColumnName: z3.string().optional(),
1449
- foreignColumnName: z3.string().optional(),
1450
- custom: z3.string().optional(),
1451
- type: z3.enum(["LEFT", "INNER", "RIGHT"]),
1452
- alias: z3.string()
1343
+ localColumnName: z2.string().optional(),
1344
+ foreignColumnName: z2.string().optional(),
1345
+ custom: z2.string().optional(),
1346
+ type: z2.enum(["LEFT", "INNER", "RIGHT"]),
1347
+ alias: z2.string()
1453
1348
  }).strict();
1454
- var requestDataSchema = z3.object({
1455
- name: z3.string(),
1456
- required: z3.boolean(),
1457
- isNullable: z3.boolean().optional(),
1458
- validator: z3.array(validatorDataSchema)
1349
+ var requestDataSchema = z2.object({
1350
+ name: z2.string(),
1351
+ required: z2.boolean(),
1352
+ isNullable: z2.boolean().optional(),
1353
+ validator: z2.array(validatorDataSchema)
1459
1354
  }).strict();
1460
- var responseDataSchema = z3.object({
1461
- name: z3.string(),
1462
- selector: z3.string().optional(),
1463
- subquery: z3.object({
1464
- table: z3.string(),
1465
- joins: z3.array(joinDataSchema),
1466
- where: z3.array(whereDataSchema),
1355
+ var responseDataSchema = z2.object({
1356
+ name: z2.string(),
1357
+ selector: z2.string().optional(),
1358
+ subquery: z2.object({
1359
+ table: z2.string(),
1360
+ joins: z2.array(joinDataSchema),
1361
+ where: z2.array(whereDataSchema),
1467
1362
  get properties() {
1468
- return z3.array(responseDataSchema);
1363
+ return z2.array(responseDataSchema);
1469
1364
  },
1470
1365
  groupBy: groupBySchema.optional(),
1471
1366
  orderBy: orderBySchema.optional()
1472
1367
  }).optional(),
1473
- type: z3.string().optional()
1368
+ type: z2.string().optional()
1474
1369
  // Type allows you to override the type of the response, used in custom selectors
1475
1370
  }).strict();
1476
- var routeDataBaseSchema = z3.object({
1477
- method: z3.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
1478
- name: z3.string(),
1479
- description: z3.string(),
1480
- path: z3.string(),
1481
- deprecation: z3.object({
1482
- date: z3.iso.datetime(),
1483
- message: z3.string().optional()
1371
+ var routeDataBaseSchema = z2.object({
1372
+ method: z2.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
1373
+ name: z2.string(),
1374
+ description: z2.string(),
1375
+ path: z2.string(),
1376
+ deprecation: z2.object({
1377
+ date: z2.iso.datetime(),
1378
+ message: z2.string().optional()
1484
1379
  }).optional(),
1485
- roles: z3.array(z3.string()),
1486
- scopes: z3.array(z3.string())
1380
+ roles: z2.array(z2.string()),
1381
+ scopes: z2.array(z2.string())
1487
1382
  }).strict();
1488
1383
  var standardRouteSchema = routeDataBaseSchema.extend({
1489
- type: z3.enum(["ONE", "ARRAY", "PAGED"]),
1490
- table: z3.string(),
1491
- joins: z3.array(joinDataSchema),
1492
- assignments: z3.array(assignmentDataSchema),
1493
- where: z3.array(whereDataSchema),
1494
- request: z3.array(requestDataSchema),
1495
- response: z3.array(responseDataSchema),
1384
+ type: z2.enum(["ONE", "ARRAY", "PAGED"]),
1385
+ table: z2.string(),
1386
+ joins: z2.array(joinDataSchema),
1387
+ assignments: z2.array(assignmentDataSchema),
1388
+ where: z2.array(whereDataSchema),
1389
+ request: z2.array(requestDataSchema),
1390
+ response: z2.array(responseDataSchema),
1496
1391
  groupBy: groupBySchema.optional(),
1497
1392
  orderBy: orderBySchema.optional()
1498
1393
  }).strict();
1499
1394
  var customRouteSchema = routeDataBaseSchema.extend({
1500
- type: z3.enum(["CUSTOM_ONE", "CUSTOM_ARRAY", "CUSTOM_PAGED"]),
1501
- responseType: z3.union([z3.string(), z3.enum(["string", "number", "boolean"])]),
1502
- requestType: z3.string().optional(),
1503
- request: z3.array(requestDataSchema).optional(),
1504
- fileUploadType: z3.enum(["SINGLE", "MULTIPLE"]).optional()
1395
+ type: z2.enum(["CUSTOM_ONE", "CUSTOM_ARRAY", "CUSTOM_PAGED"]),
1396
+ responseType: z2.union([z2.string(), z2.enum(["string", "number", "boolean"])]),
1397
+ requestType: z2.string().optional(),
1398
+ request: z2.array(requestDataSchema).optional(),
1399
+ fileUploadType: z2.enum(["SINGLE", "MULTIPLE"]).optional()
1505
1400
  }).strict();
1506
- var postgresColumnNumericTypesSchema = z3.enum([
1401
+ var postgresColumnNumericTypesSchema = z2.enum([
1507
1402
  "SMALLINT",
1508
1403
  // 2 bytes, -32,768 to 32,767
1509
1404
  "INTEGER",
@@ -1523,7 +1418,7 @@ var postgresColumnNumericTypesSchema = z3.enum([
1523
1418
  "BIGSERIAL"
1524
1419
  // auto-incrementing big integer
1525
1420
  ]);
1526
- var postgresColumnStringTypesSchema = z3.enum([
1421
+ var postgresColumnStringTypesSchema = z2.enum([
1527
1422
  "CHAR",
1528
1423
  // fixed-length, blank-padded
1529
1424
  "VARCHAR",
@@ -1533,7 +1428,7 @@ var postgresColumnStringTypesSchema = z3.enum([
1533
1428
  "BYTEA"
1534
1429
  // binary data
1535
1430
  ]);
1536
- var postgresColumnDateTypesSchema = z3.enum([
1431
+ var postgresColumnDateTypesSchema = z2.enum([
1537
1432
  "DATE",
1538
1433
  // calendar date (year, month, day)
1539
1434
  "TIMESTAMP",
@@ -1545,13 +1440,13 @@ var postgresColumnDateTypesSchema = z3.enum([
1545
1440
  "INTERVAL"
1546
1441
  // time span
1547
1442
  ]);
1548
- var postgresColumnJsonTypesSchema = z3.enum([
1443
+ var postgresColumnJsonTypesSchema = z2.enum([
1549
1444
  "JSON",
1550
1445
  // stores JSON data as raw text
1551
1446
  "JSONB"
1552
1447
  // stores JSON data in a binary format, optimized for query performance
1553
1448
  ]);
1554
- var mariaDbColumnNumericTypesSchema = z3.enum([
1449
+ var mariaDbColumnNumericTypesSchema = z2.enum([
1555
1450
  "BOOLEAN",
1556
1451
  // 1-byte A synonym for "TINYINT(1)". Supported from version 1.2.0 onwards.
1557
1452
  "TINYINT",
@@ -1571,7 +1466,7 @@ var mariaDbColumnNumericTypesSchema = z3.enum([
1571
1466
  "DOUBLE"
1572
1467
  // 8 bytes Stored in 64-bit IEEE-754 floating point format. As such, the number of significant digits is about 15 and the range of values is approximately +/-1e308.
1573
1468
  ]);
1574
- var mariaDbColumnStringTypesSchema = z3.enum([
1469
+ var mariaDbColumnStringTypesSchema = z2.enum([
1575
1470
  "CHAR",
1576
1471
  // 1, 2, 4, or 8 bytes Holds letters and special characters of fixed length. Max length is 255. Default and minimum size is 1 byte.
1577
1472
  "VARCHAR",
@@ -1597,7 +1492,7 @@ var mariaDbColumnStringTypesSchema = z3.enum([
1597
1492
  "ENUM"
1598
1493
  // Enum type
1599
1494
  ]);
1600
- var mariaDbColumnDateTypesSchema = z3.enum([
1495
+ var mariaDbColumnDateTypesSchema = z2.enum([
1601
1496
  "DATE",
1602
1497
  // 4-bytes Date has year, month, and day.
1603
1498
  "DATETIME",
@@ -1607,9 +1502,9 @@ var mariaDbColumnDateTypesSchema = z3.enum([
1607
1502
  "TIMESTAMP"
1608
1503
  // 4-bytes Values are stored as the number of seconds since 1970-01-01 00:00:00 UTC, and optionally microseconds.
1609
1504
  ]);
1610
- var columnDataSchema = z3.object({
1611
- name: z3.string(),
1612
- type: z3.union([
1505
+ var columnDataSchema = z2.object({
1506
+ name: z2.string(),
1507
+ type: z2.union([
1613
1508
  postgresColumnNumericTypesSchema,
1614
1509
  postgresColumnStringTypesSchema,
1615
1510
  postgresColumnDateTypesSchema,
@@ -1618,26 +1513,26 @@ var columnDataSchema = z3.object({
1618
1513
  mariaDbColumnStringTypesSchema,
1619
1514
  mariaDbColumnDateTypesSchema
1620
1515
  ]),
1621
- isNullable: z3.boolean(),
1622
- roles: z3.array(z3.string()),
1623
- scopes: z3.array(z3.string()),
1624
- comment: z3.string().optional(),
1625
- default: z3.string().optional(),
1626
- value: z3.string().optional(),
1627
- isPrimary: z3.boolean().optional(),
1628
- isUnique: z3.boolean().optional(),
1629
- hasAutoIncrement: z3.boolean().optional(),
1630
- length: z3.number().optional()
1516
+ isNullable: z2.boolean(),
1517
+ roles: z2.array(z2.string()),
1518
+ scopes: z2.array(z2.string()),
1519
+ comment: z2.string().optional(),
1520
+ default: z2.string().optional(),
1521
+ value: z2.string().optional(),
1522
+ isPrimary: z2.boolean().optional(),
1523
+ isUnique: z2.boolean().optional(),
1524
+ hasAutoIncrement: z2.boolean().optional(),
1525
+ length: z2.number().optional()
1631
1526
  }).strict();
1632
- var indexDataSchema = z3.object({
1633
- name: z3.string(),
1634
- columns: z3.array(z3.string()),
1635
- isUnique: z3.boolean(),
1636
- isPrimaryKey: z3.boolean(),
1637
- order: z3.enum(["ASC", "DESC"]),
1638
- where: z3.string().optional()
1527
+ var indexDataSchema = z2.object({
1528
+ name: z2.string(),
1529
+ columns: z2.array(z2.string()),
1530
+ isUnique: z2.boolean(),
1531
+ isPrimaryKey: z2.boolean(),
1532
+ order: z2.enum(["ASC", "DESC"]),
1533
+ where: z2.string().optional()
1639
1534
  }).strict();
1640
- var foreignKeyActionsSchema = z3.enum([
1535
+ var foreignKeyActionsSchema = z2.enum([
1641
1536
  "CASCADE",
1642
1537
  // CASCADE action for foreign keys
1643
1538
  "SET NULL",
@@ -1649,50 +1544,50 @@ var foreignKeyActionsSchema = z3.enum([
1649
1544
  "SET DEFAULT"
1650
1545
  // SET DEFAULT action for foreign keys
1651
1546
  ]);
1652
- var foreignKeyDataSchema = z3.object({
1653
- name: z3.string(),
1654
- column: z3.string(),
1655
- refTable: z3.string(),
1656
- refColumn: z3.string(),
1547
+ var foreignKeyDataSchema = z2.object({
1548
+ name: z2.string(),
1549
+ column: z2.string(),
1550
+ refTable: z2.string(),
1551
+ refColumn: z2.string(),
1657
1552
  onDelete: foreignKeyActionsSchema,
1658
1553
  onUpdate: foreignKeyActionsSchema
1659
1554
  }).strict();
1660
- var checkConstraintDataSchema = z3.object({
1661
- name: z3.string(),
1662
- check: z3.string()
1555
+ var checkConstraintDataSchema = z2.object({
1556
+ name: z2.string(),
1557
+ check: z2.string()
1663
1558
  }).strict();
1664
- var tableDataSchema = z3.object({
1665
- name: z3.string(),
1666
- columns: z3.array(columnDataSchema),
1667
- indexes: z3.array(indexDataSchema),
1668
- foreignKeys: z3.array(foreignKeyDataSchema),
1669
- checkConstraints: z3.array(checkConstraintDataSchema),
1670
- roles: z3.array(z3.string()),
1671
- scopes: z3.array(z3.string()),
1672
- notify: z3.union([z3.literal("ALL"), z3.array(z3.string())]).optional()
1559
+ var tableDataSchema = z2.object({
1560
+ name: z2.string(),
1561
+ columns: z2.array(columnDataSchema),
1562
+ indexes: z2.array(indexDataSchema),
1563
+ foreignKeys: z2.array(foreignKeyDataSchema),
1564
+ checkConstraints: z2.array(checkConstraintDataSchema),
1565
+ roles: z2.array(z2.string()),
1566
+ scopes: z2.array(z2.string()),
1567
+ notify: z2.union([z2.literal("ALL"), z2.array(z2.string())]).optional()
1673
1568
  }).strict();
1674
- var endpointDataSchema = z3.object({
1675
- name: z3.string(),
1676
- description: z3.string(),
1677
- baseUrl: z3.string(),
1678
- routes: z3.array(z3.union([standardRouteSchema, customRouteSchema]))
1569
+ var endpointDataSchema = z2.object({
1570
+ name: z2.string(),
1571
+ description: z2.string(),
1572
+ baseUrl: z2.string(),
1573
+ routes: z2.array(z2.union([standardRouteSchema, customRouteSchema]))
1679
1574
  }).strict();
1680
- var resturaSchema = z3.object({
1681
- database: z3.array(tableDataSchema),
1682
- endpoints: z3.array(endpointDataSchema),
1683
- globalParams: z3.array(z3.string()),
1684
- roles: z3.array(z3.string()),
1685
- scopes: z3.array(z3.string()),
1686
- customTypes: z3.array(z3.string())
1575
+ var resturaSchema = z2.object({
1576
+ database: z2.array(tableDataSchema),
1577
+ endpoints: z2.array(endpointDataSchema),
1578
+ globalParams: z2.array(z2.string()),
1579
+ roles: z2.array(z2.string()),
1580
+ scopes: z2.array(z2.string()),
1581
+ customTypes: z2.array(z2.string())
1687
1582
  }).strict();
1688
1583
  async function isSchemaValid(schemaToCheck) {
1689
1584
  try {
1690
1585
  resturaSchema.parse(schemaToCheck);
1691
1586
  return true;
1692
1587
  } catch (error) {
1693
- if (error instanceof z3.ZodError) {
1588
+ if (error instanceof z2.ZodError) {
1694
1589
  logger.error("Schema failed to validate with the following error:");
1695
- console.error(z3.prettifyError(error));
1590
+ console.error(z2.prettifyError(error));
1696
1591
  } else {
1697
1592
  logger.error(error);
1698
1593
  }
@@ -1903,33 +1798,23 @@ async function schemaValidation(req, res, next) {
1903
1798
  }
1904
1799
 
1905
1800
  // src/restura/schemas/resturaConfigSchema.ts
1906
- import { z as z4 } from "zod";
1801
+ import { z as z3 } from "zod";
1907
1802
  var isTsx = process.argv[1]?.endsWith(".ts");
1908
1803
  var isTsNode = process.env.TS_NODE_DEV || process.env.TS_NODE_PROJECT;
1909
1804
  var customApiFolderPath = isTsx || isTsNode ? "/src/api" : "/dist/api";
1910
- var resturaConfigSchema = z4.object({
1911
- authToken: z4.string().min(1, "Missing Restura Auth Token"),
1912
- sendErrorStackTrace: z4.boolean().default(false),
1913
- schemaFilePath: z4.string().default(process.cwd() + "/restura.schema.json"),
1914
- customApiFolderPath: z4.string().default(process.cwd() + customApiFolderPath),
1915
- generatedTypesPath: z4.string().default(process.cwd() + "/src/@types"),
1916
- fileTempCachePath: z4.string().optional(),
1917
- scratchDatabaseSuffix: z4.string().optional()
1805
+ var resturaConfigSchema = z3.object({
1806
+ authToken: z3.string().min(1, "Missing Restura Auth Token"),
1807
+ sendErrorStackTrace: z3.boolean().default(false),
1808
+ schemaFilePath: z3.string().default(process.cwd() + "/restura.schema.json"),
1809
+ customApiFolderPath: z3.string().default(process.cwd() + customApiFolderPath),
1810
+ generatedTypesPath: z3.string().default(process.cwd() + "/src/@types"),
1811
+ fileTempCachePath: z3.string().optional(),
1812
+ scratchDatabaseSuffix: z3.string().optional()
1918
1813
  });
1919
1814
 
1920
1815
  // src/restura/sql/PsqlEngine.ts
1921
1816
  import { ObjectUtils as ObjectUtils3 } from "@redskytech/core-utils";
1922
- import getDiff from "@wmfs/pg-diff-sync";
1923
- import pgInfo from "@wmfs/pg-info";
1924
- import pg2 from "pg";
1925
-
1926
- // src/restura/sql/PsqlPool.ts
1927
- import pg from "pg";
1928
-
1929
- // src/restura/sql/PsqlConnection.ts
1930
- import crypto from "crypto";
1931
- import { format as sqlFormat } from "sql-formatter";
1932
- import { z as z5 } from "zod";
1817
+ import pg3 from "pg";
1933
1818
 
1934
1819
  // src/restura/sql/PsqlUtils.ts
1935
1820
  import format from "pg-format";
@@ -2020,135 +1905,6 @@ function toSqlLiteral(value) {
2020
1905
  return format.literal(value);
2021
1906
  }
2022
1907
 
2023
- // src/restura/sql/PsqlConnection.ts
2024
- var PsqlConnection = class {
2025
- instanceId;
2026
- constructor(instanceId) {
2027
- this.instanceId = instanceId || crypto.randomUUID();
2028
- }
2029
- async queryOne(query, options, requesterDetails) {
2030
- const formattedQuery = questionMarksToOrderedParams(query);
2031
- const meta = { connectionInstanceId: this.instanceId, ...requesterDetails };
2032
- const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
2033
- `;
2034
- const startTime = process.hrtime();
2035
- try {
2036
- const response = await this.query(queryMetadata + formattedQuery, options);
2037
- if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
2038
- else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
2039
- this.logSqlStatement(formattedQuery, options, meta, startTime);
2040
- return response.rows[0];
2041
- } catch (error) {
2042
- this.logSqlStatement(formattedQuery, options, meta, startTime);
2043
- if (RsError.isRsError(error)) throw error;
2044
- if (error?.routine === "_bt_check_unique") {
2045
- throw new RsError("DUPLICATE", error.message);
2046
- }
2047
- throw new RsError("DATABASE_ERROR", `${error.message}`);
2048
- }
2049
- }
2050
- async queryOneSchema(query, params, requesterDetails, zodSchema) {
2051
- const result = await this.queryOne(query, params, requesterDetails);
2052
- try {
2053
- return zodSchema.parse(result);
2054
- } catch (error) {
2055
- if (error instanceof z5.ZodError) {
2056
- logger.error("Invalid data returned from database:");
2057
- logger.silly("\n" + JSON.stringify(result, null, 2));
2058
- logger.error("\n" + z5.prettifyError(error));
2059
- } else {
2060
- logger.error(error);
2061
- }
2062
- throw new RsError("DATABASE_ERROR", `Invalid data returned from database`);
2063
- }
2064
- }
2065
- async runQuery(query, options, requesterDetails) {
2066
- const formattedQuery = questionMarksToOrderedParams(query);
2067
- const meta = { connectionInstanceId: this.instanceId, ...requesterDetails };
2068
- const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
2069
- `;
2070
- const startTime = process.hrtime();
2071
- try {
2072
- const response = await this.query(queryMetadata + formattedQuery, options);
2073
- this.logSqlStatement(formattedQuery, options, meta, startTime);
2074
- return response.rows;
2075
- } catch (error) {
2076
- this.logSqlStatement(formattedQuery, options, meta, startTime);
2077
- if (error?.routine === "_bt_check_unique") {
2078
- throw new RsError("DUPLICATE", error.message);
2079
- }
2080
- throw new RsError("DATABASE_ERROR", `${error.message}`);
2081
- }
2082
- }
2083
- async runQuerySchema(query, params, requesterDetails, zodSchema) {
2084
- const result = await this.runQuery(query, params, requesterDetails);
2085
- try {
2086
- return z5.array(zodSchema).parse(result);
2087
- } catch (error) {
2088
- if (error instanceof z5.ZodError) {
2089
- logger.error("Invalid data returned from database:");
2090
- logger.silly("\n" + JSON.stringify(result, null, 2));
2091
- logger.error("\n" + z5.prettifyError(error));
2092
- } else {
2093
- logger.error(error);
2094
- }
2095
- throw new RsError("DATABASE_ERROR", `Invalid data returned from database`);
2096
- }
2097
- }
2098
- logSqlStatement(query, options, queryMetadata, startTime, prefix = "") {
2099
- if (logger.level !== "trace" && logger.level !== "silly") return;
2100
- const sqlStatement = query.replace(/\$(\d+)/g, (_, num) => {
2101
- const paramIndex = parseInt(num) - 1;
2102
- if (paramIndex >= options.length) return "INVALID_PARAM_INDEX";
2103
- return toSqlLiteral(options[paramIndex]);
2104
- });
2105
- const formattedSql = sqlFormat(sqlStatement, {
2106
- language: "postgresql",
2107
- linesBetweenQueries: 2,
2108
- indentStyle: "standard",
2109
- keywordCase: "upper",
2110
- useTabs: true,
2111
- tabWidth: 4
2112
- });
2113
- const [seconds, nanoseconds] = process.hrtime(startTime);
2114
- const durationMs = seconds * 1e3 + nanoseconds / 1e6;
2115
- let initiator = "Anonymous";
2116
- if ("userId" in queryMetadata && queryMetadata.userId)
2117
- initiator = `User Id (${queryMetadata.userId.toString()})`;
2118
- if ("isSystemUser" in queryMetadata && queryMetadata.isSystemUser) initiator = "SYSTEM";
2119
- logger.silly(`${prefix}query by ${initiator}, Query ->
2120
- ${formattedSql}`, {
2121
- durationMs
2122
- });
2123
- }
2124
- };
2125
-
2126
- // src/restura/sql/PsqlPool.ts
2127
- var { Pool } = pg;
2128
- var PsqlPool = class extends PsqlConnection {
2129
- constructor(poolConfig) {
2130
- super();
2131
- this.poolConfig = poolConfig;
2132
- this.pool = new Pool(poolConfig);
2133
- this.queryOne("SELECT NOW();", [], {
2134
- isSystemUser: true,
2135
- role: "",
2136
- host: "localhost",
2137
- ipAddress: "",
2138
- scopes: []
2139
- }).then(() => {
2140
- logger.info("Connected to PostgreSQL database");
2141
- }).catch((error) => {
2142
- logger.error("Error connecting to database", error);
2143
- process.exit(1);
2144
- });
2145
- }
2146
- pool;
2147
- async query(query, values) {
2148
- return this.pool.query(query, values);
2149
- }
2150
- };
2151
-
2152
1908
  // src/restura/sql/SqlEngine.ts
2153
1909
  import { ObjectUtils as ObjectUtils2 } from "@redskytech/core-utils";
2154
1910
  var SqlEngine = class {
@@ -2530,7 +2286,15 @@ NullOperator
2530
2286
  / "null"i { return function(col) { return formatColumn(col) + ' IS NULL'; }; }
2531
2287
 
2532
2288
  OperatorWithValue
2533
- = "in"i _ "," _ val:CastedValueWithPipes { return function(col) { return buildInClauseWithCast(formatColumn(col), val.value, val.cast); }; }
2289
+ = "in"i _ "," _ vals:InValueList cast:TypeCast? { return function(col) {
2290
+ var formattedCol = formatColumn(col);
2291
+ var literals = vals.map(function(v) {
2292
+ var unescaped = unescapeValue(v);
2293
+ var formatted = formatValue(unescaped);
2294
+ return cast ? formatted + '::' + cast : formatted;
2295
+ });
2296
+ return formattedCol + ' IN (' + literals.join(', ') + ')';
2297
+ }; }
2534
2298
  / "ne"i _ "," _ val:CastedValue { return function(col) { return formatColumn(col) + ' <> ' + formatValueWithCast(val.value, val.cast); }; }
2535
2299
  / "gte"i _ "," _ val:CastedValue { return function(col) { return formatColumn(col) + ' >= ' + formatValueWithCast(val.value, val.cast); }; }
2536
2300
  / "gt"i _ "," _ val:CastedValue { return function(col) { return formatColumn(col) + ' > ' + formatValueWithCast(val.value, val.cast); }; }
@@ -2540,6 +2304,26 @@ OperatorWithValue
2540
2304
  / "sw"i _ "," _ val:CastedValue { return function(col) { var formatted = format.literal(unescapeValue(val.value) + '%'); return formatColumn(col) + ' ILIKE ' + (val.cast ? formatted + '::' + val.cast : formatted); }; }
2541
2305
  / "ew"i _ "," _ val:CastedValue { return function(col) { var formatted = format.literal('%' + unescapeValue(val.value)); return formatColumn(col) + ' ILIKE ' + (val.cast ? formatted + '::' + val.cast : formatted); }; }
2542
2306
 
2307
+ InValueList
2308
+ = first:InValue rest:("|" InValue)* {
2309
+ var values = [first];
2310
+ for (var i = 0; i < rest.length; i++) {
2311
+ values.push(rest[i][1]);
2312
+ }
2313
+ return values;
2314
+ }
2315
+
2316
+ InValue
2317
+ = QuotedString
2318
+ / chars:InValueChar+ { return chars.join(''); }
2319
+
2320
+ InValueChar
2321
+ = "\\\\\\\\" { return '\\\\\\\\'; }
2322
+ / "\\\\," { return '\\\\,'; }
2323
+ / "\\\\|" { return '\\\\|'; }
2324
+ / [a-zA-Z0-9_\\-!#/@$%^&*+=<>?~.;'" ]
2325
+ / c:":" !":" { return c; }
2326
+
2543
2327
  CastedValue
2544
2328
  = val:Value cast:TypeCast? { return { value: val, cast: cast }; }
2545
2329
 
@@ -2550,24 +2334,40 @@ TypeCast
2550
2334
  = "::" type:("timestamptz"i / "timestamp"i / "boolean"i / "numeric"i / "bigint"i / "text"i / "date"i / "int"i)
2551
2335
  { return type.toLowerCase(); }
2552
2336
 
2337
+ QuotedString
2338
+ = '"' chars:DoubleQuotedChar* '"' { return chars.join(''); }
2339
+ / "'" chars:SingleQuotedChar* "'" { return chars.join(''); }
2340
+
2341
+ DoubleQuotedChar
2342
+ = '\\\\"' { return '"'; }
2343
+ / '\\\\\\\\' { return '\\\\'; }
2344
+ / [^"\\\\]
2345
+
2346
+ SingleQuotedChar
2347
+ = "\\\\'" { return "'"; }
2348
+ / '\\\\\\\\' { return '\\\\'; }
2349
+ / [^'\\\\]
2350
+
2553
2351
  Value
2554
- = chars:ValueChar+ { return chars.join(''); }
2352
+ = QuotedString
2353
+ / chars:ValueChar+ { return chars.join(''); }
2555
2354
 
2556
2355
  ValueChar
2557
2356
  = "\\\\\\\\" { return '\\\\\\\\'; }
2558
2357
  / "\\\\," { return '\\\\,'; }
2559
2358
  / "\\\\|" { return '\\\\|'; }
2560
- / [^,()\\\\|:]
2359
+ / [a-zA-Z0-9_\\-!#/@$%^&*+=<>?~.;'" ]
2561
2360
  / c:":" !":" { return c; }
2562
2361
 
2563
2362
  ValueWithPipes
2564
- = chars:ValueWithPipesChar+ { return chars.join(''); }
2363
+ = QuotedString
2364
+ / chars:ValueWithPipesChar+ { return chars.join(''); }
2565
2365
 
2566
2366
  ValueWithPipesChar
2567
2367
  = "\\\\\\\\" { return '\\\\\\\\'; }
2568
2368
  / "\\\\," { return '\\\\,'; }
2569
2369
  / "\\\\|" { return '\\\\|'; }
2570
- / [^,()\\\\:]
2370
+ / [a-zA-Z0-9_\\-!#/@$%^&*+=<>?~.;'" |]
2571
2371
  / c:":" !":" { return c; }
2572
2372
  `;
2573
2373
  var fullGrammar = entryGrammar + oldGrammar + newGrammar;
@@ -2577,278 +2377,654 @@ var filterPsqlParser = peg.generate(fullGrammar, {
2577
2377
  });
2578
2378
  var filterPsqlParser_default = filterPsqlParser;
2579
2379
 
2580
- // src/restura/sql/PsqlEngine.ts
2581
- var { Client, types } = pg2;
2582
- var systemUser = {
2583
- role: "",
2584
- scopes: [],
2585
- host: "",
2586
- ipAddress: "",
2587
- isSystemUser: true
2588
- };
2589
- var PsqlEngine = class extends SqlEngine {
2590
- // 5 seconds
2591
- constructor(psqlConnectionPool, shouldListenForDbTriggers = false, scratchDatabaseSuffix = "") {
2592
- super();
2593
- this.psqlConnectionPool = psqlConnectionPool;
2594
- this.setupPgReturnTypes();
2595
- if (shouldListenForDbTriggers) {
2596
- this.setupTriggerListeners = this.listenForDbTriggers();
2597
- }
2598
- this.scratchDbName = `${psqlConnectionPool.poolConfig.database}_scratch${scratchDatabaseSuffix ? `_${scratchDatabaseSuffix}` : ""}`;
2380
+ // src/restura/sql/psqlSchemaUtils.ts
2381
+ import getDiff from "@wmfs/pg-diff-sync";
2382
+ import pgInfo from "@wmfs/pg-info";
2383
+ import pg2 from "pg";
2384
+
2385
+ // src/restura/sql/PsqlPool.ts
2386
+ import pg from "pg";
2387
+
2388
+ // src/restura/sql/PsqlConnection.ts
2389
+ import crypto from "crypto";
2390
+ import { format as sqlFormat } from "sql-formatter";
2391
+ import { z as z4 } from "zod";
2392
+ var PsqlConnection = class {
2393
+ instanceId;
2394
+ constructor(instanceId) {
2395
+ this.instanceId = instanceId || crypto.randomUUID();
2599
2396
  }
2600
- setupTriggerListeners;
2601
- triggerClient;
2602
- scratchDbName = "";
2603
- reconnectAttempts = 0;
2604
- MAX_RECONNECT_ATTEMPTS = 5;
2605
- INITIAL_RECONNECT_DELAY = 5e3;
2606
- async close() {
2607
- if (this.triggerClient) {
2608
- await this.triggerClient.end();
2397
+ async queryOne(query, options, requesterDetails) {
2398
+ const formattedQuery = questionMarksToOrderedParams(query);
2399
+ const meta = { connectionInstanceId: this.instanceId, ...requesterDetails };
2400
+ const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
2401
+ `;
2402
+ const startTime = process.hrtime();
2403
+ try {
2404
+ const response = await this.query(queryMetadata + formattedQuery, options);
2405
+ if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
2406
+ else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
2407
+ this.logSqlStatement(formattedQuery, options, meta, startTime);
2408
+ return response.rows[0];
2409
+ } catch (error) {
2410
+ this.logSqlStatement(formattedQuery, options, meta, startTime);
2411
+ if (RsError.isRsError(error)) throw error;
2412
+ if (error?.routine === "_bt_check_unique") {
2413
+ throw new RsError("DUPLICATE", error.message);
2414
+ }
2415
+ throw new RsError("DATABASE_ERROR", `${error.message}`);
2609
2416
  }
2610
2417
  }
2611
- /**
2612
- * Setup the return types for the PostgreSQL connection.
2613
- * For example return DATE as a string instead of a Date object and BIGINT as a number instead of a string.
2614
- */
2615
- setupPgReturnTypes() {
2616
- const PG_TYPE_OID = {
2617
- BIGINT: 20,
2618
- DATE: 1082,
2619
- TIME: 1083,
2620
- TIMESTAMP: 1114,
2621
- TIMESTAMPTZ: 1184,
2622
- TIMETZ: 1266
2623
- };
2624
- types.setTypeParser(PG_TYPE_OID.BIGINT, (val) => val === null ? null : Number(val));
2625
- types.setTypeParser(PG_TYPE_OID.DATE, (val) => val);
2626
- types.setTypeParser(PG_TYPE_OID.TIME, (val) => val);
2627
- types.setTypeParser(PG_TYPE_OID.TIMETZ, (val) => val);
2628
- types.setTypeParser(PG_TYPE_OID.TIMESTAMP, (val) => val === null ? null : new Date(val).toISOString());
2629
- types.setTypeParser(PG_TYPE_OID.TIMESTAMPTZ, (val) => val === null ? null : new Date(val).toISOString());
2630
- }
2631
- async reconnectTriggerClient() {
2632
- if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
2633
- logger.error("Max reconnection attempts reached for trigger client. Stopping reconnection attempts.");
2634
- return;
2635
- }
2636
- if (this.triggerClient) {
2637
- try {
2638
- await this.triggerClient.end();
2639
- } catch (error) {
2640
- logger.error(`Error closing trigger client: ${error}`);
2418
+ async queryOneSchema(query, params, requesterDetails, zodSchema) {
2419
+ const result = await this.queryOne(query, params, requesterDetails);
2420
+ try {
2421
+ return zodSchema.parse(result);
2422
+ } catch (error) {
2423
+ if (error instanceof z4.ZodError) {
2424
+ logger.error("Invalid data returned from database:");
2425
+ logger.trace("\n" + JSON.stringify(result, null, 2));
2426
+ logger.error("\n" + z4.prettifyError(error));
2427
+ } else {
2428
+ logger.error(error);
2641
2429
  }
2430
+ throw new RsError("DATABASE_ERROR", `Invalid data returned from database`);
2642
2431
  }
2643
- const delay = this.INITIAL_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts);
2644
- logger.info(
2645
- `Attempting to reconnect trigger client in ${delay / 1e3} seconds... (Attempt ${this.reconnectAttempts + 1}/${this.MAX_RECONNECT_ATTEMPTS})`
2646
- );
2647
- await new Promise((resolve2) => setTimeout(resolve2, delay));
2648
- this.reconnectAttempts++;
2432
+ }
2433
+ async runQuery(query, options, requesterDetails) {
2434
+ const formattedQuery = questionMarksToOrderedParams(query);
2435
+ const meta = { connectionInstanceId: this.instanceId, ...requesterDetails };
2436
+ const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
2437
+ `;
2438
+ const startTime = process.hrtime();
2649
2439
  try {
2650
- await this.listenForDbTriggers();
2651
- this.reconnectAttempts = 0;
2440
+ const response = await this.query(queryMetadata + formattedQuery, options);
2441
+ this.logSqlStatement(formattedQuery, options, meta, startTime);
2442
+ return response.rows;
2652
2443
  } catch (error) {
2653
- logger.error(`Reconnection attempt ${this.reconnectAttempts} failed: ${error}`);
2654
- if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
2655
- await this.reconnectTriggerClient();
2444
+ this.logSqlStatement(formattedQuery, options, meta, startTime);
2445
+ if (error?.routine === "_bt_check_unique") {
2446
+ throw new RsError("DUPLICATE", error.message);
2656
2447
  }
2448
+ throw new RsError("DATABASE_ERROR", `${error.message}`);
2657
2449
  }
2658
2450
  }
2659
- async listenForDbTriggers() {
2660
- this.triggerClient = new Client({
2661
- user: this.psqlConnectionPool.poolConfig.user,
2662
- host: this.psqlConnectionPool.poolConfig.host,
2663
- database: this.psqlConnectionPool.poolConfig.database,
2664
- password: this.psqlConnectionPool.poolConfig.password,
2665
- port: this.psqlConnectionPool.poolConfig.port,
2666
- connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
2667
- });
2451
+ async runQuerySchema(query, params, requesterDetails, zodSchema) {
2452
+ const result = await this.runQuery(query, params, requesterDetails);
2668
2453
  try {
2669
- await this.triggerClient.connect();
2670
- const promises = [];
2671
- promises.push(this.triggerClient.query("LISTEN insert"));
2672
- promises.push(this.triggerClient.query("LISTEN update"));
2673
- promises.push(this.triggerClient.query("LISTEN delete"));
2674
- await Promise.all(promises);
2675
- this.triggerClient.on("error", async (error) => {
2676
- logger.error(`Trigger client error: ${error}`);
2677
- await this.reconnectTriggerClient();
2678
- });
2679
- this.triggerClient.on("notification", async (msg) => {
2680
- if (msg.channel === "insert" || msg.channel === "update" || msg.channel === "delete") {
2681
- const payload = ObjectUtils3.safeParse(msg.payload);
2682
- await this.handleTrigger(payload, msg.channel.toUpperCase());
2683
- }
2684
- });
2685
- logger.info("Successfully connected to database triggers");
2454
+ return z4.array(zodSchema).parse(result);
2686
2455
  } catch (error) {
2687
- logger.error(`Failed to setup trigger listeners: ${error}`);
2688
- await this.reconnectTriggerClient();
2456
+ if (error instanceof z4.ZodError) {
2457
+ logger.error("Invalid data returned from database:");
2458
+ logger.trace("\n" + JSON.stringify(result, null, 2));
2459
+ logger.error("\n" + z4.prettifyError(error));
2460
+ } else {
2461
+ logger.error(error);
2462
+ }
2463
+ throw new RsError("DATABASE_ERROR", `Invalid data returned from database`);
2689
2464
  }
2690
2465
  }
2691
- async handleTrigger(payload, mutationType) {
2692
- if (payload.queryMetadata && payload.queryMetadata.connectionInstanceId === this.psqlConnectionPool.instanceId) {
2693
- await eventManager_default.fireActionFromDbTrigger({ queryMetadata: payload.queryMetadata, mutationType }, payload);
2466
+ logSqlStatement(query, options, queryMetadata, startTime, prefix = "") {
2467
+ if (logger.level !== "trace") return;
2468
+ const sqlStatement = query.replace(/\$(\d+)/g, (_, num) => {
2469
+ const paramIndex = parseInt(num) - 1;
2470
+ if (paramIndex >= options.length) return "INVALID_PARAM_INDEX";
2471
+ return toSqlLiteral(options[paramIndex]);
2472
+ });
2473
+ const formattedSql = sqlFormat(sqlStatement, {
2474
+ language: "postgresql",
2475
+ linesBetweenQueries: 2,
2476
+ indentStyle: "standard",
2477
+ keywordCase: "upper",
2478
+ useTabs: true,
2479
+ tabWidth: 4
2480
+ });
2481
+ const [seconds, nanoseconds] = process.hrtime(startTime);
2482
+ const durationMs = seconds * 1e3 + nanoseconds / 1e6;
2483
+ let initiator = "Anonymous";
2484
+ if ("userId" in queryMetadata && queryMetadata.userId)
2485
+ initiator = `User Id (${queryMetadata.userId.toString()})`;
2486
+ if ("isSystemUser" in queryMetadata && queryMetadata.isSystemUser) initiator = "SYSTEM";
2487
+ logger.trace(`${prefix}query by ${initiator}, Query ->
2488
+ ${formattedSql}`, {
2489
+ durationMs
2490
+ });
2491
+ }
2492
+ };
2493
+
2494
+ // src/restura/sql/PsqlPool.ts
2495
+ var { Pool } = pg;
2496
+ var PsqlPool = class extends PsqlConnection {
2497
+ constructor(poolConfig) {
2498
+ super();
2499
+ this.poolConfig = poolConfig;
2500
+ if (poolConfig.connectionString) {
2501
+ let url;
2502
+ try {
2503
+ url = new URL(poolConfig.connectionString);
2504
+ } catch {
2505
+ throw new Error(`Invalid connectionString: ${poolConfig.connectionString}`);
2506
+ }
2507
+ poolConfig.host = url.hostname;
2508
+ poolConfig.port = url.port ? parseInt(url.port) : 5432;
2509
+ poolConfig.user = url.username;
2510
+ poolConfig.password = url.password;
2511
+ poolConfig.database = url.pathname.replace(/^\//, "");
2694
2512
  }
2513
+ this.pool = new Pool(poolConfig);
2514
+ this.queryOne("SELECT NOW();", [], {
2515
+ isSystemUser: true,
2516
+ role: "",
2517
+ host: "localhost",
2518
+ ipAddress: "",
2519
+ scopes: []
2520
+ }).then(() => {
2521
+ logger.info("Connected to PostgreSQL database");
2522
+ }).catch((error) => {
2523
+ logger.error("Error connecting to database", error);
2524
+ process.exit(1);
2525
+ });
2695
2526
  }
2696
- async createDatabaseFromSchema(schema, connection) {
2697
- const sqlFullStatement = this.generateDatabaseSchemaFromSchema(schema);
2698
- await connection.runQuery(sqlFullStatement, [], systemUser);
2699
- return sqlFullStatement;
2527
+ pool;
2528
+ async query(query, values) {
2529
+ return this.pool.query(query, values);
2700
2530
  }
2701
- generateDatabaseSchemaFromSchema(schema) {
2702
- const sqlStatements = [];
2703
- const indexes = [];
2704
- const triggers = [];
2705
- for (const table of schema.database) {
2706
- if (table.notify) {
2707
- triggers.push(this.createInsertTriggers(table.name, table.notify));
2708
- triggers.push(this.createUpdateTrigger(table.name, table.notify));
2709
- triggers.push(this.createDeleteTrigger(table.name, table.notify));
2710
- }
2711
- let sql = `CREATE TABLE "${table.name}"
2712
- ( `;
2713
- const tableColumns = [];
2714
- for (const column of table.columns) {
2715
- let columnSql = "";
2716
- columnSql += ` "${column.name}" ${this.schemaToPsqlType(column)}`;
2717
- let value = column.value;
2718
- if (column.type === "JSON") value = "";
2719
- if (column.type === "JSONB") value = "";
2720
- if (column.type === "DECIMAL" && value) {
2721
- value = value.replace("-", ",").replace(/['"]/g, "");
2722
- }
2723
- if (value && column.type !== "ENUM") {
2724
- columnSql += `(${value})`;
2725
- } else if (column.length) columnSql += `(${column.length})`;
2726
- if (column.isPrimary) {
2727
- columnSql += " PRIMARY KEY ";
2728
- }
2729
- if (column.isUnique) {
2730
- columnSql += ` CONSTRAINT "${table.name}_${column.name}_unique_index" UNIQUE `;
2731
- }
2732
- if (column.isNullable) columnSql += " NULL";
2733
- else columnSql += " NOT NULL";
2734
- if (column.default) columnSql += ` DEFAULT ${column.default}`;
2735
- if (value && column.type === "ENUM") {
2736
- columnSql += ` CHECK ("${column.name}" IN (${value}))`;
2737
- }
2738
- tableColumns.push(columnSql);
2739
- }
2740
- sql += tableColumns.join(", \n");
2741
- for (const index of table.indexes) {
2742
- if (!index.isPrimaryKey) {
2743
- let unique = " ";
2744
- if (index.isUnique) unique = "UNIQUE ";
2745
- let indexSQL = ` CREATE ${unique}INDEX "${index.name}" ON "${table.name}"`;
2746
- indexSQL += ` (${index.columns.map((item) => `"${item}" ${index.order}`).join(", ")})`;
2747
- indexSQL += index.where ? ` WHERE ${index.where}` : "";
2748
- indexSQL += ";";
2749
- indexes.push(indexSQL);
2750
- }
2531
+ };
2532
+
2533
+ // src/restura/sql/psqlSchemaUtils.ts
2534
+ var { Client } = pg2;
2535
+ var systemUser = {
2536
+ role: "",
2537
+ scopes: [],
2538
+ host: "",
2539
+ ipAddress: "",
2540
+ isSystemUser: true
2541
+ };
2542
+ function schemaToPsqlType(column) {
2543
+ if (column.hasAutoIncrement) return "BIGSERIAL";
2544
+ if (column.type === "ENUM") return "TEXT";
2545
+ if (column.type === "DATETIME") return "TIMESTAMPTZ";
2546
+ if (column.type === "MEDIUMINT") return "INT";
2547
+ return column.type;
2548
+ }
2549
+ function createInsertTriggerSql(tableName, notify) {
2550
+ if (!notify) return "";
2551
+ if (notify === "ALL") {
2552
+ return `
2553
+ CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
2554
+ RETURNS TRIGGER AS $$
2555
+ DECLARE
2556
+ query_metadata JSON;
2557
+ BEGIN
2558
+ SELECT INTO query_metadata
2559
+ (regexp_match(
2560
+ current_query(),
2561
+ '^--QUERY_METADATA\\(({.*})', 'n'
2562
+ ))[1]::json;
2563
+
2564
+ PERFORM pg_notify(
2565
+ 'insert',
2566
+ json_build_object(
2567
+ 'table', '${tableName}',
2568
+ 'queryMetadata', query_metadata,
2569
+ 'insertedId', NEW.id,
2570
+ 'record', NEW
2571
+ )::text
2572
+ );
2573
+
2574
+ RETURN NEW;
2575
+ END;
2576
+ $$ LANGUAGE plpgsql;
2577
+
2578
+ CREATE OR REPLACE TRIGGER "${tableName}_insert"
2579
+ AFTER INSERT ON "${tableName}"
2580
+ FOR EACH ROW
2581
+ EXECUTE FUNCTION notify_${tableName}_insert();
2582
+ `;
2583
+ }
2584
+ const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
2585
+ return `
2586
+ CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
2587
+ RETURNS TRIGGER AS $$
2588
+ DECLARE
2589
+ query_metadata JSON;
2590
+ BEGIN
2591
+ SELECT INTO query_metadata
2592
+ (regexp_match(
2593
+ current_query(),
2594
+ '^--QUERY_METADATA\\(({.*})', 'n'
2595
+ ))[1]::json;
2596
+
2597
+ PERFORM pg_notify(
2598
+ 'insert',
2599
+ json_build_object(
2600
+ 'table', '${tableName}',
2601
+ 'queryMetadata', query_metadata,
2602
+ 'insertedId', NEW.id,
2603
+ 'record', json_build_object(
2604
+ ${notifyColumnNewBuildString}
2605
+ )
2606
+ )::text
2607
+ );
2608
+
2609
+ RETURN NEW;
2610
+ END;
2611
+ $$ LANGUAGE plpgsql;
2612
+
2613
+ CREATE OR REPLACE TRIGGER "${tableName}_insert"
2614
+ AFTER INSERT ON "${tableName}"
2615
+ FOR EACH ROW
2616
+ EXECUTE FUNCTION notify_${tableName}_insert();
2617
+ `;
2618
+ }
2619
+ function createUpdateTriggerSql(tableName, notify) {
2620
+ if (!notify) return "";
2621
+ if (notify === "ALL") {
2622
+ return `
2623
+ CREATE OR REPLACE FUNCTION notify_${tableName}_update()
2624
+ RETURNS TRIGGER AS $$
2625
+ DECLARE
2626
+ query_metadata JSON;
2627
+ BEGIN
2628
+ SELECT INTO query_metadata
2629
+ (regexp_match(
2630
+ current_query(),
2631
+ '^--QUERY_METADATA\\(({.*})', 'n'
2632
+ ))[1]::json;
2633
+
2634
+ PERFORM pg_notify(
2635
+ 'update',
2636
+ json_build_object(
2637
+ 'table', '${tableName}',
2638
+ 'queryMetadata', query_metadata,
2639
+ 'changedId', NEW.id,
2640
+ 'record', NEW,
2641
+ 'previousRecord', OLD
2642
+ )::text
2643
+ );
2644
+ RETURN NEW;
2645
+ END;
2646
+ $$ LANGUAGE plpgsql;
2647
+
2648
+ CREATE OR REPLACE TRIGGER ${tableName}_update
2649
+ AFTER UPDATE ON "${tableName}"
2650
+ FOR EACH ROW
2651
+ EXECUTE FUNCTION notify_${tableName}_update();
2652
+ `;
2653
+ }
2654
+ const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
2655
+ const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
2656
+ return `
2657
+ CREATE OR REPLACE FUNCTION notify_${tableName}_update()
2658
+ RETURNS TRIGGER AS $$
2659
+ DECLARE
2660
+ query_metadata JSON;
2661
+ BEGIN
2662
+ SELECT INTO query_metadata
2663
+ (regexp_match(
2664
+ current_query(),
2665
+ '^--QUERY_METADATA\\(({.*})', 'n'
2666
+ ))[1]::json;
2667
+
2668
+ PERFORM pg_notify(
2669
+ 'update',
2670
+ json_build_object(
2671
+ 'table', '${tableName}',
2672
+ 'queryMetadata', query_metadata,
2673
+ 'changedId', NEW.id,
2674
+ 'record', json_build_object(
2675
+ ${notifyColumnNewBuildString}
2676
+ ),
2677
+ 'previousRecord', json_build_object(
2678
+ ${notifyColumnOldBuildString}
2679
+ )
2680
+ )::text
2681
+ );
2682
+ RETURN NEW;
2683
+ END;
2684
+ $$ LANGUAGE plpgsql;
2685
+
2686
+ CREATE OR REPLACE TRIGGER ${tableName}_update
2687
+ AFTER UPDATE ON "${tableName}"
2688
+ FOR EACH ROW
2689
+ EXECUTE FUNCTION notify_${tableName}_update();
2690
+ `;
2691
+ }
2692
+ function createDeleteTriggerSql(tableName, notify) {
2693
+ if (!notify) return "";
2694
+ if (notify === "ALL") {
2695
+ return `
2696
+ CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
2697
+ RETURNS TRIGGER AS $$
2698
+ DECLARE
2699
+ query_metadata JSON;
2700
+ BEGIN
2701
+ SELECT INTO query_metadata
2702
+ (regexp_match(
2703
+ current_query(),
2704
+ '^--QUERY_METADATA\\(({.*})', 'n'
2705
+ ))[1]::json;
2706
+
2707
+ PERFORM pg_notify(
2708
+ 'delete',
2709
+ json_build_object(
2710
+ 'table', '${tableName}',
2711
+ 'queryMetadata', query_metadata,
2712
+ 'deletedId', OLD.id,
2713
+ 'previousRecord', OLD
2714
+ )::text
2715
+ );
2716
+ RETURN OLD;
2717
+ END;
2718
+ $$ LANGUAGE plpgsql;
2719
+
2720
+ CREATE OR REPLACE TRIGGER "${tableName}_delete"
2721
+ AFTER DELETE ON "${tableName}"
2722
+ FOR EACH ROW
2723
+ EXECUTE FUNCTION notify_${tableName}_delete();
2724
+ `;
2725
+ }
2726
+ const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
2727
+ return `
2728
+ CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
2729
+ RETURNS TRIGGER AS $$
2730
+ DECLARE
2731
+ query_metadata JSON;
2732
+ BEGIN
2733
+ SELECT INTO query_metadata
2734
+ (regexp_match(
2735
+ current_query(),
2736
+ '^--QUERY_METADATA\\(({.*})', 'n'
2737
+ ))[1]::json;
2738
+
2739
+ PERFORM pg_notify(
2740
+ 'delete',
2741
+ json_build_object(
2742
+ 'table', '${tableName}',
2743
+ 'queryMetadata', query_metadata,
2744
+ 'deletedId', OLD.id,
2745
+ 'previousRecord', json_build_object(
2746
+ ${notifyColumnOldBuildString}
2747
+ )
2748
+ )::text
2749
+ );
2750
+ RETURN OLD;
2751
+ END;
2752
+ $$ LANGUAGE plpgsql;
2753
+
2754
+ CREATE OR REPLACE TRIGGER "${tableName}_delete"
2755
+ AFTER DELETE ON "${tableName}"
2756
+ FOR EACH ROW
2757
+ EXECUTE FUNCTION notify_${tableName}_delete();
2758
+ `;
2759
+ }
2760
+ function generateDatabaseSchemaFromSchema(schema) {
2761
+ const sqlStatements = [];
2762
+ const indexes = [];
2763
+ const triggers = [];
2764
+ for (const table of schema.database) {
2765
+ if (table.notify) {
2766
+ triggers.push(createInsertTriggerSql(table.name, table.notify));
2767
+ triggers.push(createUpdateTriggerSql(table.name, table.notify));
2768
+ triggers.push(createDeleteTriggerSql(table.name, table.notify));
2769
+ }
2770
+ let sql = `CREATE TABLE "${table.name}"
2771
+ ( `;
2772
+ const tableColumns = [];
2773
+ for (const column of table.columns) {
2774
+ let columnSql = "";
2775
+ columnSql += ` "${column.name}" ${schemaToPsqlType(column)}`;
2776
+ let value = column.value;
2777
+ if (column.type === "JSON") value = "";
2778
+ if (column.type === "JSONB") value = "";
2779
+ if (column.type === "DECIMAL" && value) {
2780
+ value = value.replace("-", ",").replace(/['"]/g, "");
2781
+ }
2782
+ if (value && column.type !== "ENUM") {
2783
+ columnSql += `(${value})`;
2784
+ } else if (column.length) columnSql += `(${column.length})`;
2785
+ if (column.isPrimary) {
2786
+ columnSql += " PRIMARY KEY ";
2787
+ }
2788
+ if (column.isUnique) {
2789
+ columnSql += ` CONSTRAINT "${table.name}_${column.name}_unique_index" UNIQUE `;
2790
+ }
2791
+ if (column.isNullable) columnSql += " NULL";
2792
+ else columnSql += " NOT NULL";
2793
+ if (column.default) columnSql += ` DEFAULT ${column.default}`;
2794
+ if (value && column.type === "ENUM") {
2795
+ columnSql += ` CHECK ("${column.name}" IN (${value}))`;
2796
+ }
2797
+ tableColumns.push(columnSql);
2798
+ }
2799
+ sql += tableColumns.join(", \n");
2800
+ for (const index of table.indexes) {
2801
+ if (!index.isPrimaryKey) {
2802
+ let unique = " ";
2803
+ if (index.isUnique) unique = "UNIQUE ";
2804
+ let indexSQL = ` CREATE ${unique}INDEX "${index.name}" ON "${table.name}"`;
2805
+ indexSQL += ` (${index.columns.map((item) => `"${item}" ${index.order}`).join(", ")})`;
2806
+ indexSQL += index.where ? ` WHERE ${index.where}` : "";
2807
+ indexSQL += ";";
2808
+ indexes.push(indexSQL);
2751
2809
  }
2752
- sql += "\n);";
2753
- sqlStatements.push(sql);
2754
2810
  }
2755
- for (const table of schema.database) {
2756
- if (!table.foreignKeys.length) continue;
2757
- const sql = `ALTER TABLE "${table.name}" `;
2758
- const constraints = [];
2759
- for (const foreignKey of table.foreignKeys) {
2760
- let constraint = ` ADD CONSTRAINT "${foreignKey.name}"
2811
+ sql += "\n);";
2812
+ sqlStatements.push(sql);
2813
+ }
2814
+ for (const table of schema.database) {
2815
+ if (!table.foreignKeys.length) continue;
2816
+ const sql = `ALTER TABLE "${table.name}" `;
2817
+ const constraints = [];
2818
+ for (const foreignKey of table.foreignKeys) {
2819
+ let constraint = ` ADD CONSTRAINT "${foreignKey.name}"
2761
2820
  FOREIGN KEY ("${foreignKey.column}") REFERENCES "${foreignKey.refTable}" ("${foreignKey.refColumn}")`;
2762
- constraint += ` ON DELETE ${foreignKey.onDelete}`;
2763
- constraint += ` ON UPDATE ${foreignKey.onUpdate}`;
2764
- constraints.push(constraint);
2765
- }
2766
- sqlStatements.push(sql + constraints.join(",\n") + ";");
2767
- }
2768
- for (const table of schema.database) {
2769
- if (!table.checkConstraints.length) continue;
2770
- const sql = `ALTER TABLE "${table.name}" `;
2771
- const constraints = [];
2772
- for (const check of table.checkConstraints) {
2773
- const constraint = `ADD CONSTRAINT "${check.name}" CHECK (${check.check})`;
2774
- constraints.push(constraint);
2775
- }
2776
- sqlStatements.push(sql + constraints.join(",\n") + ";");
2777
- }
2778
- sqlStatements.push(indexes.join("\n"));
2779
- sqlStatements.push(triggers.join("\n"));
2780
- return sqlStatements.join("\n\n");
2781
- }
2782
- async getNewPublicSchemaAndScratchPool() {
2783
- const scratchDbExists = await this.psqlConnectionPool.runQuery(
2784
- `SELECT *
2785
- FROM pg_database
2786
- WHERE datname = '${this.scratchDbName}';`,
2787
- [],
2788
- systemUser
2821
+ constraint += ` ON DELETE ${foreignKey.onDelete}`;
2822
+ constraint += ` ON UPDATE ${foreignKey.onUpdate}`;
2823
+ constraints.push(constraint);
2824
+ }
2825
+ sqlStatements.push(sql + constraints.join(",\n") + ";");
2826
+ }
2827
+ for (const table of schema.database) {
2828
+ if (!table.checkConstraints.length) continue;
2829
+ const sql = `ALTER TABLE "${table.name}" `;
2830
+ const constraints = [];
2831
+ for (const check of table.checkConstraints) {
2832
+ const constraint = `ADD CONSTRAINT "${check.name}" CHECK (${check.check})`;
2833
+ constraints.push(constraint);
2834
+ }
2835
+ sqlStatements.push(sql + constraints.join(",\n") + ";");
2836
+ }
2837
+ sqlStatements.push(indexes.join("\n"));
2838
+ sqlStatements.push(triggers.join("\n"));
2839
+ return sqlStatements.join("\n\n");
2840
+ }
2841
+ async function getNewPublicSchemaAndScratchPool(targetPool, scratchDbName) {
2842
+ const scratchDbExists = await targetPool.runQuery(
2843
+ `SELECT * FROM pg_database WHERE datname = ?;`,
2844
+ [scratchDbName],
2845
+ systemUser
2846
+ );
2847
+ if (scratchDbExists.length === 0) {
2848
+ await targetPool.runQuery(`CREATE DATABASE ${escapeColumnName(scratchDbName)};`, [], systemUser);
2849
+ }
2850
+ const scratchPool = new PsqlPool({
2851
+ host: targetPool.poolConfig.host,
2852
+ port: targetPool.poolConfig.port,
2853
+ user: targetPool.poolConfig.user,
2854
+ database: scratchDbName,
2855
+ password: targetPool.poolConfig.password,
2856
+ max: targetPool.poolConfig.max,
2857
+ idleTimeoutMillis: targetPool.poolConfig.idleTimeoutMillis,
2858
+ connectionTimeoutMillis: targetPool.poolConfig.connectionTimeoutMillis
2859
+ });
2860
+ await scratchPool.runQuery(`DROP SCHEMA public CASCADE;`, [], systemUser);
2861
+ await scratchPool.runQuery(`CREATE SCHEMA public AUTHORIZATION ${escapeColumnName(targetPool.poolConfig.user)};`, [], systemUser);
2862
+ const schemaComment = await targetPool.runQuery(
2863
+ `
2864
+ SELECT pg_description.description
2865
+ FROM pg_description
2866
+ JOIN pg_namespace ON pg_namespace.oid = pg_description.objoid
2867
+ WHERE pg_namespace.nspname = 'public';`,
2868
+ [],
2869
+ systemUser
2870
+ );
2871
+ if (schemaComment[0]?.description) {
2872
+ await scratchPool.runQuery(`COMMENT ON SCHEMA public IS $1;`, [schemaComment[0].description], systemUser);
2873
+ }
2874
+ return scratchPool;
2875
+ }
2876
+ async function diffDatabaseToSchema(schema, targetPool, scratchDbName) {
2877
+ let scratchPool;
2878
+ let originalClient;
2879
+ let scratchClient;
2880
+ try {
2881
+ scratchPool = await getNewPublicSchemaAndScratchPool(targetPool, scratchDbName);
2882
+ const sqlFullStatement = generateDatabaseSchemaFromSchema(schema);
2883
+ await scratchPool.runQuery(sqlFullStatement, [], systemUser);
2884
+ const connectionConfig = {
2885
+ host: targetPool.poolConfig.host,
2886
+ port: targetPool.poolConfig.port,
2887
+ user: targetPool.poolConfig.user,
2888
+ password: targetPool.poolConfig.password,
2889
+ ssl: targetPool.poolConfig.ssl
2890
+ };
2891
+ originalClient = new Client({ ...connectionConfig, database: targetPool.poolConfig.database });
2892
+ scratchClient = new Client({ ...connectionConfig, database: scratchDbName });
2893
+ await Promise.all([originalClient.connect(), scratchClient.connect()]);
2894
+ const [info1, info2] = await Promise.all([
2895
+ pgInfo({ client: originalClient }),
2896
+ pgInfo({ client: scratchClient })
2897
+ ]);
2898
+ const diff = getDiff(info1, info2);
2899
+ return diff.join("\n");
2900
+ } finally {
2901
+ const cleanups = [];
2902
+ if (originalClient) cleanups.push(originalClient.end());
2903
+ if (scratchClient) cleanups.push(scratchClient.end());
2904
+ if (scratchPool) cleanups.push(scratchPool.pool.end());
2905
+ await Promise.allSettled(cleanups);
2906
+ }
2907
+ }
2908
+
2909
+ // src/restura/sql/PsqlEngine.ts
2910
+ var { Client: Client2, types } = pg3;
2911
+ var PsqlEngine = class extends SqlEngine {
2912
+ // 5 seconds
2913
+ constructor(psqlConnectionPool, shouldListenForDbTriggers = false, scratchDatabaseSuffix = "") {
2914
+ super();
2915
+ this.psqlConnectionPool = psqlConnectionPool;
2916
+ this.setupPgReturnTypes();
2917
+ if (shouldListenForDbTriggers) {
2918
+ this.setupTriggerListeners = this.listenForDbTriggers();
2919
+ }
2920
+ this.scratchDbName = `${psqlConnectionPool.poolConfig.database}_scratch${scratchDatabaseSuffix ? `_${scratchDatabaseSuffix}` : ""}`;
2921
+ }
2922
+ setupTriggerListeners;
2923
+ triggerClient;
2924
+ scratchDbName = "";
2925
+ reconnectAttempts = 0;
2926
+ MAX_RECONNECT_ATTEMPTS = 5;
2927
+ INITIAL_RECONNECT_DELAY = 5e3;
2928
+ async close() {
2929
+ if (this.triggerClient) {
2930
+ await this.triggerClient.end();
2931
+ }
2932
+ }
2933
+ /**
2934
+ * Setup the return types for the PostgreSQL connection.
2935
+ * For example return DATE as a string instead of a Date object and BIGINT as a number instead of a string.
2936
+ */
2937
+ setupPgReturnTypes() {
2938
+ const PG_TYPE_OID = {
2939
+ BIGINT: 20,
2940
+ DATE: 1082,
2941
+ TIME: 1083,
2942
+ TIMESTAMP: 1114,
2943
+ TIMESTAMPTZ: 1184,
2944
+ TIMETZ: 1266
2945
+ };
2946
+ types.setTypeParser(PG_TYPE_OID.BIGINT, (val) => val === null ? null : Number(val));
2947
+ types.setTypeParser(PG_TYPE_OID.DATE, (val) => val);
2948
+ types.setTypeParser(PG_TYPE_OID.TIME, (val) => val);
2949
+ types.setTypeParser(PG_TYPE_OID.TIMETZ, (val) => val);
2950
+ types.setTypeParser(PG_TYPE_OID.TIMESTAMP, (val) => val === null ? null : new Date(val).toISOString());
2951
+ types.setTypeParser(PG_TYPE_OID.TIMESTAMPTZ, (val) => val === null ? null : new Date(val).toISOString());
2952
+ }
2953
+ async reconnectTriggerClient() {
2954
+ if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
2955
+ logger.error("Max reconnection attempts reached for trigger client. Stopping reconnection attempts.");
2956
+ return;
2957
+ }
2958
+ if (this.triggerClient) {
2959
+ try {
2960
+ await this.triggerClient.end();
2961
+ } catch (error) {
2962
+ logger.error(`Error closing trigger client: ${error}`);
2963
+ }
2964
+ }
2965
+ const delay = this.INITIAL_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts);
2966
+ logger.info(
2967
+ `Attempting to reconnect trigger client in ${delay / 1e3} seconds... (Attempt ${this.reconnectAttempts + 1}/${this.MAX_RECONNECT_ATTEMPTS})`
2789
2968
  );
2790
- if (scratchDbExists.length === 0) {
2791
- await this.psqlConnectionPool.runQuery(`CREATE DATABASE ${this.scratchDbName};`, [], systemUser);
2969
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
2970
+ this.reconnectAttempts++;
2971
+ try {
2972
+ await this.listenForDbTriggers();
2973
+ this.reconnectAttempts = 0;
2974
+ } catch (error) {
2975
+ logger.error(`Reconnection attempt ${this.reconnectAttempts} failed: ${error}`);
2976
+ if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
2977
+ await this.reconnectTriggerClient();
2978
+ }
2792
2979
  }
2793
- const scratchPool = new PsqlPool({
2794
- host: this.psqlConnectionPool.poolConfig.host,
2795
- port: this.psqlConnectionPool.poolConfig.port,
2980
+ }
2981
+ async listenForDbTriggers() {
2982
+ this.triggerClient = new Client2({
2796
2983
  user: this.psqlConnectionPool.poolConfig.user,
2797
- database: this.scratchDbName,
2984
+ host: this.psqlConnectionPool.poolConfig.host,
2985
+ database: this.psqlConnectionPool.poolConfig.database,
2798
2986
  password: this.psqlConnectionPool.poolConfig.password,
2799
- max: this.psqlConnectionPool.poolConfig.max,
2800
- idleTimeoutMillis: this.psqlConnectionPool.poolConfig.idleTimeoutMillis,
2987
+ port: this.psqlConnectionPool.poolConfig.port,
2801
2988
  connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
2802
2989
  });
2803
- await scratchPool.runQuery(`DROP SCHEMA public CASCADE;`, [], systemUser);
2804
- await scratchPool.runQuery(
2805
- `CREATE SCHEMA public AUTHORIZATION ${this.psqlConnectionPool.poolConfig.user};`,
2806
- [],
2807
- systemUser
2808
- );
2809
- const schemaComment = await this.psqlConnectionPool.runQuery(
2810
- `
2811
- SELECT pg_description.description
2812
- FROM pg_description
2813
- JOIN pg_namespace ON pg_namespace.oid = pg_description.objoid
2814
- WHERE pg_namespace.nspname = 'public';`,
2815
- [],
2816
- systemUser
2817
- );
2818
- if (schemaComment[0]?.description) {
2819
- await scratchPool.runQuery(
2820
- `COMMENT ON SCHEMA public IS '${schemaComment[0]?.description}';`,
2821
- [],
2822
- systemUser
2823
- );
2990
+ try {
2991
+ await this.triggerClient.connect();
2992
+ const promises = [];
2993
+ promises.push(this.triggerClient.query("LISTEN insert"));
2994
+ promises.push(this.triggerClient.query("LISTEN update"));
2995
+ promises.push(this.triggerClient.query("LISTEN delete"));
2996
+ await Promise.all(promises);
2997
+ this.triggerClient.on("error", async (error) => {
2998
+ logger.error(`Trigger client error: ${error}`);
2999
+ await this.reconnectTriggerClient();
3000
+ });
3001
+ this.triggerClient.on("notification", async (msg) => {
3002
+ if (msg.channel === "insert" || msg.channel === "update" || msg.channel === "delete") {
3003
+ const payload = ObjectUtils3.safeParse(msg.payload);
3004
+ await this.handleTrigger(payload, msg.channel.toUpperCase());
3005
+ }
3006
+ });
3007
+ logger.info("Successfully connected to database triggers");
3008
+ } catch (error) {
3009
+ logger.error(`Failed to setup trigger listeners: ${error}`);
3010
+ await this.reconnectTriggerClient();
3011
+ }
3012
+ }
3013
+ async handleTrigger(payload, mutationType) {
3014
+ if (payload.queryMetadata && payload.queryMetadata.connectionInstanceId === this.psqlConnectionPool.instanceId) {
3015
+ await eventManager_default.fireActionFromDbTrigger({ queryMetadata: payload.queryMetadata, mutationType }, payload);
2824
3016
  }
2825
- return scratchPool;
3017
+ }
3018
+ async createDatabaseFromSchema(schema, connection) {
3019
+ const sqlFullStatement = this.generateDatabaseSchemaFromSchema(schema);
3020
+ await connection.runQuery(sqlFullStatement, [], systemUser);
3021
+ return sqlFullStatement;
3022
+ }
3023
+ generateDatabaseSchemaFromSchema(schema) {
3024
+ return generateDatabaseSchemaFromSchema(schema);
2826
3025
  }
2827
3026
  async diffDatabaseToSchema(schema) {
2828
- const scratchPool = await this.getNewPublicSchemaAndScratchPool();
2829
- await this.createDatabaseFromSchema(schema, scratchPool);
2830
- const originalClient = new Client({
2831
- database: this.psqlConnectionPool.poolConfig.database,
2832
- user: this.psqlConnectionPool.poolConfig.user,
2833
- password: this.psqlConnectionPool.poolConfig.password,
2834
- host: this.psqlConnectionPool.poolConfig.host,
2835
- port: this.psqlConnectionPool.poolConfig.port
2836
- });
2837
- const scratchClient = new Client({
2838
- database: this.scratchDbName,
2839
- user: this.psqlConnectionPool.poolConfig.user,
2840
- password: this.psqlConnectionPool.poolConfig.password,
2841
- host: this.psqlConnectionPool.poolConfig.host,
2842
- port: this.psqlConnectionPool.poolConfig.port
2843
- });
2844
- const promises = [originalClient.connect(), scratchClient.connect()];
2845
- await Promise.all(promises);
2846
- const infoPromises = [pgInfo({ client: originalClient }), pgInfo({ client: scratchClient })];
2847
- const [info1, info2] = await Promise.all(infoPromises);
2848
- const diff = getDiff(info1, info2);
2849
- const endPromises = [originalClient.end(), scratchClient.end()];
2850
- await Promise.all(endPromises);
2851
- return diff.join("\n");
3027
+ return diffDatabaseToSchema(schema, this.psqlConnectionPool, this.scratchDbName);
2852
3028
  }
2853
3029
  createNestedSelect(req, schema, item, routeData, sqlParams) {
2854
3030
  if (!item.subquery) return "";
@@ -3112,316 +3288,98 @@ DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
3112
3288
  });
3113
3289
  return joinStatements;
3114
3290
  }
3115
- generateGroupBy(routeData) {
3116
- let groupBy = "";
3117
- if (routeData.groupBy) {
3118
- groupBy = `GROUP BY ${escapeColumnName(routeData.groupBy.tableName)}.${escapeColumnName(routeData.groupBy.columnName)}
3119
- `;
3120
- }
3121
- return groupBy;
3122
- }
3123
- generateOrderBy(req, routeData) {
3124
- let orderBy = "";
3125
- const orderOptions = {
3126
- ASC: "ASC",
3127
- DESC: "DESC"
3128
- };
3129
- const data = req.data;
3130
- if (routeData.type === "PAGED" && "sortBy" in data) {
3131
- const sortOrder = orderOptions[data.sortOrder] || "ASC";
3132
- orderBy = `ORDER BY ${escapeColumnName(data.sortBy)} ${sortOrder}
3133
- `;
3134
- } else if (routeData.orderBy) {
3135
- const sortOrder = orderOptions[routeData.orderBy.order] || "ASC";
3136
- orderBy = `ORDER BY ${escapeColumnName(routeData.orderBy.tableName)}.${escapeColumnName(routeData.orderBy.columnName)} ${sortOrder}
3137
- `;
3138
- }
3139
- return orderBy;
3140
- }
3141
- generateWhereClause(req, where, routeData, sqlParams) {
3142
- let whereClause = "";
3143
- where.forEach((item, index) => {
3144
- if (index === 0) whereClause = "WHERE ";
3145
- if (item.custom) {
3146
- const customReplaced = this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
3147
- whereClause += ` ${item.conjunction || ""} ${customReplaced}
3148
- `;
3149
- return;
3150
- }
3151
- if (item.operator === void 0 || item.value === void 0 || item.columnName === void 0 || item.tableName === void 0)
3152
- throw new RsError(
3153
- "SCHEMA_ERROR",
3154
- `Invalid where clause in route ${routeData.name}, missing required fields if not custom`
3155
- );
3156
- let operator = item.operator;
3157
- let value = item.value;
3158
- if (operator === "LIKE") {
3159
- value = `'%' || ${value} || '%'`;
3160
- } else if (operator === "NOT LIKE") {
3161
- value = `'%' || ${value} || '%'`;
3162
- } else if (operator === "STARTS WITH") {
3163
- operator = "LIKE";
3164
- value = `${value} || '%'`;
3165
- } else if (operator === "ENDS WITH") {
3166
- operator = "LIKE";
3167
- value = `'%' || ${value}`;
3168
- }
3169
- const replacedValue = this.replaceParamKeywords(value, routeData, req, sqlParams);
3170
- whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator.replace("LIKE", "ILIKE")} ${["IN", "NOT IN"].includes(operator) ? `(${replacedValue})` : replacedValue}
3171
- `;
3172
- });
3173
- const data = req.data;
3174
- if (routeData.type === "PAGED" && !!data?.filter) {
3175
- let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
3176
- const requestParam = routeData.request.find((item) => {
3177
- return item.name === value.replace("$", "");
3178
- });
3179
- if (!requestParam)
3180
- throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
3181
- return data[requestParam.name]?.toString() || "";
3182
- });
3183
- statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
3184
- const requestParam = routeData.request.find((item) => {
3185
- return item.name === value.replace("#", "");
3186
- });
3187
- if (!requestParam)
3188
- throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
3189
- return data[requestParam.name]?.toString() || "";
3190
- });
3191
- const parseResult = filterPsqlParser_default.parse(statement);
3192
- if (parseResult.usedOldSyntax) {
3193
- logger.warn(
3194
- `Deprecated filter syntax detected in route "${routeData.name}" (${routeData.path}). Please migrate to the new filter syntax.`
3195
- );
3196
- }
3197
- statement = parseResult.sql;
3198
- if (whereClause.startsWith("WHERE")) {
3199
- whereClause += ` AND (${statement})
3200
- `;
3201
- } else {
3202
- whereClause += `WHERE ${statement}
3203
- `;
3204
- }
3205
- }
3206
- return whereClause;
3207
- }
3208
- createUpdateTrigger(tableName, notify) {
3209
- if (!notify) return "";
3210
- if (notify === "ALL") {
3211
- return `
3212
- CREATE OR REPLACE FUNCTION notify_${tableName}_update()
3213
- RETURNS TRIGGER AS $$
3214
- DECLARE
3215
- query_metadata JSON;
3216
- BEGIN
3217
- SELECT INTO query_metadata
3218
- (regexp_match(
3219
- current_query(),
3220
- '^--QUERY_METADATA\\(({.*})', 'n'
3221
- ))[1]::json;
3222
-
3223
- PERFORM pg_notify(
3224
- 'update',
3225
- json_build_object(
3226
- 'table', '${tableName}',
3227
- 'queryMetadata', query_metadata,
3228
- 'changedId', NEW.id,
3229
- 'record', NEW,
3230
- 'previousRecord', OLD
3231
- )::text
3232
- );
3233
- RETURN NEW;
3234
- END;
3235
- $$ LANGUAGE plpgsql;
3236
-
3237
- CREATE OR REPLACE TRIGGER ${tableName}_update
3238
- AFTER UPDATE ON "${tableName}"
3239
- FOR EACH ROW
3240
- EXECUTE FUNCTION notify_${tableName}_update();
3241
- `;
3242
- }
3243
- const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
3244
- const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
3245
- return `
3246
- CREATE OR REPLACE FUNCTION notify_${tableName}_update()
3247
- RETURNS TRIGGER AS $$
3248
- DECLARE
3249
- query_metadata JSON;
3250
- BEGIN
3251
- SELECT INTO query_metadata
3252
- (regexp_match(
3253
- current_query(),
3254
- '^--QUERY_METADATA\\(({.*})', 'n'
3255
- ))[1]::json;
3256
-
3257
- PERFORM pg_notify(
3258
- 'update',
3259
- json_build_object(
3260
- 'table', '${tableName}',
3261
- 'queryMetadata', query_metadata,
3262
- 'changedId', NEW.id,
3263
- 'record', json_build_object(
3264
- ${notifyColumnNewBuildString}
3265
- ),
3266
- 'previousRecord', json_build_object(
3267
- ${notifyColumnOldBuildString}
3268
- )
3269
- )::text
3270
- );
3271
- RETURN NEW;
3272
- END;
3273
- $$ LANGUAGE plpgsql;
3274
-
3275
- CREATE OR REPLACE TRIGGER ${tableName}_update
3276
- AFTER UPDATE ON "${tableName}"
3277
- FOR EACH ROW
3278
- EXECUTE FUNCTION notify_${tableName}_update();
3279
- `;
3280
- }
3281
- createDeleteTrigger(tableName, notify) {
3282
- if (!notify) return "";
3283
- if (notify === "ALL") {
3284
- return `
3285
- CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
3286
- RETURNS TRIGGER AS $$
3287
- DECLARE
3288
- query_metadata JSON;
3289
- BEGIN
3290
- SELECT INTO query_metadata
3291
- (regexp_match(
3292
- current_query(),
3293
- '^--QUERY_METADATA\\(({.*})', 'n'
3294
- ))[1]::json;
3295
-
3296
- PERFORM pg_notify(
3297
- 'delete',
3298
- json_build_object(
3299
- 'table', '${tableName}',
3300
- 'queryMetadata', query_metadata,
3301
- 'deletedId', OLD.id,
3302
- 'previousRecord', OLD
3303
- )::text
3304
- );
3305
- RETURN NEW;
3306
- END;
3307
- $$ LANGUAGE plpgsql;
3308
-
3309
- CREATE OR REPLACE TRIGGER "${tableName}_delete"
3310
- AFTER DELETE ON "${tableName}"
3311
- FOR EACH ROW
3312
- EXECUTE FUNCTION notify_${tableName}_delete();
3313
- `;
3314
- }
3315
- const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
3316
- return `
3317
- CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
3318
- RETURNS TRIGGER AS $$
3319
- DECLARE
3320
- query_metadata JSON;
3321
- BEGIN
3322
- SELECT INTO query_metadata
3323
- (regexp_match(
3324
- current_query(),
3325
- '^--QUERY_METADATA\\(({.*})', 'n'
3326
- ))[1]::json;
3327
-
3328
- PERFORM pg_notify(
3329
- 'delete',
3330
- json_build_object(
3331
- 'table', '${tableName}',
3332
- 'queryMetadata', query_metadata,
3333
- 'deletedId', OLD.id,
3334
- 'previousRecord', json_build_object(
3335
- ${notifyColumnOldBuildString}
3336
- )
3337
- )::text
3338
- );
3339
- RETURN NEW;
3340
- END;
3341
- $$ LANGUAGE plpgsql;
3342
-
3343
- CREATE OR REPLACE TRIGGER "${tableName}_delete"
3344
- AFTER DELETE ON "${tableName}"
3345
- FOR EACH ROW
3346
- EXECUTE FUNCTION notify_${tableName}_delete();
3347
- `;
3348
- }
3349
- createInsertTriggers(tableName, notify) {
3350
- if (!notify) return "";
3351
- if (notify === "ALL") {
3352
- return `
3353
- CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
3354
- RETURNS TRIGGER AS $$
3355
- DECLARE
3356
- query_metadata JSON;
3357
- BEGIN
3358
- SELECT INTO query_metadata
3359
- (regexp_match(
3360
- current_query(),
3361
- '^--QUERY_METADATA\\(({.*})', 'n'
3362
- ))[1]::json;
3363
-
3364
- PERFORM pg_notify(
3365
- 'insert',
3366
- json_build_object(
3367
- 'table', '${tableName}',
3368
- 'queryMetadata', query_metadata,
3369
- 'insertedId', NEW.id,
3370
- 'record', NEW
3371
- )::text
3372
- );
3373
-
3374
- RETURN NEW;
3375
- END;
3376
- $$ LANGUAGE plpgsql;
3377
-
3378
- CREATE OR REPLACE TRIGGER "${tableName}_insert"
3379
- AFTER INSERT ON "${tableName}"
3380
- FOR EACH ROW
3381
- EXECUTE FUNCTION notify_${tableName}_insert();
3291
+ generateGroupBy(routeData) {
3292
+ let groupBy = "";
3293
+ if (routeData.groupBy) {
3294
+ groupBy = `GROUP BY ${escapeColumnName(routeData.groupBy.tableName)}.${escapeColumnName(routeData.groupBy.columnName)}
3382
3295
  `;
3383
3296
  }
3384
- const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
3385
- return `
3386
- CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
3387
- RETURNS TRIGGER AS $$
3388
- DECLARE
3389
- query_metadata JSON;
3390
- BEGIN
3391
- SELECT INTO query_metadata
3392
- (regexp_match(
3393
- current_query(),
3394
- '^--QUERY_METADATA\\(({.*})', 'n'
3395
- ))[1]::json;
3396
-
3397
- PERFORM pg_notify(
3398
- 'insert',
3399
- json_build_object(
3400
- 'table', '${tableName}',
3401
- 'queryMetadata', query_metadata,
3402
- 'insertedId', NEW.id,
3403
- 'record', json_build_object(
3404
- ${notifyColumnNewBuildString}
3405
- )
3406
- )::text
3407
- );
3408
-
3409
- RETURN NEW;
3410
- END;
3411
- $$ LANGUAGE plpgsql;
3412
-
3413
- CREATE OR REPLACE TRIGGER "${tableName}_insert"
3414
- AFTER INSERT ON "${tableName}"
3415
- FOR EACH ROW
3416
- EXECUTE FUNCTION notify_${tableName}_insert();
3297
+ return groupBy;
3298
+ }
3299
+ generateOrderBy(req, routeData) {
3300
+ let orderBy = "";
3301
+ const orderOptions = {
3302
+ ASC: "ASC",
3303
+ DESC: "DESC"
3304
+ };
3305
+ const data = req.data;
3306
+ if (routeData.type === "PAGED" && "sortBy" in data) {
3307
+ const sortOrder = orderOptions[data.sortOrder] || "ASC";
3308
+ orderBy = `ORDER BY ${escapeColumnName(data.sortBy)} ${sortOrder}
3309
+ `;
3310
+ } else if (routeData.orderBy) {
3311
+ const sortOrder = orderOptions[routeData.orderBy.order] || "ASC";
3312
+ orderBy = `ORDER BY ${escapeColumnName(routeData.orderBy.tableName)}.${escapeColumnName(routeData.orderBy.columnName)} ${sortOrder}
3417
3313
  `;
3314
+ }
3315
+ return orderBy;
3418
3316
  }
3419
- schemaToPsqlType(column) {
3420
- if (column.hasAutoIncrement) return "BIGSERIAL";
3421
- if (column.type === "ENUM") return `TEXT`;
3422
- if (column.type === "DATETIME") return "TIMESTAMPTZ";
3423
- if (column.type === "MEDIUMINT") return "INT";
3424
- return column.type;
3317
+ generateWhereClause(req, where, routeData, sqlParams) {
3318
+ let whereClause = "";
3319
+ where.forEach((item, index) => {
3320
+ if (index === 0) whereClause = "WHERE ";
3321
+ if (item.custom) {
3322
+ const customReplaced = this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
3323
+ whereClause += ` ${item.conjunction || ""} ${customReplaced}
3324
+ `;
3325
+ return;
3326
+ }
3327
+ if (item.operator === void 0 || item.value === void 0 || item.columnName === void 0 || item.tableName === void 0)
3328
+ throw new RsError(
3329
+ "SCHEMA_ERROR",
3330
+ `Invalid where clause in route ${routeData.name}, missing required fields if not custom`
3331
+ );
3332
+ let operator = item.operator;
3333
+ let value = item.value;
3334
+ if (operator === "LIKE") {
3335
+ value = `'%' || ${value} || '%'`;
3336
+ } else if (operator === "NOT LIKE") {
3337
+ value = `'%' || ${value} || '%'`;
3338
+ } else if (operator === "STARTS WITH") {
3339
+ operator = "LIKE";
3340
+ value = `${value} || '%'`;
3341
+ } else if (operator === "ENDS WITH") {
3342
+ operator = "LIKE";
3343
+ value = `'%' || ${value}`;
3344
+ }
3345
+ const replacedValue = this.replaceParamKeywords(value, routeData, req, sqlParams);
3346
+ whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator.replace("LIKE", "ILIKE")} ${["IN", "NOT IN"].includes(operator) ? `(${replacedValue})` : replacedValue}
3347
+ `;
3348
+ });
3349
+ const data = req.data;
3350
+ if (routeData.type === "PAGED" && !!data?.filter) {
3351
+ let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
3352
+ const requestParam = routeData.request.find((item) => {
3353
+ return item.name === value.replace("$", "");
3354
+ });
3355
+ if (!requestParam)
3356
+ throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
3357
+ return data[requestParam.name]?.toString() || "";
3358
+ });
3359
+ statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
3360
+ const requestParam = routeData.request.find((item) => {
3361
+ return item.name === value.replace("#", "");
3362
+ });
3363
+ if (!requestParam)
3364
+ throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
3365
+ return data[requestParam.name]?.toString() || "";
3366
+ });
3367
+ const parseResult = filterPsqlParser_default.parse(statement);
3368
+ if (parseResult.usedOldSyntax) {
3369
+ logger.warn(
3370
+ `Deprecated filter syntax detected in route "${routeData.name}" (${routeData.path}). Please migrate to the new filter syntax.`
3371
+ );
3372
+ }
3373
+ statement = parseResult.sql;
3374
+ if (whereClause.startsWith("WHERE")) {
3375
+ whereClause += ` AND (${statement})
3376
+ `;
3377
+ } else {
3378
+ whereClause += `WHERE ${statement}
3379
+ `;
3380
+ }
3381
+ }
3382
+ return whereClause;
3425
3383
  }
3426
3384
  };
3427
3385
 
@@ -3498,8 +3456,9 @@ var ResturaEngine = class {
3498
3456
  * @param app - The Express application instance to initialize with Restura.
3499
3457
  * @returns A promise that resolves when the initialization is complete.
3500
3458
  */
3501
- async init(app, authenticationHandler, psqlConnectionPool) {
3502
- this.resturaConfig = await config2.validate("restura", resturaConfigSchema);
3459
+ async init(app, authenticationHandler, psqlConnectionPool, options) {
3460
+ if (options?.logger) setLogger(options.logger);
3461
+ this.resturaConfig = await config.validate("restura", resturaConfigSchema);
3503
3462
  this.multerCommonUpload = getMulterUpload(this.resturaConfig.fileTempCachePath);
3504
3463
  new TempCache(this.resturaConfig.fileTempCachePath);
3505
3464
  this.psqlConnectionPool = psqlConnectionPool;
@@ -3853,14 +3812,556 @@ __decorateClass([
3853
3812
  ], ResturaEngine.prototype, "runCustomRouteLogic", 1);
3854
3813
  var restura = new ResturaEngine();
3855
3814
 
3815
+ // src/restura/sql/psqlIntrospect.ts
3816
+ var RESTURA_TO_PG_UDT = {
3817
+ BIGSERIAL: "int8",
3818
+ SERIAL: "int4",
3819
+ BIGINT: "int8",
3820
+ INTEGER: "int4",
3821
+ INT: "int4",
3822
+ SMALLINT: "int2",
3823
+ DECIMAL: "numeric",
3824
+ NUMERIC: "numeric",
3825
+ REAL: "float4",
3826
+ "DOUBLE PRECISION": "float8",
3827
+ FLOAT: "float8",
3828
+ DOUBLE: "float8",
3829
+ BOOLEAN: "bool",
3830
+ TEXT: "text",
3831
+ VARCHAR: "varchar",
3832
+ CHAR: "bpchar",
3833
+ BYTEA: "bytea",
3834
+ JSON: "json",
3835
+ JSONB: "jsonb",
3836
+ DATE: "date",
3837
+ TIME: "time",
3838
+ TIMESTAMP: "timestamp",
3839
+ TIMESTAMPTZ: "timestamptz",
3840
+ INTERVAL: "interval",
3841
+ ENUM: "text",
3842
+ DATETIME: "timestamptz",
3843
+ MEDIUMINT: "int4",
3844
+ TINYINT: "int2"
3845
+ };
3846
+ function resturaTypeToUdt(column) {
3847
+ const psqlType = schemaToPsqlType(column);
3848
+ return RESTURA_TO_PG_UDT[psqlType] ?? psqlType.toLowerCase();
3849
+ }
3850
+ var PG_FK_ACTION = {
3851
+ a: "NO ACTION",
3852
+ r: "RESTRICT",
3853
+ c: "CASCADE",
3854
+ n: "SET NULL",
3855
+ d: "SET DEFAULT"
3856
+ };
3857
+ async function introspectDatabase(pool) {
3858
+ const [tableRows, columnRows, indexRows, fkRows, checkRows] = await Promise.all([
3859
+ pool.runQuery(
3860
+ `SELECT table_name
3861
+ FROM information_schema.tables
3862
+ WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
3863
+ ORDER BY table_name`,
3864
+ [],
3865
+ systemUser
3866
+ ),
3867
+ pool.runQuery(
3868
+ `SELECT table_name, column_name, udt_name, is_nullable, column_default,
3869
+ character_maximum_length, numeric_precision, numeric_scale
3870
+ FROM information_schema.columns
3871
+ WHERE table_schema = 'public'
3872
+ ORDER BY table_name, ordinal_position`,
3873
+ [],
3874
+ systemUser
3875
+ ),
3876
+ pool.runQuery(
3877
+ `SELECT pi.tablename, pi.indexname, pi.indexdef, ix.indisprimary
3878
+ FROM pg_indexes pi
3879
+ JOIN pg_class ic ON ic.relname = pi.indexname
3880
+ JOIN pg_index ix ON ix.indexrelid = ic.oid
3881
+ WHERE pi.schemaname = 'public'
3882
+ ORDER BY pi.tablename, pi.indexname`,
3883
+ [],
3884
+ systemUser
3885
+ ),
3886
+ pool.runQuery(
3887
+ `SELECT
3888
+ constraint_def.conname AS constraint_name,
3889
+ source_table.relname AS table_name,
3890
+ source_column.attname AS column_name,
3891
+ referenced_table.relname AS ref_table,
3892
+ referenced_column.attname AS ref_column,
3893
+ constraint_def.confdeltype AS delete_rule,
3894
+ constraint_def.confupdtype AS update_rule
3895
+ FROM pg_constraint constraint_def
3896
+ JOIN pg_class source_table ON source_table.oid = constraint_def.conrelid
3897
+ JOIN pg_namespace schema_ns ON schema_ns.oid = source_table.relnamespace
3898
+ JOIN pg_class referenced_table ON referenced_table.oid = constraint_def.confrelid
3899
+ JOIN pg_attribute source_column ON source_column.attrelid = constraint_def.conrelid AND source_column.attnum = ANY(constraint_def.conkey)
3900
+ JOIN pg_attribute referenced_column ON referenced_column.attrelid = constraint_def.confrelid AND referenced_column.attnum = ANY(constraint_def.confkey)
3901
+ WHERE constraint_def.contype = 'f' AND schema_ns.nspname = 'public'
3902
+ ORDER BY source_table.relname, constraint_def.conname`,
3903
+ [],
3904
+ systemUser
3905
+ ),
3906
+ pool.runQuery(
3907
+ `SELECT
3908
+ constraint_def.conname AS constraint_name,
3909
+ parent_table.relname AS table_name,
3910
+ pg_get_constraintdef(constraint_def.oid) AS check_clause
3911
+ FROM pg_constraint constraint_def
3912
+ JOIN pg_class parent_table ON parent_table.oid = constraint_def.conrelid
3913
+ JOIN pg_namespace schema_ns ON schema_ns.oid = parent_table.relnamespace
3914
+ WHERE constraint_def.contype = 'c' AND schema_ns.nspname = 'public'
3915
+ AND constraint_def.conname NOT LIKE '%_not_null'
3916
+ ORDER BY parent_table.relname, constraint_def.conname`,
3917
+ [],
3918
+ systemUser
3919
+ )
3920
+ ]);
3921
+ const tableMap = /* @__PURE__ */ new Map();
3922
+ for (const row of tableRows) {
3923
+ tableMap.set(row.table_name, {
3924
+ name: row.table_name,
3925
+ columns: [],
3926
+ indexes: [],
3927
+ foreignKeys: [],
3928
+ checkConstraints: []
3929
+ });
3930
+ }
3931
+ for (const row of columnRows) {
3932
+ const table = tableMap.get(row.table_name);
3933
+ if (!table) continue;
3934
+ table.columns.push({
3935
+ name: row.column_name,
3936
+ udtName: row.udt_name,
3937
+ isNullable: row.is_nullable === "YES",
3938
+ columnDefault: row.column_default,
3939
+ characterMaximumLength: row.character_maximum_length,
3940
+ numericPrecision: row.numeric_precision,
3941
+ numericScale: row.numeric_scale
3942
+ });
3943
+ }
3944
+ for (const row of indexRows) {
3945
+ const table = tableMap.get(row.tablename);
3946
+ if (!table) continue;
3947
+ const isPrimary = row.indisprimary;
3948
+ const unique = /CREATE UNIQUE INDEX/i.test(row.indexdef);
3949
+ const order = row.indexdef.toUpperCase().includes(" DESC") ? "DESC" : "ASC";
3950
+ const columnMatch = row.indexdef.match(/\((.+?)\)(?:\s+WHERE\s+(.+))?$/i);
3951
+ const columns = columnMatch ? columnMatch[1].split(",").map(
3952
+ (colExpr) => colExpr.trim().replace(/^"(.*)"$/, "$1").replace(/\s+(ASC|DESC)$/i, "")
3953
+ ) : [];
3954
+ const whereClause = columnMatch?.[2] ?? null;
3955
+ table.indexes.push({
3956
+ name: row.indexname,
3957
+ tableName: row.tablename,
3958
+ isUnique: unique,
3959
+ isPrimary,
3960
+ columns,
3961
+ order,
3962
+ where: whereClause
3963
+ });
3964
+ }
3965
+ for (const row of fkRows) {
3966
+ const table = tableMap.get(row.table_name);
3967
+ if (!table) continue;
3968
+ table.foreignKeys.push({
3969
+ name: row.constraint_name,
3970
+ tableName: row.table_name,
3971
+ column: row.column_name,
3972
+ refTable: row.ref_table,
3973
+ refColumn: row.ref_column,
3974
+ onDelete: PG_FK_ACTION[row.delete_rule] ?? "NO ACTION",
3975
+ onUpdate: PG_FK_ACTION[row.update_rule] ?? "NO ACTION"
3976
+ });
3977
+ }
3978
+ for (const row of checkRows) {
3979
+ const table = tableMap.get(row.table_name);
3980
+ if (!table) continue;
3981
+ table.checkConstraints.push({
3982
+ name: row.constraint_name,
3983
+ tableName: row.table_name,
3984
+ expression: row.check_clause
3985
+ });
3986
+ }
3987
+ return { tables: Array.from(tableMap.values()) };
3988
+ }
3989
+ function diffSchemaToDatabase(schema, snapshot) {
3990
+ const statements = [];
3991
+ const desiredTables = new Map(schema.database.map((table) => [table.name, table]));
3992
+ const liveTableMap = new Map(snapshot.tables.map((table) => [table.name, table]));
3993
+ const tablesToCreate = schema.database.filter((table) => !liveTableMap.has(table.name));
3994
+ const tablesToDrop = snapshot.tables.filter((table) => !desiredTables.has(table.name));
3995
+ const tablesToAlter = schema.database.filter((table) => liveTableMap.has(table.name));
3996
+ const changedChecksPerTable = /* @__PURE__ */ new Map();
3997
+ for (const desired of tablesToAlter) {
3998
+ const live = liveTableMap.get(desired.name);
3999
+ const desiredFkNames = new Set(desired.foreignKeys.map((fk) => fk.name));
4000
+ for (const liveFk of live.foreignKeys) {
4001
+ if (!desiredFkNames.has(liveFk.name) || isFkChanged(desired, liveFk)) {
4002
+ statements.push(`ALTER TABLE "${desired.name}" DROP CONSTRAINT "${liveFk.name}";`);
4003
+ }
4004
+ }
4005
+ const desiredCheckExprMap = /* @__PURE__ */ new Map();
4006
+ for (const check of desired.checkConstraints) {
4007
+ desiredCheckExprMap.set(check.name, check.check);
4008
+ }
4009
+ for (const col of desired.columns) {
4010
+ if (col.type === "ENUM" && col.value) {
4011
+ desiredCheckExprMap.set(`${desired.name}_${col.name}_check`, `"${col.name}" IN (${col.value})`);
4012
+ }
4013
+ }
4014
+ const changedChecks = /* @__PURE__ */ new Set();
4015
+ for (const liveCheck of live.checkConstraints) {
4016
+ if (!desiredCheckExprMap.has(liveCheck.name)) {
4017
+ statements.push(`ALTER TABLE "${desired.name}" DROP CONSTRAINT "${liveCheck.name}";`);
4018
+ } else if (normalizeCheckExpression(desiredCheckExprMap.get(liveCheck.name)) !== normalizeCheckExpression(liveCheck.expression)) {
4019
+ statements.push(`ALTER TABLE "${desired.name}" DROP CONSTRAINT "${liveCheck.name}";`);
4020
+ changedChecks.add(liveCheck.name);
4021
+ }
4022
+ }
4023
+ changedChecksPerTable.set(desired.name, changedChecks);
4024
+ const desiredIdxSignatures = /* @__PURE__ */ new Map();
4025
+ for (const idx of desired.indexes) {
4026
+ if (idx.isPrimaryKey) continue;
4027
+ desiredIdxSignatures.set(idx.name, indexSignature(idx.name, idx.columns, idx.isUnique, idx.order, idx.where));
4028
+ }
4029
+ const autoUniqueNames = /* @__PURE__ */ new Set();
4030
+ for (const col of desired.columns) {
4031
+ if (col.isUnique) {
4032
+ autoUniqueNames.add(`${desired.name}_${col.name}_unique_index`);
4033
+ }
4034
+ }
4035
+ for (const liveIdx of live.indexes) {
4036
+ if (liveIdx.isPrimary) continue;
4037
+ if (autoUniqueNames.has(liveIdx.name)) continue;
4038
+ const liveSig = indexSignature(liveIdx.name, liveIdx.columns, liveIdx.isUnique, liveIdx.order, liveIdx.where);
4039
+ const desiredSig = desiredIdxSignatures.get(liveIdx.name);
4040
+ if (!desiredSig || desiredSig !== liveSig) {
4041
+ statements.push(`DROP INDEX "${liveIdx.name}";`);
4042
+ }
4043
+ }
4044
+ diffColumns(desired, live, statements);
4045
+ }
4046
+ for (const table of tablesToDrop) {
4047
+ statements.push(`DROP TABLE "${table.name}";`);
4048
+ }
4049
+ const { sorted: sortedTablesToCreate, deferredFkNames } = topologicalSortTables(tablesToCreate);
4050
+ for (const table of sortedTablesToCreate) {
4051
+ statements.push(buildCreateTable(table, deferredFkNames));
4052
+ }
4053
+ for (const table of sortedTablesToCreate) {
4054
+ for (const index of table.indexes) {
4055
+ if (!index.isPrimaryKey) {
4056
+ statements.push(buildCreateIndex(table.name, index));
4057
+ }
4058
+ }
4059
+ }
4060
+ for (const desired of tablesToAlter) {
4061
+ const live = liveTableMap.get(desired.name);
4062
+ const liveIdxSignatures = /* @__PURE__ */ new Map();
4063
+ for (const idx of live.indexes) {
4064
+ liveIdxSignatures.set(idx.name, indexSignature(idx.name, idx.columns, idx.isUnique, idx.order, idx.where));
4065
+ }
4066
+ for (const index of desired.indexes) {
4067
+ if (index.isPrimaryKey) continue;
4068
+ const desiredSig = indexSignature(index.name, index.columns, index.isUnique, index.order, index.where);
4069
+ const liveSig = liveIdxSignatures.get(index.name);
4070
+ if (!liveSig || liveSig !== desiredSig) {
4071
+ statements.push(buildCreateIndex(desired.name, index));
4072
+ }
4073
+ }
4074
+ }
4075
+ for (const table of sortedTablesToCreate) {
4076
+ for (const fk of table.foreignKeys) {
4077
+ if (deferredFkNames.has(fk.name)) {
4078
+ statements.push(buildAddForeignKey(table.name, fk));
4079
+ }
4080
+ }
4081
+ }
4082
+ for (const desired of tablesToAlter) {
4083
+ const live = liveTableMap.get(desired.name);
4084
+ const liveFkNames = new Set(live.foreignKeys.map((fk) => fk.name));
4085
+ for (const fk of desired.foreignKeys) {
4086
+ if (!liveFkNames.has(fk.name) || isFkChanged(
4087
+ desired,
4088
+ liveTableMap.get(desired.name).foreignKeys.find((liveFk) => liveFk.name === fk.name)
4089
+ )) {
4090
+ statements.push(buildAddForeignKey(desired.name, fk));
4091
+ }
4092
+ }
4093
+ }
4094
+ for (const desired of tablesToAlter) {
4095
+ const live = liveTableMap.get(desired.name);
4096
+ const liveCheckNames = new Set(live.checkConstraints.map((check) => check.name));
4097
+ const changedChecks = changedChecksPerTable.get(desired.name) ?? /* @__PURE__ */ new Set();
4098
+ for (const check of desired.checkConstraints) {
4099
+ if (!liveCheckNames.has(check.name) || changedChecks.has(check.name)) {
4100
+ statements.push(buildAddCheckConstraint(desired.name, check));
4101
+ }
4102
+ }
4103
+ for (const col of desired.columns) {
4104
+ if (col.type === "ENUM" && col.value) {
4105
+ const checkName = `${desired.name}_${col.name}_check`;
4106
+ if (!liveCheckNames.has(checkName) || changedChecks.has(checkName)) {
4107
+ statements.push(
4108
+ `ALTER TABLE "${desired.name}" ADD CONSTRAINT "${checkName}" CHECK ("${col.name}" IN (${col.value}));`
4109
+ );
4110
+ }
4111
+ }
4112
+ }
4113
+ }
4114
+ return statements;
4115
+ }
4116
+ function diffColumns(desired, live, statements) {
4117
+ const liveColMap = new Map(live.columns.map((col) => [col.name, col]));
4118
+ const desiredColNames = new Set(desired.columns.map((col) => col.name));
4119
+ for (const liveCol of live.columns) {
4120
+ if (!desiredColNames.has(liveCol.name)) {
4121
+ statements.push(`ALTER TABLE "${desired.name}" DROP COLUMN "${liveCol.name}";`);
4122
+ }
4123
+ }
4124
+ for (const column of desired.columns) {
4125
+ const liveColumn = liveColMap.get(column.name);
4126
+ if (!liveColumn) {
4127
+ statements.push(buildAddColumn(desired.name, column));
4128
+ continue;
4129
+ }
4130
+ const desiredUdt = resturaTypeToUdt(column);
4131
+ const udtMismatch = liveColumn.udtName !== desiredUdt && !isSerialMatch(column, liveColumn);
4132
+ if (udtMismatch || !udtMismatch && modifiersDiffer(column, liveColumn)) {
4133
+ const pgType = resturaTypeToPgCast(column);
4134
+ statements.push(
4135
+ `ALTER TABLE "${desired.name}" ALTER COLUMN "${column.name}" TYPE ${pgType} USING "${column.name}"::${pgType};`
4136
+ );
4137
+ }
4138
+ if (column.isNullable && !liveColumn.isNullable) {
4139
+ statements.push(`ALTER TABLE "${desired.name}" ALTER COLUMN "${column.name}" DROP NOT NULL;`);
4140
+ } else if (!column.isNullable && liveColumn.isNullable) {
4141
+ statements.push(`ALTER TABLE "${desired.name}" ALTER COLUMN "${column.name}" SET NOT NULL;`);
4142
+ }
4143
+ const desiredDefault = getDesiredDefault(column);
4144
+ const liveDefault = liveColumn.columnDefault;
4145
+ if (!defaultsMatch(desiredDefault, liveDefault, column)) {
4146
+ if (desiredDefault === null) {
4147
+ statements.push(`ALTER TABLE "${desired.name}" ALTER COLUMN "${column.name}" DROP DEFAULT;`);
4148
+ } else {
4149
+ statements.push(
4150
+ `ALTER TABLE "${desired.name}" ALTER COLUMN "${column.name}" SET DEFAULT ${desiredDefault};`
4151
+ );
4152
+ }
4153
+ }
4154
+ }
4155
+ }
4156
+ function topologicalSortTables(tables) {
4157
+ const tableNames = new Set(tables.map((t) => t.name));
4158
+ const tableMap = new Map(tables.map((t) => [t.name, t]));
4159
+ const inDegree = /* @__PURE__ */ new Map();
4160
+ const tableDeps = /* @__PURE__ */ new Map();
4161
+ const reverseDeps = /* @__PURE__ */ new Map();
4162
+ for (const table of tables) {
4163
+ inDegree.set(table.name, 0);
4164
+ tableDeps.set(table.name, /* @__PURE__ */ new Set());
4165
+ reverseDeps.set(table.name, /* @__PURE__ */ new Set());
4166
+ }
4167
+ for (const table of tables) {
4168
+ for (const fk of table.foreignKeys) {
4169
+ if (tableNames.has(fk.refTable) && fk.refTable !== table.name && !tableDeps.get(table.name).has(fk.refTable)) {
4170
+ tableDeps.get(table.name).add(fk.refTable);
4171
+ inDegree.set(table.name, (inDegree.get(table.name) ?? 0) + 1);
4172
+ reverseDeps.get(fk.refTable).add(table.name);
4173
+ }
4174
+ }
4175
+ }
4176
+ const queue = [];
4177
+ for (const [name, degree] of inDegree) {
4178
+ if (degree === 0) queue.push(name);
4179
+ }
4180
+ const sorted = [];
4181
+ while (queue.length > 0) {
4182
+ const name = queue.shift();
4183
+ sorted.push(name);
4184
+ for (const dependent of reverseDeps.get(name) ?? []) {
4185
+ const newDegree = (inDegree.get(dependent) ?? 1) - 1;
4186
+ inDegree.set(dependent, newDegree);
4187
+ if (newDegree === 0) queue.push(dependent);
4188
+ }
4189
+ }
4190
+ const sortedSet = new Set(sorted);
4191
+ const deferredFkNames = /* @__PURE__ */ new Set();
4192
+ const cycleTables = tables.filter((t) => !sortedSet.has(t.name));
4193
+ const placed = new Set(sorted);
4194
+ for (const table of cycleTables) {
4195
+ sorted.push(table.name);
4196
+ for (const fk of table.foreignKeys) {
4197
+ if (fk.refTable !== table.name && tableNames.has(fk.refTable) && !placed.has(fk.refTable)) {
4198
+ deferredFkNames.add(fk.name);
4199
+ }
4200
+ }
4201
+ placed.add(table.name);
4202
+ }
4203
+ return { sorted: sorted.map((name) => tableMap.get(name)), deferredFkNames };
4204
+ }
4205
+ function buildCreateTable(table, deferredFkNames = /* @__PURE__ */ new Set()) {
4206
+ const definitions = [];
4207
+ for (const column of table.columns) {
4208
+ let definition = `"${column.name}" ${buildColumnType(column)}`;
4209
+ if (column.isPrimary) definition += " PRIMARY KEY";
4210
+ if (column.isUnique) definition += ` CONSTRAINT "${table.name}_${column.name}_unique_index" UNIQUE`;
4211
+ if (!column.isNullable) definition += " NOT NULL";
4212
+ else definition += " NULL";
4213
+ if (column.default) definition += ` DEFAULT ${column.default}`;
4214
+ definitions.push(definition);
4215
+ }
4216
+ for (const fk of table.foreignKeys) {
4217
+ if (deferredFkNames.has(fk.name)) continue;
4218
+ definitions.push(
4219
+ `CONSTRAINT "${fk.name}" FOREIGN KEY ("${fk.column}") REFERENCES "${fk.refTable}" ("${fk.refColumn}") ON DELETE ${fk.onDelete} ON UPDATE ${fk.onUpdate}`
4220
+ );
4221
+ }
4222
+ for (const check of table.checkConstraints) {
4223
+ definitions.push(`CONSTRAINT "${check.name}" CHECK (${check.check})`);
4224
+ }
4225
+ for (const col of table.columns) {
4226
+ if (col.type === "ENUM" && col.value) {
4227
+ definitions.push(
4228
+ `CONSTRAINT "${table.name}_${col.name}_check" CHECK ("${col.name}" IN (${col.value}))`
4229
+ );
4230
+ }
4231
+ }
4232
+ return `CREATE TABLE "${table.name}" (
4233
+ ${definitions.join(",\n ")}
4234
+ );`;
4235
+ }
4236
+ function buildColumnType(column) {
4237
+ const baseType = schemaToPsqlType(column);
4238
+ let value = column.value;
4239
+ if (column.type === "JSON" || column.type === "JSONB") value = "";
4240
+ if (column.type === "DECIMAL" && value) {
4241
+ value = value.replace("-", ",").replace(/['"]/g, "");
4242
+ }
4243
+ if (value && column.type !== "ENUM") {
4244
+ return `${baseType}(${value})`;
4245
+ }
4246
+ if (column.length) return `${baseType}(${column.length})`;
4247
+ return baseType;
4248
+ }
4249
+ function buildAddColumn(tableName, column) {
4250
+ let definition = `ALTER TABLE "${tableName}" ADD COLUMN "${column.name}" ${buildColumnType(column)}`;
4251
+ if (column.isPrimary) definition += " PRIMARY KEY";
4252
+ if (column.isUnique) definition += ` CONSTRAINT "${tableName}_${column.name}_unique_index" UNIQUE`;
4253
+ if (!column.isNullable) definition += " NOT NULL";
4254
+ else definition += " NULL";
4255
+ if (column.default) definition += ` DEFAULT ${column.default}`;
4256
+ definition += ";";
4257
+ return definition;
4258
+ }
4259
+ function buildCreateIndex(tableName, index) {
4260
+ const unique = index.isUnique ? "UNIQUE " : "";
4261
+ let sql = `CREATE ${unique}INDEX "${index.name}" ON "${tableName}" (${index.columns.map((column) => `"${column}" ${index.order}`).join(", ")})`;
4262
+ if (index.where) sql += ` WHERE ${index.where}`;
4263
+ sql += ";";
4264
+ return sql;
4265
+ }
4266
+ function buildAddForeignKey(tableName, foreignKey) {
4267
+ return `ALTER TABLE "${tableName}" ADD CONSTRAINT "${foreignKey.name}" FOREIGN KEY ("${foreignKey.column}") REFERENCES "${foreignKey.refTable}" ("${foreignKey.refColumn}") ON DELETE ${foreignKey.onDelete} ON UPDATE ${foreignKey.onUpdate};`;
4268
+ }
4269
+ function buildAddCheckConstraint(tableName, constraint) {
4270
+ return `ALTER TABLE "${tableName}" ADD CONSTRAINT "${constraint.name}" CHECK (${constraint.check});`;
4271
+ }
4272
+ function indexSignature(name, columns, isUnique, order, where) {
4273
+ return `${name}|${columns.join(",")}|${isUnique}|${order}|${where || ""}`;
4274
+ }
4275
+ function isFkChanged(desired, liveFk) {
4276
+ const desiredFk = desired.foreignKeys.find((fk) => fk.name === liveFk.name);
4277
+ if (!desiredFk) return true;
4278
+ return desiredFk.column !== liveFk.column || desiredFk.refTable !== liveFk.refTable || desiredFk.refColumn !== liveFk.refColumn || desiredFk.onDelete !== liveFk.onDelete || desiredFk.onUpdate !== liveFk.onUpdate;
4279
+ }
4280
+ function normalizeCheckExpression(expr) {
4281
+ let s = expr;
4282
+ const checkMatch = s.match(/^CHECK\s*\(([\s\S]*)\)\s*$/i);
4283
+ if (checkMatch) s = checkMatch[1];
4284
+ s = s.replace(/::\w+(\[\])?/g, "");
4285
+ s = s.replace(/=\s*ANY\s*\(\s*\(?\s*ARRAY\s*\[([^\]]*)\]\s*\)?\s*\)/gi, "IN ($1)");
4286
+ s = s.replace(/"(\w+)"/g, "$1");
4287
+ s = s.replace(/\((\w+)\)/g, "$1");
4288
+ s = s.replace(/\s+/g, " ").trim().toLowerCase();
4289
+ while (s.startsWith("(") && s.endsWith(")")) {
4290
+ const inner = s.slice(1, -1);
4291
+ let depth = 0;
4292
+ let balanced = true;
4293
+ for (const char of inner) {
4294
+ if (char === "(") depth++;
4295
+ if (char === ")") depth--;
4296
+ if (depth < 0) {
4297
+ balanced = false;
4298
+ break;
4299
+ }
4300
+ }
4301
+ if (balanced && depth === 0) s = inner.trim();
4302
+ else break;
4303
+ }
4304
+ s = s.replace(/\s*,\s*/g, ", ");
4305
+ return s;
4306
+ }
4307
+ function isSerialMatch(column, liveCol) {
4308
+ if (column.hasAutoIncrement || column.type === "BIGSERIAL" || column.type === "SERIAL") {
4309
+ const serialUdt = column.type === "SERIAL" ? "int4" : "int8";
4310
+ return liveCol.udtName === serialUdt && liveCol.columnDefault?.startsWith("nextval(") === true;
4311
+ }
4312
+ return false;
4313
+ }
4314
+ function modifiersDiffer(column, liveColumn) {
4315
+ const desiredLength = column.length ?? null;
4316
+ if (desiredLength !== liveColumn.characterMaximumLength) return true;
4317
+ if (column.type === "DECIMAL" && column.value) {
4318
+ const parts = column.value.replace(/['"]/g, "").split("-");
4319
+ const desiredPrecision = parseInt(parts[0], 10);
4320
+ const desiredScale = parts.length > 1 ? parseInt(parts[1], 10) : 0;
4321
+ if (liveColumn.numericPrecision !== desiredPrecision || liveColumn.numericScale !== desiredScale) return true;
4322
+ }
4323
+ return false;
4324
+ }
4325
+ function resturaTypeToPgCast(column) {
4326
+ const baseType = schemaToPsqlType(column);
4327
+ const castMap = {
4328
+ BIGSERIAL: "BIGINT",
4329
+ SERIAL: "INTEGER",
4330
+ INT: "INTEGER",
4331
+ TIMESTAMPTZ: "TIMESTAMPTZ",
4332
+ TIMESTAMP: "TIMESTAMP",
4333
+ "DOUBLE PRECISION": "DOUBLE PRECISION"
4334
+ };
4335
+ let pgType = castMap[baseType] ?? baseType;
4336
+ let value = column.value;
4337
+ if (column.type === "DECIMAL" && value) {
4338
+ value = value.replace("-", ",").replace(/['"]/g, "");
4339
+ pgType = `${pgType}(${value})`;
4340
+ } else if (column.length) {
4341
+ pgType = `${pgType}(${column.length})`;
4342
+ }
4343
+ return pgType;
4344
+ }
4345
+ function getDesiredDefault(column) {
4346
+ if (column.hasAutoIncrement || column.type === "BIGSERIAL" || column.type === "SERIAL") return null;
4347
+ return column.default ?? null;
4348
+ }
4349
+ function defaultsMatch(desired, live, column) {
4350
+ if (column.hasAutoIncrement || column.type === "BIGSERIAL" || column.type === "SERIAL") return true;
4351
+ if (desired === null && live === null) return true;
4352
+ if (desired === null || live === null) return false;
4353
+ const normalizedLive = live.replace(/::[^\s]+$/g, "").trim();
4354
+ return desired.trim() === normalizedLive;
4355
+ }
4356
+
3856
4357
  // src/restura/sql/PsqlTransaction.ts
3857
- import pg3 from "pg";
3858
- var { Client: Client2 } = pg3;
4358
+ import pg4 from "pg";
4359
+ var { Client: Client3 } = pg4;
3859
4360
  var PsqlTransaction = class extends PsqlConnection {
3860
4361
  constructor(clientConfig, instanceId) {
3861
4362
  super(instanceId);
3862
4363
  this.clientConfig = clientConfig;
3863
- this.client = new Client2(clientConfig);
4364
+ this.client = new Client3(clientConfig);
3864
4365
  this.connectPromise = this.client.connect();
3865
4366
  this.beginTransactionPromise = this.beginTransaction();
3866
4367
  }
@@ -3900,10 +4401,18 @@ export {
3900
4401
  RsError,
3901
4402
  SQL,
3902
4403
  apiGenerator,
4404
+ createDeleteTriggerSql,
4405
+ createInsertTriggerSql,
4406
+ createUpdateTriggerSql,
4407
+ diffDatabaseToSchema,
4408
+ diffSchemaToDatabase,
3903
4409
  escapeColumnName,
3904
4410
  eventManager_default as eventManager,
3905
4411
  filterPsqlParser_default as filterPsqlParser,
4412
+ generateDatabaseSchemaFromSchema,
4413
+ getNewPublicSchemaAndScratchPool,
3906
4414
  insertObjectQuery,
4415
+ introspectDatabase,
3907
4416
  isSchemaValid,
3908
4417
  isValueNumber,
3909
4418
  logger,
@@ -3912,6 +4421,9 @@ export {
3912
4421
  restura,
3913
4422
  resturaGlobalTypesGenerator,
3914
4423
  resturaSchema,
4424
+ schemaToPsqlType,
4425
+ setLogger,
4426
+ systemUser,
3915
4427
  toSqlLiteral,
3916
4428
  updateObjectQuery
3917
4429
  };