@restura/core 0.1.0-alpha.9 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1428 -285
- package/dist/index.d.ts +1428 -285
- package/dist/index.js +1372 -458
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1356 -447
- package/dist/index.mjs.map +1 -1
- package/package.json +25 -16
package/dist/index.js
CHANGED
|
@@ -65,13 +65,18 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
// src/index.ts
|
|
68
|
-
var
|
|
69
|
-
__export(
|
|
68
|
+
var index_exports = {};
|
|
69
|
+
__export(index_exports, {
|
|
70
70
|
HtmlStatusCodes: () => HtmlStatusCodes,
|
|
71
|
+
PsqlConnection: () => PsqlConnection,
|
|
72
|
+
PsqlEngine: () => PsqlEngine,
|
|
71
73
|
PsqlPool: () => PsqlPool,
|
|
74
|
+
PsqlTransaction: () => PsqlTransaction,
|
|
72
75
|
RsError: () => RsError,
|
|
73
76
|
SQL: () => SQL,
|
|
74
77
|
escapeColumnName: () => escapeColumnName,
|
|
78
|
+
eventManager: () => eventManager_default,
|
|
79
|
+
filterPsqlParser: () => filterPsqlParser_default,
|
|
75
80
|
insertObjectQuery: () => insertObjectQuery,
|
|
76
81
|
isValueNumber: () => isValueNumber2,
|
|
77
82
|
logger: () => logger,
|
|
@@ -79,25 +84,18 @@ __export(src_exports, {
|
|
|
79
84
|
restura: () => restura,
|
|
80
85
|
updateObjectQuery: () => updateObjectQuery
|
|
81
86
|
});
|
|
82
|
-
module.exports = __toCommonJS(
|
|
87
|
+
module.exports = __toCommonJS(index_exports);
|
|
83
88
|
|
|
84
89
|
// src/logger/logger.ts
|
|
85
90
|
var import_internal = require("@restura/internal");
|
|
86
91
|
var import_winston = __toESM(require("winston"));
|
|
87
92
|
var import_logform = require("logform");
|
|
88
93
|
|
|
89
|
-
// src/
|
|
94
|
+
// src/logger/loggerConfigSchema.ts
|
|
90
95
|
var import_zod = require("zod");
|
|
91
96
|
var loggerConfigSchema = import_zod.z.object({
|
|
92
97
|
level: import_zod.z.enum(["info", "warn", "error", "debug", "silly"]).default("info")
|
|
93
98
|
});
|
|
94
|
-
var resturaConfigSchema = import_zod.z.object({
|
|
95
|
-
authToken: import_zod.z.string().min(1, "Missing Restura Auth Token"),
|
|
96
|
-
sendErrorStackTrace: import_zod.z.boolean().default(false),
|
|
97
|
-
schemaFilePath: import_zod.z.string().default(process.cwd() + "/restura.schema.json"),
|
|
98
|
-
customApiFolderPath: import_zod.z.string().default(process.cwd() + "/dist/api"),
|
|
99
|
-
generatedTypesPath: import_zod.z.string().default(process.cwd() + "/src/@types")
|
|
100
|
-
});
|
|
101
99
|
|
|
102
100
|
// src/logger/logger.ts
|
|
103
101
|
var loggerConfig = import_internal.config.validate("logger", loggerConfigSchema);
|
|
@@ -134,7 +132,198 @@ var logger = import_winston.default.createLogger({
|
|
|
134
132
|
]
|
|
135
133
|
});
|
|
136
134
|
|
|
137
|
-
// src/restura/
|
|
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
|
+
|
|
267
|
+
// src/restura/restura.ts
|
|
268
|
+
var import_core_utils7 = require("@redskytech/core-utils");
|
|
269
|
+
var import_internal4 = require("@restura/internal");
|
|
270
|
+
|
|
271
|
+
// ../../node_modules/.pnpm/autobind-decorator@2.4.0/node_modules/autobind-decorator/lib/esm/index.js
|
|
272
|
+
function _typeof(obj) {
|
|
273
|
+
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
|
|
274
|
+
_typeof = function _typeof2(obj2) {
|
|
275
|
+
return typeof obj2;
|
|
276
|
+
};
|
|
277
|
+
} else {
|
|
278
|
+
_typeof = function _typeof2(obj2) {
|
|
279
|
+
return obj2 && typeof Symbol === "function" && obj2.constructor === Symbol && obj2 !== Symbol.prototype ? "symbol" : typeof obj2;
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
return _typeof(obj);
|
|
283
|
+
}
|
|
284
|
+
function boundMethod(target, key, descriptor) {
|
|
285
|
+
var fn = descriptor.value;
|
|
286
|
+
if (typeof fn !== "function") {
|
|
287
|
+
throw new TypeError("@boundMethod decorator can only be applied to methods not: ".concat(_typeof(fn)));
|
|
288
|
+
}
|
|
289
|
+
var definingProperty = false;
|
|
290
|
+
return {
|
|
291
|
+
configurable: true,
|
|
292
|
+
get: function get() {
|
|
293
|
+
if (definingProperty || this === target.prototype || this.hasOwnProperty(key) || typeof fn !== "function") {
|
|
294
|
+
return fn;
|
|
295
|
+
}
|
|
296
|
+
var boundFn = fn.bind(this);
|
|
297
|
+
definingProperty = true;
|
|
298
|
+
Object.defineProperty(this, key, {
|
|
299
|
+
configurable: true,
|
|
300
|
+
get: function get2() {
|
|
301
|
+
return boundFn;
|
|
302
|
+
},
|
|
303
|
+
set: function set(value) {
|
|
304
|
+
fn = value;
|
|
305
|
+
delete this[key];
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
definingProperty = false;
|
|
309
|
+
return boundFn;
|
|
310
|
+
},
|
|
311
|
+
set: function set(value) {
|
|
312
|
+
fn = value;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/restura/restura.ts
|
|
318
|
+
var import_body_parser = __toESM(require("body-parser"));
|
|
319
|
+
var import_compression = __toESM(require("compression"));
|
|
320
|
+
var import_cookie_parser = __toESM(require("cookie-parser"));
|
|
321
|
+
var express = __toESM(require("express"));
|
|
322
|
+
var import_fs4 = __toESM(require("fs"));
|
|
323
|
+
var import_path5 = __toESM(require("path"));
|
|
324
|
+
var prettier3 = __toESM(require("prettier"));
|
|
325
|
+
|
|
326
|
+
// src/restura/RsError.ts
|
|
138
327
|
var HtmlStatusCodes = /* @__PURE__ */ ((HtmlStatusCodes2) => {
|
|
139
328
|
HtmlStatusCodes2[HtmlStatusCodes2["BAD_REQUEST"] = 400] = "BAD_REQUEST";
|
|
140
329
|
HtmlStatusCodes2[HtmlStatusCodes2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
|
|
@@ -158,7 +347,6 @@ var RsError = class _RsError {
|
|
|
158
347
|
static htmlStatus(code) {
|
|
159
348
|
return htmlStatusMap[code];
|
|
160
349
|
}
|
|
161
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
350
|
static isRsError(error) {
|
|
163
351
|
return error instanceof _RsError;
|
|
164
352
|
}
|
|
@@ -200,73 +388,171 @@ var htmlStatusMap = {
|
|
|
200
388
|
SCHEMA_ERROR: 500 /* SERVER_ERROR */
|
|
201
389
|
};
|
|
202
390
|
|
|
203
|
-
// src/restura/
|
|
204
|
-
var
|
|
205
|
-
var
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
216
408
|
};
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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);
|
|
231
423
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
+
});
|
|
242
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"
|
|
243
464
|
});
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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);
|
|
249
503
|
}
|
|
250
|
-
}
|
|
251
|
-
|
|
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;
|
|
252
545
|
|
|
253
|
-
// src/restura/
|
|
254
|
-
var
|
|
255
|
-
var
|
|
256
|
-
var import_cookie_parser = __toESM(require("cookie-parser"));
|
|
257
|
-
var import_crypto = require("crypto");
|
|
258
|
-
var express = __toESM(require("express"));
|
|
259
|
-
var import_fs3 = __toESM(require("fs"));
|
|
260
|
-
var import_path3 = __toESM(require("path"));
|
|
261
|
-
var import_pg2 = __toESM(require("pg"));
|
|
262
|
-
var prettier3 = __toESM(require("prettier"));
|
|
546
|
+
// src/restura/generators/apiGenerator.ts
|
|
547
|
+
var import_core_utils = require("@redskytech/core-utils");
|
|
548
|
+
var import_prettier = __toESM(require("prettier"));
|
|
263
549
|
|
|
264
550
|
// src/restura/sql/SqlUtils.ts
|
|
265
551
|
var SqlUtils = class _SqlUtils {
|
|
266
552
|
static convertDatabaseTypeToTypescript(type, value) {
|
|
267
553
|
type = type.toLocaleLowerCase();
|
|
268
554
|
if (type.startsWith("tinyint") || type.startsWith("boolean")) return "boolean";
|
|
269
|
-
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"))
|
|
270
556
|
return "number";
|
|
271
557
|
if (type === "json") {
|
|
272
558
|
if (!value) return "object";
|
|
@@ -287,7 +573,7 @@ var SqlUtils = class _SqlUtils {
|
|
|
287
573
|
}
|
|
288
574
|
};
|
|
289
575
|
|
|
290
|
-
// src/restura/ResponseValidator.ts
|
|
576
|
+
// src/restura/validators/ResponseValidator.ts
|
|
291
577
|
var ResponseValidator = class _ResponseValidator {
|
|
292
578
|
constructor(schema) {
|
|
293
579
|
this.database = schema.database;
|
|
@@ -352,9 +638,9 @@ var ResponseValidator = class _ResponseValidator {
|
|
|
352
638
|
return { validator: "any" };
|
|
353
639
|
}
|
|
354
640
|
getTypeFromTable(selector, name) {
|
|
355
|
-
const
|
|
356
|
-
if (
|
|
357
|
-
const tableName =
|
|
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];
|
|
358
644
|
const table = this.database.find((t) => t.name == tableName);
|
|
359
645
|
const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
|
|
360
646
|
if (!table || !column) return { validator: "any", isOptional: false };
|
|
@@ -436,9 +722,7 @@ var ResponseValidator = class _ResponseValidator {
|
|
|
436
722
|
}
|
|
437
723
|
};
|
|
438
724
|
|
|
439
|
-
// src/restura/apiGenerator.ts
|
|
440
|
-
var import_core_utils = require("@redskytech/core-utils");
|
|
441
|
-
var import_prettier = __toESM(require("prettier"));
|
|
725
|
+
// src/restura/generators/apiGenerator.ts
|
|
442
726
|
var ApiTree = class _ApiTree {
|
|
443
727
|
constructor(namespace, database) {
|
|
444
728
|
this.database = database;
|
|
@@ -536,7 +820,7 @@ var ApiTree = class _ApiTree {
|
|
|
536
820
|
break;
|
|
537
821
|
}
|
|
538
822
|
}
|
|
539
|
-
return `'${p.name}'${p.required ? "" : "?"}:${requestType}`;
|
|
823
|
+
return `'${p.name}'${p.required ? "" : "?"}:${requestType}${p.isNullable ? " | null" : ""}`;
|
|
540
824
|
}).join(";\n")}${import_core_utils.ObjectUtils.isArrayWithData(route.request) ? ";" : ""}
|
|
541
825
|
`;
|
|
542
826
|
modelString += `}`;
|
|
@@ -550,30 +834,37 @@ var ApiTree = class _ApiTree {
|
|
|
550
834
|
return `export type Res = CustomTypes.${route.responseType}[]`;
|
|
551
835
|
else return `export type Res = CustomTypes.${route.responseType}`;
|
|
552
836
|
}
|
|
553
|
-
return `export interface Res ${this.getFields(route.response)}`;
|
|
837
|
+
return `export interface Res ${this.getFields(route.response, route.table, route.joins)}`;
|
|
554
838
|
}
|
|
555
|
-
getFields(fields) {
|
|
556
|
-
const nameFields = fields.map((f) => this.getNameAndType(f));
|
|
839
|
+
getFields(fields, routeBaseTable, joins) {
|
|
840
|
+
const nameFields = fields.map((f) => this.getNameAndType(f, routeBaseTable, joins));
|
|
557
841
|
const nested = `{
|
|
558
842
|
${nameFields.join(";\n ")}${import_core_utils.ObjectUtils.isArrayWithData(nameFields) ? ";" : ""}
|
|
559
843
|
}`;
|
|
560
844
|
return nested;
|
|
561
845
|
}
|
|
562
|
-
getNameAndType(p) {
|
|
563
|
-
let responseType = "any",
|
|
846
|
+
getNameAndType(p, routeBaseTable, joins) {
|
|
847
|
+
let responseType = "any", isNullable = false, array = false;
|
|
564
848
|
if (p.selector) {
|
|
565
|
-
({ responseType,
|
|
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
|
+
}
|
|
566
857
|
} else if (p.subquery) {
|
|
567
|
-
responseType = this.getFields(p.subquery.properties);
|
|
858
|
+
responseType = this.getFields(p.subquery.properties, p.subquery.table, p.subquery.joins);
|
|
568
859
|
array = true;
|
|
569
860
|
}
|
|
570
|
-
return `${p.name}${
|
|
861
|
+
return `${p.name}:${responseType}${array ? "[]" : ""}${isNullable ? " | null" : ""}`;
|
|
571
862
|
}
|
|
572
863
|
getTypeFromTable(selector, name) {
|
|
573
|
-
const
|
|
574
|
-
if (
|
|
575
|
-
let tableName =
|
|
576
|
-
const columnName =
|
|
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];
|
|
577
868
|
let table = this.database.find((t) => t.name == tableName);
|
|
578
869
|
if (!table && tableName.includes("_")) {
|
|
579
870
|
const tableAliasSplit = tableName.split("_");
|
|
@@ -581,18 +872,19 @@ var ApiTree = class _ApiTree {
|
|
|
581
872
|
table = this.database.find((t) => t.name == tableName);
|
|
582
873
|
}
|
|
583
874
|
const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
|
|
584
|
-
if (!table || !column) return { responseType: "any",
|
|
875
|
+
if (!table || !column) return { responseType: "any", isNullable: false };
|
|
585
876
|
return {
|
|
586
877
|
responseType: SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value),
|
|
587
|
-
|
|
878
|
+
isNullable: column.roles.length > 0 || column.isNullable
|
|
588
879
|
};
|
|
589
880
|
}
|
|
590
881
|
};
|
|
591
|
-
function pathToNamespaces(
|
|
592
|
-
return
|
|
882
|
+
function pathToNamespaces(path5) {
|
|
883
|
+
return path5.split("/").map((e) => import_core_utils.StringUtils.toPascalCasing(e)).filter((e) => e);
|
|
593
884
|
}
|
|
594
|
-
function apiGenerator(schema
|
|
595
|
-
let apiString = `/** Auto generated file
|
|
885
|
+
function apiGenerator(schema) {
|
|
886
|
+
let apiString = `/** Auto generated file. DO NOT MODIFY **/
|
|
887
|
+
`;
|
|
596
888
|
const rootNamespace = ApiTree.createRootNode(schema.database);
|
|
597
889
|
for (const endpoint of schema.endpoints) {
|
|
598
890
|
const endpointNamespaces = pathToNamespaces(endpoint.baseUrl);
|
|
@@ -607,7 +899,7 @@ function apiGenerator(schema, schemaHash) {
|
|
|
607
899
|
apiString += `
|
|
608
900
|
|
|
609
901
|
declare namespace CustomTypes {
|
|
610
|
-
${schema.customTypes}
|
|
902
|
+
${schema.customTypes.join("\n")}
|
|
611
903
|
}`;
|
|
612
904
|
}
|
|
613
905
|
return import_prettier.default.format(apiString, __spreadValues({
|
|
@@ -622,79 +914,32 @@ function apiGenerator(schema, schemaHash) {
|
|
|
622
914
|
}));
|
|
623
915
|
}
|
|
624
916
|
|
|
625
|
-
// src/restura/
|
|
626
|
-
var import_fs = __toESM(require("fs"));
|
|
627
|
-
var import_path = __toESM(require("path"));
|
|
628
|
-
var CustomApiFactory = class {
|
|
629
|
-
constructor() {
|
|
630
|
-
this.customApis = {};
|
|
631
|
-
}
|
|
632
|
-
async loadApiFiles(baseFolderPath) {
|
|
633
|
-
const apiVersions = ["v1"];
|
|
634
|
-
for (const apiVersion of apiVersions) {
|
|
635
|
-
const apiVersionFolderPath = import_path.default.join(baseFolderPath, apiVersion);
|
|
636
|
-
if (!import_fs.default.existsSync(apiVersionFolderPath)) continue;
|
|
637
|
-
await this.addDirectory(apiVersionFolderPath, apiVersion);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
getCustomApi(customApiName) {
|
|
641
|
-
return this.customApis[customApiName];
|
|
642
|
-
}
|
|
643
|
-
async addDirectory(directoryPath, apiVersion) {
|
|
644
|
-
const entries = import_fs.default.readdirSync(directoryPath, {
|
|
645
|
-
withFileTypes: true
|
|
646
|
-
});
|
|
647
|
-
for (const entry of entries) {
|
|
648
|
-
if (entry.isFile()) {
|
|
649
|
-
if (entry.name.endsWith(`.api.${apiVersion}.js`) === false) continue;
|
|
650
|
-
try {
|
|
651
|
-
const importPath = `${import_path.default.join(directoryPath, entry.name)}`;
|
|
652
|
-
const ApiImport = await import(importPath);
|
|
653
|
-
const customApiClass = new ApiImport.default();
|
|
654
|
-
logger.info(`Registering custom API: ${ApiImport.default.name}`);
|
|
655
|
-
this.bindMethodsToInstance(customApiClass);
|
|
656
|
-
this.customApis[ApiImport.default.name] = customApiClass;
|
|
657
|
-
} catch (e) {
|
|
658
|
-
console.error(e);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
bindMethodsToInstance(instance) {
|
|
664
|
-
const proto = Object.getPrototypeOf(instance);
|
|
665
|
-
Object.getOwnPropertyNames(proto).forEach((key) => {
|
|
666
|
-
const property = instance[key];
|
|
667
|
-
if (typeof property === "function" && key !== "constructor") {
|
|
668
|
-
instance[key] = property.bind(instance);
|
|
669
|
-
}
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
};
|
|
673
|
-
var customApiFactory = new CustomApiFactory();
|
|
674
|
-
var customApiFactory_default = customApiFactory;
|
|
675
|
-
|
|
676
|
-
// src/restura/customTypeValidationGenerator.ts
|
|
917
|
+
// src/restura/generators/customTypeValidationGenerator.ts
|
|
677
918
|
var import_fs2 = __toESM(require("fs"));
|
|
678
|
-
var TJS = __toESM(require("typescript-json-schema"));
|
|
679
919
|
var import_path2 = __toESM(require("path"));
|
|
680
920
|
var import_tmp = __toESM(require("tmp"));
|
|
681
|
-
var
|
|
921
|
+
var TJS = __toESM(require("typescript-json-schema"));
|
|
682
922
|
function customTypeValidationGenerator(currentSchema) {
|
|
683
923
|
const schemaObject = {};
|
|
684
|
-
const customInterfaceNames = currentSchema.customTypes.
|
|
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);
|
|
685
929
|
if (!customInterfaceNames) return {};
|
|
686
930
|
const temporaryFile = import_tmp.default.fileSync({ mode: 420, prefix: "prefix-", postfix: ".ts" });
|
|
687
|
-
import_fs2.default.writeFileSync(temporaryFile.name, currentSchema.customTypes);
|
|
931
|
+
import_fs2.default.writeFileSync(temporaryFile.name, currentSchema.customTypes.join("\n"));
|
|
688
932
|
const compilerOptions = {
|
|
689
933
|
strictNullChecks: true,
|
|
690
934
|
skipLibCheck: true
|
|
935
|
+
// Needed if we are processing ES modules
|
|
691
936
|
};
|
|
692
937
|
const program = TJS.getProgramFromFiles(
|
|
693
938
|
[
|
|
694
939
|
(0, import_path2.resolve)(temporaryFile.name),
|
|
695
|
-
|
|
696
|
-
import_path2.default.join(
|
|
697
|
-
import_path2.default.join(
|
|
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")
|
|
698
943
|
],
|
|
699
944
|
compilerOptions
|
|
700
945
|
);
|
|
@@ -708,6 +953,61 @@ function customTypeValidationGenerator(currentSchema) {
|
|
|
708
953
|
return schemaObject;
|
|
709
954
|
}
|
|
710
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}: ${SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value)}${column.isNullable ? " | null" : ""};
|
|
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
|
+
|
|
711
1011
|
// src/restura/middleware/addApiResponseFunctions.ts
|
|
712
1012
|
function addApiResponseFunctions(req, res, next) {
|
|
713
1013
|
res.sendData = function(data, statusCode = 200) {
|
|
@@ -740,16 +1040,41 @@ function addApiResponseFunctions(req, res, next) {
|
|
|
740
1040
|
function authenticateUser(applicationAuthenticateHandler) {
|
|
741
1041
|
return (req, res, next) => {
|
|
742
1042
|
applicationAuthenticateHandler(req, res, (userDetails) => {
|
|
743
|
-
req.requesterDetails = __spreadValues(
|
|
1043
|
+
req.requesterDetails = __spreadValues({ host: req.hostname, ipAddress: req.ip || "" }, userDetails);
|
|
744
1044
|
next();
|
|
745
1045
|
});
|
|
746
1046
|
};
|
|
747
1047
|
}
|
|
748
1048
|
|
|
749
|
-
// src/restura/
|
|
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
|
|
750
1075
|
var import_zod3 = require("zod");
|
|
751
1076
|
|
|
752
|
-
// src/restura/
|
|
1077
|
+
// src/restura/schemas/validatorDataSchema.ts
|
|
753
1078
|
var import_zod2 = require("zod");
|
|
754
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())]);
|
|
755
1080
|
var validatorDataSchema = import_zod2.z.object({
|
|
@@ -757,7 +1082,7 @@ var validatorDataSchema = import_zod2.z.object({
|
|
|
757
1082
|
value: validatorDataSchemeValue
|
|
758
1083
|
}).strict();
|
|
759
1084
|
|
|
760
|
-
// src/restura/
|
|
1085
|
+
// src/restura/schemas/resturaSchema.ts
|
|
761
1086
|
var orderBySchema = import_zod3.z.object({
|
|
762
1087
|
columnName: import_zod3.z.string(),
|
|
763
1088
|
order: import_zod3.z.enum(["ASC", "DESC"]),
|
|
@@ -770,7 +1095,7 @@ var groupBySchema = import_zod3.z.object({
|
|
|
770
1095
|
var whereDataSchema = import_zod3.z.object({
|
|
771
1096
|
tableName: import_zod3.z.string().optional(),
|
|
772
1097
|
columnName: import_zod3.z.string().optional(),
|
|
773
|
-
operator: import_zod3.z.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH"]).optional(),
|
|
1098
|
+
operator: import_zod3.z.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH", "IS", "IS NOT"]).optional(),
|
|
774
1099
|
value: import_zod3.z.string().or(import_zod3.z.number()).optional(),
|
|
775
1100
|
custom: import_zod3.z.string().optional(),
|
|
776
1101
|
conjunction: import_zod3.z.enum(["AND", "OR"]).optional()
|
|
@@ -790,6 +1115,7 @@ var joinDataSchema = import_zod3.z.object({
|
|
|
790
1115
|
var requestDataSchema = import_zod3.z.object({
|
|
791
1116
|
name: import_zod3.z.string(),
|
|
792
1117
|
required: import_zod3.z.boolean(),
|
|
1118
|
+
isNullable: import_zod3.z.boolean().optional(),
|
|
793
1119
|
validator: import_zod3.z.array(validatorDataSchema)
|
|
794
1120
|
}).strict();
|
|
795
1121
|
var responseDataSchema = import_zod3.z.object({
|
|
@@ -875,6 +1201,12 @@ var postgresColumnDateTypesSchema = import_zod3.z.enum([
|
|
|
875
1201
|
"INTERVAL"
|
|
876
1202
|
// time span
|
|
877
1203
|
]);
|
|
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
|
+
]);
|
|
878
1210
|
var mariaDbColumnNumericTypesSchema = import_zod3.z.enum([
|
|
879
1211
|
"BOOLEAN",
|
|
880
1212
|
// 1-byte A synonym for "TINYINT(1)". Supported from version 1.2.0 onwards.
|
|
@@ -937,6 +1269,7 @@ var columnDataSchema = import_zod3.z.object({
|
|
|
937
1269
|
postgresColumnNumericTypesSchema,
|
|
938
1270
|
postgresColumnStringTypesSchema,
|
|
939
1271
|
postgresColumnDateTypesSchema,
|
|
1272
|
+
postgresColumnJsonTypesSchema,
|
|
940
1273
|
mariaDbColumnNumericTypesSchema,
|
|
941
1274
|
mariaDbColumnStringTypesSchema,
|
|
942
1275
|
mariaDbColumnDateTypesSchema
|
|
@@ -988,7 +1321,8 @@ var tableDataSchema = import_zod3.z.object({
|
|
|
988
1321
|
indexes: import_zod3.z.array(indexDataSchema),
|
|
989
1322
|
foreignKeys: import_zod3.z.array(foreignKeyDataSchema),
|
|
990
1323
|
checkConstraints: import_zod3.z.array(checkConstraintDataSchema),
|
|
991
|
-
roles: import_zod3.z.array(import_zod3.z.string())
|
|
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()
|
|
992
1326
|
}).strict();
|
|
993
1327
|
var endpointDataSchema = import_zod3.z.object({
|
|
994
1328
|
name: import_zod3.z.string(),
|
|
@@ -996,16 +1330,16 @@ var endpointDataSchema = import_zod3.z.object({
|
|
|
996
1330
|
baseUrl: import_zod3.z.string(),
|
|
997
1331
|
routes: import_zod3.z.array(import_zod3.z.union([standardRouteSchema, customRouteSchema]))
|
|
998
1332
|
}).strict();
|
|
999
|
-
var
|
|
1333
|
+
var resturaSchema = import_zod3.z.object({
|
|
1000
1334
|
database: import_zod3.z.array(tableDataSchema),
|
|
1001
1335
|
endpoints: import_zod3.z.array(endpointDataSchema),
|
|
1002
1336
|
globalParams: import_zod3.z.array(import_zod3.z.string()),
|
|
1003
1337
|
roles: import_zod3.z.array(import_zod3.z.string()),
|
|
1004
|
-
customTypes: import_zod3.z.string()
|
|
1338
|
+
customTypes: import_zod3.z.array(import_zod3.z.string())
|
|
1005
1339
|
}).strict();
|
|
1006
1340
|
async function isSchemaValid(schemaToCheck) {
|
|
1007
1341
|
try {
|
|
1008
|
-
|
|
1342
|
+
resturaSchema.parse(schemaToCheck);
|
|
1009
1343
|
return true;
|
|
1010
1344
|
} catch (error) {
|
|
1011
1345
|
logger.error(error);
|
|
@@ -1013,12 +1347,12 @@ async function isSchemaValid(schemaToCheck) {
|
|
|
1013
1347
|
}
|
|
1014
1348
|
}
|
|
1015
1349
|
|
|
1016
|
-
// src/restura/
|
|
1017
|
-
var
|
|
1350
|
+
// src/restura/validators/requestValidator.ts
|
|
1351
|
+
var import_core_utils3 = require("@redskytech/core-utils");
|
|
1018
1352
|
var import_jsonschema = __toESM(require("jsonschema"));
|
|
1019
1353
|
var import_zod4 = require("zod");
|
|
1020
1354
|
|
|
1021
|
-
// src/restura/utils/
|
|
1355
|
+
// src/restura/utils/utils.ts
|
|
1022
1356
|
function addQuotesToStrings(variable) {
|
|
1023
1357
|
if (typeof variable === "string") {
|
|
1024
1358
|
return `'${variable}'`;
|
|
@@ -1029,9 +1363,20 @@ function addQuotesToStrings(variable) {
|
|
|
1029
1363
|
return variable;
|
|
1030
1364
|
}
|
|
1031
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
|
+
}
|
|
1032
1377
|
|
|
1033
|
-
// src/restura/
|
|
1034
|
-
function
|
|
1378
|
+
// src/restura/validators/requestValidator.ts
|
|
1379
|
+
function requestValidator(req, routeData, validationSchema) {
|
|
1035
1380
|
const requestData = getRequestData(req);
|
|
1036
1381
|
req.data = requestData;
|
|
1037
1382
|
if (routeData.request === void 0) {
|
|
@@ -1065,6 +1410,7 @@ function validateRequestParams(req, routeData, validationSchema) {
|
|
|
1065
1410
|
});
|
|
1066
1411
|
}
|
|
1067
1412
|
function validateRequestSingleParam(requestValue, requestParam) {
|
|
1413
|
+
if (requestParam.isNullable && requestValue === null) return;
|
|
1068
1414
|
requestParam.validator.forEach((validator) => {
|
|
1069
1415
|
switch (validator.type) {
|
|
1070
1416
|
case "TYPE_CHECK":
|
|
@@ -1152,7 +1498,7 @@ function performMaxCheck(requestValue, validator, requestParamName) {
|
|
|
1152
1498
|
);
|
|
1153
1499
|
}
|
|
1154
1500
|
function performOneOfCheck(requestValue, validator, requestParamName) {
|
|
1155
|
-
if (!
|
|
1501
|
+
if (!import_core_utils3.ObjectUtils.isArrayWithData(validator.value))
|
|
1156
1502
|
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not of type array`);
|
|
1157
1503
|
if (typeof requestValue === "object")
|
|
1158
1504
|
throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not of type string or number`);
|
|
@@ -1181,13 +1527,19 @@ function getRequestData(req) {
|
|
|
1181
1527
|
if (isNaN(Number(value))) continue;
|
|
1182
1528
|
attrList.push(Number(value));
|
|
1183
1529
|
}
|
|
1184
|
-
if (
|
|
1530
|
+
if (import_core_utils3.ObjectUtils.isArrayWithData(attrList)) {
|
|
1185
1531
|
bodyData[attr] = attrList;
|
|
1186
1532
|
}
|
|
1187
1533
|
} else {
|
|
1188
|
-
bodyData[attr]
|
|
1189
|
-
|
|
1190
|
-
|
|
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
|
+
}
|
|
1191
1543
|
}
|
|
1192
1544
|
}
|
|
1193
1545
|
}
|
|
@@ -1198,7 +1550,7 @@ function getRequestData(req) {
|
|
|
1198
1550
|
async function schemaValidation(req, res, next) {
|
|
1199
1551
|
req.data = getRequestData(req);
|
|
1200
1552
|
try {
|
|
1201
|
-
|
|
1553
|
+
resturaSchema.parse(req.data);
|
|
1202
1554
|
next();
|
|
1203
1555
|
} catch (error) {
|
|
1204
1556
|
logger.error(error);
|
|
@@ -1206,43 +1558,34 @@ async function schemaValidation(req, res, next) {
|
|
|
1206
1558
|
}
|
|
1207
1559
|
}
|
|
1208
1560
|
|
|
1209
|
-
// src/restura/
|
|
1210
|
-
var
|
|
1211
|
-
var
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
}, {
|
|
1224
|
-
trailingComma: "none",
|
|
1225
|
-
tabWidth: 4,
|
|
1226
|
-
useTabs: true,
|
|
1227
|
-
endOfLine: "lf",
|
|
1228
|
-
printWidth: 120,
|
|
1229
|
-
singleQuote: true
|
|
1230
|
-
}));
|
|
1231
|
-
}
|
|
1232
|
-
function convertTable(table) {
|
|
1233
|
-
let modelString = ` export interface ${import_core_utils3.StringUtils.capitalizeFirst(table.name)} {
|
|
1234
|
-
`;
|
|
1235
|
-
for (const column of table.columns) {
|
|
1236
|
-
modelString += ` ${column.name}${column.isNullable ? "?" : ""}: ${SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value)};
|
|
1237
|
-
`;
|
|
1238
|
-
}
|
|
1239
|
-
modelString += ` }
|
|
1240
|
-
`;
|
|
1241
|
-
return modelString;
|
|
1242
|
-
}
|
|
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
|
+
});
|
|
1243
1575
|
|
|
1244
1576
|
// src/restura/sql/PsqlEngine.ts
|
|
1245
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
|
+
var import_sql_formatter = require("sql-formatter");
|
|
1246
1589
|
|
|
1247
1590
|
// src/restura/sql/PsqlUtils.ts
|
|
1248
1591
|
var import_pg_format = __toESM(require("pg-format"));
|
|
@@ -1252,16 +1595,33 @@ function escapeColumnName(columnName) {
|
|
|
1252
1595
|
}
|
|
1253
1596
|
function questionMarksToOrderedParams(query) {
|
|
1254
1597
|
let count = 1;
|
|
1255
|
-
|
|
1598
|
+
let inSingleQuote = false;
|
|
1599
|
+
let inDoubleQuote = false;
|
|
1600
|
+
return query.replace(/('|"|\?)/g, (char) => {
|
|
1601
|
+
if (char === "'") {
|
|
1602
|
+
inSingleQuote = !inSingleQuote && !inDoubleQuote;
|
|
1603
|
+
return char;
|
|
1604
|
+
}
|
|
1605
|
+
if (char === '"') {
|
|
1606
|
+
inDoubleQuote = !inDoubleQuote && !inSingleQuote;
|
|
1607
|
+
return char;
|
|
1608
|
+
}
|
|
1609
|
+
if (char === "?" && !inSingleQuote && !inDoubleQuote) {
|
|
1610
|
+
return `$${count++}`;
|
|
1611
|
+
}
|
|
1612
|
+
return char;
|
|
1613
|
+
});
|
|
1256
1614
|
}
|
|
1257
1615
|
function insertObjectQuery(table, obj) {
|
|
1258
1616
|
const keys = Object.keys(obj);
|
|
1259
1617
|
const params = Object.values(obj);
|
|
1260
1618
|
const columns = keys.map((column) => escapeColumnName(column)).join(", ");
|
|
1261
1619
|
const values = params.map((value) => SQL`${value}`).join(", ");
|
|
1262
|
-
|
|
1620
|
+
let query = `
|
|
1621
|
+
INSERT INTO "${table}" (${columns})
|
|
1263
1622
|
VALUES (${values})
|
|
1264
1623
|
RETURNING *`;
|
|
1624
|
+
query = query.replace(/'(\?)'/, "?");
|
|
1265
1625
|
return query;
|
|
1266
1626
|
}
|
|
1267
1627
|
function updateObjectQuery(table, obj, whereStatement) {
|
|
@@ -1269,25 +1629,132 @@ function updateObjectQuery(table, obj, whereStatement) {
|
|
|
1269
1629
|
for (const i in obj) {
|
|
1270
1630
|
setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
|
|
1271
1631
|
}
|
|
1272
|
-
return `
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1632
|
+
return `
|
|
1633
|
+
UPDATE ${escapeColumnName(table)}
|
|
1634
|
+
SET ${setArray.join(", ")} ${whereStatement}
|
|
1635
|
+
RETURNING *`;
|
|
1636
|
+
}
|
|
1637
|
+
function isValueNumber2(value) {
|
|
1638
|
+
return !isNaN(Number(value));
|
|
1639
|
+
}
|
|
1640
|
+
function SQL(strings, ...values) {
|
|
1641
|
+
let query = strings[0];
|
|
1642
|
+
values.forEach((value, index) => {
|
|
1643
|
+
if (typeof value === "boolean") {
|
|
1644
|
+
query += value;
|
|
1645
|
+
} else if (typeof value === "number") {
|
|
1646
|
+
query += value;
|
|
1647
|
+
} else if (Array.isArray(value)) {
|
|
1648
|
+
query += import_pg_format.default.literal(JSON.stringify(value)) + "::jsonb";
|
|
1649
|
+
} else {
|
|
1650
|
+
query += import_pg_format.default.literal(value);
|
|
1651
|
+
}
|
|
1652
|
+
query += strings[index + 1];
|
|
1653
|
+
});
|
|
1654
|
+
return query;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// src/restura/sql/PsqlConnection.ts
|
|
1658
|
+
var PsqlConnection = class {
|
|
1659
|
+
constructor(instanceId) {
|
|
1660
|
+
this.instanceId = instanceId || import_crypto.default.randomUUID();
|
|
1661
|
+
}
|
|
1662
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1663
|
+
async queryOne(query, options, requesterDetails) {
|
|
1664
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1665
|
+
const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
|
|
1666
|
+
this.logSqlStatement(formattedQuery, options, meta);
|
|
1667
|
+
const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
|
|
1668
|
+
`;
|
|
1669
|
+
const startTime = process.hrtime();
|
|
1670
|
+
try {
|
|
1671
|
+
const response = await this.query(queryMetadata + formattedQuery, options);
|
|
1672
|
+
this.logQueryDuration(startTime);
|
|
1673
|
+
if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
|
|
1674
|
+
else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
|
|
1675
|
+
return response.rows[0];
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
if (RsError.isRsError(error)) throw error;
|
|
1678
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1679
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1680
|
+
}
|
|
1681
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1685
|
+
async runQuery(query, options, requesterDetails) {
|
|
1686
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1687
|
+
const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
|
|
1688
|
+
this.logSqlStatement(formattedQuery, options, meta);
|
|
1689
|
+
const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
|
|
1690
|
+
`;
|
|
1691
|
+
const startTime = process.hrtime();
|
|
1692
|
+
try {
|
|
1693
|
+
const response = await this.query(queryMetadata + formattedQuery, options);
|
|
1694
|
+
this.logQueryDuration(startTime);
|
|
1695
|
+
return response.rows;
|
|
1696
|
+
} catch (error) {
|
|
1697
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1698
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1699
|
+
}
|
|
1700
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
logQueryDuration(startTime) {
|
|
1704
|
+
if (logger.level === "silly") {
|
|
1705
|
+
const [seconds, nanoseconds] = process.hrtime(startTime);
|
|
1706
|
+
const duration = seconds * 1e3 + nanoseconds / 1e6;
|
|
1707
|
+
logger.silly(`Query duration: ${duration.toFixed(2)}ms`);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
logSqlStatement(query, options, queryMetadata, prefix = "") {
|
|
1711
|
+
if (logger.level !== "silly") return;
|
|
1712
|
+
let sqlStatement = "";
|
|
1713
|
+
if (options.length === 0) {
|
|
1714
|
+
sqlStatement = query;
|
|
1284
1715
|
} else {
|
|
1285
|
-
|
|
1716
|
+
let stringIndex = 0;
|
|
1717
|
+
sqlStatement = query.replace(/\$\d+/g, () => {
|
|
1718
|
+
const value = options[stringIndex++];
|
|
1719
|
+
if (typeof value === "number") return value.toString();
|
|
1720
|
+
return import_pg_format2.default.literal(value);
|
|
1721
|
+
});
|
|
1286
1722
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1723
|
+
const formattedSql = (0, import_sql_formatter.format)(sqlStatement, {
|
|
1724
|
+
language: "postgresql",
|
|
1725
|
+
linesBetweenQueries: 2,
|
|
1726
|
+
indentStyle: "standard",
|
|
1727
|
+
keywordCase: "upper",
|
|
1728
|
+
useTabs: true,
|
|
1729
|
+
tabWidth: 4
|
|
1730
|
+
});
|
|
1731
|
+
let initiator = "Anonymous";
|
|
1732
|
+
if ("userId" in queryMetadata && queryMetadata.userId)
|
|
1733
|
+
initiator = `User Id (${queryMetadata.userId.toString()})`;
|
|
1734
|
+
if ("isSystemUser" in queryMetadata && queryMetadata.isSystemUser) initiator = "SYSTEM";
|
|
1735
|
+
logger.silly(`${prefix}query by ${initiator}, Query ->
|
|
1736
|
+
${formattedSql}`);
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
|
|
1740
|
+
// src/restura/sql/PsqlPool.ts
|
|
1741
|
+
var { Pool } = import_pg.default;
|
|
1742
|
+
var PsqlPool = class extends PsqlConnection {
|
|
1743
|
+
constructor(poolConfig) {
|
|
1744
|
+
super();
|
|
1745
|
+
this.poolConfig = poolConfig;
|
|
1746
|
+
this.pool = new Pool(poolConfig);
|
|
1747
|
+
this.queryOne("SELECT NOW();", [], { isSystemUser: true, role: "", host: "localhost", ipAddress: "" }).then(() => {
|
|
1748
|
+
logger.info("Connected to PostgreSQL database");
|
|
1749
|
+
}).catch((error) => {
|
|
1750
|
+
logger.error("Error connecting to database", error);
|
|
1751
|
+
process.exit(1);
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
async query(query, values) {
|
|
1755
|
+
return this.pool.query(query, values);
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1291
1758
|
|
|
1292
1759
|
// src/restura/sql/SqlEngine.ts
|
|
1293
1760
|
var import_core_utils4 = require("@redskytech/core-utils");
|
|
@@ -1355,11 +1822,11 @@ var SqlEngine = class {
|
|
|
1355
1822
|
return returnValue;
|
|
1356
1823
|
}
|
|
1357
1824
|
replaceLocalParamKeywords(value, routeData, req, sqlParams) {
|
|
1358
|
-
var
|
|
1825
|
+
var _a2;
|
|
1359
1826
|
if (!routeData.request) return value;
|
|
1360
1827
|
const data = req.data;
|
|
1361
1828
|
if (typeof value === "string") {
|
|
1362
|
-
(
|
|
1829
|
+
(_a2 = value.match(/\$[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a2.forEach((param) => {
|
|
1363
1830
|
const requestParam = routeData.request.find((item) => {
|
|
1364
1831
|
return item.name === param.replace("$", "");
|
|
1365
1832
|
});
|
|
@@ -1372,9 +1839,9 @@ var SqlEngine = class {
|
|
|
1372
1839
|
return value;
|
|
1373
1840
|
}
|
|
1374
1841
|
replaceGlobalParamKeywords(value, routeData, req, sqlParams) {
|
|
1375
|
-
var
|
|
1842
|
+
var _a2;
|
|
1376
1843
|
if (typeof value === "string") {
|
|
1377
|
-
(
|
|
1844
|
+
(_a2 = value.match(/#[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a2.forEach((param) => {
|
|
1378
1845
|
param = param.replace("#", "");
|
|
1379
1846
|
const globalParamValue = req.requesterDetails[param];
|
|
1380
1847
|
if (!globalParamValue)
|
|
@@ -1393,32 +1860,86 @@ var SqlEngine = class {
|
|
|
1393
1860
|
// src/restura/sql/filterPsqlParser.ts
|
|
1394
1861
|
var import_pegjs = __toESM(require("pegjs"));
|
|
1395
1862
|
var filterSqlGrammar = `
|
|
1863
|
+
{
|
|
1864
|
+
// ported from pg-format but intentionally will add double quotes to every column
|
|
1865
|
+
function quoteSqlIdentity(value) {
|
|
1866
|
+
if (value === undefined || value === null) {
|
|
1867
|
+
throw new Error('SQL identifier cannot be null or undefined');
|
|
1868
|
+
} else if (value === false) {
|
|
1869
|
+
return '"f"';
|
|
1870
|
+
} else if (value === true) {
|
|
1871
|
+
return '"t"';
|
|
1872
|
+
} else if (value instanceof Date) {
|
|
1873
|
+
// return '"' + formatDate(value.toISOString()) + '"';
|
|
1874
|
+
} else if (value instanceof Buffer) {
|
|
1875
|
+
throw new Error('SQL identifier cannot be a buffer');
|
|
1876
|
+
} else if (Array.isArray(value) === true) {
|
|
1877
|
+
var temp = [];
|
|
1878
|
+
for (var i = 0; i < value.length; i++) {
|
|
1879
|
+
if (Array.isArray(value[i]) === true) {
|
|
1880
|
+
throw new Error('Nested array to grouped list conversion is not supported for SQL identifier');
|
|
1881
|
+
} else {
|
|
1882
|
+
// temp.push(quoteIdent(value[i]));
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
return temp.toString();
|
|
1886
|
+
} else if (value === Object(value)) {
|
|
1887
|
+
throw new Error('SQL identifier cannot be an object');
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
var ident = value.toString().slice(0); // create copy
|
|
1891
|
+
|
|
1892
|
+
// do not quote a valid, unquoted identifier
|
|
1893
|
+
// if (/^[a-z_][a-z0-9_$]*$/.test(ident) === true && isReserved(ident) === false) {
|
|
1894
|
+
// return ident;
|
|
1895
|
+
// }
|
|
1896
|
+
|
|
1897
|
+
var quoted = '"';
|
|
1898
|
+
|
|
1899
|
+
for (var i = 0; i < ident.length; i++) {
|
|
1900
|
+
var c = ident[i];
|
|
1901
|
+
if (c === '"') {
|
|
1902
|
+
quoted += c + c;
|
|
1903
|
+
} else {
|
|
1904
|
+
quoted += c;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
quoted += '"';
|
|
1909
|
+
|
|
1910
|
+
return quoted;
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1396
1914
|
start = expressionList
|
|
1397
1915
|
|
|
1916
|
+
_ = [ \\t\\r\\n]* // Matches spaces, tabs, and line breaks
|
|
1917
|
+
|
|
1398
1918
|
expressionList =
|
|
1399
|
-
|
|
1919
|
+
leftExpression:expression _ operator:operator _ rightExpression:expressionList
|
|
1400
1920
|
{ return \`\${leftExpression} \${operator} \${rightExpression}\`;}
|
|
1401
1921
|
/ expression
|
|
1402
1922
|
|
|
1403
1923
|
expression =
|
|
1404
|
-
negate:negate?"(" "column:"
|
|
1405
|
-
{return \`\${negate? "
|
|
1924
|
+
negate:negate? _ "(" _ "column" _ ":" column:column _ ","? _ value:value? ","? _ type:type? _ ")"_
|
|
1925
|
+
{return \`\${negate? " NOT " : ""}(\${type? type(column, value) : \`\${column} = \${format.literal(value)}\`})\`;}
|
|
1406
1926
|
/
|
|
1407
|
-
negate:negate?"("expression:expressionList")" { return \`\${negate? "
|
|
1927
|
+
negate:negate?"("expression:expressionList")" { return \`\${negate? " NOT " : ""}(\${expression})\`; }
|
|
1408
1928
|
|
|
1409
1929
|
negate = "!"
|
|
1410
1930
|
|
|
1411
1931
|
operator = "and"i / "or"i
|
|
1412
1932
|
|
|
1413
1933
|
|
|
1414
|
-
column = left:text "." right:text { return \`\${
|
|
1934
|
+
column = left:text "." right:text { return \`\${quoteSqlIdentity(left)}.\${quoteSqlIdentity(right)}\`; }
|
|
1415
1935
|
/
|
|
1416
|
-
text:text { return
|
|
1936
|
+
text:text { return quoteSqlIdentity(text); }
|
|
1417
1937
|
|
|
1418
1938
|
|
|
1419
|
-
text = text:[a-z0-9
|
|
1939
|
+
text = text:[a-z0-9 \\t\\r\\n\\-_:@']i+ { return text.join(""); }
|
|
1420
1940
|
|
|
1421
|
-
|
|
1941
|
+
|
|
1942
|
+
type = "type" _ ":" _ type:typeString { return type; }
|
|
1422
1943
|
typeString = text:"startsWith" { return function(column, value) { return \`\${column} ILIKE '\${format.literal(value).slice(1,-1)}%'\`; } } /
|
|
1423
1944
|
text:"endsWith" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1424
1945
|
text:"contains" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}%'\`; } } /
|
|
@@ -1428,8 +1949,9 @@ typeString = text:"startsWith" { return function(column, value) { return \`\${co
|
|
|
1428
1949
|
text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1429
1950
|
text:"lessThan" { return function(column, value) { return \`\${column} < '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1430
1951
|
text:"isNull" { return function(column, value) { return \`isNull(\${column})\`; } }
|
|
1431
|
-
|
|
1432
|
-
value = "value:" value:text { return value; }
|
|
1952
|
+
|
|
1953
|
+
value = "value" _ ":" value:text { return value; }
|
|
1954
|
+
|
|
1433
1955
|
|
|
1434
1956
|
`;
|
|
1435
1957
|
var filterPsqlParser = import_pegjs.default.generate(filterSqlGrammar, {
|
|
@@ -1439,18 +1961,224 @@ var filterPsqlParser = import_pegjs.default.generate(filterSqlGrammar, {
|
|
|
1439
1961
|
var filterPsqlParser_default = filterPsqlParser;
|
|
1440
1962
|
|
|
1441
1963
|
// src/restura/sql/PsqlEngine.ts
|
|
1964
|
+
var { Client, types } = import_pg2.default;
|
|
1965
|
+
var systemUser = {
|
|
1966
|
+
role: "",
|
|
1967
|
+
host: "",
|
|
1968
|
+
ipAddress: "",
|
|
1969
|
+
isSystemUser: true
|
|
1970
|
+
};
|
|
1442
1971
|
var PsqlEngine = class extends SqlEngine {
|
|
1443
|
-
constructor(psqlConnectionPool) {
|
|
1972
|
+
constructor(psqlConnectionPool, shouldListenForDbTriggers = false) {
|
|
1444
1973
|
super();
|
|
1445
1974
|
this.psqlConnectionPool = psqlConnectionPool;
|
|
1975
|
+
this.setupPgReturnTypes();
|
|
1976
|
+
if (shouldListenForDbTriggers) {
|
|
1977
|
+
this.setupTriggerListeners = this.listenForDbTriggers();
|
|
1978
|
+
}
|
|
1446
1979
|
}
|
|
1447
|
-
async
|
|
1448
|
-
|
|
1449
|
-
|
|
1980
|
+
async close() {
|
|
1981
|
+
if (this.triggerClient) {
|
|
1982
|
+
await this.triggerClient.end();
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
setupPgReturnTypes() {
|
|
1986
|
+
const TIMESTAMPTZ_OID = 1184;
|
|
1987
|
+
types.setTypeParser(TIMESTAMPTZ_OID, (val) => {
|
|
1988
|
+
return val === null ? null : new Date(val).toISOString();
|
|
1989
|
+
});
|
|
1990
|
+
const BIGINT_OID = 20;
|
|
1991
|
+
types.setTypeParser(BIGINT_OID, (val) => {
|
|
1992
|
+
return val === null ? null : Number(val);
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
async listenForDbTriggers() {
|
|
1996
|
+
this.triggerClient = new Client({
|
|
1997
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
1998
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
1999
|
+
database: this.psqlConnectionPool.poolConfig.database,
|
|
2000
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
2001
|
+
port: this.psqlConnectionPool.poolConfig.port,
|
|
2002
|
+
connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
|
|
2003
|
+
});
|
|
2004
|
+
await this.triggerClient.connect();
|
|
2005
|
+
const promises = [];
|
|
2006
|
+
promises.push(this.triggerClient.query("LISTEN insert"));
|
|
2007
|
+
promises.push(this.triggerClient.query("LISTEN update"));
|
|
2008
|
+
promises.push(this.triggerClient.query("LISTEN delete"));
|
|
2009
|
+
await Promise.all(promises);
|
|
2010
|
+
this.triggerClient.on("notification", async (msg) => {
|
|
2011
|
+
if (msg.channel === "insert" || msg.channel === "update" || msg.channel === "delete") {
|
|
2012
|
+
const payload = import_core_utils5.ObjectUtils.safeParse(msg.payload);
|
|
2013
|
+
await this.handleTrigger(payload, msg.channel.toUpperCase());
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
async handleTrigger(payload, mutationType) {
|
|
2018
|
+
if (payload.queryMetadata && payload.queryMetadata.connectionInstanceId === this.psqlConnectionPool.instanceId) {
|
|
2019
|
+
await eventManager_default.fireActionFromDbTrigger({ queryMetadata: payload.queryMetadata, mutationType }, payload);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
async createDatabaseFromSchema(schema, connection) {
|
|
2023
|
+
const sqlFullStatement = this.generateDatabaseSchemaFromSchema(schema);
|
|
2024
|
+
await connection.runQuery(sqlFullStatement, [], systemUser);
|
|
2025
|
+
return sqlFullStatement;
|
|
1450
2026
|
}
|
|
1451
2027
|
generateDatabaseSchemaFromSchema(schema) {
|
|
1452
|
-
|
|
1453
|
-
|
|
2028
|
+
const sqlStatements = [];
|
|
2029
|
+
const indexes = [];
|
|
2030
|
+
const triggers = [];
|
|
2031
|
+
for (const table of schema.database) {
|
|
2032
|
+
if (table.notify) {
|
|
2033
|
+
triggers.push(this.createInsertTriggers(table.name, table.notify));
|
|
2034
|
+
triggers.push(this.createUpdateTrigger(table.name, table.notify));
|
|
2035
|
+
triggers.push(this.createDeleteTrigger(table.name, table.notify));
|
|
2036
|
+
}
|
|
2037
|
+
let sql = `CREATE TABLE "${table.name}"
|
|
2038
|
+
( `;
|
|
2039
|
+
const tableColumns = [];
|
|
2040
|
+
for (const column of table.columns) {
|
|
2041
|
+
let columnSql = "";
|
|
2042
|
+
columnSql += ` "${column.name}" ${this.schemaToPsqlType(column)}`;
|
|
2043
|
+
let value = column.value;
|
|
2044
|
+
if (column.type === "JSON") value = "";
|
|
2045
|
+
if (column.type === "JSONB") value = "";
|
|
2046
|
+
if (column.type === "DECIMAL" && value) {
|
|
2047
|
+
value = value.replace("-", ",").replace(/['"]/g, "");
|
|
2048
|
+
}
|
|
2049
|
+
if (value && column.type !== "ENUM") {
|
|
2050
|
+
columnSql += `(${value})`;
|
|
2051
|
+
} else if (column.length) columnSql += `(${column.length})`;
|
|
2052
|
+
if (column.isPrimary) {
|
|
2053
|
+
columnSql += " PRIMARY KEY ";
|
|
2054
|
+
}
|
|
2055
|
+
if (column.isUnique) {
|
|
2056
|
+
columnSql += ` CONSTRAINT "${table.name}_${column.name}_unique_index" UNIQUE `;
|
|
2057
|
+
}
|
|
2058
|
+
if (column.isNullable) columnSql += " NULL";
|
|
2059
|
+
else columnSql += " NOT NULL";
|
|
2060
|
+
if (column.default) columnSql += ` DEFAULT ${column.default}`;
|
|
2061
|
+
if (value && column.type === "ENUM") {
|
|
2062
|
+
columnSql += ` CHECK ("${column.name}" IN (${value}))`;
|
|
2063
|
+
}
|
|
2064
|
+
tableColumns.push(columnSql);
|
|
2065
|
+
}
|
|
2066
|
+
sql += tableColumns.join(", \n");
|
|
2067
|
+
for (const index of table.indexes) {
|
|
2068
|
+
if (!index.isPrimaryKey) {
|
|
2069
|
+
let unique = " ";
|
|
2070
|
+
if (index.isUnique) unique = "UNIQUE ";
|
|
2071
|
+
indexes.push(
|
|
2072
|
+
` CREATE ${unique}INDEX "${index.name}" ON "${table.name}" (${index.columns.map((item) => {
|
|
2073
|
+
return `"${item}" ${index.order}`;
|
|
2074
|
+
}).join(", ")});`
|
|
2075
|
+
);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
sql += "\n);";
|
|
2079
|
+
sqlStatements.push(sql);
|
|
2080
|
+
}
|
|
2081
|
+
for (const table of schema.database) {
|
|
2082
|
+
if (!table.foreignKeys.length) continue;
|
|
2083
|
+
const sql = `ALTER TABLE "${table.name}" `;
|
|
2084
|
+
const constraints = [];
|
|
2085
|
+
for (const foreignKey of table.foreignKeys) {
|
|
2086
|
+
let constraint = ` ADD CONSTRAINT "${foreignKey.name}"
|
|
2087
|
+
FOREIGN KEY ("${foreignKey.column}") REFERENCES "${foreignKey.refTable}" ("${foreignKey.refColumn}")`;
|
|
2088
|
+
constraint += ` ON DELETE ${foreignKey.onDelete}`;
|
|
2089
|
+
constraint += ` ON UPDATE ${foreignKey.onUpdate}`;
|
|
2090
|
+
constraints.push(constraint);
|
|
2091
|
+
}
|
|
2092
|
+
sqlStatements.push(sql + constraints.join(",\n") + ";");
|
|
2093
|
+
}
|
|
2094
|
+
for (const table of schema.database) {
|
|
2095
|
+
if (!table.checkConstraints.length) continue;
|
|
2096
|
+
const sql = `ALTER TABLE "${table.name}" `;
|
|
2097
|
+
const constraints = [];
|
|
2098
|
+
for (const check of table.checkConstraints) {
|
|
2099
|
+
const constraint = `ADD CONSTRAINT "${check.name}" CHECK (${check.check})`;
|
|
2100
|
+
constraints.push(constraint);
|
|
2101
|
+
}
|
|
2102
|
+
sqlStatements.push(sql + constraints.join(",\n") + ";");
|
|
2103
|
+
}
|
|
2104
|
+
sqlStatements.push(indexes.join("\n"));
|
|
2105
|
+
sqlStatements.push(triggers.join("\n"));
|
|
2106
|
+
return sqlStatements.join("\n\n");
|
|
2107
|
+
}
|
|
2108
|
+
async getScratchPool() {
|
|
2109
|
+
var _a2, _b;
|
|
2110
|
+
const scratchDbExists = await this.psqlConnectionPool.runQuery(
|
|
2111
|
+
`SELECT *
|
|
2112
|
+
FROM pg_database
|
|
2113
|
+
WHERE datname = '${this.psqlConnectionPool.poolConfig.database}_scratch';`,
|
|
2114
|
+
[],
|
|
2115
|
+
systemUser
|
|
2116
|
+
);
|
|
2117
|
+
if (scratchDbExists.length === 0) {
|
|
2118
|
+
await this.psqlConnectionPool.runQuery(
|
|
2119
|
+
`CREATE DATABASE ${this.psqlConnectionPool.poolConfig.database}_scratch;`,
|
|
2120
|
+
[],
|
|
2121
|
+
systemUser
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
const scratchPool = new PsqlPool({
|
|
2125
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
2126
|
+
port: this.psqlConnectionPool.poolConfig.port,
|
|
2127
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
2128
|
+
database: this.psqlConnectionPool.poolConfig.database + "_scratch",
|
|
2129
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
2130
|
+
max: this.psqlConnectionPool.poolConfig.max,
|
|
2131
|
+
idleTimeoutMillis: this.psqlConnectionPool.poolConfig.idleTimeoutMillis,
|
|
2132
|
+
connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
|
|
2133
|
+
});
|
|
2134
|
+
await scratchPool.runQuery(`DROP SCHEMA public CASCADE;`, [], systemUser);
|
|
2135
|
+
await scratchPool.runQuery(
|
|
2136
|
+
`CREATE SCHEMA public AUTHORIZATION ${this.psqlConnectionPool.poolConfig.user};`,
|
|
2137
|
+
[],
|
|
2138
|
+
systemUser
|
|
2139
|
+
);
|
|
2140
|
+
const schemaComment = await this.psqlConnectionPool.runQuery(
|
|
2141
|
+
`SELECT pg_description.description
|
|
2142
|
+
FROM pg_description
|
|
2143
|
+
JOIN pg_namespace ON pg_namespace.oid = pg_description.objoid
|
|
2144
|
+
WHERE pg_namespace.nspname = 'public';`,
|
|
2145
|
+
[],
|
|
2146
|
+
systemUser
|
|
2147
|
+
);
|
|
2148
|
+
if ((_a2 = schemaComment[0]) == null ? void 0 : _a2.description) {
|
|
2149
|
+
await scratchPool.runQuery(
|
|
2150
|
+
`COMMENT ON SCHEMA public IS '${(_b = schemaComment[0]) == null ? void 0 : _b.description}';`,
|
|
2151
|
+
[],
|
|
2152
|
+
systemUser
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
return scratchPool;
|
|
2156
|
+
}
|
|
2157
|
+
async diffDatabaseToSchema(schema) {
|
|
2158
|
+
const scratchPool = await this.getScratchPool();
|
|
2159
|
+
await this.createDatabaseFromSchema(schema, scratchPool);
|
|
2160
|
+
const originalClient = new Client({
|
|
2161
|
+
database: this.psqlConnectionPool.poolConfig.database,
|
|
2162
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
2163
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
2164
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
2165
|
+
port: this.psqlConnectionPool.poolConfig.port
|
|
2166
|
+
});
|
|
2167
|
+
const scratchClient = new Client({
|
|
2168
|
+
database: this.psqlConnectionPool.poolConfig.database + "_scratch",
|
|
2169
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
2170
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
2171
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
2172
|
+
port: this.psqlConnectionPool.poolConfig.port
|
|
2173
|
+
});
|
|
2174
|
+
const promises = [originalClient.connect(), scratchClient.connect()];
|
|
2175
|
+
await Promise.all(promises);
|
|
2176
|
+
const infoPromises = [(0, import_pg_info.default)({ client: originalClient }), (0, import_pg_info.default)({ client: scratchClient })];
|
|
2177
|
+
const [info1, info2] = await Promise.all(infoPromises);
|
|
2178
|
+
const diff = (0, import_pg_diff_sync.default)(info1, info2);
|
|
2179
|
+
const endPromises = [originalClient.end(), scratchClient.end()];
|
|
2180
|
+
await Promise.all(endPromises);
|
|
2181
|
+
return diff.join("\n");
|
|
1454
2182
|
}
|
|
1455
2183
|
createNestedSelect(req, schema, item, routeData, userRole, sqlParams) {
|
|
1456
2184
|
if (!item.subquery) return "";
|
|
@@ -1464,8 +2192,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1464
2192
|
)) {
|
|
1465
2193
|
return "'[]'";
|
|
1466
2194
|
}
|
|
1467
|
-
return `COALESCE((
|
|
1468
|
-
SELECT JSON_AGG(JSON_BUILD_OBJECT(
|
|
2195
|
+
return `COALESCE((SELECT JSON_AGG(JSON_BUILD_OBJECT(
|
|
1469
2196
|
${item.subquery.properties.map((nestedItem) => {
|
|
1470
2197
|
if (!this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
|
|
1471
2198
|
...routeData.joins,
|
|
@@ -1474,7 +2201,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1474
2201
|
return;
|
|
1475
2202
|
}
|
|
1476
2203
|
if (nestedItem.subquery) {
|
|
1477
|
-
return `
|
|
2204
|
+
return `'${nestedItem.name}', ${this.createNestedSelect(
|
|
1478
2205
|
// recursion
|
|
1479
2206
|
req,
|
|
1480
2207
|
schema,
|
|
@@ -1485,7 +2212,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1485
2212
|
)}`;
|
|
1486
2213
|
}
|
|
1487
2214
|
return `'${nestedItem.name}', ${escapeColumnName(nestedItem.selector)}`;
|
|
1488
|
-
}).filter(Boolean).join(",")}
|
|
2215
|
+
}).filter(Boolean).join(", ")}
|
|
1489
2216
|
))
|
|
1490
2217
|
FROM
|
|
1491
2218
|
"${item.subquery.table}"
|
|
@@ -1500,16 +2227,19 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1500
2227
|
parameterObj[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
|
|
1501
2228
|
});
|
|
1502
2229
|
const query = insertObjectQuery(routeData.table, __spreadValues(__spreadValues({}, req.data), parameterObj));
|
|
1503
|
-
const createdItem = await this.psqlConnectionPool.queryOne(
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
2230
|
+
const createdItem = await this.psqlConnectionPool.queryOne(
|
|
2231
|
+
query,
|
|
2232
|
+
sqlParams,
|
|
2233
|
+
req.requesterDetails
|
|
2234
|
+
);
|
|
2235
|
+
const insertId = createdItem.id;
|
|
2236
|
+
const whereId = {
|
|
2237
|
+
tableName: routeData.table,
|
|
2238
|
+
value: insertId,
|
|
2239
|
+
columnName: "id",
|
|
2240
|
+
operator: "="
|
|
2241
|
+
};
|
|
2242
|
+
const whereData = [whereId];
|
|
1513
2243
|
req.data = { id: insertId };
|
|
1514
2244
|
return this.executeGetRequest(req, __spreadProps(__spreadValues({}, routeData), { where: whereData }), schema);
|
|
1515
2245
|
}
|
|
@@ -1528,7 +2258,9 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1528
2258
|
let selectStatement = "SELECT \n";
|
|
1529
2259
|
selectStatement += ` ${selectColumns.map((item) => {
|
|
1530
2260
|
if (item.subquery) {
|
|
1531
|
-
return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${
|
|
2261
|
+
return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${escapeColumnName(
|
|
2262
|
+
item.name
|
|
2263
|
+
)}`;
|
|
1532
2264
|
}
|
|
1533
2265
|
return `${escapeColumnName(item.selector)} AS ${escapeColumnName(item.name)}`;
|
|
1534
2266
|
}).join(",\n ")}
|
|
@@ -1561,29 +2293,31 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1561
2293
|
);
|
|
1562
2294
|
} else if (routeData.type === "PAGED") {
|
|
1563
2295
|
const data = req.data;
|
|
1564
|
-
const
|
|
1565
|
-
`${selectStatement}${sqlStatement}${groupByOrderByStatement} LIMIT
|
|
1566
|
-
|
|
1567
|
-
[
|
|
1568
|
-
...sqlParams,
|
|
1569
|
-
data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER,
|
|
1570
|
-
(data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER,
|
|
1571
|
-
...sqlParams
|
|
1572
|
-
],
|
|
2296
|
+
const pagePromise = this.psqlConnectionPool.runQuery(
|
|
2297
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement}` + SQL`LIMIT ${data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER} OFFSET ${(data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER};`,
|
|
2298
|
+
sqlParams,
|
|
1573
2299
|
req.requesterDetails
|
|
1574
2300
|
);
|
|
2301
|
+
const totalQuery = `SELECT COUNT(${routeData.groupBy ? `DISTINCT ${routeData.groupBy.tableName}.${routeData.groupBy.columnName}` : "*"}) AS total
|
|
2302
|
+
${sqlStatement};`;
|
|
2303
|
+
const totalPromise = this.psqlConnectionPool.runQuery(
|
|
2304
|
+
totalQuery,
|
|
2305
|
+
sqlParams,
|
|
2306
|
+
req.requesterDetails
|
|
2307
|
+
);
|
|
2308
|
+
const [pageResults, totalResponse] = await Promise.all([pagePromise, totalPromise]);
|
|
1575
2309
|
let total = 0;
|
|
1576
|
-
if (import_core_utils5.ObjectUtils.isArrayWithData(
|
|
1577
|
-
total =
|
|
2310
|
+
if (import_core_utils5.ObjectUtils.isArrayWithData(totalResponse)) {
|
|
2311
|
+
total = totalResponse[0].total;
|
|
1578
2312
|
}
|
|
1579
|
-
return { data: pageResults
|
|
2313
|
+
return { data: pageResults, total };
|
|
1580
2314
|
} else {
|
|
1581
2315
|
throw new RsError("UNKNOWN_ERROR", "Unknown route type.");
|
|
1582
2316
|
}
|
|
1583
2317
|
}
|
|
1584
2318
|
async executeUpdateRequest(req, routeData, schema) {
|
|
1585
2319
|
const sqlParams = [];
|
|
1586
|
-
const
|
|
2320
|
+
const _a2 = req.body, { id } = _a2, bodyNoId = __objRest(_a2, ["id"]);
|
|
1587
2321
|
const table = schema.database.find((item) => {
|
|
1588
2322
|
return item.name === routeData.table;
|
|
1589
2323
|
});
|
|
@@ -1594,10 +2328,10 @@ ${sqlStatement};`,
|
|
|
1594
2328
|
for (const assignment of routeData.assignments) {
|
|
1595
2329
|
const column = table.columns.find((column2) => column2.name === assignment.name);
|
|
1596
2330
|
if (!column) continue;
|
|
1597
|
-
const
|
|
2331
|
+
const assignmentEscaped = escapeColumnName(assignment.name);
|
|
1598
2332
|
if (SqlUtils.convertDatabaseTypeToTypescript(column.type) === "number")
|
|
1599
|
-
bodyNoId[
|
|
1600
|
-
else bodyNoId[
|
|
2333
|
+
bodyNoId[assignmentEscaped] = Number(assignment.value);
|
|
2334
|
+
else bodyNoId[assignmentEscaped] = assignment.value;
|
|
1601
2335
|
}
|
|
1602
2336
|
const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
1603
2337
|
const query = updateObjectQuery(routeData.table, bodyNoId, whereClause);
|
|
@@ -1615,10 +2349,12 @@ ${sqlStatement};`,
|
|
|
1615
2349
|
req.requesterDetails.role,
|
|
1616
2350
|
sqlParams
|
|
1617
2351
|
);
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
2352
|
+
const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
2353
|
+
if (whereClause.replace(/\s/g, "") === "") {
|
|
2354
|
+
throw new RsError("DELETE_FORBIDDEN", "Deletes need a where clause");
|
|
2355
|
+
}
|
|
2356
|
+
const deleteStatement = `
|
|
2357
|
+
DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
|
|
1622
2358
|
await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams, req.requesterDetails);
|
|
1623
2359
|
return true;
|
|
1624
2360
|
}
|
|
@@ -1629,7 +2365,7 @@ ${sqlStatement};`,
|
|
|
1629
2365
|
throw new RsError("UNAUTHORIZED", "You do not have permission to access this table");
|
|
1630
2366
|
if (item.custom) {
|
|
1631
2367
|
const customReplaced = this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
|
|
1632
|
-
joinStatements += ` ${item.type} JOIN ${escapeColumnName(item.table)} ON ${customReplaced}
|
|
2368
|
+
joinStatements += ` ${item.type} JOIN ${escapeColumnName(item.table)}${item.alias ? `AS "${item.alias}"` : ""} ON ${customReplaced}
|
|
1633
2369
|
`;
|
|
1634
2370
|
} else {
|
|
1635
2371
|
joinStatements += ` ${item.type} JOIN ${escapeColumnName(item.table)}${item.alias ? `AS "${item.alias}"` : ""} ON "${baseTable}"."${item.localColumnName}" = ${escapeColumnName(item.alias ? item.alias : item.table)}.${escapeColumnName(
|
|
@@ -1681,38 +2417,37 @@ ${sqlStatement};`,
|
|
|
1681
2417
|
);
|
|
1682
2418
|
let operator = item.operator;
|
|
1683
2419
|
if (operator === "LIKE") {
|
|
1684
|
-
|
|
2420
|
+
item.value = `'%${item.value}%'`;
|
|
1685
2421
|
} else if (operator === "STARTS WITH") {
|
|
1686
2422
|
operator = "LIKE";
|
|
1687
|
-
|
|
2423
|
+
item.value = `'${item.value}%'`;
|
|
1688
2424
|
} else if (operator === "ENDS WITH") {
|
|
1689
2425
|
operator = "LIKE";
|
|
1690
|
-
|
|
2426
|
+
item.value = `'%${item.value}'`;
|
|
1691
2427
|
}
|
|
1692
2428
|
const replacedValue = this.replaceParamKeywords(item.value, routeData, req, sqlParams);
|
|
1693
|
-
|
|
1694
|
-
whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator} ${["IN", "NOT IN"].includes(operator) ? `(${escapedValue})` : escapedValue}
|
|
2429
|
+
whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator.replace("LIKE", "ILIKE")} ${["IN", "NOT IN"].includes(operator) ? `(${replacedValue})` : replacedValue}
|
|
1695
2430
|
`;
|
|
1696
2431
|
});
|
|
1697
2432
|
const data = req.data;
|
|
1698
2433
|
if (routeData.type === "PAGED" && !!(data == null ? void 0 : data.filter)) {
|
|
1699
2434
|
let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
1700
|
-
var
|
|
2435
|
+
var _a2;
|
|
1701
2436
|
const requestParam = routeData.request.find((item) => {
|
|
1702
2437
|
return item.name === value.replace("$", "");
|
|
1703
2438
|
});
|
|
1704
2439
|
if (!requestParam)
|
|
1705
2440
|
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1706
|
-
return ((
|
|
2441
|
+
return ((_a2 = data[requestParam.name]) == null ? void 0 : _a2.toString()) || "";
|
|
1707
2442
|
});
|
|
1708
2443
|
statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
1709
|
-
var
|
|
2444
|
+
var _a2;
|
|
1710
2445
|
const requestParam = routeData.request.find((item) => {
|
|
1711
2446
|
return item.name === value.replace("#", "");
|
|
1712
2447
|
});
|
|
1713
2448
|
if (!requestParam)
|
|
1714
2449
|
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1715
|
-
return ((
|
|
2450
|
+
return ((_a2 = data[requestParam.name]) == null ? void 0 : _a2.toString()) || "";
|
|
1716
2451
|
});
|
|
1717
2452
|
statement = filterPsqlParser_default.parse(statement);
|
|
1718
2453
|
if (whereClause.startsWith("WHERE")) {
|
|
@@ -1725,82 +2460,259 @@ ${sqlStatement};`,
|
|
|
1725
2460
|
}
|
|
1726
2461
|
return whereClause;
|
|
1727
2462
|
}
|
|
1728
|
-
|
|
2463
|
+
createUpdateTrigger(tableName, notify) {
|
|
2464
|
+
if (!notify) return "";
|
|
2465
|
+
if (notify === "ALL") {
|
|
2466
|
+
return `
|
|
2467
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_update()
|
|
2468
|
+
RETURNS TRIGGER AS $$
|
|
2469
|
+
DECLARE
|
|
2470
|
+
query_metadata JSON;
|
|
2471
|
+
BEGIN
|
|
2472
|
+
SELECT INTO query_metadata
|
|
2473
|
+
(regexp_match(
|
|
2474
|
+
current_query(),
|
|
2475
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2476
|
+
))[1]::json;
|
|
1729
2477
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
try {
|
|
1750
|
-
const response = await this.pool.query(formattedQuery, options);
|
|
1751
|
-
if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
|
|
1752
|
-
else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
|
|
1753
|
-
return response.rows[0];
|
|
1754
|
-
} catch (error) {
|
|
1755
|
-
console.error(error, query, options);
|
|
1756
|
-
if (RsError.isRsError(error)) throw error;
|
|
1757
|
-
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1758
|
-
throw new RsError("DUPLICATE", error.message);
|
|
1759
|
-
}
|
|
1760
|
-
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
2478
|
+
PERFORM pg_notify(
|
|
2479
|
+
'update',
|
|
2480
|
+
json_build_object(
|
|
2481
|
+
'table', '${tableName}',
|
|
2482
|
+
'queryMetadata', query_metadata,
|
|
2483
|
+
'changedId', NEW.id,
|
|
2484
|
+
'record', NEW,
|
|
2485
|
+
'previousRecord', OLD
|
|
2486
|
+
)::text
|
|
2487
|
+
);
|
|
2488
|
+
RETURN NEW;
|
|
2489
|
+
END;
|
|
2490
|
+
$$ LANGUAGE plpgsql;
|
|
2491
|
+
|
|
2492
|
+
CREATE OR REPLACE TRIGGER ${tableName}_update
|
|
2493
|
+
AFTER UPDATE ON "${tableName}"
|
|
2494
|
+
FOR EACH ROW
|
|
2495
|
+
EXECUTE FUNCTION notify_${tableName}_update();
|
|
2496
|
+
`;
|
|
1761
2497
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
2498
|
+
const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
|
|
2499
|
+
const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
|
|
2500
|
+
return `
|
|
2501
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_update()
|
|
2502
|
+
RETURNS TRIGGER AS $$
|
|
2503
|
+
DECLARE
|
|
2504
|
+
query_metadata JSON;
|
|
2505
|
+
BEGIN
|
|
2506
|
+
SELECT INTO query_metadata
|
|
2507
|
+
(regexp_match(
|
|
2508
|
+
current_query(),
|
|
2509
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2510
|
+
))[1]::json;
|
|
2511
|
+
|
|
2512
|
+
PERFORM pg_notify(
|
|
2513
|
+
'update',
|
|
2514
|
+
json_build_object(
|
|
2515
|
+
'table', '${tableName}',
|
|
2516
|
+
'queryMetadata', query_metadata,
|
|
2517
|
+
'changedId', NEW.id,
|
|
2518
|
+
'record', json_build_object(
|
|
2519
|
+
${notifyColumnNewBuildString}
|
|
2520
|
+
),
|
|
2521
|
+
'previousRecord', json_build_object(
|
|
2522
|
+
${notifyColumnOldBuildString}
|
|
2523
|
+
)
|
|
2524
|
+
)::text
|
|
2525
|
+
);
|
|
2526
|
+
RETURN NEW;
|
|
2527
|
+
END;
|
|
2528
|
+
$$ LANGUAGE plpgsql;
|
|
2529
|
+
|
|
2530
|
+
CREATE OR REPLACE TRIGGER ${tableName}_update
|
|
2531
|
+
AFTER UPDATE ON "${tableName}"
|
|
2532
|
+
FOR EACH ROW
|
|
2533
|
+
EXECUTE FUNCTION notify_${tableName}_update();
|
|
2534
|
+
`;
|
|
2535
|
+
}
|
|
2536
|
+
createDeleteTrigger(tableName, notify) {
|
|
2537
|
+
if (!notify) return "";
|
|
2538
|
+
if (notify === "ALL") {
|
|
2539
|
+
return `
|
|
2540
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
|
|
2541
|
+
RETURNS TRIGGER AS $$
|
|
2542
|
+
DECLARE
|
|
2543
|
+
query_metadata JSON;
|
|
2544
|
+
BEGIN
|
|
2545
|
+
SELECT INTO query_metadata
|
|
2546
|
+
(regexp_match(
|
|
2547
|
+
current_query(),
|
|
2548
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2549
|
+
))[1]::json;
|
|
2550
|
+
|
|
2551
|
+
PERFORM pg_notify(
|
|
2552
|
+
'delete',
|
|
2553
|
+
json_build_object(
|
|
2554
|
+
'table', '${tableName}',
|
|
2555
|
+
'queryMetadata', query_metadata,
|
|
2556
|
+
'deletedId', OLD.id,
|
|
2557
|
+
'previousRecord', OLD
|
|
2558
|
+
)::text
|
|
2559
|
+
);
|
|
2560
|
+
RETURN NEW;
|
|
2561
|
+
END;
|
|
2562
|
+
$$ LANGUAGE plpgsql;
|
|
2563
|
+
|
|
2564
|
+
CREATE OR REPLACE TRIGGER "${tableName}_delete"
|
|
2565
|
+
AFTER DELETE ON "${tableName}"
|
|
2566
|
+
FOR EACH ROW
|
|
2567
|
+
EXECUTE FUNCTION notify_${tableName}_delete();
|
|
2568
|
+
`;
|
|
1778
2569
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
2570
|
+
const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
|
|
2571
|
+
return `
|
|
2572
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
|
|
2573
|
+
RETURNS TRIGGER AS $$
|
|
2574
|
+
DECLARE
|
|
2575
|
+
query_metadata JSON;
|
|
2576
|
+
BEGIN
|
|
2577
|
+
SELECT INTO query_metadata
|
|
2578
|
+
(regexp_match(
|
|
2579
|
+
current_query(),
|
|
2580
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2581
|
+
))[1]::json;
|
|
2582
|
+
|
|
2583
|
+
PERFORM pg_notify(
|
|
2584
|
+
'delete',
|
|
2585
|
+
json_build_object(
|
|
2586
|
+
'table', '${tableName}',
|
|
2587
|
+
'queryMetadata', query_metadata,
|
|
2588
|
+
'deletedId', OLD.id,
|
|
2589
|
+
'previousRecord', json_build_object(
|
|
2590
|
+
${notifyColumnOldBuildString}
|
|
2591
|
+
)
|
|
2592
|
+
)::text
|
|
2593
|
+
);
|
|
2594
|
+
RETURN NEW;
|
|
2595
|
+
END;
|
|
2596
|
+
$$ LANGUAGE plpgsql;
|
|
2597
|
+
|
|
2598
|
+
CREATE OR REPLACE TRIGGER "${tableName}_delete"
|
|
2599
|
+
AFTER DELETE ON "${tableName}"
|
|
2600
|
+
FOR EACH ROW
|
|
2601
|
+
EXECUTE FUNCTION notify_${tableName}_delete();
|
|
2602
|
+
`;
|
|
2603
|
+
}
|
|
2604
|
+
createInsertTriggers(tableName, notify) {
|
|
2605
|
+
if (!notify) return "";
|
|
2606
|
+
if (notify === "ALL") {
|
|
2607
|
+
return `
|
|
2608
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
|
|
2609
|
+
RETURNS TRIGGER AS $$
|
|
2610
|
+
DECLARE
|
|
2611
|
+
query_metadata JSON;
|
|
2612
|
+
BEGIN
|
|
2613
|
+
SELECT INTO query_metadata
|
|
2614
|
+
(regexp_match(
|
|
2615
|
+
current_query(),
|
|
2616
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2617
|
+
))[1]::json;
|
|
2618
|
+
|
|
2619
|
+
PERFORM pg_notify(
|
|
2620
|
+
'insert',
|
|
2621
|
+
json_build_object(
|
|
2622
|
+
'table', '${tableName}',
|
|
2623
|
+
'queryMetadata', query_metadata,
|
|
2624
|
+
'insertedId', NEW.id,
|
|
2625
|
+
'record', NEW
|
|
2626
|
+
)::text
|
|
2627
|
+
);
|
|
2628
|
+
|
|
2629
|
+
RETURN NEW;
|
|
2630
|
+
END;
|
|
2631
|
+
$$ LANGUAGE plpgsql;
|
|
2632
|
+
|
|
2633
|
+
CREATE OR REPLACE TRIGGER "${tableName}_insert"
|
|
2634
|
+
AFTER INSERT ON "${tableName}"
|
|
2635
|
+
FOR EACH ROW
|
|
2636
|
+
EXECUTE FUNCTION notify_${tableName}_insert();
|
|
2637
|
+
`;
|
|
1792
2638
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
2639
|
+
const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
|
|
2640
|
+
return `
|
|
2641
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
|
|
2642
|
+
RETURNS TRIGGER AS $$
|
|
2643
|
+
DECLARE
|
|
2644
|
+
query_metadata JSON;
|
|
2645
|
+
BEGIN
|
|
2646
|
+
SELECT INTO query_metadata
|
|
2647
|
+
(regexp_match(
|
|
2648
|
+
current_query(),
|
|
2649
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2650
|
+
))[1]::json;
|
|
2651
|
+
|
|
2652
|
+
PERFORM pg_notify(
|
|
2653
|
+
'insert',
|
|
2654
|
+
json_build_object(
|
|
2655
|
+
'table', '${tableName}',
|
|
2656
|
+
'queryMetadata', query_metadata,
|
|
2657
|
+
'insertedId', NEW.id,
|
|
2658
|
+
'record', json_build_object(
|
|
2659
|
+
${notifyColumnNewBuildString}
|
|
2660
|
+
)
|
|
2661
|
+
)::text
|
|
2662
|
+
);
|
|
2663
|
+
|
|
2664
|
+
RETURN NEW;
|
|
2665
|
+
END;
|
|
2666
|
+
$$ LANGUAGE plpgsql;
|
|
2667
|
+
|
|
2668
|
+
CREATE OR REPLACE TRIGGER "${tableName}_insert"
|
|
2669
|
+
AFTER INSERT ON "${tableName}"
|
|
2670
|
+
FOR EACH ROW
|
|
2671
|
+
EXECUTE FUNCTION notify_${tableName}_insert();
|
|
2672
|
+
`;
|
|
2673
|
+
}
|
|
2674
|
+
schemaToPsqlType(column) {
|
|
2675
|
+
if (column.hasAutoIncrement) return "BIGSERIAL";
|
|
2676
|
+
if (column.type === "ENUM") return `TEXT`;
|
|
2677
|
+
if (column.type === "DATETIME") return "TIMESTAMPTZ";
|
|
2678
|
+
if (column.type === "MEDIUMINT") return "INT";
|
|
2679
|
+
return column.type;
|
|
2680
|
+
}
|
|
2681
|
+
};
|
|
2682
|
+
|
|
2683
|
+
// src/restura/utils/TempCache.ts
|
|
2684
|
+
var import_fs3 = __toESM(require("fs"));
|
|
2685
|
+
var import_path4 = __toESM(require("path"));
|
|
2686
|
+
var import_core_utils6 = require("@redskytech/core-utils");
|
|
2687
|
+
var import_internal3 = require("@restura/internal");
|
|
2688
|
+
var import_bluebird3 = __toESM(require("bluebird"));
|
|
2689
|
+
var os2 = __toESM(require("os"));
|
|
2690
|
+
var TempCache = class {
|
|
2691
|
+
constructor(location) {
|
|
2692
|
+
this.maxDurationDays = 7;
|
|
2693
|
+
this.location = location || os2.tmpdir();
|
|
2694
|
+
import_internal3.FileUtils.ensureDir(this.location).catch((e) => {
|
|
2695
|
+
throw e;
|
|
2696
|
+
});
|
|
2697
|
+
}
|
|
2698
|
+
async cleanup() {
|
|
2699
|
+
const fileList = await import_fs3.default.promises.readdir(this.location);
|
|
2700
|
+
await import_bluebird3.default.map(
|
|
2701
|
+
fileList,
|
|
2702
|
+
async (file) => {
|
|
2703
|
+
const fullFilePath = import_path4.default.join(this.location, file);
|
|
2704
|
+
const fileStats = await import_fs3.default.promises.stat(fullFilePath);
|
|
2705
|
+
if (import_core_utils6.DateUtils.daysBetweenStartAndEndDates(new Date(fileStats.mtimeMs), /* @__PURE__ */ new Date()) > this.maxDurationDays) {
|
|
2706
|
+
logger.info(`Deleting old temp file: ${file}`);
|
|
2707
|
+
await import_fs3.default.promises.unlink(fullFilePath);
|
|
2708
|
+
}
|
|
2709
|
+
},
|
|
2710
|
+
{ concurrency: 10 }
|
|
2711
|
+
);
|
|
1799
2712
|
}
|
|
1800
2713
|
};
|
|
1801
2714
|
|
|
1802
2715
|
// src/restura/restura.ts
|
|
1803
|
-
var { types } = import_pg2.default;
|
|
1804
2716
|
var ResturaEngine = class {
|
|
1805
2717
|
constructor() {
|
|
1806
2718
|
this.publicEndpoints = {
|
|
@@ -1818,10 +2730,11 @@ var ResturaEngine = class {
|
|
|
1818
2730
|
* @returns A promise that resolves when the initialization is complete.
|
|
1819
2731
|
*/
|
|
1820
2732
|
async init(app, authenticationHandler, psqlConnectionPool) {
|
|
1821
|
-
this.resturaConfig =
|
|
2733
|
+
this.resturaConfig = import_internal4.config.validate("restura", resturaConfigSchema);
|
|
2734
|
+
this.multerCommonUpload = getMulterUpload(this.resturaConfig.fileTempCachePath);
|
|
2735
|
+
new TempCache(this.resturaConfig.fileTempCachePath);
|
|
1822
2736
|
this.psqlConnectionPool = psqlConnectionPool;
|
|
1823
|
-
this.psqlEngine = new PsqlEngine(this.psqlConnectionPool);
|
|
1824
|
-
setupPgReturnTypes();
|
|
2737
|
+
this.psqlEngine = new PsqlEngine(this.psqlConnectionPool, true);
|
|
1825
2738
|
await customApiFactory_default.loadApiFiles(this.resturaConfig.customApiFolderPath);
|
|
1826
2739
|
this.authenticationHandler = authenticationHandler;
|
|
1827
2740
|
app.use((0, import_compression.default)());
|
|
@@ -1885,10 +2798,7 @@ var ResturaEngine = class {
|
|
|
1885
2798
|
* @returns A promise that resolves when the API has been successfully generated and written to the output file.
|
|
1886
2799
|
*/
|
|
1887
2800
|
async generateApiFromSchema(outputFile, providedSchema) {
|
|
1888
|
-
|
|
1889
|
-
outputFile,
|
|
1890
|
-
await apiGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
|
|
1891
|
-
);
|
|
2801
|
+
import_fs4.default.writeFileSync(outputFile, await apiGenerator(providedSchema));
|
|
1892
2802
|
}
|
|
1893
2803
|
/**
|
|
1894
2804
|
* Generates a model from the provided schema and writes it to the specified output file.
|
|
@@ -1898,10 +2808,15 @@ var ResturaEngine = class {
|
|
|
1898
2808
|
* @returns A promise that resolves when the model has been successfully written to the output file.
|
|
1899
2809
|
*/
|
|
1900
2810
|
async generateModelFromSchema(outputFile, providedSchema) {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
2811
|
+
import_fs4.default.writeFileSync(outputFile, await modelGenerator(providedSchema));
|
|
2812
|
+
}
|
|
2813
|
+
/**
|
|
2814
|
+
* Generates the ambient module declaration for Restura global types and writes it to the specified output file.
|
|
2815
|
+
* These types are used sometimes in the CustomTypes
|
|
2816
|
+
* @param outputFile
|
|
2817
|
+
*/
|
|
2818
|
+
generateResturaGlobalTypes(outputFile) {
|
|
2819
|
+
import_fs4.default.writeFileSync(outputFile, resturaGlobalTypesGenerator());
|
|
1905
2820
|
}
|
|
1906
2821
|
/**
|
|
1907
2822
|
* Retrieves the latest file system schema for Restura.
|
|
@@ -1910,12 +2825,12 @@ var ResturaEngine = class {
|
|
|
1910
2825
|
* @throws {Error} If the schema file is missing or the schema is not valid.
|
|
1911
2826
|
*/
|
|
1912
2827
|
async getLatestFileSystemSchema() {
|
|
1913
|
-
if (!
|
|
2828
|
+
if (!import_fs4.default.existsSync(this.resturaConfig.schemaFilePath)) {
|
|
1914
2829
|
logger.error(`Missing restura schema file, expected path: ${this.resturaConfig.schemaFilePath}`);
|
|
1915
2830
|
throw new Error("Missing restura schema file");
|
|
1916
2831
|
}
|
|
1917
|
-
const schemaFileData =
|
|
1918
|
-
const schema =
|
|
2832
|
+
const schemaFileData = import_fs4.default.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
|
|
2833
|
+
const schema = import_core_utils7.ObjectUtils.safeParse(schemaFileData);
|
|
1919
2834
|
const isValid = await isSchemaValid(schema);
|
|
1920
2835
|
if (!isValid) {
|
|
1921
2836
|
logger.error("Schema is not valid");
|
|
@@ -1923,28 +2838,6 @@ var ResturaEngine = class {
|
|
|
1923
2838
|
}
|
|
1924
2839
|
return schema;
|
|
1925
2840
|
}
|
|
1926
|
-
/**
|
|
1927
|
-
* Asynchronously generates and retrieves hashes for the provided schema and related generated files.
|
|
1928
|
-
*
|
|
1929
|
-
* @param providedSchema - The schema for which hashes need to be generated.
|
|
1930
|
-
* @returns A promise that resolves to an object containing:
|
|
1931
|
-
* - `schemaHash`: The hash of the provided schema.
|
|
1932
|
-
* - `apiCreatedSchemaHash`: The hash extracted from the generated `api.d.ts` file.
|
|
1933
|
-
* - `modelCreatedSchemaHash`: The hash extracted from the generated `models.d.ts` file.
|
|
1934
|
-
*/
|
|
1935
|
-
async getHashes(providedSchema) {
|
|
1936
|
-
var _a, _b, _c, _d;
|
|
1937
|
-
const schemaHash = await this.generateHashForSchema(providedSchema);
|
|
1938
|
-
const apiFile = import_fs3.default.readFileSync(import_path3.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
|
|
1939
|
-
const apiCreatedSchemaHash = (_b = (_a = apiFile.toString().match(/\((.*)\)/)) == null ? void 0 : _a[1]) != null ? _b : "";
|
|
1940
|
-
const modelFile = import_fs3.default.readFileSync(import_path3.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
|
|
1941
|
-
const modelCreatedSchemaHash = (_d = (_c = modelFile.toString().match(/\((.*)\)/)) == null ? void 0 : _c[1]) != null ? _d : "";
|
|
1942
|
-
return {
|
|
1943
|
-
schemaHash,
|
|
1944
|
-
apiCreatedSchemaHash,
|
|
1945
|
-
modelCreatedSchemaHash
|
|
1946
|
-
};
|
|
1947
|
-
}
|
|
1948
2841
|
async reloadEndpoints() {
|
|
1949
2842
|
this.schema = await this.getLatestFileSystemSchema();
|
|
1950
2843
|
this.customTypeValidation = customTypeValidationGenerator(this.schema);
|
|
@@ -1973,30 +2866,10 @@ var ResturaEngine = class {
|
|
|
1973
2866
|
logger.info(`Restura loaded (${routeCount}) endpoint${routeCount > 1 ? "s" : ""}`);
|
|
1974
2867
|
}
|
|
1975
2868
|
async validateGeneratedTypesFolder() {
|
|
1976
|
-
if (!
|
|
1977
|
-
|
|
1978
|
-
}
|
|
1979
|
-
const hasApiFile = import_fs3.default.existsSync(import_path3.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
|
|
1980
|
-
const hasModelsFile = import_fs3.default.existsSync(import_path3.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
|
|
1981
|
-
if (!hasApiFile) {
|
|
1982
|
-
await this.generateApiFromSchema(import_path3.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1983
|
-
}
|
|
1984
|
-
if (!hasModelsFile) {
|
|
1985
|
-
await this.generateModelFromSchema(
|
|
1986
|
-
import_path3.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1987
|
-
this.schema
|
|
1988
|
-
);
|
|
1989
|
-
}
|
|
1990
|
-
const hashes = await this.getHashes(this.schema);
|
|
1991
|
-
if (hashes.schemaHash !== hashes.apiCreatedSchemaHash) {
|
|
1992
|
-
await this.generateApiFromSchema(import_path3.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1993
|
-
}
|
|
1994
|
-
if (hashes.schemaHash !== hashes.modelCreatedSchemaHash) {
|
|
1995
|
-
await this.generateModelFromSchema(
|
|
1996
|
-
import_path3.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1997
|
-
this.schema
|
|
1998
|
-
);
|
|
2869
|
+
if (!import_fs4.default.existsSync(this.resturaConfig.generatedTypesPath)) {
|
|
2870
|
+
import_fs4.default.mkdirSync(this.resturaConfig.generatedTypesPath, { recursive: true });
|
|
1999
2871
|
}
|
|
2872
|
+
this.updateTypes();
|
|
2000
2873
|
}
|
|
2001
2874
|
resturaAuthentication(req, res, next) {
|
|
2002
2875
|
if (req.headers["x-auth-token"] !== this.resturaConfig.authToken) res.status(401).send("Unauthorized");
|
|
@@ -2004,7 +2877,7 @@ var ResturaEngine = class {
|
|
|
2004
2877
|
}
|
|
2005
2878
|
async previewCreateSchema(req, res) {
|
|
2006
2879
|
try {
|
|
2007
|
-
const schemaDiff =
|
|
2880
|
+
const schemaDiff = await compareSchema_default.diffSchema(req.data, this.schema, this.psqlEngine);
|
|
2008
2881
|
res.send({ data: schemaDiff });
|
|
2009
2882
|
} catch (err) {
|
|
2010
2883
|
res.status(400).send(err);
|
|
@@ -2012,7 +2885,7 @@ var ResturaEngine = class {
|
|
|
2012
2885
|
}
|
|
2013
2886
|
async updateSchema(req, res) {
|
|
2014
2887
|
try {
|
|
2015
|
-
this.schema = req.data;
|
|
2888
|
+
this.schema = sortObjectKeysAlphabetically(req.data);
|
|
2016
2889
|
await this.storeFileSystemSchema();
|
|
2017
2890
|
await this.reloadEndpoints();
|
|
2018
2891
|
await this.updateTypes();
|
|
@@ -2023,11 +2896,12 @@ var ResturaEngine = class {
|
|
|
2023
2896
|
}
|
|
2024
2897
|
}
|
|
2025
2898
|
async updateTypes() {
|
|
2026
|
-
await this.generateApiFromSchema(
|
|
2899
|
+
await this.generateApiFromSchema(import_path5.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
2027
2900
|
await this.generateModelFromSchema(
|
|
2028
|
-
|
|
2901
|
+
import_path5.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
2029
2902
|
this.schema
|
|
2030
2903
|
);
|
|
2904
|
+
this.generateResturaGlobalTypes(import_path5.default.join(this.resturaConfig.generatedTypesPath, "restura.d.ts"));
|
|
2031
2905
|
}
|
|
2032
2906
|
async getSchema(req, res) {
|
|
2033
2907
|
res.send({ data: this.schema });
|
|
@@ -2035,19 +2909,38 @@ var ResturaEngine = class {
|
|
|
2035
2909
|
async getSchemaAndTypes(req, res) {
|
|
2036
2910
|
try {
|
|
2037
2911
|
const schema = await this.getLatestFileSystemSchema();
|
|
2038
|
-
const
|
|
2039
|
-
const
|
|
2040
|
-
const modelsText = await modelGenerator(schema, schemaHash);
|
|
2912
|
+
const apiText = await apiGenerator(schema);
|
|
2913
|
+
const modelsText = await modelGenerator(schema);
|
|
2041
2914
|
res.send({ schema, api: apiText, models: modelsText });
|
|
2042
2915
|
} catch (err) {
|
|
2043
2916
|
res.status(400).send({ error: err });
|
|
2044
2917
|
}
|
|
2045
2918
|
}
|
|
2919
|
+
async getMulterFilesIfAny(req, res, routeData) {
|
|
2920
|
+
var _a2;
|
|
2921
|
+
if (!((_a2 = req.header("content-type")) == null ? void 0 : _a2.includes("multipart/form-data"))) return;
|
|
2922
|
+
if (!this.isCustomRoute(routeData)) return;
|
|
2923
|
+
if (!routeData.fileUploadType) {
|
|
2924
|
+
throw new RsError("BAD_REQUEST", "File upload type not defined for route");
|
|
2925
|
+
}
|
|
2926
|
+
const multerFileUploadFunction = routeData.fileUploadType === "MULTIPLE" ? this.multerCommonUpload.array("files") : this.multerCommonUpload.single("file");
|
|
2927
|
+
return new Promise((resolve2, reject) => {
|
|
2928
|
+
multerFileUploadFunction(req, res, (err) => {
|
|
2929
|
+
if (err) {
|
|
2930
|
+
logger.warn("Multer error: " + err);
|
|
2931
|
+
reject(err);
|
|
2932
|
+
}
|
|
2933
|
+
if (req.body["data"]) req.body = JSON.parse(req.body["data"]);
|
|
2934
|
+
resolve2();
|
|
2935
|
+
});
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2046
2938
|
async executeRouteLogic(req, res, next) {
|
|
2047
2939
|
try {
|
|
2048
2940
|
const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
|
|
2049
2941
|
this.validateAuthorization(req, routeData);
|
|
2050
|
-
|
|
2942
|
+
await this.getMulterFilesIfAny(req, res, routeData);
|
|
2943
|
+
requestValidator(req, routeData, this.customTypeValidation);
|
|
2051
2944
|
if (this.isCustomRoute(routeData)) {
|
|
2052
2945
|
await this.runCustomRouteLogic(req, res, routeData);
|
|
2053
2946
|
return;
|
|
@@ -2072,33 +2965,21 @@ var ResturaEngine = class {
|
|
|
2072
2965
|
let domain = routeData.path.split("/")[1];
|
|
2073
2966
|
domain = domain.split("-").reduce((acc, value, index) => {
|
|
2074
2967
|
if (index === 0) acc = value;
|
|
2075
|
-
else acc +=
|
|
2968
|
+
else acc += import_core_utils7.StringUtils.capitalizeFirst(value);
|
|
2076
2969
|
return acc;
|
|
2077
2970
|
}, "");
|
|
2078
|
-
const customApiName = `${
|
|
2971
|
+
const customApiName = `${import_core_utils7.StringUtils.capitalizeFirst(domain)}Api${import_core_utils7.StringUtils.capitalizeFirst(version)}`;
|
|
2079
2972
|
const customApi = customApiFactory_default.getCustomApi(customApiName);
|
|
2080
2973
|
if (!customApi) throw new RsError("NOT_FOUND", `API domain ${domain}-${version} not found`);
|
|
2081
2974
|
const functionName = `${routeData.method.toLowerCase()}${routeData.path.replace(new RegExp("-", "g"), "/").split("/").reduce((acc, cur) => {
|
|
2082
2975
|
if (cur === "") return acc;
|
|
2083
|
-
return acc +
|
|
2976
|
+
return acc + import_core_utils7.StringUtils.capitalizeFirst(cur);
|
|
2084
2977
|
}, "")}`;
|
|
2085
2978
|
const customFunction = customApi[functionName];
|
|
2086
|
-
if (!customFunction)
|
|
2979
|
+
if (!customFunction)
|
|
2980
|
+
throw new RsError("NOT_FOUND", `API path ${routeData.path} not implemented ${functionName}`);
|
|
2087
2981
|
await customFunction(req, res, routeData);
|
|
2088
2982
|
}
|
|
2089
|
-
async generateHashForSchema(providedSchema) {
|
|
2090
|
-
const schemaPrettyStr = await prettier3.format(JSON.stringify(providedSchema), __spreadValues({
|
|
2091
|
-
parser: "json"
|
|
2092
|
-
}, {
|
|
2093
|
-
trailingComma: "none",
|
|
2094
|
-
tabWidth: 4,
|
|
2095
|
-
useTabs: true,
|
|
2096
|
-
endOfLine: "lf",
|
|
2097
|
-
printWidth: 120,
|
|
2098
|
-
singleQuote: true
|
|
2099
|
-
}));
|
|
2100
|
-
return (0, import_crypto.createHash)("sha256").update(schemaPrettyStr).digest("hex");
|
|
2101
|
-
}
|
|
2102
2983
|
async storeFileSystemSchema() {
|
|
2103
2984
|
const schemaPrettyStr = await prettier3.format(JSON.stringify(this.schema), __spreadValues({
|
|
2104
2985
|
parser: "json"
|
|
@@ -2110,7 +2991,7 @@ var ResturaEngine = class {
|
|
|
2110
2991
|
printWidth: 120,
|
|
2111
2992
|
singleQuote: true
|
|
2112
2993
|
}));
|
|
2113
|
-
|
|
2994
|
+
import_fs4.default.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
|
|
2114
2995
|
}
|
|
2115
2996
|
resetPublicEndpoints() {
|
|
2116
2997
|
this.publicEndpoints = {
|
|
@@ -2127,13 +3008,13 @@ var ResturaEngine = class {
|
|
|
2127
3008
|
if (!routeData.roles.includes(role))
|
|
2128
3009
|
throw new RsError("UNAUTHORIZED", "Not authorized to access this endpoint");
|
|
2129
3010
|
}
|
|
2130
|
-
getRouteData(method, baseUrl,
|
|
3011
|
+
getRouteData(method, baseUrl, path5) {
|
|
2131
3012
|
const endpoint = this.schema.endpoints.find((item) => {
|
|
2132
3013
|
return item.baseUrl === baseUrl;
|
|
2133
3014
|
});
|
|
2134
3015
|
if (!endpoint) throw new RsError("NOT_FOUND", "Route not found");
|
|
2135
3016
|
const route = endpoint.routes.find((item) => {
|
|
2136
|
-
return item.method === method && item.path ===
|
|
3017
|
+
return item.method === method && item.path === path5;
|
|
2137
3018
|
});
|
|
2138
3019
|
if (!route) throw new RsError("NOT_FOUND", "Route not found");
|
|
2139
3020
|
return route;
|
|
@@ -2154,6 +3035,9 @@ __decorateClass([
|
|
|
2154
3035
|
__decorateClass([
|
|
2155
3036
|
boundMethod
|
|
2156
3037
|
], ResturaEngine.prototype, "getSchemaAndTypes", 1);
|
|
3038
|
+
__decorateClass([
|
|
3039
|
+
boundMethod
|
|
3040
|
+
], ResturaEngine.prototype, "getMulterFilesIfAny", 1);
|
|
2157
3041
|
__decorateClass([
|
|
2158
3042
|
boundMethod
|
|
2159
3043
|
], ResturaEngine.prototype, "executeRouteLogic", 1);
|
|
@@ -2163,25 +3047,55 @@ __decorateClass([
|
|
|
2163
3047
|
__decorateClass([
|
|
2164
3048
|
boundMethod
|
|
2165
3049
|
], ResturaEngine.prototype, "runCustomRouteLogic", 1);
|
|
2166
|
-
var setupPgReturnTypes = () => {
|
|
2167
|
-
const TIMESTAMPTZ_OID = 1184;
|
|
2168
|
-
types.setTypeParser(TIMESTAMPTZ_OID, (val) => {
|
|
2169
|
-
return val === null ? null : new Date(val).toISOString();
|
|
2170
|
-
});
|
|
2171
|
-
const BIGINT_OID = 20;
|
|
2172
|
-
types.setTypeParser(BIGINT_OID, (val) => {
|
|
2173
|
-
return val === null ? null : Number(val);
|
|
2174
|
-
});
|
|
2175
|
-
};
|
|
2176
|
-
setupPgReturnTypes();
|
|
2177
3050
|
var restura = new ResturaEngine();
|
|
3051
|
+
|
|
3052
|
+
// src/restura/sql/PsqlTransaction.ts
|
|
3053
|
+
var import_pg3 = __toESM(require("pg"));
|
|
3054
|
+
var { Client: Client2 } = import_pg3.default;
|
|
3055
|
+
var PsqlTransaction = class extends PsqlConnection {
|
|
3056
|
+
constructor(clientConfig, instanceId) {
|
|
3057
|
+
super(instanceId);
|
|
3058
|
+
this.clientConfig = clientConfig;
|
|
3059
|
+
this.client = new Client2(clientConfig);
|
|
3060
|
+
this.connectPromise = this.client.connect();
|
|
3061
|
+
this.beginTransactionPromise = this.beginTransaction();
|
|
3062
|
+
}
|
|
3063
|
+
async close() {
|
|
3064
|
+
if (this.client) {
|
|
3065
|
+
await this.client.end();
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
async beginTransaction() {
|
|
3069
|
+
await this.connectPromise;
|
|
3070
|
+
return this.client.query("BEGIN");
|
|
3071
|
+
}
|
|
3072
|
+
async rollback() {
|
|
3073
|
+
return this.query("ROLLBACK");
|
|
3074
|
+
}
|
|
3075
|
+
async commit() {
|
|
3076
|
+
return this.query("COMMIT");
|
|
3077
|
+
}
|
|
3078
|
+
async release() {
|
|
3079
|
+
return this.client.end();
|
|
3080
|
+
}
|
|
3081
|
+
async query(query, values) {
|
|
3082
|
+
await this.connectPromise;
|
|
3083
|
+
await this.beginTransactionPromise;
|
|
3084
|
+
return this.client.query(query, values);
|
|
3085
|
+
}
|
|
3086
|
+
};
|
|
2178
3087
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2179
3088
|
0 && (module.exports = {
|
|
2180
3089
|
HtmlStatusCodes,
|
|
3090
|
+
PsqlConnection,
|
|
3091
|
+
PsqlEngine,
|
|
2181
3092
|
PsqlPool,
|
|
3093
|
+
PsqlTransaction,
|
|
2182
3094
|
RsError,
|
|
2183
3095
|
SQL,
|
|
2184
3096
|
escapeColumnName,
|
|
3097
|
+
eventManager,
|
|
3098
|
+
filterPsqlParser,
|
|
2185
3099
|
insertObjectQuery,
|
|
2186
3100
|
isValueNumber,
|
|
2187
3101
|
logger,
|