@venizia/ignis-docs 0.0.7-2 → 0.0.8-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp-server/common/paths.d.ts +4 -2
- package/dist/mcp-server/common/paths.d.ts.map +1 -1
- package/dist/mcp-server/common/paths.js +8 -6
- package/dist/mcp-server/common/paths.js.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.js +7 -7
- package/dist/mcp-server/tools/docs/get-document-metadata.tool.js +3 -3
- package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-package-overview.tool.js +1 -1
- package/package.json +1 -1
- package/wiki/best-practices/api-usage-examples.md +9 -9
- package/wiki/best-practices/architectural-patterns.md +19 -3
- package/wiki/best-practices/architecture-decisions.md +6 -6
- package/wiki/best-practices/code-style-standards/advanced-patterns.md +1 -1
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/code-style-standards/function-patterns.md +2 -2
- package/wiki/best-practices/code-style-standards/index.md +2 -2
- package/wiki/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/wiki/best-practices/code-style-standards/route-definitions.md +4 -4
- package/wiki/best-practices/data-modeling.md +1 -1
- package/wiki/best-practices/deployment-strategies.md +1 -1
- package/wiki/best-practices/error-handling.md +2 -2
- package/wiki/best-practices/performance-optimization.md +3 -3
- package/wiki/best-practices/security-guidelines.md +2 -2
- package/wiki/best-practices/troubleshooting-tips.md +1 -1
- package/wiki/{references → extensions}/components/authentication/api.md +12 -20
- package/wiki/{references → extensions}/components/authentication/errors.md +1 -1
- package/wiki/{references → extensions}/components/authentication/index.md +5 -8
- package/wiki/{references → extensions}/components/authentication/usage.md +20 -36
- package/wiki/{references → extensions}/components/authorization/api.md +62 -13
- package/wiki/{references → extensions}/components/authorization/errors.md +12 -7
- package/wiki/{references → extensions}/components/authorization/index.md +93 -6
- package/wiki/{references → extensions}/components/authorization/usage.md +42 -4
- package/wiki/{references → extensions}/components/health-check.md +5 -4
- package/wiki/{references → extensions}/components/index.md +2 -0
- package/wiki/{references → extensions}/components/mail/index.md +1 -1
- package/wiki/{references → extensions}/components/request-tracker.md +1 -1
- package/wiki/{references → extensions}/components/socket-io/api.md +2 -2
- package/wiki/{references → extensions}/components/socket-io/errors.md +2 -0
- package/wiki/{references → extensions}/components/socket-io/index.md +24 -20
- package/wiki/{references → extensions}/components/socket-io/usage.md +2 -2
- package/wiki/{references → extensions}/components/static-asset/api.md +14 -15
- package/wiki/{references → extensions}/components/static-asset/errors.md +3 -1
- package/wiki/{references → extensions}/components/static-asset/index.md +158 -89
- package/wiki/{references → extensions}/components/static-asset/usage.md +8 -5
- package/wiki/{references → extensions}/components/swagger.md +3 -3
- package/wiki/{references → extensions}/components/template/index.md +4 -4
- package/wiki/{references → extensions}/components/template/setup-page.md +1 -1
- package/wiki/{references → extensions}/components/template/single-page.md +1 -1
- package/wiki/{references → extensions}/components/websocket/api.md +7 -6
- package/wiki/{references → extensions}/components/websocket/errors.md +17 -3
- package/wiki/{references → extensions}/components/websocket/index.md +17 -11
- package/wiki/{references → extensions}/components/websocket/usage.md +2 -2
- package/wiki/{references → extensions}/helpers/crypto/index.md +1 -1
- package/wiki/{references → extensions}/helpers/env/index.md +9 -5
- package/wiki/{references → extensions}/helpers/error/index.md +2 -7
- package/wiki/{references → extensions}/helpers/index.md +18 -6
- package/wiki/{references → extensions}/helpers/kafka/admin.md +33 -16
- package/wiki/extensions/helpers/kafka/consumer.md +384 -0
- package/wiki/extensions/helpers/kafka/examples.md +361 -0
- package/wiki/extensions/helpers/kafka/index.md +639 -0
- package/wiki/{references → extensions}/helpers/kafka/producer.md +100 -96
- package/wiki/extensions/helpers/kafka/schema-registry.md +214 -0
- package/wiki/{references → extensions}/helpers/logger/index.md +2 -2
- package/wiki/{references → extensions}/helpers/queue/index.md +400 -4
- package/wiki/{references → extensions}/helpers/storage/api.md +170 -10
- package/wiki/{references → extensions}/helpers/storage/index.md +44 -8
- package/wiki/{references → extensions}/helpers/template/index.md +1 -1
- package/wiki/{references → extensions}/helpers/testing/index.md +4 -4
- package/wiki/{references → extensions}/helpers/types/index.md +63 -16
- package/wiki/{references → extensions}/helpers/websocket/index.md +1 -1
- package/wiki/extensions/index.md +48 -0
- package/wiki/guides/core-concepts/application/bootstrapping.md +55 -37
- package/wiki/guides/core-concepts/application/index.md +95 -35
- package/wiki/guides/core-concepts/components-guide.md +23 -19
- package/wiki/guides/core-concepts/components.md +34 -10
- package/wiki/guides/core-concepts/dependency-injection.md +99 -34
- package/wiki/guides/core-concepts/grpc-controllers.md +295 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +27 -8
- package/wiki/guides/core-concepts/persistent/models.md +43 -1
- package/wiki/guides/core-concepts/persistent/repositories.md +75 -8
- package/wiki/guides/core-concepts/persistent/transactions.md +38 -8
- package/wiki/guides/core-concepts/{controllers.md → rest-controllers.md} +30 -33
- package/wiki/guides/core-concepts/services.md +19 -5
- package/wiki/guides/get-started/5-minute-quickstart.md +6 -7
- package/wiki/guides/get-started/philosophy.md +1 -1
- package/wiki/guides/index.md +2 -2
- package/wiki/guides/reference/glossary.md +7 -7
- package/wiki/guides/reference/mcp-docs-server.md +1 -1
- package/wiki/guides/tutorials/building-a-crud-api.md +2 -2
- package/wiki/guides/tutorials/complete-installation.md +17 -14
- package/wiki/guides/tutorials/ecommerce-api.md +18 -18
- package/wiki/guides/tutorials/realtime-chat.md +8 -8
- package/wiki/guides/tutorials/testing.md +2 -2
- package/wiki/index.md +4 -3
- package/wiki/references/base/application.md +341 -21
- package/wiki/references/base/bootstrapping.md +43 -13
- package/wiki/references/base/components.md +259 -8
- package/wiki/references/base/controllers.md +556 -253
- package/wiki/references/base/datasources.md +159 -79
- package/wiki/references/base/dependency-injection.md +299 -48
- package/wiki/references/base/filter-system/application-usage.md +18 -2
- package/wiki/references/base/filter-system/array-operators.md +14 -6
- package/wiki/references/base/filter-system/comparison-operators.md +9 -3
- package/wiki/references/base/filter-system/default-filter.md +28 -3
- package/wiki/references/base/filter-system/fields-order-pagination.md +17 -13
- package/wiki/references/base/filter-system/index.md +169 -11
- package/wiki/references/base/filter-system/json-filtering.md +51 -18
- package/wiki/references/base/filter-system/list-operators.md +4 -3
- package/wiki/references/base/filter-system/logical-operators.md +7 -2
- package/wiki/references/base/filter-system/null-operators.md +50 -0
- package/wiki/references/base/filter-system/quick-reference.md +82 -243
- package/wiki/references/base/filter-system/range-operators.md +7 -1
- package/wiki/references/base/filter-system/tips.md +34 -7
- package/wiki/references/base/filter-system/use-cases.md +6 -5
- package/wiki/references/base/grpc-controllers.md +984 -0
- package/wiki/references/base/index.md +32 -24
- package/wiki/references/base/middleware.md +347 -0
- package/wiki/references/base/models.md +390 -46
- package/wiki/references/base/providers.md +14 -14
- package/wiki/references/base/repositories/advanced.md +84 -69
- package/wiki/references/base/repositories/index.md +447 -12
- package/wiki/references/base/repositories/mixins.md +103 -98
- package/wiki/references/base/repositories/relations.md +129 -45
- package/wiki/references/base/repositories/soft-deletable.md +104 -23
- package/wiki/references/base/services.md +94 -14
- package/wiki/references/index.md +12 -10
- package/wiki/references/quick-reference.md +98 -65
- package/wiki/references/utilities/crypto.md +21 -4
- package/wiki/references/utilities/date.md +25 -7
- package/wiki/references/utilities/index.md +26 -24
- package/wiki/references/utilities/jsx.md +54 -54
- package/wiki/references/utilities/module.md +8 -6
- package/wiki/references/utilities/parse.md +16 -9
- package/wiki/references/utilities/performance.md +22 -7
- package/wiki/references/utilities/promise.md +19 -16
- package/wiki/references/utilities/request.md +48 -26
- package/wiki/references/utilities/schema.md +69 -6
- package/wiki/references/utilities/statuses.md +131 -140
- package/wiki/references/helpers/kafka/consumer.md +0 -473
- package/wiki/references/helpers/kafka/examples.md +0 -234
- package/wiki/references/helpers/kafka/index.md +0 -482
- /package/wiki/{references → extensions}/components/mail/api.md +0 -0
- /package/wiki/{references → extensions}/components/mail/errors.md +0 -0
- /package/wiki/{references → extensions}/components/mail/usage.md +0 -0
- /package/wiki/{references → extensions}/components/template/api-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/errors-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/usage-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/cron/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/inversion/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/redis/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/template/single-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/uid/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/websocket/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/worker-thread/index.md +0 -0
- /package/wiki/{references → extensions}/src-details/mcp-server.md +0 -0
|
@@ -28,21 +28,17 @@ await repo.find({
|
|
|
28
28
|
### Object Format
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
|
-
// Include specific fields
|
|
31
|
+
// Include specific fields (only keys with `true` are selected)
|
|
32
32
|
await repo.find({
|
|
33
33
|
filter: {
|
|
34
34
|
fields: { id: true, email: true, name: true }
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
|
-
|
|
38
|
-
// Exclude specific fields
|
|
39
|
-
await repo.find({
|
|
40
|
-
filter: {
|
|
41
|
-
fields: { password: false, secret: false }
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
37
|
```
|
|
45
38
|
|
|
39
|
+
> [!NOTE]
|
|
40
|
+
> The object format only supports inclusion (`true` values). Keys set to `false` are simply ignored -- they do not exclude fields. To select specific fields, list the ones you want with `true` or use the array format.
|
|
41
|
+
|
|
46
42
|
|
|
47
43
|
## Ordering
|
|
48
44
|
|
|
@@ -65,6 +61,14 @@ await repo.find({
|
|
|
65
61
|
});
|
|
66
62
|
```
|
|
67
63
|
|
|
64
|
+
### Valid Directions
|
|
65
|
+
|
|
66
|
+
Only `ASC` and `DESC` (case-insensitive) are accepted. Invalid directions throw an error:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Error: Invalid direction: 'RANDOM' | Expected: 'ASC' or 'DESC'
|
|
70
|
+
```
|
|
71
|
+
|
|
68
72
|
### JSON Path Ordering
|
|
69
73
|
|
|
70
74
|
Order by nested fields in JSON columns:
|
|
@@ -96,10 +100,10 @@ await repo.find({
|
|
|
96
100
|
|
|
97
101
|
### Limit and Skip/Offset
|
|
98
102
|
|
|
99
|
-
Both `skip` and `offset` are supported as aliases
|
|
103
|
+
Both `skip` and `offset` are supported as aliases -- they both map to the SQL `OFFSET` clause. When both are provided, `skip` takes precedence.
|
|
100
104
|
|
|
101
105
|
```typescript
|
|
102
|
-
// First 10 results
|
|
106
|
+
// First 10 results (default limit is 10)
|
|
103
107
|
await repo.find({
|
|
104
108
|
filter: { limit: 10 }
|
|
105
109
|
});
|
|
@@ -126,7 +130,7 @@ await repo.find({
|
|
|
126
130
|
```
|
|
127
131
|
|
|
128
132
|
> [!TIP]
|
|
129
|
-
> Always use `limit` for public-facing endpoints to prevent memory exhaustion.
|
|
133
|
+
> Always use `limit` for public-facing endpoints to prevent memory exhaustion. The default limit is 10 if not specified.
|
|
130
134
|
|
|
131
135
|
### Pagination Helper
|
|
132
136
|
|
|
@@ -186,7 +190,7 @@ const contentRange = data.length > 0
|
|
|
186
190
|
: `records */${range.total}`;
|
|
187
191
|
|
|
188
192
|
res.setHeader('Content-Range', contentRange);
|
|
189
|
-
//
|
|
193
|
+
// -> "records 20-29/100"
|
|
190
194
|
```
|
|
191
195
|
|
|
192
196
|
### TDataRange Type
|
|
@@ -242,5 +246,5 @@ const { data, range } = await repo.find({
|
|
|
242
246
|
});
|
|
243
247
|
|
|
244
248
|
console.log(`Showing ${range.start}-${range.end} of ${range.total}`);
|
|
245
|
-
//
|
|
249
|
+
// -> "Showing 0-19 of 150"
|
|
246
250
|
```
|
|
@@ -45,17 +45,17 @@ Before reading this document, you should understand:
|
|
|
45
45
|
|
|
46
46
|
## Filter Structure
|
|
47
47
|
|
|
48
|
-
The `
|
|
48
|
+
The `TFilter<T>` object is the core mechanism for querying data in Ignis. It provides a structured way to express complex queries without writing raw SQL.
|
|
49
49
|
|
|
50
50
|
```typescript
|
|
51
51
|
type TFilter<T> = {
|
|
52
52
|
where?: TWhere<T>; // Query conditions (SQL WHERE)
|
|
53
53
|
fields?: TFields<T>; // Column selection (SQL SELECT)
|
|
54
|
-
order?: string[];
|
|
55
|
-
limit?: number; // Max results (SQL LIMIT)
|
|
54
|
+
order?: string[]; // Sorting (SQL ORDER BY)
|
|
55
|
+
limit?: number; // Max results (SQL LIMIT, default: 10)
|
|
56
56
|
skip?: number; // Pagination offset (SQL OFFSET)
|
|
57
57
|
offset?: number; // Alias for skip
|
|
58
|
-
include?: TInclusion[]; // Related data (
|
|
58
|
+
include?: TInclusion[]; // Related data (Drizzle relational queries)
|
|
59
59
|
};
|
|
60
60
|
```
|
|
61
61
|
|
|
@@ -69,7 +69,7 @@ type TFilter<T> = {
|
|
|
69
69
|
| `order` | `ORDER BY` | Sort results |
|
|
70
70
|
| `limit` | `LIMIT` | Restrict number of results |
|
|
71
71
|
| `skip` / `offset` | `OFFSET` | Skip rows for pagination |
|
|
72
|
-
| `include` |
|
|
72
|
+
| `include` | Separate relational query | Include related data |
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
## Basic Example
|
|
@@ -86,7 +86,7 @@ const filter = {
|
|
|
86
86
|
|
|
87
87
|
// Equivalent SQL
|
|
88
88
|
// SELECT "id", "name", "email"
|
|
89
|
-
// FROM "
|
|
89
|
+
// FROM "users"
|
|
90
90
|
// WHERE "status" = 'active' AND "role" = 'admin'
|
|
91
91
|
// ORDER BY "created_at" DESC
|
|
92
92
|
// LIMIT 10 OFFSET 0
|
|
@@ -98,20 +98,23 @@ const filter = {
|
|
|
98
98
|
| Want to... | Filter Syntax |
|
|
99
99
|
|------------|---------------|
|
|
100
100
|
| Equals | `{ field: value }` or `{ field: { eq: value } }` |
|
|
101
|
-
| Not equals | `{ field: { ne: value } }` |
|
|
101
|
+
| Not equals | `{ field: { ne: value } }` or `{ field: { neq: value } }` |
|
|
102
102
|
| Greater than | `{ field: { gt: value } }` |
|
|
103
103
|
| Greater or equal | `{ field: { gte: value } }` |
|
|
104
104
|
| Less than | `{ field: { lt: value } }` |
|
|
105
105
|
| Less or equal | `{ field: { lte: value } }` |
|
|
106
106
|
| Is null | `{ field: null }` or `{ field: { is: null } }` |
|
|
107
|
-
| Is not null | `{ field: { isn: null } }` |
|
|
108
|
-
| In list | `{ field: { in: [a, b, c] } }` |
|
|
107
|
+
| Is not null | `{ field: { isn: null } }` or `{ field: { ne: null } }` |
|
|
108
|
+
| In list | `{ field: { in: [a, b, c] } }` or `{ field: { inq: [a, b, c] } }` |
|
|
109
109
|
| Not in list | `{ field: { nin: [a, b, c] } }` |
|
|
110
110
|
| Range | `{ field: { between: [min, max] } }` |
|
|
111
111
|
| Outside range | `{ field: { notBetween: [min, max] } }` |
|
|
112
112
|
| Contains pattern | `{ field: { like: '%pattern%' } }` |
|
|
113
|
+
| Not contains pattern | `{ field: { nlike: '%pattern%' } }` |
|
|
113
114
|
| Case-insensitive | `{ field: { ilike: '%pattern%' } }` |
|
|
115
|
+
| Not case-insensitive | `{ field: { nilike: '%pattern%' } }` |
|
|
114
116
|
| Regex match | `{ field: { regexp: '^pattern$' } }` |
|
|
117
|
+
| Case-insensitive regex | `{ field: { iregexp: '^pattern$' } }` |
|
|
115
118
|
| Array contains all | `{ arrayField: { contains: [a, b] } }` |
|
|
116
119
|
| Array is subset | `{ arrayField: { containedBy: [a, b, c] } }` |
|
|
117
120
|
| Array overlaps | `{ arrayField: { overlaps: [a, b] } }` |
|
|
@@ -121,7 +124,162 @@ const filter = {
|
|
|
121
124
|
| OR conditions | `{ or: [{ a: 1 }, { b: 2 }] }` |
|
|
122
125
|
| Include relation | `{ include: [{ relation: 'name' }] }` |
|
|
123
126
|
| Nested include | `{ include: [{ relation: 'a', scope: { include: [{ relation: 'b' }] } }] }` |
|
|
124
|
-
| Select fields | `{ fields: ['id', 'name'] }` |
|
|
127
|
+
| Select fields | `{ fields: ['id', 'name'] }` or `{ fields: { id: true, name: true } }` |
|
|
125
128
|
| Order by | `{ order: ['field DESC'] }` |
|
|
126
129
|
| Order by JSON | `{ order: ['jsonField.path DESC'] }` |
|
|
127
|
-
| Paginate | `{ limit: 10, skip: 20 }` |
|
|
130
|
+
| Paginate | `{ limit: 10, skip: 20 }` or `{ limit: 10, offset: 20 }` |
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
## Common Filter Patterns
|
|
134
|
+
|
|
135
|
+
### Multi-Condition Search
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
{
|
|
139
|
+
where: {
|
|
140
|
+
and: [
|
|
141
|
+
{ age: { gte: 18, lte: 65 } }, // Between 18 and 65
|
|
142
|
+
{ status: { in: ['active', 'pending'] } },
|
|
143
|
+
{ or: [
|
|
144
|
+
{ email: { ilike: '%@company.com' } },
|
|
145
|
+
{ role: 'admin' }
|
|
146
|
+
]}
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Text Search
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
{
|
|
156
|
+
where: {
|
|
157
|
+
or: [
|
|
158
|
+
{ name: { ilike: '%john%' } },
|
|
159
|
+
{ email: { ilike: '%john%' } },
|
|
160
|
+
{ username: { ilike: '%john%' } }
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Date Range
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
{
|
|
170
|
+
where: {
|
|
171
|
+
createdAt: {
|
|
172
|
+
gte: new Date('2024-01-01'),
|
|
173
|
+
lt: new Date('2024-02-01')
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Exclude Soft Deleted
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
{
|
|
183
|
+
where: {
|
|
184
|
+
and: [
|
|
185
|
+
{ isDeleted: false },
|
|
186
|
+
{ status: 'active' }
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Multi-Tenant Filtering
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
{
|
|
196
|
+
where: {
|
|
197
|
+
and: [
|
|
198
|
+
{ tenantId: currentTenantId },
|
|
199
|
+
{ isActive: true }
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
## Operator Precedence
|
|
207
|
+
|
|
208
|
+
When combining operators, IGNIS follows standard SQL precedence:
|
|
209
|
+
|
|
210
|
+
1. **AND** - Higher precedence
|
|
211
|
+
2. **OR** - Lower precedence
|
|
212
|
+
|
|
213
|
+
Use explicit nesting (via `and`/`or` arrays) for clarity:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// Clear precedence
|
|
217
|
+
{
|
|
218
|
+
where: {
|
|
219
|
+
and: [
|
|
220
|
+
{ status: 'active' },
|
|
221
|
+
{ or: [
|
|
222
|
+
{ role: 'admin' },
|
|
223
|
+
{ role: 'moderator' }
|
|
224
|
+
]}
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
## Performance Tips
|
|
232
|
+
|
|
233
|
+
1. **Index frequently filtered columns:**
|
|
234
|
+
```sql
|
|
235
|
+
CREATE INDEX idx_users_status ON users(status);
|
|
236
|
+
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
2. **Use `eq` instead of `like` when possible:**
|
|
240
|
+
```typescript
|
|
241
|
+
// Fast: Uses index
|
|
242
|
+
{ status: { eq: 'active' } }
|
|
243
|
+
|
|
244
|
+
// Slower: Full table scan
|
|
245
|
+
{ status: { like: 'active' } }
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
3. **Limit array contains operations:**
|
|
249
|
+
```typescript
|
|
250
|
+
// Better performance with smaller arrays
|
|
251
|
+
{ tags: { contains: ['typescript'] } } // Good
|
|
252
|
+
{ tags: { contains: ['tag1', 'tag2', /* ... 100 tags */] } } // Slow
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
4. **Use pagination for large result sets:**
|
|
256
|
+
```typescript
|
|
257
|
+
{
|
|
258
|
+
where: { isActive: true },
|
|
259
|
+
limit: 100,
|
|
260
|
+
skip: 0,
|
|
261
|
+
order: ['id ASC']
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
## See Also
|
|
267
|
+
|
|
268
|
+
- **Detailed Guides:**
|
|
269
|
+
- [Comparison Operators](./comparison-operators.md)
|
|
270
|
+
- [Logical Operators](./logical-operators.md)
|
|
271
|
+
- [Pattern Matching](./pattern-matching.md)
|
|
272
|
+
- [JSON Filtering](./json-filtering.md)
|
|
273
|
+
- [Array Operators](./array-operators.md)
|
|
274
|
+
|
|
275
|
+
- **Related References:**
|
|
276
|
+
- [Repositories](../repositories/) - Using filters in repository queries
|
|
277
|
+
- [Models](../models.md) - Defining model schemas
|
|
278
|
+
|
|
279
|
+
- **Usage Guides:**
|
|
280
|
+
- [Application Usage](./application-usage.md) - Filters in the full stack
|
|
281
|
+
- [Use Case Gallery](./use-cases.md) - Real-world examples
|
|
282
|
+
- [Pro Tips & Edge Cases](./tips.md) - Advanced patterns
|
|
283
|
+
|
|
284
|
+
- **Quick Reference:**
|
|
285
|
+
- [Main Quick Reference](/references/quick-reference.md) - All IGNIS APIs
|
|
@@ -11,15 +11,18 @@ Query nested fields within JSON/JSONB columns using dot notation. This is a Post
|
|
|
11
11
|
|
|
12
12
|
## Basic JSON Path Syntax
|
|
13
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
|
+
|
|
14
16
|
```typescript
|
|
15
17
|
// Column: metadata jsonb
|
|
16
18
|
// Data: { "user": { "id": 123, "role": "admin" }, "tags": ["urgent"] }
|
|
17
19
|
|
|
18
20
|
// Simple nested field
|
|
19
21
|
{ where: { 'metadata.user.id': 123 } }
|
|
20
|
-
// SQL: "metadata" #>> '{user,id}'
|
|
22
|
+
// SQL: CASE WHEN ("metadata" #>> '{user,id}') ~ '^-?[0-9]+(\.[0-9]+)?$'
|
|
23
|
+
// THEN ("metadata" #>> '{user,id}')::numeric ELSE NULL END = 123
|
|
21
24
|
|
|
22
|
-
//
|
|
25
|
+
// String field (no numeric casting)
|
|
23
26
|
{ where: { 'metadata.user.role': 'admin' } }
|
|
24
27
|
// SQL: "metadata" #>> '{user,role}' = 'admin'
|
|
25
28
|
|
|
@@ -60,18 +63,26 @@ All standard operators work with JSON paths:
|
|
|
60
63
|
// Between
|
|
61
64
|
{ where: { 'metadata.score': { between: [70, 90] } } }
|
|
62
65
|
|
|
63
|
-
// Pattern matching
|
|
66
|
+
// Pattern matching (text comparison, no numeric casting)
|
|
64
67
|
{ where: { 'metadata.level': { ilike: '%high%' } } }
|
|
65
68
|
// SQL: "metadata" #>> '{level}' ILIKE '%high%'
|
|
66
69
|
|
|
67
|
-
// IN operator
|
|
70
|
+
// IN operator (text comparison)
|
|
68
71
|
{ where: { 'metadata.status': { in: ['pending', 'review'] } } }
|
|
72
|
+
|
|
73
|
+
// Regex
|
|
74
|
+
{ 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'
|
|
69
80
|
```
|
|
70
81
|
|
|
71
82
|
|
|
72
83
|
## Safe Numeric Casting
|
|
73
84
|
|
|
74
|
-
|
|
85
|
+
When a numeric comparison operator (`gt`, `gte`, `lt`, `lte`, `between`, `notBetween`) is used with a JSON path, Ignis wraps the extraction in a safe CASE expression. This prevents database errors when JSON fields contain mixed types:
|
|
75
86
|
|
|
76
87
|
```typescript
|
|
77
88
|
// Data in database:
|
|
@@ -82,6 +93,12 @@ JSON fields may contain mixed types. Ignis uses safe casting to prevent database
|
|
|
82
93
|
// Query with numeric operator
|
|
83
94
|
{ where: { 'metadata.score': { gt: 50 } } }
|
|
84
95
|
|
|
96
|
+
// Generated SQL:
|
|
97
|
+
// CASE WHEN ("metadata" #>> '{score}') ~ '^-?[0-9]+(\.[0-9]+)?$'
|
|
98
|
+
// THEN ("metadata" #>> '{score}')::numeric
|
|
99
|
+
// ELSE NULL
|
|
100
|
+
// END > 50
|
|
101
|
+
|
|
85
102
|
// Result:
|
|
86
103
|
// Row 1: 85 > 50 -> matched
|
|
87
104
|
// Row 2: "high" -> NULL -> not matched
|
|
@@ -91,19 +108,27 @@ JSON fields may contain mixed types. Ignis uses safe casting to prevent database
|
|
|
91
108
|
| JSON Value | Numeric Operation Result |
|
|
92
109
|
|------------|-------------------------|
|
|
93
110
|
| `{ "score": 85 }` | Compares as `85` |
|
|
111
|
+
| `{ "score": "85" }` | Compares as `85` (string passes regex) |
|
|
94
112
|
| `{ "score": "high" }` | Treated as `NULL` (no match) |
|
|
95
113
|
| `{ "score": null }` | Treated as `NULL` (no match) |
|
|
96
114
|
|
|
115
|
+
Non-numeric operators (`eq`, `ne`, `like`, `ilike`, `in`, etc.) use text comparison via `#>>` without numeric casting.
|
|
97
116
|
|
|
98
|
-
## Boolean Values
|
|
99
117
|
|
|
100
|
-
|
|
118
|
+
## Numeric Value Equality
|
|
119
|
+
|
|
120
|
+
When a JSON path is compared to a number using direct equality (not an operator object), numeric casting is also applied:
|
|
101
121
|
|
|
102
122
|
```typescript
|
|
103
|
-
|
|
123
|
+
{ where: { 'metadata.user.id': 123 } }
|
|
124
|
+
// Uses numeric CASE expression since value is typeof number
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
When compared to a string, it uses text comparison:
|
|
104
128
|
|
|
105
|
-
|
|
106
|
-
|
|
129
|
+
```typescript
|
|
130
|
+
{ where: { 'metadata.user.role': 'admin' } }
|
|
131
|
+
// Uses "metadata" #>> '{user,role}' text comparison
|
|
107
132
|
```
|
|
108
133
|
|
|
109
134
|
|
|
@@ -112,13 +137,16 @@ Booleans are compared as TEXT strings:
|
|
|
112
137
|
Order results by JSON fields:
|
|
113
138
|
|
|
114
139
|
```typescript
|
|
115
|
-
{
|
|
140
|
+
{ order: ['metadata.priority DESC'] }
|
|
116
141
|
// SQL: ORDER BY "metadata" #> '{priority}' DESC
|
|
117
142
|
|
|
118
143
|
// Multiple JSON fields
|
|
119
|
-
{
|
|
144
|
+
{ order: ['metadata.priority DESC', 'metadata.score ASC'] }
|
|
120
145
|
```
|
|
121
146
|
|
|
147
|
+
> [!NOTE]
|
|
148
|
+
> JSON ordering uses `#>` (returns JSONB, preserves native types) unlike where clauses which use `#>>` (returns text). This means JSONB sort order applies.
|
|
149
|
+
|
|
122
150
|
**Sort Order for JSONB Types:**
|
|
123
151
|
|
|
124
152
|
| JSONB Type | Sort Order |
|
|
@@ -133,7 +161,7 @@ Order results by JSON fields:
|
|
|
133
161
|
|
|
134
162
|
## Path Validation & Security
|
|
135
163
|
|
|
136
|
-
Path components are validated to prevent SQL injection:
|
|
164
|
+
Path components are validated against the pattern `/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\d+$/` to prevent SQL injection:
|
|
137
165
|
|
|
138
166
|
```typescript
|
|
139
167
|
// Valid paths
|
|
@@ -145,7 +173,7 @@ Path components are validated to prevent SQL injection:
|
|
|
145
173
|
|
|
146
174
|
// Invalid (throws error)
|
|
147
175
|
'metadata.field;DROP TABLE'
|
|
148
|
-
'data.123invalid'
|
|
176
|
+
'data.123invalid' // starts with digit (not array index context)
|
|
149
177
|
'config.(SELECT * FROM users)'
|
|
150
178
|
```
|
|
151
179
|
|
|
@@ -158,6 +186,8 @@ Error: Column 'name' is not JSON/JSONB type | dataType: 'text'
|
|
|
158
186
|
Error: Invalid JSON path component: 'field;DROP'
|
|
159
187
|
```
|
|
160
188
|
|
|
189
|
+
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.
|
|
190
|
+
|
|
161
191
|
|
|
162
192
|
## Performance Tips
|
|
163
193
|
|
|
@@ -169,16 +199,16 @@ CREATE INDEX idx_metadata_gin ON "Product" USING GIN ("metadata");
|
|
|
169
199
|
|
|
170
200
|
2. **Use Appropriate Types in JSON:**
|
|
171
201
|
```json
|
|
172
|
-
// Good
|
|
202
|
+
// Good - numeric operators will work correctly
|
|
173
203
|
{ "priority": 3, "enabled": true }
|
|
174
204
|
|
|
175
|
-
// Bad
|
|
205
|
+
// Bad - numeric operators will need string-to-number casting
|
|
176
206
|
{ "priority": "3", "enabled": "true" }
|
|
177
207
|
```
|
|
178
208
|
|
|
179
209
|
3. **Keep Paths Shallow:**
|
|
180
210
|
```typescript
|
|
181
|
-
// Easier to work with
|
|
211
|
+
// Easier to work with and index
|
|
182
212
|
'metadata.priority'
|
|
183
213
|
|
|
184
214
|
// Harder to optimize
|
|
@@ -193,7 +223,10 @@ CREATE INDEX idx_metadata_gin ON "Product" USING GIN ("metadata");
|
|
|
193
223
|
// This is safe - no errors, just no matches
|
|
194
224
|
{ where: { 'metadata.nonexistent.field': 'value' } }
|
|
195
225
|
// SQL: "metadata" #>> '{nonexistent,field}' = 'value'
|
|
226
|
+
// Result: No rows (NULL != 'value')
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
|
|
196
230
|
## See Also
|
|
197
231
|
|
|
198
232
|
- [Nested JSON Updates](../repositories/advanced.md#nested-json-updates) - Updating JSON fields
|
|
199
|
-
|
|
@@ -11,7 +11,7 @@ Operators for matching values against arrays.
|
|
|
11
11
|
|
|
12
12
|
## in / inq - In Array
|
|
13
13
|
|
|
14
|
-
Matches records where field value is in the provided array.
|
|
14
|
+
Matches records where field value is in the provided array. `in` and `inq` are aliases and behave identically.
|
|
15
15
|
|
|
16
16
|
```typescript
|
|
17
17
|
{ where: { status: { in: ['active', 'pending', 'review'] } } }
|
|
@@ -37,9 +37,10 @@ Matches records where field value is in the provided array.
|
|
|
37
37
|
|
|
38
38
|
| Scenario | Behavior |
|
|
39
39
|
|----------|----------|
|
|
40
|
-
| `{ in: [] }` (empty array) | Returns no rows (`false`) |
|
|
41
|
-
| `{ nin: [] }` (empty array) | Returns all rows (`true`) |
|
|
40
|
+
| `{ in: [] }` (empty array) | Returns no rows (`WHERE false`) |
|
|
41
|
+
| `{ nin: [] }` (empty array) | Returns all rows (`WHERE true`) |
|
|
42
42
|
| `{ in: 'value' }` (non-array) | Treated as `{ eq: 'value' }` |
|
|
43
|
+
| `{ nin: 'value' }` (non-array) | Treated as `{ ne: 'value' }` |
|
|
43
44
|
|
|
44
45
|
> [!WARNING]
|
|
45
46
|
> `NOT IN` excludes rows where the column is `NULL`. If your column can be `NULL`, use `OR` to include them:
|
|
@@ -107,11 +107,12 @@ Combine AND and OR for complex logic:
|
|
|
107
107
|
|
|
108
108
|
## NOT Logic
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
Ignis does not have a standalone `not` logical operator. Instead, use negation operators for NOT conditions:
|
|
111
111
|
|
|
112
112
|
```typescript
|
|
113
113
|
// NOT equal
|
|
114
114
|
{ where: { status: { ne: 'deleted' } } }
|
|
115
|
+
{ where: { status: { neq: 'deleted' } } }
|
|
115
116
|
|
|
116
117
|
// NOT IN
|
|
117
118
|
{ where: { status: { nin: ['deleted', 'banned'] } } }
|
|
@@ -119,8 +120,12 @@ Use negation operators for NOT conditions:
|
|
|
119
120
|
// NOT LIKE
|
|
120
121
|
{ where: { email: { nlike: '%@test.com' } } }
|
|
121
122
|
|
|
122
|
-
// NOT
|
|
123
|
+
// NOT ILIKE
|
|
124
|
+
{ where: { email: { nilike: '%@test.com' } } }
|
|
125
|
+
|
|
126
|
+
// IS NOT NULL
|
|
123
127
|
{ where: { verifiedAt: { isn: null } } }
|
|
128
|
+
{ where: { verifiedAt: { ne: null } } }
|
|
124
129
|
|
|
125
130
|
// NOT BETWEEN
|
|
126
131
|
{ where: { score: { notBetween: [40, 60] } } }
|
|
@@ -9,6 +9,42 @@ difficulty: intermediate
|
|
|
9
9
|
Operators for checking null and non-null values.
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
## Direct Null Assignment
|
|
13
|
+
|
|
14
|
+
The simplest way to check for NULL:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// IS NULL (implicit)
|
|
18
|
+
{ where: { deletedAt: null } }
|
|
19
|
+
// SQL: WHERE "deleted_at" IS NULL
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## eq with Null
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
{ where: { deletedAt: { eq: null } } }
|
|
27
|
+
// SQL: WHERE "deleted_at" IS NULL
|
|
28
|
+
|
|
29
|
+
{ where: { status: { eq: 'active' } } }
|
|
30
|
+
// SQL: WHERE "status" = 'active'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## ne / neq with Null
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// IS NOT NULL
|
|
38
|
+
{ where: { deletedAt: { ne: null } } }
|
|
39
|
+
{ where: { deletedAt: { neq: null } } }
|
|
40
|
+
// SQL: WHERE "deleted_at" IS NOT NULL
|
|
41
|
+
|
|
42
|
+
// Not equal to value
|
|
43
|
+
{ where: { status: { ne: 'deleted' } } }
|
|
44
|
+
// SQL: WHERE "status" != 'deleted'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
|
|
12
48
|
## is - IS NULL / Equality
|
|
13
49
|
|
|
14
50
|
```typescript
|
|
@@ -35,6 +71,20 @@ Operators for checking null and non-null values.
|
|
|
35
71
|
```
|
|
36
72
|
|
|
37
73
|
|
|
74
|
+
## Null Check Summary
|
|
75
|
+
|
|
76
|
+
| Syntax | SQL | Description |
|
|
77
|
+
|--------|-----|-------------|
|
|
78
|
+
| `{ field: null }` | `IS NULL` | Direct null check |
|
|
79
|
+
| `{ field: { eq: null } }` | `IS NULL` | Explicit null equality |
|
|
80
|
+
| `{ field: { is: null } }` | `IS NULL` | IS operator with null |
|
|
81
|
+
| `{ field: { ne: null } }` | `IS NOT NULL` | Not-equal null check |
|
|
82
|
+
| `{ field: { neq: null } }` | `IS NOT NULL` | Alias for ne with null |
|
|
83
|
+
| `{ field: { isn: null } }` | `IS NOT NULL` | IS NOT operator with null |
|
|
84
|
+
|
|
85
|
+
All six syntaxes for IS NULL / IS NOT NULL are equivalent -- use whichever reads best in context.
|
|
86
|
+
|
|
87
|
+
|
|
38
88
|
## Common Patterns
|
|
39
89
|
|
|
40
90
|
### Soft Delete Pattern
|