@venizia/ignis-docs 0.0.3 → 0.0.4-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 (131) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -2
  3. package/wiki/best-practices/api-usage-examples.md +591 -0
  4. package/wiki/best-practices/architectural-patterns.md +415 -0
  5. package/wiki/best-practices/architecture-decisions.md +488 -0
  6. package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +406 -17
  7. package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
  8. package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
  9. package/wiki/best-practices/data-modeling.md +376 -0
  10. package/wiki/best-practices/deployment-strategies.md +698 -0
  11. package/wiki/best-practices/index.md +27 -0
  12. package/wiki/best-practices/performance-optimization.md +196 -0
  13. package/wiki/best-practices/security-guidelines.md +218 -0
  14. package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
  15. package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
  16. package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
  17. package/wiki/changelogs/2025-12-17-refactor.md +1 -1
  18. package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
  19. package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
  20. package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +2 -2
  21. package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
  22. package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
  23. package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
  24. package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
  25. package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
  26. package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
  27. package/wiki/changelogs/index.md +6 -0
  28. package/wiki/changelogs/planned-schema-migrator.md +0 -8
  29. package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
  30. package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
  31. package/wiki/guides/core-concepts/components-guide.md +509 -0
  32. package/wiki/{get-started → guides}/core-concepts/components.md +24 -17
  33. package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
  34. package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
  35. package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
  36. package/wiki/guides/core-concepts/persistent/index.md +119 -0
  37. package/wiki/guides/core-concepts/persistent/models.md +241 -0
  38. package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
  39. package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
  40. package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
  41. package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
  42. package/wiki/guides/get-started/philosophy.md +682 -0
  43. package/wiki/guides/get-started/setup.md +157 -0
  44. package/wiki/guides/index.md +89 -0
  45. package/wiki/guides/reference/glossary.md +243 -0
  46. package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
  47. package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
  48. package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
  49. package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
  50. package/wiki/guides/tutorials/realtime-chat.md +1261 -0
  51. package/wiki/guides/tutorials/testing.md +723 -0
  52. package/wiki/index.md +176 -37
  53. package/wiki/references/base/application.md +27 -0
  54. package/wiki/references/base/bootstrapping.md +30 -26
  55. package/wiki/references/base/components.md +24 -7
  56. package/wiki/references/base/controllers.md +51 -20
  57. package/wiki/references/base/datasources.md +30 -0
  58. package/wiki/references/base/dependency-injection.md +39 -3
  59. package/wiki/references/base/filter-system/application-usage.md +224 -0
  60. package/wiki/references/base/filter-system/array-operators.md +132 -0
  61. package/wiki/references/base/filter-system/comparison-operators.md +109 -0
  62. package/wiki/references/base/filter-system/default-filter.md +428 -0
  63. package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
  64. package/wiki/references/base/filter-system/index.md +127 -0
  65. package/wiki/references/base/filter-system/json-filtering.md +197 -0
  66. package/wiki/references/base/filter-system/list-operators.md +71 -0
  67. package/wiki/references/base/filter-system/logical-operators.md +156 -0
  68. package/wiki/references/base/filter-system/null-operators.md +58 -0
  69. package/wiki/references/base/filter-system/pattern-matching.md +108 -0
  70. package/wiki/references/base/filter-system/quick-reference.md +431 -0
  71. package/wiki/references/base/filter-system/range-operators.md +63 -0
  72. package/wiki/references/base/filter-system/tips.md +190 -0
  73. package/wiki/references/base/filter-system/use-cases.md +452 -0
  74. package/wiki/references/base/index.md +90 -0
  75. package/wiki/references/base/middlewares.md +602 -0
  76. package/wiki/references/base/models.md +215 -23
  77. package/wiki/references/base/providers.md +732 -0
  78. package/wiki/references/base/repositories/advanced.md +555 -0
  79. package/wiki/references/base/repositories/index.md +228 -0
  80. package/wiki/references/base/repositories/mixins.md +331 -0
  81. package/wiki/references/base/repositories/relations.md +486 -0
  82. package/wiki/references/base/repositories.md +40 -635
  83. package/wiki/references/base/services.md +28 -4
  84. package/wiki/references/components/authentication.md +22 -2
  85. package/wiki/references/components/health-check.md +12 -0
  86. package/wiki/references/components/index.md +23 -0
  87. package/wiki/references/components/mail.md +687 -0
  88. package/wiki/references/components/request-tracker.md +16 -0
  89. package/wiki/references/components/socket-io.md +18 -0
  90. package/wiki/references/components/static-asset.md +14 -26
  91. package/wiki/references/components/swagger.md +17 -0
  92. package/wiki/references/configuration/environment-variables.md +427 -0
  93. package/wiki/references/configuration/index.md +73 -0
  94. package/wiki/references/helpers/cron.md +14 -0
  95. package/wiki/references/helpers/crypto.md +15 -0
  96. package/wiki/references/helpers/env.md +16 -0
  97. package/wiki/references/helpers/error.md +17 -0
  98. package/wiki/references/helpers/index.md +14 -0
  99. package/wiki/references/helpers/inversion.md +24 -4
  100. package/wiki/references/helpers/logger.md +19 -0
  101. package/wiki/references/helpers/network.md +11 -0
  102. package/wiki/references/helpers/queue.md +19 -0
  103. package/wiki/references/helpers/redis.md +21 -0
  104. package/wiki/references/helpers/socket-io.md +24 -5
  105. package/wiki/references/helpers/storage.md +18 -10
  106. package/wiki/references/helpers/testing.md +18 -0
  107. package/wiki/references/helpers/types.md +16 -0
  108. package/wiki/references/helpers/uid.md +167 -0
  109. package/wiki/references/helpers/worker-thread.md +16 -0
  110. package/wiki/references/index.md +177 -0
  111. package/wiki/references/quick-reference.md +634 -0
  112. package/wiki/references/src-details/boot.md +3 -3
  113. package/wiki/references/src-details/dev-configs.md +0 -4
  114. package/wiki/references/src-details/docs.md +2 -2
  115. package/wiki/references/src-details/index.md +86 -0
  116. package/wiki/references/src-details/inversion.md +1 -6
  117. package/wiki/references/src-details/mcp-server.md +3 -15
  118. package/wiki/references/utilities/index.md +86 -10
  119. package/wiki/references/utilities/jsx.md +577 -0
  120. package/wiki/references/utilities/request.md +0 -2
  121. package/wiki/references/utilities/statuses.md +740 -0
  122. package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
  123. package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
  124. package/wiki/get-started/best-practices/data-modeling.md +0 -177
  125. package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
  126. package/wiki/get-started/best-practices/performance-optimization.md +0 -97
  127. package/wiki/get-started/best-practices/security-guidelines.md +0 -99
  128. package/wiki/get-started/core-concepts/persistent.md +0 -539
  129. package/wiki/get-started/index.md +0 -65
  130. package/wiki/get-started/philosophy.md +0 -296
  131. package/wiki/get-started/prerequisites.md +0 -113
