nestjs-paginate 13.0.0 → 13.2.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/README.md +42 -0
- package/lib/__tests__/cat.entity.d.ts +1 -0
- package/lib/__tests__/cat.entity.js +5 -1
- package/lib/__tests__/cat.entity.js.map +1 -1
- package/lib/filter.d.ts +74 -7
- package/lib/filter.js +400 -58
- package/lib/filter.js.map +1 -1
- package/lib/filter.spec.js +120 -0
- package/lib/filter.spec.js.map +1 -1
- package/lib/paginate.d.ts +4 -3
- package/lib/paginate.js +5 -2
- package/lib/paginate.js.map +1 -1
- package/lib/paginate.spec.js +348 -0
- package/lib/paginate.spec.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -558,6 +558,48 @@ Assume `CatEntity` has a one‑to‑many relation `toys: CatToyEntity[]` where `
|
|
|
558
558
|
GET /cats?filter.toys.name=$any:$not:$eq:Squeaky
|
|
559
559
|
```
|
|
560
560
|
|
|
561
|
+
### AND-mode: entity must have ALL of the specified related values
|
|
562
|
+
|
|
563
|
+
Use the `$and` comparator to require that a parent entity has **all** of the specified related values.
|
|
564
|
+
Each `$and` value produces a separate correlated EXISTS subquery, ANDed on the outer query.
|
|
565
|
+
|
|
566
|
+
Enable it in `filterableColumns`:
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
filterableColumns: {
|
|
570
|
+
'toys.name': [FilterOperator.EQ, FilterComparator.AND],
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
- Cat must have **both** a toy named "Ball" **and** a toy named "Mouse":
|
|
575
|
+
|
|
576
|
+
```url
|
|
577
|
+
GET /cats?filter.toys.name=$and:Ball&filter.toys.name=$and:Mouse
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
- Cat must have a toy named "Ball" **and** be friends with a cat named "Garfield" (two independent to-many paths):
|
|
581
|
+
|
|
582
|
+
```url
|
|
583
|
+
GET /cats?filter.toys.name=$and:Ball&filter.friends.name=$and:Garfield
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
**Restrictions and performance notes**:
|
|
587
|
+
|
|
588
|
+
- `$and` may only be used on to-many relationship columns (one-to-many or many-to-many).
|
|
589
|
+
- `$and` values may not be mixed with non-`$and` values on the same sub-column.
|
|
590
|
+
- `$and` may not be combined with `$none` or `$all` quantifiers.
|
|
591
|
+
- Each `$and` value adds one correlated EXISTS subquery. For N values and a relation path of depth D, this produces N × D joins. The default cap is 20 values per sub-column; override with `maxAndValues` in `PaginateConfig`.
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
const config: PaginateConfig<CatEntity> = {
|
|
595
|
+
sortableColumns: ['id'],
|
|
596
|
+
filterableColumns: {
|
|
597
|
+
'toys.name': [FilterOperator.EQ, FilterComparator.AND],
|
|
598
|
+
},
|
|
599
|
+
maxAndValues: 10, // optional, default is 20
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
561
603
|
## Usage with Eager Loading
|
|
562
604
|
|
|
563
605
|
Eager loading should work with TypeORM's eager property out of the box:
|
|
@@ -82,10 +82,14 @@ __decorate([
|
|
|
82
82
|
__metadata("design:type", String)
|
|
83
83
|
], CatEntity.prototype, "deletedAt", void 0);
|
|
84
84
|
__decorate([
|
|
85
|
-
(0, typeorm_1.ManyToMany)(() => CatEntity),
|
|
85
|
+
(0, typeorm_1.ManyToMany)(() => CatEntity, (cat) => cat.friendOf),
|
|
86
86
|
(0, typeorm_1.JoinTable)(),
|
|
87
87
|
__metadata("design:type", Array)
|
|
88
88
|
], CatEntity.prototype, "friends", void 0);
|
|
89
|
+
__decorate([
|
|
90
|
+
(0, typeorm_1.ManyToMany)(() => CatEntity, (cat) => cat.friends),
|
|
91
|
+
__metadata("design:type", Array)
|
|
92
|
+
], CatEntity.prototype, "friendOf", void 0);
|
|
89
93
|
__decorate([
|
|
90
94
|
(0, typeorm_1.Column)({ type: 'decimal', precision: 5, scale: 2, nullable: true }),
|
|
91
95
|
__metadata("design:type", Number)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cat.entity.js","sourceRoot":"","sources":["../../src/__tests__/cat.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAYgB;AAChB,uDAAiD;AACjD,qDAA+C;AAC/C,mDAAuE;AACvE,6CAAwC;AAExC,IAAY,aAIX;AAJD,WAAY,aAAa;IACrB,4BAAW,CAAA;IACX,kCAAiB,CAAA;IACjB,8BAAa,CAAA;AACjB,CAAC,EAJW,aAAa,6BAAb,aAAa,QAIxB;AAGM,IAAM,SAAS,GAAf,MAAM,SAAS;
|
|
1
|
+
{"version":3,"file":"cat.entity.js","sourceRoot":"","sources":["../../src/__tests__/cat.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAYgB;AAChB,uDAAiD;AACjD,qDAA+C;AAC/C,mDAAuE;AACvE,6CAAwC;AAExC,IAAY,aAIX;AAJD,WAAY,aAAa;IACrB,4BAAW,CAAA;IACX,kCAAiB,CAAA;IACjB,8BAAa,CAAA;AACjB,CAAC,EAJW,aAAa,6BAAb,aAAa,QAIxB;AAGM,IAAM,SAAS,GAAf,MAAM,SAAS;IAkDV,SAAS;;QACb,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,CAAA,MAAA,IAAI,CAAC,IAAI,0CAAE,EAAE,CAAA,EAAE,CAAC;YAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QACpB,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA,CAAC,gDAAgD;QAClG,CAAC;IACL,CAAC;CACJ,CAAA;AA3DY,8BAAS;AAElB;IADC,IAAA,gCAAsB,GAAE;;qCACf;AAGV;IADC,IAAA,gBAAM,GAAE;;uCACG;AAGZ;IADC,IAAA,gBAAM,GAAE;;wCACI;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;sCACT;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,yFAAyF;;;gDACvF;AAG5B;IADC,IAAA,gBAAM,EAAC,kCAAkB,CAAC;8BACb,IAAI;+CAAO;AAGzB;IADC,IAAA,gBAAM,EAAC,GAAG,EAAE,CAAC,sBAAS,CAAC;8BAClB,sBAAS;uCAAA;AAKf;IAHC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,6BAAY,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE;QACnD,KAAK,EAAE,IAAI;KACd,CAAC;;uCACkB;AAIpB;IAFC,IAAA,kBAAQ,EAAC,GAAG,EAAE,CAAC,+BAAa,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3E,IAAA,oBAAU,GAAE;8BACP,+BAAa;uCAAA;AAGnB;IADC,IAAA,0BAAgB,EAAC,iCAAiB,CAAC;;4CACnB;AAGjB;IADC,IAAA,0BAAgB,EAAC,kCAAkB,CAAC;;4CACnB;AAIlB;IAFC,IAAA,oBAAU,EAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClD,IAAA,mBAAS,GAAE;;0CACQ;AAGpB;IADC,IAAA,oBAAU,EAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC;;2CAC7B;AAGrB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CACzC;AAKnB;IAHP,IAAA,mBAAS,GAAE;IACZ,yDAAyD;IACzD,mGAAmG;;;;;0CASlG;oBA1DQ,SAAS;IADrB,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;GACX,SAAS,CA2DrB"}
|
package/lib/filter.d.ts
CHANGED
|
@@ -33,6 +33,17 @@ export declare enum FilterComparator {
|
|
|
33
33
|
OR = "$or"
|
|
34
34
|
}
|
|
35
35
|
export declare function isComparator(value: unknown): value is FilterComparator;
|
|
36
|
+
/**
|
|
37
|
+
* Returns true when the raw filter string explicitly carries the `$and` comparator token.
|
|
38
|
+
*
|
|
39
|
+
* This is distinct from the default AND comparator that every token carries implicitly —
|
|
40
|
+
* we only want to enter AND-mode when the user deliberately wrote `$and:` in the filter value.
|
|
41
|
+
* Using `parseFilterToken` (rather than a naive substring split) ensures that `$and` embedded
|
|
42
|
+
* inside a user value (e.g. `$eq:$and`) is not misidentified as the comparator.
|
|
43
|
+
*
|
|
44
|
+
* Must be called after `parseFilterToken` is defined (hoisting applies to function declarations).
|
|
45
|
+
*/
|
|
46
|
+
export declare function hasExplicitAndComparator(raw: string): boolean;
|
|
36
47
|
export declare const OperatorSymbolToFunction: Map<FilterOperator | FilterSuffix, (...args: any[]) => FindOperator<string>>;
|
|
37
48
|
type Filter = {
|
|
38
49
|
quantifier: FilterQuantifier;
|
|
@@ -45,6 +56,27 @@ type ColumnFilters = {
|
|
|
45
56
|
type ColumnJoinMethods = {
|
|
46
57
|
[columnName: string]: JoinMethod;
|
|
47
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Matches TypeORM named parameters (`:name` and `:...name` spread form) while skipping
|
|
61
|
+
* PostgreSQL cast syntax (`::type`).
|
|
62
|
+
*
|
|
63
|
+
* TypeORM parameter names may contain letters, digits, underscores, and dots (the latter
|
|
64
|
+
* for embedded-property paths, e.g. `size.height0`). The pattern captures the full name
|
|
65
|
+
* including any embedded-path dots.
|
|
66
|
+
*
|
|
67
|
+
* Capture groups:
|
|
68
|
+
* 1 — optional `...` spread prefix (present for IN parameters)
|
|
69
|
+
* 2 — parameter name (may contain dots for embedded paths)
|
|
70
|
+
*
|
|
71
|
+
* Examples:
|
|
72
|
+
* `:name` → matches, spread=undefined, name='name'
|
|
73
|
+
* `:...vals` → matches, spread='...', name='vals'
|
|
74
|
+
* `:size.height0` → matches, spread=undefined, name='size.height0'
|
|
75
|
+
* `col::text` → no match (lookbehind rejects `::`)
|
|
76
|
+
* `:param::int` → matches `:param`, skips `::int`
|
|
77
|
+
*/
|
|
78
|
+
/** @internal Exported for testing only. */
|
|
79
|
+
export declare const TYPEORM_PARAM_REGEX: RegExp;
|
|
48
80
|
export interface FilterToken {
|
|
49
81
|
quantifier: FilterQuantifier;
|
|
50
82
|
comparator: FilterComparator;
|
|
@@ -61,7 +93,7 @@ export declare function generatePredicateCondition(qb: SelectQueryBuilder<unknow
|
|
|
61
93
|
export declare function addWhereCondition<T>(qb: SelectQueryBuilder<T>, column: string, filter: ColumnFilters): void;
|
|
62
94
|
export declare function parseFilterToken(raw?: string): FilterToken | null;
|
|
63
95
|
export declare function parseFilter<T>(query: PaginateQuery, filterableColumns?: {
|
|
64
|
-
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier)[] | true;
|
|
96
|
+
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier | FilterComparator)[] | true;
|
|
65
97
|
}, qb?: SelectQueryBuilder<T>): ColumnFilters;
|
|
66
98
|
/**
|
|
67
99
|
* Retrieves the relation path for a given column name within the provided metadata.
|
|
@@ -77,19 +109,54 @@ export declare function parseFilter<T>(query: PaginateQuery, filterableColumns?:
|
|
|
77
109
|
* Throws an error if no matching relation or embedded metadata is found.
|
|
78
110
|
*/
|
|
79
111
|
export declare function getRelationPath(columnName: string, metadata: EntityMetadata | EmbeddedMetadata): [string, RelationMetadata | EmbeddedMetadata][];
|
|
112
|
+
export interface AddFilterOptions {
|
|
113
|
+
/**
|
|
114
|
+
* Maximum number of `$and` values allowed per sub-column in a single to-many filter.
|
|
115
|
+
* Each value produces a separate correlated EXISTS subquery, so large values have a
|
|
116
|
+
* linear performance cost. Defaults to 20.
|
|
117
|
+
*/
|
|
118
|
+
maxAndValues?: number;
|
|
119
|
+
/**
|
|
120
|
+
* When false, skips the validation that rejects `$and` on non-to-many columns.
|
|
121
|
+
* Set to false when calling `addFilter` recursively for EXISTS sub-queries, where the
|
|
122
|
+
* entity metadata is the leaf entity and the to-many check would incorrectly throw.
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
validateAndComparator?: boolean;
|
|
126
|
+
}
|
|
80
127
|
export declare function addFilter<T>(qb: SelectQueryBuilder<T>, query: PaginateQuery, filterableColumns?: {
|
|
81
|
-
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier)[] | true;
|
|
82
|
-
}): ColumnJoinMethods;
|
|
128
|
+
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier | FilterComparator)[] | true;
|
|
129
|
+
}, opts?: AddFilterOptions): ColumnJoinMethods;
|
|
83
130
|
export declare function addDirectFilters<T>(qb: SelectQueryBuilder<T>, filter: ColumnFilters): void;
|
|
131
|
+
/**
|
|
132
|
+
* Adds correlated EXISTS subqueries to `qb` for all to-many relationship filters in `filter`.
|
|
133
|
+
*
|
|
134
|
+
* **AND-mode (`$and` comparator)**
|
|
135
|
+
*
|
|
136
|
+
* When a sub-column filter uses the `$and` comparator (e.g. `filter[toys.name]=$and:Ball`),
|
|
137
|
+
* each distinct `$and` value produces a separate correlated EXISTS subquery, ANDed on the
|
|
138
|
+
* outer query. This is the only correct way to express "entity has ALL of these related values"
|
|
139
|
+
* — a single EXISTS with AND conditions on the same column is always false on a single row.
|
|
140
|
+
*
|
|
141
|
+
* **Performance note**: each `$and` value adds one correlated EXISTS with the full join chain
|
|
142
|
+
* for that relation path. For a relation path of depth D and N `$and` values, this produces
|
|
143
|
+
* N × D joins. The `maxAndValues` option (default 20) caps N to limit query complexity.
|
|
144
|
+
*
|
|
145
|
+
* **Restrictions**:
|
|
146
|
+
* - `$and` may only be used on to-many relationship columns.
|
|
147
|
+
* - `$and` values may not be mixed with non-`$and` values on the same sub-column.
|
|
148
|
+
* - `$and` may not be combined with `$none` or `$all` quantifiers.
|
|
149
|
+
* - `$and` may only be applied to a single sub-column per relation path at a time.
|
|
150
|
+
*/
|
|
84
151
|
export declare function addToManySubFilters<T>(qb: SelectQueryBuilder<T>, filter: ColumnFilters, query: PaginateQuery, filterableColumns?: {
|
|
85
|
-
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier)[] | true;
|
|
86
|
-
}): void;
|
|
152
|
+
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier | FilterComparator)[] | true;
|
|
153
|
+
}, { maxAndValues, validateAndComparator }?: AddFilterOptions): void;
|
|
87
154
|
export declare function createSubFilter(query: PaginateQuery, filterableColumns: {
|
|
88
|
-
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier)[] | true;
|
|
155
|
+
[column: string]: (FilterOperator | FilterSuffix | FilterQuantifier | FilterComparator)[] | true;
|
|
89
156
|
}, column: string): {
|
|
90
157
|
subQuery: PaginateQuery;
|
|
91
158
|
subFilterableColumns: {
|
|
92
|
-
[column: string]: true | (FilterOperator | FilterSuffix | FilterQuantifier)[];
|
|
159
|
+
[column: string]: true | (FilterOperator | FilterSuffix | FilterQuantifier | FilterComparator)[];
|
|
93
160
|
};
|
|
94
161
|
};
|
|
95
162
|
export {};
|