@restura/core 0.1.0-alpha.8 → 0.1.1

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.
Files changed (38) hide show
  1. package/dist/index.d.mts +1439 -177
  2. package/dist/index.d.ts +1439 -177
  3. package/dist/index.js +1767 -708
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1751 -705
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +17 -6
  8. package/dist/acorn-SW5GI5G7.mjs +0 -3016
  9. package/dist/acorn-SW5GI5G7.mjs.map +0 -1
  10. package/dist/angular-FYEH6QOL.mjs +0 -1547
  11. package/dist/angular-FYEH6QOL.mjs.map +0 -1
  12. package/dist/babel-V6GZHMYX.mjs +0 -6911
  13. package/dist/babel-V6GZHMYX.mjs.map +0 -1
  14. package/dist/chunk-TL4KRYOF.mjs +0 -58
  15. package/dist/chunk-TL4KRYOF.mjs.map +0 -1
  16. package/dist/estree-67ZCSSSI.mjs +0 -4396
  17. package/dist/estree-67ZCSSSI.mjs.map +0 -1
  18. package/dist/flow-SJW7PRXX.mjs +0 -26365
  19. package/dist/flow-SJW7PRXX.mjs.map +0 -1
  20. package/dist/glimmer-PF2X22V2.mjs +0 -2995
  21. package/dist/glimmer-PF2X22V2.mjs.map +0 -1
  22. package/dist/graphql-NOJ5HX7K.mjs +0 -1253
  23. package/dist/graphql-NOJ5HX7K.mjs.map +0 -1
  24. package/dist/html-ROPIWVPQ.mjs +0 -2780
  25. package/dist/html-ROPIWVPQ.mjs.map +0 -1
  26. package/dist/index.cjs +0 -34
  27. package/dist/index.cjs.map +0 -1
  28. package/dist/index.d.cts +0 -3
  29. package/dist/markdown-PFYT4MSP.mjs +0 -3423
  30. package/dist/markdown-PFYT4MSP.mjs.map +0 -1
  31. package/dist/meriyah-5NLZXLMA.mjs +0 -2356
  32. package/dist/meriyah-5NLZXLMA.mjs.map +0 -1
  33. package/dist/postcss-ITO6IEN5.mjs +0 -5027
  34. package/dist/postcss-ITO6IEN5.mjs.map +0 -1
  35. package/dist/typescript-6IE7K56Q.mjs +0 -13392
  36. package/dist/typescript-6IE7K56Q.mjs.map +0 -1
  37. package/dist/yaml-DB2OVPLH.mjs +0 -4230
  38. package/dist/yaml-DB2OVPLH.mjs.map +0 -1
package/dist/index.js CHANGED
@@ -67,9 +67,22 @@ var __decorateClass = (decorators, target, key, kind) => {
67
67
  // src/index.ts
68
68
  var src_exports = {};
69
69
  __export(src_exports, {
70
+ HtmlStatusCodes: () => HtmlStatusCodes,
71
+ PsqlConnection: () => PsqlConnection,
72
+ PsqlEngine: () => PsqlEngine,
70
73
  PsqlPool: () => PsqlPool,
74
+ PsqlTransaction: () => PsqlTransaction,
75
+ RsError: () => RsError,
76
+ SQL: () => SQL,
77
+ escapeColumnName: () => escapeColumnName,
78
+ eventManager: () => eventManager_default,
79
+ filterPsqlParser: () => filterPsqlParser_default,
80
+ insertObjectQuery: () => insertObjectQuery,
81
+ isValueNumber: () => isValueNumber2,
71
82
  logger: () => logger,
72
- restura: () => restura
83
+ questionMarksToOrderedParams: () => questionMarksToOrderedParams,
84
+ restura: () => restura,
85
+ updateObjectQuery: () => updateObjectQuery
73
86
  });
74
87
  module.exports = __toCommonJS(src_exports);
75
88
 
@@ -78,16 +91,10 @@ var import_internal = require("@restura/internal");
78
91
  var import_winston = __toESM(require("winston"));
79
92
  var import_logform = require("logform");
80
93
 
81
- // src/config.schema.ts
94
+ // src/logger/loggerConfigSchema.ts
82
95
  var import_zod = require("zod");
83
96
  var loggerConfigSchema = import_zod.z.object({
84
- level: import_zod.z.enum(["info", "warn", "error", "debug"]).default("info")
85
- });
86
- var resturaConfigSchema = import_zod.z.object({
87
- authToken: import_zod.z.string().min(1, "Missing Restura Auth Token"),
88
- sendErrorStackTrace: import_zod.z.boolean().default(false),
89
- schemaFilePath: import_zod.z.string().default(process.cwd() + "/restura.schema.json"),
90
- generatedTypesPath: import_zod.z.string().default(process.cwd() + "/src/@types")
97
+ level: import_zod.z.enum(["info", "warn", "error", "debug", "silly"]).default("info")
91
98
  });
92
99
 
93
100
  // src/logger/logger.ts
@@ -125,9 +132,141 @@ var logger = import_winston.default.createLogger({
125
132
  ]
126
133
  });
127
134
 
135
+ // src/restura/eventManager.ts
136
+ var import_bluebird = __toESM(require("bluebird"));
137
+ var EventManager = class {
138
+ constructor() {
139
+ this.actionHandlers = {
140
+ DATABASE_ROW_DELETE: [],
141
+ DATABASE_ROW_INSERT: [],
142
+ DATABASE_COLUMN_UPDATE: []
143
+ };
144
+ }
145
+ addRowInsertHandler(onInsert, filter) {
146
+ this.actionHandlers.DATABASE_ROW_INSERT.push({
147
+ callback: onInsert,
148
+ filter
149
+ });
150
+ }
151
+ addColumnChangeHandler(onUpdate, filter) {
152
+ this.actionHandlers.DATABASE_COLUMN_UPDATE.push({
153
+ callback: onUpdate,
154
+ filter
155
+ });
156
+ }
157
+ addRowDeleteHandler(onDelete, filter) {
158
+ this.actionHandlers.DATABASE_ROW_DELETE.push({
159
+ callback: onDelete,
160
+ filter
161
+ });
162
+ }
163
+ async fireActionFromDbTrigger(sqlMutationData, result) {
164
+ if (sqlMutationData.mutationType === "INSERT") {
165
+ await this.fireInsertActions(sqlMutationData, result);
166
+ } else if (sqlMutationData.mutationType === "UPDATE") {
167
+ await this.fireUpdateActions(sqlMutationData, result);
168
+ } else if (sqlMutationData.mutationType === "DELETE") {
169
+ await this.fireDeleteActions(sqlMutationData, result);
170
+ }
171
+ }
172
+ async fireInsertActions(data, triggerResult) {
173
+ await import_bluebird.default.map(
174
+ this.actionHandlers.DATABASE_ROW_INSERT,
175
+ ({ callback, filter }) => {
176
+ if (!this.hasHandlersForEventType("DATABASE_ROW_INSERT", filter, triggerResult)) return;
177
+ const insertData = {
178
+ tableName: triggerResult.table,
179
+ insertedId: triggerResult.insertedId || 0,
180
+ insertObject: triggerResult.record,
181
+ queryMetadata: data.queryMetadata
182
+ };
183
+ callback(insertData, data.queryMetadata);
184
+ },
185
+ { concurrency: 10 }
186
+ );
187
+ }
188
+ async fireDeleteActions(data, triggerResult) {
189
+ await import_bluebird.default.map(
190
+ this.actionHandlers.DATABASE_ROW_DELETE,
191
+ ({ callback, filter }) => {
192
+ if (!this.hasHandlersForEventType("DATABASE_ROW_DELETE", filter, triggerResult)) return;
193
+ const deleteData = {
194
+ tableName: triggerResult.table,
195
+ deletedId: triggerResult.deletedId || 0,
196
+ deletedRow: triggerResult.previousRecord,
197
+ queryMetadata: data.queryMetadata
198
+ };
199
+ callback(deleteData, data.queryMetadata);
200
+ },
201
+ { concurrency: 10 }
202
+ );
203
+ }
204
+ async fireUpdateActions(data, triggerResult) {
205
+ await import_bluebird.default.map(
206
+ this.actionHandlers.DATABASE_COLUMN_UPDATE,
207
+ ({ callback, filter }) => {
208
+ if (!this.hasHandlersForEventType("DATABASE_COLUMN_UPDATE", filter, triggerResult)) return;
209
+ const columnChangeData = {
210
+ tableName: triggerResult.table,
211
+ changedId: triggerResult.changedId || 0,
212
+ newData: triggerResult.record,
213
+ oldData: triggerResult.previousRecord,
214
+ queryMetadata: data.queryMetadata
215
+ };
216
+ callback(columnChangeData, data.queryMetadata);
217
+ },
218
+ { concurrency: 10 }
219
+ );
220
+ }
221
+ hasHandlersForEventType(eventType, filter, triggerResult) {
222
+ if (filter) {
223
+ switch (eventType) {
224
+ case "DATABASE_ROW_INSERT":
225
+ case "DATABASE_ROW_DELETE":
226
+ if (filter.tableName && filter.tableName !== triggerResult.table) return false;
227
+ break;
228
+ case "DATABASE_COLUMN_UPDATE":
229
+ const filterColumnChange = filter;
230
+ if (filterColumnChange.tableName !== triggerResult.table) return false;
231
+ if (filterColumnChange.columns.length === 1) {
232
+ const firstColumn = filterColumnChange.columns[0];
233
+ if (firstColumn === "*") return true;
234
+ }
235
+ if (!filterColumnChange.columns.some((item) => {
236
+ const updatedColumns = Object.keys(
237
+ changedValues(triggerResult.record, triggerResult.previousRecord)
238
+ );
239
+ return updatedColumns.includes(item);
240
+ }))
241
+ return false;
242
+ break;
243
+ }
244
+ }
245
+ return true;
246
+ }
247
+ };
248
+ var eventManager = new EventManager();
249
+ var eventManager_default = eventManager;
250
+ function changedValues(record, previousRecord) {
251
+ const changed = {};
252
+ for (const i in previousRecord) {
253
+ if (previousRecord[i] !== record[i]) {
254
+ if (typeof previousRecord[i] === "object" && typeof record[i] === "object") {
255
+ const nestedChanged = changedValues(record[i], previousRecord[i]);
256
+ if (Object.keys(nestedChanged).length > 0) {
257
+ changed[i] = record[i];
258
+ }
259
+ } else {
260
+ changed[i] = record[i];
261
+ }
262
+ }
263
+ }
264
+ return changed;
265
+ }
266
+
128
267
  // src/restura/restura.ts
129
- var import_core_utils6 = require("@redskytech/core-utils");
130
- var import_internal2 = require("@restura/internal");
268
+ var import_core_utils7 = require("@redskytech/core-utils");
269
+ var import_internal4 = require("@restura/internal");
131
270
 
132
271
  // ../../node_modules/.pnpm/autobind-decorator@2.4.0/node_modules/autobind-decorator/lib/esm/index.js
