@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
@@ -1,27 +1,29 @@
1
1
  ---
2
2
  title: Advanced Repository Features
3
- description: Transactions, hidden properties, and performance optimization
3
+ description: Transactions, row-level locking, hidden properties, performance, and safety guards
4
4
  difficulty: intermediate
5
5
  ---
6
6
 
7
7
  # Advanced Repository Features
8
8
 
9
- Transactions, hidden properties, performance optimization, type inference, and debugging.
9
+ Exhaustive reference for everything beyond basic CRUD - transactions, row-level locking, hidden-property exclusion, performance tuning, return-type inference, debugging, and the built-in safety guards. For the common tasks, start with the [Repositories overview](/references/base/repositories/).
10
10
 
11
- ## Prerequisites
11
+ **Files:**
12
12
 
13
- Before reading this document, you should understand:
14
-
15
- - [Basic Repository Operations](./index.md) - CRUD operations and basic filtering
16
- - [Filter System](../filter-system/) - Advanced query building
17
- - Database transactions - ACID properties and isolation levels
18
- - TypeScript advanced types - Utility types and type inference
13
+ - [`packages/core/src/base/repositories/core/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/repositories/core/abstract.ts) - engine-neutral `AbstractRepository`
14
+ - [`packages/core/src/base/repositories/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/repositories/common/types.ts) - `IExtraOptions`, `TLockOptions`, `TCount`, `TDataRange`
15
+ - [`packages/core/src/connectors/postgres/repositories/core/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/core/base.ts) - `RelationalBaseRepository` - hidden-column exclusion, `buildQuery`, `resolveConnector`, lock validation
16
+ - [`packages/core/src/connectors/postgres/repositories/core/readable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/core/readable.ts) - `ReadableRelationalRepository` - Core API vs. Query API selection, `shouldQueryRange`
17
+ - [`packages/core/src/connectors/postgres/repositories/core/persistable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/core/persistable.ts) - `PersistableRelationalRepository` - create/update/delete, empty-where guard
18
+ - [`packages/core/src/connectors/postgres/repositories/core/soft-deletable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/core/soft-deletable.ts) - `SoftDeletableRelationalRepository`
19
+ - [`packages/core/src/connectors/postgres/repositories/dialect/update.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/dialect/update.ts) - `UpdateBuilder` - nested JSON path updates
20
+ - [`packages/core/src/connectors/postgres/datasources/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/datasources/abstract.ts) - `beginTransaction()`, isolation levels
19
21
 
20
22
  ## Transactions
21
23
 
22
- Orchestrate atomic operations across multiple repositories.
24
+ Orchestrate atomic operations across multiple repositories. `repository.beginTransaction()` delegates to `dataSource.beginTransaction()` and returns an `IDatabaseTransaction` (`isActive`, `commit()`, `rollback()`, `connector`, `isolationLevel`).
23
25
 
24
- ### Basic Transaction
26
+ ### Basic transaction
25
27
 
26
28
  ```typescript
27
29
  const tx = await repository.beginTransaction();
@@ -30,66 +32,67 @@ try {
30
32
  // All operations use the same transaction
31
33
  const user = await userRepository.create({
32
34
  data: { name: 'Alice', email: 'alice@example.com' },
33
- options: { transaction: tx }
35
+ options: { transaction: tx },
34
36
  });
35
37
 
36
38
  const profile = await profileRepository.create({
37
39
  data: { userId: user.data.id, bio: 'Hello!' },
38
- options: { transaction: tx }
40
+ options: { transaction: tx },
39
41
  });
40
42
 
41
- // Commit if all succeeded
42
43
  await tx.commit();
43
-
44
44
  return { user: user.data, profile: profile.data };
45
45
  } catch (error) {
46
- // Rollback on any error
47
46
  await tx.rollback();
48
47
  throw error;
49
48
  }
50
49
  ```
51
50
 
