@restura/core 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +14 -2
- package/dist/index.js +321 -255
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -878,7 +878,7 @@ declare namespace Restura {
|
|
|
878
878
|
}
|
|
879
879
|
|
|
880
880
|
// src/restura/restura.ts
|
|
881
|
-
import { ObjectUtils as
|
|
881
|
+
import { ObjectUtils as ObjectUtils4, StringUtils as StringUtils3 } from "@redskytech/core-utils";
|
|
882
882
|
import { config as config2 } from "@restura/internal";
|
|
883
883
|
|
|
884
884
|
// ../../node_modules/.pnpm/autobind-decorator@2.4.0/node_modules/autobind-decorator/lib/esm/index.js
|
|
@@ -1093,7 +1093,120 @@ import fs2 from "fs";
|
|
|
1093
1093
|
import path2, { resolve } from "path";
|
|
1094
1094
|
import tmp from "tmp";
|
|
1095
1095
|
import * as TJS from "typescript-json-schema";
|
|
1096
|
-
|
|
1096
|
+
|
|
1097
|
+
// src/restura/generators/schemaGeneratorUtils.ts
|
|
1098
|
+
function buildRouteSchema(requestParams) {
|
|
1099
|
+
const properties = {};
|
|
1100
|
+
const required = [];
|
|
1101
|
+
for (const param of requestParams) {
|
|
1102
|
+
if (param.required) {
|
|
1103
|
+
required.push(param.name);
|
|
1104
|
+
}
|
|
1105
|
+
const propertySchema = buildPropertySchemaFromRequest(param);
|
|
1106
|
+
properties[param.name] = propertySchema;
|
|
1107
|
+
}
|
|
1108
|
+
return {
|
|
1109
|
+
type: "object",
|
|
1110
|
+
properties,
|
|
1111
|
+
...required.length > 0 && { required },
|
|
1112
|
+
// Only include if not empty
|
|
1113
|
+
additionalProperties: false
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
function buildPropertySchemaFromRequest(param) {
|
|
1117
|
+
const propertySchema = {};
|
|
1118
|
+
const typeCheckValidator = param.validator.find((v) => v.type === "TYPE_CHECK");
|
|
1119
|
+
const oneOfValidator = param.validator.find((v) => v.type === "ONE_OF");
|
|
1120
|
+
if (oneOfValidator && Array.isArray(oneOfValidator.value)) {
|
|
1121
|
+
propertySchema.enum = oneOfValidator.value;
|
|
1122
|
+
if (!typeCheckValidator && oneOfValidator.value.length > 0) {
|
|
1123
|
+
const firstValue = oneOfValidator.value[0];
|
|
1124
|
+
propertySchema.type = typeof firstValue === "number" ? "number" : "string";
|
|
1125
|
+
return propertySchema;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (!typeCheckValidator) {
|
|
1129
|
+
return propertySchema;
|
|
1130
|
+
}
|
|
1131
|
+
const typeValue = typeCheckValidator.value;
|
|
1132
|
+
if (typeof typeValue === "string" && typeValue.endsWith("[]")) {
|
|
1133
|
+
const itemType = typeValue.replace("[]", "");
|
|
1134
|
+
if (param.isNullable) {
|
|
1135
|
+
propertySchema.type = ["array", "null"];
|
|
1136
|
+
} else {
|
|
1137
|
+
propertySchema.type = "array";
|
|
1138
|
+
}
|
|
1139
|
+
propertySchema.items = {
|
|
1140
|
+
type: mapTypeToJsonSchemaType(itemType)
|
|
1141
|
+
};
|
|
1142
|
+
applyArrayValidators(propertySchema, param);
|
|
1143
|
+
} else {
|
|
1144
|
+
if (param.isNullable) {
|
|
1145
|
+
propertySchema.type = [mapTypeToJsonSchemaType(typeValue), "null"];
|
|
1146
|
+
} else {
|
|
1147
|
+
propertySchema.type = mapTypeToJsonSchemaType(typeValue);
|
|
1148
|
+
}
|
|
1149
|
+
const type = propertySchema.type;
|
|
1150
|
+
const isNumericType = type === "number" || type === "integer" || Array.isArray(type) && (type.includes("number") || type.includes("integer"));
|
|
1151
|
+
const isStringType = type === "string" || Array.isArray(type) && type.includes("string");
|
|
1152
|
+
if (isNumericType) {
|
|
1153
|
+
applyNumericValidators(propertySchema, param);
|
|
1154
|
+
} else if (isStringType) {
|
|
1155
|
+
applyStringValidators(propertySchema, param);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return propertySchema;
|
|
1159
|
+
}
|
|
1160
|
+
function mapTypeToJsonSchemaType(type) {
|
|
1161
|
+
if (typeof type !== "string") {
|
|
1162
|
+
throw new Error(`Invalid type for JSON Schema mapping: ${type}`);
|
|
1163
|
+
}
|
|
1164
|
+
switch (type) {
|
|
1165
|
+
case "number":
|
|
1166
|
+
return "number";
|
|
1167
|
+
case "string":
|
|
1168
|
+
return "string";
|
|
1169
|
+
case "boolean":
|
|
1170
|
+
return "boolean";
|
|
1171
|
+
case "object":
|
|
1172
|
+
return "object";
|
|
1173
|
+
default:
|
|
1174
|
+
throw new Error(`Unknown type: ${type}`);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
function applyNumericValidators(propertySchema, param) {
|
|
1178
|
+
for (const validator of param.validator) {
|
|
1179
|
+
if (validator.type === "MIN" && typeof validator.value === "number") {
|
|
1180
|
+
propertySchema.minimum = validator.value;
|
|
1181
|
+
}
|
|
1182
|
+
if (validator.type === "MAX" && typeof validator.value === "number") {
|
|
1183
|
+
propertySchema.maximum = validator.value;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
function applyStringValidators(propertySchema, param) {
|
|
1188
|
+
for (const validator of param.validator) {
|
|
1189
|
+
if (validator.type === "MIN" && typeof validator.value === "number") {
|
|
1190
|
+
propertySchema.minLength = validator.value;
|
|
1191
|
+
}
|
|
1192
|
+
if (validator.type === "MAX" && typeof validator.value === "number") {
|
|
1193
|
+
propertySchema.maxLength = validator.value;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
function applyArrayValidators(propertySchema, param) {
|
|
1198
|
+
for (const validator of param.validator) {
|
|
1199
|
+
if (validator.type === "MIN" && typeof validator.value === "number") {
|
|
1200
|
+
propertySchema.minItems = validator.value;
|
|
1201
|
+
}
|
|
1202
|
+
if (validator.type === "MAX" && typeof validator.value === "number") {
|
|
1203
|
+
propertySchema.maxItems = validator.value;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/restura/generators/customTypeValidationGenerator.ts
|
|
1209
|
+
function customTypeValidationGenerator(currentSchema, ignoreGeneratedTypes = false) {
|
|
1097
1210
|
const schemaObject = {};
|
|
1098
1211
|
const customInterfaceNames = currentSchema.customTypes.map((customType) => {
|
|
1099
1212
|
const matches = customType.match(/(?<=interface\s)(\w+)|(?<=type\s)(\w+)/g);
|
|
@@ -1111,9 +1224,11 @@ function customTypeValidationGenerator(currentSchema) {
|
|
|
1111
1224
|
const program = TJS.getProgramFromFiles(
|
|
1112
1225
|
[
|
|
1113
1226
|
resolve(temporaryFile.name),
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1227
|
+
...ignoreGeneratedTypes ? [] : [
|
|
1228
|
+
path2.join(restura.resturaConfig.generatedTypesPath, "restura.d.ts"),
|
|
1229
|
+
path2.join(restura.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1230
|
+
path2.join(restura.resturaConfig.generatedTypesPath, "api.d.ts")
|
|
1231
|
+
]
|
|
1117
1232
|
],
|
|
1118
1233
|
compilerOptions
|
|
1119
1234
|
);
|
|
@@ -1124,6 +1239,35 @@ function customTypeValidationGenerator(currentSchema) {
|
|
|
1124
1239
|
schemaObject[item] = ddlSchema || {};
|
|
1125
1240
|
});
|
|
1126
1241
|
temporaryFile.removeCallback();
|
|
1242
|
+
for (const endpoint of currentSchema.endpoints) {
|
|
1243
|
+
for (const route of endpoint.routes) {
|
|
1244
|
+
if (route.type !== "CUSTOM_ONE" && route.type !== "CUSTOM_ARRAY" && route.type !== "CUSTOM_PAGED") continue;
|
|
1245
|
+
if (!route.request || !Array.isArray(route.request)) continue;
|
|
1246
|
+
const routeKey = `${route.method}:${route.path}`;
|
|
1247
|
+
schemaObject[routeKey] = buildRouteSchema(route.request);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return schemaObject;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// src/restura/generators/standardTypeValidationGenerator.ts
|
|
1254
|
+
function standardTypeValidationGenerator(currentSchema) {
|
|
1255
|
+
const schemaObject = {};
|
|
1256
|
+
for (const endpoint of currentSchema.endpoints) {
|
|
1257
|
+
for (const route of endpoint.routes) {
|
|
1258
|
+
if (route.type !== "ONE" && route.type !== "ARRAY" && route.type !== "PAGED") continue;
|
|
1259
|
+
const routeKey = `${route.method}:${route.path}`;
|
|
1260
|
+
if (!route.request || route.request.length === 0) {
|
|
1261
|
+
schemaObject[routeKey] = {
|
|
1262
|
+
type: "object",
|
|
1263
|
+
properties: {},
|
|
1264
|
+
additionalProperties: false
|
|
1265
|
+
};
|
|
1266
|
+
} else {
|
|
1267
|
+
schemaObject[routeKey] = buildRouteSchema(route.request);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1127
1271
|
return schemaObject;
|
|
1128
1272
|
}
|
|
1129
1273
|
|
|
@@ -1496,174 +1640,41 @@ async function isSchemaValid(schemaToCheck) {
|
|
|
1496
1640
|
}
|
|
1497
1641
|
|
|
1498
1642
|
// src/restura/validators/requestValidator.ts
|
|
1499
|
-
import { ObjectUtils as ObjectUtils2 } from "@redskytech/core-utils";
|
|
1500
1643
|
import jsonschema from "jsonschema";
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
} else {
|
|
1511
|
-
return variable;
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
function sortObjectKeysAlphabetically(obj) {
|
|
1515
|
-
if (Array.isArray(obj)) {
|
|
1516
|
-
return obj.map(sortObjectKeysAlphabetically);
|
|
1517
|
-
} else if (obj !== null && typeof obj === "object") {
|
|
1518
|
-
return Object.keys(obj).sort().reduce((sorted, key) => {
|
|
1519
|
-
sorted[key] = sortObjectKeysAlphabetically(obj[key]);
|
|
1520
|
-
return sorted;
|
|
1521
|
-
}, {});
|
|
1522
|
-
}
|
|
1523
|
-
return obj;
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
// src/restura/validators/requestValidator.ts
|
|
1527
|
-
function requestValidator(req, routeData, validationSchema) {
|
|
1528
|
-
const requestData = getRequestData(req);
|
|
1529
|
-
req.data = requestData;
|
|
1530
|
-
if (routeData.request === void 0) {
|
|
1531
|
-
if (routeData.type !== "CUSTOM_ONE" && routeData.type !== "CUSTOM_ARRAY" && routeData.type !== "CUSTOM_PAGED")
|
|
1532
|
-
throw new RsError("BAD_REQUEST", `No request parameters provided for standard request.`);
|
|
1644
|
+
function requestValidator(req, routeData, customValidationSchema, standardValidationSchema) {
|
|
1645
|
+
let schemaForCoercion;
|
|
1646
|
+
if (routeData.type === "ONE" || routeData.type === "ARRAY" || routeData.type === "PAGED") {
|
|
1647
|
+
const routeKey = `${routeData.method}:${routeData.path}`;
|
|
1648
|
+
schemaForCoercion = standardValidationSchema[routeKey];
|
|
1649
|
+
if (!schemaForCoercion) {
|
|
1650
|
+
throw new RsError("BAD_REQUEST", `No schema found for standard request route: ${routeKey}.`);
|
|
1651
|
+
}
|
|
1652
|
+
} else if (routeData.type === "CUSTOM_ONE" || routeData.type === "CUSTOM_ARRAY" || routeData.type === "CUSTOM_PAGED") {
|
|
1533
1653
|
if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
|
|
1534
|
-
if (!routeData.requestType
|
|
1535
|
-
|
|
1536
|
-
const
|
|
1537
|
-
const
|
|
1654
|
+
if (!routeData.requestType && !routeData.request)
|
|
1655
|
+
throw new RsError("BAD_REQUEST", `No request type defined for custom request.`);
|
|
1656
|
+
const routeKey = `${routeData.method}:${routeData.path}`;
|
|
1657
|
+
const currentInterface = customValidationSchema[routeData.requestType || routeKey];
|
|
1658
|
+
schemaForCoercion = {
|
|
1538
1659
|
...currentInterface,
|
|
1539
1660
|
additionalProperties: false
|
|
1540
1661
|
};
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
throw new RsError(
|
|
1544
|
-
"BAD_REQUEST",
|
|
1545
|
-
`Request custom setup has failed the following check: (${executeValidation.errors})`
|
|
1546
|
-
);
|
|
1547
|
-
}
|
|
1548
|
-
return;
|
|
1549
|
-
}
|
|
1550
|
-
Object.keys(req.data).forEach((requestParamName) => {
|
|
1551
|
-
const requestParam = routeData.request.find((param) => param.name === requestParamName);
|
|
1552
|
-
if (!requestParam) {
|
|
1553
|
-
throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not allowed`);
|
|
1554
|
-
}
|
|
1555
|
-
});
|
|
1556
|
-
routeData.request.forEach((requestParam) => {
|
|
1557
|
-
const requestValue = requestData[requestParam.name];
|
|
1558
|
-
if (requestParam.required && requestValue === void 0)
|
|
1559
|
-
throw new RsError("BAD_REQUEST", `Request param (${requestParam.name}) is required but missing`);
|
|
1560
|
-
else if (!requestParam.required && requestValue === void 0) return;
|
|
1561
|
-
validateRequestSingleParam(requestValue, requestParam);
|
|
1562
|
-
});
|
|
1563
|
-
}
|
|
1564
|
-
function validateRequestSingleParam(requestValue, requestParam) {
|
|
1565
|
-
if (requestParam.isNullable && requestValue === null) return;
|
|
1566
|
-
requestParam.validator.forEach((validator) => {
|
|
1567
|
-
switch (validator.type) {
|
|
1568
|
-
case "TYPE_CHECK":
|
|
1569
|
-
performTypeCheck(requestValue, validator, requestParam.name);
|
|
1570
|
-
break;
|
|
1571
|
-
case "MIN":
|
|
1572
|
-
performMinCheck(requestValue, validator, requestParam.name);
|
|
1573
|
-
break;
|
|
1574
|
-
case "MAX":
|
|
1575
|
-
performMaxCheck(requestValue, validator, requestParam.name);
|
|
1576
|
-
break;
|
|
1577
|
-
case "ONE_OF":
|
|
1578
|
-
performOneOfCheck(requestValue, validator, requestParam.name);
|
|
1579
|
-
break;
|
|
1580
|
-
}
|
|
1581
|
-
});
|
|
1582
|
-
}
|
|
1583
|
-
function isValidType(type, requestValue) {
|
|
1584
|
-
try {
|
|
1585
|
-
expectValidType(type, requestValue);
|
|
1586
|
-
return true;
|
|
1587
|
-
} catch {
|
|
1588
|
-
return false;
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
function expectValidType(type, requestValue) {
|
|
1592
|
-
if (type === "number") {
|
|
1593
|
-
return z4.number().parse(requestValue);
|
|
1594
|
-
}
|
|
1595
|
-
if (type === "string") {
|
|
1596
|
-
return z4.string().parse(requestValue);
|
|
1597
|
-
}
|
|
1598
|
-
if (type === "boolean") {
|
|
1599
|
-
return z4.boolean().parse(requestValue);
|
|
1600
|
-
}
|
|
1601
|
-
if (type === "string[]") {
|
|
1602
|
-
return z4.array(z4.string()).parse(requestValue);
|
|
1603
|
-
}
|
|
1604
|
-
if (type === "number[]") {
|
|
1605
|
-
return z4.array(z4.number()).parse(requestValue);
|
|
1606
|
-
}
|
|
1607
|
-
if (type === "any[]") {
|
|
1608
|
-
return z4.array(z4.any()).parse(requestValue);
|
|
1609
|
-
}
|
|
1610
|
-
if (type === "object") {
|
|
1611
|
-
return z4.object({}).strict().parse(requestValue);
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
function performTypeCheck(requestValue, validator, requestParamName) {
|
|
1615
|
-
if (!isValidType(validator.value, requestValue)) {
|
|
1616
|
-
throw new RsError(
|
|
1617
|
-
"BAD_REQUEST",
|
|
1618
|
-
`Request param (${requestParamName}) with value (${addQuotesToStrings(requestValue)}) is not of type (${validator.value})`
|
|
1619
|
-
);
|
|
1662
|
+
} else {
|
|
1663
|
+
throw new RsError("BAD_REQUEST", `Invalid route type: ${routeData.type}`);
|
|
1620
1664
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1665
|
+
const requestData = getRequestData(req, schemaForCoercion);
|
|
1666
|
+
req.data = requestData;
|
|
1667
|
+
const validator = new jsonschema.Validator();
|
|
1668
|
+
const executeValidation = validator.validate(req.data, schemaForCoercion);
|
|
1669
|
+
if (!executeValidation.valid) {
|
|
1670
|
+
const errorMessages = executeValidation.errors.map((err) => {
|
|
1671
|
+
const property = err.property.replace("instance.", "");
|
|
1672
|
+
return `${property}: ${err.message}`;
|
|
1673
|
+
}).join(", ");
|
|
1674
|
+
throw new RsError("BAD_REQUEST", `Request validation failed: ${errorMessages}`);
|
|
1625
1675
|
}
|
|
1626
1676
|
}
|
|
1627
|
-
function
|
|
1628
|
-
if (!isValueNumber(requestValue))
|
|
1629
|
-
throw new RsError(
|
|
1630
|
-
"BAD_REQUEST",
|
|
1631
|
-
`Request param (${requestParamName}) with value (${requestValue}) is not of type number`
|
|
1632
|
-
);
|
|
1633
|
-
if (!isValueNumber(validator.value))
|
|
1634
|
-
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value} is not of type number`);
|
|
1635
|
-
}
|
|
1636
|
-
function performMinCheck(requestValue, validator, requestParamName) {
|
|
1637
|
-
expectOnlyNumbers(requestValue, validator, requestParamName);
|
|
1638
|
-
if (requestValue < validator.value)
|
|
1639
|
-
throw new RsError(
|
|
1640
|
-
"BAD_REQUEST",
|
|
1641
|
-
`Request param (${requestParamName}) with value (${requestValue}) is less than (${validator.value})`
|
|
1642
|
-
);
|
|
1643
|
-
}
|
|
1644
|
-
function performMaxCheck(requestValue, validator, requestParamName) {
|
|
1645
|
-
expectOnlyNumbers(requestValue, validator, requestParamName);
|
|
1646
|
-
if (requestValue > validator.value)
|
|
1647
|
-
throw new RsError(
|
|
1648
|
-
"BAD_REQUEST",
|
|
1649
|
-
`Request param (${requestParamName}) with value (${requestValue}) is more than (${validator.value})`
|
|
1650
|
-
);
|
|
1651
|
-
}
|
|
1652
|
-
function performOneOfCheck(requestValue, validator, requestParamName) {
|
|
1653
|
-
if (!ObjectUtils2.isArrayWithData(validator.value))
|
|
1654
|
-
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not of type array`);
|
|
1655
|
-
if (typeof requestValue === "object")
|
|
1656
|
-
throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not of type string or number`);
|
|
1657
|
-
if (!validator.value.includes(requestValue))
|
|
1658
|
-
throw new RsError(
|
|
1659
|
-
"BAD_REQUEST",
|
|
1660
|
-
`Request param (${requestParamName}) with value (${requestValue}) is not one of (${validator.value.join(", ")})`
|
|
1661
|
-
);
|
|
1662
|
-
}
|
|
1663
|
-
function isValueNumber(value) {
|
|
1664
|
-
return !isNaN(Number(value));
|
|
1665
|
-
}
|
|
1666
|
-
function getRequestData(req) {
|
|
1677
|
+
function getRequestData(req, schema) {
|
|
1667
1678
|
let body = "";
|
|
1668
1679
|
if (req.method === "GET" || req.method === "DELETE") {
|
|
1669
1680
|
body = "query";
|
|
@@ -1671,50 +1682,74 @@ function getRequestData(req) {
|
|
|
1671
1682
|
body = "body";
|
|
1672
1683
|
}
|
|
1673
1684
|
const bodyData = req[body];
|
|
1674
|
-
if (bodyData && body === "query") {
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1685
|
+
if (bodyData && body === "query" && schema) {
|
|
1686
|
+
return coerceBySchema(bodyData, schema);
|
|
1687
|
+
}
|
|
1688
|
+
return bodyData;
|
|
1689
|
+
}
|
|
1690
|
+
function coerceBySchema(data, schema) {
|
|
1691
|
+
const normalized = {};
|
|
1692
|
+
const properties = schema.properties || {};
|
|
1693
|
+
for (const attr in data) {
|
|
1694
|
+
const cleanAttr = attr.replace(/\[\]$/, "");
|
|
1695
|
+
const isArrayNotation = attr.includes("[]");
|
|
1696
|
+
let value = data[attr];
|
|
1697
|
+
const propertySchema = properties[cleanAttr];
|
|
1698
|
+
if (isArrayNotation && !Array.isArray(value)) {
|
|
1699
|
+
value = [value];
|
|
1700
|
+
}
|
|
1701
|
+
if (!propertySchema) {
|
|
1702
|
+
normalized[cleanAttr] = value;
|
|
1703
|
+
continue;
|
|
1704
|
+
}
|
|
1705
|
+
if (Array.isArray(value)) {
|
|
1706
|
+
const itemSchema = Array.isArray(propertySchema.items) ? propertySchema.items[0] : propertySchema.items || { type: "string" };
|
|
1707
|
+
normalized[cleanAttr] = value.map((item) => coerceValue(item, itemSchema));
|
|
1708
|
+
} else {
|
|
1709
|
+
normalized[cleanAttr] = coerceValue(value, propertySchema);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
return normalized;
|
|
1713
|
+
}
|
|
1714
|
+
function coerceValue(value, propertySchema) {
|
|
1715
|
+
if (value === void 0 || value === null) {
|
|
1716
|
+
return value;
|
|
1717
|
+
}
|
|
1718
|
+
const targetType = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
|
|
1719
|
+
if (value === "") {
|
|
1720
|
+
return targetType === "string" ? "" : void 0;
|
|
1721
|
+
}
|
|
1722
|
+
switch (targetType) {
|
|
1723
|
+
case "number":
|
|
1724
|
+
case "integer":
|
|
1725
|
+
const num = Number(value);
|
|
1726
|
+
return isNaN(num) ? value : num;
|
|
1727
|
+
case "boolean":
|
|
1728
|
+
if (value === "true") return true;
|
|
1729
|
+
if (value === "false") return false;
|
|
1730
|
+
if (typeof value === "string") {
|
|
1731
|
+
return value === "true" || value === "1";
|
|
1679
1732
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
return
|
|
1689
|
-
});
|
|
1690
|
-
normalizedData[cleanAttr] = parsedList;
|
|
1691
|
-
} else {
|
|
1692
|
-
let value = bodyData[attr];
|
|
1693
|
-
if (value === "true") {
|
|
1694
|
-
value = true;
|
|
1695
|
-
} else if (value === "false") {
|
|
1696
|
-
value = false;
|
|
1697
|
-
} else if (value === void 0) {
|
|
1698
|
-
value = void 0;
|
|
1699
|
-
} else if (value === "") {
|
|
1700
|
-
value = "";
|
|
1701
|
-
} else {
|
|
1702
|
-
value = ObjectUtils2.safeParse(value);
|
|
1703
|
-
if (!isNaN(Number(value))) {
|
|
1704
|
-
value = Number(value);
|
|
1705
|
-
}
|
|
1733
|
+
return Boolean(value);
|
|
1734
|
+
case "string":
|
|
1735
|
+
return String(value);
|
|
1736
|
+
case "object":
|
|
1737
|
+
if (typeof value === "string") {
|
|
1738
|
+
try {
|
|
1739
|
+
return JSON.parse(value);
|
|
1740
|
+
} catch {
|
|
1741
|
+
return value;
|
|
1706
1742
|
}
|
|
1707
|
-
normalizedData[cleanAttr] = value;
|
|
1708
1743
|
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1744
|
+
return value;
|
|
1745
|
+
default:
|
|
1746
|
+
return value;
|
|
1711
1747
|
}
|
|
1712
|
-
return bodyData;
|
|
1713
1748
|
}
|
|
1714
1749
|
|
|
1715
1750
|
// src/restura/middleware/schemaValidation.ts
|
|
1716
1751
|
async function schemaValidation(req, res, next) {
|
|
1717
|
-
req.data = getRequestData(req);
|
|
1752
|
+
req.data = getRequestData(req, {});
|
|
1718
1753
|
try {
|
|
1719
1754
|
resturaSchema.parse(req.data);
|
|
1720
1755
|
next();
|
|
@@ -1725,22 +1760,22 @@ async function schemaValidation(req, res, next) {
|
|
|
1725
1760
|
}
|
|
1726
1761
|
|
|
1727
1762
|
// src/restura/schemas/resturaConfigSchema.ts
|
|
1728
|
-
import { z as
|
|
1763
|
+
import { z as z4 } from "zod";
|
|
1729
1764
|
var isTsx = process.argv[1]?.endsWith(".ts");
|
|
1730
1765
|
var isTsNode = process.env.TS_NODE_DEV || process.env.TS_NODE_PROJECT;
|
|
1731
1766
|
var customApiFolderPath = isTsx || isTsNode ? "/src/api" : "/dist/api";
|
|
1732
|
-
var resturaConfigSchema =
|
|
1733
|
-
authToken:
|
|
1734
|
-
sendErrorStackTrace:
|
|
1735
|
-
schemaFilePath:
|
|
1736
|
-
customApiFolderPath:
|
|
1737
|
-
generatedTypesPath:
|
|
1738
|
-
fileTempCachePath:
|
|
1739
|
-
scratchDatabaseSuffix:
|
|
1767
|
+
var resturaConfigSchema = z4.object({
|
|
1768
|
+
authToken: z4.string().min(1, "Missing Restura Auth Token"),
|
|
1769
|
+
sendErrorStackTrace: z4.boolean().default(false),
|
|
1770
|
+
schemaFilePath: z4.string().default(process.cwd() + "/restura.schema.json"),
|
|
1771
|
+
customApiFolderPath: z4.string().default(process.cwd() + customApiFolderPath),
|
|
1772
|
+
generatedTypesPath: z4.string().default(process.cwd() + "/src/@types"),
|
|
1773
|
+
fileTempCachePath: z4.string().optional(),
|
|
1774
|
+
scratchDatabaseSuffix: z4.string().optional()
|
|
1740
1775
|
});
|
|
1741
1776
|
|
|
1742
1777
|
// src/restura/sql/PsqlEngine.ts
|
|
1743
|
-
import { ObjectUtils as
|
|
1778
|
+
import { ObjectUtils as ObjectUtils3 } from "@redskytech/core-utils";
|
|
1744
1779
|
import getDiff from "@wmfs/pg-diff-sync";
|
|
1745
1780
|
import pgInfo from "@wmfs/pg-info";
|
|
1746
1781
|
import pg2 from "pg";
|
|
@@ -1752,7 +1787,7 @@ import pg from "pg";
|
|
|
1752
1787
|
import crypto from "crypto";
|
|
1753
1788
|
import format2 from "pg-format";
|
|
1754
1789
|
import { format as sqlFormat } from "sql-formatter";
|
|
1755
|
-
import { z as
|
|
1790
|
+
import { z as z5 } from "zod";
|
|
1756
1791
|
|
|
1757
1792
|
// src/restura/sql/PsqlUtils.ts
|
|
1758
1793
|
import format from "pg-format";
|
|
@@ -1788,20 +1823,25 @@ function insertObjectQuery(table, obj) {
|
|
|
1788
1823
|
INSERT INTO "${table}" (${columns})
|
|
1789
1824
|
VALUES (${values})
|
|
1790
1825
|
RETURNING *`;
|
|
1791
|
-
query = query.replace(/'(\?)'
|
|
1826
|
+
query = query.replace(/'(\?)'/g, "?");
|
|
1792
1827
|
return query;
|
|
1793
1828
|
}
|
|
1794
|
-
function updateObjectQuery(table, obj, whereStatement) {
|
|
1829
|
+
function updateObjectQuery(table, obj, whereStatement, incrementSyncVersion = false) {
|
|
1795
1830
|
const setArray = [];
|
|
1796
1831
|
for (const i in obj) {
|
|
1797
|
-
|
|
1832
|
+
let value = SQL`${obj[i]}`;
|
|
1833
|
+
value = value.replace(/'(\?)'/g, "?");
|
|
1834
|
+
setArray.push(`${escapeColumnName(i)} = ` + value);
|
|
1835
|
+
}
|
|
1836
|
+
if (incrementSyncVersion) {
|
|
1837
|
+
setArray.push(`"syncVersion" = "syncVersion" + 1`);
|
|
1798
1838
|
}
|
|
1799
1839
|
return `
|
|
1800
|
-
UPDATE ${escapeColumnName(table)}
|
|
1801
|
-
|
|
1802
|
-
|
|
1840
|
+
UPDATE ${escapeColumnName(table)}
|
|
1841
|
+
SET ${setArray.join(", ")} ${whereStatement}
|
|
1842
|
+
RETURNING *`;
|
|
1803
1843
|
}
|
|
1804
|
-
function
|
|
1844
|
+
function isValueNumber(value) {
|
|
1805
1845
|
return !isNaN(Number(value));
|
|
1806
1846
|
}
|
|
1807
1847
|
function SQL(strings, ...values) {
|
|
@@ -1835,9 +1875,9 @@ var PsqlConnection = class {
|
|
|
1835
1875
|
const startTime = process.hrtime();
|
|
1836
1876
|
try {
|
|
1837
1877
|
const response = await this.query(queryMetadata + formattedQuery, options);
|
|
1838
|
-
this.logSqlStatement(formattedQuery, options, meta, startTime);
|
|
1839
1878
|
if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
|
|
1840
1879
|
else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
|
|
1880
|
+
this.logSqlStatement(formattedQuery, options, meta, startTime);
|
|
1841
1881
|
return response.rows[0];
|
|
1842
1882
|
} catch (error) {
|
|
1843
1883
|
this.logSqlStatement(formattedQuery, options, meta, startTime);
|
|
@@ -1853,10 +1893,10 @@ var PsqlConnection = class {
|
|
|
1853
1893
|
try {
|
|
1854
1894
|
return zodSchema.parse(result);
|
|
1855
1895
|
} catch (error) {
|
|
1856
|
-
if (error instanceof
|
|
1896
|
+
if (error instanceof z5.ZodError) {
|
|
1857
1897
|
logger.error("Invalid data returned from database:");
|
|
1858
1898
|
logger.silly("\n" + JSON.stringify(result, null, 2));
|
|
1859
|
-
logger.error("\n" +
|
|
1899
|
+
logger.error("\n" + z5.prettifyError(error));
|
|
1860
1900
|
} else {
|
|
1861
1901
|
logger.error(error);
|
|
1862
1902
|
}
|
|
@@ -1884,12 +1924,12 @@ var PsqlConnection = class {
|
|
|
1884
1924
|
async runQuerySchema(query, params, requesterDetails, zodSchema) {
|
|
1885
1925
|
const result = await this.runQuery(query, params, requesterDetails);
|
|
1886
1926
|
try {
|
|
1887
|
-
return
|
|
1927
|
+
return z5.array(zodSchema).parse(result);
|
|
1888
1928
|
} catch (error) {
|
|
1889
|
-
if (error instanceof
|
|
1929
|
+
if (error instanceof z5.ZodError) {
|
|
1890
1930
|
logger.error("Invalid data returned from database:");
|
|
1891
1931
|
logger.silly("\n" + JSON.stringify(result, null, 2));
|
|
1892
|
-
logger.error("\n" +
|
|
1932
|
+
logger.error("\n" + z5.prettifyError(error));
|
|
1893
1933
|
} else {
|
|
1894
1934
|
logger.error(error);
|
|
1895
1935
|
}
|
|
@@ -1962,7 +2002,7 @@ var PsqlPool = class extends PsqlConnection {
|
|
|
1962
2002
|
};
|
|
1963
2003
|
|
|
1964
2004
|
// src/restura/sql/SqlEngine.ts
|
|
1965
|
-
import { ObjectUtils as
|
|
2005
|
+
import { ObjectUtils as ObjectUtils2 } from "@redskytech/core-utils";
|
|
1966
2006
|
var SqlEngine = class {
|
|
1967
2007
|
async runQueryForRoute(req, routeData, schema) {
|
|
1968
2008
|
if (!this.canRequesterAccessTable(
|
|
@@ -2005,18 +2045,18 @@ var SqlEngine = class {
|
|
|
2005
2045
|
const columnSchema = tableSchema.columns.find((item2) => item2.name === columnName);
|
|
2006
2046
|
if (!columnSchema)
|
|
2007
2047
|
throw new RsError("SCHEMA_ERROR", `Column ${columnName} not found in table ${tableName}`);
|
|
2008
|
-
if (
|
|
2048
|
+
if (ObjectUtils2.isArrayWithData(columnSchema.roles)) {
|
|
2009
2049
|
if (!requesterRole) return false;
|
|
2010
2050
|
return columnSchema.roles.includes(requesterRole);
|
|
2011
2051
|
}
|
|
2012
|
-
if (
|
|
2052
|
+
if (ObjectUtils2.isArrayWithData(columnSchema.scopes)) {
|
|
2013
2053
|
if (!requesterScopes) return false;
|
|
2014
2054
|
return columnSchema.scopes.every((scope) => requesterScopes.includes(scope));
|
|
2015
2055
|
}
|
|
2016
2056
|
return true;
|
|
2017
2057
|
}
|
|
2018
2058
|
if (item.subquery) {
|
|
2019
|
-
return
|
|
2059
|
+
return ObjectUtils2.isArrayWithData(
|
|
2020
2060
|
item.subquery.properties.filter((nestedItem) => {
|
|
2021
2061
|
return this.canRequesterAccessColumn(requesterRole, requesterScopes, schema, nestedItem, joins);
|
|
2022
2062
|
})
|
|
@@ -2026,11 +2066,11 @@ var SqlEngine = class {
|
|
|
2026
2066
|
}
|
|
2027
2067
|
canRequesterAccessTable(requesterRole, requesterScopes, schema, tableName) {
|
|
2028
2068
|
const tableSchema = this.getTableSchema(schema, tableName);
|
|
2029
|
-
if (
|
|
2069
|
+
if (ObjectUtils2.isArrayWithData(tableSchema.roles)) {
|
|
2030
2070
|
if (!requesterRole) return false;
|
|
2031
2071
|
return tableSchema.roles.includes(requesterRole);
|
|
2032
2072
|
}
|
|
2033
|
-
if (
|
|
2073
|
+
if (ObjectUtils2.isArrayWithData(tableSchema.scopes)) {
|
|
2034
2074
|
if (!requesterScopes) return false;
|
|
2035
2075
|
return tableSchema.scopes.some((scope) => requesterScopes.includes(scope));
|
|
2036
2076
|
}
|
|
@@ -2306,7 +2346,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2306
2346
|
});
|
|
2307
2347
|
this.triggerClient.on("notification", async (msg) => {
|
|
2308
2348
|
if (msg.channel === "insert" || msg.channel === "update" || msg.channel === "delete") {
|
|
2309
|
-
const payload =
|
|
2349
|
+
const payload = ObjectUtils3.safeParse(msg.payload);
|
|
2310
2350
|
await this.handleTrigger(payload, msg.channel.toUpperCase());
|
|
2311
2351
|
}
|
|
2312
2352
|
});
|
|
@@ -2480,7 +2520,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2480
2520
|
}
|
|
2481
2521
|
createNestedSelect(req, schema, item, routeData, sqlParams) {
|
|
2482
2522
|
if (!item.subquery) return "";
|
|
2483
|
-
if (!
|
|
2523
|
+
if (!ObjectUtils3.isArrayWithData(
|
|
2484
2524
|
item.subquery.properties.filter((nestedItem) => {
|
|
2485
2525
|
return this.canRequesterAccessColumn(
|
|
2486
2526
|
req.requesterDetails.role,
|
|
@@ -2516,7 +2556,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2516
2556
|
}
|
|
2517
2557
|
return `'${nestedItem.name}', ${escapeColumnName(nestedItem.selector)}`;
|
|
2518
2558
|
}).filter(Boolean).join(", ")}
|
|
2519
|
-
))
|
|
2559
|
+
))
|
|
2520
2560
|
FROM
|
|
2521
2561
|
"${item.subquery.table}"
|
|
2522
2562
|
${this.generateJoinStatements(req, item.subquery.joins, item.subquery.table, routeData, schema, sqlParams)}
|
|
@@ -2624,7 +2664,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2624
2664
|
);
|
|
2625
2665
|
const [pageResults, totalResponse] = await Promise.all([pagePromise, totalPromise]);
|
|
2626
2666
|
let total = 0;
|
|
2627
|
-
if (
|
|
2667
|
+
if (ObjectUtils3.isArrayWithData(totalResponse)) {
|
|
2628
2668
|
total = totalResponse[0].total;
|
|
2629
2669
|
}
|
|
2630
2670
|
return { data: pageResults, total };
|
|
@@ -2632,9 +2672,19 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2632
2672
|
throw new RsError("UNKNOWN_ERROR", "Unknown route type.");
|
|
2633
2673
|
}
|
|
2634
2674
|
}
|
|
2675
|
+
/**
|
|
2676
|
+
* Executes an update request. The request will pull out the id and baseSyncVersion from the request body.
|
|
2677
|
+
* (If Present) The baseSyncVersion is used to check if the record has been modified since the last sync.
|
|
2678
|
+
* If the update fails because the baseSyncVersion has changed, a conflict error will be thrown.
|
|
2679
|
+
* IDs can not be updated using this method.
|
|
2680
|
+
* @param req - The request object.
|
|
2681
|
+
* @param routeData - The route data object.
|
|
2682
|
+
* @param schema - The schema object.
|
|
2683
|
+
* @returns The response object.
|
|
2684
|
+
*/
|
|
2635
2685
|
async executeUpdateRequest(req, routeData, schema) {
|
|
2636
2686
|
const sqlParams = [];
|
|
2637
|
-
const { id,
|
|
2687
|
+
const { id, baseSyncVersion, ...bodyNoId } = req.body;
|
|
2638
2688
|
const table = schema.database.find((item) => {
|
|
2639
2689
|
return item.name === routeData.table;
|
|
2640
2690
|
});
|
|
@@ -2642,28 +2692,24 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2642
2692
|
if (table.columns.find((column) => column.name === "modifiedOn")) {
|
|
2643
2693
|
bodyNoId.modifiedOn = (/* @__PURE__ */ new Date()).toISOString();
|
|
2644
2694
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
bodyNoId[assignmentEscaped] = Number(assignment.value);
|
|
2651
|
-
else bodyNoId[assignmentEscaped] = assignment.value;
|
|
2652
|
-
}
|
|
2695
|
+
let incrementSyncVersion = false;
|
|
2696
|
+
if (table.columns.find((column) => column.name === "syncVersion")) incrementSyncVersion = true;
|
|
2697
|
+
(routeData.assignments || []).forEach((assignment) => {
|
|
2698
|
+
bodyNoId[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
|
|
2699
|
+
});
|
|
2653
2700
|
let whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
2654
2701
|
const originalWhereClause = whereClause;
|
|
2655
2702
|
const originalSqlParams = [...sqlParams];
|
|
2656
|
-
if (
|
|
2657
|
-
const
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
whereClause = modifiedOnCheck;
|
|
2703
|
+
if (baseSyncVersion) {
|
|
2704
|
+
const syncVersionCheck = whereClause ? `${whereClause} AND "syncVersion" = ?` : `"syncVersion" = ?`;
|
|
2705
|
+
sqlParams.push(baseSyncVersion.toString());
|
|
2706
|
+
whereClause = syncVersionCheck;
|
|
2661
2707
|
}
|
|
2662
|
-
const query = updateObjectQuery(routeData.table, bodyNoId, whereClause);
|
|
2708
|
+
const query = updateObjectQuery(routeData.table, bodyNoId, whereClause, incrementSyncVersion);
|
|
2663
2709
|
try {
|
|
2664
2710
|
await this.psqlConnectionPool.queryOne(query, [...sqlParams], req.requesterDetails);
|
|
2665
2711
|
} catch (error) {
|
|
2666
|
-
if (!
|
|
2712
|
+
if (!baseSyncVersion || !(error instanceof RsError) || error.err !== "NOT_FOUND") throw error;
|
|
2667
2713
|
let isConflict = false;
|
|
2668
2714
|
try {
|
|
2669
2715
|
await this.psqlConnectionPool.queryOne(
|
|
@@ -2677,7 +2723,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2677
2723
|
if (isConflict)
|
|
2678
2724
|
throw new RsError(
|
|
2679
2725
|
"CONFLICT",
|
|
2680
|
-
"The record has been modified since the
|
|
2726
|
+
"The record has been modified since the baseSyncVersion value was provided."
|
|
2681
2727
|
);
|
|
2682
2728
|
throw error;
|
|
2683
2729
|
}
|
|
@@ -3074,6 +3120,19 @@ var TempCache = class {
|
|
|
3074
3120
|
}
|
|
3075
3121
|
};
|
|
3076
3122
|
|
|
3123
|
+
// src/restura/utils/utils.ts
|
|
3124
|
+
function sortObjectKeysAlphabetically(obj) {
|
|
3125
|
+
if (Array.isArray(obj)) {
|
|
3126
|
+
return obj.map(sortObjectKeysAlphabetically);
|
|
3127
|
+
} else if (obj !== null && typeof obj === "object") {
|
|
3128
|
+
return Object.keys(obj).sort().reduce((sorted, key) => {
|
|
3129
|
+
sorted[key] = sortObjectKeysAlphabetically(obj[key]);
|
|
3130
|
+
return sorted;
|
|
3131
|
+
}, {});
|
|
3132
|
+
}
|
|
3133
|
+
return obj;
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3077
3136
|
// src/restura/restura.ts
|
|
3078
3137
|
var ResturaEngine = class {
|
|
3079
3138
|
// Make public so other modules can access without re-parsing the config
|
|
@@ -3092,6 +3151,7 @@ var ResturaEngine = class {
|
|
|
3092
3151
|
responseValidator;
|
|
3093
3152
|
authenticationHandler;
|
|
3094
3153
|
customTypeValidation;
|
|
3154
|
+
standardTypeValidation;
|
|
3095
3155
|
psqlConnectionPool;
|
|
3096
3156
|
psqlEngine;
|
|
3097
3157
|
/**
|
|
@@ -3201,7 +3261,7 @@ var ResturaEngine = class {
|
|
|
3201
3261
|
throw new Error("Missing restura schema file");
|
|
3202
3262
|
}
|
|
3203
3263
|
const schemaFileData = fs4.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
|
|
3204
|
-
const schema =
|
|
3264
|
+
const schema = ObjectUtils4.safeParse(schemaFileData);
|
|
3205
3265
|
const isValid = await isSchemaValid(schema);
|
|
3206
3266
|
if (!isValid) {
|
|
3207
3267
|
logger.error("Schema is not valid");
|
|
@@ -3212,6 +3272,7 @@ var ResturaEngine = class {
|
|
|
3212
3272
|
async reloadEndpoints() {
|
|
3213
3273
|
this.schema = await this.getLatestFileSystemSchema();
|
|
3214
3274
|
this.customTypeValidation = customTypeValidationGenerator(this.schema);
|
|
3275
|
+
this.standardTypeValidation = standardTypeValidationGenerator(this.schema);
|
|
3215
3276
|
this.resturaRouter = express.Router();
|
|
3216
3277
|
this.resetPublicEndpoints();
|
|
3217
3278
|
let routeCount = 0;
|
|
@@ -3311,7 +3372,12 @@ var ResturaEngine = class {
|
|
|
3311
3372
|
const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
|
|
3312
3373
|
this.validateAuthorization(req, routeData);
|
|
3313
3374
|
await this.getMulterFilesIfAny(req, res, routeData);
|
|
3314
|
-
requestValidator(
|
|
3375
|
+
requestValidator(
|
|
3376
|
+
req,
|
|
3377
|
+
routeData,
|
|
3378
|
+
this.customTypeValidation,
|
|
3379
|
+
this.standardTypeValidation
|
|
3380
|
+
);
|
|
3315
3381
|
if (this.isCustomRoute(routeData)) {
|
|
3316
3382
|
await this.runCustomRouteLogic(req, res, routeData);
|
|
3317
3383
|
return;
|
|
@@ -3488,7 +3554,7 @@ export {
|
|
|
3488
3554
|
filterPsqlParser_default as filterPsqlParser,
|
|
3489
3555
|
insertObjectQuery,
|
|
3490
3556
|
isSchemaValid,
|
|
3491
|
-
|
|
3557
|
+
isValueNumber,
|
|
3492
3558
|
logger,
|
|
3493
3559
|
modelGenerator,
|
|
3494
3560
|
questionMarksToOrderedParams,
|