@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.
Files changed (174) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +24 -13
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +8 -4
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +247 -153
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +109 -323
  26. package/content/extensions/components/authentication/api.md +489 -602
  27. package/content/extensions/components/authentication/errors.md +135 -498
  28. package/content/extensions/components/authentication/index.md +89 -801
  29. package/content/extensions/components/authentication/usage.md +231 -955
  30. package/content/extensions/components/authorization/api.md +991 -644
  31. package/content/extensions/components/authorization/errors.md +204 -208
  32. package/content/extensions/components/authorization/getting-started.md +227 -0
  33. package/content/extensions/components/authorization/index.md +88 -795
  34. package/content/extensions/components/authorization/usage.md +219 -528
  35. package/content/extensions/components/health-check.md +77 -243
  36. package/content/extensions/components/index.md +24 -90
  37. package/content/extensions/components/mail/api.md +553 -285
  38. package/content/extensions/components/mail/errors.md +76 -62
  39. package/content/extensions/components/mail/index.md +111 -463
  40. package/content/extensions/components/mail/usage.md +139 -173
  41. package/content/extensions/components/request-tracker.md +70 -173
  42. package/content/extensions/components/socket-io/api.md +462 -785
  43. package/content/extensions/components/socket-io/errors.md +49 -51
  44. package/content/extensions/components/socket-io/index.md +69 -372
  45. package/content/extensions/components/socket-io/usage.md +212 -105
  46. package/content/extensions/components/static-asset/api.md +461 -141
  47. package/content/extensions/components/static-asset/errors.md +121 -53
  48. package/content/extensions/components/static-asset/index.md +84 -606
  49. package/content/extensions/components/static-asset/usage.md +184 -299
  50. package/content/extensions/components/template/index.md +3 -3
  51. package/content/extensions/components/websocket/api.md +311 -404
  52. package/content/extensions/components/websocket/errors.md +47 -56
  53. package/content/extensions/components/websocket/index.md +75 -407
  54. package/content/extensions/components/websocket/usage.md +120 -338
  55. package/content/extensions/helpers/cron/index.md +52 -160
  56. package/content/extensions/helpers/crypto/index.md +67 -480
  57. package/content/extensions/helpers/crypto/reference.md +528 -0
  58. package/content/extensions/helpers/env/index.md +64 -178
  59. package/content/extensions/helpers/error/index.md +286 -196
  60. package/content/extensions/helpers/index.md +61 -47
  61. package/content/extensions/helpers/inversion/index.md +76 -550
  62. package/content/extensions/helpers/inversion/reference.md +530 -0
  63. package/content/extensions/helpers/kafka/admin.md +23 -3
  64. package/content/extensions/helpers/kafka/compile-binary.md +83 -52
  65. package/content/extensions/helpers/kafka/consumer.md +65 -28
  66. package/content/extensions/helpers/kafka/examples.md +46 -253
  67. package/content/extensions/helpers/kafka/index.md +55 -617
  68. package/content/extensions/helpers/kafka/producer.md +156 -22
  69. package/content/extensions/helpers/kafka/schema-registry.md +59 -79
  70. package/content/extensions/helpers/logger/hf-logger.md +220 -0
  71. package/content/extensions/helpers/logger/index.md +78 -552
  72. package/content/extensions/helpers/logger/pino.md +105 -0
  73. package/content/extensions/helpers/logger/reference.md +937 -0
  74. package/content/extensions/helpers/network/api.md +276 -195
  75. package/content/extensions/helpers/network/index.md +87 -524
  76. package/content/extensions/helpers/queue/index.md +80 -897
  77. package/content/extensions/helpers/queue/reference.md +494 -0
  78. package/content/extensions/helpers/redis/index.md +86 -640
  79. package/content/extensions/helpers/redis/reference.md +784 -0
  80. package/content/extensions/helpers/secrets/index.md +136 -0
  81. package/content/extensions/helpers/socket-io/api.md +325 -204
  82. package/content/extensions/helpers/socket-io/index.md +79 -429
  83. package/content/extensions/helpers/storage/api.md +584 -464
  84. package/content/extensions/helpers/storage/index.md +80 -573
  85. package/content/extensions/helpers/types/index.md +75 -495
  86. package/content/extensions/helpers/types/reference.md +689 -0
  87. package/content/extensions/helpers/uid/index.md +176 -189
  88. package/content/extensions/helpers/websocket/api.md +366 -214
  89. package/content/extensions/helpers/websocket/index.md +68 -503
  90. package/content/extensions/helpers/worker-thread/index.md +62 -396
  91. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  92. package/content/extensions/index.md +38 -39
  93. package/content/extensions/src-details/mcp-server.md +96 -548
  94. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  95. package/content/guides/core-concepts/persistent/index.md +5 -1
  96. package/content/guides/core-concepts/persistent/models.md +1 -1
  97. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  98. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  99. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  100. package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
  101. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  102. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  103. package/content/guides/core-concepts/secrets-vault.md +177 -0
  104. package/content/guides/core-concepts/services.md +1 -1
  105. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  106. package/content/guides/get-started/philosophy.md +135 -670
  107. package/content/guides/get-started/setup.md +53 -74
  108. package/content/guides/migrations/redis-helpers-migration.md +5 -4
  109. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  110. package/content/guides/migrations/unified-connectors-migration.md +8 -7
  111. package/content/guides/tutorials/ecommerce-api.md +3 -8
  112. package/content/guides/tutorials/realtime-chat.md +1 -1
  113. package/content/references/base/application.md +64 -22
  114. package/content/references/base/components.md +3 -3
  115. package/content/references/base/connectors.md +79 -136
  116. package/content/references/base/controllers.md +12 -12
  117. package/content/references/base/datasources-reference.md +600 -0
  118. package/content/references/base/datasources.md +84 -444
  119. package/content/references/base/dependency-injection.md +25 -46
  120. package/content/references/base/filter-system/application-usage.md +95 -123
  121. package/content/references/base/filter-system/array-operators.md +33 -43
  122. package/content/references/base/filter-system/comparison-operators.md +55 -63
  123. package/content/references/base/filter-system/default-filter.md +144 -350
  124. package/content/references/base/filter-system/fields-order-pagination.md +116 -148
  125. package/content/references/base/filter-system/index.md +114 -253
  126. package/content/references/base/filter-system/json-filtering.md +52 -181
  127. package/content/references/base/filter-system/list-operators.md +28 -44
  128. package/content/references/base/filter-system/logical-operators.md +69 -114
  129. package/content/references/base/filter-system/null-operators.md +41 -98
  130. package/content/references/base/filter-system/pattern-matching.md +48 -51
  131. package/content/references/base/filter-system/quick-reference.md +93 -196
  132. package/content/references/base/filter-system/range-operators.md +22 -38
  133. package/content/references/base/filter-system/tips.md +70 -133
  134. package/content/references/base/filter-system/use-cases.md +173 -232
  135. package/content/references/base/grpc-controllers.md +53 -17
  136. package/content/references/base/index.md +5 -3
  137. package/content/references/base/middlewares.md +43 -28
  138. package/content/references/base/models-reference.md +886 -0
  139. package/content/references/base/models.md +81 -1452
  140. package/content/references/base/providers.md +8 -8
  141. package/content/references/base/repositories/advanced.md +281 -419
  142. package/content/references/base/repositories/index.md +84 -644
  143. package/content/references/base/repositories/mixins.md +25 -21
  144. package/content/references/base/repositories/relations.md +194 -375
  145. package/content/references/base/repositories/soft-deletable.md +67 -55
  146. package/content/references/base/secrets.md +267 -0
  147. package/content/references/base/services.md +8 -6
  148. package/content/references/configuration/environment-variables.md +73 -21
  149. package/content/references/configuration/index.md +51 -31
  150. package/content/references/index.md +1 -1
  151. package/content/references/quick-reference.md +3 -16
  152. package/content/references/utilities/crypto.md +35 -76
  153. package/content/references/utilities/date.md +33 -73
  154. package/content/references/utilities/duration.md +85 -0
  155. package/content/references/utilities/index.md +6 -2
  156. package/content/references/utilities/jsx-reference.md +298 -0
  157. package/content/references/utilities/jsx.md +82 -525
  158. package/content/references/utilities/module.md +81 -61
  159. package/content/references/utilities/parse.md +34 -64
  160. package/content/references/utilities/performance.md +33 -58
  161. package/content/references/utilities/promise.md +28 -62
  162. package/content/references/utilities/request.md +58 -218
  163. package/content/references/utilities/retry.md +139 -0
  164. package/content/references/utilities/schema.md +43 -137
  165. package/content/references/utilities/statuses-reference.md +361 -0
  166. package/content/references/utilities/statuses.md +63 -667
  167. package/dist/mcp-server/index.js +0 -0
  168. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  169. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  171. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  172. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  173. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  174. package/package.json +24 -23
