@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,145 +6,115 @@ difficulty: intermediate
|
|
|
6
6
|
|
|
7
7
|
# Fields, Ordering & Pagination
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## Field Selection
|
|
13
|
-
|
|
14
|
-
Control which fields are returned using `fields`:
|
|
15
|
-
|
|
16
|
-
### Array Format (Recommended)
|
|
9
|
+
The `filter` object controls which columns come back, in what order, and how many rows - through the `fields`, `order`, `limit`, and `skip`/`offset` properties.
|
|
17
10
|
|
|
18
11
|
```typescript
|
|
19
12
|
import { userRepository } from '@/repositories';
|
|
20
13
|
|
|
21
14
|
await userRepository.find({
|
|
22
|
-
filter: {
|
|
23
|
-
where: { status: 'active' },
|
|
24
|
-
fields: ['id', 'email', 'name']
|
|
25
|
-
}
|
|
15
|
+
filter: { fields: ['id', 'email'], order: ['createdAt DESC'], limit: 10 },
|
|
26
16
|
});
|
|
27
|
-
// Returns only: { id, email, name }
|
|
28
17
|
```
|
|
29
18
|
|
|
30
|
-
|
|
19
|
+
## Options
|
|
20
|
+
|
|
21
|
+
| Option | Type | Default | Meaning |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| `fields` | `string[] \| Record<string, boolean>` | every column | Inclusion-only column selection. |
|
|
24
|
+
| `order` | `string[]` (`'column ASC\|DESC'`) | insertion order | Sort columns; `ASC` if no direction is given. |
|
|
25
|
+
| `limit` | `number` | `settings.defaultLimit ?? 10` | Row cap. An explicit value always wins. |
|
|
26
|
+
| `skip` / `offset` | `number` | `0` | Rows to skip. Aliases for the same `OFFSET` clause; `skip` wins if both are set. |
|
|
27
|
+
| `options.shouldQueryRange` | `boolean` | `false` | Adds a `range` envelope (`start`/`end`/`total`) to the result. |
|
|
28
|
+
|
|
29
|
+
## Field selection
|
|
30
|
+
|
|
31
|
+
`fields` accepts an array or an object. Both select the same columns:
|
|
31
32
|
|
|
32
33
|
```typescript
|
|
33
|
-
//
|
|
34
|
+
// Array format (recommended)
|
|
34
35
|
await userRepository.find({
|
|
35
|
-
filter: {
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
filter: { where: { status: 'active' }, fields: ['id', 'email', 'name'] },
|
|
37
|
+
});
|
|
38
|
+
// Returns only: { id, email, name }
|
|
39
|
+
|
|
40
|
+
// Object format - only keys set to `true` are selected
|
|
41
|
+
await userRepository.find({
|
|
42
|
+
filter: { fields: { id: true, email: true, name: true } },
|
|
38
43
|
});
|
|
39
44
|
```
|
|
40
45
|
|
|
41
46
|
> [!NOTE]
|
|
42
|
-
> The object format
|
|
43
|
-
|
|
47
|
+
> The object format is inclusion-only. A key set to `false` is ignored, not excluded - it neither adds nor removes the column. To exclude a column, omit its key or use the array format.
|
|
44
48
|
|
|
45
49
|
## Ordering
|
|
46
50
|
|
|
47
|
-
|
|
51
|
+
Each entry in `order` is a `'column DIRECTION'` string. Direction defaults to `ASC` and only `ASC`/`DESC` (case-insensitive) are valid:
|
|
48
52
|
|
|
49
53
|
```typescript
|
|
50
|
-
|
|
51
|
-
await userRepository.find({
|
|
52
|
-
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Multiple columns
|
|
56
|
-
await userRepository.find({
|
|
57
|
-
filter: { order: ['status ASC', 'createdAt DESC'] }
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Default direction is ASC
|
|
61
|
-
await userRepository.find({
|
|
62
|
-
filter: { order: ['name'] } // Same as 'name ASC'
|
|
63
|
-
});
|
|
54
|
+
await userRepository.find({ filter: { order: ['createdAt DESC'] } });
|
|
55
|
+
await userRepository.find({ filter: { order: ['status ASC', 'createdAt DESC'] } });
|
|
56
|
+
await userRepository.find({ filter: { order: ['name'] } }); // same as 'name ASC'
|
|
64
57
|
```
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
Only `ASC` and `DESC` (case-insensitive) are accepted. Invalid directions throw an error:
|
|
59
|
+
An invalid direction throws before the query runs:
|
|
69
60
|
|
|
70
61
|
```
|
|
71
62
|
Error: Invalid direction: 'RANDOM' | Expected: 'ASC' or 'DESC'
|
|
72
63
|
```
|
|
73
64
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Order by nested fields in JSON columns:
|
|
65
|
+
Order by a nested key inside a JSON column with dot-path notation:
|
|
77
66
|
|
|
78
67
|
```typescript
|
|
79
|
-
await userRepository.find({
|
|
80
|
-
filter: { order: ['metadata.priority DESC'] }
|
|
81
|
-
});
|
|
68
|
+
await userRepository.find({ filter: { order: ['metadata.priority DESC'] } });
|
|
82
69
|
// SQL: ORDER BY "metadata" #> '{priority}' DESC
|
|
83
70
|
|
|
84
|
-
await userRepository.find({
|
|
85
|
-
filter: { order: ['settings.display.theme ASC'] }
|
|
86
|
-
});
|
|
71
|
+
await userRepository.find({ filter: { order: ['settings.display.theme ASC'] } });
|
|
87
72
|
```
|
|
88
73
|
|
|
89
|
-
|
|
74
|
+
JSONB values sort by type first, then by value within the type:
|
|
90
75
|
|
|
91
|
-
| JSONB
|
|
92
|
-
|
|
76
|
+
| JSONB type | Sort position |
|
|
77
|
+
|---|---|
|
|
93
78
|
| `null` | First (lowest) |
|
|
94
|
-
| `boolean` | `false`
|
|
79
|
+
| `boolean` | `false` before `true` |
|
|
95
80
|
| `number` | Numeric order |
|
|
96
|
-
| `string` | Lexicographic |
|
|
81
|
+
| `string` | Lexicographic order |
|
|
97
82
|
| `array` | Element-wise |
|
|
98
|
-
| `object` | Key-value |
|
|
83
|
+
| `object` | Key-value order |
|
|
99
84
|
|
|
85
|
+
See [JSON Filtering](./json-filtering) for the full path syntax.
|
|
100
86
|
|
|
101
87
|
## Pagination
|
|
102
88
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Both `skip` and `offset` are supported as aliases -- they both map to the SQL `OFFSET` clause. When both are provided, `skip` takes precedence.
|
|
89
|
+
`limit` caps the row count; `skip` (or its alias `offset`) sets how many rows to skip. Combine them for page N:
|
|
106
90
|
|
|
107
91
|
```typescript
|
|
108
|
-
|
|
109
|
-
await userRepository.find({
|
|
110
|
-
filter: { limit: 10 }
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Page 2 (skip first 10, get next 10)
|
|
114
|
-
await userRepository.find({
|
|
115
|
-
filter: { limit: 10, skip: 10 }
|
|
116
|
-
});
|
|
92
|
+
await userRepository.find({ filter: { limit: 10 } }); // first 10
|
|
93
|
+
await userRepository.find({ filter: { limit: 10, skip: 10 } }); // page 2
|
|
117
94
|
|
|
118
|
-
// Using offset (equivalent to skip)
|
|
119
|
-
await userRepository.find({
|
|
120
|
-
filter: { limit: 10, offset: 10 }
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Page N formula: skip = (page - 1) * limit
|
|
124
95
|
const page = 3;
|
|
125
96
|
const pageSize = 20;
|
|
126
97
|
await userRepository.find({
|
|
127
|
-
filter: {
|
|
128
|
-
limit: pageSize,
|
|
129
|
-
skip: (page - 1) * pageSize
|
|
130
|
-
}
|
|
98
|
+
filter: { limit: pageSize, skip: (page - 1) * pageSize },
|
|
131
99
|
});
|
|
132
100
|
```
|
|
133
101
|
|
|
134
102
|
> [!TIP]
|
|
135
|
-
>
|
|
103
|
+
> Set `limit` on every public-facing endpoint. An unbounded query can exhaust memory - the repository always falls back to a default of `10`, never to "no limit".
|
|
136
104
|
|
|
137
|
-
### Default
|
|
105
|
+
### Default limit resolution
|
|
138
106
|
|
|
139
|
-
|
|
107
|
+
A query that omits `limit` gets one from this precedence chain:
|
|
140
108
|
|
|
141
109
|
```
|
|
142
|
-
query.limit ??
|
|
110
|
+
query.limit ?? settings.defaultLimit ?? DEFAULT_LIMIT (10)
|
|
143
111
|
```
|
|
144
112
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
113
|
+
| Source | Meaning |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `query.limit` | An explicit `limit` in the caller's filter. Always wins. |
|
|
116
|
+
| `settings.defaultLimit` | A per-model default on the `@model` decorator. Must be a positive integer - `@model` validates it at decoration time. Applies to top-level `find()` and to every to-many relation, using the related model's own `defaultLimit`. |
|
|
117
|
+
| `DEFAULT_LIMIT` | The global fallback, `10`. |
|
|
148
118
|
|
|
149
119
|
```typescript
|
|
150
120
|
import { model, BaseEntity } from '@venizia/ignis';
|
|
@@ -153,105 +123,80 @@ import { countryRepository } from '@/repositories';
|
|
|
153
123
|
|
|
154
124
|
@model({
|
|
155
125
|
type: 'entity',
|
|
156
|
-
settings: { defaultLimit: 200 },
|
|
126
|
+
settings: { defaultLimit: 200 }, // small lookup table - default to 200 rows
|
|
157
127
|
})
|
|
158
128
|
export class Country extends BaseEntity<typeof Country.schema> {
|
|
159
129
|
static override schema = countryTable;
|
|
160
130
|
}
|
|
161
131
|
|
|
162
|
-
await countryRepository.find({ filter: {} });
|
|
163
|
-
await countryRepository.find({ filter: { limit: 10 } }); // LIMIT 10
|
|
132
|
+
await countryRepository.find({ filter: {} }); // LIMIT 200
|
|
133
|
+
await countryRepository.find({ filter: { limit: 10 } }); // LIMIT 10 (explicit wins)
|
|
164
134
|
```
|
|
165
135
|
|
|
166
136
|
> [!NOTE]
|
|
167
|
-
> `defaultLimit` is independent of `defaultFilter
|
|
137
|
+
> `defaultLimit` is independent of `defaultFilter`. Passing `shouldSkipDefaultFilter` bypasses the default `where` clause but never drops the default limit. There is no "unbounded" sentinel - to fetch more rows, pass an explicit `limit`.
|
|
168
138
|
|
|
169
|
-
|
|
139
|
+
A small helper keeps page-to-filter math in one place:
|
|
170
140
|
|
|
171
141
|
```typescript
|
|
172
142
|
function getPaginationFilter(page: number, pageSize: number = 20) {
|
|
173
|
-
return {
|
|
174
|
-
limit: pageSize,
|
|
175
|
-
skip: (page - 1) * pageSize
|
|
176
|
-
};
|
|
143
|
+
return { limit: pageSize, skip: (page - 1) * pageSize };
|
|
177
144
|
}
|
|
178
145
|
|
|
179
|
-
|
|
180
|
-
const filter = {
|
|
181
|
-
where: { status: 'active' },
|
|
182
|
-
...getPaginationFilter(3, 20)
|
|
183
|
-
};
|
|
146
|
+
const filter = { where: { status: 'active' }, ...getPaginationFilter(3, 20) };
|
|
184
147
|
// { where: {...}, limit: 20, skip: 40 }
|
|
185
148
|
```
|
|
186
149
|
|
|
150
|
+
## Range queries (Content-Range header)
|
|
187
151
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
When building paginated APIs, you often need to return the total count alongside the data for pagination UI. Use `shouldQueryRange: true` to get range information following the HTTP Content-Range standard.
|
|
191
|
-
|
|
192
|
-
### Basic Usage
|
|
152
|
+
Set `options.shouldQueryRange: true` to get the total row count alongside the data, formatted for the HTTP `Content-Range` header:
|
|
193
153
|
|
|
194
154
|
```typescript
|
|
195
155
|
const result = await userRepository.find({
|
|
196
156
|
filter: { limit: 10, skip: 20 },
|
|
197
|
-
options: { shouldQueryRange: true }
|
|
157
|
+
options: { shouldQueryRange: true },
|
|
198
158
|
});
|
|
199
159
|
|
|
200
|
-
//
|
|
201
|
-
// {
|
|
202
|
-
// data: [...], // Array of records
|
|
203
|
-
// range: {
|
|
204
|
-
// start: 20, // Starting index (inclusive)
|
|
205
|
-
// end: 29, // Ending index (inclusive)
|
|
206
|
-
// total: 100 // Total matching records
|
|
207
|
-
// }
|
|
208
|
-
// }
|
|
160
|
+
// result.data -> the matching rows
|
|
161
|
+
// result.range -> { start: 20, end: 29, total: 100 }
|
|
209
162
|
```
|
|
210
163
|
|
|
211
|
-
|
|
164
|
+
`range` has this shape:
|
|
212
165
|
|
|
213
|
-
|
|
166
|
+
```typescript
|
|
167
|
+
type TDataRange = {
|
|
168
|
+
start: number; // starting index, 0-based, inclusive
|
|
169
|
+
end: number; // ending index, 0-based, inclusive
|
|
170
|
+
total: number; // total rows matching the query
|
|
171
|
+
};
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Build the header value from `range`:
|
|
214
175
|
|
|
215
176
|
```typescript
|
|
216
177
|
const { data, range } = await userRepository.find({
|
|
217
178
|
filter: { limit: 10, skip: 20, where: { status: 'active' } },
|
|
218
|
-
options: { shouldQueryRange: true }
|
|
179
|
+
options: { shouldQueryRange: true },
|
|
219
180
|
});
|
|
220
181
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
? `records ${range.start}-${range.end}/${range.total}`
|
|
224
|
-
: `records */${range.total}`;
|
|
182
|
+
const contentRange =
|
|
183
|
+
data.length > 0 ? `records ${range.start}-${range.end}/${range.total}` : `records */${range.total}`;
|
|
225
184
|
|
|
226
185
|
res.setHeader('Content-Range', contentRange);
|
|
227
186
|
// -> "records 20-29/100"
|
|
228
187
|
```
|
|
229
188
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
type TDataRange = {
|
|
234
|
-
start: number; // Starting index (0-based, inclusive)
|
|
235
|
-
end: number; // Ending index (0-based, inclusive)
|
|
236
|
-
total: number; // Total count matching the query
|
|
237
|
-
};
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
### Content-Range Format Reference
|
|
241
|
-
|
|
242
|
-
| Scenario | Content-Range Header |
|
|
243
|
-
|----------|---------------------|
|
|
189
|
+
| Scenario | Content-Range header |
|
|
190
|
+
|---|---|
|
|
244
191
|
| Items 0-9 of 100 | `records 0-9/100` |
|
|
245
192
|
| Items 20-29 of 100 | `records 20-29/100` |
|
|
246
193
|
| No items found | `records */0` |
|
|
247
194
|
| Last page (items 90-99) | `records 90-99/100` |
|
|
248
195
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
When `shouldQueryRange: true`, the repository executes the data query and count query **in parallel** using `Promise.all` for optimal performance.
|
|
252
|
-
|
|
196
|
+
> [!NOTE]
|
|
197
|
+
> With `shouldQueryRange: true`, the repository runs the data query and the count query in parallel via `Promise.all`.
|
|
253
198
|
|
|
254
|
-
## Combined
|
|
199
|
+
## Combined example
|
|
255
200
|
|
|
256
201
|
```typescript
|
|
257
202
|
await userRepository.find({
|
|
@@ -260,12 +205,12 @@ await userRepository.find({
|
|
|
260
205
|
fields: ['id', 'name', 'price', 'createdAt'],
|
|
261
206
|
order: ['price ASC', 'createdAt DESC'],
|
|
262
207
|
limit: 20,
|
|
263
|
-
skip: 0
|
|
264
|
-
}
|
|
208
|
+
skip: 0,
|
|
209
|
+
},
|
|
265
210
|
});
|
|
266
211
|
```
|
|
267
212
|
|
|
268
|
-
|
|
213
|
+
With range information:
|
|
269
214
|
|
|
270
215
|
```typescript
|
|
271
216
|
const { data, range } = await userRepository.find({
|
|
@@ -274,9 +219,9 @@ const { data, range } = await userRepository.find({
|
|
|
274
219
|
fields: ['id', 'name', 'price', 'createdAt'],
|
|
275
220
|
order: ['price ASC', 'createdAt DESC'],
|
|
276
221
|
limit: 20,
|
|
277
|
-
skip: 0
|
|
222
|
+
skip: 0,
|
|
278
223
|
},
|
|
279
|
-
options: { shouldQueryRange: true }
|
|
224
|
+
options: { shouldQueryRange: true },
|
|
280
225
|
});
|
|
281
226
|
|
|
282
227
|
console.log(`Showing ${range.start}-${range.end} of ${range.total}`);
|
|
@@ -292,7 +237,8 @@ console.log(`Showing ${range.start}-${range.end} of ${range.total}`);
|
|
|
292
237
|
|
|
293
238
|
**Files:**
|
|
294
239
|
|
|
295
|
-
- [`packages/core/src/connectors/
|
|
296
|
-
- [`packages/core/src/
|
|
297
|
-
- [`packages/
|
|
298
|
-
- [`packages/core/src/base/repositories/common/
|
|
240
|
+
- [`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`, `toColumns`/`toOrderBy`
|
|
241
|
+
- [`packages/core-server/src/connectors/postgres/repositories/core/readable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/core/readable.ts) - `find()`'s `query.limit ?? getDefaultLimit() ?? DEFAULT_LIMIT` resolution
|
|
242
|
+
- [`packages/filter/src/common/operators.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/filter/src/common/operators.ts) - `Sorts` constants
|
|
243
|
+
- [`packages/core-server/src/base/repositories/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/repositories/common/constants.ts) - `DEFAULT_LIMIT`
|
|
244
|
+
- [`packages/core-server/src/base/repositories/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/repositories/common/types.ts) - `TDataRange`, `buildDataRange`
|
|
@@ -6,11 +6,20 @@ difficulty: intermediate
|
|
|
6
6
|
|
|
7
7
|
# Filter System
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Every repository read, update, and delete verb takes the same `filter` object. It picks rows (`where`), columns (`fields`), order (`order`), and how many (`limit`/`skip`).
|
|
10
|
+
|
|
11
|
+
The vocabulary ships as its own package, **`@venizia/ignis-filter`**. Applications on `@venizia/ignis` already get every name here re-exported from the core barrel, so nothing changes for them. Install it directly only when you want the filter language **without** the server framework - a browser or a Web Worker - since it resolves no node builtin and no server-only dependency:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { QueryOperators, Sorts, type TFilter } from '@venizia/ignis-filter';
|
|
15
|
+
import { FilterSchema, WhereSchema } from '@venizia/ignis-filter/schemas';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
On a server take the schemas from `@venizia/ignis` instead: the ones on that subpath carry no OpenAPI metadata, so a route built on them documents nothing.
|
|
10
19
|
|
|
11
20
|
## In one example
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
`where` picks rows, `fields` picks columns, `order` sorts, `limit` bounds the result:
|
|
14
23
|
|
|
15
24
|
```typescript
|
|
16
25
|
import { postRepository } from '@/repositories';
|
|
@@ -28,7 +37,7 @@ const posts = await postRepository.find({
|
|
|
28
37
|
});
|
|
29
38
|
```
|
|
30
39
|
|
|
31
|
-
`postRepository` is a `@repository({ model: Post, dataSource })`-bound repository
|
|
40
|
+
`postRepository` is a `@repository({ model: Post, dataSource })`-bound repository. `Post`'s schema comes from `@/schemas` - see [Models](/references/base/models) and [Repositories](../repositories/).
|
|
32
41
|
|
|
33
42
|
```sql
|
|
34
43
|
-- Equivalent SQL
|
|
@@ -42,9 +51,9 @@ LIMIT 20
|
|
|
42
51
|
## How it works
|
|
43
52
|
|
|
44
53
|
- **`TFilter` maps straight to SQL.** Every property corresponds to one clause of the generated query - see the table below.
|
|
45
|
-
- **`where` takes a bare value or an operator object.** A bare value is implicit equality (`null` becomes `IS NULL`, an array becomes `IN`)
|
|
46
|
-
- **Multiple `where` keys are an implicit AND.** A dot-notation key (`'metadata.path'`) targets a JSON/JSONB column instead of a top-level column and accepts the same operators
|
|
47
|
-
- **A model's `settings.defaultFilter` merges into every query for that model
|
|
54
|
+
- **`where` takes a bare value or an operator object.** A bare value is implicit equality (`null` becomes `IS NULL`, an array becomes `IN`). An operator object keys into one of the operator families.
|
|
55
|
+
- **Multiple `where` keys are an implicit AND.** A dot-notation key (`'metadata.path'`) targets a JSON/JSONB column instead of a top-level column, and accepts the same operators. IGNIS casts the operand automatically when it's a number.
|
|
56
|
+
- **A model's `settings.defaultFilter` merges into every query for that model** - see [Default filter](#default-filter) below.
|
|
48
57
|
|
|
49
58
|
| Filter property | SQL equivalent | Purpose |
|
|
50
59
|
|---|---|---|
|
|
@@ -68,6 +77,8 @@ LIMIT 20
|
|
|
68
77
|
| Array (PostgreSQL) | `contains`, `containedBy`, `overlaps` | `{ tags: { contains: ['typescript'] } }` |
|
|
69
78
|
| JSON path | comparison, null, list, range, and pattern operators, on a `'column.path'` key | `{ 'metadata.score': { gt: 80 } }` |
|
|
70
79
|
|
|
80
|
+
Full operator-by-operator tables, one line each, live on the [Quick Reference](./quick-reference) page.
|
|
81
|
+
|
|
71
82
|
### Fields, order, and pagination
|
|
72
83
|
|
|
73
84
|
- **`fields`** selects columns - an array, or a `{ field: true }` object (inclusion-only; `false` is ignored).
|
|
@@ -78,7 +89,8 @@ LIMIT 20
|
|
|
78
89
|
### Default filter
|
|
79
90
|
|
|
80
91
|
- **Applies automatically.** A model's `settings.defaultFilter` merges into every read, update, and delete for that model.
|
|
81
|
-
- **
|
|
92
|
+
- **AND-composes on collision.** When the default and the caller's filter constrain the same field, IGNIS AND-composes the two conditions instead of one replacing the other.
|
|
93
|
+
- **One override escape.** Setting that same field to a plain scalar (not an operator object) replaces the default outright - the one intentional opt-out, and it needs no `shouldSkipDefaultFilter`. The full collision table lives on the [Default Filter](./default-filter) page.
|
|
82
94
|
|
|
83
95
|
```typescript
|
|
84
96
|
import { model, BaseEntity } from '@venizia/ignis';
|
|
@@ -93,13 +105,13 @@ export class Post extends BaseEntity<typeof Post.schema> {
|
|
|
93
105
|
}
|
|
94
106
|
|
|
95
107
|
await postRepository.find({ filter: { where: { status: 'published' } } });
|
|
96
|
-
// WHERE "isDeleted" = false AND "status" = 'published' - both
|
|
108
|
+
// WHERE "isDeleted" = false AND "status" = 'published' - different keys, both apply
|
|
97
109
|
|
|
98
110
|
await postRepository.find({
|
|
99
111
|
filter: { where: { status: 'published' } },
|
|
100
112
|
options: { shouldSkipDefaultFilter: true },
|
|
101
113
|
});
|
|
102
|
-
// WHERE "status" = 'published' - default filter skipped
|
|
114
|
+
// WHERE "status" = 'published' - default filter skipped entirely
|
|
103
115
|
```
|
|
104
116
|
|
|
105
117
|
## Operators
|
|
@@ -118,7 +130,7 @@ Each operator family and every long-form topic has its own page:
|
|
|
118
130
|
| [Array Operators](./array-operators) | `contains`, `containedBy`, `overlaps` (PostgreSQL array columns) |
|
|
119
131
|
| [JSON Filtering](./json-filtering) | Dot-path queries into JSON/JSONB columns |
|
|
120
132
|
| [Fields, Order & Pagination](./fields-order-pagination) | `fields`, `order`, `limit`/`skip`/`offset`, `defaultLimit` |
|
|
121
|
-
| [Default Filter](./default-filter) | `settings.defaultFilter`,
|
|
133
|
+
| [Default Filter](./default-filter) | `settings.defaultFilter`, the collision/narrowing law, `shouldSkipDefaultFilter` |
|
|
122
134
|
| [Application Usage](./application-usage) | How a filter flows controller -> service -> repository |
|
|
123
135
|
| [Use Case Gallery](./use-cases) | Real-world filters with the SQL they produce |
|
|
124
136
|
| [Tips & Edge Cases](./tips) | Performance notes and common gotchas |
|
|
@@ -132,6 +144,6 @@ Each operator family and every long-form topic has its own page:
|
|
|
132
144
|
|
|
133
145
|
**Files:**
|
|
134
146
|
|
|
135
|
-
- [`packages/core/src/connectors/
|
|
136
|
-
- [`packages/
|
|
137
|
-
- [`packages/
|
|
147
|
+
- [`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
|
|
148
|
+
- [`packages/filter/src/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/filter/src/common/types.ts) - `TFilter`/`TInclusion` types
|
|
149
|
+
- [`packages/filter/src/common/operators.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/filter/src/common/operators.ts) - `QueryOperators`/`Sorts` constants
|