@restura/core 1.5.0 → 1.7.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
@@ -197,7 +197,7 @@ var EventManager = class {
197
197
  async fireInsertActions(data, triggerResult) {
198
198
  await Bluebird.map(
199
199
  this.actionHandlers.DATABASE_ROW_INSERT,
200
- ({ callback, filter }) => {
200
+ async ({ callback, filter }) => {
201
201
  if (!this.hasHandlersForEventType("DATABASE_ROW_INSERT", filter, triggerResult)) return;
202
202
  const insertData = {
203
203
  tableName: triggerResult.table,
@@ -205,7 +205,11 @@ var EventManager = class {
205
205
  insertObject: triggerResult.record,
206
206
  queryMetadata: data.queryMetadata
207
207
  };
208
- callback(insertData, data.queryMetadata);
208
+ try {
209
+ await callback(insertData, data.queryMetadata);
210
+ } catch (error) {
211
+ logger.error(`Error firing insert action for table ${triggerResult.table}`, error);
212
+ }
209
213
  },
210
214
  { concurrency: 10 }
211
215
  );
@@ -213,7 +217,7 @@ var EventManager = class {
213
217
  async fireDeleteActions(data, triggerResult) {
214
218
  await Bluebird.map(
215
219
  this.actionHandlers.DATABASE_ROW_DELETE,
216
- ({ callback, filter }) => {
220
+ async ({ callback, filter }) => {
217
221
  if (!this.hasHandlersForEventType("DATABASE_ROW_DELETE", filter, triggerResult)) return;
218
222
  const deleteData = {
219
223
  tableName: triggerResult.table,
@@ -221,7 +225,11 @@ var EventManager = class {
221
225
  deletedRow: triggerResult.previousRecord,
222
226
  queryMetadata: data.queryMetadata
223
227
  };
224
- callback(deleteData, data.queryMetadata);
228
+ try {
229
+ await callback(deleteData, data.queryMetadata);
230
+ } catch (error) {
231
+ logger.error(`Error firing delete action for table ${triggerResult.table}`, error);
232
+ }
225
233
  },
226
234
  { concurrency: 10 }
227
235
  );
@@ -229,7 +237,7 @@ var EventManager = class {
229
237
  async fireUpdateActions(data, triggerResult) {
230
238
  await Bluebird.map(
231
239
  this.actionHandlers.DATABASE_COLUMN_UPDATE,
232
- ({ callback, filter }) => {
240
+ async ({ callback, filter }) => {
233
241
  if (!this.hasHandlersForEventType("DATABASE_COLUMN_UPDATE", filter, triggerResult)) return;
234
242
  const columnChangeData = {
235
243
  tableName: triggerResult.table,
@@ -238,7 +246,11 @@ var EventManager = class {
238
246
  oldData: triggerResult.previousRecord,
239
247
  queryMetadata: data.queryMetadata
240
248
  };
241
- callback(columnChangeData, data.queryMetadata);
249
+ try {
250
+ await callback(columnChangeData, data.queryMetadata);
251
+ } catch (error) {
252
+ logger.error(`Error firing update action for table ${triggerResult.table}`, error);
253
+ }
242
254
  },
243
255
  { concurrency: 10 }
244
256
  );
@@ -1092,10 +1104,10 @@ var customApiFactory_default = customApiFactory;
1092
1104
  import fs2 from "fs";
1093
1105
  import path2, { resolve } from "path";
1094
1106
  import tmp from "tmp";
1095
- import * as TJS from "typescript-json-schema";
1107
+ import { createGenerator } from "ts-json-schema-generator";
1096
1108
 
1097
1109
  // src/restura/generators/schemaGeneratorUtils.ts
1098
- function buildRouteSchema(requestParams) {
1110
+ function buildRouteSchema(routeKey, requestParams) {
1099
1111
  const properties = {};
1100
1112
  const required = [];
1101
1113
  for (const param of requestParams) {
@@ -1105,13 +1117,19 @@ function buildRouteSchema(requestParams) {
1105
1117
  const propertySchema = buildPropertySchemaFromRequest(param);
1106
1118
  properties[param.name] = propertySchema;
1107
1119
  }
1108
- return {
1120
+ const schemaDefinition = {
1109
1121
  type: "object",
1110
1122
  properties,
1111
1123
  ...required.length > 0 && { required },
1112
- // Only include if not empty
1113
1124
  additionalProperties: false
1114
1125
  };
1126
+ return {
1127
+ $schema: "http://json-schema.org/draft-07/schema#",
1128
+ $ref: `#/definitions/${routeKey}`,
1129
+ definitions: {
1130
+ [routeKey]: schemaDefinition
1131
+ }
1132
+ };
1115
1133
  }
1116
1134
  function buildPropertySchemaFromRequest(param) {
1117
1135
  const propertySchema = {};
@@ -1215,28 +1233,31 @@ function customTypeValidationGenerator(currentSchema, ignoreGeneratedTypes = fal
1215
1233
  }).filter(Boolean);
1216
1234
  if (!customInterfaceNames) return {};
1217
1235
  const temporaryFile = tmp.fileSync({ mode: 420, prefix: "prefix-", postfix: ".ts" });
1218
- fs2.writeFileSync(temporaryFile.name, currentSchema.customTypes.join("\n"));
1219
- const compilerOptions = {
1220
- strictNullChecks: true,
1221
- skipLibCheck: true
1222
- // Needed if we are processing ES modules
1236
+ const additionalImports = ignoreGeneratedTypes ? "" : [
1237
+ `/// <reference path="${toForwardSlashPath(path2.join(restura.resturaConfig.generatedTypesPath, "restura.d.ts"))}" />`,
1238
+ `/// <reference path="${toForwardSlashPath(path2.join(restura.resturaConfig.generatedTypesPath, "models.d.ts"))}" />`,
1239
+ `/// <reference path="${toForwardSlashPath(path2.join(restura.resturaConfig.generatedTypesPath, "api.d.ts"))}" />`
1240
+ ].join("\n") + "\n";
1241
+ const typesWithExport = currentSchema.customTypes.map((type) => {
1242
+ if (!type.trim().startsWith("export ")) {
1243
+ return "export " + type;
1244
+ }
1245
+ return type;
1246
+ });
1247
+ fs2.writeFileSync(temporaryFile.name, additionalImports + typesWithExport.join("\n"));
1248
+ const config3 = {
1249
+ path: resolve(temporaryFile.name),
1250
+ tsconfig: path2.join(process.cwd(), "tsconfig.json"),
1251
+ skipTypeCheck: true
1223
1252
  };
1224
- const program = TJS.getProgramFromFiles(
1225
- [
1226
- resolve(temporaryFile.name),
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
- ]
1232
- ],
1233
- compilerOptions
1234
- );
1253
+ const generator = createGenerator(config3);
1235
1254
  customInterfaceNames.forEach((item) => {
1236
- const ddlSchema = TJS.generateSchema(program, item, {
1237
- required: true
1238
- });
1239
- schemaObject[item] = ddlSchema || {};
1255
+ try {
1256
+ const ddlSchema = generator.createSchema(item);
1257
+ schemaObject[item] = ddlSchema || {};
1258
+ } catch (error) {
1259
+ logger.error("Failed to generate schema for custom type: " + item, error);
1260
+ }
1240
1261
  });
1241
1262
  temporaryFile.removeCallback();
1242
1263
  for (const endpoint of currentSchema.endpoints) {
@@ -1244,11 +1265,14 @@ function customTypeValidationGenerator(currentSchema, ignoreGeneratedTypes = fal
1244
1265
  if (route.type !== "CUSTOM_ONE" && route.type !== "CUSTOM_ARRAY" && route.type !== "CUSTOM_PAGED") continue;
1245
1266
  if (!route.request || !Array.isArray(route.request)) continue;
1246
1267
  const routeKey = `${route.method}:${route.path}`;
1247
- schemaObject[routeKey] = buildRouteSchema(route.request);
1268
+ schemaObject[routeKey] = buildRouteSchema(routeKey, route.request);
1248
1269
  }
1249
1270
  }
1250
1271
  return schemaObject;
1251
1272
  }
1273
+ function toForwardSlashPath(path5) {
1274
+ return path5.replaceAll("\\", "/");
1275
+ }
1252
1276
 
1253
1277
  // src/restura/generators/standardTypeValidationGenerator.ts
1254
1278
  function standardTypeValidationGenerator(currentSchema) {
@@ -1259,12 +1283,18 @@ function standardTypeValidationGenerator(currentSchema) {
1259
1283
  const routeKey = `${route.method}:${route.path}`;
1260
1284
  if (!route.request || route.request.length === 0) {
1261
1285
  schemaObject[routeKey] = {
1262
- type: "object",
1263
- properties: {},
1264
- additionalProperties: false
1286
+ $schema: "http://json-schema.org/draft-07/schema#",
1287
+ $ref: `#/definitions/${routeKey}`,
1288
+ definitions: {
1289
+ [routeKey]: {
1290
+ type: "object",
1291
+ properties: {},
1292
+ additionalProperties: false
1293
+ }
1294
+ }
1265
1295
  };
1266
1296
  } else {
1267
- schemaObject[routeKey] = buildRouteSchema(route.request);
1297
+ schemaObject[routeKey] = buildRouteSchema(routeKey, route.request);
1268
1298
  }
1269
1299
  }
1270
1300
  }
@@ -1300,6 +1330,18 @@ function addApiResponseFunctions(req, res, next) {
1300
1330
  next();
1301
1331
  }
1302
1332
 
1333
+ // src/restura/middleware/addDeprecationResponse.ts
1334
+ function addDeprecationResponse(req, res, next) {
1335
+ const deprecation = req.routeData?.deprecation;
1336
+ if (deprecation) {
1337
+ const { date, message } = deprecation;
1338
+ const dateObject = new Date(date);
1339
+ res.set("Deprecation", `@${dateObject.getTime().toString()}`);
1340
+ res.set("Deprecation-Message", message ?? "This endpoint is deprecated and will be removed in the future.");
1341
+ }
1342
+ next();
1343
+ }
1344
+
1303
1345
  // src/restura/middleware/authenticateRequester.ts
1304
1346
  function authenticateRequester(applicationAuthenticateHandler) {
1305
1347
  return (req, res, next) => {
@@ -1422,6 +1464,10 @@ var routeDataBaseSchema = z3.object({
1422
1464
  name: z3.string(),
1423
1465
  description: z3.string(),
1424
1466
  path: z3.string(),
1467
+ deprecation: z3.object({
1468
+ date: z3.iso.datetime(),
1469
+ message: z3.string().optional()
1470
+ }).optional(),
1425
1471
  roles: z3.array(z3.string()),
1426
1472
  scopes: z3.array(z3.string())
1427
1473
  }).strict();
@@ -1574,7 +1620,8 @@ var indexDataSchema = z3.object({
1574
1620
  columns: z3.array(z3.string()),
1575
1621
  isUnique: z3.boolean(),
1576
1622
  isPrimaryKey: z3.boolean(),
1577
- order: z3.enum(["ASC", "DESC"])
1623
+ order: z3.enum(["ASC", "DESC"]),
1624
+ where: z3.string().optional()
1578
1625
  }).strict();
1579
1626
  var foreignKeyActionsSchema = z3.enum([
1580
1627
  "CASCADE",
@@ -1641,31 +1688,81 @@ async function isSchemaValid(schemaToCheck) {
1641
1688
 
1642
1689
  // src/restura/validators/requestValidator.ts
1643
1690
  import jsonschema from "jsonschema";
1691
+ function deepResolveSchemaRefs(schema, definitions, seen = /* @__PURE__ */ new Set()) {
1692
+ if (!schema || typeof schema !== "object") {
1693
+ return schema;
1694
+ }
1695
+ if ("$ref" in schema && typeof schema.$ref === "string") {
1696
+ const refPath = schema.$ref;
1697
+ if (refPath.startsWith("#/definitions/") && definitions) {
1698
+ const defName = refPath.substring("#/definitions/".length);
1699
+ if (seen.has(defName)) {
1700
+ return { type: "object", properties: {} };
1701
+ }
1702
+ const resolved = definitions[defName];
1703
+ if (resolved) {
1704
+ seen.add(defName);
1705
+ return deepResolveSchemaRefs(resolved, definitions, seen);
1706
+ }
1707
+ }
1708
+ return schema;
1709
+ }
1710
+ const result = {};
1711
+ for (const [key, value] of Object.entries(schema)) {
1712
+ if (key === "definitions") {
1713
+ continue;
1714
+ }
1715
+ if (value && typeof value === "object") {
1716
+ if (Array.isArray(value)) {
1717
+ result[key] = value.map(
1718
+ (item) => typeof item === "object" ? deepResolveSchemaRefs(item, definitions, new Set(seen)) : item
1719
+ );
1720
+ } else {
1721
+ result[key] = deepResolveSchemaRefs(value, definitions, new Set(seen));
1722
+ }
1723
+ } else {
1724
+ result[key] = value;
1725
+ }
1726
+ }
1727
+ return result;
1728
+ }
1729
+ function resolveSchemaRef(schema, definitions) {
1730
+ return deepResolveSchemaRefs(schema, definitions);
1731
+ }
1644
1732
  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") {
1733
+ const routeKey = `${routeData.method}:${routeData.path}`;
1734
+ const isCustom = routeData.type === "CUSTOM_ONE" || routeData.type === "CUSTOM_ARRAY" || routeData.type === "CUSTOM_PAGED";
1735
+ const isStandard = routeData.type === "ONE" || routeData.type === "ARRAY" || routeData.type === "PAGED";
1736
+ if (!isStandard && !isCustom) {
1737
+ throw new RsError("BAD_REQUEST", `Invalid route type: ${routeData.type}`);
1738
+ }
1739
+ if (isCustom) {
1653
1740
  if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
1654
1741
  if (!routeData.requestType && !routeData.request)
1655
1742
  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 = {
1659
- ...currentInterface,
1660
- additionalProperties: false
1661
- };
1662
- } else {
1663
- throw new RsError("BAD_REQUEST", `Invalid route type: ${routeData.type}`);
1664
1743
  }
1744
+ const schemaKey = isCustom ? routeData.requestType || routeKey : routeKey;
1745
+ if (!schemaKey) throw new RsError("BAD_REQUEST", `No schema key defined for request: ${routeKey}.`);
1746
+ const schemaDictionary = isCustom ? customValidationSchema : standardValidationSchema;
1747
+ const schemaRoot = schemaDictionary[schemaKey];
1748
+ if (!schemaRoot) {
1749
+ const requestType = isCustom ? "custom" : "standard";
1750
+ throw new RsError("BAD_REQUEST", `No schema found for ${requestType} request: ${schemaKey}.`);
1751
+ }
1752
+ const schemaForValidation = schemaRoot;
1753
+ const schemaDefinitions = schemaRoot.definitions;
1754
+ const rawInterface = schemaRoot.definitions[schemaKey];
1755
+ const schemaForCoercion = isCustom ? resolveSchemaRef(rawInterface, schemaDefinitions) : rawInterface;
1665
1756
  const requestData = getRequestData(req, schemaForCoercion);
1666
1757
  req.data = requestData;
1667
1758
  const validator = new jsonschema.Validator();
1668
- const executeValidation = validator.validate(req.data, schemaForCoercion);
1759
+ if (schemaDefinitions) {
1760
+ for (const [defName, defSchema] of Object.entries(schemaDefinitions)) {
1761
+ validator.addSchema(defSchema, `/definitions/${defName}`);
1762
+ }
1763
+ }
1764
+ const resolvedSchema = resolveSchemaRef(schemaForValidation, schemaDefinitions);
1765
+ const executeValidation = validator.validate(req.data, resolvedSchema);
1669
1766
  if (!executeValidation.valid) {
1670
1767
  const errorMessages = executeValidation.errors.map((err) => {
1671
1768
  const property = err.property.replace("instance.", "");
@@ -1785,7 +1882,6 @@ import pg from "pg";
1785
1882
 
1786
1883
  // src/restura/sql/PsqlConnection.ts
1787
1884
  import crypto from "crypto";
1788
- import format2 from "pg-format";
1789
1885
  import { format as sqlFormat } from "sql-formatter";
1790
1886
  import { z as z5 } from "zod";
1791
1887
 
@@ -1860,6 +1956,13 @@ function SQL(strings, ...values) {
1860
1956
  });
1861
1957
  return query;
1862
1958
  }
1959
+ function toSqlLiteral(value) {
1960
+ if (value === null || value === void 0) return "NULL";
1961
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "NULL";
1962
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
1963
+ if (Array.isArray(value)) return `ARRAY[${value.map((v) => toSqlLiteral(v)).join(", ")}]`;
1964
+ return format.literal(value);
1965
+ }
1863
1966
 
1864
1967
  // src/restura/sql/PsqlConnection.ts
1865
1968
  var PsqlConnection = class {
@@ -1938,21 +2041,11 @@ var PsqlConnection = class {
1938
2041
  }
1939
2042
  logSqlStatement(query, options, queryMetadata, startTime, prefix = "") {
1940
2043
  if (logger.level !== "trace" && logger.level !== "silly") return;
1941
- let sqlStatement = "";
1942
- if (options.length === 0) {
1943
- sqlStatement = query;
1944
- } else {
1945
- sqlStatement = query.replace(/\$\d+/g, (match) => {
1946
- const paramIndex = parseInt(match.substring(1)) - 1;
1947
- if (paramIndex < 0 || paramIndex >= options.length) {
1948
- return "INVALID_PARAM_INDEX";
1949
- }
1950
- const value = options[paramIndex];
1951
- if (typeof value === "number") return value.toString();
1952
- if (typeof value === "boolean") return value.toString();
1953
- return format2.literal(value);
1954
- });
1955
- }
2044
+ const sqlStatement = query.replace(/\$(\d+)/g, (_, num) => {
2045
+ const paramIndex = parseInt(num) - 1;
2046
+ if (paramIndex >= options.length) return "INVALID_PARAM_INDEX";
2047
+ return toSqlLiteral(options[paramIndex]);
2048
+ });
1956
2049
  const formattedSql = sqlFormat(sqlStatement, {
1957
2050
  language: "postgresql",
1958
2051
  linesBetweenQueries: 2,
@@ -1969,8 +2062,7 @@ var PsqlConnection = class {
1969
2062
  if ("isSystemUser" in queryMetadata && queryMetadata.isSystemUser) initiator = "SYSTEM";
1970
2063
  logger.silly(`${prefix}query by ${initiator}, Query ->
1971
2064
  ${formattedSql}`, {
1972
- duration: `${durationMs.toFixed(2)}ms`,
1973
- _meta: { durationNs: nanoseconds }
2065
+ durationMs
1974
2066
  });
1975
2067
  }
1976
2068
  };
@@ -2132,124 +2224,246 @@ var SqlEngine = class {
2132
2224
 
2133
2225
  // src/restura/sql/filterPsqlParser.ts
2134
2226
  import peg from "pegjs";
2135
- var filterSqlGrammar = `
2136
- {
2137
- // ported from pg-format but intentionally will add double quotes to every column
2138
- function quoteSqlIdentity(value) {
2139
- if (value === undefined || value === null) {
2140
- throw new Error('SQL identifier cannot be null or undefined');
2141
- } else if (value === false) {
2142
- return '"f"';
2143
- } else if (value === true) {
2144
- return '"t"';
2145
- } else if (value instanceof Date) {
2146
- // return '"' + formatDate(value.toISOString()) + '"';
2147
- } else if (value instanceof Buffer) {
2148
- throw new Error('SQL identifier cannot be a buffer');
2149
- } else if (Array.isArray(value) === true) {
2150
- var temp = [];
2151
- for (var i = 0; i < value.length; i++) {
2152
- if (Array.isArray(value[i]) === true) {
2153
- throw new Error('Nested array to grouped list conversion is not supported for SQL identifier');
2227
+ var initializers = `
2228
+ // Quotes a SQL identifier (column/table name) with double quotes, escaping any embedded quotes
2229
+ function quoteSqlIdentity(value) {
2230
+ return '"' + value.replace(/"/g, '""') + '"';
2231
+ }
2232
+
2233
+ // Unescape special characters in values: \\, -> , | \\| -> | | \\\\ -> \\
2234
+ function unescapeValue(str) {
2235
+ var result = '';
2236
+ for (var i = 0; i < str.length; i++) {
2237
+ if (str[i] === '\\\\' && i + 1 < str.length) {
2238
+ var next = str[i + 1];
2239
+ if (next === ',' || next === '|' || next === '\\\\') {
2240
+ result += next;
2241
+ i++;
2242
+ } else {
2243
+ result += str[i];
2244
+ }
2154
2245
  } else {
2155
- // temp.push(quoteIdent(value[i]));
2246
+ result += str[i];
2156
2247
  }
2157
2248
  }
2158
- return temp.toString();
2159
- } else if (value === Object(value)) {
2160
- throw new Error('SQL identifier cannot be an object');
2249
+ return result;
2250
+ }
2251
+
2252
+ // Split pipe-separated values respecting escaped pipes
2253
+ function splitPipeValues(str) {
2254
+ var values = [];
2255
+ var current = '';
2256
+ for (var i = 0; i < str.length; i++) {
2257
+ if (str[i] === '\\\\' && i + 1 < str.length && str[i + 1] === '|') {
2258
+ current += '|';
2259
+ i++;
2260
+ } else if (str[i] === '|') {
2261
+ values.push(unescapeValue(current));
2262
+ current = '';
2263
+ } else {
2264
+ current += str[i];
2265
+ }
2266
+ }
2267
+ if (current.length > 0) {
2268
+ values.push(unescapeValue(current));
2269
+ }
2270
+ return values;
2161
2271
  }
2162
2272
 
2163
- var ident = value.toString().slice(0); // create copy
2164
-
2165
- // do not quote a valid, unquoted identifier
2166
- // if (/^[a-z_][a-z0-9_$]*$/.test(ident) === true && isReserved(ident) === false) {
2167
- // return ident;
2168
- // }
2169
-
2170
- var quoted = '"';
2273
+ // Build SQL IN clause from pipe-separated values
2274
+ function buildInClause(column, rawValue) {
2275
+ var values = splitPipeValues(rawValue);
2276
+ var literals = values.map(function(v) { return formatValue(v); });
2277
+ return column + ' IN (' + literals.join(', ') + ')';
2278
+ }
2171
2279
 
2172
- for (var i = 0; i < ident.length; i++) {
2173
- var c = ident[i];
2174
- if (c === '"') {
2175
- quoted += c + c;
2176
- } else {
2177
- quoted += c;
2280
+ // Check if a value is numeric and format appropriately
2281
+ function formatValue(value) {
2282
+ // Check if the value is a valid number (integer or decimal)
2283
+ if (/^-?\\d+(\\.\\d+)?$/.test(value)) {
2284
+ return value; // Return as-is without quotes
2178
2285
  }
2286
+ return format.literal(value);
2179
2287
  }
2180
-
2181
- quoted += '"';
2182
-
2183
- return quoted;
2184
- };
2288
+ `;
2289
+ var entryGrammar = `
2290
+ {
2291
+ ${initializers}
2185
2292
  }
2186
2293
 
2187
- start = expressionList
2188
-
2189
- _ = [ \\t\\r\\n]* // Matches spaces, tabs, and line breaks
2190
-
2191
- expressionList =
2192
- leftExpression:expression _ operator:operator _ rightExpression:expressionList
2193
- { return \`\${leftExpression} \${operator} \${rightExpression}\`;}
2194
- / expression
2195
-
2196
- expression =
2197
- negate:negate? _ "(" _ "column" _ ":" column:column _ ","? _ value:value? ","? _ type:type? _ ")"_
2198
- {return \`\${negate? " NOT " : ""}(\${type? type(column, value) : \`\${column} = \${format.literal(value)}\`})\`;}
2294
+ Start
2295
+ = sql:StartOld { return { sql: sql, usedOldSyntax: true }; }
2296
+ / sql:StartNew { return { sql: sql, usedOldSyntax: false }; }
2297
+ `;
2298
+ var oldGrammar = `
2299
+ StartOld
2300
+ = OldExpressionList
2301
+ _
2302
+ = [ \\t\\r\\n]* // Matches spaces, tabs, and line breaks
2303
+
2304
+ OldExpressionList
2305
+ = leftExpression:OldExpression _ operator:OldOperator _ rightExpression:OldExpressionList
2306
+ { return \`\${leftExpression} \${operator} \${rightExpression}\`;}
2307
+ / OldExpression
2308
+
2309
+ OldExpression
2310
+ = negate:OldNegate? _ "(" _ "column" _ ":" column:OldColumn _ ","? _ value:OldValue? ","? _ type:OldType? _ ")"_
2311
+ {return \`\${negate? " NOT " : ""}(\${type ? type(column, value) : (value == null ? \`\${column} IS NULL\` : \`\${column} = \${formatValue(value)}\`)})\`;}
2199
2312
  /
2200
- negate:negate?"("expression:expressionList")" { return \`\${negate? " NOT " : ""}(\${expression})\`; }
2313
+ negate:OldNegate?"("expression:OldExpressionList")" { return \`\${negate? " NOT " : ""}(\${expression})\`; }
2201
2314
 
2202
- negate = "!"
2315
+ OldNegate
2316
+ = "!"
2203
2317
 
2204
- operator = "and"i / "or"i
2205
-
2206
-
2207
- column = first:text rest:("." text)* {
2208
- const partsArray = [first];
2209
- if (rest && rest.length > 0) {
2210
- partsArray.push(...rest.map(item => item[1]));
2211
- }
2212
-
2213
- if (partsArray.length > 3) {
2214
- throw new SyntaxError('Column path cannot have more than 3 parts (table.column.jsonField)');
2215
- }
2216
-
2217
- if (partsArray.length === 1) {
2218
- return quoteSqlIdentity(partsArray[0]);
2219
- }
2220
- const tableName = quoteSqlIdentity(partsArray[0]);
2221
-
2222
- // If we only have two parts (table.column), use regular dot notation
2223
- if (partsArray.length === 2) {
2224
- return tableName + "." + quoteSqlIdentity(partsArray[1]);
2318
+ OldOperator
2319
+ = "and"i / "or"i
2320
+
2321
+ OldColumn
2322
+ = first:OldText rest:("." OldText)* {
2323
+ const partsArray = [first];
2324
+ if (rest && rest.length > 0) {
2325
+ partsArray.push(...rest.map(item => item[1]));
2326
+ }
2327
+
2328
+ if (partsArray.length > 3) {
2329
+ throw new SyntaxError('Column path cannot have more than 3 parts (table.column.jsonField)');
2330
+ }
2331
+
2332
+ if (partsArray.length === 1) {
2333
+ return quoteSqlIdentity(partsArray[0]);
2334
+ }
2335
+ const tableName = quoteSqlIdentity(partsArray[0]);
2336
+
2337
+ // If we only have two parts (table.column), use regular dot notation
2338
+ if (partsArray.length === 2) {
2339
+ return tableName + "." + quoteSqlIdentity(partsArray[1]);
2340
+ }
2341
+
2342
+ // For JSON paths (more than 2 parts), first part is a column, last part uses ->>
2343
+ const jsonColumn = quoteSqlIdentity(partsArray[1]);
2344
+ const lastPart = partsArray[partsArray.length - 1];
2345
+ const escapedLast = lastPart.replace(/'/g, "''");
2346
+ const result = tableName + "." + jsonColumn + "->>'" + escapedLast + "'";
2347
+ return result;
2225
2348
  }
2226
-
2227
- // For JSON paths (more than 2 parts), first part is a column, last part uses ->>
2228
- const jsonColumn = quoteSqlIdentity(partsArray[1]);
2229
- const lastPart = partsArray[partsArray.length - 1];
2230
- const result = tableName + "." + jsonColumn + "->>'" + lastPart + "'";
2231
- return result;
2232
- }
2233
-
2234
- text = text:[a-z0-9 \\t\\r\\n\\-_:@']i+ { return text.join(""); }
2235
2349
 
2350
+ OldText
2351
+ = text:[a-z0-9 \\t\\r\\n\\-_:@']i+ {
2352
+ return text.join("");
2353
+ }
2236
2354
 
2237
- type = "type" _ ":" _ type:typeString { return type; }
2238
- typeString = text:"startsWith" { return function(column, value) { return \`\${column}::text ILIKE '\${format.literal(value).slice(1,-1)}%'\`; } } /
2239
- text:"endsWith" { return function(column, value) { return \`\${column}::text ILIKE '%\${format.literal(value).slice(1,-1)}'\`; } } /
2240
- text:"contains" { return function(column, value) { return \`\${column}::text ILIKE '%\${format.literal(value).slice(1,-1)}%'\`; } } /
2241
- text:"exact" { return function(column, value) { return \`\${column} = '\${format.literal(value).slice(1,-1)}'\`; } } /
2242
- text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= '\${format.literal(value).slice(1,-1)}'\`; } } /
2243
- text:"greaterThan" { return function(column, value) { return \`\${column} > '\${format.literal(value).slice(1,-1)}'\`; } } /
2244
- text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${format.literal(value).slice(1,-1)}'\`; } } /
2245
- text:"lessThan" { return function(column, value) { return \`\${column} < '\${format.literal(value).slice(1,-1)}'\`; } } /
2246
- text:"isNull" { return function(column, value) { return \`isNull(\${column})\`; } }
2247
-
2248
- value = "value" _ ":" value:text { return value; }
2355
+ OldType
2356
+ = "type" _ ":" _ type:OldTypeString {
2357
+ return type;
2358
+ }
2249
2359
 
2360
+ OldTypeString
2361
+ = text:"startsWith" { return function(column, value) { return \`\${column}::text ILIKE '\${format.literal(value).slice(1,-1)}%'\`; } }
2362
+ / text:"endsWith" { return function(column, value) { return \`\${column}::text ILIKE '%\${format.literal(value).slice(1,-1)}'\`; } }
2363
+ / text:"contains" { return function(column, value) { return \`\${column}::text ILIKE '%\${format.literal(value).slice(1,-1)}%'\`; } }
2364
+ / text:"exact" { return function(column, value) { return \`\${column} = \${formatValue(value)}\`; } }
2365
+ / text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= \${formatValue(value)}\`; } }
2366
+ / text:"greaterThan" { return function(column, value) { return \`\${column} > \${formatValue(value)}\`; } }
2367
+ / text:"lessThanEqual" { return function(column, value) { return \`\${column} <= \${formatValue(value)}\`; } }
2368
+ / text:"lessThan" { return function(column, value) { return \`\${column} < \${formatValue(value)}\`; } }
2369
+ / text:"isNull" { return function(column, value) { return \`\${column} IS NULL\`; } }
2250
2370
 
2371
+ OldValue
2372
+ = "value" _ ":" value:OldText {
2373
+ return value;
2374
+ }
2251
2375
  `;
2252
- var filterPsqlParser = peg.generate(filterSqlGrammar, {
2376
+ var newGrammar = `
2377
+ StartNew
2378
+ = ExpressionList
2379
+
2380
+ ExpressionList
2381
+ = left:Expression _ op:("and"i / "or"i) _ right:ExpressionList
2382
+ { return left + ' ' + op.toUpperCase() + ' ' + right; }
2383
+ / Expression
2384
+
2385
+ Expression
2386
+ = negate:"!"? _ "(" _ inner:SimpleExprList _ ")" _
2387
+ { return (negate ? 'NOT ' : '') + '(' + inner + ')'; }
2388
+ / SimpleExpr
2389
+
2390
+ SimpleExprList
2391
+ = left:SimpleExpr _ op:("and"i / "or"i) _ right:SimpleExprList
2392
+ { return left + ' ' + op.toUpperCase() + ' ' + right; }
2393
+ / SimpleExpr
2394
+
2395
+ SimpleExpr
2396
+ = negate:"!"? _ "(" _ col:Column _ "," _ op:OperatorWithValue _ ")" _
2397
+ { return (negate ? 'NOT ' : '') + '(' + op(col) + ')'; }
2398
+ / negate:"!"? _ "(" _ col:Column _ "," _ op:NullOperator _ ")" _
2399
+ { return (negate ? 'NOT ' : '') + '(' + op(col) + ')'; }
2400
+ / negate:"!"? _ "(" _ col:Column _ "," _ val:Value _ ")" _
2401
+ { return (negate ? 'NOT ' : '') + '(' + col + ' = ' + formatValue(unescapeValue(val)) + ')'; }
2402
+
2403
+ Column
2404
+ = first:ColPart rest:("." ColPart)* {
2405
+ const partsArray = [first];
2406
+ if (rest && rest.length > 0) {
2407
+ partsArray.push(...rest.map(item => item[1]));
2408
+ }
2409
+
2410
+ if (partsArray.length > 3) {
2411
+ throw new SyntaxError('Column path cannot have more than 3 parts (table.column.jsonField)');
2412
+ }
2413
+
2414
+ if (partsArray.length === 1) {
2415
+ return quoteSqlIdentity(partsArray[0]);
2416
+ }
2417
+ const tableName = quoteSqlIdentity(partsArray[0]);
2418
+
2419
+ if (partsArray.length === 2) {
2420
+ return tableName + '.' + quoteSqlIdentity(partsArray[1]);
2421
+ }
2422
+
2423
+ const jsonColumn = quoteSqlIdentity(partsArray[1]);
2424
+ const lastPart = partsArray[partsArray.length - 1];
2425
+ const escapedLast = lastPart.replace(/'/g, "''");
2426
+ return tableName + '.' + jsonColumn + "->>'" + escapedLast + "'";
2427
+ }
2428
+
2429
+ ColPart
2430
+ = chars:[a-zA-Z0-9_]+ { return chars.join(''); }
2431
+
2432
+ NullOperator
2433
+ = "notnull"i { return function(col) { return col + ' IS NOT NULL'; }; }
2434
+ / "null"i { return function(col) { return col + ' IS NULL'; }; }
2435
+
2436
+ OperatorWithValue
2437
+ = "in"i _ "," _ val:ValueWithPipes { return function(col) { return buildInClause(col, val); }; }
2438
+ / "ne"i _ "," _ val:Value { return function(col) { return col + ' <> ' + formatValue(unescapeValue(val)); }; }
2439
+ / "gte"i _ "," _ val:Value { return function(col) { return col + ' >= ' + formatValue(unescapeValue(val)); }; }
2440
+ / "gt"i _ "," _ val:Value { return function(col) { return col + ' > ' + formatValue(unescapeValue(val)); }; }
2441
+ / "lte"i _ "," _ val:Value { return function(col) { return col + ' <= ' + formatValue(unescapeValue(val)); }; }
2442
+ / "lt"i _ "," _ val:Value { return function(col) { return col + ' < ' + formatValue(unescapeValue(val)); }; }
2443
+ / "has"i _ "," _ val:Value { return function(col) { return col + '::text ILIKE ' + format.literal('%' + unescapeValue(val) + '%'); }; }
2444
+ / "sw"i _ "," _ val:Value { return function(col) { return col + '::text ILIKE ' + format.literal(unescapeValue(val) + '%'); }; }
2445
+ / "ew"i _ "," _ val:Value { return function(col) { return col + '::text ILIKE ' + format.literal('%' + unescapeValue(val)); }; }
2446
+
2447
+ Value
2448
+ = chars:ValueChar+ { return chars.join(''); }
2449
+
2450
+ ValueChar
2451
+ = "\\\\\\\\" { return '\\\\\\\\'; }
2452
+ / "\\\\," { return '\\\\,'; }
2453
+ / "\\\\|" { return '\\\\|'; }
2454
+ / [^,()\\\\|]
2455
+
2456
+ ValueWithPipes
2457
+ = chars:ValueWithPipesChar+ { return chars.join(''); }
2458
+
2459
+ ValueWithPipesChar
2460
+ = "\\\\\\\\" { return '\\\\\\\\'; }
2461
+ / "\\\\," { return '\\\\,'; }
2462
+ / "\\\\|" { return '\\\\|'; }
2463
+ / [^,()\\\\]
2464
+ `;
2465
+ var fullGrammar = entryGrammar + oldGrammar + newGrammar;
2466
+ var filterPsqlParser = peg.generate(fullGrammar, {
2253
2467
  format: "commonjs",
2254
2468
  dependencies: { format: "pg-format" }
2255
2469
  });
@@ -2410,11 +2624,11 @@ var PsqlEngine = class extends SqlEngine {
2410
2624
  if (!index.isPrimaryKey) {
2411
2625
  let unique = " ";
2412
2626
  if (index.isUnique) unique = "UNIQUE ";
2413
- indexes.push(
2414
- ` CREATE ${unique}INDEX "${index.name}" ON "${table.name}" (${index.columns.map((item) => {
2415
- return `"${item}" ${index.order}`;
2416
- }).join(", ")});`
2417
- );
2627
+ let indexSQL = ` CREATE ${unique}INDEX "${index.name}" ON "${table.name}"`;
2628
+ indexSQL += ` (${index.columns.map((item) => `"${item}" ${index.order}`).join(", ")})`;
2629
+ indexSQL += index.where ? ` WHERE ${index.where}` : "";
2630
+ indexSQL += ";";
2631
+ indexes.push(indexSQL);
2418
2632
  }
2419
2633
  }
2420
2634
  sql += "\n);";
@@ -2856,7 +3070,13 @@ DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
2856
3070
  throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
2857
3071
  return data[requestParam.name]?.toString() || "";
2858
3072
  });
2859
- statement = filterPsqlParser_default.parse(statement);
3073
+ const parseResult = filterPsqlParser_default.parse(statement);
3074
+ if (parseResult.usedOldSyntax) {
3075
+ logger.warn(
3076
+ `Deprecated filter syntax detected in route "${routeData.name}" (${routeData.path}). Please migrate to the new filter syntax.`
3077
+ );
3078
+ }
3079
+ statement = parseResult.sql;
2860
3080
  if (whereClause.startsWith("WHERE")) {
2861
3081
  whereClause += ` AND (${statement})
2862
3082
  `;
@@ -3290,6 +3510,8 @@ var ResturaEngine = class {
3290
3510
  this.resturaRouter[route.method.toLowerCase()](
3291
3511
  route.path,
3292
3512
  // <-- Notice we only use path here since the baseUrl is already added to the router.
3513
+ this.attachRouteData,
3514
+ addDeprecationResponse,
3293
3515
  this.executeRouteLogic
3294
3516
  );
3295
3517
  routeCount++;
@@ -3367,9 +3589,17 @@ var ResturaEngine = class {
3367
3589
  });
3368
3590
  });
3369
3591
  }
3592
+ attachRouteData(req, _res, next) {
3593
+ try {
3594
+ req.routeData = this.getRouteData(req.method, req.baseUrl, req.path);
3595
+ next();
3596
+ } catch (e) {
3597
+ next(e);
3598
+ }
3599
+ }
3370
3600
  async executeRouteLogic(req, res, next) {
3371
3601
  try {
3372
- const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
3602
+ const routeData = req.routeData ?? this.getRouteData(req.method, req.baseUrl, req.path);
3373
3603
  this.validateAuthorization(req, routeData);
3374
3604
  await this.getMulterFilesIfAny(req, res, routeData);
3375
3605
  requestValidator(
@@ -3491,6 +3721,9 @@ __decorateClass([
3491
3721
  __decorateClass([
3492
3722
  boundMethod
3493
3723
  ], ResturaEngine.prototype, "getMulterFilesIfAny", 1);
3724
+ __decorateClass([
3725
+ boundMethod
3726
+ ], ResturaEngine.prototype, "attachRouteData", 1);
3494
3727
  __decorateClass([
3495
3728
  boundMethod
3496
3729
  ], ResturaEngine.prototype, "executeRouteLogic", 1);
@@ -3561,6 +3794,7 @@ export {
3561
3794
  restura,
3562
3795
  resturaGlobalTypesGenerator,
3563
3796
  resturaSchema,
3797
+ toSqlLiteral,
3564
3798
  updateObjectQuery
3565
3799
  };
3566
3800
  //# sourceMappingURL=index.js.map