@ruiapp/rapid-core 0.10.3 → 0.10.5

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.
@@ -0,0 +1,2 @@
1
+ export declare function interpreteExpression(expressionString: string, rootVars: Record<string, any>): any;
2
+ export declare function interpreteConfigExpressions(config: Record<string, any>, vars: Record<string, any>): void;
@@ -1,15 +1,15 @@
1
- import { RpdApplicationConfig } from "../types";
2
1
  import { IRpdServer, RapidPlugin } from "./server";
3
2
  import { Next, RouteContext } from "./routeContext";
4
3
  import { Logger } from "../facilities/log/LogFacility";
5
4
  export interface ActionHandlerContext {
6
5
  logger: Logger;
7
6
  routerContext: RouteContext;
8
- next: Next;
7
+ next?: Next;
9
8
  server: IRpdServer;
10
- applicationConfig: RpdApplicationConfig;
9
+ vars?: Record<string, any>;
11
10
  input?: any;
12
11
  output?: any;
12
+ results: any[];
13
13
  status?: Response["status"];
14
14
  }
15
15
  export type ActionHandler = (ctx: ActionHandlerContext, options: any) => void | Promise<void>;
@@ -31,6 +31,7 @@ export interface IRpdServer {
31
31
  registerEntityWatcher(entityWatcher: EntityWatcherType): any;
32
32
  emitEvent<TEventName extends keyof RpdServerEventTypes>(event: EmitServerEventOptions<TEventName>): void;
33
33
  handleRequest(request: RapidRequest, next: Next): Promise<Response>;
34
+ runActionHandlers(handlerContext: ActionHandlerContext, actions: RpdRouteActionConfig[]): Promise<void>;
34
35
  beforeRunRouteActions(handlerContext: ActionHandlerContext): Promise<void>;
35
36
  beforeRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig): Promise<void>;
36
37
  beforeCreateEntity(model: RpdDataModel, options: CreateEntityOptions): Promise<void>;
package/dist/index.js CHANGED
@@ -1028,8 +1028,8 @@ async function buildRoutes(server, applicationConfig) {
1028
1028
  routerContext,
1029
1029
  next,
1030
1030
  server,
1031
- applicationConfig,
1032
1031
  input,
1032
+ results: [],
1033
1033
  };
1034
1034
  await server.beforeRunRouteActions(handlerContext);
1035
1035
  let handler = routeConfig.handler;
@@ -1582,6 +1582,13 @@ var bootstrapApplicationConfig = {
1582
1582
  type: "text",
1583
1583
  required: false,
1584
1584
  },
