@venizia/ignis-docs 0.0.4-0 → 0.0.4-2

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 (44) hide show
  1. package/package.json +1 -1
  2. package/wiki/best-practices/api-usage-examples.md +1 -0
  3. package/wiki/best-practices/code-style-standards/advanced-patterns.md +259 -0
  4. package/wiki/best-practices/code-style-standards/constants-configuration.md +225 -0
  5. package/wiki/best-practices/code-style-standards/control-flow.md +245 -0
  6. package/wiki/best-practices/code-style-standards/documentation.md +221 -0
  7. package/wiki/best-practices/code-style-standards/function-patterns.md +142 -0
  8. package/wiki/best-practices/code-style-standards/index.md +110 -0
  9. package/wiki/best-practices/code-style-standards/naming-conventions.md +174 -0
  10. package/wiki/best-practices/code-style-standards/route-definitions.md +150 -0
  11. package/wiki/best-practices/code-style-standards/tooling.md +155 -0
  12. package/wiki/best-practices/code-style-standards/type-safety.md +165 -0
  13. package/wiki/best-practices/common-pitfalls.md +164 -3
  14. package/wiki/best-practices/contribution-workflow.md +1 -1
  15. package/wiki/best-practices/data-modeling.md +102 -2
  16. package/wiki/best-practices/error-handling.md +468 -0
  17. package/wiki/best-practices/index.md +204 -21
  18. package/wiki/best-practices/performance-optimization.md +180 -0
  19. package/wiki/best-practices/security-guidelines.md +249 -0
  20. package/wiki/best-practices/testing-strategies.md +620 -0
  21. package/wiki/changelogs/2026-01-05-range-queries-content-range.md +184 -0
  22. package/wiki/changelogs/2026-01-06-basic-authentication.md +103 -0
  23. package/wiki/changelogs/2026-01-07-controller-route-customization.md +209 -0
  24. package/wiki/changelogs/index.md +3 -0
  25. package/wiki/guides/core-concepts/components-guide.md +1 -1
  26. package/wiki/guides/core-concepts/persistent/models.md +10 -0
  27. package/wiki/guides/tutorials/complete-installation.md +1 -1
  28. package/wiki/guides/tutorials/testing.md +1 -1
  29. package/wiki/references/base/bootstrapping.md +4 -3
  30. package/wiki/references/base/components.md +47 -29
  31. package/wiki/references/base/controllers.md +220 -24
  32. package/wiki/references/base/filter-system/fields-order-pagination.md +84 -0
  33. package/wiki/references/base/middlewares.md +37 -3
  34. package/wiki/references/base/models.md +40 -2
  35. package/wiki/references/base/providers.md +1 -2
  36. package/wiki/references/base/repositories/index.md +3 -1
  37. package/wiki/references/base/services.md +2 -2
  38. package/wiki/references/components/authentication.md +261 -247
  39. package/wiki/references/helpers/index.md +1 -1
  40. package/wiki/references/helpers/socket-io.md +1 -1
  41. package/wiki/references/quick-reference.md +2 -2
  42. package/wiki/references/src-details/core.md +1 -1
  43. package/wiki/references/utilities/statuses.md +4 -4
  44. package/wiki/best-practices/code-style-standards.md +0 -1193
@@ -329,29 +329,42 @@ This factory method returns a `BaseController` class that is already set up with
329
329
 
330
330
  ### Routes Configuration
331
331
 
332
- The `routes` option provides a unified way to configure both schema overrides and authentication for each endpoint:
332
+ The `routes` option provides a unified way to configure request/response schemas and authentication for each endpoint:
333
333
 
