@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
@@ -6,10 +6,33 @@ difficulty: intermediate
6
6
 
7
7
  # Use Case Gallery
8
8
 
9
- Real-world examples of filter usage with corresponding SQL.
9
+ Runnable `filter` objects, paired with the SQL `FilterBuilder` produces for them. Copy the shape closest to what you need. For the operators themselves, start at the [Filter System Overview](./).
10
10
 
11
+ ## Soft delete
11
12
 
12
- ## E-commerce Product Search
13
+ Goal: return only non-deleted rows, or only deleted ones.
14
+
15
+ ```typescript
16
+ // Active (non-deleted) records
17
+ const activeRecords = await repository.find({
18
+ filter: { where: { deletedAt: { is: null } } },
19
+ });
20
+ // SQL: SELECT * FROM "Record" WHERE "deleted_at" IS NULL
21
+
22
+ // ONLY soft-deleted records
23
+ const deletedRecords = await repository.find({
24
+ filter: { where: { deletedAt: { isn: null } } },
25
+ });
26
+ // SQL: SELECT * FROM "Record" WHERE "deleted_at" IS NOT NULL
27
+ ```
28
+
29
+ Notice: `is`/`isn` against a nullable timestamp is the whole pattern - no separate `deleted: boolean` column needed.
30
+
31
+ If every query on a model should exclude deleted rows, encode this once as `settings.defaultFilter` instead of repeating it at every call site. See [Default Filter](./default-filter).
32
+
33
+ ## E-commerce product search
34
+
35
+ Goal: a price range, a minimum quantity, and a status, sorted and paged for a listing page.
13
36
 
14
37
  ```typescript
15
38
  const products = await productRepository.find({
@@ -23,7 +46,7 @@ const products = await productRepository.find({
23
46
  order: ['rating DESC', 'reviewCount DESC'],
24
47
  fields: ['id', 'name', 'price', 'rating', 'imageUrl'],
25
48
  limit: 24,
26
- }
49
+ },
27
50
  });
28
51
 
29
52
  // SQL:
@@ -37,8 +60,11 @@ const products = await productRepository.find({
37
60
  // LIMIT 24
38
61
  ```
39
62
 
63
+ Notice: four `where` keys AND-compose automatically - no explicit `and` needed for a flat condition list.
64
+
65
+ ## Admin dashboard: recent users
40
66
 
41
- ## Admin Dashboard: Recent Users
67
+ Goal: users created in the last 30 days, excluding banned or suspended accounts, with a verified email.
42
68
 
43
69
  ```typescript
44
70
  const thirtyDaysAgo = new Date();
@@ -54,7 +80,7 @@ const recentUsers = await userRepository.find({
54
80
  order: ['createdAt DESC'],
55
81
  fields: ['id', 'email', 'name', 'createdAt', 'status'],
56
82
  limit: 50,
57
- }
83
+ },
58
84
  });
59
85
 
60
86
  // SQL:
@@ -67,8 +93,39 @@ const recentUsers = await userRepository.find({
67
93
  // LIMIT 50
68
94
  ```
69
95
 
96
+ Notice: `nin` on `status` silently drops any row where `status` is `NULL`. See [Tips & Edge Cases](./tips) before relying on this for a nullable column.
97
+
98
+ ## Multi-tenant isolation at the call site
70
99
 