@@ -1,657 +1,62 @@
1
- # Deep Dive: Repositories
1
+ ---
2
+ title: Repositories Reference
3
+ description: Technical reference for repository classes and CRUD operations
4
+ difficulty: intermediate
5
+ ---
2
6
 
3
- Technical reference for repository classes - the data access layer in Ignis.
7
+ # Repositories
4
8
 
5
- **Files:** `packages/core/src/base/repositories/core/*.ts`
9
+ ::: tip Documentation Reorganized
10
+ This documentation has been reorganized into focused guides for easier navigation.
11
+ :::
6
12
 
7
- ## Quick Reference
13
+ ## Quick Links
8
14
 
9
- | Class | Capabilities | Use Case |
10
- |-------|--------------|----------|
11
- | **AbstractRepository** | Base class with properties | Extend for custom repositories |
12
- | **ReadableRepository** | Read-only operations | Views, external tables |
13
- | **PersistableRepository** | Read + Write operations | Rarely used directly |
14
- | **DefaultCRUDRepository** | Full CRUD operations | Standard data tables |
15
+ | Guide | Description |
16
+ |-------|-------------|
17
+ | [**Overview**](./repositories/) | Repository basics, methods, setup |
18
+ | [**Filter System**](./filter-system/) | Where clauses, comparison, pattern matching |
19
+ | [**Default Filter**](./filter-system/default-filter) | Automatic filter application |
20
+ | [**Relations & Includes**](./repositories/relations) | Eager loading, nested queries |
21
+ | [**JSON Path Filtering**](./filter-system/json-filtering) | Query JSONB columns |
22
+ | [**Array Operators**](./filter-system/array-operators) | PostgreSQL array queries |
23
+ | [**Advanced Features**](./repositories/advanced) | Transactions, hidden props, performance |
24
+ | [**Repository Mixins**](./repositories/mixins) | Composable repository features |
15
25
 
