@ruiapp/rapid-core 0.5.2 → 0.5.3

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.
@@ -1,4 +1,4 @@
1
- import { CreateEntityOptions, EmitServerEventOptions, EntityWatcherType, GetDataAccessorOptions, GetModelOptions, IDatabaseAccessor, IDatabaseConfig, IQueryBuilder, IRpdDataAccessor, RapidServerConfig, RpdApplicationConfig, RpdDataModel, RpdDataModelProperty, RpdServerEventTypes, UpdateEntityByIdOptions } from "../types";
1
+ import { CreateEntityOptions, EmitServerEventOptions, EntityWatcherType, GetDataAccessorOptions, GetModelOptions, IDatabaseAccessor, IDatabaseClient, IDatabaseConfig, IQueryBuilder, IRpdDataAccessor, RapidServerConfig, RpdApplicationConfig, RpdDataModel, RpdDataModelProperty, RpdServerEventTypes, UpdateEntityByIdOptions } from "../types";
2
2
  import { IPluginActionHandler, ActionHandler, ActionHandlerContext } from "./actionHandler";
3
3
  import { Next, RouteContext } from "./routeContext";
4
4
  import EntityManager from "../dataAccess/entityManager";
@@ -12,8 +12,8 @@ export interface IRpdServer {
12
12
  registerFacilityFactory(factory: FacilityFactory): any;
13
13
  getFacility<TFacility = any>(name: string, options?: any): Promise<TFacility>;
14
14
  getDatabaseAccessor(): IDatabaseAccessor;
15
- queryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown>) => Promise<any[]>;
16
- tryQueryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown>) => Promise<any[]>;
15
+ queryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient) => Promise<any[]>;
16
+ tryQueryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient) => Promise<any[]>;
17
17
  registerMiddleware(middleware: any): void;
18
18
  registerActionHandler(plugin: RapidPlugin, options: IPluginActionHandler): void;
19
19
  getActionHandlerByCode(code: string): ActionHandler | undefined;
@@ -3,6 +3,7 @@ import { IRpdServer, RapidPlugin } from "../core/server";
3
3
  import { RouteContext } from "../core/routeContext";