@@ -2,27 +2,12 @@
2
2
  title: Default Filter
3
3
  description: Automatically apply filter conditions to all repository queries
4
4
  difficulty: intermediate
5
- lastUpdated: 2026-03-15
5
+ lastUpdated: 2026-07-23
6
6
  ---
7
7
 
8
8
  # Default Filter <Badge type="tip" text="v0.0.5+" />
9
9
 
10
- Automatically apply filter conditions to all repository queries at the model level.
11
-
12
- > [!NOTE] Added in v0.0.5
13
- > This feature was introduced in IGNIS v0.0.5 to support soft delete, multi-tenancy, and other automatic filtering patterns.
14
-
15
- > [!NOTE]
16
- > Default filters are ideal for:
17
- > - **Soft Delete**: Automatically exclude deleted records
18
- > - **Multi-Tenancy**: Isolate data by tenant
19
- > - **Active Records**: Filter to active/non-expired records
20
- > - **Query Limits**: Prevent unbounded queries
21
-
22
-
23
- ## Quick Start
24
-
25
- Configure a default filter in your model:
10
+ A model's `settings.defaultFilter` merges into every `find`/`findOne`/`findById`/`count`/`updateById`/`updateAll`/`deleteById`/`deleteAll` call for that model. It's the standard way to implement soft delete, multi-tenancy, active-record scoping, and query-limit protection without repeating a `where` clause at every call site.
26
11
 