16
- ## `AbstractRepository`
26
+ ---
17
27
 
18
- Base class for all repositories - sets up fundamental properties and dependencies for data access.
28
+ ## Quick Start
19
29
 
20
- **File:** `packages/core/src/base/repositories/core/base.ts`
21
-
22
- ### Key Properties
23
-
24
- - `entity` (`BaseEntity`): An instance of the model class associated with this repository. It provides access to the Drizzle schema. Auto-resolved from `@repository` metadata or passed in constructor.
25
- - `dataSource` (`IDataSource`): The datasource instance injected into the repository, which holds the database connection. Auto-injected from `@repository` decorator or passed in constructor.
26
- - `connector`: A getter that provides direct access to the Drizzle ORM instance from the datasource.
27
- - `filterBuilder` (`DrizzleFilterBuilder`): An instance of the filter builder responsible for converting `Ignis`'s filter objects into Drizzle-compatible query options.
28
- - `operationScope` (`TRepositoryOperationScope`): Defines whether the repository is read-only or read-write.
29
- - `defaultLimit` (`number`): Default limit for queries (default: 10).
30
-
31
- ### Key Methods
32
-
33
- - `getEntityRelations()`: Returns a map of relation configurations from the entity's static `relations` property.
34
-
35
- ### Abstract Methods
36
-
37
- `AbstractRepository` defines the method signatures for standard CRUD operations that concrete repository classes must implement:
38
- - `count()`
39
- - `existsWith()`
40
- - `find()`
41
- - `findOne()`
42
- - `findById()`
43
- - `create()`
44
- - `updateById()`
45
- - `deleteById()`
46
- - (and `...All` variants)
47
-
48
- ## `ReadableRepository`
49
-
50
- The `ReadableRepository` provides a **read-only** implementation of the repository pattern. It is ideal for data sources that should not be modified, such as views or tables from an external system.
51
-
52
- - **File:** `packages/core/src/base/repositories/core/readable.ts`
53
-
54
- ### Implemented Methods
55
-
56
- `ReadableRepository` provides concrete implementations for all read operations:
57
-
58
- - **`find(opts)`**: Returns an array of entities matching the filter.
59
- - **`findOne(opts)`**: Returns the first entity matching the filter.
60
- - **`findById(opts)`**: A convenience method that calls `findOne` with an ID-based `where` clause.
61
- - **`count(opts)`**: Returns the number of entities matching the `where` clause.
62
- - **`existsWith(opts)`**: Returns `true` if at least one entity matches the `where` clause.
63
-
64
- ### Write Operations
65
-
66
- `ReadableRepository` throws a "NOT ALLOWED" error for all write operations (`create`, `update`, `delete`).
67
-
68
- ## `PersistableRepository`
69
-
70
- The `PersistableRepository` extends `ReadableRepository` and adds **write operations**. It provides the core logic for creating, updating, and deleting records with built-in safety mechanisms.
71
-
72
- - **File:** `packages/core/src/base/repositories/core/persistable.ts`
73
-
74
- ### Implemented Methods
75
-
76
- - `create(opts)`
77
- - `createAll(opts)`
78
- - `updateById(opts)`
79
- - `updateAll(opts)`
80
- - `updateBy(opts)` - Alias for `updateAll`
81
- - `deleteById(opts)`
82
- - `deleteAll(opts)`
83
- - `deleteBy(opts)` - Alias for `deleteAll`
84
-
85
- ### Safety Features
86
-
87
- #### Empty Where Clause Protection
88
-
89
- The `PersistableRepository` includes safety mechanisms to prevent accidental mass updates or deletions:
90
-
91
- **Update Operations (`updateAll`):**
92
30
  ```typescript
93
- // Throws error: Empty where condition without force flag
94
- await repository.updateAll({
95
- data: { status: 'inactive' },
96
- where: {}, // Empty condition
97
- });
98
-
99
- // ✅ Warning logged: Explicitly allow mass update with force flag
100
- await repository.updateAll({
101
- data: { status: 'inactive' },
102
- where: {},
103
- options: { force: true }, // Force flag allows empty where
104
- });
105
- ```
106
-
107
- **Delete Operations (`deleteAll`):**
108
- ```typescript
109
- // ❌ Throws error: Empty where condition without force flag
110
- await repository.deleteAll({
111
- where: {}, // Empty condition
112
- });
113
-
114
- // ✅ Warning logged: Explicitly allow mass delete with force flag
115
- await repository.deleteAll({
116
- where: {},
117
- options: { force: true }, // Force flag allows empty where
118
- });
119
- ```
120
-
121
- #### Behavior Summary
122
-
123
- | Scenario | `force: false` (default) | `force: true` |
124
- |----------|-------------------------|---------------|
125
- | Empty `where` clause | ❌ Throws error | ✅ Logs warning and proceeds |
126
- | Valid `where` clause | ✅ Executes normally | ✅ Executes normally |
127
-
128
- **Warning Messages:**
129
-
130
- When performing operations with empty `where` conditions and `force: true`, the repository logs a warning:
131
-
132
- ```
133
- [_update] Entity: MyEntity | Performing update with empty condition | data: {...} | condition: {}
134
- [_delete] Entity: MyEntity | Performing delete with empty condition | condition: {}
135
- ```
136
-
137
- This helps track potentially dangerous operations in your logs.
138
-
139
- You will typically not use this class directly, but rather the `DefaultCRUDRepository`.
140
-
141
- ## `DefaultCRUDRepository`
142
-
143
- This is the primary class you should extend for repositories that require full **Create, Read, Update, and Delete (CRUD)** capabilities. It extends `PersistableRepository` and serves as the standard, full-featured repository implementation.
144
-
145
- - **File:** `packages/core/src/base/repositories/core/default-crud.ts`
146
-
147
- ### @repository Decorator Requirements
148
-
149
- **IMPORTANT:** Both `model` AND `dataSource` are required in the `@repository` decorator for schema auto-discovery. Without both, the model won't be registered and relational queries will fail.
150
-
151
- ```typescript
152
- // ❌ WRONG - Will throw error
153
- @repository({ model: User }) // Missing dataSource!
154
- export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {}
155
-
156
- // ❌ WRONG - Will throw error
157
- @repository({ dataSource: PostgresDataSource }) // Missing model!
158
- export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {}
31
+ import { DefaultCRUDRepository, repository } from '@venizia/ignis';
32
+ import { Todo } from '@/models/todo.model';
33
+ import { PostgresDataSource } from '@/datasources/postgres.datasource';
159
34
 
160
- // ✅ CORRECT - Both model and dataSource provided
161
- @repository({ model: User, dataSource: PostgresDataSource })
162
- export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {}
163
- ```
164
-
165
- ### Injection Patterns
166
-
167
- The `@repository` decorator supports two injection patterns:
168
-
169
- #### Pattern 1: Zero Boilerplate (Recommended)
170
-
171
- DataSource is auto-injected from metadata - no constructor needed:
172
-
173
- ```typescript
174
- @repository({ model: User, dataSource: PostgresDataSource })
175
- export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
176
- // No constructor needed - datasource auto-injected at param index 0
177
-
178
- async findByEmail(email: string) {
179
- return this.findOne({ filter: { where: { email } } });
180
- }
35
+ @repository({ model: Todo, dataSource: PostgresDataSource })
36
+ export class TodoRepository extends DefaultCRUDRepository<typeof Todo.schema> {
37
+ // Inherits: find, findOne, create, updateById, deleteById, etc.
181
38
  }
182
39
  ```