52
- ### Isolation Levels
51
+ > [!WARNING] `rollback()` can throw
52
+ > A failed `COMMIT` or `ROLLBACK` **throws** rather than resolving as success - a poisoned connection is destroyed rather than returned to the pool. Because `rollback()` can throw and is normally called from a `catch`, nest it in its own `try...catch` if the rollback error matters; a `rollback()` called after the transaction already failed is a silent no-op, so the `catch { await tx.rollback(); throw error; }` pattern shown above is always safe. See [DataSources - Full Reference](/references/base/datasources-reference#transaction-support) for the full commit/rollback lifecycle.
53
53
 
54
- Control how transactions interact with concurrent operations:
54
+ ### Isolation levels
55
+
56
+ Control how transactions interact with concurrent operations using the `IsolationLevels` const class:
55
57
 
56
58
  ```typescript
59
+ import { IsolationLevels } from '@venizia/ignis/postgres';
60
+
57
61
  const tx = await repository.beginTransaction({
58
- isolationLevel: 'SERIALIZABLE'
62
+ isolationLevel: IsolationLevels.SERIALIZABLE,
59
63
  });
60
64
  ```
61
65
 
62
- | Level | Description | Use Case |
66
+ | Level | Description | Use case |
63
67
  |-------|-------------|----------|
64
- | `READ COMMITTED` | Default. See committed data only | Most applications |
65
- | `REPEATABLE READ` | Consistent reads within transaction | Reports, analytics |
66
- | `SERIALIZABLE` | Full isolation, prevents anomalies | Financial, inventory |
68
+ | `IsolationLevels.READ_COMMITTED` | Default. See committed data only | Most applications |
69
+ | `IsolationLevels.REPEATABLE_READ` | Consistent reads within transaction | Reports, analytics |
70
+ | `IsolationLevels.SERIALIZABLE` | Full isolation, prevents anomalies | Financial, inventory |
67
71
 
68
- ### Transaction with Multiple Repositories
72
+ A plain string literal (`'SERIALIZABLE'`) works too - `isolationLevel` is typed `TIsolationLevel`, a string union the const class's values satisfy.
73
+
74
+ ### Transaction with multiple repositories
69
75
 
70
76
  ```typescript
71
77
  async function transferFunds(fromId: string, toId: string, amount: number) {
72
78
  const tx = await accountRepository.beginTransaction();
73
79
 
74
80
  try {
75
- // Debit source account
76
81
  await accountRepository.updateById({
77
82
  id: fromId,
78
83
  data: { balance: sql`balance - ${amount}` },
79
- options: { transaction: tx }
84
+ options: { transaction: tx },
80
85
  });
81
86
 
82
- // Credit destination account
83
87
  await accountRepository.updateById({
84
88
  id: toId,
85
89
  data: { balance: sql`balance + ${amount}` },
86
- options: { transaction: tx }
90
+ options: { transaction: tx },
87
91
  });
88
92
 
89
- // Record the transfer
90
93
  await transferRepository.create({
91
94
  data: { fromId, toId, amount, status: 'completed' },
92
- options: { transaction: tx }
95
+ options: { transaction: tx },
93
96
  });
94
97
 
95
98
  await tx.commit();
@@ -100,14 +103,13 @@ async function transferFunds(fromId: string, toId: string, amount: number) {
100
103
  }
101
104
  ```
102
105
 
103
-
104
106
  ## Row-Level Locking
105
107
 
106
108
  Acquire pessimistic locks on selected rows within a transaction using PostgreSQL's `SELECT ... FOR UPDATE/SHARE` syntax.
107
109
 
108
- ### Basic Usage
110
+ ### Basic usage
109
111
 
110
- Pass `lock` in options alongside a `transaction`:
112
+ Pass `lock` in `options` alongside a `transaction`:
111
113
 
112
114
  ```typescript
113
115
  const tx = await repository.beginTransaction();
@@ -136,9 +138,9 @@ try {
136
138
  }
137
139
  ```
138
140
 
139
- ### Lock Strengths
141
+ ### Lock strengths
140
142
 
141
- Use the `LockStrengths` constant class or string literals:
143
+ Use the `LockStrengths` const class or string literals:
142
144
 
143
145
  ```typescript
144
146
  import { LockStrengths } from '@venizia/ignis';
@@ -150,16 +152,16 @@ lock: { strength: LockStrengths.UPDATE }
150
152
  lock: { strength: 'update' }
151
153
  ```
152
154
 
153
- | Strength | SQL | Use Case |
155
+ | Strength | SQL | Use case |
154
156
  |----------|-----|----------|
155
157
  | `update` | `FOR UPDATE` | Exclusive lock for writes |
156
158
  | `no key update` | `FOR NO KEY UPDATE` | Exclusive lock, allows concurrent `FOR KEY SHARE` |
157
159
  | `share` | `FOR SHARE` | Shared read lock, prevents writes |
158
160
  | `key share` | `FOR KEY SHARE` | Weakest lock, only prevents key changes |
159
161
 
160
- ### Wait Behavior
162
+ ### Wait behavior
161
163
 
162
- Control what happens when rows are already locked:
164
+ Control what happens when rows are already locked via `lock.config`:
163
165
 
164
166
  ```typescript
165
167
  // Skip locked rows (queue-style worker pattern)
@@ -187,10 +189,12 @@ const item = await repository.findOne({
187
189
  | `{ noWait: true }` | `FOR UPDATE NOWAIT` | Throw error immediately if locked |
188
190
  | `{ skipLocked: true }` | `FOR UPDATE SKIP LOCKED` | Silently skip locked rows |
189
191
 
192
+ `noWait` and `skipLocked` are mutually exclusive at the type level (`TLockConfig`).
193
+
190
194
  ### Constraints
191
195
 
192
196
  > [!WARNING]
193
- > Row-level locking requires a **transaction** and is **incompatible with `include`/`fields`** in the filter (these use the Drizzle Query API which does not support `.for()`).
197
+ > Row-level locking requires a **transaction** and is **incompatible with `include`/`fields`** in the filter (these force the Drizzle Query API, which does not support `.for()`).
194
198
 
195
199
  ```typescript
196
200
  // Error - no transaction
@@ -199,15 +203,14 @@ await repository.findOne({
199
203
  options: { lock: { strength: 'update' } },
200
204
  });
201
205
 
202
- // Error - include uses Query API
206
+ // Error - include forces the Query API
203
207
  await repository.findOne({
204
208
  filter: { where: { id: '123' }, include: [{ relation: 'posts' }] },
205
209
  options: { transaction: tx, lock: { strength: 'update' } },
206
210
  });
207
211
  ```
208
212
 
209
- **Supported methods:** `find`, `findOne`, `findById`
210
-
213
+ **Supported methods:** `find`, `findOne`, `findById` (delegates to `findOne`). Not `count`/`existsWith`.
211
214
 
212
215
  ## Hidden Properties
213
216
 
@@ -229,7 +232,7 @@ export class User extends BaseEntity<typeof User.schema> {
229
232
  }
230
233
  ```
231
234
 
232
- ### Automatic Exclusion
235
+ ### Automatic exclusion
233
236
 
234
237
  Hidden properties are excluded at the **SQL level** for maximum security:
235
238
 
@@ -241,43 +244,41 @@ const user = await userRepository.findById({ id: '123' });
241
244
 
242
245
  // Write operations exclude hidden from RETURNING clause
243
246
  const created = await userRepository.create({
244
- data: { email: 'new@example.com', password: 'hashed_secret' }
247
+ data: { email: 'new@example.com', password: 'hashed_secret' },
245
248
  });
246
249
  // Result: { count: 1, data: { id: '456', email: 'new@example.com' } }
247
250
  // Note: password stored in DB but not returned
248
251
  ```
249
252
 
250
- ### Filtering by Hidden Properties
253
+ ### Filtering by hidden properties
251
254
 
252
255
  You **can** filter by hidden properties - you just can't see them in results:
253
256
 
254
257
  ```typescript
255
258
  // This works! Finds user but password not in result
256
259
  const user = await userRepository.findOne({
257
- filter: { where: { password: 'hashed_value' } }
260
+ filter: { where: { password: 'hashed_value' } },
258
261
  });
259
262
  ```
260
263
 
261
- ### Relations with Hidden Properties
264
+ ### Relations with hidden properties
262
265
 
263
- Hidden properties are also excluded from included relations:
266
+ Hidden properties are also excluded from included relations - see [Relations & Includes](./relations#hidden-properties-in-relations).
264
267
 
265
268
  ```typescript
266
269
  const post = await postRepository.findOne({
267
- filter: {
268
- include: [{ relation: 'author' }]
269
- }
270
+ filter: { include: [{ relation: 'author' }] },
270
271
  });
271
272
  // post.author will NOT include password, secret, etc.
272
273
  ```
273
274
 
274
- ### Accessing Hidden Data
275
+ ### Accessing hidden data
275
276
 
276
- When you need hidden fields (e.g., for authentication), bypass the repository:
277
+ When you need hidden fields (e.g., for authentication), bypass the repository via the `connector` getter:
277
278
 
278
279
  ```typescript
279
280
  // Direct connector access - includes all fields
280
- const connector = userRepository.getConnector();
281
+ const connector = userRepository.connector;
281
282
  const [fullUser] = await connector
282
283
  .select()
283
284
  .from(User.schema)
@@ -285,12 +286,11 @@ const [fullUser] = await connector
285
286
  // fullUser includes password, secret, apiKey
286
287
  ```
287
288
 
288
-
289
289
  ## Performance Optimization
290
290
 
291
- ### Core API for Flat Queries
291
+ ### Core API for flat queries
292
292
 
293
- The repository automatically uses Drizzle's Core API (faster) for simple queries:
293
+ The repository automatically uses Drizzle's Core API (faster) for simple queries; a query with `include` or `fields` forces the Query API instead:
294
294
 
295
295
  ```typescript
296
296
  // Automatically optimized - uses Core API
@@ -298,8 +298,8 @@ const users = await repository.find({
298
298
  filter: {
299
299
  where: { status: 'active' },
300
300
  limit: 10,
301
- order: ['createdAt DESC']
302
- }
301
+ order: ['createdAt DESC'],
302
+ },
303
303
  });
