@venizia/ignis-docs 0.2.0 → 0.2.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) 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 +22 -11
  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 +26 -2
  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 +6 -2
  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 +182 -93
  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 +107 -322
  26. package/content/extensions/components/authentication/api.md +454 -603
  27. package/content/extensions/components/authentication/errors.md +121 -498
  28. package/content/extensions/components/authentication/index.md +88 -801
  29. package/content/extensions/components/authentication/usage.md +207 -956
  30. package/content/extensions/components/authorization/api.md +736 -656
  31. package/content/extensions/components/authorization/errors.md +168 -206
  32. package/content/extensions/components/authorization/index.md +82 -797
  33. package/content/extensions/components/authorization/usage.md +194 -527
  34. package/content/extensions/components/health-check.md +71 -243
  35. package/content/extensions/components/mail/api.md +504 -287
  36. package/content/extensions/components/mail/errors.md +73 -61
  37. package/content/extensions/components/mail/index.md +96 -467
  38. package/content/extensions/components/mail/usage.md +130 -172
  39. package/content/extensions/components/request-tracker.md +66 -173
  40. package/content/extensions/components/socket-io/api.md +195 -13
  41. package/content/extensions/components/socket-io/errors.md +3 -3
  42. package/content/extensions/components/socket-io/index.md +50 -337
  43. package/content/extensions/components/socket-io/usage.md +143 -26
  44. package/content/extensions/components/static-asset/api.md +410 -142
  45. package/content/extensions/components/static-asset/errors.md +110 -53
  46. package/content/extensions/components/static-asset/index.md +79 -608
  47. package/content/extensions/components/static-asset/usage.md +180 -300
  48. package/content/extensions/components/websocket/api.md +275 -399
  49. package/content/extensions/components/websocket/errors.md +47 -56
  50. package/content/extensions/components/websocket/index.md +74 -407
  51. package/content/extensions/components/websocket/usage.md +110 -341
  52. package/content/extensions/helpers/cron/index.md +51 -160
  53. package/content/extensions/helpers/crypto/index.md +62 -483
  54. package/content/extensions/helpers/crypto/reference.md +456 -0
  55. package/content/extensions/helpers/env/index.md +60 -178
  56. package/content/extensions/helpers/error/index.md +221 -207
  57. package/content/extensions/helpers/inversion/index.md +65 -556
  58. package/content/extensions/helpers/inversion/reference.md +522 -0
  59. package/content/extensions/helpers/kafka/admin.md +20 -1
  60. package/content/extensions/helpers/kafka/compile-binary.md +41 -35
  61. package/content/extensions/helpers/kafka/consumer.md +54 -20
  62. package/content/extensions/helpers/kafka/examples.md +21 -16
  63. package/content/extensions/helpers/kafka/index.md +80 -610
  64. package/content/extensions/helpers/kafka/producer.md +134 -5
  65. package/content/extensions/helpers/kafka/schema-registry.md +45 -70
  66. package/content/extensions/helpers/logger/hf-logger.md +193 -0
  67. package/content/extensions/helpers/logger/index.md +64 -563
  68. package/content/extensions/helpers/logger/pino.md +85 -0
  69. package/content/extensions/helpers/logger/reference.md +746 -0
  70. package/content/extensions/helpers/network/api.md +241 -195
  71. package/content/extensions/helpers/network/index.md +72 -530
  72. package/content/extensions/helpers/queue/index.md +72 -900
  73. package/content/extensions/helpers/queue/reference.md +467 -0
  74. package/content/extensions/helpers/redis/index.md +73 -645
  75. package/content/extensions/helpers/redis/reference.md +727 -0
  76. package/content/extensions/helpers/secrets/index.md +66 -0
  77. package/content/extensions/helpers/socket-io/api.md +305 -203
  78. package/content/extensions/helpers/socket-io/index.md +66 -432
  79. package/content/extensions/helpers/storage/api.md +564 -462
  80. package/content/extensions/helpers/storage/index.md +77 -573
  81. package/content/extensions/helpers/types/index.md +66 -499
  82. package/content/extensions/helpers/types/reference.md +650 -0
  83. package/content/extensions/helpers/uid/index.md +58 -227
  84. package/content/extensions/helpers/websocket/api.md +329 -216
  85. package/content/extensions/helpers/websocket/index.md +65 -503
  86. package/content/extensions/helpers/worker-thread/index.md +58 -396
  87. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  88. package/content/guides/core-concepts/persistent/models.md +1 -1
  89. package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
  90. package/content/guides/core-concepts/persistent/transactions.md +1 -1
  91. package/content/guides/core-concepts/secrets-vault.md +177 -0
  92. package/content/guides/core-concepts/services.md +1 -1
  93. package/content/guides/migrations/redis-helpers-migration.md +1 -1
  94. package/content/guides/migrations/unified-connectors-migration.md +2 -2
  95. package/content/guides/tutorials/ecommerce-api.md +3 -8
  96. package/content/references/base/application.md +1 -1
  97. package/content/references/base/connectors.md +79 -136
  98. package/content/references/base/datasources-reference.md +599 -0
  99. package/content/references/base/datasources.md +84 -444
  100. package/content/references/base/dependency-injection.md +17 -39
  101. package/content/references/base/filter-system/application-usage.md +69 -121
  102. package/content/references/base/filter-system/array-operators.md +12 -0
  103. package/content/references/base/filter-system/comparison-operators.md +12 -0
  104. package/content/references/base/filter-system/default-filter.md +136 -348
  105. package/content/references/base/filter-system/fields-order-pagination.md +38 -16
  106. package/content/references/base/filter-system/index.md +106 -257
  107. package/content/references/base/filter-system/json-filtering.md +12 -2
  108. package/content/references/base/filter-system/list-operators.md +16 -2
  109. package/content/references/base/filter-system/logical-operators.md +13 -0
  110. package/content/references/base/filter-system/null-operators.md +13 -0
  111. package/content/references/base/filter-system/pattern-matching.md +12 -0
  112. package/content/references/base/filter-system/quick-reference.md +11 -2
  113. package/content/references/base/filter-system/range-operators.md +12 -0
  114. package/content/references/base/filter-system/tips.md +70 -133
  115. package/content/references/base/filter-system/use-cases.md +156 -233
  116. package/content/references/base/middlewares.md +35 -21
  117. package/content/references/base/models-reference.md +886 -0
  118. package/content/references/base/models.md +80 -1452
  119. package/content/references/base/repositories/advanced.md +156 -192
  120. package/content/references/base/repositories/index.md +77 -650
  121. package/content/references/base/repositories/mixins.md +22 -18
  122. package/content/references/base/repositories/relations.md +123 -171
  123. package/content/references/base/repositories/soft-deletable.md +58 -56
  124. package/content/references/base/secrets.md +263 -0
  125. package/content/references/base/services.md +2 -2
  126. package/content/references/configuration/environment-variables.md +48 -4
  127. package/content/references/configuration/index.md +49 -31
  128. package/content/references/quick-reference.md +3 -16
  129. package/content/references/utilities/crypto.md +35 -76
  130. package/content/references/utilities/date.md +33 -73
  131. package/content/references/utilities/index.md +1 -1
  132. package/content/references/utilities/jsx-reference.md +298 -0
  133. package/content/references/utilities/jsx.md +82 -525
  134. package/content/references/utilities/module.md +29 -62
  135. package/content/references/utilities/parse.md +34 -64
  136. package/content/references/utilities/performance.md +33 -58
  137. package/content/references/utilities/promise.md +28 -62
  138. package/content/references/utilities/request.md +57 -218
  139. package/content/references/utilities/schema.md +43 -137
  140. package/content/references/utilities/statuses-reference.md +361 -0
  141. package/content/references/utilities/statuses.md +63 -667
  142. package/package.json +8 -8
