@solidxai/core 0.1.12 → 0.1.13-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/view-metadata.controller.d.ts +1 -1
- package/dist/decorators/workflow-field-data-provider.decorator.d.ts +3 -0
- package/dist/decorators/workflow-field-data-provider.decorator.d.ts.map +1 -0
- package/dist/decorators/workflow-field-data-provider.decorator.js +11 -0
- package/dist/decorators/workflow-field-data-provider.decorator.js.map +1 -0
- package/dist/helpers/solid-registry.d.ts +5 -1
- package/dist/helpers/solid-registry.d.ts.map +1 -1
- package/dist/helpers/solid-registry.js +17 -0
- package/dist/helpers/solid-registry.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +24 -0
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/repository/security-rule.repository.d.ts +1 -0
- package/dist/repository/security-rule.repository.d.ts.map +1 -1
- package/dist/repository/security-rule.repository.js +28 -7
- package/dist/repository/security-rule.repository.js.map +1 -1
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +1 -1
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/crud-helper.service.d.ts +16 -1
- package/dist/services/crud-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +99 -22
- package/dist/services/crud-helper.service.js.map +1 -1
- package/dist/services/crud.service.d.ts +1 -0
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +15 -0
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/solid-introspect.service.d.ts +1 -0
- package/dist/services/solid-introspect.service.d.ts.map +1 -1
- package/dist/services/solid-introspect.service.js +12 -0
- package/dist/services/solid-introspect.service.js.map +1 -1
- package/dist/services/view-metadata.service.d.ts +5 -1
- package/dist/services/view-metadata.service.d.ts.map +1 -1
- package/dist/services/view-metadata.service.js +63 -14
- package/dist/services/view-metadata.service.js.map +1 -1
- package/package.json +1 -1
- package/src/decorators/workflow-field-data-provider.decorator.ts +7 -0
- package/src/helpers/solid-registry.ts +21 -1
- package/src/index.ts +1 -0
- package/src/interfaces.ts +29 -0
- package/src/repository/security-rule.repository.ts +52 -10
- package/src/services/chatter-message.service.ts +5 -1
- package/src/services/crud-helper.service.ts +220 -22
- package/src/services/crud.service.ts +30 -0
- package/src/services/solid-introspect.service.ts +19 -0
- package/src/services/view-metadata.service.ts +87 -21
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SelectQueryBuilder, WhereExpressionBuilder } from "typeorm";
|
|
1
|
+
import { EntityMetadata, SelectQueryBuilder, WhereExpressionBuilder } from "typeorm";
|
|
2
2
|
import { BasicFilterDto } from "../dtos/basic-filters.dto";
|
|
3
3
|
import { ActiveUserData } from "../interfaces/active-user-data.interface";
|
|
4
4
|
export declare enum FilterCombinator {
|
|
@@ -9,9 +9,23 @@ export declare enum UserIdFields {
|
|
|
9
9
|
CREATED_BY = "createdBy",
|
|
10
10
|
UPDATED_BY = "updatedBy"
|
|
11
11
|
}
|
|
12
|
+
export interface ResolvedFieldPath {
|
|
13
|
+
relationSegments: string[];
|
|
14
|
+
leafProperty?: string;
|
|
15
|
+
leafIsRelation: boolean;
|
|
16
|
+
}
|
|
12
17
|
export declare class CrudHelperService {
|
|
13
18
|
constructor();
|
|
14
19
|
private readonly logger;
|
|
20
|
+
resolveFieldPathFromMetadata(rootMetadata: EntityMetadata, pathParts: string[], { allowRelationLeaf }?: {
|
|
21
|
+
allowRelationLeaf?: boolean;
|
|
22
|
+
}): ResolvedFieldPath;
|
|
23
|
+
resolveFieldPath(qb: SelectQueryBuilder<any>, pathParts: string[], { allowRelationLeaf, startAlias }?: {
|
|
24
|
+
allowRelationLeaf?: boolean;
|
|
25
|
+
startAlias?: string;
|
|
26
|
+
}): ResolvedFieldPath;
|
|
27
|
+
private findAliasMetadata;
|
|
28
|
+
private describeInvalidField;
|
|
15
29
|
private orderOptions;
|
|
16
30
|
applyFilters(qb: WhereExpressionBuilder, filters: any, alias: string, selectQb: SelectQueryBuilder<any>): void;
|
|
17
31
|
private buildOperatorQuery;
|
|
@@ -42,6 +56,7 @@ export declare class CrudHelperService {
|
|
|
42
56
|
countGroups(qb: SelectQueryBuilder<any>): Promise<number>;
|
|
43
57
|
private buildJoinQueryForRelation;
|
|
44
58
|
private wrapFieldWithAlias;
|
|
59
|
+
private resolveOwnColumn;
|
|
45
60
|
isAggregateField(field: string): boolean;
|
|
46
61
|
isAggregateFieldKey(key: string, aggregateAliases: Set<string>): boolean;
|
|
47
62
|
getFieldFromQueryFieldKey(queryFieldKey: string, alias: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud-helper.service.d.ts","sourceRoot":"","sources":["../../src/services/crud-helper.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAC/
|
|
1
|
+
{"version":3,"file":"crud-helper.service.d.ts","sourceRoot":"","sources":["../../src/services/crud-helper.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,8 @@ var UserIdFields;
|
|
|
17
17
|
UserIdFields["CREATED_BY"] = "createdBy";
|
|
18
18
|
UserIdFields["UPDATED_BY"] = "updatedBy";
|
|
19
19
|
})(UserIdFields || (exports.UserIdFields = UserIdFields = {}));
|
|
20
|
+
const SUPPORTED_FIELD_FUNCTIONS = ['COUNT', 'SUM', 'AVG', 'MIN', 'MAX'];
|
|
21
|
+
const SUPPORTED_GRANULARITIES = ['day', 'week', 'month', 'year'];
|
|
20
22
|
class CrudHelperService {
|
|
21
23
|
constructor() {
|
|
22
24
|
this.logger = new common_1.Logger(CrudHelperService.name);
|
|
@@ -61,6 +63,50 @@ class CrudHelperService {
|
|
|
61
63
|
return matchingPermssions.length > 0;
|
|
62
64
|
};
|
|
63
65
|
}
|
|
66
|
+
resolveFieldPathFromMetadata(rootMetadata, pathParts, { allowRelationLeaf = false } = {}) {
|
|
67
|
+
if (!rootMetadata)
|
|
68
|
+
throw new common_1.BadRequestException(`Cannot resolve field '${this.describeInvalidField(pathParts?.join('.'))}'`);
|
|
69
|
+
if (!pathParts?.length || pathParts.some(part => !part)) {
|
|
70
|
+
throw new common_1.BadRequestException(`Invalid field path '${this.describeInvalidField(pathParts?.join('.'))}'`);
|
|
71
|
+
}
|
|
72
|
+
let metadata = rootMetadata;
|
|
73
|
+
const relationSegments = [];
|
|
74
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
75
|
+
const isLeaf = i === pathParts.length - 1;
|
|
76
|
+
const relation = metadata.findRelationWithPropertyPath(pathParts[i]);
|
|
77
|
+
const column = metadata.findColumnWithPropertyName(pathParts[i]);
|
|
78
|
+
if (!isLeaf) {
|
|
79
|
+
if (!relation) {
|
|
80
|
+
throw new common_1.BadRequestException(`Invalid relation '${this.describeInvalidField(pathParts[i])}' in '${this.describeInvalidField(pathParts.join('.'))}'`);
|
|
81
|
+
}
|
|
82
|
+
relationSegments.push(relation.propertyName);
|
|
83
|
+
metadata = relation.inverseEntityMetadata;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (allowRelationLeaf && relation) {
|
|
87
|
+
relationSegments.push(relation.propertyName);
|
|
88
|
+
return { relationSegments, leafIsRelation: true };
|
|
89
|
+
}
|
|
90
|
+
if (column) {
|
|
91
|
+
return { relationSegments, leafProperty: column.propertyName, leafIsRelation: false };
|
|
92
|
+
}
|
|
93
|
+
throw new common_1.BadRequestException(`Invalid field '${this.describeInvalidField(pathParts[i])}' in '${this.describeInvalidField(pathParts.join('.'))}'`);
|
|
94
|
+
}
|
|
95
|
+
throw new common_1.BadRequestException(`Invalid field path '${this.describeInvalidField(pathParts.join('.'))}'`);
|
|
96
|
+
}
|
|
97
|
+
resolveFieldPath(qb, pathParts, { allowRelationLeaf = false, startAlias } = {}) {
|
|
98
|
+
const aliasMetadata = startAlias ? this.findAliasMetadata(qb, startAlias) : undefined;
|
|
99
|
+
const rootMetadata = aliasMetadata ?? qb?.expressionMap?.mainAlias?.metadata;
|
|
100
|
+
return this.resolveFieldPathFromMetadata(rootMetadata, pathParts, { allowRelationLeaf });
|
|
101
|
+
}
|
|
102
|
+
findAliasMetadata(qb, alias) {
|
|
103
|
+
const found = qb?.expressionMap?.aliases?.find(a => a.name === alias);
|
|
104
|
+
return found?.hasMetadata ? found.metadata : undefined;
|
|
105
|
+
}
|
|
106
|
+
describeInvalidField(value) {
|
|
107
|
+
const text = String(value ?? '');
|
|
108
|
+
return text.length > 80 ? `${text.slice(0, 80)}...` : text;
|
|
109
|
+
}
|
|
64
110
|
orderOptions(sort = []) {
|
|
65
111
|
const orderOptions = {};
|
|
66
112
|
sort.forEach((s) => {
|
|
@@ -106,26 +152,34 @@ class CrudHelperService {
|
|
|
106
152
|
const operatorOrField = Object.keys(normalizedPrimaryFilterObj)[0];
|
|
107
153
|
if (operatorOrField.startsWith('$')) {
|
|
108
154
|
const operator = operatorOrField;
|
|
155
|
+
const { leafProperty } = this.resolveFieldPath(selectQb, [rawField], { startAlias: alias });
|
|
109
156
|
let columnExpression;
|
|
110
157
|
if (funcAlias) {
|
|
111
158
|
try {
|
|
112
|
-
columnExpression = this.buildDateGranularityExpression(this.getDriver(selectQb), `${alias}.${
|
|
159
|
+
columnExpression = this.buildDateGranularityExpression(this.getDriver(selectQb), `${alias}.${leafProperty}`, funcAlias);
|
|
113
160
|
}
|
|
114
|
-
catch {
|
|
115
|
-
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (error instanceof common_1.BadRequestException)
|
|
163
|
+
throw error;
|
|
164
|
+
throw new common_1.BadRequestException(`Unsupported field function '${this.describeInvalidField(funcAlias)}'. Supported functions are: ${SUPPORTED_GRANULARITIES.join(', ')}.`);
|
|
116
165
|
}
|
|
117
166
|
}
|
|
118
|
-
this.buildOperatorQuery(qb, alias,
|
|
167
|
+
this.buildOperatorQuery(qb, alias, leafProperty, normalizedPrimaryFilterObj, operator, columnExpression);
|
|
119
168
|
return;
|
|
120
169
|
}
|
|
121
170
|
else {
|
|
122
171
|
if (funcAlias) {
|
|
123
172
|
throw new common_1.BadRequestException(`Function alias ':${funcAlias}' is not valid on relation field '${rawField}'. It can only be applied to scalar fields.`);
|
|
124
173
|
}
|
|
125
|
-
const
|
|
174
|
+
const resolvedRelation = this.resolveFieldPath(selectQb, [rawField], { allowRelationLeaf: true, startAlias: alias });
|
|
175
|
+
if (!resolvedRelation.leafIsRelation) {
|
|
176
|
+
throw new common_1.BadRequestException(`'${this.describeInvalidField(rawField)}' is not a relation and cannot contain nested filters.`);
|
|
177
|
+
}
|
|
178
|
+
const relationName = resolvedRelation.relationSegments[resolvedRelation.relationSegments.length - 1];
|
|
179
|
+
const joinField = `${alias}.${relationName}`;
|
|
126
180
|
if (!this.isRelationJoined(selectQb, joinField))
|
|
127
|
-
selectQb.leftJoin(joinField,
|
|
128
|
-
this.applyFilters(qb, primaryFilterObj,
|
|
181
|
+
selectQb.leftJoin(joinField, relationName);
|
|
182
|
+
this.applyFilters(qb, primaryFilterObj, relationName, selectQb);
|
|
129
183
|
}
|
|
130
184
|
});
|
|
131
185
|
}
|
|
@@ -287,7 +341,7 @@ class CrudHelperService {
|
|
|
287
341
|
}
|
|
288
342
|
if (normalizedFields && normalizedFields.length) {
|
|
289
343
|
qb.select(normalizedFields.map(field => {
|
|
290
|
-
return this.wrapFieldWithAlias(field, entityAlias);
|
|
344
|
+
return this.wrapFieldWithAlias(qb, field, entityAlias);
|
|
291
345
|
}));
|
|
292
346
|
}
|
|
293
347
|
if (applySorting && normalizedSort && normalizedSort.length) {
|
|
@@ -309,7 +363,8 @@ class CrudHelperService {
|
|
|
309
363
|
qb.addSelect(orderColumn);
|
|
310
364
|
}
|
|
311
365
|
else {
|
|
312
|
-
|
|
366
|
+
const { leafProperty } = this.resolveFieldPath(qb, [field]);
|
|
367
|
+
qb.addOrderBy(`${entityAlias}.${leafProperty}`, value);
|
|
313
368
|
}
|
|
314
369
|
});
|
|
315
370
|
if (!hasExplicitIdSort) {
|
|
@@ -322,7 +377,7 @@ class CrudHelperService {
|
|
|
322
377
|
}
|
|
323
378
|
if (showSoftDeleted === 'exclusive') {
|
|
324
379
|
qb.withDeleted();
|
|
325
|
-
qb.
|
|
380
|
+
qb.andWhere(`${entityAlias}.deletedAt IS NOT NULL`);
|
|
326
381
|
}
|
|
327
382
|
if (applyPagination) {
|
|
328
383
|
if (limit)
|
|
@@ -359,13 +414,14 @@ class CrudHelperService {
|
|
|
359
414
|
return existingJoin?.alias?.name;
|
|
360
415
|
}
|
|
361
416
|
ensureRelationPathJoined(qb, rootAlias, pathParts) {
|
|
417
|
+
const resolved = this.resolveFieldPath(qb, pathParts);
|
|
362
418
|
const mainAlias = qb.expressionMap?.mainAlias?.name ||
|
|
363
419
|
qb.expressionMap?.aliases?.find(a => a.metadata)?.name ||
|
|
364
420
|
qb.expressionMap?.aliases?.[0]?.name;
|
|
365
421
|
let parentAlias = mainAlias || rootAlias;
|
|
366
422
|
let leafJoinCreated = false;
|
|
367
|
-
for (let i = 0; i <
|
|
368
|
-
const part =
|
|
423
|
+
for (let i = 0; i < resolved.relationSegments.length; i++) {
|
|
424
|
+
const part = resolved.relationSegments[i];
|
|
369
425
|
const joinProperty = `${parentAlias}.${part}`;
|
|
370
426
|
const existingAlias = this.getExistingJoinAlias(qb, joinProperty);
|
|
371
427
|
const joinAlias = existingAlias ?? this.sanitizeAlias(`${parentAlias}_${part}`);
|
|
@@ -378,12 +434,15 @@ class CrudHelperService {
|
|
|
378
434
|
}
|
|
379
435
|
parentAlias = joinAlias;
|
|
380
436
|
}
|
|
381
|
-
return { alias: parentAlias, property:
|
|
437
|
+
return { alias: parentAlias, property: resolved.leafProperty, created: leafJoinCreated };
|
|
382
438
|
}
|
|
383
439
|
getDriver(qb) {
|
|
384
440
|
return qb.connection.options.type;
|
|
385
441
|
}
|
|
386
442
|
buildDateGranularityExpression(driver, columnExpr, granularity) {
|
|
443
|
+
if (!SUPPORTED_GRANULARITIES.includes(granularity)) {
|
|
444
|
+
throw new common_1.BadRequestException(`Unsupported granularity '${this.describeInvalidField(granularity)}'. Supported granularities are: ${SUPPORTED_GRANULARITIES.join(', ')}.`);
|
|
445
|
+
}
|
|
387
446
|
switch (driver) {
|
|
388
447
|
case 'postgres':
|
|
389
448
|
case 'cockroachdb':
|
|
@@ -494,7 +553,10 @@ class CrudHelperService {
|
|
|
494
553
|
const orderOptions = this.orderOptions(normalizedSort);
|
|
495
554
|
const orderOptionKeys = Object.keys(orderOptions);
|
|
496
555
|
orderOptionKeys.forEach((key) => {
|
|
497
|
-
const resolvedKey = aliasMap[key]
|
|
556
|
+
const resolvedKey = aliasMap[key];
|
|
557
|
+
if (!resolvedKey) {
|
|
558
|
+
throw new common_1.BadRequestException(`Cannot sort by '${this.describeInvalidField(String(key))}' on a grouped query. Sort only by a groupBy or aggregate field.`);
|
|
559
|
+
}
|
|
498
560
|
const value = orderOptions[key];
|
|
499
561
|
qb.addOrderBy(`"${resolvedKey}"`, value);
|
|
500
562
|
});
|
|
@@ -518,11 +580,15 @@ class CrudHelperService {
|
|
|
518
580
|
}
|
|
519
581
|
buildJoinQueryForRelation(qb, entityAlias, relation) {
|
|
520
582
|
const relationParts = relation.split('.');
|
|
583
|
+
const { relationSegments, leafIsRelation } = this.resolveFieldPath(qb, relationParts, { allowRelationLeaf: true });
|
|
584
|
+
if (!leafIsRelation) {
|
|
585
|
+
throw new common_1.BadRequestException(`'${this.describeInvalidField(relation)}' is not a relation and cannot be populated.`);
|
|
586
|
+
}
|
|
521
587
|
let parentAlias = entityAlias;
|
|
522
|
-
|
|
588
|
+
relationSegments.forEach((part, i) => {
|
|
523
589
|
const joinProperty = `${parentAlias}.${part}`;
|
|
524
590
|
if (!this.isRelationJoined(qb, joinProperty)) {
|
|
525
|
-
const joinAlias =
|
|
591
|
+
const joinAlias = this.sanitizeAlias(relationSegments.slice(0, i + 1).join('_'));
|
|
526
592
|
qb.leftJoinAndSelect(joinProperty, joinAlias);
|
|
527
593
|
}
|
|
528
594
|
else {
|
|
@@ -532,13 +598,24 @@ class CrudHelperService {
|
|
|
532
598
|
});
|
|
533
599
|
return qb;
|
|
534
600
|
}
|
|
535
|
-
wrapFieldWithAlias(field, entityAlias) {
|
|
536
|
-
if (!this.isAggregateField(field))
|
|
537
|
-
return `${entityAlias}.${field}`;
|
|
601
|
+
wrapFieldWithAlias(qb, field, entityAlias) {
|
|
602
|
+
if (!this.isAggregateField(field)) {
|
|
603
|
+
return `${entityAlias}.${this.resolveOwnColumn(qb, field, 'fields')}`;
|
|
604
|
+
}
|
|
538
605
|
const fieldParts = field.split('(');
|
|
539
|
-
const aggregateFunction = fieldParts[0];
|
|
540
|
-
|
|
541
|
-
|
|
606
|
+
const aggregateFunction = fieldParts[0].trim().toUpperCase();
|
|
607
|
+
if (!SUPPORTED_FIELD_FUNCTIONS.includes(aggregateFunction)) {
|
|
608
|
+
throw new common_1.BadRequestException(`Unsupported field function '${this.describeInvalidField(fieldParts[0])}'. Supported functions are: ${SUPPORTED_FIELD_FUNCTIONS.join(', ')}.`);
|
|
609
|
+
}
|
|
610
|
+
const fieldName = fieldParts[1].replace(')', '').trim();
|
|
611
|
+
return `${aggregateFunction}(${entityAlias}.${this.resolveOwnColumn(qb, fieldName, 'fields')})`;
|
|
612
|
+
}
|
|
613
|
+
resolveOwnColumn(qb, field, context) {
|
|
614
|
+
const resolved = this.resolveFieldPath(qb, field.split('.'));
|
|
615
|
+
if (resolved.relationSegments.length) {
|
|
616
|
+
throw new common_1.BadRequestException(`Related field '${this.describeInvalidField(field)}' is not supported in '${context}'. Use 'populate' to fetch related records.`);
|
|
617
|
+
}
|
|
618
|
+
return resolved.leafProperty;
|
|
542
619
|
}
|
|
543
620
|
isAggregateField(field) {
|
|
544
621
|
return field.includes('(');
|