@venizia/ignis-docs 0.0.1-9 → 0.0.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 (32) hide show
  1. package/LICENSE.md +1 -0
  2. package/package.json +2 -2
  3. package/wiki/changelogs/{v0.0.1-7-initial-architecture.md → 2025-12-16-initial-architecture.md} +20 -12
  4. package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +300 -0
  5. package/wiki/changelogs/2025-12-17-refactor.md +80 -12
  6. package/wiki/changelogs/2025-12-18-performance-optimizations.md +28 -90
  7. package/wiki/changelogs/2025-12-18-repository-validation-security.md +101 -297
  8. package/wiki/changelogs/index.md +19 -8
  9. package/wiki/changelogs/planned-transaction-support.md +216 -0
  10. package/wiki/changelogs/template.md +123 -0
  11. package/wiki/get-started/best-practices/api-usage-examples.md +0 -2
  12. package/wiki/get-started/best-practices/architectural-patterns.md +2 -2
  13. package/wiki/get-started/best-practices/common-pitfalls.md +5 -3
  14. package/wiki/get-started/best-practices/contribution-workflow.md +2 -0
  15. package/wiki/get-started/best-practices/data-modeling.md +91 -34
  16. package/wiki/get-started/best-practices/security-guidelines.md +3 -1
  17. package/wiki/get-started/building-a-crud-api.md +3 -3
  18. package/wiki/get-started/core-concepts/application.md +72 -3
  19. package/wiki/get-started/core-concepts/bootstrapping.md +566 -0
  20. package/wiki/get-started/core-concepts/components.md +4 -2
  21. package/wiki/get-started/core-concepts/persistent.md +350 -378
  22. package/wiki/get-started/core-concepts/services.md +21 -27
  23. package/wiki/references/base/bootstrapping.md +789 -0
  24. package/wiki/references/base/components.md +1 -1
  25. package/wiki/references/base/dependency-injection.md +95 -2
  26. package/wiki/references/base/services.md +2 -2
  27. package/wiki/references/components/authentication.md +4 -3
  28. package/wiki/references/components/index.md +1 -1
  29. package/wiki/references/helpers/error.md +2 -2
  30. package/wiki/references/src-details/boot.md +379 -0
  31. package/wiki/references/src-details/core.md +2 -2
  32. package/wiki/changelogs/v0.0.1-8-model-repo-datasource-refactor.md +0 -278