@@ -6,10 +6,31 @@ 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 that's 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
+ The smallest real use case - `is`/`isn` against a nullable timestamp:
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
+ 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).
30
+
31
+ ## E-commerce product search
32
+
33
+ Range, list, and pattern operators combined with field selection and sorting:
13
34
 
14
35
  ```typescript
15
36
  const products = await productRepository.find({
@@ -23,7 +44,7 @@ const products = await productRepository.find({
23
44
  order: ['rating DESC', 'reviewCount DESC'],
24
45
  fields: ['id', 'name', 'price', 'rating', 'imageUrl'],
25
46
  limit: 24,
26
- }
47
+ },
27
48
  });
28
49
 
29
50
  // SQL:
@@ -37,8 +58,9 @@ const products = await productRepository.find({
37
58
  // LIMIT 24
38
59
  ```
39
60
 
61
+ ## Admin dashboard: recent users
40
62
 
41
- ## Admin Dashboard: Recent Users
63
+ `gte` for a rolling window, `nin` to exclude states, `isn` for presence:
42
64
 
43
65
  ```typescript
44
66
  const thirtyDaysAgo = new Date();
@@ -54,7 +76,7 @@ const recentUsers = await userRepository.find({
54
76
  order: ['createdAt DESC'],
55
77
  fields: ['id', 'email', 'name', 'createdAt', 'status'],
56
78
  limit: 50,
57
- }
79
+ },
58
80
  });
59
81
 
60
82
  // SQL:
@@ -67,8 +89,37 @@ const recentUsers = await userRepository.find({
67
89
  // LIMIT 50
68
90
  ```