304
304
  // Uses: db.select().from(table).where(...).orderBy(...).limit(10)
305
305
 
@@ -307,41 +307,38 @@ const users = await repository.find({
307
307
  const usersWithPosts = await repository.find({
308
308
  filter: {
309
309
  where: { status: 'active' },
310
- include: [{ relation: 'posts' }]
311
- }
310
+ include: [{ relation: 'posts' }],
311
+ },
312
312
  });
313
313
  // Uses: db.query.tableName.findMany({ with: { posts: true }, ... })
314
314
  ```
315
315
 
316
- | Filter Options | API Used | Performance |
316
+ | Filter options | API used | Performance |
317
317
  |----------------|----------|-------------|
318
318
  | `where`, `limit`, `order`, `offset`/`skip` only | Core API | ~15-20% faster |
319
319
  | Has `include` (relations) | Query API | Standard |
320
320
  | Has `fields` selection | Query API | Standard |
321
321
 
322
- ### Always Use Limit
322
+ ### Always use limit
323
323
 
324
324
  Prevent memory exhaustion on large tables:
325
325
 
326
326
  ```typescript
327
327
  // Good - bounded result set
328
328
  await repository.find({
329
- filter: {
330
- where: { status: 'active' },
331
- limit: 100
332
- }
329
+ filter: { where: { status: 'active' }, limit: 100 },
333
330
  });
334
331
 
335
332
  // Dangerous - could return millions of rows
336
333
  await repository.find({
337
- filter: { where: { status: 'active' } }
334
+ filter: { where: { status: 'active' } },
338
335
  });
339
336
  ```
340
337
 
341
338
  > [!NOTE]
342
- > `find()` always applies a default limit of `10` when no `limit` is set in the filter. Pass an explicit `limit` in the filter to override this default.
339
+ > `find()` always applies a default limit when no `limit` is set in the filter - the model's `@model({ settings: { defaultLimit } })` if declared, otherwise the global default of `10`. Pass an explicit `limit` in the filter to override either default. `findOne`/`findById` are unaffected - they force `limit: 1` on the Core API path regardless.
343
340
 
344
- ### Pagination with Data Range
341
+ ### Pagination with data range
345
342
 
346
343
  Use `shouldQueryRange` to get both data and total count in a single call:
347
344
 
@@ -351,9 +348,9 @@ const result = await userRepository.find({
351
348
  where: { status: 'active' },
352
349
  limit: 20,
353
350
  skip: 40,
354
- order: ['createdAt DESC']
351
+ order: ['createdAt DESC'],
355
352
  },
356
- options: { shouldQueryRange: true }
353
+ options: { shouldQueryRange: true },
357
354
  });
358
355
 
359
356
  // Result type: { data: User[], range: { start: number, end: number, total: number } }
@@ -361,22 +358,18 @@ const result = await userRepository.find({
361
358
  // Example: { data: [...20 users], range: { start: 40, end: 59, total: 150 } }
362
359
  ```
363
360
 
364
- This runs `find` and `count` in parallel via `Promise.all` for optimal performance. Inside a transaction the two queries run sequentially instead - a transaction connector wraps a single client, so parallel queries on it are not safe.
361
+ - **Parallel by default.** `find` and `count` execute concurrently via `Promise.all`.
362
+ - **Sequential inside a transaction.** A transaction connector wraps a single client, so parallel queries on it are not safe - the two queries run one after another instead.
365
363
 
366
- ### WeakMap Cache
367
-
368
- The filter builder caches table column metadata, avoiding repeated reflection:
369
-
370
- ```typescript
371
- // Internal optimization - automatic
372
- // First query: getTableColumns(schema) -> cached in WeakMap
373
- // Subsequent queries: retrieved from WeakMap cache
374
- ```
364
+ ### WeakMap cache
375
365
 
366
+ - **Column metadata is cached per schema.** The filter builder caches table column metadata (`getCachedColumns`) to avoid repeated reflection.
367
+ - **Populated on first access.** The first access calls Drizzle's `getTableColumns()` and caches the result; subsequent queries retrieve it from the `WeakMap`.
368
+ - **No configuration needed.** The cache is automatic.
376
369
 
377
370
  ## TypeScript Return Types
378
371
 
379
- ### shouldReturn Inference
372
+ ### shouldReturn inference
380
373
 
381
374
  Repository methods infer return types based on `shouldReturn`:
382
375
 
@@ -384,14 +377,14 @@ Repository methods infer return types based on `shouldReturn`:
384
377
  // shouldReturn: false - TypeScript knows data is null
385
378
  const result1 = await repository.create({
386
379
  data: { name: 'John' },
387
- options: { shouldReturn: false }
380
+ options: { shouldReturn: false },
388
381
  });
389
382
  // Type: Promise<{ count: number; data: undefined | null }>
390
383
 
391
384
  // shouldReturn: true (default) - TypeScript knows data is the entity
392
385
  const result2 = await repository.create({
393
386
  data: { name: 'John' },
394
- options: { shouldReturn: true }
387
+ options: { shouldReturn: true },
395
388
  });
396
389
  // Type: Promise<{ count: number; data: User }>
397
390
  console.log(result2.data.name); // 'John' - fully typed!
@@ -399,55 +392,48 @@ console.log(result2.data.name); // 'John' - fully typed!
399
392
  // Array operations
400
393
  const results = await repository.createAll({
401
394
  data: [{ name: 'John' }, { name: 'Jane' }],
402
- options: { shouldReturn: true }
395
+ options: { shouldReturn: true },
403
396
  });
404
397
  // Type: Promise<{ count: number; data: User[] }>
405
398
  ```
406
399
 
407
- ### Generic Return Types
400
+ ### Generic return types
408
401
 
409
402
  Override return types for queries with relations:
410
403
 
411
404
  ```typescript
412
- // Define expected return type
413
405
  type UserWithPosts = User & {
414
406
  posts: Post[];
415
407
  };
416
408
 
417
- // Use generic override
418
409
  const user = await userRepository.findOne<UserWithPosts>({
419
410
  filter: {
420
411
  where: { id: '123' },
421
- include: [{ relation: 'posts' }]
422
- }
412
+ include: [{ relation: 'posts' }],
413
+ },
423
414
  });
