@restura/core 1.6.0 → 1.8.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
@@ -14,6 +14,94 @@ import { config } from "@restura/internal";
14
14
  import pino from "pino";
15
15
  import pinoPretty from "pino-pretty";
16
16
 
17
+ // src/restura/RsError.ts
18
+ var HtmlStatusCodes = /* @__PURE__ */ ((HtmlStatusCodes2) => {
19
+ HtmlStatusCodes2[HtmlStatusCodes2["BAD_REQUEST"] = 400] = "BAD_REQUEST";
20
+ HtmlStatusCodes2[HtmlStatusCodes2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
21
+ HtmlStatusCodes2[HtmlStatusCodes2["PAYMENT_REQUIRED"] = 402] = "PAYMENT_REQUIRED";
22
+ HtmlStatusCodes2[HtmlStatusCodes2["FORBIDDEN"] = 403] = "FORBIDDEN";
23
+ HtmlStatusCodes2[HtmlStatusCodes2["NOT_FOUND"] = 404] = "NOT_FOUND";
24
+ HtmlStatusCodes2[HtmlStatusCodes2["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
25
+ HtmlStatusCodes2[HtmlStatusCodes2["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT";
26
+ HtmlStatusCodes2[HtmlStatusCodes2["CONFLICT"] = 409] = "CONFLICT";
27
+ HtmlStatusCodes2[HtmlStatusCodes2["GONE"] = 410] = "GONE";
28
+ HtmlStatusCodes2[HtmlStatusCodes2["PAYLOAD_TOO_LARGE"] = 413] = "PAYLOAD_TOO_LARGE";
29
+ HtmlStatusCodes2[HtmlStatusCodes2["UNSUPPORTED_MEDIA_TYPE"] = 415] = "UNSUPPORTED_MEDIA_TYPE";
30
+ HtmlStatusCodes2[HtmlStatusCodes2["UPGRADE_REQUIRED"] = 426] = "UPGRADE_REQUIRED";
31
+ HtmlStatusCodes2[HtmlStatusCodes2["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
32
+ HtmlStatusCodes2[HtmlStatusCodes2["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
33
+ HtmlStatusCodes2[HtmlStatusCodes2["SERVER_ERROR"] = 500] = "SERVER_ERROR";
34
+ HtmlStatusCodes2[HtmlStatusCodes2["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED";
35
+ HtmlStatusCodes2[HtmlStatusCodes2["BAD_GATEWAY"] = 502] = "BAD_GATEWAY";
36
+ HtmlStatusCodes2[HtmlStatusCodes2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
37
+ HtmlStatusCodes2[HtmlStatusCodes2["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT";
38
+ HtmlStatusCodes2[HtmlStatusCodes2["NETWORK_CONNECT_TIMEOUT"] = 599] = "NETWORK_CONNECT_TIMEOUT";
39
+ return HtmlStatusCodes2;
40
+ })(HtmlStatusCodes || {});
41
+ var RsError = class _RsError extends Error {
42
+ err;
43
+ msg;
44
+ options;
45
+ status;
46
+ constructor(errCode, message, options) {
47
+ super(message);
48
+ this.name = "RsError";
49
+ this.err = errCode;
50
+ this.msg = message || "";
51
+ this.status = _RsError.htmlStatus(errCode);
52
+ this.options = options;
53
+ }
54
+ toJSON() {
55
+ return {
56
+ type: this.name,
57
+ err: this.err,
58
+ message: this.message,
59
+ msg: this.msg,
60
+ status: this.status ?? 500,
61
+ stack: this.stack ?? "",
62
+ options: this.options
63
+ };
64
+ }
65
+ static htmlStatus(code) {
66
+ return htmlStatusMap[code];
67
+ }
68
+ static isRsError(error) {
69
+ return error instanceof _RsError;
70
+ }
71
+ };
72
+ var htmlStatusMap = {
73
+ // 1:1 mappings to HTTP status codes
74
+ BAD_REQUEST: 400 /* BAD_REQUEST */,
75
+ UNAUTHORIZED: 401 /* UNAUTHORIZED */,
76
+ PAYMENT_REQUIRED: 402 /* PAYMENT_REQUIRED */,
77
+ FORBIDDEN: 403 /* FORBIDDEN */,
78
+ NOT_FOUND: 404 /* NOT_FOUND */,
79
+ METHOD_NOT_ALLOWED: 405 /* METHOD_NOT_ALLOWED */,
80
+ REQUEST_TIMEOUT: 408 /* REQUEST_TIMEOUT */,
81
+ CONFLICT: 409 /* CONFLICT */,
82
+ GONE: 410 /* GONE */,
83
+ PAYLOAD_TOO_LARGE: 413 /* PAYLOAD_TOO_LARGE */,
84
+ UNSUPPORTED_MEDIA_TYPE: 415 /* UNSUPPORTED_MEDIA_TYPE */,
85
+ UPGRADE_REQUIRED: 426 /* UPGRADE_REQUIRED */,
86
+ UNPROCESSABLE_ENTITY: 422 /* UNPROCESSABLE_ENTITY */,
87
+ TOO_MANY_REQUESTS: 429 /* TOO_MANY_REQUESTS */,
88
+ SERVER_ERROR: 500 /* SERVER_ERROR */,
89
+ NOT_IMPLEMENTED: 501 /* NOT_IMPLEMENTED */,
90
+ BAD_GATEWAY: 502 /* BAD_GATEWAY */,
91
+ SERVICE_UNAVAILABLE: 503 /* SERVICE_UNAVAILABLE */,
92
+ GATEWAY_TIMEOUT: 504 /* GATEWAY_TIMEOUT */,
93
+ NETWORK_CONNECT_TIMEOUT: 599 /* NETWORK_CONNECT_TIMEOUT */,
94
+ // Specific business errors mapped to appropriate HTTP codes
95
+ UNKNOWN_ERROR: 500 /* SERVER_ERROR */,
96
+ RATE_LIMIT_EXCEEDED: 429 /* TOO_MANY_REQUESTS */,
97
+ INVALID_TOKEN: 401 /* UNAUTHORIZED */,
98
+ INCORRECT_EMAIL_OR_PASSWORD: 401 /* UNAUTHORIZED */,
99
+ DUPLICATE: 409 /* CONFLICT */,
100
+ CONNECTION_ERROR: 504 /* GATEWAY_TIMEOUT */,
101
+ SCHEMA_ERROR: 500 /* SERVER_ERROR */,
102
+ DATABASE_ERROR: 500 /* SERVER_ERROR */
103
+ };
104
+
17
105
  // src/logger/loggerConfigSchema.ts
18
106
  import { z } from "zod";
19
107
  var loggerConfigSchema = z.object({
@@ -68,6 +156,9 @@ var defaultSerializer = (error) => {
68
156
  responseData: err.response?.data
69
157
  };
70
158
  }
159
+ if (RsError.isRsError(error)) {
160
+ return error.toJSON();
161
+ }
71
162
  return baseSerializer(error);
72
163
  };
73
164
  var errorSerializer = (() => {
@@ -197,7 +288,7 @@ var EventManager = class {
197
288
  async fireInsertActions(data, triggerResult) {
198
289
  await Bluebird.map(
199
290
  this.actionHandlers.DATABASE_ROW_INSERT,
200
- ({ callback, filter }) => {
291
+ async ({ callback, filter }) => {
201
292
  if (!this.hasHandlersForEventType("DATABASE_ROW_INSERT", filter, triggerResult)) return;
202
293
  const insertData = {
203
294
  tableName: triggerResult.table,
@@ -205,7 +296,11 @@ var EventManager = class {
205
296
  insertObject: triggerResult.record,
206
297
  queryMetadata: data.queryMetadata
207
298
  };
208
- callback(insertData, data.queryMetadata);
299
+ try {
300
+ await callback(insertData, data.queryMetadata);
301
+ } catch (error) {
302
+ logger.error(`Error firing insert action for table ${triggerResult.table}`, error);
303
+ }
209
304
  },
210
305
  { concurrency: 10 }
211
306
  );
@@ -213,7 +308,7 @@ var EventManager = class {
213
308
  async fireDeleteActions(data, triggerResult) {
214
309
  await Bluebird.map(
215
310
  this.actionHandlers.DATABASE_ROW_DELETE,
216
- ({ callback, filter }) => {
311
+ async ({ callback, filter }) => {
217
312
  if (!this.hasHandlersForEventType("DATABASE_ROW_DELETE", filter, triggerResult)) return;
218
313
  const deleteData = {
219
314
  tableName: triggerResult.table,
@@ -221,7 +316,11 @@ var EventManager = class {
221
316
  deletedRow: triggerResult.previousRecord,
222
317
  queryMetadata: data.queryMetadata
223
318
  };
224
- callback(deleteData, data.queryMetadata);
319
+ try {
320
+ await callback(deleteData, data.queryMetadata);
321
+ } catch (error) {
322
+ logger.error(`Error firing delete action for table ${triggerResult.table}`, error);
323
+ }
225
324
  },
226
325
  { concurrency: 10 }
227
326
  );
@@ -229,7 +328,7 @@ var EventManager = class {
229
328
  async fireUpdateActions(data, triggerResult) {
230
329
  await Bluebird.map(
231
330
  this.actionHandlers.DATABASE_COLUMN_UPDATE,
232
- ({ callback, filter }) => {
331
+ async ({ callback, filter }) => {
233
332
  if (!this.hasHandlersForEventType("DATABASE_COLUMN_UPDATE", filter, triggerResult)) return;
234
333
  const columnChangeData = {
235
334
  tableName: triggerResult.table,
@@ -238,7 +337,11 @@ var EventManager = class {
238
337
  oldData: triggerResult.previousRecord,
239
338
  queryMetadata: data.queryMetadata
240
339
  };
241
- callback(columnChangeData, data.queryMetadata);
340
+ try {
341
+ await callback(columnChangeData, data.queryMetadata);
342
+ } catch (error) {
343
+ logger.error(`Error firing update action for table ${triggerResult.table}`, error);
344
+ }
242
345
  },
243
346
  { concurrency: 10 }
244
347
  );
@@ -320,83 +423,6 @@ var SqlUtils = class _SqlUtils {
320
423
  }
321
424
  };
322
425
 
323
- // src/restura/RsError.ts
324
- var HtmlStatusCodes = /* @__PURE__ */ ((HtmlStatusCodes2) => {
325
- HtmlStatusCodes2[HtmlStatusCodes2["BAD_REQUEST"] = 400] = "BAD_REQUEST";
326
- HtmlStatusCodes2[HtmlStatusCodes2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
327
- HtmlStatusCodes2[HtmlStatusCodes2["PAYMENT_REQUIRED"] = 402] = "PAYMENT_REQUIRED";
328
- HtmlStatusCodes2[HtmlStatusCodes2["FORBIDDEN"] = 403] = "FORBIDDEN";
329
- HtmlStatusCodes2[HtmlStatusCodes2["NOT_FOUND"] = 404] = "NOT_FOUND";
330
- HtmlStatusCodes2[HtmlStatusCodes2["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
331
- HtmlStatusCodes2[HtmlStatusCodes2["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT";
332
- HtmlStatusCodes2[HtmlStatusCodes2["CONFLICT"] = 409] = "CONFLICT";
333
- HtmlStatusCodes2[HtmlStatusCodes2["GONE"] = 410] = "GONE";
334
- HtmlStatusCodes2[HtmlStatusCodes2["PAYLOAD_TOO_LARGE"] = 413] = "PAYLOAD_TOO_LARGE";
335
- HtmlStatusCodes2[HtmlStatusCodes2["UNSUPPORTED_MEDIA_TYPE"] = 415] = "UNSUPPORTED_MEDIA_TYPE";
336
- HtmlStatusCodes2[HtmlStatusCodes2["UPGRADE_REQUIRED"] = 426] = "UPGRADE_REQUIRED";
337
- HtmlStatusCodes2[HtmlStatusCodes2["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
338
- HtmlStatusCodes2[HtmlStatusCodes2["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
339
- HtmlStatusCodes2[HtmlStatusCodes2["SERVER_ERROR"] = 500] = "SERVER_ERROR";
340
- HtmlStatusCodes2[HtmlStatusCodes2["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED";
341
- HtmlStatusCodes2[HtmlStatusCodes2["BAD_GATEWAY"] = 502] = "BAD_GATEWAY";
342
- HtmlStatusCodes2[HtmlStatusCodes2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
343
- HtmlStatusCodes2[HtmlStatusCodes2["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT";
344
- HtmlStatusCodes2[HtmlStatusCodes2["NETWORK_CONNECT_TIMEOUT"] = 599] = "NETWORK_CONNECT_TIMEOUT";
345
- return HtmlStatusCodes2;
346
- })(HtmlStatusCodes || {});
347
- var RsError = class _RsError {
348
- err;
349
- msg;
350
- options;
351
- status;
352
- stack;
353
- constructor(errCode, message, options) {
354
- this.err = errCode;
355
- this.msg = message || "";
356
- this.status = _RsError.htmlStatus(errCode);
357
- this.stack = new Error().stack || "";
358
- this.options = options;
359
- }
360
- static htmlStatus(code) {
361
- return htmlStatusMap[code];
362
- }
363
- static isRsError(error) {
364
- return error instanceof _RsError;
365
- }
366
- };
367
- var htmlStatusMap = {
368
- // 1:1 mappings to HTTP status codes
369
- BAD_REQUEST: 400 /* BAD_REQUEST */,
370
- UNAUTHORIZED: 401 /* UNAUTHORIZED */,
371
- PAYMENT_REQUIRED: 402 /* PAYMENT_REQUIRED */,
372
- FORBIDDEN: 403 /* FORBIDDEN */,
373
- NOT_FOUND: 404 /* NOT_FOUND */,
374
- METHOD_NOT_ALLOWED: 405 /* METHOD_NOT_ALLOWED */,
375
- REQUEST_TIMEOUT: 408 /* REQUEST_TIMEOUT */,
376
- CONFLICT: 409 /* CONFLICT */,
377
- GONE: 410 /* GONE */,
378
- PAYLOAD_TOO_LARGE: 413 /* PAYLOAD_TOO_LARGE */,
379
- UNSUPPORTED_MEDIA_TYPE: 415 /* UNSUPPORTED_MEDIA_TYPE */,
380
- UPGRADE_REQUIRED: 426 /* UPGRADE_REQUIRED */,
381
- UNPROCESSABLE_ENTITY: 422 /* UNPROCESSABLE_ENTITY */,
382
- TOO_MANY_REQUESTS: 429 /* TOO_MANY_REQUESTS */,
383
- SERVER_ERROR: 500 /* SERVER_ERROR */,
384
- NOT_IMPLEMENTED: 501 /* NOT_IMPLEMENTED */,
385
- BAD_GATEWAY: 502 /* BAD_GATEWAY */,
386
- SERVICE_UNAVAILABLE: 503 /* SERVICE_UNAVAILABLE */,
387
- GATEWAY_TIMEOUT: 504 /* GATEWAY_TIMEOUT */,
388
- NETWORK_CONNECT_TIMEOUT: 599 /* NETWORK_CONNECT_TIMEOUT */,
389
- // Specific business errors mapped to appropriate HTTP codes
390
- UNKNOWN_ERROR: 500 /* SERVER_ERROR */,
391
- RATE_LIMIT_EXCEEDED: 429 /* TOO_MANY_REQUESTS */,
392
- INVALID_TOKEN: 401 /* UNAUTHORIZED */,
393
- INCORRECT_EMAIL_OR_PASSWORD: 401 /* UNAUTHORIZED */,
394
- DUPLICATE: 409 /* CONFLICT */,
395
- CONNECTION_ERROR: 504 /* GATEWAY_TIMEOUT */,
396
- SCHEMA_ERROR: 500 /* SERVER_ERROR */,
397
- DATABASE_ERROR: 500 /* SERVER_ERROR */
398
- };
399
-
400
426
  // src/restura/validators/ResponseValidator.ts
401
427
  var ResponseValidator = class _ResponseValidator {
402
428
  rootMap;
@@ -1092,10 +1118,10 @@ var customApiFactory_default = customApiFactory;
1092
1118
  import fs2 from "fs";
1093
1119
  import path2, { resolve } from "path";
1094
1120
  import tmp from "tmp";
1095
- import * as TJS from "typescript-json-schema";
1121
+ import { createGenerator } from "ts-json-schema-generator";
1096
1122
 
1097
1123
  // src/restura/generators/schemaGeneratorUtils.ts
1098
- function buildRouteSchema(requestParams) {
1124
+ function buildRouteSchema(routeKey, requestParams) {
1099
1125
  const properties = {};
1100
1126
  const required = [];
1101
1127
  for (const param of requestParams) {
@@ -1105,13 +1131,19 @@ function buildRouteSchema(requestParams) {
1105
1131
  const propertySchema = buildPropertySchemaFromRequest(param);
1106
1132
  properties[param.name] = propertySchema;
1107
1133
  }
1108
- return {
1134
+ const schemaDefinition = {
1109
1135
  type: "object",
1110
1136
  properties,
1111
1137
  ...required.length > 0 && { required },
1112
- // Only include if not empty
1113
1138
  additionalProperties: false
1114
1139
  };
1140
+ return {
1141
+ $schema: "http://json-schema.org/draft-07/schema#",
1142
+ $ref: `#/definitions/${routeKey}`,
1143
+ definitions: {
1144
+ [routeKey]: schemaDefinition
1145
+ }
1146
+ };
1115
1147
  }
1116
1148
  function buildPropertySchemaFromRequest(param) {
1117
1149
  const propertySchema = {};
@@ -1215,28 +1247,31 @@ function customTypeValidationGenerator(currentSchema, ignoreGeneratedTypes = fal
1215
1247
  }).filter(Boolean);
1216
1248
  if (!customInterfaceNames) return {};
1217
1249
  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
1250
+ const additionalImports = ignoreGeneratedTypes ? "" : [
1251
+ `/// <reference path="${toForwardSlashPath(path2.join(restura.resturaConfig.generatedTypesPath, "restura.d.ts"))}" />`,
1252
+ `/// <reference path="${toForwardSlashPath(path2.join(restura.resturaConfig.generatedTypesPath, "models.d.ts"))}" />`,
1253
+ `/// <reference path="${toForwardSlashPath(path2.join(restura.resturaConfig.generatedTypesPath, "api.d.ts"))}" />`
1254
+ ].join("\n") + "\n";
1255
+ const typesWithExport = currentSchema.customTypes.map((type) => {
1256
+ if (!type.trim().startsWith("export ")) {
1257
+ return "export " + type;
1258
+ }
1259
+ return type;
1260
+ });
1261
+ fs2.writeFileSync(temporaryFile.name, additionalImports + typesWithExport.join("\n"));
1262
+ const config3 = {
1263
+ path: resolve(temporaryFile.name),
1264
+ tsconfig: path2.join(process.cwd(), "tsconfig.json"),
1265
+ skipTypeCheck: true
1223
1266
  };
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
- );
1267
+ const generator = createGenerator(config3);
1235
1268
  customInterfaceNames.forEach((item) => {
1236
- const ddlSchema = TJS.generateSchema(program, item, {
1237
- required: true
1238
- });
1239
- schemaObject[item] = ddlSchema || {};
1269
+ try {
1270
+ const ddlSchema = generator.createSchema(item);
1271
+ schemaObject[item] = ddlSchema || {};
1272
+ } catch (error) {
1273
+ logger.error("Failed to generate schema for custom type: " + item, error);
1274
+ }
1240
1275
  });
1241
1276
  temporaryFile.removeCallback();
1242
1277
  for (const endpoint of currentSchema.endpoints) {
@@ -1244,11 +1279,14 @@ function customTypeValidationGenerator(currentSchema, ignoreGeneratedTypes = fal
1244
1279
  if (route.type !== "CUSTOM_ONE" && route.type !== "CUSTOM_ARRAY" && route.type !== "CUSTOM_PAGED") continue;
1245
1280
  if (!route.request || !Array.isArray(route.request)) continue;
1246
1281
  const routeKey = `${route.method}:${route.path}`;
1247
- schemaObject[routeKey] = buildRouteSchema(route.request);
1282
+ schemaObject[routeKey] = buildRouteSchema(routeKey, route.request);
1248
1283
  }
1249
1284
  }
1250
1285
  return schemaObject;
1251
1286
  }
1287
+ function toForwardSlashPath(path5) {
1288
+ return path5.replaceAll("\\", "/");
1289
+ }
1252
1290
 
1253
1291
  // src/restura/generators/standardTypeValidationGenerator.ts
1254
1292
  function standardTypeValidationGenerator(currentSchema) {
@@ -1259,12 +1297,18 @@ function standardTypeValidationGenerator(currentSchema) {
1259
1297
  const routeKey = `${route.method}:${route.path}`;
1260
1298
  if (!route.request || route.request.length === 0) {
1261
1299
  schemaObject[routeKey] = {
1262
- type: "object",
1263
- properties: {},
1264
- additionalProperties: false
1300
+ $schema: "http://json-schema.org/draft-07/schema#",
1301
+ $ref: `#/definitions/${routeKey}`,
1302
+ definitions: {
1303
+ [routeKey]: {
1304
+ type: "object",
1305
+ properties: {},
1306
+ additionalProperties: false
1307
+ }
1308
+ }
1265
1309
  };
1266
1310
  } else {
1267
- schemaObject[routeKey] = buildRouteSchema(route.request);
1311
+ schemaObject[routeKey] = buildRouteSchema(routeKey, route.request);
1268
1312
  }
1269
1313
  }
1270
1314
  }
@@ -1658,31 +1702,81 @@ async function isSchemaValid(schemaToCheck) {
1658
1702
 
1659
1703
  // src/restura/validators/requestValidator.ts
1660
1704
  import jsonschema from "jsonschema";
1705
+ function deepResolveSchemaRefs(schema, definitions, seen = /* @__PURE__ */ new Set()) {
1706
+ if (!schema || typeof schema !== "object") {
1707
+ return schema;
1708
+ }
1709
+ if ("$ref" in schema && typeof schema.$ref === "string") {
1710
+ const refPath = schema.$ref;
1711
+ if (refPath.startsWith("#/definitions/") && definitions) {
1712
+ const defName = refPath.substring("#/definitions/".length);
1713
+ if (seen.has(defName)) {
1714
+ return { type: "object", properties: {} };
1715
+ }
1716
+ const resolved = definitions[defName];
1717
+ if (resolved) {
1718
+ seen.add(defName);
1719
+ return deepResolveSchemaRefs(resolved, definitions, seen);
1720
+ }
1721
+ }
1722
+ return schema;
1723
+ }
1724
+ const result = {};
1725
+ for (const [key, value] of Object.entries(schema)) {
1726
+ if (key === "definitions") {
1727
+ continue;
1728
+ }
1729
+ if (value && typeof value === "object") {
1730
+ if (Array.isArray(value)) {
1731
+ result[key] = value.map(
1732
+ (item) => typeof item === "object" ? deepResolveSchemaRefs(item, definitions, new Set(seen)) : item
1733
+ );
1734
+ } else {
1735
+ result[key] = deepResolveSchemaRefs(value, definitions, new Set(seen));
1736
+ }
1737
+ } else {
1738
+ result[key] = value;
1739
+ }
1740
+ }
1741
+ return result;
1742
+ }
1743
+ function resolveSchemaRef(schema, definitions) {
1744
+ return deepResolveSchemaRefs(schema, definitions);
1745
+ }
1661
1746
  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") {
1747
+ const routeKey = `${routeData.method}:${routeData.path}`;
1748
+ const isCustom = routeData.type === "CUSTOM_ONE" || routeData.type === "CUSTOM_ARRAY" || routeData.type === "CUSTOM_PAGED";
1749
+ const isStandard = routeData.type === "ONE" || routeData.type === "ARRAY" || routeData.type === "PAGED";
1750
+ if (!isStandard && !isCustom) {
1751
+ throw new RsError("BAD_REQUEST", `Invalid route type: ${routeData.type}`);
1752
+ }
1753
+ if (isCustom) {
1670
1754
  if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
1671
1755
  if (!routeData.requestType && !routeData.request)
1672
1756
  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 = {
1676
- ...currentInterface,
1677
- additionalProperties: false
1678
- };
1679
- } else {
1680
- throw new RsError("BAD_REQUEST", `Invalid route type: ${routeData.type}`);
1681
1757
  }
1758
+ const schemaKey = isCustom ? routeData.requestType || routeKey : routeKey;
1759
+ if (!schemaKey) throw new RsError("BAD_REQUEST", `No schema key defined for request: ${routeKey}.`);
1760
+ const schemaDictionary = isCustom ? customValidationSchema : standardValidationSchema;
1761
+ const schemaRoot = schemaDictionary[schemaKey];
1762
+ if (!schemaRoot) {
1763
+ const requestType = isCustom ? "custom" : "standard";
1764
+ throw new RsError("BAD_REQUEST", `No schema found for ${requestType} request: ${schemaKey}.`);
1765
+ }
1766
+ const schemaForValidation = schemaRoot;
1767
+ const schemaDefinitions = schemaRoot.definitions;
1768
+ const rawInterface = schemaRoot.definitions[schemaKey];
1769
+ const schemaForCoercion = isCustom ? resolveSchemaRef(rawInterface, schemaDefinitions) : rawInterface;
1682
1770
  const requestData = getRequestData(req, schemaForCoercion);
1683
1771
  req.data = requestData;
1684
1772
  const validator = new jsonschema.Validator();
1685
- const executeValidation = validator.validate(req.data, schemaForCoercion);
1773
+ if (schemaDefinitions) {
1774
+ for (const [defName, defSchema] of Object.entries(schemaDefinitions)) {
1775
+ validator.addSchema(defSchema, `/definitions/${defName}`);
1776
+ }
1777
+ }
1778
+ const resolvedSchema = resolveSchemaRef(schemaForValidation, schemaDefinitions);
1779
+ const executeValidation = validator.validate(req.data, resolvedSchema);
1686
1780
  if (!executeValidation.valid) {
1687
1781
  const errorMessages = executeValidation.errors.map((err) => {
1688
1782
  const property = err.property.replace("instance.", "");
@@ -1802,7 +1896,6 @@ import pg from "pg";
1802
1896
 
1803
1897
  // src/restura/sql/PsqlConnection.ts
1804
1898
  import crypto from "crypto";
1805
- import format2 from "pg-format";
1806
1899
  import { format as sqlFormat } from "sql-formatter";
1807
1900
  import { z as z5 } from "zod";
1808
1901
 
@@ -1831,15 +1924,25 @@ function questionMarksToOrderedParams(query) {
1831
1924
  return char;
1832
1925
  });
1833
1926
  }
1834
- function insertObjectQuery(table, obj) {
1927
+ function insertObjectQuery(table, obj, options) {
1928
+ const { customSelect } = options ?? {};
1835
1929
  const keys = Object.keys(obj);
1836
1930
  const params = Object.values(obj);
1837
1931
  const columns = keys.map((column) => escapeColumnName(column)).join(", ");
1838
1932
  const values = params.map((value) => SQL`${value}`).join(", ");
1839
1933
  let query = `
1840
- INSERT INTO "${table}" (${columns})
1841
- VALUES (${values})
1842
- RETURNING *`;
1934
+ INSERT INTO "${table}" (${columns})
1935
+ VALUES (${values})
1936
+ RETURNING *`;
1937
+ if (customSelect) {
1938
+ query = `
1939
+ WITH inserted AS (
1940
+ INSERT INTO "${table}" (${columns})
1941
+ VALUES (${values})
1942
+ RETURNING *
1943
+ )
1944
+ ${customSelect}`;
1945
+ }
1843
1946
  query = query.replace(/'(\?)'/g, "?");
1844
1947
  return query;
1845
1948
  }
@@ -1877,6 +1980,13 @@ function SQL(strings, ...values) {
1877
1980
  });
1878
1981
  return query;
1879
1982
  }
1983
+ function toSqlLiteral(value) {
1984
+ if (value === null || value === void 0) return "NULL";
1985
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "NULL";
1986
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
1987
+ if (Array.isArray(value)) return `ARRAY[${value.map((v) => toSqlLiteral(v)).join(", ")}]`;
1988
+ return format.literal(value);
1989
+ }
1880
1990
 
1881
1991
  // src/restura/sql/PsqlConnection.ts
1882
1992
  var PsqlConnection = class {
@@ -1955,21 +2065,11 @@ var PsqlConnection = class {
1955
2065
  }
1956
2066
  logSqlStatement(query, options, queryMetadata, startTime, prefix = "") {
1957
2067
  if (logger.level !== "trace" && logger.level !== "silly") return;
1958
- let sqlStatement = "";
1959
- if (options.length === 0) {
1960
- sqlStatement = query;
1961
- } else {
1962
- sqlStatement = query.replace(/\$\d+/g, (match) => {
1963
- const paramIndex = parseInt(match.substring(1)) - 1;
1964
- if (paramIndex < 0 || paramIndex >= options.length) {
1965
- return "INVALID_PARAM_INDEX";
1966
- }
1967
- const value = options[paramIndex];
1968
- if (typeof value === "number") return value.toString();
1969
- if (typeof value === "boolean") return value.toString();
1970
- return format2.literal(value);
1971
- });
1972
- }
2068
+ const sqlStatement = query.replace(/\$(\d+)/g, (_, num) => {
2069
+ const paramIndex = parseInt(num) - 1;
2070
+ if (paramIndex >= options.length) return "INVALID_PARAM_INDEX";
2071
+ return toSqlLiteral(options[paramIndex]);
2072
+ });
1973
2073
  const formattedSql = sqlFormat(sqlStatement, {
1974
2074
  language: "postgresql",
1975
2075
  linesBetweenQueries: 2,
@@ -2148,124 +2248,279 @@ var SqlEngine = class {
2148
2248
 
2149
2249
  // src/restura/sql/filterPsqlParser.ts
2150
2250
  import peg from "pegjs";
2151
- var filterSqlGrammar = `
2152
- {
2153
- // ported from pg-format but intentionally will add double quotes to every column
2154
- function quoteSqlIdentity(value) {
2155
- if (value === undefined || value === null) {
2156
- throw new Error('SQL identifier cannot be null or undefined');
2157
- } else if (value === false) {
2158
- return '"f"';
2159
- } else if (value === true) {
2160
- return '"t"';
2161
- } else if (value instanceof Date) {
2162
- // return '"' + formatDate(value.toISOString()) + '"';
2163
- } else if (value instanceof Buffer) {
2164
- throw new Error('SQL identifier cannot be a buffer');
2165
- } else if (Array.isArray(value) === true) {
2166
- var temp = [];
2167
- for (var i = 0; i < value.length; i++) {
2168
- if (Array.isArray(value[i]) === true) {
2169
- throw new Error('Nested array to grouped list conversion is not supported for SQL identifier');
2251
+ var initializers = `
2252
+ // Quotes a SQL identifier (column/table name) with double quotes, escaping any embedded quotes
2253
+ function quoteSqlIdentity(value) {
2254
+ return '"' + value.replace(/"/g, '""') + '"';
2255
+ }
2256
+
2257
+ // Unescape special characters in values: \\, -> , | \\| -> | | \\\\ -> \\
2258
+ function unescapeValue(str) {
2259
+ var result = '';
2260
+ for (var i = 0; i < str.length; i++) {
2261
+ if (str[i] === '\\\\' && i + 1 < str.length) {
2262
+ var next = str[i + 1];
2263
+ if (next === ',' || next === '|' || next === '\\\\') {
2264
+ result += next;
2265
+ i++;
2266
+ } else {
2267
+ result += str[i];
2268
+ }
2269
+ } else {
2270
+ result += str[i];
2271
+ }
2272
+ }
2273
+ return result;
2274
+ }
2275
+
2276
+ // Split pipe-separated values respecting escaped pipes
2277
+ function splitPipeValues(str) {
2278
+ var values = [];
2279
+ var current = '';
2280
+ for (var i = 0; i < str.length; i++) {
2281
+ if (str[i] === '\\\\' && i + 1 < str.length && str[i + 1] === '|') {
2282
+ current += '|';
2283
+ i++;
2284
+ } else if (str[i] === '|') {
2285
+ values.push(unescapeValue(current));
2286
+ current = '';
2170
2287
  } else {
2171
- // temp.push(quoteIdent(value[i]));
2288
+ current += str[i];
2172
2289
  }
2173
2290
  }
2174
- return temp.toString();
2175
- } else if (value === Object(value)) {
2176
- throw new Error('SQL identifier cannot be an object');
2291
+ if (current.length > 0) {
2292
+ values.push(unescapeValue(current));
2293
+ }
2294
+ return values;
2177
2295
  }
2178
2296
 
2179
- var ident = value.toString().slice(0); // create copy
2180
-
2181
- // do not quote a valid, unquoted identifier
2182
- // if (/^[a-z_][a-z0-9_$]*$/.test(ident) === true && isReserved(ident) === false) {
2183
- // return ident;
2184
- // }
2185
-
2186
- var quoted = '"';
2297
+ // Build SQL IN clause from pipe-separated values
2298
+ function buildInClause(column, rawValue) {
2299
+ var values = splitPipeValues(rawValue);
2300
+ var literals = values.map(function(v) { return formatValue(v); });
2301
+ return column + ' IN (' + literals.join(', ') + ')';
2302
+ }
2187
2303
 
2188
- for (var i = 0; i < ident.length; i++) {
2189
- var c = ident[i];
2190
- if (c === '"') {
2191
- quoted += c + c;
2192
- } else {
2193
- quoted += c;
2304
+ // Check if a value is numeric and format appropriately
2305
+ function formatValue(value) {
2306
+ // Check if the value is a valid number (integer or decimal)
2307
+ if (/^-?\\d+(\\.\\d+)?$/.test(value)) {
2308
+ return value; // Return as-is without quotes
2194
2309
  }
2310
+ return format.literal(value);
2195
2311
  }
2196
2312
 
2197
- quoted += '"';
2313
+ // Format a value with optional type cast
2314
+ function formatValueWithCast(rawValue, cast) {
2315
+ var formatted = formatValue(unescapeValue(rawValue));
2316
+ return cast ? formatted + '::' + cast : formatted;
2317
+ }
2198
2318
 
2199
- return quoted;
2200
- };
2319
+ // Build SQL IN clause from pipe-separated values with optional cast
2320
+ function buildInClauseWithCast(column, rawValue, cast) {
2321
+ var values = splitPipeValues(rawValue);
2322
+ var literals = values.map(function(v) {
2323
+ var formatted = formatValue(v);
2324
+ return cast ? formatted + '::' + cast : formatted;
2325
+ });
2326
+ return column + ' IN (' + literals.join(', ') + ')';
2327
+ }
2328
+ `;
2329
+ var entryGrammar = `
2330
+ {
2331
+ ${initializers}
2201
2332
  }
2202
2333
 
2203
- start = expressionList
2204
-
2205
- _ = [ \\t\\r\\n]* // Matches spaces, tabs, and line breaks
2206
-
2207
- expressionList =
2208
- leftExpression:expression _ operator:operator _ rightExpression:expressionList
2209
- { return \`\${leftExpression} \${operator} \${rightExpression}\`;}
2210
- / expression
2211
-
2212
- expression =
2213
- negate:negate? _ "(" _ "column" _ ":" column:column _ ","? _ value:value? ","? _ type:type? _ ")"_
2214
- {return \`\${negate? " NOT " : ""}(\${type? type(column, value) : \`\${column} = \${format.literal(value)}\`})\`;}
2334
+ Start
2335
+ = sql:StartOld { return { sql: sql, usedOldSyntax: true }; }
2336
+ / sql:StartNew { return { sql: sql, usedOldSyntax: false }; }
2337
+ `;
2338
+ var oldGrammar = `
2339
+ StartOld
2340
+ = OldExpressionList
2341
+ _
2342
+ = [ \\t\\r\\n]* // Matches spaces, tabs, and line breaks
2343
+
2344
+ OldExpressionList
2345
+ = leftExpression:OldExpression _ operator:OldOperator _ rightExpression:OldExpressionList
2346
+ { return \`\${leftExpression} \${operator} \${rightExpression}\`;}
2347
+ / OldExpression
2348
+
2349
+ OldExpression
2350
+ = negate:OldNegate? _ "(" _ "column" _ ":" column:OldColumn _ ","? _ value:OldValue? ","? _ type:OldType? _ ")"_
2351
+ {return \`\${negate? " NOT " : ""}(\${type ? type(column, value) : (value == null ? \`\${column} IS NULL\` : \`\${column} = \${formatValue(value)}\`)})\`;}
2215
2352
  /
2216
- negate:negate?"("expression:expressionList")" { return \`\${negate? " NOT " : ""}(\${expression})\`; }
2217
-
2218
- negate = "!"
2219
-
2220
- operator = "and"i / "or"i
2353
+ negate:OldNegate?"("expression:OldExpressionList")" { return \`\${negate? " NOT " : ""}(\${expression})\`; }
2221
2354
 
2222
-
2223
- column = first:text rest:("." text)* {
2224
- const partsArray = [first];
2225
- if (rest && rest.length > 0) {
2226
- partsArray.push(...rest.map(item => item[1]));
2227
- }
2355
+ OldNegate
2356
+ = "!"
2228
2357
 
2229
- if (partsArray.length > 3) {
2230
- throw new SyntaxError('Column path cannot have more than 3 parts (table.column.jsonField)');
2231
- }
2232
-
2233
- if (partsArray.length === 1) {
2234
- return quoteSqlIdentity(partsArray[0]);
2235
- }
2236
- const tableName = quoteSqlIdentity(partsArray[0]);
2237
-
2238
- // If we only have two parts (table.column), use regular dot notation
2239
- if (partsArray.length === 2) {
2240
- return tableName + "." + quoteSqlIdentity(partsArray[1]);
2358
+ OldOperator
2359
+ = "and"i / "or"i
2360
+
2361
+ OldColumn
2362
+ = first:OldColumnPart rest:("." OldColumnPart)* {
2363
+ const partsArray = [first];
2364
+ if (rest && rest.length > 0) {
2365
+ partsArray.push(...rest.map(item => item[1]));
2366
+ }
2367
+
2368
+ if (partsArray.length > 3) {
2369
+ throw new SyntaxError('Column path cannot have more than 3 parts (table.column.jsonField)');
2370
+ }
2371
+
2372
+ if (partsArray.length === 1) {
2373
+ return quoteSqlIdentity(partsArray[0]);
2374
+ }
2375
+ const tableName = quoteSqlIdentity(partsArray[0]);
2376
+
2377
+ // If we only have two parts (table.column), use regular dot notation
2378
+ if (partsArray.length === 2) {
2379
+ return tableName + "." + quoteSqlIdentity(partsArray[1]);
2380
+ }
2381
+
2382
+ // For JSON paths (more than 2 parts), first part is a column, last part uses ->>
2383
+ const jsonColumn = quoteSqlIdentity(partsArray[1]);
2384
+ const lastPart = partsArray[partsArray.length - 1];
2385
+ const escapedLast = lastPart.replace(/'/g, "''");
2386
+ const result = tableName + "." + jsonColumn + "->>'" + escapedLast + "'";
2387
+ return result;
2241
2388
  }
2242
-
2243
- // For JSON paths (more than 2 parts), first part is a column, last part uses ->>
2244
- const jsonColumn = quoteSqlIdentity(partsArray[1]);
2245
- const lastPart = partsArray[partsArray.length - 1];
2246
- const result = tableName + "." + jsonColumn + "->>'" + lastPart + "'";
2247
- return result;
2248
- }
2249
-
2250
- text = text:[a-z0-9 \\t\\r\\n\\-_:@']i+ { return text.join(""); }
2251
2389
 
2390
+ OldColumnPart
2391
+ = text:[a-z0-9 \\t\\r\\n\\-_:@']i+ {
2392
+ return text.join("");
2393
+ }
2252
2394
 
2253
- type = "type" _ ":" _ type:typeString { return type; }
2254
- typeString = text:"startsWith" { return function(column, value) { return \`\${column}::text ILIKE '\${format.literal(value).slice(1,-1)}%'\`; } } /
2255
- text:"endsWith" { return function(column, value) { return \`\${column}::text ILIKE '%\${format.literal(value).slice(1,-1)}'\`; } } /
2256
- text:"contains" { return function(column, value) { return \`\${column}::text ILIKE '%\${format.literal(value).slice(1,-1)}%'\`; } } /
2257
- text:"exact" { return function(column, value) { return \`\${column} = '\${format.literal(value).slice(1,-1)}'\`; } } /
2258
- text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= '\${format.literal(value).slice(1,-1)}'\`; } } /
2259
- text:"greaterThan" { return function(column, value) { return \`\${column} > '\${format.literal(value).slice(1,-1)}'\`; } } /
2260
- text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${format.literal(value).slice(1,-1)}'\`; } } /
2261
- text:"lessThan" { return function(column, value) { return \`\${column} < '\${format.literal(value).slice(1,-1)}'\`; } } /
2262
- text:"isNull" { return function(column, value) { return \`isNull(\${column})\`; } }
2395
+ OldText
2396
+ = text:[a-z0-9 \\t\\r\\n\\-_:@'.]i+ {
2397
+ return text.join("");
2398
+ }
2263
2399
 
2264
- value = "value" _ ":" value:text { return value; }
2400
+ OldType
2401
+ = "type" _ ":" _ type:OldTypeString {
2402
+ return type;
2403
+ }
2265
2404
 
2405
+ OldTypeString
2406
+ = text:"startsWith" { return function(column, value) { return \`\${column} ILIKE '\${format.literal(value).slice(1,-1)}%'\`; } }
2407
+ / text:"endsWith" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}'\`; } }
2408
+ / text:"contains" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}%'\`; } }
2409
+ / text:"exact" { return function(column, value) { return \`\${column} = \${formatValue(value)}\`; } }
2410
+ / text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= \${formatValue(value)}\`; } }
2411
+ / text:"greaterThan" { return function(column, value) { return \`\${column} > \${formatValue(value)}\`; } }
2412
+ / text:"lessThanEqual" { return function(column, value) { return \`\${column} <= \${formatValue(value)}\`; } }
2413
+ / text:"lessThan" { return function(column, value) { return \`\${column} < \${formatValue(value)}\`; } }
2414
+ / text:"isNull" { return function(column, value) { return \`\${column} IS NULL\`; } }
2266
2415
 
2416
+ OldValue
2417
+ = "value" _ ":" value:OldText {
2418
+ return value;
2419
+ }
2267
2420
  `;
2268
- var filterPsqlParser = peg.generate(filterSqlGrammar, {
2421
+ var newGrammar = `
2422
+ StartNew
2423
+ = ExpressionList
2424
+
2425
+ ExpressionList
2426
+ = left:Expression _ op:("and"i / "or"i) _ right:ExpressionList
2427
+ { return left + ' ' + op.toUpperCase() + ' ' + right; }
2428
+ / Expression
2429
+
2430
+ Expression
2431
+ = negate:"!"? _ "(" _ inner:SimpleExprList _ ")" _
2432
+ { return (negate ? 'NOT ' : '') + '(' + inner + ')'; }
2433
+ / SimpleExpr
2434
+
2435
+ SimpleExprList
2436
+ = left:SimpleExpr _ op:("and"i / "or"i) _ right:SimpleExprList
2437
+ { return left + ' ' + op.toUpperCase() + ' ' + right; }
2438
+ / SimpleExpr
2439
+
2440
+ SimpleExpr
2441
+ = negate:"!"? _ "(" _ col:Column _ "," _ op:OperatorWithValue _ ")" _
2442
+ { return (negate ? 'NOT ' : '') + '(' + op(col) + ')'; }
2443
+ / negate:"!"? _ "(" _ col:Column _ "," _ op:NullOperator _ ")" _
2444
+ { return (negate ? 'NOT ' : '') + '(' + op(col) + ')'; }
2445
+ / negate:"!"? _ "(" _ col:Column _ "," _ val:CastedValue _ ")" _
2446
+ { return (negate ? 'NOT ' : '') + '(' + col + ' = ' + formatValueWithCast(val.value, val.cast) + ')'; }
2447
+
2448
+ Column
2449
+ = first:ColPart rest:("." ColPart)* {
2450
+ const partsArray = [first];
2451
+ if (rest && rest.length > 0) {
2452
+ partsArray.push(...rest.map(item => item[1]));
2453
+ }
2454
+
2455
+ if (partsArray.length > 3) {
2456
+ throw new SyntaxError('Column path cannot have more than 3 parts (table.column.jsonField)');
2457
+ }
2458
+
2459
+ if (partsArray.length === 1) {
2460
+ return quoteSqlIdentity(partsArray[0]);
2461
+ }
2462
+ const tableName = quoteSqlIdentity(partsArray[0]);
2463
+
2464
+ if (partsArray.length === 2) {
2465
+ return tableName + '.' + quoteSqlIdentity(partsArray[1]);
2466
+ }
2467
+
2468
+ const jsonColumn = quoteSqlIdentity(partsArray[1]);
2469
+ const lastPart = partsArray[partsArray.length - 1];
2470
+ const escapedLast = lastPart.replace(/'/g, "''");
2471
+ return tableName + '.' + jsonColumn + "->>'" + escapedLast + "'";
2472
+ }
2473
+
2474
+ ColPart
2475
+ = chars:[a-zA-Z0-9_]+ { return chars.join(''); }
2476
+
2477
+ NullOperator
2478
+ = "notnull"i { return function(col) { return col + ' IS NOT NULL'; }; }
2479
+ / "null"i { return function(col) { return col + ' IS NULL'; }; }
2480
+
2481
+ OperatorWithValue
2482
+ = "in"i _ "," _ val:CastedValueWithPipes { return function(col) { return buildInClauseWithCast(col, val.value, val.cast); }; }
2483
+ / "ne"i _ "," _ val:CastedValue { return function(col) { return col + ' <> ' + formatValueWithCast(val.value, val.cast); }; }
2484
+ / "gte"i _ "," _ val:CastedValue { return function(col) { return col + ' >= ' + formatValueWithCast(val.value, val.cast); }; }
2485
+ / "gt"i _ "," _ val:CastedValue { return function(col) { return col + ' > ' + formatValueWithCast(val.value, val.cast); }; }
2486
+ / "lte"i _ "," _ val:CastedValue { return function(col) { return col + ' <= ' + formatValueWithCast(val.value, val.cast); }; }
2487
+ / "lt"i _ "," _ val:CastedValue { return function(col) { return col + ' < ' + formatValueWithCast(val.value, val.cast); }; }
2488
+ / "has"i _ "," _ val:CastedValue { return function(col) { var formatted = format.literal('%' + unescapeValue(val.value) + '%'); return col + ' ILIKE ' + (val.cast ? formatted + '::' + val.cast : formatted); }; }
2489
+ / "sw"i _ "," _ val:CastedValue { return function(col) { var formatted = format.literal(unescapeValue(val.value) + '%'); return col + ' ILIKE ' + (val.cast ? formatted + '::' + val.cast : formatted); }; }
2490
+ / "ew"i _ "," _ val:CastedValue { return function(col) { var formatted = format.literal('%' + unescapeValue(val.value)); return col + ' ILIKE ' + (val.cast ? formatted + '::' + val.cast : formatted); }; }
2491
+
2492
+ CastedValue
2493
+ = val:Value cast:TypeCast? { return { value: val, cast: cast }; }
2494
+
2495
+ CastedValueWithPipes
2496
+ = val:ValueWithPipes cast:TypeCast? { return { value: val, cast: cast }; }
2497
+
2498
+ TypeCast
2499
+ = "::" type:("timestamptz"i / "timestamp"i / "boolean"i / "numeric"i / "bigint"i / "text"i / "date"i / "int"i)
2500
+ { return type.toLowerCase(); }
2501
+
2502
+ Value
2503
+ = chars:ValueChar+ { return chars.join(''); }
2504
+
2505
+ ValueChar
2506
+ = "\\\\\\\\" { return '\\\\\\\\'; }
2507
+ / "\\\\," { return '\\\\,'; }
2508
+ / "\\\\|" { return '\\\\|'; }
2509
+ / [^,()\\\\|:]
2510
+ / c:":" !":" { return c; }
2511
+
2512
+ ValueWithPipes
2513
+ = chars:ValueWithPipesChar+ { return chars.join(''); }
2514
+
2515
+ ValueWithPipesChar
2516
+ = "\\\\\\\\" { return '\\\\\\\\'; }
2517
+ / "\\\\," { return '\\\\,'; }
2518
+ / "\\\\|" { return '\\\\|'; }
2519
+ / [^,()\\\\:]
2520
+ / c:":" !":" { return c; }
2521
+ `;
2522
+ var fullGrammar = entryGrammar + oldGrammar + newGrammar;
2523
+ var filterPsqlParser = peg.generate(fullGrammar, {
2269
2524
  format: "commonjs",
2270
2525
  dependencies: { format: "pg-format" }
2271
2526
  });
@@ -2872,7 +3127,13 @@ DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
2872
3127
  throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
2873
3128
  return data[requestParam.name]?.toString() || "";
2874
3129
  });
2875
- statement = filterPsqlParser_default.parse(statement);
3130
+ const parseResult = filterPsqlParser_default.parse(statement);
3131
+ if (parseResult.usedOldSyntax) {
3132
+ logger.warn(
3133
+ `Deprecated filter syntax detected in route "${routeData.name}" (${routeData.path}). Please migrate to the new filter syntax.`
3134
+ );
3135
+ }
3136
+ statement = parseResult.sql;
2876
3137
  if (whereClause.startsWith("WHERE")) {
2877
3138
  whereClause += ` AND (${statement})
2878
3139
  `;
@@ -3590,6 +3851,7 @@ export {
3590
3851
  restura,
3591
3852
  resturaGlobalTypesGenerator,
3592
3853
  resturaSchema,
3854
+ toSqlLiteral,
3593
3855
  updateObjectQuery
3594
3856
  };
3595
3857
  //# sourceMappingURL=index.js.map