@venizia/ignis-docs 0.0.1 → 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.
@@ -40,17 +40,26 @@ Prevent blocking the event loop with Worker Threads:
40
40
  |-----------|---------|--------|
41
41
  | **Select specific fields** | `fields: { id: true, name: true }` | Reduce data transfer |
42
42
  | **Use indexes** | Create indexes on WHERE/JOIN columns | 10-100x faster queries |
43
+ | **Mandatory Limit** | `limit: 20` | Prevent fetching massive datasets |
43
44
  | **Paginate results** | `limit: 20, offset: 0` | Prevent memory overflow |
44
45
  | **Eager load relations** | `include: [{ relation: 'creator' }]` | Solve N+1 problem |
45
46
 
47
+ > [!TIP]
48
+ > **Avoid Deep Nesting:** While Ignis supports deeply nested `include` filters, each level adds significant overhead to query construction and result mapping. We strongly recommend a **maximum of 2 levels** (e.g., `User -> Orders -> Items`). For more complex data fetching, consider separate queries.
49
+
46
50
  **Example:**
47
51
  ```typescript
48
52
  await userRepository.find({
49
53
  filter: {
50
54
  fields: { id: true, name: true, email: true }, // ✅ Specific fields
51
55
  where: { status: 'ACTIVE' },
52
- limit: 20, // ✅ Pagination
53
- include: [{ relation: 'profile' }], // ✅ Eager load
56
+ limit: 20, // ✅ Mandatory limit
57
+ include: [{
58
+ relation: 'orders',
59
+ scope: {
60
+ include: [{ relation: 'items' }] // ✅ Level 2 (Recommended limit)
61
+ }
62
+ }],
54
63
  },
55
64
  });
56
65
  ```
@@ -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 technical details.
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
- `Ignis` comes with several built-in components, which you can explore in the [**Components Reference**](../../references/components/) section:
16
+ ## Built-in Components
17
17
 
18
- - **`AuthenticateComponent`**: Sets up JWT-based authentication.
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
- ## Creating a Component
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
- To create a new component, extend the `BaseComponent` class. The constructor is the ideal place to define any **default bindings** that the component provides.
29
+ ## Creating a Simple Component
27
30
 
28
31
  ```typescript
29
- import { BaseApplication, BaseComponent, inject, CoreBindings, ValueOrPromise, Binding, BindingScopes } from '@venizia/ignis';
32
+ import { BaseApplication, BaseComponent, inject, CoreBindings, ValueOrPromise, Binding } from '@venizia/ignis';
30
33
 
31
- // An example service that the component will provide
32
- class MyFeatureService {
33
- // ... business logic
34
+ // Define a service
35
+ class NotificationService {
36
+ send(message: string) { /* ... */ }
34
37
  }
35
38
 
36
- // A controller that depends on the service
37
- @controller({ path: '/my-feature' })
38
- class MyFeatureController {
39
+ // Define a controller
40
+ @controller({ path: '/notifications' })
41
+ class NotificationController extends BaseController {
39
42
  constructor(
40
- @inject({ key: 'services.MyFeatureService' })
41
- private myFeatureService: MyFeatureService
42
- ) { /* ... */ }
43
+ @inject({ key: 'services.NotificationService' })
44
+ private notificationService: NotificationService
45
+ ) {
46
+ super({ scope: NotificationController.name });
47
+ }
43
48
  }
44
49
 
45
- export class MyFeatureComponent extends BaseComponent {
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: MyFeatureComponent.name,
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.MyFeatureService': Binding.bind({ key: 'services.MyFeatureService' })
57
- .toClass(MyFeatureService)
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
- // This is where you configure logic that USES the bindings.
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()` | The component is created by the DI container. This is where you call `super()` and define the component's `bindings`. If `initDefault` is enabled, these bindings are registered with the application container immediately. |
79
- | **2. Configuration**| `binding()` | Called by the application during the `registerComponents` startup phase. This is where you should set up resources (like controllers) that *depend* on the bindings you defined in the constructor. |
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
- To activate a component, you must register it with the application instance, usually in the `preConfigure` method of your `Application` class.
81
+ Register components in your application's `preConfigure` method:
84
82
 
85
83
  ```typescript
86
- // in src/application.ts
87
- import { MyFeatureComponent } from './components/my-feature.component';
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
- // ... inside your Application class
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.component(MyFeatureComponent);
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
- When the application starts, it will find the `MyFeatureComponent` binding, instantiate it, and then call its `binding()` method at the appropriate time. This modular approach keeps your main application class clean and makes it easy to toggle features on and off.
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 (Planned)
453
-
454
- > **Status:** Planned - Not yet implemented. See [full plan](../../changelogs/planned-transaction-support).
455
-
456
- ### The Problem
437
+ ## 5. Transactions
457
438
 
458
- Drizzle's callback-based transactions make it hard to pass transactions across services:
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
- ### Planned Solution
441
+ ### Using Transactions
468
442
 
469
- Loopback 4-style explicit transaction objects that can be passed anywhere:
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 from repository
446
+ // 1. Start a transaction
473
447
  const tx = await userRepo.beginTransaction({
474
- isolationLevel: 'SERIALIZABLE' // Optional, defaults to 'READ COMMITTED'
448
+ isolationLevel: 'SERIALIZABLE' // Optional, defaults to 'READ COMMITTED'
475
449
  });
476
450
 
477
451
  try {
478
- // Pass tx to multiple services/repositories
479
- const user = await userRepo.create({ data, options: { transaction: tx } });
480
- await profileRepo.create({ data: { userId: user.id }, options: { transaction: tx } });
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. Sees only committed data | General use |
495
- | `REPEATABLE READ` | Snapshot from transaction start | Reports, consistent reads |
496
- | `SERIALIZABLE` | Full isolation, may throw errors | Financial, critical data |
497
-
498
- ### Benefits
499
-
500
- | Aspect | Current (Callback) | Planned (Pass-through) |
501
- |--------|-------------------|------------------------|
502
- | Service composition | Hard | Easy - pass tx anywhere |
503
- | Separation of concerns | Services coupled | Services independent |
504
- | Testing | Complex mocking | Easy to mock tx |
505
- | Code organization | Nested callbacks | Flat, sequential |
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