@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.
- package/LICENSE.md +1 -0
- package/package.json +2 -2
- package/wiki/changelogs/{v0.0.1-7-initial-architecture.md → 2025-12-16-initial-architecture.md} +20 -12
- package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +300 -0
- package/wiki/changelogs/2025-12-17-refactor.md +80 -12
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +28 -90
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +101 -297
- package/wiki/changelogs/index.md +19 -8
- package/wiki/changelogs/planned-transaction-support.md +216 -0
- package/wiki/changelogs/template.md +123 -0
- package/wiki/get-started/best-practices/api-usage-examples.md +0 -2
- package/wiki/get-started/best-practices/architectural-patterns.md +2 -2
- package/wiki/get-started/best-practices/common-pitfalls.md +5 -3
- package/wiki/get-started/best-practices/contribution-workflow.md +2 -0
- package/wiki/get-started/best-practices/data-modeling.md +91 -34
- package/wiki/get-started/best-practices/security-guidelines.md +3 -1
- package/wiki/get-started/building-a-crud-api.md +3 -3
- package/wiki/get-started/core-concepts/application.md +72 -3
- package/wiki/get-started/core-concepts/bootstrapping.md +566 -0
- package/wiki/get-started/core-concepts/components.md +4 -2
- package/wiki/get-started/core-concepts/persistent.md +350 -378
- package/wiki/get-started/core-concepts/services.md +21 -27
- package/wiki/references/base/bootstrapping.md +789 -0
- package/wiki/references/base/components.md +1 -1
- package/wiki/references/base/dependency-injection.md +95 -2
- package/wiki/references/base/services.md +2 -2
- package/wiki/references/components/authentication.md +4 -3
- package/wiki/references/components/index.md +1 -1
- package/wiki/references/helpers/error.md +2 -2
- package/wiki/references/src-details/boot.md +379 -0
- package/wiki/references/src-details/core.md +2 -2
- 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.
|
|
@@ -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:
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
20
|
-
export
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
| `
|
|
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
|
-
|
|
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 {
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
relations: [
|
|
100
|
+
// Define relations
|
|
101
|
+
static override relations = (): TRelationConfig[] => [
|
|
96
102
|
{
|
|
97
103
|
name: 'creator',
|
|
98
104
|
type: RelationTypes.ONE,
|
|
99
|
-
schema:
|
|
105
|
+
schema: User.schema,
|
|
100
106
|
metadata: {
|
|
101
|
-
fields: [
|
|
102
|
-
references: [
|
|
107
|
+
fields: [Configuration.schema.createdBy],
|
|
108
|
+
references: [User.schema.id],
|
|
103
109
|
},
|
|
104
110
|
},
|
|
105
|
-
]
|
|
106
|
-
}
|
|
111
|
+
];
|
|
112
|
+
}
|
|
107
113
|
```
|
|
108
114
|
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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!
|
|
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
|
|
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
|
|
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
|
|
670
|
+
throw getError({ message: 'Todo already exists' });
|
|
671
671
|
}
|
|
672
672
|
|
|
673
673
|
return this.todoRepository.create({ data });
|