@venizia/ignis-docs 0.2.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/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +24 -13
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +8 -4
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +247 -153
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +109 -323
- package/content/extensions/components/authentication/api.md +489 -602
- package/content/extensions/components/authentication/errors.md +135 -498
- package/content/extensions/components/authentication/index.md +89 -801
- package/content/extensions/components/authentication/usage.md +231 -955
- package/content/extensions/components/authorization/api.md +991 -644
- package/content/extensions/components/authorization/errors.md +204 -208
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +88 -795
- package/content/extensions/components/authorization/usage.md +219 -528
- package/content/extensions/components/health-check.md +77 -243
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +553 -285
- package/content/extensions/components/mail/errors.md +76 -62
- package/content/extensions/components/mail/index.md +111 -463
- package/content/extensions/components/mail/usage.md +139 -173
- package/content/extensions/components/request-tracker.md +70 -173
- package/content/extensions/components/socket-io/api.md +462 -785
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +69 -372
- package/content/extensions/components/socket-io/usage.md +212 -105
- package/content/extensions/components/static-asset/api.md +461 -141
- package/content/extensions/components/static-asset/errors.md +121 -53
- package/content/extensions/components/static-asset/index.md +84 -606
- package/content/extensions/components/static-asset/usage.md +184 -299
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +311 -404
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +75 -407
- package/content/extensions/components/websocket/usage.md +120 -338
- package/content/extensions/helpers/cron/index.md +52 -160
- package/content/extensions/helpers/crypto/index.md +67 -480
- package/content/extensions/helpers/crypto/reference.md +528 -0
- package/content/extensions/helpers/env/index.md +64 -178
- package/content/extensions/helpers/error/index.md +286 -196
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +76 -550
- package/content/extensions/helpers/inversion/reference.md +530 -0
- package/content/extensions/helpers/kafka/admin.md +23 -3
- package/content/extensions/helpers/kafka/compile-binary.md +83 -52
- package/content/extensions/helpers/kafka/consumer.md +65 -28
- package/content/extensions/helpers/kafka/examples.md +46 -253
- package/content/extensions/helpers/kafka/index.md +55 -617
- package/content/extensions/helpers/kafka/producer.md +156 -22
- package/content/extensions/helpers/kafka/schema-registry.md +59 -79
- package/content/extensions/helpers/logger/hf-logger.md +220 -0
- package/content/extensions/helpers/logger/index.md +78 -552
- package/content/extensions/helpers/logger/pino.md +105 -0
- package/content/extensions/helpers/logger/reference.md +937 -0
- package/content/extensions/helpers/network/api.md +276 -195
- package/content/extensions/helpers/network/index.md +87 -524
- package/content/extensions/helpers/queue/index.md +80 -897
- package/content/extensions/helpers/queue/reference.md +494 -0
- package/content/extensions/helpers/redis/index.md +86 -640
- package/content/extensions/helpers/redis/reference.md +784 -0
- package/content/extensions/helpers/secrets/index.md +136 -0
- package/content/extensions/helpers/socket-io/api.md +325 -204
- package/content/extensions/helpers/socket-io/index.md +79 -429
- package/content/extensions/helpers/storage/api.md +584 -464
- package/content/extensions/helpers/storage/index.md +80 -573
- package/content/extensions/helpers/types/index.md +75 -495
- package/content/extensions/helpers/types/reference.md +689 -0
- package/content/extensions/helpers/uid/index.md +176 -189
- package/content/extensions/helpers/websocket/api.md +366 -214
- package/content/extensions/helpers/websocket/index.md +68 -503
- package/content/extensions/helpers/worker-thread/index.md +62 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- 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/models.md +1 -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 +28 -16
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- 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 +5 -4
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +8 -7
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +64 -22
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +600 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +25 -46
- package/content/references/base/filter-system/application-usage.md +95 -123
- package/content/references/base/filter-system/array-operators.md +33 -43
- package/content/references/base/filter-system/comparison-operators.md +55 -63
- package/content/references/base/filter-system/default-filter.md +144 -350
- package/content/references/base/filter-system/fields-order-pagination.md +116 -148
- package/content/references/base/filter-system/index.md +114 -253
- package/content/references/base/filter-system/json-filtering.md +52 -181
- package/content/references/base/filter-system/list-operators.md +28 -44
- package/content/references/base/filter-system/logical-operators.md +69 -114
- package/content/references/base/filter-system/null-operators.md +41 -98
- package/content/references/base/filter-system/pattern-matching.md +48 -51
- package/content/references/base/filter-system/quick-reference.md +93 -196
- package/content/references/base/filter-system/range-operators.md +22 -38
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +173 -232
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +43 -28
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +81 -1452
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +281 -419
- package/content/references/base/repositories/index.md +84 -644
- package/content/references/base/repositories/mixins.md +25 -21
- package/content/references/base/repositories/relations.md +194 -375
- package/content/references/base/repositories/soft-deletable.md +67 -55
- package/content/references/base/secrets.md +267 -0
- package/content/references/base/services.md +8 -6
- package/content/references/configuration/environment-variables.md +73 -21
- package/content/references/configuration/index.md +51 -31
- package/content/references/index.md +1 -1
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +6 -2
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +81 -61
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +58 -218
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- 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 +24 -23
|
@@ -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,206 +16,107 @@ 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 |
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
## Path Validation & Security
|
|
92
|
+
> Ordering uses `#>` (returns JSONB, preserves native type ordering); `where` uses `#>>` (returns text) instead.
|
|
182
93
|
|
|
183
|
-
Path components are validated against the pattern `/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\d+$/` to prevent SQL injection:
|
|
184
94
|
|
|
185
|
-
|
|
186
|
-
// Valid paths
|
|
187
|
-
'metadata.fieldName'
|
|
188
|
-
'metadata.nested.deep.value'
|
|
189
|
-
'data.items[0]'
|
|
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
|
-
```
|
|
95
|
+
## Path Validation
|
|
198
96
|
|
|
199
|
-
|
|
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'
|
|
206
|
-
```
|
|
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.
|
|
207
98
|
|
|
208
|
-
The column referenced by the first path segment (before the first `.` or `[`) must be a `json` or `jsonb` column type. Using a JSON path on a non-JSON column throws an error.
|
|
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
99
|
```typescript
|
|
230
|
-
//
|
|
231
|
-
'
|
|
232
|
-
|
|
233
|
-
//
|
|
234
|
-
'data.level1.level2.level3.level4.value'
|
|
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
|
|
235
104
|
```
|
|
236
105
|
|
|
106
|
+
> [!NOTE]
|
|
107
|
+
> A path on a non-JSON column also throws: `Column 'name' is not a JSON column`.
|
|
237
108
|
|
|
238
|
-
## Null-Safe JSON Paths
|
|
239
109
|
|
|
240
|
-
|
|
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
|
-
```
|
|
110
|
+
## See also
|
|
247
111
|
|
|
112
|
+
- [Filter System Overview](./) - the `filter` shape and the full `where` operator table
|
|
113
|
+
- [Fields, Order & Pagination](./fields-order-pagination) - JSON path ordering (`#>`, sorted by native JSONB type)
|
|
114
|
+
- [Pattern Matching](./pattern-matching) - `like`/`ilike`/`regexp` also work on a JSON path, with no numeric casting
|
|
115
|
+
- [Nested JSON Updates](../repositories/advanced.md#nested-json-updates) - writing to JSON paths
|
|
116
|
+
- [Quick Reference](./quick-reference) - every operator, one line each
|
|
248
117
|
|
|
249
|
-
|
|
118
|
+
**Files:**
|
|
250
119
|
|
|
251
|
-
- [
|
|
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,67 +6,51 @@ 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
|
-
|
|
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.
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|----------|----------|
|
|
40
|
-
| `{ in: [] }` (empty array) | Returns no rows (`WHERE false`) |
|
|
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' }` |
|
|
46
|
+
## See also
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
> where: {
|
|
49
|
-
> or: [
|
|
50
|
-
> { status: { nin: ['deleted'] } },
|
|
51
|
-
> { status: { is: null } }
|
|
52
|
-
> ]
|
|
53
|
-
> }
|
|
54
|
-
> ```
|
|
48
|
+
- [Filter System Overview](./) - the `filter` shape and the full `where` operator table
|
|
49
|
+
- [Array Operators](./array-operators) - `contains`/`containedBy`/`overlaps` match against array COLUMNS, not to be confused with `in`/`nin`
|
|
50
|
+
- [Quick Reference](./quick-reference) - every operator, one line each
|
|
55
51
|
|
|
52
|
+
**Files:**
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// For very large arrays (1000+ items), consider chunking
|
|
61
|
-
const allIds = getLargeIdList(); // 5000 IDs
|
|
62
|
-
|
|
63
|
-
const chunkSize = 500;
|
|
64
|
-
const results = [];
|
|
65
|
-
for (let i = 0; i < allIds.length; i += chunkSize) {
|
|
66
|
-
const chunk = allIds.slice(i, i + chunkSize);
|
|
67
|
-
const chunkResults = await repository.find({
|
|
68
|
-
filter: { where: { id: { in: chunk } } }
|
|
69
|
-
});
|
|
70
|
-
results.push(...chunkResults);
|
|
71
|
-
}
|
|
72
|
-
```
|
|
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,139 +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
|
|
|
63
|
+
## NOT
|
|
64
|
+
|
|
65
|
+
`not` negates whatever it wraps: a bare value negates `eq`, a nested operator object negates that operator.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
{ where: { status: { not: 'archived' } } }
|
|
69
|
+
// SQL: WHERE NOT ("status" = 'archived')
|
|
70
|
+
|
|
71
|
+
{ where: { views: { not: { gt: 100 } } } }
|
|
72
|
+
// SQL: WHERE NOT ("views" > 100)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
> [!NOTE]
|
|
76
|
+
> `not` is supported on the PostgreSQL connector. The dedicated negation operators below are often clearer for a single condition.
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## Dedicated Negation Operators
|
|
80
|
+
|
|
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` |
|
|
89
|
+
|
|
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.
|
|
92
|
+
|
|
93
|
+
|
|
68
94
|
## Nested AND/OR
|
|
69
95
|
|
|
70
|
-
Combine AND and OR for
|
|
96
|
+
Combine AND and OR for multi-level logic.
|
|
71
97
|
|
|
72
98
|
```typescript
|
|
73
99
|
// (status = 'active' AND verified = true) OR (role = 'admin')
|
|
74
100
|
{
|
|
75
101
|
where: {
|
|
76
102
|
or: [
|
|
77
|
-
{
|
|
78
|
-
and: [
|
|
79
|
-
{ status: 'active' },
|
|
80
|
-
{ verified: true },
|
|
81
|
-
]
|
|
82
|
-
},
|
|
103
|
+
{ and: [{ status: 'active' }, { verified: true }] },
|
|
83
104
|
{ role: 'admin' },
|
|
84
105
|
]
|
|
85
106
|
}
|
|
86
107
|
}
|
|
87
|
-
|
|
88
|
-
// status = 'active' AND (role = 'admin' OR role = 'moderator')
|
|
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
|
-
}
|
|
105
108
|
```
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
## Empty Groups
|
|
109
|
-
|
|
110
|
-
An empty `and`/`or` array is not a no-op -- each empty case resolves to what the operator means with zero conditions:
|
|
110
|
+
A top-level key alongside `or` ANDs with it - these two filters are equivalent:
|
|
111
111
|
|
|
112
112
|
```typescript
|
|
113
|
-
//
|
|
114
|
-
{ where: {
|
|
115
|
-
// SQL: (no condition added)
|
|
113
|
+
// status = 'active' AND (role = 'admin' OR role = 'moderator')
|
|
114
|
+
{ where: { status: 'active', or: [{ role: 'admin' }, { role: 'moderator' }] } }
|
|
116
115
|
|
|
117
|
-
//
|
|
118
|
-
{ where: {
|
|
119
|
-
// SQL: WHERE false
|
|
116
|
+
// Same result, using in instead
|
|
117
|
+
{ where: { status: 'active', role: { in: ['admin', 'moderator'] } } }
|
|
120
118
|
```
|
|
121
119
|
|
|
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.
|
|
123
|
-
|
|
124
120
|
|
|
125
|
-
##
|
|
121
|
+
## Empty Groups
|
|
126
122
|
|
|
127
|
-
|
|
123
|
+
An empty `and`/`or` array is not a no-op - each resolves to what the operator means with zero conditions.
|
|
128
124
|
|
|
129
125
|
```typescript
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// SQL: WHERE NOT ("status" = 'archived')
|
|
126
|
+
{ where: { and: [] } }
|
|
127
|
+
// Vacuously TRUE - dropped from the query entirely, no condition added
|
|
133
128
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// SQL: WHERE NOT ("views" > 100)
|
|
129
|
+
{ where: { or: [] } }
|
|
130
|
+
// SQL: WHERE false - vacuously FALSE, matches nothing
|
|
137
131
|
```
|
|
138
132
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
// NOT equal
|
|
143
|
-
{ where: { status: { ne: 'deleted' } } }
|
|
144
|
-
{ where: { status: { neq: 'deleted' } } }
|
|
145
|
-
|
|
146
|
-
// NOT IN
|
|
147
|
-
{ where: { status: { nin: ['deleted', 'banned'] } } }
|
|
148
|
-
|
|
149
|
-
// NOT LIKE
|
|
150
|
-
{ where: { email: { nlike: '%@test.com' } } }
|
|
151
|
-
|
|
152
|
-
// NOT ILIKE
|
|
153
|
-
{ where: { email: { nilike: '%@test.com' } } }
|
|
154
|
-
|
|
155
|
-
// IS NOT NULL
|
|
156
|
-
{ where: { verifiedAt: { isn: null } } }
|
|
157
|
-
{ where: { verifiedAt: { ne: null } } }
|
|
133
|
+
> [!NOTE]
|
|
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.
|
|
158
135
|
|
|
159
|
-
// NOT BETWEEN
|
|
160
|
-
{ where: { score: { notBetween: [40, 60] } } }
|
|
161
|
-
```
|
|
162
136
|
|
|
163
|
-
|
|
164
|
-
> `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 when you need NULL rows in the result.
|
|
137
|
+
## See also
|
|
165
138
|
|
|
139
|
+
- [Filter System Overview](./) - the `filter` shape and the full `where` operator table
|
|
140
|
+
- [Null Operators](./null-operators) - `isn`, one of the dedicated negation operators above
|
|
141
|
+
- [Comparison Operators](./comparison-operators) - `ne`/`neq`, the other dedicated negation operators
|
|
142
|
+
- [Quick Reference](./quick-reference) - every operator, one line each
|
|
166
143
|
|
|
167
|
-
|
|
144
|
+
**Files:**
|
|
168
145
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
```
|
|
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
|