@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.d.ts +32 -9
- package/dist/index.js +508 -246
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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
|
|
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
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
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
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1842
|
-
|
|
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
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
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
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
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
|
-
|
|
2288
|
+
current += str[i];
|
|
2172
2289
|
}
|
|
2173
2290
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2291
|
+
if (current.length > 0) {
|
|
2292
|
+
values.push(unescapeValue(current));
|
|
2293
|
+
}
|
|
2294
|
+
return values;
|
|
2177
2295
|
}
|
|
2178
2296
|
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
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
|
-
|
|
2189
|
-
|
|
2190
|
-
if (
|
|
2191
|
-
|
|
2192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
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:
|
|
2217
|
-
|
|
2218
|
-
negate = "!"
|
|
2219
|
-
|
|
2220
|
-
operator = "and"i / "or"i
|
|
2353
|
+
negate:OldNegate?"("expression:OldExpressionList")" { return \`\${negate? " NOT " : ""}(\${expression})\`; }
|
|
2221
2354
|
|
|
2222
|
-
|
|
2223
|
-
|
|
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
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
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
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|