@ruiapp/rapid-core 0.1.62 → 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,
@@ -2354,68 +2401,99 @@ async function convertEntityFiltersToRowFilters(server, model, baseModel, filter
2354
2401
  }
2355
2402
  return replacedFilters;
2356
2403
  }
2357
- async function findManyRelationLinksViaLinkTable(server, targetModel, relationProperty, entityIds) {
2404
+ async function findManyRelationLinksViaLinkTable(options) {
2405
+ const { server, relationProperty, mainModel: relationModel, mainEntityIds, selectRelationOptions } = options;
2358
2406
  const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
2359
2407
  schema: relationProperty.linkSchema,
2360
2408
  tableName: relationProperty.linkTableName,
2361
2409
  })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = ANY($1::int[])`;
2362
- const params = [entityIds];
2410
+ const params = [mainEntityIds];
2363
2411
  const links = await server.queryDatabaseObject(command, params);
2364
2412
  const targetEntityIds = links.map((link) => link[relationProperty.targetIdColumnName]);
2413
+ const dataAccessor = server.getDataAccessor({
2414
+ namespace: relationModel.namespace,
2415
+ singularCode: relationModel.singularCode,
2416
+ });
2365
2417
  const findEntityOptions = {
2366
2418
  filters: [
2367
2419
  {
2368
- field: {
2369
- name: "id",
2370
- },
2420
+ field: "id",
2371
2421
  operator: "in",
2372
2422
  value: targetEntityIds,
2373
2423
  },
2374
2424
  ],
2425
+ keepNonPropertyFields: true,
2375
2426
  };
2376
- const dataAccessor = server.getDataAccessor({
2377
- namespace: targetModel.namespace,
2378
- singularCode: targetModel.singularCode,
2379
- });
2380
- 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);
2381
2438
  lodash.forEach(links, (link) => {
2382
2439
  link.targetEntity = lodash.find(targetEntities, (e) => e["id"] == link[relationProperty.targetIdColumnName]);
2383
2440
  });
2384
2441
  return links;
2385
2442
  }
2386
- 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
+ });
2387
2448
  const findEntityOptions = {
2388
2449
  filters: [
2389
2450
  {
2390
- field: {
2391
- name: relationProperty.selfIdColumnName,
2392
- },
2451
+ field: relationProperty.selfIdColumnName,
2393
2452
  operator: "in",
2394
- value: entityIds,
2453
+ value: mainEntityIds,
2395
2454
  },
2396
2455
  ],
2456
+ extraColumnsToSelect: [relationProperty.selfIdColumnName],
2457
+ keepNonPropertyFields: true,
2397
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;
2398
2473
  const dataAccessor = server.getDataAccessor({
2399
2474
  singularCode: relationProperty.targetSingularCode,
2400
2475
  });
2401
- return dataAccessor.find(findEntityOptions);
2402
- }
2403
- function findOneRelatedEntitiesViaIdPropertyCode(server, model, relationProperty, targetEntityIds) {
2404
2476
  const findEntityOptions = {
2405
2477
  filters: [
2406
2478
  {
2407
- field: {
2408
- name: "id",
2409
- },
2479
+ field: "id",
2410
2480
  operator: "in",
2411
- value: targetEntityIds,
2481
+ value: relationEntityIds,
2412
2482
  },
2413
2483
  ],
2484
+ keepNonPropertyFields: true,
2414
2485
  };
2415
- const dataAccessor = server.getDataAccessor({
2416
- singularCode: relationProperty.targetSingularCode,
2417
- });
2418
- 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);
2419
2497
  }
2420
2498
  async function createEntity(server, dataAccessor, options, plugin) {
2421
2499
  const model = dataAccessor.getModel();
@@ -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.62",
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
 
@@ -127,6 +150,30 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
127
150
  });
128
151
  }
129
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
+
130
177
  const rowFilters = await convertEntityFiltersToRowFilters(server, model, baseModel, options.filters);
131
178
  const findRowOptions: FindRowOptions = {
132
179
  filters: rowFilters,
@@ -145,24 +192,36 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
145
192
  const isManyRelation = relationProperty.relation === "many";
146
193
 
147
194
  if (relationProperty.linkTableName) {
148
- const targetModel = server.getModel({ singularCode: relationProperty.targetSingularCode! });
149
- if (!targetModel) {
195
+ const relationModel = server.getModel({ singularCode: relationProperty.targetSingularCode! });
196
+ if (!relationModel) {
150
197
  continue;
151
198
  }
152
199
 
153
200
  if (isManyRelation) {
154
- 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
+ });
155
208
 
156
209
  forEach(rows, (row: any) => {
157
210
  row[relationProperty.code] = filter(relationLinks, (link: any) => {
158
211
  return link[relationProperty.selfIdColumnName!] == row["id"];
159
- }).map((link) => mapDbRowToEntity(server, targetModel, link.targetEntity, options.keepNonPropertyFields));
212
+ }).map((link) => mapDbRowToEntity(server, relationModel, link.targetEntity, options.keepNonPropertyFields));
160
213
  });
161
214
  }
162
215
  } else {
163
216
  let relatedEntities: any[];
164
217
  if (isManyRelation) {
165
- 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
+ });
166
225
  } else {
167
226
  const targetEntityIds = uniq(
168
227
  reject(
@@ -170,7 +229,13 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
170
229
  isNullOrUndefined,
171
230
  ),
172
231
  );
173
- 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
+ });
174
239
  }
175
240
 
176
241
  const targetModel = server.getModel({
@@ -451,30 +516,44 @@ async function convertEntityFiltersToRowFilters(
451
516
  return replacedFilters;
452
517
  }
453
518
 
454
- async function findManyRelationLinksViaLinkTable(server: IRpdServer, targetModel: RpdDataModel, relationProperty: RpdDataModelProperty, entityIds: any[]) {
519
+ async function findManyRelationLinksViaLinkTable(options: FindManyRelationEntitiesOptions) {
520
+ const { server, relationProperty, mainModel: relationModel, mainEntityIds, selectRelationOptions } = options;
455
521
  const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
456
522
  schema: relationProperty.linkSchema,
457
523
  tableName: relationProperty.linkTableName!,
458
524
  })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = ANY($1::int[])`;