424
415
 
425
- // TypeScript knows the structure!
426
416
  if (user) {
427
417
  console.log(user.posts[0].title); // Fully typed
428
418
  }
429
419
  ```
430
420
 
431
- **Supported Methods:**
421
+ **Supported methods:**
432
422
  - `find<R>()`, `findOne<R>()`, `findById<R>()`
433
423
  - `create<R>()`, `createAll<R>()`
434
424
  - `updateById<R>()`, `updateAll<R>()`, `updateBy<R>()`
435
425
  - `deleteById<R>()`, `deleteAll<R>()`, `deleteBy<R>()`
436
426
 
437
-
438
427
  ## Debugging
439
428
 
440
- ### Log Option
429
+ ### Log option
441
430
 
442
- Enable logging for specific operations:
431
+ Enable logging for specific operations via `TRepositoryLogOptions` (`{ use: boolean; level?: TLogLevel }`):
443
432
 
444
433
  ```typescript
445
- // Enable debug logging
446
434
  await repository.create({
447
435
  data: { name: 'John', email: 'john@example.com' },
448
- options: {
449
- log: { use: true, level: 'debug' }
450
- }
436
+ options: { log: { use: true, level: 'debug' } },
451
437
  });
452
438
  // Output: [_create] Executing with opts: { data: [...], options: {...} }
