@venizia/ignis-docs 0.0.2 → 0.0.4-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 +1 -1
- package/package.json +4 -2
- package/wiki/best-practices/api-usage-examples.md +591 -0
- package/wiki/best-practices/architectural-patterns.md +415 -0
- package/wiki/best-practices/architecture-decisions.md +488 -0
- package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +647 -182
- package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
- package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
- package/wiki/best-practices/data-modeling.md +376 -0
- package/wiki/best-practices/deployment-strategies.md +698 -0
- package/wiki/best-practices/index.md +27 -0
- package/wiki/best-practices/performance-optimization.md +196 -0
- package/wiki/best-practices/security-guidelines.md +218 -0
- package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
- package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
- package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
- package/wiki/changelogs/2025-12-17-refactor.md +1 -1
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
- package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +86 -0
- package/wiki/changelogs/2025-12-26-transaction-support.md +57 -0
- package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
- package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
- package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
- package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
- package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
- package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
- package/wiki/changelogs/index.md +8 -1
- package/wiki/changelogs/planned-schema-migrator.md +2 -10
- package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
- package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
- package/wiki/guides/core-concepts/components-guide.md +509 -0
- package/wiki/guides/core-concepts/components.md +122 -0
- package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
- package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
- package/wiki/guides/core-concepts/persistent/index.md +119 -0
- package/wiki/guides/core-concepts/persistent/models.md +241 -0
- package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
- package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
- package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
- package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
- package/wiki/guides/get-started/philosophy.md +682 -0
- package/wiki/guides/get-started/setup.md +157 -0
- package/wiki/guides/index.md +89 -0
- package/wiki/guides/reference/glossary.md +243 -0
- package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
- package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
- package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
- package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
- package/wiki/guides/tutorials/realtime-chat.md +1261 -0
- package/wiki/guides/tutorials/testing.md +723 -0
- package/wiki/index.md +176 -37
- package/wiki/references/base/application.md +27 -0
- package/wiki/references/base/bootstrapping.md +30 -26
- package/wiki/references/base/components.md +532 -31
- package/wiki/references/base/controllers.md +136 -38
- package/wiki/references/base/datasources.md +108 -5
- package/wiki/references/base/dependency-injection.md +39 -3
- package/wiki/references/base/filter-system/application-usage.md +224 -0
- package/wiki/references/base/filter-system/array-operators.md +132 -0
- package/wiki/references/base/filter-system/comparison-operators.md +109 -0
- package/wiki/references/base/filter-system/default-filter.md +428 -0
- package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
- package/wiki/references/base/filter-system/index.md +127 -0
- package/wiki/references/base/filter-system/json-filtering.md +197 -0
- package/wiki/references/base/filter-system/list-operators.md +71 -0
- package/wiki/references/base/filter-system/logical-operators.md +156 -0
- package/wiki/references/base/filter-system/null-operators.md +58 -0
- package/wiki/references/base/filter-system/pattern-matching.md +108 -0
- package/wiki/references/base/filter-system/quick-reference.md +431 -0
- package/wiki/references/base/filter-system/range-operators.md +63 -0
- package/wiki/references/base/filter-system/tips.md +190 -0
- package/wiki/references/base/filter-system/use-cases.md +452 -0
- package/wiki/references/base/index.md +90 -0
- package/wiki/references/base/middlewares.md +602 -0
- package/wiki/references/base/models.md +215 -23
- package/wiki/references/base/providers.md +732 -0
- package/wiki/references/base/repositories/advanced.md +555 -0
- package/wiki/references/base/repositories/index.md +228 -0
- package/wiki/references/base/repositories/mixins.md +331 -0
- package/wiki/references/base/repositories/relations.md +486 -0
- package/wiki/references/base/repositories.md +40 -549
- package/wiki/references/base/services.md +28 -4
- package/wiki/references/components/authentication.md +22 -2
- package/wiki/references/components/health-check.md +12 -0
- package/wiki/references/components/index.md +23 -0
- package/wiki/references/components/mail.md +687 -0
- package/wiki/references/components/request-tracker.md +16 -0
- package/wiki/references/components/socket-io.md +18 -0
- package/wiki/references/components/static-asset.md +14 -26
- package/wiki/references/components/swagger.md +17 -0
- package/wiki/references/configuration/environment-variables.md +427 -0
- package/wiki/references/configuration/index.md +73 -0
- package/wiki/references/helpers/cron.md +14 -0
- package/wiki/references/helpers/crypto.md +15 -0
- package/wiki/references/helpers/env.md +16 -0
- package/wiki/references/helpers/error.md +17 -0
- package/wiki/references/helpers/index.md +15 -0
- package/wiki/references/helpers/inversion.md +24 -4
- package/wiki/references/helpers/logger.md +19 -0
- package/wiki/references/helpers/network.md +11 -0
- package/wiki/references/helpers/queue.md +19 -0
- package/wiki/references/helpers/redis.md +21 -0
- package/wiki/references/helpers/socket-io.md +24 -5
- package/wiki/references/helpers/storage.md +18 -10
- package/wiki/references/helpers/testing.md +18 -0
- package/wiki/references/helpers/types.md +167 -0
- package/wiki/references/helpers/uid.md +167 -0
- package/wiki/references/helpers/worker-thread.md +16 -0
- package/wiki/references/index.md +177 -0
- package/wiki/references/quick-reference.md +634 -0
- package/wiki/references/src-details/boot.md +3 -3
- package/wiki/references/src-details/dev-configs.md +0 -4
- package/wiki/references/src-details/docs.md +2 -2
- package/wiki/references/src-details/index.md +86 -0
- package/wiki/references/src-details/inversion.md +1 -6
- package/wiki/references/src-details/mcp-server.md +3 -15
- package/wiki/references/utilities/index.md +86 -10
- package/wiki/references/utilities/jsx.md +577 -0
- package/wiki/references/utilities/request.md +0 -2
- package/wiki/references/utilities/statuses.md +740 -0
- package/wiki/changelogs/planned-transaction-support.md +0 -216
- package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
- package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
- package/wiki/get-started/best-practices/data-modeling.md +0 -177
- package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
- package/wiki/get-started/best-practices/performance-optimization.md +0 -88
- package/wiki/get-started/best-practices/security-guidelines.md +0 -99
- package/wiki/get-started/core-concepts/components.md +0 -98
- package/wiki/get-started/core-concepts/persistent.md +0 -543
- package/wiki/get-started/index.md +0 -65
- package/wiki/get-started/philosophy.md +0 -296
- package/wiki/get-started/prerequisites.md +0 -113
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Default Filter
|
|
3
|
+
description: Automatically apply filter conditions to all repository queries
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
lastUpdated: 2026-01-02
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Default Filter <Badge type="tip" text="v0.0.5+" />
|
|
9
|
+
|
|
10
|
+
Automatically apply filter conditions to all repository queries at the model level.
|
|
11
|
+
|
|
12
|
+
::: info 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
|
+
|
|
16
|
+
> [!NOTE]
|
|
17
|
+
> Default filters are ideal for:
|
|
18
|
+
> - **Soft Delete**: Automatically exclude deleted records
|
|
19
|
+
> - **Multi-Tenancy**: Isolate data by tenant
|
|
20
|
+
> - **Active Records**: Filter to active/non-expired records
|
|
21
|
+
> - **Query Limits**: Prevent unbounded queries
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
Configure a default filter in your model:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { model, BaseEntity } from '@venizia/ignis';
|
|
30
|
+
import { userTable } from '@/schemas';
|
|
31
|
+
|
|
32
|
+
@model({
|
|
33
|
+
type: 'entity',
|
|
34
|
+
settings: {
|
|
35
|
+
// Applied to all repository queries
|
|
36
|
+
defaultFilter: {
|
|
37
|
+
where: { isDeleted: false },
|
|
38
|
+
limit: 100,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
export class User extends BaseEntity<typeof User.schema> {
|
|
43
|
+
static override schema = userTable;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Now all queries automatically include the default filter:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Your code
|
|
51
|
+
await userRepo.find({
|
|
52
|
+
filter: { where: { status: 'active' } }
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Actual query executed
|
|
56
|
+
// WHERE isDeleted = false AND status = 'active' LIMIT 100
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
|
|
62
|
+
### Default Filter Properties
|
|
63
|
+
|
|
64
|
+
All standard filter properties are supported:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
@model({
|
|
68
|
+
type: 'entity',
|
|
69
|
+
settings: {
|
|
70
|
+
defaultFilter: {
|
|
71
|
+
// WHERE conditions
|
|
72
|
+
where: { isDeleted: false, tenantId: 'tenant-123' },
|
|
73
|
+
|
|
74
|
+
// Maximum results (prevents unbounded queries)
|
|
75
|
+
limit: 100,
|
|
76
|
+
|
|
77
|
+
// Default pagination offset
|
|
78
|
+
offset: 0,
|
|
79
|
+
|
|
80
|
+
// Default sort order
|
|
81
|
+
order: ['createdAt DESC'],
|
|
82
|
+
|
|
83
|
+
// Default field selection
|
|
84
|
+
fields: ['id', 'name', 'email', 'createdAt'],
|
|
85
|
+
|
|
86
|
+
// Default relations to include
|
|
87
|
+
include: [{ relation: 'profile' }],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
export class User extends BaseEntity<typeof User.schema> {}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Merge Behavior
|
|
96
|
+
|
|
97
|
+
When a user provides a filter, it is merged with the default filter:
|
|
98
|
+
|
|
99
|
+
| Property | Merge Strategy |
|
|
100
|
+
|----------|----------------|
|
|
101
|
+
| `where` | **Deep merge** - user values override matching keys |
|
|
102
|
+
| `limit` | User replaces default (if provided) |
|
|
103
|
+
| `offset`/`skip` | User replaces default (if provided) |
|
|
104
|
+
| `order` | User replaces default (if provided) |
|
|
105
|
+
| `fields` | User replaces default (if provided) |
|
|
106
|
+
| `include` | User replaces default (if provided) |
|
|
107
|
+
|
|
108
|
+
### Where Clause Merging
|
|
109
|
+
|
|
110
|
+
The `where` clause uses deep merge with user values taking precedence:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Default filter
|
|
114
|
+
{ where: { isDeleted: false, status: 'pending' }, limit: 100 }
|
|
115
|
+
|
|
116
|
+
// User filter
|
|
117
|
+
{ where: { status: 'active', role: 'admin' }, limit: 10 }
|
|
118
|
+
|
|
119
|
+
// Merged result
|
|
120
|
+
{
|
|
121
|
+
where: {
|
|
122
|
+
isDeleted: false, // From default (preserved)
|
|
123
|
+
status: 'active', // User overrides default
|
|
124
|
+
role: 'admin' // From user (added)
|
|
125
|
+
},
|
|
126
|
+
limit: 10 // User overrides default
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Complex Where Conditions
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Default: soft delete and tenant isolation
|
|
134
|
+
const defaultFilter = {
|
|
135
|
+
where: {
|
|
136
|
+
isDeleted: false,
|
|
137
|
+
tenantId: 'tenant-123',
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// User: OR conditions
|
|
142
|
+
const userFilter = {
|
|
143
|
+
where: {
|
|
144
|
+
or: [{ status: 'active' }, { priority: 'high' }]
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Result: AND of default + OR from user
|
|
149
|
+
// WHERE isDeleted = false AND tenantId = 'tenant-123'
|
|
150
|
+
// AND (status = 'active' OR priority = 'high')
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Operator Object Merging
|
|
154
|
+
|
|
155
|
+
Operator objects are deep merged, allowing range combinations:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// Default: created after 2024
|
|
159
|
+
const defaultFilter = {
|
|
160
|
+
where: {
|
|
161
|
+
createdAt: { gte: '2024-01-01' }
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// User: created before end of 2024
|
|
166
|
+
const userFilter = {
|
|
167
|
+
where: {
|
|
168
|
+
createdAt: { lte: '2024-12-31' }
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Result: date range
|
|
173
|
+
{
|
|
174
|
+
where: {
|
|
175
|
+
createdAt: { gte: '2024-01-01', lte: '2024-12-31' }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
## Bypassing Default Filter
|
|
182
|
+
|
|
183
|
+
Use `shouldSkipDefaultFilter: true` to bypass the default filter:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// Normal query - default filter applies
|
|
187
|
+
await repo.find({
|
|
188
|
+
filter: { where: { role: 'admin' } }
|
|
189
|
+
});
|
|
190
|
+
// WHERE isDeleted = false AND role = 'admin'
|
|
191
|
+
|
|
192
|
+
// Admin query - bypass default filter
|
|
193
|
+
await repo.find({
|
|
194
|
+
filter: { where: { role: 'admin' } },
|
|
195
|
+
options: { shouldSkipDefaultFilter: true }
|
|
196
|
+
});
|
|
197
|
+
// WHERE role = 'admin' (includes deleted records)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Supported Operations
|
|
201
|
+
|
|
202
|
+
`shouldSkipDefaultFilter` works with all repository methods:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// Read operations
|
|
206
|
+
await repo.find({ filter, options: { shouldSkipDefaultFilter: true } });
|
|
207
|
+
await repo.findOne({ filter, options: { shouldSkipDefaultFilter: true } });
|
|
208
|
+
await repo.findById({ id, options: { shouldSkipDefaultFilter: true } });
|
|
209
|
+
await repo.count({ where, options: { shouldSkipDefaultFilter: true } });
|
|
210
|
+
|
|
211
|
+
// Update operations
|
|
212
|
+
await repo.updateById({ id, data, options: { shouldSkipDefaultFilter: true } });
|
|
213
|
+
await repo.updateAll({ where, data, options: { shouldSkipDefaultFilter: true } });
|
|
214
|
+
|
|
215
|
+
// Delete operations
|
|
216
|
+
await repo.deleteById({ id, options: { shouldSkipDefaultFilter: true } });
|
|
217
|
+
await repo.deleteAll({ where, options: { shouldSkipDefaultFilter: true, force: true } });
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Use Cases for Bypassing
|
|
221
|
+
|
|
222
|
+
| Scenario | Example |
|
|
223
|
+
|----------|---------|
|
|
224
|
+
| Admin dashboard | View all records including deleted |
|
|
225
|
+
| Data recovery | Restore soft-deleted records |
|
|
226
|
+
| Analytics | Count across all tenants |
|
|
227
|
+
| Data migration | Update records regardless of status |
|
|
228
|
+
| Audit logs | Access historical data |
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
## Common Patterns
|
|
232
|
+
|
|
233
|
+
### Soft Delete
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
@model({
|
|
237
|
+
type: 'entity',
|
|
238
|
+
settings: {
|
|
239
|
+
defaultFilter: {
|
|
240
|
+
where: { deletedAt: null }, // or { isDeleted: false }
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
export class Post extends BaseEntity<typeof Post.schema> {}
|
|
245
|
+
|
|
246
|
+
// All queries exclude deleted posts
|
|
247
|
+
await postRepo.find({ filter: {} });
|
|
248
|
+
// WHERE deletedAt IS NULL
|
|
249
|
+
|
|
250
|
+
// Restore a deleted post
|
|
251
|
+
await postRepo.updateById({
|
|
252
|
+
id: postId,
|
|
253
|
+
data: { deletedAt: null },
|
|
254
|
+
options: { shouldSkipDefaultFilter: true }
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Multi-Tenant Isolation
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
@model({
|
|
262
|
+
type: 'entity',
|
|
263
|
+
settings: {
|
|
264
|
+
defaultFilter: {
|
|
265
|
+
where: { tenantId: 'current-tenant' },
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
})
|
|
269
|
+
export class Document extends BaseEntity<typeof Document.schema> {}
|
|
270
|
+
|
|
271
|
+
// Queries scoped to tenant
|
|
272
|
+
await docRepo.find({ filter: { where: { type: 'invoice' } } });
|
|
273
|
+
// WHERE tenantId = 'current-tenant' AND type = 'invoice'
|
|
274
|
+
|
|
275
|
+
// Cross-tenant admin query
|
|
276
|
+
await docRepo.find({
|
|
277
|
+
filter: { where: { type: 'invoice' } },
|
|
278
|
+
options: { shouldSkipDefaultFilter: true }
|
|
279
|
+
});
|
|
280
|
+
// WHERE type = 'invoice'
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Active Records
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
@model({
|
|
287
|
+
type: 'entity',
|
|
288
|
+
settings: {
|
|
289
|
+
defaultFilter: {
|
|
290
|
+
where: {
|
|
291
|
+
isActive: true,
|
|
292
|
+
expiresAt: { gt: new Date().toISOString() },
|
|
293
|
+
},
|
|
294
|
+
limit: 50,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
})
|
|
298
|
+
export class Subscription extends BaseEntity<typeof Subscription.schema> {}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Query Limit Protection
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
@model({
|
|
305
|
+
type: 'entity',
|
|
306
|
+
settings: {
|
|
307
|
+
defaultFilter: {
|
|
308
|
+
limit: 1000, // Prevent unbounded queries
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
})
|
|
312
|
+
export class LogEntry extends BaseEntity<typeof LogEntry.schema> {}
|
|
313
|
+
|
|
314
|
+
// User can override limit, but there's always a sensible default
|
|
315
|
+
await logRepo.find({ filter: {} }); // LIMIT 1000
|
|
316
|
+
await logRepo.find({ filter: { limit: 50 } }); // LIMIT 50
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
## IExtraOptions Interface
|
|
321
|
+
|
|
322
|
+
The `shouldSkipDefaultFilter` option is part of the `IExtraOptions` interface:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
interface IExtraOptions extends IWithTransaction {
|
|
326
|
+
/**
|
|
327
|
+
* If true, bypass the default filter configured in model settings.
|
|
328
|
+
*/
|
|
329
|
+
shouldSkipDefaultFilter?: boolean;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
interface IWithTransaction {
|
|
333
|
+
transaction?: ITransaction;
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
This allows combining with transactions:
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
const tx = await repo.beginTransaction();
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
// Both transaction and shouldSkipDefaultFilter
|
|
344
|
+
await repo.updateAll({
|
|
345
|
+
where: { status: 'archived' },
|
|
346
|
+
data: { isDeleted: true },
|
|
347
|
+
options: {
|
|
348
|
+
transaction: tx,
|
|
349
|
+
shouldSkipDefaultFilter: true,
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
await tx.commit();
|
|
354
|
+
} catch (e) {
|
|
355
|
+
await tx.rollback();
|
|
356
|
+
throw e;
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
## How It Works
|
|
362
|
+
|
|
363
|
+
### Architecture
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
+------------------+ +------------------+ +------------------+
|
|
367
|
+
| Model Settings | --> | DefaultFilterMixin | --> | Repository Method |
|
|
368
|
+
| defaultFilter | | applyDefaultFilter | | find/count/etc |
|
|
369
|
+
+------------------+ +------------------+ +------------------+
|
|
370
|
+
|
|
|
371
|
+
v
|
|
372
|
+
+------------------+
|
|
373
|
+
| FilterBuilder |
|
|
374
|
+
| mergeFilter() |
|
|
375
|
+
+------------------+
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### DefaultFilterMixin
|
|
379
|
+
|
|
380
|
+
The `DefaultFilterMixin` provides:
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// Check if default filter is configured
|
|
384
|
+
hasDefaultFilter(): boolean
|
|
385
|
+
|
|
386
|
+
// Get the raw default filter from model metadata
|
|
387
|
+
getDefaultFilter(): TFilter | undefined
|
|
388
|
+
|
|
389
|
+
// Merge default filter with user filter
|
|
390
|
+
applyDefaultFilter(opts: {
|
|
391
|
+
userFilter?: TFilter;
|
|
392
|
+
shouldSkipDefaultFilter?: boolean;
|
|
393
|
+
}): TFilter
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### FilterBuilder.mergeFilter()
|
|
397
|
+
|
|
398
|
+
The merge logic is implemented in `FilterBuilder`:
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
const filterBuilder = new FilterBuilder();
|
|
402
|
+
|
|
403
|
+
const merged = filterBuilder.mergeFilter({
|
|
404
|
+
defaultFilter: { where: { isDeleted: false }, limit: 100 },
|
|
405
|
+
userFilter: { where: { status: 'active' }, limit: 10 }
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Result:
|
|
409
|
+
// { where: { isDeleted: false, status: 'active' }, limit: 10 }
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
## Quick Reference
|
|
414
|
+
|
|
415
|
+
| Want to... | Code |
|
|
416
|
+
|------------|------|
|
|
417
|
+
| Configure default filter | `@model({ settings: { defaultFilter: { ... } } })` |
|
|
418
|
+
| Bypass default filter | `options: { shouldSkipDefaultFilter: true }` |
|
|
419
|
+
| Combine with transaction | `options: { transaction: tx, shouldSkipDefaultFilter: true }` |
|
|
420
|
+
| Check if model has default | `repo.hasDefaultFilter()` |
|
|
421
|
+
| Get raw default filter | `repo.getDefaultFilter()` |
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
## Next Steps
|
|
425
|
+
|
|
426
|
+
- [Filter System Overview](./index.md) - Filter structure and operators
|
|
427
|
+
- [Repository Mixins](../repositories/mixins.md) - Mixin architecture
|
|
428
|
+
- [Advanced Features](../repositories/advanced.md) - Transactions, hidden properties
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Fields, Ordering & Pagination
|
|
3
|
+
description: Control field selection, sorting, and pagination
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Fields, Ordering & Pagination
|
|
8
|
+
|
|
9
|
+
Control which fields are returned, how results are sorted, and how to paginate.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Field Selection
|
|
13
|
+
|
|
14
|
+
Control which fields are returned using `fields`:
|
|
15
|
+
|
|
16
|
+
### Array Format (Recommended)
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
await repo.find({
|
|
20
|
+
filter: {
|
|
21
|
+
where: { status: 'active' },
|
|
22
|
+
fields: ['id', 'email', 'name']
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
// Returns only: { id, email, name }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Object Format
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// Include specific fields
|
|
32
|
+
await repo.find({
|
|
33
|
+
filter: {
|
|
34
|
+
fields: { id: true, email: true, name: true }
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Exclude specific fields
|
|
39
|
+
await repo.find({
|
|
40
|
+
filter: {
|
|
41
|
+
fields: { password: false, secret: false }
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## Ordering
|
|
48
|
+
|
|
49
|
+
### Basic Ordering
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// Single column, descending
|
|
53
|
+
await repo.find({
|
|
54
|
+
filter: { order: ['createdAt DESC'] }
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Multiple columns
|
|
58
|
+
await repo.find({
|
|
59
|
+
filter: { order: ['status ASC', 'createdAt DESC'] }
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Default direction is ASC
|
|
63
|
+
await repo.find({
|
|
64
|
+
filter: { order: ['name'] } // Same as 'name ASC'
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### JSON Path Ordering
|
|
69
|
+
|
|
70
|
+
Order by nested fields in JSON columns:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
await repo.find({
|
|
74
|
+
filter: { order: ['metadata.priority DESC'] }
|
|
75
|
+
});
|
|
76
|
+
// SQL: ORDER BY "metadata" #> '{priority}' DESC
|
|
77
|
+
|
|
78
|
+
await repo.find({
|
|
79
|
+
filter: { order: ['settings.display.theme ASC'] }
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### JSONB Sort Order
|
|
84
|
+
|
|
85
|
+
| JSONB Type | Sort Order |
|
|
86
|
+
|------------|------------|
|
|
87
|
+
| `null` | First (lowest) |
|
|
88
|
+
| `boolean` | `false` < `true` |
|
|
89
|
+
| `number` | Numeric order |
|
|
90
|
+
| `string` | Lexicographic |
|
|
91
|
+
| `array` | Element-wise |
|
|
92
|
+
| `object` | Key-value |
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Pagination
|
|
96
|
+
|
|
97
|
+
### Limit and Skip
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// First 10 results
|
|
101
|
+
await repo.find({
|
|
102
|
+
filter: { limit: 10 }
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Page 2 (skip first 10, get next 10)
|
|
106
|
+
await repo.find({
|
|
107
|
+
filter: { limit: 10, skip: 10 }
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Page N formula: skip = (page - 1) * limit
|
|
111
|
+
const page = 3;
|
|
112
|
+
const pageSize = 20;
|
|
113
|
+
await repo.find({
|
|
114
|
+
filter: {
|
|
115
|
+
limit: pageSize,
|
|
116
|
+
skip: (page - 1) * pageSize
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> [!TIP]
|
|
122
|
+
> Always use `limit` for public-facing endpoints to prevent memory exhaustion.
|
|
123
|
+
|
|
124
|
+
### Pagination Helper
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
function getPaginationFilter(page: number, pageSize: number = 20) {
|
|
128
|
+
return {
|
|
129
|
+
limit: pageSize,
|
|
130
|
+
skip: (page - 1) * pageSize
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Usage
|
|
135
|
+
const filter = {
|
|
136
|
+
where: { status: 'active' },
|
|
137
|
+
...getPaginationFilter(3, 20)
|
|
138
|
+
};
|
|
139
|
+
// { where: {...}, limit: 20, skip: 40 }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## Combined Example
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
await repo.find({
|
|
147
|
+
filter: {
|
|
148
|
+
where: { status: 'active' },
|
|
149
|
+
fields: ['id', 'name', 'price', 'createdAt'],
|
|
150
|
+
order: ['price ASC', 'createdAt DESC'],
|
|
151
|
+
limit: 20,
|
|
152
|
+
skip: 0
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Filter System Overview
|
|
3
|
+
description: Complete reference for the IGNIS filter system
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Filter System
|
|
8
|
+
|
|
9
|
+
Complete reference for the Ignis filter system - operators, JSON filtering, array operators, default filters, and query patterns.
|
|
10
|
+
|
|
11
|
+
> [!NOTE]
|
|
12
|
+
> If you're new to Ignis, start with:
|
|
13
|
+
> - [5-Minute Quickstart](/guides/get-started/5-minute-quickstart) - Get up and running
|
|
14
|
+
> - [Building a CRUD API](/guides/tutorials/building-a-crud-api) - Learn the basics
|
|
15
|
+
> - [Repositories](/references/base/repositories) - Repository overview
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
Before reading this document, you should understand:
|
|
20
|
+
|
|
21
|
+
- [Repositories](../repositories/) - Basic repository operations (find, create, update, delete)
|
|
22
|
+
- [Models](../models.md) - Entity definitions and schemas
|
|
23
|
+
- SQL basics - Understanding of WHERE clauses and operators
|
|
24
|
+
- TypeScript type system - Type safety and inference
|
|
25
|
+
|
|
26
|
+
## Documentation
|
|
27
|
+
|
|
28
|
+
| Guide | Description |
|
|
29
|
+
|-------|-------------|
|
|
30
|
+
| [**⚡ Quick Reference**](./quick-reference.md) | **Single-page cheat sheet of all operators** |
|
|
31
|
+
| [Comparison Operators](./comparison-operators.md) | Equality, range, null checks |
|
|
32
|
+
| [Pattern Matching](./pattern-matching.md) | LIKE, ILIKE, regex |
|
|
33
|
+
| [Logical Operators](./logical-operators.md) | AND, OR combinations |
|
|
34
|
+
| [List Operators](./list-operators.md) | IN, NOT IN |
|
|
35
|
+
| [Range Operators](./range-operators.md) | BETWEEN, NOT BETWEEN |
|
|
36
|
+
| [Null Operators](./null-operators.md) | IS NULL, IS NOT NULL |
|
|
37
|
+
| [Array Operators](./array-operators.md) | PostgreSQL array operations |
|
|
38
|
+
| [JSON Filtering](./json-filtering.md) | JSON/JSONB path queries |
|
|
39
|
+
| [Fields, Order, Pagination](./fields-order-pagination.md) | SELECT, ORDER BY, LIMIT |
|
|
40
|
+
| [**Default Filter**](./default-filter.md) | Automatic filter application |
|
|
41
|
+
| [Application Usage](./application-usage.md) | Filter flow in applications |
|
|
42
|
+
| [Tips & Best Practices](./tips.md) | Performance and patterns |
|
|
43
|
+
| [Use Cases](./use-cases.md) | Real-world examples |
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Filter Structure
|
|
47
|
+
|
|
48
|
+
The `Filter<T>` object is the core mechanism for querying data in Ignis. It provides a structured, type-safe way to express complex queries without writing raw SQL.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
type TFilter<T> = {
|
|
52
|
+
where?: TWhere<T>; // Query conditions (SQL WHERE)
|
|
53
|
+
fields?: TFields<T>; // Column selection (SQL SELECT)
|
|
54
|
+
order?: string[]; // Sorting (SQL ORDER BY)
|
|
55
|
+
limit?: number; // Max results (SQL LIMIT)
|
|
56
|
+
skip?: number; // Pagination offset (SQL OFFSET)
|
|
57
|
+
offset?: number; // Alias for skip
|
|
58
|
+
include?: TInclusion[]; // Related data (SQL JOIN / subqueries)
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## SQL Mapping Overview
|
|
64
|
+
|
|
65
|
+
| Filter Property | SQL Equivalent | Purpose |
|
|
66
|
+
|-----------------|----------------|---------|
|
|
67
|
+
| `where` | `WHERE` | Filter rows by conditions |
|
|
68
|
+
| `fields` | `SELECT col1, col2` | Select specific columns |
|
|
69
|
+
| `order` | `ORDER BY` | Sort results |
|
|
70
|
+
| `limit` | `LIMIT` | Restrict number of results |
|
|
71
|
+
| `skip` / `offset` | `OFFSET` | Skip rows for pagination |
|
|
72
|
+
| `include` | `JOIN` / subquery | Include related data |
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
## Basic Example
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// Filter object
|
|
79
|
+
const filter = {
|
|
80
|
+
where: { status: 'active', role: 'admin' },
|
|
81
|
+
fields: ['id', 'name', 'email'],
|
|
82
|
+
order: ['createdAt DESC'],
|
|
83
|
+
limit: 10,
|
|
84
|
+
skip: 0
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Equivalent SQL
|
|
88
|
+
// SELECT "id", "name", "email"
|
|
89
|
+
// FROM "User"
|
|
90
|
+
// WHERE "status" = 'active' AND "role" = 'admin'
|
|
91
|
+
// ORDER BY "created_at" DESC
|
|
92
|
+
// LIMIT 10 OFFSET 0
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
## Quick Reference
|
|
97
|
+
|
|
98
|
+
| Want to... | Filter Syntax |
|
|
99
|
+
|------------|---------------|
|
|
100
|
+
| Equals | `{ field: value }` or `{ field: { eq: value } }` |
|
|
101
|
+
| Not equals | `{ field: { ne: value } }` |
|
|
102
|
+
| Greater than | `{ field: { gt: value } }` |
|
|
103
|
+
| Greater or equal | `{ field: { gte: value } }` |
|
|
104
|
+
| Less than | `{ field: { lt: value } }` |
|
|
105
|
+
| Less or equal | `{ field: { lte: value } }` |
|
|
106
|
+
| Is null | `{ field: null }` or `{ field: { is: null } }` |
|
|
107
|
+
| Is not null | `{ field: { isn: null } }` |
|
|
108
|
+
| In list | `{ field: { in: [a, b, c] } }` |
|
|
109
|
+
| Not in list | `{ field: { nin: [a, b, c] } }` |
|
|
110
|
+
| Range | `{ field: { between: [min, max] } }` |
|
|
111
|
+
| Outside range | `{ field: { notBetween: [min, max] } }` |
|
|
112
|
+
| Contains pattern | `{ field: { like: '%pattern%' } }` |
|
|
113
|
+
| Case-insensitive | `{ field: { ilike: '%pattern%' } }` |
|
|
114
|
+
| Regex match | `{ field: { regexp: '^pattern$' } }` |
|
|
115
|
+
| Array contains all | `{ arrayField: { contains: [a, b] } }` |
|
|
116
|
+
| Array is subset | `{ arrayField: { containedBy: [a, b, c] } }` |
|
|
117
|
+
| Array overlaps | `{ arrayField: { overlaps: [a, b] } }` |
|
|
118
|
+
| JSON nested | `{ 'jsonField.nested.path': value }` |
|
|
119
|
+
| JSON with operator | `{ 'jsonField.path': { gt: 10 } }` |
|
|
120
|
+
| AND conditions | `{ a: 1, b: 2 }` or `{ and: [{a: 1}, {b: 2}] }` |
|
|
121
|
+
| OR conditions | `{ or: [{ a: 1 }, { b: 2 }] }` |
|
|
122
|
+
| Include relation | `{ include: [{ relation: 'name' }] }` |
|
|
123
|
+
| Nested include | `{ include: [{ relation: 'a', scope: { include: [{ relation: 'b' }] } }] }` |
|
|
124
|
+
| Select fields | `{ fields: ['id', 'name'] }` |
|
|
125
|
+
| Order by | `{ order: ['field DESC'] }` |
|
|
126
|
+
| Order by JSON | `{ order: ['jsonField.path DESC'] }` |
|
|
127
|
+
| Paginate | `{ limit: 10, skip: 20 }` |
|