133
272
  function _typeof(obj) {
@@ -179,13 +318,25 @@ function boundMethod(target, key, descriptor) {
179
318
  var import_body_parser = __toESM(require("body-parser"));
180
319
  var import_compression = __toESM(require("compression"));
181
320
  var import_cookie_parser = __toESM(require("cookie-parser"));
182
- var import_crypto = require("crypto");
183
321
  var express = __toESM(require("express"));
184
- var import_fs2 = __toESM(require("fs"));
185
- var import_path2 = __toESM(require("path"));
322
+ var import_fs4 = __toESM(require("fs"));
323
+ var import_path5 = __toESM(require("path"));
186
324
  var prettier3 = __toESM(require("prettier"));
187
325
 
188
- // src/restura/errors.ts
326
+ // src/restura/RsError.ts
327
+ var HtmlStatusCodes = /* @__PURE__ */ ((HtmlStatusCodes2) => {
328
+ HtmlStatusCodes2[HtmlStatusCodes2["BAD_REQUEST"] = 400] = "BAD_REQUEST";
329
+ HtmlStatusCodes2[HtmlStatusCodes2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
330
+ HtmlStatusCodes2[HtmlStatusCodes2["FORBIDDEN"] = 403] = "FORBIDDEN";
331
+ HtmlStatusCodes2[HtmlStatusCodes2["NOT_FOUND"] = 404] = "NOT_FOUND";
332
+ HtmlStatusCodes2[HtmlStatusCodes2["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
333
+ HtmlStatusCodes2[HtmlStatusCodes2["CONFLICT"] = 409] = "CONFLICT";
334
+ HtmlStatusCodes2[HtmlStatusCodes2["VERSION_OUT_OF_DATE"] = 418] = "VERSION_OUT_OF_DATE";
335
+ HtmlStatusCodes2[HtmlStatusCodes2["SERVER_ERROR"] = 500] = "SERVER_ERROR";
336
+ HtmlStatusCodes2[HtmlStatusCodes2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
337
+ HtmlStatusCodes2[HtmlStatusCodes2["NETWORK_CONNECT_TIMEOUT"] = 599] = "NETWORK_CONNECT_TIMEOUT";
338
+ return HtmlStatusCodes2;
339
+ })(HtmlStatusCodes || {});
189
340
  var RsError = class _RsError {
190
341
  constructor(errCode, message) {
191
342
  this.err = errCode;
@@ -196,6 +347,9 @@ var RsError = class _RsError {
196
347
  static htmlStatus(code) {
197
348
  return htmlStatusMap[code];
198
349
  }
350
+ static isRsError(error) {
351
+ return error instanceof _RsError;
352
+ }
199
353
  };
200
354
  var htmlStatusMap = {
201
355
  UNKNOWN_ERROR: 500 /* SERVER_ERROR */,
@@ -234,12 +388,171 @@ var htmlStatusMap = {
234
388
  SCHEMA_ERROR: 500 /* SERVER_ERROR */
235
389
  };
236
390
 
391
+ // src/restura/compareSchema.ts
392
+ var import_lodash = __toESM(require("lodash.clonedeep"));
393
+ var CompareSchema = class {
394
+ async diffSchema(newSchema, latestSchema, psqlEngine) {
395
+ const endPoints = this.diffEndPoints(newSchema.endpoints[0].routes, latestSchema.endpoints[0].routes);
396
+ const globalParams = this.diffStringArray(newSchema.globalParams, latestSchema.globalParams);
397
+ const roles = this.diffStringArray(newSchema.roles, latestSchema.roles);
398
+ let commands = "";
399
+ if (JSON.stringify(newSchema.database) !== JSON.stringify(latestSchema.database))
400
+ commands = await psqlEngine.diffDatabaseToSchema(newSchema);
401
+ const hasCustomTypesChanged = JSON.stringify(newSchema.customTypes) !== JSON.stringify(latestSchema.customTypes);
402
+ const schemaPreview = {
403
+ endPoints,
404
+ globalParams,
405
+ roles,
406
+ commands,
407
+ customTypes: hasCustomTypesChanged
408
+ };
409
+ return schemaPreview;
410
+ }
411
+ diffStringArray(newArray, originalArray) {
412
+ const stringsDiff = [];
413
+ const originalClone = new Set(originalArray);
414
+ newArray.forEach((item) => {
415
+ const originalIndex = originalClone.has(item);
416
+ if (!originalIndex) {
417
+ stringsDiff.push({
418
+ name: item,
419
+ changeType: "NEW"
420
+ });
421
+ } else {
422
+ originalClone.delete(item);
423
+ }
424
+ });
425
+ originalClone.forEach((item) => {
426
+ stringsDiff.push({
427
+ name: item,
428
+ changeType: "DELETED"
429
+ });
430
+ });
431
+ return stringsDiff;
432
+ }
433
+ diffEndPoints(newEndPoints, originalEndpoints) {
434
+ const originalClone = (0, import_lodash.default)(originalEndpoints);
435
+ const diffObj = [];
436
+ newEndPoints.forEach((endPoint) => {
437
+ const { path: path5, method } = endPoint;
438
+ const endPointIndex = originalClone.findIndex((original) => {
439
+ return original.path === endPoint.path && original.method === endPoint.method;
440
+ });
441
+ if (endPointIndex === -1) {
442
+ diffObj.push({
443
+ name: `${method} ${path5}`,
444
+ changeType: "NEW"
445
+ });
446
+ } else {
447
+ const original = originalClone.findIndex((original2) => {
448
+ return this.compareEndPoints(endPoint, original2);
449
+ });
450
+ if (original === -1) {
451
+ diffObj.push({
452
+ name: `${method} ${path5}`,
453
+ changeType: "MODIFIED"
454
+ });
455
+ }
456
+ originalClone.splice(endPointIndex, 1);
457
+ }
458
+ });
459
+ originalClone.forEach((original) => {
460
+ const { path: path5, method } = original;
461
+ diffObj.push({
462
+ name: `${method} ${path5}`,
463
+ changeType: "DELETED"
464
+ });
465
+ });
466
+ return diffObj;
467
+ }
468
+ compareEndPoints(endPoint1, endPoint2) {
469
+ return JSON.stringify(endPoint1) === JSON.stringify(endPoint2);
470
+ }
471
+ };
472
+ __decorateClass([
473
+ boundMethod
474
+ ], CompareSchema.prototype, "diffSchema", 1);
475
+ __decorateClass([
476
+ boundMethod
477
+ ], CompareSchema.prototype, "diffStringArray", 1);
478
+ __decorateClass([
479
+ boundMethod
480
+ ], CompareSchema.prototype, "diffEndPoints", 1);
481
+ __decorateClass([
482
+ boundMethod
483
+ ], CompareSchema.prototype, "compareEndPoints", 1);
484
+ var compareSchema = new CompareSchema();
485
+ var compareSchema_default = compareSchema;
486
+
487
+ // src/restura/customApiFactory.ts
488
+ var import_bluebird2 = __toESM(require("bluebird"));
489
+ var import_fs = __toESM(require("fs"));
490
+ var import_path = __toESM(require("path"));
491
+ var import_internal2 = require("@restura/internal");
492
+ var CustomApiFactory = class {
493
+ constructor() {
494
+ this.customApis = {};
495
+ }
496
+ async loadApiFiles(baseFolderPath) {
497
+ const apiVersions = ["v1"];
498
+ for (const apiVersion of apiVersions) {
499
+ const apiVersionFolderPath = import_path.default.join(baseFolderPath, apiVersion);
500
+ const directoryExists = await import_internal2.FileUtils.existDir(apiVersionFolderPath);
501
+ if (!directoryExists) continue;
502
+ await this.addDirectory(apiVersionFolderPath, apiVersion);
503
+ }
504
+ }
505
+ getCustomApi(customApiName) {
506
+ return this.customApis[customApiName];
507
+ }
508
+ async addDirectory(directoryPath, apiVersion) {
509
+ var _a2;
510
+ const entries = await import_fs.default.promises.readdir(directoryPath, {
511
+ withFileTypes: true
512
+ });
513
+ const isTsx2 = (_a2 = process.argv[1]) == null ? void 0 : _a2.endsWith(".ts");
514
+ const isTsNode2 = process.env.TS_NODE_DEV || process.env.TS_NODE_PROJECT;
515
+ const extension = isTsx2 || isTsNode2 ? "ts" : "js";
516
+ const shouldEndWith = `.api.${apiVersion}.${extension}`;
517
+ await import_bluebird2.default.map(entries, async (entry) => {
518
+ if (entry.isFile()) {
519
+ if (entry.name.endsWith(shouldEndWith) === false) return;
520
+ try {
521
+ const importPath = `${import_path.default.join(directoryPath, entry.name)}`;
522
+ const ApiImport = await import(importPath);
523
+ const customApiClass = new ApiImport.default();
524
+ logger.info(`Registering custom API: ${ApiImport.default.name}`);
525
+ this.bindMethodsToInstance(customApiClass);
526
+ this.customApis[ApiImport.default.name] = customApiClass;
527
+ } catch (e) {
528
+ logger.error(e);
529
+ }
530
+ }
531
+ });
532
+ }
533
+ bindMethodsToInstance(instance) {
534
+ const proto = Object.getPrototypeOf(instance);
535
+ Object.getOwnPropertyNames(proto).forEach((key) => {
536
+ const property = instance[key];
537
+ if (typeof property === "function" && key !== "constructor") {
538
+ instance[key] = property.bind(instance);
539
+ }
540
+ });
541
+ }
542
+ };
543
+ var customApiFactory = new CustomApiFactory();
544
+ var customApiFactory_default = customApiFactory;
545
+
546
+ // src/restura/generators/apiGenerator.ts
547
+ var import_core_utils = require("@redskytech/core-utils");
548
+ var import_prettier = __toESM(require("prettier"));
549
+
237
550
  // src/restura/sql/SqlUtils.ts
238
551
  var SqlUtils = class _SqlUtils {
239
552
  static convertDatabaseTypeToTypescript(type, value) {
240
553
  type = type.toLocaleLowerCase();
241
554
  if (type.startsWith("tinyint") || type.startsWith("boolean")) return "boolean";
242
- if (type.indexOf("int") > -1 || type.startsWith("decimal") || type.startsWith("double") || type.startsWith("float"))
555
+ if (type.indexOf("int") > -1 || type.startsWith("decimal") || type.startsWith("double") || type.startsWith("float") || type.indexOf("serial") > -1 || type.startsWith("decimal") || type.startsWith("real") || type.startsWith("double precision") || type.startsWith("numeric"))
243
556
  return "number";
244
557
  if (type === "json") {
245
558
  if (!value) return "object";
@@ -260,7 +573,7 @@ var SqlUtils = class _SqlUtils {
260
573
  }
261
574
  };
262
575
 
263
- // src/restura/ResponseValidator.ts
576
+ // src/restura/validators/ResponseValidator.ts
264
577
  var ResponseValidator = class _ResponseValidator {
265
578
  constructor(schema) {
266
579
  this.database = schema.database;
@@ -325,9 +638,9 @@ var ResponseValidator = class _ResponseValidator {
325
638
  return { validator: "any" };
326
639
  }
327
640
  getTypeFromTable(selector, name) {
328
- const path3 = selector.split(".");
329
- if (path3.length === 0 || path3.length > 2 || path3[0] === "") return { validator: "any", isOptional: false };
330
- const tableName = path3.length == 2 ? path3[0] : name, columnName = path3.length == 2 ? path3[1] : path3[0];
641
+ const path5 = selector.split(".");
642
+ if (path5.length === 0 || path5.length > 2 || path5[0] === "") return { validator: "any", isOptional: false };
643
+ const tableName = path5.length == 2 ? path5[0] : name, columnName = path5.length == 2 ? path5[1] : path5[0];
331
644
  const table = this.database.find((t) => t.name == tableName);
332
645
  const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
333
646
  if (!table || !column) return { validator: "any", isOptional: false };
@@ -409,9 +722,7 @@ var ResponseValidator = class _ResponseValidator {
409
722
  }
410
723
  };
411
724
 
412
- // src/restura/apiGenerator.ts
413
- var import_core_utils = require("@redskytech/core-utils");
414
- var import_prettier = __toESM(require("prettier"));
725
+ // src/restura/generators/apiGenerator.ts
415
726
  var ApiTree = class _ApiTree {
416
727
  constructor(namespace, database) {
417
728
  this.database = database;
@@ -509,7 +820,7 @@ var ApiTree = class _ApiTree {
509
820
  break;
510
821
  }
511
822
  }
512
- return `'${p.name}'${p.required ? "" : "?"}:${requestType}`;
823
+ return `'${p.name}'${p.required ? "" : "?"}:${requestType}${p.isNullable ? " | null" : ""}`;
513
824
  }).join(";\n")}${import_core_utils.ObjectUtils.isArrayWithData(route.request) ? ";" : ""}
514
825
  `;
515
826
  modelString += `}`;
@@ -523,30 +834,37 @@ var ApiTree = class _ApiTree {
523
834
  return `export type Res = CustomTypes.${route.responseType}[]`;
524
835
  else return `export type Res = CustomTypes.${route.responseType}`;
525
836
  }
526
- return `export interface Res ${this.getFields(route.response)}`;
837
+ return `export interface Res ${this.getFields(route.response, route.table, route.joins)}`;
527
838
  }
528
- getFields(fields) {
529
- const nameFields = fields.map((f) => this.getNameAndType(f));
839
+ getFields(fields, routeBaseTable, joins) {
840
+ const nameFields = fields.map((f) => this.getNameAndType(f, routeBaseTable, joins));
530
841
  const nested = `{
531
842
  ${nameFields.join(";\n ")}${import_core_utils.ObjectUtils.isArrayWithData(nameFields) ? ";" : ""}
532
843
  }`;
533
844
  return nested;
534
845
  }
535
- getNameAndType(p) {
536
- let responseType = "any", optional = false, array = false;
846
+ getNameAndType(p, routeBaseTable, joins) {
847
+ let responseType = "any", isNullable = false, array = false;
537
848
  if (p.selector) {
538
- ({ responseType, optional } = this.getTypeFromTable(p.selector, p.name));
849
+ ({ responseType, isNullable } = this.getTypeFromTable(p.selector, p.name));
850
+ const selectorKey = p.selector.split(".")[0];
851
+ if (selectorKey !== routeBaseTable) {
852
+ const join = joins.find((j) => j.alias === selectorKey);
853
+ if (join && join.type !== "INNER") {
854
+ isNullable = true;
855
+ }
856
+ }
539
857
  } else if (p.subquery) {
540
- responseType = this.getFields(p.subquery.properties);
858
+ responseType = this.getFields(p.subquery.properties, p.subquery.table, p.subquery.joins);
541
859
  array = true;
542
860
  }
543
- return `${p.name}${optional ? "?" : ""}:${responseType}${array ? "[]" : ""}`;
861
+ return `${p.name}:${responseType}${array ? "[]" : ""}${isNullable ? " | null" : ""}`;
544
862
  }
545
863
  getTypeFromTable(selector, name) {
546
- const path3 = selector.split(".");
547
- if (path3.length === 0 || path3.length > 2 || path3[0] === "") return { responseType: "any", optional: false };
548
- let tableName = path3.length == 2 ? path3[0] : name;
549
- const columnName = path3.length == 2 ? path3[1] : path3[0];
864
+ const path5 = selector.split(".");
865
+ if (path5.length === 0 || path5.length > 2 || path5[0] === "") return { responseType: "any", isNullable: false };
866
+ let tableName = path5.length == 2 ? path5[0] : name;
867
+ const columnName = path5.length == 2 ? path5[1] : path5[0];
550
868
  let table = this.database.find((t) => t.name == tableName);
551
869
  if (!table && tableName.includes("_")) {
552
870
  const tableAliasSplit = tableName.split("_");
@@ -554,18 +872,19 @@ var ApiTree = class _ApiTree {
554
872
  table = this.database.find((t) => t.name == tableName);
555
873
  }
556
874
  const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
557
- if (!table || !column) return { responseType: "any", optional: false };
875
+ if (!table || !column) return { responseType: "any", isNullable: false };
558
876
  return {
559
877
  responseType: SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value),
560
- optional: column.roles.length > 0 || column.isNullable
878
+ isNullable: column.roles.length > 0 || column.isNullable
561
879
  };
562
880
  }
563
881
  };
564
- function pathToNamespaces(path3) {
565
- return path3.split("/").map((e) => import_core_utils.StringUtils.toPascalCasing(e)).filter((e) => e);
882
+ function pathToNamespaces(path5) {
883
+ return path5.split("/").map((e) => import_core_utils.StringUtils.toPascalCasing(e)).filter((e) => e);
566
884
  }
567
- function apiGenerator(schema, schemaHash) {
568
- let apiString = `/** Auto generated file from Schema Hash (${schemaHash}). DO NOT MODIFY **/`;
885
+ function apiGenerator(schema) {
886
+ let apiString = `/** Auto generated file. DO NOT MODIFY **/
887
+ `;
569
888
  const rootNamespace = ApiTree.createRootNode(schema.database);
570
889
  for (const endpoint of schema.endpoints) {
571
890
  const endpointNamespaces = pathToNamespaces(endpoint.baseUrl);
@@ -580,7 +899,7 @@ function apiGenerator(schema, schemaHash) {
580
899
  apiString += `
581
900
 
582
901
  declare namespace CustomTypes {
583
- ${schema.customTypes}
902
+ ${schema.customTypes.join("\n")}
584
903
  }`;
585
904
  }
586
905
  return import_prettier.default.format(apiString, __spreadValues({
@@ -595,6 +914,100 @@ function apiGenerator(schema, schemaHash) {
595
914
  }));
596
915
  }
597
916
 
917
+ // src/restura/generators/customTypeValidationGenerator.ts
918
+ var import_fs2 = __toESM(require("fs"));
919
+ var import_path2 = __toESM(require("path"));
920
+ var import_tmp = __toESM(require("tmp"));
921
+ var TJS = __toESM(require("typescript-json-schema"));
922
+ function customTypeValidationGenerator(currentSchema) {
923
+ const schemaObject = {};
924
+ const customInterfaceNames = currentSchema.customTypes.map((customType) => {
925
+ const matches = customType.match(new RegExp("(?<=interface\\s)(\\w+)|(?<=type\\s)(\\w+)", "g"));
926
+ if (matches && matches.length > 0) return matches[0];
927
+ return "";
928
+ }).filter(Boolean);
929
+ if (!customInterfaceNames) return {};
930
+ const temporaryFile = import_tmp.default.fileSync({ mode: 420, prefix: "prefix-", postfix: ".ts" });
931
+ import_fs2.default.writeFileSync(temporaryFile.name, currentSchema.customTypes.join("\n"));
932
+ const compilerOptions = {
933
+ strictNullChecks: true,
934
+ skipLibCheck: true
935
+ // Needed if we are processing ES modules
936
+ };
937
+ const program = TJS.getProgramFromFiles(
938
+ [
939
+ (0, import_path2.resolve)(temporaryFile.name),
940
+ import_path2.default.join(restura.resturaConfig.generatedTypesPath, "restura.d.ts"),
941
+ import_path2.default.join(restura.resturaConfig.generatedTypesPath, "models.d.ts"),
942
+ import_path2.default.join(restura.resturaConfig.generatedTypesPath, "api.d.ts")
943
+ ],
944
+ compilerOptions
945
+ );
946
+ customInterfaceNames.forEach((item) => {
947
+ const ddlSchema = TJS.generateSchema(program, item, {
948
+ required: true
949
+ });
950
+ schemaObject[item] = ddlSchema || {};
951
+ });
952
+ temporaryFile.removeCallback();
953
+ return schemaObject;
954
+ }
955
+
956
+ // src/restura/generators/modelGenerator.ts
957
+ var import_core_utils2 = require("@redskytech/core-utils");
958
+ var import_prettier2 = __toESM(require("prettier"));
959
+ function modelGenerator(schema) {
960
+ let modelString = `/** Auto generated file. DO NOT MODIFY **/
961
+
962
+ `;
963
+ modelString += `declare namespace Model {
964
+ `;
965
+ for (const table of schema.database) {
966
+ modelString += convertTable(table);
967
+ }
968
+ modelString += `}`;
969
+ return import_prettier2.default.format(modelString, __spreadValues({
970
+ parser: "typescript"
971
+ }, {
972
+ trailingComma: "none",
973
+ tabWidth: 4,
974
+ useTabs: true,
975
+ endOfLine: "lf",
976
+ printWidth: 120,
977
+ singleQuote: true
978
+ }));
979
+ }
980
+ function convertTable(table) {
981
+ let modelString = ` export interface ${import_core_utils2.StringUtils.capitalizeFirst(table.name)} {
982
+ `;
983
+ for (const column of table.columns) {
984
+ modelString += ` ${column.name}${column.isNullable ? "?" : ""}: ${SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value)};
985
+ `;
986
+ }
987
+ modelString += ` }
988
+ `;
989
+ return modelString;
990
+ }
991
+
992
+ // src/restura/generators/resturaGlobalTypesGenerator.ts
993
+ function resturaGlobalTypesGenerator() {
994
+ return `/** Auto generated file. DO NOT MODIFY **/
995
+ /** This file contains types that may be used in the CustomTypes of Restura **/
996
+ /** For example export interface MyPagedQuery extends Restura.PageQuery { } **/
997
+
998
+ declare namespace Restura {
999
+ export type StandardOrderTypes = 'ASC' | 'DESC' | 'RAND' | 'NONE';
1000
+ export interface PageQuery {
1001
+ page?: number;
1002
+ perPage?: number;
1003
+ sortBy?: string;
1004
+ sortOrder?: StandardOrderTypes;
1005
+ filter?: string;
1006
+ }
1007
+ }
1008
+ `;
1009
+ }
1010
+
598
1011
  // src/restura/middleware/addApiResponseFunctions.ts
599
1012
  function addApiResponseFunctions(req, res, next) {
600
1013
  res.sendData = function(data, statusCode = 200) {
@@ -623,12 +1036,45 @@ function addApiResponseFunctions(req, res, next) {
623
1036
  next();
624
1037
  }
625
1038
 
626
- // src/restura/validateRequestParams.ts
627
- var import_core_utils2 = require("@redskytech/core-utils");
628
- var import_jsonschema = __toESM(require("jsonschema"));
1039
+ // src/restura/middleware/authenticateUser.ts
1040
+ function authenticateUser(applicationAuthenticateHandler) {
1041
+ return (req, res, next) => {
1042
+ applicationAuthenticateHandler(req, res, (userDetails) => {
1043
+ req.requesterDetails = __spreadValues({ host: req.hostname, ipAddress: req.ip || "" }, userDetails);
1044
+ next();
1045
+ });
1046
+ };
1047
+ }
1048
+
1049
+ // src/restura/middleware/getMulterUpload.ts
1050
+ var import_multer = __toESM(require("multer"));
1051
+ var os = __toESM(require("os"));
1052
+ var import_path3 = require("path");
1053
+ var OneHundredMB = 100 * 1024 * 1024;
1054
+ var commonUpload = null;
1055
+ var getMulterUpload = (directory) => {
1056
+ if (commonUpload) return commonUpload;
1057
+ const storage = import_multer.default.diskStorage({
1058
+ destination: directory || os.tmpdir(),
1059
+ filename: function(request, file, cb) {
1060
+ const extension = (0, import_path3.extname)(file.originalname);
1061
+ const uniqueName = Date.now() + "-" + Math.round(Math.random() * 1e3);
1062
+ cb(null, `${uniqueName}${extension}`);
1063
+ }
1064
+ });
1065
+ commonUpload = (0, import_multer.default)({
1066
+ storage,
1067
+ limits: {
1068
+ fileSize: OneHundredMB
1069
+ }
1070
+ });
1071
+ return commonUpload;
1072
+ };
1073
+
1074
+ // src/restura/schemas/resturaSchema.ts
629
1075
  var import_zod3 = require("zod");
630
1076
 
631
- // src/restura/types/validation.types.ts
1077
+ // src/restura/schemas/validatorDataSchema.ts
632
1078
  var import_zod2 = require("zod");
633
1079
  var validatorDataSchemeValue = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.array(import_zod2.z.string()), import_zod2.z.number(), import_zod2.z.array(import_zod2.z.number())]);
634
1080
  var validatorDataSchema = import_zod2.z.object({
@@ -636,264 +1082,84 @@ var validatorDataSchema = import_zod2.z.object({
636
1082
  value: validatorDataSchemeValue
637
1083
  }).strict();
638
1084
 
639
- // src/restura/utils/addQuotesToStrings.ts
640
- function addQuotesToStrings(variable) {
641
- if (typeof variable === "string") {
642
- return `'${variable}'`;
643
- } else if (Array.isArray(variable)) {
644
- const arrayWithQuotes = variable.map(addQuotesToStrings);
645
- return arrayWithQuotes;
646
- } else {
647
- return variable;
648
- }
649
- }
650
-
651
- // src/restura/validateRequestParams.ts
652
- function validateRequestParams(req, routeData, validationSchema) {
653
- const requestData = getRequestData(req);
654
- req.data = requestData;
655
- if (routeData.request === void 0) {
656
- if (routeData.type !== "CUSTOM_ONE" && routeData.type !== "CUSTOM_ARRAY" && routeData.type !== "CUSTOM_PAGED")
657
- throw new RsError("BAD_REQUEST", `No request parameters provided for standard request.`);
658
- if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
659
- if (!routeData.requestType) throw new RsError("BAD_REQUEST", `No request type defined for custom request.`);
660
- const currentInterface = validationSchema[routeData.requestType];
661
- const validator = new import_jsonschema.default.Validator();
662
- const executeValidation = validator.validate(req.data, currentInterface);
663
- if (!executeValidation.valid) {
664
- throw new RsError(
665
- "BAD_REQUEST",
666
- `Request custom setup has failed the following check: (${executeValidation.errors})`
667
- );
668
- }
669
- return;
670
- }
671
- Object.keys(req.data).forEach((requestParamName) => {
672
- const requestParam = routeData.request.find((param) => param.name === requestParamName);
673
- if (!requestParam) {
674
- throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not allowed`);
675
- }
676
- });
677
- routeData.request.forEach((requestParam) => {
678
- const requestValue = requestData[requestParam.name];
679
- if (requestParam.required && requestValue === void 0)
680
- throw new RsError("BAD_REQUEST", `Request param (${requestParam.name}) is required but missing`);
681
- else if (!requestParam.required && requestValue === void 0) return;
682
- validateRequestSingleParam(requestValue, requestParam);
683
- });
684
- }
685
- function validateRequestSingleParam(requestValue, requestParam) {
686
- requestParam.validator.forEach((validator) => {
687
- switch (validator.type) {
688
- case "TYPE_CHECK":
689
- performTypeCheck(requestValue, validator, requestParam.name);
690
- break;
691
- case "MIN":
692
- performMinCheck(requestValue, validator, requestParam.name);
693
- break;
694
- case "MAX":
695
- performMaxCheck(requestValue, validator, requestParam.name);
696
- break;
697
- case "ONE_OF":
698
- performOneOfCheck(requestValue, validator, requestParam.name);
699
- break;
700
- }
701
- });
702
- }
703
- function isValidType(type, requestValue) {
704
- try {
705
- expectValidType(type, requestValue);
706
- return true;
707
- } catch (e) {
708
- return false;
709
- }
710
- }
711
- function expectValidType(type, requestValue) {
712
- if (type === "number") {
713
- return import_zod3.z.number().parse(requestValue);
714
- }
715
- if (type === "string") {
716
- return import_zod3.z.string().parse(requestValue);
717
- }
718
- if (type === "boolean") {
719
- return import_zod3.z.boolean().parse(requestValue);
720
- }
721
- if (type === "string[]") {
722
- return import_zod3.z.array(import_zod3.z.string()).parse(requestValue);
723
- }
724
- if (type === "number[]") {
725
- return import_zod3.z.array(import_zod3.z.number()).parse(requestValue);
726
- }
727
- if (type === "any[]") {
728
- return import_zod3.z.array(import_zod3.z.any()).parse(requestValue);
729
- }
730
- if (type === "object") {
731
- return import_zod3.z.object({}).strict().parse(requestValue);
732
- }
733
- }
734
- function performTypeCheck(requestValue, validator, requestParamName) {
735
- if (!isValidType(validator.value, requestValue)) {
736
- throw new RsError(
737
- "BAD_REQUEST",
738
- `Request param (${requestParamName}) with value (${addQuotesToStrings(requestValue)}) is not of type (${validator.value})`
739
- );
740
- }
741
- try {
742
- validatorDataSchemeValue.parse(validator.value);
743
- } catch (e) {
744
- throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not a valid type`);
745
- }
746
- }
747
- function expectOnlyNumbers(requestValue, validator, requestParamName) {
748
- if (!isValueNumber(requestValue))
749
- throw new RsError(
750
- "BAD_REQUEST",
751
- `Request param (${requestParamName}) with value (${requestValue}) is not of type number`
752
- );
753
- if (!isValueNumber(validator.value))
754
- throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value} is not of type number`);
755
- }
756
- function performMinCheck(requestValue, validator, requestParamName) {
757
- expectOnlyNumbers(requestValue, validator, requestParamName);
758
- if (requestValue < validator.value)
759
- throw new RsError(
760
- "BAD_REQUEST",
761
- `Request param (${requestParamName}) with value (${requestValue}) is less than (${validator.value})`
762
- );
763
- }
764
- function performMaxCheck(requestValue, validator, requestParamName) {
765
- expectOnlyNumbers(requestValue, validator, requestParamName);
766
- if (requestValue > validator.value)
767
- throw new RsError(
768
- "BAD_REQUEST",
769
- `Request param (${requestParamName}) with value (${requestValue}) is more than (${validator.value})`
770
- );
771
- }
772
- function performOneOfCheck(requestValue, validator, requestParamName) {
773
- if (!import_core_utils2.ObjectUtils.isArrayWithData(validator.value))
774
- throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not of type array`);
775
- if (typeof requestValue === "object")
776
- throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not of type string or number`);
777
- if (!validator.value.includes(requestValue))
778
- throw new RsError(
779
- "BAD_REQUEST",
780
- `Request param (${requestParamName}) with value (${requestValue}) is not one of (${validator.value.join(", ")})`
781
- );
782
- }
783
- function isValueNumber(value) {
784
- return !isNaN(Number(value));
785
- }
786
- function getRequestData(req) {
787
- let body = "";
788
- if (req.method === "GET" || req.method === "DELETE") {
789
- body = "query";
790
- } else {
791
- body = "body";
792
- }
793
- const bodyData = req[body];
794
- if (bodyData) {
795
- for (const attr in bodyData) {
796
- if (attr === "token") {
797
- delete bodyData[attr];
798
- continue;
799
- }
800
- if (bodyData[attr] instanceof Array) {
801
- const attrList = [];
802
- for (const value of bodyData[attr]) {
803
- if (isNaN(Number(value))) continue;
804
- attrList.push(Number(value));
805
- }
806
- if (import_core_utils2.ObjectUtils.isArrayWithData(attrList)) {
807
- bodyData[attr] = attrList;
808
- }
809
- } else {
810
- bodyData[attr] = import_core_utils2.ObjectUtils.safeParse(bodyData[attr]);
811
- if (isNaN(Number(bodyData[attr]))) continue;
812
- bodyData[attr] = Number(bodyData[attr]);
813
- }
814
- }
815
- }
816
- return bodyData;
817
- }
818
-
819
- // src/restura/restura.schema.ts
820
- var import_zod4 = require("zod");
821
- var orderBySchema = import_zod4.z.object({
822
- columnName: import_zod4.z.string(),
823
- order: import_zod4.z.enum(["ASC", "DESC"]),
824
- tableName: import_zod4.z.string()
1085
+ // src/restura/schemas/resturaSchema.ts
1086
+ var orderBySchema = import_zod3.z.object({
1087
+ columnName: import_zod3.z.string(),
1088
+ order: import_zod3.z.enum(["ASC", "DESC"]),
1089
+ tableName: import_zod3.z.string()
825
1090
  }).strict();
826
- var groupBySchema = import_zod4.z.object({
827
- columnName: import_zod4.z.string(),
828
- tableName: import_zod4.z.string()
1091
+ var groupBySchema = import_zod3.z.object({
1092
+ columnName: import_zod3.z.string(),
1093
+ tableName: import_zod3.z.string()
829
1094
  }).strict();
830
- var whereDataSchema = import_zod4.z.object({
831
- tableName: import_zod4.z.string().optional(),
832
- columnName: import_zod4.z.string().optional(),
833
- operator: import_zod4.z.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH"]).optional(),
834
- value: import_zod4.z.string().or(import_zod4.z.number()).optional(),
835
- custom: import_zod4.z.string().optional(),
836
- conjunction: import_zod4.z.enum(["AND", "OR"]).optional()
1095
+ var whereDataSchema = import_zod3.z.object({
1096
+ tableName: import_zod3.z.string().optional(),
1097
+ columnName: import_zod3.z.string().optional(),
1098
+ operator: import_zod3.z.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH", "IS", "IS NOT"]).optional(),
1099
+ value: import_zod3.z.string().or(import_zod3.z.number()).optional(),
1100
+ custom: import_zod3.z.string().optional(),
1101
+ conjunction: import_zod3.z.enum(["AND", "OR"]).optional()
837
1102
  }).strict();
838
- var assignmentDataSchema = import_zod4.z.object({
839
- name: import_zod4.z.string(),
840
- value: import_zod4.z.string()
1103
+ var assignmentDataSchema = import_zod3.z.object({
1104
+ name: import_zod3.z.string(),
1105
+ value: import_zod3.z.string()
841
1106
  }).strict();
842
- var joinDataSchema = import_zod4.z.object({
843
- table: import_zod4.z.string(),
844
- localColumnName: import_zod4.z.string().optional(),
845
- foreignColumnName: import_zod4.z.string().optional(),
846
- custom: import_zod4.z.string().optional(),
847
- type: import_zod4.z.enum(["LEFT", "INNER"]),
848
- alias: import_zod4.z.string().optional()
1107
+ var joinDataSchema = import_zod3.z.object({
1108
+ table: import_zod3.z.string(),
1109
+ localColumnName: import_zod3.z.string().optional(),
1110
+ foreignColumnName: import_zod3.z.string().optional(),
1111
+ custom: import_zod3.z.string().optional(),
1112
+ type: import_zod3.z.enum(["LEFT", "INNER"]),
1113
+ alias: import_zod3.z.string().optional()
849
1114
  }).strict();
850
- var requestDataSchema = import_zod4.z.object({
851
- name: import_zod4.z.string(),
852
- required: import_zod4.z.boolean(),
853
- validator: import_zod4.z.array(validatorDataSchema)
1115
+ var requestDataSchema = import_zod3.z.object({
1116
+ name: import_zod3.z.string(),
1117
+ required: import_zod3.z.boolean(),
1118
+ isNullable: import_zod3.z.boolean().optional(),
1119
+ validator: import_zod3.z.array(validatorDataSchema)
854
1120
  }).strict();
855
- var responseDataSchema = import_zod4.z.object({
856
- name: import_zod4.z.string(),
857
- selector: import_zod4.z.string().optional(),
858
- subquery: import_zod4.z.object({
859
- table: import_zod4.z.string(),
860
- joins: import_zod4.z.array(joinDataSchema),
861
- where: import_zod4.z.array(whereDataSchema),
862
- properties: import_zod4.z.array(import_zod4.z.lazy(() => responseDataSchema)),
1121
+ var responseDataSchema = import_zod3.z.object({
1122
+ name: import_zod3.z.string(),
1123
+ selector: import_zod3.z.string().optional(),
1124
+ subquery: import_zod3.z.object({
1125
+ table: import_zod3.z.string(),
1126
+ joins: import_zod3.z.array(joinDataSchema),
1127
+ where: import_zod3.z.array(whereDataSchema),
1128
+ properties: import_zod3.z.array(import_zod3.z.lazy(() => responseDataSchema)),
863
1129
  // Explicit type for the lazy schema
864
1130
  groupBy: groupBySchema.optional(),
865
1131
  orderBy: orderBySchema.optional()
866
1132
  }).optional()
867
1133
  }).strict();
868
- var routeDataBaseSchema = import_zod4.z.object({
869
- method: import_zod4.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
870
- name: import_zod4.z.string(),
871
- description: import_zod4.z.string(),
872
- path: import_zod4.z.string(),
873
- roles: import_zod4.z.array(import_zod4.z.string())
1134
+ var routeDataBaseSchema = import_zod3.z.object({
1135
+ method: import_zod3.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
1136
+ name: import_zod3.z.string(),
1137
+ description: import_zod3.z.string(),
1138
+ path: import_zod3.z.string(),
1139
+ roles: import_zod3.z.array(import_zod3.z.string())
874
1140
  }).strict();
875
1141
  var standardRouteSchema = routeDataBaseSchema.extend({
876
- type: import_zod4.z.enum(["ONE", "ARRAY", "PAGED"]),
877
- table: import_zod4.z.string(),
878
- joins: import_zod4.z.array(joinDataSchema),
879
- assignments: import_zod4.z.array(assignmentDataSchema),
880
- where: import_zod4.z.array(whereDataSchema),
881
- request: import_zod4.z.array(requestDataSchema),
882
- response: import_zod4.z.array(responseDataSchema),
1142
+ type: import_zod3.z.enum(["ONE", "ARRAY", "PAGED"]),
1143
+ table: import_zod3.z.string(),
1144
+ joins: import_zod3.z.array(joinDataSchema),
1145
+ assignments: import_zod3.z.array(assignmentDataSchema),
1146
+ where: import_zod3.z.array(whereDataSchema),
1147
+ request: import_zod3.z.array(requestDataSchema),
1148
+ response: import_zod3.z.array(responseDataSchema),
883
1149
  groupBy: groupBySchema.optional(),
884
1150
  orderBy: orderBySchema.optional()
885
1151
  }).strict();
886
1152
  var customRouteSchema = routeDataBaseSchema.extend({
887
- type: import_zod4.z.enum(["CUSTOM_ONE", "CUSTOM_ARRAY", "CUSTOM_PAGED"]),
888
- responseType: import_zod4.z.union([import_zod4.z.string(), import_zod4.z.enum(["string", "number", "boolean"])]),
889
- requestType: import_zod4.z.string().optional(),
890
- request: import_zod4.z.array(requestDataSchema).optional(),
891
- table: import_zod4.z.undefined(),
892
- joins: import_zod4.z.undefined(),
893
- assignments: import_zod4.z.undefined(),
894
- fileUploadType: import_zod4.z.enum(["SINGLE", "MULTIPLE"]).optional()
1153
+ type: import_zod3.z.enum(["CUSTOM_ONE", "CUSTOM_ARRAY", "CUSTOM_PAGED"]),
1154
+ responseType: import_zod3.z.union([import_zod3.z.string(), import_zod3.z.enum(["string", "number", "boolean"])]),
1155
+ requestType: import_zod3.z.string().optional(),
1156
+ request: import_zod3.z.array(requestDataSchema).optional(),
1157
+ table: import_zod3.z.undefined(),
1158
+ joins: import_zod3.z.undefined(),
1159
+ assignments: import_zod3.z.undefined(),
1160
+ fileUploadType: import_zod3.z.enum(["SINGLE", "MULTIPLE"]).optional()
895
1161
  }).strict();
896
- var postgresColumnNumericTypesSchema = import_zod4.z.enum([
1162
+ var postgresColumnNumericTypesSchema = import_zod3.z.enum([
897
1163
  "SMALLINT",
898
1164
  // 2 bytes, -32,768 to 32,767
899
1165
  "INTEGER",
@@ -913,7 +1179,7 @@ var postgresColumnNumericTypesSchema = import_zod4.z.enum([
913
1179
  "BIGSERIAL"
914
1180
  // auto-incrementing big integer
915
1181
  ]);
916
- var postgresColumnStringTypesSchema = import_zod4.z.enum([
1182
+ var postgresColumnStringTypesSchema = import_zod3.z.enum([
917
1183
  "CHAR",
918
1184
  // fixed-length, blank-padded
919
1185
  "VARCHAR",
@@ -923,7 +1189,7 @@ var postgresColumnStringTypesSchema = import_zod4.z.enum([
923
1189
  "BYTEA"
924
1190
  // binary data
925
1191
  ]);
926
- var postgresColumnDateTypesSchema = import_zod4.z.enum([
1192
+ var postgresColumnDateTypesSchema = import_zod3.z.enum([
927
1193
  "DATE",
928
1194
  // calendar date (year, month, day)
929
1195
  "TIMESTAMP",
@@ -935,7 +1201,13 @@ var postgresColumnDateTypesSchema = import_zod4.z.enum([
935
1201
  "INTERVAL"
936
1202
  // time span
937
1203
  ]);
938
- var mariaDbColumnNumericTypesSchema = import_zod4.z.enum([
1204
+ var postgresColumnJsonTypesSchema = import_zod3.z.enum([
1205
+ "JSON",
1206
+ // stores JSON data as raw text
1207
+ "JSONB"
1208
+ // stores JSON data in a binary format, optimized for query performance
1209
+ ]);
1210
+ var mariaDbColumnNumericTypesSchema = import_zod3.z.enum([
939
1211
  "BOOLEAN",
940
1212
  // 1-byte A synonym for "TINYINT(1)". Supported from version 1.2.0 onwards.
941
1213
  "TINYINT",
@@ -955,7 +1227,7 @@ var mariaDbColumnNumericTypesSchema = import_zod4.z.enum([
955
1227
  "DOUBLE"
956
1228
  // 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.
957
1229
  ]);
958
- var mariaDbColumnStringTypesSchema = import_zod4.z.enum([
1230
+ var mariaDbColumnStringTypesSchema = import_zod3.z.enum([
959
1231
  "CHAR",
960
1232
  // 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.
961
1233
  "VARCHAR",
@@ -981,7 +1253,7 @@ var mariaDbColumnStringTypesSchema = import_zod4.z.enum([
981
1253
  "ENUM"
982
1254
  // Enum type
983
1255
  ]);
984
- var mariaDbColumnDateTypesSchema = import_zod4.z.enum([
1256
+ var mariaDbColumnDateTypesSchema = import_zod3.z.enum([
985
1257
  "DATE",
986
1258
  // 4-bytes Date has year, month, and day.
987
1259
  "DATETIME",
@@ -991,34 +1263,35 @@ var mariaDbColumnDateTypesSchema = import_zod4.z.enum([
991
1263
  "TIMESTAMP"
992
1264
  // 4-bytes Values are stored as the number of seconds since 1970-01-01 00:00:00 UTC, and optionally microseconds.
993
1265
  ]);
994
- var columnDataSchema = import_zod4.z.object({
995
- name: import_zod4.z.string(),
996
- type: import_zod4.z.union([
1266
+ var columnDataSchema = import_zod3.z.object({
1267
+ name: import_zod3.z.string(),
1268
+ type: import_zod3.z.union([
997
1269
  postgresColumnNumericTypesSchema,
998
1270
  postgresColumnStringTypesSchema,
999
1271
  postgresColumnDateTypesSchema,
1272
+ postgresColumnJsonTypesSchema,
1000
1273
  mariaDbColumnNumericTypesSchema,
1001
1274
  mariaDbColumnStringTypesSchema,
1002
1275
  mariaDbColumnDateTypesSchema
1003
1276
  ]),
1004
- isNullable: import_zod4.z.boolean(),
1005
- roles: import_zod4.z.array(import_zod4.z.string()),
1006
- comment: import_zod4.z.string().optional(),
1007
- default: import_zod4.z.string().optional(),
1008
- value: import_zod4.z.string().optional(),
1009
- isPrimary: import_zod4.z.boolean().optional(),
1010
- isUnique: import_zod4.z.boolean().optional(),
1011
- hasAutoIncrement: import_zod4.z.boolean().optional(),
1012
- length: import_zod4.z.number().optional()
1277
+ isNullable: import_zod3.z.boolean(),
1278
+ roles: import_zod3.z.array(import_zod3.z.string()),
1279
+ comment: import_zod3.z.string().optional(),
1280
+ default: import_zod3.z.string().optional(),
1281
+ value: import_zod3.z.string().optional(),
1282
+ isPrimary: import_zod3.z.boolean().optional(),
1283
+ isUnique: import_zod3.z.boolean().optional(),
1284
+ hasAutoIncrement: import_zod3.z.boolean().optional(),
1285
+ length: import_zod3.z.number().optional()
1013
1286
  }).strict();
1014
- var indexDataSchema = import_zod4.z.object({
1015
- name: import_zod4.z.string(),
1016
- columns: import_zod4.z.array(import_zod4.z.string()),
1017
- isUnique: import_zod4.z.boolean(),
1018
- isPrimaryKey: import_zod4.z.boolean(),
1019
- order: import_zod4.z.enum(["ASC", "DESC"])
1287
+ var indexDataSchema = import_zod3.z.object({
1288
+ name: import_zod3.z.string(),
1289
+ columns: import_zod3.z.array(import_zod3.z.string()),
1290
+ isUnique: import_zod3.z.boolean(),
1291
+ isPrimaryKey: import_zod3.z.boolean(),
1292
+ order: import_zod3.z.enum(["ASC", "DESC"])
1020
1293
  }).strict();
1021
- var foreignKeyActionsSchema = import_zod4.z.enum([
1294
+ var foreignKeyActionsSchema = import_zod3.z.enum([
1022
1295
  "CASCADE",
1023
1296
  // CASCADE action for foreign keys
1024
1297
  "SET NULL",
@@ -1030,42 +1303,43 @@ var foreignKeyActionsSchema = import_zod4.z.enum([
1030
1303
  "SET DEFAULT"
1031
1304
  // SET DEFAULT action for foreign keys
1032
1305
  ]);
1033
- var foreignKeyDataSchema = import_zod4.z.object({
1034
- name: import_zod4.z.string(),
1035
- column: import_zod4.z.string(),
1036
- refTable: import_zod4.z.string(),
1037
- refColumn: import_zod4.z.string(),
1306
+ var foreignKeyDataSchema = import_zod3.z.object({
1307
+ name: import_zod3.z.string(),
1308
+ column: import_zod3.z.string(),
1309
+ refTable: import_zod3.z.string(),
1310
+ refColumn: import_zod3.z.string(),
1038
1311
  onDelete: foreignKeyActionsSchema,
1039
1312
  onUpdate: foreignKeyActionsSchema
1040
1313
  }).strict();
1041
- var checkConstraintDataSchema = import_zod4.z.object({
1042
- name: import_zod4.z.string(),
1043
- check: import_zod4.z.string()
1314
+ var checkConstraintDataSchema = import_zod3.z.object({
1315
+ name: import_zod3.z.string(),
1316
+ check: import_zod3.z.string()
1044
1317
  }).strict();
1045
- var tableDataSchema = import_zod4.z.object({
1046
- name: import_zod4.z.string(),
1047
- columns: import_zod4.z.array(columnDataSchema),
1048
- indexes: import_zod4.z.array(indexDataSchema),
1049
- foreignKeys: import_zod4.z.array(foreignKeyDataSchema),
1050
- checkConstraints: import_zod4.z.array(checkConstraintDataSchema),
1051
- roles: import_zod4.z.array(import_zod4.z.string())
1318
+ var tableDataSchema = import_zod3.z.object({
1319
+ name: import_zod3.z.string(),
1320
+ columns: import_zod3.z.array(columnDataSchema),
1321
+ indexes: import_zod3.z.array(indexDataSchema),
1322
+ foreignKeys: import_zod3.z.array(foreignKeyDataSchema),
1323
+ checkConstraints: import_zod3.z.array(checkConstraintDataSchema),
1324
+ roles: import_zod3.z.array(import_zod3.z.string()),
1325
+ notify: import_zod3.z.union([import_zod3.z.literal("ALL"), import_zod3.z.array(import_zod3.z.string())]).optional()
1052
1326
  }).strict();
1053
- var endpointDataSchema = import_zod4.z.object({
1054
- name: import_zod4.z.string(),
1055
- description: import_zod4.z.string(),
1056
- baseUrl: import_zod4.z.string(),
1057
- routes: import_zod4.z.array(import_zod4.z.union([standardRouteSchema, customRouteSchema]))
1327
+ var endpointDataSchema = import_zod3.z.object({
1328
+ name: import_zod3.z.string(),
1329
+ description: import_zod3.z.string(),
1330
+ baseUrl: import_zod3.z.string(),
1331
+ routes: import_zod3.z.array(import_zod3.z.union([standardRouteSchema, customRouteSchema]))
1058
1332
  }).strict();
1059
- var resturaZodSchema = import_zod4.z.object({
1060
- database: import_zod4.z.array(tableDataSchema),
1061
- endpoints: import_zod4.z.array(endpointDataSchema),
1062
- globalParams: import_zod4.z.array(import_zod4.z.string()),
1063
- roles: import_zod4.z.array(import_zod4.z.string()),
1064
- customTypes: import_zod4.z.string()
1333
+ var resturaSchema = import_zod3.z.object({
1334
+ database: import_zod3.z.array(tableDataSchema),
1335
+ endpoints: import_zod3.z.array(endpointDataSchema),
1336
+ globalParams: import_zod3.z.array(import_zod3.z.string()),
1337
+ roles: import_zod3.z.array(import_zod3.z.string()),
1338
+ customTypes: import_zod3.z.array(import_zod3.z.string())
1065
1339
  }).strict();
1066
1340
  async function isSchemaValid(schemaToCheck) {
1067
1341
  try {
1068
- resturaZodSchema.parse(schemaToCheck);
1342
+ resturaSchema.parse(schemaToCheck);
1069
1343
  return true;
1070
1344
  } catch (error) {
1071
1345
  logger.error(error);
@@ -1073,11 +1347,210 @@ async function isSchemaValid(schemaToCheck) {
1073
1347
  }
1074
1348
  }
1075
1349
 
1350
+ // src/restura/validators/requestValidator.ts
1351
+ var import_core_utils3 = require("@redskytech/core-utils");
1352
+ var import_jsonschema = __toESM(require("jsonschema"));
1353
+ var import_zod4 = require("zod");
1354
+
1355
+ // src/restura/utils/utils.ts
1356
+ function addQuotesToStrings(variable) {
1357
+ if (typeof variable === "string") {
1358
+ return `'${variable}'`;
1359
+ } else if (Array.isArray(variable)) {
1360
+ const arrayWithQuotes = variable.map(addQuotesToStrings);
1361
+ return arrayWithQuotes;
1362
+ } else {
1363
+ return variable;
1364
+ }
1365
+ }
1366
+ function sortObjectKeysAlphabetically(obj) {
1367
+ if (Array.isArray(obj)) {
1368
+ return obj.map(sortObjectKeysAlphabetically);
1369
+ } else if (obj !== null && typeof obj === "object") {
1370
+ return Object.keys(obj).sort().reduce((sorted, key) => {
1371
+ sorted[key] = sortObjectKeysAlphabetically(obj[key]);
1372
+ return sorted;
1373
+ }, {});
1374
+ }
1375
+ return obj;
1376
+ }
1377
+
1378
+ // src/restura/validators/requestValidator.ts
1379
+ function requestValidator(req, routeData, validationSchema) {
1380
+ const requestData = getRequestData(req);
1381
+ req.data = requestData;
1382
+ if (routeData.request === void 0) {
1383
+ if (routeData.type !== "CUSTOM_ONE" && routeData.type !== "CUSTOM_ARRAY" && routeData.type !== "CUSTOM_PAGED")
1384
+ throw new RsError("BAD_REQUEST", `No request parameters provided for standard request.`);
1385
+ if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
1386
+ if (!routeData.requestType) throw new RsError("BAD_REQUEST", `No request type defined for custom request.`);
1387
+ const currentInterface = validationSchema[routeData.requestType];
1388
+ const validator = new import_jsonschema.default.Validator();
1389
+ const executeValidation = validator.validate(req.data, currentInterface);
1390
+ if (!executeValidation.valid) {
1391
+ throw new RsError(
1392
+ "BAD_REQUEST",
1393
+ `Request custom setup has failed the following check: (${executeValidation.errors})`
1394
+ );
1395
+ }
1396
+ return;
1397
+ }
1398
+ Object.keys(req.data).forEach((requestParamName) => {
1399
+ const requestParam = routeData.request.find((param) => param.name === requestParamName);
1400
+ if (!requestParam) {
1401
+ throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not allowed`);
1402
+ }
1403
+ });
1404
+ routeData.request.forEach((requestParam) => {
1405
+ const requestValue = requestData[requestParam.name];
1406
+ if (requestParam.required && requestValue === void 0)
1407
+ throw new RsError("BAD_REQUEST", `Request param (${requestParam.name}) is required but missing`);
1408
+ else if (!requestParam.required && requestValue === void 0) return;
1409
+ validateRequestSingleParam(requestValue, requestParam);
1410
+ });
1411
+ }
1412
+ function validateRequestSingleParam(requestValue, requestParam) {
1413
+ if (requestParam.isNullable && requestValue === null) return;
1414
+ requestParam.validator.forEach((validator) => {
1415
+ switch (validator.type) {
1416
+ case "TYPE_CHECK":
1417
+ performTypeCheck(requestValue, validator, requestParam.name);
1418
+ break;
1419
+ case "MIN":
1420
+ performMinCheck(requestValue, validator, requestParam.name);
1421
+ break;
1422
+ case "MAX":
1423
+ performMaxCheck(requestValue, validator, requestParam.name);
1424
+ break;
1425
+ case "ONE_OF":
1426
+ performOneOfCheck(requestValue, validator, requestParam.name);
1427
+ break;
1428
+ }
1429
+ });
1430
+ }
1431
+ function isValidType(type, requestValue) {
1432
+ try {
1433
+ expectValidType(type, requestValue);
1434
+ return true;
1435
+ } catch (e) {
1436
+ return false;
1437
+ }
1438
+ }
1439
+ function expectValidType(type, requestValue) {
1440
+ if (type === "number") {
1441
+ return import_zod4.z.number().parse(requestValue);
1442
+ }
1443
+ if (type === "string") {
1444
+ return import_zod4.z.string().parse(requestValue);
1445
+ }
1446
+ if (type === "boolean") {
1447
+ return import_zod4.z.boolean().parse(requestValue);
1448
+ }
1449
+ if (type === "string[]") {
1450
+ return import_zod4.z.array(import_zod4.z.string()).parse(requestValue);
1451
+ }
1452
+ if (type === "number[]") {
1453
+ return import_zod4.z.array(import_zod4.z.number()).parse(requestValue);
1454
+ }
1455
+ if (type === "any[]") {
1456
+ return import_zod4.z.array(import_zod4.z.any()).parse(requestValue);
1457
+ }
1458
+ if (type === "object") {
1459
+ return import_zod4.z.object({}).strict().parse(requestValue);
1460
+ }
1461
+ }
1462
+ function performTypeCheck(requestValue, validator, requestParamName) {
1463
+ if (!isValidType(validator.value, requestValue)) {
1464
+ throw new RsError(
1465
+ "BAD_REQUEST",
1466
+ `Request param (${requestParamName}) with value (${addQuotesToStrings(requestValue)}) is not of type (${validator.value})`
1467
+ );
1468
+ }
1469
+ try {
1470
+ validatorDataSchemeValue.parse(validator.value);
1471
+ } catch (e) {
1472
+ throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not a valid type`);
1473
+ }
1474
+ }
1475
+ function expectOnlyNumbers(requestValue, validator, requestParamName) {
1476
+ if (!isValueNumber(requestValue))
1477
+ throw new RsError(
1478
+ "BAD_REQUEST",
1479
+ `Request param (${requestParamName}) with value (${requestValue}) is not of type number`
1480
+ );
1481
+ if (!isValueNumber(validator.value))
1482
+ throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value} is not of type number`);
1483
+ }
1484
+ function performMinCheck(requestValue, validator, requestParamName) {
1485
+ expectOnlyNumbers(requestValue, validator, requestParamName);
1486
+ if (requestValue < validator.value)
1487
+ throw new RsError(
1488
+ "BAD_REQUEST",
1489
+ `Request param (${requestParamName}) with value (${requestValue}) is less than (${validator.value})`
1490
+ );
1491
+ }
1492
+ function performMaxCheck(requestValue, validator, requestParamName) {
1493
+ expectOnlyNumbers(requestValue, validator, requestParamName);
1494
+ if (requestValue > validator.value)
1495
+ throw new RsError(
1496
+ "BAD_REQUEST",
1497
+ `Request param (${requestParamName}) with value (${requestValue}) is more than (${validator.value})`
1498
+ );
1499
+ }
1500
+ function performOneOfCheck(requestValue, validator, requestParamName) {
1501
+ if (!import_core_utils3.ObjectUtils.isArrayWithData(validator.value))
1502
+ throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not of type array`);
1503
+ if (typeof requestValue === "object")
1504
+ throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not of type string or number`);
1505
+ if (!validator.value.includes(requestValue))
1506
+ throw new RsError(
1507
+ "BAD_REQUEST",
1508
+ `Request param (${requestParamName}) with value (${requestValue}) is not one of (${validator.value.join(", ")})`
1509
+ );
1510
+ }
1511
+ function isValueNumber(value) {
1512
+ return !isNaN(Number(value));
1513
+ }
1514
+ function getRequestData(req) {
1515
+ let body = "";
1516
+ if (req.method === "GET" || req.method === "DELETE") {
1517
+ body = "query";
1518
+ } else {
1519
+ body = "body";
1520
+ }
1521
+ const bodyData = req[body];
1522
+ if (bodyData && body === "query") {
1523
+ for (const attr in bodyData) {
1524
+ if (bodyData[attr] instanceof Array) {
1525
+ const attrList = [];
1526
+ for (const value of bodyData[attr]) {
1527
+ if (isNaN(Number(value))) continue;
1528
+ attrList.push(Number(value));
1529
+ }
1530
+ if (import_core_utils3.ObjectUtils.isArrayWithData(attrList)) {
1531
+ bodyData[attr] = attrList;
1532
+ }
1533
+ } else {
1534
+ if (bodyData[attr] === "true") {
1535
+ bodyData[attr] = true;
1536
+ } else if (bodyData[attr] === "false") {
1537
+ bodyData[attr] = false;
1538
+ } else {
1539
+ bodyData[attr] = import_core_utils3.ObjectUtils.safeParse(bodyData[attr]);
1540
+ if (isNaN(Number(bodyData[attr]))) continue;
1541
+ bodyData[attr] = Number(bodyData[attr]);
1542
+ }
1543
+ }
1544
+ }
1545
+ }
1546
+ return bodyData;
1547
+ }
1548
+
1076
1549
  // src/restura/middleware/schemaValidation.ts
1077
1550
  async function schemaValidation(req, res, next) {
1078
1551
  req.data = getRequestData(req);
1079
1552
  try {
1080
- resturaZodSchema.parse(req.data);
1553
+ resturaSchema.parse(req.data);
1081
1554
  next();
1082
1555
  } catch (error) {
1083
1556
  logger.error(error);
@@ -1085,91 +1558,183 @@ async function schemaValidation(req, res, next) {
1085
1558
  }
1086
1559
  }
1087
1560
 
1088
- // src/restura/modelGenerator.ts
1089
- var import_core_utils3 = require("@redskytech/core-utils");
1090
- var import_prettier2 = __toESM(require("prettier"));
1091
- function modelGenerator(schema, schemaHash) {
1092
- let modelString = `/** Auto generated file from Schema Hash (${schemaHash}). DO NOT MODIFY **/
1093
- `;
1094
- modelString += `declare namespace Model {
1095
- `;
1096
- for (const table of schema.database) {
1097
- modelString += convertTable(table);
1098
- }
1099
- modelString += `}`;
1100
- return import_prettier2.default.format(modelString, __spreadValues({
1101
- parser: "typescript"
1102
- }, {
1103
- trailingComma: "none",
1104
- tabWidth: 4,
1105
- useTabs: true,
1106
- endOfLine: "lf",
1107
- printWidth: 120,
1108
- singleQuote: true
1109
- }));
1561
+ // src/restura/schemas/resturaConfigSchema.ts
1562
+ var import_zod5 = require("zod");
1563
+ var _a;
1564
+ var isTsx = (_a = process.argv[1]) == null ? void 0 : _a.endsWith(".ts");
1565
+ var isTsNode = process.env.TS_NODE_DEV || process.env.TS_NODE_PROJECT;
1566
+ var customApiFolderPath = isTsx || isTsNode ? "/src/api" : "/dist/api";
1567
+ var resturaConfigSchema = import_zod5.z.object({
1568
+ authToken: import_zod5.z.string().min(1, "Missing Restura Auth Token"),
1569
+ sendErrorStackTrace: import_zod5.z.boolean().default(false),
1570
+ schemaFilePath: import_zod5.z.string().default(process.cwd() + "/restura.schema.json"),
1571
+ customApiFolderPath: import_zod5.z.string().default(process.cwd() + customApiFolderPath),
1572
+ generatedTypesPath: import_zod5.z.string().default(process.cwd() + "/src/@types"),
1573
+ fileTempCachePath: import_zod5.z.string().optional()
1574
+ });
1575
+
1576
+ // src/restura/sql/PsqlEngine.ts
1577
+ var import_core_utils5 = require("@redskytech/core-utils");
1578
+ var import_pg_diff_sync = __toESM(require("@wmfs/pg-diff-sync"));
1579
+ var import_pg_info = __toESM(require("@wmfs/pg-info"));
1580
+ var import_pg2 = __toESM(require("pg"));
1581
+
1582
+ // src/restura/sql/PsqlPool.ts
1583
+ var import_pg = __toESM(require("pg"));
1584
+
1585
+ // src/restura/sql/PsqlConnection.ts
1586
+ var import_crypto = __toESM(require("crypto"));
1587
+ var import_pg_format2 = __toESM(require("pg-format"));
1588
+
1589
+ // src/restura/sql/PsqlUtils.ts
1590
+ var import_pg_format = __toESM(require("pg-format"));
1591
+ function escapeColumnName(columnName) {
1592
+ if (columnName === void 0) return "";
1593
+ return `"${columnName.replace(/"/g, "")}"`.replace(".", '"."');
1110
1594
  }
1111
- function convertTable(table) {
1112
- let modelString = ` export interface ${import_core_utils3.StringUtils.capitalizeFirst(table.name)} {
1113
- `;
1114
- for (const column of table.columns) {
1115
- modelString += ` ${column.name}${column.isNullable ? "?" : ""}: ${SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value)};
1116
- `;
1595
+ function questionMarksToOrderedParams(query) {
1596
+ let count = 1;
1597
+ let inSingleQuote = false;
1598
+ let inDoubleQuote = false;
1599
+ return query.replace(/('|"|\?)/g, (char) => {
1600
+ if (char === "'") {
1601
+ inSingleQuote = !inSingleQuote && !inDoubleQuote;
1602
+ return char;
1603
+ }
1604
+ if (char === '"') {
1605
+ inDoubleQuote = !inDoubleQuote && !inSingleQuote;
1606
+ return char;
1607
+ }
1608
+ if (char === "?" && !inSingleQuote && !inDoubleQuote) {
1609
+ return `$${count++}`;
1610
+ }
1611
+ return char;
1612
+ });
1613
+ }
1614
+ function insertObjectQuery(table, obj) {
1615
+ const keys = Object.keys(obj);
1616
+ const params = Object.values(obj);
1617
+ const columns = keys.map((column) => escapeColumnName(column)).join(", ");
1618
+ const values = params.map((value) => SQL`${value}`).join(", ");
1619
+ let query = `
1620
+ INSERT INTO "${table}" (${columns})
1621
+ VALUES (${values})
1622
+ RETURNING *`;
1623
+ query = query.replace(/'(\?)'/, "?");
1624
+ return query;
1625
+ }
1626
+ function updateObjectQuery(table, obj, whereStatement) {
1627
+ const setArray = [];
1628
+ for (const i in obj) {
1629
+ setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
1117
1630
  }
1118
- modelString += ` }
1119
- `;
1120
- return modelString;
1631
+ return `
1632
+ UPDATE ${escapeColumnName(table)}
1633
+ SET ${setArray.join(", ")} ${whereStatement}
1634
+ RETURNING *`;
1121
1635
  }
1122
-
1123
- // src/restura/middleware/authenticateUser.ts
1124
- function authenticateUser(applicationAuthenticateHandler) {
1125
- return (req, res, next) => {
1126
- applicationAuthenticateHandler(
1127
- req,
1128
- (userDetails) => {
1129
- req.requesterDetails = __spreadValues(__spreadValues({}, req.requesterDetails), userDetails);
1130
- next();
1131
- },
1132
- (errorMessage) => {
1133
- res.sendError("UNAUTHORIZED", errorMessage);
1134
- }
1135
- );
1136
- };
1636
+ function isValueNumber2(value) {
1637
+ return !isNaN(Number(value));
1638
+ }
1639
+ function SQL(strings, ...values) {
1640
+ let query = strings[0];
1641
+ values.forEach((value, index) => {
1642
+ if (typeof value === "boolean") {
1643
+ query += value;
1644
+ } else if (typeof value === "number") {
1645
+ query += value;
1646
+ } else if (Array.isArray(value)) {
1647
+ query += import_pg_format.default.literal(JSON.stringify(value)) + "::jsonb";
1648
+ } else {
1649
+ query += import_pg_format.default.literal(value);
1650
+ }
1651
+ query += strings[index + 1];
1652
+ });
1653
+ return query;
1137
1654
  }
1138
1655
 
1139
- // src/restura/customTypeValidationGenerator.ts
1140
- var import_fs = __toESM(require("fs"));
1141
- var TJS = __toESM(require("typescript-json-schema"));
1142
- var import_path = __toESM(require("path"));
1143
- var import_tmp = __toESM(require("tmp"));
1144
- var process2 = __toESM(require("process"));
1145
- function customTypeValidationGenerator(currentSchema) {
1146
- const schemaObject = {};
1147
- const customInterfaceNames = currentSchema.customTypes.match(new RegExp("(?<=interface\\s)(\\w+)|(?<=type\\s)(\\w+)", "g"));
1148
- if (!customInterfaceNames) return {};
1149
- const temporaryFile = import_tmp.default.fileSync({ mode: 420, prefix: "prefix-", postfix: ".ts" });
1150
- import_fs.default.writeFileSync(temporaryFile.name, currentSchema.customTypes);
1151
- const compilerOptions = {
1152
- strictNullChecks: true,
1153
- skipLibCheck: true
1154
- };
1155
- const program = TJS.getProgramFromFiles(
1156
- [
1157
- (0, import_path.resolve)(temporaryFile.name),
1158
- // find a way to remove
1159
- import_path.default.join(process2.cwd(), "src/@types/models.d.ts"),
1160
- import_path.default.join(process2.cwd(), "src/@types/api.d.ts")
1161
- ],
1162
- compilerOptions
1163
- );
1164
- customInterfaceNames.forEach((item) => {
1165
- const ddlSchema = TJS.generateSchema(program, item, {
1166
- required: true
1656
+ // src/restura/sql/PsqlConnection.ts
1657
+ var PsqlConnection = class {
1658
+ constructor(instanceId) {
1659
+ this.instanceId = instanceId || import_crypto.default.randomUUID();
1660
+ }
1661
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1662
+ async queryOne(query, options, requesterDetails) {
1663
+ const formattedQuery = questionMarksToOrderedParams(query);
1664
+ const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
1665
+ this.logSqlStatement(formattedQuery, options, meta);
1666
+ const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
1667
+ `;
1668
+ try {
1669
+ const response = await this.query(queryMetadata + formattedQuery, options);
1670
+ if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
1671
+ else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
1672
+ return response.rows[0];
1673
+ } catch (error) {
1674
+ if (RsError.isRsError(error)) throw error;
1675
+ if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
1676
+ throw new RsError("DUPLICATE", error.message);
1677
+ }
1678
+ throw new RsError("DATABASE_ERROR", `${error.message}`);
1679
+ }
1680
+ }
1681
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1682
+ async runQuery(query, options, requesterDetails) {
1683
+ const formattedQuery = questionMarksToOrderedParams(query);
1684
+ const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
1685
+ this.logSqlStatement(formattedQuery, options, meta);
1686
+ const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
1687
+ `;
1688
+ try {
1689
+ const response = await this.query(queryMetadata + formattedQuery, options);
1690
+ return response.rows;
1691
+ } catch (error) {
1692
+ if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
1693
+ throw new RsError("DUPLICATE", error.message);
1694
+ }
1695
+ throw new RsError("DATABASE_ERROR", `${error.message}`);
1696
+ }
1697
+ }
1698
+ logSqlStatement(query, options, queryMetadata, prefix = "") {
1699
+ if (logger.level !== "silly") return;
1700
+ let sqlStatement = "";
1701
+ if (options.length === 0) {
1702
+ sqlStatement = query;
1703
+ } else {
1704
+ let stringIndex = 0;
1705
+ sqlStatement = query.replace(/\$\d+/g, () => {
1706
+ const value = options[stringIndex++];
1707
+ if (typeof value === "number") return value.toString();
1708
+ return import_pg_format2.default.literal(value);
1709
+ });
1710
+ }
1711
+ let initiator = "Anonymous";
1712
+ if ("userId" in queryMetadata && queryMetadata.userId)
1713
+ initiator = `User Id (${queryMetadata.userId.toString()})`;
1714
+ if ("isSystemUser" in queryMetadata && queryMetadata.isSystemUser) initiator = "SYSTEM";
1715
+ logger.silly(`${prefix}query by ${initiator}, Query ->
1716
+ ${sqlStatement}`);
1717
+ }
1718
+ };
1719
+
1720
+ // src/restura/sql/PsqlPool.ts
1721
+ var { Pool } = import_pg.default;
1722
+ var PsqlPool = class extends PsqlConnection {
1723
+ constructor(poolConfig) {
1724
+ super();
1725
+ this.poolConfig = poolConfig;
1726
+ this.pool = new Pool(poolConfig);
1727
+ this.queryOne("SELECT NOW();", [], { isSystemUser: true, role: "", host: "localhost", ipAddress: "" }).then(() => {
1728
+ logger.info("Connected to PostgreSQL database");
1729
+ }).catch((error) => {
1730
+ logger.error("Error connecting to database", error);
1731
+ process.exit(1);
1167
1732
  });
1168
- schemaObject[item] = ddlSchema || {};
1169
- });
1170
- temporaryFile.removeCallback();
1171
- return schemaObject;
1172
- }
1733
+ }
1734
+ async query(query, values) {
1735
+ return this.pool.query(query, values);
1736
+ }
1737
+ };
1173
1738
 
1174
1739
  // src/restura/sql/SqlEngine.ts
1175
1740
  var import_core_utils4 = require("@redskytech/core-utils");
@@ -1237,11 +1802,11 @@ var SqlEngine = class {
1237
1802
  return returnValue;
1238
1803
  }
1239
1804
  replaceLocalParamKeywords(value, routeData, req, sqlParams) {
1240
- var _a;
1805
+ var _a2;
1241
1806
  if (!routeData.request) return value;
1242
1807
  const data = req.data;
1243
1808
  if (typeof value === "string") {
1244
- (_a = value.match(/\$[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a.forEach((param) => {
1809
+ (_a2 = value.match(/\$[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a2.forEach((param) => {
1245
1810
  const requestParam = routeData.request.find((item) => {
1246
1811
  return item.name === param.replace("$", "");
1247
1812
  });
@@ -1254,13 +1819,16 @@ var SqlEngine = class {
1254
1819
  return value;
1255
1820
  }
1256
1821
  replaceGlobalParamKeywords(value, routeData, req, sqlParams) {
1257
- var _a;
1822
+ var _a2;
1258
1823
  if (typeof value === "string") {
1259
- (_a = value.match(/#[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a.forEach((param) => {
1824
+ (_a2 = value.match(/#[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a2.forEach((param) => {
1260
1825
  param = param.replace("#", "");
1261
1826
  const globalParamValue = req.requesterDetails[param];
1262
1827
  if (!globalParamValue)
1263
- throw new RsError("SCHEMA_ERROR", `Invalid global keyword clause in route ${routeData.name}`);
1828
+ throw new RsError(
1829
+ "SCHEMA_ERROR",
1830
+ `Invalid global keyword clause in route (${routeData.path}) when looking for (#${param})`
1831
+ );
1264
1832
  sqlParams.push(globalParamValue);
1265
1833
  });
1266
1834
  return value.replace(new RegExp(/#[a-zA-Z][a-zA-Z0-9_]+/g), "?");
@@ -1269,118 +1837,368 @@ var SqlEngine = class {
1269
1837
  }
1270
1838
  };
1271
1839
 
1272
- // src/restura/sql/filterSqlParser.ts
1840
+ // src/restura/sql/filterPsqlParser.ts
1273
1841
  var import_pegjs = __toESM(require("pegjs"));
1274
1842
  var filterSqlGrammar = `
1843
+ {
1844
+ // ported from pg-format but intentionally will add double quotes to every column
1845
+ function quoteSqlIdentity(value) {
1846
+ if (value === undefined || value === null) {
1847
+ throw new Error('SQL identifier cannot be null or undefined');
1848
+ } else if (value === false) {
1849
+ return '"f"';
1850
+ } else if (value === true) {
1851
+ return '"t"';
1852
+ } else if (value instanceof Date) {
1853
+ // return '"' + formatDate(value.toISOString()) + '"';
1854
+ } else if (value instanceof Buffer) {
1855
+ throw new Error('SQL identifier cannot be a buffer');
1856
+ } else if (Array.isArray(value) === true) {
1857
+ var temp = [];
1858
+ for (var i = 0; i < value.length; i++) {
1859
+ if (Array.isArray(value[i]) === true) {
1860
+ throw new Error('Nested array to grouped list conversion is not supported for SQL identifier');
1861
+ } else {
1862
+ // temp.push(quoteIdent(value[i]));
1863
+ }
1864
+ }
1865
+ return temp.toString();
1866
+ } else if (value === Object(value)) {
1867
+ throw new Error('SQL identifier cannot be an object');
1868
+ }
1869
+
1870
+ var ident = value.toString().slice(0); // create copy
1871
+
1872
+ // do not quote a valid, unquoted identifier
1873
+ // if (/^[a-z_][a-z0-9_$]*$/.test(ident) === true && isReserved(ident) === false) {
1874
+ // return ident;
1875
+ // }
1876
+
1877
+ var quoted = '"';
1878
+
1879
+ for (var i = 0; i < ident.length; i++) {
1880
+ var c = ident[i];
1881
+ if (c === '"') {
1882
+ quoted += c + c;
1883
+ } else {
1884
+ quoted += c;
1885
+ }
1886
+ }
1887
+
1888
+ quoted += '"';
1889
+
1890
+ return quoted;
1891
+ };
1892
+ }
1893
+
1275
1894
  start = expressionList
1276
1895
 
1896
+ _ = [ \\t\\r\\n]* // Matches spaces, tabs, and line breaks
1897
+
1277
1898
  expressionList =
1278
- leftExpression:expression operator:operator rightExpression:expressionList
1899
+ leftExpression:expression _ operator:operator _ rightExpression:expressionList
1279
1900
  { return \`\${leftExpression} \${operator} \${rightExpression}\`;}
1280
1901
  / expression
1281
1902
 
1282
1903
  expression =
1283
- negate:negate?"(" "column:" column:column ","? value:value? ","? type:type? ")"
1284
- {return \`\${negate? "!" : ""}(\${type? type(column, value) : \`\${column} = \${mysql.escape(value)}\`})\`;}
1904
+ negate:negate? _ "(" _ "column" _ ":" column:column _ ","? _ value:value? ","? _ type:type? _ ")"_
1905
+ {return \`\${negate? " NOT " : ""}(\${type? type(column, value) : \`\${column} = \${format.literal(value)}\`})\`;}
1285
1906
  /
1286
- negate:negate?"("expression:expressionList")" { return \`\${negate? "!" : ""}(\${expression})\`; }
1907
+ negate:negate?"("expression:expressionList")" { return \`\${negate? " NOT " : ""}(\${expression})\`; }
1287
1908
 
1288
1909
  negate = "!"
1289
1910
 
1290
1911
  operator = "and"i / "or"i
1291
1912
 
1292
- column = left:text "." right:text { return \`\${mysql.escapeId(left)}.\${mysql.escapeId(right)}\`; }
1293
- /
1294
- text:text { return mysql.escapeId(text); }
1913
+
1914
+ column = left:text "." right:text { return \`\${quoteSqlIdentity(left)}.\${quoteSqlIdentity(right)}\`; }
1915
+ /
1916
+ text:text { return quoteSqlIdentity(text); }
1917
+
1295
1918
 
1919
+ text = text:[a-z0-9 \\t\\r\\n\\-_:@']i+ { return text.join(""); }
1296
1920
 
1297
- text = text:[ a-z0-9-_:.@]i+ { return text.join("");}
1298
1921
 
1299
- type = "type:" type:typeString { return type; }
1300
- typeString = text:"startsWith" { return function(column, value) { return \`\${column} LIKE '\${mysql.escape(value).slice(1,-1)}%'\`; } } /
1301
- text:"endsWith" { return function(column, value) { return \`\${column} LIKE '%\${mysql.escape(value).slice(1,-1)}'\`; } } /
1302
- text:"contains" { return function(column, value) { return \`\${column} LIKE '%\${mysql.escape(value).slice(1,-1)}%'\`; } } /
1303
- text:"exact" { return function(column, value) { return \`\${column} = '\${mysql.escape(value).slice(1,-1)}'\`; } } /
1304
- text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= '\${mysql.escape(value).slice(1,-1)}'\`; } } /
1305
- text:"greaterThan" { return function(column, value) { return \`\${column} > '\${mysql.escape(value).slice(1,-1)}'\`; } } /
1306
- text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${mysql.escape(value).slice(1,-1)}'\`; } } /
1307
- text:"lessThan" { return function(column, value) { return \`\${column} < '\${mysql.escape(value).slice(1,-1)}'\`; } } /
1922
+ type = "type" _ ":" _ type:typeString { return type; }
1923
+ typeString = text:"startsWith" { return function(column, value) { return \`\${column} ILIKE '\${format.literal(value).slice(1,-1)}%'\`; } } /
1924
+ text:"endsWith" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}'\`; } } /
1925
+ text:"contains" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}%'\`; } } /
1926
+ text:"exact" { return function(column, value) { return \`\${column} = '\${format.literal(value).slice(1,-1)}'\`; } } /
1927
+ text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= '\${format.literal(value).slice(1,-1)}'\`; } } /
1928
+ text:"greaterThan" { return function(column, value) { return \`\${column} > '\${format.literal(value).slice(1,-1)}'\`; } } /
1929
+ text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${format.literal(value).slice(1,-1)}'\`; } } /
1930
+ text:"lessThan" { return function(column, value) { return \`\${column} < '\${format.literal(value).slice(1,-1)}'\`; } } /
1308
1931
  text:"isNull" { return function(column, value) { return \`isNull(\${column})\`; } }
1309
-
1310
- value = "value:" value:text { return value; }
1311
1932
 
1312
- `;
1313
- var filterSqlParser = import_pegjs.default.generate(filterSqlGrammar, {
1314
- format: "commonjs"
1315
- // dependencies: { mysql: 'mysql' } // todo: figure out a better way to escape values depending on the database type
1316
- });
1317
- var filterSqlParser_default = filterSqlParser;
1933
+ value = "value" _ ":" value:text { return value; }
1318
1934
 
1319
- // src/restura/sql/PsqlEngine.ts
1320
- var import_core_utils5 = require("@redskytech/core-utils");
1321
1935
 
1322
- // src/restura/sql/PsqlUtils.ts
1323
- var import_pg_format = __toESM(require("pg-format"));
1324
- function escapeColumnName(columnName) {
1325
- if (columnName === void 0) return "";
1326
- return `"${columnName.replace(/"/g, "")}"`.replace(".", '"."');
1327
- }
1328
- function questionMarksToOrderedParams(query) {
1329
- let count = 1;
1330
- return query.replace(/'\?'/g, () => `$${count++}`);
1331
- }
1332
- function insertObjectQuery(table, obj) {
1333
- const keys = Object.keys(obj);
1334
- const params = Object.values(obj);
1335
- const columns = keys.map((column) => escapeColumnName(column)).join(", ");
1336
- const values = params.map((value) => SQL`${value}`).join(", ");
1337
- const query = `INSERT INTO "${table}" (${columns})
1338
- VALUES (${values})
1339
- RETURNING *`;
1340
- return query;
1341
- }
1342
- function updateObjectQuery(table, obj, whereStatement) {
1343
- const setArray = [];
1344
- for (const i in obj) {
1345
- setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
1346
- }
1347
- return `UPDATE ${escapeColumnName(table)}
1348
- SET ${setArray.join(", ")} ${whereStatement}
1349
- RETURNING *`;
1350
- }
1351
- function isValueNumber2(value) {
1352
- return !isNaN(Number(value));
1353
- }
1354
- function SQL(strings, ...values) {
1355
- let query = strings[0];
1356
- values.forEach((value, index) => {
1357
- if (isValueNumber2(value)) {
1358
- query += value;
1359
- } else {
1360
- query += import_pg_format.default.literal(value);
1361
- }
1362
- query += strings[index + 1];
1363
- });
1364
- return query;
1365
- }
1936
+ `;
1937
+ var filterPsqlParser = import_pegjs.default.generate(filterSqlGrammar, {
1938
+ format: "commonjs",
1939
+ dependencies: { format: "pg-format" }
1940
+ });
1941
+ var filterPsqlParser_default = filterPsqlParser;
1366
1942
 
1367
1943
  // src/restura/sql/PsqlEngine.ts
1944
+ var { Client, types } = import_pg2.default;
1945
+ var systemUser = {
1946
+ role: "",
1947
+ host: "",
1948
+ ipAddress: "",
1949
+ isSystemUser: true
1950
+ };
1368
1951
  var PsqlEngine = class extends SqlEngine {
1369
- constructor(psqlConnectionPool) {
1952
+ constructor(psqlConnectionPool, shouldListenForDbTriggers = false) {
1370
1953
  super();
1371
1954
  this.psqlConnectionPool = psqlConnectionPool;
1955
+ this.setupPgReturnTypes();
1956
+ if (shouldListenForDbTriggers) {
1957
+ this.setupTriggerListeners = this.listenForDbTriggers();
1958
+ }
1372
1959
  }
1373
- async diffDatabaseToSchema(schema) {
1374
- console.log(schema);
1375
- return Promise.resolve("");
1960
+ async close() {
1961
+ if (this.triggerClient) {
1962
+ await this.triggerClient.end();
1963
+ }
1964
+ }
1965
+ setupPgReturnTypes() {
1966
+ const TIMESTAMPTZ_OID = 1184;
1967
+ types.setTypeParser(TIMESTAMPTZ_OID, (val) => {
1968
+ return val === null ? null : new Date(val).toISOString();
1969
+ });
1970
+ const BIGINT_OID = 20;
1971
+ types.setTypeParser(BIGINT_OID, (val) => {
1972
+ return val === null ? null : Number(val);
1973
+ });
1974
+ }
1975
+ async listenForDbTriggers() {
1976
+ this.triggerClient = new Client({
1977
+ user: this.psqlConnectionPool.poolConfig.user,
1978
+ host: this.psqlConnectionPool.poolConfig.host,
1979
+ database: this.psqlConnectionPool.poolConfig.database,
1980
+ password: this.psqlConnectionPool.poolConfig.password,
1981
+ port: this.psqlConnectionPool.poolConfig.port,
1982
+ connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
1983
+ });
1984
+ await this.triggerClient.connect();
1985
+ const promises = [];
1986
+ promises.push(this.triggerClient.query("LISTEN insert"));
1987
+ promises.push(this.triggerClient.query("LISTEN update"));
1988
+ promises.push(this.triggerClient.query("LISTEN delete"));
1989
+ await Promise.all(promises);
1990
+ this.triggerClient.on("notification", async (msg) => {
1991
+ if (msg.channel === "insert" || msg.channel === "update" || msg.channel === "delete") {
1992
+ const payload = import_core_utils5.ObjectUtils.safeParse(msg.payload);
1993
+ await this.handleTrigger(payload, msg.channel.toUpperCase());
1994
+ }
1995
+ });
1996
+ }
1997
+ async handleTrigger(payload, mutationType) {
1998
+ if (payload.queryMetadata && payload.queryMetadata.connectionInstanceId === this.psqlConnectionPool.instanceId) {
1999
+ await eventManager_default.fireActionFromDbTrigger({ queryMetadata: payload.queryMetadata, mutationType }, payload);
2000
+ }
2001
+ }
2002
+ async createDatabaseFromSchema(schema, connection) {
2003
+ const sqlFullStatement = this.generateDatabaseSchemaFromSchema(schema);
2004
+ await connection.runQuery(sqlFullStatement, [], systemUser);
2005
+ return sqlFullStatement;
1376
2006
  }
1377
2007
  generateDatabaseSchemaFromSchema(schema) {
1378
- console.log(schema);
1379
- return "";
2008
+ const sqlStatements = [];
2009
+ const indexes = [];
2010
+ const triggers = [];
2011
+ for (const table of schema.database) {
2012
+ if (table.notify) {
2013
+ triggers.push(this.createInsertTriggers(table.name, table.notify));
2014
+ triggers.push(this.createUpdateTrigger(table.name, table.notify));
2015
+ triggers.push(this.createDeleteTrigger(table.name, table.notify));
2016
+ }
2017
+ let sql = `CREATE TABLE "${table.name}"
2018
+ ( `;
2019
+ const tableColumns = [];
2020
+ for (const column of table.columns) {
2021
+ let columnSql = "";
2022
+ columnSql += ` "${column.name}" ${this.schemaToPsqlType(column)}`;
2023
+ let value = column.value;
2024
+ if (column.type === "JSON") value = "";
2025
+ if (column.type === "JSONB") value = "";
2026
+ if (column.type === "DECIMAL" && value) {
2027
+ value = value.replace("-", ",").replace(/['"]/g, "");
2028
+ }
2029
+ if (value && column.type !== "ENUM") {
2030
+ columnSql += `(${value})`;
2031
+ } else if (column.length) columnSql += `(${column.length})`;
2032
+ if (column.isPrimary) {
2033
+ columnSql += " PRIMARY KEY ";
2034
+ }
2035
+ if (column.isUnique) {
2036
+ columnSql += ` CONSTRAINT "${table.name}_${column.name}_unique_index" UNIQUE `;
2037
+ }
2038
+ if (column.isNullable) columnSql += " NULL";
2039
+ else columnSql += " NOT NULL";
2040
+ if (column.default) columnSql += ` DEFAULT ${column.default}`;
2041
+ if (value && column.type === "ENUM") {
2042
+ columnSql += ` CHECK ("${column.name}" IN (${value}))`;
2043
+ }
2044
+ tableColumns.push(columnSql);
2045
+ }
2046
+ sql += tableColumns.join(", \n");
2047
+ for (const index of table.indexes) {
2048
+ if (!index.isPrimaryKey) {
2049
+ let unique = " ";
2050
+ if (index.isUnique) unique = "UNIQUE ";
2051
+ indexes.push(
2052
+ ` CREATE ${unique}INDEX "${index.name}" ON "${table.name}" (${index.columns.map((item) => {
2053
+ return `"${item}" ${index.order}`;
2054
+ }).join(", ")});`
2055
+ );
2056
+ }
2057
+ }
2058
+ sql += "\n);";
2059
+ sqlStatements.push(sql);
2060
+ }
2061
+ for (const table of schema.database) {
2062
+ if (!table.foreignKeys.length) continue;
2063
+ const sql = `ALTER TABLE "${table.name}" `;
2064
+ const constraints = [];
2065
+ for (const foreignKey of table.foreignKeys) {
2066
+ let constraint = ` ADD CONSTRAINT "${foreignKey.name}"
2067
+ FOREIGN KEY ("${foreignKey.column}") REFERENCES "${foreignKey.refTable}" ("${foreignKey.refColumn}")`;
2068
+ constraint += ` ON DELETE ${foreignKey.onDelete}`;
2069
+ constraint += ` ON UPDATE ${foreignKey.onUpdate}`;
2070
+ constraints.push(constraint);
2071
+ }
2072
+ sqlStatements.push(sql + constraints.join(",\n") + ";");
2073
+ }
2074
+ for (const table of schema.database) {
2075
+ if (!table.checkConstraints.length) continue;
2076
+ const sql = `ALTER TABLE "${table.name}" `;
2077
+ const constraints = [];
2078
+ for (const check of table.checkConstraints) {
2079
+ const constraint = `ADD CONSTRAINT "${check.name}" CHECK (${check.check})`;
2080
+ constraints.push(constraint);
2081
+ }
2082
+ sqlStatements.push(sql + constraints.join(",\n") + ";");
2083
+ }
2084
+ sqlStatements.push(indexes.join("\n"));
2085
+ sqlStatements.push(triggers.join("\n"));
2086
+ return sqlStatements.join("\n\n");
2087
+ }
2088
+ async getScratchPool() {
2089
+ var _a2, _b;
2090
+ const scratchDbExists = await this.psqlConnectionPool.runQuery(
2091
+ `SELECT *
2092
+ FROM pg_database
2093
+ WHERE datname = '${this.psqlConnectionPool.poolConfig.database}_scratch';`,
2094
+ [],
2095
+ systemUser
2096
+ );
2097
+ if (scratchDbExists.length === 0) {
2098
+ await this.psqlConnectionPool.runQuery(
2099
+ `CREATE DATABASE ${this.psqlConnectionPool.poolConfig.database}_scratch;`,
2100
+ [],
2101
+ systemUser
2102
+ );
2103
+ }
2104
+ const scratchPool = new PsqlPool({
2105
+ host: this.psqlConnectionPool.poolConfig.host,
2106
+ port: this.psqlConnectionPool.poolConfig.port,
2107
+ user: this.psqlConnectionPool.poolConfig.user,
2108
+ database: this.psqlConnectionPool.poolConfig.database + "_scratch",
2109
+ password: this.psqlConnectionPool.poolConfig.password,
2110
+ max: this.psqlConnectionPool.poolConfig.max,
2111
+ idleTimeoutMillis: this.psqlConnectionPool.poolConfig.idleTimeoutMillis,
2112
+ connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
2113
+ });
2114
+ await scratchPool.runQuery(`DROP SCHEMA public CASCADE;`, [], systemUser);
2115
+ await scratchPool.runQuery(
2116
+ `CREATE SCHEMA public AUTHORIZATION ${this.psqlConnectionPool.poolConfig.user};`,
2117
+ [],
2118
+ systemUser
2119
+ );
2120
+ const schemaComment = await this.psqlConnectionPool.runQuery(
2121
+ `SELECT pg_description.description
2122
+ FROM pg_description
2123
+ JOIN pg_namespace ON pg_namespace.oid = pg_description.objoid
2124
+ WHERE pg_namespace.nspname = 'public';`,
2125
+ [],
2126
+ systemUser
2127
+ );
2128
+ if ((_a2 = schemaComment[0]) == null ? void 0 : _a2.description) {
2129
+ await scratchPool.runQuery(
2130
+ `COMMENT ON SCHEMA public IS '${(_b = schemaComment[0]) == null ? void 0 : _b.description}';`,
2131
+ [],
2132
+ systemUser
2133
+ );
2134
+ }
2135
+ return scratchPool;
2136
+ }
2137
+ async diffDatabaseToSchema(schema) {
2138
+ const scratchPool = await this.getScratchPool();
2139
+ await this.createDatabaseFromSchema(schema, scratchPool);
2140
+ const originalClient = new Client({
2141
+ database: this.psqlConnectionPool.poolConfig.database,
2142
+ user: this.psqlConnectionPool.poolConfig.user,
2143
+ password: this.psqlConnectionPool.poolConfig.password,
2144
+ host: this.psqlConnectionPool.poolConfig.host,
2145
+ port: this.psqlConnectionPool.poolConfig.port
2146
+ });
2147
+ const scratchClient = new Client({
2148
+ database: this.psqlConnectionPool.poolConfig.database + "_scratch",
2149
+ user: this.psqlConnectionPool.poolConfig.user,
2150
+ password: this.psqlConnectionPool.poolConfig.password,
2151
+ host: this.psqlConnectionPool.poolConfig.host,
2152
+ port: this.psqlConnectionPool.poolConfig.port
2153
+ });
2154
+ const promises = [originalClient.connect(), scratchClient.connect()];
2155
+ await Promise.all(promises);
2156
+ const infoPromises = [(0, import_pg_info.default)({ client: originalClient }), (0, import_pg_info.default)({ client: scratchClient })];
2157
+ const [info1, info2] = await Promise.all(infoPromises);
2158
+ const diff = (0, import_pg_diff_sync.default)(info1, info2);
2159
+ const endPromises = [originalClient.end(), scratchClient.end()];
2160
+ await Promise.all(endPromises);
2161
+ return diff.join("\n");
1380
2162
  }
1381
2163
  createNestedSelect(req, schema, item, routeData, userRole, sqlParams) {
1382
- console.log(req, schema, item, routeData, userRole, sqlParams);
1383
- return "";
2164
+ if (!item.subquery) return "";
2165
+ if (!import_core_utils5.ObjectUtils.isArrayWithData(
2166
+ item.subquery.properties.filter((nestedItem) => {
2167
+ return this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
2168
+ ...routeData.joins,
2169
+ ...item.subquery.joins
2170
+ ]);
2171
+ })
2172
+ )) {
2173
+ return "'[]'";
2174
+ }
2175
+ return `COALESCE((SELECT JSON_AGG(JSON_BUILD_OBJECT(
2176
+ ${item.subquery.properties.map((nestedItem) => {
2177
+ if (!this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
2178
+ ...routeData.joins,
2179
+ ...item.subquery.joins
2180
+ ])) {
2181
+ return;
2182
+ }
2183
+ if (nestedItem.subquery) {
2184
+ return `'${nestedItem.name}', ${this.createNestedSelect(
2185
+ // recursion
2186
+ req,
2187
+ schema,
2188
+ nestedItem,
2189
+ routeData,
2190
+ userRole,
2191
+ sqlParams
2192
+ )}`;
2193
+ }
2194
+ return `'${nestedItem.name}', ${escapeColumnName(nestedItem.selector)}`;
2195
+ }).filter(Boolean).join(", ")}
2196
+ ))
2197
+ FROM
2198
+ "${item.subquery.table}"
2199
+ ${this.generateJoinStatements(req, item.subquery.joins, item.subquery.table, routeData, schema, userRole, sqlParams)}
2200
+ ${this.generateWhereClause(req, item.subquery.where, routeData, sqlParams)}
2201
+ ), '[]')`;
1384
2202
  }
1385
2203
  async executeCreateRequest(req, routeData, schema) {
1386
2204
  const sqlParams = [];
@@ -1389,16 +2207,19 @@ var PsqlEngine = class extends SqlEngine {
1389
2207
  parameterObj[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
1390
2208
  });
1391
2209
  const query = insertObjectQuery(routeData.table, __spreadValues(__spreadValues({}, req.data), parameterObj));
1392
- const createdItem = await this.psqlConnectionPool.queryOne(query, sqlParams);
1393
- const insertId = createdItem == null ? void 0 : createdItem.id;
1394
- const whereData = [
1395
- {
1396
- tableName: routeData.table,
1397
- value: insertId,
1398
- columnName: "id",
1399
- operator: "="
1400
- }
1401
- ];
2210
+ const createdItem = await this.psqlConnectionPool.queryOne(
2211
+ query,
2212
+ sqlParams,
2213
+ req.requesterDetails
2214
+ );
2215
+ const insertId = createdItem.id;
2216
+ const whereId = {
2217
+ tableName: routeData.table,
2218
+ value: insertId,
2219
+ columnName: "id",
2220
+ operator: "="
2221
+ };
2222
+ const whereData = [whereId];
1402
2223
  req.data = { id: insertId };
1403
2224
  return this.executeGetRequest(req, __spreadProps(__spreadValues({}, routeData), { where: whereData }), schema);
1404
2225
  }
@@ -1417,7 +2238,9 @@ var PsqlEngine = class extends SqlEngine {
1417
2238
  let selectStatement = "SELECT \n";
1418
2239
  selectStatement += ` ${selectColumns.map((item) => {
1419
2240
  if (item.subquery) {
1420
- return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${item.name}`;
2241
+ return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${escapeColumnName(
2242
+ item.name
2243
+ )}`;
1421
2244
  }
1422
2245
  return `${escapeColumnName(item.selector)} AS ${escapeColumnName(item.name)}`;
1423
2246
  }).join(",\n ")}
@@ -1439,37 +2262,42 @@ var PsqlEngine = class extends SqlEngine {
1439
2262
  if (routeData.type === "ONE") {
1440
2263
  return this.psqlConnectionPool.queryOne(
1441
2264
  `${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
1442
- sqlParams
2265
+ sqlParams,
2266
+ req.requesterDetails
1443
2267
  );
1444
2268
  } else if (routeData.type === "ARRAY") {
1445
2269
  return this.psqlConnectionPool.runQuery(
1446
2270
  `${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
1447
- sqlParams
2271
+ sqlParams,
2272
+ req.requesterDetails
1448
2273
  );
1449
2274
  } else if (routeData.type === "PAGED") {
1450
2275
  const data = req.data;
1451
- const pageResults = await this.psqlConnectionPool.runQuery(
1452
- `${selectStatement}${sqlStatement}${groupByOrderByStatement} LIMIT ? OFFSET ?;SELECT COUNT(${routeData.groupBy ? `DISTINCT ${routeData.groupBy.tableName}.${routeData.groupBy.columnName}` : "*"}) AS total
1453
- ${sqlStatement};`,
1454
- [
1455
- ...sqlParams,
1456
- data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER,
1457
- (data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER,
1458
- ...sqlParams
1459
- ]
2276
+ const pagePromise = this.psqlConnectionPool.runQuery(
2277
+ `${selectStatement}${sqlStatement}${groupByOrderByStatement}` + SQL`LIMIT ${data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER} OFFSET ${(data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER};`,
2278
+ sqlParams,
2279
+ req.requesterDetails
1460
2280
  );
2281
+ const totalQuery = `SELECT COUNT(${routeData.groupBy ? `DISTINCT ${routeData.groupBy.tableName}.${routeData.groupBy.columnName}` : "*"}) AS total
2282
+ ${sqlStatement};`;
2283
+ const totalPromise = this.psqlConnectionPool.runQuery(
2284
+ totalQuery,
2285
+ sqlParams,
2286
+ req.requesterDetails
2287
+ );
2288
+ const [pageResults, totalResponse] = await Promise.all([pagePromise, totalPromise]);
1461
2289
  let total = 0;
1462
- if (import_core_utils5.ObjectUtils.isArrayWithData(pageResults)) {
1463
- total = pageResults[1][0].total;
2290
+ if (import_core_utils5.ObjectUtils.isArrayWithData(totalResponse)) {
2291
+ total = totalResponse[0].total;
1464
2292
  }
1465
- return { data: pageResults[0], total };
2293
+ return { data: pageResults, total };
1466
2294
  } else {
1467
2295
  throw new RsError("UNKNOWN_ERROR", "Unknown route type.");
1468
2296
  }
1469
2297
  }
1470
2298
  async executeUpdateRequest(req, routeData, schema) {
1471
2299
  const sqlParams = [];
1472
- const _a = req.body, { id } = _a, bodyNoId = __objRest(_a, ["id"]);
2300
+ const _a2 = req.body, { id } = _a2, bodyNoId = __objRest(_a2, ["id"]);
1473
2301
  const table = schema.database.find((item) => {
1474
2302
  return item.name === routeData.table;
1475
2303
  });
@@ -1487,7 +2315,7 @@ ${sqlStatement};`,
1487
2315
  }
1488
2316
  const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
1489
2317
  const query = updateObjectQuery(routeData.table, bodyNoId, whereClause);
1490
- await this.psqlConnectionPool.queryOne(query, [...sqlParams]);
2318
+ await this.psqlConnectionPool.queryOne(query, [...sqlParams], req.requesterDetails);
1491
2319
  return this.executeGetRequest(req, routeData, schema);
1492
2320
  }
1493
2321
  async executeDeleteRequest(req, routeData, schema) {
@@ -1501,20 +2329,32 @@ ${sqlStatement};`,
1501
2329
  req.requesterDetails.role,
1502
2330
  sqlParams
1503
2331
  );
1504
- let deleteStatement = `DELETE
1505
- FROM "${routeData.table}" ${joinStatement}`;
1506
- deleteStatement += this.generateWhereClause(req, routeData.where, routeData, sqlParams);
1507
- deleteStatement += ";";
1508
- await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams);
2332
+ const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
2333
+ if (whereClause.replace(/\s/g, "") === "") {
2334
+ throw new RsError("DELETE_FORBIDDEN", "Deletes need a where clause");
2335
+ }
2336
+ const deleteStatement = `
2337
+ DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
2338
+ await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams, req.requesterDetails);
1509
2339
  return true;
1510
2340
  }
1511
2341
  generateJoinStatements(req, joins, baseTable, routeData, schema, userRole, sqlParams) {
1512
- console.log(req, joins, baseTable, routeData, schema, userRole, sqlParams);
1513
- return "";
1514
- }
1515
- getTableSchema(schema, tableName) {
1516
- console.log(schema, tableName);
1517
- return {};
2342
+ let joinStatements = "";
2343
+ joins.forEach((item) => {
2344
+ if (!this.doesRoleHavePermissionToTable(userRole, schema, item.table))
2345
+ throw new RsError("UNAUTHORIZED", "You do not have permission to access this table");
2346
+ if (item.custom) {
2347
+ const customReplaced = this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
2348
+ joinStatements += ` ${item.type} JOIN ${escapeColumnName(item.table)} ON ${customReplaced}
2349
+ `;
2350
+ } else {
2351
+ joinStatements += ` ${item.type} JOIN ${escapeColumnName(item.table)}${item.alias ? `AS "${item.alias}"` : ""} ON "${baseTable}"."${item.localColumnName}" = ${escapeColumnName(item.alias ? item.alias : item.table)}.${escapeColumnName(
2352
+ item.foreignColumnName
2353
+ )}
2354
+ `;
2355
+ }
2356
+ });
2357
+ return joinStatements;
1518
2358
  }
1519
2359
  generateGroupBy(routeData) {
1520
2360
  let groupBy = "";
@@ -1557,38 +2397,39 @@ ${sqlStatement};`,
1557
2397
  );
1558
2398
  let operator = item.operator;
1559
2399
  if (operator === "LIKE") {
1560
- sqlParams[sqlParams.length - 1] = `%${sqlParams[sqlParams.length - 1]}%`;
2400
+ item.value = `'%${item.value}%'`;
1561
2401
  } else if (operator === "STARTS WITH") {
1562
2402
  operator = "LIKE";
1563
- sqlParams[sqlParams.length - 1] = `${sqlParams[sqlParams.length - 1]}%`;
2403
+ item.value = `'${item.value}%'`;
1564
2404
  } else if (operator === "ENDS WITH") {
1565
2405
  operator = "LIKE";
1566
- sqlParams[sqlParams.length - 1] = `%${sqlParams[sqlParams.length - 1]}`;
2406
+ item.value = `'%${item.value}'`;
1567
2407
  }
1568
2408
  const replacedValue = this.replaceParamKeywords(item.value, routeData, req, sqlParams);
1569
- const escapedValue = SQL`${replacedValue}`;
1570
- whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator} ${["IN", "NOT IN"].includes(operator) ? `(${escapedValue})` : escapedValue}
2409
+ whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator.replace("LIKE", "ILIKE")} ${["IN", "NOT IN"].includes(operator) ? `(${replacedValue})` : replacedValue}
1571
2410
  `;
1572
2411
  });
1573
2412
  const data = req.data;
1574
2413
  if (routeData.type === "PAGED" && !!(data == null ? void 0 : data.filter)) {
1575
2414
  let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
2415
+ var _a2;
1576
2416
  const requestParam = routeData.request.find((item) => {
1577
2417
  return item.name === value.replace("$", "");
1578
2418
  });
1579
2419
  if (!requestParam)
1580
2420
  throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
1581
- return data[requestParam.name];
2421
+ return ((_a2 = data[requestParam.name]) == null ? void 0 : _a2.toString()) || "";
1582
2422
  });
1583
2423
  statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
2424
+ var _a2;
1584
2425
  const requestParam = routeData.request.find((item) => {
1585
2426
  return item.name === value.replace("#", "");
1586
2427
  });
1587
2428
  if (!requestParam)
1588
2429
  throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
1589
- return data[requestParam.name];
2430
+ return ((_a2 = data[requestParam.name]) == null ? void 0 : _a2.toString()) || "";
1590
2431
  });
1591
- statement = filterSqlParser_default.parse(statement);
2432
+ statement = filterPsqlParser_default.parse(statement);
1592
2433
  if (whereClause.startsWith("WHERE")) {
1593
2434
  whereClause += ` AND (${statement})
1594
2435
  `;
@@ -1599,47 +2440,255 @@ ${sqlStatement};`,
1599
2440
  }
1600
2441
  return whereClause;
1601
2442
  }
1602
- };
2443
+ createUpdateTrigger(tableName, notify) {
2444
+ if (!notify) return "";
2445
+ if (notify === "ALL") {
2446
+ return `
2447
+ CREATE OR REPLACE FUNCTION notify_${tableName}_update()
2448
+ RETURNS TRIGGER AS $$
2449
+ DECLARE
2450
+ query_metadata JSON;
2451
+ BEGIN
2452
+ SELECT INTO query_metadata
2453
+ (regexp_match(
2454
+ current_query(),
2455
+ '^--QUERY_METADATA\\(({.*})', 'n'
2456
+ ))[1]::json;
1603
2457
 
1604
- // src/restura/restura.ts
1605
- var import_pg2 = require("pg");
2458
+ PERFORM pg_notify(
2459
+ 'update',
2460
+ json_build_object(
2461
+ 'table', '${tableName}',
2462
+ 'queryMetadata', query_metadata,
2463
+ 'changedId', NEW.id,
2464
+ 'record', NEW,
2465
+ 'previousRecord', OLD
2466
+ )::text
2467
+ );
2468
+ RETURN NEW;
2469
+ END;
2470
+ $$ LANGUAGE plpgsql;
1606
2471
 
1607
- // src/restura/sql/PsqlPool.ts
1608
- var import_pg = require("pg");
1609
- var PsqlPool = class {
1610
- constructor(poolConfig) {
1611
- this.poolConfig = poolConfig;
1612
- this.pool = new import_pg.Pool(poolConfig);
1613
- }
1614
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1615
- async queryOne(query, options) {
1616
- const formattedQuery = questionMarksToOrderedParams(query);
1617
- try {
1618
- const response = await this.pool.query(formattedQuery, options);
1619
- return response.rows[0];
1620
- } catch (error) {
1621
- console.error(error, query, options);
1622
- if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
1623
- throw new RsError("DUPLICATE", error.message);
1624
- }
1625
- throw new RsError("DATABASE_ERROR", `${error.message}`);
2472
+ CREATE OR REPLACE TRIGGER ${tableName}_update
2473
+ AFTER UPDATE ON "${tableName}"
2474
+ FOR EACH ROW
2475
+ EXECUTE FUNCTION notify_${tableName}_update();
2476
+ `;
1626
2477
  }
1627
- }
1628
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1629
- async runQuery(query, options) {
1630
- const formattedQuery = questionMarksToOrderedParams(query);
1631
- const queryUpdated = query.replace(/[\t\n]/g, " ");
1632
- console.log(queryUpdated, options);
1633
- try {
1634
- const response = await this.pool.query(formattedQuery, options);
1635
- return response.rows;
1636
- } catch (error) {
1637
- console.error(error, query, options);
1638
- if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
1639
- throw new RsError("DUPLICATE", error.message);
1640
- }
1641
- throw new RsError("DATABASE_ERROR", `${error.message}`);
2478
+ const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
2479
+ const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
2480
+ return `
2481
+ CREATE OR REPLACE FUNCTION notify_${tableName}_update()
2482
+ RETURNS TRIGGER AS $$
2483
+ DECLARE
2484
+ query_metadata JSON;
2485
+ BEGIN
2486
+ SELECT INTO query_metadata
2487
+ (regexp_match(
2488
+ current_query(),
2489
+ '^--QUERY_METADATA\\(({.*})', 'n'
2490
+ ))[1]::json;
2491
+
2492
+ PERFORM pg_notify(
2493
+ 'update',
2494
+ json_build_object(
2495
+ 'table', '${tableName}',
2496
+ 'queryMetadata', query_metadata,
2497
+ 'changedId', NEW.id,
2498
+ 'record', json_build_object(
2499
+ ${notifyColumnNewBuildString}
2500
+ ),
2501
+ 'previousRecord', json_build_object(
2502
+ ${notifyColumnOldBuildString}
2503
+ )
2504
+ )::text
2505
+ );
2506
+ RETURN NEW;
2507
+ END;
2508
+ $$ LANGUAGE plpgsql;
2509
+
2510
+ CREATE OR REPLACE TRIGGER ${tableName}_update
2511
+ AFTER UPDATE ON "${tableName}"
2512
+ FOR EACH ROW
2513
+ EXECUTE FUNCTION notify_${tableName}_update();
2514
+ `;
2515
+ }
2516
+ createDeleteTrigger(tableName, notify) {
2517
+ if (!notify) return "";
2518
+ if (notify === "ALL") {
2519
+ return `
2520
+ CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
2521
+ RETURNS TRIGGER AS $$
2522
+ DECLARE
2523
+ query_metadata JSON;
2524
+ BEGIN
2525
+ SELECT INTO query_metadata
2526
+ (regexp_match(
2527
+ current_query(),
2528
+ '^--QUERY_METADATA\\(({.*})', 'n'
2529
+ ))[1]::json;
2530
+
2531
+ PERFORM pg_notify(
2532
+ 'delete',
2533
+ json_build_object(
2534
+ 'table', '${tableName}',
2535
+ 'queryMetadata', query_metadata,
2536
+ 'deletedId', OLD.id,
2537
+ 'previousRecord', OLD
2538
+ )::text
2539
+ );
2540
+ RETURN NEW;
2541
+ END;
2542
+ $$ LANGUAGE plpgsql;
2543
+
2544
+ CREATE OR REPLACE TRIGGER "${tableName}_delete"
2545
+ AFTER DELETE ON "${tableName}"
2546
+ FOR EACH ROW
2547
+ EXECUTE FUNCTION notify_${tableName}_delete();
2548
+ `;
2549
+ }
2550
+ const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
2551
+ return `
2552
+ CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
2553
+ RETURNS TRIGGER AS $$
2554
+ DECLARE
2555
+ query_metadata JSON;
2556
+ BEGIN
2557
+ SELECT INTO query_metadata
2558
+ (regexp_match(
2559
+ current_query(),
2560
+ '^--QUERY_METADATA\\(({.*})', 'n'
2561
+ ))[1]::json;
2562
+
2563
+ PERFORM pg_notify(
2564
+ 'delete',
2565
+ json_build_object(
2566
+ 'table', '${tableName}',
2567
+ 'queryMetadata', query_metadata,
2568
+ 'deletedId', OLD.id,
2569
+ 'previousRecord', json_build_object(
2570
+ ${notifyColumnOldBuildString}
2571
+ )
2572
+ )::text
2573
+ );
2574
+ RETURN NEW;
2575
+ END;
2576
+ $$ LANGUAGE plpgsql;
2577
+
2578
+ CREATE OR REPLACE TRIGGER "${tableName}_delete"
2579
+ AFTER DELETE ON "${tableName}"
2580
+ FOR EACH ROW
2581
+ EXECUTE FUNCTION notify_${tableName}_delete();
2582
+ `;
2583
+ }
2584
+ createInsertTriggers(tableName, notify) {
2585
+ if (!notify) return "";
2586
+ if (notify === "ALL") {
2587
+ return `
2588
+ CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
2589
+ RETURNS TRIGGER AS $$
2590
+ DECLARE
2591
+ query_metadata JSON;
2592
+ BEGIN
2593
+ SELECT INTO query_metadata
2594
+ (regexp_match(
2595
+ current_query(),
2596
+ '^--QUERY_METADATA\\(({.*})', 'n'
2597
+ ))[1]::json;
2598
+
2599
+ PERFORM pg_notify(
2600
+ 'insert',
2601
+ json_build_object(
2602
+ 'table', '${tableName}',
2603
+ 'queryMetadata', query_metadata,
2604
+ 'insertedId', NEW.id,
2605
+ 'record', NEW
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
+ `;
1642
2618
  }
2619
+ const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
2620
+ return `
2621
+ CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
2622
+ RETURNS TRIGGER AS $$
2623
+ DECLARE
2624
+ query_metadata JSON;
2625
+ BEGIN
2626
+ SELECT INTO query_metadata
2627
+ (regexp_match(
2628
+ current_query(),
2629
+ '^--QUERY_METADATA\\(({.*})', 'n'
2630
+ ))[1]::json;
2631
+
2632
+ PERFORM pg_notify(
2633
+ 'insert',
2634
+ json_build_object(
2635
+ 'table', '${tableName}',
2636
+ 'queryMetadata', query_metadata,
2637
+ 'insertedId', NEW.id,
2638
+ 'record', json_build_object(
2639
+ ${notifyColumnNewBuildString}
2640
+ )
2641
+ )::text
2642
+ );
2643
+
2644
+ RETURN NEW;
2645
+ END;
2646
+ $$ LANGUAGE plpgsql;
2647
+
2648
+ CREATE OR REPLACE TRIGGER "${tableName}_insert"
2649
+ AFTER INSERT ON "${tableName}"
2650
+ FOR EACH ROW
2651
+ EXECUTE FUNCTION notify_${tableName}_insert();
2652
+ `;
2653
+ }
2654
+ schemaToPsqlType(column) {
2655
+ if (column.hasAutoIncrement) return "BIGSERIAL";
2656
+ if (column.type === "ENUM") return `TEXT`;
2657
+ if (column.type === "DATETIME") return "TIMESTAMPTZ";
2658
+ if (column.type === "MEDIUMINT") return "INT";
2659
+ return column.type;
2660
+ }
2661
+ };
2662
+
2663
+ // src/restura/utils/TempCache.ts
2664
+ var import_fs3 = __toESM(require("fs"));
2665
+ var import_path4 = __toESM(require("path"));
2666
+ var import_core_utils6 = require("@redskytech/core-utils");
2667
+ var import_internal3 = require("@restura/internal");
2668
+ var import_bluebird3 = __toESM(require("bluebird"));
2669
+ var os2 = __toESM(require("os"));
2670
+ var TempCache = class {
2671
+ constructor(location) {
2672
+ this.maxDurationDays = 7;
2673
+ this.location = location || os2.tmpdir();
2674
+ import_internal3.FileUtils.ensureDir(this.location).catch((e) => {
2675
+ throw e;
2676
+ });
2677
+ }
2678
+ async cleanup() {
2679
+ const fileList = await import_fs3.default.promises.readdir(this.location);
2680
+ await import_bluebird3.default.map(
2681
+ fileList,
2682
+ async (file) => {
2683
+ const fullFilePath = import_path4.default.join(this.location, file);
2684
+ const fileStats = await import_fs3.default.promises.stat(fullFilePath);
2685
+ if (import_core_utils6.DateUtils.daysBetweenStartAndEndDates(new Date(fileStats.mtimeMs), /* @__PURE__ */ new Date()) > this.maxDurationDays) {
2686
+ logger.info(`Deleting old temp file: ${file}`);
2687
+ await import_fs3.default.promises.unlink(fullFilePath);
2688
+ }
2689
+ },
2690
+ { concurrency: 10 }
2691
+ );
1643
2692
  }
1644
2693
  };
1645
2694
 
@@ -1661,10 +2710,12 @@ var ResturaEngine = class {
1661
2710
  * @returns A promise that resolves when the initialization is complete.
1662
2711
  */
1663
2712
  async init(app, authenticationHandler, psqlConnectionPool) {
2713
+ this.resturaConfig = import_internal4.config.validate("restura", resturaConfigSchema);
2714
+ this.multerCommonUpload = getMulterUpload(this.resturaConfig.fileTempCachePath);
2715
+ new TempCache(this.resturaConfig.fileTempCachePath);
1664
2716
  this.psqlConnectionPool = psqlConnectionPool;
1665
- this.psqlEngine = new PsqlEngine(this.psqlConnectionPool);
1666
- setupPgReturnTypes();
1667
- this.resturaConfig = import_internal2.config.validate("restura", resturaConfigSchema);
2717
+ this.psqlEngine = new PsqlEngine(this.psqlConnectionPool, true);
2718
+ await customApiFactory_default.loadApiFiles(this.resturaConfig.customApiFolderPath);
1668
2719
  this.authenticationHandler = authenticationHandler;
1669
2720
  app.use((0, import_compression.default)());
1670
2721
  app.use(import_body_parser.default.json({ limit: "32mb" }));
@@ -1727,10 +2778,7 @@ var ResturaEngine = class {
1727
2778
  * @returns A promise that resolves when the API has been successfully generated and written to the output file.
1728
2779
  */
1729
2780
  async generateApiFromSchema(outputFile, providedSchema) {
1730
- import_fs2.default.writeFileSync(
1731
- outputFile,
1732
- await apiGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
1733
- );
2781
+ import_fs4.default.writeFileSync(outputFile, await apiGenerator(providedSchema));
1734
2782
  }
1735
2783
  /**
1736
2784
  * Generates a model from the provided schema and writes it to the specified output file.
@@ -1740,10 +2788,15 @@ var ResturaEngine = class {
1740
2788
  * @returns A promise that resolves when the model has been successfully written to the output file.
1741
2789
  */
1742
2790
  async generateModelFromSchema(outputFile, providedSchema) {
1743
- import_fs2.default.writeFileSync(
1744
- outputFile,
1745
- await modelGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
1746
- );
2791
+ import_fs4.default.writeFileSync(outputFile, await modelGenerator(providedSchema));
2792
+ }
2793
+ /**
2794
+ * Generates the ambient module declaration for Restura global types and writes it to the specified output file.
2795
+ * These types are used sometimes in the CustomTypes
2796
+ * @param outputFile
2797
+ */
2798
+ generateResturaGlobalTypes(outputFile) {
2799
+ import_fs4.default.writeFileSync(outputFile, resturaGlobalTypesGenerator());
1747
2800
  }
1748
2801
  /**
1749
2802
  * Retrieves the latest file system schema for Restura.
@@ -1752,12 +2805,12 @@ var ResturaEngine = class {
1752
2805
  * @throws {Error} If the schema file is missing or the schema is not valid.
1753
2806
  */
1754
2807
  async getLatestFileSystemSchema() {
1755
- if (!import_fs2.default.existsSync(this.resturaConfig.schemaFilePath)) {
2808
+ if (!import_fs4.default.existsSync(this.resturaConfig.schemaFilePath)) {
1756
2809
  logger.error(`Missing restura schema file, expected path: ${this.resturaConfig.schemaFilePath}`);
1757
2810
  throw new Error("Missing restura schema file");
1758
2811
  }
1759
- const schemaFileData = import_fs2.default.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
1760
- const schema = import_core_utils6.ObjectUtils.safeParse(schemaFileData);
2812
+ const schemaFileData = import_fs4.default.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
2813
+ const schema = import_core_utils7.ObjectUtils.safeParse(schemaFileData);
1761
2814
  const isValid = await isSchemaValid(schema);
1762
2815
  if (!isValid) {
1763
2816
  logger.error("Schema is not valid");
@@ -1765,28 +2818,6 @@ var ResturaEngine = class {
1765
2818
  }
1766
2819
  return schema;
1767
2820
  }
1768
- /**
1769
- * Asynchronously generates and retrieves hashes for the provided schema and related generated files.
1770
- *
1771
- * @param providedSchema - The schema for which hashes need to be generated.
1772
- * @returns A promise that resolves to an object containing:
1773
- * - `schemaHash`: The hash of the provided schema.
1774
- * - `apiCreatedSchemaHash`: The hash extracted from the generated `api.d.ts` file.
1775
- * - `modelCreatedSchemaHash`: The hash extracted from the generated `models.d.ts` file.
1776
- */
1777
- async getHashes(providedSchema) {
1778
- var _a, _b, _c, _d;
1779
- const schemaHash = await this.generateHashForSchema(providedSchema);
1780
- const apiFile = import_fs2.default.readFileSync(import_path2.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
1781
- const apiCreatedSchemaHash = (_b = (_a = apiFile.toString().match(/\((.*)\)/)) == null ? void 0 : _a[1]) != null ? _b : "";
1782
- const modelFile = import_fs2.default.readFileSync(import_path2.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
1783
- const modelCreatedSchemaHash = (_d = (_c = modelFile.toString().match(/\((.*)\)/)) == null ? void 0 : _c[1]) != null ? _d : "";
1784
- return {
1785
- schemaHash,
1786
- apiCreatedSchemaHash,
1787
- modelCreatedSchemaHash
1788
- };
1789
- }
1790
2821
  async reloadEndpoints() {
1791
2822
  this.schema = await this.getLatestFileSystemSchema();
1792
2823
  this.customTypeValidation = customTypeValidationGenerator(this.schema);
@@ -1815,30 +2846,10 @@ var ResturaEngine = class {
1815
2846
  logger.info(`Restura loaded (${routeCount}) endpoint${routeCount > 1 ? "s" : ""}`);
1816
2847
  }
1817
2848
  async validateGeneratedTypesFolder() {
1818
- if (!import_fs2.default.existsSync(this.resturaConfig.generatedTypesPath)) {
1819
- import_fs2.default.mkdirSync(this.resturaConfig.generatedTypesPath, { recursive: true });
1820
- }
1821
- const hasApiFile = import_fs2.default.existsSync(import_path2.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
1822
- const hasModelsFile = import_fs2.default.existsSync(import_path2.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
1823
- if (!hasApiFile) {
1824
- await this.generateApiFromSchema(import_path2.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
1825
- }
1826
- if (!hasModelsFile) {
1827
- await this.generateModelFromSchema(
1828
- import_path2.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
1829
- this.schema
1830
- );
1831
- }
1832
- const hashes = await this.getHashes(this.schema);
1833
- if (hashes.schemaHash !== hashes.apiCreatedSchemaHash) {
1834
- await this.generateApiFromSchema(import_path2.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
1835
- }
1836
- if (hashes.schemaHash !== hashes.modelCreatedSchemaHash) {
1837
- await this.generateModelFromSchema(
1838
- import_path2.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
1839
- this.schema
1840
- );
2849
+ if (!import_fs4.default.existsSync(this.resturaConfig.generatedTypesPath)) {
2850
+ import_fs4.default.mkdirSync(this.resturaConfig.generatedTypesPath, { recursive: true });
1841
2851
  }
2852
+ this.updateTypes();
1842
2853
  }
1843
2854
  resturaAuthentication(req, res, next) {
1844
2855
  if (req.headers["x-auth-token"] !== this.resturaConfig.authToken) res.status(401).send("Unauthorized");
@@ -1846,7 +2857,7 @@ var ResturaEngine = class {
1846
2857
  }
1847
2858
  async previewCreateSchema(req, res) {
1848
2859
  try {
1849
- const schemaDiff = { commands: "", endPoints: [], globalParams: [], roles: [], customTypes: false };
2860
+ const schemaDiff = await compareSchema_default.diffSchema(req.data, this.schema, this.psqlEngine);
1850
2861
  res.send({ data: schemaDiff });
1851
2862
  } catch (err) {
1852
2863
  res.status(400).send(err);
@@ -1854,7 +2865,7 @@ var ResturaEngine = class {
1854
2865
  }
1855
2866
  async updateSchema(req, res) {
1856
2867
  try {
1857
- this.schema = req.data;
2868
+ this.schema = sortObjectKeysAlphabetically(req.data);
1858
2869
  await this.storeFileSystemSchema();
1859
2870
  await this.reloadEndpoints();
1860
2871
  await this.updateTypes();
@@ -1865,11 +2876,12 @@ var ResturaEngine = class {
1865
2876
  }
1866
2877
  }
1867
2878
  async updateTypes() {
1868
- await this.generateApiFromSchema(import_path2.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
2879
+ await this.generateApiFromSchema(import_path5.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
1869
2880
  await this.generateModelFromSchema(
1870
- import_path2.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
2881
+ import_path5.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
1871
2882
  this.schema
1872
2883
  );
2884
+ this.generateResturaGlobalTypes(import_path5.default.join(this.resturaConfig.generatedTypesPath, "restura.d.ts"));
1873
2885
  }
1874
2886
  async getSchema(req, res) {
1875
2887
  res.send({ data: this.schema });
@@ -1877,24 +2889,48 @@ var ResturaEngine = class {
1877
2889
  async getSchemaAndTypes(req, res) {
1878
2890
  try {
1879
2891
  const schema = await this.getLatestFileSystemSchema();
1880
- const schemaHash = await this.generateHashForSchema(schema);
1881
- const apiText = await apiGenerator(schema, schemaHash);
1882
- const modelsText = await modelGenerator(schema, schemaHash);
2892
+ const apiText = await apiGenerator(schema);
2893
+ const modelsText = await modelGenerator(schema);
1883
2894
  res.send({ schema, api: apiText, models: modelsText });
1884
2895
  } catch (err) {
1885
2896
  res.status(400).send({ error: err });
1886
2897
  }
1887
2898
  }
2899
+ async getMulterFilesIfAny(req, res, routeData) {
2900
+ var _a2;
2901
+ if (!((_a2 = req.header("content-type")) == null ? void 0 : _a2.includes("multipart/form-data"))) return;
2902
+ if (!this.isCustomRoute(routeData)) return;
2903
+ if (!routeData.fileUploadType) {
2904
+ throw new RsError("BAD_REQUEST", "File upload type not defined for route");
2905
+ }
2906
+ const multerFileUploadFunction = routeData.fileUploadType === "MULTIPLE" ? this.multerCommonUpload.array("files") : this.multerCommonUpload.single("file");
2907
+ return new Promise((resolve2, reject) => {
2908
+ multerFileUploadFunction(req, res, (err) => {
2909
+ if (err) {
2910
+ logger.warn("Multer error: " + err);
2911
+ reject(err);
2912
+ }
2913
+ if (req.body["data"]) req.body = JSON.parse(req.body["data"]);
2914
+ resolve2();
2915
+ });
2916
+ });
2917
+ }
1888
2918
  async executeRouteLogic(req, res, next) {
1889
2919
  try {
1890
2920
  const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
1891
2921
  this.validateAuthorization(req, routeData);
1892
- validateRequestParams(req, routeData, this.customTypeValidation);
2922
+ await this.getMulterFilesIfAny(req, res, routeData);
2923
+ requestValidator(req, routeData, this.customTypeValidation);
2924
+ if (this.isCustomRoute(routeData)) {
2925
+ await this.runCustomRouteLogic(req, res, routeData);
2926
+ return;
2927
+ }
1893
2928
  const data = await this.psqlEngine.runQueryForRoute(
1894
2929
  req,
1895
2930
  routeData,
1896
2931
  this.schema
1897
2932
  );
2933
+ this.responseValidator.validateResponseParams(data, req.baseUrl, routeData);
1898
2934
  if (routeData.type === "PAGED") res.sendNoWrap(data);
1899
2935
  else res.sendData(data);
1900
2936
  } catch (e) {
@@ -1904,46 +2940,25 @@ var ResturaEngine = class {
1904
2940
  isCustomRoute(route) {
1905
2941
  return route.type === "CUSTOM_ONE" || route.type === "CUSTOM_ARRAY" || route.type === "CUSTOM_PAGED";
1906
2942
  }
1907
- // @boundMethod
1908
- // private async runCustomRouteLogic<T>(req: RsRequest<T>, res: RsResponse<T>, routeData: RouteData) {
1909
- // const version = req.baseUrl.split('/')[2];
1910
- // let domain = routeData.path.split('/')[1];
1911
- // domain = domain.split('-').reduce((acc, value, index) => {
1912
- // if (index === 0) acc = value;
1913
- // else acc += StringUtils.capitalizeFirst(value);
1914
- // return acc;
1915
- // }, '');
1916
- // const customApiName = `${StringUtils.capitalizeFirst(domain)}Api${StringUtils.capitalizeFirst(version)}`;
1917
- // const customApi = apiFactory.getCustomApi(customApiName);
1918
- // if (!customApi) throw new RsError('NOT_FOUND', `API domain ${domain}-${version} not found`);
1919
- // const functionName = `${routeData.method.toLowerCase()}${routeData.path
1920
- // .replace(new RegExp('-', 'g'), '/')
1921
- // .split('/')
1922
- // .reduce((acc, cur) => {
1923
- // if (cur === '') return acc;
1924
- // return acc + StringUtils.capitalizeFirst(cur);
1925
- // }, '')}`;
1926
- // // @ts-expect-error - Here we are dynamically calling the function from a custom class, not sure how to typescript this
1927
- // const customFunction = customApi[functionName] as (
1928
- // req: RsRequest<T>,
1929
- // res: RsResponse<T>,
1930
- // routeData: RouteData
1931
- // ) => Promise<void>;
1932
- // if (!customFunction) throw new RsError('NOT_FOUND', `API path ${routeData.path} not implemented`);
1933
- // await customFunction(req, res, routeData);
1934
- // }
1935
- async generateHashForSchema(providedSchema) {
1936
- const schemaPrettyStr = await prettier3.format(JSON.stringify(providedSchema), __spreadValues({
1937
- parser: "json"
1938
- }, {
1939
- trailingComma: "none",
1940
- tabWidth: 4,
1941
- useTabs: true,
1942
- endOfLine: "lf",
1943
- printWidth: 120,
1944
- singleQuote: true
1945
- }));
1946
- return (0, import_crypto.createHash)("sha256").update(schemaPrettyStr).digest("hex");
2943
+ async runCustomRouteLogic(req, res, routeData) {
2944
+ const version = req.baseUrl.split("/")[2];
2945
+ let domain = routeData.path.split("/")[1];
2946
+ domain = domain.split("-").reduce((acc, value, index) => {
2947
+ if (index === 0) acc = value;
2948
+ else acc += import_core_utils7.StringUtils.capitalizeFirst(value);
2949
+ return acc;
2950
+ }, "");
2951
+ const customApiName = `${import_core_utils7.StringUtils.capitalizeFirst(domain)}Api${import_core_utils7.StringUtils.capitalizeFirst(version)}`;
2952
+ const customApi = customApiFactory_default.getCustomApi(customApiName);
2953
+ if (!customApi) throw new RsError("NOT_FOUND", `API domain ${domain}-${version} not found`);
2954
+ const functionName = `${routeData.method.toLowerCase()}${routeData.path.replace(new RegExp("-", "g"), "/").split("/").reduce((acc, cur) => {
2955
+ if (cur === "") return acc;
2956
+ return acc + import_core_utils7.StringUtils.capitalizeFirst(cur);
2957
+ }, "")}`;
2958
+ const customFunction = customApi[functionName];
2959
+ if (!customFunction)
2960
+ throw new RsError("NOT_FOUND", `API path ${routeData.path} not implemented ${functionName}`);
2961
+ await customFunction(req, res, routeData);
1947
2962
  }
1948
2963
  async storeFileSystemSchema() {
1949
2964
  const schemaPrettyStr = await prettier3.format(JSON.stringify(this.schema), __spreadValues({
@@ -1956,7 +2971,7 @@ var ResturaEngine = class {
1956
2971
  printWidth: 120,
1957
2972
  singleQuote: true
1958
2973
  }));
1959
- import_fs2.default.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
2974
+ import_fs4.default.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
1960
2975
  }
1961
2976
  resetPublicEndpoints() {
1962
2977
  this.publicEndpoints = {
@@ -1973,13 +2988,13 @@ var ResturaEngine = class {
1973
2988
  if (!routeData.roles.includes(role))
1974
2989
  throw new RsError("UNAUTHORIZED", "Not authorized to access this endpoint");
1975
2990
  }
1976
- getRouteData(method, baseUrl, path3) {
2991
+ getRouteData(method, baseUrl, path5) {
1977
2992
  const endpoint = this.schema.endpoints.find((item) => {
1978
2993
  return item.baseUrl === baseUrl;
1979
2994
  });
1980
2995
  if (!endpoint) throw new RsError("NOT_FOUND", "Route not found");
1981
2996
  const route = endpoint.routes.find((item) => {
1982
- return item.method === method && item.path === path3;
2997
+ return item.method === method && item.path === path5;
1983
2998
  });
1984
2999
  if (!route) throw new RsError("NOT_FOUND", "Route not found");
1985
3000
  return route;
@@ -2000,28 +3015,72 @@ __decorateClass([
2000
3015
  __decorateClass([
2001
3016
  boundMethod
2002
3017
  ], ResturaEngine.prototype, "getSchemaAndTypes", 1);
3018
+ __decorateClass([
3019
+ boundMethod
3020
+ ], ResturaEngine.prototype, "getMulterFilesIfAny", 1);
2003
3021
  __decorateClass([
2004
3022
  boundMethod
2005
3023
  ], ResturaEngine.prototype, "executeRouteLogic", 1);
2006
3024
  __decorateClass([
2007
3025
  boundMethod
2008
3026
  ], ResturaEngine.prototype, "isCustomRoute", 1);
2009
- var setupPgReturnTypes = () => {
2010
- const TIMESTAMPTZ_OID = 1184;
2011
- import_pg2.types.setTypeParser(TIMESTAMPTZ_OID, (val) => {
2012
- return val === null ? null : new Date(val).toISOString();
2013
- });
2014
- const BIGINT_OID = 20;
2015
- import_pg2.types.setTypeParser(BIGINT_OID, (val) => {
2016
- return val === null ? null : Number(val);
2017
- });
2018
- };
2019
- setupPgReturnTypes();
3027
+ __decorateClass([
3028
+ boundMethod
3029
+ ], ResturaEngine.prototype, "runCustomRouteLogic", 1);
2020
3030
  var restura = new ResturaEngine();
3031
+
3032
+ // src/restura/sql/PsqlTransaction.ts
3033
+ var import_pg3 = __toESM(require("pg"));
3034
+ var { Client: Client2 } = import_pg3.default;
3035
+ var PsqlTransaction = class extends PsqlConnection {
3036
+ constructor(clientConfig, instanceId) {
3037
+ super(instanceId);
3038
+ this.clientConfig = clientConfig;
3039
+ this.client = new Client2(clientConfig);
3040
+ this.connectPromise = this.client.connect();
3041
+ this.beginTransactionPromise = this.beginTransaction();
3042
+ }
3043
+ async close() {
3044
+ if (this.client) {
3045
+ await this.client.end();
3046
+ }
3047
+ }
3048
+ async beginTransaction() {
3049
+ await this.connectPromise;
3050
+ return this.client.query("BEGIN");
3051
+ }
3052
+ async rollback() {
3053
+ return this.query("ROLLBACK");
3054
+ }
3055
+ async commit() {
3056
+ return this.query("COMMIT");
3057
+ }
3058
+ async release() {
3059
+ return this.client.end();
3060
+ }
3061
+ async query(query, values) {
3062
+ await this.connectPromise;
3063
+ await this.beginTransactionPromise;
3064
+ return this.client.query(query, values);
3065
+ }
3066
+ };
2021
3067
  // Annotate the CommonJS export names for ESM import in node:
2022
3068
  0 && (module.exports = {
3069
+ HtmlStatusCodes,
3070
+ PsqlConnection,
3071
+ PsqlEngine,
2023
3072
  PsqlPool,
3073
+ PsqlTransaction,
3074
+ RsError,
3075
+ SQL,
3076
+ escapeColumnName,
3077
+ eventManager,
3078
+ filterPsqlParser,
3079
+ insertObjectQuery,
3080
+ isValueNumber,
2024
3081
  logger,
2025
- restura
3082
+ questionMarksToOrderedParams,
3083
+ restura,
3084
+ updateObjectQuery
2026
3085
  });
2027
3086
  //# sourceMappingURL=index.js.map