183
40
 
184
- #### Pattern 2: Explicit @inject
185
-
186
- When you need constructor control, use explicit `@inject`. **Important:** The first parameter must extend `AbstractDataSource` - this is enforced via reflection:
41
+ ## Common Operations
187
42
 
188
43
  ```typescript
189
- @repository({ model: User, dataSource: PostgresDataSource })
190
- export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
191
- constructor(
192
- @inject({ key: 'datasources.PostgresDataSource' })
193
- dataSource: PostgresDataSource, // ✅ Must be concrete DataSource type, NOT 'any'
194
- ) {
195
- super(dataSource);
196
- }
197
- }
198
- ```
199
-
200
- **Note:** When `@inject` is at param index 0, auto-injection is skipped (your `@inject` takes precedence).
201
-
202
- ### Constructor Type Validation
203
-
204
- The framework validates constructor parameters at decorator time:
205
-
206
- 1. **First parameter must extend `AbstractDataSource`** - Using `any`, `object`, or non-DataSource types will throw an error
207
- 2. **Type compatibility check** - The constructor parameter type must be compatible with the `dataSource` specified in `@repository`
208
-
209
- ```typescript
210
- // ❌ Error: First parameter must extend AbstractDataSource | Received: 'Object'
211
- @repository({ model: User, dataSource: PostgresDataSource })
212
- export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
213
- constructor(
214
- @inject({ key: 'datasources.PostgresDataSource' })
215
- dataSource: any, // Will cause runtime error!
216
- ) {
217
- super(dataSource);
218
- }
219
- }
220
-
221
- // ❌ Error: Type mismatch | Constructor expects 'MongoDataSource' but @repository specifies 'PostgresDataSource'
222
- @repository({ model: User, dataSource: PostgresDataSource })
223
- export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
224
- constructor(
225
- @inject({ key: 'datasources.MongoDataSource' })
226
- dataSource: MongoDataSource, // Wrong type!
227
- ) {
228
- super(dataSource);
229
- }
230
- }
231
- ```
232
-
233
- ### Example Implementation
234
-
235
- ```typescript
236
- // src/repositories/configuration.repository.ts
237
- import { Configuration, TConfigurationSchema } from '@/models/entities';
238
- import { PostgresDataSource } from '@/datasources';
239
- import { inject, repository, DefaultCRUDRepository } from '@venizia/ignis';
240
-
241
- // Pattern 1: Zero boilerplate (recommended)
242
- @repository({ model: Configuration, dataSource: PostgresDataSource })
243
- export class ConfigurationRepository extends DefaultCRUDRepository<TConfigurationSchema> {
244
- // No constructor needed - datasource and entity auto-resolved!
245
-
246
- // Custom data access methods
247
- async findByCode(code: string): Promise<Configuration | undefined> {
248
- const result = await this.connector.query.Configuration.findFirst({
249
- where: (table, { eq }) => eq(table.code, code)
250
- });
251
- return result;
252
- }
253
- }
254
-
255
- // Pattern 2: With explicit constructor (when you need custom initialization)
256
- @repository({ model: Configuration, dataSource: PostgresDataSource })
257
- export class ConfigurationRepository extends DefaultCRUDRepository<TConfigurationSchema> {
258
- constructor(
259
- @inject({ key: 'datasources.PostgresDataSource' })
260
- dataSource: PostgresDataSource,
261
- ) {
262
- super(dataSource); // Just pass dataSource - entity and relations auto-resolved!
263
- }
264
- }
265
- ```
266
-
267
- This architecture provides a clean and powerful abstraction for data access, separating the "how" of data fetching (Drizzle logic) from the "what" of business logic (services).
268
-
269
- ## Advanced Features
270
-
271
- ### Log Option for Debugging
272
-
273
- All CRUD operations support a `log` option for debugging:
274
-
275
- ```typescript
276
- // Enable logging for a specific operation
277
- await repo.create({
278
- data: { name: 'John', email: 'john@example.com' },
279
- options: {
280
- log: { use: true, level: 'debug' }
281
- }
282
- });
283
- // Output: [_create] Executing with opts: { data: [...], options: {...} }
284
-
285
- // Available log levels: 'debug', 'info', 'warn', 'error'
286
- await repo.updateById({
287
- id: '123',
288
- data: { name: 'Jane' },
289
- options: { log: { use: true, level: 'info' } }
290
- });
291
- ```
292
-
293
- **Available on:** `create`, `createAll`, `updateById`, `updateAll`, `deleteById`, `deleteAll`
294
-
295
- ### TypeScript Return Type Inference
296
-
297
- Repository methods now have improved type inference based on `shouldReturn`:
298
-
299
- ```typescript
300
- // When shouldReturn: false - TypeScript knows data is null
301
- const result1 = await repo.create({
302
- data: { name: 'John' },
303
- options: { shouldReturn: false }
304
- });
305
- // Type: Promise<TCount & { data: null }>
306
- console.log(result1.data); // null
307
-
308
- // When shouldReturn: true (default) - TypeScript knows data is the entity
309
- const result2 = await repo.create({
310
- data: { name: 'John' },
311
- options: { shouldReturn: true }
312
- });
313
- // Type: Promise<TCount & { data: User }>
314
- console.log(result2.data.name); // 'John' - fully typed!
315
-
316
- // Same for array operations
317
- const results = await repo.createAll({
318
- data: [{ name: 'John' }, { name: 'Jane' }],
319
- options: { shouldReturn: true }
320
- });
321
- // Type: Promise<TCount & { data: User[] }>
322
- ```
323
-
324
- ### Generic Return Types
325
-
326
- For queries involving relations (`include`) or custom mapped types, you can override the return type of repository methods. This provides stronger type safety at the application layer without losing the convenience of the repository pattern.
327
-
328
- ```typescript
329
- // Define custom return type with relations
330
- type ProductWithChannels = Product & {
331
- saleChannelProducts: (SaleChannelProduct & {
332
- saleChannel: SaleChannel
333
- })[]
334
- };
335
-
336
- // Use generic override
337
- const product = await productRepo.findOne<ProductWithChannels>({
338
- filter: {
339
- where: { id: '...' },
340
- include: [{
341
- relation: 'saleChannelProducts',
342
- scope: { include: [{ relation: 'saleChannel' }] }
343
- }]
344
- }
345
- });
346
-
347
- // TypeScript knows the structure!
348
- if (product) {
349
- console.log(product.saleChannelProducts[0].saleChannel.name);
350
- }
351
- ```
352
-
353
- **Supported Methods:**
354
- - `find<R>()`
355
- - `findOne<R>()`
356
- - `findById<R>()`
357
- - `create<R>()`, `createAll<R>()`
358
- - `updateById<R>()`, `updateAll<R>()`
359
- - `deleteById<R>()`, `deleteAll<R>()`
360
-
361
- ### Transactions
362
-
363
- Repositories provide direct access to transaction management via the `beginTransaction()` method. This allows you to orchestrate atomic operations across multiple repositories or services.
364
-
365
- ```typescript
366
- // Start a transaction
367
- const tx = await repo.beginTransaction();
368
-
369
- try {
370
- // Perform operations within the transaction
371
- const user = await userRepo.create({
372
- data: { name: 'Alice' },
373
- options: { transaction: tx } // Pass the transaction object
374
- });
375
-
376
- const profile = await profileRepo.create({
377
- data: { userId: user.id, bio: 'Hello' },
378
- options: { transaction: tx } // Use the same transaction
379
- });
380
-
381
- // Commit changes
382
- await tx.commit();
383
- } catch (error) {
384
- // Rollback on error
385
- await tx.rollback();
386
- throw error;
387
- }
388
- ```
389
-
390
- **Isolation Levels:**
391
- You can specify the isolation level when starting a transaction:
392
- ```typescript
393
- const tx = await repo.beginTransaction({
394
- isolationLevel: 'SERIALIZABLE' // 'READ COMMITTED' | 'REPEATABLE READ' | 'SERIALIZABLE'
395
- });
396
- ```
397
-
398
- ### Relations Auto-Resolution
399
-
400
- Relations are automatically resolved from the entity's static `relations` property. This resolution is **recursive**, allowing for deeply nested `include` queries across multiple levels of the entity graph.
401
-
402
- > [!WARNING]
403
- > **Performance Recommendation:** Each nested `include` adds significant overhead to SQL generation and result mapping. We strongly recommend a **maximum of 2 levels** (e.g., `Product -> Junction -> SaleChannel`). For deeper relationships, fetching data in multiple smaller queries is often more performant.
404
-
405
- ```typescript
406
- // Define entity with static relations
407
- @model({ type: 'entity' })
408
- export class User extends BaseEntity<typeof User.schema> {
409
- static override schema = userTable;
410
- static override relations = () => userRelations.definitions;
411
- }
412
-
413
- // Repository automatically uses entity's relations
414
- @repository({ model: User, dataSource: PostgresDataSource })
415
- export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
416
- // No need to pass relations in constructor - auto-resolved!
417
- }
418
-
419
- // Nested inclusion (Product -> Junction -> SaleChannel)
420
- const product = await repo.findOne({
421
- filter: {
422
- include: [
423
- {
424
- relation: 'saleChannelProducts', // Level 1
425
- scope: {
426
- include: [{ relation: 'saleChannel' }] // Level 2 (Nested)
427
- }
428
- }
429
- ]
430
- }
431
- });
432
- ```
433
-
434
- ### Query Interface Validation
435
-
436
- The `getQueryInterface()` method validates that the entity's schema is properly registered:
437
-
438
- ```typescript
439
- // If schema key doesn't match, you get a helpful error:
440
- // Error: [UserRepository] Schema key mismatch | Entity name 'User' not found in connector.query | Available keys: [Configuration, Post] | Ensure the model's TABLE_NAME matches the schema registration key
441
- ```
442
-
443
- ## Performance Optimizations
444
-
445
- ### Core API for Flat Queries
446
-
447
- The `ReadableRepository` automatically optimizes flat queries (no relations, no field selection) using Drizzle's Core API instead of Query API. This provides ~15-20% performance improvement for simple queries.
448
-
449
- > [!IMPORTANT]
450
- > **Always use `limit`:** To ensure consistent performance and prevent memory exhaustion, always provide a `limit` in your filter options, especially for public-facing endpoints.
451
-
452
- **Automatic Optimization:**
453
-
454
- ```typescript
455
- // This query is automatically optimized to use Core API
456
- const users = await repo.find({
457
- filter: {
458
- where: { status: 'active' },
459
- limit: 10, // ✅ Mandatory limit for performance
460
- order: ['createdAt DESC'],
461
- }
462
- });
463
- // Uses: db.select().from(table).where(...).orderBy(...).limit(10)
464
-
465
- // This query uses Query API (relations need relational mapper)
466
- const usersWithPosts = await repo.find({
467
- filter: {
468
- where: { status: 'active' },
469
- include: [{ relation: 'posts' }], // Has relations
470
- }
471
- });
472
- // Uses: db.query.tableName.findMany({ with: { posts: true }, ... })
473
- ```
474
-
475
- **When Core API is used:**
476
-
477
- | Filter Options | API Used | Reason |
478
- |----------------|----------|--------|
479
- | `where`, `limit`, `order`, `offset` only | Core API | Flat query, no overhead |
480
- | Has `include` (relations) | Query API | Needs relational mapper |
481
- | Has `fields` selection | Query API | Core API field syntax differs |
482
-
483
- **Protected Helper Method:**
484
-
485
- For advanced use cases, you can directly use the `findWithCoreAPI` method:
486
-
487
- ```typescript
488
- // Available in subclasses
489
- protected async findWithCoreAPI(opts: {
490
- filter: TFilter<DataObject>;
491
- findOne?: boolean;
492
- }): Promise<Array<DataObject>>;
493
-
494
- // Check if Core API can be used
495
- protected canUseCoreAPI(filter: TFilter<DataObject>): boolean;
496
- ```
497
-
498
- ### WeakMap Cache for Filter Builder
499
-
500
- The `DrizzleFilterBuilder` uses a static WeakMap cache for `getTableColumns()` results, avoiding repeated reflection calls:
501
-
502
- ```typescript
503
- // Internal optimization - no action needed
504
- // First call: getTableColumns(schema) → cached
505
- // Subsequent calls: retrieved from WeakMap
506
- ```
507
-
508
- This is especially beneficial for:
509
- - High-concurrency environments
510
- - Queries with nested AND/OR conditions (each recursion reuses cache)
511
- - Multiple queries to the same table
512
-
513
- ## Query Operators
514
-
515
- The filter builder supports a comprehensive set of query operators for building complex queries.
516
-
517
- **File:** `packages/core/src/base/repositories/operators/query.ts`
518
-
519
- ### Available Operators
520
-
521
- | Operator | Alias | SQL Equivalent | Description |
522
- |----------|-------|----------------|-------------|
523
- | `eq` | - | `=` | Equal to |
524
- | `ne` | `neq` | `!=` | Not equal to |
525
- | `gt` | - | `>` | Greater than |
526
- | `gte` | - | `>=` | Greater than or equal |
527
- | `lt` | - | `<` | Less than |
528
- | `lte` | - | `<=` | Less than or equal |
529
- | `like` | - | `LIKE` | Pattern matching (case-sensitive) |
530
- | `nlike` | - | `NOT LIKE` | Negative pattern matching |
531
- | `ilike` | - | `ILIKE` | Pattern matching (case-insensitive, PostgreSQL) |
532
- | `nilike` | - | `NOT ILIKE` | Negative case-insensitive pattern |
533
- | `in` | `inq` | `IN` | Value in array |
534
- | `nin` | - | `NOT IN` | Value not in array |
535
- | `between` | - | `BETWEEN` | Value between two values |
536
- | `is` | - | `IS NULL` | Null check |
537
- | `isn` | - | `IS NOT NULL` | Not null check |
538
- | `regexp` | - | `~` | PostgreSQL POSIX regex (case-sensitive) |
539
- | `iregexp` | - | `~*` | PostgreSQL POSIX regex (case-insensitive) |
540
-
541
- ### Logical Operators
542
-
543
- | Operator | Description |
544
- |----------|-------------|
545
- | `and` | Combine conditions with AND |
546
- | `or` | Combine conditions with OR |
547
-
548
- ### Usage Examples
549
-
550
- **Simple equality:**
551
- ```typescript
44
+ // Find all active
552
45
  await repo.find({ filter: { where: { status: 'active' } } });
553
- // SQL: WHERE status = 'active'
554
- ```
555
-
556
- **Comparison operators:**
557
- ```typescript
558
- await repo.find({
559
- filter: {
560
- where: {
561
- age: { gte: 18, lt: 65 },
562
- score: { gt: 100 }
563
- }
564
- }
565
- });
566
- // SQL: WHERE age >= 18 AND age < 65 AND score > 100
567
- ```
568
-
569
- **Array operators:**
570
- ```typescript
571
- // IN operator
572
- await repo.find({ filter: { where: { id: [1, 2, 3] } } });
573
- // SQL: WHERE id IN (1, 2, 3)
574
-
575
- // Using explicit IN
576
- await repo.find({ filter: { where: { status: { in: ['active', 'pending'] } } } });
577
-
578
- // NOT IN
579
- await repo.find({ filter: { where: { status: { nin: ['deleted', 'archived'] } } } });
580
- ```
581
46
 