71
- ## Task Management: Priority Tags
100
+ `settings.defaultFilter` (see [Default Filter](./default-filter)) is the model-level way to enforce a tenant scope. The alternative below is a helper that injects `tenantId` at every call site instead. Use it when tenant isolation is a caller concern, not a per-model constant.
101
+
102
+ ```typescript
103
+ const getTenantProducts = (tenantId: string, filter: TFilter<TProductSchema>) =>
104
+ productRepository.find({
105
+ filter: {
106
+ ...filter,
107
+ where: { ...filter.where, tenantId, deletedAt: { is: null } },
108
+ },
109
+ });
110
+
111
+ await getTenantProducts('tenant-abc', {
112
+ where: { category: 'electronics' },
113
+ order: ['createdAt DESC'],
114
+ limit: 20,
115
+ });
116
+
117
+ // SQL:
118
+ // SELECT * FROM "Product"
119
+ // WHERE "category" = 'electronics' AND "tenant_id" = 'tenant-abc' AND "deleted_at" IS NULL
120
+ // ORDER BY "created_at" DESC
121
+ // LIMIT 20
122
+ ```
123
+
124
+ Notice: this is a plain object spread, not `mergeFilter`'s narrowing merge - `tenantId`/`deletedAt` overwrite same-named keys from `filter.where` because they're spread last.
125
+
126
+ ## Task management: priority tags
127
+
128
+ Goal: open tasks assigned to the current user that carry an urgent or high-priority tag, with the parent project loaded.
72
129
 
73
130
  ```typescript
74
131
  const priorityTasks = await taskRepository.find({
@@ -80,7 +137,7 @@ const priorityTasks = await taskRepository.find({
80
137
  },
81
138
  order: ['dueDate ASC', 'createdAt ASC'],
82
139
  include: [{ relation: 'project' }],
83
- }
140
+ },
84
141
  });
85
142
 
86
143
  // SQL:
@@ -95,35 +152,87 @@ const priorityTasks = await taskRepository.find({
95
152
  // SELECT * FROM "Project" WHERE "id" IN (...)
96
153
  ```
97
154
 
155
+ Notice: `include` runs as a separate query, not a SQL `JOIN` - see [Relations & Includes](../repositories/relations).
98
156
 
99
- ## Soft Delete Handling
157
+ ## Date range queries
158
+
159
+ Goal: a closed window (a specific week) versus a rolling window (the last 7 days).
100
160
 
101
161
  ```typescript
102
- // Find active records (soft delete pattern)
103
- const activeRecords = await repository.find({
162
+ const startOfWeek = new Date('2024-12-29');
163
+ const endOfWeek = new Date('2025-01-04');
164
+
165
+ const weekEvents = await eventRepository.find({
104
166
  filter: {
105
- where: { deletedAt: { is: null } },
106
- }
167
+ where: { eventDate: { between: [startOfWeek, endOfWeek] } },
168
+ order: ['eventDate ASC'],
169
+ },
107
170
  });
171
+ // SQL: SELECT * FROM "Event" WHERE "event_date" BETWEEN '2024-12-29' AND '2025-01-04' ORDER BY "event_date" ASC
172
+ ```
108
173
 
174
+ ```typescript
175
+ const sevenDaysAgo = new Date();
176
+ sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
177
+
178
+ const recentOrders = await orderRepository.find({
179
+ filter: {
180
+ where: {
181
+ createdAt: { gte: sevenDaysAgo },
182
+ status: { in: ['completed', 'shipped'] },
183
+ total: { gte: 100 },
184
+ },
185
+ order: ['total DESC'],
186
+ limit: 100,
187
+ },
188
+ });
109
189
  // SQL:
110
- // SELECT * FROM "Record" WHERE "deleted_at" IS NULL
190
+ // SELECT * FROM "Order"
191
+ // WHERE "created_at" >= '2024-12-24T00:00:00.000Z' AND "status" IN ('completed', 'shipped') AND "total" >= 100
192
+ // ORDER BY "total" DESC LIMIT 100
111
193
  ```
112
194
 
