@ruiapp/rapid-core 0.5.2 → 0.5.4
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/server.d.ts +3 -3
- package/dist/dataAccess/entityManager.d.ts +2 -0
- package/dist/helpers/metaHelper.d.ts +2 -2
- package/dist/index.js +230 -86
- package/dist/server.d.ts +3 -3
- package/dist/types.d.ts +12 -3
- package/package.json +1 -1
- package/src/bootstrapApplicationConfig.ts +7 -0
- package/src/core/server.ts +3 -2
- package/src/dataAccess/entityManager.ts +261 -85
- package/src/helpers/metaHelper.ts +22 -7
- package/src/server.ts +5 -4
- package/src/types.ts +17 -3
package/dist/core/server.d.ts
CHANGED
|
@@ -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
|
|
16
|
-
tryQueryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown
|
|
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
|
|
2123
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
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());
|
|
@@ -3441,9 +3470,12 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3441
3470
|
}
|
|
3442
3471
|
}
|
|
3443
3472
|
}
|
|
3444
|
-
// save many-relation properties
|
|
3473
|
+
// save many-relation properties (only 'overwrite' mode was supported right now)
|
|
3445
3474
|
for (const property of manyRelationPropertiesToUpdate) {
|
|
3446
3475
|
const relatedEntities = [];
|
|
3476
|
+
const targetModel = server.getModel({
|
|
3477
|
+
singularCode: property.targetSingularCode,
|
|
3478
|
+
});
|
|
3447
3479
|
const targetDataAccessor = server.getDataAccessor({
|
|
3448
3480
|
singularCode: property.targetSingularCode,
|
|
3449
3481
|
});
|
|
@@ -3469,24 +3501,55 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3469
3501
|
const targetLinks = await server.queryDatabaseObject(`SELECT ${server.queryBuilder.quoteObject(property.targetIdColumnName)} FROM ${server.queryBuilder.quoteTable({
|
|
3470
3502
|
schema: property.linkSchema,
|
|
3471
3503
|
tableName: property.linkTableName,
|
|
3472
|
-
})} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1`, [id]);
|
|
3504
|
+
})} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
|
|
3473
3505
|
currentTargetIds = targetLinks.map((item) => item[property.targetIdColumnName]);
|
|
3474
3506
|
await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
|
|
3475
3507
|
schema: property.linkSchema,
|
|
3476
3508
|
tableName: property.linkTableName,
|
|
3477
3509
|
})} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1
|
|
3478
|
-
AND ${server.queryBuilder.quoteObject(property.targetIdColumnName)} <> ALL($2::int[])`, [id, targetIdsToKeep]);
|
|
3510
|
+
AND ${server.queryBuilder.quoteObject(property.targetIdColumnName)} <> ALL($2::int[])`, [id, targetIdsToKeep], routeContext?.getDbTransactionClient());
|
|
3479
3511
|
}
|
|
3480
3512
|
else {
|
|
3481
|
-
const targetModel = server.getModel({
|
|
3482
|
-
singularCode: property.targetSingularCode,
|
|
3483
|
-
});
|
|
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
|
}
|
|
3519
|
+
const targetIdsToRemove = currentTargetIds.filter((currentId) => !targetIdsToKeep.includes(currentId));
|
|
3520
|
+
if (targetIdsToRemove.length) {
|
|
3521
|
+
if (property.linkTableName) ;
|
|
3522
|
+
else {
|
|
3523
|
+
const updateRelationPropertiesOptions = lodash.get(options.relationPropertiesToUpdate, property.code);
|
|
3524
|
+
let relationRemoveMode = "unlink";
|
|
3525
|
+
if (updateRelationPropertiesOptions === true) {
|
|
3526
|
+
relationRemoveMode = "delete";
|
|
3527
|
+
}
|
|
3528
|
+
else {
|
|
3529
|
+
relationRemoveMode = updateRelationPropertiesOptions.relationRemoveMode;
|
|
3530
|
+
}
|
|
3531
|
+
const relationModel = server.getModel({
|
|
3532
|
+
singularCode: property.targetSingularCode,
|
|
3533
|
+
});
|
|
3534
|
+
if (relationRemoveMode === "unlink") {
|
|
3535
|
+
await server.queryDatabaseObject(`UPDATE ${server.queryBuilder.quoteTable({
|
|
3536
|
+
schema: relationModel.schema,
|
|
3537
|
+
tableName: relationModel.tableName,
|
|
3538
|
+
})}
|
|
3539
|
+
SET ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = null
|
|
3540
|
+
WHERE id = ANY($1::int[])`, [targetIdsToRemove], routeContext?.getDbTransactionClient());
|
|
3541
|
+
}
|
|
3542
|
+
else {
|
|
3543
|
+
// relationRemoveMode === "delete"
|
|
3544
|
+
for (const targetIdToRemove of targetIdsToRemove) {
|
|
3545
|
+
await deleteEntityById(server, targetDataAccessor, {
|
|
3546
|
+
id: targetIdToRemove,
|
|
3547
|
+
routeContext,
|
|
3548
|
+
}, plugin);
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3490
3553
|
for (const relatedEntityToBeSaved of relatedEntitiesToBeSaved) {
|
|
3491
3554
|
let relatedEntityId;
|
|
3492
3555
|
if (lodash.isObject(relatedEntityToBeSaved)) {
|
|
@@ -3507,7 +3570,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3507
3570
|
tableName: property.linkTableName,
|
|
3508
3571
|
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
3509
3572
|
const params = [id, newTargetEntity.id];
|
|
3510
|
-
await server.queryDatabaseObject(command, params);
|
|
3573
|
+
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
3511
3574
|
}
|
|
3512
3575
|
relatedEntities.push(newTargetEntity);
|
|
3513
3576
|
}
|
|
@@ -3548,7 +3611,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3548
3611
|
tableName: property.linkTableName,
|
|
3549
3612
|
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
3550
3613
|
const params = [id, relatedEntityId];
|
|
3551
|
-
await server.queryDatabaseObject(command, params);
|
|
3614
|
+
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
3552
3615
|
}
|
|
3553
3616
|
else {
|
|
3554
3617
|
await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName]: id }, routeContext?.getDbTransactionClient());
|
|
@@ -3572,7 +3635,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3572
3635
|
tableName: property.linkTableName,
|
|
3573
3636
|
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
3574
3637
|
const params = [id, relatedEntityId];
|
|
3575
|
-
await server.queryDatabaseObject(command, params);
|
|
3638
|
+
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
3576
3639
|
}
|
|
3577
3640
|
else {
|
|
3578
3641
|
await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName]: id }, routeContext?.getDbTransactionClient());
|
|
@@ -3654,6 +3717,146 @@ function getEntityDuplicatedErrorMessage(server, model, indexConfig) {
|
|
|
3654
3717
|
});
|
|
3655
3718
|
return `已存在 ${propertyNames.join(", ")} 相同的记录。`;
|
|
3656
3719
|
}
|
|
3720
|
+
async function deleteEntityById(server, dataAccessor, options, plugin) {
|
|
3721
|
+
// options is id
|
|
3722
|
+
if (!lodash.isObject(options)) {
|
|
3723
|
+
options = {
|
|
3724
|
+
id: options,
|
|
3725
|
+
};
|
|
3726
|
+
}
|
|
3727
|
+
const model = dataAccessor.getModel();
|
|
3728
|
+
if (model.derivedTypePropertyCode) {
|
|
3729
|
+
// TODO: should be allowed.
|
|
3730
|
+
throw newEntityOperationError("Delete base entity directly is not allowed.");
|
|
3731
|
+
}
|
|
3732
|
+
const { id, routeContext } = options;
|
|
3733
|
+
const entity = await findById(server, dataAccessor, {
|
|
3734
|
+
id,
|
|
3735
|
+
keepNonPropertyFields: true,
|
|
3736
|
+
routeContext,
|
|
3737
|
+
});
|
|
3738
|
+
if (!entity) {
|
|
3739
|
+
return;
|
|
3740
|
+
}
|
|
3741
|
+
if (model.softDelete) {
|
|
3742
|
+
if (entity.deletedAt) {
|
|
3743
|
+
return;
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
await server.emitEvent({
|
|
3747
|
+
eventName: "entity.beforeDelete",
|
|
3748
|
+
payload: {
|
|
3749
|
+
namespace: model.namespace,
|
|
3750
|
+
modelSingularCode: model.singularCode,
|
|
3751
|
+
before: entity,
|
|
3752
|
+
},
|
|
3753
|
+
sender: plugin,
|
|
3754
|
+
routeContext,
|
|
3755
|
+
});
|
|
3756
|
+
if (model.softDelete) {
|
|
3757
|
+
const currentUserId = routeContext?.state?.userId;
|
|
3758
|
+
await dataAccessor.updateById(id, {
|
|
3759
|
+
deleted_at: getNowStringWithTimezone(),
|
|
3760
|
+
deleter_id: currentUserId,
|
|
3761
|
+
}, routeContext?.getDbTransactionClient());
|
|
3762
|
+
}
|
|
3763
|
+
else {
|
|
3764
|
+
const relationPropertiesWithDeletingReaction = getEntityPropertiesIncludingBase(server, model, (property) => {
|
|
3765
|
+
return isRelationProperty(property) && property.entityDeletingReaction && property.entityDeletingReaction !== "doNothing";
|
|
3766
|
+
});
|
|
3767
|
+
for (const relationProperty of relationPropertiesWithDeletingReaction) {
|
|
3768
|
+
const relationDataAccessor = server.getDataAccessor({
|
|
3769
|
+
singularCode: relationProperty.targetSingularCode,
|
|
3770
|
+
});
|
|
3771
|
+
if (relationProperty.entityDeletingReaction === "cascadingDelete") {
|
|
3772
|
+
if (relationProperty.relation === "one") {
|
|
3773
|
+
const relatedEntityId = entity[relationProperty.targetIdColumnName];
|
|
3774
|
+
if (relatedEntityId) {
|
|
3775
|
+
await deleteEntityById(server, relationDataAccessor, {
|
|
3776
|
+
routeContext,
|
|
3777
|
+
id: relatedEntityId,
|
|
3778
|
+
}, plugin);
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
else if (relationProperty.relation === "many") {
|
|
3782
|
+
if (relationProperty.linkTableName) {
|
|
3783
|
+
const targetLinks = await server.queryDatabaseObject(`SELECT ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName)} FROM ${server.queryBuilder.quoteTable({
|
|
3784
|
+
schema: relationProperty.linkSchema,
|
|
3785
|
+
tableName: relationProperty.linkTableName,
|
|
3786
|
+
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
|
|
3787
|
+
const targetEntityIds = targetLinks.map((item) => item[relationProperty.targetIdColumnName]);
|
|
3788
|
+
await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
|
|
3789
|
+
schema: relationProperty.linkSchema,
|
|
3790
|
+
tableName: relationProperty.linkTableName,
|
|
3791
|
+
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
|
|
3792
|
+
for (const targetEntityId of targetEntityIds) {
|
|
3793
|
+
await deleteEntityById(server, relationDataAccessor, {
|
|
3794
|
+
routeContext,
|
|
3795
|
+
id: targetEntityId,
|
|
3796
|
+
}, plugin);
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
else {
|
|
3800
|
+
const targetModel = server.getModel({
|
|
3801
|
+
singularCode: relationProperty.targetSingularCode,
|
|
3802
|
+
});
|
|
3803
|
+
const targetRows = await server.queryDatabaseObject(`SELECT id FROM ${server.queryBuilder.quoteTable({
|
|
3804
|
+
schema: targetModel.schema,
|
|
3805
|
+
tableName: targetModel.tableName,
|
|
3806
|
+
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
|
|
3807
|
+
const targetEntityIds = targetRows.map((item) => item.id);
|
|
3808
|
+
for (const targetEntityId of targetEntityIds) {
|
|
3809
|
+
await deleteEntityById(server, relationDataAccessor, {
|
|
3810
|
+
routeContext,
|
|
3811
|
+
id: targetEntityId,
|
|
3812
|
+
}, plugin);
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
else if (relationProperty.entityDeletingReaction === "unlink") {
|
|
3818
|
+
if (relationProperty.relation === "one") ;
|
|
3819
|
+
else if (relationProperty.relation === "many") {
|
|
3820
|
+
if (relationProperty.linkTableName) {
|
|
3821
|
+
await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
|
|
3822
|
+
schema: relationProperty.linkSchema,
|
|
3823
|
+
tableName: relationProperty.linkTableName,
|
|
3824
|
+
})}
|
|
3825
|
+
WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
|
|
3826
|
+
}
|
|
3827
|
+
else {
|
|
3828
|
+
const relationModel = server.getModel({
|
|
3829
|
+
singularCode: relationProperty.targetSingularCode,
|
|
3830
|
+
});
|
|
3831
|
+
await server.queryDatabaseObject(`UPDATE ${server.queryBuilder.quoteTable({
|
|
3832
|
+
schema: relationModel.schema,
|
|
3833
|
+
tableName: relationModel.tableName,
|
|
3834
|
+
})}
|
|
3835
|
+
SET ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = null
|
|
3836
|
+
WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
await dataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
|
|
3842
|
+
if (model.base) {
|
|
3843
|
+
const baseDataAccessor = server.getDataAccessor({
|
|
3844
|
+
singularCode: model.base,
|
|
3845
|
+
});
|
|
3846
|
+
await baseDataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
await server.emitEvent({
|
|
3850
|
+
eventName: "entity.delete",
|
|
3851
|
+
payload: {
|
|
3852
|
+
namespace: model.namespace,
|
|
3853
|
+
modelSingularCode: model.singularCode,
|
|
3854
|
+
before: entity,
|
|
3855
|
+
},
|
|
3856
|
+
sender: plugin,
|
|
3857
|
+
routeContext,
|
|
3858
|
+
});
|
|
3859
|
+
}
|
|
3657
3860
|
class EntityManager {
|
|
3658
3861
|
#server;
|
|
3659
3862
|
#dataAccessor;
|
|
@@ -3700,66 +3903,7 @@ class EntityManager {
|
|
|
3700
3903
|
return await this.#dataAccessor.count(countRowOptions, routeContext?.getDbTransactionClient());
|
|
3701
3904
|
}
|
|
3702
3905
|
async deleteById(options, plugin) {
|
|
3703
|
-
|
|
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
|
-
});
|
|
3906
|
+
return await deleteEntityById(this.#server, this.#dataAccessor, options, plugin);
|
|
3763
3907
|
}
|
|
3764
3908
|
async addRelations(options, plugin) {
|
|
3765
3909
|
const server = this.#server;
|
|
@@ -3792,7 +3936,7 @@ class EntityManager {
|
|
|
3792
3936
|
WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}=$2
|
|
3793
3937
|
)`;
|
|
3794
3938
|
const params = [id, relation.id];
|
|
3795
|
-
await server.queryDatabaseObject(command, params);
|
|
3939
|
+
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
3796
3940
|
}
|
|
3797
3941
|
}
|
|
3798
3942
|
await server.emitEvent({
|
|
@@ -3832,7 +3976,7 @@ class EntityManager {
|
|
|
3832
3976
|
const command = `DELETE FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
|
|
3833
3977
|
WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}=$2;`;
|
|
3834
3978
|
const params = [id, relation.id];
|
|
3835
|
-
await server.queryDatabaseObject(command, params);
|
|
3979
|
+
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
3836
3980
|
}
|
|
3837
3981
|
}
|
|
3838
3982
|
await server.emitEvent({
|
|
@@ -4139,18 +4283,18 @@ class RapidServer {
|
|
|
4139
4283
|
}
|
|
4140
4284
|
return await factory.createFacility(this, options);
|
|
4141
4285
|
}
|
|
4142
|
-
async queryDatabaseObject(sql, params) {
|
|
4286
|
+
async queryDatabaseObject(sql, params, client) {
|
|
4143
4287
|
try {
|
|
4144
|
-
return await this.#databaseAccessor.queryDatabaseObject(sql, params);
|
|
4288
|
+
return await this.#databaseAccessor.queryDatabaseObject(sql, params, client);
|
|
4145
4289
|
}
|
|
4146
4290
|
catch (err) {
|
|
4147
4291
|
this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
|
|
4148
4292
|
throw err;
|
|
4149
4293
|
}
|
|
4150
4294
|
}
|
|
4151
|
-
async tryQueryDatabaseObject(sql, params) {
|
|
4295
|
+
async tryQueryDatabaseObject(sql, params, client) {
|
|
4152
4296
|
try {
|
|
4153
|
-
return await this.queryDatabaseObject(sql, params);
|
|
4297
|
+
return await this.queryDatabaseObject(sql, params, client);
|
|
4154
4298
|
}
|
|
4155
4299
|
catch (err) {
|
|
4156
4300
|
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
|
|
45
|
-
tryQueryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown
|
|
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
|
*/
|
|
@@ -509,11 +514,15 @@ export interface UpdateEntityByIdOptions {
|
|
|
509
514
|
/**
|
|
510
515
|
* 指定需要更新关联对象的哪些属性。更新实体时,会创建关联对象,但是默认不更新关联对象的属性。
|
|
511
516
|
*/
|
|
512
|
-
relationPropertiesToUpdate?: Record<string,
|
|
517
|
+
relationPropertiesToUpdate?: Record<string, UpdateRelationPropertyOptions>;
|
|
513
518
|
}
|
|
514
|
-
export type
|
|
519
|
+
export type UpdateRelationPropertyOptions = true | {
|
|
520
|
+
/**
|
|
521
|
+
* 当需移除关系时是删除关联实体,还是取消关联。默认为`delete`。此配置仅对没有配置`linkTableName`的属性有效。
|
|
522
|
+
*/
|
|
523
|
+
relationRemoveMode?: "unlink" | "delete";
|
|
515
524
|
propertiesToUpdate?: string[];
|
|
516
|
-
relationPropertiesToUpdate?: Record<string,
|
|
525
|
+
relationPropertiesToUpdate?: Record<string, UpdateRelationPropertyOptions>;
|
|
517
526
|
};
|
|
518
527
|
export interface DeleteEntityOptions {
|
|
519
528
|
routeContext?: RouteContext;
|
package/package.json
CHANGED
|
@@ -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",
|
package/src/core/server.ts
CHANGED
|
@@ -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
|
|
36
|
-
tryQueryDatabaseObject: (sql: string, params?: unknown[] | Record<string, unknown
|
|
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;
|
|
@@ -1265,9 +1271,12 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1265
1271
|
}
|
|
1266
1272
|
}
|
|
1267
1273
|
|
|
1268
|
-
// save many-relation properties
|
|
1274
|
+
// save many-relation properties (only 'overwrite' mode was supported right now)
|
|
1269
1275
|
for (const property of manyRelationPropertiesToUpdate) {
|
|
1270
1276
|
const relatedEntities: any[] = [];
|
|
1277
|
+
const targetModel = server.getModel({
|
|
1278
|
+
singularCode: property.targetSingularCode,
|
|
1279
|
+
});
|
|
1271
1280
|
const targetDataAccessor = server.getDataAccessor({
|
|
1272
1281
|
singularCode: property.targetSingularCode!,
|
|
1273
1282
|
});
|
|
@@ -1298,6 +1307,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1298
1307
|
tableName: property.linkTableName,
|
|
1299
1308
|
})} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName!)} = $1`,
|
|
1300
1309
|
[id],
|
|
1310
|
+
routeContext?.getDbTransactionClient(),
|
|
1301
1311
|
);
|
|
1302
1312
|
currentTargetIds = targetLinks.map((item) => item[property.targetIdColumnName]);
|
|
1303
1313
|
|
|
@@ -1308,21 +1318,63 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1308
1318
|
})} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName!)} = $1
|
|
1309
1319
|
AND ${server.queryBuilder.quoteObject(property.targetIdColumnName!)} <> ALL($2::int[])`,
|
|
1310
1320
|
[id, targetIdsToKeep],
|
|
1321
|
+
routeContext?.getDbTransactionClient(),
|
|
1311
1322
|
);
|
|
1312
1323
|
} else {
|
|
1313
|
-
const targetModel = server.getModel({
|
|
1314
|
-
singularCode: property.targetSingularCode,
|
|
1315
|
-
});
|
|
1316
1324
|
const targetRows = await server.queryDatabaseObject(
|
|
1317
1325
|
`SELECT id FROM ${server.queryBuilder.quoteTable({
|
|
1318
1326
|
schema: targetModel.schema,
|
|
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
|
}
|
|
1325
1334
|
|
|
1335
|
+
const targetIdsToRemove = currentTargetIds.filter((currentId) => !targetIdsToKeep.includes(currentId));
|
|
1336
|
+
if (targetIdsToRemove.length) {
|
|
1337
|
+
if (property.linkTableName) {
|
|
1338
|
+
// do nothing. we've remove the link rows before.
|
|
1339
|
+
} else {
|
|
1340
|
+
const updateRelationPropertiesOptions = get(options.relationPropertiesToUpdate, property.code);
|
|
1341
|
+
let relationRemoveMode: "unlink" | "delete" = "unlink";
|
|
1342
|
+
if (updateRelationPropertiesOptions === true) {
|
|
1343
|
+
relationRemoveMode = "delete";
|
|
1344
|
+
} else {
|
|
1345
|
+
relationRemoveMode = updateRelationPropertiesOptions.relationRemoveMode;
|
|
1346
|
+
}
|
|
1347
|
+
const relationModel = server.getModel({
|
|
1348
|
+
singularCode: property.targetSingularCode,
|
|
1349
|
+
});
|
|
1350
|
+
if (relationRemoveMode === "unlink") {
|
|
1351
|
+
await server.queryDatabaseObject(
|
|
1352
|
+
`UPDATE ${server.queryBuilder.quoteTable({
|
|
1353
|
+
schema: relationModel.schema,
|
|
1354
|
+
tableName: relationModel.tableName,
|
|
1355
|
+
})}
|
|
1356
|
+
SET ${server.queryBuilder.quoteObject(property.selfIdColumnName!)} = null
|
|
1357
|
+
WHERE id = ANY($1::int[])`,
|
|
1358
|
+
[targetIdsToRemove],
|
|
1359
|
+
routeContext?.getDbTransactionClient(),
|
|
1360
|
+
);
|
|
1361
|
+
} else {
|
|
1362
|
+
// relationRemoveMode === "delete"
|
|
1363
|
+
for (const targetIdToRemove of targetIdsToRemove) {
|
|
1364
|
+
await deleteEntityById(
|
|
1365
|
+
server,
|
|
1366
|
+
targetDataAccessor,
|
|
1367
|
+
{
|
|
1368
|
+
id: targetIdToRemove,
|
|
1369
|
+
routeContext,
|
|
1370
|
+
},
|
|
1371
|
+
plugin,
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1326
1378
|
for (const relatedEntityToBeSaved of relatedEntitiesToBeSaved) {
|
|
1327
1379
|
let relatedEntityId: any;
|
|
1328
1380
|
if (isObject(relatedEntityToBeSaved)) {
|
|
@@ -1344,7 +1396,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1344
1396
|
tableName: property.linkTableName,
|
|
1345
1397
|
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
1346
1398
|
const params = [id, newTargetEntity.id];
|
|
1347
|
-
await server.queryDatabaseObject(command, params);
|
|
1399
|
+
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
1348
1400
|
}
|
|
1349
1401
|
|
|
1350
1402
|
relatedEntities.push(newTargetEntity);
|
|
@@ -1386,7 +1438,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1386
1438
|
tableName: property.linkTableName,
|
|
1387
1439
|
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
1388
1440
|
const params = [id, relatedEntityId];
|
|
1389
|
-
await server.queryDatabaseObject(command, params);
|
|
1441
|
+
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
1390
1442
|
} else {
|
|
1391
1443
|
await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName!]: id }, routeContext?.getDbTransactionClient());
|
|
1392
1444
|
targetEntity[property.selfIdColumnName!] = id;
|
|
@@ -1409,7 +1461,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1409
1461
|
tableName: property.linkTableName,
|
|
1410
1462
|
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
1411
1463
|
const params = [id, relatedEntityId];
|
|
1412
|
-
await server.queryDatabaseObject(command, params);
|
|
1464
|
+
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
1413
1465
|
} else {
|
|
1414
1466
|
await targetDataAccessor.updateById(targetEntity.id, { [property.selfIdColumnName!]: id }, routeContext?.getDbTransactionClient());
|
|
1415
1467
|
targetEntity[property.selfIdColumnName!] = id;
|
|
@@ -1508,6 +1560,199 @@ function getEntityDuplicatedErrorMessage(server: IRpdServer, model: RpdDataModel
|
|
|
1508
1560
|
return `已存在 ${propertyNames.join(", ")} 相同的记录。`;
|
|
1509
1561
|
}
|
|
1510
1562
|
|
|
1563
|
+
async function deleteEntityById(
|
|
1564
|
+
server: IRpdServer,
|
|
1565
|
+
dataAccessor: IRpdDataAccessor,
|
|
1566
|
+
options: DeleteEntityByIdOptions | string | number,
|
|
1567
|
+
plugin?: RapidPlugin,
|
|
1568
|
+
): Promise<void> {
|
|
1569
|
+
// options is id
|
|
1570
|
+
if (!isObject(options)) {
|
|
1571
|
+
options = {
|
|
1572
|
+
id: options,
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
const model = dataAccessor.getModel();
|
|
1577
|
+
if (model.derivedTypePropertyCode) {
|
|
1578
|
+
// TODO: should be allowed.
|
|
1579
|
+
throw newEntityOperationError("Delete base entity directly is not allowed.");
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
const { id, routeContext } = options;
|
|
1583
|
+
|
|
1584
|
+
const entity = await findById(server, dataAccessor, {
|
|
1585
|
+
id,
|
|
1586
|
+
keepNonPropertyFields: true,
|
|
1587
|
+
routeContext,
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
if (!entity) {
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
if (model.softDelete) {
|
|
1595
|
+
if (entity.deletedAt) {
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
await server.emitEvent({
|
|
1601
|
+
eventName: "entity.beforeDelete",
|
|
1602
|
+
payload: {
|
|
1603
|
+
namespace: model.namespace,
|
|
1604
|
+
modelSingularCode: model.singularCode,
|
|
1605
|
+
before: entity,
|
|
1606
|
+
},
|
|
1607
|
+
sender: plugin,
|
|
1608
|
+
routeContext,
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
if (model.softDelete) {
|
|
1612
|
+
const currentUserId = routeContext?.state?.userId;
|
|
1613
|
+
await dataAccessor.updateById(
|
|
1614
|
+
id,
|
|
1615
|
+
{
|
|
1616
|
+
deleted_at: getNowStringWithTimezone(),
|
|
1617
|
+
deleter_id: currentUserId,
|
|
1618
|
+
},
|
|
1619
|
+
routeContext?.getDbTransactionClient(),
|
|
1620
|
+
);
|
|
1621
|
+
} else {
|
|
1622
|
+
const relationPropertiesWithDeletingReaction = getEntityPropertiesIncludingBase(server, model, (property) => {
|
|
1623
|
+
return isRelationProperty(property) && property.entityDeletingReaction && property.entityDeletingReaction !== "doNothing";
|
|
1624
|
+
});
|
|
1625
|
+
|
|
1626
|
+
for (const relationProperty of relationPropertiesWithDeletingReaction) {
|
|
1627
|
+
const relationDataAccessor = server.getDataAccessor({
|
|
1628
|
+
singularCode: relationProperty.targetSingularCode,
|
|
1629
|
+
});
|
|
1630
|
+
if (relationProperty.entityDeletingReaction === "cascadingDelete") {
|
|
1631
|
+
if (relationProperty.relation === "one") {
|
|
1632
|
+
const relatedEntityId = entity[relationProperty.targetIdColumnName];
|
|
1633
|
+
if (relatedEntityId) {
|
|
1634
|
+
await deleteEntityById(
|
|
1635
|
+
server,
|
|
1636
|
+
relationDataAccessor,
|
|
1637
|
+
{
|
|
1638
|
+
routeContext,
|
|
1639
|
+
id: relatedEntityId,
|
|
1640
|
+
},
|
|
1641
|
+
plugin,
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
} else if (relationProperty.relation === "many") {
|
|
1645
|
+
if (relationProperty.linkTableName) {
|
|
1646
|
+
const targetLinks = await server.queryDatabaseObject(
|
|
1647
|
+
`SELECT ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName)} FROM ${server.queryBuilder.quoteTable({
|
|
1648
|
+
schema: relationProperty.linkSchema,
|
|
1649
|
+
tableName: relationProperty.linkTableName,
|
|
1650
|
+
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = $1`,
|
|
1651
|
+
[id],
|
|
1652
|
+
routeContext?.getDbTransactionClient(),
|
|
1653
|
+
);
|
|
1654
|
+
const targetEntityIds = targetLinks.map((item) => item[relationProperty.targetIdColumnName]);
|
|
1655
|
+
|
|
1656
|
+
await server.queryDatabaseObject(
|
|
1657
|
+
`DELETE FROM ${server.queryBuilder.quoteTable({
|
|
1658
|
+
schema: relationProperty.linkSchema,
|
|
1659
|
+
tableName: relationProperty.linkTableName,
|
|
1660
|
+
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = $1`,
|
|
1661
|
+
[id],
|
|
1662
|
+
routeContext?.getDbTransactionClient(),
|
|
1663
|
+
);
|
|
1664
|
+
|
|
1665
|
+
for (const targetEntityId of targetEntityIds) {
|
|
1666
|
+
await deleteEntityById(
|
|
1667
|
+
server,
|
|
1668
|
+
relationDataAccessor,
|
|
1669
|
+
{
|
|
1670
|
+
routeContext,
|
|
1671
|
+
id: targetEntityId,
|
|
1672
|
+
},
|
|
1673
|
+
plugin,
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
} else {
|
|
1677
|
+
const targetModel = server.getModel({
|
|
1678
|
+
singularCode: relationProperty.targetSingularCode,
|
|
1679
|
+
});
|
|
1680
|
+
const targetRows = await server.queryDatabaseObject(
|
|
1681
|
+
`SELECT id FROM ${server.queryBuilder.quoteTable({
|
|
1682
|
+
schema: targetModel.schema,
|
|
1683
|
+
tableName: targetModel.tableName,
|
|
1684
|
+
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = $1`,
|
|
1685
|
+
[id],
|
|
1686
|
+
routeContext?.getDbTransactionClient(),
|
|
1687
|
+
);
|
|
1688
|
+
const targetEntityIds = targetRows.map((item) => item.id);
|
|
1689
|
+
for (const targetEntityId of targetEntityIds) {
|
|
1690
|
+
await deleteEntityById(
|
|
1691
|
+
server,
|
|
1692
|
+
relationDataAccessor,
|
|
1693
|
+
{
|
|
1694
|
+
routeContext,
|
|
1695
|
+
id: targetEntityId,
|
|
1696
|
+
},
|
|
1697
|
+
plugin,
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
} else if (relationProperty.entityDeletingReaction === "unlink") {
|
|
1703
|
+
if (relationProperty.relation === "one") {
|
|
1704
|
+
// do nothing, entity will be deleted later.
|
|
1705
|
+
} else if (relationProperty.relation === "many") {
|
|
1706
|
+
if (relationProperty.linkTableName) {
|
|
1707
|
+
await server.queryDatabaseObject(
|
|
1708
|
+
`DELETE FROM ${server.queryBuilder.quoteTable({
|
|
1709
|
+
schema: relationProperty.linkSchema,
|
|
1710
|
+
tableName: relationProperty.linkTableName,
|
|
1711
|
+
})}
|
|
1712
|
+
WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = $1`,
|
|
1713
|
+
[id],
|
|
1714
|
+
routeContext?.getDbTransactionClient(),
|
|
1715
|
+
);
|
|
1716
|
+
} else {
|
|
1717
|
+
const relationModel = server.getModel({
|
|
1718
|
+
singularCode: relationProperty.targetSingularCode,
|
|
1719
|
+
});
|
|
1720
|
+
await server.queryDatabaseObject(
|
|
1721
|
+
`UPDATE ${server.queryBuilder.quoteTable({
|
|
1722
|
+
schema: relationModel.schema,
|
|
1723
|
+
tableName: relationModel.tableName,
|
|
1724
|
+
})}
|
|
1725
|
+
SET ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = null
|
|
1726
|
+
WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = $1`,
|
|
1727
|
+
[id],
|
|
1728
|
+
routeContext?.getDbTransactionClient(),
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
await dataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
|
|
1736
|
+
if (model.base) {
|
|
1737
|
+
const baseDataAccessor = server.getDataAccessor({
|
|
1738
|
+
singularCode: model.base,
|
|
1739
|
+
});
|
|
1740
|
+
await baseDataAccessor.deleteById(id, routeContext?.getDbTransactionClient());
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
await server.emitEvent({
|
|
1745
|
+
eventName: "entity.delete",
|
|
1746
|
+
payload: {
|
|
1747
|
+
namespace: model.namespace,
|
|
1748
|
+
modelSingularCode: model.singularCode,
|
|
1749
|
+
before: entity,
|
|
1750
|
+
},
|
|
1751
|
+
sender: plugin,
|
|
1752
|
+
routeContext,
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1511
1756
|
export default class EntityManager<TEntity = any> {
|
|
1512
1757
|
#server: IRpdServer;
|
|
1513
1758
|
#dataAccessor: IRpdDataAccessor;
|
|
@@ -1563,76 +1808,7 @@ export default class EntityManager<TEntity = any> {
|
|
|
1563
1808
|
}
|
|
1564
1809
|
|
|
1565
1810
|
async deleteById(options: DeleteEntityByIdOptions | string | number, plugin?: RapidPlugin): Promise<void> {
|
|
1566
|
-
|
|
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
|
-
});
|
|
1811
|
+
return await deleteEntityById(this.#server, this.#dataAccessor, options, plugin);
|
|
1636
1812
|
}
|
|
1637
1813
|
|
|
1638
1814
|
async addRelations(options: AddEntityRelationsOptions, plugin?: RapidPlugin): Promise<void> {
|
|
@@ -1669,7 +1845,7 @@ export default class EntityManager<TEntity = any> {
|
|
|
1669
1845
|
WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName!)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName!)}=$2
|
|
1670
1846
|
)`;
|
|
1671
1847
|
const params = [id, relation.id];
|
|
1672
|
-
await server.queryDatabaseObject(command, params);
|
|
1848
|
+
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
1673
1849
|
}
|
|
1674
1850
|
}
|
|
1675
1851
|
|
|
@@ -1714,7 +1890,7 @@ export default class EntityManager<TEntity = any> {
|
|
|
1714
1890
|
const command = `DELETE FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
|
|
1715
1891
|
WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName!)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName!)}=$2;`;
|
|
1716
1892
|
const params = [id, relation.id];
|
|
1717
|
-
await server.queryDatabaseObject(command, params);
|
|
1893
|
+
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
1718
1894
|
}
|
|
1719
1895
|
}
|
|
1720
1896
|
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
*/
|
|
@@ -652,14 +659,21 @@ export interface UpdateEntityByIdOptions {
|
|
|
652
659
|
/**
|
|
653
660
|
* 指定需要更新关联对象的哪些属性。更新实体时,会创建关联对象,但是默认不更新关联对象的属性。
|
|
654
661
|
*/
|
|
655
|
-
relationPropertiesToUpdate?: Record<string,
|
|
662
|
+
relationPropertiesToUpdate?: Record<string, UpdateRelationPropertyOptions>;
|
|
656
663
|
}
|
|
657
664
|
|
|
658
|
-
export type
|
|
665
|
+
export type UpdateRelationPropertyOptions =
|
|
659
666
|
| true
|
|
660
667
|
| {
|
|
668
|
+
// TODO: impl savingMode 'patch'
|
|
669
|
+
// savingMode?: "overwrite" | "patch";
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* 当需移除关系时是删除关联实体,还是取消关联。默认为`delete`。此配置仅对没有配置`linkTableName`的属性有效。
|
|
673
|
+
*/
|
|
674
|
+
relationRemoveMode?: "unlink" | "delete";
|
|
661
675
|
propertiesToUpdate?: string[];
|
|
662
|
-
relationPropertiesToUpdate?: Record<string,
|
|
676
|
+
relationPropertiesToUpdate?: Record<string, UpdateRelationPropertyOptions>;
|
|
663
677
|
};
|
|
664
678
|
|
|
665
679
|
export interface DeleteEntityOptions {
|