582
- **Pattern matching:**
583
- ```typescript
584
- // LIKE (case-sensitive)
585
- await repo.find({ filter: { where: { name: { like: '%john%' } } } });
586
-
587
- // ILIKE (case-insensitive, PostgreSQL)
588
- await repo.find({ filter: { where: { email: { ilike: '%@gmail.com' } } } });
589
- ```
47
+ // Find by ID
48
+ await repo.findById({ id: '123' });
590
49
 
591
- **Regex (PostgreSQL):**
592
- ```typescript
593
- // Case-sensitive regex
594
- await repo.find({ filter: { where: { name: { regexp: '^John' } } } });
595
-
596
- // Case-insensitive regex
597
- await repo.find({ filter: { where: { name: { iregexp: '^john' } } } });
598
- ```
599
-
600
- **Between:**
601
- ```typescript
602
- await repo.find({
603
- filter: {
604
- where: {
605
- createdAt: { between: [new Date('2024-01-01'), new Date('2024-12-31')] }
606
- }
607
- }
608
- });
609
- // SQL: WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31'
610
- ```
611
-
612
- **Logical operators:**
613
- ```typescript
614
- // OR conditions
615
- await repo.find({
616
- filter: {
617
- where: {
618
- or: [
619
- { status: 'active' },
620
- { isPublished: true }
621
- ]
622
- }
623
- }
624
- });
50
+ // Create
51
+ await repo.create({ data: { name: 'New Item' } });
625
52
 
626
- // AND conditions (explicit)
627
- await repo.find({
628
- filter: {
629
- where: {
630
- and: [
631
- { role: 'admin' },
632
- { createdAt: { gte: new Date('2024-01-01') } }
633
- ]
634
- }
635
- }
636
- });
53
+ // Update
54
+ await repo.updateById({ id: '123', data: { name: 'Updated' } });
637
55
 
638
- // Nested conditions
639
- await repo.find({
640
- filter: {
641
- where: {
642
- status: 'active',
643
- or: [
644
- { role: 'admin' },
645
- { and: [{ role: 'user' }, { verified: true }] }
646
- ]
647
- }
648
- }
649
- });
56
+ // Delete
57
+ await repo.deleteById({ id: '123' });
650
58
  ```
651
59
 
652
- ### Security Notes
60
+ ---
653
61
 
654
- - **Empty IN array:** Returns `false` (no rows), preventing security bypass
655
- - **Empty NOT IN array:** Returns `true` (all rows match)
656
- - **BETWEEN validation:** Requires exactly 2 elements in array, throws error otherwise
657
- - **Invalid columns:** Throws error if column doesn't exist in schema
62
+ **[Continue to Overview →](./repositories/)**