195
+ Notice: `between` needs exactly two elements - `FilterBuilder` throws on any other array length.
196
+
197
+ ## Inventory low-stock alert
198
+
199
+ Goal: active products at or under a reorder threshold, flagged critical below 5 units or fast-moving stock at 10 or fewer.
200
+
113
201
  ```typescript
114
- // Find ONLY soft-deleted records
115
- const deletedRecords = await repository.find({
202
+ const lowStockProducts = await productRepository.find({
116
203
  filter: {
117
- where: { deletedAt: { isn: null } },
118
- }
204
+ where: {
205
+ status: 'active',
206
+ quantity: { lte: 10 },
207
+ 'metadata.reorderPoint': { isn: null },
208
+ or: [
209
+ { quantity: { lt: 5 } }, // Critical: below 5
210
+ { and: [{ quantity: { lte: 10 } }, { 'metadata.fastMoving': true }] },
211
+ ],
212
+ },
213
+ order: ['quantity ASC'],
214
+ fields: ['id', 'name', 'quantity', 'metadata'],
215
+ },
119
216
  });
120
217
 
121
218
  // SQL:
122
- // SELECT * FROM "Record" WHERE "deleted_at" IS NOT NULL
219
+ // SELECT "id", "name", "quantity", "metadata"
220
+ // FROM "Product"
221
+ // WHERE "status" = 'active'
222
+ // AND "quantity" <= 10
223
+ // AND "metadata" #>> '{reorderPoint}' IS NOT NULL
224
+ // AND (
225
+ // "quantity" < 5
226
+ // OR ("quantity" <= 10 AND "metadata" #>> '{fastMoving}' = 'true')
227
+ // )
228
+ // ORDER BY "quantity" ASC
123
229
  ```
124
230
 
231
+ Notice: `or` nests an `and` group one level deep - `FilterBuilder` recurses through logical groups, so nesting depth is not limited to one.
232
+
233
+ ## Complex authorization filter
125
234
 
126
- ## Complex Authorization Filter
235
+ Goal: an admin sees everything; everyone else sees only what they own, what is public, or what is shared with them.
127
236
 
128
237
  ```typescript
129
238
  const getAuthorizedFilter = (user: User): TWhere<TDocumentSchema> => {
@@ -143,23 +252,11 @@ const getAuthorizedFilter = (user: User): TWhere<TDocumentSchema> => {
143
252
  };
144
253
 
145
254
  const documents = await documentRepository.find({
146
- filter: {
147
- where: getAuthorizedFilter(currentUser),
148
- order: ['updatedAt DESC'],
149
- limit: 100,
150
- },
255
+ filter: { where: getAuthorizedFilter(currentUser), order: ['updatedAt DESC'], limit: 100 },
151
256
  });
152
257
 
153
- // SQL (for admin):
154
- // SELECT *
155
- // FROM "Document"
156
- // WHERE "deleted_at" IS NULL
157
- // ORDER BY "updated_at" DESC
158
- // LIMIT 100
159
-
160
- // SQL (for regular user):
161
- // SELECT *
162
- // FROM "Document"
258
+ // SQL (regular user):
259
+ // SELECT * FROM "Document"
163
260
  // WHERE "deleted_at" IS NULL
164
261
  // AND (
165
262
  // "owner_id" = 'user-123'
@@ -167,23 +264,21 @@ const documents = await documentRepository.find({
167
264
  // OR "shared_with_teams"::text[] && ARRAY['team-1', 'team-2']::text[]
168
265
  // OR "shared_with_users"::text[] @> ARRAY['user-123']::text[]
169
266
  // )
170
- // ORDER BY "updated_at" DESC
171
- // LIMIT 100
267
+ // ORDER BY "updated_at" DESC LIMIT 100
172
268
  ```
173
269
 
270
+ Notice: a plain TypeScript function builds the `where`, branching on role. A filter is a normal object, not a DSL with its own control flow.
271
+
272
+ ## Full-text search with metadata
174
273
 
175
- ## Full-Text Search with Metadata
274
+ Goal: assemble a `where` clause from optional caller input, adding a key only when the caller supplied it.
176
275
 