69
91
 
92
+ ## Multi-tenant isolation at the call site
70
93
 
71
- ## Task Management: Priority Tags
94
+ `settings.defaultFilter` (see [Default Filter](./default-filter)) is the model-level way to enforce a tenant scope. A helper that injects `tenantId` at every call site is the call-site alternative - useful when tenant isolation is a caller concern rather than a per-model constant:
95
+
96
+ ```typescript
97
+ const getTenantProducts = (tenantId: string, filter: TFilter<TProductSchema>) =>
98
+ productRepository.find({
99
+ filter: {
100
+ ...filter,
101
+ where: { ...filter.where, tenantId, deletedAt: { is: null } },
102
+ },
103
+ });
104
+
105
+ await getTenantProducts('tenant-abc', {
106
+ where: { category: 'electronics' },
107
+ order: ['createdAt DESC'],
108
+ limit: 20,
109
+ });
110
+
111
+ // SQL:
112
+ // SELECT * FROM "Product"
113
+ // WHERE "category" = 'electronics' AND "tenant_id" = 'tenant-abc' AND "deleted_at" IS NULL
114
+ // ORDER BY "created_at" DESC
115
+ // LIMIT 20
116
+ ```
117
+
118
+ Unlike `defaultFilter`'s narrowing merge, this is a plain object spread - `tenantId`/`deletedAt` simply overwrite same-named keys from `filter.where` because they're spread last.
119
+
120
+ ## Task management: priority tags
121
+
122
+ `nin` plus an array `overlaps` operator, with a relation include:
72
123
 
73
124
  ```typescript
74
125
  const priorityTasks = await taskRepository.find({
@@ -80,7 +131,7 @@ const priorityTasks = await taskRepository.find({
80
131
  },
81
132
  order: ['dueDate ASC', 'createdAt ASC'],
82
133
  include: [{ relation: 'project' }],
83
- }
134
+ },
84
135
  });
85
136
 
86
137
  // SQL:
@@ -95,35 +146,81 @@ const priorityTasks = await taskRepository.find({
95
146
  // SELECT * FROM "Project" WHERE "id" IN (...)
96
147
  ```
97
148
 
149
+ ## Date range queries
98
150
 
99
- ## Soft Delete Handling
151
+ `between` for a closed window, `gte` for a rolling one:
100
152
 