453
439
 
@@ -455,15 +441,15 @@ await repository.create({
455
441
  await repository.updateById({
456
442
  id: '123',
457
443
  data: { name: 'Jane' },
458
- options: { log: { use: true, level: 'info' } }
444
+ options: { log: { use: true, level: 'info' } },
459
445
  });
460
446
  ```
461
447
 
462
- **Available on:** `create`, `createAll`, `updateById`, `updateAll`, `updateBy`, `deleteById`, `deleteAll`, `deleteBy` (all write operations that go through `_create`, `_update`, or `_delete` internal methods)
448
+ **Available on:** `create`, `createAll`, `updateById`, `updateAll`, `updateBy`, `deleteById`, `deleteAll`, `deleteBy` - every write operation that goes through the internal `_create`, `_update`, or `_delete` methods.
463
449
 
464
- ### Query Interface Validation
450
+ ### Query interface validation
465
451
 
466
- The repository validates schema registration on startup:
452
+ The repository validates schema registration on first Query API access:
467
453
 
468
454
  ```typescript
469
455
  // If schema key doesn't match, you get a helpful error:
@@ -473,12 +459,11 @@ The repository validates schema registration on startup:
473
459
  // | Ensure the model's TABLE_NAME matches the schema registration key
474
460
  ```
475
461
 
476
-
477
462
  ## Safety Features
478
463
 
479
- ### Empty Where Protection
464
+ ### Empty where protection
480
465
 
481
- Prevents accidental mass updates/deletes:
466
+ `updateAll`/`updateBy`/`deleteAll`/`deleteBy` refuse an empty `where` unless `force: true` is passed - prevents accidental mass updates/deletes:
482
467
 
483
468
  ```typescript
484
469
  // Throws error - empty where without force
@@ -487,7 +472,7 @@ await repository.deleteAll({ where: {} });
487
472
  // Explicit force flag - logs warning, proceeds
488
473
  await repository.deleteAll({
489
474
  where: {},
490
- options: { force: true }
475
+ options: { force: true },
491
476
  });
492
477
  // Warning: [_delete] Entity: User | Performing delete with empty condition
493
478
  ```
@@ -498,25 +483,23 @@ await repository.deleteAll({
498
483
  | Valid `where` | Executes normally | Executes normally |
499
484
 
500
485
  > [!NOTE]
501
- > This protection applies to `updateAll`, `updateBy`, `deleteAll`, and `deleteBy`. The `updateById` and `deleteById` methods always have a non-empty where (`{ id }`) so they are not affected.
486
+ > `updateById` and `deleteById` always have a non-empty where (`{ id }`) so they are not affected by this guard.
502
487
 
503
- ### Transaction Safety
488
+ ### Transaction safety
504
489
 
505
- The `resolveConnector` method validates transaction state before use:
490
+ `resolveConnector` validates transaction state before use:
506
491
 
507
492
  ```typescript
508
493
  // If a transaction has already been committed or rolled back:
509
494
  // Error: [UserRepository][resolveConnector] Transaction is no longer active
510
495
  ```
511
496
 
512
-
513
497
  ## Direct Connector Access
514
498
 
515
- For advanced queries not supported by the repository API:
499
+ For advanced queries not supported by the repository API, `repository.connector` is a getter (not a method) that resolves the datasource's Drizzle connector:
516
500
 
517
501
  ```typescript
518
- // Get the Drizzle connector
519
- const connector = repository.getConnector();
502
+ const connector = repository.connector;
520
503
 
521
504
  // Raw Drizzle query
522
505
  const results = await connector
@@ -532,19 +515,22 @@ const results = await connector
532
515
  // Use with caution - bypasses repository features like hidden properties
533
516
  ```
534
517
 
535
-
536
518
  ## Repository Class Hierarchy
537
519
 
538
- | Class | Scope | Description |
539
- |-------|-------|-------------|
540
- | `AbstractRepository` | N/A | Engine-neutral abstract base (`src/base`), defines all method signatures, lazy `dataSource`/`entity` resolution. No mixin composition - plain `BaseHelper` subclass. |
541
- | `PostgresBaseRepository` | N/A | PostgreSQL connector base. Adds `FilterBuilder`, hidden-column exclusion (`getHiddenProperties`/`getVisibleProperties`), default-filter application (`getDefaultFilter`/`applyDefaultFilter`) - the behavior formerly provided by the now-removed `FieldsVisibilityMixin`/`DefaultFilterMixin` (see [Repository Mixins](./mixins.md)). |
542
- | `ReadableRepository` | `READ_ONLY` | Read-only operations (`find`, `findOne`, `findById`, `count`, `existsWith`). Write operations throw errors. |
543
- | `PersistableRepository` | `READ_WRITE` | Adds write operations (`create`, `update`, `delete`) with `UpdateBuilder` |
544
- | `DefaultCRUDRepository` | `READ_WRITE` | Extends `PersistableRepository` with no additional logic - **recommended default** |
545
- | `SoftDeletableRepository` | `READ_WRITE` | Extends `DefaultCRUDRepository` with soft delete + restore operations |
520
+ The PostgreSQL connector's canonical names carry the engine in the class name; the historical `*Repository` names remain available as compatibility aliases re-exporting the exact same classes.
521
+
522
+ | Canonical class | Alias | Scope | Description |
523
+ |---|---|---|---|
524
+ | `AbstractRepository` | - | N/A | Engine-neutral abstract base (`src/base`), defines all method signatures, lazy `dataSource`/`entity` resolution. Plain `BaseHelper` subclass, no mixin composition. |
525
+ | `RelationalBaseRepository` | `PostgresBaseRepository` | N/A | PostgreSQL connector base. Adds `FilterBuilder`/`UpdateBuilder`, hidden-column exclusion (`getHiddenProperties`/`getVisibleProperties`), default-filter application (`getDefaultFilter`/`applyDefaultFilter`) - the behavior formerly provided by the now-removed `FieldsVisibilityMixin`/`DefaultFilterMixin` (see [Repository Mixins](./mixins)). |
526
+ | `ReadableRelationalRepository` | `ReadableRepository` | `READ_ONLY` | Read-only operations (`find`, `findOne`, `findById`, `count`, `existsWith`). Write operations throw errors. |
527
+ | `PersistableRelationalRepository` | `PersistableRepository` | `READ_WRITE` | Adds write operations (`create`, `update`, `delete`) with `UpdateBuilder`. |
528
+ | `DefaultRelationalRepository` | `DefaultCRUDRepository` | `READ_WRITE` | Extends `PersistableRelationalRepository` with no additional logic - **recommended default**. |
529
+ | `SoftDeletableRelationalRepository` | `SoftDeletableRepository` | `READ_WRITE` | Extends `DefaultRelationalRepository` with soft delete + restore operations - see [SoftDeletableRepository](./soft-deletable). |
546
530
 
547
- ### Creating a Read-Only Repository
531
+ Code samples throughout the docs use the alias names (`DefaultCRUDRepository`, `ReadableRepository`) since that is what `@venizia/ignis/postgres` code most commonly imports today.
532
+
533
+ ### Creating a read-only repository
548
534
 
549
535
  ```typescript
550
536
  @repository({ model: AuditLog, dataSource: PostgresDataSource })
@@ -554,7 +540,7 @@ export class AuditLogRepository extends ReadableRepository<typeof AuditLog.schem
554
540
  }
555
541
  ```
556
542
 
557
- ### Alias Methods
543
+ ### Alias methods
558
544
 
559
545
  `AbstractRepository` provides two alias methods for convenience:
560
546
 
@@ -563,7 +549,6 @@ export class AuditLogRepository extends ReadableRepository<typeof AuditLog.schem
563
549
 
564
550
  Both accept the same parameters (`where`, `data`/`options`) and support `shouldReturn` and `force` options.
565
551
 
566
-
567
552
  ## Default Filter Bypass
568
553
 
569
554
  When models have a `defaultFilter` configured, you can bypass it for admin/maintenance operations:
@@ -571,14 +556,14 @@ When models have a `defaultFilter` configured, you can bypass it for admin/maint
571
556
  ```typescript
572
557
  // Normal query - default filter applies
573
558
  await repository.find({
574
- filter: { where: { status: 'active' } }
559
+ filter: { where: { status: 'active' } },
575
560
  });
576
561
  // WHERE isDeleted = false AND status = 'active' (if model has soft-delete default)
577
562
 
578
563
  // Admin query - bypass default filter
579
564
  await repository.find({
580
565
  filter: { where: { status: 'active' } },
581
- options: { shouldSkipDefaultFilter: true }
566
+ options: { shouldSkipDefaultFilter: true },
582
567
  });
583
568
  // WHERE status = 'active' (includes deleted records)
584
569
  ```
@@ -605,21 +590,20 @@ await repository.updateAll({
605
590
  data: { isDeleted: true },
606
591
  options: {
607
592
  transaction: tx,
608
- shouldSkipDefaultFilter: true
609
- }
593
+ shouldSkipDefaultFilter: true,
594
+ },
610
595
  });
611
596
  await tx.commit();
612
597
  ```
613
598
 
614
599
  > [!TIP]
615
- > See [Default Filter](../filter-system/default-filter.md) for full documentation on configuring model default filters.
616
-
600
+ > See [Default Filter](../filter-system/default-filter) for full documentation on configuring model default filters.
617
601
 
618
602
  ## Nested JSON Updates
619
603
 
620
- Repositories support updating specific fields within `json` or `jsonb` columns without overwriting the entire object. This is achieved using **JSON Path Notation** in the update data via the `UpdateBuilder`.
604
+ Repositories support updating specific fields within `json`/`jsonb` columns without overwriting the entire object, via **JSON path notation** in the update data - handled by `UpdateBuilder`.
621
605
 
622
- ### Basic Usage
606
+ ### Basic usage
623
607
 
624
608
  Use dot notation keys to target nested properties:
625
609
 
@@ -631,51 +615,49 @@ await repository.updateById({
631
615
  id: '123',
632
616
  data: {
633
617
  // Update only the theme, preserving other fields
634
- 'metadata.theme': 'dark'
635
- }
618
+ 'metadata.theme': 'dark',
619
+ },
636
620
  });