177
276
  ```typescript
178
- const searchProducts = async (query: string, filters: {
179
- minRating?: number;
180
- maxPrice?: number;
181
- categories?: string[];
182
- }) => {
183
- const where: TWhere<TProductSchema> = {
184
- status: 'active',
185
- deletedAt: { is: null },
186
- };
277
+ const searchProducts = async (
278
+ query: string,
279
+ filters: { minRating?: number; maxPrice?: number; categories?: string[] },
280
+ ) => {
281
+ const where: TWhere<TProductSchema> = { status: 'active', deletedAt: { is: null } };
187
282
 
188
283
  if (query) {
189
284
  where.or = [
@@ -192,50 +287,33 @@ const searchProducts = async (query: string, filters: {
192
287
  { 'metadata.keywords': { ilike: `%${query}%` } },
193
288
  ];
194
289
  }
195
-
196
- if (filters.minRating) {
197
- where.rating = { gte: filters.minRating };
198
- }
199
-
200
- if (filters.maxPrice) {
201
- where.price = { lte: filters.maxPrice };
202
- }
203
-
204
- if (filters.categories?.length) {
205
- // Use array operator on a PostgreSQL array column
206
- where.categories = { contains: filters.categories };
207
- }
290
+ if (filters.minRating) where.rating = { gte: filters.minRating };
291
+ if (filters.maxPrice) where.price = { lte: filters.maxPrice };
292
+ if (filters.categories?.length) where.categories = { contains: filters.categories };
208
293
 
209
294
  return productRepository.find({
210
- filter: {
211
- where,
212
- order: ['rating DESC', 'createdAt DESC'],
213
- limit: 50,
214
- },
295
+ filter: { where, order: ['rating DESC', 'createdAt DESC'], limit: 50 },
215
296
  });
216
297
  };
217
298
 
218
- // Example: searchProducts('wireless', { minRating: 4, maxPrice: 200, categories: ['electronics'] })
299
+ // searchProducts('wireless', { minRating: 4, maxPrice: 200, categories: ['electronics'] })
219
300
  //
220
301
  // SQL:
221
- // SELECT *
222
- // FROM "Product"
302
+ // SELECT * FROM "Product"
223
303
  // WHERE "status" = 'active'
224
304
  // AND "deleted_at" IS NULL
225
- // AND (
226
- // "name" ILIKE '%wireless%'
227
- // OR "description" ILIKE '%wireless%'
228
- // OR "metadata" #>> '{keywords}' ILIKE '%wireless%'
229
- // )
305
+ // AND ("name" ILIKE '%wireless%' OR "description" ILIKE '%wireless%' OR "metadata" #>> '{keywords}' ILIKE '%wireless%')
230
306
  // AND "rating" >= 4
231
307
  // AND "price" <= 200
232
308
  // AND "categories"::text[] @> ARRAY['electronics']::text[]
233
- // ORDER BY "rating" DESC, "created_at" DESC
234
- // LIMIT 50
309
+ // ORDER BY "rating" DESC, "created_at" DESC LIMIT 50
235
310
  ```
236
311
 
312
+ Notice: `ilike` reaches into a JSON path (`'metadata.keywords'`) the same way it reaches a top-level column.
313
+
314
+ ## Everything at once
237
315
 
238
- ## Massive Filter Example
316
+ Every operator family, a JSON path, a three-way `or`, and a scoped relation include - all in one filter. This is the ceiling of what a single `TFilter` can express.
239
317
 