27
12
  ```typescript
28
13
  import { model, BaseEntity } from '@venizia/ignis';
@@ -30,444 +15,253 @@ import { userTable } from '@/schemas';
30
15
 
31
16
  @model({
32
17
  type: 'entity',
33
- settings: {
34
- // Applied to all repository queries
35
- defaultFilter: {
36
- where: { isDeleted: false },
37
- limit: 100,
38
- },
39
- },
18
+ settings: { defaultFilter: { where: { isDeleted: false }, limit: 100 } },
40
19
  })
41
20
  export class User extends BaseEntity<typeof User.schema> {
42
21
  static override schema = userTable;
43
22
  }
44
23
  ```
45
24
 
46
- Now all queries automatically include the default filter:
25
+ ## Options
47
26
 
48
- ```typescript
49
- // Your code
50
- await userRepository.find({
51
- filter: { where: { status: 'active' } }
52
- });
53
-
54
- // Actual query executed
55
- // WHERE isDeleted = false AND status = 'active' LIMIT 100
56
- ```
57
-
58
-
59
- ## Configuration
60
-
61
- ### Default Filter Properties
62
-
63
- All standard filter properties are supported:
27
+ | Option | Type | Default | Meaning |
28
+ |---|---|---|---|
29
+ | `settings.defaultFilter` | `TFilter` | none | Filter merged into every query for the model - `where`, `limit`, `offset`, `order`, `fields`, `include` are all valid inside it. |
30
+ | `settings.defaultLimit` | `number` (positive integer) | `DEFAULT_LIMIT` (`10`) | Per-model row cap. Independent of `defaultFilter` - see [Fields, Order & Pagination -> Default limit resolution](./fields-order-pagination#default-limit-resolution). |
31
+ | `options.shouldSkipDefaultFilter` | `boolean` | `false` | Skips the `defaultFilter` merge for one call. Does not drop `defaultLimit`. |
64
32
 
65
33
  ```typescript
66
- @model({
67
- type: 'entity',
68
- settings: {
69
- defaultFilter: {
70
- // WHERE conditions
71
- where: { isDeleted: false, tenantId: 'tenant-123' },
34
+ import { userRepository } from '@/repositories';
72
35
 
73
- // Maximum results (prevents unbounded queries)
74
- limit: 100,
75
-
76
- // Default pagination offset
77
- offset: 0,
78
-
79
- // Default sort order
80
- order: ['createdAt DESC'],
81
-
82
- // Default field selection
83
- fields: ['id', 'name', 'email', 'createdAt'],
84
-
85
- // Default relations to include
86
- include: [{ relation: 'profile' }],
87
- },
88
- },
89
- })
90
- export class User extends BaseEntity<typeof User.schema> {}
36
+ await userRepository.find({ filter: { where: { status: 'active' } } });
37
+ // WHERE "isDeleted" = false AND "status" = 'active' LIMIT 100
91
38
  ```
92
39
 
40
+ ## Merge semantics
93
41
 
94
- ## Merge Behavior
95
-
96
- When a user provides a filter, it is merged with the default filter using `FilterBuilder.mergeFilter()`. Non-`where` properties are user-wins; the `where` clause follows a **narrowing** collision law so a default scope can never be widened or dropped.
97
-
98
- | Property | Merge Strategy |
99
- |----------|----------------|
100
- | `where` | **Per-key narrowing** -- non-colliding keys carry over; a colliding key is composed so the default condition always survives (see below) |
101
- | `limit` | User replaces default (if provided) |
102
- | `offset`/`skip` | User replaces default (if provided) |
103
- | `order` | User replaces default (if provided) |
104
- | `fields` | User replaces default (if provided) |
105
- | `include` | User replaces default (if provided) |
106
-
107
- A user value of `undefined` **never** overrides a defined default -- a caller cannot blow away a tenant or soft-delete scope by passing `undefined`.
42
+ `applyDefaultFilter()` merges the model's `defaultFilter` with the caller's filter via `FilterBuilder.mergeFilter()`.
108
43
 
109
- ### Where Clause Collision Law
44
+ - **`where` narrows per-key.** See the narrowing law below.
45
+ - **Everything else is user-wins-if-provided.** A caller value replaces the default, but a caller value of `undefined` never does. A filter built by spreading an optional object can't silently blow away a tenant scope or a limit.
110
46
 
111
- Within `where`, keys present on only one side pass through untouched. When the **same key** appears in both the default and the user filter, the outcome depends on the shapes:
47
+ | Property | Merge strategy |
48
+ |---|---|
49
+ | `where` | Per-key narrowing (below) |
50
+ | `limit`, `offset`/`skip`, `order`, `fields`, `include` | Caller replaces default, if the caller's value is defined |
112
51
 
113
- | Default | User | Result |
114
- |---------|------|--------|
115
- | scalar | scalar | **User wins** (the soft-delete opt-out -- e.g. `isDeleted: false` becomes `isDeleted: true`) |
116
- | operator | operator | **AND-composed** into an `and: [...]` group (both conditions enforced) |
117
- | scalar | operator | **AND-composed** |
118
- | operator | scalar | **AND-composed** |
52
+ ### The `where` narrowing law
119
53
 
120
- A colliding `and` key concatenates both conjunct lists (both survive). A colliding `or` key AND-composes the two disjunction groups as separate conjuncts -- the user's `or` cannot swallow the default's `or`. Every AND-composed pair is appended to any existing `and` group.
54
+ Keys present on only one side pass through untouched. When the same key appears on both sides, the outcome depends on shape:
121
55
 
122
- > [!IMPORTANT]
123
- > Because operator collisions AND-compose rather than replace, a default scope (a `createdAt` floor, a tenant `inq`) can no longer be widened or dropped by a user filter. Only a bare scalar-over-scalar collision is a true override.
56
+ | Default | Caller | Result |
57
+ |---|---|---|
58
+ | scalar | scalar | **Caller wins** - the one true override (`isDeleted: false` -> `isDeleted: true` opts an admin out of soft-delete) |
59
+ | operator object | operator object | **AND-composed** into an `and: [...]` group - both conditions apply |
60
+ | scalar | operator object | **AND-composed** |
61
+ | operator object | scalar | **AND-composed** |
124
62
 
125
- ### Narrowing Example
63
+ - **`and` collisions concatenate.** Both conjunct lists merge into one.
64
+ - **`or` collisions cannot concatenate** - that would union, not narrow - so each side's `or` group becomes its own conjunct instead.
65
+ - **Non-scalar collisions always AND-compose.** Take a default scope - a `createdAt` floor, a tenant `inq`. A caller filter can narrow it, but never widen or drop it.
66
+ - **Only scalar-over-scalar is a true override.** Every other collision shape composes rather than replaces.
126
67
 
127
68
  ```typescript
128
- // Default filter: a floor on createdAt plus a tenant scope
129
- const defaultFilter = {
130
- where: { createdAt: { gte: '2024-01-01' }, tenantId: { inq: ['t1', 't2'] } },
131
- };
132
-
133
- // User filter: an upper bound on createdAt
134
- const userFilter = {
135
- where: { createdAt: { lte: '2024-12-31' } },
136
- };
137
- ```
138
-
139
- **Before** (old wholesale-replace law) -- the user key replaced the default's `createdAt`, dropping the floor:
69
+ // Default: a floor on createdAt. Caller: an upper bound on the same key.
70
+ const defaultFilter = { where: { createdAt: { gte: '2024-01-01' } } };
71
+ const userFilter = { where: { createdAt: { lte: '2024-12-31' } } };
140
72
 
141
- ```typescript
142
- // { where: { createdAt: { lte: '2024-12-31' }, tenantId: { inq: ['t1', 't2'] } } }
73
+ // Both operator objects on the same key -> AND-composed, so the floor survives:
74
+ // { where: { and: [{ createdAt: { gte: '2024-01-01' } }, { createdAt: { lte: '2024-12-31' } }] } }
143
75
  ```
144
76
 
145
- **After** (narrowing law) -- operator over operator is AND-composed, so the floor survives:
77
+ Non-colliding keys still combine with an implicit AND, exactly like two `where` objects merged by hand:
146
78
 
147
79
  ```typescript
148
- {
149
- where: {
150
- tenantId: { inq: ['t1', 't2'] },
151
- and: [
152
- { createdAt: { gte: '2024-01-01' } },
153
- { createdAt: { lte: '2024-12-31' } },
154
- ],
155
- },
156
- }
80
+ // Default: { where: { isDeleted: false, tenantId: 'tenant-123' } }
81
+ // Caller: { where: { or: [{ status: 'active' }, { priority: 'high' }] } }
82
+ // Result: WHERE "isDeleted" = false AND "tenantId" = 'tenant-123' AND ("status" = 'active' OR "priority" = 'high')
157
83
  ```
158
84
 
159
- ### Scalar Override (soft-delete opt-out)
85
+ ## Bypassing the default filter
160
86
 
161
- A plain scalar on both sides is the one case where the user still wins outright -- this is what lets an admin flip a soft-delete flag:
87
+ Pass `shouldSkipDefaultFilter: true` in `options` to skip the merge entirely. Every repository verb honors it - `find`, `findOne`, `findById`, `count`, `updateById`, `updateAll`, `deleteById`, `deleteAll`:
162
88
 
163
89
  ```typescript
164
- // Default: { where: { isDeleted: false } }
165
- // User: { where: { isDeleted: true } }
166
- // Result: { where: { isDeleted: true } }
167
- ```
90
+ // Normal - default filter applies
91
+ await repository.find({ filter: { where: { role: 'admin' } } });
92
+ // WHERE "isDeleted" = false AND "role" = 'admin'
168
93
 
169
- ### Complex Where Conditions
94
+ // Admin/maintenance path - bypassed
95
+ await repository.find({
96
+ filter: { where: { role: 'admin' } },
97
+ options: { shouldSkipDefaultFilter: true },
98
+ });
99
+ // WHERE "role" = 'admin' (includes soft-deleted rows)
100
+ ```
170
101
 
171
- Keys that do not collide combine with an implicit AND:
102
+ `updateById` and `deleteById` merge the default filter into their `{ id }` condition the same way `updateAll`/`deleteAll` merge it into their `where`. The bypass applies to all four identically:
172
103
 
173
104
  ```typescript
174
- // Default: soft delete and tenant isolation
175
- const defaultFilter = {
176
- where: {
177
- isDeleted: false,
178
- tenantId: 'tenant-123',
179
- }
180
- };
181
-
182
- // User: OR conditions (a distinct key, so it carries through)
183
- const userFilter = {
184
- where: {
185
- or: [{ status: 'active' }, { priority: 'high' }]
186
- }
187
- };
188
-
189
- // Result: AND of default + OR from user
190
- // WHERE isDeleted = false AND tenantId = 'tenant-123'
191
- // AND (status = 'active' OR priority = 'high')
105
+ // Also merges the default filter into { id: postId } - skip to update a soft-deleted row
106
+ await postRepository.updateById({
107
+ id: postId,
108
+ data: { title: 'Restored' },
109
+ options: { shouldSkipDefaultFilter: true },
110
+ });
192
111
  ```
193
112
 
194
-
195
- ## Bypassing Default Filter
196
-
197
- Use `shouldSkipDefaultFilter: true` to bypass the default filter:
113
+ It composes with a transaction the same way any other option does:
198
114
 
199
115
  ```typescript
200
- // Normal query - default filter applies
201
- await repository.find({
202
- filter: { where: { role: 'admin' } }
203
- });
204
- // WHERE isDeleted = false AND role = 'admin'
205
-
206
- // Admin query - bypass default filter
207
- await repository.find({
208
- filter: { where: { role: 'admin' } },
209
- options: { shouldSkipDefaultFilter: true }
210
- });
211
- // WHERE role = 'admin' (includes deleted records)
116
+ const tx = await repository.beginTransaction();
117
+ try {
118
+ await repository.updateAll({
119
+ where: { status: 'archived' },
120
+ data: { isDeleted: true },
121
+ options: { transaction: tx, shouldSkipDefaultFilter: true },
122
+ });
123
+ await tx.commit();
124
+ } catch (e) {
125
+ await tx.rollback();
126
+ throw e;
127
+ }
212
128
  ```
213
129
 
214
- ### Supported Operations
130
+ `updateAll`/`deleteAll` additionally require `force: true` when the resulting `where` is empty - see [Advanced Repository Features -> Empty where protection](../repositories/advanced#empty-where-protection).
131
+
132
+ | Scenario | Why bypass |
133
+ |---|---|
134
+ | Admin dashboard | View records a default scope would otherwise hide |
135
+ | Data recovery | Restore soft-deleted rows |
136
+ | Cross-tenant analytics | Count/aggregate across every tenant |
137
+ | Data migration | Update rows regardless of status |
215
138
 
216
- `shouldSkipDefaultFilter` works with all repository methods:
139
+ `shouldSkipDefaultFilter` lives on the same options interface every repository verb accepts:
217
140
 
218
141
  ```typescript
219
- // Read operations
220
- await repository.find({ filter, options: { shouldSkipDefaultFilter: true } });
221
- await repository.findOne({ filter, options: { shouldSkipDefaultFilter: true } });
222
- await repository.findById({ id, options: { shouldSkipDefaultFilter: true } });
223
- await repository.count({ where, options: { shouldSkipDefaultFilter: true } });
224
-
225
- // Update operations
226
- await repository.updateById({ id, data, options: { shouldSkipDefaultFilter: true } });
227
- await repository.updateAll({ where, data, options: { shouldSkipDefaultFilter: true } });
228
-
229
- // Delete operations
230
- await repository.deleteById({ id, options: { shouldSkipDefaultFilter: true } });
231
- await repository.deleteAll({ where, options: { shouldSkipDefaultFilter: true, force: true } });
232
- ```
142
+ interface IExtraOptions extends IWithTransaction {
143
+ shouldSkipDefaultFilter?: boolean;
144
+ log?: TRepositoryLogOptions;
145
+ lock?: TLockOptions;
146
+ }
233
147
 
234
- ### Use Cases for Bypassing
148
+ interface IWithTransaction {
149
+ transaction?: ITransaction;
150
+ }
151
+ ```
235
152
 
236
- | Scenario | Example |
237
- |----------|---------|
238
- | Admin dashboard | View all records including deleted |
239
- | Data recovery | Restore soft-deleted records |
240
- | Analytics | Count across all tenants |
241
- | Data migration | Update records regardless of status |
242
- | Audit logs | Access historical data |
153
+ `log` and `lock` are documented in [Advanced Repository Features](../repositories/advanced.md) - `log` only takes effect on write verbs (`create`/`updateById`/`updateAll`/`deleteById`/`deleteAll`), not on reads.
243
154
 
155
+ ## Configuring a default filter
244
156
 
245
- ## Common Patterns
157
+ Any `TFilter` property is valid inside `defaultFilter` - `where`, `limit`, `offset`, `order`, `fields`, `include` (see [Filter System Overview](./)). Two shapes cover most cases.
246
158
 
247
- ### Soft Delete
159
+ **Soft delete or multi-tenant scoping** - a `where` clause that every query must carry:
248
160
 
249
161
  ```typescript
250
162
  @model({
251
163
  type: 'entity',
252
- settings: {
253
- defaultFilter: {
254
- where: { deletedAt: null }, // or { isDeleted: false }
255
- },
256
- },
164
+ settings: { defaultFilter: { where: { deletedAt: null } } },
257
165
  })
258
166
  export class Post extends BaseEntity<typeof Post.schema> {}
259
167
 
260
- // All queries exclude deleted posts
261
168
  await postRepository.find({ filter: {} });
262
- // WHERE deletedAt IS NULL
169
+ // WHERE "deletedAt" IS NULL
263
170
 
264
- // Restore a deleted post
265
171
  await postRepository.updateById({
266
172
  id: postId,
267
173
  data: { deletedAt: null },
268
- options: { shouldSkipDefaultFilter: true }
174
+ options: { shouldSkipDefaultFilter: true }, // restore
269
175
  });
270
176
  ```
271
177
 
272
- ### Multi-Tenant Isolation
273
-
274
- ```typescript
275
- @model({
276
- type: 'entity',
277
- settings: {
278
- defaultFilter: {
279
- where: { tenantId: 'current-tenant' },
280
- },
281
- },
282
- })
283
- export class Document extends BaseEntity<typeof Document.schema> {}
284
-
285
- // Queries scoped to tenant
286
- await documentRepository.find({ filter: { where: { type: 'invoice' } } });
287
- // WHERE tenantId = 'current-tenant' AND type = 'invoice'
288
-
289
- // Cross-tenant admin query
290
- await documentRepository.find({
291
- filter: { where: { type: 'invoice' } },
292
- options: { shouldSkipDefaultFilter: true }
293
- });
294
- // WHERE type = 'invoice'
295
- ```
296
-
297
- ### Active Records
298
-
299
- ```typescript
300
- @model({
301
- type: 'entity',
302
- settings: {
303
- defaultFilter: {
304
- where: {
305
- isActive: true,
306
- expiresAt: { gt: new Date().toISOString() },
307
- },
308
- limit: 50,
309
- },
310
- },
311
- })
312
- export class Subscription extends BaseEntity<typeof Subscription.schema> {}
313
- ```
314
-
315
- ### Query Limit Protection
316
-
317
- Use the dedicated `settings.defaultLimit` to raise (or lower) the per-model default page size. Prefer it over putting `limit` inside `defaultFilter`:
178
+ **Query-limit protection** - prefer the dedicated `settings.defaultLimit` over a `limit` inside `defaultFilter`. It resolves independently (`query.limit ?? defaultLimit ?? 10`, see [Fields, Order & Pagination -> Default limit resolution](./fields-order-pagination#default-limit-resolution)). Unlike `defaultFilter`, it is not dropped by `shouldSkipDefaultFilter`:
318
179
 
319
180
  ```typescript
320
181
  @model({
321
182
  type: 'entity',
322
- settings: {
323
- defaultLimit: 1000, // Per-model default when a query omits `limit`
324
- },
183
+ settings: { defaultLimit: 1000 },
325
184
  })
326
185
  export class LogEntry extends BaseEntity<typeof LogEntry.schema> {}
327
186
 
328
- // User can override limit, but there's always a sensible default
329
- await logEntryRepository.find({ filter: {} }); // LIMIT 1000
187
+ await logEntryRepository.find({ filter: {} }); // LIMIT 1000
330
188
  await logEntryRepository.find({ filter: { limit: 50 } }); // LIMIT 50
331
189
  ```
332
190
 
333
- > [!TIP]
334
- > `defaultLimit` is independent of `defaultFilter`: bypassing the default filter via `shouldSkipDefaultFilter` does **not** drop the limit. See [Pagination → Default Limit](/references/base/filter-system/fields-order-pagination#default-limit).
191
+ `@model` validates `defaultLimit` at decoration time - it must be a positive integer or the class throws on load.
335
192
 
193
+ ## Relation include default filters
336
194
 
337
- ## Relation Include Default Filters
338
-
339
- When using `include` to load relations, the default filter of the related model is also applied. You can bypass it per-relation:
195
+ `include` also applies the related model's `defaultFilter`, and it can be bypassed or scoped per relation:
340
196
 
341
197
  ```typescript
342
198
  await repository.find({
343
199
  filter: {
344
200
  include: [
345
- // Default filter of related model applies
346
- { relation: 'posts' },
347
-
348
- // Skip default filter for this specific relation
349
- { relation: 'comments', shouldSkipDefaultFilter: true },
350
-
351
- // Apply a custom scope (merged with relation's default filter)
352
- { relation: 'tags', scope: { limit: 10, order: ['name ASC'] } },
353
- ]
354
- }
201
+ { relation: 'posts' }, // related model's default filter applies
202
+ { relation: 'comments', shouldSkipDefaultFilter: true }, // skipped for this relation only
203
+ { relation: 'tags', scope: { limit: 10, order: ['name ASC'] } }, // scope merges with the default filter
204
+ ],
205
+ },
355
206
  });
356
207
  ```
357
208
 
358
-
359
- ## IExtraOptions Interface
360
-
361
- The `shouldSkipDefaultFilter` option is part of the `IExtraOptions` interface:
362
-
363
- ```typescript
364
- interface IExtraOptions extends IWithTransaction {
365
- /**
366
- * If true, bypass the default filter configured in model settings.
367
- */
368
- shouldSkipDefaultFilter?: boolean;
369
- }
370
-
371
- interface IWithTransaction {
372
- transaction?: ITransaction;
373
- }
374
- ```
375
-
376
- This allows combining with transactions:
377
-
378
- ```typescript
379
- const tx = await repository.beginTransaction();
380
-
381
- try {
382
- // Both transaction and shouldSkipDefaultFilter
383
- await repository.updateAll({
384
- where: { status: 'archived' },
385
- data: { isDeleted: true },
386
- options: {
387
- transaction: tx,
388
- shouldSkipDefaultFilter: true,
389
- }
390
- });
391
-
392
- await tx.commit();
393
- } catch (e) {
394
- await tx.rollback();
395
- throw e;
396
- }
397
- ```
398
-
399
-
400
- ## How It Works
401
-
402
- ### Architecture
209
+ ## How it works
403
210
 
404
211
  ```
405
- +------------------+ +----------------------+ +------------------+
406
- | Model Settings | --> | PostgresBaseRepository | --> | Repository Method |
407
- | defaultFilter | | applyDefaultFilter() | | find/count/etc |
408
- +------------------+ +----------------------+ +------------------+
409
- |
410
- v
411
- +------------------+
412
- | FilterBuilder |
413
- | mergeFilter() |
414
- +------------------+
212
+ +------------------+ +--------------------------+ +------------------+
213
+ | Model Settings | --> | RelationalBaseRepository | --> | Repository Method |
214
+ | defaultFilter | | applyDefaultFilter() | | find/count/etc |
215
+ +------------------+ +--------------------------+ +------------------+
216
+ |
217
+ v
218
+ +------------------+
219
+ | FilterBuilder |
220
+ | mergeFilter() |
221
+ +------------------+
415
222
  ```
416
223
 
417
- ### PostgresBaseRepository
418
-
419
- `PostgresBaseRepository` (`packages/core/src/connectors/postgres/repositories/core/base.ts`) implements the default-filter behavior directly as protected methods - no mixin is composed onto it:
224
+ `RelationalBaseRepository` (compatibility alias `PostgresBaseRepository`, `packages/core-server/src/connectors/postgres/repositories/core/base.ts`) implements the default-filter behavior directly - no mixin is composed onto it:
420
225
 
421
226
  ```typescript
422
- // Check if default filter is configured
423
227
  hasDefaultFilter(): boolean
424
-
425
- // Get the raw default filter from model metadata
426
228
  getDefaultFilter(): TFilter | undefined
427
-
428
- // Merge default filter with user filter
429
- applyDefaultFilter(opts: {
430
- userFilter?: TFilter;
431
- shouldSkipDefaultFilter?: boolean;
432
- }): TFilter
229
+ getDefaultLimit(): number | undefined
230
+ applyDefaultFilter(opts: { userFilter?: TFilter; shouldSkipDefaultFilter?: boolean }): TFilter
433
231
  ```
434
232
 
435
- `getDefaultFilter()` reads `this.modelSettings?.defaultFilter`, where `modelSettings` is a protected getter on `AbstractRepository` (`src/base/repositories/core/abstract.ts`) resolved from `MetadataRegistry` keyed by the entity's constructor (not by name string) on first access, and cached for subsequent calls.
233
+ `getDefaultFilter()` reads `this.modelSettings?.defaultFilter`. `modelSettings` is a protected getter on `AbstractRepository` (`src/base/repositories/core/abstract.ts`), resolved from `MetadataRegistry` by the entity's constructor - not by name string - on first access, then memoized.
436
234
 
437
- > [!NOTE]
438
- > An older `DefaultFilterMixin` implemented this same behavior via mixin composition. It is no longer composed onto any repository class - see [Repository Mixins (Legacy)](../repositories/mixins.md) for history.
235
+ Read verbs (`find`/`findOne`/`findById`/`count`) call `applyDefaultFilter()` directly. Write verbs (`updateById`/`updateAll`/`deleteById`/`deleteAll`) route through the shared `_update`/`_delete` helpers instead. Those helpers call it against `{ where: opts.where }` (or `{ id }` for the `ById` forms) before building the SQL condition.
439
236
 
440
- ### FilterBuilder.mergeFilter()
237
+ > [!NOTE]
238
+ > An older `DefaultFilterMixin` implemented this same behavior via mixin composition. It is no longer composed onto any repository class - see [Repository Mixins (Removed)](../repositories/mixins.md) for history.
441
239
 
442
- The merge logic is implemented in `FilterBuilder`:
240
+ The merge itself is `FilterBuilder.mergeFilter()`. Reach it through the datasource's query dialect -
241
+ `FilterBuilder` is abstract, so you never construct it directly:
443
242
 
444
243
  ```typescript
445
- const filterBuilder = new FilterBuilder();
244
+ const queryDialect = dataSource.getQueryDialect();
446
245
 
447
- const merged = filterBuilder.mergeFilter({
246
+ queryDialect.mergeFilter({
448
247
  defaultFilter: { where: { isDeleted: false }, limit: 100 },
449
- userFilter: { where: { status: 'active' }, limit: 10 }
248
+ userFilter: { where: { status: 'active' }, limit: 10 },
450
249
  });
451
-
452
- // Result:
453
250
  // { where: { isDeleted: false, status: 'active' }, limit: 10 }
454
251
  ```
455
252
 
253
+ ## See also
456
254
 
457
- ## Quick Reference
458
-
459
- | Want to... | Code |
460
- |------------|------|
461
- | Configure default filter | `@model({ settings: { defaultFilter: { ... } } })` |
462
- | Bypass default filter | `options: { shouldSkipDefaultFilter: true }` |
463
- | Bypass for relation | `include: [{ relation: 'x', shouldSkipDefaultFilter: true }]` |
464
- | Combine with transaction | `options: { transaction: tx, shouldSkipDefaultFilter: true }` |
465
- | Check if model has default | `repository.hasDefaultFilter()` |
466
- | Get raw default filter | `repository.getDefaultFilter()` |
467
-
255
+ - [Filter System Overview](./) - the `filter` shape and every operator family
256
+ - [Fields, Order & Pagination](./fields-order-pagination) - `defaultLimit` resolution in full
257
+ - [Advanced Repository Features](../repositories/advanced.md) - transactions, `log`/`lock` options, empty-where protection
258
+ - [Repository Mixins (Removed)](../repositories/mixins.md) - history of the removed `DefaultFilterMixin`
468
259
 
469
- ## Next Steps
260
+ **Files:**
470
261
 
471
- - [Filter System Overview](./index.md) - Filter structure and operators
472
- - [Repository Mixins (Legacy)](../repositories/mixins.md) - Historical mixin architecture
473
- - [Advanced Features](../repositories/advanced.md) - Transactions, hidden properties
262
+ - [`packages/core-server/src/connectors/relational/repositories/dialect/filter.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/relational/repositories/dialect/filter.ts) - `FilterBuilder.mergeFilter()`/`mergeWhere()`, the narrowing merge
263
+ - [`packages/core-server/src/connectors/postgres/repositories/core/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/core/base.ts) - `RelationalBaseRepository`, `applyDefaultFilter`/`getDefaultFilter`/`getDefaultLimit`
264
+ - [`packages/core-server/src/connectors/postgres/repositories/core/readable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/core/readable.ts) - `find`/`findOne`/`count` calling `applyDefaultFilter`
265
+ - [`packages/core-server/src/connectors/postgres/repositories/core/persistable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/core/persistable.ts) - `_update`/`_delete` calling `applyDefaultFilter` for `updateById`/`updateAll`/`deleteById`/`deleteAll`
266
+ - [`packages/core-server/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/metadata/persistents.ts) - `@model` decorator, `defaultLimit` validation
267
+ - [`packages/core-server/src/base/repositories/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/repositories/common/types.ts) - `IExtraOptions`, `IWithTransaction`