@venizia/ignis-docs 0.0.2 → 0.0.3
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/package.json +2 -2
- package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +86 -0
- package/wiki/changelogs/2025-12-26-transaction-support.md +57 -0
- package/wiki/changelogs/index.md +2 -1
- package/wiki/changelogs/planned-schema-migrator.md +2 -2
- package/wiki/get-started/best-practices/code-style-standards.md +298 -222
- package/wiki/get-started/best-practices/performance-optimization.md +11 -2
- package/wiki/get-started/core-concepts/components.md +59 -42
- package/wiki/get-started/core-concepts/persistent.md +43 -47
- package/wiki/references/base/components.md +515 -31
- package/wiki/references/base/controllers.md +85 -18
- package/wiki/references/base/datasources.md +78 -5
- package/wiki/references/base/repositories.md +92 -6
- package/wiki/references/helpers/index.md +1 -0
- package/wiki/references/helpers/types.md +151 -0
- package/wiki/changelogs/planned-transaction-support.md +0 -216
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Components are reusable, pluggable modules that encapsulate a group of related features. A component acts as a powerful container for various resources—including providers, services, controllers, repositories, and even entire mini-applications—making it easy to share and integrate complex functionality across projects.
|
|
4
4
|
|
|
5
|
-
> **Deep Dive:** See [Components Reference](../../references/base/components.md) for
|
|
5
|
+
> **Deep Dive:** See [Components Reference](../../references/base/components.md) for detailed implementation patterns, directory structure, and best practices.
|
|
6
6
|
|
|
7
7
|
## What is a Component?
|
|
8
8
|
|
|
@@ -13,86 +13,103 @@ A component is a class that extends `BaseComponent` and is responsible for:
|
|
|
13
13
|
|
|
14
14
|
A single component can bundle everything needed for a specific domain—for example, an "AuthComponent" might include multiple services for token management, repositories for user data, and controllers for login/signup endpoints, essentially functioning as a plug-and-play mini-application.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
## Built-in Components
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
- **`SwaggerComponent`**: Generates interactive OpenAPI documentation.
|
|
20
|
-
- **`HealthCheckComponent`**: Adds a health check endpoint.
|
|
21
|
-
- **`RequestTrackerComponent`**: Adds request logging and tracing.
|
|
22
|
-
- **`SocketIOComponent`**: Integrates Socket.IO for real-time communication.
|
|
18
|
+
`Ignis` comes with several built-in components, which you can explore in the [**Components Reference**](../../references/components/) section:
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
| Component | Description |
|
|
21
|
+
|-----------|-------------|
|
|
22
|
+
| `AuthenticateComponent` | JWT-based authentication with token services |
|
|
23
|
+
| `SwaggerComponent` | Interactive OpenAPI documentation |
|
|
24
|
+
| `HealthCheckComponent` | Health check endpoints |
|
|
25
|
+
| `RequestTrackerComponent` | Request logging and tracing |
|
|
26
|
+
| `SocketIOComponent` | Real-time communication with Socket.IO |
|
|
27
|
+
| `StaticAssetComponent` | File upload and storage management |
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
## Creating a Simple Component
|
|
27
30
|
|
|
28
31
|
```typescript
|
|
29
|
-
import { BaseApplication, BaseComponent, inject, CoreBindings, ValueOrPromise, Binding
|
|
32
|
+
import { BaseApplication, BaseComponent, inject, CoreBindings, ValueOrPromise, Binding } from '@venizia/ignis';
|
|
30
33
|
|
|
31
|
-
//
|
|
32
|
-
class
|
|
33
|
-
|
|
34
|
+
// Define a service
|
|
35
|
+
class NotificationService {
|
|
36
|
+
send(message: string) { /* ... */ }
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
//
|
|
37
|
-
@controller({ path: '/
|
|
38
|
-
class
|
|
39
|
+
// Define a controller
|
|
40
|
+
@controller({ path: '/notifications' })
|
|
41
|
+
class NotificationController extends BaseController {
|
|
39
42
|
constructor(
|
|
40
|
-
@inject({ key: 'services.
|
|
41
|
-
private
|
|
42
|
-
) {
|
|
43
|
+
@inject({ key: 'services.NotificationService' })
|
|
44
|
+
private notificationService: NotificationService
|
|
45
|
+
) {
|
|
46
|
+
super({ scope: NotificationController.name });
|
|
47
|
+
}
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
// Create the component
|
|
51
|
+
export class NotificationComponent extends BaseComponent {
|
|
46
52
|
constructor(
|
|
47
53
|
@inject({ key: CoreBindings.APPLICATION_INSTANCE })
|
|
48
54
|
private application: BaseApplication,
|
|
49
55
|
) {
|
|
50
56
|
super({
|
|
51
|
-
scope:
|
|
52
|
-
// Enable default bindings for this component
|
|
57
|
+
scope: NotificationComponent.name,
|
|
53
58
|
initDefault: { enable: true, container: application },
|
|
54
|
-
// Define the default bindings this component provides
|
|
55
59
|
bindings: {
|
|
56
|
-
'services.
|
|
57
|
-
.toClass(
|
|
58
|
-
.setScope(BindingScopes.SINGLETON), // Make it a singleton
|
|
60
|
+
'services.NotificationService': Binding.bind({ key: 'services.NotificationService' })
|
|
61
|
+
.toClass(NotificationService),
|
|
59
62
|
},
|
|
60
63
|
});
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
override binding(): ValueOrPromise<void> {
|
|
64
|
-
|
|
65
|
-
// Here, we register a controller that depends on the service
|
|
66
|
-
// we just bound in the constructor.
|
|
67
|
-
this.application.controller(MyFeatureController);
|
|
67
|
+
this.application.controller(NotificationController);
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
## Component Lifecycle
|
|
73
73
|
|
|
74
|
-
Components have a simple, two-stage lifecycle managed by the application.
|
|
75
|
-
|
|
76
74
|
| Stage | Method | Description |
|
|
77
75
|
| :--- | :--- | :--- |
|
|
78
|
-
| **1. Instantiation** | `constructor()` |
|
|
79
|
-
| **2. Configuration
|
|
76
|
+
| **1. Instantiation** | `constructor()` | Component is created. Define default `bindings` here. |
|
|
77
|
+
| **2. Configuration** | `binding()` | Called during startup. Register controllers and resources here. |
|
|
80
78
|
|
|
81
79
|
## Registering a Component
|
|
82
80
|
|
|
83
|
-
|
|
81
|
+
Register components in your application's `preConfigure` method:
|
|
84
82
|
|
|
85
83
|
```typescript
|
|
86
|
-
//
|
|
87
|
-
|
|
84
|
+
// src/application.ts
|
|
85
|
+
export class Application extends BaseApplication {
|
|
86
|
+
preConfigure(): ValueOrPromise<void> {
|
|
87
|
+
this.component(HealthCheckComponent);
|
|
88
|
+
this.component(SwaggerComponent);
|
|
89
|
+
this.component(NotificationComponent);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
88
93
|
|
|
89
|
-
|
|
94
|
+
## Customizing Component Options
|
|
90
95
|
|
|
96
|
+
Most components accept configuration options. Override them before registration:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// src/application.ts
|
|
100
|
+
export class Application extends BaseApplication {
|
|
91
101
|
preConfigure(): ValueOrPromise<void> {
|
|
92
|
-
//
|
|
93
|
-
this.
|
|
94
|
-
|
|
102
|
+
// Override options BEFORE registering component
|
|
103
|
+
this.bind<IHealthCheckOptions>({ key: HealthCheckBindingKeys.HEALTH_CHECK_OPTIONS })
|
|
104
|
+
.toValue({ restOptions: { path: '/api/health' } });
|
|
105
|
+
|
|
106
|
+
this.component(HealthCheckComponent);
|
|
95
107
|
}
|
|
108
|
+
}
|
|
96
109
|
```
|
|
97
110
|
|
|
98
|
-
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
> **Next Steps:**
|
|
114
|
+
> - [Components Reference](../../references/base/components.md) - Directory structure, keys, types, constants patterns
|
|
115
|
+
> - [Built-in Components](../../references/components/) - Detailed documentation for each component
|
|
@@ -418,21 +418,6 @@ export class Application extends BaseApplication {
|
|
|
418
418
|
|
|
419
419
|
Ignis automatically optimizes "flat" queries (no relations, no field selection) by using Drizzle's Core API. This provides **~15-20% faster** queries for simple reads.
|
|
420
420
|
|
|
421
|
-
### Transactions (Current)
|
|
422
|
-
|
|
423
|
-
Currently, use Drizzle's callback-based `connector.transaction` for atomic operations:
|
|
424
|
-
|
|
425
|
-
```typescript
|
|
426
|
-
const ds = this.get<PostgresDataSource>({ key: 'datasources.PostgresDataSource' });
|
|
427
|
-
|
|
428
|
-
await ds.connector.transaction(async (tx) => {
|
|
429
|
-
await tx.insert(User.schema).values({ /* ... */ });
|
|
430
|
-
await tx.insert(Configuration.schema).values({ /* ... */ });
|
|
431
|
-
});
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
> **Note:** This callback-based approach requires all transaction logic to be in one callback. See [Section 5](#5-transactions-planned) for the planned improvement.
|
|
435
|
-
|
|
436
421
|
### Modular Persistence with Components
|
|
437
422
|
|
|
438
423
|
Bundle related persistence resources into Components for better organization:
|
|
@@ -449,39 +434,41 @@ export class UserManagementComponent extends BaseComponent {
|
|
|
449
434
|
|
|
450
435
|
---
|
|
451
436
|
|
|
452
|
-
## 5. Transactions
|
|
453
|
-
|
|
454
|
-
> **Status:** Planned - Not yet implemented. See [full plan](../../changelogs/planned-transaction-support).
|
|
455
|
-
|
|
456
|
-
### The Problem
|
|
437
|
+
## 5. Transactions
|
|
457
438
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
```typescript
|
|
461
|
-
// Current: Everything must be inside the callback
|
|
462
|
-
await ds.connector.transaction(async (tx) => {
|
|
463
|
-
// Can't easily call other services with this tx
|
|
464
|
-
});
|
|
465
|
-
```
|
|
439
|
+
Ignis supports explicit transaction objects that can be passed across multiple services and repositories, allowing for complex, multi-step business logic to be atomic.
|
|
466
440
|
|
|
467
|
-
###
|
|
441
|
+
### Using Transactions
|
|
468
442
|
|
|
469
|
-
|
|
443
|
+
To use transactions, start one from a repository or datasource, and then pass it to subsequent operations via the `options` parameter.
|
|
470
444
|
|
|
471
445
|
```typescript
|
|
472
|
-
// Start transaction
|
|
446
|
+
// 1. Start a transaction
|
|
473
447
|
const tx = await userRepo.beginTransaction({
|
|
474
|
-
isolationLevel: 'SERIALIZABLE'
|
|
448
|
+
isolationLevel: 'SERIALIZABLE' // Optional, defaults to 'READ COMMITTED'
|
|
475
449
|
});
|
|
476
450
|
|
|
477
451
|
try {
|
|
478
|
-
// Pass
|
|
479
|
-
|
|
480
|
-
await
|
|
452
|
+
// 2. Pass transaction to operations
|
|
453
|
+
// Create user
|
|
454
|
+
const user = await userRepo.create({
|
|
455
|
+
data: userData,
|
|
456
|
+
options: { transaction: tx }
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// Create profile (using same transaction)
|
|
460
|
+
await profileRepo.create({
|
|
461
|
+
data: { userId: user.id, ...profileData },
|
|
462
|
+
options: { transaction: tx }
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Call a service method (passing the transaction)
|
|
481
466
|
await orderService.createInitialOrder(user.id, { transaction: tx });
|
|
482
467
|
|
|
468
|
+
// 3. Commit the transaction
|
|
483
469
|
await tx.commit();
|
|
484
470
|
} catch (err) {
|
|
471
|
+
// 4. Rollback on error
|
|
485
472
|
await tx.rollback();
|
|
486
473
|
throw err;
|
|
487
474
|
}
|
|
@@ -489,20 +476,29 @@ try {
|
|
|
489
476
|
|
|
490
477
|
### Isolation Levels
|
|
491
478
|
|
|
479
|
+
Ignis supports standard PostgreSQL isolation levels:
|
|
480
|
+
|
|
492
481
|
| Level | Description | Use Case |
|
|
493
482
|
|-------|-------------|----------|
|
|
494
|
-
| `READ COMMITTED` | Default
|
|
495
|
-
| `REPEATABLE READ` |
|
|
496
|
-
| `SERIALIZABLE` |
|
|
497
|
-
|
|
498
|
-
###
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
483
|
+
| `READ COMMITTED` | (Default) Queries see only data committed before the query began. | General use, prevents dirty reads. |
|
|
484
|
+
| `REPEATABLE READ` | Queries see a snapshot as of the start of the transaction. | Reports, consistent reads across multiple queries. |
|
|
485
|
+
| `SERIALIZABLE` | Strictest level. Emulates serial execution. | Financial transactions, critical data integrity. |
|
|
486
|
+
|
|
487
|
+
### Best Practices
|
|
488
|
+
|
|
489
|
+
1. **Always use `try...catch...finally`**: Ensure `rollback()` is called on error to release the connection.
|
|
490
|
+
2. **Keep it short**: Long-running transactions hold database locks and connections.
|
|
491
|
+
3. **Pass explicit options**: When calling other services inside a transaction, ensure they accept and use the `transaction` option.
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
// Service method supporting transactions
|
|
495
|
+
async createInitialOrder(userId: string, opts?: { transaction?: ITransaction }) {
|
|
496
|
+
return this.orderRepository.create({
|
|
497
|
+
data: { userId, status: 'PENDING' },
|
|
498
|
+
options: { transaction: opts?.transaction } // Forward the transaction
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
```
|
|
506
502
|
|
|
507
503
|
---
|
|
508
504
|
|