@ruiapp/rapid-core 0.0.2 → 0.1.1
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/core/request.d.ts +7 -3
- package/dist/core/server.d.ts +1 -0
- package/dist/dataAccess/dataAccessor.d.ts +2 -2
- package/dist/index.js +265 -159
- package/dist/queryBuilder/queryBuilder.d.ts +1 -0
- package/dist/server.d.ts +2 -1
- package/dist/types.d.ts +2 -1
- package/package.json +1 -1
- package/src/bootstrapApplicationConfig.ts +3 -3
- package/src/core/request.ts +52 -7
- package/src/core/response.ts +2 -2
- package/src/core/routeContext.ts +5 -0
- package/src/core/routesBuilder.ts +24 -11
- package/src/core/server.ts +1 -0
- package/src/dataAccess/dataAccessor.ts +2 -1
- package/src/plugins/authManager/mod.ts +2 -2
- package/src/plugins/authManager/routes/getMyProfile.ts +1 -1
- package/src/plugins/authManager/routes/signin.ts +1 -1
- package/src/plugins/authManager/routes/signout.ts +1 -1
- package/src/plugins/dataManager/mod.ts +7 -3
- package/src/plugins/metaManager/mod.ts +3 -3
- package/src/plugins/webhooks/mod.ts +3 -3
- package/src/queryBuilder/queryBuilder.ts +15 -9
- package/src/server.ts +40 -2
- package/src/types.ts +2 -1
package/dist/core/request.d.ts
CHANGED
|
@@ -3,13 +3,17 @@ export declare const GlobalRequest: {
|
|
|
3
3
|
prototype: Request;
|
|
4
4
|
};
|
|
5
5
|
export interface RapidRequestBody {
|
|
6
|
-
type:
|
|
6
|
+
type: "form-data" | "json" | "form";
|
|
7
7
|
value: any;
|
|
8
8
|
}
|
|
9
9
|
export declare class RapidRequest {
|
|
10
|
+
#private;
|
|
10
11
|
method: string;
|
|
11
12
|
url: URL;
|
|
13
|
+
headers: Headers;
|
|
12
14
|
hasBody: boolean;
|
|
13
|
-
constructor(
|
|
14
|
-
|
|
15
|
+
constructor(req: Request);
|
|
16
|
+
parseBody(): Promise<void>;
|
|
17
|
+
get rawRequest(): Request;
|
|
18
|
+
get body(): RapidRequestBody;
|
|
15
19
|
}
|
package/dist/core/server.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface IRpdServer {
|
|
|
12
12
|
getHttpHandlerByCode(code: string): HttpRequestHandler | undefined;
|
|
13
13
|
getDataAccessor<T = any>(options: GetDataAccessorOptions): IRpdDataAccessor<T>;
|
|
14
14
|
getApplicationConfig(): RpdApplicationConfig;
|
|
15
|
+
appendApplicationConfig(config: Partial<RpdApplicationConfig>): any;
|
|
15
16
|
getModel(options: GetModelOptions): RpdDataModel | undefined;
|
|
16
17
|
registerEventHandler<K extends keyof RpdServerEventTypes>(eventName: K, listener: (...args: RpdServerEventTypes[K]) => void): this;
|
|
17
18
|
emitEvent<K extends keyof RpdServerEventTypes>(eventName: K, sender: IPluginInstance, payload: RpdServerEventTypes[K][1]): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CountEntityOptions, FindEntityOptions, IRpdDataAccessor, RpdDataModel } from "~/types";
|
|
1
|
+
import { CountEntityOptions, FindEntityOptions, IRpdDataAccessor, RpdDataModel, IDatabaseAccessor } from "~/types";
|
|
2
2
|
import QueryBuilder from "~/queryBuilder/queryBuilder";
|
|
3
3
|
export interface IDataAccessorOptions {
|
|
4
4
|
model: RpdDataModel;
|
|
@@ -6,7 +6,7 @@ export interface IDataAccessorOptions {
|
|
|
6
6
|
}
|
|
7
7
|
export default class DataAccessor<T = any> implements IRpdDataAccessor<T> {
|
|
8
8
|
#private;
|
|
9
|
-
constructor(options: IDataAccessorOptions);
|
|
9
|
+
constructor(databaseAccessor: IDatabaseAccessor, options: IDataAccessorOptions);
|
|
10
10
|
getModel(): RpdDataModel;
|
|
11
11
|
create(entity: Partial<T>): Promise<T>;
|
|
12
12
|
updateById(id: any, entity: Partial<T>): Promise<{
|
package/dist/index.js
CHANGED
|
@@ -41,7 +41,8 @@ class DataAccessor {
|
|
|
41
41
|
#model;
|
|
42
42
|
#queryBuilder;
|
|
43
43
|
#databaseAccessor;
|
|
44
|
-
constructor(options) {
|
|
44
|
+
constructor(databaseAccessor, options) {
|
|
45
|
+
this.#databaseAccessor = databaseAccessor;
|
|
45
46
|
this.#queryBuilder = options.queryBuilder;
|
|
46
47
|
this.#model = options.model;
|
|
47
48
|
}
|
|
@@ -153,6 +154,7 @@ class QueryBuilder {
|
|
|
153
154
|
}
|
|
154
155
|
select(model, options) {
|
|
155
156
|
const ctx = {
|
|
157
|
+
builder: this,
|
|
156
158
|
params: [],
|
|
157
159
|
};
|
|
158
160
|
let { properties, filters, orderBy, pagination } = options;
|
|
@@ -191,6 +193,7 @@ class QueryBuilder {
|
|
|
191
193
|
}
|
|
192
194
|
count(model, options) {
|
|
193
195
|
const ctx = {
|
|
196
|
+
builder: this,
|
|
194
197
|
params: [],
|
|
195
198
|
};
|
|
196
199
|
let { filters } = options;
|
|
@@ -208,6 +211,7 @@ class QueryBuilder {
|
|
|
208
211
|
insert(model, options) {
|
|
209
212
|
const params = [];
|
|
210
213
|
const ctx = {
|
|
214
|
+
builder: this,
|
|
211
215
|
params,
|
|
212
216
|
};
|
|
213
217
|
const { entity } = options;
|
|
@@ -242,6 +246,7 @@ class QueryBuilder {
|
|
|
242
246
|
update(model, options) {
|
|
243
247
|
const params = [];
|
|
244
248
|
const ctx = {
|
|
249
|
+
builder: this,
|
|
245
250
|
params,
|
|
246
251
|
};
|
|
247
252
|
let { entity, filters } = options;
|
|
@@ -279,6 +284,7 @@ class QueryBuilder {
|
|
|
279
284
|
delete(model, options) {
|
|
280
285
|
const params = [];
|
|
281
286
|
const ctx = {
|
|
287
|
+
builder: this,
|
|
282
288
|
params,
|
|
283
289
|
};
|
|
284
290
|
let { filters } = options;
|
|
@@ -353,7 +359,7 @@ function buildLogicalFilterQuery(level, ctx, filter) {
|
|
|
353
359
|
return command;
|
|
354
360
|
}
|
|
355
361
|
function buildUnaryFilterQuery(ctx, filter) {
|
|
356
|
-
let command =
|
|
362
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
357
363
|
if (filter.operator === "null") {
|
|
358
364
|
command += " IS NULL";
|
|
359
365
|
}
|
|
@@ -363,7 +369,7 @@ function buildUnaryFilterQuery(ctx, filter) {
|
|
|
363
369
|
return command;
|
|
364
370
|
}
|
|
365
371
|
function buildInFilterQuery(ctx, filter) {
|
|
366
|
-
let command =
|
|
372
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
367
373
|
if (filter.operator === "in") {
|
|
368
374
|
command += " = ";
|
|
369
375
|
}
|
|
@@ -375,49 +381,49 @@ function buildInFilterQuery(ctx, filter) {
|
|
|
375
381
|
return command;
|
|
376
382
|
}
|
|
377
383
|
function buildContainsFilterQuery(ctx, filter) {
|
|
378
|
-
let command =
|
|
384
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
379
385
|
command += " LIKE ";
|
|
380
386
|
ctx.params.push(`%${filter.value}%`);
|
|
381
387
|
command += "$" + ctx.params.length;
|
|
382
388
|
return command;
|
|
383
389
|
}
|
|
384
390
|
function buildNotContainsFilterQuery(ctx, filter) {
|
|
385
|
-
let command =
|
|
391
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
386
392
|
command += " NOT LIKE ";
|
|
387
393
|
ctx.params.push(`%${filter.value}%`);
|
|
388
394
|
command += "$" + ctx.params.length;
|
|
389
395
|
return command;
|
|
390
396
|
}
|
|
391
397
|
function buildStartsWithFilterQuery(ctx, filter) {
|
|
392
|
-
let command =
|
|
398
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
393
399
|
command += " LIKE ";
|
|
394
400
|
ctx.params.push(`${filter.value}%`);
|
|
395
401
|
command += "$" + ctx.params.length;
|
|
396
402
|
return command;
|
|
397
403
|
}
|
|
398
404
|
function buildNotStartsWithFilterQuery(ctx, filter) {
|
|
399
|
-
let command =
|
|
405
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
400
406
|
command += " NOT LIKE ";
|
|
401
407
|
ctx.params.push(`${filter.value}%`);
|
|
402
408
|
command += "$" + ctx.params.length;
|
|
403
409
|
return command;
|
|
404
410
|
}
|
|
405
411
|
function buildEndsWithFilterQuery(ctx, filter) {
|
|
406
|
-
let command =
|
|
412
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
407
413
|
command += " LIKE ";
|
|
408
414
|
ctx.params.push(`%${filter.value}`);
|
|
409
415
|
command += "$" + ctx.params.length;
|
|
410
416
|
return command;
|
|
411
417
|
}
|
|
412
418
|
function buildNotEndsWithFilterQuery(ctx, filter) {
|
|
413
|
-
let command =
|
|
419
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
414
420
|
command += " NOT LIKE ";
|
|
415
421
|
ctx.params.push(`%${filter.value}`);
|
|
416
422
|
command += "$" + ctx.params.length;
|
|
417
423
|
return command;
|
|
418
424
|
}
|
|
419
425
|
function buildRelationalFilterQuery(ctx, filter) {
|
|
420
|
-
let command =
|
|
426
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
421
427
|
command += relationalOperatorsMap.get(filter.operator);
|
|
422
428
|
ctx.params.push(filter.value);
|
|
423
429
|
command += "$" + ctx.params.length;
|
|
@@ -1124,10 +1130,10 @@ async function handleEntityDeleteEvent(server, sender, payload) {
|
|
|
1124
1130
|
async function configureModels$3(server, applicationConfig) {
|
|
1125
1131
|
try {
|
|
1126
1132
|
const models = await listCollections(server, applicationConfig);
|
|
1127
|
-
|
|
1133
|
+
server.appendApplicationConfig({ models });
|
|
1128
1134
|
}
|
|
1129
1135
|
catch (ex) {
|
|
1130
|
-
console.warn("Failed to loading existing meta of models.", ex
|
|
1136
|
+
console.warn("Failed to loading existing meta of models.", ex);
|
|
1131
1137
|
}
|
|
1132
1138
|
}
|
|
1133
1139
|
function listCollections(server, applicationConfig) {
|
|
@@ -1808,7 +1814,7 @@ const routeConfigs = [
|
|
|
1808
1814
|
},
|
|
1809
1815
|
{
|
|
1810
1816
|
code: "updateById",
|
|
1811
|
-
method: "
|
|
1817
|
+
method: "patch",
|
|
1812
1818
|
endpoint: "/:id",
|
|
1813
1819
|
handlerCode: "updateCollectionEntityById",
|
|
1814
1820
|
},
|
|
@@ -1836,7 +1842,8 @@ async function registerHttpHandlers$3(server) {
|
|
|
1836
1842
|
server.registerHttpHandler(_plugin$3, queryDatabase);
|
|
1837
1843
|
}
|
|
1838
1844
|
async function configureRoutes$3(server, applicationConfig) {
|
|
1839
|
-
const {
|
|
1845
|
+
const { models } = applicationConfig;
|
|
1846
|
+
const routes = [];
|
|
1840
1847
|
models.forEach((model) => {
|
|
1841
1848
|
const { namespace, singularCode, pluralCode } = model;
|
|
1842
1849
|
routeConfigs.forEach((routeConfig) => {
|
|
@@ -1846,7 +1853,7 @@ async function configureRoutes$3(server, applicationConfig) {
|
|
|
1846
1853
|
code: `${namespace}.${singularCode}.${routeConfig.code}`,
|
|
1847
1854
|
type: "RESTful",
|
|
1848
1855
|
method: routeConfig.method,
|
|
1849
|
-
endpoint:
|
|
1856
|
+
endpoint: `/${namespace}/${pluralCode}${routeConfig.endpoint}`,
|
|
1850
1857
|
handlers: [
|
|
1851
1858
|
{
|
|
1852
1859
|
code: routeConfig.handlerCode,
|
|
@@ -1859,6 +1866,7 @@ async function configureRoutes$3(server, applicationConfig) {
|
|
|
1859
1866
|
});
|
|
1860
1867
|
});
|
|
1861
1868
|
});
|
|
1869
|
+
server.appendApplicationConfig({ routes });
|
|
1862
1870
|
}
|
|
1863
1871
|
async function onApplicationLoaded$4(server, application) {
|
|
1864
1872
|
console.log("[dataManager.onApplicationLoaded]");
|
|
@@ -1953,11 +1961,21 @@ var listMetaRoutes = /*#__PURE__*/Object.freeze({
|
|
|
1953
1961
|
|
|
1954
1962
|
async function buildRoutes(server, applicationConfig) {
|
|
1955
1963
|
const router = new Router__default["default"]();
|
|
1964
|
+
let baseUrl = server.config.baseUrl;
|
|
1965
|
+
if (baseUrl) {
|
|
1966
|
+
if (baseUrl.endsWith('/')) {
|
|
1967
|
+
baseUrl = baseUrl.substring(0, baseUrl.length - 1);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
else {
|
|
1971
|
+
baseUrl = "";
|
|
1972
|
+
}
|
|
1956
1973
|
applicationConfig.routes.forEach((routeConfig) => {
|
|
1957
1974
|
if (routeConfig.type !== "RESTful") {
|
|
1958
1975
|
return;
|
|
1959
1976
|
}
|
|
1960
|
-
|
|
1977
|
+
const routePath = baseUrl + routeConfig.endpoint;
|
|
1978
|
+
router[routeConfig.method](routePath, async (routerContext, next) => {
|
|
1961
1979
|
const { request, params } = routerContext;
|
|
1962
1980
|
let search = request.url.search;
|
|
1963
1981
|
if (search && search.startsWith("?")) {
|
|
@@ -1967,17 +1985,19 @@ async function buildRoutes(server, applicationConfig) {
|
|
|
1967
1985
|
const input = Object.assign({}, params, query);
|
|
1968
1986
|
const requestMethod = request.method;
|
|
1969
1987
|
if ((requestMethod === "POST" || requestMethod === "PUT" ||
|
|
1970
|
-
requestMethod === "PATCH")
|
|
1971
|
-
const body = request.body
|
|
1972
|
-
if (body
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1988
|
+
requestMethod === "PATCH")) {
|
|
1989
|
+
const body = request.body;
|
|
1990
|
+
if (body) {
|
|
1991
|
+
if (body.type === "form-data") {
|
|
1992
|
+
const formDataReader = body.value;
|
|
1993
|
+
const formDataBody = await formDataReader.read({ maxFileSize: 1073741824 /* 1GB */ });
|
|
1994
|
+
Object.assign(input, {
|
|
1995
|
+
formData: formDataBody
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
else {
|
|
1999
|
+
Object.assign(input, body.value);
|
|
2000
|
+
}
|
|
1981
2001
|
}
|
|
1982
2002
|
}
|
|
1983
2003
|
// Normalize input value
|
|
@@ -2170,9 +2190,9 @@ async function initPlugin$1(plugin, server) {
|
|
|
2170
2190
|
_plugin$1 = plugin;
|
|
2171
2191
|
}
|
|
2172
2192
|
async function configureModels$2(server, applicationConfig) {
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
}
|
|
2193
|
+
server.appendApplicationConfig({
|
|
2194
|
+
models: pluginConfig.models,
|
|
2195
|
+
});
|
|
2176
2196
|
}
|
|
2177
2197
|
async function registerEventHandlers$2(server) {
|
|
2178
2198
|
const events = [
|
|
@@ -2631,7 +2651,7 @@ var getMyProfile = {
|
|
|
2631
2651
|
code: "",
|
|
2632
2652
|
type: "RESTful",
|
|
2633
2653
|
method: "get",
|
|
2634
|
-
endpoint: "/
|
|
2654
|
+
endpoint: "/me",
|
|
2635
2655
|
handlers: [
|
|
2636
2656
|
{
|
|
2637
2657
|
code: "getMyProfile",
|
|
@@ -2645,7 +2665,7 @@ var signin = {
|
|
|
2645
2665
|
code: "",
|
|
2646
2666
|
type: "RESTful",
|
|
2647
2667
|
method: "post",
|
|
2648
|
-
endpoint: "/
|
|
2668
|
+
endpoint: "/signin",
|
|
2649
2669
|
handlers: [
|
|
2650
2670
|
{
|
|
2651
2671
|
code: "createSession",
|
|
@@ -2659,7 +2679,7 @@ var signout = {
|
|
|
2659
2679
|
code: "",
|
|
2660
2680
|
type: "RESTful",
|
|
2661
2681
|
method: "get",
|
|
2662
|
-
endpoint: "/
|
|
2682
|
+
endpoint: "/signout",
|
|
2663
2683
|
handlers: [
|
|
2664
2684
|
{
|
|
2665
2685
|
code: "deleteSession",
|
|
@@ -2693,10 +2713,10 @@ async function registerHttpHandlers$1(server) {
|
|
|
2693
2713
|
async function registerEventHandlers$1(server) {
|
|
2694
2714
|
}
|
|
2695
2715
|
async function configureModels$1(server, applicationConfig) {
|
|
2696
|
-
|
|
2716
|
+
server.appendApplicationConfig({ models: pluginModels });
|
|
2697
2717
|
}
|
|
2698
2718
|
async function configureRoutes$1(server, applicationConfig) {
|
|
2699
|
-
|
|
2719
|
+
server.appendApplicationConfig({ routes: pluginRoutes });
|
|
2700
2720
|
}
|
|
2701
2721
|
async function onApplicationLoaded$1(server, applicationConfig) {
|
|
2702
2722
|
console.log("authManager.onApplicationLoaded");
|
|
@@ -2863,8 +2883,8 @@ class RapidResponse {
|
|
|
2863
2883
|
headers;
|
|
2864
2884
|
constructor(body, init) {
|
|
2865
2885
|
this.body = body;
|
|
2866
|
-
this.headers = new Headers(init
|
|
2867
|
-
this.status = init
|
|
2886
|
+
this.headers = new Headers(init?.headers);
|
|
2887
|
+
this.status = init?.status;
|
|
2868
2888
|
}
|
|
2869
2889
|
json(obj, status, headers) {
|
|
2870
2890
|
const body = JSON.stringify(obj);
|
|
@@ -2904,7 +2924,11 @@ class RouteContext {
|
|
|
2904
2924
|
this.request = request;
|
|
2905
2925
|
this.state = {};
|
|
2906
2926
|
this.response = new RapidResponse();
|
|
2927
|
+
// `method` and `path` are used by `koa-tree-router` to match route
|
|
2928
|
+
this.method = request.method;
|
|
2929
|
+
this.path = request.url.pathname;
|
|
2907
2930
|
}
|
|
2931
|
+
// `koa-tree-router` uses this method to set headers
|
|
2908
2932
|
set(headerName, headerValue) {
|
|
2909
2933
|
this.response.headers.set(headerName, headerValue);
|
|
2910
2934
|
}
|
|
@@ -2918,133 +2942,59 @@ class RouteContext {
|
|
|
2918
2942
|
|
|
2919
2943
|
const GlobalRequest = global.Request;
|
|
2920
2944
|
class RapidRequest {
|
|
2945
|
+
#raw;
|
|
2946
|
+
#bodyParsed;
|
|
2947
|
+
#body;
|
|
2921
2948
|
method;
|
|
2922
2949
|
url;
|
|
2950
|
+
headers;
|
|
2923
2951
|
hasBody;
|
|
2924
|
-
constructor(
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
class RapidServer {
|
|
2935
|
-
#eventManager;
|
|
2936
|
-
#middlewares;
|
|
2937
|
-
#bootstrapApplicationConfig;
|
|
2938
|
-
#applicationConfig;
|
|
2939
|
-
#httpHandlersMapByCode;
|
|
2940
|
-
#databaseAccessor;
|
|
2941
|
-
queryBuilder;
|
|
2942
|
-
config;
|
|
2943
|
-
databaseConfig;
|
|
2944
|
-
#buildedRoutes;
|
|
2945
|
-
constructor(options) {
|
|
2946
|
-
this.#eventManager = new EventManager();
|
|
2947
|
-
this.#middlewares = [];
|
|
2948
|
-
this.#bootstrapApplicationConfig = options.applicationConfig;
|
|
2949
|
-
this.#applicationConfig = {};
|
|
2950
|
-
this.#httpHandlersMapByCode = new Map();
|
|
2951
|
-
this.#databaseAccessor = options.databaseAccessor;
|
|
2952
|
-
this.queryBuilder = new QueryBuilder({
|
|
2953
|
-
dbDefaultSchema: options.databaseConfig.dbDefaultSchema,
|
|
2954
|
-
});
|
|
2955
|
-
this.databaseConfig = options.databaseConfig;
|
|
2956
|
-
this.config = options.serverConfig;
|
|
2957
|
-
}
|
|
2958
|
-
getApplicationConfig() {
|
|
2959
|
-
return this.#applicationConfig;
|
|
2960
|
-
}
|
|
2961
|
-
registerHttpHandler(plugin, options) {
|
|
2962
|
-
const handler = ___namespace.bind(options.handler, null, plugin);
|
|
2963
|
-
this.#httpHandlersMapByCode.set(options.code, handler);
|
|
2964
|
-
}
|
|
2965
|
-
getHttpHandlerByCode(code) {
|
|
2966
|
-
return this.#httpHandlersMapByCode.get(code);
|
|
2967
|
-
}
|
|
2968
|
-
registerMiddleware(middleware) {
|
|
2969
|
-
this.#middlewares.push(middleware);
|
|
2970
|
-
}
|
|
2971
|
-
getDataAccessor(options) {
|
|
2972
|
-
const { namespace, singularCode } = options;
|
|
2973
|
-
// TODO: Should reuse the and DataAccessor instance
|
|
2974
|
-
const model = this.getModel(options);
|
|
2975
|
-
if (!model) {
|
|
2976
|
-
throw new Error(`Data model ${namespace}.${singularCode} not found.`);
|
|
2952
|
+
constructor(req) {
|
|
2953
|
+
this.#raw = req;
|
|
2954
|
+
this.method = req.method;
|
|
2955
|
+
this.url = new URL(req.url);
|
|
2956
|
+
this.headers = req.headers;
|
|
2957
|
+
}
|
|
2958
|
+
async parseBody() {
|
|
2959
|
+
if (this.#bodyParsed) {
|
|
2960
|
+
console.warn("Request body has been parsed. 'parseBody()' method should not be called more than once.");
|
|
2961
|
+
return;
|
|
2977
2962
|
}
|
|
2978
|
-
const
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2963
|
+
const requestMethod = this.method;
|
|
2964
|
+
if (requestMethod === "POST" || requestMethod === "PUT" || requestMethod === "PATCH") {
|
|
2965
|
+
const req = this.#raw;
|
|
2966
|
+
const contentType = this.headers.get("Content-Type");
|
|
2967
|
+
if (contentType.includes("json")) {
|
|
2968
|
+
this.#body = {
|
|
2969
|
+
type: "json",
|
|
2970
|
+
value: await req.json(),
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
else if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
|
2974
|
+
const bodyText = await req.text();
|
|
2975
|
+
this.#body = {
|
|
2976
|
+
type: "form",
|
|
2977
|
+
value: qs__default["default"].parse(bodyText),
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
else if (contentType.startsWith("multipart/form-data")) {
|
|
2981
|
+
this.#body = {
|
|
2982
|
+
type: "form-data",
|
|
2983
|
+
value: await req.formData(),
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2987
2986
|
}
|
|
2988
|
-
|
|
2987
|
+
this.#body = null;
|
|
2988
|
+
this.#bodyParsed = true;
|
|
2989
2989
|
}
|
|
2990
|
-
|
|
2991
|
-
this.#
|
|
2992
|
-
return this;
|
|
2990
|
+
get rawRequest() {
|
|
2991
|
+
return this.#raw;
|
|
2993
2992
|
}
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
// TODO: should move this logic into metaManager
|
|
2998
|
-
// if (
|
|
2999
|
-
// (eventName === "entity.create" || eventName === "entity.update" ||
|
|
3000
|
-
// eventName === "entity.delete") &&
|
|
3001
|
-
// payload.namespace === "meta"
|
|
3002
|
-
// ) {
|
|
3003
|
-
// await this.configureApplication();
|
|
3004
|
-
// }
|
|
3005
|
-
}
|
|
3006
|
-
async start() {
|
|
3007
|
-
console.log("Starting rapid server...");
|
|
3008
|
-
await loadPlugins();
|
|
3009
|
-
await initPlugins(this);
|
|
3010
|
-
await registerMiddlewares(this);
|
|
3011
|
-
await registerHttpHandlers(this);
|
|
3012
|
-
await registerEventHandlers(this);
|
|
3013
|
-
await registerMessageHandlers(this);
|
|
3014
|
-
await registerTaskProcessors(this);
|
|
3015
|
-
await this.configureApplication();
|
|
3016
|
-
console.log(`Application ready.`);
|
|
3017
|
-
await onApplicationReady(this, this.#applicationConfig);
|
|
3018
|
-
}
|
|
3019
|
-
async configureApplication() {
|
|
3020
|
-
this.#applicationConfig = ___namespace.merge({}, this.#bootstrapApplicationConfig);
|
|
3021
|
-
await onLoadingApplication(this, this.#applicationConfig);
|
|
3022
|
-
await configureModels(this, this.#applicationConfig);
|
|
3023
|
-
await configureModelProperties(this, this.#applicationConfig);
|
|
3024
|
-
await configureRoutes(this, this.#applicationConfig);
|
|
3025
|
-
// TODO: check application configuration.
|
|
3026
|
-
await onApplicationLoaded(this, this.#applicationConfig);
|
|
3027
|
-
this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
|
|
3028
|
-
}
|
|
3029
|
-
async queryDatabaseObject(sql, params) {
|
|
3030
|
-
return await this.#databaseAccessor.queryDatabaseObject(sql, params);
|
|
3031
|
-
}
|
|
3032
|
-
async tryQueryDatabaseObject(sql, params) {
|
|
3033
|
-
try {
|
|
3034
|
-
return await this.queryDatabaseObject(sql, params);
|
|
3035
|
-
}
|
|
3036
|
-
catch (err) {
|
|
3037
|
-
console.error(err.message);
|
|
2993
|
+
get body() {
|
|
2994
|
+
if (!this.#bodyParsed) {
|
|
2995
|
+
throw new Error("Request body not parsed, you should call 'parseBody()' method before getting the body.");
|
|
3038
2996
|
}
|
|
3039
|
-
return
|
|
3040
|
-
}
|
|
3041
|
-
get middlewares() {
|
|
3042
|
-
return this.#middlewares;
|
|
3043
|
-
}
|
|
3044
|
-
async handleRequest(request, next) {
|
|
3045
|
-
const routeContext = new RouteContext(new RapidRequest(request));
|
|
3046
|
-
await this.#buildedRoutes(routeContext, next);
|
|
3047
|
-
return routeContext.response.getResponse();
|
|
2997
|
+
return this.#body;
|
|
3048
2998
|
}
|
|
3049
2999
|
}
|
|
3050
3000
|
|
|
@@ -3535,7 +3485,7 @@ var bootstrapApplicationConfig = {
|
|
|
3535
3485
|
code: "meta.model.list",
|
|
3536
3486
|
type: "RESTful",
|
|
3537
3487
|
method: "get",
|
|
3538
|
-
endpoint: "/
|
|
3488
|
+
endpoint: "/_meta/models",
|
|
3539
3489
|
handlers: [
|
|
3540
3490
|
{
|
|
3541
3491
|
code: "listMetaModels",
|
|
@@ -3548,7 +3498,7 @@ var bootstrapApplicationConfig = {
|
|
|
3548
3498
|
code: "meta.model.getDetail",
|
|
3549
3499
|
type: "RESTful",
|
|
3550
3500
|
method: "get",
|
|
3551
|
-
endpoint: "/
|
|
3501
|
+
endpoint: "/_meta/models/:namespace/:singularCode",
|
|
3552
3502
|
handlers: [
|
|
3553
3503
|
{
|
|
3554
3504
|
code: "getMetaModelDetail",
|
|
@@ -3561,7 +3511,7 @@ var bootstrapApplicationConfig = {
|
|
|
3561
3511
|
code: "meta.route.list",
|
|
3562
3512
|
type: "RESTful",
|
|
3563
3513
|
method: "get",
|
|
3564
|
-
endpoint: "/
|
|
3514
|
+
endpoint: "/_meta/routes",
|
|
3565
3515
|
handlers: [
|
|
3566
3516
|
{
|
|
3567
3517
|
code: "listMetaRoutes",
|
|
@@ -3576,6 +3526,162 @@ var bootstrapApplicationConfig$1 = /*#__PURE__*/Object.freeze({
|
|
|
3576
3526
|
'default': bootstrapApplicationConfig
|
|
3577
3527
|
});
|
|
3578
3528
|
|
|
3529
|
+
class RapidServer {
|
|
3530
|
+
#eventManager;
|
|
3531
|
+
#middlewares;
|
|
3532
|
+
#bootstrapApplicationConfig;
|
|
3533
|
+
#applicationConfig;
|
|
3534
|
+
#httpHandlersMapByCode;
|
|
3535
|
+
#databaseAccessor;
|
|
3536
|
+
queryBuilder;
|
|
3537
|
+
config;
|
|
3538
|
+
databaseConfig;
|
|
3539
|
+
#buildedRoutes;
|
|
3540
|
+
constructor(options) {
|
|
3541
|
+
this.#eventManager = new EventManager();
|
|
3542
|
+
this.#middlewares = [];
|
|
3543
|
+
this.#bootstrapApplicationConfig = options.applicationConfig || bootstrapApplicationConfig;
|
|
3544
|
+
this.#applicationConfig = {};
|
|
3545
|
+
this.#httpHandlersMapByCode = new Map();
|
|
3546
|
+
this.#databaseAccessor = options.databaseAccessor;
|
|
3547
|
+
this.queryBuilder = new QueryBuilder({
|
|
3548
|
+
dbDefaultSchema: options.databaseConfig.dbDefaultSchema,
|
|
3549
|
+
});
|
|
3550
|
+
this.databaseConfig = options.databaseConfig;
|
|
3551
|
+
this.config = options.serverConfig;
|
|
3552
|
+
}
|
|
3553
|
+
getApplicationConfig() {
|
|
3554
|
+
return this.#applicationConfig;
|
|
3555
|
+
}
|
|
3556
|
+
appendApplicationConfig(config) {
|
|
3557
|
+
const { models, routes } = config;
|
|
3558
|
+
if (models) {
|
|
3559
|
+
for (const model of models) {
|
|
3560
|
+
const originalModel = ___namespace.find(this.#applicationConfig.models, (item) => item.singularCode == model.singularCode);
|
|
3561
|
+
if (originalModel) {
|
|
3562
|
+
originalModel.name = model.name;
|
|
3563
|
+
const originalProperties = originalModel.properties;
|
|
3564
|
+
for (const property of model.properties) {
|
|
3565
|
+
const originalProperty = ___namespace.find(originalProperties, (item) => item.code == property.code);
|
|
3566
|
+
if (originalProperty) {
|
|
3567
|
+
originalProperty.name = property.name;
|
|
3568
|
+
}
|
|
3569
|
+
else {
|
|
3570
|
+
originalProperties.push(property);
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
else {
|
|
3575
|
+
this.#applicationConfig.models.push(model);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
if (routes) {
|
|
3580
|
+
for (const route of routes) {
|
|
3581
|
+
const originalRoute = ___namespace.find(this.#applicationConfig.routes, (item) => item.code == route.code);
|
|
3582
|
+
if (originalRoute) {
|
|
3583
|
+
originalRoute.name = route.name;
|
|
3584
|
+
originalRoute.handlers = route.handlers;
|
|
3585
|
+
}
|
|
3586
|
+
else {
|
|
3587
|
+
this.#applicationConfig.routes.push(route);
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
registerHttpHandler(plugin, options) {
|
|
3593
|
+
const handler = ___namespace.bind(options.handler, null, plugin);
|
|
3594
|
+
this.#httpHandlersMapByCode.set(options.code, handler);
|
|
3595
|
+
}
|
|
3596
|
+
getHttpHandlerByCode(code) {
|
|
3597
|
+
return this.#httpHandlersMapByCode.get(code);
|
|
3598
|
+
}
|
|
3599
|
+
registerMiddleware(middleware) {
|
|
3600
|
+
this.#middlewares.push(middleware);
|
|
3601
|
+
}
|
|
3602
|
+
getDataAccessor(options) {
|
|
3603
|
+
const { namespace, singularCode } = options;
|
|
3604
|
+
// TODO: Should reuse the and DataAccessor instance
|
|
3605
|
+
const model = this.getModel(options);
|
|
3606
|
+
if (!model) {
|
|
3607
|
+
throw new Error(`Data model ${namespace}.${singularCode} not found.`);
|
|
3608
|
+
}
|
|
3609
|
+
const dataAccessor = new DataAccessor(this.#databaseAccessor, {
|
|
3610
|
+
model,
|
|
3611
|
+
queryBuilder: this.queryBuilder,
|
|
3612
|
+
});
|
|
3613
|
+
return dataAccessor;
|
|
3614
|
+
}
|
|
3615
|
+
getModel(options) {
|
|
3616
|
+
if (options.namespace) {
|
|
3617
|
+
return this.#applicationConfig?.models.find((e) => e.namespace === options.namespace && e.singularCode === options.singularCode);
|
|
3618
|
+
}
|
|
3619
|
+
return this.#applicationConfig?.models.find((e) => e.singularCode === options.singularCode);
|
|
3620
|
+
}
|
|
3621
|
+
registerEventHandler(eventName, listener) {
|
|
3622
|
+
this.#eventManager.on(eventName, listener);
|
|
3623
|
+
return this;
|
|
3624
|
+
}
|
|
3625
|
+
async emitEvent(eventName, sender, payload) {
|
|
3626
|
+
console.log(`Emit event "${eventName}"`, payload);
|
|
3627
|
+
await this.#eventManager.emit(eventName, sender, payload);
|
|
3628
|
+
// TODO: should move this logic into metaManager
|
|
3629
|
+
// if (
|
|
3630
|
+
// (eventName === "entity.create" || eventName === "entity.update" ||
|
|
3631
|
+
// eventName === "entity.delete") &&
|
|
3632
|
+
// payload.namespace === "meta"
|
|
3633
|
+
// ) {
|
|
3634
|
+
// await this.configureApplication();
|
|
3635
|
+
// }
|
|
3636
|
+
}
|
|
3637
|
+
async start() {
|
|
3638
|
+
console.log("Starting rapid server...");
|
|
3639
|
+
await loadPlugins();
|
|
3640
|
+
await initPlugins(this);
|
|
3641
|
+
await registerMiddlewares(this);
|
|
3642
|
+
await registerHttpHandlers(this);
|
|
3643
|
+
await registerEventHandlers(this);
|
|
3644
|
+
await registerMessageHandlers(this);
|
|
3645
|
+
await registerTaskProcessors(this);
|
|
3646
|
+
await this.configureApplication();
|
|
3647
|
+
console.log(`Application ready.`);
|
|
3648
|
+
await onApplicationReady(this, this.#applicationConfig);
|
|
3649
|
+
}
|
|
3650
|
+
async configureApplication() {
|
|
3651
|
+
this.#applicationConfig = ___namespace.merge({}, this.#bootstrapApplicationConfig);
|
|
3652
|
+
await onLoadingApplication(this, this.#applicationConfig);
|
|
3653
|
+
await configureModels(this, this.#applicationConfig);
|
|
3654
|
+
await configureModelProperties(this, this.#applicationConfig);
|
|
3655
|
+
await configureRoutes(this, this.#applicationConfig);
|
|
3656
|
+
// TODO: check application configuration.
|
|
3657
|
+
await onApplicationLoaded(this, this.#applicationConfig);
|
|
3658
|
+
this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
|
|
3659
|
+
}
|
|
3660
|
+
async queryDatabaseObject(sql, params) {
|
|
3661
|
+
return await this.#databaseAccessor.queryDatabaseObject(sql, params);
|
|
3662
|
+
}
|
|
3663
|
+
async tryQueryDatabaseObject(sql, params) {
|
|
3664
|
+
try {
|
|
3665
|
+
return await this.queryDatabaseObject(sql, params);
|
|
3666
|
+
}
|
|
3667
|
+
catch (err) {
|
|
3668
|
+
console.error(err.message);
|
|
3669
|
+
}
|
|
3670
|
+
return [];
|
|
3671
|
+
}
|
|
3672
|
+
get middlewares() {
|
|
3673
|
+
return this.#middlewares;
|
|
3674
|
+
}
|
|
3675
|
+
async handleRequest(request, next) {
|
|
3676
|
+
const rapidRequest = new RapidRequest(request);
|
|
3677
|
+
await rapidRequest.parseBody();
|
|
3678
|
+
const routeContext = new RouteContext(rapidRequest);
|
|
3679
|
+
console.log('routeContext', routeContext);
|
|
3680
|
+
await this.#buildedRoutes(routeContext, next);
|
|
3681
|
+
return routeContext.response.getResponse();
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3579
3685
|
fixBigIntJSONSerialize();
|
|
3580
3686
|
|
|
3581
3687
|
exports.GlobalRequest = GlobalRequest;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CountEntityOptions, DeleteEntityOptions, EntityFilterOptions, FindEntityOptions, CreateEntityOptions, RpdDataModel, UpdateEntityOptions, QuoteTableOptions } from "../types";
|
|
2
2
|
export interface BuildQueryContext {
|
|
3
|
+
builder: QueryBuilder;
|
|
3
4
|
params: any[];
|
|
4
5
|
}
|
|
5
6
|
export interface InitQueryBuilderOptions {
|
package/dist/server.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface InitServerOptions {
|
|
|
6
6
|
databaseAccessor: IDatabaseAccessor;
|
|
7
7
|
databaseConfig: IDatabaseConfig;
|
|
8
8
|
serverConfig: RapidServerConfig;
|
|
9
|
-
applicationConfig
|
|
9
|
+
applicationConfig?: RpdApplicationConfig;
|
|
10
10
|
}
|
|
11
11
|
export declare class RapidServer implements IRpdServer {
|
|
12
12
|
#private;
|
|
@@ -15,6 +15,7 @@ export declare class RapidServer implements IRpdServer {
|
|
|
15
15
|
databaseConfig: IDatabaseConfig;
|
|
16
16
|
constructor(options: InitServerOptions);
|
|
17
17
|
getApplicationConfig(): RpdApplicationConfig;
|
|
18
|
+
appendApplicationConfig(config: Partial<RpdApplicationConfig>): void;
|
|
18
19
|
registerHttpHandler(plugin: IPluginInstance, options: IPluginHttpHandler): void;
|
|
19
20
|
getHttpHandlerByCode(code: string): HttpRequestHandler;
|
|
20
21
|
registerMiddleware(middleware: any): void;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type RapidServerConfig = {
|
|
2
|
+
baseUrl?: string;
|
|
2
3
|
sessionCookieName: string;
|
|
3
4
|
jwtKey: string;
|
|
4
5
|
localFileStoragePath: string;
|
|
@@ -246,7 +247,7 @@ export interface RpdRoute {
|
|
|
246
247
|
endpoint: string;
|
|
247
248
|
handlers: RpdHttpHandler[];
|
|
248
249
|
}
|
|
249
|
-
export type RpdHttpMethod = "get" | "post" | "put" | "delete";
|
|
250
|
+
export type RpdHttpMethod = "get" | "post" | "put" | "delete" | "patch";
|
|
250
251
|
export interface RpdHttpHandler {
|
|
251
252
|
code: string;
|
|
252
253
|
config: any;
|
package/package.json
CHANGED
|
@@ -487,7 +487,7 @@ export default {
|
|
|
487
487
|
code: "meta.model.list",
|
|
488
488
|
type: "RESTful",
|
|
489
489
|
method: "get",
|
|
490
|
-
endpoint: "/
|
|
490
|
+
endpoint: "/_meta/models",
|
|
491
491
|
handlers: [
|
|
492
492
|
{
|
|
493
493
|
code: "listMetaModels",
|
|
@@ -500,7 +500,7 @@ export default {
|
|
|
500
500
|
code: "meta.model.getDetail",
|
|
501
501
|
type: "RESTful",
|
|
502
502
|
method: "get",
|
|
503
|
-
endpoint: "/
|
|
503
|
+
endpoint: "/_meta/models/:namespace/:singularCode",
|
|
504
504
|
handlers: [
|
|
505
505
|
{
|
|
506
506
|
code: "getMetaModelDetail",
|
|
@@ -513,7 +513,7 @@ export default {
|
|
|
513
513
|
code: "meta.route.list",
|
|
514
514
|
type: "RESTful",
|
|
515
515
|
method: "get",
|
|
516
|
-
endpoint: "/
|
|
516
|
+
endpoint: "/_meta/routes",
|
|
517
517
|
handlers: [
|
|
518
518
|
{
|
|
519
519
|
code: "listMetaRoutes",
|
package/src/core/request.ts
CHANGED
|
@@ -1,23 +1,68 @@
|
|
|
1
|
-
|
|
1
|
+
import qs from "qs";
|
|
2
2
|
|
|
3
|
+
export const GlobalRequest = global.Request;
|
|
3
4
|
|
|
4
5
|
export interface RapidRequestBody {
|
|
5
|
-
type:
|
|
6
|
+
type: "form-data" | "json" | "form";
|
|
6
7
|
value: any;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export class RapidRequest {
|
|
11
|
+
#raw: Request;
|
|
12
|
+
#bodyParsed: boolean;
|
|
13
|
+
#body: RapidRequestBody;
|
|
10
14
|
method: string;
|
|
11
15
|
url: URL;
|
|
16
|
+
headers: Headers;
|
|
12
17
|
hasBody: boolean;
|
|
13
18
|
|
|
14
|
-
constructor(
|
|
19
|
+
constructor(req: Request) {
|
|
20
|
+
this.#raw = req;
|
|
21
|
+
this.method = req.method;
|
|
22
|
+
this.url = new URL(req.url);
|
|
23
|
+
this.headers = req.headers;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async parseBody(): Promise<void> {
|
|
27
|
+
if (this.#bodyParsed) {
|
|
28
|
+
console.warn("Request body has been parsed. 'parseBody()' method should not be called more than once.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const requestMethod = this.method;
|
|
33
|
+
if (requestMethod === "POST" || requestMethod === "PUT" || requestMethod === "PATCH") {
|
|
34
|
+
const req = this.#raw;
|
|
35
|
+
const contentType = this.headers.get("Content-Type");
|
|
36
|
+
if (contentType.includes("json")) {
|
|
37
|
+
this.#body = {
|
|
38
|
+
type: "json",
|
|
39
|
+
value: await req.json(),
|
|
40
|
+
};
|
|
41
|
+
} else if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
|
42
|
+
const bodyText = await req.text();
|
|
43
|
+
this.#body = {
|
|
44
|
+
type: "form",
|
|
45
|
+
value: qs.parse(bodyText),
|
|
46
|
+
}
|
|
47
|
+
} else if (contentType.startsWith("multipart/form-data")) {
|
|
48
|
+
this.#body = {
|
|
49
|
+
type: "form-data",
|
|
50
|
+
value: await req.formData(),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
this.#body = null;
|
|
55
|
+
this.#bodyParsed = true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get rawRequest(): Request {
|
|
59
|
+
return this.#raw;
|
|
15
60
|
}
|
|
16
61
|
|
|
17
|
-
body(): RapidRequestBody {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
value: {}
|
|
62
|
+
get body(): RapidRequestBody {
|
|
63
|
+
if (!this.#bodyParsed) {
|
|
64
|
+
throw new Error("Request body not parsed, you should call 'parseBody()' method before getting the body.")
|
|
21
65
|
}
|
|
66
|
+
return this.#body;
|
|
22
67
|
}
|
|
23
68
|
}
|
package/src/core/response.ts
CHANGED
|
@@ -40,8 +40,8 @@ export class RapidResponse {
|
|
|
40
40
|
|
|
41
41
|
constructor(body?: BodyInit, init?: ResponseInit) {
|
|
42
42
|
this.body = body;
|
|
43
|
-
this.headers = new Headers(init
|
|
44
|
-
this.status = init
|
|
43
|
+
this.headers = new Headers(init?.headers);
|
|
44
|
+
this.status = init?.status;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
json(
|
package/src/core/routeContext.ts
CHANGED
|
@@ -18,8 +18,13 @@ export class RouteContext {
|
|
|
18
18
|
this.request = request;
|
|
19
19
|
this.state = {};
|
|
20
20
|
this.response = new RapidResponse();
|
|
21
|
+
|
|
22
|
+
// `method` and `path` are used by `koa-tree-router` to match route
|
|
23
|
+
this.method = request.method;
|
|
24
|
+
this.path = request.url.pathname;
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
// `koa-tree-router` uses this method to set headers
|
|
23
28
|
set(headerName: string, headerValue: string) {
|
|
24
29
|
this.response.headers.set(headerName, headerValue);
|
|
25
30
|
}
|
|
@@ -12,13 +12,24 @@ export async function buildRoutes(
|
|
|
12
12
|
) {
|
|
13
13
|
const router = new Router();
|
|
14
14
|
|
|
15
|
+
let baseUrl = server.config.baseUrl;
|
|
16
|
+
if (baseUrl) {
|
|
17
|
+
if (baseUrl.endsWith('/')) {
|
|
18
|
+
baseUrl = baseUrl.substring(0, baseUrl.length - 1);
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
baseUrl = "";
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
applicationConfig.routes.forEach((routeConfig) => {
|
|
16
25
|
if (routeConfig.type !== "RESTful") {
|
|
17
26
|
return;
|
|
18
27
|
}
|
|
19
28
|
|
|
29
|
+
const routePath = baseUrl + routeConfig.endpoint;
|
|
30
|
+
|
|
20
31
|
(router as any)[routeConfig.method](
|
|
21
|
-
|
|
32
|
+
routePath,
|
|
22
33
|
async (routerContext: RouteContext, next: Next) => {
|
|
23
34
|
const { request, params } = routerContext;
|
|
24
35
|
|
|
@@ -32,17 +43,19 @@ export async function buildRoutes(
|
|
|
32
43
|
const requestMethod = request.method;
|
|
33
44
|
if (
|
|
34
45
|
(requestMethod === "POST" || requestMethod === "PUT" ||
|
|
35
|
-
requestMethod === "PATCH")
|
|
46
|
+
requestMethod === "PATCH")
|
|
36
47
|
) {
|
|
37
|
-
const body = request.body
|
|
38
|
-
if (body
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
const body = request.body;
|
|
49
|
+
if (body) {
|
|
50
|
+
if (body.type === "form-data") {
|
|
51
|
+
const formDataReader = body.value;
|
|
52
|
+
const formDataBody = await formDataReader.read({ maxFileSize: 1073741824 /* 1GB */});
|
|
53
|
+
Object.assign(input, {
|
|
54
|
+
formData: formDataBody
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
Object.assign(input, body.value);
|
|
58
|
+
}
|
|
46
59
|
}
|
|
47
60
|
}
|
|
48
61
|
|
package/src/core/server.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface IRpdServer {
|
|
|
24
24
|
options: GetDataAccessorOptions,
|
|
25
25
|
): IRpdDataAccessor<T>;
|
|
26
26
|
getApplicationConfig(): RpdApplicationConfig;
|
|
27
|
+
appendApplicationConfig(config: Partial<RpdApplicationConfig>);
|
|
27
28
|
getModel(options: GetModelOptions): RpdDataModel | undefined;
|
|
28
29
|
registerEventHandler<K extends keyof RpdServerEventTypes>(
|
|
29
30
|
eventName: K,
|
|
@@ -20,7 +20,8 @@ export default class DataAccessor<T = any> implements IRpdDataAccessor<T> {
|
|
|
20
20
|
#queryBuilder: QueryBuilder;
|
|
21
21
|
#databaseAccessor: IDatabaseAccessor;
|
|
22
22
|
|
|
23
|
-
constructor(options: IDataAccessorOptions) {
|
|
23
|
+
constructor(databaseAccessor: IDatabaseAccessor, options: IDataAccessorOptions) {
|
|
24
|
+
this.#databaseAccessor = databaseAccessor;
|
|
24
25
|
this.#queryBuilder = options.queryBuilder;
|
|
25
26
|
this.#model = options.model;
|
|
26
27
|
}
|
|
@@ -38,14 +38,14 @@ export async function configureModels(
|
|
|
38
38
|
server: IRpdServer,
|
|
39
39
|
applicationConfig: RpdApplicationConfig,
|
|
40
40
|
) {
|
|
41
|
-
|
|
41
|
+
server.appendApplicationConfig({ models: pluginModels });
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export async function configureRoutes(
|
|
45
45
|
server: IRpdServer,
|
|
46
46
|
applicationConfig: RpdApplicationConfig,
|
|
47
47
|
) {
|
|
48
|
-
|
|
48
|
+
server.appendApplicationConfig({ routes: pluginRoutes });
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export async function onApplicationLoaded(
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
IPluginInstance,
|
|
10
10
|
RpdApplicationConfig,
|
|
11
11
|
RpdHttpMethod,
|
|
12
|
+
RpdRoute,
|
|
12
13
|
RunEntityHttpHandlerOptions,
|
|
13
14
|
} from "~/types";
|
|
14
15
|
|
|
@@ -80,7 +81,7 @@ const routeConfigs: {
|
|
|
80
81
|
},
|
|
81
82
|
{
|
|
82
83
|
code: "updateById",
|
|
83
|
-
method: "
|
|
84
|
+
method: "patch",
|
|
84
85
|
endpoint: "/:id",
|
|
85
86
|
handlerCode: "updateCollectionEntityById",
|
|
86
87
|
},
|
|
@@ -115,7 +116,8 @@ export async function configureRoutes(
|
|
|
115
116
|
server: IRpdServer,
|
|
116
117
|
applicationConfig: RpdApplicationConfig,
|
|
117
118
|
) {
|
|
118
|
-
const {
|
|
119
|
+
const { models } = applicationConfig;
|
|
120
|
+
const routes: RpdRoute[] = [];
|
|
119
121
|
|
|
120
122
|
models.forEach((model) => {
|
|
121
123
|
const { namespace, singularCode, pluralCode } = model;
|
|
@@ -127,7 +129,7 @@ export async function configureRoutes(
|
|
|
127
129
|
code: `${namespace}.${singularCode}.${routeConfig.code}`,
|
|
128
130
|
type: "RESTful",
|
|
129
131
|
method: routeConfig.method,
|
|
130
|
-
endpoint:
|
|
132
|
+
endpoint: `/${namespace}/${pluralCode}${routeConfig.endpoint}`,
|
|
131
133
|
handlers: [
|
|
132
134
|
{
|
|
133
135
|
code: routeConfig.handlerCode,
|
|
@@ -140,6 +142,8 @@ export async function configureRoutes(
|
|
|
140
142
|
});
|
|
141
143
|
});
|
|
142
144
|
});
|
|
145
|
+
|
|
146
|
+
server.appendApplicationConfig({ routes });
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
export async function onApplicationLoaded(
|
|
@@ -155,10 +155,10 @@ export async function configureModels(
|
|
|
155
155
|
applicationConfig: RpdApplicationConfig,
|
|
156
156
|
) {
|
|
157
157
|
try {
|
|
158
|
-
const models = await listCollections(server, applicationConfig);
|
|
159
|
-
|
|
158
|
+
const models: RpdDataModel[] = await listCollections(server, applicationConfig);
|
|
159
|
+
server.appendApplicationConfig({ models });
|
|
160
160
|
} catch (ex) {
|
|
161
|
-
console.warn("Failed to loading existing meta of models.", ex
|
|
161
|
+
console.warn("Failed to loading existing meta of models.", ex);
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -44,9 +44,9 @@ export async function configureModels(
|
|
|
44
44
|
server: IRpdServer,
|
|
45
45
|
applicationConfig: RpdApplicationConfig,
|
|
46
46
|
) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
47
|
+
server.appendApplicationConfig({
|
|
48
|
+
models: pluginConfig.models,
|
|
49
|
+
});
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export async function registerEventHandlers(server: IRpdServer) {
|
|
@@ -32,6 +32,7 @@ const relationalOperatorsMap = new Map<EntityFilterRelationalOperators, string>(
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
export interface BuildQueryContext {
|
|
35
|
+
builder: QueryBuilder;
|
|
35
36
|
params: any[];
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -63,6 +64,7 @@ export default class QueryBuilder {
|
|
|
63
64
|
|
|
64
65
|
select(model: RpdDataModel, options: FindEntityOptions) {
|
|
65
66
|
const ctx: BuildQueryContext = {
|
|
67
|
+
builder: this,
|
|
66
68
|
params: [],
|
|
67
69
|
};
|
|
68
70
|
let { properties, filters, orderBy, pagination } = options;
|
|
@@ -107,6 +109,7 @@ export default class QueryBuilder {
|
|
|
107
109
|
|
|
108
110
|
count(model: RpdDataModel, options: CountEntityOptions) {
|
|
109
111
|
const ctx: BuildQueryContext = {
|
|
112
|
+
builder: this,
|
|
110
113
|
params: [],
|
|
111
114
|
};
|
|
112
115
|
let { filters } = options;
|
|
@@ -128,6 +131,7 @@ export default class QueryBuilder {
|
|
|
128
131
|
insert(model: RpdDataModel, options: CreateEntityOptions) {
|
|
129
132
|
const params: any[] = [];
|
|
130
133
|
const ctx: BuildQueryContext = {
|
|
134
|
+
builder: this,
|
|
131
135
|
params,
|
|
132
136
|
};
|
|
133
137
|
const { entity } = options;
|
|
@@ -171,6 +175,7 @@ export default class QueryBuilder {
|
|
|
171
175
|
update(model: RpdDataModel, options: UpdateEntityOptions) {
|
|
172
176
|
const params: any[] = [];
|
|
173
177
|
const ctx: BuildQueryContext = {
|
|
178
|
+
builder: this,
|
|
174
179
|
params,
|
|
175
180
|
};
|
|
176
181
|
let { entity, filters } = options;
|
|
@@ -218,6 +223,7 @@ export default class QueryBuilder {
|
|
|
218
223
|
delete(model: RpdDataModel, options: DeleteEntityOptions) {
|
|
219
224
|
const params: any[] = [];
|
|
220
225
|
const ctx: BuildQueryContext = {
|
|
226
|
+
builder: this,
|
|
221
227
|
params,
|
|
222
228
|
};
|
|
223
229
|
let { filters } = options;
|
|
@@ -305,7 +311,7 @@ function buildUnaryFilterQuery(
|
|
|
305
311
|
ctx: BuildQueryContext,
|
|
306
312
|
filter: FindEntityUnaryFilterOptions,
|
|
307
313
|
) {
|
|
308
|
-
let command =
|
|
314
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
309
315
|
if (filter.operator === "null") {
|
|
310
316
|
command += " IS NULL";
|
|
311
317
|
} else {
|
|
@@ -318,7 +324,7 @@ function buildInFilterQuery(
|
|
|
318
324
|
ctx: BuildQueryContext,
|
|
319
325
|
filter: FindEntitySetFilterOptions,
|
|
320
326
|
) {
|
|
321
|
-
let command =
|
|
327
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
322
328
|
|
|
323
329
|
if (filter.operator === "in") {
|
|
324
330
|
command += " = ";
|
|
@@ -335,7 +341,7 @@ function buildContainsFilterQuery(
|
|
|
335
341
|
ctx: BuildQueryContext,
|
|
336
342
|
filter: FindEntityRelationalFilterOptions,
|
|
337
343
|
) {
|
|
338
|
-
let command =
|
|
344
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
339
345
|
|
|
340
346
|
command += " LIKE ";
|
|
341
347
|
ctx.params.push(`%${filter.value}%`);
|
|
@@ -348,7 +354,7 @@ function buildNotContainsFilterQuery(
|
|
|
348
354
|
ctx: BuildQueryContext,
|
|
349
355
|
filter: FindEntityRelationalFilterOptions,
|
|
350
356
|
) {
|
|
351
|
-
let command =
|
|
357
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
352
358
|
|
|
353
359
|
command += " NOT LIKE ";
|
|
354
360
|
ctx.params.push(`%${filter.value}%`);
|
|
@@ -361,7 +367,7 @@ function buildStartsWithFilterQuery(
|
|
|
361
367
|
ctx: BuildQueryContext,
|
|
362
368
|
filter: FindEntityRelationalFilterOptions,
|
|
363
369
|
) {
|
|
364
|
-
let command =
|
|
370
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
365
371
|
|
|
366
372
|
command += " LIKE ";
|
|
367
373
|
ctx.params.push(`${filter.value}%`);
|
|
@@ -374,7 +380,7 @@ function buildNotStartsWithFilterQuery(
|
|
|
374
380
|
ctx: BuildQueryContext,
|
|
375
381
|
filter: FindEntityRelationalFilterOptions,
|
|
376
382
|
) {
|
|
377
|
-
let command =
|
|
383
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
378
384
|
|
|
379
385
|
command += " NOT LIKE ";
|
|
380
386
|
ctx.params.push(`${filter.value}%`);
|
|
@@ -387,7 +393,7 @@ function buildEndsWithFilterQuery(
|
|
|
387
393
|
ctx: BuildQueryContext,
|
|
388
394
|
filter: FindEntityRelationalFilterOptions,
|
|
389
395
|
) {
|
|
390
|
-
let command =
|
|
396
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
391
397
|
|
|
392
398
|
command += " LIKE ";
|
|
393
399
|
ctx.params.push(`%${filter.value}`);
|
|
@@ -400,7 +406,7 @@ function buildNotEndsWithFilterQuery(
|
|
|
400
406
|
ctx: BuildQueryContext,
|
|
401
407
|
filter: FindEntityRelationalFilterOptions,
|
|
402
408
|
) {
|
|
403
|
-
let command =
|
|
409
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
404
410
|
|
|
405
411
|
command += " NOT LIKE ";
|
|
406
412
|
ctx.params.push(`%${filter.value}`);
|
|
@@ -413,7 +419,7 @@ function buildRelationalFilterQuery(
|
|
|
413
419
|
ctx: BuildQueryContext,
|
|
414
420
|
filter: FindEntityRelationalFilterOptions,
|
|
415
421
|
) {
|
|
416
|
-
let command =
|
|
422
|
+
let command = ctx.builder.quoteObject(filter.field);
|
|
417
423
|
|
|
418
424
|
command += relationalOperatorsMap.get(filter.operator);
|
|
419
425
|
|
package/src/server.ts
CHANGED
|
@@ -63,6 +63,41 @@ export class RapidServer implements IRpdServer {
|
|
|
63
63
|
return this.#applicationConfig;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
appendApplicationConfig(config: Partial<RpdApplicationConfig>) {
|
|
67
|
+
const { models, routes } = config;
|
|
68
|
+
if (models) {
|
|
69
|
+
for (const model of models) {
|
|
70
|
+
const originalModel = _.find(this.#applicationConfig.models, (item) => item.singularCode == model.singularCode);
|
|
71
|
+
if (originalModel) {
|
|
72
|
+
originalModel.name = model.name;
|
|
73
|
+
const originalProperties = originalModel.properties;
|
|
74
|
+
for (const property of model.properties) {
|
|
75
|
+
const originalProperty = _.find(originalProperties, (item) => item.code == property.code);
|
|
76
|
+
if (originalProperty) {
|
|
77
|
+
originalProperty.name = property.name;
|
|
78
|
+
} else {
|
|
79
|
+
originalProperties.push(property);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
this.#applicationConfig.models.push(model);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (routes) {
|
|
89
|
+
for (const route of routes) {
|
|
90
|
+
const originalRoute = _.find(this.#applicationConfig.routes, (item) => item.code == route.code);
|
|
91
|
+
if (originalRoute) {
|
|
92
|
+
originalRoute.name = route.name;
|
|
93
|
+
originalRoute.handlers = route.handlers;
|
|
94
|
+
} else {
|
|
95
|
+
this.#applicationConfig.routes.push(route);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
66
101
|
registerHttpHandler(
|
|
67
102
|
plugin: IPluginInstance,
|
|
68
103
|
options: IPluginHttpHandler,
|
|
@@ -89,7 +124,7 @@ export class RapidServer implements IRpdServer {
|
|
|
89
124
|
throw new Error(`Data model ${namespace}.${singularCode} not found.`);
|
|
90
125
|
}
|
|
91
126
|
|
|
92
|
-
const dataAccessor = new DataAccessor<T>({
|
|
127
|
+
const dataAccessor = new DataAccessor<T>(this.#databaseAccessor, {
|
|
93
128
|
model,
|
|
94
129
|
queryBuilder: this.queryBuilder as QueryBuilder,
|
|
95
130
|
});
|
|
@@ -185,7 +220,10 @@ export class RapidServer implements IRpdServer {
|
|
|
185
220
|
}
|
|
186
221
|
|
|
187
222
|
async handleRequest(request: Request, next: Next) {
|
|
188
|
-
const
|
|
223
|
+
const rapidRequest = new RapidRequest(request);
|
|
224
|
+
await rapidRequest.parseBody();
|
|
225
|
+
const routeContext = new RouteContext(rapidRequest);
|
|
226
|
+
console.log('routeContext', routeContext)
|
|
189
227
|
await this.#buildedRoutes(routeContext, next);
|
|
190
228
|
return routeContext.response.getResponse();
|
|
191
229
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type RapidServerConfig = {
|
|
2
|
+
baseUrl?: string;
|
|
2
3
|
sessionCookieName: string;
|
|
3
4
|
jwtKey: string;
|
|
4
5
|
localFileStoragePath: string;
|
|
@@ -303,7 +304,7 @@ export interface RpdRoute {
|
|
|
303
304
|
handlers: RpdHttpHandler[];
|
|
304
305
|
}
|
|
305
306
|
|
|
306
|
-
export type RpdHttpMethod = "get" | "post" | "put" | "delete";
|
|
307
|
+
export type RpdHttpMethod = "get" | "post" | "put" | "delete" | "patch";
|
|
307
308
|
|
|
308
309
|
export interface RpdHttpHandler {
|
|
309
310
|
code: string;
|