1585
+ {
1586
+ name: "target type column name",
1587
+ code: "targetTypeColumnName",
1588
+ columnName: "target_type_column_name",
1589
+ type: "text",
1590
+ required: false,
1591
+ },
1585
1592
  {
1586
1593
  name: "target id column name",
1587
1594
  code: "targetIdColumnName",
@@ -4110,14 +4117,51 @@ var healthz = {
4110
4117
  type: "RESTful",
4111
4118
  method: "GET",
4112
4119
  endpoint: "/healthz",
4113
- handler: handler$x,
4120
+ handler: handler$y,
4114
4121
  };
4115
- async function handler$x(ctx) {
4122
+ async function handler$y(ctx) {
4116
4123
  ctx.output = {};
4117
4124
  }
4118
4125
 
4119
4126
  var coreRoutes = [healthz];
4120
4127
 
4128
+ const genExpression = lodash.memoize((varNames, expressionString) => {
4129
+ // eslint-disable-next-line no-new-func
4130
+ return new Function(...varNames, `return (${expressionString})`);
4131
+ }, (varNames, expressionString) => {
4132
+ return varNames.join(",") + ":" + expressionString;
4133
+ });
4134
+ function interpreteExpression(expressionString, rootVars) {
4135
+ const varNames = [];
4136
+ const varValues = [];
4137
+ for (const name in rootVars) {
4138
+ varNames.push(name);
4139
+ varValues.push(rootVars[name]);
4140
+ }
4141
+ let result;
4142
+ try {
4143
+ const expression = genExpression(varNames, expressionString);
4144
+ result = expression(...varValues);
4145
+ }
4146
+ catch (err) {
4147
+ console.error(`Expression interprete error. expression: '${expressionString}', error:`, err.message);
4148
+ }
4149
+ return result;
4150
+ }
4151
+ function interpreteConfigExpressions(config, vars) {
4152
+ if (!config) {
4153
+ return;
4154
+ }
4155
+ const propExpressions = config.$exps;
4156
+ if (!propExpressions) {
4157
+ return;
4158
+ }
4159
+ for (const propName in propExpressions) {
4160
+ const propValue = interpreteExpression(propExpressions[propName], vars);
4161
+ lodash.set(config, propName, propValue);
4162
+ }
4163
+ }
4164
+
4121
4165
  class RapidServer {
4122
4166
  #logger;
4123
4167
  #facilityFactories;
@@ -4484,6 +4528,22 @@ class RapidServer {
4484
4528
  }
4485
4529
  return response.getResponse();
4486
4530
  }
4531
+ async runActionHandlers(handlerContext, actions) {
4532
+ if (!actions) {
4533
+ return;
4534
+ }
4535
+ for (const action of actions) {
4536
+ const actionCode = action.code;
4537
+ interpreteConfigExpressions(action.config, handlerContext.vars);
4538
+ const handler = this.getActionHandlerByCode(actionCode);
4539
+ if (!handler) {
4540
+ throw new Error("Unknown handler: " + actionCode);
4541
+ }
4542
+ await this.beforeRunActionHandler(handlerContext, action);
4543
+ const result = await handler(handlerContext, action.config);
4544
+ handlerContext.results.push(result);
4545
+ }
4546
+ }
4487
4547
  async beforeRunRouteActions(handlerContext) {
4488
4548
  await this.#pluginManager.beforeRunRouteActions(handlerContext);
4489
4549
  }
@@ -4996,7 +5056,7 @@ class RapidRequest {
4996
5056
  else if (contentType.startsWith("multipart/form-data")) {
4997
5057
  this.#body = {
4998
5058
  type: "form-data",
4999
- value: await parseFormDataBody(req),
5059
+ value: await parseFormDataBody(req, { all: true }),
5000
5060
  };
5001
5061
  }
5002
5062
  this.#bodyParsed = true;
@@ -5491,32 +5551,34 @@ class CacheFactory {
5491
5551
  }
5492
5552
  }
5493
5553
 
5494
- const code$w = "listMetaModels";
5495
- async function handler$w(plugin, ctx, options) {
5496
- const { applicationConfig } = ctx;
5554
+ const code$x = "listMetaModels";
5555
+ async function handler$x(plugin, ctx, options) {
5556
+ const { server } = ctx;
5557
+ const applicationConfig = server.getApplicationConfig();
5497
5558
  ctx.output = { list: applicationConfig.models };
5498
5559
  }
5499
5560
 
5500
5561
  var listMetaModels = /*#__PURE__*/Object.freeze({
5501
5562
  __proto__: null,
5502
- code: code$w,
5503
- handler: handler$w
5563
+ code: code$x,
5564
+ handler: handler$x
5504
5565
  });
5505
5566
 
5506
- const code$v = "listMetaRoutes";
5507
- async function handler$v(plugin, ctx, options) {
5508
- const { applicationConfig } = ctx;
5567
+ const code$w = "listMetaRoutes";
5568
+ async function handler$w(plugin, ctx, options) {
5569
+ const { server } = ctx;
5570
+ const applicationConfig = server.getApplicationConfig();
5509
5571
  ctx.output = { list: applicationConfig.routes };
5510
5572
  }
5511
5573
 
5512
5574
  var listMetaRoutes = /*#__PURE__*/Object.freeze({
5513
5575
  __proto__: null,
5514
- code: code$v,
5515
- handler: handler$v
5576
+ code: code$w,
5577
+ handler: handler$w
5516
5578
  });
5517
5579
 
5518
- const code$u = "getMetaModelDetail";
5519
- async function handler$u(plugin, ctx, options) {
5580
+ const code$v = "getMetaModelDetail";
5581
+ async function handler$v(plugin, ctx, options) {
5520
5582
  const { server, input } = ctx;
5521
5583
  const model = server.getModel(input);
5522
5584
  ctx.output = model;
@@ -5524,8 +5586,8 @@ async function handler$u(plugin, ctx, options) {
5524
5586
 
5525
5587
  var getMetaModelDetail = /*#__PURE__*/Object.freeze({
5526
5588
  __proto__: null,
5527
- code: code$u,
5528
- handler: handler$u
5589
+ code: code$v,
5590
+ handler: handler$v
5529
5591
  });
5530
5592
 
5531
5593
  function removeFiltersWithNullValue(filters) {
@@ -6114,9 +6176,9 @@ async function runCollectionEntityActionHandler(ctx, options, code, autoMergeInp
6114
6176
  }
6115
6177
  }
6116
6178
 
6117
- const code$t = "findCollectionEntities";
6118
- async function handler$t(plugin, ctx, options) {
6119
- await runCollectionEntityActionHandler(ctx, options, code$t, true, false, async (entityManager, input) => {
6179
+ const code$u = "findCollectionEntities";
6180
+ async function handler$u(plugin, ctx, options) {
6181
+ await runCollectionEntityActionHandler(ctx, options, code$u, true, false, async (entityManager, input) => {
6120
6182
  const { routerContext: routeContext } = ctx;
6121
6183
  input.filters = removeFiltersWithNullValue(input.filters);
6122
6184
  input.routeContext = routeContext;
@@ -6133,13 +6195,13 @@ async function handler$t(plugin, ctx, options) {
6133
6195
 
6134
6196
  var findCollectionEntities = /*#__PURE__*/Object.freeze({
6135
6197
  __proto__: null,
6136
- code: code$t,
6137
- handler: handler$t
6198
+ code: code$u,
6199
+ handler: handler$u
6138
6200
  });
6139
6201
 
6140
- const code$s = "findCollectionEntityById";
6141
- async function handler$s(plugin, ctx, options) {
6142
- await runCollectionEntityActionHandler(ctx, options, code$s, true, true, async (entityManager, input) => {
6202
+ const code$t = "findCollectionEntityById";
6203
+ async function handler$t(plugin, ctx, options) {
6204
+ await runCollectionEntityActionHandler(ctx, options, code$t, true, true, async (entityManager, input) => {
6143
6205
  const { routerContext: routeContext } = ctx;
6144
6206
  const { id } = input;
6145
6207
  const entity = await entityManager.findById({
@@ -6161,13 +6223,13 @@ async function handler$s(plugin, ctx, options) {
6161
6223
 
6162
6224
  var findCollectionEntityById = /*#__PURE__*/Object.freeze({
6163
6225
  __proto__: null,
6164
- code: code$s,
6165
- handler: handler$s
6226
+ code: code$t,
6227
+ handler: handler$t
6166
6228
  });
6167
6229
 
6168
- const code$r = "countCollectionEntities";
6169
- async function handler$r(plugin, ctx, options) {
6170
- await runCollectionEntityActionHandler(ctx, options, code$r, true, false, async (entityManager, input) => {
6230
+ const code$s = "countCollectionEntities";
6231
+ async function handler$s(plugin, ctx, options) {
6232
+ await runCollectionEntityActionHandler(ctx, options, code$s, true, false, async (entityManager, input) => {
6171
6233
  const { routerContext: routeContext } = ctx;
6172
6234
  input.filters = removeFiltersWithNullValue(input.filters);
6173
6235
  input.routeContext = routeContext;
@@ -6178,13 +6240,13 @@ async function handler$r(plugin, ctx, options) {
6178
6240
 
6179
6241
  var countCollectionEntities = /*#__PURE__*/Object.freeze({
6180
6242
  __proto__: null,
6181
- code: code$r,
6182
- handler: handler$r
6243
+ code: code$s,
6244
+ handler: handler$s
6183
6245
  });
6184
6246
 
6185
- const code$q = "createCollectionEntity";
6186
- async function handler$q(plugin, ctx, options) {
6187
- await runCollectionEntityActionHandler(ctx, options, code$q, true, true, async (entityManager, input) => {
6247
+ const code$r = "createCollectionEntity";
6248
+ async function handler$r(plugin, ctx, options) {
6249
+ await runCollectionEntityActionHandler(ctx, options, code$r, true, true, async (entityManager, input) => {
6188
6250
  const { routerContext: routeContext } = ctx;
6189
6251
  const output = await entityManager.createEntity({
6190
6252
  entity: input,
@@ -6196,16 +6258,16 @@ async function handler$q(plugin, ctx, options) {
6196
6258
 
6197
6259
  var createCollectionEntity = /*#__PURE__*/Object.freeze({
6198
6260
  __proto__: null,
6199
- code: code$q,
6200
- handler: handler$q
6261
+ code: code$r,
6262
+ handler: handler$r
6201
6263
  });
6202
6264
 
6203
- const code$p = "createCollectionEntitiesBatch";
6204
- async function handler$p(plugin, ctx, options) {
6265
+ const code$q = "createCollectionEntitiesBatch";
6266
+ async function handler$q(plugin, ctx, options) {
6205
6267
  const { input } = ctx;
6206
6268
  const { noTransaction } = input;
6207
6269
  const { defaultInput, fixedInput } = options;
6208
- await runCollectionEntityActionHandler(ctx, options, code$p, false, !noTransaction, async (entityManager, input) => {
6270
+ await runCollectionEntityActionHandler(ctx, options, code$q, false, !noTransaction, async (entityManager, input) => {
6209
6271
  const { routerContext: routeContext } = ctx;
6210
6272
  const { entities } = input;
6211
6273
  if (!lodash.isArray(entities)) {
@@ -6243,13 +6305,13 @@ async function createEntities(options) {
6243
6305
 
6244
6306
  var createCollectionEntitiesBatch = /*#__PURE__*/Object.freeze({
6245
6307
  __proto__: null,
6246
- code: code$p,
6247
- handler: handler$p
6308
+ code: code$q,
6309
+ handler: handler$q
6248
6310
  });
6249
6311
 
6250
- const code$o = "updateCollectionEntityById";
6251
- async function handler$o(plugin, ctx, options) {
6252
- await runCollectionEntityActionHandler(ctx, options, code$o, true, true, async (entityManager, input) => {
6312
+ const code$p = "updateCollectionEntityById";
6313
+ async function handler$p(plugin, ctx, options) {
6314
+ await runCollectionEntityActionHandler(ctx, options, code$p, true, true, async (entityManager, input) => {
6253
6315
  const { routerContext: routeContext } = ctx;
6254
6316
  const operation = input.$operation;
6255
6317
  if (operation) {
@@ -6278,15 +6340,15 @@ async function handler$o(plugin, ctx, options) {
6278
6340
 
6279
6341
  var updateCollectionEntityById = /*#__PURE__*/Object.freeze({
6280
6342
  __proto__: null,
6281
- code: code$o,
6282
- handler: handler$o
6343
+ code: code$p,
6344
+ handler: handler$p
6283
6345
  });
6284
6346
 
6285
- const code$n = "deleteCollectionEntities";
6286
- async function handler$n(plugin, ctx, options) {
6347
+ const code$o = "deleteCollectionEntities";
6348
+ async function handler$o(plugin, ctx, options) {
6287
6349
  const { input } = ctx;
6288
6350
  const { noTransaction } = input;
6289
- await runCollectionEntityActionHandler(ctx, options, code$n, true, !noTransaction, async (entityManager, input) => {
6351
+ await runCollectionEntityActionHandler(ctx, options, code$o, true, !noTransaction, async (entityManager, input) => {
6290
6352
  const { routerContext: routeContext } = ctx;
6291
6353
  const { filters } = input;
6292
6354
  if (!filters || !filters.length) {
@@ -6308,13 +6370,13 @@ async function handler$n(plugin, ctx, options) {
6308
6370
 
6309
6371
  var deleteCollectionEntities = /*#__PURE__*/Object.freeze({
6310
6372
  __proto__: null,
6311
- code: code$n,
6312
- handler: handler$n
6373
+ code: code$o,
6374
+ handler: handler$o
6313
6375
  });
6314
6376
 
6315
- const code$m = "deleteCollectionEntityById";
6316
- async function handler$m(plugin, ctx, options) {
6317
- await runCollectionEntityActionHandler(ctx, options, code$m, true, true, async (entityManager, input) => {
6377
+ const code$n = "deleteCollectionEntityById";
6378
+ async function handler$n(plugin, ctx, options) {
6379
+ await runCollectionEntityActionHandler(ctx, options, code$n, true, true, async (entityManager, input) => {
6318
6380
  const { routerContext: routeContext } = ctx;
6319
6381
  await entityManager.deleteById({
6320
6382
  id: input.id,
@@ -6326,13 +6388,13 @@ async function handler$m(plugin, ctx, options) {
6326
6388
 
6327
6389
  var deleteCollectionEntityById = /*#__PURE__*/Object.freeze({
6328
6390
  __proto__: null,
6329
- code: code$m,
6330
- handler: handler$m
6391
+ code: code$n,
6392
+ handler: handler$n
6331
6393
  });
6332
6394
 
6333
- const code$l = "addEntityRelations";
6334
- async function handler$l(plugin, ctx, options) {
6335
- await runCollectionEntityActionHandler(ctx, options, code$l, true, true, async (entityManager, input) => {
6395
+ const code$m = "addEntityRelations";
6396
+ async function handler$m(plugin, ctx, options) {
6397
+ await runCollectionEntityActionHandler(ctx, options, code$m, true, true, async (entityManager, input) => {
6336
6398
  const { routerContext: routeContext } = ctx;
6337
6399
  input.routeContext = routeContext;
6338
6400
  await entityManager.addRelations(input, plugin);
@@ -6342,13 +6404,13 @@ async function handler$l(plugin, ctx, options) {
6342
6404
 
6343
6405
  var addEntityRelations = /*#__PURE__*/Object.freeze({
6344
6406
  __proto__: null,
6345
- code: code$l,
6346
- handler: handler$l
6407
+ code: code$m,
6408
+ handler: handler$m
6347
6409
  });
6348
6410
 
6349
- const code$k = "removeEntityRelations";
6350
- async function handler$k(plugin, ctx, options) {
6351
- await runCollectionEntityActionHandler(ctx, options, code$k, true, true, async (entityManager, input) => {
6411
+ const code$l = "removeEntityRelations";
6412
+ async function handler$l(plugin, ctx, options) {
6413
+ await runCollectionEntityActionHandler(ctx, options, code$l, true, true, async (entityManager, input) => {
6352
6414
  const { routerContext: routeContext } = ctx;
6353
6415
  input.routeContext = routeContext;
6354
6416
  await entityManager.removeRelations(input, plugin);
@@ -6357,6 +6419,41 @@ async function handler$k(plugin, ctx, options) {
6357
6419
  }
6358
6420
 
6359
6421
  var removeEntityRelations = /*#__PURE__*/Object.freeze({
6422
+ __proto__: null,
6423
+ code: code$l,
6424
+ handler: handler$l
6425
+ });
6426
+
6427
+ const code$k = "saveEntity";
6428
+ async function handler$k(plugin, ctx, options) {
6429
+ const { server, routerContext: routeContext, vars } = ctx;
6430
+ interpreteConfigExpressions(options, vars || {});
6431
+ const input = options.entity;
6432
+ const operation = input.$operation;
6433
+ if (operation) {
6434
+ delete input.$operation;
6435
+ }
6436
+ const stateProperties = input.$stateProperties;
6437
+ if (stateProperties) {
6438
+ delete input.$stateProperties;
6439
+ }
6440
+ const relationPropertiesToUpdate = input.$relationPropertiesToUpdate;
6441
+ if (relationPropertiesToUpdate) {
6442
+ delete input.$relationPropertiesToUpdate;
6443
+ }
6444
+ const entityManager = server.getEntityManager(options.singularCode);
6445
+ const updateEntityByIdOptions = {
6446
+ id: input.id,
6447
+ entityToSave: input,
6448
+ operation,
6449
+ stateProperties,
6450
+ relationPropertiesToUpdate,
6451
+ routeContext,
6452
+ };
6453
+ ctx.output = await entityManager.updateEntityById(updateEntityByIdOptions, plugin);
6454
+ }
6455
+
6456
+ var saveEntity = /*#__PURE__*/Object.freeze({
6360
6457
  __proto__: null,
6361
6458
  code: code$k,
6362
6459
  handler: handler$k
@@ -6477,6 +6574,7 @@ class DataManager {
6477
6574
  server.registerActionHandler(this, removeEntityRelations);
6478
6575
  server.registerActionHandler(this, deleteCollectionEntities);
6479
6576
  server.registerActionHandler(this, deleteCollectionEntityById);
6577
+ server.registerActionHandler(this, saveEntity);
6480
6578
  server.registerActionHandler(this, queryDatabase);
6481
6579
  }
6482
6580
  async configureRoutes(server, applicationConfig) {
@@ -7672,7 +7770,7 @@ function getFileBaseName(pathname) {
7672
7770
 
7673
7771
  const code$a = "downloadDocument";
7674
7772
  async function handler$a(plugin, ctx, options) {
7675
- const { server, applicationConfig, routerContext: routeContext, input } = ctx;
7773
+ const { server, routerContext: routeContext, input } = ctx;
7676
7774
  const { request, response } = routeContext;
7677
7775
  const documentDataAccessor = ctx.server.getDataAccessor({
7678
7776
  singularCode: "ecm_document",
@@ -7733,7 +7831,7 @@ var downloadDocumentActionHandler = /*#__PURE__*/Object.freeze({
7733
7831
 
7734
7832
  const code$9 = "downloadFile";
7735
7833
  async function handler$9(plugin, ctx, options) {
7736
- const { server, applicationConfig, routerContext: routeContext } = ctx;
7834
+ const { server, routerContext: routeContext } = ctx;
7737
7835
  const { request, response } = routeContext;
7738
7836
  //TODO: only public files can download by this handler
7739
7837
  const input = ctx.input;
@@ -7764,7 +7862,7 @@ var downloadFileActionHandler = /*#__PURE__*/Object.freeze({
7764
7862
 
7765
7863
  const code$8 = "uploadFile";
7766
7864
  async function handler$8(plugin, ctx, options) {
7767
- const { server, applicationConfig, routerContext, input } = ctx;
7865
+ const { server, routerContext, input } = ctx;
7768
7866
  let file = input.file || input.files;
7769
7867
  if (lodash.isArray(file)) {
7770
7868
  file = file[0];
@@ -9390,10 +9488,9 @@ class CronJobService {
9390
9488
  let handlerContext = {
9391
9489
  logger,
9392
9490
  routerContext: RouteContext.newSystemOperationContext(server),
9393
- next: null,
9394
9491
  server,
9395
- applicationConfig: null,
9396
9492
  input,
9493
+ results: [],
9397
9494
  };
9398
9495
  let result;
9399
9496
  let lastErrorMessage;
@@ -0,0 +1,8 @@
1
+ import { ActionHandlerContext } from "../../../core/actionHandler";
2
+ import { RapidPlugin } from "../../../core/server";
3
+ export declare const code = "saveEntity";
4
+ export type SaveEntityActionHandlerOptions = {
5
+ singularCode: string;
6
+ entity?: Record<string, any>;
7
+ };
8
+ export declare function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: SaveEntityActionHandlerOptions): Promise<void>;
package/dist/server.d.ts CHANGED
@@ -57,6 +57,7 @@ export declare class RapidServer implements IRpdServer {
57
57
  tryQueryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient, dropErrorLog?: boolean): Promise<any[]>;
58
58
  get middlewares(): any[];
59
59
  handleRequest(rapidRequest: RapidRequest, next: Next): Promise<Response>;
60
+ runActionHandlers(handlerContext: ActionHandlerContext, actions: RpdRouteActionConfig[]): Promise<void>;
60
61
  beforeRunRouteActions(handlerContext: ActionHandlerContext): Promise<void>;
61
62
  beforeRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig): Promise<void>;
62
63
  beforeCreateEntity(model: RpdDataModel, options: CreateEntityOptions): Promise<void>;
package/dist/types.d.ts CHANGED
@@ -29,6 +29,9 @@ export interface IDatabaseAccessor {
29
29
  getClient(): Promise<IDatabaseClient>;
30
30
  queryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient) => Promise<any[]>;
31
31
  }
32
+ export type RunActionHandlersOptions = {
33
+ actions: RpdRouteActionConfig[];
34
+ };
32
35
  export interface RunEntityActionHandlerOptions {
33
36
  /** 模型所在的命名空间 */
34
37
  namespace: string;
@@ -315,6 +318,10 @@ export interface RpdDataModelProperty {
315
318
  * 关联实体的singular code,不管 relation 为 one 或者 many 都需要设置。
316
319
  */
317
320
  targetSingularCode?: string;
321
+ /**
322
+ * 保存关联实体类型的字段名。当设置 targetTypeColumnName 时,表示关联实体类型不固定,可以关联不同类型的实体。
323
+ */
324
+ targetTypeColumnName?: string;
318
325
  /**
319
326
  * 当 relation 为 one 时,设置当前模型表中表示关联实体 id 的列名。
320
327
  * 当 relation 为 many,并且使用关联关系表保存关联信息时,设置关联关系表中表示关联实体 id 的列名。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruiapp/rapid-core",
3
- "version": "0.10.3",
3
+ "version": "0.10.5",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -350,6 +350,13 @@ export default {
350
350
  type: "text",
351
351
  required: false,
352
352
  },
353
+ {
354
+ name: "target type column name",
355
+ code: "targetTypeColumnName",
356
+ columnName: "target_type_column_name",
357
+ type: "text",
358
+ required: false,
359
+ },
353
360
  {
354
361
  name: "target id column name",
355
362
  code: "targetIdColumnName",
@@ -0,0 +1,45 @@
1
+ import { memoize, set } from "lodash";
2
+
3
+ const genExpression = memoize(
4
+ (varNames: string[], expressionString: string) => {
5
+ // eslint-disable-next-line no-new-func
6
+ return new Function(...varNames, `return (${expressionString})`);
7
+ },
8
+ (varNames, expressionString) => {
9
+ return varNames.join(",") + ":" + expressionString;
10
+ },
11
+ );
12
+
13
+ export function interpreteExpression(expressionString: string, rootVars: Record<string, any>) {
14
+ const varNames = [];
15
+ const varValues = [];
16
+ for (const name in rootVars) {
17
+ varNames.push(name);
18
+ varValues.push(rootVars[name]);
19
+ }
20
+
21
+ let result;
22
+ try {
23
+ const expression = genExpression(varNames, expressionString);
24
+ result = expression(...varValues);
25
+ } catch (err: any) {
26
+ console.error(`Expression interprete error. expression: '${expressionString}', error:`, err.message);
27
+ }
28
+ return result;
29
+ }
30
+
31
+ export function interpreteConfigExpressions(config: Record<string, any>, vars: Record<string, any>) {
32
+ if (!config) {
33
+ return;
34
+ }
35
+
36
+ const propExpressions = config.$exps;
37
+ if (!propExpressions) {
38
+ return;
39
+ }
40
+
41
+ for (const propName in propExpressions) {
42
+ const propValue = interpreteExpression(propExpressions[propName], vars);
43
+ set(config, propName, propValue);
44
+ }
45
+ }
@@ -7,11 +7,12 @@ import { CronJobConfiguration } from "~/types/cron-job-types";
7
7
  export interface ActionHandlerContext {
8
8
  logger: Logger;
9
9
  routerContext: RouteContext;
10
- next: Next;
10
+ next?: Next;
11
11
  server: IRpdServer;
12
- applicationConfig: RpdApplicationConfig;
12
+ vars?: Record<string, any>;
13
13
  input?: any;
14
14
  output?: any;
15
+ results: any[];
15
16
  status?: Response["status"];
16
17
  }
17
18
 
@@ -1,5 +1,3 @@
1
- import type { RapidRequest } from "../request";
2
-
3
1
  export type BodyData = Record<string, string | File | (string | File)[]>;
4
2
  export type ParseBodyOptions = {
5
3
  /**
@@ -66,7 +66,7 @@ export class RapidRequest {
66
66
  } else if (contentType.startsWith("multipart/form-data")) {
67
67
  this.#body = {
68
68
  type: "form-data",
69
- value: await parseFormDataBody(req),
69
+ value: await parseFormDataBody(req, { all: true }),
70
70
  };
71
71
  }
72
72
  this.#bodyParsed = true;
@@ -61,8 +61,8 @@ export async function buildRoutes(server: IRpdServer, applicationConfig: RpdAppl
61
61
  routerContext,
62
62
  next,
63
63
  server,
64
- applicationConfig,
65
64
  input,
65
+ results: [],
66
66
  };
67
67
 
68
68
  await server.beforeRunRouteActions(handlerContext);
@@ -15,6 +15,7 @@ import {
15
15
  RpdDataModelProperty,
16
16
  RpdRouteActionConfig,
17
17
  RpdServerEventTypes,
18
+ RunActionHandlersOptions,
18
19
  UpdateEntityByIdOptions,
19
20
  } from "~/types";
20
21
  import { IPluginActionHandler, ActionHandler, ActionHandlerContext } from "./actionHandler";
@@ -53,6 +54,7 @@ export interface IRpdServer {
53
54
  registerEntityWatcher(entityWatcher: EntityWatcherType);
54
55
  emitEvent<TEventName extends keyof RpdServerEventTypes>(event: EmitServerEventOptions<TEventName>): void;
55
56
  handleRequest(request: RapidRequest, next: Next): Promise<Response>;
57
+ runActionHandlers(handlerContext: ActionHandlerContext, actions: RpdRouteActionConfig[]): Promise<void>;
56
58
  beforeRunRouteActions(handlerContext: ActionHandlerContext): Promise<void>;
57
59
  beforeRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig): Promise<void>;
58
60
  beforeCreateEntity(model: RpdDataModel, options: CreateEntityOptions): Promise<void>;
@@ -133,10 +133,9 @@ export default class CronJobService {
133
133
  let handlerContext: ActionHandlerContext = {
134
134
  logger,
135
135
  routerContext: RouteContext.newSystemOperationContext(server),
136
- next: null,
137
136
  server,
138
- applicationConfig: null,
139
137
  input,
138
+ results: [],
140
139
  };
141
140
 
142
141
  let result: JobRunningResult;
@@ -16,6 +16,7 @@ import * as deleteCollectionEntities from "./actionHandlers/deleteCollectionEnti
16
16
  import * as deleteCollectionEntityById from "./actionHandlers/deleteCollectionEntityById";
17
17
  import * as addEntityRelations from "./actionHandlers/addEntityRelations";
18
18
  import * as removeEntityRelations from "./actionHandlers/removeEntityRelations";
19
+ import * as saveEntity from "./actionHandlers/saveEntity";
19
20
  import * as queryDatabase from "./actionHandlers/queryDatabase";
20
21
  import {
21
22
  RpdServerPluginExtendingAbilities,
@@ -125,6 +126,7 @@ class DataManager implements RapidPlugin {
125
126
  server.registerActionHandler(this, removeEntityRelations);
126
127
  server.registerActionHandler(this, deleteCollectionEntities);
127
128
  server.registerActionHandler(this, deleteCollectionEntityById);
129
+ server.registerActionHandler(this, saveEntity);
128
130
  server.registerActionHandler(this, queryDatabase);
129
131
  }
130
132
 
@@ -0,0 +1,46 @@
1
+ import { RunEntityActionHandlerOptions, UpdateEntityByIdOptions } from "~/types";
2
+ import { ActionHandlerContext } from "~/core/actionHandler";
3
+ import { RapidPlugin } from "~/core/server";
4
+ import runCollectionEntityActionHandler from "~/helpers/runCollectionEntityActionHandler";
5
+ import { interpreteConfigExpressions } from "~/core/ExpressionInterpreter";
6
+
7
+ export const code = "saveEntity";
8
+
9
+ export type SaveEntityActionHandlerOptions = {
10
+ singularCode: string;
11
+ entity?: Record<string, any>;
12
+ };
13
+
14
+ export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: SaveEntityActionHandlerOptions) {
15
+ const { server, routerContext: routeContext, vars } = ctx;
16
+
17
+ interpreteConfigExpressions(options, vars || {});
18
+
19
+ const input = options.entity;
20
+
21
+ const operation = input.$operation;
22
+ if (operation) {
23
+ delete input.$operation;
24
+ }
25
+
26
+ const stateProperties = input.$stateProperties;
27
+ if (stateProperties) {
28
+ delete input.$stateProperties;
29
+ }
30
+
31
+ const relationPropertiesToUpdate = input.$relationPropertiesToUpdate;
32
+ if (relationPropertiesToUpdate) {
33
+ delete input.$relationPropertiesToUpdate;
34
+ }
35
+
36
+ const entityManager = server.getEntityManager(options.singularCode);
37
+ const updateEntityByIdOptions: UpdateEntityByIdOptions = {
38
+ id: input.id,
39
+ entityToSave: input,
40
+ operation,
41
+ stateProperties,
42
+ relationPropertiesToUpdate,
43
+ routeContext,
44
+ };
45
+ ctx.output = await entityManager.updateEntityById(updateEntityByIdOptions, plugin);
46
+ }
@@ -7,7 +7,7 @@ import { getFileBaseName } from "~/utilities/pathUtility";
7
7
  export const code = "downloadDocument";
8
8
 
9
9
  export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any) {
10
- const { server, applicationConfig, routerContext: routeContext, input } = ctx;
10
+ const { server, routerContext: routeContext, input } = ctx;
11
11
  const { request, response } = routeContext;
12
12
 
13
13
  const documentDataAccessor = ctx.server.getDataAccessor({
@@ -13,7 +13,7 @@ export type DownloadFileInput = {
13
13
  export const code = "downloadFile";
14
14
 
15
15
  export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any) {
16
- const { server, applicationConfig, routerContext: routeContext } = ctx;
16
+ const { server, routerContext: routeContext } = ctx;
17
17
  const { request, response } = routeContext;
18
18
  //TODO: only public files can download by this handler
19
19
 
@@ -8,7 +8,7 @@ import { RapidPlugin } from "~/core/server";
8
8
  export const code = "uploadFile";
9
9
 
10
10
  export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any) {
11
- const { server, applicationConfig, routerContext, input } = ctx;
11
+ const { server, routerContext, input } = ctx;
12
12
  const { request, response } = routerContext;
13
13
 
14
14
  let file: File | File[] | null = input.file || input.files;
@@ -4,6 +4,7 @@ import { RapidPlugin } from "~/core/server";
4
4
  export const code = "listMetaModels";
5
5
 
6
6
  export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any) {
7
- const { applicationConfig } = ctx;
7
+ const { server } = ctx;
8
+ const applicationConfig = server.getApplicationConfig();
8
9
  ctx.output = { list: applicationConfig.models };
9
10
  }
@@ -4,6 +4,7 @@ import { RapidPlugin } from "~/core/server";
4
4
  export const code = "listMetaRoutes";
5
5
 
6
6
  export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any) {
7
- const { applicationConfig } = ctx;
7
+ const { server } = ctx;
8
+ const applicationConfig = server.getApplicationConfig();
8
9
  ctx.output = { list: applicationConfig.routes };
9
10
  }
package/src/server.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  EmitServerEventOptions,
20
20
  IDatabaseClient,
21
21
  RpdRouteActionConfig,
22
+ RunActionHandlersOptions,
22
23
  } from "./types";
23
24
 
24
25
  import QueryBuilder from "./queryBuilder/queryBuilder";
@@ -36,6 +37,7 @@ import { Logger } from "./facilities/log/LogFacility";
36
37
  import { FacilityFactory } from "./core/facility";
37
38
  import { CronJobConfiguration } from "./types/cron-job-types";
38
39
  import coreRoutes from "./core/routes";
40
+ import { interpreteConfigExpressions } from "./core/ExpressionInterpreter";
39
41
 
40
42
  export interface InitServerOptions {
41
43
  logger: Logger;
@@ -474,6 +476,26 @@ export class RapidServer implements IRpdServer {
474
476
  return response.getResponse();
475
477
  }
476
478
 
479
+ async runActionHandlers(handlerContext: ActionHandlerContext, actions: RpdRouteActionConfig[]) {
480
+ if (!actions) {
481
+ return;
482
+ }
483
+ for (const action of actions) {
484
+ const actionCode = action.code;
485
+ interpreteConfigExpressions(action.config, handlerContext.vars);
486
+
487
+ const handler = this.getActionHandlerByCode(actionCode);
488
+ if (!handler) {
489
+ throw new Error("Unknown handler: " + actionCode);
490
+ }
491
+
492
+ await this.beforeRunActionHandler(handlerContext, action);
493
+
494
+ const result = await handler(handlerContext, action.config);
495
+ handlerContext.results.push(result);
496
+ }
497
+ }
498
+
477
499
  async beforeRunRouteActions(handlerContext: ActionHandlerContext) {
478
500
  await this.#pluginManager.beforeRunRouteActions(handlerContext);
479
501
  }
package/src/types.ts CHANGED
@@ -35,6 +35,10 @@ export interface IDatabaseAccessor {
35
35
  queryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient) => Promise<any[]>;
36
36
  }
37
37
 
38
+ export type RunActionHandlersOptions = {
39
+ actions: RpdRouteActionConfig[];
40
+ };
41
+
38
42
  export interface RunEntityActionHandlerOptions {
39
43
  /** 模型所在的命名空间 */
40
44
  namespace: string;
@@ -359,6 +363,10 @@ export interface RpdDataModelProperty {
359
363
  * 关联实体的singular code,不管 relation 为 one 或者 many 都需要设置。
360
364
  */
361
365
  targetSingularCode?: string;
366
+ /**
367
+ * 保存关联实体类型的字段名。当设置 targetTypeColumnName 时,表示关联实体类型不固定,可以关联不同类型的实体。
368
+ */
369
+ targetTypeColumnName?: string;
362
370
  /**
363
371
  * 当 relation 为 one 时,设置当前模型表中表示关联实体 id 的列名。
364
372
  * 当 relation 为 many,并且使用关联关系表保存关联信息时,设置关联关系表中表示关联实体 id 的列名。