@venizia/ignis-docs 0.0.3 → 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 +406 -17
- 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 +2 -2
- 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 +6 -0
- package/wiki/changelogs/planned-schema-migrator.md +0 -8
- 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/{get-started → guides}/core-concepts/components.md +24 -17
- 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 +24 -7
- package/wiki/references/base/controllers.md +51 -20
- package/wiki/references/base/datasources.md +30 -0
- 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 -635
- 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 +14 -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 +16 -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/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 -97
- package/wiki/get-started/best-practices/security-guidelines.md +0 -99
- package/wiki/get-started/core-concepts/persistent.md +0 -539
- 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,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Comparison Operators
|
|
3
|
+
description: Equality and comparison operators for filtering records
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Comparison Operators
|
|
8
|
+
|
|
9
|
+
Equality and comparison operators for filtering records.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## eq - Equal To
|
|
13
|
+
|
|
14
|
+
Matches records where field equals the value.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// Implicit equality
|
|
18
|
+
{ where: { status: 'active' } }
|
|
19
|
+
|
|
20
|
+
// Explicit form
|
|
21
|
+
{ where: { status: { eq: 'active' } } }
|
|
22
|
+
|
|
23
|
+
// SQL: WHERE "status" = 'active'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Special Cases:**
|
|
27
|
+
```typescript
|
|
28
|
+
// Null equality
|
|
29
|
+
{ where: { deletedAt: null } }
|
|
30
|
+
{ where: { deletedAt: { eq: null } } }
|
|
31
|
+
// SQL: WHERE "deleted_at" IS NULL
|
|
32
|
+
|
|
33
|
+
// Array shorthand (becomes IN)
|
|
34
|
+
{ where: { id: [1, 2, 3] } }
|
|
35
|
+
// SQL: WHERE "id" IN (1, 2, 3)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## ne / neq - Not Equal To
|
|
40
|
+
|
|
41
|
+
Matches records where field does NOT equal the value.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
{ where: { status: { ne: 'deleted' } } }
|
|
45
|
+
{ where: { status: { neq: 'deleted' } } } // Alias
|
|
46
|
+
|
|
47
|
+
// SQL: WHERE "status" != 'deleted'
|
|
48
|
+
|
|
49
|
+
// Null handling
|
|
50
|
+
{ where: { deletedAt: { ne: null } } }
|
|
51
|
+
// SQL: WHERE "deleted_at" IS NOT NULL
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## gt - Greater Than
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Numbers
|
|
59
|
+
{ where: { price: { gt: 100 } } }
|
|
60
|
+
// SQL: WHERE "price" > 100
|
|
61
|
+
|
|
62
|
+
// Dates
|
|
63
|
+
{ where: { createdAt: { gt: new Date('2024-01-01') } } }
|
|
64
|
+
// SQL: WHERE "created_at" > '2024-01-01'
|
|
65
|
+
|
|
66
|
+
// Strings (lexicographic)
|
|
67
|
+
{ where: { name: { gt: 'M' } } }
|
|
68
|
+
// SQL: WHERE "name" > 'M'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
## gte - Greater Than or Equal
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
{ where: { quantity: { gte: 10 } } }
|
|
76
|
+
// SQL: WHERE "quantity" >= 10
|
|
77
|
+
|
|
78
|
+
// Combined with other operators
|
|
79
|
+
{ where: { age: { gte: 18, lt: 65 } } }
|
|
80
|
+
// SQL: WHERE "age" >= 18 AND "age" < 65
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
## lt - Less Than
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
{ where: { stock: { lt: 5 } } }
|
|
88
|
+
// SQL: WHERE "stock" < 5
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
## lte - Less Than or Equal
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
{ where: { rating: { lte: 3 } } }
|
|
96
|
+
// SQL: WHERE "rating" <= 3
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## Summary
|
|
101
|
+
|
|
102
|
+
| Operator | SQL | Description |
|
|
103
|
+
|----------|-----|-------------|
|
|
104
|
+
| `eq` | `=` | Equal to |
|
|
105
|
+
| `ne` / `neq` | `!=` | Not equal to |
|
|
106
|
+
| `gt` | `>` | Greater than |
|
|
107
|
+
| `gte` | `>=` | Greater than or equal |
|
|
108
|
+
| `lt` | `<` | Less than |
|
|
109
|
+
| `lte` | `<=` | Less than or equal |
|
|
@@ -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
|
+
```
|