637
621
 
638
622
  // New value: { theme: 'dark', notifications: { email: true } }
639
623
  ```
640
624
 
641
- ### Supported Features
625
+ ### Supported features
642
626
 
643
- - **Deep Nesting:** Update properties at any depth (e.g., `settings.display.font.size`).
644
- - **Array Access:** Update array elements by index (e.g., `tags[0]`).
645
- - **Auto-Creation:** Creates missing intermediate keys automatically (`jsonb_set` with `create_missing = true`).
646
- - **Type Safety:** Validates that the target column is a JSON/JSONB type.
647
- - **Multiple Updates:** Multiple updates to the same column are chained as nested `jsonb_set` calls.
648
- - **Mixed Updates:** Combine regular column updates with JSON path updates in a single call.
627
+ - **Deep nesting:** update properties at any depth (e.g., `settings.display.font.size`).
628
+ - **Array access:** update array elements by index (e.g., `tags[0]`).
629
+ - **Auto-creation:** creates missing intermediate keys automatically (`jsonb_set` with `create_missing = true`).
630
+ - **Type safety:** validates that the target column is a JSON/JSONB type.
631
+ - **Multiple updates:** multiple updates to the same column are chained as nested `jsonb_set` calls.
632
+ - **Mixed updates:** combine regular column updates with JSON path updates in a single call.
649
633
 
650
634
  ### Examples
651
635
 
652
- #### Deeply Nested Updates
636
+ #### Deeply nested updates
653
637
 
654
638
  ```typescript