334
334
  ```typescript
335
335
  type TRouteAuthConfig =
336
336
  | { skipAuth: true }
337
337
  | { skipAuth?: false; authStrategies: Array<TAuthStrategy> };
338
338
 
339
- type TReadRouteConfig = TRouteAuthConfig & { schema?: z.ZodObject };
340
- type TWriteRouteConfig = TReadRouteConfig & { requestBody?: z.ZodObject };
341
- type TDeleteRouteConfig = TRouteAuthConfig & { schema?: z.ZodObject };
339
+ type TRequestConfig = {
340
+ query?: z.ZodObject; // Custom query parameters
341
+ headers?: z.ZodObject; // Custom headers
342
+ params?: z.ZodObject; // Custom path parameters
343
+ body?: z.ZodObject; // Custom request body (write routes only)
344
+ };
345
+
346
+ type TResponseConfig = {
347
+ schema?: z.ZodObject; // Custom response body schema
348
+ headers?: z.ZodObject; // Custom response headers
349
+ };
350
+
351
+ type TBaseRouteConfig = TRouteAuthConfig & {
352
+ request?: TRequestConfig;
353
+ response?: TResponseConfig;
354
+ };
342
355
  ```
343
356
 
344
- | Route | Type | Description |
357
+ | Route | Customizable Components | Description |
345
358
  | :--- | :--- | :--- |
346
- | `count` | `TReadRouteConfig` | Config for count endpoint |
347
- | `find` | `TReadRouteConfig` | Config for find endpoint |
348
- | `findOne` | `TReadRouteConfig` | Config for findOne endpoint |
349
- | `findById` | `TReadRouteConfig` | Config for findById endpoint |
350
- | `create` | `TWriteRouteConfig` | Config for create endpoint (supports `requestBody`) |
351
- | `updateById` | `TWriteRouteConfig` | Config for updateById endpoint (supports `requestBody`) |
352
- | `updateBy` | `TWriteRouteConfig` | Config for updateBy endpoint (supports `requestBody`) |
353
- | `deleteById` | `TDeleteRouteConfig` | Config for deleteById endpoint |
354
- | `deleteBy` | `TDeleteRouteConfig` | Config for deleteBy endpoint |
359
+ | `count` | query, headers, response | Config for count endpoint |
360
+ | `find` | query, headers, response | Config for find endpoint |
361
+ | `findOne` | query, headers, response | Config for findOne endpoint |
362
+ | `findById` | query, headers, params, response | Config for findById endpoint |
363
+ | `create` | headers, body, response | Config for create endpoint |
364
+ | `updateById` | headers, params, body, response | Config for updateById endpoint |
365
+ | `updateBy` | query, headers, body, response | Config for updateBy endpoint |
366
+ | `deleteById` | headers, params, response | Config for deleteById endpoint |
367
+ | `deleteBy` | query, headers, response | Config for deleteBy endpoint |
355
368
 
356
369
  ### Auth Resolution Priority
357
370
 
@@ -397,17 +410,70 @@ const ArticleController = ControllerFactory.defineCrudController({
397
410
  },
398
411
  });
399
412
 
400
- // 4. Custom schema with auth configuration
413
+ // 4. Custom request/response schemas with auth configuration
401
414
  const OrderController = ControllerFactory.defineCrudController({
402
415
  entity: OrderEntity,
403
416
  repository: { name: 'OrderRepository' },
404
417
  controller: { name: 'OrderController', basePath: '/orders' },
405
418
  authStrategies: ['jwt'],
406
419
  routes: {
407
- find: { schema: CustomOrderListSchema, skipAuth: true },
420
+ find: {
421
+ skipAuth: true,
422
+ response: { schema: CustomOrderListSchema },
423
+ },
424
+ create: {
425
+ request: { body: CustomOrderCreateSchema },
426
+ response: { schema: CustomOrderResponseSchema },
427
+ },
428
+ },
429
+ });
430
+ ```
431
+
432
+ ### Route Customization Examples
433
+
434
+ ```typescript
435
+ import { z } from '@hono/zod-openapi';
436
+
437
+ // Custom request body for create
438
+ const CreateUserSchema = z.object({
439
+ name: z.string().min(1).max(100),
440
+ email: z.string().email(),
441
+ role: z.enum(['admin', 'user']).default('user'),
442
+ });
443
+
444
+ // Custom response schema (omit sensitive fields)
445
+ const PublicUserSchema = z.object({
446
+ id: z.string(),
447
+ name: z.string(),
448
+ role: z.string(),
449
+ createdAt: z.string(),
450
+ });
451
+
452
+ const UserController = ControllerFactory.defineCrudController({
453
+ entity: UserEntity,
454
+ repository: { name: 'UserRepository' },
455
+ controller: { name: 'UserController', basePath: '/users' },
456
+ authStrategies: ['jwt'],
457
+ routes: {
458
+ // Public read endpoints
459
+ find: {
460
+ skipAuth: true,
461
+ response: { schema: z.array(PublicUserSchema) },
462
+ },
463
+ findById: {
464
+ skipAuth: true,
465
+ response: { schema: PublicUserSchema },
466
+ },
467
+
468
+ // Custom create with custom body and response
408
469
  create: {
409
- schema: CustomOrderResponseSchema,
410
- requestBody: CustomOrderCreateSchema,
470
+ request: { body: CreateUserSchema },
471
+ response: { schema: PublicUserSchema },
472
+ },
473
+
474
+ // Delete requires JWT auth (uses default schema)
475
+ deleteById: {
476
+ authStrategies: ['jwt'],
411
477
  },
412
478
  },
413
479
  });
