@restura/core 0.1.0-alpha.7 → 0.1.0-alpha.8
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 +58 -24
- package/dist/index.d.ts +58 -24
- package/dist/index.js +850 -148
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +849 -148
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
7
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
8
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
7
9
|
var __getProtoOf = Object.getPrototypeOf;
|
|
@@ -19,6 +21,19 @@ var __spreadValues = (a, b) => {
|
|
|
19
21
|
}
|
|
20
22
|
return a;
|
|
21
23
|
};
|
|
24
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
25
|
+
var __objRest = (source, exclude) => {
|
|
26
|
+
var target = {};
|
|
27
|
+
for (var prop in source)
|
|
28
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
29
|
+
target[prop] = source[prop];
|
|
30
|
+
if (source != null && __getOwnPropSymbols)
|
|
31
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
32
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
33
|
+
target[prop] = source[prop];
|
|
34
|
+
}
|
|
35
|
+
return target;
|
|
36
|
+
};
|
|
22
37
|
var __export = (target, all) => {
|
|
23
38
|
for (var name in all)
|
|
24
39
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -52,6 +67,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
52
67
|
// src/index.ts
|
|
53
68
|
var src_exports = {};
|
|
54
69
|
__export(src_exports, {
|
|
70
|
+
PsqlPool: () => PsqlPool,
|
|
55
71
|
logger: () => logger,
|
|
56
72
|
restura: () => restura
|
|
57
73
|
});
|
|
@@ -110,7 +126,7 @@ var logger = import_winston.default.createLogger({
|
|
|
110
126
|
});
|
|
111
127
|
|
|
112
128
|
// src/restura/restura.ts
|
|
113
|
-
var
|
|
129
|
+
var import_core_utils6 = require("@redskytech/core-utils");
|
|
114
130
|
var import_internal2 = require("@restura/internal");
|
|
115
131
|
|
|
116
132
|
// ../../node_modules/.pnpm/autobind-decorator@2.4.0/node_modules/autobind-decorator/lib/esm/index.js
|
|
@@ -165,8 +181,8 @@ var import_compression = __toESM(require("compression"));
|
|
|
165
181
|
var import_cookie_parser = __toESM(require("cookie-parser"));
|
|
166
182
|
var import_crypto = require("crypto");
|
|
167
183
|
var express = __toESM(require("express"));
|
|
168
|
-
var
|
|
169
|
-
var
|
|
184
|
+
var import_fs2 = __toESM(require("fs"));
|
|
185
|
+
var import_path2 = __toESM(require("path"));
|
|
170
186
|
var prettier3 = __toESM(require("prettier"));
|
|
171
187
|
|
|
172
188
|
// src/restura/errors.ts
|
|
@@ -218,7 +234,7 @@ var htmlStatusMap = {
|
|
|
218
234
|
SCHEMA_ERROR: 500 /* SERVER_ERROR */
|
|
219
235
|
};
|
|
220
236
|
|
|
221
|
-
// src/restura/SqlUtils.ts
|
|
237
|
+
// src/restura/sql/SqlUtils.ts
|
|
222
238
|
var SqlUtils = class _SqlUtils {
|
|
223
239
|
static convertDatabaseTypeToTypescript(type, value) {
|
|
224
240
|
type = type.toLocaleLowerCase();
|
|
@@ -309,9 +325,9 @@ var ResponseValidator = class _ResponseValidator {
|
|
|
309
325
|
return { validator: "any" };
|
|
310
326
|
}
|
|
311
327
|
getTypeFromTable(selector, name) {
|
|
312
|
-
const
|
|
313
|
-
if (
|
|
314
|
-
const tableName =
|
|
328
|
+
const path3 = selector.split(".");
|
|
329
|
+
if (path3.length === 0 || path3.length > 2 || path3[0] === "") return { validator: "any", isOptional: false };
|
|
330
|
+
const tableName = path3.length == 2 ? path3[0] : name, columnName = path3.length == 2 ? path3[1] : path3[0];
|
|
315
331
|
const table = this.database.find((t) => t.name == tableName);
|
|
316
332
|
const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
|
|
317
333
|
if (!table || !column) return { validator: "any", isOptional: false };
|
|
@@ -527,10 +543,10 @@ var ApiTree = class _ApiTree {
|
|
|
527
543
|
return `${p.name}${optional ? "?" : ""}:${responseType}${array ? "[]" : ""}`;
|
|
528
544
|
}
|
|
529
545
|
getTypeFromTable(selector, name) {
|
|
530
|
-
const
|
|
531
|
-
if (
|
|
532
|
-
let tableName =
|
|
533
|
-
const columnName =
|
|
546
|
+
const path3 = selector.split(".");
|
|
547
|
+
if (path3.length === 0 || path3.length > 2 || path3[0] === "") return { responseType: "any", optional: false };
|
|
548
|
+
let tableName = path3.length == 2 ? path3[0] : name;
|
|
549
|
+
const columnName = path3.length == 2 ? path3[1] : path3[0];
|
|
534
550
|
let table = this.database.find((t) => t.name == tableName);
|
|
535
551
|
if (!table && tableName.includes("_")) {
|
|
536
552
|
const tableAliasSplit = tableName.split("_");
|
|
@@ -545,8 +561,8 @@ var ApiTree = class _ApiTree {
|
|
|
545
561
|
};
|
|
546
562
|
}
|
|
547
563
|
};
|
|
548
|
-
function pathToNamespaces(
|
|
549
|
-
return
|
|
564
|
+
function pathToNamespaces(path3) {
|
|
565
|
+
return path3.split("/").map((e) => import_core_utils.StringUtils.toPascalCasing(e)).filter((e) => e);
|
|
550
566
|
}
|
|
551
567
|
function apiGenerator(schema, schemaHash) {
|
|
552
568
|
let apiString = `/** Auto generated file from Schema Hash (${schemaHash}). DO NOT MODIFY **/`;
|
|
@@ -610,6 +626,163 @@ function addApiResponseFunctions(req, res, next) {
|
|
|
610
626
|
// src/restura/validateRequestParams.ts
|
|
611
627
|
var import_core_utils2 = require("@redskytech/core-utils");
|
|
612
628
|
var import_jsonschema = __toESM(require("jsonschema"));
|
|
629
|
+
var import_zod3 = require("zod");
|
|
630
|
+
|
|
631
|
+
// src/restura/types/validation.types.ts
|
|
632
|
+
var import_zod2 = require("zod");
|
|
633
|
+
var validatorDataSchemeValue = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.array(import_zod2.z.string()), import_zod2.z.number(), import_zod2.z.array(import_zod2.z.number())]);
|
|
634
|
+
var validatorDataSchema = import_zod2.z.object({
|
|
635
|
+
type: import_zod2.z.enum(["TYPE_CHECK", "MIN", "MAX", "ONE_OF"]),
|
|
636
|
+
value: validatorDataSchemeValue
|
|
637
|
+
}).strict();
|
|
638
|
+
|
|
639
|
+
// src/restura/utils/addQuotesToStrings.ts
|
|
640
|
+
function addQuotesToStrings(variable) {
|
|
641
|
+
if (typeof variable === "string") {
|
|
642
|
+
return `'${variable}'`;
|
|
643
|
+
} else if (Array.isArray(variable)) {
|
|
644
|
+
const arrayWithQuotes = variable.map(addQuotesToStrings);
|
|
645
|
+
return arrayWithQuotes;
|
|
646
|
+
} else {
|
|
647
|
+
return variable;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// src/restura/validateRequestParams.ts
|
|
652
|
+
function validateRequestParams(req, routeData, validationSchema) {
|
|
653
|
+
const requestData = getRequestData(req);
|
|
654
|
+
req.data = requestData;
|
|
655
|
+
if (routeData.request === void 0) {
|
|
656
|
+
if (routeData.type !== "CUSTOM_ONE" && routeData.type !== "CUSTOM_ARRAY" && routeData.type !== "CUSTOM_PAGED")
|
|
657
|
+
throw new RsError("BAD_REQUEST", `No request parameters provided for standard request.`);
|
|
658
|
+
if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
|
|
659
|
+
if (!routeData.requestType) throw new RsError("BAD_REQUEST", `No request type defined for custom request.`);
|
|
660
|
+
const currentInterface = validationSchema[routeData.requestType];
|
|
661
|
+
const validator = new import_jsonschema.default.Validator();
|
|
662
|
+
const executeValidation = validator.validate(req.data, currentInterface);
|
|
663
|
+
if (!executeValidation.valid) {
|
|
664
|
+
throw new RsError(
|
|
665
|
+
"BAD_REQUEST",
|
|
666
|
+
`Request custom setup has failed the following check: (${executeValidation.errors})`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
Object.keys(req.data).forEach((requestParamName) => {
|
|
672
|
+
const requestParam = routeData.request.find((param) => param.name === requestParamName);
|
|
673
|
+
if (!requestParam) {
|
|
674
|
+
throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not allowed`);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
routeData.request.forEach((requestParam) => {
|
|
678
|
+
const requestValue = requestData[requestParam.name];
|
|
679
|
+
if (requestParam.required && requestValue === void 0)
|
|
680
|
+
throw new RsError("BAD_REQUEST", `Request param (${requestParam.name}) is required but missing`);
|
|
681
|
+
else if (!requestParam.required && requestValue === void 0) return;
|
|
682
|
+
validateRequestSingleParam(requestValue, requestParam);
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
function validateRequestSingleParam(requestValue, requestParam) {
|
|
686
|
+
requestParam.validator.forEach((validator) => {
|
|
687
|
+
switch (validator.type) {
|
|
688
|
+
case "TYPE_CHECK":
|
|
689
|
+
performTypeCheck(requestValue, validator, requestParam.name);
|
|
690
|
+
break;
|
|
691
|
+
case "MIN":
|
|
692
|
+
performMinCheck(requestValue, validator, requestParam.name);
|
|
693
|
+
break;
|
|
694
|
+
case "MAX":
|
|
695
|
+
performMaxCheck(requestValue, validator, requestParam.name);
|
|
696
|
+
break;
|
|
697
|
+
case "ONE_OF":
|
|
698
|
+
performOneOfCheck(requestValue, validator, requestParam.name);
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
function isValidType(type, requestValue) {
|
|
704
|
+
try {
|
|
705
|
+
expectValidType(type, requestValue);
|
|
706
|
+
return true;
|
|
707
|
+
} catch (e) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function expectValidType(type, requestValue) {
|
|
712
|
+
if (type === "number") {
|
|
713
|
+
return import_zod3.z.number().parse(requestValue);
|
|
714
|
+
}
|
|
715
|
+
if (type === "string") {
|
|
716
|
+
return import_zod3.z.string().parse(requestValue);
|
|
717
|
+
}
|
|
718
|
+
if (type === "boolean") {
|
|
719
|
+
return import_zod3.z.boolean().parse(requestValue);
|
|
720
|
+
}
|
|
721
|
+
if (type === "string[]") {
|
|
722
|
+
return import_zod3.z.array(import_zod3.z.string()).parse(requestValue);
|
|
723
|
+
}
|
|
724
|
+
if (type === "number[]") {
|
|
725
|
+
return import_zod3.z.array(import_zod3.z.number()).parse(requestValue);
|
|
726
|
+
}
|
|
727
|
+
if (type === "any[]") {
|
|
728
|
+
return import_zod3.z.array(import_zod3.z.any()).parse(requestValue);
|
|
729
|
+
}
|
|
730
|
+
if (type === "object") {
|
|
731
|
+
return import_zod3.z.object({}).strict().parse(requestValue);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function performTypeCheck(requestValue, validator, requestParamName) {
|
|
735
|
+
if (!isValidType(validator.value, requestValue)) {
|
|
736
|
+
throw new RsError(
|
|
737
|
+
"BAD_REQUEST",
|
|
738
|
+
`Request param (${requestParamName}) with value (${addQuotesToStrings(requestValue)}) is not of type (${validator.value})`
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
try {
|
|
742
|
+
validatorDataSchemeValue.parse(validator.value);
|
|
743
|
+
} catch (e) {
|
|
744
|
+
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not a valid type`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function expectOnlyNumbers(requestValue, validator, requestParamName) {
|
|
748
|
+
if (!isValueNumber(requestValue))
|
|
749
|
+
throw new RsError(
|
|
750
|
+
"BAD_REQUEST",
|
|
751
|
+
`Request param (${requestParamName}) with value (${requestValue}) is not of type number`
|
|
752
|
+
);
|
|
753
|
+
if (!isValueNumber(validator.value))
|
|
754
|
+
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value} is not of type number`);
|
|
755
|
+
}
|
|
756
|
+
function performMinCheck(requestValue, validator, requestParamName) {
|
|
757
|
+
expectOnlyNumbers(requestValue, validator, requestParamName);
|
|
758
|
+
if (requestValue < validator.value)
|
|
759
|
+
throw new RsError(
|
|
760
|
+
"BAD_REQUEST",
|
|
761
|
+
`Request param (${requestParamName}) with value (${requestValue}) is less than (${validator.value})`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
function performMaxCheck(requestValue, validator, requestParamName) {
|
|
765
|
+
expectOnlyNumbers(requestValue, validator, requestParamName);
|
|
766
|
+
if (requestValue > validator.value)
|
|
767
|
+
throw new RsError(
|
|
768
|
+
"BAD_REQUEST",
|
|
769
|
+
`Request param (${requestParamName}) with value (${requestValue}) is more than (${validator.value})`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
function performOneOfCheck(requestValue, validator, requestParamName) {
|
|
773
|
+
if (!import_core_utils2.ObjectUtils.isArrayWithData(validator.value))
|
|
774
|
+
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not of type array`);
|
|
775
|
+
if (typeof requestValue === "object")
|
|
776
|
+
throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not of type string or number`);
|
|
777
|
+
if (!validator.value.includes(requestValue))
|
|
778
|
+
throw new RsError(
|
|
779
|
+
"BAD_REQUEST",
|
|
780
|
+
`Request param (${requestParamName}) with value (${requestValue}) is not one of (${validator.value.join(", ")})`
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
function isValueNumber(value) {
|
|
784
|
+
return !isNaN(Number(value));
|
|
785
|
+
}
|
|
613
786
|
function getRequestData(req) {
|
|
614
787
|
let body = "";
|
|
615
788
|
if (req.method === "GET" || req.method === "DELETE") {
|
|
@@ -644,84 +817,83 @@ function getRequestData(req) {
|
|
|
644
817
|
}
|
|
645
818
|
|
|
646
819
|
// src/restura/restura.schema.ts
|
|
647
|
-
var
|
|
648
|
-
var orderBySchema =
|
|
649
|
-
columnName:
|
|
650
|
-
order:
|
|
651
|
-
tableName:
|
|
652
|
-
}).strict();
|
|
653
|
-
var groupBySchema = import_zod2.z.object({
|
|
654
|
-
columnName: import_zod2.z.string(),
|
|
655
|
-
tableName: import_zod2.z.string()
|
|
820
|
+
var import_zod4 = require("zod");
|
|
821
|
+
var orderBySchema = import_zod4.z.object({
|
|
822
|
+
columnName: import_zod4.z.string(),
|
|
823
|
+
order: import_zod4.z.enum(["ASC", "DESC"]),
|
|
824
|
+
tableName: import_zod4.z.string()
|
|
656
825
|
}).strict();
|
|
657
|
-
var
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
operator: import_zod2.z.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH"]).optional(),
|
|
661
|
-
value: import_zod2.z.string().optional(),
|
|
662
|
-
custom: import_zod2.z.string().optional(),
|
|
663
|
-
conjunction: import_zod2.z.enum(["AND", "OR"]).optional()
|
|
826
|
+
var groupBySchema = import_zod4.z.object({
|
|
827
|
+
columnName: import_zod4.z.string(),
|
|
828
|
+
tableName: import_zod4.z.string()
|
|
664
829
|
}).strict();
|
|
665
|
-
var
|
|
666
|
-
|
|
667
|
-
|
|
830
|
+
var whereDataSchema = import_zod4.z.object({
|
|
831
|
+
tableName: import_zod4.z.string().optional(),
|
|
832
|
+
columnName: import_zod4.z.string().optional(),
|
|
833
|
+
operator: import_zod4.z.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH"]).optional(),
|
|
834
|
+
value: import_zod4.z.string().or(import_zod4.z.number()).optional(),
|
|
835
|
+
custom: import_zod4.z.string().optional(),
|
|
836
|
+
conjunction: import_zod4.z.enum(["AND", "OR"]).optional()
|
|
668
837
|
}).strict();
|
|
669
|
-
var
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
foreignColumnName: import_zod2.z.string().optional(),
|
|
673
|
-
custom: import_zod2.z.string().optional(),
|
|
674
|
-
type: import_zod2.z.enum(["LEFT", "INNER"]),
|
|
675
|
-
alias: import_zod2.z.string().optional()
|
|
838
|
+
var assignmentDataSchema = import_zod4.z.object({
|
|
839
|
+
name: import_zod4.z.string(),
|
|
840
|
+
value: import_zod4.z.string()
|
|
676
841
|
}).strict();
|
|
677
|
-
var
|
|
678
|
-
|
|
679
|
-
|
|
842
|
+
var joinDataSchema = import_zod4.z.object({
|
|
843
|
+
table: import_zod4.z.string(),
|
|
844
|
+
localColumnName: import_zod4.z.string().optional(),
|
|
845
|
+
foreignColumnName: import_zod4.z.string().optional(),
|
|
846
|
+
custom: import_zod4.z.string().optional(),
|
|
847
|
+
type: import_zod4.z.enum(["LEFT", "INNER"]),
|
|
848
|
+
alias: import_zod4.z.string().optional()
|
|
680
849
|
}).strict();
|
|
681
|
-
var requestDataSchema =
|
|
682
|
-
name:
|
|
683
|
-
required:
|
|
684
|
-
validator:
|
|
850
|
+
var requestDataSchema = import_zod4.z.object({
|
|
851
|
+
name: import_zod4.z.string(),
|
|
852
|
+
required: import_zod4.z.boolean(),
|
|
853
|
+
validator: import_zod4.z.array(validatorDataSchema)
|
|
685
854
|
}).strict();
|
|
686
|
-
var responseDataSchema =
|
|
687
|
-
name:
|
|
688
|
-
selector:
|
|
689
|
-
subquery:
|
|
690
|
-
table:
|
|
691
|
-
joins:
|
|
692
|
-
where:
|
|
693
|
-
properties:
|
|
855
|
+
var responseDataSchema = import_zod4.z.object({
|
|
856
|
+
name: import_zod4.z.string(),
|
|
857
|
+
selector: import_zod4.z.string().optional(),
|
|
858
|
+
subquery: import_zod4.z.object({
|
|
859
|
+
table: import_zod4.z.string(),
|
|
860
|
+
joins: import_zod4.z.array(joinDataSchema),
|
|
861
|
+
where: import_zod4.z.array(whereDataSchema),
|
|
862
|
+
properties: import_zod4.z.array(import_zod4.z.lazy(() => responseDataSchema)),
|
|
694
863
|
// Explicit type for the lazy schema
|
|
695
864
|
groupBy: groupBySchema.optional(),
|
|
696
865
|
orderBy: orderBySchema.optional()
|
|
697
866
|
}).optional()
|
|
698
867
|
}).strict();
|
|
699
|
-
var routeDataBaseSchema =
|
|
700
|
-
method:
|
|
701
|
-
name:
|
|
702
|
-
description:
|
|
703
|
-
path:
|
|
704
|
-
roles:
|
|
868
|
+
var routeDataBaseSchema = import_zod4.z.object({
|
|
869
|
+
method: import_zod4.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
870
|
+
name: import_zod4.z.string(),
|
|
871
|
+
description: import_zod4.z.string(),
|
|
872
|
+
path: import_zod4.z.string(),
|
|
873
|
+
roles: import_zod4.z.array(import_zod4.z.string())
|
|
705
874
|
}).strict();
|
|
706
875
|
var standardRouteSchema = routeDataBaseSchema.extend({
|
|
707
|
-
type:
|
|
708
|
-
table:
|
|
709
|
-
joins:
|
|
710
|
-
assignments:
|
|
711
|
-
where:
|
|
712
|
-
request:
|
|
713
|
-
response:
|
|
876
|
+
type: import_zod4.z.enum(["ONE", "ARRAY", "PAGED"]),
|
|
877
|
+
table: import_zod4.z.string(),
|
|
878
|
+
joins: import_zod4.z.array(joinDataSchema),
|
|
879
|
+
assignments: import_zod4.z.array(assignmentDataSchema),
|
|
880
|
+
where: import_zod4.z.array(whereDataSchema),
|
|
881
|
+
request: import_zod4.z.array(requestDataSchema),
|
|
882
|
+
response: import_zod4.z.array(responseDataSchema),
|
|
714
883
|
groupBy: groupBySchema.optional(),
|
|
715
884
|
orderBy: orderBySchema.optional()
|
|
716
885
|
}).strict();
|
|
717
886
|
var customRouteSchema = routeDataBaseSchema.extend({
|
|
718
|
-
type:
|
|
719
|
-
responseType:
|
|
720
|
-
requestType:
|
|
721
|
-
request:
|
|
722
|
-
|
|
887
|
+
type: import_zod4.z.enum(["CUSTOM_ONE", "CUSTOM_ARRAY", "CUSTOM_PAGED"]),
|
|
888
|
+
responseType: import_zod4.z.union([import_zod4.z.string(), import_zod4.z.enum(["string", "number", "boolean"])]),
|
|
889
|
+
requestType: import_zod4.z.string().optional(),
|
|
890
|
+
request: import_zod4.z.array(requestDataSchema).optional(),
|
|
891
|
+
table: import_zod4.z.undefined(),
|
|
892
|
+
joins: import_zod4.z.undefined(),
|
|
893
|
+
assignments: import_zod4.z.undefined(),
|
|
894
|
+
fileUploadType: import_zod4.z.enum(["SINGLE", "MULTIPLE"]).optional()
|
|
723
895
|
}).strict();
|
|
724
|
-
var postgresColumnNumericTypesSchema =
|
|
896
|
+
var postgresColumnNumericTypesSchema = import_zod4.z.enum([
|
|
725
897
|
"SMALLINT",
|
|
726
898
|
// 2 bytes, -32,768 to 32,767
|
|
727
899
|
"INTEGER",
|
|
@@ -741,7 +913,7 @@ var postgresColumnNumericTypesSchema = import_zod2.z.enum([
|
|
|
741
913
|
"BIGSERIAL"
|
|
742
914
|
// auto-incrementing big integer
|
|
743
915
|
]);
|
|
744
|
-
var postgresColumnStringTypesSchema =
|
|
916
|
+
var postgresColumnStringTypesSchema = import_zod4.z.enum([
|
|
745
917
|
"CHAR",
|
|
746
918
|
// fixed-length, blank-padded
|
|
747
919
|
"VARCHAR",
|
|
@@ -751,7 +923,7 @@ var postgresColumnStringTypesSchema = import_zod2.z.enum([
|
|
|
751
923
|
"BYTEA"
|
|
752
924
|
// binary data
|
|
753
925
|
]);
|
|
754
|
-
var postgresColumnDateTypesSchema =
|
|
926
|
+
var postgresColumnDateTypesSchema = import_zod4.z.enum([
|
|
755
927
|
"DATE",
|
|
756
928
|
// calendar date (year, month, day)
|
|
757
929
|
"TIMESTAMP",
|
|
@@ -763,7 +935,7 @@ var postgresColumnDateTypesSchema = import_zod2.z.enum([
|
|
|
763
935
|
"INTERVAL"
|
|
764
936
|
// time span
|
|
765
937
|
]);
|
|
766
|
-
var mariaDbColumnNumericTypesSchema =
|
|
938
|
+
var mariaDbColumnNumericTypesSchema = import_zod4.z.enum([
|
|
767
939
|
"BOOLEAN",
|
|
768
940
|
// 1-byte A synonym for "TINYINT(1)". Supported from version 1.2.0 onwards.
|
|
769
941
|
"TINYINT",
|
|
@@ -783,7 +955,7 @@ var mariaDbColumnNumericTypesSchema = import_zod2.z.enum([
|
|
|
783
955
|
"DOUBLE"
|
|
784
956
|
// 8 bytes Stored in 64-bit IEEE-754 floating point format. As such, the number of significant digits is about 15 and the range of values is approximately +/-1e308.
|
|
785
957
|
]);
|
|
786
|
-
var mariaDbColumnStringTypesSchema =
|
|
958
|
+
var mariaDbColumnStringTypesSchema = import_zod4.z.enum([
|
|
787
959
|
"CHAR",
|
|
788
960
|
// 1, 2, 4, or 8 bytes Holds letters and special characters of fixed length. Max length is 255. Default and minimum size is 1 byte.
|
|
789
961
|
"VARCHAR",
|
|
@@ -809,7 +981,7 @@ var mariaDbColumnStringTypesSchema = import_zod2.z.enum([
|
|
|
809
981
|
"ENUM"
|
|
810
982
|
// Enum type
|
|
811
983
|
]);
|
|
812
|
-
var mariaDbColumnDateTypesSchema =
|
|
984
|
+
var mariaDbColumnDateTypesSchema = import_zod4.z.enum([
|
|
813
985
|
"DATE",
|
|
814
986
|
// 4-bytes Date has year, month, and day.
|
|
815
987
|
"DATETIME",
|
|
@@ -819,9 +991,9 @@ var mariaDbColumnDateTypesSchema = import_zod2.z.enum([
|
|
|
819
991
|
"TIMESTAMP"
|
|
820
992
|
// 4-bytes Values are stored as the number of seconds since 1970-01-01 00:00:00 UTC, and optionally microseconds.
|
|
821
993
|
]);
|
|
822
|
-
var columnDataSchema =
|
|
823
|
-
name:
|
|
824
|
-
type:
|
|
994
|
+
var columnDataSchema = import_zod4.z.object({
|
|
995
|
+
name: import_zod4.z.string(),
|
|
996
|
+
type: import_zod4.z.union([
|
|
825
997
|
postgresColumnNumericTypesSchema,
|
|
826
998
|
postgresColumnStringTypesSchema,
|
|
827
999
|
postgresColumnDateTypesSchema,
|
|
@@ -829,24 +1001,24 @@ var columnDataSchema = import_zod2.z.object({
|
|
|
829
1001
|
mariaDbColumnStringTypesSchema,
|
|
830
1002
|
mariaDbColumnDateTypesSchema
|
|
831
1003
|
]),
|
|
832
|
-
isNullable:
|
|
833
|
-
roles:
|
|
834
|
-
comment:
|
|
835
|
-
default:
|
|
836
|
-
value:
|
|
837
|
-
isPrimary:
|
|
838
|
-
isUnique:
|
|
839
|
-
hasAutoIncrement:
|
|
840
|
-
length:
|
|
1004
|
+
isNullable: import_zod4.z.boolean(),
|
|
1005
|
+
roles: import_zod4.z.array(import_zod4.z.string()),
|
|
1006
|
+
comment: import_zod4.z.string().optional(),
|
|
1007
|
+
default: import_zod4.z.string().optional(),
|
|
1008
|
+
value: import_zod4.z.string().optional(),
|
|
1009
|
+
isPrimary: import_zod4.z.boolean().optional(),
|
|
1010
|
+
isUnique: import_zod4.z.boolean().optional(),
|
|
1011
|
+
hasAutoIncrement: import_zod4.z.boolean().optional(),
|
|
1012
|
+
length: import_zod4.z.number().optional()
|
|
841
1013
|
}).strict();
|
|
842
|
-
var indexDataSchema =
|
|
843
|
-
name:
|
|
844
|
-
columns:
|
|
845
|
-
isUnique:
|
|
846
|
-
isPrimaryKey:
|
|
847
|
-
order:
|
|
1014
|
+
var indexDataSchema = import_zod4.z.object({
|
|
1015
|
+
name: import_zod4.z.string(),
|
|
1016
|
+
columns: import_zod4.z.array(import_zod4.z.string()),
|
|
1017
|
+
isUnique: import_zod4.z.boolean(),
|
|
1018
|
+
isPrimaryKey: import_zod4.z.boolean(),
|
|
1019
|
+
order: import_zod4.z.enum(["ASC", "DESC"])
|
|
848
1020
|
}).strict();
|
|
849
|
-
var foreignKeyActionsSchema =
|
|
1021
|
+
var foreignKeyActionsSchema = import_zod4.z.enum([
|
|
850
1022
|
"CASCADE",
|
|
851
1023
|
// CASCADE action for foreign keys
|
|
852
1024
|
"SET NULL",
|
|
@@ -858,38 +1030,38 @@ var foreignKeyActionsSchema = import_zod2.z.enum([
|
|
|
858
1030
|
"SET DEFAULT"
|
|
859
1031
|
// SET DEFAULT action for foreign keys
|
|
860
1032
|
]);
|
|
861
|
-
var foreignKeyDataSchema =
|
|
862
|
-
name:
|
|
863
|
-
column:
|
|
864
|
-
refTable:
|
|
865
|
-
refColumn:
|
|
1033
|
+
var foreignKeyDataSchema = import_zod4.z.object({
|
|
1034
|
+
name: import_zod4.z.string(),
|
|
1035
|
+
column: import_zod4.z.string(),
|
|
1036
|
+
refTable: import_zod4.z.string(),
|
|
1037
|
+
refColumn: import_zod4.z.string(),
|
|
866
1038
|
onDelete: foreignKeyActionsSchema,
|
|
867
1039
|
onUpdate: foreignKeyActionsSchema
|
|
868
1040
|
}).strict();
|
|
869
|
-
var checkConstraintDataSchema =
|
|
870
|
-
name:
|
|
871
|
-
check:
|
|
1041
|
+
var checkConstraintDataSchema = import_zod4.z.object({
|
|
1042
|
+
name: import_zod4.z.string(),
|
|
1043
|
+
check: import_zod4.z.string()
|
|
872
1044
|
}).strict();
|
|
873
|
-
var tableDataSchema =
|
|
874
|
-
name:
|
|
875
|
-
columns:
|
|
876
|
-
indexes:
|
|
877
|
-
foreignKeys:
|
|
878
|
-
checkConstraints:
|
|
879
|
-
roles:
|
|
1045
|
+
var tableDataSchema = import_zod4.z.object({
|
|
1046
|
+
name: import_zod4.z.string(),
|
|
1047
|
+
columns: import_zod4.z.array(columnDataSchema),
|
|
1048
|
+
indexes: import_zod4.z.array(indexDataSchema),
|
|
1049
|
+
foreignKeys: import_zod4.z.array(foreignKeyDataSchema),
|
|
1050
|
+
checkConstraints: import_zod4.z.array(checkConstraintDataSchema),
|
|
1051
|
+
roles: import_zod4.z.array(import_zod4.z.string())
|
|
880
1052
|
}).strict();
|
|
881
|
-
var endpointDataSchema =
|
|
882
|
-
name:
|
|
883
|
-
description:
|
|
884
|
-
baseUrl:
|
|
885
|
-
routes:
|
|
1053
|
+
var endpointDataSchema = import_zod4.z.object({
|
|
1054
|
+
name: import_zod4.z.string(),
|
|
1055
|
+
description: import_zod4.z.string(),
|
|
1056
|
+
baseUrl: import_zod4.z.string(),
|
|
1057
|
+
routes: import_zod4.z.array(import_zod4.z.union([standardRouteSchema, customRouteSchema]))
|
|
886
1058
|
}).strict();
|
|
887
|
-
var resturaZodSchema =
|
|
888
|
-
database:
|
|
889
|
-
endpoints:
|
|
890
|
-
globalParams:
|
|
891
|
-
roles:
|
|
892
|
-
customTypes:
|
|
1059
|
+
var resturaZodSchema = import_zod4.z.object({
|
|
1060
|
+
database: import_zod4.z.array(tableDataSchema),
|
|
1061
|
+
endpoints: import_zod4.z.array(endpointDataSchema),
|
|
1062
|
+
globalParams: import_zod4.z.array(import_zod4.z.string()),
|
|
1063
|
+
roles: import_zod4.z.array(import_zod4.z.string()),
|
|
1064
|
+
customTypes: import_zod4.z.string()
|
|
893
1065
|
}).strict();
|
|
894
1066
|
async function isSchemaValid(schemaToCheck) {
|
|
895
1067
|
try {
|
|
@@ -949,7 +1121,7 @@ function convertTable(table) {
|
|
|
949
1121
|
}
|
|
950
1122
|
|
|
951
1123
|
// src/restura/middleware/authenticateUser.ts
|
|
952
|
-
|
|
1124
|
+
function authenticateUser(applicationAuthenticateHandler) {
|
|
953
1125
|
return (req, res, next) => {
|
|
954
1126
|
applicationAuthenticateHandler(
|
|
955
1127
|
req,
|
|
@@ -964,6 +1136,513 @@ async function authenticateUser(applicationAuthenticateHandler) {
|
|
|
964
1136
|
};
|
|
965
1137
|
}
|
|
966
1138
|
|
|
1139
|
+
// src/restura/customTypeValidationGenerator.ts
|
|
1140
|
+
var import_fs = __toESM(require("fs"));
|
|
1141
|
+
var TJS = __toESM(require("typescript-json-schema"));
|
|
1142
|
+
var import_path = __toESM(require("path"));
|
|
1143
|
+
var import_tmp = __toESM(require("tmp"));
|
|
1144
|
+
var process2 = __toESM(require("process"));
|
|
1145
|
+
function customTypeValidationGenerator(currentSchema) {
|
|
1146
|
+
const schemaObject = {};
|
|
1147
|
+
const customInterfaceNames = currentSchema.customTypes.match(new RegExp("(?<=interface\\s)(\\w+)|(?<=type\\s)(\\w+)", "g"));
|
|
1148
|
+
if (!customInterfaceNames) return {};
|
|
1149
|
+
const temporaryFile = import_tmp.default.fileSync({ mode: 420, prefix: "prefix-", postfix: ".ts" });
|
|
1150
|
+
import_fs.default.writeFileSync(temporaryFile.name, currentSchema.customTypes);
|
|
1151
|
+
const compilerOptions = {
|
|
1152
|
+
strictNullChecks: true,
|
|
1153
|
+
skipLibCheck: true
|
|
1154
|
+
};
|
|
1155
|
+
const program = TJS.getProgramFromFiles(
|
|
1156
|
+
[
|
|
1157
|
+
(0, import_path.resolve)(temporaryFile.name),
|
|
1158
|
+
// find a way to remove
|
|
1159
|
+
import_path.default.join(process2.cwd(), "src/@types/models.d.ts"),
|
|
1160
|
+
import_path.default.join(process2.cwd(), "src/@types/api.d.ts")
|
|
1161
|
+
],
|
|
1162
|
+
compilerOptions
|
|
1163
|
+
);
|
|
1164
|
+
customInterfaceNames.forEach((item) => {
|
|
1165
|
+
const ddlSchema = TJS.generateSchema(program, item, {
|
|
1166
|
+
required: true
|
|
1167
|
+
});
|
|
1168
|
+
schemaObject[item] = ddlSchema || {};
|
|
1169
|
+
});
|
|
1170
|
+
temporaryFile.removeCallback();
|
|
1171
|
+
return schemaObject;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// src/restura/sql/SqlEngine.ts
|
|
1175
|
+
var import_core_utils4 = require("@redskytech/core-utils");
|
|
1176
|
+
var SqlEngine = class {
|
|
1177
|
+
async runQueryForRoute(req, routeData, schema) {
|
|
1178
|
+
if (!this.doesRoleHavePermissionToTable(req.requesterDetails.role, schema, routeData.table))
|
|
1179
|
+
throw new RsError("UNAUTHORIZED", "You do not have permission to access this table");
|
|
1180
|
+
switch (routeData.method) {
|
|
1181
|
+
case "POST":
|
|
1182
|
+
return this.executeCreateRequest(req, routeData, schema);
|
|
1183
|
+
case "GET":
|
|
1184
|
+
return this.executeGetRequest(req, routeData, schema);
|
|
1185
|
+
case "PUT":
|
|
1186
|
+
case "PATCH":
|
|
1187
|
+
return this.executeUpdateRequest(req, routeData, schema);
|
|
1188
|
+
case "DELETE":
|
|
1189
|
+
return this.executeDeleteRequest(req, routeData, schema);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
getTableSchema(schema, tableName) {
|
|
1193
|
+
const tableSchema = schema.database.find((item) => item.name === tableName);
|
|
1194
|
+
if (!tableSchema) throw new RsError("SCHEMA_ERROR", `Table ${tableName} not found in schema`);
|
|
1195
|
+
return tableSchema;
|
|
1196
|
+
}
|
|
1197
|
+
doesRoleHavePermissionToColumn(role, schema, item, joins) {
|
|
1198
|
+
if (item.selector) {
|
|
1199
|
+
let tableName = item.selector.split(".")[0];
|
|
1200
|
+
const columnName = item.selector.split(".")[1];
|
|
1201
|
+
let tableSchema = schema.database.find((item2) => item2.name === tableName);
|
|
1202
|
+
if (!tableSchema) {
|
|
1203
|
+
const join = joins.find((join2) => join2.alias === tableName);
|
|
1204
|
+
if (!join) throw new RsError("SCHEMA_ERROR", `Table ${tableName} not found in schema`);
|
|
1205
|
+
tableName = join.table;
|
|
1206
|
+
tableSchema = schema.database.find((item2) => item2.name === tableName);
|
|
1207
|
+
}
|
|
1208
|
+
if (!tableSchema) throw new RsError("SCHEMA_ERROR", `Table ${tableName} not found in schema`);
|
|
1209
|
+
const columnSchema = tableSchema.columns.find((item2) => item2.name === columnName);
|
|
1210
|
+
if (!columnSchema)
|
|
1211
|
+
throw new RsError("SCHEMA_ERROR", `Column ${columnName} not found in table ${tableName}`);
|
|
1212
|
+
const doesColumnHaveRoles = import_core_utils4.ObjectUtils.isArrayWithData(columnSchema.roles);
|
|
1213
|
+
if (!doesColumnHaveRoles) return true;
|
|
1214
|
+
if (!role) return false;
|
|
1215
|
+
return columnSchema.roles.includes(role);
|
|
1216
|
+
}
|
|
1217
|
+
if (item.subquery) {
|
|
1218
|
+
return import_core_utils4.ObjectUtils.isArrayWithData(
|
|
1219
|
+
item.subquery.properties.filter((nestedItem) => {
|
|
1220
|
+
return this.doesRoleHavePermissionToColumn(role, schema, nestedItem, joins);
|
|
1221
|
+
})
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
return false;
|
|
1225
|
+
}
|
|
1226
|
+
doesRoleHavePermissionToTable(userRole, schema, tableName) {
|
|
1227
|
+
const tableSchema = this.getTableSchema(schema, tableName);
|
|
1228
|
+
const doesTableHaveRoles = import_core_utils4.ObjectUtils.isArrayWithData(tableSchema.roles);
|
|
1229
|
+
if (!doesTableHaveRoles) return true;
|
|
1230
|
+
if (!userRole) return false;
|
|
1231
|
+
return tableSchema.roles.includes(userRole);
|
|
1232
|
+
}
|
|
1233
|
+
replaceParamKeywords(value, routeData, req, sqlParams) {
|
|
1234
|
+
let returnValue = value;
|
|
1235
|
+
returnValue = this.replaceLocalParamKeywords(returnValue, routeData, req, sqlParams);
|
|
1236
|
+
returnValue = this.replaceGlobalParamKeywords(returnValue, routeData, req, sqlParams);
|
|
1237
|
+
return returnValue;
|
|
1238
|
+
}
|
|
1239
|
+
replaceLocalParamKeywords(value, routeData, req, sqlParams) {
|
|
1240
|
+
var _a;
|
|
1241
|
+
if (!routeData.request) return value;
|
|
1242
|
+
const data = req.data;
|
|
1243
|
+
if (typeof value === "string") {
|
|
1244
|
+
(_a = value.match(/\$[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a.forEach((param) => {
|
|
1245
|
+
const requestParam = routeData.request.find((item) => {
|
|
1246
|
+
return item.name === param.replace("$", "");
|
|
1247
|
+
});
|
|
1248
|
+
if (!requestParam)
|
|
1249
|
+
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1250
|
+
sqlParams.push(data[requestParam.name]);
|
|
1251
|
+
});
|
|
1252
|
+
return value.replace(new RegExp(/\$[a-zA-Z][a-zA-Z0-9_]+/g), "?");
|
|
1253
|
+
}
|
|
1254
|
+
return value;
|
|
1255
|
+
}
|
|
1256
|
+
replaceGlobalParamKeywords(value, routeData, req, sqlParams) {
|
|
1257
|
+
var _a;
|
|
1258
|
+
if (typeof value === "string") {
|
|
1259
|
+
(_a = value.match(/#[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a.forEach((param) => {
|
|
1260
|
+
param = param.replace("#", "");
|
|
1261
|
+
const globalParamValue = req.requesterDetails[param];
|
|
1262
|
+
if (!globalParamValue)
|
|
1263
|
+
throw new RsError("SCHEMA_ERROR", `Invalid global keyword clause in route ${routeData.name}`);
|
|
1264
|
+
sqlParams.push(globalParamValue);
|
|
1265
|
+
});
|
|
1266
|
+
return value.replace(new RegExp(/#[a-zA-Z][a-zA-Z0-9_]+/g), "?");
|
|
1267
|
+
}
|
|
1268
|
+
return value;
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
// src/restura/sql/filterSqlParser.ts
|
|
1273
|
+
var import_pegjs = __toESM(require("pegjs"));
|
|
1274
|
+
var filterSqlGrammar = `
|
|
1275
|
+
start = expressionList
|
|
1276
|
+
|
|
1277
|
+
expressionList =
|
|
1278
|
+
leftExpression:expression operator:operator rightExpression:expressionList
|
|
1279
|
+
{ return \`\${leftExpression} \${operator} \${rightExpression}\`;}
|
|
1280
|
+
/ expression
|
|
1281
|
+
|
|
1282
|
+
expression =
|
|
1283
|
+
negate:negate?"(" "column:" column:column ","? value:value? ","? type:type? ")"
|
|
1284
|
+
{return \`\${negate? "!" : ""}(\${type? type(column, value) : \`\${column} = \${mysql.escape(value)}\`})\`;}
|
|
1285
|
+
/
|
|
1286
|
+
negate:negate?"("expression:expressionList")" { return \`\${negate? "!" : ""}(\${expression})\`; }
|
|
1287
|
+
|
|
1288
|
+
negate = "!"
|
|
1289
|
+
|
|
1290
|
+
operator = "and"i / "or"i
|
|
1291
|
+
|
|
1292
|
+
column = left:text "." right:text { return \`\${mysql.escapeId(left)}.\${mysql.escapeId(right)}\`; }
|
|
1293
|
+
/
|
|
1294
|
+
text:text { return mysql.escapeId(text); }
|
|
1295
|
+
|
|
1296
|
+
|
|
1297
|
+
text = text:[ a-z0-9-_:.@]i+ { return text.join("");}
|
|
1298
|
+
|
|
1299
|
+
type = "type:" type:typeString { return type; }
|
|
1300
|
+
typeString = text:"startsWith" { return function(column, value) { return \`\${column} LIKE '\${mysql.escape(value).slice(1,-1)}%'\`; } } /
|
|
1301
|
+
text:"endsWith" { return function(column, value) { return \`\${column} LIKE '%\${mysql.escape(value).slice(1,-1)}'\`; } } /
|
|
1302
|
+
text:"contains" { return function(column, value) { return \`\${column} LIKE '%\${mysql.escape(value).slice(1,-1)}%'\`; } } /
|
|
1303
|
+
text:"exact" { return function(column, value) { return \`\${column} = '\${mysql.escape(value).slice(1,-1)}'\`; } } /
|
|
1304
|
+
text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= '\${mysql.escape(value).slice(1,-1)}'\`; } } /
|
|
1305
|
+
text:"greaterThan" { return function(column, value) { return \`\${column} > '\${mysql.escape(value).slice(1,-1)}'\`; } } /
|
|
1306
|
+
text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${mysql.escape(value).slice(1,-1)}'\`; } } /
|
|
1307
|
+
text:"lessThan" { return function(column, value) { return \`\${column} < '\${mysql.escape(value).slice(1,-1)}'\`; } } /
|
|
1308
|
+
text:"isNull" { return function(column, value) { return \`isNull(\${column})\`; } }
|
|
1309
|
+
|
|
1310
|
+
value = "value:" value:text { return value; }
|
|
1311
|
+
|
|
1312
|
+
`;
|
|
1313
|
+
var filterSqlParser = import_pegjs.default.generate(filterSqlGrammar, {
|
|
1314
|
+
format: "commonjs"
|
|
1315
|
+
// dependencies: { mysql: 'mysql' } // todo: figure out a better way to escape values depending on the database type
|
|
1316
|
+
});
|
|
1317
|
+
var filterSqlParser_default = filterSqlParser;
|
|
1318
|
+
|
|
1319
|
+
// src/restura/sql/PsqlEngine.ts
|
|
1320
|
+
var import_core_utils5 = require("@redskytech/core-utils");
|
|
1321
|
+
|
|
1322
|
+
// src/restura/sql/PsqlUtils.ts
|
|
1323
|
+
var import_pg_format = __toESM(require("pg-format"));
|
|
1324
|
+
function escapeColumnName(columnName) {
|
|
1325
|
+
if (columnName === void 0) return "";
|
|
1326
|
+
return `"${columnName.replace(/"/g, "")}"`.replace(".", '"."');
|
|
1327
|
+
}
|
|
1328
|
+
function questionMarksToOrderedParams(query) {
|
|
1329
|
+
let count = 1;
|
|
1330
|
+
return query.replace(/'\?'/g, () => `$${count++}`);
|
|
1331
|
+
}
|
|
1332
|
+
function insertObjectQuery(table, obj) {
|
|
1333
|
+
const keys = Object.keys(obj);
|
|
1334
|
+
const params = Object.values(obj);
|
|
1335
|
+
const columns = keys.map((column) => escapeColumnName(column)).join(", ");
|
|
1336
|
+
const values = params.map((value) => SQL`${value}`).join(", ");
|
|
1337
|
+
const query = `INSERT INTO "${table}" (${columns})
|
|
1338
|
+
VALUES (${values})
|
|
1339
|
+
RETURNING *`;
|
|
1340
|
+
return query;
|
|
1341
|
+
}
|
|
1342
|
+
function updateObjectQuery(table, obj, whereStatement) {
|
|
1343
|
+
const setArray = [];
|
|
1344
|
+
for (const i in obj) {
|
|
1345
|
+
setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
|
|
1346
|
+
}
|
|
1347
|
+
return `UPDATE ${escapeColumnName(table)}
|
|
1348
|
+
SET ${setArray.join(", ")} ${whereStatement}
|
|
1349
|
+
RETURNING *`;
|
|
1350
|
+
}
|
|
1351
|
+
function isValueNumber2(value) {
|
|
1352
|
+
return !isNaN(Number(value));
|
|
1353
|
+
}
|
|
1354
|
+
function SQL(strings, ...values) {
|
|
1355
|
+
let query = strings[0];
|
|
1356
|
+
values.forEach((value, index) => {
|
|
1357
|
+
if (isValueNumber2(value)) {
|
|
1358
|
+
query += value;
|
|
1359
|
+
} else {
|
|
1360
|
+
query += import_pg_format.default.literal(value);
|
|
1361
|
+
}
|
|
1362
|
+
query += strings[index + 1];
|
|
1363
|
+
});
|
|
1364
|
+
return query;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// src/restura/sql/PsqlEngine.ts
|
|
1368
|
+
var PsqlEngine = class extends SqlEngine {
|
|
1369
|
+
constructor(psqlConnectionPool) {
|
|
1370
|
+
super();
|
|
1371
|
+
this.psqlConnectionPool = psqlConnectionPool;
|
|
1372
|
+
}
|
|
1373
|
+
async diffDatabaseToSchema(schema) {
|
|
1374
|
+
console.log(schema);
|
|
1375
|
+
return Promise.resolve("");
|
|
1376
|
+
}
|
|
1377
|
+
generateDatabaseSchemaFromSchema(schema) {
|
|
1378
|
+
console.log(schema);
|
|
1379
|
+
return "";
|
|
1380
|
+
}
|
|
1381
|
+
createNestedSelect(req, schema, item, routeData, userRole, sqlParams) {
|
|
1382
|
+
console.log(req, schema, item, routeData, userRole, sqlParams);
|
|
1383
|
+
return "";
|
|
1384
|
+
}
|
|
1385
|
+
async executeCreateRequest(req, routeData, schema) {
|
|
1386
|
+
const sqlParams = [];
|
|
1387
|
+
const parameterObj = {};
|
|
1388
|
+
(routeData.assignments || []).forEach((assignment) => {
|
|
1389
|
+
parameterObj[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
|
|
1390
|
+
});
|
|
1391
|
+
const query = insertObjectQuery(routeData.table, __spreadValues(__spreadValues({}, req.data), parameterObj));
|
|
1392
|
+
const createdItem = await this.psqlConnectionPool.queryOne(query, sqlParams);
|
|
1393
|
+
const insertId = createdItem == null ? void 0 : createdItem.id;
|
|
1394
|
+
const whereData = [
|
|
1395
|
+
{
|
|
1396
|
+
tableName: routeData.table,
|
|
1397
|
+
value: insertId,
|
|
1398
|
+
columnName: "id",
|
|
1399
|
+
operator: "="
|
|
1400
|
+
}
|
|
1401
|
+
];
|
|
1402
|
+
req.data = { id: insertId };
|
|
1403
|
+
return this.executeGetRequest(req, __spreadProps(__spreadValues({}, routeData), { where: whereData }), schema);
|
|
1404
|
+
}
|
|
1405
|
+
async executeGetRequest(req, routeData, schema) {
|
|
1406
|
+
const DEFAULT_PAGED_PAGE_NUMBER = 0;
|
|
1407
|
+
const DEFAULT_PAGED_PER_PAGE_NUMBER = 25;
|
|
1408
|
+
const sqlParams = [];
|
|
1409
|
+
const userRole = req.requesterDetails.role;
|
|
1410
|
+
let sqlStatement = "";
|
|
1411
|
+
const selectColumns = [];
|
|
1412
|
+
routeData.response.forEach((item) => {
|
|
1413
|
+
if (item.subquery || this.doesRoleHavePermissionToColumn(userRole, schema, item, routeData.joins))
|
|
1414
|
+
selectColumns.push(item);
|
|
1415
|
+
});
|
|
1416
|
+
if (!selectColumns.length) throw new RsError("UNAUTHORIZED", `You do not have permission to access this data.`);
|
|
1417
|
+
let selectStatement = "SELECT \n";
|
|
1418
|
+
selectStatement += ` ${selectColumns.map((item) => {
|
|
1419
|
+
if (item.subquery) {
|
|
1420
|
+
return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${item.name}`;
|
|
1421
|
+
}
|
|
1422
|
+
return `${escapeColumnName(item.selector)} AS ${escapeColumnName(item.name)}`;
|
|
1423
|
+
}).join(",\n ")}
|
|
1424
|
+
`;
|
|
1425
|
+
sqlStatement += `FROM "${routeData.table}"
|
|
1426
|
+
`;
|
|
1427
|
+
sqlStatement += this.generateJoinStatements(
|
|
1428
|
+
req,
|
|
1429
|
+
routeData.joins,
|
|
1430
|
+
routeData.table,
|
|
1431
|
+
routeData,
|
|
1432
|
+
schema,
|
|
1433
|
+
userRole,
|
|
1434
|
+
sqlParams
|
|
1435
|
+
);
|
|
1436
|
+
sqlStatement += this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
1437
|
+
let groupByOrderByStatement = this.generateGroupBy(routeData);
|
|
1438
|
+
groupByOrderByStatement += this.generateOrderBy(req, routeData);
|
|
1439
|
+
if (routeData.type === "ONE") {
|
|
1440
|
+
return this.psqlConnectionPool.queryOne(
|
|
1441
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
|
|
1442
|
+
sqlParams
|
|
1443
|
+
);
|
|
1444
|
+
} else if (routeData.type === "ARRAY") {
|
|
1445
|
+
return this.psqlConnectionPool.runQuery(
|
|
1446
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
|
|
1447
|
+
sqlParams
|
|
1448
|
+
);
|
|
1449
|
+
} else if (routeData.type === "PAGED") {
|
|
1450
|
+
const data = req.data;
|
|
1451
|
+
const pageResults = await this.psqlConnectionPool.runQuery(
|
|
1452
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement} LIMIT ? OFFSET ?;SELECT COUNT(${routeData.groupBy ? `DISTINCT ${routeData.groupBy.tableName}.${routeData.groupBy.columnName}` : "*"}) AS total
|
|
1453
|
+
${sqlStatement};`,
|
|
1454
|
+
[
|
|
1455
|
+
...sqlParams,
|
|
1456
|
+
data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER,
|
|
1457
|
+
(data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER,
|
|
1458
|
+
...sqlParams
|
|
1459
|
+
]
|
|
1460
|
+
);
|
|
1461
|
+
let total = 0;
|
|
1462
|
+
if (import_core_utils5.ObjectUtils.isArrayWithData(pageResults)) {
|
|
1463
|
+
total = pageResults[1][0].total;
|
|
1464
|
+
}
|
|
1465
|
+
return { data: pageResults[0], total };
|
|
1466
|
+
} else {
|
|
1467
|
+
throw new RsError("UNKNOWN_ERROR", "Unknown route type.");
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
async executeUpdateRequest(req, routeData, schema) {
|
|
1471
|
+
const sqlParams = [];
|
|
1472
|
+
const _a = req.body, { id } = _a, bodyNoId = __objRest(_a, ["id"]);
|
|
1473
|
+
const table = schema.database.find((item) => {
|
|
1474
|
+
return item.name === routeData.table;
|
|
1475
|
+
});
|
|
1476
|
+
if (!table) throw new RsError("UNKNOWN_ERROR", "Unknown table.");
|
|
1477
|
+
if (table.columns.find((column) => column.name === "modifiedOn")) {
|
|
1478
|
+
bodyNoId.modifiedOn = (/* @__PURE__ */ new Date()).toISOString();
|
|
1479
|
+
}
|
|
1480
|
+
for (const assignment of routeData.assignments) {
|
|
1481
|
+
const column = table.columns.find((column2) => column2.name === assignment.name);
|
|
1482
|
+
if (!column) continue;
|
|
1483
|
+
const assignmentWithPrefix = escapeColumnName(`${routeData.table}.${assignment.name}`);
|
|
1484
|
+
if (SqlUtils.convertDatabaseTypeToTypescript(column.type) === "number")
|
|
1485
|
+
bodyNoId[assignmentWithPrefix] = Number(assignment.value);
|
|
1486
|
+
else bodyNoId[assignmentWithPrefix] = assignment.value;
|
|
1487
|
+
}
|
|
1488
|
+
const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
1489
|
+
const query = updateObjectQuery(routeData.table, bodyNoId, whereClause);
|
|
1490
|
+
await this.psqlConnectionPool.queryOne(query, [...sqlParams]);
|
|
1491
|
+
return this.executeGetRequest(req, routeData, schema);
|
|
1492
|
+
}
|
|
1493
|
+
async executeDeleteRequest(req, routeData, schema) {
|
|
1494
|
+
const sqlParams = [];
|
|
1495
|
+
const joinStatement = this.generateJoinStatements(
|
|
1496
|
+
req,
|
|
1497
|
+
routeData.joins,
|
|
1498
|
+
routeData.table,
|
|
1499
|
+
routeData,
|
|
1500
|
+
schema,
|
|
1501
|
+
req.requesterDetails.role,
|
|
1502
|
+
sqlParams
|
|
1503
|
+
);
|
|
1504
|
+
let deleteStatement = `DELETE
|
|
1505
|
+
FROM "${routeData.table}" ${joinStatement}`;
|
|
1506
|
+
deleteStatement += this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
1507
|
+
deleteStatement += ";";
|
|
1508
|
+
await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams);
|
|
1509
|
+
return true;
|
|
1510
|
+
}
|
|
1511
|
+
generateJoinStatements(req, joins, baseTable, routeData, schema, userRole, sqlParams) {
|
|
1512
|
+
console.log(req, joins, baseTable, routeData, schema, userRole, sqlParams);
|
|
1513
|
+
return "";
|
|
1514
|
+
}
|
|
1515
|
+
getTableSchema(schema, tableName) {
|
|
1516
|
+
console.log(schema, tableName);
|
|
1517
|
+
return {};
|
|
1518
|
+
}
|
|
1519
|
+
generateGroupBy(routeData) {
|
|
1520
|
+
let groupBy = "";
|
|
1521
|
+
if (routeData.groupBy) {
|
|
1522
|
+
groupBy = `GROUP BY ${escapeColumnName(routeData.groupBy.tableName)}.${escapeColumnName(routeData.groupBy.columnName)}
|
|
1523
|
+
`;
|
|
1524
|
+
}
|
|
1525
|
+
return groupBy;
|
|
1526
|
+
}
|
|
1527
|
+
generateOrderBy(req, routeData) {
|
|
1528
|
+
let orderBy = "";
|
|
1529
|
+
const orderOptions = {
|
|
1530
|
+
ASC: "ASC",
|
|
1531
|
+
DESC: "DESC"
|
|
1532
|
+
};
|
|
1533
|
+
const data = req.data;
|
|
1534
|
+
if (routeData.type === "PAGED" && "sortBy" in data) {
|
|
1535
|
+
const sortOrder = orderOptions[data.sortOrder] || "ASC";
|
|
1536
|
+
orderBy = `ORDER BY ${escapeColumnName(data.sortBy)} ${sortOrder}
|
|
1537
|
+
`;
|
|
1538
|
+
} else if (routeData.orderBy) {
|
|
1539
|
+
const sortOrder = orderOptions[routeData.orderBy.order] || "ASC";
|
|
1540
|
+
orderBy = `ORDER BY ${escapeColumnName(routeData.orderBy.tableName)}.${escapeColumnName(routeData.orderBy.columnName)} ${sortOrder}
|
|
1541
|
+
`;
|
|
1542
|
+
}
|
|
1543
|
+
return orderBy;
|
|
1544
|
+
}
|
|
1545
|
+
generateWhereClause(req, where, routeData, sqlParams) {
|
|
1546
|
+
let whereClause = "";
|
|
1547
|
+
where.forEach((item, index) => {
|
|
1548
|
+
if (index === 0) whereClause = "WHERE ";
|
|
1549
|
+
if (item.custom) {
|
|
1550
|
+
whereClause += this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
if (item.operator === void 0 || item.value === void 0 || item.columnName === void 0 || item.tableName === void 0)
|
|
1554
|
+
throw new RsError(
|
|
1555
|
+
"SCHEMA_ERROR",
|
|
1556
|
+
`Invalid where clause in route ${routeData.name}, missing required fields if not custom`
|
|
1557
|
+
);
|
|
1558
|
+
let operator = item.operator;
|
|
1559
|
+
if (operator === "LIKE") {
|
|
1560
|
+
sqlParams[sqlParams.length - 1] = `%${sqlParams[sqlParams.length - 1]}%`;
|
|
1561
|
+
} else if (operator === "STARTS WITH") {
|
|
1562
|
+
operator = "LIKE";
|
|
1563
|
+
sqlParams[sqlParams.length - 1] = `${sqlParams[sqlParams.length - 1]}%`;
|
|
1564
|
+
} else if (operator === "ENDS WITH") {
|
|
1565
|
+
operator = "LIKE";
|
|
1566
|
+
sqlParams[sqlParams.length - 1] = `%${sqlParams[sqlParams.length - 1]}`;
|
|
1567
|
+
}
|
|
1568
|
+
const replacedValue = this.replaceParamKeywords(item.value, routeData, req, sqlParams);
|
|
1569
|
+
const escapedValue = SQL`${replacedValue}`;
|
|
1570
|
+
whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator} ${["IN", "NOT IN"].includes(operator) ? `(${escapedValue})` : escapedValue}
|
|
1571
|
+
`;
|
|
1572
|
+
});
|
|
1573
|
+
const data = req.data;
|
|
1574
|
+
if (routeData.type === "PAGED" && !!(data == null ? void 0 : data.filter)) {
|
|
1575
|
+
let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
1576
|
+
const requestParam = routeData.request.find((item) => {
|
|
1577
|
+
return item.name === value.replace("$", "");
|
|
1578
|
+
});
|
|
1579
|
+
if (!requestParam)
|
|
1580
|
+
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1581
|
+
return data[requestParam.name];
|
|
1582
|
+
});
|
|
1583
|
+
statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
1584
|
+
const requestParam = routeData.request.find((item) => {
|
|
1585
|
+
return item.name === value.replace("#", "");
|
|
1586
|
+
});
|
|
1587
|
+
if (!requestParam)
|
|
1588
|
+
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1589
|
+
return data[requestParam.name];
|
|
1590
|
+
});
|
|
1591
|
+
statement = filterSqlParser_default.parse(statement);
|
|
1592
|
+
if (whereClause.startsWith("WHERE")) {
|
|
1593
|
+
whereClause += ` AND (${statement})
|
|
1594
|
+
`;
|
|
1595
|
+
} else {
|
|
1596
|
+
whereClause += `WHERE ${statement}
|
|
1597
|
+
`;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
return whereClause;
|
|
1601
|
+
}
|
|
1602
|
+
};
|
|
1603
|
+
|
|
1604
|
+
// src/restura/restura.ts
|
|
1605
|
+
var import_pg2 = require("pg");
|
|
1606
|
+
|
|
1607
|
+
// src/restura/sql/PsqlPool.ts
|
|
1608
|
+
var import_pg = require("pg");
|
|
1609
|
+
var PsqlPool = class {
|
|
1610
|
+
constructor(poolConfig) {
|
|
1611
|
+
this.poolConfig = poolConfig;
|
|
1612
|
+
this.pool = new import_pg.Pool(poolConfig);
|
|
1613
|
+
}
|
|
1614
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1615
|
+
async queryOne(query, options) {
|
|
1616
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1617
|
+
try {
|
|
1618
|
+
const response = await this.pool.query(formattedQuery, options);
|
|
1619
|
+
return response.rows[0];
|
|
1620
|
+
} catch (error) {
|
|
1621
|
+
console.error(error, query, options);
|
|
1622
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1623
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1624
|
+
}
|
|
1625
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1629
|
+
async runQuery(query, options) {
|
|
1630
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1631
|
+
const queryUpdated = query.replace(/[\t\n]/g, " ");
|
|
1632
|
+
console.log(queryUpdated, options);
|
|
1633
|
+
try {
|
|
1634
|
+
const response = await this.pool.query(formattedQuery, options);
|
|
1635
|
+
return response.rows;
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
console.error(error, query, options);
|
|
1638
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1639
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1640
|
+
}
|
|
1641
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
|
|
967
1646
|
// src/restura/restura.ts
|
|
968
1647
|
var ResturaEngine = class {
|
|
969
1648
|
constructor() {
|
|
@@ -975,14 +1654,16 @@ var ResturaEngine = class {
|
|
|
975
1654
|
DELETE: []
|
|
976
1655
|
};
|
|
977
1656
|
}
|
|
978
|
-
// private customTypeValidation!: ValidationDictionary;
|
|
979
1657
|
/**
|
|
980
1658
|
* Initializes the Restura engine with the provided Express application.
|
|
981
1659
|
*
|
|
982
1660
|
* @param app - The Express application instance to initialize with Restura.
|
|
983
1661
|
* @returns A promise that resolves when the initialization is complete.
|
|
984
1662
|
*/
|
|
985
|
-
async init(app, authenticationHandler) {
|
|
1663
|
+
async init(app, authenticationHandler, psqlConnectionPool) {
|
|
1664
|
+
this.psqlConnectionPool = psqlConnectionPool;
|
|
1665
|
+
this.psqlEngine = new PsqlEngine(this.psqlConnectionPool);
|
|
1666
|
+
setupPgReturnTypes();
|
|
986
1667
|
this.resturaConfig = import_internal2.config.validate("restura", resturaConfigSchema);
|
|
987
1668
|
this.authenticationHandler = authenticationHandler;
|
|
988
1669
|
app.use((0, import_compression.default)());
|
|
@@ -991,7 +1672,7 @@ var ResturaEngine = class {
|
|
|
991
1672
|
app.use((0, import_cookie_parser.default)());
|
|
992
1673
|
app.disable("x-powered-by");
|
|
993
1674
|
app.use("/", addApiResponseFunctions);
|
|
994
|
-
app.use("/api/", authenticateUser);
|
|
1675
|
+
app.use("/api/", authenticateUser(this.authenticationHandler));
|
|
995
1676
|
app.use("/restura", this.resturaAuthentication);
|
|
996
1677
|
app.put(
|
|
997
1678
|
"/restura/v1/schema",
|
|
@@ -1007,7 +1688,7 @@ var ResturaEngine = class {
|
|
|
1007
1688
|
app.get("/restura/v1/schema/types", this.getSchemaAndTypes);
|
|
1008
1689
|
this.expressApp = app;
|
|
1009
1690
|
await this.reloadEndpoints();
|
|
1010
|
-
this.validateGeneratedTypesFolder();
|
|
1691
|
+
await this.validateGeneratedTypesFolder();
|
|
1011
1692
|
logger.info("Restura Engine Initialized");
|
|
1012
1693
|
}
|
|
1013
1694
|
/**
|
|
@@ -1046,7 +1727,7 @@ var ResturaEngine = class {
|
|
|
1046
1727
|
* @returns A promise that resolves when the API has been successfully generated and written to the output file.
|
|
1047
1728
|
*/
|
|
1048
1729
|
async generateApiFromSchema(outputFile, providedSchema) {
|
|
1049
|
-
|
|
1730
|
+
import_fs2.default.writeFileSync(
|
|
1050
1731
|
outputFile,
|
|
1051
1732
|
await apiGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
|
|
1052
1733
|
);
|
|
@@ -1059,7 +1740,7 @@ var ResturaEngine = class {
|
|
|
1059
1740
|
* @returns A promise that resolves when the model has been successfully written to the output file.
|
|
1060
1741
|
*/
|
|
1061
1742
|
async generateModelFromSchema(outputFile, providedSchema) {
|
|
1062
|
-
|
|
1743
|
+
import_fs2.default.writeFileSync(
|
|
1063
1744
|
outputFile,
|
|
1064
1745
|
await modelGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
|
|
1065
1746
|
);
|
|
@@ -1071,12 +1752,12 @@ var ResturaEngine = class {
|
|
|
1071
1752
|
* @throws {Error} If the schema file is missing or the schema is not valid.
|
|
1072
1753
|
*/
|
|
1073
1754
|
async getLatestFileSystemSchema() {
|
|
1074
|
-
if (!
|
|
1755
|
+
if (!import_fs2.default.existsSync(this.resturaConfig.schemaFilePath)) {
|
|
1075
1756
|
logger.error(`Missing restura schema file, expected path: ${this.resturaConfig.schemaFilePath}`);
|
|
1076
1757
|
throw new Error("Missing restura schema file");
|
|
1077
1758
|
}
|
|
1078
|
-
const schemaFileData =
|
|
1079
|
-
const schema =
|
|
1759
|
+
const schemaFileData = import_fs2.default.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
|
|
1760
|
+
const schema = import_core_utils6.ObjectUtils.safeParse(schemaFileData);
|
|
1080
1761
|
const isValid = await isSchemaValid(schema);
|
|
1081
1762
|
if (!isValid) {
|
|
1082
1763
|
logger.error("Schema is not valid");
|
|
@@ -1096,9 +1777,9 @@ var ResturaEngine = class {
|
|
|
1096
1777
|
async getHashes(providedSchema) {
|
|
1097
1778
|
var _a, _b, _c, _d;
|
|
1098
1779
|
const schemaHash = await this.generateHashForSchema(providedSchema);
|
|
1099
|
-
const apiFile =
|
|
1780
|
+
const apiFile = import_fs2.default.readFileSync(import_path2.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
|
|
1100
1781
|
const apiCreatedSchemaHash = (_b = (_a = apiFile.toString().match(/\((.*)\)/)) == null ? void 0 : _a[1]) != null ? _b : "";
|
|
1101
|
-
const modelFile =
|
|
1782
|
+
const modelFile = import_fs2.default.readFileSync(import_path2.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
|
|
1102
1783
|
const modelCreatedSchemaHash = (_d = (_c = modelFile.toString().match(/\((.*)\)/)) == null ? void 0 : _c[1]) != null ? _d : "";
|
|
1103
1784
|
return {
|
|
1104
1785
|
schemaHash,
|
|
@@ -1108,6 +1789,7 @@ var ResturaEngine = class {
|
|
|
1108
1789
|
}
|
|
1109
1790
|
async reloadEndpoints() {
|
|
1110
1791
|
this.schema = await this.getLatestFileSystemSchema();
|
|
1792
|
+
this.customTypeValidation = customTypeValidationGenerator(this.schema);
|
|
1111
1793
|
this.resturaRouter = express.Router();
|
|
1112
1794
|
this.resetPublicEndpoints();
|
|
1113
1795
|
let routeCount = 0;
|
|
@@ -1133,27 +1815,27 @@ var ResturaEngine = class {
|
|
|
1133
1815
|
logger.info(`Restura loaded (${routeCount}) endpoint${routeCount > 1 ? "s" : ""}`);
|
|
1134
1816
|
}
|
|
1135
1817
|
async validateGeneratedTypesFolder() {
|
|
1136
|
-
if (!
|
|
1137
|
-
|
|
1818
|
+
if (!import_fs2.default.existsSync(this.resturaConfig.generatedTypesPath)) {
|
|
1819
|
+
import_fs2.default.mkdirSync(this.resturaConfig.generatedTypesPath, { recursive: true });
|
|
1138
1820
|
}
|
|
1139
|
-
const hasApiFile =
|
|
1140
|
-
const hasModelsFile =
|
|
1821
|
+
const hasApiFile = import_fs2.default.existsSync(import_path2.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
|
|
1822
|
+
const hasModelsFile = import_fs2.default.existsSync(import_path2.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
|
|
1141
1823
|
if (!hasApiFile) {
|
|
1142
|
-
await this.generateApiFromSchema(
|
|
1824
|
+
await this.generateApiFromSchema(import_path2.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1143
1825
|
}
|
|
1144
1826
|
if (!hasModelsFile) {
|
|
1145
1827
|
await this.generateModelFromSchema(
|
|
1146
|
-
|
|
1828
|
+
import_path2.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1147
1829
|
this.schema
|
|
1148
1830
|
);
|
|
1149
1831
|
}
|
|
1150
1832
|
const hashes = await this.getHashes(this.schema);
|
|
1151
1833
|
if (hashes.schemaHash !== hashes.apiCreatedSchemaHash) {
|
|
1152
|
-
await this.generateApiFromSchema(
|
|
1834
|
+
await this.generateApiFromSchema(import_path2.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1153
1835
|
}
|
|
1154
1836
|
if (hashes.schemaHash !== hashes.modelCreatedSchemaHash) {
|
|
1155
1837
|
await this.generateModelFromSchema(
|
|
1156
|
-
|
|
1838
|
+
import_path2.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1157
1839
|
this.schema
|
|
1158
1840
|
);
|
|
1159
1841
|
}
|
|
@@ -1183,9 +1865,9 @@ var ResturaEngine = class {
|
|
|
1183
1865
|
}
|
|
1184
1866
|
}
|
|
1185
1867
|
async updateTypes() {
|
|
1186
|
-
await this.generateApiFromSchema(
|
|
1868
|
+
await this.generateApiFromSchema(import_path2.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1187
1869
|
await this.generateModelFromSchema(
|
|
1188
|
-
|
|
1870
|
+
import_path2.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1189
1871
|
this.schema
|
|
1190
1872
|
);
|
|
1191
1873
|
}
|
|
@@ -1207,6 +1889,14 @@ var ResturaEngine = class {
|
|
|
1207
1889
|
try {
|
|
1208
1890
|
const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
|
|
1209
1891
|
this.validateAuthorization(req, routeData);
|
|
1892
|
+
validateRequestParams(req, routeData, this.customTypeValidation);
|
|
1893
|
+
const data = await this.psqlEngine.runQueryForRoute(
|
|
1894
|
+
req,
|
|
1895
|
+
routeData,
|
|
1896
|
+
this.schema
|
|
1897
|
+
);
|
|
1898
|
+
if (routeData.type === "PAGED") res.sendNoWrap(data);
|
|
1899
|
+
else res.sendData(data);
|
|
1210
1900
|
} catch (e) {
|
|
1211
1901
|
next(e);
|
|
1212
1902
|
}
|
|
@@ -1266,7 +1956,7 @@ var ResturaEngine = class {
|
|
|
1266
1956
|
printWidth: 120,
|
|
1267
1957
|
singleQuote: true
|
|
1268
1958
|
}));
|
|
1269
|
-
|
|
1959
|
+
import_fs2.default.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
|
|
1270
1960
|
}
|
|
1271
1961
|
resetPublicEndpoints() {
|
|
1272
1962
|
this.publicEndpoints = {
|
|
@@ -1283,13 +1973,13 @@ var ResturaEngine = class {
|
|
|
1283
1973
|
if (!routeData.roles.includes(role))
|
|
1284
1974
|
throw new RsError("UNAUTHORIZED", "Not authorized to access this endpoint");
|
|
1285
1975
|
}
|
|
1286
|
-
getRouteData(method, baseUrl,
|
|
1976
|
+
getRouteData(method, baseUrl, path3) {
|
|
1287
1977
|
const endpoint = this.schema.endpoints.find((item) => {
|
|
1288
1978
|
return item.baseUrl === baseUrl;
|
|
1289
1979
|
});
|
|
1290
1980
|
if (!endpoint) throw new RsError("NOT_FOUND", "Route not found");
|
|
1291
1981
|
const route = endpoint.routes.find((item) => {
|
|
1292
|
-
return item.method === method && item.path ===
|
|
1982
|
+
return item.method === method && item.path === path3;
|
|
1293
1983
|
});
|
|
1294
1984
|
if (!route) throw new RsError("NOT_FOUND", "Route not found");
|
|
1295
1985
|
return route;
|
|
@@ -1316,9 +2006,21 @@ __decorateClass([
|
|
|
1316
2006
|
__decorateClass([
|
|
1317
2007
|
boundMethod
|
|
1318
2008
|
], ResturaEngine.prototype, "isCustomRoute", 1);
|
|
2009
|
+
var setupPgReturnTypes = () => {
|
|
2010
|
+
const TIMESTAMPTZ_OID = 1184;
|
|
2011
|
+
import_pg2.types.setTypeParser(TIMESTAMPTZ_OID, (val) => {
|
|
2012
|
+
return val === null ? null : new Date(val).toISOString();
|
|
2013
|
+
});
|
|
2014
|
+
const BIGINT_OID = 20;
|
|
2015
|
+
import_pg2.types.setTypeParser(BIGINT_OID, (val) => {
|
|
2016
|
+
return val === null ? null : Number(val);
|
|
2017
|
+
});
|
|
2018
|
+
};
|
|
2019
|
+
setupPgReturnTypes();
|
|
1319
2020
|
var restura = new ResturaEngine();
|
|
1320
2021
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1321
2022
|
0 && (module.exports = {
|
|
2023
|
+
PsqlPool,
|
|
1322
2024
|
logger,
|
|
1323
2025
|
restura
|
|
1324
2026
|
});
|