@venizia/ignis-docs 0.2.1-0 → 0.2.1-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/best-practices/architectural-patterns.md +3 -3
- package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/content/best-practices/contribution-workflow.md +2 -2
- package/content/best-practices/error-handling.md +94 -89
- package/content/best-practices/security-guidelines.md +5 -5
- package/content/extensions/components/api-reference.md +22 -21
- package/content/extensions/components/authentication/api.md +64 -28
- package/content/extensions/components/authentication/errors.md +19 -5
- package/content/extensions/components/authentication/index.md +19 -18
- package/content/extensions/components/authentication/usage.md +55 -30
- package/content/extensions/components/authorization/api.md +351 -84
- package/content/extensions/components/authorization/errors.md +51 -17
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +24 -16
- package/content/extensions/components/authorization/usage.md +45 -21
- package/content/extensions/components/health-check.md +15 -9
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +105 -54
- package/content/extensions/components/mail/errors.md +12 -10
- package/content/extensions/components/mail/index.md +32 -13
- package/content/extensions/components/mail/usage.md +26 -18
- package/content/extensions/components/request-tracker.md +18 -14
- package/content/extensions/components/socket-io/api.md +377 -882
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +72 -88
- package/content/extensions/components/socket-io/usage.md +107 -117
- package/content/extensions/components/static-asset/api.md +83 -31
- package/content/extensions/components/static-asset/errors.md +18 -7
- package/content/extensions/components/static-asset/index.md +18 -11
- package/content/extensions/components/static-asset/usage.md +11 -6
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +58 -27
- package/content/extensions/components/websocket/errors.md +3 -3
- package/content/extensions/components/websocket/index.md +8 -7
- package/content/extensions/components/websocket/usage.md +21 -8
- package/content/extensions/helpers/cron/index.md +8 -7
- package/content/extensions/helpers/crypto/index.md +16 -8
- package/content/extensions/helpers/crypto/reference.md +96 -24
- package/content/extensions/helpers/env/index.md +14 -10
- package/content/extensions/helpers/error/index.md +99 -23
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +23 -6
- package/content/extensions/helpers/inversion/reference.md +30 -22
- package/content/extensions/helpers/kafka/admin.md +3 -2
- package/content/extensions/helpers/kafka/compile-binary.md +69 -44
- package/content/extensions/helpers/kafka/consumer.md +24 -21
- package/content/extensions/helpers/kafka/examples.md +22 -234
- package/content/extensions/helpers/kafka/index.md +30 -62
- package/content/extensions/helpers/kafka/producer.md +31 -26
- package/content/extensions/helpers/kafka/schema-registry.md +19 -14
- package/content/extensions/helpers/logger/hf-logger.md +49 -22
- package/content/extensions/helpers/logger/index.md +37 -12
- package/content/extensions/helpers/logger/pino.md +31 -11
- package/content/extensions/helpers/logger/reference.md +243 -52
- package/content/extensions/helpers/network/api.md +65 -30
- package/content/extensions/helpers/network/index.md +32 -11
- package/content/extensions/helpers/queue/index.md +17 -6
- package/content/extensions/helpers/queue/reference.md +52 -25
- package/content/extensions/helpers/redis/index.md +29 -11
- package/content/extensions/helpers/redis/reference.md +85 -28
- package/content/extensions/helpers/secrets/index.md +82 -12
- package/content/extensions/helpers/socket-io/api.md +40 -21
- package/content/extensions/helpers/socket-io/index.md +26 -10
- package/content/extensions/helpers/storage/api.md +42 -24
- package/content/extensions/helpers/storage/index.md +10 -7
- package/content/extensions/helpers/types/index.md +20 -7
- package/content/extensions/helpers/types/reference.md +53 -14
- package/content/extensions/helpers/uid/index.md +166 -10
- package/content/extensions/helpers/websocket/api.md +52 -13
- package/content/extensions/helpers/websocket/index.md +7 -4
- package/content/extensions/helpers/worker-thread/index.md +9 -5
- package/content/extensions/helpers/worker-thread/reference.md +20 -20
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +4 -3
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +7 -6
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +63 -21
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +14 -14
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +32 -31
- package/content/references/base/datasources.md +6 -6
- package/content/references/base/dependency-injection.md +12 -11
- package/content/references/base/filter-system/application-usage.md +54 -30
- package/content/references/base/filter-system/array-operators.md +24 -46
- package/content/references/base/filter-system/comparison-operators.md +47 -67
- package/content/references/base/filter-system/default-filter.md +59 -53
- package/content/references/base/filter-system/fields-order-pagination.md +92 -146
- package/content/references/base/filter-system/index.md +25 -13
- package/content/references/base/filter-system/json-filtering.md +45 -184
- package/content/references/base/filter-system/list-operators.md +23 -53
- package/content/references/base/filter-system/logical-operators.md +63 -121
- package/content/references/base/filter-system/null-operators.md +34 -104
- package/content/references/base/filter-system/pattern-matching.md +40 -55
- package/content/references/base/filter-system/quick-reference.md +86 -198
- package/content/references/base/filter-system/range-operators.md +18 -46
- package/content/references/base/filter-system/tips.md +6 -6
- package/content/references/base/filter-system/use-cases.md +33 -15
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +11 -10
- package/content/references/base/models-reference.md +17 -17
- package/content/references/base/models.md +4 -3
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +224 -326
- package/content/references/base/repositories/index.md +19 -6
- package/content/references/base/repositories/mixins.md +5 -5
- package/content/references/base/repositories/relations.md +160 -293
- package/content/references/base/repositories/soft-deletable.md +16 -6
- package/content/references/base/secrets.md +17 -13
- package/content/references/base/services.md +6 -4
- package/content/references/configuration/environment-variables.md +31 -23
- package/content/references/configuration/index.md +6 -4
- package/content/references/index.md +1 -1
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +5 -1
- package/content/references/utilities/jsx-reference.md +11 -11
- package/content/references/utilities/jsx.md +2 -2
- package/content/references/utilities/module.md +78 -25
- package/content/references/utilities/request.md +2 -1
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +2 -2
- package/content/references/utilities/statuses-reference.md +4 -4
- package/content/references/utilities/statuses.md +5 -5
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +17 -16
|
@@ -23,44 +23,34 @@ Find rows where the array column contains **all** specified elements.
|
|
|
23
23
|
// Schema: tags varchar(100)[]
|
|
24
24
|
// Data: Product A has ['electronics', 'featured', 'sale']
|
|
25
25
|
|
|
26
|
-
// Find products with BOTH 'electronics' AND 'featured'
|
|
27
26
|
{ where: { tags: { contains: ['electronics', 'featured'] } } }
|
|
28
27
|
// SQL: "tags"::text[] @> ARRAY['electronics', 'featured']::text[]
|
|
29
|
-
|
|
30
|
-
// Single element (can pass single value or array)
|
|
31
|
-
{ where: { tags: { contains: ['featured'] } } }
|
|
32
|
-
{ where: { tags: { contains: 'featured' } } } // Also works
|
|
33
|
-
// Matches: ['featured'], ['featured', 'sale'], ['a', 'featured', 'b']
|
|
34
28
|
```
|
|
35
29
|
|
|
30
|
+
> [!NOTE]
|
|
31
|
+
> A single value is wrapped in an array automatically: `{ contains: 'featured' }` is treated as `{ contains: ['featured'] }`.
|
|
32
|
+
|
|
36
33
|
|
|
37
34
|
## containedBy (<@)
|
|
38
35
|
|
|
39
36
|
Find rows where **all** array elements are within the specified set.
|
|
40
37
|
|
|
41
38
|
```typescript
|
|
42
|
-
// Find products where ALL tags are in the allowed list
|
|
43
39
|
{ where: { tags: { containedBy: ['sale', 'featured', 'new', 'popular'] } } }
|
|
44
40
|
// SQL: "tags"::text[] <@ ARRAY['sale', 'featured', 'new', 'popular']::text[]
|
|
45
|
-
|
|
46
|
-
// Product A ['featured', 'sale'] -> matches (all in list)
|
|
47
|
-
// Product B ['featured', 'clearance'] -> no match ('clearance' not in list)
|
|
48
|
-
// Product C [] -> matches (empty is subset of everything)
|
|
49
41
|
```
|
|
50
42
|
|
|
43
|
+
> [!NOTE]
|
|
44
|
+
> An empty array is a subset of every set, so `tags: []` always matches `containedBy`.
|
|
45
|
+
|
|
51
46
|
|
|
52
47
|
## overlaps (&&)
|
|
53
48
|
|
|
54
49
|
Find rows where the arrays share at least one common element.
|
|
55
50
|
|
|
56
51
|
```typescript
|
|
57
|
-
// Find products with 'premium' OR 'sale' tag
|
|
58
52
|
{ where: { tags: { overlaps: ['premium', 'sale'] } } }
|
|
59
53
|
// SQL: "tags"::text[] && ARRAY['premium', 'sale']::text[]
|
|
60
|
-
|
|
61
|
-
// Product A ['featured', 'sale'] -> matches (has 'sale')
|
|
62
|
-
// Product B ['premium', 'luxury'] -> matches (has 'premium')
|
|
63
|
-
// Product C ['new', 'featured'] -> no match (no overlap)
|
|
64
54
|
```
|
|
65
55
|
|
|
66
56
|
|
|
@@ -84,52 +74,41 @@ Find rows where the arrays share at least one common element.
|
|
|
84
74
|
| "Must have AT LEAST ONE of these tags" | `overlaps` |
|
|
85
75
|
|
|
86
76
|
|
|
87
|
-
## Empty Array Behavior
|
|
88
|
-
|
|
89
|
-
| Operator | SQL Generated | Behavior |
|
|
90
|
-
|----------|---------------|----------|
|
|
91
|
-
| `contains: []` | `WHERE true` | Returns **ALL** rows |
|
|
92
|
-
| `containedBy: []` | `WHERE "col" = '{}'` | Returns only rows with **empty arrays** |
|
|
93
|
-
| `overlaps: []` | `WHERE false` | Returns **NO** rows |
|
|
94
|
-
|
|
95
|
-
> [!NOTE]
|
|
96
|
-
> Single values are automatically wrapped in an array: `{ contains: 'value' }` is treated as `{ contains: ['value'] }`.
|
|
97
|
-
|
|
98
|
-
|
|
99
77
|
## Type Handling
|
|
100
78
|
|
|
101
|
-
|
|
79
|
+
The element type of the array column decides the cast in the generated SQL.
|
|
80
|
+
|
|
102
81
|
```typescript
|
|
82
|
+
// String arrays (varchar[], text[], char[]) - both sides cast to text[]
|
|
103
83
|
{ where: { tags: { contains: ['a', 'b'] } } }
|
|
104
84
|
// SQL: "tags"::text[] @> ARRAY['a', 'b']::text[]
|
|
105
|
-
```
|
|
106
85
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
**Numeric Arrays** (`integer[]`, `numeric[]`):
|
|
110
|
-
```typescript
|
|
86
|
+
// Numeric arrays (integer[], numeric[]) - no cast needed
|
|
111
87
|
{ where: { scores: { contains: [100, 200] } } }
|
|
112
88
|
// SQL: "scores" @> ARRAY[100, 200]
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
No casting needed for numeric arrays.
|
|
116
89
|
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
90
|
+
// Boolean arrays - no cast needed
|
|
119
91
|
{ where: { flags: { contains: [true, false] } } }
|
|
120
92
|
// SQL: "flags" @> ARRAY[true, false]
|
|
121
93
|
```
|
|
122
94
|
|
|
123
95
|
|
|
96
|
+
## Empty Array Behavior
|
|
97
|
+
|
|
98
|
+
| Operator | SQL generated | Behavior |
|
|
99
|
+
|----------|---------------|----------|
|
|
100
|
+
| `contains: []` | `WHERE true` | Returns **ALL** rows |
|
|
101
|
+
| `containedBy: []` | `WHERE "col" = '{}'` | Returns only rows with **empty arrays** |
|
|
102
|
+
| `overlaps: []` | `WHERE false` | Returns **NO** rows |
|
|
103
|
+
|
|
104
|
+
|
|
124
105
|
## Security: Parameterized Values
|
|
125
106
|
|
|
126
|
-
Every element of `contains`/`containedBy`/`overlaps` is bound as a query parameter
|
|
107
|
+
Every element of `contains`/`containedBy`/`overlaps` is bound as a query parameter - only the operator token (`@>`/`<@`/`&&`) is raw SQL. See [The Hardening Round](../../../changelogs/2026-07-13-hardening-round) for the prior injection this closed.
|
|
127
108
|
|
|
128
109
|
|
|
129
110
|
## Defining Array Columns
|
|
130
111
|
|
|
131
|
-
In your Drizzle schema:
|
|
132
|
-
|
|
133
112
|
```typescript
|
|
134
113
|
import { pgTable, text, varchar, integer } from 'drizzle-orm/pg-core';
|
|
135
114
|
|
|
@@ -137,7 +116,6 @@ export const productTable = pgTable('Product', {
|
|
|
137
116
|
id: text('id').primaryKey(),
|
|
138
117
|
name: text('name').notNull(),
|
|
139
118
|
|
|
140
|
-
// Array columns
|
|
141
119
|
tags: varchar('tags', { length: 100 }).array(), // varchar(100)[]
|
|
142
120
|
categories: text('categories').array(), // text[]
|
|
143
121
|
scores: integer('scores').array(), // integer[]
|
|
@@ -152,6 +130,6 @@ export const productTable = pgTable('Product', {
|
|
|
152
130
|
|
|
153
131
|
**Files:**
|
|
154
132
|
|
|
155
|
-
- [`packages/core/src/connectors/postgres/repositories/dialect/query.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/dialect/query.ts) - `PostgresQueryOperators.FNS`, `buildPgArrayComparison`
|
|
156
|
-
- [`packages/core/src/connectors/
|
|
157
|
-
- [`packages/
|
|
133
|
+
- [`packages/core-server/src/connectors/postgres/repositories/dialect/query.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/dialect/query.ts) - `PostgresQueryOperators.FNS`, `buildPgArrayComparison`
|
|
134
|
+
- [`packages/core-server/src/connectors/relational/repositories/dialect/filter.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/relational/repositories/dialect/filter.ts) - `FilterBuilder`, translates `TFilter` to Drizzle/SQL
|
|
135
|
+
- [`packages/filter/src/common/operators.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/filter/src/common/operators.ts) - `QueryOperators` constants
|
|
@@ -6,116 +6,96 @@ difficulty: intermediate
|
|
|
6
6
|
|
|
7
7
|
# Comparison Operators
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Compares a field against a value: equality, inequality, and ordering.
|
|
10
10
|
|
|
11
|
+
| Operator | SQL | Meaning |
|
|
12
|
+
|----------|-----|---------|
|
|
13
|
+
| `eq` | `=` / `IS NULL` | Equal to |
|
|
14
|
+
| `ne` | `!=` / `IS NOT NULL` | Not equal to |
|
|
15
|
+
| `neq` | `!=` / `IS NOT NULL` | Alias for `ne` |
|
|
16
|
+
| `gt` | `>` | Greater than |
|
|
17
|
+
| `gte` | `>=` | Greater than or equal |
|
|
18
|
+
| `lt` | `<` | Less than |
|
|
19
|
+
| `lte` | `<=` | Less than or equal |
|
|
11
20
|
|
|
12
|
-
## eq
|
|
13
|
-
|
|
14
|
-
Matches records where field equals the value.
|
|
21
|
+
## eq
|
|
15
22
|
|
|
16
23
|
```typescript
|
|
17
|
-
// Implicit equality
|
|
18
|
-
{ where: { status: 'active' } }
|
|
19
|
-
|
|
20
|
-
// Explicit form
|
|
21
24
|
{ where: { status: { eq: 'active' } } }
|
|
22
|
-
|
|
23
25
|
// SQL: WHERE "status" = 'active'
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
**
|
|
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
|
-
// Empty array shorthand
|
|
38
|
-
{ where: { id: [] } }
|
|
39
|
-
// SQL: WHERE false (no results)
|
|
40
|
-
```
|
|
41
|
-
|
|
28
|
+
**Notice:** the bare shorthand `{ status: 'active' }` (no operator key) means the same thing.
|
|
42
29
|
|
|
43
|
-
|
|
30
|
+
**Edge cases:**
|
|
31
|
+
- `{ eq: null }` compiles to `IS NULL`, never `= NULL`.
|
|
32
|
+
- Bare array `{ field: [1, 2, 3] }` (no operator key) compiles to `IN (1, 2, 3)`.
|
|
33
|
+
- An explicit `{ eq: [1, 2, 3] }` does not become `IN` - it compares the column to an array value.
|
|
34
|
+
- Bare empty array `{ field: [] }` matches no rows (`WHERE false`).
|
|
44
35
|
|
|
45
|
-
|
|
36
|
+
## ne / neq
|
|
46
37
|
|
|
47
38
|
```typescript
|
|
48
39
|
{ where: { status: { ne: 'deleted' } } }
|
|
49
|
-
{ where: { status: { neq: 'deleted' } } } // Alias
|
|
50
|
-
|
|
51
40
|
// SQL: WHERE "status" != 'deleted'
|
|
52
|
-
|
|
53
|
-
// Null handling
|
|
54
|
-
{ where: { deletedAt: { ne: null } } }
|
|
55
|
-
{ where: { deletedAt: { neq: null } } }
|
|
56
|
-
// SQL: WHERE "deleted_at" IS NOT NULL
|
|
57
41
|
```
|
|
58
42
|
|
|
59
|
-
|
|
60
|
-
> When compared against a **real value** (not `null`), `ne`/`neq` follow SQL three-valued logic: a row whose field is `NULL` never matches `{ field: { neq: value } }`, because `NULL <> value` evaluates to UNKNOWN rather than TRUE. To include NULL rows, add an explicit branch: `{ or: [{ field: { neq: value } }, { field: null }] }`.
|
|
43
|
+
**Notice:** `ne` and `neq` are the same operator under two names.
|
|
61
44
|
|
|
45
|
+
**Edge cases:**
|
|
46
|
+
- `{ ne: null }` compiles to `IS NOT NULL`.
|
|
47
|
+
- SQL three-valued logic applies: a `NULL` field never matches `{ ne: value }`, because `NULL <> value` is UNKNOWN.
|
|
48
|
+
- Add an `or` branch to include NULL rows: `{ or: [{ field: { ne: value } }, { field: null }] }`.
|
|
62
49
|
|
|
63
|
-
## gt
|
|
50
|
+
## gt
|
|
64
51
|
|
|
65
52
|
```typescript
|
|
66
|
-
// Numbers
|
|
67
53
|
{ where: { price: { gt: 100 } } }
|
|
68
54
|
// SQL: WHERE "price" > 100
|
|
69
|
-
|
|
70
|
-
// Dates
|
|
71
|
-
{ where: { createdAt: { gt: new Date('2024-01-01') } } }
|
|
72
|
-
// SQL: WHERE "created_at" > '2024-01-01'
|
|
73
|
-
|
|
74
|
-
// Strings (lexicographic)
|
|
75
|
-
{ where: { name: { gt: 'M' } } }
|
|
76
|
-
// SQL: WHERE "name" > 'M'
|
|
77
55
|
```
|
|
78
56
|
|
|
57
|
+
**Notice:** works on numbers, dates, and strings (lexicographic comparison).
|
|
58
|
+
|
|
59
|
+
**Edge cases:**
|
|
60
|
+
- `{ gt: null }` compiles to `"price" > NULL`, which is never true - no rows match.
|
|
61
|
+
- Use `is`/`exists` instead to check for null.
|
|
62
|
+
- Combine with other operators in the same object: `{ gte: 18, lt: 65 }`.
|
|
79
63
|
|
|
80
|
-
## gte
|
|
64
|
+
## gte
|
|
81
65
|
|
|
82
66
|
```typescript
|
|
83
67
|
{ where: { quantity: { gte: 10 } } }
|
|
84
68
|
// SQL: WHERE "quantity" >= 10
|
|
85
|
-
|
|
86
|
-
// Combined with other operators
|
|
87
|
-
{ where: { age: { gte: 18, lt: 65 } } }
|
|
88
|
-
// SQL: WHERE "age" >= 18 AND "age" < 65
|
|
89
69
|
```
|
|
90
70
|
|
|
71
|
+
**Notice:** inclusive of the boundary value.
|
|
72
|
+
|
|
73
|
+
**Edge cases:**
|
|
74
|
+
- Same null behavior as `gt`: `{ gte: null }` matches no rows.
|
|
91
75
|
|
|
92
|
-
## lt
|
|
76
|
+
## lt
|
|
93
77
|
|
|
94
78
|
```typescript
|
|
95
79
|
{ where: { stock: { lt: 5 } } }
|
|
96
80
|
// SQL: WHERE "stock" < 5
|
|
97
81
|
```
|
|
98
82
|
|
|
83
|
+
**Notice:** exclusive of the boundary value.
|
|
84
|
+
|
|
85
|
+
**Edge cases:**
|
|
86
|
+
- Same null behavior as `gt`: `{ lt: null }` matches no rows.
|
|
99
87
|
|
|
100
|
-
## lte
|
|
88
|
+
## lte
|
|
101
89
|
|
|
102
90
|
```typescript
|
|
103
91
|
{ where: { rating: { lte: 3 } } }
|
|
104
92
|
// SQL: WHERE "rating" <= 3
|
|
105
93
|
```
|
|
106
94
|
|
|
95
|
+
**Notice:** inclusive of the boundary value.
|
|
107
96
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
| Operator | SQL | Description |
|
|
111
|
-
|----------|-----|-------------|
|
|
112
|
-
| `eq` | `=` / `IS NULL` | Equal to (handles null) |
|
|
113
|
-
| `ne` | `!=` / `IS NOT NULL` | Not equal to (handles null) |
|
|
114
|
-
| `neq` | `!=` / `IS NOT NULL` | Alias for `ne` |
|
|
115
|
-
| `gt` | `>` | Greater than |
|
|
116
|
-
| `gte` | `>=` | Greater than or equal |
|
|
117
|
-
| `lt` | `<` | Less than |
|
|
118
|
-
| `lte` | `<=` | Less than or equal |
|
|
97
|
+
**Edge cases:**
|
|
98
|
+
- Same null behavior as `gt`: `{ lte: null }` matches no rows.
|
|
119
99
|
|
|
120
100
|
## See also
|
|
121
101
|
|
|
@@ -125,6 +105,6 @@ Matches records where field does NOT equal the value. Both `ne` and `neq` are al
|
|
|
125
105
|
|
|
126
106
|
**Files:**
|
|
127
107
|
|
|
128
|
-
- [`packages/core/src/connectors/postgres/repositories/dialect/query.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/dialect/query.ts) - `PostgresQueryOperators.FNS`, per-operator SQL builders
|
|
129
|
-
- [`packages/core/src/connectors/
|
|
130
|
-
- [`packages/
|
|
108
|
+
- [`packages/core-server/src/connectors/postgres/repositories/dialect/query.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/dialect/query.ts) - `PostgresQueryOperators.FNS`, per-operator SQL builders
|
|
109
|
+
- [`packages/core-server/src/connectors/relational/repositories/dialect/filter.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/relational/repositories/dialect/filter.ts) - `FilterBuilder`, translates `TFilter` to Drizzle/SQL
|
|
110
|
+
- [`packages/filter/src/common/operators.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/filter/src/common/operators.ts) - `QueryOperators` constants
|
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
title: Default Filter
|
|
3
3
|
description: Automatically apply filter conditions to all repository queries
|
|
4
4
|
difficulty: intermediate
|
|
5
|
-
lastUpdated: 2026-
|
|
5
|
+
lastUpdated: 2026-07-23
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Default Filter <Badge type="tip" text="v0.0.5+" />
|
|
9
9
|
|
|
10
|
-
A model's `settings.defaultFilter` merges into every `find`/`findOne`/`findById`/`count`/`updateAll`/`deleteAll` call for that model
|
|
11
|
-
|
|
12
|
-
## Quick start
|
|
10
|
+
A model's `settings.defaultFilter` merges into every `find`/`findOne`/`findById`/`count`/`updateById`/`updateAll`/`deleteById`/`deleteAll` call for that model. It's the standard way to implement soft delete, multi-tenancy, active-record scoping, and query-limit protection without repeating a `where` clause at every call site.
|
|
13
11
|
|
|
14
12
|
```typescript
|
|
15
13
|
import { model, BaseEntity } from '@venizia/ignis';
|
|
@@ -17,15 +15,21 @@ import { userTable } from '@/schemas';
|
|
|
17
15
|
|
|
18
16
|
@model({
|
|
19
17
|
type: 'entity',
|
|
20
|
-
settings: {
|
|
21
|
-
defaultFilter: { where: { isDeleted: false }, limit: 100 },
|
|
22
|
-
},
|
|
18
|
+
settings: { defaultFilter: { where: { isDeleted: false }, limit: 100 } },
|
|
23
19
|
})
|
|
24
20
|
export class User extends BaseEntity<typeof User.schema> {
|
|
25
21
|
static override schema = userTable;
|
|
26
22
|
}
|
|
27
23
|
```
|
|
28
24
|
|
|
25
|
+
## Options
|
|
26
|
+
|
|
27
|
+
| Option | Type | Default | Meaning |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `settings.defaultFilter` | `TFilter` | none | Filter merged into every query for the model - `where`, `limit`, `offset`, `order`, `fields`, `include` are all valid inside it. |
|
|
30
|
+
| `settings.defaultLimit` | `number` (positive integer) | `DEFAULT_LIMIT` (`10`) | Per-model row cap. Independent of `defaultFilter` - see [Fields, Order & Pagination -> Default limit resolution](./fields-order-pagination#default-limit-resolution). |
|
|
31
|
+
| `options.shouldSkipDefaultFilter` | `boolean` | `false` | Skips the `defaultFilter` merge for one call. Does not drop `defaultLimit`. |
|
|
32
|
+
|
|
29
33
|
```typescript
|
|
30
34
|
import { userRepository } from '@/repositories';
|
|
31
35
|
|
|
@@ -38,7 +42,7 @@ await userRepository.find({ filter: { where: { status: 'active' } } });
|
|
|
38
42
|
`applyDefaultFilter()` merges the model's `defaultFilter` with the caller's filter via `FilterBuilder.mergeFilter()`.
|
|
39
43
|
|
|
40
44
|
- **`where` narrows per-key.** See the narrowing law below.
|
|
41
|
-
- **Everything else is user-wins-if-provided.** A caller value replaces the default, but a caller value of `undefined` never does
|
|
45
|
+
- **Everything else is user-wins-if-provided.** A caller value replaces the default, but a caller value of `undefined` never does. A filter built by spreading an optional object can't silently blow away a tenant scope or a limit.
|
|
42
46
|
|
|
43
47
|
| Property | Merge strategy |
|
|
44
48
|
|---|---|
|
|
@@ -47,7 +51,7 @@ await userRepository.find({ filter: { where: { status: 'active' } } });
|
|
|
47
51
|
|
|
48
52
|
### The `where` narrowing law
|
|
49
53
|
|
|
50
|
-
Keys present on only one side pass through untouched. When the
|
|
54
|
+
Keys present on only one side pass through untouched. When the same key appears on both sides, the outcome depends on shape:
|
|
51
55
|
|
|
52
56
|
| Default | Caller | Result |
|
|
53
57
|
|---|---|---|
|
|
@@ -58,7 +62,7 @@ Keys present on only one side pass through untouched. When the **same key** appe
|
|
|
58
62
|
|
|
59
63
|
- **`and` collisions concatenate.** Both conjunct lists merge into one.
|
|
60
64
|
- **`or` collisions cannot concatenate** - that would union, not narrow - so each side's `or` group becomes its own conjunct instead.
|
|
61
|
-
- **Non-scalar collisions always AND-compose.**
|
|
65
|
+
- **Non-scalar collisions always AND-compose.** Take a default scope - a `createdAt` floor, a tenant `inq`. A caller filter can narrow it, but never widen or drop it.
|
|
62
66
|
- **Only scalar-over-scalar is a true override.** Every other collision shape composes rather than replaces.
|
|
63
67
|
|
|
64
68
|
```typescript
|
|
@@ -80,7 +84,7 @@ Non-colliding keys still combine with an implicit AND, exactly like two `where`
|
|
|
80
84
|
|
|
81
85
|
## Bypassing the default filter
|
|
82
86
|
|
|
83
|
-
Pass `shouldSkipDefaultFilter: true` in `options` to skip the merge entirely.
|
|
87
|
+
Pass `shouldSkipDefaultFilter: true` in `options` to skip the merge entirely. Every repository verb honors it - `find`, `findOne`, `findById`, `count`, `updateById`, `updateAll`, `deleteById`, `deleteAll`:
|
|
84
88
|
|
|
85
89
|
```typescript
|
|
86
90
|
// Normal - default filter applies
|
|
@@ -95,6 +99,17 @@ await repository.find({
|
|
|
95
99
|
// WHERE "role" = 'admin' (includes soft-deleted rows)
|
|
96
100
|
```
|
|
97
101
|
|
|
102
|
+
`updateById` and `deleteById` merge the default filter into their `{ id }` condition the same way `updateAll`/`deleteAll` merge it into their `where`. The bypass applies to all four identically:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Also merges the default filter into { id: postId } - skip to update a soft-deleted row
|
|
106
|
+
await postRepository.updateById({
|
|
107
|
+
id: postId,
|
|
108
|
+
data: { title: 'Restored' },
|
|
109
|
+
options: { shouldSkipDefaultFilter: true },
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
98
113
|
It composes with a transaction the same way any other option does:
|
|
99
114
|
|
|
100
115
|
```typescript
|
|
@@ -121,9 +136,25 @@ try {
|
|
|
121
136
|
| Cross-tenant analytics | Count/aggregate across every tenant |
|
|
122
137
|
| Data migration | Update rows regardless of status |
|
|
123
138
|
|
|
139
|
+
`shouldSkipDefaultFilter` lives on the same options interface every repository verb accepts:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
interface IExtraOptions extends IWithTransaction {
|
|
143
|
+
shouldSkipDefaultFilter?: boolean;
|
|
144
|
+
log?: TRepositoryLogOptions;
|
|
145
|
+
lock?: TLockOptions;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface IWithTransaction {
|
|
149
|
+
transaction?: ITransaction;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`log` and `lock` are documented in [Advanced Repository Features](../repositories/advanced.md) - `log` only takes effect on write verbs (`create`/`updateById`/`updateAll`/`deleteById`/`deleteAll`), not on reads.
|
|
154
|
+
|
|
124
155
|
## Configuring a default filter
|
|
125
156
|
|
|
126
|
-
Any `TFilter` property is valid inside `defaultFilter` - `where`, `limit`, `offset`, `order`, `fields`, `include` (see [Filter System Overview](./)).
|
|
157
|
+
Any `TFilter` property is valid inside `defaultFilter` - `where`, `limit`, `offset`, `order`, `fields`, `include` (see [Filter System Overview](./)). Two shapes cover most cases.
|
|
127
158
|
|
|
128
159
|
**Soft delete or multi-tenant scoping** - a `where` clause that every query must carry:
|
|
129
160
|
|
|
@@ -144,7 +175,7 @@ await postRepository.updateById({
|
|
|
144
175
|
});
|
|
145
176
|
```
|
|
146
177
|
|
|
147
|
-
**Query-limit protection** - prefer the dedicated `settings.defaultLimit` over a `limit` inside `defaultFilter`. It resolves independently (`query.limit ?? defaultLimit ?? 10`, see [Fields, Order & Pagination -> Default
|
|
178
|
+
**Query-limit protection** - prefer the dedicated `settings.defaultLimit` over a `limit` inside `defaultFilter`. It resolves independently (`query.limit ?? defaultLimit ?? 10`, see [Fields, Order & Pagination -> Default limit resolution](./fields-order-pagination#default-limit-resolution)). Unlike `defaultFilter`, it is not dropped by `shouldSkipDefaultFilter`:
|
|
148
179
|
|
|
149
180
|
```typescript
|
|
150
181
|
@model({
|
|
@@ -153,7 +184,7 @@ await postRepository.updateById({
|
|
|
153
184
|
})
|
|
154
185
|
export class LogEntry extends BaseEntity<typeof LogEntry.schema> {}
|
|
155
186
|
|
|
156
|
-
await logEntryRepository.find({ filter: {} });
|
|
187
|
+
await logEntryRepository.find({ filter: {} }); // LIMIT 1000
|
|
157
188
|
await logEntryRepository.find({ filter: { limit: 50 } }); // LIMIT 50
|
|
158
189
|
```
|
|
159
190
|
|
|
@@ -190,7 +221,7 @@ await repository.find({
|
|
|
190
221
|
+------------------+
|
|
191
222
|
```
|
|
192
223
|
|
|
193
|
-
`RelationalBaseRepository` (compatibility alias `PostgresBaseRepository`, `packages/core/src/connectors/postgres/repositories/core/base.ts`) implements the default-filter behavior directly - no mixin is composed onto it:
|
|
224
|
+
`RelationalBaseRepository` (compatibility alias `PostgresBaseRepository`, `packages/core-server/src/connectors/postgres/repositories/core/base.ts`) implements the default-filter behavior directly - no mixin is composed onto it:
|
|
194
225
|
|
|
195
226
|
```typescript
|
|
196
227
|
hasDefaultFilter(): boolean
|
|
@@ -199,53 +230,26 @@ getDefaultLimit(): number | undefined
|
|
|
199
230
|
applyDefaultFilter(opts: { userFilter?: TFilter; shouldSkipDefaultFilter?: boolean }): TFilter
|
|
200
231
|
```
|
|
201
232
|
|
|
202
|
-
`getDefaultFilter()` reads `this.modelSettings?.defaultFilter
|
|
233
|
+
`getDefaultFilter()` reads `this.modelSettings?.defaultFilter`. `modelSettings` is a protected getter on `AbstractRepository` (`src/base/repositories/core/abstract.ts`), resolved from `MetadataRegistry` by the entity's constructor - not by name string - on first access, then memoized.
|
|
234
|
+
|
|
235
|
+
Read verbs (`find`/`findOne`/`findById`/`count`) call `applyDefaultFilter()` directly. Write verbs (`updateById`/`updateAll`/`deleteById`/`deleteAll`) route through the shared `_update`/`_delete` helpers instead. Those helpers call it against `{ where: opts.where }` (or `{ id }` for the `ById` forms) before building the SQL condition.
|
|
203
236
|
|
|
204
237
|
> [!NOTE]
|
|
205
238
|
> An older `DefaultFilterMixin` implemented this same behavior via mixin composition. It is no longer composed onto any repository class - see [Repository Mixins (Removed)](../repositories/mixins.md) for history.
|
|
206
239
|
|
|
207
|
-
The merge itself is `FilterBuilder.mergeFilter()
|
|
240
|
+
The merge itself is `FilterBuilder.mergeFilter()`. Reach it through the datasource's query dialect -
|
|
241
|
+
`FilterBuilder` is abstract, so you never construct it directly:
|
|
208
242
|
|
|
209
243
|
```typescript
|
|
210
|
-
const
|
|
244
|
+
const queryDialect = dataSource.getQueryDialect();
|
|
211
245
|
|
|
212
|
-
|
|
246
|
+
queryDialect.mergeFilter({
|
|
213
247
|
defaultFilter: { where: { isDeleted: false }, limit: 100 },
|
|
214
248
|
userFilter: { where: { status: 'active' }, limit: 10 },
|
|
215
249
|
});
|
|
216
250
|
// { where: { isDeleted: false, status: 'active' }, limit: 10 }
|
|
217
251
|
```
|
|
218
252
|
|
|
219
|
-
### `IExtraOptions`
|
|
220
|
-
|
|
221
|
-
`shouldSkipDefaultFilter` lives on the same options interface every repository verb accepts:
|
|
222
|
-
|
|
223
|
-
```typescript
|
|
224
|
-
interface IExtraOptions extends IWithTransaction {
|
|
225
|
-
shouldSkipDefaultFilter?: boolean;
|
|
226
|
-
log?: TRepositoryLogOptions;
|
|
227
|
-
lock?: TLockOptions;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
interface IWithTransaction {
|
|
231
|
-
transaction?: ITransaction;
|
|
232
|
-
}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
`log` and `lock` are documented in [Advanced Repository Features](../repositories/advanced.md) - `log` only takes effect on write verbs (`create`/`updateById`/`updateAll`/`deleteById`/`deleteAll`), not on reads.
|
|
236
|
-
|
|
237
|
-
## Quick reference
|
|
238
|
-
|
|
239
|
-
| Want to... | Code |
|
|
240
|
-
|---|---|
|
|
241
|
-
| Configure a default filter | `@model({ settings: { defaultFilter: { ... } } })` |
|
|
242
|
-
| Bypass the default filter | `options: { shouldSkipDefaultFilter: true }` |
|
|
243
|
-
| Bypass for one relation | `include: [{ relation: 'x', shouldSkipDefaultFilter: true }]` |
|
|
244
|
-
| Combine with a transaction | `options: { transaction: tx, shouldSkipDefaultFilter: true }` |
|
|
245
|
-
| Check if a model has a default | `repository.hasDefaultFilter()` |
|
|
246
|
-
| Read the raw default filter | `repository.getDefaultFilter()` |
|
|
247
|
-
| Read the raw default limit | `repository.getDefaultLimit()` |
|
|
248
|
-
|
|
249
253
|
## See also
|
|
250
254
|
|
|
251
255
|
- [Filter System Overview](./) - the `filter` shape and every operator family
|
|
@@ -255,7 +259,9 @@ interface IWithTransaction {
|
|
|
255
259
|
|
|
256
260
|
**Files:**
|
|
257
261
|
|
|
258
|
-
- [`packages/core/src/connectors/
|
|
259
|
-
- [`packages/core/src/connectors/postgres/repositories/core/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/core/base.ts) - `RelationalBaseRepository`, `applyDefaultFilter`/`getDefaultFilter`/`getDefaultLimit`
|
|
260
|
-
- [`packages/core/src/
|
|
261
|
-
- [`packages/core/src/
|
|
262
|
+
- [`packages/core-server/src/connectors/relational/repositories/dialect/filter.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/relational/repositories/dialect/filter.ts) - `FilterBuilder.mergeFilter()`/`mergeWhere()`, the narrowing merge
|
|
263
|
+
- [`packages/core-server/src/connectors/postgres/repositories/core/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/core/base.ts) - `RelationalBaseRepository`, `applyDefaultFilter`/`getDefaultFilter`/`getDefaultLimit`
|
|
264
|
+
- [`packages/core-server/src/connectors/postgres/repositories/core/readable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/core/readable.ts) - `find`/`findOne`/`count` calling `applyDefaultFilter`
|
|
265
|
+
- [`packages/core-server/src/connectors/postgres/repositories/core/persistable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/core/persistable.ts) - `_update`/`_delete` calling `applyDefaultFilter` for `updateById`/`updateAll`/`deleteById`/`deleteAll`
|
|
266
|
+
- [`packages/core-server/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/metadata/persistents.ts) - `@model` decorator, `defaultLimit` validation
|
|
267
|
+
- [`packages/core-server/src/base/repositories/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/repositories/common/types.ts) - `IExtraOptions`, `IWithTransaction`
|