240
318
  ```typescript
241
319
  const massiveFilter: TFilter<TProductSchema> = {
@@ -254,12 +332,9 @@ const massiveFilter: TFilter<TProductSchema> = {
254
332
  { isFeatured: true },
255
333
  { 'metadata.promotion.active': true },
256
334
  { 'metadata.promotion.discount': { gte: 20 } },
257
- ]
258
- },
259
- {
260
- createdAt: { gte: new Date('2024-12-01') },
261
- 'metadata.isNewArrival': true,
335
+ ],
262
336
  },
337
+ { createdAt: { gte: new Date('2024-12-01') }, 'metadata.isNewArrival': true },
263
338
  ],
264
339
  category: { nin: ['discontinued', 'recalled'] },
265
340
  suppliers: { overlaps: ['supplier-a', 'supplier-b'] },
@@ -270,14 +345,7 @@ const massiveFilter: TFilter<TProductSchema> = {
270
345
  skip: 0,
271
346
  include: [
272
347
  { relation: 'category' },
273
- {
274
- relation: 'reviews',
275
- scope: {
276
- where: { rating: { gte: 4 } },
277
- order: ['createdAt DESC'],
278
- limit: 5,
279
- },
280
- },
348
+ { relation: 'reviews', scope: { where: { rating: { gte: 4 } }, order: ['createdAt DESC'], limit: 5 } },
281
349
  ],
282
350
  };
283
351
 
@@ -291,163 +359,36 @@ const products = await productRepository.find({ filter: massiveFilter });
291
359
  // AND "price" >= 50 AND "price" <= 500
292
360
  // AND "quantity" > 0
293
361
  // AND "tags"::text[] @> ARRAY['electronics', 'portable']::text[]
294
- // AND CASE
295
- // WHEN ("metadata" #>> '{priority}') ~ '^-?[0-9]+(\.[0-9]+)?$'
296
- // THEN ("metadata" #>> '{priority}')::numeric ELSE NULL
297
- // END >= 3
362
+ // AND CASE WHEN ("metadata" #>> '{priority}') ~ '^-?[0-9]+(\.[0-9]+)?$'
363
+ // THEN ("metadata" #>> '{priority}')::numeric ELSE NULL END >= 3
298
364
  // AND "metadata" #>> '{features,wireless}' = 'true'
299
365
  // AND (
300
366
  // "rating" >= 4.5
301
- // OR (
302
- // "is_featured" = true
303
- // AND "metadata" #>> '{promotion,active}' = 'true'
304
- // AND CASE
305
- // WHEN ("metadata" #>> '{promotion,discount}') ~ '^-?[0-9]+(\.[0-9]+)?$'
306
- // THEN ("metadata" #>> '{promotion,discount}')::numeric ELSE NULL
307
- // END >= 20
308
- // )
309
- // OR (
310
- // "created_at" >= '2024-12-01T00:00:00.000Z'
311
- // AND "metadata" #>> '{isNewArrival}' = 'true'
312
- // )
367
+ // OR ("is_featured" = true AND "metadata" #>> '{promotion,active}' = 'true'
368
+ // AND CASE WHEN ("metadata" #>> '{promotion,discount}') ~ '^-?[0-9]+(\.[0-9]+)?$'
369
+ // THEN ("metadata" #>> '{promotion,discount}')::numeric ELSE NULL END >= 20)
370
+ // OR ("created_at" >= '2024-12-01T00:00:00.000Z' AND "metadata" #>> '{isNewArrival}' = 'true')
313
371
  // )
314
372
  // AND "category" NOT IN ('discontinued', 'recalled')
315
373
  // AND "suppliers"::text[] && ARRAY['supplier-a', 'supplier-b']::text[]
316
374
  // ORDER BY "metadata" #> '{priority}' DESC, "rating" DESC, "created_at" DESC
317
375
  // LIMIT 20 OFFSET 0
318
376
  //
319
- // -- Separate query for category relation:
377
+ // -- Separate queries for relations:
320
378
  // SELECT * FROM "Category" WHERE "id" IN (...)
321
- //
322
- // -- Separate query for reviews relation:
323
- // SELECT * FROM "Review"
324
- // WHERE "product_id" IN (...) AND "rating" >= 4
325
- // ORDER BY "created_at" DESC
326
- // LIMIT 5
379
+ // SELECT * FROM "Review" WHERE "product_id" IN (...) AND "rating" >= 4 ORDER BY "created_at" DESC LIMIT 5
327
380
  ```
328
381
 
382
+ Notice: `'metadata.priority': { gte: 3 }` gets the numeric `CASE` cast because the operand is a number. `'metadata.isNewArrival': true` does not, because it compares as text. See [Tips & Edge Cases](./tips) for the full casting rule.
329
383
 