@@ -0,0 +1,216 @@
1
+ ---
2
+ title: Planned - Transaction Support
3
+ description: Implementation plan for Loopback 4-style explicit transaction objects
4
+ ---
5
+
6
+ # Planned: Transaction Support
7
+
8
+ **Status:** Planned (Not Yet Implemented)
9
+ **Priority:** Future Enhancement
10
+
11
+ ## Goal
12
+
13
+ Implement Loopback 4-style explicit transaction objects, allowing transactions to be passed through multiple services/repositories instead of using Drizzle's callback-based approach.
14
+
15
+ ## Target API
16
+
17
+ ```typescript
18
+ // Default isolation level (READ COMMITTED)
19
+ const tx = await userRepo.beginTransaction();
20
+
21
+ // Or with specific isolation level
22
+ const tx = await userRepo.beginTransaction({
23
+ isolationLevel: 'SERIALIZABLE'
24
+ });
25
+
26
+ try {
27
+ await userRepo.create({ data, options: { transaction: tx } });
28
+ await profileRepo.create({ data, options: { transaction: tx } });
29
+ await tx.commit();
30
+ } catch (err) {
31
+ await tx.rollback();
32
+ throw err;
33
+ }
34
+ ```
35
+
36
+ ### Isolation Levels
37
+
38
+ | Level | Description | Use Case |
39
+ |-------|-------------|----------|
40
+ | `READ COMMITTED` | Default. Sees only committed data at query start | General use, most common |
41
+ | `REPEATABLE READ` | Sees snapshot from transaction start | Reports, consistent reads |
42
+ | `SERIALIZABLE` | Strictest. Full isolation, may throw serialization errors | Financial transactions, critical data |
43
+
44
+ ---
45
+
46
+ ## Implementation Steps
47
+
48
+ ### Step 1: Define Transaction Types
49
+
50
+ **File:** `packages/core/src/base/datasources/types.ts`
51
+
52
+ ```typescript
53
+ /** PostgreSQL transaction isolation levels */
54
+ export type TIsolationLevel = 'READ COMMITTED' | 'REPEATABLE READ' | 'SERIALIZABLE';
55
+
56
+ /** Options for starting a transaction */
57
+ export interface ITransactionOptions {
58
+ isolationLevel?: TIsolationLevel;
59
+ }
60
+
61
+ /** Transaction object returned by beginTransaction() */
62
+ export interface ITransaction<Connector = TNodePostgresConnector> {
63
+ /** Isolated Drizzle instance bound to this transaction */
64
+ connector: Connector;
65
+
66
+ /** Commit the transaction */
67
+ commit(): Promise<void>;
68
+
69
+ /** Rollback the transaction */
70
+ rollback(): Promise<void>;
71
+
72
+ /** Check if transaction is still active */
73
+ isActive: boolean;
74
+
75
+ /** The isolation level used for this transaction */
76
+ isolationLevel: TIsolationLevel;
77
+ }
78
+ ```
79
+
80
+ ### Step 2: Add `beginTransaction()` to DataSource
81
+
82
+ **File:** `packages/core/src/base/datasources/base.ts`
83
+
84
+ ```typescript
85
+ async beginTransaction(
86
+ opts?: ITransactionOptions
87
+ ): Promise<ITransaction<Connector>> {
88
+ // 1. Get raw client from pool
89
+ const pool = this.connector.client as Pool;
90
+ const client = await pool.connect();
91
+
92
+ // 2. Determine isolation level (default: READ COMMITTED)
93
+ const isolationLevel: TIsolationLevel = opts?.isolationLevel ?? 'READ COMMITTED';
94
+
95
+ // 3. Execute BEGIN with isolation level
96
+ await client.query(`BEGIN TRANSACTION ISOLATION LEVEL ${isolationLevel}`);
97
+
98
+ // 4. Create isolated Drizzle instance with this client
99
+ const txConnector = drizzle({ client, schema: this.schema });
100
+
101
+ // 5. Return transaction object
102
+ let isActive = true;
103
+
104
+ return {
105
+ connector: txConnector as Connector,
106
+ isActive,
107
+ isolationLevel,
108
+
109
+ async commit() {
110
+ if (!isActive) throw new Error('Transaction already ended');
111
+ try {
112
+ await client.query('COMMIT');
113
+ } finally {
114
+ isActive = false;
115
+ client.release();
116
+ }
117
+ },
118
+
119
+ async rollback() {
120
+ if (!isActive) throw new Error('Transaction already ended');
121
+ try {
122
+ await client.query('ROLLBACK');
123
+ } finally {
124
+ isActive = false;
125
+ client.release();
126
+ }
127
+ },
128
+ };
129
+ }
130
+ ```
131
+
132
+ ### Step 3: Update Repository Base
133
+
134
+ **File:** `packages/core/src/base/repositories/core/base.ts`
135
+
136
+ ```typescript
137
+ // Add method to start transaction (delegates to DataSource)
138
+ async beginTransaction(opts?: ITransactionOptions): Promise<ITransaction> {
139
+ return this.dataSource.beginTransaction(opts);
140
+ }
141
+
142
+ // Replace this.connector with getConnector(opts)
143
+ protected getConnector(opts?: { transaction?: ITransaction }) {
144
+ if (opts?.transaction) {
145
+ if (!opts.transaction.isActive) {
146
+ throw getError({ message: 'Transaction is no longer active' });
147
+ }
148
+ return opts.transaction.connector;
149
+ }
150
+ return this.dataSource.connector;
151
+ }
152
+ ```
153
+
154
+ ### Step 4: Update CRUD Options Types
155
+
156
+ **File:** `packages/core/src/base/repositories/common/types.ts`
157
+
158
+ ```typescript
159
+ export type TTransactionOption = {
160
+ transaction?: ITransaction;
161
+ };
162
+
163
+ // Add to existing option types
164
+ export type TCreateOptions = TTransactionOption & {
165
+ shouldReturn?: boolean;
166
+ log?: TRepositoryLogOptions;
167
+ };
168
+ ```
169
+
170
+ ### Step 5: Update CRUD Methods
171
+
172
+ **Files:** `readable.ts`, `persistable.ts`
173
+
174
+ Change all methods from:
175
+ ```typescript
176
+ this.connector.insert(...)
177
+ ```
178
+
179
+ To:
180
+ ```typescript
181
+ this.getConnector(opts.options).insert(...)
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Files to Modify
187
+
188
+ | File | Changes |
189
+ |------|---------|
190
+ | `packages/core/src/base/datasources/types.ts` | Add `TIsolationLevel`, `ITransactionOptions`, `ITransaction` |
191
+ | `packages/core/src/base/datasources/base.ts` | Add `beginTransaction(opts?)` method |
192
+ | `packages/core/src/base/repositories/common/types.ts` | Add `TTransactionOption` |
193
+ | `packages/core/src/base/repositories/core/base.ts` | Add `beginTransaction(opts?)`, `getConnector(opts)` |
194
+ | `packages/core/src/base/repositories/core/readable.ts` | Use `getConnector(opts)` in all methods |
195
+ | `packages/core/src/base/repositories/core/persistable.ts` | Use `getConnector(opts)` in all methods |
196
+
197
+ ---
198
+
199
+ ## Breaking Changes
200
+
201
+ 1. **`this.connector`** → `this.getConnector(opts)`
202
+ - Backward compatible when called without args
203
+
204
+ 2. **Options parameter** - Now includes optional `transaction` field
205
+ - Non-breaking: transaction is optional
206
+
207
+ ---
208
+
209
+ ## Benefits
210
+
211
+ | Aspect | Current (Drizzle Callback) | After (Pass-through) |
212
+ |--------|---------------------------|----------------------|
213
+ | Service composition | Hard - all in one callback | Easy - pass tx anywhere |
214
+ | Separation of concerns | Services must know each other | Services stay independent |
215
+ | Testing | Complex mocking | Easy to mock tx object |
216
+ | Code organization | Nested callbacks | Flat, sequential flow |
@@ -0,0 +1,123 @@
1
+ ---
2
+ title: [Short Title]
3
+ description: [Brief description of the changes]
4
+ ---
5
+
6
+ # Changelog - YYYY-MM-DD
7
+
8
+ ## [Main Title/Focus Area]
9
+
10
+ [A brief, high-level summary of the changes in this release. What is the main focus?]
11
+
12
+ ## Overview
13
+
14
+ - **[Change 1]**: Brief description
15
+ - **[Change 2]**: Brief description
16
+ - **[Change 3]**: Brief description
17
+
18
+ ## Breaking Changes
19
+
20
+ > [!WARNING]
21
+ > This section contains changes that require migration or manual updates to existing code.
22
+
23
+ ### 1. [Breaking Change Title]
24
+
25
+ **Before:**
26
+ ```typescript
27
+ // Code that no longer works or is deprecated
28
+ ```
29
+
30
+ **After:**
31
+ ```typescript
32
+ // New pattern
33
+ ```
34
+
35
+ ## New Features
36
+
37
+ ### [Feature Name]
38
+
39
+ **File:** `packages/core/src/path/to/file.ts`
40
+
41
+ **Problem:** [What problem does this solve?]
42
+
43
+ **Solution:** [How does it solve it?]
44
+
45
+ ```typescript
46
+ // Example usage
47
+ ```
48
+
49
+ **Benefits:**
50
+ - Benefit 1
51
+ - Benefit 2
52
+
53
+ ## Security Fixes
54
+
55
+ ### [Security Issue Title]
56
+
57
+ **Vulnerability:** [Describe the vulnerability]
58
+
59
+ **Fix:** [Describe the fix]
60
+
61
+ ```typescript
62
+ // Before: vulnerable code behavior
63
+ // After: secure code behavior
64
+ ```
65
+
66
+ ## Performance Improvements
67
+
68
+ ### [Performance Improvement Title]
69
+
70
+ **File:** `packages/core/src/path/to/file.ts`
71
+
72
+ **Problem:** [What was slow/inefficient?]
73
+
74
+ **Solution:** [How was it optimized?]
75
+
76
+ | Scenario | Improvement |
77
+ |----------|-------------|
78
+ | [Use case 1] | [Improvement metric] |
79
+ | [Use case 2] | [Improvement metric] |
80
+
81
+ ## Files Changed
82
+
83
+ ### Core Package (`packages/core`)
84
+
85
+ | File | Changes |
86
+ |------|---------|
87
+ | `src/base/models/base.ts` | [Description of changes] |
88
+ | `src/base/repositories/core/readable.ts` | [Description of changes] |
89
+
90
+ ### Helpers Package (`packages/helpers`)
91
+
92
+ | File | Changes |
93
+ |------|---------|
94
+ | `src/utils/index.ts` | [Description of changes] |
95
+
96
+ ### Examples (`examples/vert`)
97
+
98
+ | File | Changes |
99
+ |------|---------|
100
+ | `src/models/entities/user.model.ts` | [Description of changes] |
101
+
102
+ ## Migration Guide
103
+
104
+ > [!NOTE]
105
+ > Follow these steps if you're upgrading from a previous version.
106
+
107
+ ### Step 1: [Action Name]
108
+
109
+ [Instructions]
110
+
111
+ ```typescript
112
+ // Example of the change to apply
113
+ ```
114
+
115
+ ### Step 2: [Action Name]
116
+
117
+ [Instructions]
118
+
119
+ ## No Breaking Changes
120
+
121
+ [Use this section instead of "Breaking Changes" and "Migration Guide" if there are no breaking changes]
122
+
123
+ All changes are internal optimizations. No API changes or migration required.
@@ -264,5 +264,3 @@ export class PageController extends BaseController {
264
264
  }
265
265
  }
266
266
  ```
