@dudousxd/nestjs-filter 1.0.1
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/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/adapter/adapter.d.ts +158 -0
- package/dist/adapter/adapter.d.ts.map +1 -0
- package/dist/adapter/adapter.js +2 -0
- package/dist/adapter/adapter.js.map +1 -0
- package/dist/als-store.d.ts +15 -0
- package/dist/als-store.d.ts.map +1 -0
- package/dist/als-store.js +6 -0
- package/dist/als-store.js.map +1 -0
- package/dist/base-filter.d.ts +46 -0
- package/dist/base-filter.d.ts.map +1 -0
- package/dist/base-filter.js +95 -0
- package/dist/base-filter.js.map +1 -0
- package/dist/cli/generate.d.ts +3 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +43 -0
- package/dist/cli/generate.js.map +1 -0
- package/dist/decorator/apply-filter.decorator.d.ts +11 -0
- package/dist/decorator/apply-filter.decorator.d.ts.map +1 -0
- package/dist/decorator/apply-filter.decorator.js +22 -0
- package/dist/decorator/apply-filter.decorator.js.map +1 -0
- package/dist/decorator/filter-for.decorator.d.ts +4 -0
- package/dist/decorator/filter-for.decorator.d.ts.map +1 -0
- package/dist/decorator/filter-for.decorator.js +29 -0
- package/dist/decorator/filter-for.decorator.js.map +1 -0
- package/dist/decorator/filterable.decorator.d.ts +5 -0
- package/dist/decorator/filterable.decorator.d.ts.map +1 -0
- package/dist/decorator/filterable.decorator.js +24 -0
- package/dist/decorator/filterable.decorator.js.map +1 -0
- package/dist/decorator/relations.decorator.d.ts +29 -0
- package/dist/decorator/relations.decorator.d.ts.map +1 -0
- package/dist/decorator/relations.decorator.js +40 -0
- package/dist/decorator/relations.decorator.js.map +1 -0
- package/dist/errors/exceptions.d.ts +32 -0
- package/dist/errors/exceptions.d.ts.map +1 -0
- package/dist/errors/exceptions.js +56 -0
- package/dist/errors/exceptions.js.map +1 -0
- package/dist/filter/filter-exception.filter.d.ts +6 -0
- package/dist/filter/filter-exception.filter.d.ts.map +1 -0
- package/dist/filter/filter-exception.filter.js +32 -0
- package/dist/filter/filter-exception.filter.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/input/dispatcher.d.ts +2 -0
- package/dist/input/dispatcher.d.ts.map +1 -0
- package/dist/input/dispatcher.js +25 -0
- package/dist/input/dispatcher.js.map +1 -0
- package/dist/input/normalizer.d.ts +9 -0
- package/dist/input/normalizer.d.ts.map +1 -0
- package/dist/input/normalizer.js +51 -0
- package/dist/input/normalizer.js.map +1 -0
- package/dist/input/source-resolver.d.ts +3 -0
- package/dist/input/source-resolver.d.ts.map +1 -0
- package/dist/input/source-resolver.js +49 -0
- package/dist/input/source-resolver.js.map +1 -0
- package/dist/input/validator.d.ts +3 -0
- package/dist/input/validator.d.ts.map +1 -0
- package/dist/input/validator.js +33 -0
- package/dist/input/validator.js.map +1 -0
- package/dist/interceptor/apply-filter.interceptor.d.ts +10 -0
- package/dist/interceptor/apply-filter.interceptor.d.ts.map +1 -0
- package/dist/interceptor/apply-filter.interceptor.js +81 -0
- package/dist/interceptor/apply-filter.interceptor.js.map +1 -0
- package/dist/module.d.ts +15 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +111 -0
- package/dist/module.js.map +1 -0
- package/dist/operators/column-filter.dto.d.ts +22 -0
- package/dist/operators/column-filter.dto.d.ts.map +1 -0
- package/dist/operators/column-filter.dto.js +61 -0
- package/dist/operators/column-filter.dto.js.map +1 -0
- package/dist/operators/index.d.ts +5 -0
- package/dist/operators/index.d.ts.map +1 -0
- package/dist/operators/index.js +4 -0
- package/dist/operators/index.js.map +1 -0
- package/dist/operators/types.d.ts +21 -0
- package/dist/operators/types.d.ts.map +1 -0
- package/dist/operators/types.js +28 -0
- package/dist/operators/types.js.map +1 -0
- package/dist/operators/validate-column-filter.d.ts +24 -0
- package/dist/operators/validate-column-filter.d.ts.map +1 -0
- package/dist/operators/validate-column-filter.js +118 -0
- package/dist/operators/validate-column-filter.js.map +1 -0
- package/dist/runner.d.ts +105 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +554 -0
- package/dist/runner.js.map +1 -0
- package/dist/testing/filter-testing.module.d.ts +7 -0
- package/dist/testing/filter-testing.module.d.ts.map +1 -0
- package/dist/testing/filter-testing.module.js +41 -0
- package/dist/testing/filter-testing.module.js.map +1 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +3 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-query-builder.d.ts +6 -0
- package/dist/testing/mock-query-builder.d.ts.map +1 -0
- package/dist/testing/mock-query-builder.js +22 -0
- package/dist/testing/mock-query-builder.js.map +1 -0
- package/dist/tokens.d.ts +8 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +8 -0
- package/dist/tokens.js.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/escape-like.d.ts +10 -0
- package/dist/utils/escape-like.d.ts.map +1 -0
- package/dist/utils/escape-like.js +15 -0
- package/dist/utils/escape-like.js.map +1 -0
- package/package.json +82 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @dudousxd/nestjs-filter
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`6b22e31`](https://github.com/DavideCarvalho/nestjs-filter/commit/6b22e3153f1f0b275bd579f6fade8facae61b8a7) - Initial release of nestjs-filter.
|
|
8
|
+
|
|
9
|
+
Structured input format with three top-level keys: filter, include, search.
|
|
10
|
+
|
|
11
|
+
Core features:
|
|
12
|
+
|
|
13
|
+
- Declarative filter classes with @FilterFor, @Filterable, @ApplyFilter
|
|
14
|
+
- Auto-fields with entity metadata introspection
|
|
15
|
+
- 22 built-in operators with AND/OR composition
|
|
16
|
+
- Dot-notation relation filtering (posts.title)
|
|
17
|
+
- Eager loading via ?include=role,posts
|
|
18
|
+
- Global search via ?search=term (ILIKE or tsvector)
|
|
19
|
+
- applyDynamic() for querying any entity without a filter class
|
|
20
|
+
- AsyncLocalStorage state isolation
|
|
21
|
+
- class-validator integration
|
|
22
|
+
- FilterTestingModule + makeMockQueryBuilder
|
|
23
|
+
|
|
24
|
+
Adapters: MikroORM 7, TypeORM 0.3+
|
|
25
|
+
Client: Zero-dependency fluent query builder
|
|
26
|
+
|
|
27
|
+
## 1.0.0
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- [`0cd738a`](https://github.com/DavideCarvalho/nestjs-filter/commit/0cd738a41105812bad6bee876d4c707bf815258f) - Initial release. Declarative ORM-agnostic filter classes for NestJS.
|
|
32
|
+
|
|
33
|
+
Core: BaseFilter, FilterRunner, @Filterable, @FilterFor, @ApplyFilter decorators, FilterModule, auto-fields with entity metadata introspection, dot-notation relation filtering, 22 built-in operators with AND/OR composition, bracket notation query string support, class-validator integration, FilterExceptionFilter, FilterTestingModule, makeMockQueryBuilder.
|
|
34
|
+
|
|
35
|
+
Adapters: MikroORM 7 and TypeORM with full operator support, entity metadata introspection, and relation filtering.
|
|
36
|
+
|
|
37
|
+
Client: Zero-dependency fluent query builder for browser and Node.js with type-safe operator validation.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Davi Carvalho
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# @dudousxd/nestjs-filter
|
|
2
|
+
|
|
3
|
+
Core package for nestjs-filter -- declarative, ORM-agnostic filter classes for NestJS.
|
|
4
|
+
|
|
5
|
+
Provides `BaseFilter`, `FilterRunner`, decorators (`@Filterable`, `@FilterFor`, `@ApplyFilter`), `FilterModule`, exception handling, and testing utilities.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @dudousxd/nestjs-filter
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You also need an ORM adapter package:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# MikroORM
|
|
17
|
+
pnpm add @dudousxd/nestjs-filter-mikro-orm
|
|
18
|
+
|
|
19
|
+
# TypeORM
|
|
20
|
+
pnpm add @dudousxd/nestjs-filter-typeorm
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { Injectable } from '@nestjs/common';
|
|
27
|
+
import { Filterable, FilterFor, BaseFilter, FilterModule, ApplyFilter } from '@dudousxd/nestjs-filter';
|
|
28
|
+
|
|
29
|
+
// 1. Define a filter
|
|
30
|
+
@Injectable()
|
|
31
|
+
@Filterable({ entity: User })
|
|
32
|
+
class UserFilter extends BaseFilter<QueryBuilder> {
|
|
33
|
+
@FilterFor('name')
|
|
34
|
+
applyName(value: string) {
|
|
35
|
+
this.$query.andWhere({ name: value });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2. Register
|
|
40
|
+
@Module({
|
|
41
|
+
imports: [
|
|
42
|
+
FilterModule.forRoot({ inputNormalizer: 'camelCase' }),
|
|
43
|
+
FilterModule.forFeature([UserFilter]),
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
class AppModule {}
|
|
47
|
+
|
|
48
|
+
// 3. Use in controller
|
|
49
|
+
@Controller('users')
|
|
50
|
+
class UsersController {
|
|
51
|
+
@Get()
|
|
52
|
+
list(@ApplyFilter(UserFilter) qb: QueryBuilder) {
|
|
53
|
+
return qb.getResultList();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API Reference
|
|
59
|
+
|
|
60
|
+
### Decorators
|
|
61
|
+
|
|
62
|
+
- **`@Filterable({ entity, allowed?, blocked? })`** -- Class decorator. Associates a filter with an entity. `allowed` whitelists keys; `blocked` blacklists them.
|
|
63
|
+
- **`@FilterFor(inputKey?)`** -- Method decorator. Maps an input key to the method. Defaults to the method name if omitted.
|
|
64
|
+
- **`@ApplyFilter(FilterClass, options?)`** -- Parameter decorator. Resolves input from the request, runs the filter, and injects the QueryBuilder. Options: `source` (`'auto'|'query'|'body'|Function`), `dto`, `resolve` (dynamic filter selection).
|
|
65
|
+
|
|
66
|
+
### Classes
|
|
67
|
+
|
|
68
|
+
- **`BaseFilter<TQuery>`** -- Abstract base class. Provides `$query`, `$input`, `$context`, `$adapter` via AsyncLocalStorage. Optional `setup()` hook.
|
|
69
|
+
- **`FilterRunner`** -- Injectable service. `apply(FilterClass, input, qb, context?)` runs a filter programmatically.
|
|
70
|
+
- **`FilterModule`** -- `forRoot(options?)` registers global infrastructure. `forFeature(filters)` registers filter classes.
|
|
71
|
+
|
|
72
|
+
### Exceptions
|
|
73
|
+
|
|
74
|
+
- `FilterException` -- Abstract base.
|
|
75
|
+
- `FilterNotRegisteredException` -- Filter class not in DI container.
|
|
76
|
+
- `FilterMissingEntityException` -- Missing `@Filterable({ entity })`.
|
|
77
|
+
- `FilterStateUnavailableException` -- Accessing `$query` outside `FilterRunner.apply()`.
|
|
78
|
+
- `UnknownFilterKeyException` -- Unknown key when `onUnknownKey: 'throw'`.
|
|
79
|
+
- `FilterValidationException` -- class-validator validation failed.
|
|
80
|
+
- `FilterMethodException` -- A filter method (or `setup()`) threw an error.
|
|
81
|
+
|
|
82
|
+
### Exception Filter
|
|
83
|
+
|
|
84
|
+
- **`FilterExceptionFilter`** -- Catches `FilterValidationException` and returns `{ statusCode: 400, message, errors }`.
|
|
85
|
+
|
|
86
|
+
### Testing (from `@dudousxd/nestjs-filter/testing`)
|
|
87
|
+
|
|
88
|
+
- **`FilterTestingModule`** -- `forRoot(options?)` and `forFeature(filters)`. Defaults to `validation: 'off'`.
|
|
89
|
+
- **`makeMockQueryBuilder<E>()`** -- Proxy-based mock QB that records all calls. Access via `qb.calls`.
|
|
90
|
+
|
|
91
|
+
### Types
|
|
92
|
+
|
|
93
|
+
- **`FilterInput<F>`** -- Extracts the input shape from a filter class.
|
|
94
|
+
- **`FilterContext`** -- `{ req?, user?, raw? }`.
|
|
95
|
+
- **`FilterModuleOptions`** -- `{ inputNormalizer?, dropId?, onUnknownKey?, validation? }`.
|
|
96
|
+
- **`ApplyFilterOptions`** -- `{ source?, dto?, resolve? }`.
|
|
97
|
+
- **`InputSource`** -- `'auto' | 'query' | 'body' | ((req) => Record<string, unknown>)`.
|
|
98
|
+
|
|
99
|
+
## Configuration
|
|
100
|
+
|
|
101
|
+
| Option | Type | Default | Description |
|
|
102
|
+
|--------|------|---------|-------------|
|
|
103
|
+
| `inputNormalizer` | `'camelCase' \| 'snakeCase' \| fn` | `'camelCase'` | Normalize input keys. |
|
|
104
|
+
| `dropId` | `boolean` | `false` | Strip trailing `Id`/`_id`. |
|
|
105
|
+
| `onUnknownKey` | `'ignore' \| 'warn' \| 'throw'` | `'ignore'` | Policy for unrecognized keys. |
|
|
106
|
+
| `validation` | `'auto' \| 'off'` | `'auto'` | Validate with class-validator if installed. |
|
|
107
|
+
|
|
108
|
+
See the [root README](../../README.md) for full documentation.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { Type } from '@nestjs/common';
|
|
2
|
+
import type { ColumnFilter } from '../operators/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Describes a single scalar field on an entity, as reported by the ORM's
|
|
5
|
+
* metadata layer. Used by `autoFields: true` to validate that incoming
|
|
6
|
+
* filter keys correspond to real entity columns.
|
|
7
|
+
*/
|
|
8
|
+
export interface EntityFieldInfo {
|
|
9
|
+
/** Property name on the entity class (e.g. 'name', 'email', 'createdAt'). */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Actual database column name (e.g. 'name', 'email', 'created_at'). */
|
|
12
|
+
columnName: string;
|
|
13
|
+
/** Simplified type classification for the column. */
|
|
14
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'json' | 'unknown';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Describes a relation on an entity, as reported by the ORM's metadata layer.
|
|
18
|
+
* Used by dot-notation relation filtering to auto-join and filter by related
|
|
19
|
+
* entity fields (e.g. `posts.title` joins `posts` and filters by `title`).
|
|
20
|
+
*/
|
|
21
|
+
export interface EntityRelationInfo {
|
|
22
|
+
/** Relation property name on the entity class (e.g. 'posts', 'author'). */
|
|
23
|
+
name: string;
|
|
24
|
+
/** Target entity name (e.g. 'Post', 'User'). */
|
|
25
|
+
targetEntity: string;
|
|
26
|
+
/** Relation cardinality type. */
|
|
27
|
+
type: 'one-to-one' | 'many-to-one' | 'one-to-many' | 'many-to-many';
|
|
28
|
+
}
|
|
29
|
+
export interface FilterAdapter {
|
|
30
|
+
createQueryBuilder<E>(entity: Type<E>): unknown;
|
|
31
|
+
/**
|
|
32
|
+
* Applies a constraint on a relation. The adapter joins (without selecting)
|
|
33
|
+
* the given relation and passes the query builder to the callback, which adds
|
|
34
|
+
* WHERE conditions for the related entity's fields.
|
|
35
|
+
*
|
|
36
|
+
* Implementations should use an inner join (not a left join with select) to
|
|
37
|
+
* avoid eagerly loading relation data and producing duplicate parent rows.
|
|
38
|
+
*
|
|
39
|
+
* Optional — adapters that don't support relation filtering should not implement this.
|
|
40
|
+
*/
|
|
41
|
+
applyRelationConstraint?(qb: unknown, relationName: string, callback: (relationQb: unknown) => Promise<void>): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Applies an array of generic ColumnFilter conditions to a query builder.
|
|
44
|
+
*
|
|
45
|
+
* Each adapter translates the operator-based filters into ORM-specific
|
|
46
|
+
* syntax (MikroORM FilterQuery objects, TypeORM andWhere/Brackets, etc.).
|
|
47
|
+
*
|
|
48
|
+
* Optional — adapters that don't support operator-based filtering should
|
|
49
|
+
* not implement this.
|
|
50
|
+
*
|
|
51
|
+
* @param qb - The query builder instance.
|
|
52
|
+
* @param filters - Array of ColumnFilter conditions to apply.
|
|
53
|
+
*/
|
|
54
|
+
applyColumnFilters?(qb: unknown, filters: ColumnFilter[]): void;
|
|
55
|
+
/**
|
|
56
|
+
* Applies an auto-field value to the query builder.
|
|
57
|
+
*
|
|
58
|
+
* Auto-fields are input keys that have no @FilterFor mapping but are
|
|
59
|
+
* explicitly declared in the filter class via `autoFields`.
|
|
60
|
+
*
|
|
61
|
+
* The adapter handles three value shapes:
|
|
62
|
+
* - Single value → `equals` (andWhere)
|
|
63
|
+
* - Array value → `in` ($in / IN)
|
|
64
|
+
* - Object with operator keys → apply those operators
|
|
65
|
+
*
|
|
66
|
+
* Optional — adapters that don't support auto-fields should not implement this.
|
|
67
|
+
*
|
|
68
|
+
* @param qb - The query builder instance.
|
|
69
|
+
* @param field - The field/column name.
|
|
70
|
+
* @param value - The filter value (scalar, array, or operator object).
|
|
71
|
+
*/
|
|
72
|
+
applyAutoField?(qb: unknown, field: string, value: unknown): void;
|
|
73
|
+
/**
|
|
74
|
+
* Introspects the ORM's metadata for the given entity class and returns
|
|
75
|
+
* an array of scalar (non-relation) field descriptors.
|
|
76
|
+
*
|
|
77
|
+
* Used by `autoFields: true` to restrict accepted input keys to actual
|
|
78
|
+
* entity columns, preventing unknown-column probing and SQL errors.
|
|
79
|
+
*
|
|
80
|
+
* Optional — when absent or when it returns `null`, `autoFields: true`
|
|
81
|
+
* falls back to accepting any key (legacy behavior) with a logged warning.
|
|
82
|
+
*
|
|
83
|
+
* @param entity - The entity class to introspect.
|
|
84
|
+
* @returns Array of field descriptors, or `null` if metadata is unavailable.
|
|
85
|
+
*/
|
|
86
|
+
getEntityFields?(entity: Type<unknown>): EntityFieldInfo[] | null;
|
|
87
|
+
/**
|
|
88
|
+
* Introspects the ORM's metadata for the given entity class and returns
|
|
89
|
+
* an array of relation descriptors.
|
|
90
|
+
*
|
|
91
|
+
* Used by dot-notation relation filtering (`posts.title`) to discover
|
|
92
|
+
* which input key prefixes correspond to real entity relations.
|
|
93
|
+
*
|
|
94
|
+
* Optional — when absent or when it returns `null`, dot-notation
|
|
95
|
+
* relation filtering is disabled.
|
|
96
|
+
*
|
|
97
|
+
* @param entity - The entity class to introspect.
|
|
98
|
+
* @returns Array of relation descriptors, or `null` if metadata is unavailable.
|
|
99
|
+
*/
|
|
100
|
+
getEntityRelations?(entity: Type<unknown>): EntityRelationInfo[] | null;
|
|
101
|
+
/**
|
|
102
|
+
* Applies a dot-notation relation field filter to the query builder.
|
|
103
|
+
*
|
|
104
|
+
* Auto-joins the relation (if not already joined) and applies a WHERE
|
|
105
|
+
* condition on the related entity's field.
|
|
106
|
+
*
|
|
107
|
+
* The value follows the same shapes as `applyAutoField`:
|
|
108
|
+
* - Single value → equals
|
|
109
|
+
* - Array value → IN
|
|
110
|
+
* - Object with operator keys → apply those operators
|
|
111
|
+
*
|
|
112
|
+
* Optional — adapters that don't support dot-notation relation filtering
|
|
113
|
+
* should not implement this.
|
|
114
|
+
*
|
|
115
|
+
* @param qb - The query builder instance.
|
|
116
|
+
* @param relationName - The relation property name (e.g. 'posts').
|
|
117
|
+
* @param field - The field name on the related entity (e.g. 'title').
|
|
118
|
+
* @param value - The filter value (scalar, array, or operator object).
|
|
119
|
+
*/
|
|
120
|
+
applyAutoRelationField?(qb: unknown, relationName: string, field: string, value: unknown): void;
|
|
121
|
+
/**
|
|
122
|
+
* Applies eager loading for the given relation paths.
|
|
123
|
+
*
|
|
124
|
+
* Each include path is a dot-separated relation name (e.g. 'posts', 'posts.comments').
|
|
125
|
+
* The adapter should left-join-and-select these relations on the query builder.
|
|
126
|
+
*
|
|
127
|
+
* Optional — adapters that don't support includes should not implement this.
|
|
128
|
+
*
|
|
129
|
+
* @param qb - The query builder instance.
|
|
130
|
+
* @param includes - Array of relation paths to eagerly load.
|
|
131
|
+
* @param entity - The root entity class.
|
|
132
|
+
*/
|
|
133
|
+
applyIncludes?(qb: unknown, includes: string[], entity: Type<unknown>): void;
|
|
134
|
+
/**
|
|
135
|
+
* Applies a global ILIKE search across the given columns.
|
|
136
|
+
*
|
|
137
|
+
* Generates an OR condition: `col1 ILIKE '%term%' OR col2 ILIKE '%term%' OR ...`
|
|
138
|
+
*
|
|
139
|
+
* Optional — adapters that don't support search should not implement this.
|
|
140
|
+
*
|
|
141
|
+
* @param qb - The query builder instance.
|
|
142
|
+
* @param term - The search term (already trimmed).
|
|
143
|
+
* @param columns - Column names to search across.
|
|
144
|
+
* @param entity - The root entity class.
|
|
145
|
+
*/
|
|
146
|
+
applySearch?(qb: unknown, term: string, columns: string[], entity: Type<unknown>): void;
|
|
147
|
+
/**
|
|
148
|
+
* Applies a full-text vector search using a tsvector column.
|
|
149
|
+
*
|
|
150
|
+
* Optional — adapters that don't support vector search should not implement this.
|
|
151
|
+
*
|
|
152
|
+
* @param qb - The query builder instance.
|
|
153
|
+
* @param term - The search term.
|
|
154
|
+
* @param vectorColumn - The tsvector column name.
|
|
155
|
+
*/
|
|
156
|
+
applyVectorSearch?(qb: unknown, term: string, vectorColumn: string): void;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACrE;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,IAAI,EAAE,YAAY,GAAG,aAAa,GAAG,aAAa,GAAG,cAAc,CAAC;CACrE;AAED,MAAM,WAAW,aAAa;IAC5B,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;IAEhD;;;;;;;;;OASG;IACH,uBAAuB,CAAC,CACtB,EAAE,EAAE,OAAO,EACX,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/C,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;;;;;;;;;OAWG;IACH,kBAAkB,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAEhE;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAElE;;;;;;;;;;;;OAYG;IACH,eAAe,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,eAAe,EAAE,GAAG,IAAI,CAAC;IAElE;;;;;;;;;;;;OAYG;IACH,kBAAkB,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,kBAAkB,EAAE,GAAG,IAAI,CAAC;IAExE;;;;;;;;;;;;;;;;;;OAkBG;IACH,sBAAsB,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAEhG;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAE7E;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAExF;;;;;;;;OAQG;IACH,iBAAiB,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/adapter/adapter.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import type { FilterAdapter } from './adapter/adapter.js';
|
|
3
|
+
import type { FilterContext } from './types.js';
|
|
4
|
+
export interface FilterState {
|
|
5
|
+
$query: unknown;
|
|
6
|
+
$input: Readonly<Record<string, unknown>>;
|
|
7
|
+
$context: FilterContext;
|
|
8
|
+
$adapter: FilterAdapter | null;
|
|
9
|
+
$whitelisted: Set<string>;
|
|
10
|
+
$blacklisted: Set<string>;
|
|
11
|
+
$pushed: Array<[string, unknown]>;
|
|
12
|
+
}
|
|
13
|
+
export declare const filterAls: AsyncLocalStorage<FilterState>;
|
|
14
|
+
export declare function runWithFilterState<T>(state: FilterState, fn: () => T): T;
|
|
15
|
+
//# sourceMappingURL=als-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"als-store.d.ts","sourceRoot":"","sources":["../src/als-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1C,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACnC;AAED,eAAO,MAAM,SAAS,gCAAuC,CAAC;AAE9D,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAExE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"als-store.js","sourceRoot":"","sources":["../src/als-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAcrD,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,iBAAiB,EAAe,CAAC;AAE9D,MAAM,UAAU,kBAAkB,CAAI,KAAkB,EAAE,EAAW;IACnE,OAAO,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { FilterAdapter } from './adapter/adapter.js';
|
|
2
|
+
import type { FilterContext } from './types.js';
|
|
3
|
+
export declare abstract class BaseFilter<TQuery = unknown> {
|
|
4
|
+
get $query(): TQuery;
|
|
5
|
+
get $input(): Readonly<Record<string, unknown>>;
|
|
6
|
+
get $context(): FilterContext;
|
|
7
|
+
get $adapter(): FilterAdapter | null;
|
|
8
|
+
/**
|
|
9
|
+
* Dynamically whitelist a filter key at runtime (typically called in setup()).
|
|
10
|
+
* Whitelisted keys bypass static allowed/blocked checks in the dispatcher.
|
|
11
|
+
*/
|
|
12
|
+
protected whitelistMethod(key: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Dynamically blacklist a filter key at runtime (typically called in setup()).
|
|
15
|
+
* Blacklisted keys are skipped during dispatch regardless of static configuration.
|
|
16
|
+
*/
|
|
17
|
+
protected blacklistMethod(key: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the full input object, a single input value by key,
|
|
20
|
+
* or a default value if the key is not present.
|
|
21
|
+
*/
|
|
22
|
+
input(): Readonly<Record<string, unknown>>;
|
|
23
|
+
input(key: string): unknown;
|
|
24
|
+
input(key: string, defaultValue: unknown): unknown;
|
|
25
|
+
/**
|
|
26
|
+
* Injects additional key/value pairs into the filter input queue.
|
|
27
|
+
* Pushed entries are dispatched after the current dispatch loop completes.
|
|
28
|
+
*/
|
|
29
|
+
protected push(key: string, value: unknown): void;
|
|
30
|
+
protected push(input: Record<string, unknown>): void;
|
|
31
|
+
/**
|
|
32
|
+
* Imperatively constrain a relation. Delegates to the adapter's
|
|
33
|
+
* `applyRelationConstraint` with the given conditions applied via callback.
|
|
34
|
+
*
|
|
35
|
+
* @param relationName - The relation property name on the entity (e.g. 'posts').
|
|
36
|
+
* @param conditions - A record of column/value pairs to match on the related entity.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* await this.related('posts', { status: 'published' });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
protected related(relationName: string, conditions: Record<string, unknown>): Promise<void>;
|
|
44
|
+
setup?(): void | Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=base-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-filter.d.ts","sourceRoot":"","sources":["../src/base-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,8BAAsB,UAAU,CAAC,MAAM,GAAG,OAAO;IAC/C,IAAI,MAAM,IAAI,MAAM,CAInB;IAED,IAAI,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAI9C;IAED,IAAI,QAAQ,IAAI,aAAa,CAI5B;IAED,IAAI,QAAQ,IAAI,aAAa,GAAG,IAAI,CAInC;IAED;;;OAGG;IACH,SAAS,CAAC,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAM5C;;;OAGG;IACH,SAAS,CAAC,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAM5C;;;OAGG;IACH,KAAK,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAC3B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO;IAOlD;;;OAGG;IACH,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IACjD,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAapD;;;;;;;;;;;OAWG;cACa,OAAO,CACrB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC;IAsBhB,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAC/B"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { filterAls } from './als-store.js';
|
|
2
|
+
import { FilterStateUnavailableException } from './errors/exceptions.js';
|
|
3
|
+
export class BaseFilter {
|
|
4
|
+
get $query() {
|
|
5
|
+
const s = filterAls.getStore();
|
|
6
|
+
if (!s)
|
|
7
|
+
throw new FilterStateUnavailableException();
|
|
8
|
+
return s.$query;
|
|
9
|
+
}
|
|
10
|
+
get $input() {
|
|
11
|
+
const s = filterAls.getStore();
|
|
12
|
+
if (!s)
|
|
13
|
+
throw new FilterStateUnavailableException();
|
|
14
|
+
return s.$input;
|
|
15
|
+
}
|
|
16
|
+
get $context() {
|
|
17
|
+
const s = filterAls.getStore();
|
|
18
|
+
if (!s)
|
|
19
|
+
throw new FilterStateUnavailableException();
|
|
20
|
+
return s.$context;
|
|
21
|
+
}
|
|
22
|
+
get $adapter() {
|
|
23
|
+
const s = filterAls.getStore();
|
|
24
|
+
if (!s)
|
|
25
|
+
throw new FilterStateUnavailableException();
|
|
26
|
+
return s.$adapter;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Dynamically whitelist a filter key at runtime (typically called in setup()).
|
|
30
|
+
* Whitelisted keys bypass static allowed/blocked checks in the dispatcher.
|
|
31
|
+
*/
|
|
32
|
+
whitelistMethod(key) {
|
|
33
|
+
const s = filterAls.getStore();
|
|
34
|
+
if (!s)
|
|
35
|
+
throw new FilterStateUnavailableException();
|
|
36
|
+
s.$whitelisted.add(key);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Dynamically blacklist a filter key at runtime (typically called in setup()).
|
|
40
|
+
* Blacklisted keys are skipped during dispatch regardless of static configuration.
|
|
41
|
+
*/
|
|
42
|
+
blacklistMethod(key) {
|
|
43
|
+
const s = filterAls.getStore();
|
|
44
|
+
if (!s)
|
|
45
|
+
throw new FilterStateUnavailableException();
|
|
46
|
+
s.$blacklisted.add(key);
|
|
47
|
+
}
|
|
48
|
+
input(key, defaultValue) {
|
|
49
|
+
const inp = this.$input;
|
|
50
|
+
if (key === undefined)
|
|
51
|
+
return inp;
|
|
52
|
+
return key in inp ? inp[key] : defaultValue;
|
|
53
|
+
}
|
|
54
|
+
push(keyOrInput, value) {
|
|
55
|
+
const state = filterAls.getStore();
|
|
56
|
+
if (!state)
|
|
57
|
+
throw new FilterStateUnavailableException();
|
|
58
|
+
if (typeof keyOrInput === 'string') {
|
|
59
|
+
state.$pushed.push([keyOrInput, value]);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
for (const [k, v] of Object.entries(keyOrInput)) {
|
|
63
|
+
state.$pushed.push([k, v]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Imperatively constrain a relation. Delegates to the adapter's
|
|
69
|
+
* `applyRelationConstraint` with the given conditions applied via callback.
|
|
70
|
+
*
|
|
71
|
+
* @param relationName - The relation property name on the entity (e.g. 'posts').
|
|
72
|
+
* @param conditions - A record of column/value pairs to match on the related entity.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* await this.related('posts', { status: 'published' });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
async related(relationName, conditions) {
|
|
80
|
+
const adapter = this.$adapter;
|
|
81
|
+
if (!adapter?.applyRelationConstraint) {
|
|
82
|
+
throw new Error(`Adapter does not support relation constraints. Use $query directly to filter relation "${relationName}".`);
|
|
83
|
+
}
|
|
84
|
+
await adapter.applyRelationConstraint(this.$query, relationName, async (relationQb) => {
|
|
85
|
+
// Apply each condition as an andWhere on the relation query builder.
|
|
86
|
+
// The exact shape depends on the adapter -- TypeORM and MikroORM both
|
|
87
|
+
// support object-based andWhere calls.
|
|
88
|
+
const qb = relationQb;
|
|
89
|
+
for (const [column, value] of Object.entries(conditions)) {
|
|
90
|
+
qb.andWhere({ [column]: value });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=base-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-filter.js","sourceRoot":"","sources":["../src/base-filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/cli/generate.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const command = args[0];
|
|
6
|
+
const name = args[1];
|
|
7
|
+
const ormArg = args.find((a) => a.startsWith('--orm='));
|
|
8
|
+
const orm = ormArg?.split('=')[1] ?? 'mikro-orm';
|
|
9
|
+
if (!command || !name || !['generate', 'g'].includes(command)) {
|
|
10
|
+
console.log('Usage: npx nestjs-filter generate <name> [--orm=mikro-orm|typeorm]');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const pascal = name.charAt(0).toUpperCase() + name.slice(1);
|
|
14
|
+
const filterImport = orm === 'typeorm'
|
|
15
|
+
? "import { TypeOrmFilter } from '@dudousxd/nestjs-filter-typeorm';"
|
|
16
|
+
: "import { MikroOrmFilter } from '@dudousxd/nestjs-filter-mikro-orm';";
|
|
17
|
+
const baseClass = orm === 'typeorm' ? `TypeOrmFilter<${pascal}>` : `MikroOrmFilter<${pascal}>`;
|
|
18
|
+
const template = `import { Injectable } from '@nestjs/common';
|
|
19
|
+
import { Filterable, FilterFor } from '@dudousxd/nestjs-filter';
|
|
20
|
+
${filterImport}
|
|
21
|
+
import { ${pascal} } from './${name}.entity.js';
|
|
22
|
+
|
|
23
|
+
@Injectable()
|
|
24
|
+
@Filterable({ entity: ${pascal} })
|
|
25
|
+
export class ${pascal}Filter extends ${baseClass} {
|
|
26
|
+
// @FilterFor('fieldName')
|
|
27
|
+
// applyField(value: string) {
|
|
28
|
+
// this.$query.andWhere({ field: value });
|
|
29
|
+
// }
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
const outDir = join(process.cwd(), 'src');
|
|
33
|
+
const outPath = join(outDir, `${name}.filter.ts`);
|
|
34
|
+
if (existsSync(outPath)) {
|
|
35
|
+
console.error(`File already exists: ${outPath}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
if (!existsSync(outDir)) {
|
|
39
|
+
mkdirSync(outDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
writeFileSync(outPath, template);
|
|
42
|
+
console.log(`Created ${outPath}`);
|
|
43
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/cli/generate.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AACxB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AACrB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;AACxD,MAAM,GAAG,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC;AAEjD,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;IAC9D,OAAO,CAAC,GAAG,CAAC,oEAAoE,CAAC,CAAC;IAClF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC5D,MAAM,YAAY,GAChB,GAAG,KAAK,SAAS;IACf,CAAC,CAAC,kEAAkE;IACpE,CAAC,CAAC,qEAAqE,CAAC;AAC5E,MAAM,SAAS,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,iBAAiB,MAAM,GAAG,CAAC,CAAC,CAAC,kBAAkB,MAAM,GAAG,CAAC;AAE/F,MAAM,QAAQ,GAAG;;EAEf,YAAY;WACH,MAAM,cAAc,IAAI;;;wBAGX,MAAM;eACf,MAAM,kBAAkB,SAAS;;;;;;CAM/C,CAAC;AAEF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;AAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,CAAC;AAElD,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;IACxB,OAAO,CAAC,KAAK,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;IACxB,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACjC,OAAO,CAAC,GAAG,CAAC,WAAW,OAAO,EAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { type Type } from '@nestjs/common';
|
|
3
|
+
import type { ApplyFilterOptions } from '../types.js';
|
|
4
|
+
export interface ApplyFilterMetadataEntry {
|
|
5
|
+
filterClass: Type<unknown>;
|
|
6
|
+
options: ApplyFilterOptions;
|
|
7
|
+
paramIndex: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function ApplyFilter(filterClass: Type<unknown>, options?: ApplyFilterOptions): ParameterDecorator;
|
|
10
|
+
export declare function getApplyFilterMetadata(controllerCtor: Function, methodName: string | symbol): ApplyFilterMetadataEntry[];
|
|
11
|
+
//# sourceMappingURL=apply-filter.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-filter.decorator.d.ts","sourceRoot":"","sources":["../../src/decorator/apply-filter.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAyB,KAAK,IAAI,EAAwB,MAAM,gBAAgB,CAAC;AAExF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,WAAW,CACzB,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,EAC1B,OAAO,GAAE,kBAAuB,GAC/B,kBAAkB,CAsBpB;AAED,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,QAAQ,EACxB,UAAU,EAAE,MAAM,GAAG,MAAM,GAC1B,wBAAwB,EAAE,CAG5B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { createParamDecorator } from '@nestjs/common';
|
|
3
|
+
import { APPLY_FILTER_METADATA, APPLY_FILTER_REQ_KEY } from '../tokens.js';
|
|
4
|
+
export function ApplyFilter(filterClass, options = {}) {
|
|
5
|
+
return (target, propertyKey, parameterIndex) => {
|
|
6
|
+
const existing = (Reflect.getOwnMetadata(APPLY_FILTER_METADATA, target.constructor, propertyKey) ?? []);
|
|
7
|
+
existing.push({ filterClass, options, paramIndex: parameterIndex });
|
|
8
|
+
Reflect.defineMetadata(APPLY_FILTER_METADATA, existing, target.constructor, propertyKey);
|
|
9
|
+
return createParamDecorator((_data, ctx) => {
|
|
10
|
+
const req = ctx.switchToHttp().getRequest();
|
|
11
|
+
const slot = req[APPLY_FILTER_REQ_KEY];
|
|
12
|
+
if (!slot)
|
|
13
|
+
return undefined;
|
|
14
|
+
return slot[parameterIndex];
|
|
15
|
+
})()(target, propertyKey, parameterIndex);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function getApplyFilterMetadata(controllerCtor, methodName) {
|
|
19
|
+
return (Reflect.getOwnMetadata(APPLY_FILTER_METADATA, controllerCtor, methodName) ??
|
|
20
|
+
[]);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=apply-filter.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-filter.decorator.js","sourceRoot":"","sources":["../../src/decorator/apply-filter.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAoC,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAS3E,MAAM,UAAU,WAAW,CACzB,WAA0B,EAC1B,UAA8B,EAAE;IAEhC,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,EAAE;QAC7C,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,cAAc,CACtC,qBAAqB,EACrB,MAAM,CAAC,WAAW,EAClB,WAAqB,CACtB,IAAI,EAAE,CAA+B,CAAC;QACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC,CAAC;QACpE,OAAO,CAAC,cAAc,CACpB,qBAAqB,EACrB,QAAQ,EACR,MAAM,CAAC,WAAW,EAClB,WAAqB,CACtB,CAAC;QAEF,OAAO,oBAAoB,CAAC,CAAC,KAAc,EAAE,GAAqB,EAAE,EAAE;YACpE,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAA6B,CAAC;YACvE,MAAM,IAAI,GAAG,GAAG,CAAC,oBAAoB,CAA0B,CAAC;YAChE,IAAI,CAAC,IAAI;gBAAE,OAAO,SAAS,CAAC;YAC5B,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC;QAC9B,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;IAC5C,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,cAAwB,EACxB,UAA2B;IAE3B,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,qBAAqB,EAAE,cAAc,EAAE,UAAoB,CAAC;QACzF,EAAE,CAA+B,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-for.decorator.d.ts","sourceRoot":"","sources":["../../src/decorator/filter-for.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAG1B,wBAAgB,SAAS,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,eAAe,CAU5D;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAenE"}
|