101
153
  ```typescript
102
- // Find active records (soft delete pattern)
103
- const activeRecords = await repository.find({
154
+ const startOfWeek = new Date('2024-12-29');
155
+ const endOfWeek = new Date('2025-01-04');
156
+
157
+ const weekEvents = await eventRepository.find({
104
158
  filter: {
105
- where: { deletedAt: { is: null } },
106
- }
159
+ where: { eventDate: { between: [startOfWeek, endOfWeek] } },
160
+ order: ['eventDate ASC'],
161
+ },
107
162
  });
163
+ // SQL: SELECT * FROM "Event" WHERE "event_date" BETWEEN '2024-12-29' AND '2025-01-04' ORDER BY "event_date" ASC
164
+ ```
165
+
166
+ ```typescript
167
+ const sevenDaysAgo = new Date();
168
+ sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
108
169
 
170
+ const recentOrders = await orderRepository.find({
171
+ filter: {
172
+ where: {
173
+ createdAt: { gte: sevenDaysAgo },
174
+ status: { in: ['completed', 'shipped'] },
175
+ total: { gte: 100 },
176
+ },
177
+ order: ['total DESC'],
178
+ limit: 100,
179
+ },
180
+ });
109
181
  // SQL:
110
- // SELECT * FROM "Record" WHERE "deleted_at" IS NULL
182
+ // SELECT * FROM "Order"
183
+ // WHERE "created_at" >= '2024-12-24T00:00:00.000Z' AND "status" IN ('completed', 'shipped') AND "total" >= 100
184
+ // ORDER BY "total" DESC LIMIT 100
111
185
  ```
112
186
 
187
+ ## Inventory low-stock alert
188
+
189
+ Nested `or`/`and` groups plus a JSON path presence check:
190
+
113
191
  ```typescript
114
- // Find ONLY soft-deleted records
115
- const deletedRecords = await repository.find({
192
+ const lowStockProducts = await productRepository.find({
116
193
  filter: {
117
- where: { deletedAt: { isn: null } },
118
- }
194
+ where: {
195
+ status: 'active',
196
+ quantity: { lte: 10 },
197
+ 'metadata.reorderPoint': { isn: null },
198
+ or: [
199
+ { quantity: { lt: 5 } }, // Critical: below 5
200
+ { and: [{ quantity: { lte: 10 } }, { 'metadata.fastMoving': true }] },
201
+ ],
202
+ },
203
+ order: ['quantity ASC'],
204
+ fields: ['id', 'name', 'quantity', 'metadata'],
205
+ },
119
206
  });
120
207
 
121
208
  // SQL:
122
- // SELECT * FROM "Record" WHERE "deleted_at" IS NOT NULL
209
+ // SELECT "id", "name", "quantity", "metadata"
210
+ // FROM "Product"
211
+ // WHERE "status" = 'active'
212
+ // AND "quantity" <= 10
213
+ // AND "metadata" #>> '{reorderPoint}' IS NOT NULL
214
+ // AND (
215
+ // "quantity" < 5
216
+ // OR ("quantity" <= 10 AND "metadata" #>> '{fastMoving}' = 'true')
217
+ // )
218
+ // ORDER BY "quantity" ASC
123
219
  ```
124
220
 
221
+ ## Complex authorization filter
125
222
 
126
- ## Complex Authorization Filter
223
+ A `where` builder branching on role, composed with the caller's own scope - the `or` group only appears for non-admins:
127
224
 
128
225
  ```typescript
129
226
  const getAuthorizedFilter = (user: User): TWhere<TDocumentSchema> => {
@@ -143,23 +240,11 @@ const getAuthorizedFilter = (user: User): TWhere<TDocumentSchema> => {
143
240
  };
144
241
 
145
242
  const documents = await documentRepository.find({
146
- filter: {
147
- where: getAuthorizedFilter(currentUser),
148
- order: ['updatedAt DESC'],
149
- limit: 100,
150
- },
243
+ filter: { where: getAuthorizedFilter(currentUser), order: ['updatedAt DESC'], limit: 100 },
151
244
  });
152
245
 
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"
246
+ // SQL (regular user):
247
+ // SELECT * FROM "Document"
163
248
  // WHERE "deleted_at" IS NULL
164
249
  // AND (
165
250
  // "owner_id" = 'user-123'
@@ -167,23 +252,19 @@ const documents = await documentRepository.find({
167
252
  // OR "shared_with_teams"::text[] && ARRAY['team-1', 'team-2']::text[]
168
253
  // OR "shared_with_users"::text[] @> ARRAY['user-123']::text[]
169
254
  // )
170
- // ORDER BY "updated_at" DESC
171
- // LIMIT 100
255
+ // ORDER BY "updated_at" DESC LIMIT 100
172
256
  ```