330
- ## Date Range Queries
331
-
332
- ```typescript
333
- // Events this week
334
- const startOfWeek = new Date('2024-12-29');
335
- const endOfWeek = new Date('2025-01-04');
336
-
337
- const weekEvents = await eventRepository.find({
338
- filter: {
339
- where: {
340
- eventDate: { between: [startOfWeek, endOfWeek] }
341
- },
342
- order: ['eventDate ASC']
343
- }
344
- });
345
-
346
- // SQL:
347
- // SELECT *
348
- // FROM "Event"
349
- // WHERE "event_date" BETWEEN '2024-12-29' AND '2025-01-04'
350
- // ORDER BY "event_date" ASC
351
- ```
352
-
353
- ```typescript
354
- // Orders in the last 7 days
355
- const sevenDaysAgo = new Date();
356
- sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
357
-
358
- const recentOrders = await orderRepository.find({
359
- filter: {
360
- where: {
361
- createdAt: { gte: sevenDaysAgo },
362
- status: { in: ['completed', 'shipped'] },
363
- total: { gte: 100 }
364
- },
365
- order: ['total DESC'],
366
- limit: 100
367
- }
368
- });
369
-
370
- // SQL:
371
- // SELECT *
372
- // FROM "Order"
373
- // WHERE "created_at" >= '2024-12-24T00:00:00.000Z'
374
- // AND "status" IN ('completed', 'shipped')
375
- // AND "total" >= 100
376
- // ORDER BY "total" DESC
377
- // LIMIT 100
378
- ```
384
+ ## See also
379
385
 
386
+ - [Filter System Overview](./) - the `filter` shape and every `where` operator family
387
+ - [Default Filter](./default-filter) - model-level scoping instead of the call-site pattern shown above
388
+ - [Application Usage](./application-usage) - how a filter reaches the repository from an HTTP request
389
+ - [Tips & Edge Cases](./tips) - `NULL` handling, empty-array semantics, and other gotchas that show up in filters like these
380
390
 
381
- ## Multi-Tenant Data Isolation
391
+ **Files:**
382
392
 
383
- ```typescript
384
- const getTenantProducts = async (tenantId: string, filter: TFilter<TProductSchema>) => {
385
- return productRepository.find({
386
- filter: {
387
- ...filter,
388
- where: {
389
- ...filter.where,
390
- tenantId, // Always enforce tenant isolation
391
- deletedAt: { is: null },
392
- },
393
- },
394
- });
395
- };
396
-
397
- // Usage
398
- await getTenantProducts('tenant-abc', {
399
- where: { category: 'electronics' },
400
- order: ['createdAt DESC'],
401
- limit: 20
402
- });
403
-
404
- // SQL:
405
- // SELECT *
406
- // FROM "Product"
407
- // WHERE "category" = 'electronics'
408
- // AND "tenant_id" = 'tenant-abc'
409
- // AND "deleted_at" IS NULL
410
- // ORDER BY "created_at" DESC
411
- // LIMIT 20
412
- ```
413
-
414
-
415
- ## Inventory Low Stock Alert
416
-
417
- ```typescript
418
- const lowStockProducts = await productRepository.find({
419
- filter: {
420
- where: {
421
- status: 'active',
422
- quantity: { lte: 10 },
423
- 'metadata.reorderPoint': { isn: null },
424
- or: [
425
- { quantity: { lt: 5 } }, // Critical: below 5
426
- {
427
- and: [
428
- { quantity: { lte: 10 } },
429
- { 'metadata.fastMoving': true }
430
- ]
431
- }
432
- ]
433
- },
434
- order: ['quantity ASC'],
435
- fields: ['id', 'name', 'quantity', 'metadata']
436
- }
437
- });
438
-
439
- // SQL:
440
- // SELECT "id", "name", "quantity", "metadata"
441
- // FROM "Product"
442
- // WHERE "status" = 'active'
443
- // AND "quantity" <= 10
444
- // AND "metadata" #>> '{reorderPoint}' IS NOT NULL
445
- // AND (
446
- // "quantity" < 5
447
- // OR (
448
- // "quantity" <= 10
449
- // AND "metadata" #>> '{fastMoving}' = 'true'
450
- // )
451
- // )
452
- // ORDER BY "quantity" ASC
453
- ```
393
+ - [`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
394
+ - [`packages/core-server/src/connectors/postgres/repositories/dialect/query.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/dialect/query.ts) - `PostgresQueryOperators.FNS`, per-operator SQL builders