@ruiapp/rapid-core 0.1.61 → 0.1.63

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.
@@ -6,7 +6,7 @@ export type RowFilterExistenceOperators = "exists" | "notExists";
6
6
  export type RowFilterOperators = RowFilterRelationalOperators | RowFilterSetOperators | RowFilterLogicalOperators | RowFilterUnaryOperators | RowFilterExistenceOperators;
7
7
  export type RowFilterOptions = FindRowRelationalFilterOptions | FindRowSetFilterOptions | FindRowLogicalFilterOptions | FindRowUnaryFilterOptions | FindRowExistenceFilterOptions;
8
8
  export type RowNonRelationPropertyFilterOptions = FindRowRelationalFilterOptions | FindRowSetFilterOptions | FindRowUnaryFilterOptions;
9
- export type ColumnQueryOptions = string | ColumnNameWithTableName;
9
+ export type ColumnSelectOptions = string | ColumnNameWithTableName;
10
10
  export type ColumnNameWithTableName = {
11
11
  name: string;
12
12
  tableName?: string;
@@ -15,16 +15,16 @@ export interface FindRowOptions {
15
15
  filters?: RowFilterOptions[];
16
16
  orderBy?: FindRowOrderByOptions[];
17
17
  pagination?: FindRowPaginationOptions;
18
- fields?: ColumnQueryOptions[];
18
+ fields?: ColumnSelectOptions[];
19
19
  keepNonPropertyFields?: boolean;
20
20
  }
21
21
  export interface FindRowRelationalFilterOptions {
22
- field: ColumnQueryOptions;
22
+ field: ColumnSelectOptions;
23
23
  operator: RowFilterRelationalOperators;
24
24
  value: any;
25
25
  }
26
26
  export interface FindRowSetFilterOptions {
27
- field: ColumnQueryOptions;
27
+ field: ColumnSelectOptions;
28
28
  operator: RowFilterSetOperators;
29
29
  value: any[];
30
30
  itemType?: string;
@@ -34,11 +34,11 @@ export interface FindRowLogicalFilterOptions {
34
34
  filters: RowFilterOptions[];
35
35
  }
36
36
  export interface FindRowUnaryFilterOptions {
37
- field: ColumnQueryOptions;
37
+ field: ColumnSelectOptions;
38
38
  operator: RowFilterUnaryOperators;
39
39
  }
40
40
  export interface FindRowExistenceFilterOptions {
41
- field: ColumnQueryOptions;
41
+ field: ColumnSelectOptions;
42
42
  operator: RowFilterExistenceOperators;
43
43
  filters: RowFilterOptions[];
44
44
  }
@@ -48,7 +48,7 @@ export interface FindRowPaginationOptions {
48
48
  withoutTotal?: boolean;
49
49
  }
50
50
  export interface FindRowOrderByOptions {
51
- field: ColumnQueryOptions;
51
+ field: ColumnSelectOptions;
52
52
  desc?: boolean;
53
53
  }
54
54
  export interface CountRowOptions {
@@ -1,5 +1,19 @@
1
- import { AddEntityRelationsOptions, CountEntityOptions, CountEntityResult, CreateEntityOptions, DeleteEntityByIdOptions, FindEntityByIdOptions, FindEntityOptions, IRpdDataAccessor, RemoveEntityRelationsOptions, RpdDataModel, UpdateEntityByIdOptions } from "../types";
1
+ import { AddEntityRelationsOptions, CountEntityOptions, CountEntityResult, CreateEntityOptions, DeleteEntityByIdOptions, FindEntityByIdOptions, FindEntityOptions, FindEntitySelectRelationOptions, IRpdDataAccessor, RemoveEntityRelationsOptions, RpdDataModel, RpdDataModelProperty, UpdateEntityByIdOptions } from "../types";
2
2
  import { IRpdServer, RapidPlugin } from "../core/server";
3
+ export type FindOneRelationEntitiesOptions = {
4
+ server: IRpdServer;
5
+ mainModel: RpdDataModel;
6
+ relationProperty: RpdDataModelProperty;
7
+ relationEntityIds: any[];
8
+ selectRelationOptions?: FindEntitySelectRelationOptions;
9
+ };
10
+ export type FindManyRelationEntitiesOptions = {
11
+ server: IRpdServer;
12
+ mainModel: RpdDataModel;
13
+ relationProperty: RpdDataModelProperty;
14
+ mainEntityIds: any[];
15
+ selectRelationOptions?: FindEntitySelectRelationOptions;
16
+ };
3
17
  export default class EntityManager<TEntity = any> {
4
18
  #private;
5
19
  constructor(server: IRpdServer, dataAccessor: IRpdDataAccessor);
package/dist/index.js CHANGED
@@ -277,9 +277,11 @@ class QueryBuilder {
277
277
  command += `${this.quoteObject(derivedModel.tableName)}.* FROM `;
278
278
  }
279
279
  else {
280
- command += columns.map((column) => {
280
+ command += columns
281
+ .map((column) => {
281
282
  return this.quoteColumn(column, ctx.emitTableAlias);
282
- }).join(", ");
283
+ })
284
+ .join(", ");
283
285
  command += " FROM ";
284
286
  }
285
287
  command += `${this.quoteTable(derivedModel)} LEFT JOIN ${this.quoteTable(baseModel)} ON ${this.quoteObject(derivedModel.tableName)}.id = ${this.quoteObject(baseModel.tableName)}.id`;
@@ -2001,16 +2003,18 @@ async function findEntities(server, dataAccessor, options) {
2001
2003
  singularCode: model.base,
2002
2004
  });
2003
2005
  }
2004
- let propertiesToSlect;
2006
+ let propertiesToSelect;
2007
+ let relationOptions = options.relations || {};
2008
+ let relationPropertyCodes = Object.keys(relationOptions) || [];
2005
2009
  if (!options.properties || !options.properties.length) {
2006
- propertiesToSlect = getEntityPropertiesIncludingBase(server, model).filter((property) => !isRelationProperty(property));
2010
+ propertiesToSelect = getEntityPropertiesIncludingBase(server, model).filter((property) => !isRelationProperty(property) || relationPropertyCodes.includes(property.code));
2007
2011
  }
2008
2012
  else {
2009
- propertiesToSlect = getEntityPropertiesIncludingBase(server, model).filter((property) => options.properties.includes(property.code));
2013
+ propertiesToSelect = getEntityPropertiesIncludingBase(server, model).filter((property) => options.properties.includes(property.code) || relationPropertyCodes.includes(property.code));
2010
2014
  }
2011
2015
  const columnsToSelect = [];
2012
2016
  const relationPropertiesToSelect = [];
2013
- lodash.forEach(propertiesToSlect, (property) => {
2017
+ lodash.forEach(propertiesToSelect, (property) => {
2014
2018
  if (isRelationProperty(property)) {
2015
2019
  relationPropertiesToSelect.push(property);
2016
2020
  if (property.relation === "one" && !property.linkTableName) {
@@ -2058,6 +2062,31 @@ async function findEntities(server, dataAccessor, options) {
2058
2062
  }
2059
2063
  });
2060
2064
  }
2065
+ if (options.extraColumnsToSelect) {
2066
+ lodash.forEach(options.extraColumnsToSelect, (extraColumnToSelect) => {
2067
+ const columnSelectOptionExists = lodash.find(columnsToSelect, (item) => {
2068
+ if (typeof item === "string") {
2069
+ if (typeof extraColumnToSelect === "string") {
2070
+ return item === extraColumnToSelect;
2071
+ }
2072
+ else {
2073
+ return item == extraColumnToSelect.name;
2074
+ }
2075
+ }
2076
+ else {
2077
+ if (typeof extraColumnToSelect === "string") {
2078
+ return item.name === extraColumnToSelect;
2079
+ }
2080
+ else {
2081
+ return item.name == extraColumnToSelect.name;
2082
+ }
2083
+ }
2084
+ });
2085
+ if (!columnSelectOptionExists) {
2086
+ columnsToSelect.push(extraColumnToSelect);
2087
+ }
2088
+ });
2089
+ }
2061
2090
  const rowFilters = await convertEntityFiltersToRowFilters(server, model, baseModel, options.filters);
2062
2091
  const findRowOptions = {
2063
2092
  filters: rowFilters,
@@ -2074,27 +2103,45 @@ async function findEntities(server, dataAccessor, options) {
2074
2103
  for (const relationProperty of relationPropertiesToSelect) {
2075
2104
  const isManyRelation = relationProperty.relation === "many";
2076
2105
  if (relationProperty.linkTableName) {
2077
- const targetModel = server.getModel({ singularCode: relationProperty.targetSingularCode });
2078
- if (!targetModel) {
2106
+ const relationModel = server.getModel({ singularCode: relationProperty.targetSingularCode });
2107
+ if (!relationModel) {
2079
2108
  continue;
2080
2109
  }
2081
2110
  if (isManyRelation) {
2082
- const relationLinks = await findManyRelationLinksViaLinkTable(server, targetModel, relationProperty, entityIds);
2111
+ const relationLinks = await findManyRelationLinksViaLinkTable({
2112
+ server,
2113
+ mainModel: relationModel,
2114
+ relationProperty,
2115
+ mainEntityIds: entityIds,
2116
+ selectRelationOptions: relationOptions[relationProperty.code],
2117
+ });
2083
2118
  lodash.forEach(rows, (row) => {
2084
2119
  row[relationProperty.code] = lodash.filter(relationLinks, (link) => {
2085
2120
  return link[relationProperty.selfIdColumnName] == row["id"];
2086
- }).map((link) => mapDbRowToEntity(server, targetModel, link.targetEntity, options.keepNonPropertyFields));
2121
+ }).map((link) => mapDbRowToEntity(server, relationModel, link.targetEntity, options.keepNonPropertyFields));
2087
2122
  });
2088
2123
  }
2089
2124
  }
2090
2125
  else {
2091
2126
  let relatedEntities;
2092
2127
  if (isManyRelation) {
2093
- relatedEntities = await findManyRelatedEntitiesViaIdPropertyCode(server, model, relationProperty, entityIds);
2128
+ relatedEntities = await findManyRelatedEntitiesViaIdPropertyCode({
2129
+ server,
2130
+ mainModel: model,
2131
+ relationProperty,
2132
+ mainEntityIds: entityIds,
2133
+ selectRelationOptions: relationOptions[relationProperty.code],
2134
+ });
2094
2135
  }
2095
2136
  else {
2096
2137
  const targetEntityIds = lodash.uniq(lodash.reject(lodash.map(rows, (entity) => entity[relationProperty.targetIdColumnName]), isNullOrUndefined));
2097
- relatedEntities = await findOneRelatedEntitiesViaIdPropertyCode(server, model, relationProperty, targetEntityIds);
2138
+ relatedEntities = await findOneRelatedEntitiesViaIdPropertyCode({
2139
+ server,
2140
+ mainModel: model,
2141
+ relationProperty,
2142
+ relationEntityIds: targetEntityIds,
2143
+ selectRelationOptions: relationOptions[relationProperty.code],
2144
+ });
2098
2145
  }
2099
2146
  const targetModel = server.getModel({
2100
2147
  singularCode: relationProperty.targetSingularCode,
@@ -2295,7 +2342,10 @@ async function convertEntityFiltersToRowFilters(server, model, baseModel, filter
2295
2342
  ],
2296
2343
  });
2297
2344
  const targetEntityIds = lodash.map(targetEntities, (entity) => entity["id"]);
2298
- const command = `SELECT * FROM ${server.queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName)} = ANY($1::int[])`;
2345
+ const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
2346
+ schema: relationProperty.linkSchema,
2347
+ tableName: relationProperty.linkTableName,
2348
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName)} = ANY($1::int[])`;
2299
2349
  const params = [targetEntityIds];
2300
2350
  const links = await server.queryDatabaseObject(command, params);
2301
2351
  const selfEntityIds = links.map((link) => link[relationProperty.selfIdColumnName]);
@@ -2351,65 +2401,99 @@ async function convertEntityFiltersToRowFilters(server, model, baseModel, filter
2351
2401
  }
2352
2402
  return replacedFilters;
2353
2403
  }
2354
- async function findManyRelationLinksViaLinkTable(server, targetModel, relationProperty, entityIds) {
2355
- const command = `SELECT * FROM ${server.queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = ANY($1::int[])`;
2356
- const params = [entityIds];
2404
+ async function findManyRelationLinksViaLinkTable(options) {
2405
+ const { server, relationProperty, mainModel: relationModel, mainEntityIds, selectRelationOptions } = options;
2406
+ const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
2407
+ schema: relationProperty.linkSchema,
2408
+ tableName: relationProperty.linkTableName,
2409
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = ANY($1::int[])`;
2410
+ const params = [mainEntityIds];
2357
2411
  const links = await server.queryDatabaseObject(command, params);
2358
2412
  const targetEntityIds = links.map((link) => link[relationProperty.targetIdColumnName]);
2413
+ const dataAccessor = server.getDataAccessor({
2414
+ namespace: relationModel.namespace,
2415
+ singularCode: relationModel.singularCode,
2416
+ });
2359
2417
  const findEntityOptions = {
2360
2418
  filters: [
2361
2419
  {
2362
- field: {
2363
- name: "id",
2364
- },
2420
+ field: "id",
2365
2421
  operator: "in",
2366
2422
  value: targetEntityIds,
2367
2423
  },
2368
2424
  ],
2425
+ keepNonPropertyFields: true,
2369
2426
  };
2370
- const dataAccessor = server.getDataAccessor({
2371
- namespace: targetModel.namespace,
2372
- singularCode: targetModel.singularCode,
2373
- });
2374
- const targetEntities = await dataAccessor.find(findEntityOptions);
2427
+ if (selectRelationOptions) {
2428
+ if (typeof selectRelationOptions !== "boolean") {
2429
+ if (selectRelationOptions.properties) {
2430
+ findEntityOptions.properties = ["id", ...selectRelationOptions.properties];
2431
+ }
2432
+ if (selectRelationOptions.relations) {
2433
+ findEntityOptions.relations = selectRelationOptions.relations;
2434
+ }
2435
+ }
2436
+ }
2437
+ const targetEntities = await findEntities(server, dataAccessor, findEntityOptions);
2375
2438
  lodash.forEach(links, (link) => {
2376
2439
  link.targetEntity = lodash.find(targetEntities, (e) => e["id"] == link[relationProperty.targetIdColumnName]);
2377
2440
  });
2378
2441
  return links;
2379
2442
  }
2380
- function findManyRelatedEntitiesViaIdPropertyCode(server, model, relationProperty, entityIds) {
2443
+ async function findManyRelatedEntitiesViaIdPropertyCode(options) {
2444
+ const { server, relationProperty, mainEntityIds, selectRelationOptions } = options;
2445
+ const dataAccessor = server.getDataAccessor({
2446
+ singularCode: relationProperty.targetSingularCode,
2447
+ });
2381
2448
  const findEntityOptions = {
2382
2449
  filters: [
2383
2450
  {
2384
- field: {
2385
- name: relationProperty.selfIdColumnName,
2386
- },
2451
+ field: relationProperty.selfIdColumnName,
2387
2452
  operator: "in",
2388
- value: entityIds,
2453
+ value: mainEntityIds,
2389
2454
  },
2390
2455
  ],
2456
+ extraColumnsToSelect: [relationProperty.selfIdColumnName],
2457
+ keepNonPropertyFields: true,
2391
2458
  };
2459
+ if (selectRelationOptions) {
2460
+ if (typeof selectRelationOptions !== "boolean") {
2461
+ if (selectRelationOptions.properties) {
2462
+ findEntityOptions.properties = ["id", ...selectRelationOptions.properties];
2463
+ }
2464
+ if (selectRelationOptions.relations) {
2465
+ findEntityOptions.relations = selectRelationOptions.relations;
2466
+ }
2467
+ }
2468
+ }
2469
+ return await findEntities(server, dataAccessor, findEntityOptions);
2470
+ }
2471
+ async function findOneRelatedEntitiesViaIdPropertyCode(options) {
2472
+ const { server, relationProperty, relationEntityIds, selectRelationOptions } = options;
2392
2473
  const dataAccessor = server.getDataAccessor({
2393
2474
  singularCode: relationProperty.targetSingularCode,
2394
2475
  });
2395
- return dataAccessor.find(findEntityOptions);
2396
- }
2397
- function findOneRelatedEntitiesViaIdPropertyCode(server, model, relationProperty, targetEntityIds) {
2398
2476
  const findEntityOptions = {
2399
2477
  filters: [
2400
2478
  {
2401
- field: {
2402
- name: "id",
2403
- },
2479
+ field: "id",
2404
2480
  operator: "in",
2405
- value: targetEntityIds,
2481
+ value: relationEntityIds,
2406
2482
  },
2407
2483
  ],
2484
+ keepNonPropertyFields: true,
2408
2485
  };
2409
- const dataAccessor = server.getDataAccessor({
2410
- singularCode: relationProperty.targetSingularCode,
2411
- });
2412
- return dataAccessor.find(findEntityOptions);
2486
+ if (selectRelationOptions) {
2487
+ if (typeof selectRelationOptions !== "boolean") {
2488
+ if (selectRelationOptions.properties) {
2489
+ findEntityOptions.properties = ["id", ...selectRelationOptions.properties];
2490
+ }
2491
+ if (selectRelationOptions.relations) {
2492
+ findEntityOptions.relations = selectRelationOptions.relations;
2493
+ }
2494
+ }
2495
+ }
2496
+ return await findEntities(server, dataAccessor, findEntityOptions);
2413
2497
  }
2414
2498
  async function createEntity(server, dataAccessor, options, plugin) {
2415
2499
  const model = dataAccessor.getModel();
@@ -2417,7 +2501,7 @@ async function createEntity(server, dataAccessor, options, plugin) {
2417
2501
  throw newEntityOperationError("Create base entity directly is not allowed.");
2418
2502
  }
2419
2503
  const { entity, routeContext } = options;
2420
- const userId = options.routeContext.state?.userId;
2504
+ const userId = options.routeContext?.state?.userId;
2421
2505
  if (userId) {
2422
2506
  const createdByProperty = getEntityPropertyByCode(server, model, "createdBy");
2423
2507
  if (createdByProperty) {
@@ -2536,7 +2620,10 @@ async function createEntity(server, dataAccessor, options, plugin) {
2536
2620
  entity: targetEntity,
2537
2621
  });
2538
2622
  if (property.linkTableName) {
2539
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2623
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
2624
+ schema: property.linkSchema,
2625
+ tableName: property.linkTableName,
2626
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2540
2627
  const params = [newEntity.id, newTargetEntity.id];
2541
2628
  await server.queryDatabaseObject(command, params);
2542
2629
  }
@@ -2549,7 +2636,10 @@ async function createEntity(server, dataAccessor, options, plugin) {
2549
2636
  throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`);
2550
2637
  }
2551
2638
  if (property.linkTableName) {
2552
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2639
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
2640
+ schema: property.linkSchema,
2641
+ tableName: property.linkTableName,
2642
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2553
2643
  const params = [newEntity.id, relatedEntityId];
2554
2644
  await server.queryDatabaseObject(command, params);
2555
2645
  }
@@ -2568,7 +2658,10 @@ async function createEntity(server, dataAccessor, options, plugin) {
2568
2658
  throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`);
2569
2659
  }
2570
2660
  if (property.linkTableName) {
2571
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2661
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
2662
+ schema: property.linkSchema,
2663
+ tableName: property.linkTableName,
2664
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2572
2665
  const params = [newEntity.id, relatedEntityId];
2573
2666
  await server.queryDatabaseObject(command, params);
2574
2667
  }
@@ -2612,7 +2705,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
2612
2705
  return entity;
2613
2706
  }
2614
2707
  entityToSave = changes || {};
2615
- const userId = options.routeContext.state?.userId;
2708
+ const userId = options.routeContext?.state?.userId;
2616
2709
  if (userId) {
2617
2710
  const updatedByProperty = getEntityPropertyByCode(server, model, "updatedBy");
2618
2711
  if (updatedByProperty) {
@@ -2724,7 +2817,10 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
2724
2817
  }
2725
2818
  if (property.linkTableName) {
2726
2819
  // TODO: should support removing relation
2727
- await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1`, [id]);
2820
+ await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
2821
+ schema: property.linkSchema,
2822
+ tableName: property.linkTableName,
2823
+ })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1`, [id]);
2728
2824
  }
2729
2825
  for (const relatedEntityToBeSaved of relatedEntitiesToBeSaved) {
2730
2826
  let relatedEntityId;
@@ -2740,7 +2836,10 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
2740
2836
  entity: targetEntity,
2741
2837
  });
2742
2838
  if (property.linkTableName) {
2743
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2839
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
2840
+ schema: property.linkSchema,
2841
+ tableName: property.linkTableName,
2842
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2744
2843
  const params = [id, newTargetEntity.id];
2745
2844
  await server.queryDatabaseObject(command, params);
2746
2845
  }
@@ -2753,7 +2852,10 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
2753
2852
  throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`);
2754
2853
  }
2755
2854
  if (property.linkTableName) {
2756
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2855
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
2856
+ schema: property.linkSchema,
2857
+ tableName: property.linkTableName,
2858
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2757
2859
  const params = [id, relatedEntityId];
2758
2860
  await server.queryDatabaseObject(command, params);
2759
2861
  }
@@ -2772,7 +2874,10 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
2772
2874
  throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`);
2773
2875
  }
2774
2876
  if (property.linkTableName) {
2775
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2877
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
2878
+ schema: property.linkSchema,
2879
+ tableName: property.linkTableName,
2880
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
2776
2881
  const params = [id, relatedEntityId];
2777
2882
  await server.queryDatabaseObject(command, params);
2778
2883
  }
@@ -2916,7 +3021,10 @@ class EntityManager {
2916
3021
  const { queryBuilder } = server;
2917
3022
  if (relationProperty.linkTableName) {
2918
3023
  for (const relation of relations) {
2919
- const command = `INSERT INTO ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)})
3024
+ const command = `INSERT INTO ${queryBuilder.quoteTable({
3025
+ schema: relationProperty.linkSchema,
3026
+ tableName: relationProperty.linkTableName,
3027
+ })} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)})
2920
3028
  SELECT $1, $2 WHERE NOT EXISTS (
2921
3029
  SELECT ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}
2922
3030
  FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
@@ -1,5 +1,5 @@
1
1
  import { RpdDataModel, CreateEntityOptions, QuoteTableOptions, DatabaseQuery } from "../types";
2
- import { CountRowOptions, DeleteRowOptions, FindRowOptions, RowFilterOptions, UpdateRowOptions, ColumnQueryOptions } from "../dataAccess/dataAccessTypes";
2
+ import { CountRowOptions, DeleteRowOptions, FindRowOptions, RowFilterOptions, UpdateRowOptions, ColumnSelectOptions } from "../dataAccess/dataAccessTypes";
3
3
  export interface BuildQueryContext {
4
4
  builder: QueryBuilder;
5
5
  params: any[];
@@ -13,7 +13,7 @@ export default class QueryBuilder {
13
13
  constructor(options: InitQueryBuilderOptions);
14
14
  quoteTable(options: QuoteTableOptions): string;
15
15
  quoteObject(name: string): string;
16
- quoteColumn(column: ColumnQueryOptions, emitTableAlias: boolean): string;
16
+ quoteColumn(column: ColumnSelectOptions, emitTableAlias: boolean): string;
17
17
  select(model: RpdDataModel, options: FindRowOptions): DatabaseQuery;
18
18
  selectDerived(derivedModel: RpdDataModel, baseModel: RpdDataModel, options: FindRowOptions): DatabaseQuery;
19
19
  count(model: RpdDataModel, options: CountRowOptions): DatabaseQuery;
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { RouteContext } from "./core/routeContext";
2
2
  import { IRpdServer, RapidPlugin } from "./core/server";
3
- import { CountRowOptions, FindRowOptions } from "./dataAccess/dataAccessTypes";
3
+ import { ColumnSelectOptions, CountRowOptions, FindRowOptions } from "./dataAccess/dataAccessTypes";
4
4
  export type RapidServerConfig = {
5
5
  baseUrl?: string;
6
6
  sessionCookieName: string;
@@ -355,12 +355,15 @@ export interface FindEntityOptions {
355
355
  orderBy?: FindEntityOrderByOptions[];
356
356
  pagination?: FindEntityPaginationOptions;
357
357
  properties?: string[];
358
+ relations?: Record<string, FindEntitySelectRelationOptions>;
359
+ extraColumnsToSelect?: ColumnSelectOptions[];
358
360
  keepNonPropertyFields?: boolean;
359
361
  }
360
362
  export interface FindEntityByIdOptions {
361
363
  routeContext?: RouteContext;
362
364
  id: any;
363
365
  properties?: string[];
366
+ relations?: Record<string, FindEntitySelectRelationOptions>;
364
367
  keepNonPropertyFields?: boolean;
365
368
  }
366
369
  export interface FindEntityRelationalFilterOptions {
@@ -392,6 +395,10 @@ export interface FindEntityPaginationOptions {
392
395
  limit: number;
393
396
  withoutTotal?: boolean;
394
397
  }
398
+ export type FindEntitySelectRelationOptions = true | {
399
+ properties?: string[];
400
+ relations?: Record<string, FindEntitySelectRelationOptions>;
401
+ };
395
402
  export interface FindEntityOrderByOptions {
396
403
  field: string;
397
404
  desc?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruiapp/rapid-core",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,4 +1,18 @@
1
- export type RowFilterRelationalOperators = "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "contains" | "notContains" | "containsCS" | "notContainsCS" | "startsWith" | "notStartsWith" | "endsWith" | "notEndsWith";
1
+ export type RowFilterRelationalOperators =
2
+ | "eq"
3
+ | "ne"
4
+ | "lt"
5
+ | "lte"
6
+ | "gt"
7
+ | "gte"
8
+ | "contains"
9
+ | "notContains"
10
+ | "containsCS"
11
+ | "notContainsCS"
12
+ | "startsWith"
13
+ | "notStartsWith"
14
+ | "endsWith"
15
+ | "notEndsWith";
2
16
 
3
17
  export type RowFilterSetOperators = "in" | "notIn";
4
18
 
@@ -8,38 +22,47 @@ export type RowFilterUnaryOperators = "null" | "notNull";
8
22
 
9
23
  export type RowFilterExistenceOperators = "exists" | "notExists";
10
24
 
11
- export type RowFilterOperators = RowFilterRelationalOperators | RowFilterSetOperators | RowFilterLogicalOperators | RowFilterUnaryOperators | RowFilterExistenceOperators;
25
+ export type RowFilterOperators =
26
+ | RowFilterRelationalOperators
27
+ | RowFilterSetOperators
28
+ | RowFilterLogicalOperators
29
+ | RowFilterUnaryOperators
30
+ | RowFilterExistenceOperators;
12
31
 
13
- export type RowFilterOptions = FindRowRelationalFilterOptions | FindRowSetFilterOptions | FindRowLogicalFilterOptions | FindRowUnaryFilterOptions | FindRowExistenceFilterOptions;
32
+ export type RowFilterOptions =
33
+ | FindRowRelationalFilterOptions
34
+ | FindRowSetFilterOptions
35
+ | FindRowLogicalFilterOptions
36
+ | FindRowUnaryFilterOptions
37
+ | FindRowExistenceFilterOptions;
14
38
 
15
39
  export type RowNonRelationPropertyFilterOptions = FindRowRelationalFilterOptions | FindRowSetFilterOptions | FindRowUnaryFilterOptions;
16
40
 
17
- export type ColumnQueryOptions = string | ColumnNameWithTableName;
41
+ export type ColumnSelectOptions = string | ColumnNameWithTableName;
18
42
 
19
43
  export type ColumnNameWithTableName = {
20
44
  name: string;
21
45
  tableName?: string;
22
- }
46
+ };
23
47
 
24
48
  export interface FindRowOptions {
25
49
  filters?: RowFilterOptions[];
26
50
  orderBy?: FindRowOrderByOptions[];
27
51
  pagination?: FindRowPaginationOptions;
28
52
  // TODO: may be `columns` is a better name.
29
- fields?: ColumnQueryOptions[];
53
+ fields?: ColumnSelectOptions[];
30
54
  keepNonPropertyFields?: boolean;
31
55
  }
32
56
 
33
-
34
57
  export interface FindRowRelationalFilterOptions {
35
58
  // TODO: may be `column` is a better name.
36
- field: ColumnQueryOptions;
59
+ field: ColumnSelectOptions;
37
60
  operator: RowFilterRelationalOperators;
38
61
  value: any;
39
62
  }
40
63
 
41
64
  export interface FindRowSetFilterOptions {
42
- field: ColumnQueryOptions;
65
+ field: ColumnSelectOptions;
43
66
  operator: RowFilterSetOperators;
44
67
  value: any[];
45
68
  itemType?: string;
@@ -51,12 +74,12 @@ export interface FindRowLogicalFilterOptions {
51
74
  }
52
75
 
53
76
  export interface FindRowUnaryFilterOptions {
54
- field: ColumnQueryOptions;
77
+ field: ColumnSelectOptions;
55
78
  operator: RowFilterUnaryOperators;
56
79
  }
57
80
 
58
81
  export interface FindRowExistenceFilterOptions {
59
- field: ColumnQueryOptions;
82
+ field: ColumnSelectOptions;
60
83
  operator: RowFilterExistenceOperators;
61
84
  filters: RowFilterOptions[];
62
85
  }
@@ -68,7 +91,7 @@ export interface FindRowPaginationOptions {
68
91
  }
69
92
 
70
93
  export interface FindRowOrderByOptions {
71
- field: ColumnQueryOptions;
94
+ field: ColumnSelectOptions;
72
95
  desc?: boolean;
73
96
  }
74
97
 
@@ -83,4 +106,4 @@ export interface UpdateRowOptions {
83
106
 
84
107
  export interface DeleteRowOptions {
85
108
  filters?: RowFilterOptions[];
86
- }
109
+ }
@@ -10,6 +10,7 @@ import {
10
10
  FindEntityByIdOptions,
11
11
  FindEntityOptions,
12
12
  FindEntityOrderByOptions,
13
+ FindEntitySelectRelationOptions,
13
14
  IRpdDataAccessor,
14
15
  RemoveEntityRelationsOptions,
15
16
  RpdDataModel,
@@ -24,10 +25,26 @@ import { IRpdServer, RapidPlugin } from "~/core/server";
24
25
  import { getEntityPartChanges } from "~/helpers/entityHelpers";
25
26
  import { filter, find, first, forEach, isArray, isObject, keys, map, reject, uniq } from "lodash";
26
27
  import { getEntityPropertiesIncludingBase, getEntityProperty, getEntityPropertyByCode } from "./metaHelper";
27
- import { ColumnQueryOptions, CountRowOptions, FindRowOptions, FindRowOrderByOptions, RowFilterOptions } from "./dataAccessTypes";
28
+ import { ColumnSelectOptions, CountRowOptions, FindRowOptions, FindRowOrderByOptions, RowFilterOptions } from "./dataAccessTypes";
28
29
  import { newEntityOperationError } from "~/utilities/errorUtility";
29
30
  import { getNowStringWithTimezone } from "~/utilities/timeUtility";
30
31
 
32
+ export type FindOneRelationEntitiesOptions = {
33
+ server: IRpdServer;
34
+ mainModel: RpdDataModel;
35
+ relationProperty: RpdDataModelProperty;
36
+ relationEntityIds: any[];
37
+ selectRelationOptions?: FindEntitySelectRelationOptions;
38
+ };
39
+
40
+ export type FindManyRelationEntitiesOptions = {
41
+ server: IRpdServer;
42
+ mainModel: RpdDataModel;
43
+ relationProperty: RpdDataModelProperty;
44
+ mainEntityIds: any[];
45
+ selectRelationOptions?: FindEntitySelectRelationOptions;
46
+ };
47
+
31
48
  function convertEntityOrderByToRowOrderBy(server: IRpdServer, model: RpdDataModel, baseModel?: RpdDataModel, orderByList?: FindEntityOrderByOptions[]) {
32
49
  if (!orderByList) {
33
50
  return null;
@@ -66,17 +83,23 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
66
83
  });
67
84
  }
68
85
 
69
- let propertiesToSlect: RpdDataModelProperty[];
86
+ let propertiesToSelect: RpdDataModelProperty[];
87
+ let relationOptions = options.relations || {};
88
+ let relationPropertyCodes = Object.keys(relationOptions) || [];
70
89
  if (!options.properties || !options.properties.length) {
71
- propertiesToSlect = getEntityPropertiesIncludingBase(server, model).filter((property) => !isRelationProperty(property));
90
+ propertiesToSelect = getEntityPropertiesIncludingBase(server, model).filter(
91
+ (property) => !isRelationProperty(property) || relationPropertyCodes.includes(property.code),
92
+ );
72
93
  } else {
73
- propertiesToSlect = getEntityPropertiesIncludingBase(server, model).filter((property) => options.properties.includes(property.code));
94
+ propertiesToSelect = getEntityPropertiesIncludingBase(server, model).filter(
95
+ (property) => options.properties.includes(property.code) || relationPropertyCodes.includes(property.code),
96
+ );
74
97
  }
75
98
 
76
- const columnsToSelect: ColumnQueryOptions[] = [];
99
+ const columnsToSelect: ColumnSelectOptions[] = [];
77
100
 
78
101
  const relationPropertiesToSelect: RpdDataModelProperty[] = [];
79
- forEach(propertiesToSlect, (property) => {
102
+ forEach(propertiesToSelect, (property) => {
80
103
  if (isRelationProperty(property)) {
81
104
  relationPropertiesToSelect.push(property);
82
105
 
@@ -114,7 +137,9 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
114
137
 
115
138
  // if `keepNonPropertyFields` is true and `properties` are not specified, then select relation columns automatically.
116
139
  if (options.keepNonPropertyFields && (!options.properties || !options.properties.length)) {
117
- const oneRelationPropertiesWithNoLinkTable = getEntityPropertiesIncludingBase(server, model).filter((property) => property.relation === "one" && !property.linkTableName);
140
+ const oneRelationPropertiesWithNoLinkTable = getEntityPropertiesIncludingBase(server, model).filter(
141
+ (property) => property.relation === "one" && !property.linkTableName,
142
+ );
118
143
  oneRelationPropertiesWithNoLinkTable.forEach((property) => {
119
144
  if (property.targetIdColumnName) {
120
145
  columnsToSelect.push({
@@ -125,6 +150,30 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
125
150
  });
126
151
  }
127
152
 
153
+ if (options.extraColumnsToSelect) {
154
+ forEach(options.extraColumnsToSelect, (extraColumnToSelect: ColumnSelectOptions) => {
155
+ const columnSelectOptionExists = find(columnsToSelect, (item: ColumnSelectOptions) => {
156
+ if (typeof item === "string") {
157
+ if (typeof extraColumnToSelect === "string") {
158
+ return item === extraColumnToSelect;
159
+ } else {
160
+ return item == extraColumnToSelect.name;
161
+ }
162
+ } else {
163
+ if (typeof extraColumnToSelect === "string") {
164
+ return item.name === extraColumnToSelect;
165
+ } else {
166
+ return item.name == extraColumnToSelect.name;
167
+ }
168
+ }
169
+ });
170
+
171
+ if (!columnSelectOptionExists) {
172
+ columnsToSelect.push(extraColumnToSelect);
173
+ }
174
+ });
175
+ }
176
+
128
177
  const rowFilters = await convertEntityFiltersToRowFilters(server, model, baseModel, options.filters);
129
178
  const findRowOptions: FindRowOptions = {
130
179
  filters: rowFilters,
@@ -143,24 +192,36 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
143
192
  const isManyRelation = relationProperty.relation === "many";
144
193
 
145
194
  if (relationProperty.linkTableName) {
146
- const targetModel = server.getModel({ singularCode: relationProperty.targetSingularCode! });
147
- if (!targetModel) {
195
+ const relationModel = server.getModel({ singularCode: relationProperty.targetSingularCode! });
196
+ if (!relationModel) {
148
197
  continue;
149
198
  }
150
199
 
151
200
  if (isManyRelation) {
152
- const relationLinks = await findManyRelationLinksViaLinkTable(server, targetModel, relationProperty, entityIds);
201
+ const relationLinks = await findManyRelationLinksViaLinkTable({
202
+ server,
203
+ mainModel: relationModel,
204
+ relationProperty,
205
+ mainEntityIds: entityIds,
206
+ selectRelationOptions: relationOptions[relationProperty.code],
207
+ });
153
208
 
154
209
  forEach(rows, (row: any) => {
155
210
  row[relationProperty.code] = filter(relationLinks, (link: any) => {
156
211
  return link[relationProperty.selfIdColumnName!] == row["id"];
157
- }).map((link) => mapDbRowToEntity(server, targetModel, link.targetEntity, options.keepNonPropertyFields));
212
+ }).map((link) => mapDbRowToEntity(server, relationModel, link.targetEntity, options.keepNonPropertyFields));
158
213
  });
159
214
  }
160
215
  } else {
161
216
  let relatedEntities: any[];
162
217
  if (isManyRelation) {
163
- relatedEntities = await findManyRelatedEntitiesViaIdPropertyCode(server, model, relationProperty, entityIds);
218
+ relatedEntities = await findManyRelatedEntitiesViaIdPropertyCode({
219
+ server,
220
+ mainModel: model,
221
+ relationProperty,
222
+ mainEntityIds: entityIds,
223
+ selectRelationOptions: relationOptions[relationProperty.code],
224
+ });
164
225
  } else {
165
226
  const targetEntityIds = uniq(
166
227
  reject(
@@ -168,7 +229,13 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
168
229
  isNullOrUndefined,
169
230
  ),
170
231
  );
171
- relatedEntities = await findOneRelatedEntitiesViaIdPropertyCode(server, model, relationProperty, targetEntityIds);
232
+ relatedEntities = await findOneRelatedEntitiesViaIdPropertyCode({
233
+ server,
234
+ mainModel: model,
235
+ relationProperty,
236
+ relationEntityIds: targetEntityIds,
237
+ selectRelationOptions: relationOptions[relationProperty.code],
238
+ });
172
239
  }
173
240
 
174
241
  const targetModel = server.getModel({
@@ -232,7 +299,12 @@ async function findById(server: IRpdServer, dataAccessor: IRpdDataAccessor, opti
232
299
  });
233
300
  }
234
301
 
235
- async function convertEntityFiltersToRowFilters(server: IRpdServer, model: RpdDataModel, baseModel: RpdDataModel, filters: EntityFilterOptions[] | undefined): Promise<RowFilterOptions[]> {
302
+ async function convertEntityFiltersToRowFilters(
303
+ server: IRpdServer,
304
+ model: RpdDataModel,
305
+ baseModel: RpdDataModel,
306
+ filters: EntityFilterOptions[] | undefined,
307
+ ): Promise<RowFilterOptions[]> {
236
308
  if (!filters || !filters.length) {
237
309
  return [];
238
310
  }
@@ -251,7 +323,9 @@ async function convertEntityFiltersToRowFilters(server: IRpdServer, model: RpdDa
251
323
  throw new Error(`Invalid filters. Property '${filter.field}' was not found in model '${model.namespace}.${model.singularCode}'`);
252
324
  }
253
325
  if (!isRelationProperty(relationProperty)) {
254
- throw new Error(`Invalid filters. Filter with 'existence' operator on property '${filter.field}' is not allowed. You can only use it on an relation property.`);
326
+ throw new Error(
327
+ `Invalid filters. Filter with 'existence' operator on property '${filter.field}' is not allowed. You can only use it on an relation property.`,
328
+ );
255
329
  }
256
330
 
257
331
  const relatedEntityFilters = filter.filters;
@@ -382,7 +456,10 @@ async function convertEntityFiltersToRowFilters(server: IRpdServer, model: RpdDa
382
456
  });
383
457
  const targetEntityIds = map(targetEntities, (entity: any) => entity["id"]);
384
458
 
385
- const command = `SELECT * FROM ${server.queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName! })} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName!)} = ANY($1::int[])`;
459
+ const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
460
+ schema: relationProperty.linkSchema,
461
+ tableName: relationProperty.linkTableName!,
462
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName!)} = ANY($1::int[])`;
386
463
  const params = [targetEntityIds];
387
464
  const links = await server.queryDatabaseObject(command, params);
388
465
  const selfEntityIds = links.map((link) => link[relationProperty.selfIdColumnName!]);
@@ -439,27 +516,44 @@ async function convertEntityFiltersToRowFilters(server: IRpdServer, model: RpdDa
439
516
  return replacedFilters;
440
517
  }
441
518
 
442
- async function findManyRelationLinksViaLinkTable(server: IRpdServer, targetModel: RpdDataModel, relationProperty: RpdDataModelProperty, entityIds: any[]) {
443
- const command = `SELECT * FROM ${server.queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName! })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = ANY($1::int[])`;
444
- const params = [entityIds];
519
+ async function findManyRelationLinksViaLinkTable(options: FindManyRelationEntitiesOptions) {
520
+ const { server, relationProperty, mainModel: relationModel, mainEntityIds, selectRelationOptions } = options;
521
+ const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
522
+ schema: relationProperty.linkSchema,
523
+ tableName: relationProperty.linkTableName!,
524
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = ANY($1::int[])`;
525
+ const params = [mainEntityIds];
445
526
  const links = await server.queryDatabaseObject(command, params);
446
527
  const targetEntityIds = links.map((link) => link[relationProperty.targetIdColumnName!]);
447
- const findEntityOptions: FindRowOptions = {
528
+
529
+ const dataAccessor = server.getDataAccessor({
530
+ namespace: relationModel.namespace,
531
+ singularCode: relationModel.singularCode,
532
+ });
533
+
534
+ const findEntityOptions: FindEntityOptions = {
448
535
  filters: [
449
536
  {
450
- field: {
451
- name: "id",
452
- },
537
+ field: "id",
453
538
  operator: "in",
454
539
  value: targetEntityIds,
455
540
  },
456
541
  ],
542
+ keepNonPropertyFields: true,
457
543
  };
458
- const dataAccessor = server.getDataAccessor({
459
- namespace: targetModel.namespace,
460
- singularCode: targetModel.singularCode,
461
- });
462
- const targetEntities = await dataAccessor.find(findEntityOptions);
544
+
545
+ if (selectRelationOptions) {
546
+ if (typeof selectRelationOptions !== "boolean") {
547
+ if (selectRelationOptions.properties) {
548
+ findEntityOptions.properties = ["id", ...selectRelationOptions.properties];
549
+ }
550
+ if (selectRelationOptions.relations) {
551
+ findEntityOptions.relations = selectRelationOptions.relations;
552
+ }
553
+ }
554
+ }
555
+
556
+ const targetEntities = await findEntities(server, dataAccessor, findEntityOptions);
463
557
 
464
558
  forEach(links, (link: any) => {
465
559
  link.targetEntity = find(targetEntities, (e: any) => e["id"] == link[relationProperty.targetIdColumnName!]);
@@ -468,42 +562,68 @@ async function findManyRelationLinksViaLinkTable(server: IRpdServer, targetModel
468
562
  return links;
469
563
  }
470
564
 
471
- function findManyRelatedEntitiesViaIdPropertyCode(server: IRpdServer, model: RpdDataModel, relationProperty: RpdDataModelProperty, entityIds: any[]) {
472
- const findEntityOptions: FindRowOptions = {
565
+ async function findManyRelatedEntitiesViaIdPropertyCode(options: FindManyRelationEntitiesOptions) {
566
+ const { server, relationProperty, mainEntityIds, selectRelationOptions } = options;
567
+ const dataAccessor = server.getDataAccessor({
568
+ singularCode: relationProperty.targetSingularCode as string,
569
+ });
570
+
571
+ const findEntityOptions: FindEntityOptions = {
473
572
  filters: [
474
573
  {
475
- field: {
476
- name: relationProperty.selfIdColumnName,
477
- },
574
+ field: relationProperty.selfIdColumnName,
478
575
  operator: "in",
479
- value: entityIds,
576
+ value: mainEntityIds,
480
577
  },
481
578
  ],
579
+ extraColumnsToSelect: [relationProperty.selfIdColumnName],
580
+ keepNonPropertyFields: true,
482
581
  };
582
+
583
+ if (selectRelationOptions) {
584
+ if (typeof selectRelationOptions !== "boolean") {
585
+ if (selectRelationOptions.properties) {
586
+ findEntityOptions.properties = ["id", ...selectRelationOptions.properties];
587
+ }
588
+ if (selectRelationOptions.relations) {
589
+ findEntityOptions.relations = selectRelationOptions.relations;
590
+ }
591
+ }
592
+ }
593
+
594
+ return await findEntities(server, dataAccessor, findEntityOptions);
595
+ }
596
+
597
+ async function findOneRelatedEntitiesViaIdPropertyCode(options: FindOneRelationEntitiesOptions) {
598
+ const { server, relationProperty, relationEntityIds, selectRelationOptions } = options;
599
+
483
600
  const dataAccessor = server.getDataAccessor({
484
601
  singularCode: relationProperty.targetSingularCode as string,
485
602
  });
486
603
 
487
- return dataAccessor.find(findEntityOptions);
488
- }
489
-
490
- function findOneRelatedEntitiesViaIdPropertyCode(server: IRpdServer, model: RpdDataModel, relationProperty: RpdDataModelProperty, targetEntityIds: any[]) {
491
- const findEntityOptions: FindRowOptions = {
604
+ const findEntityOptions: FindEntityOptions = {
492
605
  filters: [
493
606
  {
494
- field: {
495
- name: "id",
496
- },
607
+ field: "id",
497
608
  operator: "in",
498
- value: targetEntityIds,
609
+ value: relationEntityIds,
499
610
  },
500
611
  ],
612
+ keepNonPropertyFields: true,
501
613
  };
502
- const dataAccessor = server.getDataAccessor({
503
- singularCode: relationProperty.targetSingularCode as string,
504
- });
505
614
 
506
- return dataAccessor.find(findEntityOptions);
615
+ if (selectRelationOptions) {
616
+ if (typeof selectRelationOptions !== "boolean") {
617
+ if (selectRelationOptions.properties) {
618
+ findEntityOptions.properties = ["id", ...selectRelationOptions.properties];
619
+ }
620
+ if (selectRelationOptions.relations) {
621
+ findEntityOptions.relations = selectRelationOptions.relations;
622
+ }
623
+ }
624
+ }
625
+
626
+ return await findEntities(server, dataAccessor, findEntityOptions);
507
627
  }
508
628
 
509
629
  async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor, options: CreateEntityOptions, plugin?: RapidPlugin) {
@@ -514,7 +634,7 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
514
634
 
515
635
  const { entity, routeContext } = options;
516
636
 
517
- const userId = options.routeContext.state?.userId;
637
+ const userId = options.routeContext?.state?.userId;
518
638
  if (userId) {
519
639
  const createdByProperty = getEntityPropertyByCode(server, model, "createdBy");
520
640
  if (createdByProperty) {
@@ -583,7 +703,9 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
583
703
  routeContext,
584
704
  });
585
705
  if (!targetEntity) {
586
- throw newEntityOperationError(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
706
+ throw newEntityOperationError(
707
+ `Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`,
708
+ );
587
709
  }
588
710
  newEntityOneRelationProps[property.code] = targetEntity;
589
711
  targetRow[property.targetIdColumnName!] = targetEntityId;
@@ -596,7 +718,9 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
596
718
  routeContext,
597
719
  });
598
720
  if (!targetEntity) {
599
- throw newEntityOperationError(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
721
+ throw newEntityOperationError(
722
+ `Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`,
723
+ );
600
724
  }
601
725
  newEntityOneRelationProps[property.code] = targetEntity;
602
726
  targetRow[property.targetIdColumnName!] = targetEntityId;
@@ -643,7 +767,10 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
643
767
  });
644
768
 
645
769
  if (property.linkTableName) {
646
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
770
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
771
+ schema: property.linkSchema,
772
+ tableName: property.linkTableName,
773
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
647
774
  const params = [newEntity.id, newTargetEntity.id];
648
775
  await server.queryDatabaseObject(command, params);
649
776
  }
@@ -657,7 +784,10 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
657
784
  }
658
785
 
659
786
  if (property.linkTableName) {
660
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
787
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
788
+ schema: property.linkSchema,
789
+ tableName: property.linkTableName,
790
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
661
791
  const params = [newEntity.id, relatedEntityId];
662
792
  await server.queryDatabaseObject(command, params);
663
793
  } else {
@@ -675,7 +805,10 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
675
805
  }
676
806
 
677
807
  if (property.linkTableName) {
678
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
808
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
809
+ schema: property.linkSchema,
810
+ tableName: property.linkTableName,
811
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
679
812
  const params = [newEntity.id, relatedEntityId];
680
813
  await server.queryDatabaseObject(command, params);
681
814
  } else {
@@ -726,7 +859,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
726
859
 
727
860
  entityToSave = changes || {};
728
861
 
729
- const userId = options.routeContext.state?.userId;
862
+ const userId = options.routeContext?.state?.userId;
730
863
  if (userId) {
731
864
  const updatedByProperty = getEntityPropertyByCode(server, model, "updatedBy");
732
865
  if (updatedByProperty) {
@@ -799,7 +932,9 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
799
932
  routeContext,
800
933
  });
801
934
  if (!targetEntity) {
802
- throw newEntityOperationError(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
935
+ throw newEntityOperationError(
936
+ `Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`,
937
+ );
803
938
  }
804
939
  updatedEntityOneRelationProps[property.code] = targetEntity;
805
940
  targetRow[property.targetIdColumnName!] = targetEntityId;
@@ -812,7 +947,9 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
812
947
  routeContext,
813
948
  });
814
949
  if (!targetEntity) {
815
- throw newEntityOperationError(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
950
+ throw newEntityOperationError(
951
+ `Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`,
952
+ );
816
953
  }
817
954
  updatedEntityOneRelationProps[property.code] = targetEntity;
818
955
  targetRow[property.targetIdColumnName!] = targetEntityId;
@@ -848,7 +985,13 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
848
985
 
849
986
  if (property.linkTableName) {
850
987
  // TODO: should support removing relation
851
- await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName!)} = $1`, [id]);
988
+ await server.queryDatabaseObject(
989
+ `DELETE FROM ${server.queryBuilder.quoteTable({
990
+ schema: property.linkSchema,
991
+ tableName: property.linkTableName,
992
+ })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName!)} = $1`,
993
+ [id],
994
+ );
852
995
  }
853
996
 
854
997
  for (const relatedEntityToBeSaved of relatedEntitiesToBeSaved) {
@@ -866,7 +1009,10 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
866
1009
  });
867
1010
 
868
1011
  if (property.linkTableName) {
869
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
1012
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
1013
+ schema: property.linkSchema,
1014
+ tableName: property.linkTableName,
1015
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
870
1016
  const params = [id, newTargetEntity.id];
871
1017
  await server.queryDatabaseObject(command, params);
872
1018
  }
@@ -880,7 +1026,10 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
880
1026
  }
881
1027
 
882
1028
  if (property.linkTableName) {
883
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
1029
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
1030
+ schema: property.linkSchema,
1031
+ tableName: property.linkTableName,
1032
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
884
1033
  const params = [id, relatedEntityId];
885
1034
  await server.queryDatabaseObject(command, params);
886
1035
  } else {
@@ -898,7 +1047,10 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
898
1047
  }
899
1048
 
900
1049
  if (property.linkTableName) {
901
- const command = `INSERT INTO ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
1050
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({
1051
+ schema: property.linkSchema,
1052
+ tableName: property.linkTableName,
1053
+ })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
902
1054
  const params = [id, relatedEntityId];
903
1055
  await server.queryDatabaseObject(command, params);
904
1056
  } else {
@@ -1065,7 +1217,10 @@ export default class EntityManager<TEntity = any> {
1065
1217
  const { queryBuilder } = server;
1066
1218
  if (relationProperty.linkTableName) {
1067
1219
  for (const relation of relations) {
1068
- const command = `INSERT INTO ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName!)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName!)})
1220
+ const command = `INSERT INTO ${queryBuilder.quoteTable({
1221
+ schema: relationProperty.linkSchema,
1222
+ tableName: relationProperty.linkTableName,
1223
+ })} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName!)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName!)})
1069
1224
  SELECT $1, $2 WHERE NOT EXISTS (
1070
1225
  SELECT ${queryBuilder.quoteObject(relationProperty.selfIdColumnName!)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName!)}
1071
1226
  FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
@@ -1,11 +1,5 @@
1
1
  import { find } from "lodash";
2
- import {
3
- RpdDataModel,
4
- RpdDataModelProperty,
5
- CreateEntityOptions,
6
- QuoteTableOptions,
7
- DatabaseQuery,
8
- } from "../types";
2
+ import { RpdDataModel, RpdDataModelProperty, CreateEntityOptions, QuoteTableOptions, DatabaseQuery } from "../types";
9
3
  import {
10
4
  CountRowOptions,
11
5
  DeleteRowOptions,
@@ -17,7 +11,7 @@ import {
17
11
  RowFilterOptions,
18
12
  RowFilterRelationalOperators,
19
13
  UpdateRowOptions,
20
- ColumnQueryOptions,
14
+ ColumnSelectOptions,
21
15
  } from "~/dataAccess/dataAccessTypes";
22
16
 
23
17
  const objLeftQuoteChar = '"';
@@ -64,7 +58,7 @@ export default class QueryBuilder {
64
58
  return `${objLeftQuoteChar}${name}${objRightQuoteChar}`;
65
59
  }
66
60
 
67
- quoteColumn(column: ColumnQueryOptions, emitTableAlias: boolean) {
61
+ quoteColumn(column: ColumnSelectOptions, emitTableAlias: boolean) {
68
62
  if (typeof column === "string") {
69
63
  return `${objLeftQuoteChar}${column}${objRightQuoteChar}`;
70
64
  } else if (emitTableAlias && column.tableName) {
@@ -100,7 +94,7 @@ export default class QueryBuilder {
100
94
  command += " ORDER BY ";
101
95
  command += orderBy
102
96
  .map((item) => {
103
- const quotedName = this.quoteColumn(item.field, ctx.emitTableAlias);
97
+ const quotedName = this.quoteColumn(item.field, ctx.emitTableAlias);
104
98
  return item.desc ? quotedName + " DESC" : quotedName;
105
99
  })
106
100
  .join(", ");
@@ -133,13 +127,17 @@ export default class QueryBuilder {
133
127
  if (!columns || !columns.length) {
134
128
  command += `${this.quoteObject(derivedModel.tableName)}.* FROM `;
135
129
  } else {
136
- command += columns.map((column) => {
137
- return this.quoteColumn(column, ctx.emitTableAlias);
138
- }).join(", ");
130
+ command += columns
131
+ .map((column) => {
132
+ return this.quoteColumn(column, ctx.emitTableAlias);
133
+ })
134
+ .join(", ");
139
135
  command += " FROM ";
140
136
  }
141
137
 
142
- command += `${this.quoteTable(derivedModel)} LEFT JOIN ${this.quoteTable(baseModel)} ON ${this.quoteObject(derivedModel.tableName)}.id = ${this.quoteObject(baseModel.tableName)}.id`;
138
+ command += `${this.quoteTable(derivedModel)} LEFT JOIN ${this.quoteTable(baseModel)} ON ${this.quoteObject(derivedModel.tableName)}.id = ${this.quoteObject(
139
+ baseModel.tableName,
140
+ )}.id`;
143
141
 
144
142
  if (filters && filters.length) {
145
143
  command += " WHERE ";
@@ -203,7 +201,9 @@ export default class QueryBuilder {
203
201
  let { filters } = options;
204
202
  let command = 'SELECT COUNT(*)::int as "count" FROM ';
205
203
 
206
- command += `${this.quoteTable(derivedModel)} LEFT JOIN ${this.quoteTable(baseModel)} ON ${this.quoteObject(derivedModel.tableName)}.id = ${this.quoteObject(baseModel.tableName)}.id`;
204
+ command += `${this.quoteTable(derivedModel)} LEFT JOIN ${this.quoteTable(baseModel)} ON ${this.quoteObject(derivedModel.tableName)}.id = ${this.quoteObject(
205
+ baseModel.tableName,
206
+ )}.id`;
207
207
 
208
208
  if (filters && filters.length) {
209
209
  command += " WHERE ";
package/src/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { RouteContext } from "./core/routeContext";
2
2
  import { IRpdServer, RapidPlugin } from "./core/server";
3
- import { CountRowOptions, FindRowOptions } from "./dataAccess/dataAccessTypes";
3
+ import { ColumnSelectOptions, CountRowOptions, FindRowOptions } from "./dataAccess/dataAccessTypes";
4
4
 
5
5
  export type RapidServerConfig = {
6
6
  baseUrl?: string;
@@ -296,7 +296,21 @@ export interface RpdDataModelProperty {
296
296
  linkSchema?: string;
297
297
  }
298
298
 
299
- export type RpdDataPropertyTypes = "integer" | "long" | "float" | "double" | "decimal" | "text" | "boolean" | "date" | "time" | "datetime" | "json" | "relation" | "relation[]" | "option";
299
+ export type RpdDataPropertyTypes =
300
+ | "integer"
301
+ | "long"
302
+ | "float"
303
+ | "double"
304
+ | "decimal"
305
+ | "text"
306
+ | "boolean"
307
+ | "date"
308
+ | "time"
309
+ | "datetime"
310
+ | "json"
311
+ | "relation"
312
+ | "relation[]"
313
+ | "option";
300
314
 
301
315
  /**
302
316
  * 数据字典
@@ -388,7 +402,21 @@ export interface IRpdDataAccessor<T = any> {
388
402
  deleteById(id: any): Promise<void>;
389
403
  }
390
404
 
391
- export type EntityFilterRelationalOperators = "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "contains" | "notContains" | "containsCS" | "notContainsCS" | "startsWith" | "notStartsWith" | "endsWith" | "notEndsWith";
405
+ export type EntityFilterRelationalOperators =
406
+ | "eq"
407
+ | "ne"
408
+ | "lt"
409
+ | "lte"
410
+ | "gt"
411
+ | "gte"
412
+ | "contains"
413
+ | "notContains"
414
+ | "containsCS"
415
+ | "notContainsCS"
416
+ | "startsWith"
417
+ | "notStartsWith"
418
+ | "endsWith"
419
+ | "notEndsWith";
392
420
 
393
421
  export type EntityFilterSetOperators = "in" | "notIn";
394
422
 
@@ -398,9 +426,19 @@ export type EntityFilterUnaryOperators = "null" | "notNull";
398
426
 
399
427
  export type EntityFilterExistenceOperators = "exists" | "notExists";
400
428
 
401
- export type EntityFilterOperators = EntityFilterRelationalOperators | EntityFilterSetOperators | EntityFilterLogicalOperators | EntityFilterUnaryOperators | EntityFilterExistenceOperators;
429
+ export type EntityFilterOperators =
430
+ | EntityFilterRelationalOperators
431
+ | EntityFilterSetOperators
432
+ | EntityFilterLogicalOperators
433
+ | EntityFilterUnaryOperators
434
+ | EntityFilterExistenceOperators;
402
435
 
403
- export type EntityFilterOptions = FindEntityRelationalFilterOptions | FindEntitySetFilterOptions | FindEntityLogicalFilterOptions | FindEntityUnaryFilterOptions | FindEntityExistenceFilterOptions;
436
+ export type EntityFilterOptions =
437
+ | FindEntityRelationalFilterOptions
438
+ | FindEntitySetFilterOptions
439
+ | FindEntityLogicalFilterOptions
440
+ | FindEntityUnaryFilterOptions
441
+ | FindEntityExistenceFilterOptions;
404
442
 
405
443
  export type EntityNonRelationPropertyFilterOptions = FindEntityRelationalFilterOptions | FindEntitySetFilterOptions | FindEntityUnaryFilterOptions;
406
444
 
@@ -410,6 +448,8 @@ export interface FindEntityOptions {
410
448
  orderBy?: FindEntityOrderByOptions[];
411
449
  pagination?: FindEntityPaginationOptions;
412
450
  properties?: string[];
451
+ relations?: Record<string, FindEntitySelectRelationOptions>;
452
+ extraColumnsToSelect?: ColumnSelectOptions[];
413
453
  keepNonPropertyFields?: boolean;
414
454
  }
415
455
 
@@ -417,6 +457,7 @@ export interface FindEntityByIdOptions {
417
457
  routeContext?: RouteContext;
418
458
  id: any;
419
459
  properties?: string[];
460
+ relations?: Record<string, FindEntitySelectRelationOptions>;
420
461
  keepNonPropertyFields?: boolean;
421
462
  }
422
463
 
@@ -455,6 +496,13 @@ export interface FindEntityPaginationOptions {
455
496
  withoutTotal?: boolean;
456
497
  }
457
498
 
499
+ export type FindEntitySelectRelationOptions =
500
+ | true
501
+ | {
502
+ properties?: string[];
503
+ relations?: Record<string, FindEntitySelectRelationOptions>;
504
+ };
505
+
458
506
  export interface FindEntityOrderByOptions {
459
507
  field: string;
460
508
  desc?: boolean;
@@ -514,7 +562,13 @@ export interface RemoveEntityRelationsOptions {
514
562
  relations: { id?: number; [k: string]: any }[];
515
563
  }
516
564
 
517
- export type EntityWatcherType = EntityWatcher<"entity.create"> | EntityWatcher<"entity.update"> | EntityWatcher<"entity.delete"> | EntityWatcher<"entity.addRelations"> | EntityWatcher<"entity.removeRelations"> | EntityWatcher<any>;
565
+ export type EntityWatcherType =
566
+ | EntityWatcher<"entity.create">
567
+ | EntityWatcher<"entity.update">
568
+ | EntityWatcher<"entity.delete">
569
+ | EntityWatcher<"entity.addRelations">
570
+ | EntityWatcher<"entity.removeRelations">
571
+ | EntityWatcher<any>;
518
572
 
519
573
  export interface EntityWatcher<TEventName extends keyof RpdServerEventTypes = any> {
520
574
  eventName: TEventName;