173
257
 
258
+ ## Full-text search with metadata
174
259
 
175
- ## Full-Text Search with Metadata
260
+ Conditional `where` assembly - each filter argument adds a key only if the caller supplied it:
176
261
 
177
262
  ```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
- };
263
+ const searchProducts = async (
264
+ query: string,
265
+ filters: { minRating?: number; maxPrice?: number; categories?: string[] },
266
+ ) => {
267
+ const where: TWhere<TProductSchema> = { status: 'active', deletedAt: { is: null } };
187
268
 
188
269
  if (query) {
189
270
  where.or = [
@@ -192,50 +273,31 @@ const searchProducts = async (query: string, filters: {
192
273
  { 'metadata.keywords': { ilike: `%${query}%` } },
193
274
  ];
194
275
  }
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
- }
276
+ if (filters.minRating) where.rating = { gte: filters.minRating };
277
+ if (filters.maxPrice) where.price = { lte: filters.maxPrice };
278
+ if (filters.categories?.length) where.categories = { contains: filters.categories };
208
279
 
209
280
  return productRepository.find({
210
- filter: {
211
- where,
212
- order: ['rating DESC', 'createdAt DESC'],
213
- limit: 50,
214
- },
281
+ filter: { where, order: ['rating DESC', 'createdAt DESC'], limit: 50 },
215
282
  });
216
283
  };
217
284
 
218
- // Example: searchProducts('wireless', { minRating: 4, maxPrice: 200, categories: ['electronics'] })
285
+ // searchProducts('wireless', { minRating: 4, maxPrice: 200, categories: ['electronics'] })
219
286
  //
220
287
  // SQL:
221
- // SELECT *
222
- // FROM "Product"
288
+ // SELECT * FROM "Product"
223
289
  // WHERE "status" = 'active'
224
290
  // AND "deleted_at" IS NULL
225
- // AND (
226
- // "name" ILIKE '%wireless%'
227
- // OR "description" ILIKE '%wireless%'
228
- // OR "metadata" #>> '{keywords}' ILIKE '%wireless%'
229
- // )
291
+ // AND ("name" ILIKE '%wireless%' OR "description" ILIKE '%wireless%' OR "metadata" #>> '{keywords}' ILIKE '%wireless%')
230
292
  // AND "rating" >= 4
231
293
  // AND "price" <= 200
232
294
  // AND "categories"::text[] @> ARRAY['electronics']::text[]
233
- // ORDER BY "rating" DESC, "created_at" DESC
234
- // LIMIT 50
295
+ // ORDER BY "rating" DESC, "created_at" DESC LIMIT 50
235
296
  ```
236
297
 
298
+ ## Everything at once
237
299
 
238
- ## Massive Filter Example
300
+ Every operator family, a JSON path, a three-way `or`, and a scoped relation include in one filter - the ceiling of what a single `TFilter` can express:
239
301
 
