@venizia/ignis-docs 0.0.3 → 0.0.4-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +31 -26
  55. package/wiki/references/base/components.md +24 -7
  56. package/wiki/references/base/controllers.md +50 -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 +604 -0
  76. package/wiki/references/base/models.md +215 -23
  77. package/wiki/references/base/providers.md +731 -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
package/README.md CHANGED
@@ -145,7 +145,7 @@ bun add drizzle-orm drizzle-zod pg lodash
145
145
 
146
146
  **Development dependencies:**
147
147
  ```bash
148
- bun add -d typescript @types/bun @venizia/dev-configs tsc-alias tsconfig-paths
148
+ bun add -d typescript @types/bun @venizia/dev-configs tsc-alias
149
149
  bun add -d drizzle-kit @types/pg @types/lodash
150
150
  ```
151
151
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venizia/ignis-docs",
3
- "version": "0.0.3",
3
+ "version": "0.0.4-1",
4
4
  "description": "Documentation and MCP Server for Ignis Framework",
5
5
  "keywords": [
6
6
  "ignis",
@@ -100,6 +100,8 @@
100
100
  "dependencies": {
101
101
  "@mastra/core": "^0.24.6",
102
102
  "@mastra/mcp": "^0.14.4",
103
+ "cytoscape": "^3.33.1",
104
+ "cytoscape-cose-bilkent": "^4.1.0",
103
105
  "dayjs": "^1.11.19",
104
106
  "debug": "^4.4.3",
105
107
  "fast-glob": "^3.3.3",
@@ -111,7 +113,7 @@
111
113
  "@braintree/sanitize-url": "^7.1.1",
112
114
  "@types/bun": "^1.3.4",
113
115
  "@types/glob": "^8.1.0",
114
- "@venizia/dev-configs": "^0.0.4",
116
+ "@venizia/dev-configs": "^0.0.5-0",
115
117
  "eslint": "^9.36.0",
116
118
  "glob": "^10.4.2",
117
119
  "prettier": "^3.6.2",
@@ -0,0 +1,591 @@
1
+ # API Usage Examples
2
+
3
+ Practical examples for defining endpoints and working with data in Ignis applications.
4
+
5
+ ## Routing Patterns
6
+
7
+ ### Decorator-Based Routing (Recommended)
8
+
9
+ Use `@get`, `@post` decorators with `as const` route configs for full type safety:
10
+
11
+ **`src/controllers/test/definitions.ts`**
12
+ ```typescript
13
+ import { z } from '@hono/zod-openapi';
14
+ import { Authentication, HTTP, jsonContent, jsonResponse } from '@venizia/ignis';
15
+
16
+ // Define route configs as const for type inference
17
+ export const RouteConfigs = {
18
+ // Use UPPER_CASE descriptive names for each route
19
+ GET_TEST: {
20
+ method: HTTP.Methods.GET,
21
+ path: '/test',
22
+ responses: jsonResponse({
23
+ description: 'Test decorator GET endpoint',
24
+ schema: z.object({ message: z.string(), method: z.string() }),
25
+ }),
26
+ },
27
+ CREATE_ITEM: {
28
+ method: HTTP.Methods.POST,
29
+ path: '/items',
30
+ authStrategies: [Authentication.STRATEGY_JWT], // Secure this endpoint
31
+ request: {
32
+ body: jsonContent({
33
+ description: 'Request body for POST',
34
+ schema: z.object({ name: z.string(), age: z.number().int().positive() }),
35
+ }),
36
+ },
37
+ responses: jsonResponse({
38
+ description: 'Test decorator POST endpoint',
39
+ schema: z.object({ id: z.string(), name: z.string(), age: z.number() }),
40
+ }),
41
+ },
42
+ } as const;
43
+ ```
44
+
45
+ Then, use the decorators in your controller class. The `TRouteContext` type provides a fully typed context, including request parameters, body, and response types.
46
+
47
+ **`src/controllers/test/controller.ts`**
48
+ ```typescript
49
+ import {
50
+ BaseController,
51
+ controller,
52
+ get,
53
+ post,
54
+ TRouteContext,
55
+ HTTP,
56
+ } from '@venizia/ignis';
57
+ import { RouteConfigs } from './definitions';
58
+
59
+ @controller({ path: '/test' })
60
+ export class TestController extends BaseController {
61
+ // ...
62
+
63
+ @get({ configs: RouteConfigs.GET_TEST })
64
+ getWithDecorator(context: TRouteContext<typeof RouteConfigs.GET_TEST>) {
65
+ // context is fully typed!
66
+ return context.json({ message: 'Hello from decorator', method: 'GET' }, HTTP.ResultCodes.RS_2.Ok);
67
+ }
68
+
69
+ @post({ configs: RouteConfigs.CREATE_ITEM })
70
+ createWithDecorator(context: TRouteContext<typeof RouteConfigs.CREATE_ITEM>) {
71
+ // context.req.valid('json') is automatically typed as { name: string, age: number }
72
+ const body = context.req.valid('json');
73
+
74
+ // The response is validated against the schema
75
+ return context.json(
76
+ {
77
+ id: crypto.randomUUID(),
78
+ name: body.name,
79
+ age: body.age,
80
+ },
81
+ HTTP.ResultCodes.RS_2.Ok,
82
+ );
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### Example 2: Manual Route Definition in `binding()`
88
+
89
+ You can also define routes manually within the controller's `binding()` method using `defineRoute` or `bindRoute`. This is useful for more complex scenarios or for developers who prefer a non-decorator syntax.
90
+
91
+ **`src/controllers/test/controller.ts`**
92
+ ```typescript
93
+ import { BaseController, controller, HTTP, ValueOrPromise } from '@venizia/ignis';
94
+ import { RouteConfigs } from './definitions';
95
+
96
+ @controller({ path: '/test' })
97
+ export class TestController extends BaseController {
98
+ // ...
99
+ override binding(): ValueOrPromise<void> {
100
+ // Using 'defineRoute'
101
+ this.defineRoute({
102
+ configs: RouteConfigs.GET_HELLO,
103
+ handler: context => {
104
+ return context.json({ message: 'Hello' }, HTTP.ResultCodes.RS_2.Ok);
105
+ },
106
+ });
107
+
108
+ // Using 'bindRoute' for a fluent API
109
+ this.bindRoute({
110
+ configs: RouteConfigs.GET_GREETING,
111
+ }).to({
112
+ handler: context => {
113
+ return context.json({ message: 'Hello 3' }, HTTP.ResultCodes.RS_2.Ok);
114
+ },
115
+ });
116
+ }
117
+ // ...
118
+ }
119
+ ```
120
+
121
+ ### Example 3: Auto-Generated CRUD Controller
122
+
123
+ For standard database entities, you can use `ControllerFactory.defineCrudController` to instantly generate a controller with a full set of CRUD endpoints.
124
+
125
+ **`src/controllers/configuration.controller.ts`**
126
+ ```typescript
127
+ import { Configuration } from '@/models';
128
+ import { ConfigurationRepository } from '@/repositories';
129
+ import {
130
+ BindingKeys,
131
+ BindingNamespaces,
132
+ controller,
133
+ ControllerFactory,
134
+ inject,
135
+ } from '@venizia/ignis';
136
+
137
+ const BASE_PATH = '/configurations';
138
+
139
+ // 1. The factory generates a controller class with all CRUD routes
140
+ const _Controller = ControllerFactory.defineCrudController({
141
+ repository: { name: ConfigurationRepository.name },
142
+ controller: {
143
+ name: 'ConfigurationController',
144
+ basePath: BASE_PATH,
145
+ },
146
+ entity: () => Configuration, // The entity is used to generate OpenAPI schemas
147
+ });
148
+
149
+ // 2. Extend the generated controller to inject the repository
150
+ @controller({ path: BASE_PATH })
151
+ export class ConfigurationController extends _Controller {
152
+ constructor(
153
+ @inject({
154
+ key: BindingKeys.build({
155
+ namespace: BindingNamespaces.REPOSITORY,
156
+ key: ConfigurationRepository.name,
157
+ }),
158
+ })
159
+ repository: ConfigurationRepository,
160
+ ) {
161
+ super(repository);
162
+ }
163
+ }
164
+ ```
165
+ This automatically creates endpoints like `GET /configurations`, `POST /configurations`, `GET /configurations/:id`, etc.
166
+
167
+ ## Repository (Data Access) Usage
168
+
169
+ Repositories are used to interact with your database. The `DefaultCRUDRepository` provides a rich set of methods for data manipulation. Here are examples from the `postConfigure` method in `src/application.ts`, which demonstrates how to use an injected repository.
170
+
171
+ ```typescript
172
+ // In src/application.ts
173
+
174
+ // Get the repository instance from the DI container
175
+ const configurationRepository = this.get<ConfigurationRepository>({
176
+ key: BindingKeys.build({
177
+ namespace: BindingNamespaces.REPOSITORY,
178
+ key: ConfigurationRepository.name,
179
+ }),
180
+ });
181
+
182
+ // --- Find One Record ---
183
+ const record = await configurationRepository.findOne({
184
+ filter: { where: { code: 'CODE_1' } },
185
+ });
186
+
187
+ // --- Find Multiple Records with Relations ---
188
+ const records = await configurationRepository.find({
189
+ filter: {
190
+ where: { code: 'CODE_2' },
191
+ fields: { id: true, code: true, createdBy: true },
192
+ limit: 100,
193
+ include: [{ relation: 'creator' }], // Eager load the 'creator' relation
194
+ },
195
+ });
196
+
197
+ // --- Create a Single Record ---
198
+ const newRecord = await configurationRepository.create({
199
+ data: {
200
+ code: 'NEW_CODE',
201
+ group: 'SYSTEM',
202
+ dataType: 'TEXT',
203
+ tValue: 'some value',
204
+ },
205
+ });
206
+
207
+ // --- Create Multiple Records ---
208
+ const newRecords = await configurationRepository.createAll({
209
+ data: [
210
+ { code: 'CODE_A', group: 'SYSTEM' },
211
+ { code: 'CODE_B', group: 'SYSTEM' },
212
+ ],
213
+ });
214
+
215
+ // --- Update a Record by ID ---
216
+ const updated = await configurationRepository.updateById({
217
+ id: 'some-uuid',
218
+ data: { tValue: 'new value' },
219
+ });
220
+
221
+ // --- Delete a Record by ID ---
222
+ const deleted = await configurationRepository.deleteById({
223
+ id: newRecord.data!.id,
224
+ options: { shouldReturn: true }, // Option to return the deleted record
225
+ });
226
+
227
+ ## Server-Side Rendering (JSX)
228
+
229
+ Ignis supports server-side rendering using Hono's JSX middleware. This is useful for returning HTML content, such as landing pages or simple admin views.
230
+
231
+ **Usage:**
232
+
233
+ Use `defineJSXRoute` in your controller and `htmlResponse` for documentation.
234
+
235
+ ```typescript
236
+ import { BaseController, controller, htmlResponse } from '@venizia/ignis';
237
+
238
+ @controller({ path: '/pages' })
239
+ export class PageController extends BaseController {
240
+
241
+ override binding(): void {
242
+ this.defineJSXRoute({
243
+ configs: {
244
+ method: 'get',
245
+ path: '/welcome',
246
+ description: 'Welcome Page',
247
+ responses: htmlResponse({ description: 'HTML Welcome Page' }),
248
+ },
249
+ handler: (c) => {
250
+ const title = 'Welcome to Ignis';
251
+
252
+ // Return JSX directly
253
+ return c.html(
254
+ <html>
255
+ <head><title>{title}</title></head>
256
+ <body>
257
+ <h1>{title}</h1>
258
+ <p>Server-side rendered content.</p>
259
+ </body>
260
+ </html>
261
+ );
262
+ },
263
+ });
264
+ }
265
+ }
266
+ ```
267
+
268
+ ## Custom Middleware
269
+
270
+ Create reusable middleware using Hono's `createMiddleware` helper.
271
+
272
+ ### Basic Middleware Pattern
273
+
274
+ ```typescript
275
+ import { createMiddleware } from 'hono/factory';
276
+ import type { MiddlewareHandler } from 'hono';
277
+
278
+ // Simple middleware with options
279
+ export const rateLimiter = (opts: { maxRequests: number }): MiddlewareHandler => {
280
+ const { maxRequests } = opts;
281
+ const requests = new Map<string, number>();
282
+
283
+ return createMiddleware(async (c, next) => {
284
+ const ip = c.req.header('x-forwarded-for') ?? 'unknown';
285
+ const count = requests.get(ip) ?? 0;
286
+
287
+ if (count >= maxRequests) {
288
+ return c.json({ error: 'Too many requests' }, 429);
289
+ }
290
+
291
+ requests.set(ip, count + 1);
292
+ await next();
293
+ });
294
+ };
295
+
296
+ // Usage in application
297
+ server.use('/api/*', rateLimiter({ maxRequests: 100 }));
298
+ ```
299
+
300
+ ### Middleware with Logging
301
+
302
+ ```typescript
303
+ import { BaseHelper } from '@venizia/ignis';
304
+ import { createMiddleware } from 'hono/factory';
305
+
306
+ export const requestLogger = (): MiddlewareHandler => {
307
+ const helper = new BaseHelper({ scope: 'RequestLogger' });
308
+
309
+ return createMiddleware(async (c, next) => {
310
+ const start = performance.now();
311
+ const method = c.req.method;
312
+ const path = c.req.path;
313
+
314
+ helper.logger.info('[%s] %s - Started', method, path);
315
+
316
+ await next();
317
+
318
+ const duration = performance.now() - start;
319
+ helper.logger.info('[%s] %s - Completed in %dms', method, path, duration.toFixed(2));
320
+ });
321
+ };
322
+ ```
323
+
324
+ ### Middleware in Controllers
325
+
326
+ Apply middleware to specific routes in your controller:
327
+
328
+ ```typescript
329
+ @controller({ path: '/admin' })
330
+ export class AdminController extends BaseController {
331
+ constructor() {
332
+ super({ scope: AdminController.name, path: '/admin' });
333
+ }
334
+
335
+ override binding(): void {
336
+ // Apply middleware to all routes in this controller
337
+ this.getRouter().use('*', adminOnlyMiddleware());
338
+
339
+ this.defineRoute({
340
+ configs: { method: 'get', path: '/dashboard', /* ... */ },
341
+ handler: (c) => c.json({ /* ... */ }),
342
+ });
343
+ }
344
+ }
345
+ ```
346
+
347
+ ## Service Layer Patterns
348
+
349
+ Services contain business logic and orchestrate operations across multiple repositories.
350
+
351
+ ### Basic Service
352
+
353
+ ```typescript
354
+ import { BaseService, inject, BindingKeys, BindingNamespaces } from '@venizia/ignis';
355
+
356
+ export class UserService extends BaseService {
357
+ constructor(
358
+ @inject({
359
+ key: BindingKeys.build({
360
+ namespace: BindingNamespaces.REPOSITORY,
361
+ key: UserRepository.name,
362
+ }),
363
+ })
364
+ private userRepository: UserRepository,
365
+
366
+ @inject({
367
+ key: BindingKeys.build({
368
+ namespace: BindingNamespaces.REPOSITORY,
369
+ key: OrderRepository.name,
370
+ }),
371
+ })
372
+ private orderRepository: OrderRepository,
373
+ ) {
374
+ super({ scope: UserService.name });
375
+ }
376
+
377
+ async getUserWithOrders(userId: string) {
378
+ const user = await this.userRepository.findById({ id: userId });
379
+ if (!user.data) {
380
+ return null;
381
+ }
382
+
383
+ const orders = await this.orderRepository.find({
384
+ filter: { where: { userId } },
385
+ });
386
+
387
+ return {
388
+ ...user.data,
389
+ orders: orders.data,
390
+ };
391
+ }
392
+
393
+ async deactivateUser(userId: string) {
394
+ // Business logic: cancel pending orders before deactivating
395
+ await this.orderRepository.updateBy({
396
+ where: { userId, status: 'PENDING' },
397
+ data: { status: 'CANCELLED' },
398
+ });
399
+
400
+ return this.userRepository.updateById({
401
+ id: userId,
402
+ data: { status: 'INACTIVE' },
403
+ });
404
+ }
405
+ }
406
+ ```
407
+
408
+ ### Using Services in Controllers
409
+
410
+ ```typescript
411
+ @controller({ path: '/users' })
412
+ export class UserController extends BaseController {
413
+ constructor(
414
+ @inject({
415
+ key: BindingKeys.build({
416
+ namespace: BindingNamespaces.SERVICE,
417
+ key: UserService.name,
418
+ }),
419
+ })
420
+ private userService: UserService,
421
+ ) {
422
+ super({ scope: UserController.name, path: '/users' });
423
+ }
424
+
425
+ @get({ configs: RouteConfigs.GET_USER_WITH_ORDERS })
426
+ async getUserWithOrders(c: TRouteContext<typeof RouteConfigs.GET_USER_WITH_ORDERS>) {
427
+ const { id } = c.req.valid('param');
428
+ const result = await this.userService.getUserWithOrders(id);
429
+
430
+ if (!result) {
431
+ throw getError({ statusCode: 404, message: 'User not found' });
432
+ }
433
+
434
+ return c.json(result, HTTP.ResultCodes.RS_2.Ok);
435
+ }
436
+ }
437
+ ```
438
+
439
+ ## Batch Operations
440
+
441
+ Use `updateBy` and `deleteBy` for bulk operations with filter conditions.
442
+
443
+ ### Bulk Update
444
+
445
+ ```typescript
446
+ // Update all inactive users to archived
447
+ const result = await userRepository.updateBy({
448
+ where: { status: 'INACTIVE', lastLoginAt: { lt: new Date('2024-01-01') } },
449
+ data: { status: 'ARCHIVED' },
450
+ });
451
+ // result.count = number of affected rows
452
+
453
+ // Update ALL records (requires force flag)
454
+ await userRepository.updateBy({
455
+ where: {}, // Empty = all records
456
+ data: { notificationSent: true },
457
+ options: { force: true }, // Required for safety
458
+ });
459
+ ```
460
+
461
+ ### Bulk Delete
462
+
463
+ ```typescript
464
+ // Delete expired sessions
465
+ const result = await sessionRepository.deleteBy({
466
+ where: { expiresAt: { lt: new Date() } },
467
+ });
468
+
469
+ // Delete with return values
470
+ const deleted = await sessionRepository.deleteBy({
471
+ where: { userId: 'user-123' },
472
+ options: { shouldReturn: true }, // Returns deleted records
473
+ });
474
+ // deleted.data = array of deleted records
475
+ ```
476
+
477
+ ### Batch Create
478
+
479
+ ```typescript
480
+ // Create multiple records at once
481
+ const result = await userRepository.createAll({
482
+ data: [
483
+ { name: 'Alice', email: 'alice@example.com' },
484
+ { name: 'Bob', email: 'bob@example.com' },
485
+ { name: 'Charlie', email: 'charlie@example.com' },
486
+ ],
487
+ });
488
+ // result.data = array of created records with IDs
489
+ ```
490
+
491
+ ## Error Handling
492
+
493
+ Use `getError()` to throw structured errors that are automatically formatted by the framework.
494
+
495
+ ### Throwing Errors
496
+
497
+ ```typescript
498
+ import { getError, HTTP } from '@venizia/ignis';
499
+
500
+ // Basic error
501
+ throw getError({ message: 'Something went wrong' });
502
+ // Returns: { statusCode: 400, message: 'Something went wrong' }
503
+
504
+ // With status code
505
+ throw getError({
506
+ statusCode: HTTP.ResultCodes.RS_4.NotFound,
507
+ message: 'User not found',
508
+ });
509
+
510
+ // With message code for i18n
511
+ throw getError({
512
+ statusCode: 404,
513
+ message: 'User not found',
514
+ messageCode: 'USER_NOT_FOUND',
515
+ });
516
+ ```
517
+
518
+ ### Error Handling in Route Handlers
519
+
520
+ ```typescript
521
+ @get({ configs: RouteConfigs.GET_USER })
522
+ async getUser(c: TRouteContext<typeof RouteConfigs.GET_USER>) {
523
+ const { id } = c.req.valid('param');
524
+
525
+ const user = await this.userRepository.findById({ id });
526
+
527
+ if (!user.data) {
528
+ throw getError({
529
+ statusCode: 404,
530
+ message: `User with ID '${id}' not found`,
531
+ });
532
+ }
533
+
534
+ return c.json(user.data, HTTP.ResultCodes.RS_2.Ok);
535
+ }
536
+ ```
537
+
538
+ ### Error Response Format
539
+
540
+ All errors are automatically formatted:
541
+
542
+ ```json
543
+ {
544
+ "statusCode": 404,
545
+ "message": "User not found",
546
+ "messageCode": "USER_NOT_FOUND",
547
+ "requestId": "abc123"
548
+ }
549
+ ```
550
+
551
+ ### Try-Catch for Complex Operations
552
+
553
+ ```typescript
554
+ async processOrder(c: Context) {
555
+ const data = c.req.valid('json');
556
+
557
+ try {
558
+ const tx = await this.orderRepository.beginTransaction({
559
+ isolationLevel: 'READ COMMITTED',
560
+ });
561
+
562
+ try {
563
+ const order = await this.orderRepository.create({
564
+ data: { ...data, status: 'PENDING' },
565
+ options: { transaction: tx },
566
+ });
567
+
568
+ await this.inventoryService.decrementStock({
569
+ items: data.items,
570
+ transaction: tx,
571
+ });
572
+
573
+ await tx.commit();
574
+ return c.json(order.data, HTTP.ResultCodes.RS_2.Created);
575
+ } catch (error) {
576
+ await tx.rollback();
577
+ throw error;
578
+ }
579
+ } catch (error) {
580
+ this.logger.error('[processOrder] Failed: %s', error);
581
+
582
+ if (error instanceof ApplicationError) {
583
+ throw error; // Re-throw application errors
584
+ }
585
+
586
+ throw getError({
587
+ statusCode: 500,
588
+ message: 'Failed to process order',
589
+ });
590
+ }
591
+ }