655
639
  await repository.updateById({
656
640
  id: '123',
657
641
  data: {
658
642
  'metadata.settings.display.fontSize': 16,
659
- 'metadata.settings.display.showSidebar': true
660
- }
643
+ 'metadata.settings.display.showSidebar': true,
644
+ },
661
645
  });
662
646
  ```
663
647
 
664
- #### Array Element Updates
648
+ #### Array element updates
665
649
 
666
650
  ```typescript
667
651
  await repository.updateById({
668
652
  id: '123',
669
653
  data: {
670
654
  // Set the first address as primary
671
- 'metadata.addresses[0].primary': true
672
- }
655
+ 'metadata.addresses[0].primary': true,
656
+ },
673
657
  });
674
658
  ```
675
659
 
676
- #### Mixed Updates (Regular + JSON)
677
-
678
- You can mix regular column updates with JSON path updates:
660
+ #### Mixed updates (regular + JSON)
679
661
 
680
662
  ```typescript
681
663
  await repository.updateById({
@@ -683,29 +665,29 @@ await repository.updateById({
683
665
  data: {
684
666
  status: 'active', // Regular column
685
667
  'metadata.lastLogin': now, // JSON path
686
- 'preferences.lang': 'en' // Another JSON path
687
- }
668
+ 'preferences.lang': 'en', // Another JSON path
669
+ },
688
670
  });
689
671
  ```
690
672
 
691
- ### Security & Validation
673
+ ### Security & validation
692
674
 
693
675
  The framework validates JSON paths to prevent SQL injection:
694
- - **Allowed Characters:** Path components must match `/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\d+$/` (identifiers, kebab-case, or array indices).
695
- - **Column Type Validation:** Only `json` and `jsonb` columns are allowed. Other column types throw an error.
696
- - **Values:** Values are serialized to JSONB literals with proper escaping.
676
+
677
+ - **Allowed characters:** path components must match `/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\d+$/` (identifiers, kebab-case, or array indices).
678
+ - **Column type validation:** only `json` and `jsonb` columns are allowed. Other column types throw an error.
679
+ - **Values:** values are serialized to JSONB literals with proper escaping.
697
680
 
698
681
  > [!NOTE]
699
682
  > This feature uses PostgreSQL's `jsonb_set` function. It is only available for columns defined as `json` or `jsonb`.
700
683
 
701
-
702
684
  ## ExtraOptions Reference
703
685
 
704
- All repository operations accept an `options` parameter with these fields:
686
+ All repository operations accept an `options` parameter (`IExtraOptions`/its postgres narrowing `IDatabaseExtraOptions`) with these fields:
705
687
 
706
688
  | Option | Type | Default | Description |
707
689
  |--------|------|---------|-------------|
708
- | `transaction` | `ITransaction` | - | Transaction context for the operation |
690
+ | `transaction` | `IDatabaseTransaction` | - | Transaction context for the operation |
709
691
  | `log` | `{ use: boolean; level?: TLogLevel }` | - | Enable operation logging |
710
692
  | `shouldSkipDefaultFilter` | `boolean` | `false` | Bypass the default filter from model settings |
711
693
  | `lock` | `TLockOptions` | - | Row-level locking (requires transaction, Core API only) |
@@ -718,7 +700,6 @@ Write operations additionally support:
718
700
  | `force` | `boolean` | `false` | Allow empty `where` condition on bulk operations |
719
701
  | `shouldQueryRange` | `boolean` | `false` | Return `{ data, range }` with total count (find only) |
720
702
 
721
-
722
703
  ## Quick Reference
723
704
 
724
705
  | Feature | Code |
@@ -734,32 +715,15 @@ Write operations additionally support:
734
715
  | Force delete all | `options: { force: true }` |
735
716
  | Skip returning data | `options: { shouldReturn: false }` |
736
717
  | Get data + count | `options: { shouldQueryRange: true }` |
737
- | Access connector | `repository.getConnector()` |
738
-
739
-
740
- ## Next Steps
741
-
742
- - [Overview](./index.md) - Repository basics
743
- - [Filter System](../filter-system/) - Query operators
744
- - [Default Filter](../filter-system/default-filter.md) - Automatic filter configuration
745
- - [Repository Mixins (Removed)](./mixins.md) - Where mixin behavior lives now
746
- - [Relations & Includes](./relations.md) - Eager loading
747
- - [Soft-Deletable Repository](./soft-deletable.md) - Soft delete operations
748
- - [JSON Path Filtering](../filter-system/json-filtering) - JSONB queries
749
- - [Array Operators](../filter-system/array-operators) - PostgreSQL arrays
750
-
751
- ## See Also
752
-
753
- - **Related Concepts:**
754
- - [Repositories Overview](./index) - Core repository operations
755
- - [Transactions](/guides/core-concepts/persistent/transactions) - Transaction guide
756
- - [DataSources](/guides/core-concepts/persistent/datasources) - Database connections
757
-
758
- - **Related Topics:**
759
- - [Repository Mixins (Removed)](./mixins) - Where mixin behavior lives now
760
- - [Relations & Includes](./relations) - Loading related data
761
- - [Filter System](/references/base/filter-system/) - Query operators
762
-
763
- - **Best Practices:**
764
- - [Performance Optimization](/best-practices/performance-optimization) - Query optimization
765
- - [Data Modeling](/best-practices/data-modeling) - Repository patterns
718
+ | Access connector | `repository.connector` |
719
+
720
+ ## See also
721
+
722
+ - [Repositories overview](/references/base/repositories/) - CRUD basics, common tasks
723
+ - [Relations & Includes](./relations) - eager loading, nested `scope` filters, many-to-many
724
+ - [SoftDeletableRepository](./soft-deletable) - soft delete, restore, hard delete
725
+ - [Repository Mixins (Removed)](./mixins) - where `FieldsVisibilityMixin`/`DefaultFilterMixin` behavior lives now
726
+ - [Filter System](/references/base/filter-system/) - every `where` operator, JSON paths, array operators
727
+ - [Default Filter](/references/base/filter-system/default-filter) - automatic filter configuration
728
+ - [DataSources - Full Reference](/references/base/datasources-reference) - transaction internals, isolation levels, driver seam
729
+ - [Transactions guide](/guides/core-concepts/persistent/transactions) - multi-operation database transactions