@restura/core 0.1.0-alpha.9 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1394 -252
- package/dist/index.d.ts +1394 -252
- package/dist/index.js +1345 -451
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1332 -443
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -2
package/dist/index.js
CHANGED
|
@@ -68,10 +68,15 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
68
68
|
var src_exports = {};
|
|
69
69
|
__export(src_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,
|
|
@@ -86,18 +91,11 @@ 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}${column.isNullable ? "?" : ""}: ${SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value)};
|
|
985
|
+
`;
|
|
986
|
+
}
|
|
987
|
+
modelString += ` }
|
|
988
|
+
`;
|
|
989
|
+
return modelString;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// src/restura/generators/resturaGlobalTypesGenerator.ts
|
|
993
|
+
function resturaGlobalTypesGenerator() {
|
|
994
|
+
return `/** Auto generated file. DO NOT MODIFY **/
|
|
995
|
+
/** This file contains types that may be used in the CustomTypes of Restura **/
|
|
996
|
+
/** For example export interface MyPagedQuery extends Restura.PageQuery { } **/
|
|
997
|
+
|
|
998
|
+
declare namespace Restura {
|
|
999
|
+
export type StandardOrderTypes = 'ASC' | 'DESC' | 'RAND' | 'NONE';
|
|
1000
|
+
export interface PageQuery {
|
|
1001
|
+
page?: number;
|
|
1002
|
+
perPage?: number;
|
|
1003
|
+
sortBy?: string;
|
|
1004
|
+
sortOrder?: StandardOrderTypes;
|
|
1005
|
+
filter?: string;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
`;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
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,33 @@ 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"));
|
|
1246
1588
|
|
|
1247
1589
|
// src/restura/sql/PsqlUtils.ts
|
|
1248
1590
|
var import_pg_format = __toESM(require("pg-format"));
|
|
@@ -1252,16 +1594,33 @@ function escapeColumnName(columnName) {
|
|
|
1252
1594
|
}
|
|
1253
1595
|
function questionMarksToOrderedParams(query) {
|
|
1254
1596
|
let count = 1;
|
|
1255
|
-
|
|
1597
|
+
let inSingleQuote = false;
|
|
1598
|
+
let inDoubleQuote = false;
|
|
1599
|
+
return query.replace(/('|"|\?)/g, (char) => {
|
|
1600
|
+
if (char === "'") {
|
|
1601
|
+
inSingleQuote = !inSingleQuote && !inDoubleQuote;
|
|
1602
|
+
return char;
|
|
1603
|
+
}
|
|
1604
|
+
if (char === '"') {
|
|
1605
|
+
inDoubleQuote = !inDoubleQuote && !inSingleQuote;
|
|
1606
|
+
return char;
|
|
1607
|
+
}
|
|
1608
|
+
if (char === "?" && !inSingleQuote && !inDoubleQuote) {
|
|
1609
|
+
return `$${count++}`;
|
|
1610
|
+
}
|
|
1611
|
+
return char;
|
|
1612
|
+
});
|
|
1256
1613
|
}
|
|
1257
1614
|
function insertObjectQuery(table, obj) {
|
|
1258
1615
|
const keys = Object.keys(obj);
|
|
1259
1616
|
const params = Object.values(obj);
|
|
1260
1617
|
const columns = keys.map((column) => escapeColumnName(column)).join(", ");
|
|
1261
1618
|
const values = params.map((value) => SQL`${value}`).join(", ");
|
|
1262
|
-
|
|
1619
|
+
let query = `
|
|
1620
|
+
INSERT INTO "${table}" (${columns})
|
|
1263
1621
|
VALUES (${values})
|
|
1264
1622
|
RETURNING *`;
|
|
1623
|
+
query = query.replace(/'(\?)'/, "?");
|
|
1265
1624
|
return query;
|
|
1266
1625
|
}
|
|
1267
1626
|
function updateObjectQuery(table, obj, whereStatement) {
|
|
@@ -1269,25 +1628,113 @@ function updateObjectQuery(table, obj, whereStatement) {
|
|
|
1269
1628
|
for (const i in obj) {
|
|
1270
1629
|
setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
|
|
1271
1630
|
}
|
|
1272
|
-
return `
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1631
|
+
return `
|
|
1632
|
+
UPDATE ${escapeColumnName(table)}
|
|
1633
|
+
SET ${setArray.join(", ")} ${whereStatement}
|
|
1634
|
+
RETURNING *`;
|
|
1635
|
+
}
|
|
1636
|
+
function isValueNumber2(value) {
|
|
1637
|
+
return !isNaN(Number(value));
|
|
1638
|
+
}
|
|
1639
|
+
function SQL(strings, ...values) {
|
|
1640
|
+
let query = strings[0];
|
|
1641
|
+
values.forEach((value, index) => {
|
|
1642
|
+
if (typeof value === "boolean") {
|
|
1643
|
+
query += value;
|
|
1644
|
+
} else if (typeof value === "number") {
|
|
1645
|
+
query += value;
|
|
1646
|
+
} else if (Array.isArray(value)) {
|
|
1647
|
+
query += import_pg_format.default.literal(JSON.stringify(value)) + "::jsonb";
|
|
1648
|
+
} else {
|
|
1649
|
+
query += import_pg_format.default.literal(value);
|
|
1650
|
+
}
|
|
1651
|
+
query += strings[index + 1];
|
|
1652
|
+
});
|
|
1653
|
+
return query;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// src/restura/sql/PsqlConnection.ts
|
|
1657
|
+
var PsqlConnection = class {
|
|
1658
|
+
constructor(instanceId) {
|
|
1659
|
+
this.instanceId = instanceId || import_crypto.default.randomUUID();
|
|
1660
|
+
}
|
|
1661
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1662
|
+
async queryOne(query, options, requesterDetails) {
|
|
1663
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1664
|
+
const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
|
|
1665
|
+
this.logSqlStatement(formattedQuery, options, meta);
|
|
1666
|
+
const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
|
|
1667
|
+
`;
|
|
1668
|
+
try {
|
|
1669
|
+
const response = await this.query(queryMetadata + formattedQuery, options);
|
|
1670
|
+
if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
|
|
1671
|
+
else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
|
|
1672
|
+
return response.rows[0];
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
if (RsError.isRsError(error)) throw error;
|
|
1675
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1676
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1677
|
+
}
|
|
1678
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1682
|
+
async runQuery(query, options, requesterDetails) {
|
|
1683
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1684
|
+
const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
|
|
1685
|
+
this.logSqlStatement(formattedQuery, options, meta);
|
|
1686
|
+
const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
|
|
1687
|
+
`;
|
|
1688
|
+
try {
|
|
1689
|
+
const response = await this.query(queryMetadata + formattedQuery, options);
|
|
1690
|
+
return response.rows;
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1693
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1694
|
+
}
|
|
1695
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
logSqlStatement(query, options, queryMetadata, prefix = "") {
|
|
1699
|
+
if (logger.level !== "silly") return;
|
|
1700
|
+
let sqlStatement = "";
|
|
1701
|
+
if (options.length === 0) {
|
|
1702
|
+
sqlStatement = query;
|
|
1284
1703
|
} else {
|
|
1285
|
-
|
|
1704
|
+
let stringIndex = 0;
|
|
1705
|
+
sqlStatement = query.replace(/\$\d+/g, () => {
|
|
1706
|
+
const value = options[stringIndex++];
|
|
1707
|
+
if (typeof value === "number") return value.toString();
|
|
1708
|
+
return import_pg_format2.default.literal(value);
|
|
1709
|
+
});
|
|
1286
1710
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1711
|
+
let initiator = "Anonymous";
|
|
1712
|
+
if ("userId" in queryMetadata && queryMetadata.userId)
|
|
1713
|
+
initiator = `User Id (${queryMetadata.userId.toString()})`;
|
|
1714
|
+
if ("isSystemUser" in queryMetadata && queryMetadata.isSystemUser) initiator = "SYSTEM";
|
|
1715
|
+
logger.silly(`${prefix}query by ${initiator}, Query ->
|
|
1716
|
+
${sqlStatement}`);
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
|
|
1720
|
+
// src/restura/sql/PsqlPool.ts
|
|
1721
|
+
var { Pool } = import_pg.default;
|
|
1722
|
+
var PsqlPool = class extends PsqlConnection {
|
|
1723
|
+
constructor(poolConfig) {
|
|
1724
|
+
super();
|
|
1725
|
+
this.poolConfig = poolConfig;
|
|
1726
|
+
this.pool = new Pool(poolConfig);
|
|
1727
|
+
this.queryOne("SELECT NOW();", [], { isSystemUser: true, role: "", host: "localhost", ipAddress: "" }).then(() => {
|
|
1728
|
+
logger.info("Connected to PostgreSQL database");
|
|
1729
|
+
}).catch((error) => {
|
|
1730
|
+
logger.error("Error connecting to database", error);
|
|
1731
|
+
process.exit(1);
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
async query(query, values) {
|
|
1735
|
+
return this.pool.query(query, values);
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1291
1738
|
|
|
1292
1739
|
// src/restura/sql/SqlEngine.ts
|
|
1293
1740
|
var import_core_utils4 = require("@redskytech/core-utils");
|
|
@@ -1355,11 +1802,11 @@ var SqlEngine = class {
|
|
|
1355
1802
|
return returnValue;
|
|
1356
1803
|
}
|
|
1357
1804
|
replaceLocalParamKeywords(value, routeData, req, sqlParams) {
|
|
1358
|
-
var
|
|
1805
|
+
var _a2;
|
|
1359
1806
|
if (!routeData.request) return value;
|
|
1360
1807
|
const data = req.data;
|
|
1361
1808
|
if (typeof value === "string") {
|
|
1362
|
-
(
|
|
1809
|
+
(_a2 = value.match(/\$[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a2.forEach((param) => {
|
|
1363
1810
|
const requestParam = routeData.request.find((item) => {
|
|
1364
1811
|
return item.name === param.replace("$", "");
|
|
1365
1812
|
});
|
|
@@ -1372,9 +1819,9 @@ var SqlEngine = class {
|
|
|
1372
1819
|
return value;
|
|
1373
1820
|
}
|
|
1374
1821
|
replaceGlobalParamKeywords(value, routeData, req, sqlParams) {
|
|
1375
|
-
var
|
|
1822
|
+
var _a2;
|
|
1376
1823
|
if (typeof value === "string") {
|
|
1377
|
-
(
|
|
1824
|
+
(_a2 = value.match(/#[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a2.forEach((param) => {
|
|
1378
1825
|
param = param.replace("#", "");
|
|
1379
1826
|
const globalParamValue = req.requesterDetails[param];
|
|
1380
1827
|
if (!globalParamValue)
|
|
@@ -1393,32 +1840,86 @@ var SqlEngine = class {
|
|
|
1393
1840
|
// src/restura/sql/filterPsqlParser.ts
|
|
1394
1841
|
var import_pegjs = __toESM(require("pegjs"));
|
|
1395
1842
|
var filterSqlGrammar = `
|
|
1843
|
+
{
|
|
1844
|
+
// ported from pg-format but intentionally will add double quotes to every column
|
|
1845
|
+
function quoteSqlIdentity(value) {
|
|
1846
|
+
if (value === undefined || value === null) {
|
|
1847
|
+
throw new Error('SQL identifier cannot be null or undefined');
|
|
1848
|
+
} else if (value === false) {
|
|
1849
|
+
return '"f"';
|
|
1850
|
+
} else if (value === true) {
|
|
1851
|
+
return '"t"';
|
|
1852
|
+
} else if (value instanceof Date) {
|
|
1853
|
+
// return '"' + formatDate(value.toISOString()) + '"';
|
|
1854
|
+
} else if (value instanceof Buffer) {
|
|
1855
|
+
throw new Error('SQL identifier cannot be a buffer');
|
|
1856
|
+
} else if (Array.isArray(value) === true) {
|
|
1857
|
+
var temp = [];
|
|
1858
|
+
for (var i = 0; i < value.length; i++) {
|
|
1859
|
+
if (Array.isArray(value[i]) === true) {
|
|
1860
|
+
throw new Error('Nested array to grouped list conversion is not supported for SQL identifier');
|
|
1861
|
+
} else {
|
|
1862
|
+
// temp.push(quoteIdent(value[i]));
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
return temp.toString();
|
|
1866
|
+
} else if (value === Object(value)) {
|
|
1867
|
+
throw new Error('SQL identifier cannot be an object');
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
var ident = value.toString().slice(0); // create copy
|
|
1871
|
+
|
|
1872
|
+
// do not quote a valid, unquoted identifier
|
|
1873
|
+
// if (/^[a-z_][a-z0-9_$]*$/.test(ident) === true && isReserved(ident) === false) {
|
|
1874
|
+
// return ident;
|
|
1875
|
+
// }
|
|
1876
|
+
|
|
1877
|
+
var quoted = '"';
|
|
1878
|
+
|
|
1879
|
+
for (var i = 0; i < ident.length; i++) {
|
|
1880
|
+
var c = ident[i];
|
|
1881
|
+
if (c === '"') {
|
|
1882
|
+
quoted += c + c;
|
|
1883
|
+
} else {
|
|
1884
|
+
quoted += c;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
quoted += '"';
|
|
1889
|
+
|
|
1890
|
+
return quoted;
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1396
1894
|
start = expressionList
|
|
1397
1895
|
|
|
1896
|
+
_ = [ \\t\\r\\n]* // Matches spaces, tabs, and line breaks
|
|
1897
|
+
|
|
1398
1898
|
expressionList =
|
|
1399
|
-
|
|
1899
|
+
leftExpression:expression _ operator:operator _ rightExpression:expressionList
|
|
1400
1900
|
{ return \`\${leftExpression} \${operator} \${rightExpression}\`;}
|
|
1401
1901
|
/ expression
|
|
1402
1902
|
|
|
1403
1903
|
expression =
|
|
1404
|
-
negate:negate?"(" "column:"
|
|
1405
|
-
{return \`\${negate? "
|
|
1904
|
+
negate:negate? _ "(" _ "column" _ ":" column:column _ ","? _ value:value? ","? _ type:type? _ ")"_
|
|
1905
|
+
{return \`\${negate? " NOT " : ""}(\${type? type(column, value) : \`\${column} = \${format.literal(value)}\`})\`;}
|
|
1406
1906
|
/
|
|
1407
|
-
negate:negate?"("expression:expressionList")" { return \`\${negate? "
|
|
1907
|
+
negate:negate?"("expression:expressionList")" { return \`\${negate? " NOT " : ""}(\${expression})\`; }
|
|
1408
1908
|
|
|
1409
1909
|
negate = "!"
|
|
1410
1910
|
|
|
1411
1911
|
operator = "and"i / "or"i
|
|
1412
1912
|
|
|
1413
1913
|
|
|
1414
|
-
column = left:text "." right:text { return \`\${
|
|
1914
|
+
column = left:text "." right:text { return \`\${quoteSqlIdentity(left)}.\${quoteSqlIdentity(right)}\`; }
|
|
1415
1915
|
/
|
|
1416
|
-
text:text { return
|
|
1916
|
+
text:text { return quoteSqlIdentity(text); }
|
|
1417
1917
|
|
|
1418
1918
|
|
|
1419
|
-
text = text:[a-z0-9
|
|
1919
|
+
text = text:[a-z0-9 \\t\\r\\n\\-_:@']i+ { return text.join(""); }
|
|
1920
|
+
|
|
1420
1921
|
|
|
1421
|
-
type = "type:" type:typeString { return type; }
|
|
1922
|
+
type = "type" _ ":" _ type:typeString { return type; }
|
|
1422
1923
|
typeString = text:"startsWith" { return function(column, value) { return \`\${column} ILIKE '\${format.literal(value).slice(1,-1)}%'\`; } } /
|
|
1423
1924
|
text:"endsWith" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1424
1925
|
text:"contains" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}%'\`; } } /
|
|
@@ -1428,8 +1929,9 @@ typeString = text:"startsWith" { return function(column, value) { return \`\${co
|
|
|
1428
1929
|
text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1429
1930
|
text:"lessThan" { return function(column, value) { return \`\${column} < '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1430
1931
|
text:"isNull" { return function(column, value) { return \`isNull(\${column})\`; } }
|
|
1431
|
-
|
|
1432
|
-
value = "value:" value:text { return value; }
|
|
1932
|
+
|
|
1933
|
+
value = "value" _ ":" value:text { return value; }
|
|
1934
|
+
|
|
1433
1935
|
|
|
1434
1936
|
`;
|
|
1435
1937
|
var filterPsqlParser = import_pegjs.default.generate(filterSqlGrammar, {
|
|
@@ -1439,18 +1941,224 @@ var filterPsqlParser = import_pegjs.default.generate(filterSqlGrammar, {
|
|
|
1439
1941
|
var filterPsqlParser_default = filterPsqlParser;
|
|
1440
1942
|
|
|
1441
1943
|
// src/restura/sql/PsqlEngine.ts
|
|
1944
|
+
var { Client, types } = import_pg2.default;
|
|
1945
|
+
var systemUser = {
|
|
1946
|
+
role: "",
|
|
1947
|
+
host: "",
|
|
1948
|
+
ipAddress: "",
|
|
1949
|
+
isSystemUser: true
|
|
1950
|
+
};
|
|
1442
1951
|
var PsqlEngine = class extends SqlEngine {
|
|
1443
|
-
constructor(psqlConnectionPool) {
|
|
1952
|
+
constructor(psqlConnectionPool, shouldListenForDbTriggers = false) {
|
|
1444
1953
|
super();
|
|
1445
1954
|
this.psqlConnectionPool = psqlConnectionPool;
|
|
1955
|
+
this.setupPgReturnTypes();
|
|
1956
|
+
if (shouldListenForDbTriggers) {
|
|
1957
|
+
this.setupTriggerListeners = this.listenForDbTriggers();
|
|
1958
|
+
}
|
|
1446
1959
|
}
|
|
1447
|
-
async
|
|
1448
|
-
|
|
1449
|
-
|
|
1960
|
+
async close() {
|
|
1961
|
+
if (this.triggerClient) {
|
|
1962
|
+
await this.triggerClient.end();
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
setupPgReturnTypes() {
|
|
1966
|
+
const TIMESTAMPTZ_OID = 1184;
|
|
1967
|
+
types.setTypeParser(TIMESTAMPTZ_OID, (val) => {
|
|
1968
|
+
return val === null ? null : new Date(val).toISOString();
|
|
1969
|
+
});
|
|
1970
|
+
const BIGINT_OID = 20;
|
|
1971
|
+
types.setTypeParser(BIGINT_OID, (val) => {
|
|
1972
|
+
return val === null ? null : Number(val);
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
async listenForDbTriggers() {
|
|
1976
|
+
this.triggerClient = new Client({
|
|
1977
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
1978
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
1979
|
+
database: this.psqlConnectionPool.poolConfig.database,
|
|
1980
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
1981
|
+
port: this.psqlConnectionPool.poolConfig.port,
|
|
1982
|
+
connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
|
|
1983
|
+
});
|
|
1984
|
+
await this.triggerClient.connect();
|
|
1985
|
+
const promises = [];
|
|
1986
|
+
promises.push(this.triggerClient.query("LISTEN insert"));
|
|
1987
|
+
promises.push(this.triggerClient.query("LISTEN update"));
|
|
1988
|
+
promises.push(this.triggerClient.query("LISTEN delete"));
|
|
1989
|
+
await Promise.all(promises);
|
|
1990
|
+
this.triggerClient.on("notification", async (msg) => {
|
|
1991
|
+
if (msg.channel === "insert" || msg.channel === "update" || msg.channel === "delete") {
|
|
1992
|
+
const payload = import_core_utils5.ObjectUtils.safeParse(msg.payload);
|
|
1993
|
+
await this.handleTrigger(payload, msg.channel.toUpperCase());
|
|
1994
|
+
}
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
async handleTrigger(payload, mutationType) {
|
|
1998
|
+
if (payload.queryMetadata && payload.queryMetadata.connectionInstanceId === this.psqlConnectionPool.instanceId) {
|
|
1999
|
+
await eventManager_default.fireActionFromDbTrigger({ queryMetadata: payload.queryMetadata, mutationType }, payload);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
async createDatabaseFromSchema(schema, connection) {
|
|
2003
|
+
const sqlFullStatement = this.generateDatabaseSchemaFromSchema(schema);
|
|
2004
|
+
await connection.runQuery(sqlFullStatement, [], systemUser);
|
|
2005
|
+
return sqlFullStatement;
|
|
1450
2006
|
}
|
|
1451
2007
|
generateDatabaseSchemaFromSchema(schema) {
|
|
1452
|
-
|
|
1453
|
-
|
|
2008
|
+
const sqlStatements = [];
|
|
2009
|
+
const indexes = [];
|
|
2010
|
+
const triggers = [];
|
|
2011
|
+
for (const table of schema.database) {
|
|
2012
|
+
if (table.notify) {
|
|
2013
|
+
triggers.push(this.createInsertTriggers(table.name, table.notify));
|
|
2014
|
+
triggers.push(this.createUpdateTrigger(table.name, table.notify));
|
|
2015
|
+
triggers.push(this.createDeleteTrigger(table.name, table.notify));
|
|
2016
|
+
}
|
|
2017
|
+
let sql = `CREATE TABLE "${table.name}"
|
|
2018
|
+
( `;
|
|
2019
|
+
const tableColumns = [];
|
|
2020
|
+
for (const column of table.columns) {
|
|
2021
|
+
let columnSql = "";
|
|
2022
|
+
columnSql += ` "${column.name}" ${this.schemaToPsqlType(column)}`;
|
|
2023
|
+
let value = column.value;
|
|
2024
|
+
if (column.type === "JSON") value = "";
|
|
2025
|
+
if (column.type === "JSONB") value = "";
|
|
2026
|
+
if (column.type === "DECIMAL" && value) {
|
|
2027
|
+
value = value.replace("-", ",").replace(/['"]/g, "");
|
|
2028
|
+
}
|
|
2029
|
+
if (value && column.type !== "ENUM") {
|
|
2030
|
+
columnSql += `(${value})`;
|
|
2031
|
+
} else if (column.length) columnSql += `(${column.length})`;
|
|
2032
|
+
if (column.isPrimary) {
|
|
2033
|
+
columnSql += " PRIMARY KEY ";
|
|
2034
|
+
}
|
|
2035
|
+
if (column.isUnique) {
|
|
2036
|
+
columnSql += ` CONSTRAINT "${table.name}_${column.name}_unique_index" UNIQUE `;
|
|
2037
|
+
}
|
|
2038
|
+
if (column.isNullable) columnSql += " NULL";
|
|
2039
|
+
else columnSql += " NOT NULL";
|
|
2040
|
+
if (column.default) columnSql += ` DEFAULT ${column.default}`;
|
|
2041
|
+
if (value && column.type === "ENUM") {
|
|
2042
|
+
columnSql += ` CHECK ("${column.name}" IN (${value}))`;
|
|
2043
|
+
}
|
|
2044
|
+
tableColumns.push(columnSql);
|
|
2045
|
+
}
|
|
2046
|
+
sql += tableColumns.join(", \n");
|
|
2047
|
+
for (const index of table.indexes) {
|
|
2048
|
+
if (!index.isPrimaryKey) {
|
|
2049
|
+
let unique = " ";
|
|
2050
|
+
if (index.isUnique) unique = "UNIQUE ";
|
|
2051
|
+
indexes.push(
|
|
2052
|
+
` CREATE ${unique}INDEX "${index.name}" ON "${table.name}" (${index.columns.map((item) => {
|
|
2053
|
+
return `"${item}" ${index.order}`;
|
|
2054
|
+
}).join(", ")});`
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
sql += "\n);";
|
|
2059
|
+
sqlStatements.push(sql);
|
|
2060
|
+
}
|
|
2061
|
+
for (const table of schema.database) {
|
|
2062
|
+
if (!table.foreignKeys.length) continue;
|
|
2063
|
+
const sql = `ALTER TABLE "${table.name}" `;
|
|
2064
|
+
const constraints = [];
|
|
2065
|
+
for (const foreignKey of table.foreignKeys) {
|
|
2066
|
+
let constraint = ` ADD CONSTRAINT "${foreignKey.name}"
|
|
2067
|
+
FOREIGN KEY ("${foreignKey.column}") REFERENCES "${foreignKey.refTable}" ("${foreignKey.refColumn}")`;
|
|
2068
|
+
constraint += ` ON DELETE ${foreignKey.onDelete}`;
|
|
2069
|
+
constraint += ` ON UPDATE ${foreignKey.onUpdate}`;
|
|
2070
|
+
constraints.push(constraint);
|
|
2071
|
+
}
|
|
2072
|
+
sqlStatements.push(sql + constraints.join(",\n") + ";");
|
|
2073
|
+
}
|
|
2074
|
+
for (const table of schema.database) {
|
|
2075
|
+
if (!table.checkConstraints.length) continue;
|
|
2076
|
+
const sql = `ALTER TABLE "${table.name}" `;
|
|
2077
|
+
const constraints = [];
|
|
2078
|
+
for (const check of table.checkConstraints) {
|
|
2079
|
+
const constraint = `ADD CONSTRAINT "${check.name}" CHECK (${check.check})`;
|
|
2080
|
+
constraints.push(constraint);
|
|
2081
|
+
}
|
|
2082
|
+
sqlStatements.push(sql + constraints.join(",\n") + ";");
|
|
2083
|
+
}
|
|
2084
|
+
sqlStatements.push(indexes.join("\n"));
|
|
2085
|
+
sqlStatements.push(triggers.join("\n"));
|
|
2086
|
+
return sqlStatements.join("\n\n");
|
|
2087
|
+
}
|
|
2088
|
+
async getScratchPool() {
|
|
2089
|
+
var _a2, _b;
|
|
2090
|
+
const scratchDbExists = await this.psqlConnectionPool.runQuery(
|
|
2091
|
+
`SELECT *
|
|
2092
|
+
FROM pg_database
|
|
2093
|
+
WHERE datname = '${this.psqlConnectionPool.poolConfig.database}_scratch';`,
|
|
2094
|
+
[],
|
|
2095
|
+
systemUser
|
|
2096
|
+
);
|
|
2097
|
+
if (scratchDbExists.length === 0) {
|
|
2098
|
+
await this.psqlConnectionPool.runQuery(
|
|
2099
|
+
`CREATE DATABASE ${this.psqlConnectionPool.poolConfig.database}_scratch;`,
|
|
2100
|
+
[],
|
|
2101
|
+
systemUser
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2104
|
+
const scratchPool = new PsqlPool({
|
|
2105
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
2106
|
+
port: this.psqlConnectionPool.poolConfig.port,
|
|
2107
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
2108
|
+
database: this.psqlConnectionPool.poolConfig.database + "_scratch",
|
|
2109
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
2110
|
+
max: this.psqlConnectionPool.poolConfig.max,
|
|
2111
|
+
idleTimeoutMillis: this.psqlConnectionPool.poolConfig.idleTimeoutMillis,
|
|
2112
|
+
connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
|
|
2113
|
+
});
|
|
2114
|
+
await scratchPool.runQuery(`DROP SCHEMA public CASCADE;`, [], systemUser);
|
|
2115
|
+
await scratchPool.runQuery(
|
|
2116
|
+
`CREATE SCHEMA public AUTHORIZATION ${this.psqlConnectionPool.poolConfig.user};`,
|
|
2117
|
+
[],
|
|
2118
|
+
systemUser
|
|
2119
|
+
);
|
|
2120
|
+
const schemaComment = await this.psqlConnectionPool.runQuery(
|
|
2121
|
+
`SELECT pg_description.description
|
|
2122
|
+
FROM pg_description
|
|
2123
|
+
JOIN pg_namespace ON pg_namespace.oid = pg_description.objoid
|
|
2124
|
+
WHERE pg_namespace.nspname = 'public';`,
|
|
2125
|
+
[],
|
|
2126
|
+
systemUser
|
|
2127
|
+
);
|
|
2128
|
+
if ((_a2 = schemaComment[0]) == null ? void 0 : _a2.description) {
|
|
2129
|
+
await scratchPool.runQuery(
|
|
2130
|
+
`COMMENT ON SCHEMA public IS '${(_b = schemaComment[0]) == null ? void 0 : _b.description}';`,
|
|
2131
|
+
[],
|
|
2132
|
+
systemUser
|
|
2133
|
+
);
|
|
2134
|
+
}
|
|
2135
|
+
return scratchPool;
|
|
2136
|
+
}
|
|
2137
|
+
async diffDatabaseToSchema(schema) {
|
|
2138
|
+
const scratchPool = await this.getScratchPool();
|
|
2139
|
+
await this.createDatabaseFromSchema(schema, scratchPool);
|
|
2140
|
+
const originalClient = new Client({
|
|
2141
|
+
database: this.psqlConnectionPool.poolConfig.database,
|
|
2142
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
2143
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
2144
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
2145
|
+
port: this.psqlConnectionPool.poolConfig.port
|
|
2146
|
+
});
|
|
2147
|
+
const scratchClient = new Client({
|
|
2148
|
+
database: this.psqlConnectionPool.poolConfig.database + "_scratch",
|
|
2149
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
2150
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
2151
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
2152
|
+
port: this.psqlConnectionPool.poolConfig.port
|
|
2153
|
+
});
|
|
2154
|
+
const promises = [originalClient.connect(), scratchClient.connect()];
|
|
2155
|
+
await Promise.all(promises);
|
|
2156
|
+
const infoPromises = [(0, import_pg_info.default)({ client: originalClient }), (0, import_pg_info.default)({ client: scratchClient })];
|
|
2157
|
+
const [info1, info2] = await Promise.all(infoPromises);
|
|
2158
|
+
const diff = (0, import_pg_diff_sync.default)(info1, info2);
|
|
2159
|
+
const endPromises = [originalClient.end(), scratchClient.end()];
|
|
2160
|
+
await Promise.all(endPromises);
|
|
2161
|
+
return diff.join("\n");
|
|
1454
2162
|
}
|
|
1455
2163
|
createNestedSelect(req, schema, item, routeData, userRole, sqlParams) {
|
|
1456
2164
|
if (!item.subquery) return "";
|
|
@@ -1464,8 +2172,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1464
2172
|
)) {
|
|
1465
2173
|
return "'[]'";
|
|
1466
2174
|
}
|
|
1467
|
-
return `COALESCE((
|
|
1468
|
-
SELECT JSON_AGG(JSON_BUILD_OBJECT(
|
|
2175
|
+
return `COALESCE((SELECT JSON_AGG(JSON_BUILD_OBJECT(
|
|
1469
2176
|
${item.subquery.properties.map((nestedItem) => {
|
|
1470
2177
|
if (!this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
|
|
1471
2178
|
...routeData.joins,
|
|
@@ -1474,7 +2181,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1474
2181
|
return;
|
|
1475
2182
|
}
|
|
1476
2183
|
if (nestedItem.subquery) {
|
|
1477
|
-
return `
|
|
2184
|
+
return `'${nestedItem.name}', ${this.createNestedSelect(
|
|
1478
2185
|
// recursion
|
|
1479
2186
|
req,
|
|
1480
2187
|
schema,
|
|
@@ -1485,7 +2192,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1485
2192
|
)}`;
|
|
1486
2193
|
}
|
|
1487
2194
|
return `'${nestedItem.name}', ${escapeColumnName(nestedItem.selector)}`;
|
|
1488
|
-
}).filter(Boolean).join(",")}
|
|
2195
|
+
}).filter(Boolean).join(", ")}
|
|
1489
2196
|
))
|
|
1490
2197
|
FROM
|
|
1491
2198
|
"${item.subquery.table}"
|
|
@@ -1500,16 +2207,19 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1500
2207
|
parameterObj[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
|
|
1501
2208
|
});
|
|
1502
2209
|
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
|
-
|
|
2210
|
+
const createdItem = await this.psqlConnectionPool.queryOne(
|
|
2211
|
+
query,
|
|
2212
|
+
sqlParams,
|
|
2213
|
+
req.requesterDetails
|
|
2214
|
+
);
|
|
2215
|
+
const insertId = createdItem.id;
|
|
2216
|
+
const whereId = {
|
|
2217
|
+
tableName: routeData.table,
|
|
2218
|
+
value: insertId,
|
|
2219
|
+
columnName: "id",
|
|
2220
|
+
operator: "="
|
|
2221
|
+
};
|
|
2222
|
+
const whereData = [whereId];
|
|
1513
2223
|
req.data = { id: insertId };
|
|
1514
2224
|
return this.executeGetRequest(req, __spreadProps(__spreadValues({}, routeData), { where: whereData }), schema);
|
|
1515
2225
|
}
|
|
@@ -1528,7 +2238,9 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1528
2238
|
let selectStatement = "SELECT \n";
|
|
1529
2239
|
selectStatement += ` ${selectColumns.map((item) => {
|
|
1530
2240
|
if (item.subquery) {
|
|
1531
|
-
return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${
|
|
2241
|
+
return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${escapeColumnName(
|
|
2242
|
+
item.name
|
|
2243
|
+
)}`;
|
|
1532
2244
|
}
|
|
1533
2245
|
return `${escapeColumnName(item.selector)} AS ${escapeColumnName(item.name)}`;
|
|
1534
2246
|
}).join(",\n ")}
|
|
@@ -1561,29 +2273,31 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1561
2273
|
);
|
|
1562
2274
|
} else if (routeData.type === "PAGED") {
|
|
1563
2275
|
const data = req.data;
|
|
1564
|
-
const
|
|
1565
|
-
`${selectStatement}${sqlStatement}${groupByOrderByStatement} LIMIT
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
2276
|
+
const pagePromise = this.psqlConnectionPool.runQuery(
|
|
2277
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement}` + SQL`LIMIT ${data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER} OFFSET ${(data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER};`,
|
|
2278
|
+
sqlParams,
|
|
2279
|
+
req.requesterDetails
|
|
2280
|
+
);
|
|
2281
|
+
const totalQuery = `SELECT COUNT(${routeData.groupBy ? `DISTINCT ${routeData.groupBy.tableName}.${routeData.groupBy.columnName}` : "*"}) AS total
|
|
2282
|
+
${sqlStatement};`;
|
|
2283
|
+
const totalPromise = this.psqlConnectionPool.runQuery(
|
|
2284
|
+
totalQuery,
|
|
2285
|
+
sqlParams,
|
|
1573
2286
|
req.requesterDetails
|
|
1574
2287
|
);
|
|
2288
|
+
const [pageResults, totalResponse] = await Promise.all([pagePromise, totalPromise]);
|
|
1575
2289
|
let total = 0;
|
|
1576
|
-
if (import_core_utils5.ObjectUtils.isArrayWithData(
|
|
1577
|
-
total =
|
|
2290
|
+
if (import_core_utils5.ObjectUtils.isArrayWithData(totalResponse)) {
|
|
2291
|
+
total = totalResponse[0].total;
|
|
1578
2292
|
}
|
|
1579
|
-
return { data: pageResults
|
|
2293
|
+
return { data: pageResults, total };
|
|
1580
2294
|
} else {
|
|
1581
2295
|
throw new RsError("UNKNOWN_ERROR", "Unknown route type.");
|
|
1582
2296
|
}
|
|
1583
2297
|
}
|
|
1584
2298
|
async executeUpdateRequest(req, routeData, schema) {
|
|
1585
2299
|
const sqlParams = [];
|
|
1586
|
-
const
|
|
2300
|
+
const _a2 = req.body, { id } = _a2, bodyNoId = __objRest(_a2, ["id"]);
|
|
1587
2301
|
const table = schema.database.find((item) => {
|
|
1588
2302
|
return item.name === routeData.table;
|
|
1589
2303
|
});
|
|
@@ -1615,10 +2329,12 @@ ${sqlStatement};`,
|
|
|
1615
2329
|
req.requesterDetails.role,
|
|
1616
2330
|
sqlParams
|
|
1617
2331
|
);
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
2332
|
+
const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
2333
|
+
if (whereClause.replace(/\s/g, "") === "") {
|
|
2334
|
+
throw new RsError("DELETE_FORBIDDEN", "Deletes need a where clause");
|
|
2335
|
+
}
|
|
2336
|
+
const deleteStatement = `
|
|
2337
|
+
DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
|
|
1622
2338
|
await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams, req.requesterDetails);
|
|
1623
2339
|
return true;
|
|
1624
2340
|
}
|
|
@@ -1681,38 +2397,37 @@ ${sqlStatement};`,
|
|
|
1681
2397
|
);
|
|
1682
2398
|
let operator = item.operator;
|
|
1683
2399
|
if (operator === "LIKE") {
|
|
1684
|
-
|
|
2400
|
+
item.value = `'%${item.value}%'`;
|
|
1685
2401
|
} else if (operator === "STARTS WITH") {
|
|
1686
2402
|
operator = "LIKE";
|
|
1687
|
-
|
|
2403
|
+
item.value = `'${item.value}%'`;
|
|
1688
2404
|
} else if (operator === "ENDS WITH") {
|
|
1689
2405
|
operator = "LIKE";
|
|
1690
|
-
|
|
2406
|
+
item.value = `'%${item.value}'`;
|
|
1691
2407
|
}
|
|
1692
2408
|
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}
|
|
2409
|
+
whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator.replace("LIKE", "ILIKE")} ${["IN", "NOT IN"].includes(operator) ? `(${replacedValue})` : replacedValue}
|
|
1695
2410
|
`;
|
|
1696
2411
|
});
|
|
1697
2412
|
const data = req.data;
|
|
1698
2413
|
if (routeData.type === "PAGED" && !!(data == null ? void 0 : data.filter)) {
|
|
1699
2414
|
let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
1700
|
-
var
|
|
2415
|
+
var _a2;
|
|
1701
2416
|
const requestParam = routeData.request.find((item) => {
|
|
1702
2417
|
return item.name === value.replace("$", "");
|
|
1703
2418
|
});
|
|
1704
2419
|
if (!requestParam)
|
|
1705
2420
|
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1706
|
-
return ((
|
|
2421
|
+
return ((_a2 = data[requestParam.name]) == null ? void 0 : _a2.toString()) || "";
|
|
1707
2422
|
});
|
|
1708
2423
|
statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
1709
|
-
var
|
|
2424
|
+
var _a2;
|
|
1710
2425
|
const requestParam = routeData.request.find((item) => {
|
|
1711
2426
|
return item.name === value.replace("#", "");
|
|
1712
2427
|
});
|
|
1713
2428
|
if (!requestParam)
|
|
1714
2429
|
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1715
|
-
return ((
|
|
2430
|
+
return ((_a2 = data[requestParam.name]) == null ? void 0 : _a2.toString()) || "";
|
|
1716
2431
|
});
|
|
1717
2432
|
statement = filterPsqlParser_default.parse(statement);
|
|
1718
2433
|
if (whereClause.startsWith("WHERE")) {
|
|
@@ -1725,82 +2440,259 @@ ${sqlStatement};`,
|
|
|
1725
2440
|
}
|
|
1726
2441
|
return whereClause;
|
|
1727
2442
|
}
|
|
1728
|
-
|
|
2443
|
+
createUpdateTrigger(tableName, notify) {
|
|
2444
|
+
if (!notify) return "";
|
|
2445
|
+
if (notify === "ALL") {
|
|
2446
|
+
return `
|
|
2447
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_update()
|
|
2448
|
+
RETURNS TRIGGER AS $$
|
|
2449
|
+
DECLARE
|
|
2450
|
+
query_metadata JSON;
|
|
2451
|
+
BEGIN
|
|
2452
|
+
SELECT INTO query_metadata
|
|
2453
|
+
(regexp_match(
|
|
2454
|
+
current_query(),
|
|
2455
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2456
|
+
))[1]::json;
|
|
1729
2457
|
|
|
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}`);
|
|
2458
|
+
PERFORM pg_notify(
|
|
2459
|
+
'update',
|
|
2460
|
+
json_build_object(
|
|
2461
|
+
'table', '${tableName}',
|
|
2462
|
+
'queryMetadata', query_metadata,
|
|
2463
|
+
'changedId', NEW.id,
|
|
2464
|
+
'record', NEW,
|
|
2465
|
+
'previousRecord', OLD
|
|
2466
|
+
)::text
|
|
2467
|
+
);
|
|
2468
|
+
RETURN NEW;
|
|
2469
|
+
END;
|
|
2470
|
+
$$ LANGUAGE plpgsql;
|
|
2471
|
+
|
|
2472
|
+
CREATE OR REPLACE TRIGGER ${tableName}_update
|
|
2473
|
+
AFTER UPDATE ON "${tableName}"
|
|
2474
|
+
FOR EACH ROW
|
|
2475
|
+
EXECUTE FUNCTION notify_${tableName}_update();
|
|
2476
|
+
`;
|
|
1761
2477
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
2478
|
+
const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
|
|
2479
|
+
const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
|
|
2480
|
+
return `
|
|
2481
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_update()
|
|
2482
|
+
RETURNS TRIGGER AS $$
|
|
2483
|
+
DECLARE
|
|
2484
|
+
query_metadata JSON;
|
|
2485
|
+
BEGIN
|
|
2486
|
+
SELECT INTO query_metadata
|
|
2487
|
+
(regexp_match(
|
|
2488
|
+
current_query(),
|
|
2489
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2490
|
+
))[1]::json;
|
|
2491
|
+
|
|
2492
|
+
PERFORM pg_notify(
|
|
2493
|
+
'update',
|
|
2494
|
+
json_build_object(
|
|
2495
|
+
'table', '${tableName}',
|
|
2496
|
+
'queryMetadata', query_metadata,
|
|
2497
|
+
'changedId', NEW.id,
|
|
2498
|
+
'record', json_build_object(
|
|
2499
|
+
${notifyColumnNewBuildString}
|
|
2500
|
+
),
|
|
2501
|
+
'previousRecord', json_build_object(
|
|
2502
|
+
${notifyColumnOldBuildString}
|
|
2503
|
+
)
|
|
2504
|
+
)::text
|
|
2505
|
+
);
|
|
2506
|
+
RETURN NEW;
|
|
2507
|
+
END;
|
|
2508
|
+
$$ LANGUAGE plpgsql;
|
|
2509
|
+
|
|
2510
|
+
CREATE OR REPLACE TRIGGER ${tableName}_update
|
|
2511
|
+
AFTER UPDATE ON "${tableName}"
|
|
2512
|
+
FOR EACH ROW
|
|
2513
|
+
EXECUTE FUNCTION notify_${tableName}_update();
|
|
2514
|
+
`;
|
|
2515
|
+
}
|
|
2516
|
+
createDeleteTrigger(tableName, notify) {
|
|
2517
|
+
if (!notify) return "";
|
|
2518
|
+
if (notify === "ALL") {
|
|
2519
|
+
return `
|
|
2520
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
|
|
2521
|
+
RETURNS TRIGGER AS $$
|
|
2522
|
+
DECLARE
|
|
2523
|
+
query_metadata JSON;
|
|
2524
|
+
BEGIN
|
|
2525
|
+
SELECT INTO query_metadata
|
|
2526
|
+
(regexp_match(
|
|
2527
|
+
current_query(),
|
|
2528
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2529
|
+
))[1]::json;
|
|
2530
|
+
|
|
2531
|
+
PERFORM pg_notify(
|
|
2532
|
+
'delete',
|
|
2533
|
+
json_build_object(
|
|
2534
|
+
'table', '${tableName}',
|
|
2535
|
+
'queryMetadata', query_metadata,
|
|
2536
|
+
'deletedId', OLD.id,
|
|
2537
|
+
'previousRecord', OLD
|
|
2538
|
+
)::text
|
|
2539
|
+
);
|
|
2540
|
+
RETURN NEW;
|
|
2541
|
+
END;
|
|
2542
|
+
$$ LANGUAGE plpgsql;
|
|
2543
|
+
|
|
2544
|
+
CREATE OR REPLACE TRIGGER "${tableName}_delete"
|
|
2545
|
+
AFTER DELETE ON "${tableName}"
|
|
2546
|
+
FOR EACH ROW
|
|
2547
|
+
EXECUTE FUNCTION notify_${tableName}_delete();
|
|
2548
|
+
`;
|
|
1778
2549
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
2550
|
+
const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
|
|
2551
|
+
return `
|
|
2552
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
|
|
2553
|
+
RETURNS TRIGGER AS $$
|
|
2554
|
+
DECLARE
|
|
2555
|
+
query_metadata JSON;
|
|
2556
|
+
BEGIN
|
|
2557
|
+
SELECT INTO query_metadata
|
|
2558
|
+
(regexp_match(
|
|
2559
|
+
current_query(),
|
|
2560
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2561
|
+
))[1]::json;
|
|
2562
|
+
|
|
2563
|
+
PERFORM pg_notify(
|
|
2564
|
+
'delete',
|
|
2565
|
+
json_build_object(
|
|
2566
|
+
'table', '${tableName}',
|
|
2567
|
+
'queryMetadata', query_metadata,
|
|
2568
|
+
'deletedId', OLD.id,
|
|
2569
|
+
'previousRecord', json_build_object(
|
|
2570
|
+
${notifyColumnOldBuildString}
|
|
2571
|
+
)
|
|
2572
|
+
)::text
|
|
2573
|
+
);
|
|
2574
|
+
RETURN NEW;
|
|
2575
|
+
END;
|
|
2576
|
+
$$ LANGUAGE plpgsql;
|
|
2577
|
+
|
|
2578
|
+
CREATE OR REPLACE TRIGGER "${tableName}_delete"
|
|
2579
|
+
AFTER DELETE ON "${tableName}"
|
|
2580
|
+
FOR EACH ROW
|
|
2581
|
+
EXECUTE FUNCTION notify_${tableName}_delete();
|
|
2582
|
+
`;
|
|
2583
|
+
}
|
|
2584
|
+
createInsertTriggers(tableName, notify) {
|
|
2585
|
+
if (!notify) return "";
|
|
2586
|
+
if (notify === "ALL") {
|
|
2587
|
+
return `
|
|
2588
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
|
|
2589
|
+
RETURNS TRIGGER AS $$
|
|
2590
|
+
DECLARE
|
|
2591
|
+
query_metadata JSON;
|
|
2592
|
+
BEGIN
|
|
2593
|
+
SELECT INTO query_metadata
|
|
2594
|
+
(regexp_match(
|
|
2595
|
+
current_query(),
|
|
2596
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2597
|
+
))[1]::json;
|
|
2598
|
+
|
|
2599
|
+
PERFORM pg_notify(
|
|
2600
|
+
'insert',
|
|
2601
|
+
json_build_object(
|
|
2602
|
+
'table', '${tableName}',
|
|
2603
|
+
'queryMetadata', query_metadata,
|
|
2604
|
+
'insertedId', NEW.id,
|
|
2605
|
+
'record', NEW
|
|
2606
|
+
)::text
|
|
2607
|
+
);
|
|
2608
|
+
|
|
2609
|
+
RETURN NEW;
|
|
2610
|
+
END;
|
|
2611
|
+
$$ LANGUAGE plpgsql;
|
|
2612
|
+
|
|
2613
|
+
CREATE OR REPLACE TRIGGER "${tableName}_insert"
|
|
2614
|
+
AFTER INSERT ON "${tableName}"
|
|
2615
|
+
FOR EACH ROW
|
|
2616
|
+
EXECUTE FUNCTION notify_${tableName}_insert();
|
|
2617
|
+
`;
|
|
1792
2618
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
2619
|
+
const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
|
|
2620
|
+
return `
|
|
2621
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
|
|
2622
|
+
RETURNS TRIGGER AS $$
|
|
2623
|
+
DECLARE
|
|
2624
|
+
query_metadata JSON;
|
|
2625
|
+
BEGIN
|
|
2626
|
+
SELECT INTO query_metadata
|
|
2627
|
+
(regexp_match(
|
|
2628
|
+
current_query(),
|
|
2629
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2630
|
+
))[1]::json;
|
|
2631
|
+
|
|
2632
|
+
PERFORM pg_notify(
|
|
2633
|
+
'insert',
|
|
2634
|
+
json_build_object(
|
|
2635
|
+
'table', '${tableName}',
|
|
2636
|
+
'queryMetadata', query_metadata,
|
|
2637
|
+
'insertedId', NEW.id,
|
|
2638
|
+
'record', json_build_object(
|
|
2639
|
+
${notifyColumnNewBuildString}
|
|
2640
|
+
)
|
|
2641
|
+
)::text
|
|
2642
|
+
);
|
|
2643
|
+
|
|
2644
|
+
RETURN NEW;
|
|
2645
|
+
END;
|
|
2646
|
+
$$ LANGUAGE plpgsql;
|
|
2647
|
+
|
|
2648
|
+
CREATE OR REPLACE TRIGGER "${tableName}_insert"
|
|
2649
|
+
AFTER INSERT ON "${tableName}"
|
|
2650
|
+
FOR EACH ROW
|
|
2651
|
+
EXECUTE FUNCTION notify_${tableName}_insert();
|
|
2652
|
+
`;
|
|
2653
|
+
}
|
|
2654
|
+
schemaToPsqlType(column) {
|
|
2655
|
+
if (column.hasAutoIncrement) return "BIGSERIAL";
|
|
2656
|
+
if (column.type === "ENUM") return `TEXT`;
|
|
2657
|
+
if (column.type === "DATETIME") return "TIMESTAMPTZ";
|
|
2658
|
+
if (column.type === "MEDIUMINT") return "INT";
|
|
2659
|
+
return column.type;
|
|
2660
|
+
}
|
|
2661
|
+
};
|
|
2662
|
+
|
|
2663
|
+
// src/restura/utils/TempCache.ts
|
|
2664
|
+
var import_fs3 = __toESM(require("fs"));
|
|
2665
|
+
var import_path4 = __toESM(require("path"));
|
|
2666
|
+
var import_core_utils6 = require("@redskytech/core-utils");
|
|
2667
|
+
var import_internal3 = require("@restura/internal");
|
|
2668
|
+
var import_bluebird3 = __toESM(require("bluebird"));
|
|
2669
|
+
var os2 = __toESM(require("os"));
|
|
2670
|
+
var TempCache = class {
|
|
2671
|
+
constructor(location) {
|
|
2672
|
+
this.maxDurationDays = 7;
|
|
2673
|
+
this.location = location || os2.tmpdir();
|
|
2674
|
+
import_internal3.FileUtils.ensureDir(this.location).catch((e) => {
|
|
2675
|
+
throw e;
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
2678
|
+
async cleanup() {
|
|
2679
|
+
const fileList = await import_fs3.default.promises.readdir(this.location);
|
|
2680
|
+
await import_bluebird3.default.map(
|
|
2681
|
+
fileList,
|
|
2682
|
+
async (file) => {
|
|
2683
|
+
const fullFilePath = import_path4.default.join(this.location, file);
|
|
2684
|
+
const fileStats = await import_fs3.default.promises.stat(fullFilePath);
|
|
2685
|
+
if (import_core_utils6.DateUtils.daysBetweenStartAndEndDates(new Date(fileStats.mtimeMs), /* @__PURE__ */ new Date()) > this.maxDurationDays) {
|
|
2686
|
+
logger.info(`Deleting old temp file: ${file}`);
|
|
2687
|
+
await import_fs3.default.promises.unlink(fullFilePath);
|
|
2688
|
+
}
|
|
2689
|
+
},
|
|
2690
|
+
{ concurrency: 10 }
|
|
2691
|
+
);
|
|
1799
2692
|
}
|
|
1800
2693
|
};
|
|
1801
2694
|
|
|
1802
2695
|
// src/restura/restura.ts
|
|
1803
|
-
var { types } = import_pg2.default;
|
|
1804
2696
|
var ResturaEngine = class {
|
|
1805
2697
|
constructor() {
|
|
1806
2698
|
this.publicEndpoints = {
|
|
@@ -1818,10 +2710,11 @@ var ResturaEngine = class {
|
|
|
1818
2710
|
* @returns A promise that resolves when the initialization is complete.
|
|
1819
2711
|
*/
|
|
1820
2712
|
async init(app, authenticationHandler, psqlConnectionPool) {
|
|
1821
|
-
this.resturaConfig =
|
|
2713
|
+
this.resturaConfig = import_internal4.config.validate("restura", resturaConfigSchema);
|
|
2714
|
+
this.multerCommonUpload = getMulterUpload(this.resturaConfig.fileTempCachePath);
|
|
2715
|
+
new TempCache(this.resturaConfig.fileTempCachePath);
|
|
1822
2716
|
this.psqlConnectionPool = psqlConnectionPool;
|
|
1823
|
-
this.psqlEngine = new PsqlEngine(this.psqlConnectionPool);
|
|
1824
|
-
setupPgReturnTypes();
|
|
2717
|
+
this.psqlEngine = new PsqlEngine(this.psqlConnectionPool, true);
|
|
1825
2718
|
await customApiFactory_default.loadApiFiles(this.resturaConfig.customApiFolderPath);
|
|
1826
2719
|
this.authenticationHandler = authenticationHandler;
|
|
1827
2720
|
app.use((0, import_compression.default)());
|
|
@@ -1885,10 +2778,7 @@ var ResturaEngine = class {
|
|
|
1885
2778
|
* @returns A promise that resolves when the API has been successfully generated and written to the output file.
|
|
1886
2779
|
*/
|
|
1887
2780
|
async generateApiFromSchema(outputFile, providedSchema) {
|
|
1888
|
-
|
|
1889
|
-
outputFile,
|
|
1890
|
-
await apiGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
|
|
1891
|
-
);
|
|
2781
|
+
import_fs4.default.writeFileSync(outputFile, await apiGenerator(providedSchema));
|
|
1892
2782
|
}
|
|
1893
2783
|
/**
|
|
1894
2784
|
* Generates a model from the provided schema and writes it to the specified output file.
|
|
@@ -1898,10 +2788,15 @@ var ResturaEngine = class {
|
|
|
1898
2788
|
* @returns A promise that resolves when the model has been successfully written to the output file.
|
|
1899
2789
|
*/
|
|
1900
2790
|
async generateModelFromSchema(outputFile, providedSchema) {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
2791
|
+
import_fs4.default.writeFileSync(outputFile, await modelGenerator(providedSchema));
|
|
2792
|
+
}
|
|
2793
|
+
/**
|
|
2794
|
+
* Generates the ambient module declaration for Restura global types and writes it to the specified output file.
|
|
2795
|
+
* These types are used sometimes in the CustomTypes
|
|
2796
|
+
* @param outputFile
|
|
2797
|
+
*/
|
|
2798
|
+
generateResturaGlobalTypes(outputFile) {
|
|
2799
|
+
import_fs4.default.writeFileSync(outputFile, resturaGlobalTypesGenerator());
|
|
1905
2800
|
}
|
|
1906
2801
|
/**
|
|
1907
2802
|
* Retrieves the latest file system schema for Restura.
|
|
@@ -1910,12 +2805,12 @@ var ResturaEngine = class {
|
|
|
1910
2805
|
* @throws {Error} If the schema file is missing or the schema is not valid.
|
|
1911
2806
|
*/
|
|
1912
2807
|
async getLatestFileSystemSchema() {
|
|
1913
|
-
if (!
|
|
2808
|
+
if (!import_fs4.default.existsSync(this.resturaConfig.schemaFilePath)) {
|
|
1914
2809
|
logger.error(`Missing restura schema file, expected path: ${this.resturaConfig.schemaFilePath}`);
|
|
1915
2810
|
throw new Error("Missing restura schema file");
|
|
1916
2811
|
}
|
|
1917
|
-
const schemaFileData =
|
|
1918
|
-
const schema =
|
|
2812
|
+
const schemaFileData = import_fs4.default.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
|
|
2813
|
+
const schema = import_core_utils7.ObjectUtils.safeParse(schemaFileData);
|
|
1919
2814
|
const isValid = await isSchemaValid(schema);
|
|
1920
2815
|
if (!isValid) {
|
|
1921
2816
|
logger.error("Schema is not valid");
|
|
@@ -1923,28 +2818,6 @@ var ResturaEngine = class {
|
|
|
1923
2818
|
}
|
|
1924
2819
|
return schema;
|
|
1925
2820
|
}
|
|
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
2821
|
async reloadEndpoints() {
|
|
1949
2822
|
this.schema = await this.getLatestFileSystemSchema();
|
|
1950
2823
|
this.customTypeValidation = customTypeValidationGenerator(this.schema);
|
|
@@ -1973,30 +2846,10 @@ var ResturaEngine = class {
|
|
|
1973
2846
|
logger.info(`Restura loaded (${routeCount}) endpoint${routeCount > 1 ? "s" : ""}`);
|
|
1974
2847
|
}
|
|
1975
2848
|
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
|
-
);
|
|
2849
|
+
if (!import_fs4.default.existsSync(this.resturaConfig.generatedTypesPath)) {
|
|
2850
|
+
import_fs4.default.mkdirSync(this.resturaConfig.generatedTypesPath, { recursive: true });
|
|
1999
2851
|
}
|
|
2852
|
+
this.updateTypes();
|
|
2000
2853
|
}
|
|
2001
2854
|
resturaAuthentication(req, res, next) {
|
|
2002
2855
|
if (req.headers["x-auth-token"] !== this.resturaConfig.authToken) res.status(401).send("Unauthorized");
|
|
@@ -2004,7 +2857,7 @@ var ResturaEngine = class {
|
|
|
2004
2857
|
}
|
|
2005
2858
|
async previewCreateSchema(req, res) {
|
|
2006
2859
|
try {
|
|
2007
|
-
const schemaDiff =
|
|
2860
|
+
const schemaDiff = await compareSchema_default.diffSchema(req.data, this.schema, this.psqlEngine);
|
|
2008
2861
|
res.send({ data: schemaDiff });
|
|
2009
2862
|
} catch (err) {
|
|
2010
2863
|
res.status(400).send(err);
|
|
@@ -2012,7 +2865,7 @@ var ResturaEngine = class {
|
|
|
2012
2865
|
}
|
|
2013
2866
|
async updateSchema(req, res) {
|
|
2014
2867
|
try {
|
|
2015
|
-
this.schema = req.data;
|
|
2868
|
+
this.schema = sortObjectKeysAlphabetically(req.data);
|
|
2016
2869
|
await this.storeFileSystemSchema();
|
|
2017
2870
|
await this.reloadEndpoints();
|
|
2018
2871
|
await this.updateTypes();
|
|
@@ -2023,11 +2876,12 @@ var ResturaEngine = class {
|
|
|
2023
2876
|
}
|
|
2024
2877
|
}
|
|
2025
2878
|
async updateTypes() {
|
|
2026
|
-
await this.generateApiFromSchema(
|
|
2879
|
+
await this.generateApiFromSchema(import_path5.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
2027
2880
|
await this.generateModelFromSchema(
|
|
2028
|
-
|
|
2881
|
+
import_path5.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
2029
2882
|
this.schema
|
|
2030
2883
|
);
|
|
2884
|
+
this.generateResturaGlobalTypes(import_path5.default.join(this.resturaConfig.generatedTypesPath, "restura.d.ts"));
|
|
2031
2885
|
}
|
|
2032
2886
|
async getSchema(req, res) {
|
|
2033
2887
|
res.send({ data: this.schema });
|
|
@@ -2035,19 +2889,38 @@ var ResturaEngine = class {
|
|
|
2035
2889
|
async getSchemaAndTypes(req, res) {
|
|
2036
2890
|
try {
|
|
2037
2891
|
const schema = await this.getLatestFileSystemSchema();
|
|
2038
|
-
const
|
|
2039
|
-
const
|
|
2040
|
-
const modelsText = await modelGenerator(schema, schemaHash);
|
|
2892
|
+
const apiText = await apiGenerator(schema);
|
|
2893
|
+
const modelsText = await modelGenerator(schema);
|
|
2041
2894
|
res.send({ schema, api: apiText, models: modelsText });
|
|
2042
2895
|
} catch (err) {
|
|
2043
2896
|
res.status(400).send({ error: err });
|
|
2044
2897
|
}
|
|
2045
2898
|
}
|
|
2899
|
+
async getMulterFilesIfAny(req, res, routeData) {
|
|
2900
|
+
var _a2;
|
|
2901
|
+
if (!((_a2 = req.header("content-type")) == null ? void 0 : _a2.includes("multipart/form-data"))) return;
|
|
2902
|
+
if (!this.isCustomRoute(routeData)) return;
|
|
2903
|
+
if (!routeData.fileUploadType) {
|
|
2904
|
+
throw new RsError("BAD_REQUEST", "File upload type not defined for route");
|
|
2905
|
+
}
|
|
2906
|
+
const multerFileUploadFunction = routeData.fileUploadType === "MULTIPLE" ? this.multerCommonUpload.array("files") : this.multerCommonUpload.single("file");
|
|
2907
|
+
return new Promise((resolve2, reject) => {
|
|
2908
|
+
multerFileUploadFunction(req, res, (err) => {
|
|
2909
|
+
if (err) {
|
|
2910
|
+
logger.warn("Multer error: " + err);
|
|
2911
|
+
reject(err);
|
|
2912
|
+
}
|
|
2913
|
+
if (req.body["data"]) req.body = JSON.parse(req.body["data"]);
|
|
2914
|
+
resolve2();
|
|
2915
|
+
});
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2046
2918
|
async executeRouteLogic(req, res, next) {
|
|
2047
2919
|
try {
|
|
2048
2920
|
const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
|
|
2049
2921
|
this.validateAuthorization(req, routeData);
|
|
2050
|
-
|
|
2922
|
+
await this.getMulterFilesIfAny(req, res, routeData);
|
|
2923
|
+
requestValidator(req, routeData, this.customTypeValidation);
|
|
2051
2924
|
if (this.isCustomRoute(routeData)) {
|
|
2052
2925
|
await this.runCustomRouteLogic(req, res, routeData);
|
|
2053
2926
|
return;
|
|
@@ -2072,33 +2945,21 @@ var ResturaEngine = class {
|
|
|
2072
2945
|
let domain = routeData.path.split("/")[1];
|
|
2073
2946
|
domain = domain.split("-").reduce((acc, value, index) => {
|
|
2074
2947
|
if (index === 0) acc = value;
|
|
2075
|
-
else acc +=
|
|
2948
|
+
else acc += import_core_utils7.StringUtils.capitalizeFirst(value);
|
|
2076
2949
|
return acc;
|
|
2077
2950
|
}, "");
|
|
2078
|
-
const customApiName = `${
|
|
2951
|
+
const customApiName = `${import_core_utils7.StringUtils.capitalizeFirst(domain)}Api${import_core_utils7.StringUtils.capitalizeFirst(version)}`;
|
|
2079
2952
|
const customApi = customApiFactory_default.getCustomApi(customApiName);
|
|
2080
2953
|
if (!customApi) throw new RsError("NOT_FOUND", `API domain ${domain}-${version} not found`);
|
|
2081
2954
|
const functionName = `${routeData.method.toLowerCase()}${routeData.path.replace(new RegExp("-", "g"), "/").split("/").reduce((acc, cur) => {
|
|
2082
2955
|
if (cur === "") return acc;
|
|
2083
|
-
return acc +
|
|
2956
|
+
return acc + import_core_utils7.StringUtils.capitalizeFirst(cur);
|
|
2084
2957
|
}, "")}`;
|
|
2085
2958
|
const customFunction = customApi[functionName];
|
|
2086
|
-
if (!customFunction)
|
|
2959
|
+
if (!customFunction)
|
|
2960
|
+
throw new RsError("NOT_FOUND", `API path ${routeData.path} not implemented ${functionName}`);
|
|
2087
2961
|
await customFunction(req, res, routeData);
|
|
2088
2962
|
}
|
|
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
2963
|
async storeFileSystemSchema() {
|
|
2103
2964
|
const schemaPrettyStr = await prettier3.format(JSON.stringify(this.schema), __spreadValues({
|
|
2104
2965
|
parser: "json"
|
|
@@ -2110,7 +2971,7 @@ var ResturaEngine = class {
|
|
|
2110
2971
|
printWidth: 120,
|
|
2111
2972
|
singleQuote: true
|
|
2112
2973
|
}));
|
|
2113
|
-
|
|
2974
|
+
import_fs4.default.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
|
|
2114
2975
|
}
|
|
2115
2976
|
resetPublicEndpoints() {
|
|
2116
2977
|
this.publicEndpoints = {
|
|
@@ -2127,13 +2988,13 @@ var ResturaEngine = class {
|
|
|
2127
2988
|
if (!routeData.roles.includes(role))
|
|
2128
2989
|
throw new RsError("UNAUTHORIZED", "Not authorized to access this endpoint");
|
|
2129
2990
|
}
|
|
2130
|
-
getRouteData(method, baseUrl,
|
|
2991
|
+
getRouteData(method, baseUrl, path5) {
|
|
2131
2992
|
const endpoint = this.schema.endpoints.find((item) => {
|
|
2132
2993
|
return item.baseUrl === baseUrl;
|
|
2133
2994
|
});
|
|
2134
2995
|
if (!endpoint) throw new RsError("NOT_FOUND", "Route not found");
|
|
2135
2996
|
const route = endpoint.routes.find((item) => {
|
|
2136
|
-
return item.method === method && item.path ===
|
|
2997
|
+
return item.method === method && item.path === path5;
|
|
2137
2998
|
});
|
|
2138
2999
|
if (!route) throw new RsError("NOT_FOUND", "Route not found");
|
|
2139
3000
|
return route;
|
|
@@ -2154,6 +3015,9 @@ __decorateClass([
|
|
|
2154
3015
|
__decorateClass([
|
|
2155
3016
|
boundMethod
|
|
2156
3017
|
], ResturaEngine.prototype, "getSchemaAndTypes", 1);
|
|
3018
|
+
__decorateClass([
|
|
3019
|
+
boundMethod
|
|
3020
|
+
], ResturaEngine.prototype, "getMulterFilesIfAny", 1);
|
|
2157
3021
|
__decorateClass([
|
|
2158
3022
|
boundMethod
|
|
2159
3023
|
], ResturaEngine.prototype, "executeRouteLogic", 1);
|
|
@@ -2163,25 +3027,55 @@ __decorateClass([
|
|
|
2163
3027
|
__decorateClass([
|
|
2164
3028
|
boundMethod
|
|
2165
3029
|
], 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
3030
|
var restura = new ResturaEngine();
|
|
3031
|
+
|
|
3032
|
+
// src/restura/sql/PsqlTransaction.ts
|
|
3033
|
+
var import_pg3 = __toESM(require("pg"));
|
|
3034
|
+
var { Client: Client2 } = import_pg3.default;
|
|
3035
|
+
var PsqlTransaction = class extends PsqlConnection {
|
|
3036
|
+
constructor(clientConfig, instanceId) {
|
|
3037
|
+
super(instanceId);
|
|
3038
|
+
this.clientConfig = clientConfig;
|
|
3039
|
+
this.client = new Client2(clientConfig);
|
|
3040
|
+
this.connectPromise = this.client.connect();
|
|
3041
|
+
this.beginTransactionPromise = this.beginTransaction();
|
|
3042
|
+
}
|
|
3043
|
+
async close() {
|
|
3044
|
+
if (this.client) {
|
|
3045
|
+
await this.client.end();
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
async beginTransaction() {
|
|
3049
|
+
await this.connectPromise;
|
|
3050
|
+
return this.client.query("BEGIN");
|
|
3051
|
+
}
|
|
3052
|
+
async rollback() {
|
|
3053
|
+
return this.query("ROLLBACK");
|
|
3054
|
+
}
|
|
3055
|
+
async commit() {
|
|
3056
|
+
return this.query("COMMIT");
|
|
3057
|
+
}
|
|
3058
|
+
async release() {
|
|
3059
|
+
return this.client.end();
|
|
3060
|
+
}
|
|
3061
|
+
async query(query, values) {
|
|
3062
|
+
await this.connectPromise;
|
|
3063
|
+
await this.beginTransactionPromise;
|
|
3064
|
+
return this.client.query(query, values);
|
|
3065
|
+
}
|
|
3066
|
+
};
|
|
2178
3067
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2179
3068
|
0 && (module.exports = {
|
|
2180
3069
|
HtmlStatusCodes,
|
|
3070
|
+
PsqlConnection,
|
|
3071
|
+
PsqlEngine,
|
|
2181
3072
|
PsqlPool,
|
|
3073
|
+
PsqlTransaction,
|
|
2182
3074
|
RsError,
|
|
2183
3075
|
SQL,
|
|
2184
3076
|
escapeColumnName,
|
|
3077
|
+
eventManager,
|
|
3078
|
+
filterPsqlParser,
|
|
2185
3079
|
insertObjectQuery,
|
|
2186
3080
|
isValueNumber,
|
|
2187
3081
|
logger,
|