4
4
  export type FindOneRelationEntitiesOptions = {
5
5
  server: IRpdServer;
6
+ routeContext?: RouteContext;
6
7
  mainModel: RpdDataModel;
7
8
  relationProperty: RpdDataModelProperty;
8
9
  relationEntityIds: any[];
@@ -10,6 +11,7 @@ export type FindOneRelationEntitiesOptions = {
10
11
  };
11
12
  export type FindManyRelationEntitiesOptions = {
12
13
  server: IRpdServer;
14
+ routeContext?: RouteContext;
13
15
  mainModel: RpdDataModel;
14
16
  relationProperty: RpdDataModelProperty;
15
17
  mainEntityIds: any[];
@@ -3,8 +3,8 @@ import { RpdDataModel, RpdDataModelProperty } from "../types";
3
3
  export declare function isRelationProperty(property: RpdDataModelProperty): boolean;
4
4
  export declare function isOneRelationProperty(property: RpdDataModelProperty): boolean;
5
5
  export declare function isManyRelationProperty(property: RpdDataModelProperty): boolean;
6
- export declare function getEntityProperties(server: IRpdServer, model: RpdDataModel): RpdDataModelProperty[];
7
- export declare function getEntityPropertiesIncludingBase(server: IRpdServer, model: RpdDataModel): RpdDataModelProperty[];
6
+ export declare function getEntityProperties(server: IRpdServer, model: RpdDataModel, predicate?: (item: RpdDataModelProperty) => boolean): RpdDataModelProperty[];
7
+ export declare function getEntityPropertiesIncludingBase(server: IRpdServer, model: RpdDataModel, predicate?: (item: RpdDataModelProperty) => boolean): RpdDataModelProperty[];
8
8
  export declare function getEntityPropertyByCode(server: IRpdServer, model: RpdDataModel, propertyCode: string): RpdDataModelProperty | undefined;
9
9
  export declare function getEntityProperty(server: IRpdServer, model: RpdDataModel, predicate: (item: RpdDataModelProperty) => boolean): RpdDataModelProperty | undefined;
10
10
  export declare function getEntityOwnPropertyByCode(model: RpdDataModel, propertyCode: string): RpdDataModelProperty | undefined;
package/dist/index.js CHANGED
@@ -1712,6 +1712,13 @@ var bootstrapApplicationConfig = {
1712
1712
  type: "text",
1713
1713
  required: false,
1714
1714
  },
1715
+ {
1716
+ name: "entityDeletingReaction",
1717
+ code: "entityDeletingReaction",
1718
+ columnName: "entity_deleting_reaction",
1719
+ type: "text",
1720
+ required: false,
1721
+ },
1715
1722
  {
1716
1723
  name: "readonly",
1717
1724
  code: "readonly",
@@ -2119,22 +2126,41 @@ function isOneRelationProperty(property) {
2119
2126
  function isManyRelationProperty(property) {
2120
2127
  return isRelationProperty(property) && property.relation === "many";
2121
2128
  }
2122
- function getEntityPropertiesIncludingBase(server, model) {
2123
- if (!model.base) {
2129
+ function getEntityProperties(server, model, predicate) {
2130
+ if (!predicate) {
2124
2131
  return model.properties;
2125
2132
  }
2133
+ return lodash.filter(model.properties, predicate);
2134
+ }
2135
+ function getEntityPropertiesIncludingBase(server, model, predicate) {
2136
+ if (!model.base) {
2137
+ return getEntityProperties(server, model, predicate);
2138
+ }
2126
2139
  const baseModel = server.getModel({
2127
2140
  singularCode: model.base,
2128
2141
  });
2129
2142
  let baseProperties = [];
2130
2143
  if (baseModel) {
2131
- baseProperties = baseModel.properties.map((property) => {
2144
+ if (predicate) {
2145
+ baseProperties = lodash.filter(baseModel.properties, predicate);
2146
+ }
2147
+ else {
2148
+ baseProperties = baseModel.properties;
2149
+ }
2150
+ baseProperties = baseProperties.map((property) => {
2132
2151
  property = lodash.cloneDeep(property);
2133
2152
  property.isBaseProperty = true;
2134
2153
  return property;
2135
2154
  });
2136
2155
  }
2137
- return [...baseProperties, ...model.properties];
2156
+ let properties;
2157
+ if (predicate) {
2158
+ properties = lodash.filter(model.properties, predicate);
2159
+ }
2160
+ else {
2161
+ properties = model.properties;
2162
+ }
2163
+ return [...baseProperties, ...properties];
2138
2164
  }
2139
2165
  function getEntityPropertyByCode(server, model, propertyCode) {
2140
2166
  return getEntityProperty(server, model, (e) => e.code === propertyCode);
@@ -2539,6 +2565,7 @@ async function findEntities(server, dataAccessor, options) {
2539
2565
  if (isManyRelation) {
2540
2566
  const relationLinks = await findManyRelationLinksViaLinkTable({
2541
2567
  server,
2568
+ routeContext,
2542
2569
  mainModel: relationModel,
2543
2570
  relationProperty,
2544
2571
  mainEntityIds: entityIds,
@@ -2556,6 +2583,7 @@ async function findEntities(server, dataAccessor, options) {
2556
2583
  if (isManyRelation) {
2557
2584
  relatedEntities = await findManyRelatedEntitiesViaIdPropertyCode({
2558
2585
  server,
2586
+ routeContext,
2559
2587
  mainModel: model,
2560
2588
  relationProperty,
2561
2589
  mainEntityIds: entityIds,
@@ -2566,6 +2594,7 @@ async function findEntities(server, dataAccessor, options) {
2566
2594
  const targetEntityIds = lodash.uniq(lodash.reject(lodash.map(rows, (entity) => entity[relationProperty.targetIdColumnName]), isNullOrUndefined));
2567
2595
  relatedEntities = await findOneRelatedEntitiesViaIdPropertyCode({
2568
2596
  server,
2597
+ routeContext,
2569
2598
  mainModel: model,
2570
2599
  relationProperty,
2571
2600
  relationEntityIds: targetEntityIds,
@@ -2782,7 +2811,7 @@ async function convertEntityFiltersToRowFilters(routeContext, server, model, bas
2782
2811
  tableName: relationProperty.linkTableName,
2783
2812
  })} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName)} = ANY($1::int[])`;
2784
2813
  const params = [targetEntityIds];
2785
- const links = await server.queryDatabaseObject(command, params);
2814
+ const links = await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
2786
2815
  const selfEntityIds = links.map((link) => link[relationProperty.selfIdColumnName]);
2787
2816
  replacedFilters.push({
2788
2817
  field: {
@@ -2850,7 +2879,7 @@ async function convertEntityFiltersToRowFilters(routeContext, server, model, bas
2850
2879
  return replacedFilters;
2851
2880
  }
2852
2881
  async function findManyRelationLinksViaLinkTable(options) {
2853
- const { server, relationProperty, mainModel: relationModel, mainEntityIds, selectRelationOptions } = options;
2882
+ const { server, routeContext, relationProperty, mainModel: relationModel, mainEntityIds, selectRelationOptions } = options;
2854
2883
  const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
2855
2884
  schema: relationProperty.linkSchema,
2856
2885
  tableName: relationProperty.linkTableName,
@@ -2858,7 +2887,7 @@ async function findManyRelationLinksViaLinkTable(options) {
2858
2887
  ORDER BY id
2859
2888
  `;
2860
2889
  const params = [mainEntityIds];
2861
- const links = await server.queryDatabaseObject(command, params);
2890
+ const links = await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
2862
2891
  const targetEntityIds = links.map((link) => link[relationProperty.targetIdColumnName]);
2863
2892
  const dataAccessor = server.getDataAccessor({
2864
2893
  namespace: relationModel.namespace,
@@ -3160,7 +3189,7 @@ async function createEntity(server, dataAccessor, options, plugin) {
3160
3189
  tableName: property.linkTableName,
3161
3190
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
3162
3191
  const params = [newEntity.id, newTargetEntity.id];
3163
- await server.queryDatabaseObject(command, params);
3192
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
3164
3193
  }
3165
3194
  newEntity[property.code].push(newTargetEntity);
3166
3195
  }
@@ -3176,7 +3205,7 @@ async function createEntity(server, dataAccessor, options, plugin) {
3176
3205
  tableName: property.linkTableName,
3177
3206
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
3178
3207
  const params = [newEntity.id, relatedEntityId];
3179
- await server.queryDatabaseObject(command, params);
3208
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
3180
3209
  }
3181
3210
  else {
3182
3211
  await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName]: newEntity.id }, routeContext?.getDbTransactionClient());
@@ -3198,7 +3227,7 @@ async function createEntity(server, dataAccessor, options, plugin) {
3198
3227
  tableName: property.linkTableName,
3199
3228
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
3200
3229
  const params = [newEntity.id, relatedEntityId];
3201
- await server.queryDatabaseObject(command, params);
3230
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
3202
3231
  }
3203
3232
  else {
3204
3233
  await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName]: newEntity.id }, routeContext?.getDbTransactionClient());
@@ -3469,13 +3498,13 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
3469
3498
  const targetLinks = await server.queryDatabaseObject(`SELECT ${server.queryBuilder.quoteObject(property.targetIdColumnName)} FROM ${server.queryBuilder.quoteTable({
3470
3499
  schema: property.linkSchema,
3471
3500
  tableName: property.linkTableName,
3472
- })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1`, [id]);
3501
+ })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
3473
3502
  currentTargetIds = targetLinks.map((item) => item[property.targetIdColumnName]);
3474
3503
  await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
3475
3504
  schema: property.linkSchema,
3476
3505
  tableName: property.linkTableName,
3477
3506
  })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1
3478
- AND ${server.queryBuilder.quoteObject(property.targetIdColumnName)} <> ALL($2::int[])`, [id, targetIdsToKeep]);
3507
+ AND ${server.queryBuilder.quoteObject(property.targetIdColumnName)} <> ALL($2::int[])`, [id, targetIdsToKeep], routeContext?.getDbTransactionClient());
3479
3508
  }
3480
3509
  else {
3481
3510
  const targetModel = server.getModel({
@@ -3484,7 +3513,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
3484
3513
  const targetRows = await server.queryDatabaseObject(`SELECT id FROM ${server.queryBuilder.quoteTable({
3485
3514
  schema: targetModel.schema,
3486
3515
  tableName: targetModel.tableName,
3487
- })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1`, [id]);
3516
+ })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
3488
3517
  currentTargetIds = targetRows.map((item) => item.id);
3489
3518
  }
3490
3519
  for (const relatedEntityToBeSaved of relatedEntitiesToBeSaved) {
@@ -3507,7 +3536,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
3507
3536
  tableName: property.linkTableName,
3508
3537
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
3509
3538
  const params = [id, newTargetEntity.id];
3510
- await server.queryDatabaseObject(command, params);
3539
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
3511
3540
  }
3512
3541
  relatedEntities.push(newTargetEntity);
3513
3542
  }
@@ -3548,7 +3577,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
3548
3577
  tableName: property.linkTableName,
3549
3578
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
3550
3579
  const params = [id, relatedEntityId];
3551
- await server.queryDatabaseObject(command, params);
3580
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
3552
3581
  }
3553
3582
  else {
3554
3583
  await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName]: id }, routeContext?.getDbTransactionClient());
@@ -3572,7 +3601,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
3572
3601
  tableName: property.linkTableName,
3573
3602
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
3574
3603
  const params = [id, relatedEntityId];
3575
- await server.queryDatabaseObject(command, params);
3604
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
3576
3605
  }
3577
3606
  else {
3578
3607
  await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName]: id }, routeContext?.getDbTransactionClient());
