@venizia/ignis-docs 0.2.0 → 0.2.1-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/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +24 -13
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +8 -4
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +247 -153
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +109 -323
- package/content/extensions/components/authentication/api.md +489 -602
- package/content/extensions/components/authentication/errors.md +135 -498
- package/content/extensions/components/authentication/index.md +89 -801
- package/content/extensions/components/authentication/usage.md +231 -955
- package/content/extensions/components/authorization/api.md +991 -644
- package/content/extensions/components/authorization/errors.md +204 -208
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +88 -795
- package/content/extensions/components/authorization/usage.md +219 -528
- package/content/extensions/components/health-check.md +77 -243
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +553 -285
- package/content/extensions/components/mail/errors.md +76 -62
- package/content/extensions/components/mail/index.md +111 -463
- package/content/extensions/components/mail/usage.md +139 -173
- package/content/extensions/components/request-tracker.md +70 -173
- package/content/extensions/components/socket-io/api.md +462 -785
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +69 -372
- package/content/extensions/components/socket-io/usage.md +212 -105
- package/content/extensions/components/static-asset/api.md +461 -141
- package/content/extensions/components/static-asset/errors.md +121 -53
- package/content/extensions/components/static-asset/index.md +84 -606
- package/content/extensions/components/static-asset/usage.md +184 -299
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +311 -404
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +75 -407
- package/content/extensions/components/websocket/usage.md +120 -338
- package/content/extensions/helpers/cron/index.md +52 -160
- package/content/extensions/helpers/crypto/index.md +67 -480
- package/content/extensions/helpers/crypto/reference.md +528 -0
- package/content/extensions/helpers/env/index.md +64 -178
- package/content/extensions/helpers/error/index.md +286 -196
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +76 -550
- package/content/extensions/helpers/inversion/reference.md +530 -0
- package/content/extensions/helpers/kafka/admin.md +23 -3
- package/content/extensions/helpers/kafka/compile-binary.md +83 -52
- package/content/extensions/helpers/kafka/consumer.md +65 -28
- package/content/extensions/helpers/kafka/examples.md +46 -253
- package/content/extensions/helpers/kafka/index.md +55 -617
- package/content/extensions/helpers/kafka/producer.md +156 -22
- package/content/extensions/helpers/kafka/schema-registry.md +59 -79
- package/content/extensions/helpers/logger/hf-logger.md +220 -0
- package/content/extensions/helpers/logger/index.md +78 -552
- package/content/extensions/helpers/logger/pino.md +105 -0
- package/content/extensions/helpers/logger/reference.md +937 -0
- package/content/extensions/helpers/network/api.md +276 -195
- package/content/extensions/helpers/network/index.md +87 -524
- package/content/extensions/helpers/queue/index.md +80 -897
- package/content/extensions/helpers/queue/reference.md +494 -0
- package/content/extensions/helpers/redis/index.md +86 -640
- package/content/extensions/helpers/redis/reference.md +784 -0
- package/content/extensions/helpers/secrets/index.md +136 -0
- package/content/extensions/helpers/socket-io/api.md +325 -204
- package/content/extensions/helpers/socket-io/index.md +79 -429
- package/content/extensions/helpers/storage/api.md +584 -464
- package/content/extensions/helpers/storage/index.md +80 -573
- package/content/extensions/helpers/types/index.md +75 -495
- package/content/extensions/helpers/types/reference.md +689 -0
- package/content/extensions/helpers/uid/index.md +176 -189
- package/content/extensions/helpers/websocket/api.md +366 -214
- package/content/extensions/helpers/websocket/index.md +68 -503
- package/content/extensions/helpers/worker-thread/index.md +62 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +5 -4
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +8 -7
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +64 -22
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +600 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +25 -46
- package/content/references/base/filter-system/application-usage.md +95 -123
- package/content/references/base/filter-system/array-operators.md +33 -43
- package/content/references/base/filter-system/comparison-operators.md +55 -63
- package/content/references/base/filter-system/default-filter.md +144 -350
- package/content/references/base/filter-system/fields-order-pagination.md +116 -148
- package/content/references/base/filter-system/index.md +114 -253
- package/content/references/base/filter-system/json-filtering.md +52 -181
- package/content/references/base/filter-system/list-operators.md +28 -44
- package/content/references/base/filter-system/logical-operators.md +69 -114
- package/content/references/base/filter-system/null-operators.md +41 -98
- package/content/references/base/filter-system/pattern-matching.md +48 -51
- package/content/references/base/filter-system/quick-reference.md +93 -196
- package/content/references/base/filter-system/range-operators.md +22 -38
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +173 -232
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +43 -28
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +81 -1452
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +281 -419
- package/content/references/base/repositories/index.md +84 -644
- package/content/references/base/repositories/mixins.md +25 -21
- package/content/references/base/repositories/relations.md +194 -375
- package/content/references/base/repositories/soft-deletable.md +67 -55
- package/content/references/base/secrets.md +267 -0
- package/content/references/base/services.md +8 -6
- package/content/references/configuration/environment-variables.md +73 -21
- package/content/references/configuration/index.md +51 -31
- package/content/references/index.md +1 -1
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +6 -2
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +81 -61
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +58 -218
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +24 -23
|
@@ -2,27 +2,12 @@
|
|
|
2
2
|
title: Default Filter
|
|
3
3
|
description: Automatically apply filter conditions to all repository queries
|
|
4
4
|
difficulty: intermediate
|
|
5
|
-
lastUpdated: 2026-
|
|
5
|
+
lastUpdated: 2026-07-23
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Default Filter <Badge type="tip" text="v0.0.5+" />
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
> [!NOTE] Added in v0.0.5
|
|
13
|
-
> This feature was introduced in IGNIS v0.0.5 to support soft delete, multi-tenancy, and other automatic filtering patterns.
|
|
14
|
-
|
|
15
|
-
> [!NOTE]
|
|
16
|
-
> Default filters are ideal for:
|
|
17
|
-
> - **Soft Delete**: Automatically exclude deleted records
|
|
18
|
-
> - **Multi-Tenancy**: Isolate data by tenant
|
|
19
|
-
> - **Active Records**: Filter to active/non-expired records
|
|
20
|
-
> - **Query Limits**: Prevent unbounded queries
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Quick Start
|
|
24
|
-
|
|
25
|
-
Configure a default filter in your model:
|
|
10
|
+
A model's `settings.defaultFilter` merges into every `find`/`findOne`/`findById`/`count`/`updateById`/`updateAll`/`deleteById`/`deleteAll` call for that model. It's the standard way to implement soft delete, multi-tenancy, active-record scoping, and query-limit protection without repeating a `where` clause at every call site.
|
|
26
11
|
|
|
27
12
|
```typescript
|
|
28
13
|
import { model, BaseEntity } from '@venizia/ignis';
|
|
@@ -30,444 +15,253 @@ import { userTable } from '@/schemas';
|
|
|
30
15
|
|
|
31
16
|
@model({
|
|
32
17
|
type: 'entity',
|
|
33
|
-
settings: {
|
|
34
|
-
// Applied to all repository queries
|
|
35
|
-
defaultFilter: {
|
|
36
|
-
where: { isDeleted: false },
|
|
37
|
-
limit: 100,
|
|
38
|
-
},
|
|
39
|
-
},
|
|
18
|
+
settings: { defaultFilter: { where: { isDeleted: false }, limit: 100 } },
|
|
40
19
|
})
|
|
41
20
|
export class User extends BaseEntity<typeof User.schema> {
|
|
42
21
|
static override schema = userTable;
|
|
43
22
|
}
|
|
44
23
|
```
|
|
45
24
|
|
|
46
|
-
|
|
25
|
+
## Options
|
|
47
26
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// Actual query executed
|
|
55
|
-
// WHERE isDeleted = false AND status = 'active' LIMIT 100
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
## Configuration
|
|
60
|
-
|
|
61
|
-
### Default Filter Properties
|
|
62
|
-
|
|
63
|
-
All standard filter properties are supported:
|
|
27
|
+
| Option | Type | Default | Meaning |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `settings.defaultFilter` | `TFilter` | none | Filter merged into every query for the model - `where`, `limit`, `offset`, `order`, `fields`, `include` are all valid inside it. |
|
|
30
|
+
| `settings.defaultLimit` | `number` (positive integer) | `DEFAULT_LIMIT` (`10`) | Per-model row cap. Independent of `defaultFilter` - see [Fields, Order & Pagination -> Default limit resolution](./fields-order-pagination#default-limit-resolution). |
|
|
31
|
+
| `options.shouldSkipDefaultFilter` | `boolean` | `false` | Skips the `defaultFilter` merge for one call. Does not drop `defaultLimit`. |
|
|
64
32
|
|
|
65
33
|
```typescript
|
|
66
|
-
|
|
67
|
-
type: 'entity',
|
|
68
|
-
settings: {
|
|
69
|
-
defaultFilter: {
|
|
70
|
-
// WHERE conditions
|
|
71
|
-
where: { isDeleted: false, tenantId: 'tenant-123' },
|
|
34
|
+
import { userRepository } from '@/repositories';
|
|
72
35
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// Default pagination offset
|
|
77
|
-
offset: 0,
|
|
78
|
-
|
|
79
|
-
// Default sort order
|
|
80
|
-
order: ['createdAt DESC'],
|
|
81
|
-
|
|
82
|
-
// Default field selection
|
|
83
|
-
fields: ['id', 'name', 'email', 'createdAt'],
|
|
84
|
-
|
|
85
|
-
// Default relations to include
|
|
86
|
-
include: [{ relation: 'profile' }],
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
})
|
|
90
|
-
export class User extends BaseEntity<typeof User.schema> {}
|
|
36
|
+
await userRepository.find({ filter: { where: { status: 'active' } } });
|
|
37
|
+
// WHERE "isDeleted" = false AND "status" = 'active' LIMIT 100
|
|
91
38
|
```
|
|
92
39
|
|
|
40
|
+
## Merge semantics
|
|
93
41
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
When a user provides a filter, it is merged with the default filter using `FilterBuilder.mergeFilter()`. Non-`where` properties are user-wins; the `where` clause follows a **narrowing** collision law so a default scope can never be widened or dropped.
|
|
97
|
-
|
|
98
|
-
| Property | Merge Strategy |
|
|
99
|
-
|----------|----------------|
|
|
100
|
-
| `where` | **Per-key narrowing** -- non-colliding keys carry over; a colliding key is composed so the default condition always survives (see below) |
|
|
101
|
-
| `limit` | User replaces default (if provided) |
|
|
102
|
-
| `offset`/`skip` | User replaces default (if provided) |
|
|
103
|
-
| `order` | User replaces default (if provided) |
|
|
104
|
-
| `fields` | User replaces default (if provided) |
|
|
105
|
-
| `include` | User replaces default (if provided) |
|
|
106
|
-
|
|
107
|
-
A user value of `undefined` **never** overrides a defined default -- a caller cannot blow away a tenant or soft-delete scope by passing `undefined`.
|
|
42
|
+
`applyDefaultFilter()` merges the model's `defaultFilter` with the caller's filter via `FilterBuilder.mergeFilter()`.
|
|
108
43
|
|
|
109
|
-
|
|
44
|
+
- **`where` narrows per-key.** See the narrowing law below.
|
|
45
|
+
- **Everything else is user-wins-if-provided.** A caller value replaces the default, but a caller value of `undefined` never does. A filter built by spreading an optional object can't silently blow away a tenant scope or a limit.
|
|
110
46
|
|
|
111
|
-
|
|
47
|
+
| Property | Merge strategy |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `where` | Per-key narrowing (below) |
|
|
50
|
+
| `limit`, `offset`/`skip`, `order`, `fields`, `include` | Caller replaces default, if the caller's value is defined |
|
|
112
51
|
|
|
113
|
-
|
|
114
|
-
|---------|------|--------|
|
|
115
|
-
| scalar | scalar | **User wins** (the soft-delete opt-out -- e.g. `isDeleted: false` becomes `isDeleted: true`) |
|
|
116
|
-
| operator | operator | **AND-composed** into an `and: [...]` group (both conditions enforced) |
|
|
117
|
-
| scalar | operator | **AND-composed** |
|
|
118
|
-
| operator | scalar | **AND-composed** |
|
|
52
|
+
### The `where` narrowing law
|
|
119
53
|
|
|
120
|
-
|
|
54
|
+
Keys present on only one side pass through untouched. When the same key appears on both sides, the outcome depends on shape:
|
|
121
55
|
|
|
122
|
-
|
|
123
|
-
|
|
56
|
+
| Default | Caller | Result |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| scalar | scalar | **Caller wins** - the one true override (`isDeleted: false` -> `isDeleted: true` opts an admin out of soft-delete) |
|
|
59
|
+
| operator object | operator object | **AND-composed** into an `and: [...]` group - both conditions apply |
|
|
60
|
+
| scalar | operator object | **AND-composed** |
|
|
61
|
+
| operator object | scalar | **AND-composed** |
|
|
124
62
|
|
|
125
|
-
|
|
63
|
+
- **`and` collisions concatenate.** Both conjunct lists merge into one.
|
|
64
|
+
- **`or` collisions cannot concatenate** - that would union, not narrow - so each side's `or` group becomes its own conjunct instead.
|
|
65
|
+
- **Non-scalar collisions always AND-compose.** Take a default scope - a `createdAt` floor, a tenant `inq`. A caller filter can narrow it, but never widen or drop it.
|
|
66
|
+
- **Only scalar-over-scalar is a true override.** Every other collision shape composes rather than replaces.
|
|
126
67
|
|
|
127
68
|
```typescript
|
|
128
|
-
// Default
|
|
129
|
-
const defaultFilter = {
|
|
130
|
-
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
// User filter: an upper bound on createdAt
|
|
134
|
-
const userFilter = {
|
|
135
|
-
where: { createdAt: { lte: '2024-12-31' } },
|
|
136
|
-
};
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
**Before** (old wholesale-replace law) -- the user key replaced the default's `createdAt`, dropping the floor:
|
|
69
|
+
// Default: a floor on createdAt. Caller: an upper bound on the same key.
|
|
70
|
+
const defaultFilter = { where: { createdAt: { gte: '2024-01-01' } } };
|
|
71
|
+
const userFilter = { where: { createdAt: { lte: '2024-12-31' } } };
|
|
140
72
|
|
|
141
|
-
|
|
142
|
-
// { where: { createdAt: {
|
|
73
|
+
// Both operator objects on the same key -> AND-composed, so the floor survives:
|
|
74
|
+
// { where: { and: [{ createdAt: { gte: '2024-01-01' } }, { createdAt: { lte: '2024-12-31' } }] } }
|
|
143
75
|
```
|
|
144
76
|
|
|
145
|
-
|
|
77
|
+
Non-colliding keys still combine with an implicit AND, exactly like two `where` objects merged by hand:
|
|
146
78
|
|
|
147
79
|
```typescript
|
|
148
|
-
{
|
|
149
|
-
where: {
|
|
150
|
-
|
|
151
|
-
and: [
|
|
152
|
-
{ createdAt: { gte: '2024-01-01' } },
|
|
153
|
-
{ createdAt: { lte: '2024-12-31' } },
|
|
154
|
-
],
|
|
155
|
-
},
|
|
156
|
-
}
|
|
80
|
+
// Default: { where: { isDeleted: false, tenantId: 'tenant-123' } }
|
|
81
|
+
// Caller: { where: { or: [{ status: 'active' }, { priority: 'high' }] } }
|
|
82
|
+
// Result: WHERE "isDeleted" = false AND "tenantId" = 'tenant-123' AND ("status" = 'active' OR "priority" = 'high')
|
|
157
83
|
```
|
|
158
84
|
|
|
159
|
-
|
|
85
|
+
## Bypassing the default filter
|
|
160
86
|
|
|
161
|
-
|
|
87
|
+
Pass `shouldSkipDefaultFilter: true` in `options` to skip the merge entirely. Every repository verb honors it - `find`, `findOne`, `findById`, `count`, `updateById`, `updateAll`, `deleteById`, `deleteAll`:
|
|
162
88
|
|
|
163
89
|
```typescript
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
//
|
|
167
|
-
```
|
|
90
|
+
// Normal - default filter applies
|
|
91
|
+
await repository.find({ filter: { where: { role: 'admin' } } });
|
|
92
|
+
// WHERE "isDeleted" = false AND "role" = 'admin'
|
|
168
93
|
|
|
169
|
-
|
|
94
|
+
// Admin/maintenance path - bypassed
|
|
95
|
+
await repository.find({
|
|
96
|
+
filter: { where: { role: 'admin' } },
|
|
97
|
+
options: { shouldSkipDefaultFilter: true },
|
|
98
|
+
});
|
|
99
|
+
// WHERE "role" = 'admin' (includes soft-deleted rows)
|
|
100
|
+
```
|
|
170
101
|
|
|
171
|
-
|
|
102
|
+
`updateById` and `deleteById` merge the default filter into their `{ id }` condition the same way `updateAll`/`deleteAll` merge it into their `where`. The bypass applies to all four identically:
|
|
172
103
|
|
|
173
104
|
```typescript
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
// User: OR conditions (a distinct key, so it carries through)
|
|
183
|
-
const userFilter = {
|
|
184
|
-
where: {
|
|
185
|
-
or: [{ status: 'active' }, { priority: 'high' }]
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// Result: AND of default + OR from user
|
|
190
|
-
// WHERE isDeleted = false AND tenantId = 'tenant-123'
|
|
191
|
-
// AND (status = 'active' OR priority = 'high')
|
|
105
|
+
// Also merges the default filter into { id: postId } - skip to update a soft-deleted row
|
|
106
|
+
await postRepository.updateById({
|
|
107
|
+
id: postId,
|
|
108
|
+
data: { title: 'Restored' },
|
|
109
|
+
options: { shouldSkipDefaultFilter: true },
|
|
110
|
+
});
|
|
192
111
|
```
|
|
193
112
|
|
|
194
|
-
|
|
195
|
-
## Bypassing Default Filter
|
|
196
|
-
|
|
197
|
-
Use `shouldSkipDefaultFilter: true` to bypass the default filter:
|
|
113
|
+
It composes with a transaction the same way any other option does:
|
|
198
114
|
|
|
199
115
|
```typescript
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
await
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
116
|
+
const tx = await repository.beginTransaction();
|
|
117
|
+
try {
|
|
118
|
+
await repository.updateAll({
|
|
119
|
+
where: { status: 'archived' },
|
|
120
|
+
data: { isDeleted: true },
|
|
121
|
+
options: { transaction: tx, shouldSkipDefaultFilter: true },
|
|
122
|
+
});
|
|
123
|
+
await tx.commit();
|
|
124
|
+
} catch (e) {
|
|
125
|
+
await tx.rollback();
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
212
128
|
```
|
|
213
129
|
|
|
214
|
-
|
|
130
|
+
`updateAll`/`deleteAll` additionally require `force: true` when the resulting `where` is empty - see [Advanced Repository Features -> Empty where protection](../repositories/advanced#empty-where-protection).
|
|
131
|
+
|
|
132
|
+
| Scenario | Why bypass |
|
|
133
|
+
|---|---|
|
|
134
|
+
| Admin dashboard | View records a default scope would otherwise hide |
|
|
135
|
+
| Data recovery | Restore soft-deleted rows |
|
|
136
|
+
| Cross-tenant analytics | Count/aggregate across every tenant |
|
|
137
|
+
| Data migration | Update rows regardless of status |
|
|
215
138
|
|
|
216
|
-
`shouldSkipDefaultFilter`
|
|
139
|
+
`shouldSkipDefaultFilter` lives on the same options interface every repository verb accepts:
|
|
217
140
|
|
|
218
141
|
```typescript
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// Update operations
|
|
226
|
-
await repository.updateById({ id, data, options: { shouldSkipDefaultFilter: true } });
|
|
227
|
-
await repository.updateAll({ where, data, options: { shouldSkipDefaultFilter: true } });
|
|
228
|
-
|
|
229
|
-
// Delete operations
|
|
230
|
-
await repository.deleteById({ id, options: { shouldSkipDefaultFilter: true } });
|
|
231
|
-
await repository.deleteAll({ where, options: { shouldSkipDefaultFilter: true, force: true } });
|
|
232
|
-
```
|
|
142
|
+
interface IExtraOptions extends IWithTransaction {
|
|
143
|
+
shouldSkipDefaultFilter?: boolean;
|
|
144
|
+
log?: TRepositoryLogOptions;
|
|
145
|
+
lock?: TLockOptions;
|
|
146
|
+
}
|
|
233
147
|
|
|
234
|
-
|
|
148
|
+
interface IWithTransaction {
|
|
149
|
+
transaction?: ITransaction;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
235
152
|
|
|
236
|
-
|
|
237
|
-
|----------|---------|
|
|
238
|
-
| Admin dashboard | View all records including deleted |
|
|
239
|
-
| Data recovery | Restore soft-deleted records |
|
|
240
|
-
| Analytics | Count across all tenants |
|
|
241
|
-
| Data migration | Update records regardless of status |
|
|
242
|
-
| Audit logs | Access historical data |
|
|
153
|
+
`log` and `lock` are documented in [Advanced Repository Features](../repositories/advanced.md) - `log` only takes effect on write verbs (`create`/`updateById`/`updateAll`/`deleteById`/`deleteAll`), not on reads.
|
|
243
154
|
|
|
155
|
+
## Configuring a default filter
|
|
244
156
|
|
|
245
|
-
|
|
157
|
+
Any `TFilter` property is valid inside `defaultFilter` - `where`, `limit`, `offset`, `order`, `fields`, `include` (see [Filter System Overview](./)). Two shapes cover most cases.
|
|
246
158
|
|
|
247
|
-
|
|
159
|
+
**Soft delete or multi-tenant scoping** - a `where` clause that every query must carry:
|
|
248
160
|
|
|
249
161
|
```typescript
|
|
250
162
|
@model({
|
|
251
163
|
type: 'entity',
|
|
252
|
-
settings: {
|
|
253
|
-
defaultFilter: {
|
|
254
|
-
where: { deletedAt: null }, // or { isDeleted: false }
|
|
255
|
-
},
|
|
256
|
-
},
|
|
164
|
+
settings: { defaultFilter: { where: { deletedAt: null } } },
|
|
257
165
|
})
|
|
258
166
|
export class Post extends BaseEntity<typeof Post.schema> {}
|
|
259
167
|
|
|
260
|
-
// All queries exclude deleted posts
|
|
261
168
|
await postRepository.find({ filter: {} });
|
|
262
|
-
// WHERE deletedAt IS NULL
|
|
169
|
+
// WHERE "deletedAt" IS NULL
|
|
263
170
|
|
|
264
|
-
// Restore a deleted post
|
|
265
171
|
await postRepository.updateById({
|
|
266
172
|
id: postId,
|
|
267
173
|
data: { deletedAt: null },
|
|
268
|
-
options: { shouldSkipDefaultFilter: true }
|
|
174
|
+
options: { shouldSkipDefaultFilter: true }, // restore
|
|
269
175
|
});
|
|
270
176
|
```
|
|
271
177
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
```typescript
|
|
275
|
-
@model({
|
|
276
|
-
type: 'entity',
|
|
277
|
-
settings: {
|
|
278
|
-
defaultFilter: {
|
|
279
|
-
where: { tenantId: 'current-tenant' },
|
|
280
|
-
},
|
|
281
|
-
},
|
|
282
|
-
})
|
|
283
|
-
export class Document extends BaseEntity<typeof Document.schema> {}
|
|
284
|
-
|
|
285
|
-
// Queries scoped to tenant
|
|
286
|
-
await documentRepository.find({ filter: { where: { type: 'invoice' } } });
|
|
287
|
-
// WHERE tenantId = 'current-tenant' AND type = 'invoice'
|
|
288
|
-
|
|
289
|
-
// Cross-tenant admin query
|
|
290
|
-
await documentRepository.find({
|
|
291
|
-
filter: { where: { type: 'invoice' } },
|
|
292
|
-
options: { shouldSkipDefaultFilter: true }
|
|
293
|
-
});
|
|
294
|
-
// WHERE type = 'invoice'
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
### Active Records
|
|
298
|
-
|
|
299
|
-
```typescript
|
|
300
|
-
@model({
|
|
301
|
-
type: 'entity',
|
|
302
|
-
settings: {
|
|
303
|
-
defaultFilter: {
|
|
304
|
-
where: {
|
|
305
|
-
isActive: true,
|
|
306
|
-
expiresAt: { gt: new Date().toISOString() },
|
|
307
|
-
},
|
|
308
|
-
limit: 50,
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
})
|
|
312
|
-
export class Subscription extends BaseEntity<typeof Subscription.schema> {}
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
### Query Limit Protection
|
|
316
|
-
|
|
317
|
-
Use the dedicated `settings.defaultLimit` to raise (or lower) the per-model default page size. Prefer it over putting `limit` inside `defaultFilter`:
|
|
178
|
+
**Query-limit protection** - prefer the dedicated `settings.defaultLimit` over a `limit` inside `defaultFilter`. It resolves independently (`query.limit ?? defaultLimit ?? 10`, see [Fields, Order & Pagination -> Default limit resolution](./fields-order-pagination#default-limit-resolution)). Unlike `defaultFilter`, it is not dropped by `shouldSkipDefaultFilter`:
|
|
318
179
|
|
|
319
180
|
```typescript
|
|
320
181
|
@model({
|
|
321
182
|
type: 'entity',
|
|
322
|
-
settings: {
|
|
323
|
-
defaultLimit: 1000, // Per-model default when a query omits `limit`
|
|
324
|
-
},
|
|
183
|
+
settings: { defaultLimit: 1000 },
|
|
325
184
|
})
|
|
326
185
|
export class LogEntry extends BaseEntity<typeof LogEntry.schema> {}
|
|
327
186
|
|
|
328
|
-
|
|
329
|
-
await logEntryRepository.find({ filter: {} }); // LIMIT 1000
|
|
187
|
+
await logEntryRepository.find({ filter: {} }); // LIMIT 1000
|
|
330
188
|
await logEntryRepository.find({ filter: { limit: 50 } }); // LIMIT 50
|
|
331
189
|
```
|
|
332
190
|
|
|
333
|
-
|
|
334
|
-
> `defaultLimit` is independent of `defaultFilter`: bypassing the default filter via `shouldSkipDefaultFilter` does **not** drop the limit. See [Pagination → Default Limit](/references/base/filter-system/fields-order-pagination#default-limit).
|
|
191
|
+
`@model` validates `defaultLimit` at decoration time - it must be a positive integer or the class throws on load.
|
|
335
192
|
|
|
193
|
+
## Relation include default filters
|
|
336
194
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
When using `include` to load relations, the default filter of the related model is also applied. You can bypass it per-relation:
|
|
195
|
+
`include` also applies the related model's `defaultFilter`, and it can be bypassed or scoped per relation:
|
|
340
196
|
|
|
341
197
|
```typescript
|
|
342
198
|
await repository.find({
|
|
343
199
|
filter: {
|
|
344
200
|
include: [
|
|
345
|
-
|
|
346
|
-
{ relation: '
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
// Apply a custom scope (merged with relation's default filter)
|
|
352
|
-
{ relation: 'tags', scope: { limit: 10, order: ['name ASC'] } },
|
|
353
|
-
]
|
|
354
|
-
}
|
|
201
|
+
{ relation: 'posts' }, // related model's default filter applies
|
|
202
|
+
{ relation: 'comments', shouldSkipDefaultFilter: true }, // skipped for this relation only
|
|
203
|
+
{ relation: 'tags', scope: { limit: 10, order: ['name ASC'] } }, // scope merges with the default filter
|
|
204
|
+
],
|
|
205
|
+
},
|
|
355
206
|
});
|
|
356
207
|
```
|
|
357
208
|
|
|
358
|
-
|
|
359
|
-
## IExtraOptions Interface
|
|
360
|
-
|
|
361
|
-
The `shouldSkipDefaultFilter` option is part of the `IExtraOptions` interface:
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
interface IExtraOptions extends IWithTransaction {
|
|
365
|
-
/**
|
|
366
|
-
* If true, bypass the default filter configured in model settings.
|
|
367
|
-
*/
|
|
368
|
-
shouldSkipDefaultFilter?: boolean;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
interface IWithTransaction {
|
|
372
|
-
transaction?: ITransaction;
|
|
373
|
-
}
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
This allows combining with transactions:
|
|
377
|
-
|
|
378
|
-
```typescript
|
|
379
|
-
const tx = await repository.beginTransaction();
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
// Both transaction and shouldSkipDefaultFilter
|
|
383
|
-
await repository.updateAll({
|
|
384
|
-
where: { status: 'archived' },
|
|
385
|
-
data: { isDeleted: true },
|
|
386
|
-
options: {
|
|
387
|
-
transaction: tx,
|
|
388
|
-
shouldSkipDefaultFilter: true,
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
await tx.commit();
|
|
393
|
-
} catch (e) {
|
|
394
|
-
await tx.rollback();
|
|
395
|
-
throw e;
|
|
396
|
-
}
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
## How It Works
|
|
401
|
-
|
|
402
|
-
### Architecture
|
|
209
|
+
## How it works
|
|
403
210
|
|
|
404
211
|
```
|
|
405
|
-
+------------------+
|
|
406
|
-
| Model Settings | --> |
|
|
407
|
-
| defaultFilter | | applyDefaultFilter()
|
|
408
|
-
+------------------+
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
212
|
+
+------------------+ +--------------------------+ +------------------+
|
|
213
|
+
| Model Settings | --> | RelationalBaseRepository | --> | Repository Method |
|
|
214
|
+
| defaultFilter | | applyDefaultFilter() | | find/count/etc |
|
|
215
|
+
+------------------+ +--------------------------+ +------------------+
|
|
216
|
+
|
|
|
217
|
+
v
|
|
218
|
+
+------------------+
|
|
219
|
+
| FilterBuilder |
|
|
220
|
+
| mergeFilter() |
|
|
221
|
+
+------------------+
|
|
415
222
|
```
|
|
416
223
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
`PostgresBaseRepository` (`packages/core/src/connectors/postgres/repositories/core/base.ts`) implements the default-filter behavior directly as protected methods - no mixin is composed onto it:
|
|
224
|
+
`RelationalBaseRepository` (compatibility alias `PostgresBaseRepository`, `packages/core-server/src/connectors/postgres/repositories/core/base.ts`) implements the default-filter behavior directly - no mixin is composed onto it:
|
|
420
225
|
|
|
421
226
|
```typescript
|
|
422
|
-
// Check if default filter is configured
|
|
423
227
|
hasDefaultFilter(): boolean
|
|
424
|
-
|
|
425
|
-
// Get the raw default filter from model metadata
|
|
426
228
|
getDefaultFilter(): TFilter | undefined
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
applyDefaultFilter(opts: {
|
|
430
|
-
userFilter?: TFilter;
|
|
431
|
-
shouldSkipDefaultFilter?: boolean;
|
|
432
|
-
}): TFilter
|
|
229
|
+
getDefaultLimit(): number | undefined
|
|
230
|
+
applyDefaultFilter(opts: { userFilter?: TFilter; shouldSkipDefaultFilter?: boolean }): TFilter
|
|
433
231
|
```
|
|
434
232
|
|
|
435
|
-
`getDefaultFilter()` reads `this.modelSettings?.defaultFilter
|
|
233
|
+
`getDefaultFilter()` reads `this.modelSettings?.defaultFilter`. `modelSettings` is a protected getter on `AbstractRepository` (`src/base/repositories/core/abstract.ts`), resolved from `MetadataRegistry` by the entity's constructor - not by name string - on first access, then memoized.
|
|
436
234
|
|
|
437
|
-
|
|
438
|
-
> An older `DefaultFilterMixin` implemented this same behavior via mixin composition. It is no longer composed onto any repository class - see [Repository Mixins (Legacy)](../repositories/mixins.md) for history.
|
|
235
|
+
Read verbs (`find`/`findOne`/`findById`/`count`) call `applyDefaultFilter()` directly. Write verbs (`updateById`/`updateAll`/`deleteById`/`deleteAll`) route through the shared `_update`/`_delete` helpers instead. Those helpers call it against `{ where: opts.where }` (or `{ id }` for the `ById` forms) before building the SQL condition.
|
|
439
236
|
|
|
440
|
-
|
|
237
|
+
> [!NOTE]
|
|
238
|
+
> An older `DefaultFilterMixin` implemented this same behavior via mixin composition. It is no longer composed onto any repository class - see [Repository Mixins (Removed)](../repositories/mixins.md) for history.
|
|
441
239
|
|
|
442
|
-
The merge
|
|
240
|
+
The merge itself is `FilterBuilder.mergeFilter()`. Reach it through the datasource's query dialect -
|
|
241
|
+
`FilterBuilder` is abstract, so you never construct it directly:
|
|
443
242
|
|
|
444
243
|
```typescript
|
|
445
|
-
const
|
|
244
|
+
const queryDialect = dataSource.getQueryDialect();
|
|
446
245
|
|
|
447
|
-
|
|
246
|
+
queryDialect.mergeFilter({
|
|
448
247
|
defaultFilter: { where: { isDeleted: false }, limit: 100 },
|
|
449
|
-
userFilter: { where: { status: 'active' }, limit: 10 }
|
|
248
|
+
userFilter: { where: { status: 'active' }, limit: 10 },
|
|
450
249
|
});
|
|
451
|
-
|
|
452
|
-
// Result:
|
|
453
250
|
// { where: { isDeleted: false, status: 'active' }, limit: 10 }
|
|
454
251
|
```
|
|
455
252
|
|
|
253
|
+
## See also
|
|
456
254
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
| Configure default filter | `@model({ settings: { defaultFilter: { ... } } })` |
|
|
462
|
-
| Bypass default filter | `options: { shouldSkipDefaultFilter: true }` |
|
|
463
|
-
| Bypass for relation | `include: [{ relation: 'x', shouldSkipDefaultFilter: true }]` |
|
|
464
|
-
| Combine with transaction | `options: { transaction: tx, shouldSkipDefaultFilter: true }` |
|
|
465
|
-
| Check if model has default | `repository.hasDefaultFilter()` |
|
|
466
|
-
| Get raw default filter | `repository.getDefaultFilter()` |
|
|
467
|
-
|
|
255
|
+
- [Filter System Overview](./) - the `filter` shape and every operator family
|
|
256
|
+
- [Fields, Order & Pagination](./fields-order-pagination) - `defaultLimit` resolution in full
|
|
257
|
+
- [Advanced Repository Features](../repositories/advanced.md) - transactions, `log`/`lock` options, empty-where protection
|
|
258
|
+
- [Repository Mixins (Removed)](../repositories/mixins.md) - history of the removed `DefaultFilterMixin`
|
|
468
259
|
|
|
469
|
-
|
|
260
|
+
**Files:**
|
|
470
261
|
|
|
471
|
-
- [
|
|
472
|
-
- [
|
|
473
|
-
- [
|
|
262
|
+
- [`packages/core-server/src/connectors/relational/repositories/dialect/filter.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/relational/repositories/dialect/filter.ts) - `FilterBuilder.mergeFilter()`/`mergeWhere()`, the narrowing merge
|
|
263
|
+
- [`packages/core-server/src/connectors/postgres/repositories/core/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/core/base.ts) - `RelationalBaseRepository`, `applyDefaultFilter`/`getDefaultFilter`/`getDefaultLimit`
|
|
264
|
+
- [`packages/core-server/src/connectors/postgres/repositories/core/readable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/core/readable.ts) - `find`/`findOne`/`count` calling `applyDefaultFilter`
|
|
265
|
+
- [`packages/core-server/src/connectors/postgres/repositories/core/persistable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/core/persistable.ts) - `_update`/`_delete` calling `applyDefaultFilter` for `updateById`/`updateAll`/`deleteById`/`deleteAll`
|
|
266
|
+
- [`packages/core-server/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/metadata/persistents.ts) - `@model` decorator, `defaultLimit` validation
|
|
267
|
+
- [`packages/core-server/src/base/repositories/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/repositories/common/types.ts) - `IExtraOptions`, `IWithTransaction`
|