@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,431 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Filter Operators Quick Reference
|
|
3
|
+
description: Single-page cheat sheet of all filter operators
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
lastUpdated: 2026-01-03
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Filter Operators Quick Reference
|
|
9
|
+
|
|
10
|
+
Complete single-page reference for all IGNIS filter operators. For detailed explanations and examples, see the individual operator guides.
|
|
11
|
+
|
|
12
|
+
## Comparison Operators
|
|
13
|
+
|
|
14
|
+
| Operator | SQL | TypeScript Example | Description |
|
|
15
|
+
|----------|-----|-------------------|-------------|
|
|
16
|
+
| `eq` | `=` | `{ status: { eq: 'active' } }` | Equal to |
|
|
17
|
+
| `neq` | `!=` | `{ status: { neq: 'deleted' } }` | Not equal to |
|
|
18
|
+
| `gt` | `>` | `{ age: { gt: 18 } }` | Greater than |
|
|
19
|
+
| `gte` | `>=` | `{ age: { gte: 18 } }` | Greater than or equal |
|
|
20
|
+
| `lt` | `<` | `{ price: { lt: 100 } }` | Less than |
|
|
21
|
+
| `lte` | `<=` | `{ price: { lte: 100 } }` | Less than or equal |
|
|
22
|
+
|
|
23
|
+
**See:** [Comparison Operators Guide](./comparison-operators.md)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Range Operators
|
|
28
|
+
|
|
29
|
+
| Operator | SQL | TypeScript Example | Description |
|
|
30
|
+
|----------|-----|-------------------|-------------|
|
|
31
|
+
| `between` | `BETWEEN` | `{ age: { between: [18, 65] } }` | Value is within range (inclusive) |
|
|
32
|
+
| `notBetween` | `NOT BETWEEN` | `{ age: { notBetween: [0, 18] } }` | Value is outside range |
|
|
33
|
+
|
|
34
|
+
**See:** [Range Operators Guide](./range-operators.md)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## List Operators
|
|
39
|
+
|
|
40
|
+
| Operator | SQL | TypeScript Example | Description |
|
|
41
|
+
|----------|-----|-------------------|-------------|
|
|
42
|
+
| `in` / `inq` | `IN` | `{ status: { in: ['active', 'pending'] } }` | Value matches any in array |
|
|
43
|
+
| `notIn` / `nin` | `NOT IN` | `{ status: { notIn: ['deleted', 'banned'] } }` | Value doesn't match any in array |
|
|
44
|
+
|
|
45
|
+
**See:** [List Operators Guide](./list-operators.md)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Pattern Matching Operators
|
|
50
|
+
|
|
51
|
+
| Operator | SQL | TypeScript Example | Description |
|
|
52
|
+
|----------|-----|-------------------|-------------|
|
|
53
|
+
| `like` | `LIKE` | `{ name: { like: '%john%' } }` | Pattern match (case-sensitive) |
|
|
54
|
+
| `ilike` | `ILIKE` | `{ email: { ilike: '%@gmail.com' } }` | Pattern match (case-insensitive) |
|
|
55
|
+
| `notLike` | `NOT LIKE` | `{ name: { notLike: '%test%' } }` | Inverse pattern match (case-sensitive) |
|
|
56
|
+
| `notILike` | `NOT ILIKE` | `{ email: { notILike: '%spam%' } }` | Inverse pattern match (case-insensitive) |
|
|
57
|
+
| `startsWith` | `LIKE 'value%'` | `{ name: { startsWith: 'John' } }` | Starts with value |
|
|
58
|
+
| `endsWith` | `LIKE '%value'` | `{ email: { endsWith: '@example.com' } }` | Ends with value |
|
|
59
|
+
| `regexp` | `~` | `{ code: { regexp: '^[A-Z]{3}$' } }` | Regular expression (PostgreSQL) |
|
|
60
|
+
| `iregexp` | `~*` | `{ code: { iregexp: '^[a-z]{3}$' } }` | Case-insensitive regex (PostgreSQL) |
|
|
61
|
+
|
|
62
|
+
**Wildcard Patterns:**
|
|
63
|
+
- `%` - Matches any sequence of characters
|
|
64
|
+
- `_` - Matches any single character
|
|
65
|
+
|
|
66
|
+
**See:** [Pattern Matching Guide](./pattern-matching.md)
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Null Check Operators
|
|
71
|
+
|
|
72
|
+
| Operator | SQL | TypeScript Example | Description |
|
|
73
|
+
|----------|-----|-------------------|-------------|
|
|
74
|
+
| `isNull` | `IS NULL` | `{ deletedAt: { isNull: true } }` | Value is NULL |
|
|
75
|
+
| `isNotNull` | `IS NOT NULL` | `{ email: { isNotNull: true } }` | Value is not NULL |
|
|
76
|
+
|
|
77
|
+
**Alternative Syntax:**
|
|
78
|
+
```typescript
|
|
79
|
+
// Using 'is' operator
|
|
80
|
+
{ deletedAt: { is: null } } // IS NULL
|
|
81
|
+
{ email: { is: { not: null } } } // IS NOT NULL
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**See:** [Null Operators Guide](./null-operators.md)
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Logical Operators
|
|
89
|
+
|
|
90
|
+
| Operator | SQL | TypeScript Example | Description |
|
|
91
|
+
|----------|-----|-------------------|-------------|
|
|
92
|
+
| `and` | `AND` | `{ and: [{ age: { gt: 18 } }, { status: 'active' }] }` | All conditions must be true |
|
|
93
|
+
| `or` | `OR` | `{ or: [{ role: 'admin' }, { role: 'moderator' }] }` | At least one condition must be true |
|
|
94
|
+
| `not` | `NOT` | `{ not: { status: 'deleted' } }` | Inverts the condition |
|
|
95
|
+
|
|
96
|
+
**Implicit AND:**
|
|
97
|
+
```typescript
|
|
98
|
+
// Multiple fields = implicit AND
|
|
99
|
+
{
|
|
100
|
+
status: 'active',
|
|
101
|
+
age: { gte: 18 },
|
|
102
|
+
role: 'user'
|
|
103
|
+
}
|
|
104
|
+
// WHERE status = 'active' AND age >= 18 AND role = 'user'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**See:** [Logical Operators Guide](./logical-operators.md)
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## PostgreSQL Array Operators
|
|
112
|
+
|
|
113
|
+
These operators work with PostgreSQL array columns (`varchar[]`, `text[]`, `integer[]`, etc.).
|
|
114
|
+
|
|
115
|
+
| Operator | PostgreSQL | TypeScript Example | Description |
|
|
116
|
+
|----------|------------|-------------------|-------------|
|
|
117
|
+
| `contains` | `@>` | `{ tags: { contains: ['typescript', 'nodejs'] } }` | Array contains **ALL** specified elements |
|
|
118
|
+
| `containedBy` | `<@` | `{ tags: { containedBy: ['ts', 'js', 'go', 'rust'] } }` | Array is subset of specified array |
|
|
119
|
+
| `overlaps` | `&&` | `{ tags: { overlaps: ['react', 'vue', 'angular'] } }` | Arrays have at least one common element |
|
|
120
|
+
|
|
121
|
+
**Important:** These are array-specific operators, not to be confused with `in`/`notIn` which match scalar values against an array.
|
|
122
|
+
|
|
123
|
+
**See:** [Array Operators Guide](./array-operators.md)
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## JSON/JSONB Operators (PostgreSQL)
|
|
128
|
+
|
|
129
|
+
Query nested fields within JSON/JSONB columns using dot notation.
|
|
130
|
+
|
|
131
|
+
### Basic JSON Path
|
|
132
|
+
|
|
133
|
+
| Syntax | Example | Description |
|
|
134
|
+
|--------|---------|-------------|
|
|
135
|
+
| Dot notation | `metadata.user.name` | Access nested properties |
|
|
136
|
+
| Array index | `metadata.tags[0]` | Access array elements |
|
|
137
|
+
| Combined | `metadata.users[0].email` | Nested arrays and objects |
|
|
138
|
+
|
|
139
|
+
### JSON Path with Filters
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Query JSON field
|
|
143
|
+
{
|
|
144
|
+
metadata: {
|
|
145
|
+
jsonPath: '$.user.name',
|
|
146
|
+
eq: 'John'
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Multiple JSON conditions
|
|
151
|
+
{
|
|
152
|
+
and: [
|
|
153
|
+
{ metadata: { jsonPath: '$.user.age', gt: 18 } },
|
|
154
|
+
{ metadata: { jsonPath: '$.user.country', eq: 'US' } }
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Supported Operators with JSON
|
|
160
|
+
|
|
161
|
+
All comparison operators work with JSON path queries:
|
|
162
|
+
- `eq`, `neq`, `gt`, `gte`, `lt`, `lte`
|
|
163
|
+
- `in`, `notIn`
|
|
164
|
+
- `like`, `ilike` (for string fields)
|
|
165
|
+
- `isNull`, `isNotNull`
|
|
166
|
+
|
|
167
|
+
**See:** [JSON Filtering Guide](./json-filtering.md)
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Fields, Ordering & Pagination
|
|
172
|
+
|
|
173
|
+
### Select Specific Fields
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const users = await userRepo.find({
|
|
177
|
+
where: { isActive: true },
|
|
178
|
+
fields: ['id', 'name', 'email'], // Only return these fields
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Ordering
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// Single field
|
|
186
|
+
{ orderBy: { createdAt: 'desc' } }
|
|
187
|
+
|
|
188
|
+
// Multiple fields
|
|
189
|
+
{ orderBy: [
|
|
190
|
+
{ createdAt: 'desc' },
|
|
191
|
+
{ name: 'asc' }
|
|
192
|
+
]}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Pagination
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
{
|
|
199
|
+
limit: 10, // Max records to return
|
|
200
|
+
offset: 20, // Skip first 20 records
|
|
201
|
+
orderBy: { id: 'asc' }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Page 3 with 10 items per page
|
|
205
|
+
{
|
|
206
|
+
limit: 10,
|
|
207
|
+
offset: 20, // (page - 1) * limit = (3 - 1) * 10
|
|
208
|
+
orderBy: { createdAt: 'desc' }
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**See:** [Fields, Ordering & Pagination Guide](./fields-order-pagination.md)
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Default Filters
|
|
217
|
+
|
|
218
|
+
Automatically apply filters to all repository queries (e.g., soft delete, multi-tenant).
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { model, DefaultFilterMixin } from '@venizia/ignis';
|
|
222
|
+
|
|
223
|
+
@model()
|
|
224
|
+
class User extends DefaultFilterMixin(BaseEntity) {
|
|
225
|
+
static readonly schema = pgTable('users', {
|
|
226
|
+
id: integer('id').primaryKey(),
|
|
227
|
+
name: text('name'),
|
|
228
|
+
isDeleted: boolean('is_deleted').default(false),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Define default filter
|
|
232
|
+
static getDefaultFilter() {
|
|
233
|
+
return {
|
|
234
|
+
isDeleted: false, // Exclude deleted users by default
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// All queries automatically exclude deleted users
|
|
240
|
+
await userRepo.find({});
|
|
241
|
+
// WHERE is_deleted = false
|
|
242
|
+
|
|
243
|
+
// Skip default filter for admin operations
|
|
244
|
+
await userRepo.find({
|
|
245
|
+
where: {},
|
|
246
|
+
options: { shouldSkipDefaultFilter: true },
|
|
247
|
+
});
|
|
248
|
+
// No automatic filter applied
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**See:** [Default Filter Guide](./default-filter.md)
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Common Filter Patterns
|
|
256
|
+
|
|
257
|
+
### Multi-Condition Search
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
{
|
|
261
|
+
and: [
|
|
262
|
+
{ age: { gte: 18, lte: 65 } }, // Between 18 and 65
|
|
263
|
+
{ status: { in: ['active', 'pending'] } },
|
|
264
|
+
{ or: [
|
|
265
|
+
{ email: { endsWith: '@company.com' } },
|
|
266
|
+
{ role: 'admin' }
|
|
267
|
+
]}
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Text Search
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
{
|
|
276
|
+
or: [
|
|
277
|
+
{ name: { ilike: '%john%' } },
|
|
278
|
+
{ email: { ilike: '%john%' } },
|
|
279
|
+
{ username: { ilike: '%john%' } }
|
|
280
|
+
]
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Date Range
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
{
|
|
288
|
+
createdAt: {
|
|
289
|
+
gte: new Date('2024-01-01'),
|
|
290
|
+
lt: new Date('2024-02-01')
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Exclude Soft Deleted
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
{
|
|
299
|
+
and: [
|
|
300
|
+
{ isDeleted: false },
|
|
301
|
+
{ status: 'active' }
|
|
302
|
+
]
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Multi-Tenant Filtering
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
{
|
|
310
|
+
and: [
|
|
311
|
+
{ tenantId: currentTenantId },
|
|
312
|
+
{ isActive: true }
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Operator Precedence
|
|
320
|
+
|
|
321
|
+
When combining operators, IGNIS follows standard SQL precedence:
|
|
322
|
+
|
|
323
|
+
1. **NOT** - Highest precedence
|
|
324
|
+
2. **AND** - Medium precedence
|
|
325
|
+
3. **OR** - Lowest precedence
|
|
326
|
+
|
|
327
|
+
Use explicit parentheses (via nested `and`/`or`) for clarity:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
// Clear precedence
|
|
331
|
+
{
|
|
332
|
+
and: [
|
|
333
|
+
{ status: 'active' },
|
|
334
|
+
{ or: [
|
|
335
|
+
{ role: 'admin' },
|
|
336
|
+
{ role: 'moderator' }
|
|
337
|
+
]}
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Type Safety
|
|
345
|
+
|
|
346
|
+
All filter operators are fully typed based on your model schema:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
interface User {
|
|
350
|
+
id: number;
|
|
351
|
+
name: string;
|
|
352
|
+
age: number;
|
|
353
|
+
email: string;
|
|
354
|
+
tags: string[];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ✅ Type-safe filters
|
|
358
|
+
await userRepo.find({
|
|
359
|
+
where: {
|
|
360
|
+
age: { gt: 18 }, // number operators
|
|
361
|
+
name: { like: '%john%' }, // string operators
|
|
362
|
+
tags: { contains: ['typescript'] } // array operators
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// ❌ TypeScript error: wrong operator for type
|
|
367
|
+
await userRepo.find({
|
|
368
|
+
where: {
|
|
369
|
+
age: { like: '%18%' } // Error: 'like' not valid for numbers
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Performance Tips
|
|
377
|
+
|
|
378
|
+
1. **Index frequently filtered columns:**
|
|
379
|
+
```sql
|
|
380
|
+
CREATE INDEX idx_users_status ON users(status);
|
|
381
|
+
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
2. **Use `eq` instead of `like` when possible:**
|
|
385
|
+
```typescript
|
|
386
|
+
// ✅ Fast: Uses index
|
|
387
|
+
{ status: { eq: 'active' } }
|
|
388
|
+
|
|
389
|
+
// ❌ Slower: Full table scan
|
|
390
|
+
{ status: { like: 'active' } }
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
3. **Limit array contains operations:**
|
|
394
|
+
```typescript
|
|
395
|
+
// Better performance with smaller arrays
|
|
396
|
+
{ tags: { contains: ['typescript'] } } // ✅ Good
|
|
397
|
+
{ tags: { contains: ['tag1', 'tag2', /* ... 100 tags */] } } // ❌ Slow
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
4. **Use pagination for large result sets:**
|
|
401
|
+
```typescript
|
|
402
|
+
{
|
|
403
|
+
where: { isActive: true },
|
|
404
|
+
limit: 100,
|
|
405
|
+
offset: 0,
|
|
406
|
+
orderBy: { id: 'asc' }
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## See Also
|
|
413
|
+
|
|
414
|
+
- **Detailed Guides:**
|
|
415
|
+
- [Comparison Operators](./comparison-operators.md)
|
|
416
|
+
- [Logical Operators](./logical-operators.md)
|
|
417
|
+
- [Pattern Matching](./pattern-matching.md)
|
|
418
|
+
- [JSON Filtering](./json-filtering.md)
|
|
419
|
+
- [Array Operators](./array-operators.md)
|
|
420
|
+
|
|
421
|
+
- **Related References:**
|
|
422
|
+
- [Repositories](../repositories/) - Using filters in repository queries
|
|
423
|
+
- [Models](../models.md) - Defining model schemas
|
|
424
|
+
|
|
425
|
+
- **Usage Guides:**
|
|
426
|
+
- [Application Usage](./application-usage.md) - Filters in the full stack
|
|
427
|
+
- [Use Case Gallery](./use-cases.md) - Real-world examples
|
|
428
|
+
- [Pro Tips & Edge Cases](./tips.md) - Advanced patterns
|
|
429
|
+
|
|
430
|
+
- **Quick Reference:**
|
|
431
|
+
- [Main Quick Reference](/references/quick-reference.md) - All IGNIS APIs
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Range Operators
|
|
3
|
+
description: Operators for matching values within or outside a range
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Range Operators
|
|
8
|
+
|
|
9
|
+
Operators for matching values within or outside a range.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## between
|
|
13
|
+
|
|
14
|
+
Find values within a range (inclusive):
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// Numeric range
|
|
18
|
+
{ where: { price: { between: [100, 500] } } }
|
|
19
|
+
// SQL: WHERE "price" BETWEEN 100 AND 500
|
|
20
|
+
|
|
21
|
+
// Date range
|
|
22
|
+
{
|
|
23
|
+
where: {
|
|
24
|
+
createdAt: {
|
|
25
|
+
between: [new Date('2024-01-01'), new Date('2024-12-31')]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// SQL: WHERE "created_at" BETWEEN '2024-01-01' AND '2024-12-31'
|
|
30
|
+
|
|
31
|
+
// String range (lexicographic)
|
|
32
|
+
{ where: { lastName: { between: ['A', 'M'] } } }
|
|
33
|
+
// SQL: WHERE "last_name" BETWEEN 'A' AND 'M'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
> [!WARNING]
|
|
37
|
+
> The value MUST be an array with exactly 2 elements `[min, max]`. Invalid values throw an error.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## notBetween
|
|
41
|
+
|
|
42
|
+
Find values outside a range:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
{ where: { score: { notBetween: [40, 60] } } }
|
|
46
|
+
// SQL: WHERE NOT ("score" BETWEEN 40 AND 60)
|
|
47
|
+
// Matches: scores < 40 OR scores > 60
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Alternative: Using gte/lte
|
|
52
|
+
|
|
53
|
+
You can also express ranges using comparison operators:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Equivalent to between: [100, 500]
|
|
57
|
+
{ where: { price: { gte: 100, lte: 500 } } }
|
|
58
|
+
// SQL: WHERE "price" >= 100 AND "price" <= 500
|
|
59
|
+
|
|
60
|
+
// Exclusive range (not including boundaries)
|
|
61
|
+
{ where: { price: { gt: 100, lt: 500 } } }
|
|
62
|
+
// SQL: WHERE "price" > 100 AND "price" < 500
|
|
63
|
+
```
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Pro Tips & Edge Cases
|
|
3
|
+
description: Advanced tips and common edge cases for filters
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Pro Tips & Edge Cases
|
|
8
|
+
|
|
9
|
+
Advanced tips and common edge cases when working with filters.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Tip 1: JSON Numeric vs String Comparison
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// JSON field contains: { "priority": "3" } (string)
|
|
16
|
+
// This WON'T match numeric comparison!
|
|
17
|
+
{ where: { 'metadata.priority': { gt: 2 } } } // NULL due to safe casting
|
|
18
|
+
|
|
19
|
+
// Use string comparison instead
|
|
20
|
+
{ where: { 'metadata.priority': { gt: '2' } } } // Lexicographic compare
|
|
21
|
+
|
|
22
|
+
// Or ensure your data stores numbers properly
|
|
23
|
+
{ "priority": 3 } // Store as number, not string
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Tip 2: Empty Array Handling
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// Empty IN -> no results
|
|
31
|
+
{ where: { id: { in: [] } } } // SQL: WHERE false
|
|
32
|
+
|
|
33
|
+
// Empty NIN -> all results
|
|
34
|
+
{ where: { id: { nin: [] } } } // SQL: WHERE true
|
|
35
|
+
|
|
36
|
+
// Check array length before filtering
|
|
37
|
+
const ids = getUserSelectedIds();
|
|
38
|
+
if (ids.length === 0) {
|
|
39
|
+
return []; // Early return instead of empty IN
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## Tip 3: Null-Safe JSON Paths
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// If JSON field doesn't exist, #>> returns NULL
|
|
48
|
+
// This is safe - no errors, just no matches
|
|
49
|
+
{ where: { 'metadata.nonexistent.field': 'value' } }
|
|
50
|
+
// SQL: "metadata" #>> '{nonexistent,field}' = 'value'
|
|
51
|
+
// Result: No rows (NULL != 'value')
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## Tip 4: Performance with Large IN Arrays
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// For very large arrays (1000+ items), consider chunking
|
|
59
|
+
const allIds = getLargeIdList(); // 5000 IDs
|
|
60
|
+
|
|
61
|
+
const chunkSize = 500;
|
|
62
|
+
const results = [];
|
|
63
|
+
for (let i = 0; i < allIds.length; i += chunkSize) {
|
|
64
|
+
const chunk = allIds.slice(i, i + chunkSize);
|
|
65
|
+
const chunkResults = await repo.find({
|
|
66
|
+
filter: { where: { id: { in: chunk } } }
|
|
67
|
+
});
|
|
68
|
+
results.push(...chunkResults);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## Tip 5: Order By JSON Fields
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// JSON ordering uses #> (preserves type) not #>> (text)
|
|
77
|
+
{ order: ['metadata.priority DESC'] }
|
|
78
|
+
// SQL: "metadata" #> '{priority}' DESC
|
|
79
|
+
|
|
80
|
+
// JSONB comparison order:
|
|
81
|
+
// null < boolean < number < string < array < object
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
## Tip 6: Debugging Filters
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Enable logging to see generated SQL
|
|
89
|
+
const result = await repo.find({
|
|
90
|
+
filter: complexFilter,
|
|
91
|
+
options: {
|
|
92
|
+
log: { use: true, level: 'debug' },
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Or use buildQuery to inspect without executing
|
|
97
|
+
const queryOptions = repo.buildQuery({ filter: complexFilter });
|
|
98
|
+
console.log('Generated query options:', queryOptions);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## Tip 7: NOT IN with NULL Columns
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// NOT IN excludes NULL values!
|
|
106
|
+
{ where: { status: { nin: ['deleted'] } } }
|
|
107
|
+
// Rows where status IS NULL will NOT be returned
|
|
108
|
+
|
|
109
|
+
// Include NULL values explicitly
|
|
110
|
+
{
|
|
111
|
+
where: {
|
|
112
|
+
or: [
|
|
113
|
+
{ status: { nin: ['deleted'] } },
|
|
114
|
+
{ status: { is: null } }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Tip 8: Combining Multiple Array Conditions
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
await productRepo.find({
|
|
125
|
+
filter: {
|
|
126
|
+
where: {
|
|
127
|
+
// Must have ALL these categories
|
|
128
|
+
categories: { contains: ['electronics', 'portable'] },
|
|
129
|
+
// Tags must be subset of allowed tags
|
|
130
|
+
tags: { containedBy: ['new', 'sale', 'featured', 'popular'] },
|
|
131
|
+
// Must have at least one of these suppliers
|
|
132
|
+
suppliers: { overlaps: ['supplier-a', 'supplier-b'] }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
## Tip 9: Date Range Queries
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// This week's events
|
|
143
|
+
const startOfWeek = new Date();
|
|
144
|
+
startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay());
|
|
145
|
+
const endOfWeek = new Date(startOfWeek);
|
|
146
|
+
endOfWeek.setDate(endOfWeek.getDate() + 6);
|
|
147
|
+
|
|
148
|
+
{
|
|
149
|
+
where: {
|
|
150
|
+
eventDate: { between: [startOfWeek, endOfWeek] }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Last 30 days
|
|
155
|
+
const thirtyDaysAgo = new Date();
|
|
156
|
+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
157
|
+
|
|
158
|
+
{
|
|
159
|
+
where: {
|
|
160
|
+
createdAt: { gte: thirtyDaysAgo }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
## Tip 10: Reusable Filter Builders
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Create reusable filter builders
|
|
170
|
+
const createActiveFilter = <T extends { status: string; deletedAt: Date | null }>(): TWhere<T> => ({
|
|
171
|
+
status: 'active',
|
|
172
|
+
deletedAt: { is: null },
|
|
173
|
+
} as TWhere<T>);
|
|
174
|
+
|
|
175
|
+
const createPaginationFilter = (page: number, size: number = 20) => ({
|
|
176
|
+
limit: size,
|
|
177
|
+
skip: (page - 1) * size,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Usage
|
|
181
|
+
const products = await productRepo.find({
|
|
182
|
+
filter: {
|
|
183
|
+
where: {
|
|
184
|
+
...createActiveFilter(),
|
|
185
|
+
category: 'electronics',
|
|
186
|
+
},
|
|
187
|
+
...createPaginationFilter(3),
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
```
|