@@ -3654,6 +3683,146 @@ function getEntityDuplicatedErrorMessage(server, model, indexConfig) {
3654
3683
  });
3655
3684
  return `已存在 ${propertyNames.join(", ")} 相同的记录。`;
3656
3685
  }
3686
+ async function deleteEntityById(server, dataAccessor, options, plugin) {
3687
+ // options is id
3688
+ if (!lodash.isObject(options)) {
3689
+ options = {
3690
+ id: options,
3691
+ };
3692
+ }
3693
+ const model = dataAccessor.getModel();
3694
+ if (model.derivedTypePropertyCode) {
3695
+ // TODO: should be allowed.
3696
+ throw newEntityOperationError("Delete base entity directly is not allowed.");
3697
+ }
3698
+ const { id, routeContext } = options;
3699
+ const entity = await findById(server, dataAccessor, {
3700
+ id,
3701
+ keepNonPropertyFields: true,
3702
+ routeContext,
3703
+ });
3704
+ if (!entity) {
3705
+ return;
3706
+ }
3707
+ if (model.softDelete) {
3708
+ if (entity.deletedAt) {
3709
+ return;
3710
+ }
3711
+ }
3712
+ await server.emitEvent({
3713
+ eventName: "entity.beforeDelete",
3714
+ payload: {
3715
+ namespace: model.namespace,
3716
+ modelSingularCode: model.singularCode,
3717
+ before: entity,
3718
+ },
3719
+ sender: plugin,
3720
+ routeContext,
3721
+ });
3722
+ if (model.softDelete) {
3723
+ const currentUserId = routeContext?.state?.userId;
3724
+ await dataAccessor.updateById(id, {
3725
+ deleted_at: getNowStringWithTimezone(),
3726
+ deleter_id: currentUserId,
3727
+ }, routeContext?.getDbTransactionClient());
3728
+ }
3729
+ else {
3730
+ const relationPropertiesWithDeletingReaction = getEntityPropertiesIncludingBase(server, model, (property) => {
3731
+ return isRelationProperty(property) && property.entityDeletingReaction && property.entityDeletingReaction !== "doNothing";
3732
+ });
3733
+ for (const relationProperty of relationPropertiesWithDeletingReaction) {
3734
+ const relationDataAccessor = server.getDataAccessor({
3735
+ singularCode: relationProperty.targetSingularCode,
3736
+ });
3737
+ if (relationProperty.entityDeletingReaction === "cascadingDelete") {
3738
+ if (relationProperty.relation === "one") {
3739
+ const relatedEntityId = entity[relationProperty.targetIdColumnName];
3740
+ if (relatedEntityId) {
3741
+ await deleteEntityById(server, relationDataAccessor, {
3742
+ routeContext,
3743
+ id: relatedEntityId,
3744
+ }, plugin);
3745
+ }
3746
+ }
3747
+ else if (relationProperty.relation === "many") {
3748
+ if (relationProperty.linkTableName) {
3749
+ const targetLinks = await server.queryDatabaseObject(`SELECT ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName)} FROM ${server.queryBuilder.quoteTable({
3750
+ schema: relationProperty.linkSchema,
3751
+ tableName: relationProperty.linkTableName,
3752
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
3753
+ const targetEntityIds = targetLinks.map((item) => item[relationProperty.targetIdColumnName]);
3754
+ await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
3755
+ schema: relationProperty.linkSchema,
3756
+ tableName: relationProperty.linkTableName,
3757
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
3758
+ for (const targetEntityId of targetEntityIds) {
3759
+ await deleteEntityById(server, relationDataAccessor, {
3760
+ routeContext,
3761
+ id: targetEntityId,
3762
+ }, plugin);
3763
+ }
3764
+ }
3765
+ else {
3766
+ const targetModel = server.getModel({
3767
+ singularCode: relationProperty.targetSingularCode,
3768
+ });
3769
+ const targetRows = await server.queryDatabaseObject(`SELECT id FROM ${server.queryBuilder.quoteTable({
3770
+ schema: targetModel.schema,
3771
+ tableName: targetModel.tableName,
3772
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
3773
+ const targetEntityIds = targetRows.map((item) => item.id);
3774
+ for (const targetEntityId of targetEntityIds) {
3775
+ await deleteEntityById(server, relationDataAccessor, {
3776
+ routeContext,
3777
+ id: targetEntityId,
3778
+ }, plugin);
3779
+ }
3780
+ }
3781
+ }
3782
+ }
3783
+ else if (relationProperty.entityDeletingReaction === "unlink") {
3784
+ if (relationProperty.relation === "one") ;
3785
+ else if (relationProperty.relation === "many") {
3786
+ if (relationProperty.linkTableName) {
3787
+ await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
3788
+ schema: relationProperty.linkSchema,
3789
+ tableName: relationProperty.linkTableName,
3790
+ })}
3791
+ WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
3792
+ }
3793
+ else {
3794
+ const relationModel = server.getModel({
3795
+ singularCode: relationProperty.targetSingularCode,
3796
+ });
3797
+ await server.queryDatabaseObject(`UPDATE ${server.queryBuilder.quoteTable({
3798
+ schema: relationModel.schema,
3799
+ tableName: relationModel.tableName,
3800
+ })}
3801
+ SET ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = null
3802
+ WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
3803
+ }
3804
+ }
3805
+ }
3806
+ }
3807
+ await dataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
3808
+ if (model.base) {
3809
+ const baseDataAccessor = server.getDataAccessor({
3810
+ singularCode: model.base,
3811
+ });
3812
+ await baseDataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
3813
+ }
3814
+ }
3815
+ await server.emitEvent({
3816
+ eventName: "entity.delete",
3817
+ payload: {
3818
+ namespace: model.namespace,
3819
+ modelSingularCode: model.singularCode,
3820
+ before: entity,
3821
+ },
3822
+ sender: plugin,
3823
+ routeContext,
3824
+ });
3825
+ }
3657
3826
  class EntityManager {
3658
3827
  #server;
3659
3828
  #dataAccessor;
@@ -3700,66 +3869,7 @@ class EntityManager {
3700
3869
  return await this.#dataAccessor.count(countRowOptions, routeContext?.getDbTransactionClient());
3701
3870
  }
3702
3871
  async deleteById(options, plugin) {
3703
- // options is id
3704
- if (!lodash.isObject(options)) {
3705
- options = {
3706
- id: options,
3707
- };
3708
- }
3709
- const model = this.getModel();
3710
- if (model.derivedTypePropertyCode) {
3711
- throw newEntityOperationError("Delete base entity directly is not allowed.");
3712
- }
3713
- const { id, routeContext } = options;
3714
- const entity = await this.findById({
3715
- id,
3716
- keepNonPropertyFields: true,
3717
- routeContext,
3718
- });
3719
- if (!entity) {
3720
- return;
3721
- }
3722
- await this.#server.emitEvent({
3723
- eventName: "entity.beforeDelete",
3724
- payload: {
3725
- namespace: model.namespace,
3726
- modelSingularCode: model.singularCode,
3727
- before: entity,
3728
- },
3729
- sender: plugin,
3730
- routeContext,
3731
- });
3732
- if (model.softDelete) {
3733
- let dataAccessor = model.base
3734
- ? this.#server.getDataAccessor({
3735
- singularCode: model.base,
3736
- })
3737
- : this.#dataAccessor;
3738
- const currentUserId = routeContext?.state?.userId;
3739
- await dataAccessor.updateById(id, {
3740
- deleted_at: getNowStringWithTimezone(),
3741
- deleter_id: currentUserId,
3742
- }, routeContext?.getDbTransactionClient());
3743
- }
3744
- else {
3745
- await this.#dataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
3746
- if (model.base) {
3747
- const baseDataAccessor = this.#server.getDataAccessor({
3748
- singularCode: model.base,
3749
- });
3750
- await baseDataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
3751
- }
3752
- }
3753
- await this.#server.emitEvent({
3754
- eventName: "entity.delete",
3755
- payload: {
3756
- namespace: model.namespace,
3757
- modelSingularCode: model.singularCode,
3758
- before: entity,
3759
- },
3760
- sender: plugin,
3761
- routeContext,
3762
- });
3872
+ return await deleteEntityById(this.#server, this.#dataAccessor, options, plugin);
3763
3873
  }
3764
3874
  async addRelations(options, plugin) {
3765
3875
  const server = this.#server;
@@ -3792,7 +3902,7 @@ class EntityManager {
3792
3902
  WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}=$2
3793
3903
  )`;
3794
3904
  const params = [id, relation.id];
3795
- await server.queryDatabaseObject(command, params);
3905
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
3796
3906
  }
3797
3907
  }
3798
3908
  await server.emitEvent({
@@ -3832,7 +3942,7 @@ class EntityManager {
3832
3942
  const command = `DELETE FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
3833
3943
  WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}=$2;`;
3834
3944
  const params = [id, relation.id];
3835
- await server.queryDatabaseObject(command, params);
3945
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
3836
3946
  }
3837
3947
  }
3838
3948
  await server.emitEvent({
@@ -4139,18 +4249,18 @@ class RapidServer {
4139
4249
  }
4140
4250
  return await factory.createFacility(this, options);
4141
4251
  }
4142
- async queryDatabaseObject(sql, params) {
4252
+ async queryDatabaseObject(sql, params, client) {
4143
4253
  try {
4144
- return await this.#databaseAccessor.queryDatabaseObject(sql, params);
4254
+ return await this.#databaseAccessor.queryDatabaseObject(sql, params, client);
4145
4255
  }
4146
4256
  catch (err) {
4147
4257
  this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
4148
4258
  throw err;
4149
4259
  }
4150
4260
  }
4151
- async tryQueryDatabaseObject(sql, params) {
4261
+ async tryQueryDatabaseObject(sql, params, client) {
4152
4262
  try {
4153
- return await this.queryDatabaseObject(sql, params);
4263
+ return await this.queryDatabaseObject(sql, params, client);
4154
4264
  }
4155
4265
  catch (err) {
4156
4266
  this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { GetDataAccessorOptions, GetModelOptions, IDatabaseAccessor, IDatabaseConfig, IQueryBuilder, IRpdDataAccessor, RpdApplicationConfig, RpdDataModel, RpdServerEventTypes, RapidServerConfig, RpdDataModelProperty, CreateEntityOptions, UpdateEntityByIdOptions, EntityWatcherType, EmitServerEventOptions } from "./types";
1
+ import { GetDataAccessorOptions, GetModelOptions, IDatabaseAccessor, IDatabaseConfig, IQueryBuilder, IRpdDataAccessor, RpdApplicationConfig, RpdDataModel, RpdServerEventTypes, RapidServerConfig, RpdDataModelProperty, CreateEntityOptions, UpdateEntityByIdOptions, EntityWatcherType, EmitServerEventOptions, IDatabaseClient } from "./types";
2
2
  import { ActionHandler, ActionHandlerContext, IPluginActionHandler } from "./core/actionHandler";
3
3
  import { IRpdServer, RapidPlugin } from "./core/server";
4
4
  import { Next } from "./core/routeContext";
@@ -41,8 +41,8 @@ export declare class RapidServer implements IRpdServer {
41
41
  configureApplication(): Promise<void>;
42
42
  registerFacilityFactory(factory: FacilityFactory): void;
43
43
  getFacility<TFacility = any>(name: string, options?: any, nullIfUnknownFacility?: boolean): Promise<TFacility>;
44
- queryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>): Promise<any[]>;
45
- tryQueryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>): Promise<any[]>;
44
+ queryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient): Promise<any[]>;
45
+ tryQueryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient): Promise<any[]>;
46
46
  get middlewares(): any[];
47
47
  handleRequest(request: Request, next: Next): Promise<Response>;
48
48
  beforeRunRouteActions(handlerContext: ActionHandlerContext): Promise<void>;
package/dist/types.d.ts CHANGED
@@ -282,12 +282,17 @@ export interface RpdDataModelProperty {
282
282
  * 当设置了 linkTableName 时,可以设置关联关系表所在的 Schema。
283
283
  */
284
284
  linkSchema?: string;
285
+ /**
286
+ * 当删除实体时,针对关系属性的联动处理。
287
+ */
288
+ entityDeletingReaction?: RpdEntityDeleteRelationPropertyReaction;
285
289
  /**
286
290
  * 当设置为`true`时,仅允许在创建时设置此属性的值,不允许更新。
287
291
  */
288
292
  readonly?: boolean;
289
293
  }
290
294
  export type RpdDataPropertyTypes = "integer" | "long" | "float" | "double" | "decimal" | "text" | "boolean" | "date" | "time" | "datetime" | "json" | "relation" | "relation[]" | "option" | "option[]" | "file" | "file[]" | "image" | "image[]";
295
+ export type RpdEntityDeleteRelationPropertyReaction = "doNothing" | "unlink" | "cascadingDelete";
291
296
  /**
292
297
  * 数据字典
293
298
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruiapp/rapid-core",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -237,6 +237,13 @@ export default {
237
237
  type: "text",
238
238
  required: false,
239
239
  },
240
+ {
241
+ name: "entityDeletingReaction",
242
+ code: "entityDeletingReaction",
243
+ columnName: "entity_deleting_reaction",
244
+ type: "text",
245
+ required: false,
246
+ },
240
247
  {
241
248
  name: "readonly",
242
249
  code: "readonly",
@@ -5,6 +5,7 @@ import {
5
5
  GetDataAccessorOptions,
6
6
  GetModelOptions,
7
7
  IDatabaseAccessor,
8
+ IDatabaseClient,
8
9
  IDatabaseConfig,
9
10
  IQueryBuilder,
10
11
  IRpdDataAccessor,
@@ -32,8 +33,8 @@ export interface IRpdServer {
32
33
  getFacility<TFacility = any>(name: string, options?: any): Promise<TFacility>;
33
34
 
34
35
  getDatabaseAccessor(): IDatabaseAccessor;
35
- queryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown>) => Promise<any[]>;
36
- tryQueryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown>) => Promise<any[]>;
36
+ queryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient) => Promise<any[]>;
37
+ tryQueryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient) => Promise<any[]>;
37
38
  registerMiddleware(middleware: any): void;
38
39
  registerActionHandler(plugin: RapidPlugin, options: IPluginActionHandler): void;
39
40
  getActionHandlerByCode(code: string): ActionHandler | undefined;
@@ -45,6 +45,7 @@ import {
45
45
  uniq,
46
46
  } from "lodash";
47
47
  import {
48
+ getEntityProperties,
48
49
  getEntityPropertiesIncludingBase,
49
50
  getEntityProperty,
50
51
  getEntityPropertyByCode,
@@ -60,6 +61,7 @@ import { RouteContext } from "~/core/routeContext";
60
61
 
61
62
  export type FindOneRelationEntitiesOptions = {
62
63
  server: IRpdServer;
64
+ routeContext?: RouteContext;
63
65
  mainModel: RpdDataModel;
64
66
  relationProperty: RpdDataModelProperty;
65
67
  relationEntityIds: any[];
@@ -68,6 +70,7 @@ export type FindOneRelationEntitiesOptions = {
68
70
 
69
71
  export type FindManyRelationEntitiesOptions = {
70
72
  server: IRpdServer;
73
+ routeContext?: RouteContext;
71
74
  mainModel: RpdDataModel;
72
75
  relationProperty: RpdDataModelProperty;
73
76
  mainEntityIds: any[];
@@ -269,6 +272,7 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
269
272
  if (isManyRelation) {
270
273
  const relationLinks = await findManyRelationLinksViaLinkTable({
271
274
  server,
275
+ routeContext,
272
276
  mainModel: relationModel,
273
277
  relationProperty,
274
278
  mainEntityIds: entityIds,
@@ -286,6 +290,7 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
286
290
  if (isManyRelation) {
287
291
  relatedEntities = await findManyRelatedEntitiesViaIdPropertyCode({
288
292
  server,
293
+ routeContext,
289
294
  mainModel: model,
290
295
  relationProperty,
291
296
  mainEntityIds: entityIds,
@@ -300,6 +305,7 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
300
305
  );
301
306
  relatedEntities = await findOneRelatedEntitiesViaIdPropertyCode({
302
307
  server,
308
+ routeContext,
303
309
  mainModel: model,
304
310
  relationProperty,
305
311
  relationEntityIds: targetEntityIds,
@@ -546,7 +552,7 @@ async function convertEntityFiltersToRowFilters(
546
552
  tableName: relationProperty.linkTableName!,
547
553
  })} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName!)} = ANY($1::int[])`;
548
554
  const params = [targetEntityIds];
549
- const links = await server.queryDatabaseObject(command, params);
555
+ const links = await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
550
556
  const selfEntityIds = links.map((link) => link[relationProperty.selfIdColumnName!]);
551
557
  replacedFilters.push({
552
558
  field: {
@@ -614,7 +620,7 @@ async function convertEntityFiltersToRowFilters(
614
620
  }
615
621
 
616
622
  async function findManyRelationLinksViaLinkTable(options: FindManyRelationEntitiesOptions) {
617
- const { server, relationProperty, mainModel: relationModel, mainEntityIds, selectRelationOptions } = options;
623
+ const { server, routeContext, relationProperty, mainModel: relationModel, mainEntityIds, selectRelationOptions } = options;
618
624
  const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
619
625
  schema: relationProperty.linkSchema,
620
626
  tableName: relationProperty.linkTableName!,
@@ -622,7 +628,7 @@ async function findManyRelationLinksViaLinkTable(options: FindManyRelationEntiti
622
628
  ORDER BY id
623
629
  `;
624
630
  const params = [mainEntityIds];
625
- const links = await server.queryDatabaseObject(command, params);
631
+ const links = await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
626
632
  const targetEntityIds = links.map((link) => link[relationProperty.targetIdColumnName!]);
627
633
 
628
634
  const dataAccessor = server.getDataAccessor({
@@ -960,7 +966,7 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
960
966
  tableName: property.linkTableName,
961
967
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
962
968
  const params = [newEntity.id, newTargetEntity.id];
963
- await server.queryDatabaseObject(command, params);
969
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
964
970
  }
965
971
 
966
972
  newEntity[property.code].push(newTargetEntity);
@@ -977,7 +983,7 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
977
983
  tableName: property.linkTableName,
978
984
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
979
985
  const params = [newEntity.id, relatedEntityId];
980
- await server.queryDatabaseObject(command, params);
986
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
981
987
  } else {
982
988
  await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName!]: newEntity.id }, routeContext?.getDbTransactionClient());
983
989
  targetEntity[property.selfIdColumnName!] = newEntity.id;
@@ -998,7 +1004,7 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
998
1004
  tableName: property.linkTableName,
999
1005
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
1000
1006
  const params = [newEntity.id, relatedEntityId];
1001
- await server.queryDatabaseObject(command, params);
1007
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
1002
1008
  } else {
1003
1009
  await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName!]: newEntity.id }, routeContext?.getDbTransactionClient());
1004
1010
  targetEntity[property.selfIdColumnName!] = newEntity.id;
@@ -1298,6 +1304,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
1298
1304
  tableName: property.linkTableName,
1299
1305
  })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName!)} = $1`,
1300
1306
  [id],
1307
+ routeContext?.getDbTransactionClient(),
1301
1308
  );
1302
1309
  currentTargetIds = targetLinks.map((item) => item[property.targetIdColumnName]);
1303
1310
 
@@ -1308,6 +1315,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
1308
1315
  })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName!)} = $1
1309
1316
  AND ${server.queryBuilder.quoteObject(property.targetIdColumnName!)} <> ALL($2::int[])`,
1310
1317
  [id, targetIdsToKeep],
1318
+ routeContext?.getDbTransactionClient(),
1311
1319
  );
1312
1320
  } else {
1313
1321
  const targetModel = server.getModel({
@@ -1319,6 +1327,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
1319
1327
  tableName: targetModel.tableName,
1320
1328
  })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName!)} = $1`,
1321
1329
  [id],
1330
+ routeContext?.getDbTransactionClient(),
1322
1331
  );
1323
1332
  currentTargetIds = targetRows.map((item) => item.id);
1324
1333
  }
@@ -1344,7 +1353,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
1344
1353
  tableName: property.linkTableName,
1345
1354
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
1346
1355
  const params = [id, newTargetEntity.id];
1347
- await server.queryDatabaseObject(command, params);
1356
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
1348
1357
  }
1349
1358
 
1350
1359
  relatedEntities.push(newTargetEntity);
@@ -1386,7 +1395,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
1386
1395
  tableName: property.linkTableName,
1387
1396
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
1388
1397
  const params = [id, relatedEntityId];
1389
- await server.queryDatabaseObject(command, params);
1398
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
1390
1399
  } else {
1391
1400
  await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName!]: id }, routeContext?.getDbTransactionClient());
1392
1401
  targetEntity[property.selfIdColumnName!] = id;
@@ -1409,7 +1418,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
1409
1418
  tableName: property.linkTableName,
1410
1419
  })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
1411
1420
  const params = [id, relatedEntityId];
1412
- await server.queryDatabaseObject(command, params);
1421
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
1413
1422
  } else {
1414
1423
  await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName!]: id }, routeContext?.getDbTransactionClient());
1415
1424
  targetEntity[property.selfIdColumnName!] = id;
@@ -1508,6 +1517,199 @@ function getEntityDuplicatedErrorMessage(server: IRpdServer, model: RpdDataModel
1508
1517
  return `已存在 ${propertyNames.join(", ")} 相同的记录。`;
1509
1518
  }
1510
1519
 
1520
+ async function deleteEntityById(
1521
+ server: IRpdServer,
1522
+ dataAccessor: IRpdDataAccessor,
1523
+ options: DeleteEntityByIdOptions | string | number,
1524
+ plugin?: RapidPlugin,
1525
+ ): Promise<void> {
1526
+ // options is id
1527
+ if (!isObject(options)) {
1528
+ options = {
1529
+ id: options,
1530
+ };
1531
+ }
1532
+
1533
+ const model = dataAccessor.getModel();
1534
+ if (model.derivedTypePropertyCode) {
1535
+ // TODO: should be allowed.
1536
+ throw newEntityOperationError("Delete base entity directly is not allowed.");
1537
+ }
1538
+
1539
+ const { id, routeContext } = options;
1540
+
1541
+ const entity = await findById(server, dataAccessor, {
1542
+ id,
1543
+ keepNonPropertyFields: true,
1544
+ routeContext,
1545
+ });
1546
+
1547
+ if (!entity) {
1548
+ return;
1549
+ }
1550
+
1551
+ if (model.softDelete) {
1552
+ if (entity.deletedAt) {
1553
+ return;
1554
+ }
1555
+ }
1556
+
1557
+ await server.emitEvent({
1558
+ eventName: "entity.beforeDelete",
1559
+ payload: {
1560
+ namespace: model.namespace,
1561
+ modelSingularCode: model.singularCode,
1562
+ before: entity,
1563
+ },
1564
+ sender: plugin,
1565
+ routeContext,
1566
+ });
1567
+
1568
+ if (model.softDelete) {
1569
+ const currentUserId = routeContext?.state?.userId;
1570
+ await dataAccessor.updateById(
1571
+ id,
1572
+ {
1573
+ deleted_at: getNowStringWithTimezone(),
1574
+ deleter_id: currentUserId,
1575
+ },
1576
+ routeContext?.getDbTransactionClient(),
1577
+ );
1578
+ } else {
1579
+ const relationPropertiesWithDeletingReaction = getEntityPropertiesIncludingBase(server, model, (property) => {
1580
+ return isRelationProperty(property) && property.entityDeletingReaction && property.entityDeletingReaction !== "doNothing";
1581
+ });
1582
+
1583
+ for (const relationProperty of relationPropertiesWithDeletingReaction) {
1584
+ const relationDataAccessor = server.getDataAccessor({
1585
+ singularCode: relationProperty.targetSingularCode,
1586
+ });
1587
+ if (relationProperty.entityDeletingReaction === "cascadingDelete") {
1588
+ if (relationProperty.relation === "one") {
1589
+ const relatedEntityId = entity[relationProperty.targetIdColumnName];
1590
+ if (relatedEntityId) {
1591
+ await deleteEntityById(
1592
+ server,
1593
+ relationDataAccessor,
1594
+ {
1595
+ routeContext,
1596
+ id: relatedEntityId,
1597
+ },
1598
+ plugin,
1599
+ );
1600
+ }
1601
+ } else if (relationProperty.relation === "many") {
1602
+ if (relationProperty.linkTableName) {
1603
+ const targetLinks = await server.queryDatabaseObject(
1604
+ `SELECT ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName)} FROM ${server.queryBuilder.quoteTable({
1605
+ schema: relationProperty.linkSchema,
1606
+ tableName: relationProperty.linkTableName,
1607
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = $1`,
1608
+ [id],
1609
+ routeContext?.getDbTransactionClient(),
1610
+ );
1611
+ const targetEntityIds = targetLinks.map((item) => item[relationProperty.targetIdColumnName]);
1612
+
1613
+ await server.queryDatabaseObject(
1614
+ `DELETE FROM ${server.queryBuilder.quoteTable({
1615
+ schema: relationProperty.linkSchema,
1616
+ tableName: relationProperty.linkTableName,
1617
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = $1`,
1618
+ [id],
1619
+ routeContext?.getDbTransactionClient(),
1620
+ );
1621
+
1622
+ for (const targetEntityId of targetEntityIds) {
1623
+ await deleteEntityById(
1624
+ server,
1625
+ relationDataAccessor,
1626
+ {
1627
+ routeContext,
1628
+ id: targetEntityId,
1629
+ },
1630
+ plugin,
1631
+ );
1632
+ }
1633
+ } else {
1634
+ const targetModel = server.getModel({
1635
+ singularCode: relationProperty.targetSingularCode,
1636
+ });
1637
+ const targetRows = await server.queryDatabaseObject(
1638
+ `SELECT id FROM ${server.queryBuilder.quoteTable({
1639
+ schema: targetModel.schema,
1640
+ tableName: targetModel.tableName,
1641
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = $1`,
1642
+ [id],
1643
+ routeContext?.getDbTransactionClient(),
1644
+ );
1645
+ const targetEntityIds = targetRows.map((item) => item.id);
1646
+ for (const targetEntityId of targetEntityIds) {
1647
+ await deleteEntityById(
1648
+ server,
1649
+ relationDataAccessor,
1650
+ {
1651
+ routeContext,
1652
+ id: targetEntityId,
1653
+ },
1654
+ plugin,
1655
+ );
1656
+ }
1657
+ }
1658
+ }
1659
+ } else if (relationProperty.entityDeletingReaction === "unlink") {
1660
+ if (relationProperty.relation === "one") {
1661
+ // do nothing, entity will be deleted later.
1662
+ } else if (relationProperty.relation === "many") {
1663
+ if (relationProperty.linkTableName) {
1664
+ await server.queryDatabaseObject(
1665
+ `DELETE FROM ${server.queryBuilder.quoteTable({
1666
+ schema: relationProperty.linkSchema,
1667
+ tableName: relationProperty.linkTableName,
1668
+ })}
1669
+ WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = $1`,
1670
+ [id],
1671
+ routeContext?.getDbTransactionClient(),
1672
+ );
1673
+ } else {
1674
+ const relationModel = server.getModel({
1675
+ singularCode: relationProperty.targetSingularCode,
1676
+ });
1677
+ await server.queryDatabaseObject(
1678
+ `UPDATE ${server.queryBuilder.quoteTable({
1679
+ schema: relationModel.schema,
1680
+ tableName: relationModel.tableName,
1681
+ })}
1682
+ SET ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = null
1683
+ WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = $1`,
1684
+ [id],
1685
+ routeContext?.getDbTransactionClient(),
1686
+ );
1687
+ }
1688
+ }
1689
+ }
1690
+ }
1691
+
1692
+ await dataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
1693
+ if (model.base) {
1694
+ const baseDataAccessor = server.getDataAccessor({
1695
+ singularCode: model.base,
1696
+ });
1697
+ await baseDataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
1698
+ }
1699
+ }
1700
+
1701
+ await server.emitEvent({
1702
+ eventName: "entity.delete",
1703
+ payload: {
1704
+ namespace: model.namespace,
1705
+ modelSingularCode: model.singularCode,
1706
+ before: entity,
1707
+ },
1708
+ sender: plugin,
1709
+ routeContext,
1710
+ });
1711
+ }
1712
+
1511
1713
  export default class EntityManager<TEntity = any> {
1512
1714
  #server: IRpdServer;
1513
1715
  #dataAccessor: IRpdDataAccessor;
@@ -1563,76 +1765,7 @@ export default class EntityManager<TEntity = any> {
1563
1765
  }
1564
1766
 
1565
1767
  async deleteById(options: DeleteEntityByIdOptions | string | number, plugin?: RapidPlugin): Promise<void> {
1566
- // options is id
1567
- if (!isObject(options)) {
1568
- options = {
1569
- id: options,
1570
- };
1571
- }
1572
-
1573
- const model = this.getModel();
1574
- if (model.derivedTypePropertyCode) {
1575
- throw newEntityOperationError("Delete base entity directly is not allowed.");
1576
- }
1577
-
1578
- const { id, routeContext } = options;
1579
-
1580
- const entity = await this.findById({
1581
- id,
1582
- keepNonPropertyFields: true,
1583
- routeContext,
1584
- });
1585
-
1586
- if (!entity) {
1587
- return;
1588
- }
1589
-
1590
- await this.#server.emitEvent({
1591
- eventName: "entity.beforeDelete",
1592
- payload: {
1593
- namespace: model.namespace,
1594
- modelSingularCode: model.singularCode,
1595
- before: entity,
1596
- },
1597
- sender: plugin,
1598
- routeContext,
1599
- });
1600
-
1601
- if (model.softDelete) {
1602
- let dataAccessor = model.base
1603
- ? this.#server.getDataAccessor({
1604
- singularCode: model.base,
1605
- })
1606
- : this.#dataAccessor;
1607
- const currentUserId = routeContext?.state?.userId;
1608
- await dataAccessor.updateById(
1609
- id,
1610
- {
1611
- deleted_at: getNowStringWithTimezone(),
1612
- deleter_id: currentUserId,
1613
- },
1614
- routeContext?.getDbTransactionClient(),
1615
- );
1616
- } else {
1617
- await this.#dataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
1618
- if (model.base) {
1619
- const baseDataAccessor = this.#server.getDataAccessor({
1620
- singularCode: model.base,
1621
- });
1622
- await baseDataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
1623
- }
1624
- }
1625
-
1626
- await this.#server.emitEvent({
1627
- eventName: "entity.delete",
1628
- payload: {
1629
- namespace: model.namespace,
1630
- modelSingularCode: model.singularCode,
1631
- before: entity,
1632
- },
1633
- sender: plugin,
1634
- routeContext,
1635
- });
1768
+ return await deleteEntityById(this.#server, this.#dataAccessor, options, plugin);
1636
1769
  }
1637
1770
 
1638
1771
  async addRelations(options: AddEntityRelationsOptions, plugin?: RapidPlugin): Promise<void> {
@@ -1669,7 +1802,7 @@ export default class EntityManager<TEntity = any> {
1669
1802
  WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName!)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName!)}=$2
1670
1803
  )`;
1671
1804
  const params = [id, relation.id];
1672
- await server.queryDatabaseObject(command, params);
1805
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
1673
1806
  }
1674
1807
  }
1675
1808
 
@@ -1714,7 +1847,7 @@ export default class EntityManager<TEntity = any> {
1714
1847
  const command = `DELETE FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
1715
1848
  WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName!)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName!)}=$2;`;
1716
1849
  const params = [id, relation.id];
1717
- await server.queryDatabaseObject(command, params);
1850
+ await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
1718
1851
  }
1719
1852
  }
1720
1853
 
@@ -1,4 +1,4 @@
1
- import { cloneDeep } from "lodash";
1
+ import { cloneDeep, filter, find } from "lodash";
2
2
  import { IRpdServer } from "~/core/server";
3
3
  import { RpdDataModel, RpdDataModelProperty } from "~/types";
4
4
 
@@ -14,13 +14,17 @@ export function isManyRelationProperty(property: RpdDataModelProperty) {
14
14
  return isRelationProperty(property) && property.relation === "many";
15
15
  }
16
16
 
17
- export function getEntityProperties(server: IRpdServer, model: RpdDataModel) {
18
- return model.properties;
17
+ export function getEntityProperties(server: IRpdServer, model: RpdDataModel, predicate?: (item: RpdDataModelProperty) => boolean) {
18
+ if (!predicate) {
19
+ return model.properties;
20
+ }
21
+
22
+ return filter(model.properties, predicate);
19
23
  }
20
24
 
21
- export function getEntityPropertiesIncludingBase(server: IRpdServer, model: RpdDataModel) {
25
+ export function getEntityPropertiesIncludingBase(server: IRpdServer, model: RpdDataModel, predicate?: (item: RpdDataModelProperty) => boolean) {
22
26
  if (!model.base) {
23
- return model.properties;
27
+ return getEntityProperties(server, model, predicate);
24
28
  }
25
29
 
26
30
  const baseModel = server.getModel({
@@ -28,14 +32,25 @@ export function getEntityPropertiesIncludingBase(server: IRpdServer, model: RpdD
28
32
  });
29
33
  let baseProperties: RpdDataModelProperty[] = [];
30
34
  if (baseModel) {
31
- baseProperties = baseModel.properties.map((property) => {
35
+ if (predicate) {
36
+ baseProperties = filter(baseModel.properties, predicate);
37
+ } else {
38
+ baseProperties = baseModel.properties;
39
+ }
40
+ baseProperties = baseProperties.map((property) => {
32
41
  property = cloneDeep(property);
33
42
  property.isBaseProperty = true;
34
43
  return property;
35
44
  });
36
45
  }
37
46
 
38
- return [...baseProperties, ...model.properties];
47
+ let properties: RpdDataModelProperty[];
48
+ if (predicate) {
49
+ properties = filter(model.properties, predicate);
50
+ } else {
51
+ properties = model.properties;
52
+ }
53
+ return [...baseProperties, ...properties];
39
54
  }
40
55
 
41
56
  export function getEntityPropertyByCode(server: IRpdServer, model: RpdDataModel, propertyCode: string): RpdDataModelProperty | undefined {
package/src/server.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  EntityWatcherType,
18
18
  RpdEntityCreateEventPayload,
19
19
  EmitServerEventOptions,
20
+ IDatabaseClient,
20
21
  } from "./types";
21
22
 
22
23
  import QueryBuilder from "./queryBuilder/queryBuilder";
@@ -373,18 +374,18 @@ export class RapidServer implements IRpdServer {
373
374
  return await factory.createFacility(this, options);
374
375
  }
375
376
 
376
- async queryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>): Promise<any[]> {
377
+ async queryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient): Promise<any[]> {
377
378
  try {
378
- return await this.#databaseAccessor.queryDatabaseObject(sql, params);
379
+ return await this.#databaseAccessor.queryDatabaseObject(sql, params, client);
379
380
  } catch (err) {
380
381
  this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
381
382
  throw err;
382
383
  }
383
384
  }
384
385
 
385
- async tryQueryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>): Promise<any[]> {
386
+ async tryQueryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient): Promise<any[]> {
386
387
  try {
387
- return await this.queryDatabaseObject(sql, params);
388
+ return await this.queryDatabaseObject(sql, params, client);
388
389
  } catch (err) {
389
390
  this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
390
391
  }
package/src/types.ts CHANGED
@@ -314,6 +314,11 @@ export interface RpdDataModelProperty {
314
314
  */
315
315
  linkSchema?: string;
316
316
 
317
+ /**
318
+ * 当删除实体时,针对关系属性的联动处理。
319
+ */
320
+ entityDeletingReaction?: RpdEntityDeleteRelationPropertyReaction;
321
+
317
322
  /**
318
323
  * 当设置为`true`时,仅允许在创建时设置此属性的值,不允许更新。
319
324
  */
@@ -341,6 +346,8 @@ export type RpdDataPropertyTypes =
341
346
  | "image"
342
347
  | "image[]";
343
348
 
349
+ export type RpdEntityDeleteRelationPropertyReaction = "doNothing" | "unlink" | "cascadingDelete";
350
+
344
351
  /**
345
352
  * 数据字典
346
353
  */