@ruiapp/rapid-core 0.5.0 → 0.5.2
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 +44 -13
- package/dist/queryBuilder/queryBuilder.d.ts +3 -2
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/core/response.ts +6 -3
- package/src/plugins/dataManage/actionHandlers/findCollectionEntityById.ts +10 -1
- package/src/plugins/metaManage/MetaManagePlugin.ts +29 -3
- package/src/queryBuilder/queryBuilder.ts +11 -8
- package/src/server.ts +14 -2
- package/src/types.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -202,8 +202,8 @@ const pgPropertyTypeColumnMap = {
|
|
|
202
202
|
"image[]": "jsonb",
|
|
203
203
|
};
|
|
204
204
|
|
|
205
|
-
const objLeftQuoteChar = "
|
|
206
|
-
const objRightQuoteChar = "
|
|
205
|
+
const objLeftQuoteChar = '"';
|
|
206
|
+
const objRightQuoteChar = '"';
|
|
207
207
|
const relationalOperatorsMap = new Map([
|
|
208
208
|
["eq", "="],
|
|
209
209
|
["ne", "<>"],
|
|
@@ -250,6 +250,9 @@ class QueryBuilder {
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
|
+
formatValueToSqlLiteral(value) {
|
|
254
|
+
return formatValueToSqlLiteral(value);
|
|
255
|
+
}
|
|
253
256
|
select(model, options) {
|
|
254
257
|
const ctx = {
|
|
255
258
|
model,
|
|
@@ -369,7 +372,7 @@ class QueryBuilder {
|
|
|
369
372
|
paramToLiteral: false,
|
|
370
373
|
};
|
|
371
374
|
let { filters } = options;
|
|
372
|
-
let command =
|
|
375
|
+
let command = 'SELECT COUNT(*)::int as "count" FROM ';
|
|
373
376
|
command += this.quoteTable(model);
|
|
374
377
|
if (filters && filters.length) {
|
|
375
378
|
command += " WHERE ";
|
|
@@ -389,7 +392,7 @@ class QueryBuilder {
|
|
|
389
392
|
paramToLiteral: false,
|
|
390
393
|
};
|
|
391
394
|
let { filters } = options;
|
|
392
|
-
let command =
|
|
395
|
+
let command = 'SELECT COUNT(*)::int as "count" FROM ';
|
|
393
396
|
command += `${this.quoteTable(derivedModel)} LEFT JOIN ${this.quoteTable(baseModel)} ON ${this.quoteObject(derivedModel.tableName)}.id = ${this.quoteObject(baseModel.tableName)}.id`;
|
|
394
397
|
if (filters && filters.length) {
|
|
395
398
|
command += " WHERE ";
|
|
@@ -996,7 +999,6 @@ function mergeHeaders(target, source) {
|
|
|
996
999
|
else if (lodash.isObject(source)) {
|
|
997
1000
|
Object.entries(source).forEach(([key, value]) => target.set(key, value));
|
|
998
1001
|
}
|
|
999
|
-
return target;
|
|
1000
1002
|
}
|
|
1001
1003
|
function newResponse(options) {
|
|
1002
1004
|
return new Response(options.body, {
|
|
@@ -1005,6 +1007,7 @@ function newResponse(options) {
|
|
|
1005
1007
|
});
|
|
1006
1008
|
}
|
|
1007
1009
|
class RapidResponse {
|
|
1010
|
+
// TODO: remove this field.
|
|
1008
1011
|
#response;
|
|
1009
1012
|
status;
|
|
1010
1013
|
body;
|
|
@@ -1024,13 +1027,16 @@ class RapidResponse {
|
|
|
1024
1027
|
if (headers) {
|
|
1025
1028
|
mergeHeaders(responseHeaders, headers);
|
|
1026
1029
|
}
|
|
1027
|
-
this
|
|
1030
|
+
this.status = status || 200;
|
|
1031
|
+
this.body = body;
|
|
1032
|
+
this.#response = newResponse({ body, status: this.status, headers: responseHeaders });
|
|
1028
1033
|
}
|
|
1029
1034
|
redirect(location, status) {
|
|
1030
1035
|
this.headers.set("Location", location);
|
|
1036
|
+
this.status = status || 302;
|
|
1031
1037
|
this.#response = newResponse({
|
|
1032
1038
|
headers: this.headers,
|
|
1033
|
-
status: status
|
|
1039
|
+
status: this.status,
|
|
1034
1040
|
});
|
|
1035
1041
|
}
|
|
1036
1042
|
getResponse() {
|
|
@@ -4158,19 +4164,27 @@ class RapidServer {
|
|
|
4158
4164
|
const rapidRequest = new RapidRequest(this, request);
|
|
4159
4165
|
await rapidRequest.parseBody();
|
|
4160
4166
|
const routeContext = new RouteContext(this, rapidRequest);
|
|
4167
|
+
const { response } = routeContext;
|
|
4161
4168
|
try {
|
|
4162
4169
|
await this.#pluginManager.onPrepareRouteContext(routeContext);
|
|
4163
4170
|
await this.#buildedRoutes(routeContext, next);
|
|
4164
4171
|
}
|
|
4165
4172
|
catch (ex) {
|
|
4166
4173
|
this.#logger.error("handle request error:", ex);
|
|
4167
|
-
|
|
4174
|
+
response.json({
|
|
4168
4175
|
error: {
|
|
4169
4176
|
message: ex.message || ex,
|
|
4170
4177
|
},
|
|
4171
4178
|
}, 500);
|
|
4172
4179
|
}
|
|
4173
|
-
|
|
4180
|
+
if (!response.status && !response.body) {
|
|
4181
|
+
response.json({
|
|
4182
|
+
error: {
|
|
4183
|
+
message: "No route handler was found to handle this request.",
|
|
4184
|
+
},
|
|
4185
|
+
}, 404);
|
|
4186
|
+
}
|
|
4187
|
+
return response.getResponse();
|
|
4174
4188
|
}
|
|
4175
4189
|
async beforeRunRouteActions(handlerContext) {
|
|
4176
4190
|
await this.#pluginManager.beforeRunRouteActions(handlerContext);
|
|
@@ -4638,7 +4652,7 @@ function listDataDictionaries(server) {
|
|
|
4638
4652
|
async function syncDatabaseSchema(server, applicationConfig) {
|
|
4639
4653
|
const logger = server.getLogger();
|
|
4640
4654
|
logger.info("Synchronizing database schema...");
|
|
4641
|
-
const sqlQueryTableInformations = `SELECT table_schema, table_name FROM information_schema.tables`;
|
|
4655
|
+
const sqlQueryTableInformations = `SELECT table_schema, table_name, obj_description((table_schema||'.'||quote_ident(table_name))::regclass) as table_description FROM information_schema.tables`;
|
|
4642
4656
|
const tablesInDb = await server.queryDatabaseObject(sqlQueryTableInformations);
|
|
4643
4657
|
const { queryBuilder } = server;
|
|
4644
4658
|
for (const model of applicationConfig.models) {
|
|
@@ -4649,9 +4663,14 @@ async function syncDatabaseSchema(server, applicationConfig) {
|
|
|
4649
4663
|
if (!tableInDb) {
|
|
4650
4664
|
await server.queryDatabaseObject(`CREATE TABLE IF NOT EXISTS ${queryBuilder.quoteTable(model)} ()`, []);
|
|
4651
4665
|
}
|
|
4666
|
+
if (!tableInDb || tableInDb.table_description != model.name) {
|
|
4667
|
+
await server.queryDatabaseObject(`COMMENT ON TABLE ${queryBuilder.quoteTable(model)} IS ${queryBuilder.formatValueToSqlLiteral(model.name)};`, []);
|
|
4668
|
+
}
|
|
4652
4669
|
}
|
|
4653
|
-
const sqlQueryColumnInformations = `SELECT table_schema, table_name, column_name, data_type,
|
|
4654
|
-
|
|
4670
|
+
const sqlQueryColumnInformations = `SELECT c.table_schema, c.table_name, c.column_name, c.ordinal_position, d.description, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale
|
|
4671
|
+
FROM information_schema.columns c
|
|
4672
|
+
INNER JOIN pg_catalog.pg_statio_all_tables st ON (st.schemaname = c.table_schema and st.relname = c.table_name)
|
|
4673
|
+
LEFT JOIN pg_catalog.pg_description d ON (d.objoid = st.relid and d.objsubid = c.ordinal_position);`;
|
|
4655
4674
|
const columnsInDb = await server.queryDatabaseObject(sqlQueryColumnInformations, []);
|
|
4656
4675
|
for (const model of applicationConfig.models) {
|
|
4657
4676
|
logger.debug(`Checking data columns for '${model.namespace}.${model.singularCode}'...`);
|
|
@@ -4678,6 +4697,9 @@ async function syncDatabaseSchema(server, applicationConfig) {
|
|
|
4678
4697
|
notNull: property.required,
|
|
4679
4698
|
});
|
|
4680
4699
|
}
|
|
4700
|
+
if (!columnInDb || columnInDb.description != property.name) {
|
|
4701
|
+
await server.queryDatabaseObject(`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(property.targetIdColumnName)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`, []);
|
|
4702
|
+
}
|
|
4681
4703
|
}
|
|
4682
4704
|
else if (property.relation === "many") {
|
|
4683
4705
|
if (property.linkTableName) {
|
|
@@ -4780,6 +4802,9 @@ async function syncDatabaseSchema(server, applicationConfig) {
|
|
|
4780
4802
|
}
|
|
4781
4803
|
}
|
|
4782
4804
|
}
|
|
4805
|
+
if (!columnInDb || columnInDb.description != property.name) {
|
|
4806
|
+
await server.queryDatabaseObject(`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(property.columnName || property.code)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`, []);
|
|
4807
|
+
}
|
|
4783
4808
|
}
|
|
4784
4809
|
}
|
|
4785
4810
|
}
|
|
@@ -4975,7 +5000,13 @@ async function handler$q(plugin, ctx, options) {
|
|
|
4975
5000
|
routeContext,
|
|
4976
5001
|
});
|
|
4977
5002
|
if (!entity) {
|
|
4978
|
-
|
|
5003
|
+
ctx.routerContext.response.json({
|
|
5004
|
+
error: {
|
|
5005
|
+
message: `${options.namespace}.${options.singularCode} with id "${id}" was not found.`,
|
|
5006
|
+
},
|
|
5007
|
+
}, 404);
|
|
5008
|
+
// routerContext.json() function will not be called if the ctx.output is null or undefined.
|
|
5009
|
+
return;
|
|
4979
5010
|
}
|
|
4980
5011
|
return entity;
|
|
4981
5012
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RpdDataModel, CreateEntityOptions, QuoteTableOptions, DatabaseQuery } from "../types";
|
|
1
|
+
import { RpdDataModel, CreateEntityOptions, QuoteTableOptions, DatabaseQuery, IQueryBuilder } from "../types";
|
|
2
2
|
import { CountRowOptions, DeleteRowOptions, FindRowOptions, RowFilterOptions, UpdateRowOptions, ColumnSelectOptions } from "../dataAccess/dataAccessTypes";
|
|
3
3
|
export interface BuildQueryContext {
|
|
4
4
|
model: RpdDataModel;
|
|
@@ -13,12 +13,13 @@ export interface BuildQueryContext {
|
|
|
13
13
|
export interface InitQueryBuilderOptions {
|
|
14
14
|
dbDefaultSchema: string;
|
|
15
15
|
}
|
|
16
|
-
export default class QueryBuilder {
|
|
16
|
+
export default class QueryBuilder implements IQueryBuilder {
|
|
17
17
|
#private;
|
|
18
18
|
constructor(options: InitQueryBuilderOptions);
|
|
19
19
|
quoteTable(options: QuoteTableOptions): string;
|
|
20
20
|
quoteObject(name: string): string;
|
|
21
21
|
quoteColumn(model: RpdDataModel, column: ColumnSelectOptions, emitTableAlias: boolean): string;
|
|
22
|
+
formatValueToSqlLiteral(value: any): string;
|
|
22
23
|
select(model: RpdDataModel, options: FindRowOptions): DatabaseQuery;
|
|
23
24
|
selectDerived(derivedModel: RpdDataModel, baseModel: RpdDataModel, options: FindRowOptions): DatabaseQuery;
|
|
24
25
|
count(model: RpdDataModel, options: CountRowOptions): DatabaseQuery;
|
package/dist/types.d.ts
CHANGED
|
@@ -154,6 +154,7 @@ export interface IQueryBuilder {
|
|
|
154
154
|
quoteTable: (options: QuoteTableOptions) => string;
|
|
155
155
|
quoteObject: (name: string) => string;
|
|
156
156
|
buildFiltersExpression(model: RpdDataModel, filters: RowFilterOptions[]): any;
|
|
157
|
+
formatValueToSqlLiteral: (value: any) => string;
|
|
157
158
|
}
|
|
158
159
|
export interface RpdApplicationConfig {
|
|
159
160
|
code?: string;
|
package/package.json
CHANGED
package/src/core/response.ts
CHANGED
|
@@ -15,7 +15,6 @@ function mergeHeaders(target: Headers, source: HeadersInit) {
|
|
|
15
15
|
} else if (isObject(source)) {
|
|
16
16
|
Object.entries(source).forEach(([key, value]) => target.set(key, value));
|
|
17
17
|
}
|
|
18
|
-
return target;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
interface NewResponseOptions {
|
|
@@ -32,6 +31,7 @@ function newResponse(options: NewResponseOptions) {
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export class RapidResponse {
|
|
34
|
+
// TODO: remove this field.
|
|
35
35
|
#response: Response;
|
|
36
36
|
status: number;
|
|
37
37
|
body: BodyInit;
|
|
@@ -53,14 +53,17 @@ export class RapidResponse {
|
|
|
53
53
|
if (headers) {
|
|
54
54
|
mergeHeaders(responseHeaders, headers);
|
|
55
55
|
}
|
|
56
|
-
this
|
|
56
|
+
this.status = status || 200;
|
|
57
|
+
this.body = body;
|
|
58
|
+
this.#response = newResponse({ body, status: this.status, headers: responseHeaders });
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
redirect(location: string, status?: HttpStatus) {
|
|
60
62
|
this.headers.set("Location", location);
|
|
63
|
+
this.status = status || 302;
|
|
61
64
|
this.#response = newResponse({
|
|
62
65
|
headers: this.headers,
|
|
63
|
-
status: status
|
|
66
|
+
status: this.status,
|
|
64
67
|
});
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -14,7 +14,16 @@ export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, op
|
|
|
14
14
|
routeContext,
|
|
15
15
|
});
|
|
16
16
|
if (!entity) {
|
|
17
|
-
|
|
17
|
+
ctx.routerContext.response.json(
|
|
18
|
+
{
|
|
19
|
+
error: {
|
|
20
|
+
message: `${options.namespace}.${options.singularCode} with id "${id}" was not found.`,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
404,
|
|
24
|
+
);
|
|
25
|
+
// routerContext.json() function will not be called if the ctx.output is null or undefined.
|
|
26
|
+
return;
|
|
18
27
|
}
|
|
19
28
|
return entity;
|
|
20
29
|
});
|
|
@@ -181,12 +181,15 @@ function listDataDictionaries(server: IRpdServer) {
|
|
|
181
181
|
type TableInformation = {
|
|
182
182
|
table_schema: string;
|
|
183
183
|
table_name: string;
|
|
184
|
+
table_description: string;
|
|
184
185
|
};
|
|
185
186
|
|
|
186
187
|
type ColumnInformation = {
|
|
187
188
|
table_schema: string;
|
|
188
189
|
table_name: string;
|
|
189
190
|
column_name: string;
|
|
191
|
+
ordinal_position: number;
|
|
192
|
+
description?: string;
|
|
190
193
|
data_type: string;
|
|
191
194
|
udt_name: string;
|
|
192
195
|
is_nullable: "YES" | "NO";
|
|
@@ -206,7 +209,7 @@ type ConstraintInformation = {
|
|
|
206
209
|
async function syncDatabaseSchema(server: IRpdServer, applicationConfig: RpdApplicationConfig) {
|
|
207
210
|
const logger = server.getLogger();
|
|
208
211
|
logger.info("Synchronizing database schema...");
|
|
209
|
-
const sqlQueryTableInformations = `SELECT table_schema, table_name FROM information_schema.tables`;
|
|
212
|
+
const sqlQueryTableInformations = `SELECT table_schema, table_name, obj_description((table_schema||'.'||quote_ident(table_name))::regclass) as table_description FROM information_schema.tables`;
|
|
210
213
|
const tablesInDb: TableInformation[] = await server.queryDatabaseObject(sqlQueryTableInformations);
|
|
211
214
|
const { queryBuilder } = server;
|
|
212
215
|
|
|
@@ -219,10 +222,15 @@ async function syncDatabaseSchema(server: IRpdServer, applicationConfig: RpdAppl
|
|
|
219
222
|
if (!tableInDb) {
|
|
220
223
|
await server.queryDatabaseObject(`CREATE TABLE IF NOT EXISTS ${queryBuilder.quoteTable(model)} ()`, []);
|
|
221
224
|
}
|
|
225
|
+
if (!tableInDb || tableInDb.table_description != model.name) {
|
|
226
|
+
await server.queryDatabaseObject(`COMMENT ON TABLE ${queryBuilder.quoteTable(model)} IS ${queryBuilder.formatValueToSqlLiteral(model.name)};`, []);
|
|
227
|
+
}
|
|
222
228
|
}
|
|
223
229
|
|
|
224
|
-
const sqlQueryColumnInformations = `SELECT table_schema, table_name, column_name, data_type,
|
|
225
|
-
|
|
230
|
+
const sqlQueryColumnInformations = `SELECT c.table_schema, c.table_name, c.column_name, c.ordinal_position, d.description, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale
|
|
231
|
+
FROM information_schema.columns c
|
|
232
|
+
INNER JOIN pg_catalog.pg_statio_all_tables st ON (st.schemaname = c.table_schema and st.relname = c.table_name)
|
|
233
|
+
LEFT JOIN pg_catalog.pg_description d ON (d.objoid = st.relid and d.objsubid = c.ordinal_position);`;
|
|
226
234
|
const columnsInDb: ColumnInformation[] = await server.queryDatabaseObject(sqlQueryColumnInformations, []);
|
|
227
235
|
|
|
228
236
|
for (const model of applicationConfig.models) {
|
|
@@ -253,6 +261,15 @@ async function syncDatabaseSchema(server: IRpdServer, applicationConfig: RpdAppl
|
|
|
253
261
|
notNull: property.required,
|
|
254
262
|
});
|
|
255
263
|
}
|
|
264
|
+
|
|
265
|
+
if (!columnInDb || columnInDb.description != property.name) {
|
|
266
|
+
await server.queryDatabaseObject(
|
|
267
|
+
`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(
|
|
268
|
+
property.targetIdColumnName,
|
|
269
|
+
)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`,
|
|
270
|
+
[],
|
|
271
|
+
);
|
|
272
|
+
}
|
|
256
273
|
} else if (property.relation === "many") {
|
|
257
274
|
if (property.linkTableName) {
|
|
258
275
|
const tableInDb = find(tablesInDb, {
|
|
@@ -359,6 +376,15 @@ async function syncDatabaseSchema(server: IRpdServer, applicationConfig: RpdAppl
|
|
|
359
376
|
}
|
|
360
377
|
}
|
|
361
378
|
}
|
|
379
|
+
|
|
380
|
+
if (!columnInDb || columnInDb.description != property.name) {
|
|
381
|
+
await server.queryDatabaseObject(
|
|
382
|
+
`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(
|
|
383
|
+
property.columnName || property.code,
|
|
384
|
+
)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`,
|
|
385
|
+
[],
|
|
386
|
+
);
|
|
387
|
+
}
|
|
362
388
|
}
|
|
363
389
|
}
|
|
364
390
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { find, isBoolean, isNull, isNumber, isString, isUndefined } from "lodash";
|
|
2
|
-
import { RpdDataModel, RpdDataModelProperty, CreateEntityOptions, QuoteTableOptions, DatabaseQuery } from "../types";
|
|
2
|
+
import { RpdDataModel, RpdDataModelProperty, CreateEntityOptions, QuoteTableOptions, DatabaseQuery, IQueryBuilder } from "../types";
|
|
3
3
|
import {
|
|
4
4
|
CountRowOptions,
|
|
5
5
|
DeleteRowOptions,
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
} from "~/dataAccess/dataAccessTypes";
|
|
20
20
|
import { pgPropertyTypeColumnMap } from "~/dataAccess/columnTypeMapper";
|
|
21
21
|
|
|
22
|
-
const objLeftQuoteChar = "
|
|
23
|
-
const objRightQuoteChar = "
|
|
22
|
+
const objLeftQuoteChar = '"';
|
|
23
|
+
const objRightQuoteChar = '"';
|
|
24
24
|
|
|
25
25
|
const relationalOperatorsMap = new Map<RowFilterRelationalOperators, string>([
|
|
26
26
|
["eq", "="],
|
|
@@ -46,7 +46,7 @@ export interface InitQueryBuilderOptions {
|
|
|
46
46
|
dbDefaultSchema: string;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export default class QueryBuilder {
|
|
49
|
+
export default class QueryBuilder implements IQueryBuilder {
|
|
50
50
|
#dbDefaultSchema: string;
|
|
51
51
|
|
|
52
52
|
constructor(options: InitQueryBuilderOptions) {
|
|
@@ -84,6 +84,10 @@ export default class QueryBuilder {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
formatValueToSqlLiteral(value: any) {
|
|
88
|
+
return formatValueToSqlLiteral(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
87
91
|
select(model: RpdDataModel, options: FindRowOptions): DatabaseQuery {
|
|
88
92
|
const ctx: BuildQueryContext = {
|
|
89
93
|
model,
|
|
@@ -223,7 +227,7 @@ export default class QueryBuilder {
|
|
|
223
227
|
paramToLiteral: false,
|
|
224
228
|
};
|
|
225
229
|
let { filters } = options;
|
|
226
|
-
let command =
|
|
230
|
+
let command = 'SELECT COUNT(*)::int as "count" FROM ';
|
|
227
231
|
|
|
228
232
|
command += this.quoteTable(model);
|
|
229
233
|
|
|
@@ -247,7 +251,7 @@ export default class QueryBuilder {
|
|
|
247
251
|
paramToLiteral: false,
|
|
248
252
|
};
|
|
249
253
|
let { filters } = options;
|
|
250
|
-
let command =
|
|
254
|
+
let command = 'SELECT COUNT(*)::int as "count" FROM ';
|
|
251
255
|
|
|
252
256
|
command += `${this.quoteTable(derivedModel)} LEFT JOIN ${this.quoteTable(baseModel)} ON ${this.quoteObject(derivedModel.tableName)}.id = ${this.quoteObject(
|
|
253
257
|
baseModel.tableName,
|
|
@@ -498,11 +502,10 @@ function buildRangeFilterQuery(ctx: BuildQueryContext, filter: FindRowRangeFilte
|
|
|
498
502
|
ctx.params.push(filter.value[0]);
|
|
499
503
|
command += `$${ctx.params.length}`;
|
|
500
504
|
|
|
501
|
-
command += " AND "
|
|
505
|
+
command += " AND ";
|
|
502
506
|
|
|
503
507
|
ctx.params.push(filter.value[1]);
|
|
504
508
|
command += `$${ctx.params.length}`;
|
|
505
|
-
|
|
506
509
|
} else {
|
|
507
510
|
throw new Error(`Filter operator '${filter.operator}' is not supported.`);
|
|
508
511
|
}
|
package/src/server.ts
CHANGED
|
@@ -400,13 +400,14 @@ export class RapidServer implements IRpdServer {
|
|
|
400
400
|
const rapidRequest = new RapidRequest(this, request);
|
|
401
401
|
await rapidRequest.parseBody();
|
|
402
402
|
const routeContext: RouteContext = new RouteContext(this, rapidRequest);
|
|
403
|
+
const { response } = routeContext;
|
|
403
404
|
|
|
404
405
|
try {
|
|
405
406
|
await this.#pluginManager.onPrepareRouteContext(routeContext);
|
|
406
407
|
await this.#buildedRoutes(routeContext, next);
|
|
407
408
|
} catch (ex) {
|
|
408
409
|
this.#logger.error("handle request error:", ex);
|
|
409
|
-
|
|
410
|
+
response.json(
|
|
410
411
|
{
|
|
411
412
|
error: {
|
|
412
413
|
message: ex.message || ex,
|
|
@@ -415,7 +416,18 @@ export class RapidServer implements IRpdServer {
|
|
|
415
416
|
500,
|
|
416
417
|
);
|
|
417
418
|
}
|
|
418
|
-
|
|
419
|
+
|
|
420
|
+
if (!response.status && !response.body) {
|
|
421
|
+
response.json(
|
|
422
|
+
{
|
|
423
|
+
error: {
|
|
424
|
+
message: "No route handler was found to handle this request.",
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
404,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
return response.getResponse();
|
|
419
431
|
}
|
|
420
432
|
|
|
421
433
|
async beforeRunRouteActions(handlerContext: ActionHandlerContext) {
|
package/src/types.ts
CHANGED
|
@@ -179,6 +179,7 @@ export interface IQueryBuilder {
|
|
|
179
179
|
quoteTable: (options: QuoteTableOptions) => string;
|
|
180
180
|
quoteObject: (name: string) => string;
|
|
181
181
|
buildFiltersExpression(model: RpdDataModel, filters: RowFilterOptions[]);
|
|
182
|
+
formatValueToSqlLiteral: (value: any) => string;
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
export interface RpdApplicationConfig {
|