@restura/core 1.4.0 → 1.6.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.js CHANGED
@@ -878,7 +878,7 @@ declare namespace Restura {
878
878
  }
879
879
 
880
880
  // src/restura/restura.ts
881
- import { ObjectUtils as ObjectUtils5, StringUtils as StringUtils3 } from "@redskytech/core-utils";
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
- function customTypeValidationGenerator(currentSchema) {
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
- path2.join(restura.resturaConfig.generatedTypesPath, "restura.d.ts"),
1115
- path2.join(restura.resturaConfig.generatedTypesPath, "models.d.ts"),
1116
- path2.join(restura.resturaConfig.generatedTypesPath, "api.d.ts")
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
 
@@ -1156,6 +1300,18 @@ function addApiResponseFunctions(req, res, next) {
1156
1300
  next();
1157
1301
  }
1158
1302
 
1303
+ // src/restura/middleware/addDeprecationResponse.ts
1304
+ function addDeprecationResponse(req, res, next) {
1305
+ const deprecation = req.routeData?.deprecation;
1306
+ if (deprecation) {
1307
+ const { date, message } = deprecation;
1308
+ const dateObject = new Date(date);
1309
+ res.set("Deprecation", `@${dateObject.getTime().toString()}`);
1310
+ res.set("Deprecation-Message", message ?? "This endpoint is deprecated and will be removed in the future.");
1311
+ }
1312
+ next();
1313
+ }
1314
+
1159
1315
  // src/restura/middleware/authenticateRequester.ts
1160
1316
  function authenticateRequester(applicationAuthenticateHandler) {
1161
1317
  return (req, res, next) => {
@@ -1278,6 +1434,10 @@ var routeDataBaseSchema = z3.object({
1278
1434
  name: z3.string(),
1279
1435
  description: z3.string(),
1280
1436
  path: z3.string(),
1437
+ deprecation: z3.object({
1438
+ date: z3.iso.datetime(),
1439
+ message: z3.string().optional()
1440
+ }).optional(),
1281
1441
  roles: z3.array(z3.string()),
1282
1442
  scopes: z3.array(z3.string())
1283
1443
  }).strict();
@@ -1430,7 +1590,8 @@ var indexDataSchema = z3.object({
1430
1590
  columns: z3.array(z3.string()),
1431
1591
  isUnique: z3.boolean(),
1432
1592
  isPrimaryKey: z3.boolean(),
1433
- order: z3.enum(["ASC", "DESC"])
1593
+ order: z3.enum(["ASC", "DESC"]),
1594
+ where: z3.string().optional()
1434
1595
  }).strict();
1435
1596
  var foreignKeyActionsSchema = z3.enum([
1436
1597
  "CASCADE",
@@ -1496,174 +1657,41 @@ async function isSchemaValid(schemaToCheck) {
1496
1657
  }
1497
1658
 
1498
1659
  // src/restura/validators/requestValidator.ts
1499
- import { ObjectUtils as ObjectUtils2 } from "@redskytech/core-utils";
1500
1660
  import jsonschema from "jsonschema";
1501
- import { z as z4 } from "zod";
1502
-
1503
- // src/restura/utils/utils.ts
1504
- function addQuotesToStrings(variable) {
1505
- if (typeof variable === "string") {
1506
- return `'${variable}'`;
1507
- } else if (Array.isArray(variable)) {
1508
- const arrayWithQuotes = variable.map(addQuotesToStrings);
1509
- return arrayWithQuotes;
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.`);
1661
+ function requestValidator(req, routeData, customValidationSchema, standardValidationSchema) {
1662
+ let schemaForCoercion;
1663
+ if (routeData.type === "ONE" || routeData.type === "ARRAY" || routeData.type === "PAGED") {
1664
+ const routeKey = `${routeData.method}:${routeData.path}`;
1665
+ schemaForCoercion = standardValidationSchema[routeKey];
1666
+ if (!schemaForCoercion) {
1667
+ throw new RsError("BAD_REQUEST", `No schema found for standard request route: ${routeKey}.`);
1668
+ }
1669
+ } else if (routeData.type === "CUSTOM_ONE" || routeData.type === "CUSTOM_ARRAY" || routeData.type === "CUSTOM_PAGED") {
1533
1670
  if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
1534
- if (!routeData.requestType) throw new RsError("BAD_REQUEST", `No request type defined for custom request.`);
1535
- const currentInterface = validationSchema[routeData.requestType];
1536
- const validator = new jsonschema.Validator();
1537
- const strictSchema = {
1671
+ if (!routeData.requestType && !routeData.request)
1672
+ throw new RsError("BAD_REQUEST", `No request type defined for custom request.`);
1673
+ const routeKey = `${routeData.method}:${routeData.path}`;
1674
+ const currentInterface = customValidationSchema[routeData.requestType || routeKey];
1675
+ schemaForCoercion = {
1538
1676
  ...currentInterface,
1539
1677
  additionalProperties: false
1540
1678
  };
1541
- const executeValidation = validator.validate(req.data, strictSchema);
1542
- if (!executeValidation.valid) {
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
- );
1679
+ } else {
1680
+ throw new RsError("BAD_REQUEST", `Invalid route type: ${routeData.type}`);
1620
1681
  }
1621
- try {
1622
- validatorDataSchemeValue.parse(validator.value);
1623
- } catch {
1624
- throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not a valid type`);
1682
+ const requestData = getRequestData(req, schemaForCoercion);
1683
+ req.data = requestData;
1684
+ const validator = new jsonschema.Validator();
1685
+ const executeValidation = validator.validate(req.data, schemaForCoercion);
1686
+ if (!executeValidation.valid) {
1687
+ const errorMessages = executeValidation.errors.map((err) => {
1688
+ const property = err.property.replace("instance.", "");
1689
+ return `${property}: ${err.message}`;
1690
+ }).join(", ");
1691
+ throw new RsError("BAD_REQUEST", `Request validation failed: ${errorMessages}`);
1625
1692
  }
1626
1693
  }
1627
- function expectOnlyNumbers(requestValue, validator, requestParamName) {
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) {
1694
+ function getRequestData(req, schema) {
1667
1695
  let body = "";
1668
1696
  if (req.method === "GET" || req.method === "DELETE") {
1669
1697
  body = "query";
@@ -1671,50 +1699,74 @@ function getRequestData(req) {
1671
1699
  body = "body";
1672
1700
  }
1673
1701
  const bodyData = req[body];
1674
- if (bodyData && body === "query") {
1675
- const normalizedData = {};
1676
- for (const attr in bodyData) {
1677
- if (attr.includes("[]") && !(bodyData[attr] instanceof Array)) {
1678
- bodyData[attr] = [bodyData[attr]];
1702
+ if (bodyData && body === "query" && schema) {
1703
+ return coerceBySchema(bodyData, schema);
1704
+ }
1705
+ return bodyData;
1706
+ }
1707
+ function coerceBySchema(data, schema) {
1708
+ const normalized = {};
1709
+ const properties = schema.properties || {};
1710
+ for (const attr in data) {
1711
+ const cleanAttr = attr.replace(/\[\]$/, "");
1712
+ const isArrayNotation = attr.includes("[]");
1713
+ let value = data[attr];
1714
+ const propertySchema = properties[cleanAttr];
1715
+ if (isArrayNotation && !Array.isArray(value)) {
1716
+ value = [value];
1717
+ }
1718
+ if (!propertySchema) {
1719
+ normalized[cleanAttr] = value;
1720
+ continue;
1721
+ }
1722
+ if (Array.isArray(value)) {
1723
+ const itemSchema = Array.isArray(propertySchema.items) ? propertySchema.items[0] : propertySchema.items || { type: "string" };
1724
+ normalized[cleanAttr] = value.map((item) => coerceValue(item, itemSchema));
1725
+ } else {
1726
+ normalized[cleanAttr] = coerceValue(value, propertySchema);
1727
+ }
1728
+ }
1729
+ return normalized;
1730
+ }
1731
+ function coerceValue(value, propertySchema) {
1732
+ if (value === void 0 || value === null) {
1733
+ return value;
1734
+ }
1735
+ const targetType = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
1736
+ if (value === "") {
1737
+ return targetType === "string" ? "" : void 0;
1738
+ }
1739
+ switch (targetType) {
1740
+ case "number":
1741
+ case "integer":
1742
+ const num = Number(value);
1743
+ return isNaN(num) ? value : num;
1744
+ case "boolean":
1745
+ if (value === "true") return true;
1746
+ if (value === "false") return false;
1747
+ if (typeof value === "string") {
1748
+ return value === "true" || value === "1";
1679
1749
  }
1680
- const cleanAttr = attr.replace(/\[\]$/, "");
1681
- if (bodyData[attr] instanceof Array) {
1682
- const parsedList = bodyData[attr].map((value) => {
1683
- if (value === "true") return true;
1684
- if (value === "false") return false;
1685
- if (value === void 0) return void 0;
1686
- if (value === "") return "";
1687
- const parsed = ObjectUtils2.safeParse(value);
1688
- return isNaN(Number(parsed)) ? parsed : Number(parsed);
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
- }
1750
+ return Boolean(value);
1751
+ case "string":
1752
+ return String(value);
1753
+ case "object":
1754
+ if (typeof value === "string") {
1755
+ try {
1756
+ return JSON.parse(value);
1757
+ } catch {
1758
+ return value;
1706
1759
  }
1707
- normalizedData[cleanAttr] = value;
1708
1760
  }
1709
- }
1710
- return normalizedData;
1761
+ return value;
1762
+ default:
1763
+ return value;
1711
1764
  }
1712
- return bodyData;
1713
1765
  }
1714
1766
 
1715
1767
  // src/restura/middleware/schemaValidation.ts
1716
1768
  async function schemaValidation(req, res, next) {
1717
- req.data = getRequestData(req);
1769
+ req.data = getRequestData(req, {});
1718
1770
  try {
1719
1771
  resturaSchema.parse(req.data);
1720
1772
  next();
@@ -1725,22 +1777,22 @@ async function schemaValidation(req, res, next) {
1725
1777
  }
1726
1778
 
1727
1779
  // src/restura/schemas/resturaConfigSchema.ts
1728
- import { z as z5 } from "zod";
1780
+ import { z as z4 } from "zod";
1729
1781
  var isTsx = process.argv[1]?.endsWith(".ts");
1730
1782
  var isTsNode = process.env.TS_NODE_DEV || process.env.TS_NODE_PROJECT;
1731
1783
  var customApiFolderPath = isTsx || isTsNode ? "/src/api" : "/dist/api";
1732
- var resturaConfigSchema = z5.object({
1733
- authToken: z5.string().min(1, "Missing Restura Auth Token"),
1734
- sendErrorStackTrace: z5.boolean().default(false),
1735
- schemaFilePath: z5.string().default(process.cwd() + "/restura.schema.json"),
1736
- customApiFolderPath: z5.string().default(process.cwd() + customApiFolderPath),
1737
- generatedTypesPath: z5.string().default(process.cwd() + "/src/@types"),
1738
- fileTempCachePath: z5.string().optional(),
1739
- scratchDatabaseSuffix: z5.string().optional()
1784
+ var resturaConfigSchema = z4.object({
1785
+ authToken: z4.string().min(1, "Missing Restura Auth Token"),
1786
+ sendErrorStackTrace: z4.boolean().default(false),
1787
+ schemaFilePath: z4.string().default(process.cwd() + "/restura.schema.json"),
1788
+ customApiFolderPath: z4.string().default(process.cwd() + customApiFolderPath),
1789
+ generatedTypesPath: z4.string().default(process.cwd() + "/src/@types"),
1790
+ fileTempCachePath: z4.string().optional(),
1791
+ scratchDatabaseSuffix: z4.string().optional()
1740
1792
  });
1741
1793
 
1742
1794
  // src/restura/sql/PsqlEngine.ts
1743
- import { ObjectUtils as ObjectUtils4 } from "@redskytech/core-utils";
1795
+ import { ObjectUtils as ObjectUtils3 } from "@redskytech/core-utils";
1744
1796
  import getDiff from "@wmfs/pg-diff-sync";
1745
1797
  import pgInfo from "@wmfs/pg-info";
1746
1798
  import pg2 from "pg";
@@ -1752,7 +1804,7 @@ import pg from "pg";
1752
1804
  import crypto from "crypto";
1753
1805
  import format2 from "pg-format";
1754
1806
  import { format as sqlFormat } from "sql-formatter";
1755
- import { z as z6 } from "zod";
1807
+ import { z as z5 } from "zod";
1756
1808
 
1757
1809
  // src/restura/sql/PsqlUtils.ts
1758
1810
  import format from "pg-format";
@@ -1788,13 +1840,15 @@ function insertObjectQuery(table, obj) {
1788
1840
  INSERT INTO "${table}" (${columns})
1789
1841
  VALUES (${values})
1790
1842
  RETURNING *`;
1791
- query = query.replace(/'(\?)'/, "?");
1843
+ query = query.replace(/'(\?)'/g, "?");
1792
1844
  return query;
1793
1845
  }
1794
1846
  function updateObjectQuery(table, obj, whereStatement, incrementSyncVersion = false) {
1795
1847
  const setArray = [];
1796
1848
  for (const i in obj) {
1797
- setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
1849
+ let value = SQL`${obj[i]}`;
1850
+ value = value.replace(/'(\?)'/g, "?");
1851
+ setArray.push(`${escapeColumnName(i)} = ` + value);
1798
1852
  }
1799
1853
  if (incrementSyncVersion) {
1800
1854
  setArray.push(`"syncVersion" = "syncVersion" + 1`);
@@ -1804,7 +1858,7 @@ function updateObjectQuery(table, obj, whereStatement, incrementSyncVersion = fa
1804
1858
  SET ${setArray.join(", ")} ${whereStatement}
1805
1859
  RETURNING *`;
1806
1860
  }
1807
- function isValueNumber2(value) {
1861
+ function isValueNumber(value) {
1808
1862
  return !isNaN(Number(value));
1809
1863
  }
1810
1864
  function SQL(strings, ...values) {
@@ -1856,10 +1910,10 @@ var PsqlConnection = class {
1856
1910
  try {
1857
1911
  return zodSchema.parse(result);
1858
1912
  } catch (error) {
1859
- if (error instanceof z6.ZodError) {
1913
+ if (error instanceof z5.ZodError) {
1860
1914
  logger.error("Invalid data returned from database:");
1861
1915
  logger.silly("\n" + JSON.stringify(result, null, 2));
1862
- logger.error("\n" + z6.prettifyError(error));
1916
+ logger.error("\n" + z5.prettifyError(error));
1863
1917
  } else {
1864
1918
  logger.error(error);
1865
1919
  }
@@ -1887,12 +1941,12 @@ var PsqlConnection = class {
1887
1941
  async runQuerySchema(query, params, requesterDetails, zodSchema) {
1888
1942
  const result = await this.runQuery(query, params, requesterDetails);
1889
1943
  try {
1890
- return z6.array(zodSchema).parse(result);
1944
+ return z5.array(zodSchema).parse(result);
1891
1945
  } catch (error) {
1892
- if (error instanceof z6.ZodError) {
1946
+ if (error instanceof z5.ZodError) {
1893
1947
  logger.error("Invalid data returned from database:");
1894
1948
  logger.silly("\n" + JSON.stringify(result, null, 2));
1895
- logger.error("\n" + z6.prettifyError(error));
1949
+ logger.error("\n" + z5.prettifyError(error));
1896
1950
  } else {
1897
1951
  logger.error(error);
1898
1952
  }
@@ -1932,8 +1986,7 @@ var PsqlConnection = class {
1932
1986
  if ("isSystemUser" in queryMetadata && queryMetadata.isSystemUser) initiator = "SYSTEM";
1933
1987
  logger.silly(`${prefix}query by ${initiator}, Query ->
1934
1988
  ${formattedSql}`, {
1935
- duration: `${durationMs.toFixed(2)}ms`,
1936
- _meta: { durationNs: nanoseconds }
1989
+ durationMs
1937
1990
  });
1938
1991
  }
1939
1992
  };
@@ -1965,7 +2018,7 @@ var PsqlPool = class extends PsqlConnection {
1965
2018
  };
1966
2019
 
1967
2020
  // src/restura/sql/SqlEngine.ts
1968
- import { ObjectUtils as ObjectUtils3 } from "@redskytech/core-utils";
2021
+ import { ObjectUtils as ObjectUtils2 } from "@redskytech/core-utils";
1969
2022
  var SqlEngine = class {
1970
2023
  async runQueryForRoute(req, routeData, schema) {
1971
2024
  if (!this.canRequesterAccessTable(
@@ -2008,18 +2061,18 @@ var SqlEngine = class {
2008
2061
  const columnSchema = tableSchema.columns.find((item2) => item2.name === columnName);
2009
2062
  if (!columnSchema)
2010
2063
  throw new RsError("SCHEMA_ERROR", `Column ${columnName} not found in table ${tableName}`);
2011
- if (ObjectUtils3.isArrayWithData(columnSchema.roles)) {
2064
+ if (ObjectUtils2.isArrayWithData(columnSchema.roles)) {
2012
2065
  if (!requesterRole) return false;
2013
2066
  return columnSchema.roles.includes(requesterRole);
2014
2067
  }
2015
- if (ObjectUtils3.isArrayWithData(columnSchema.scopes)) {
2068
+ if (ObjectUtils2.isArrayWithData(columnSchema.scopes)) {
2016
2069
  if (!requesterScopes) return false;
2017
2070
  return columnSchema.scopes.every((scope) => requesterScopes.includes(scope));
2018
2071
  }
2019
2072
  return true;
2020
2073
  }
2021
2074
  if (item.subquery) {
2022
- return ObjectUtils3.isArrayWithData(
2075
+ return ObjectUtils2.isArrayWithData(
2023
2076
  item.subquery.properties.filter((nestedItem) => {
2024
2077
  return this.canRequesterAccessColumn(requesterRole, requesterScopes, schema, nestedItem, joins);
2025
2078
  })
@@ -2029,11 +2082,11 @@ var SqlEngine = class {
2029
2082
  }
2030
2083
  canRequesterAccessTable(requesterRole, requesterScopes, schema, tableName) {
2031
2084
  const tableSchema = this.getTableSchema(schema, tableName);
2032
- if (ObjectUtils3.isArrayWithData(tableSchema.roles)) {
2085
+ if (ObjectUtils2.isArrayWithData(tableSchema.roles)) {
2033
2086
  if (!requesterRole) return false;
2034
2087
  return tableSchema.roles.includes(requesterRole);
2035
2088
  }
2036
- if (ObjectUtils3.isArrayWithData(tableSchema.scopes)) {
2089
+ if (ObjectUtils2.isArrayWithData(tableSchema.scopes)) {
2037
2090
  if (!requesterScopes) return false;
2038
2091
  return tableSchema.scopes.some((scope) => requesterScopes.includes(scope));
2039
2092
  }
@@ -2309,7 +2362,7 @@ var PsqlEngine = class extends SqlEngine {
2309
2362
  });
2310
2363
  this.triggerClient.on("notification", async (msg) => {
2311
2364
  if (msg.channel === "insert" || msg.channel === "update" || msg.channel === "delete") {
2312
- const payload = ObjectUtils4.safeParse(msg.payload);
2365
+ const payload = ObjectUtils3.safeParse(msg.payload);
2313
2366
  await this.handleTrigger(payload, msg.channel.toUpperCase());
2314
2367
  }
2315
2368
  });
@@ -2373,11 +2426,11 @@ var PsqlEngine = class extends SqlEngine {
2373
2426
  if (!index.isPrimaryKey) {
2374
2427
  let unique = " ";
2375
2428
  if (index.isUnique) unique = "UNIQUE ";
2376
- indexes.push(
2377
- ` CREATE ${unique}INDEX "${index.name}" ON "${table.name}" (${index.columns.map((item) => {
2378
- return `"${item}" ${index.order}`;
2379
- }).join(", ")});`
2380
- );
2429
+ let indexSQL = ` CREATE ${unique}INDEX "${index.name}" ON "${table.name}"`;
2430
+ indexSQL += ` (${index.columns.map((item) => `"${item}" ${index.order}`).join(", ")})`;
2431
+ indexSQL += index.where ? ` WHERE ${index.where}` : "";
2432
+ indexSQL += ";";
2433
+ indexes.push(indexSQL);
2381
2434
  }
2382
2435
  }
2383
2436
  sql += "\n);";
@@ -2483,7 +2536,7 @@ var PsqlEngine = class extends SqlEngine {
2483
2536
  }
2484
2537
  createNestedSelect(req, schema, item, routeData, sqlParams) {
2485
2538
  if (!item.subquery) return "";
2486
- if (!ObjectUtils4.isArrayWithData(
2539
+ if (!ObjectUtils3.isArrayWithData(
2487
2540
  item.subquery.properties.filter((nestedItem) => {
2488
2541
  return this.canRequesterAccessColumn(
2489
2542
  req.requesterDetails.role,
@@ -2519,7 +2572,7 @@ var PsqlEngine = class extends SqlEngine {
2519
2572
  }
2520
2573
  return `'${nestedItem.name}', ${escapeColumnName(nestedItem.selector)}`;
2521
2574
  }).filter(Boolean).join(", ")}
2522
- ))
2575
+ ))
2523
2576
  FROM
2524
2577
  "${item.subquery.table}"
2525
2578
  ${this.generateJoinStatements(req, item.subquery.joins, item.subquery.table, routeData, schema, sqlParams)}
@@ -2627,7 +2680,7 @@ var PsqlEngine = class extends SqlEngine {
2627
2680
  );
2628
2681
  const [pageResults, totalResponse] = await Promise.all([pagePromise, totalPromise]);
2629
2682
  let total = 0;
2630
- if (ObjectUtils4.isArrayWithData(totalResponse)) {
2683
+ if (ObjectUtils3.isArrayWithData(totalResponse)) {
2631
2684
  total = totalResponse[0].total;
2632
2685
  }
2633
2686
  return { data: pageResults, total };
@@ -2657,14 +2710,9 @@ var PsqlEngine = class extends SqlEngine {
2657
2710
  }
2658
2711
  let incrementSyncVersion = false;
2659
2712
  if (table.columns.find((column) => column.name === "syncVersion")) incrementSyncVersion = true;
2660
- for (const assignment of routeData.assignments) {
2661
- const column = table.columns.find((column2) => column2.name === assignment.name);
2662
- if (!column) continue;
2663
- const assignmentEscaped = escapeColumnName(assignment.name);
2664
- if (SqlUtils.convertDatabaseTypeToTypescript(column.type) === "number")
2665
- bodyNoId[assignmentEscaped] = Number(assignment.value);
2666
- else bodyNoId[assignmentEscaped] = assignment.value;
2667
- }
2713
+ (routeData.assignments || []).forEach((assignment) => {
2714
+ bodyNoId[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
2715
+ });
2668
2716
  let whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
2669
2717
  const originalWhereClause = whereClause;
2670
2718
  const originalSqlParams = [...sqlParams];
@@ -3088,6 +3136,19 @@ var TempCache = class {
3088
3136
  }
3089
3137
  };
3090
3138
 
3139
+ // src/restura/utils/utils.ts
3140
+ function sortObjectKeysAlphabetically(obj) {
3141
+ if (Array.isArray(obj)) {
3142
+ return obj.map(sortObjectKeysAlphabetically);
3143
+ } else if (obj !== null && typeof obj === "object") {
3144
+ return Object.keys(obj).sort().reduce((sorted, key) => {
3145
+ sorted[key] = sortObjectKeysAlphabetically(obj[key]);
3146
+ return sorted;
3147
+ }, {});
3148
+ }
3149
+ return obj;
3150
+ }
3151
+
3091
3152
  // src/restura/restura.ts
3092
3153
  var ResturaEngine = class {
3093
3154
  // Make public so other modules can access without re-parsing the config
@@ -3106,6 +3167,7 @@ var ResturaEngine = class {
3106
3167
  responseValidator;
3107
3168
  authenticationHandler;
3108
3169
  customTypeValidation;
3170
+ standardTypeValidation;
3109
3171
  psqlConnectionPool;
3110
3172
  psqlEngine;
3111
3173
  /**
@@ -3215,7 +3277,7 @@ var ResturaEngine = class {
3215
3277
  throw new Error("Missing restura schema file");
3216
3278
  }
3217
3279
  const schemaFileData = fs4.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
3218
- const schema = ObjectUtils5.safeParse(schemaFileData);
3280
+ const schema = ObjectUtils4.safeParse(schemaFileData);
3219
3281
  const isValid = await isSchemaValid(schema);
3220
3282
  if (!isValid) {
3221
3283
  logger.error("Schema is not valid");
@@ -3226,6 +3288,7 @@ var ResturaEngine = class {
3226
3288
  async reloadEndpoints() {
3227
3289
  this.schema = await this.getLatestFileSystemSchema();
3228
3290
  this.customTypeValidation = customTypeValidationGenerator(this.schema);
3291
+ this.standardTypeValidation = standardTypeValidationGenerator(this.schema);
3229
3292
  this.resturaRouter = express.Router();
3230
3293
  this.resetPublicEndpoints();
3231
3294
  let routeCount = 0;
@@ -3243,6 +3306,8 @@ var ResturaEngine = class {
3243
3306
  this.resturaRouter[route.method.toLowerCase()](
3244
3307
  route.path,
3245
3308
  // <-- Notice we only use path here since the baseUrl is already added to the router.
3309
+ this.attachRouteData,
3310
+ addDeprecationResponse,
3246
3311
  this.executeRouteLogic
3247
3312
  );
3248
3313
  routeCount++;
@@ -3320,12 +3385,25 @@ var ResturaEngine = class {
3320
3385
  });
3321
3386
  });
3322
3387
  }
3388
+ attachRouteData(req, _res, next) {
3389
+ try {
3390
+ req.routeData = this.getRouteData(req.method, req.baseUrl, req.path);
3391
+ next();
3392
+ } catch (e) {
3393
+ next(e);
3394
+ }
3395
+ }
3323
3396
  async executeRouteLogic(req, res, next) {
3324
3397
  try {
3325
- const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
3398
+ const routeData = req.routeData ?? this.getRouteData(req.method, req.baseUrl, req.path);
3326
3399
  this.validateAuthorization(req, routeData);
3327
3400
  await this.getMulterFilesIfAny(req, res, routeData);
3328
- requestValidator(req, routeData, this.customTypeValidation);
3401
+ requestValidator(
3402
+ req,
3403
+ routeData,
3404
+ this.customTypeValidation,
3405
+ this.standardTypeValidation
3406
+ );
3329
3407
  if (this.isCustomRoute(routeData)) {
3330
3408
  await this.runCustomRouteLogic(req, res, routeData);
3331
3409
  return;
@@ -3439,6 +3517,9 @@ __decorateClass([
3439
3517
  __decorateClass([
3440
3518
  boundMethod
3441
3519
  ], ResturaEngine.prototype, "getMulterFilesIfAny", 1);
3520
+ __decorateClass([
3521
+ boundMethod
3522
+ ], ResturaEngine.prototype, "attachRouteData", 1);
3442
3523
  __decorateClass([
3443
3524
  boundMethod
3444
3525
  ], ResturaEngine.prototype, "executeRouteLogic", 1);
@@ -3502,7 +3583,7 @@ export {
3502
3583
  filterPsqlParser_default as filterPsqlParser,
3503
3584
  insertObjectQuery,
3504
3585
  isSchemaValid,
3505
- isValueNumber2 as isValueNumber,
3586
+ isValueNumber,
3506
3587
  logger,
3507
3588
  modelGenerator,
3508
3589
  questionMarksToOrderedParams,