240
302
  ```typescript
241
303
  const massiveFilter: TFilter<TProductSchema> = {
@@ -254,12 +316,9 @@ const massiveFilter: TFilter<TProductSchema> = {
254
316
  { isFeatured: true },
255
317
  { 'metadata.promotion.active': true },
256
318
  { 'metadata.promotion.discount': { gte: 20 } },
257
- ]
258
- },
259
- {
260
- createdAt: { gte: new Date('2024-12-01') },
261
- 'metadata.isNewArrival': true,
319
+ ],
262
320
  },
321
+ { createdAt: { gte: new Date('2024-12-01') }, 'metadata.isNewArrival': true },
263
322
  ],
264
323
  category: { nin: ['discontinued', 'recalled'] },
265
324
  suppliers: { overlaps: ['supplier-a', 'supplier-b'] },
@@ -270,14 +329,7 @@ const massiveFilter: TFilter<TProductSchema> = {
270
329
  skip: 0,
271
330
  include: [
272
331
  { relation: 'category' },
273
- {
274
- relation: 'reviews',
275
- scope: {
276
- where: { rating: { gte: 4 } },
277
- order: ['createdAt DESC'],
278
- limit: 5,
279
- },
280
- },
332
+ { relation: 'reviews', scope: { where: { rating: { gte: 4 } }, order: ['createdAt DESC'], limit: 5 } },
281
333
  ],
282
334
  };
283
335
 
@@ -291,163 +343,34 @@ const products = await productRepository.find({ filter: massiveFilter });
291
343
  // AND "price" >= 50 AND "price" <= 500
292
344
  // AND "quantity" > 0
293
345
  // 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
346
+ // AND CASE WHEN ("metadata" #>> '{priority}') ~ '^-?[0-9]+(\.[0-9]+)?$'
347
+ // THEN ("metadata" #>> '{priority}')::numeric ELSE NULL END >= 3
298
348
  // AND "metadata" #>> '{features,wireless}' = 'true'
299
349
  // AND (
300
350
  // "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
- // )
351
+ // OR ("is_featured" = true AND "metadata" #>> '{promotion,active}' = 'true'
352
+ // AND CASE WHEN ("metadata" #>> '{promotion,discount}') ~ '^-?[0-9]+(\.[0-9]+)?$'
353
+ // THEN ("metadata" #>> '{promotion,discount}')::numeric ELSE NULL END >= 20)
354
+ // OR ("created_at" >= '2024-12-01T00:00:00.000Z' AND "metadata" #>> '{isNewArrival}' = 'true')
313
355
  // )
314
356
  // AND "category" NOT IN ('discontinued', 'recalled')
315
357
  // AND "suppliers"::text[] && ARRAY['supplier-a', 'supplier-b']::text[]
316
358
  // ORDER BY "metadata" #> '{priority}' DESC, "rating" DESC, "created_at" DESC
317
359
  // LIMIT 20 OFFSET 0
318
360
  //
319
- // -- Separate query for category relation:
361
+ // -- Separate queries for relations:
320
362
  // 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
363
+ // SELECT * FROM "Review" WHERE "product_id" IN (...) AND "rating" >= 4 ORDER BY "created_at" DESC LIMIT 5
327
364
  ```
328
365
 
366
+ ## See also
329
367
 
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');
368
+ - [Filter System Overview](./) - the `filter` shape and every `where` operator family
369
+ - [Default Filter](./default-filter) - model-level scoping instead of the call-site pattern shown above
370
+ - [Application Usage](./application-usage) - how a filter reaches the repository from an HTTP request
371
+ - [Tips & Edge Cases](./tips) - `NULL` handling, empty-array semantics, and other gotchas that show up in filters like these
336
372
 
337
- const weekEvents = await eventRepository.find({
338
- filter: {
339
- where: {
340
- eventDate: { between: [startOfWeek, endOfWeek] }
341
- },
342
- order: ['eventDate ASC']
343
- }
344
- });
373
+ **Files:**
345
374
 
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
- ```
379
-
380
-
381
- ## Multi-Tenant Data Isolation
382
-
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
- ```
375
+ - [`packages/core/src/connectors/postgres/repositories/dialect/filter.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/dialect/filter.ts) - `FilterBuilder`, translates `TFilter` to Drizzle/SQL
376
+ - [`packages/core/src/connectors/postgres/repositories/dialect/query.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/dialect/query.ts) - `PostgresQueryOperators.FNS`, per-operator SQL builders