@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
|
@@ -6,39 +6,9 @@ difficulty: intermediate
|
|
|
6
6
|
|
|
7
7
|
# JSON/JSONB Filtering
|
|
8
8
|
|
|
9
|
-
Query nested fields within JSON/JSONB columns using dot notation.
|
|
9
|
+
Query nested fields within JSON/JSONB columns using dot notation. PostgreSQL-specific.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
## Basic JSON Path Syntax
|
|
13
|
-
|
|
14
|
-
JSON paths are expressed as dot-notation keys in the `where` clause. A key is recognized as a JSON path if it contains a `.` or `[`.
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
// Column: metadata jsonb
|
|
18
|
-
// Data: { "user": { "id": 123, "role": "admin" }, "tags": ["urgent"] }
|
|
19
|
-
|
|
20
|
-
// Simple nested field
|
|
21
|
-
{ where: { 'metadata.user.id': 123 } }
|
|
22
|
-
// SQL: CASE WHEN ("metadata" #>> '{user,id}') ~ '^-?[0-9]+(\.[0-9]+)?$'
|
|
23
|
-
// THEN ("metadata" #>> '{user,id}')::numeric ELSE NULL END = 123
|
|
24
|
-
|
|
25
|
-
// String field (no numeric casting)
|
|
26
|
-
{ where: { 'metadata.user.role': 'admin' } }
|
|
27
|
-
// SQL: "metadata" #>> '{user,role}' = 'admin'
|
|
28
|
-
|
|
29
|
-
// Array index access
|
|
30
|
-
{ where: { 'metadata.tags[0]': 'urgent' } }
|
|
31
|
-
// SQL: "metadata" #>> '{tags,0}' = 'urgent'
|
|
32
|
-
|
|
33
|
-
// Kebab-case keys
|
|
34
|
-
{ where: { 'metadata.user-id': 'abc123' } }
|
|
35
|
-
// SQL: "metadata" #>> '{user-id}' = 'abc123'
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
## Supported Path Formats
|
|
40
|
-
|
|
41
|
-
| Format | Example | SQL Path |
|
|
11
|
+
| Format | Example | SQL path |
|
|
42
12
|
|--------|---------|----------|
|
|
43
13
|
| Simple field | `metadata.name` | `{name}` |
|
|
44
14
|
| Nested field | `metadata.user.email` | `{user,email}` |
|
|
@@ -46,204 +16,95 @@ JSON paths are expressed as dot-notation keys in the `where` clause. A key is re
|
|
|
46
16
|
| Nested with array | `metadata.items[2].name` | `{items,2,name}` |
|
|
47
17
|
| Kebab-case | `metadata.user-id` | `{user-id}` |
|
|
48
18
|
|
|
19
|
+
> [!NOTE]
|
|
20
|
+
> A key is recognized as a JSON path if it contains a `.` or `[`. The column named by the first path segment must be a `json`/`jsonb` column, or the query throws.
|
|
49
21
|
|
|
50
|
-
## JSON with Operators
|
|
51
22
|
|
|
52
|
-
|
|
23
|
+
## Basic Usage
|
|
53
24
|
|
|
54
25
|
```typescript
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
// SQL: CASE WHEN ("metadata" #>> '{score}') ~ '^-?[0-9]+(\.[0-9]+)?$'
|
|
58
|
-
// THEN ("metadata" #>> '{score}')::numeric ELSE NULL END > 80
|
|
26
|
+
// Column: metadata jsonb
|
|
27
|
+
// Data: { "user": { "id": 123, "role": "admin" } }
|
|
59
28
|
|
|
60
|
-
|
|
61
|
-
|
|
29
|
+
{ where: { 'metadata.user.role': 'admin' } }
|
|
30
|
+
// SQL: "metadata" #>> '{user,role}' = 'admin'
|
|
31
|
+
```
|
|
62
32
|
|
|
63
|
-
|
|
64
|
-
{ where: { 'metadata.score': { between: [70, 90] } } }
|
|
33
|
+
All standard operators work with a JSON path key:
|
|
65
34
|
|
|
66
|
-
|
|
35
|
+
```typescript
|
|
36
|
+
{ where: { 'metadata.score': { gt: 80 } } }
|
|
67
37
|
{ where: { 'metadata.level': { ilike: '%high%' } } }
|
|
68
|
-
// SQL: "metadata" #>> '{level}' ILIKE '%high%'
|
|
69
|
-
|
|
70
|
-
// IN operator (text comparison)
|
|
71
38
|
{ where: { 'metadata.status': { in: ['pending', 'review'] } } }
|
|
72
|
-
|
|
73
|
-
// Regex
|
|
74
39
|
{ where: { 'metadata.code': { regexp: '^[A-Z]+$' } } }
|
|
75
|
-
// SQL: "metadata" #>> '{code}' ~ '^[A-Z]+$'
|
|
76
|
-
|
|
77
|
-
// Not equal
|
|
78
|
-
{ where: { 'metadata.type': { ne: 'draft' } } }
|
|
79
|
-
// SQL: "metadata" #>> '{type}' != 'draft'
|
|
80
40
|
```
|
|
81
41
|
|
|
42
|
+
> [!NOTE]
|
|
43
|
+
> If the path does not exist in a row's JSON, `#>>` returns `NULL` - the row is safely excluded, never an error.
|
|
82
44
|
|
|
83
|
-
## Safe Numeric Casting
|
|
84
45
|
|
|
85
|
-
|
|
46
|
+
## Numeric Casting
|
|
86
47
|
|
|
87
|
-
|
|
88
|
-
// Data in database:
|
|
89
|
-
// Row 1: { "score": 85 } <- number
|
|
90
|
-
// Row 2: { "score": "high" } <- string
|
|
91
|
-
// Row 3: { "score": null } <- null
|
|
48
|
+
A JSON `#>>` extraction is text, so a numeric comparison needs a cast or Postgres raises `operator does not exist`. IGNIS wraps the extraction in a safe `CASE` expression, decided per operator:
|
|
92
49
|
|
|
93
|
-
|
|
50
|
+
```typescript
|
|
94
51
|
{ where: { 'metadata.score': { gt: 50 } } }
|
|
95
|
-
|
|
96
|
-
//
|
|
97
|
-
// CASE WHEN ("metadata" #>> '{score}') ~ '^-?[0-9]+(\.[0-9]+)?$'
|
|
98
|
-
// THEN ("metadata" #>> '{score}')::numeric
|
|
99
|
-
// ELSE NULL
|
|
100
|
-
// END > 50
|
|
101
|
-
|
|
102
|
-
// Result:
|
|
103
|
-
// Row 1: 85 > 50 -> matched
|
|
104
|
-
// Row 2: "high" -> NULL -> not matched
|
|
105
|
-
// Row 3: null -> NULL -> not matched
|
|
52
|
+
// SQL: CASE WHEN ("metadata" #>> '{score}') ~ '^-?[0-9]+(\.[0-9]+)?$'
|
|
53
|
+
// THEN ("metadata" #>> '{score}')::numeric ELSE NULL END > 50
|
|
106
54
|
```
|
|
107
55
|
|
|
108
|
-
|
|
|
109
|
-
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
56
|
+
| Operators | Casts to numeric when... |
|
|
57
|
+
|-----------|---------------------------|
|
|
58
|
+
| `gt`, `gte`, `lt`, `lte` | the operand is a `number` |
|
|
59
|
+
| `between`, `notBetween` | both bounds are numbers |
|
|
60
|
+
| `eq`, `ne`, `neq` | the operand is a `number` |
|
|
61
|
+
| `in`, `inq`, `nin` | every array element is a `number` |
|
|
62
|
+
| `like`, `ilike`, `nlike`, `nilike`, `regexp`, `iregexp` | never - always text |
|
|
63
|
+
| direct value (no operator object) | the value is `typeof number` |
|
|
114
64
|
|
|
115
|
-
|
|
65
|
+
> [!NOTE]
|
|
66
|
+
> A numeric-looking string still passes the cast (`"85"` -> `85`); a non-numeric string or `null` falls through to `NULL` and never matches.
|
|
116
67
|
|
|
117
|
-
The cast
|
|
68
|
+
The cast applies per operator, not once for the whole object - a mixed object casts only the operators that need it:
|
|
118
69
|
|
|
119
70
|
```typescript
|
|
120
|
-
// Mixed operator object: gte casts, like does not
|
|
121
71
|
{ where: { 'metadata.score': { gte: 1, like: '%a%' } } }
|
|
122
|
-
//
|
|
123
|
-
// THEN ("metadata" #>> '{score}')::numeric ELSE NULL END >= 1
|
|
124
|
-
// AND "metadata" #>> '{score}' LIKE '%a%'
|
|
72
|
+
// gte casts to numeric; like stays text - both read the same #>> extraction
|
|
125
73
|
```
|
|
126
74
|
|
|
127
75
|
`not` recurses into whatever it wraps, so a numeric operator nested under `not` still gets cast:
|
|
128
76
|
|
|
129
77
|
```typescript
|
|
130
|
-
// not wrapping an operator - the nested gt still gets the numeric cast
|
|
131
78
|
{ where: { 'metadata.score': { not: { gt: 50 } } } }
|
|
132
79
|
// SQL: NOT (CASE WHEN ("metadata" #>> '{score}') ~ '^-?[0-9]+(\.[0-9]+)?$'
|
|
133
80
|
// THEN ("metadata" #>> '{score}')::numeric ELSE NULL END > 50)
|
|
134
81
|
```
|
|
135
82
|
|
|
136
83
|
|
|
137
|
-
##
|
|
138
|
-
|
|
139
|
-
When a JSON path is compared to a number using direct equality (not an operator object), numeric casting is also applied:
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
{ where: { 'metadata.user.id': 123 } }
|
|
143
|
-
// Uses numeric CASE expression since value is typeof number
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
When compared to a string, it uses text comparison:
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
{ where: { 'metadata.user.role': 'admin' } }
|
|
150
|
-
// Uses "metadata" #>> '{user,role}' text comparison
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
## JSON Path Ordering
|
|
155
|
-
|
|
156
|
-
Order results by JSON fields:
|
|
84
|
+
## Ordering
|
|
157
85
|
|
|
158
86
|
```typescript
|
|
159
87
|
{ order: ['metadata.priority DESC'] }
|
|
160
88
|
// SQL: ORDER BY "metadata" #> '{priority}' DESC
|
|
161
|
-
|
|
162
|
-
// Multiple JSON fields
|
|
163
|
-
{ order: ['metadata.priority DESC', 'metadata.score ASC'] }
|
|
164
89
|
```
|
|
165
90
|
|
|
166
91
|
> [!NOTE]
|
|
167
|
-
>
|
|
168
|
-
|
|
169
|
-
**Sort Order for JSONB Types:**
|
|
170
|
-
|
|
171
|
-
| JSONB Type | Sort Order |
|
|
172
|
-
|------------|------------|
|
|
173
|
-
| `null` | First (lowest) |
|
|
174
|
-
| `boolean` | `false` < `true` |
|
|
175
|
-
| `number` | Numeric order |
|
|
176
|
-
| `string` | Lexicographic |
|
|
177
|
-
| `array` | Element-wise |
|
|
178
|
-
| `object` | Key-value |
|
|
92
|
+
> Ordering uses `#>` (returns JSONB, preserves native type ordering); `where` uses `#>>` (returns text) instead.
|
|
179
93
|
|
|
180
94
|
|
|
181
|
-
## Path Validation
|
|
95
|
+
## Path Validation
|
|
182
96
|
|
|
183
|
-
|
|
97
|
+
Every path component must match `/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\d+$/` - a letter/underscore start followed by letters, digits, underscore or hyphen, or a bare digit run for an array index.
|
|
184
98
|
|
|
185
99
|
```typescript
|
|
186
|
-
//
|
|
187
|
-
'
|
|
188
|
-
'
|
|
189
|
-
'
|
|
190
|
-
'config.user_id'
|
|
191
|
-
'data.meta-data' // kebab-case allowed
|
|
192
|
-
|
|
193
|
-
// Invalid (throws error)
|
|
194
|
-
'metadata.field;DROP TABLE'
|
|
195
|
-
'data.123invalid' // starts with digit (not array index context)
|
|
196
|
-
'config.(SELECT * FROM users)'
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
**Error Messages:**
|
|
200
|
-
```
|
|
201
|
-
// Non-JSON column
|
|
202
|
-
Error: Column 'name' is not JSON/JSONB type | dataType: 'text'
|
|
203
|
-
|
|
204
|
-
// Invalid path
|
|
205
|
-
Error: Invalid JSON path component: 'field;DROP'
|
|
100
|
+
'metadata.fieldName' // valid
|
|
101
|
+
'data.meta-data' // valid - kebab-case allowed
|
|
102
|
+
'data.123invalid' // invalid - starts with a digit outside array-index context
|
|
103
|
+
'metadata.field;DROP TABLE' // invalid - throws
|
|
206
104
|
```
|
|
207
105
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
## Performance Tips
|
|
212
|
-
|
|
213
|
-
1. **Index Your JSON Paths:**
|
|
214
|
-
```sql
|
|
215
|
-
CREATE INDEX idx_metadata_priority ON "Product" (("metadata" ->> 'priority'));
|
|
216
|
-
CREATE INDEX idx_metadata_gin ON "Product" USING GIN ("metadata");
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
2. **Use Appropriate Types in JSON:**
|
|
220
|
-
```json
|
|
221
|
-
// Good - numeric operators will work correctly
|
|
222
|
-
{ "priority": 3, "enabled": true }
|
|
223
|
-
|
|
224
|
-
// Bad - numeric operators will need string-to-number casting
|
|
225
|
-
{ "priority": "3", "enabled": "true" }
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
3. **Keep Paths Shallow:**
|
|
229
|
-
```typescript
|
|
230
|
-
// Easier to work with and index
|
|
231
|
-
'metadata.priority'
|
|
232
|
-
|
|
233
|
-
// Harder to optimize
|
|
234
|
-
'data.level1.level2.level3.level4.value'
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
## Null-Safe JSON Paths
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
// If JSON field doesn't exist, #>> returns NULL
|
|
242
|
-
// This is safe - no errors, just no matches
|
|
243
|
-
{ where: { 'metadata.nonexistent.field': 'value' } }
|
|
244
|
-
// SQL: "metadata" #>> '{nonexistent,field}' = 'value'
|
|
245
|
-
// Result: No rows (NULL != 'value')
|
|
246
|
-
```
|
|
106
|
+
> [!NOTE]
|
|
107
|
+
> A path on a non-JSON column also throws: `Column 'name' is not a JSON column`.
|
|
247
108
|
|
|
248
109
|
|
|
249
110
|
## See also
|
|
@@ -256,6 +117,6 @@ CREATE INDEX idx_metadata_gin ON "Product" USING GIN ("metadata");
|
|
|
256
117
|
|
|
257
118
|
**Files:**
|
|
258
119
|
|
|
259
|
-
- [`packages/core/src/connectors/
|
|
260
|
-
- [`packages/core/src/connectors/
|
|
261
|
-
- [`packages/
|
|
120
|
+
- [`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`, `buildJsonWhereCondition`/`buildJsonOperatorConditions`/`buildJsonOrderBy`
|
|
121
|
+
- [`packages/core-server/src/connectors/relational/repositories/dialect/internal/json-utils.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/relational/repositories/dialect/internal/json-utils.ts) - `isJsonPath`, `parseJsonPath`, path validation regex
|
|
122
|
+
- [`packages/filter/src/common/operators.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/filter/src/common/operators.ts) - `QueryOperators` constants
|
|
@@ -6,72 +6,42 @@ difficulty: intermediate
|
|
|
6
6
|
|
|
7
7
|
# List Operators
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Matches a field against a set of candidate values.
|
|
10
10
|
|
|
11
|
+
| Operator | SQL | Meaning |
|
|
12
|
+
|----------|-----|---------|
|
|
13
|
+
| `in` | `IN` | Value is one of the array |
|
|
14
|
+
| `inq` | `IN` | Alias for `in` |
|
|
15
|
+
| `nin` | `NOT IN` | Value is none of the array |
|
|
11
16
|
|
|
12
|
-
## in / inq
|
|
13
|
-
|
|
14
|
-
Matches records where field value is in the provided array. `in` and `inq` are aliases and behave identically.
|
|
17
|
+
## in / inq
|
|
15
18
|
|
|
16
19
|
```typescript
|
|
17
20
|
{ where: { status: { in: ['active', 'pending', 'review'] } } }
|
|
18
|
-
{ where: { status: { inq: ['active', 'pending', 'review'] } } } // Alias
|
|
19
|
-
|
|
20
21
|
// SQL: WHERE "status" IN ('active', 'pending', 'review')
|
|
21
|
-
|
|
22
|
-
// Numeric IDs
|
|
23
|
-
{ where: { categoryId: { in: [1, 2, 3, 4, 5] } } }
|
|
24
|
-
// SQL: WHERE "category_id" IN (1, 2, 3, 4, 5)
|
|
25
22
|
```
|
|
26
23
|
|
|
24
|
+
**Notice:** `in` and `inq` are the same operator under two names.
|
|
25
|
+
|
|
26
|
+
**Edge cases:**
|
|
27
|
+
- `{ in: [] }` (empty array) matches no rows (`WHERE false`).
|
|
28
|
+
- `{ in: 'value' }` (non-array operand) falls back to `=`.
|
|
29
|
+
- `{ in: null }` falls back to `= NULL` (not `IS NULL`) and matches no rows; use `is`/`eq` for null checks.
|
|
27
30
|
|
|
28
|
-
## nin
|
|
31
|
+
## nin
|
|
29
32
|
|
|
30
33
|
```typescript
|
|
31
34
|
{ where: { status: { nin: ['deleted', 'archived', 'banned'] } } }
|
|
32
35
|
// SQL: WHERE "status" NOT IN ('deleted', 'archived', 'banned')
|
|
33
36
|
```
|
|
34
37
|
|
|
38
|
+
**Notice:** `NOT IN` excludes rows where the column is `NULL`.
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
| `{ nin: [] }` (empty array) | Returns all rows (`WHERE true`) |
|
|
42
|
-
| `{ in: 'value' }` (non-array) | Treated as `{ eq: 'value' }` |
|
|
43
|
-
| `{ nin: 'value' }` (non-array) | Treated as `{ ne: 'value' }` |
|
|
44
|
-
|
|
45
|
-
> [!WARNING]
|
|
46
|
-
> `NOT IN` excludes rows where the column is `NULL`. If your column can be `NULL`, use `OR` to include them:
|
|
47
|
-
> ```typescript
|
|
48
|
-
> where: {
|
|
49
|
-
> or: [
|
|
50
|
-
> { status: { nin: ['deleted'] } },
|
|
51
|
-
> { status: { is: null } }
|
|
52
|
-
> ]
|
|
53
|
-
> }
|
|
54
|
-
> ```
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
## Performance Tip
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
import { userRepository } from '@/repositories';
|
|
61
|
-
|
|
62
|
-
// For very large arrays (1000+ items), consider chunking
|
|
63
|
-
const allIds: number[] = [ /* 5000 ids */ ];
|
|
64
|
-
|
|
65
|
-
const chunkSize = 500;
|
|
66
|
-
const results = [];
|
|
67
|
-
for (let i = 0; i < allIds.length; i += chunkSize) {
|
|
68
|
-
const chunk = allIds.slice(i, i + chunkSize);
|
|
69
|
-
const chunkResults = await userRepository.find({
|
|
70
|
-
filter: { where: { id: { in: chunk } } }
|
|
71
|
-
});
|
|
72
|
-
results.push(...chunkResults);
|
|
73
|
-
}
|
|
74
|
-
```
|
|
40
|
+
**Edge cases:**
|
|
41
|
+
- Include NULL rows with an explicit `or` branch: `{ or: [{ status: { nin: [...] } }, { status: { is: null } }] }`.
|
|
42
|
+
- `{ nin: [] }` (empty array) matches all rows (`WHERE true`).
|
|
43
|
+
- `{ nin: 'value' }` (non-array operand) falls back to `!=`.
|
|
44
|
+
- `{ nin: null }` falls back to `!= NULL` (not `IS NOT NULL`) and matches no rows.
|
|
75
45
|
|
|
76
46
|
## See also
|
|
77
47
|
|
|
@@ -81,6 +51,6 @@ for (let i = 0; i < allIds.length; i += chunkSize) {
|
|
|
81
51
|
|
|
82
52
|
**Files:**
|
|
83
53
|
|
|
84
|
-
- [`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
|
|
85
|
-
- [`packages/core/src/connectors/
|
|
86
|
-
- [`packages/
|
|
54
|
+
- [`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
|
|
55
|
+
- [`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
|
|
56
|
+
- [`packages/filter/src/common/operators.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/filter/src/common/operators.ts) - `QueryOperators` constants
|
|
@@ -8,26 +8,27 @@ difficulty: intermediate
|
|
|
8
8
|
|
|
9
9
|
Combine multiple conditions with AND and OR logic.
|
|
10
10
|
|
|
11
|
+
| Form | Where shape | SQL |
|
|
12
|
+
|------|-------------|-----|
|
|
13
|
+
| Implicit AND | multiple keys in one object | `AND` between each key |
|
|
14
|
+
| Explicit AND | `{ and: [...] }` | `AND` between grouped clauses |
|
|
15
|
+
| OR | `{ or: [...] }` | `OR` between grouped clauses |
|
|
16
|
+
| NOT | `{ field: { not: ... } }` | `NOT (...)` around the negated condition |
|
|
17
|
+
|
|
11
18
|
|
|
12
19
|
## Implicit AND
|
|
13
20
|
|
|
14
|
-
Multiple conditions in the same object
|
|
21
|
+
Multiple conditions in the same object combine with AND.
|
|
15
22
|
|
|
16
23
|
```typescript
|
|
17
|
-
{
|
|
18
|
-
where: {
|
|
19
|
-
status: 'active',
|
|
20
|
-
role: 'admin',
|
|
21
|
-
verified: true,
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
+
{ where: { status: 'active', role: 'admin', verified: true } }
|
|
24
25
|
// SQL: WHERE "status" = 'active' AND "role" = 'admin' AND "verified" = true
|
|
25
26
|
```
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
## Explicit AND
|
|
29
30
|
|
|
30
|
-
Use `and` array
|
|
31
|
+
Use an `and` array to group conditions explicitly.
|
|
31
32
|
|
|
32
33
|
```typescript
|
|
33
34
|
{
|
|
@@ -35,19 +36,16 @@ Use `and` array for explicit AND conditions:
|
|
|
35
36
|
and: [
|
|
36
37
|
{ status: 'active' },
|
|
37
38
|
{ role: { in: ['admin', 'moderator'] } },
|
|
38
|
-
{ createdAt: { gte: new Date('2024-01-01') } },
|
|
39
39
|
]
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
// SQL: WHERE ("status" = 'active')
|
|
43
|
-
// AND ("role" IN ('admin', 'moderator'))
|
|
44
|
-
// AND ("created_at" >= '2024-01-01')
|
|
42
|
+
// SQL: WHERE ("status" = 'active') AND ("role" IN ('admin', 'moderator'))
|
|
45
43
|
```
|
|
46
44
|
|
|
47
45
|
|
|
48
|
-
## OR
|
|
46
|
+
## OR
|
|
49
47
|
|
|
50
|
-
Use `or` array
|
|
48
|
+
Use an `or` array to match any of several conditions.
|
|
51
49
|
|
|
52
50
|
```typescript
|
|
53
51
|
{
|
|
@@ -55,152 +53,96 @@ Use `or` array for OR conditions:
|
|
|
55
53
|
or: [
|
|
56
54
|
{ status: 'active' },
|
|
57
55
|
{ isPublished: true },
|
|
58
|
-
{ featured: true },
|
|
59
56
|
]
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
|
-
// SQL: WHERE ("status" = 'active')
|
|
63
|
-
// OR ("is_published" = true)
|
|
64
|
-
// OR ("featured" = true)
|
|
59
|
+
// SQL: WHERE ("status" = 'active') OR ("is_published" = true)
|
|
65
60
|
```
|
|
66
61
|
|
|
67
62
|
|
|
68
|
-
##
|
|
63
|
+
## NOT
|
|
69
64
|
|
|
70
|
-
|
|
65
|
+
`not` negates whatever it wraps: a bare value negates `eq`, a nested operator object negates that operator.
|
|
71
66
|
|
|
72
67
|
```typescript
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
where: {
|
|
76
|
-
or: [
|
|
77
|
-
{
|
|
78
|
-
and: [
|
|
79
|
-
{ status: 'active' },
|
|
80
|
-
{ verified: true },
|
|
81
|
-
]
|
|
82
|
-
},
|
|
83
|
-
{ role: 'admin' },
|
|
84
|
-
]
|
|
85
|
-
}
|
|
86
|
-
}
|
|
68
|
+
{ where: { status: { not: 'archived' } } }
|
|
69
|
+
// SQL: WHERE NOT ("status" = 'archived')
|
|
87
70
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
where: {
|
|
91
|
-
status: 'active',
|
|
92
|
-
or: [
|
|
93
|
-
{ role: 'admin' },
|
|
94
|
-
{ role: 'moderator' },
|
|
95
|
-
]
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// Equivalent to:
|
|
99
|
-
{
|
|
100
|
-
where: {
|
|
101
|
-
status: 'active',
|
|
102
|
-
role: { in: ['admin', 'moderator'] },
|
|
103
|
-
}
|
|
104
|
-
}
|
|
71
|
+
{ where: { views: { not: { gt: 100 } } } }
|
|
72
|
+
// SQL: WHERE NOT ("views" > 100)
|
|
105
73
|
```
|
|
106
74
|
|
|
75
|
+
> [!NOTE]
|
|
76
|
+
> `not` is supported on the PostgreSQL connector. The dedicated negation operators below are often clearer for a single condition.
|
|
107
77
|
|
|
108
|
-
## Empty Groups
|
|
109
78
|
|
|
110
|
-
|
|
79
|
+
## Dedicated Negation Operators
|
|
111
80
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
{
|
|
115
|
-
|
|
81
|
+
| Operator | Example | SQL |
|
|
82
|
+
|----------|---------|-----|
|
|
83
|
+
| `ne` / `neq` | `{ status: { ne: 'deleted' } }` | `!=` |
|
|
84
|
+
| `nin` | `{ status: { nin: ['deleted', 'banned'] } }` | `NOT IN` |
|
|
85
|
+
| `nlike` | `{ email: { nlike: '%@test.com' } }` | `NOT LIKE` |
|
|
86
|
+
| `nilike` | `{ email: { nilike: '%@test.com' } }` | `NOT ILIKE` |
|
|
87
|
+
| `isn` / `ne: null` | `{ verifiedAt: { isn: null } }` | `IS NOT NULL` |
|
|
88
|
+
| `notBetween` | `{ score: { notBetween: [40, 60] } }` | `NOT BETWEEN` |
|
|
116
89
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// SQL: WHERE false
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
This matters when the array is built from a caller-supplied list, e.g. `{ or: permittedOrgIds.map(id => ({ orgId: id })) }`: an empty permission list must return zero rows, not every row, so `or: []` matching nothing is the safe default.
|
|
90
|
+
> [!NOTE]
|
|
91
|
+
> `ne`/`neq`/`nin` follow SQL three-valued logic - a row whose field is `NULL` never matches them (`NULL <> value` is UNKNOWN, not TRUE). Use `exists`/`notExists` or an explicit `{ field: null }` branch to include NULL rows.
|
|
123
92
|
|
|
124
93
|
|
|
125
|
-
##
|
|
94
|
+
## Nested AND/OR
|
|
126
95
|
|
|
127
|
-
|
|
96
|
+
Combine AND and OR for multi-level logic.
|
|
128
97
|
|
|
129
98
|
```typescript
|
|
130
|
-
//
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{
|
|
136
|
-
|
|
99
|
+
// (status = 'active' AND verified = true) OR (role = 'admin')
|
|
100
|
+
{
|
|
101
|
+
where: {
|
|
102
|
+
or: [
|
|
103
|
+
{ and: [{ status: 'active' }, { verified: true }] },
|
|
104
|
+
{ role: 'admin' },
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
137
108
|
```
|
|
138
109
|
|
|
139
|
-
|
|
110
|
+
A top-level key alongside `or` ANDs with it - these two filters are equivalent:
|
|
140
111
|
|
|
141
112
|
```typescript
|
|
142
|
-
//
|
|
143
|
-
{ where: { status: {
|
|
144
|
-
{ where: { status: { neq: 'deleted' } } }
|
|
113
|
+
// status = 'active' AND (role = 'admin' OR role = 'moderator')
|
|
114
|
+
{ where: { status: 'active', or: [{ role: 'admin' }, { role: 'moderator' }] } }
|
|
145
115
|
|
|
146
|
-
//
|
|
147
|
-
{ where: { status: {
|
|
116
|
+
// Same result, using in instead
|
|
117
|
+
{ where: { status: 'active', role: { in: ['admin', 'moderator'] } } }
|
|
118
|
+
```
|
|
148
119
|
|
|
149
|
-
// NOT LIKE
|
|
150
|
-
{ where: { email: { nlike: '%@test.com' } } }
|
|
151
120
|
|
|
152
|
-
|
|
153
|
-
{ where: { email: { nilike: '%@test.com' } } }
|
|
121
|
+
## Empty Groups
|
|
154
122
|
|
|
155
|
-
|
|
156
|
-
{ where: { verifiedAt: { isn: null } } }
|
|
157
|
-
{ where: { verifiedAt: { ne: null } } }
|
|
123
|
+
An empty `and`/`or` array is not a no-op - each resolves to what the operator means with zero conditions.
|
|
158
124
|
|
|
159
|
-
|
|
160
|
-
{ where: {
|
|
125
|
+
```typescript
|
|
126
|
+
{ where: { and: [] } }
|
|
127
|
+
// Vacuously TRUE - dropped from the query entirely, no condition added
|
|
128
|
+
|
|
129
|
+
{ where: { or: [] } }
|
|
130
|
+
// SQL: WHERE false - vacuously FALSE, matches nothing
|
|
161
131
|
```
|
|
162
132
|
|
|
163
133
|
> [!NOTE]
|
|
164
|
-
>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
## Complex Example
|
|
134
|
+
> This matters for a caller-built list, e.g. `{ or: permittedOrgIds.map(id => ({ orgId: id })) }`: an empty permission list must return zero rows, so `or: []` matching nothing is the safe default.
|
|
168
135
|
|
|
169
|
-
```typescript
|
|
170
|
-
// Find active products that are either:
|
|
171
|
-
// - Featured with high rating, OR
|
|
172
|
-
// - On sale with good stock
|
|
173
|
-
{
|
|
174
|
-
where: {
|
|
175
|
-
status: 'active',
|
|
176
|
-
deletedAt: { is: null },
|
|
177
|
-
or: [
|
|
178
|
-
{
|
|
179
|
-
and: [
|
|
180
|
-
{ featured: true },
|
|
181
|
-
{ rating: { gte: 4.5 } }
|
|
182
|
-
]
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
and: [
|
|
186
|
-
{ onSale: true },
|
|
187
|
-
{ stock: { gte: 10 } }
|
|
188
|
-
]
|
|
189
|
-
}
|
|
190
|
-
]
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
136
|
|
|
195
137
|
## See also
|
|
196
138
|
|
|
197
139
|
- [Filter System Overview](./) - the `filter` shape and the full `where` operator table
|
|
198
|
-
- [Null Operators](./null-operators) - `isn`, one of the dedicated negation operators
|
|
140
|
+
- [Null Operators](./null-operators) - `isn`, one of the dedicated negation operators above
|
|
199
141
|
- [Comparison Operators](./comparison-operators) - `ne`/`neq`, the other dedicated negation operators
|
|
200
142
|
- [Quick Reference](./quick-reference) - every operator, one line each
|
|
201
143
|
|
|
202
144
|
**Files:**
|
|
203
145
|
|
|
204
|
-
- [`packages/core/src/connectors/
|
|
205
|
-
- [`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
|
|
206
|
-
- [`packages/
|
|
146
|
+
- [`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`, `buildLogicalGroupCondition`/`buildNotCondition`
|
|
147
|
+
- [`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
|
|
148
|
+
- [`packages/filter/src/common/operators.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/filter/src/common/operators.ts) - `QueryOperators` constants
|