267
-
268
- ```
@@ -90,7 +90,7 @@ export class ConfigurationController extends _Controller {
90
90
  // an instance of ConfigurationRepository here.
91
91
  @inject({
92
92
  key: BindingKeys.build({
93
- namespace: BindingNames...REPOSITORY,
93
+ namespace: BindingNamespaces.REPOSITORY,
94
94
  key: ConfigurationRepository.name,
95
95
  }),
96
96
  })
@@ -103,7 +103,7 @@ export class ConfigurationController extends _Controller {
103
103
 
104
104
  ## 3. Component-Based Modularity
105
105
 
106
- Components bundle related features into self-contained, pluggable modules.
106
+ Components bundle a group of related, reusable, and pluggable features into self-contained modules. A single component can encapsulate multiple providers, services, controllers, and repositories, essentially functioning as a mini-application that can be easily "plugged in" to any Ignis project.
107
107
 
108
108
  **Built-in Components:**
109
109
  - `AuthenticateComponent` - JWT authentication
@@ -66,6 +66,8 @@ export class Application extends BaseApplication {
66
66
 
67
67
  - **Bad:**
68
68
  ```typescript
69
+ import { ApplicationError, getError } from '@venizia/ignis';
70
+
69
71
  // In a Controller
70
72
  async createUser(c: Context) {
71
73
  const { name, email, companyName } = c.req.valid('json');
@@ -73,7 +75,7 @@ export class Application extends BaseApplication {
73
75
  // Complex logic inside the controller
74
76
  const existingUser = await this.userRepository.findByEmail(email);
75
77
  if (existingUser) {
76
- throw new ApplicationError({ message: 'Email already exists' });
78
+ throw getError({ message: 'Email already exists' });
77
79
  }
78
80
 
79
81
  const company = await this.companyRepository.findOrCreate(companyName);
@@ -101,7 +103,7 @@ export class Application extends BaseApplication {
101
103
  }
102
104
  ```
103
105
 
104
- ### 4. Missing Environment Variables
106
+ ## 4. Missing Environment Variables
105
107
 
106
108
  **Pitfall:** The application fails to start or behaves unexpectedly because required environment variables are not defined in your `.env` file. The framework validates variables prefixed with `APP_ENV_` by default.
107
109
 
@@ -121,7 +123,7 @@ APP_ENV_POSTGRES_PASSWORD=password
121
123
  APP_ENV_POSTGRES_DATABASE=db
122
124
  ```
123
125
 
124
- ### 5. Not Using `as const` for Route Definitions
126
+ ## 5. Not Using `as const` for Route Definitions
125
127
 
126
128
  **Pitfall:** When using the decorator-based routing with a shared `ROUTE_CONFIGS` object, you forget to add `as const` to the object definition. TypeScript will infer the types too broadly, and you will lose the benefits of type-safe contexts (`TRouteContext`).
127
129
 
@@ -56,6 +56,7 @@ The project uses a Makefile for common development tasks:
56
56
  **Individual package builds:**
57
57
  ```bash
58
58
  make core # Build @venizia/ignis (after dependencies)
59
+ make boot # Build @venizia/ignis-boot
59
60
  make helpers # Build @venizia/ignis-helpers
60
61
  make inversion # Build @venizia/ignis-inversion
61
62
  make dev-configs # Build @venizia/dev-configs
@@ -65,6 +66,7 @@ make docs # Build documentation
65
66
  **Force update individual packages:**
66
67
  ```bash
67
68
  make update-core
69
+ make update-boot
68
70
  make update-helpers
69
71
  make update-inversion
70
72
  make update-dev-configs
@@ -6,26 +6,24 @@ Ignis streamlines data modeling with Drizzle ORM by providing powerful helpers a
6
6
 
7
7
  All entity models should extend `BaseEntity`. This provides integration with the framework's repository layer and automatic schema generation support.
8
8
 
9
- **Example (`src/models/entities/configuration.model.ts`):**
9
+ The recommended pattern is to define the schema and relations as **static properties** on the class. This keeps the definition self-contained and enables powerful type inference.
10
+
11
+ **Example (`src/models/entities/user.model.ts`):**
10
12
 
11
13
  ```typescript
12
- import {
13
- BaseEntity,
14
- model,
15
- TTableObject,
16
- } from '@venizia/ignis';
17
- import { configurationTable, configurationRelations } from './schema'; // Your Drizzle schema
14
+ import { BaseEntity, extraUserColumns, generateIdColumnDefs, model } from '@venizia/ignis';
15
+ import { pgTable } from 'drizzle-orm/pg-core';
18
16
 
19
- // Define types for TypeScript inference
20
- export type TConfigurationSchema = typeof configurationTable;
21
- export type TConfiguration = TTableObject<TConfigurationSchema>;
17
+ @model({ type: 'entity' })
18
+ export class User extends BaseEntity<typeof User.schema> {
19
+ // 1. Define schema as a static property
20
+ static override schema = pgTable('User', {
21
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
22
+ ...extraUserColumns({ idType: 'string' }),
23
+ });
22
24
 
23
- @model({ type: 'entity', skipMigrate: false })
24
- export class Configuration extends BaseEntity<typeof Configuration.schema> {
25
- // Use static properties (recommended pattern)
26
- static override schema = configurationTable;
27
- static override relations = () => configurationRelations.definitions;
28
- static override TABLE_NAME = 'Configuration';
25
+ // 2. Define relations as a static method (return empty array if none)
26
+ static override relations = () => [];
29
27
  }
30
28
  ```
31
29
 
@@ -41,7 +39,7 @@ Instead of manually defining common columns like primary keys, timestamps, or au
41
39
  | `generateTzColumnDefs` | Adds timestamps | `createdAt`, `modifiedAt` (auto-updating) |
42
40
  | `generateUserAuditColumnDefs` | Adds audit fields | `createdBy`, `modifiedBy` |
43
41
  | `generateDataTypeColumnDefs` | Adds generic value fields | `nValue` (number), `tValue` (text), `jValue` (json), etc. |
44
- | `generatePrincipalColumnDefs` | Adds polymorphic relation fields | `principalType`, `principalId` |
42
+ | `extraUserColumns` | Comprehensive user fields | Combines audit, timestamps, status, and type fields |
45
43
 
46
44
  **Usage Example:**
47
45
 
@@ -82,39 +80,98 @@ export const configurationTable = pgTable(
82
80
 
83
81
  ## 3. Defining Relations
84
82
 
85
- Use the `createRelations` helper to define relationships cleanly. This abstracts the Drizzle `relations` function and makes it easy to bind to repositories.
83
+ Relations are defined using the `TRelationConfig` structure within the static `relations` method of your model.
86
84
 
87
- **Example:**
85
+ **Example (`src/models/entities/configuration.model.ts`):**
88
86
 
89
87
  ```typescript
90
- import { createRelations, RelationTypes } from '@venizia/ignis';
91
- import { userTable } from './user.model';
88
+ import {
89
+ BaseEntity,
90
+ model,
91
+ RelationTypes,
92
+ TRelationConfig,
93
+ } from '@venizia/ignis';
94
+ import { User } from './user.model';
95
+
96
+ @model({ type: 'entity' })
97
+ export class Configuration extends BaseEntity<typeof Configuration.schema> {
98
+ // ... schema definition ...
92
99
 
93
- export const configurationRelations = createRelations({
94
- source: configurationTable,
95
- relations: [
100
+ // Define relations
101
+ static override relations = (): TRelationConfig[] => [
96
102
  {
97
103
  name: 'creator',
98
104
  type: RelationTypes.ONE,
99
- schema: userTable,
105
+ schema: User.schema,
100
106
  metadata: {
101
- fields: [configurationTable.createdBy],
102
- references: [userTable.id],
107
+ fields: [Configuration.schema.createdBy],
108
+ references: [User.schema.id],
103
109
  },
104
110
  },
105
- ],
106
- });
111
+ ];
112
+ }
107
113
  ```
108
114
 
109
- This configuration is automatically used when you define your Repository with the `@repository` decorator:
115
+ ## 4. Repositories and Auto-Discovery
116
+
117
+ Ignis simplifies the connection between models, repositories, and datasources.
118
+
119
+ ### DataSource Auto-Discovery
120
+
121
+ DataSources automatically discover their schema from the repositories that bind to them. You **do not** need to manually register schemas in the DataSource constructor.
110
122
 
111
123
  ```typescript
112
- import { PostgresDataSource } from '@/datasources';
124
+ // src/datasources/postgres.datasource.ts
125
+ @datasource({ driver: 'node-postgres' })
126
+ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
127
+ constructor() {
128
+ super({
129
+ name: PostgresDataSource.name,
130
+ config: { /* connection config */ },
131
+ // NO schema property needed - auto-discovered!
132
+ });
133
+ }
134
+
135
+ override configure(): ValueOrPromise<void> {
136
+ // This method automatically collects all schemas from bound repositories
137
+ const schema = this.getSchema();
138
+ this.connector = drizzle({ client: new Pool(this.settings), schema });
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### Repository Binding
144
+
145
+ Repositories use the `@repository` decorator to bind a **Model** to a **DataSource**. This binding is what powers the auto-discovery mechanism.
146
+
147
+ **Pattern 1: Zero Boilerplate (Recommended)**
113
148
 
114
- // Both 'model' and 'dataSource' are required for schema auto-discovery
149
+ For most repositories, you don't need a constructor. The DataSource is automatically injected.
150
+
151
+ ```typescript
115
152
  @repository({ model: Configuration, dataSource: PostgresDataSource })
116
153
  export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {
117
- // No constructor needed! DataSource and relations are auto-resolved
118
- // from the @repository decorator and entity's static properties
154
+ // No constructor needed!
119
155
  }
120
156
  ```
157
+
158
+ **Pattern 2: Explicit Injection (Advanced)**
159
+
160
+ If you need to perform custom initialization or inject additional dependencies, you can define a constructor. **Important:** The first parameter must be the DataSource.
161
+
162
+ ```typescript
163
+ @repository({ model: User, dataSource: PostgresDataSource })
164
+ export class UserRepository extends ReadableRepository<typeof User.schema> {
165
+ constructor(
166
+ @inject({ key: 'datasources.PostgresDataSource' })
167
+ dataSource: PostgresDataSource,
168
+ ) {
169
+ super(dataSource);
170
+ }
171
+
172
+ // Custom methods
173
+ async findByRealm(realm: string) {
174
+ return this.findOne({ filter: { where: { realm } } });
175
+ }
176
+ }
177
+ ```
@@ -72,9 +72,11 @@ const SecureRoute = {
72
72
 
73
73
  **Access user in protected routes:**
74
74
  ```typescript
75
+ import { Authentication, IJWTTokenPayload, ApplicationError, getError } from '@venizia/ignis';
76
+
75
77
  const user = c.get(Authentication.CURRENT_USER) as IJWTTokenPayload;
76
78
  if (!user.roles.includes('admin')) {
77
- throw new ApplicationError({ statusCode: 403, message: 'Forbidden' });
79
+ throw getError({ statusCode: 403, message: 'Forbidden' });
78
80
  }
79
81
  ```
80
82
 
@@ -645,7 +645,7 @@ For complex validation or business rules, create a Service layer:
645
645
 
646
646
  ```typescript
647
647
  // src/services/todo.service.ts
648
- import { BaseService, inject } from '@venizia/ignis';
648
+ import { BaseService, inject, getError } from '@venizia/ignis';
649
649
  import { TodoRepository } from '@/repositories/todo.repository';
650
650
 
651
651
  export class TodoService extends BaseService {
@@ -659,7 +659,7 @@ export class TodoService extends BaseService {
659
659
  async createTodo(data: any) {
660
660
  // Business logic validation
661
661
  if (data.title.length < 3) {
662
- throw new Error('Title too short');
662
+ throw getError({ message: 'Title too short' });
663
663
  }
664
664
 
665
665
  // Check for duplicates
@@ -667,7 +667,7 @@ export class TodoService extends BaseService {
667
667
  filter: { where: { title: data.title } },
668
668
  });
669
669
  if (existing) {
670
- throw new Error('Todo already exists');
670
+ throw getError({ message: 'Todo already exists' });
671
671
  }
672
672
 
673
673
  return this.todoRepository.create({ data });