459
- const params = [entityIds];
525
+ const params = [mainEntityIds];
460
526
  const links = await server.queryDatabaseObject(command, params);
461
527
  const targetEntityIds = links.map((link) => link[relationProperty.targetIdColumnName!]);
462
- const findEntityOptions: FindRowOptions = {
528
+
529
+ const dataAccessor = server.getDataAccessor({
530
+ namespace: relationModel.namespace,
531
+ singularCode: relationModel.singularCode,
532
+ });
533
+
534
+ const findEntityOptions: FindEntityOptions = {
463
535
  filters: [
464
536
  {
465
- field: {
466
- name: "id",
467
- },
537
+ field: "id",
468
538
  operator: "in",
469
539
  value: targetEntityIds,
470
540
  },
471
541
  ],
542
+ keepNonPropertyFields: true,
472
543
  };
473
- const dataAccessor = server.getDataAccessor({
474
- namespace: targetModel.namespace,
475
- singularCode: targetModel.singularCode,
476
- });
477
- 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);
478
557
 
479
558
  forEach(links, (link: any) => {
480
559
  link.targetEntity = find(targetEntities, (e: any) => e["id"] == link[relationProperty.targetIdColumnName!]);
@@ -483,42 +562,68 @@ async function findManyRelationLinksViaLinkTable(server: IRpdServer, targetModel
483
562
  return links;
484
563
  }
485
564
 
486
- function findManyRelatedEntitiesViaIdPropertyCode(server: IRpdServer, model: RpdDataModel, relationProperty: RpdDataModelProperty, entityIds: any[]) {
487
- 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 = {
488
572
  filters: [
489
573
  {
490
- field: {
491
- name: relationProperty.selfIdColumnName,
492
- },
574
+ field: relationProperty.selfIdColumnName,
493
575
  operator: "in",
494
- value: entityIds,
576
+ value: mainEntityIds,
495
577
  },
496
578
  ],
579
+ extraColumnsToSelect: [relationProperty.selfIdColumnName],
580
+ keepNonPropertyFields: true,
497
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
+
498
600
  const dataAccessor = server.getDataAccessor({
499
601
  singularCode: relationProperty.targetSingularCode as string,
500
602
  });
501
603
 
502
- return dataAccessor.find(findEntityOptions);
503
- }
504
-
505
- function findOneRelatedEntitiesViaIdPropertyCode(server: IRpdServer, model: RpdDataModel, relationProperty: RpdDataModelProperty, targetEntityIds: any[]) {
506
- const findEntityOptions: FindRowOptions = {
604
+ const findEntityOptions: FindEntityOptions = {
507
605
  filters: [
508
606
  {
509
- field: {
510
- name: "id",
511
- },
607
+ field: "id",
512
608
  operator: "in",
513
- value: targetEntityIds,
609
+ value: relationEntityIds,
514
610
  },
515
611
  ],
612
+ keepNonPropertyFields: true,
516
613
  };
517
- const dataAccessor = server.getDataAccessor({
518
- singularCode: relationProperty.targetSingularCode as string,
519
- });
520
614
 
521
- 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);
522
627
  }
523
628
 
524
629
  async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor, options: CreateEntityOptions, plugin?: RapidPlugin) {
@@ -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;