@@ -459,6 +525,137 @@ export class ConfigurationController extends _ConfigurationController {
459
525
 
460
526
  By leveraging these structured configuration options and the `ControllerFactory`, you ensure that your API is not only functional but also well-documented, easy to validate, and rapidly deployable for standard CRUD operations.
461
527
 
528
+ ### Overriding CRUD Methods with Strong Typing
529
+
530
+ When extending a generated CRUD controller, you can override methods with full type safety using the helper types `THandlerContext` and `TInferSchema`.
531
+
532
+ #### Helper Types
533
+
534
+ | Type | Purpose |
535
+ | :--- | :--- |
536
+ | `THandlerContext<Definitions, Key>` | Extracts the strongly-typed Hono context for a specific route |
537
+ | `TInferSchema<ZodSchema>` | Extracts TypeScript type from a Zod schema |
538
+
539
+ #### Example: Full Controller Override Pattern
540
+
541
+ ```typescript
542
+ import { Configuration } from '@/models';
543
+ import { ConfigurationRepository } from '@/repositories';
544
+ import {
545
+ Authentication,
546
+ BindingKeys,
547
+ BindingNamespaces,
548
+ controller,
549
+ ControllerFactory,
550
+ inject,
551
+ THandlerContext,
552
+ TInferSchema,
553
+ } from '@venizia/ignis';
554
+ import { z } from '@hono/zod-openapi';
555
+
556
+ const BASE_PATH = '/configurations';
557
+
558
+ // Custom request body schema
559
+ const CreateConfigurationSchema = z.object({
560
+ code: z.string().min(1).max(100),
561
+ description: z.string().max(500).optional(),
562
+ group: z.string().min(1).max(50),
563
+ });
564
+
565
+ // Custom response schema
566
+ const CreateResponseSchema = z.object({
567
+ id: z.string(),
568
+ code: z.string(),
569
+ message: z.string(),
570
+ });
571
+
572
+ // Define the CRUD controller with custom schemas
573
+ const _Controller = ControllerFactory.defineCrudController({
574
+ repository: { name: ConfigurationRepository.name },
575
+ controller: { name: 'ConfigurationController', basePath: BASE_PATH },
576
+ authStrategies: [Authentication.STRATEGY_JWT],
577
+ entity: () => Configuration,
578
+ routes: {
579
+ count: { skipAuth: true },
580
+ create: {
581
+ request: { body: CreateConfigurationSchema },
582
+ response: { schema: CreateResponseSchema },
583
+ },
584
+ },
585
+ });
586
+
587
+ // Extract definitions type for method overrides
588
+ type TRouteDefinitions = InstanceType<typeof _Controller>['definitions'];
589
+
590
+ @controller({ path: BASE_PATH })
591
+ export class ConfigurationController extends _Controller {
592
+ constructor(
593
+ @inject({
594
+ key: BindingKeys.build({
595
+ namespace: BindingNamespaces.REPOSITORY,
596
+ key: ConfigurationRepository.name,
597
+ }),
598
+ })
599
+ repository: ConfigurationRepository,
600
+ ) {
601
+ super(repository);
602
+ }
603
+
604
+ // Override with full type safety
605
+ override async create(opts: { context: THandlerContext<TRouteDefinitions, 'CREATE'> }) {
606
+ const { context } = opts;
607
+
608
+ // Get typed request body using TInferSchema
609
+ const data = context.req.valid('json') as TInferSchema<typeof CreateConfigurationSchema>;
610
+
611
+ // Access typed properties
612
+ this.logger.info('[create] code: %s, group: %s', data.code, data.group);
613
+
614
+ // Custom business logic here...
615
+
616
+ // Call parent or return custom response
617
+ return super.create(opts);
618
+ }
619
+
620
+ // Override updateById
621
+ override async updateById(opts: { context: THandlerContext<TRouteDefinitions, 'UPDATE_BY_ID'> }) {
622
+ const { context } = opts;
623
+ const { id } = context.req.valid('param');
624
+ const data = context.req.valid('json');
625
+
626
+ this.logger.info('[updateById] id: %s', id);
627
+
628
+ return super.updateById(opts);
629
+ }
630
+
631
+ // Override deleteById with audit logging
632
+ override async deleteById(opts: { context: THandlerContext<TRouteDefinitions, 'DELETE_BY_ID'> }) {
633
+ const { context } = opts;
634
+ const { id } = context.req.valid('param');
635
+
636
+ this.logger.warn('[deleteById] Deleting id: %s', id);
637
+
638
+ return super.deleteById(opts);
639
+ }
640
+ }
641
+ ```
642
+
643
+ #### Available Route Keys
644
+
645
+ Use these keys with `THandlerContext`:
646
+
647
+ | Key | Method | Path |
648
+ | :--- | :--- | :--- |
649
+ | `'COUNT'` | GET | /count |
650
+ | `'FIND'` | GET | / |
651
+ | `'FIND_BY_ID'` | GET | /:id |
652
+ | `'FIND_ONE'` | GET | /find-one |
653
+ | `'CREATE'` | POST | / |
654
+ | `'UPDATE_BY_ID'` | PATCH | /:id |
655
+ | `'UPDATE_BY'` | PATCH | / |
656
+ | `'DELETE_BY_ID'` | DELETE | /:id |
657
+ | `'DELETE_BY'` | DELETE | / |
658
+
462
659
  ---
463
660
 
464
661
  ## See Also
@@ -471,14 +668,13 @@ By leveraging these structured configuration options and the `ControllerFactory`
471
668
  - [Dependency Injection](./dependency-injection.md) - DI patterns and injection
472
669
 
473
670
  - **Guides:**
474
- - [Building Your First API](/guides/getting-started/first-api.md)
475
- - [Controllers Guide](/guides/core-concepts/controllers.md)
476
- - [Routing and Decorators](/guides/core-concepts/routing.md)
671
+ - [Controllers Guide](/guides/core-concepts/controllers)
672
+ - [Building a CRUD API](/guides/tutorials/building-a-crud-api)
477
673
 
478
674
  - **Best Practices:**
479
- - [API Design Patterns](/best-practices/architecture/api-design.md)
480
- - [Error Handling](/best-practices/architecture/error-handling.md)
481
- - [Request Validation](/best-practices/security/input-validation.md)
675
+ - [API Usage Examples](/best-practices/api-usage-examples)
676
+ - [Troubleshooting Tips](/best-practices/troubleshooting-tips)
677
+ - [Security Guidelines](/best-practices/security-guidelines)
482
678
 
483
679
  - **External Resources:**
484
680
  - [OpenAPI Specification](https://swagger.io/specification/)
@@ -140,6 +140,72 @@ const filter = {
140
140
  ```
141
141
 
142
142
 
143
+ ## Range Queries (Content-Range Header)
144
+
145
+ When building paginated APIs, you often need to return the total count alongside the data for pagination UI. Use `shouldQueryRange: true` to get range information following the HTTP Content-Range standard.
146
+
147
+ ### Basic Usage
148
+
149
+ ```typescript
150
+ const result = await repo.find({
151
+ filter: { limit: 10, skip: 20 },
152
+ options: { shouldQueryRange: true }
153
+ });
154
+
155
+ // Result structure:
156
+ // {
157
+ // data: [...], // Array of records
158
+ // range: {
159
+ // start: 20, // Starting index (inclusive)
160
+ // end: 29, // Ending index (inclusive)
161
+ // total: 100 // Total matching records
162
+ // }
163
+ // }
164
+ ```
165
+
166
+ ### Setting HTTP Headers
167
+
168
+ Use the range information to set standard HTTP headers:
169
+
170
+ ```typescript
171
+ const { data, range } = await repo.find({
172
+ filter: { limit: 10, skip: 20, where: { status: 'active' } },
173
+ options: { shouldQueryRange: true }
174
+ });
175
+
176
+ // Format: "records start-end/total"
177
+ const contentRange = data.length > 0
178
+ ? `records ${range.start}-${range.end}/${range.total}`
179
+ : `records */${range.total}`;
180
+
181
+ res.setHeader('Content-Range', contentRange);
182
+ // → "records 20-29/100"
183
+ ```
184
+
185
+ ### TDataRange Type
186
+
187
+ ```typescript
188
+ type TDataRange = {
189
+ start: number; // Starting index (0-based, inclusive)
190
+ end: number; // Ending index (0-based, inclusive)
191
+ total: number; // Total count matching the query
192
+ };
193
+ ```
194
+
195
+ ### Content-Range Format Reference
196
+
197
+ | Scenario | Content-Range Header |
198
+ |----------|---------------------|
199
+ | Items 0-9 of 100 | `records 0-9/100` |
200
+ | Items 20-29 of 100 | `records 20-29/100` |
201
+ | No items found | `records */0` |
202
+ | Last page (items 90-99) | `records 90-99/100` |
203
+
204
+ ### Performance Note
205
+
206
+ When `shouldQueryRange: true`, the repository executes the data query and count query **in parallel** using `Promise.all` for optimal performance.
207
+
208
+
143
209
  ## Combined Example
144
210
 
145
211
  ```typescript
@@ -153,3 +219,21 @@ await repo.find({
153
219
  }
154
220
  });
155
221
  ```
222
+
223
+ ### With Range Information
224
+
225
+ ```typescript
226
+ const { data, range } = await repo.find({
227
+ filter: {
228
+ where: { status: 'active' },
229
+ fields: ['id', 'name', 'price', 'createdAt'],
230
+ order: ['price ASC', 'createdAt DESC'],
231
+ limit: 20,
232
+ skip: 0
233
+ },
234
+ options: { shouldQueryRange: true }
235
+ });
236
+
237
+ console.log(`Showing ${range.start}-${range.end} of ${range.total}`);
238
+ // → "Showing 0-19 of 150"
239
+ ```
@@ -51,9 +51,10 @@ The error handler middleware catches all unhandled errors in your application an
51
51
 
52
52
  - **Automatic Error Formatting**: Converts all errors to structured JSON responses
53
53
  - **ZodError Support**: Special handling for Zod validation errors with detailed field-level messages
54
+ - **Database Error Handling**: Automatically returns 400 for database constraint violations (unique, foreign key, not null, etc.)
54
55
  - **Environment-Aware**: Hides stack traces and error causes in production
55
56
  - **Request Tracking**: Includes `requestId` for debugging and tracing
56
- - **Status Code Detection**: Automatically extracts HTTP status codes from errors
57
+ - **Status Code Detection**: Automatically extracts `statusCode` from errors
57
58
 
58
59
  #### Usage
59
60
 
@@ -110,6 +111,37 @@ app.onError(appErrorHandler({
110
111
  }
111
112
  ```
112
113
 
114
+ **Database Constraint Error:**
115
+
116
+ Database constraint violations (unique, foreign key, not null, check) are automatically detected and returned as 400 Bad Request with a human-readable message:
117
+
118
+ ```json
119
+ {
120
+ "message": "Unique constraint violation\nDetail: Key (email)=(test@example.com) already exists.\nTable: User\nConstraint: UQ_User_email",
121
+ "statusCode": 400,
122
+ "requestId": "abc123",
123
+ "details": {
124
+ "url": "http://localhost:3000/api/users",
125
+ "path": "/api/users",
126
+ "stack": "...", // development only
127
+ "cause": { ... } // development only
128
+ }
129
+ }
130
+ ```
131
+
132
+ **Supported PostgreSQL Error Codes:**
133
+
134
+ | Code | Error Type |
135
+ |------|------------|
136
+ | 23505 | Unique constraint violation |
137
+ | 23503 | Foreign key constraint violation |
138
+ | 23502 | Not null constraint violation |
139
+ | 23514 | Check constraint violation |
140
+ | 23P01 | Exclusion constraint violation |
141
+ | 22P02 | Invalid text representation |
142
+ | 22003 | Numeric value out of range |
143
+ | 22001 | String data too long |
144
+
113
145
  #### API Reference
114
146
 
115
147
  ##### `appErrorHandler(options)`
@@ -594,8 +626,10 @@ Error handlers log every error. For high error rates, consider:
594
626
  - [Dependency Injection](./dependency-injection.md) - DI container and providers
595
627
 
596
628
  - **Guides:**
597
- - [Building Your First API](/guides/getting-started/first-api.md)
598
- - [Error Handling Best Practices](/best-practices/architecture/error-handling.md)
629
+ - [Building a CRUD API](/guides/tutorials/building-a-crud-api)
630
+
631
+ - **Best Practices:**
632
+ - [Troubleshooting Tips](/best-practices/troubleshooting-tips)
599
633
 
600
634
  - **External Resources:**
601
635
  - [Hono Middleware Documentation](https://hono.dev/docs/guides/middleware)
@@ -709,6 +709,7 @@ generateUserAuditColumnDefs(opts?: TUserAuditEnricherOptions): {
709
709
  type TUserAuditColumnOpts = {
710
710
  dataType: 'string' | 'number'; // Required - type of user ID
711
711
  columnName: string; // Column name in database
712
+ allowAnonymous?: boolean; // Allow null user ID (default: true)
712
713
  };
713
714
 
714
715
  type TUserAuditEnricherOptions = {
@@ -718,8 +719,27 @@ type TUserAuditEnricherOptions = {
718
719
  ```
719
720
 
720
721
  **Default values:**
721
- - `created`: `{ dataType: 'number', columnName: 'created_by' }`
722
- - `modified`: `{ dataType: 'number', columnName: 'modified_by' }`
722
+ - `created`: `{ dataType: 'number', columnName: 'created_by', allowAnonymous: true }`
723
+ - `modified`: `{ dataType: 'number', columnName: 'modified_by', allowAnonymous: true }`
724
+
725
+ #### `allowAnonymous` Behavior
726
+
727
+ The `allowAnonymous` option controls whether the enricher requires an authenticated user context:
728
+
729
+ | `allowAnonymous` | No Context | No User ID | Has User ID |
730
+ |------------------|------------|------------|-------------|
731
+ | `true` (default) | Returns `null` | Returns `null` | Returns user ID |
732
+ | `false` | Throws error | Throws error | Returns user ID |
733
+
734
+ **When to use `allowAnonymous: false`:**
735
+ - Sensitive audit trails that must track the responsible user
736
+ - Tables where anonymous operations should be forbidden
737
+ - Compliance requirements that mandate user attribution
738
+
739
+ **When to use `allowAnonymous: true` (default):**
740
+ - Background jobs, migrations, or seed scripts without user context
741
+ - System-generated records
742
+ - Tables that allow both authenticated and anonymous operations
723
743
 
724
744
  #### Generated Columns
725
745
 
@@ -795,6 +815,24 @@ export const myTable = pgTable('MyTable', {
795
815
  // modifiedBy: integer('editor_id')
796
816
  ```
797
817
 
818
+ **Requiring authenticated user (allowAnonymous: false):**
819
+
820
+ ```typescript
821
+ // For sensitive tables that must track the responsible user
822
+ export const auditLogTable = pgTable('AuditLog', {
823
+ ...generateIdColumnDefs(),
824
+ ...generateUserAuditColumnDefs({
825
+ created: { dataType: 'number', columnName: 'created_by', allowAnonymous: false },
826
+ modified: { dataType: 'number', columnName: 'modified_by', allowAnonymous: false },
827
+ }),
828
+ action: text('action').notNull(),
829
+ details: text('details'),
830
+ });
831
+
832
+ // If no authenticated user context is available, throws:
833
+ // Error: [getCurrentUserId] Invalid request context to identify user | columnName: createdBy | allowAnonymous: false
834
+ ```
835
+
798
836
 
799
837
  ## Schema Utilities
800
838
 
@@ -724,8 +724,7 @@ export class ConfigProvider extends BaseProvider<Config> {
724
724
  - [Building Services](/guides/core-concepts/services.md)
725
725
 
726
726
  - **Best Practices:**
727
- - [Dependency Injection Patterns](/best-practices/architecture/dependency-injection.md)
728
- - [Service Layer Patterns](/best-practices/architecture/service-patterns.md)
727
+ - [Architectural Patterns](/best-practices/architectural-patterns)
729
728
 
730
729
  - **External Resources:**
731
730
  - [Factory Pattern](https://refactoring.guru/design-patterns/factory-method)
@@ -39,6 +39,7 @@ export class TodoRepository extends DefaultCRUDRepository<typeof Todo.schema> {
39
39
  | Method | Description | Example |
40
40
  |--------|-------------|---------|
41
41
  | `find(opts)` | Find multiple records | `repo.find({ filter: { where: { status: 'active' } } })` |
42
+ | `find(opts)` with range | Find with pagination range | `repo.find({ filter, options: { shouldQueryRange: true } })` |
42
43
  | `findOne(opts)` | Find single record | `repo.findOne({ filter: { where: { email } } })` |
43
44
  | `findById(opts)` | Find by primary key | `repo.findById({ id: '123' })` |
44
45
  | `count(opts)` | Count matching records | `repo.count({ where: { status: 'active' } })` |
@@ -186,6 +187,7 @@ await repo.deleteAll({ where: {}, options: { force: true } });
186
187
  | Want to... | Code |
187
188
  |------------|------|
188
189
  | Find all active | `repo.find({ filter: { where: { status: 'active' } } })` |
190
+ | Find with range info | `repo.find({ filter, options: { shouldQueryRange: true } })` |
189
191
  | Find by ID | `repo.findById({ id: '123' })` |
190
192
  | Find with relations | `repo.find({ filter: { include: [{ relation: 'posts' }] } })` |
191
193
  | Create one | `repo.create({ data: { name: 'John' } })` |
@@ -207,7 +209,7 @@ await repo.deleteAll({ where: {}, options: { force: true } });
207
209
  - [Repositories Guide](/guides/core-concepts/persistent/repositories) - Creating repositories tutorial
208
210
  - [Models](/guides/core-concepts/persistent/models) - Entity definitions used by repositories
209
211
  - [DataSources](/guides/core-concepts/persistent/datasources) - Database connections
210
- - [Services](/guides/core-concepts/persistent/services) - Use repositories for data access
212
+ - [Services](/guides/core-concepts/services) - Use repositories for data access
211
213
  - [Transactions](/guides/core-concepts/persistent/transactions) - Multi-operation consistency
212
214
 
213
215
  - **Repository Topics:**
@@ -117,5 +117,5 @@ By adhering to this pattern, you keep your code organized, testable, and maintai
117
117
  - [Dependency Injection Guide](/guides/core-concepts/dependency-injection.md)
118
118
 
119
119
  - **Best Practices:**
120
- - [Service Layer Patterns](/best-practices/architecture/service-patterns.md)
121
- - [Testing Services](/best-practices/testing/unit-testing.md)
120
+ - [Architectural Patterns](/best-practices/architectural-patterns)
121
+ - [Testing Guide](/guides/tutorials/testing)