@venizia/ignis-docs 0.0.2 → 0.0.4-0
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/README.md +1 -1
- package/package.json +4 -2
- package/wiki/best-practices/api-usage-examples.md +591 -0
- package/wiki/best-practices/architectural-patterns.md +415 -0
- package/wiki/best-practices/architecture-decisions.md +488 -0
- package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +647 -182
- package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
- package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
- package/wiki/best-practices/data-modeling.md +376 -0
- package/wiki/best-practices/deployment-strategies.md +698 -0
- package/wiki/best-practices/index.md +27 -0
- package/wiki/best-practices/performance-optimization.md +196 -0
- package/wiki/best-practices/security-guidelines.md +218 -0
- package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
- package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
- package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
- package/wiki/changelogs/2025-12-17-refactor.md +1 -1
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
- 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/2025-12-29-dynamic-binding-registration.md +104 -0
- package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
- package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
- package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
- package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
- package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
- package/wiki/changelogs/index.md +8 -1
- package/wiki/changelogs/planned-schema-migrator.md +2 -10
- package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
- package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
- package/wiki/guides/core-concepts/components-guide.md +509 -0
- package/wiki/guides/core-concepts/components.md +122 -0
- package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
- package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
- package/wiki/guides/core-concepts/persistent/index.md +119 -0
- package/wiki/guides/core-concepts/persistent/models.md +241 -0
- package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
- package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
- package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
- package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
- package/wiki/guides/get-started/philosophy.md +682 -0
- package/wiki/guides/get-started/setup.md +157 -0
- package/wiki/guides/index.md +89 -0
- package/wiki/guides/reference/glossary.md +243 -0
- package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
- package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
- package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
- package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
- package/wiki/guides/tutorials/realtime-chat.md +1261 -0
- package/wiki/guides/tutorials/testing.md +723 -0
- package/wiki/index.md +176 -37
- package/wiki/references/base/application.md +27 -0
- package/wiki/references/base/bootstrapping.md +30 -26
- package/wiki/references/base/components.md +532 -31
- package/wiki/references/base/controllers.md +136 -38
- package/wiki/references/base/datasources.md +108 -5
- package/wiki/references/base/dependency-injection.md +39 -3
- package/wiki/references/base/filter-system/application-usage.md +224 -0
- package/wiki/references/base/filter-system/array-operators.md +132 -0
- package/wiki/references/base/filter-system/comparison-operators.md +109 -0
- package/wiki/references/base/filter-system/default-filter.md +428 -0
- package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
- package/wiki/references/base/filter-system/index.md +127 -0
- package/wiki/references/base/filter-system/json-filtering.md +197 -0
- package/wiki/references/base/filter-system/list-operators.md +71 -0
- package/wiki/references/base/filter-system/logical-operators.md +156 -0
- package/wiki/references/base/filter-system/null-operators.md +58 -0
- package/wiki/references/base/filter-system/pattern-matching.md +108 -0
- package/wiki/references/base/filter-system/quick-reference.md +431 -0
- package/wiki/references/base/filter-system/range-operators.md +63 -0
- package/wiki/references/base/filter-system/tips.md +190 -0
- package/wiki/references/base/filter-system/use-cases.md +452 -0
- package/wiki/references/base/index.md +90 -0
- package/wiki/references/base/middlewares.md +602 -0
- package/wiki/references/base/models.md +215 -23
- package/wiki/references/base/providers.md +732 -0
- package/wiki/references/base/repositories/advanced.md +555 -0
- package/wiki/references/base/repositories/index.md +228 -0
- package/wiki/references/base/repositories/mixins.md +331 -0
- package/wiki/references/base/repositories/relations.md +486 -0
- package/wiki/references/base/repositories.md +40 -549
- package/wiki/references/base/services.md +28 -4
- package/wiki/references/components/authentication.md +22 -2
- package/wiki/references/components/health-check.md +12 -0
- package/wiki/references/components/index.md +23 -0
- package/wiki/references/components/mail.md +687 -0
- package/wiki/references/components/request-tracker.md +16 -0
- package/wiki/references/components/socket-io.md +18 -0
- package/wiki/references/components/static-asset.md +14 -26
- package/wiki/references/components/swagger.md +17 -0
- package/wiki/references/configuration/environment-variables.md +427 -0
- package/wiki/references/configuration/index.md +73 -0
- package/wiki/references/helpers/cron.md +14 -0
- package/wiki/references/helpers/crypto.md +15 -0
- package/wiki/references/helpers/env.md +16 -0
- package/wiki/references/helpers/error.md +17 -0
- package/wiki/references/helpers/index.md +15 -0
- package/wiki/references/helpers/inversion.md +24 -4
- package/wiki/references/helpers/logger.md +19 -0
- package/wiki/references/helpers/network.md +11 -0
- package/wiki/references/helpers/queue.md +19 -0
- package/wiki/references/helpers/redis.md +21 -0
- package/wiki/references/helpers/socket-io.md +24 -5
- package/wiki/references/helpers/storage.md +18 -10
- package/wiki/references/helpers/testing.md +18 -0
- package/wiki/references/helpers/types.md +167 -0
- package/wiki/references/helpers/uid.md +167 -0
- package/wiki/references/helpers/worker-thread.md +16 -0
- package/wiki/references/index.md +177 -0
- package/wiki/references/quick-reference.md +634 -0
- package/wiki/references/src-details/boot.md +3 -3
- package/wiki/references/src-details/dev-configs.md +0 -4
- package/wiki/references/src-details/docs.md +2 -2
- package/wiki/references/src-details/index.md +86 -0
- package/wiki/references/src-details/inversion.md +1 -6
- package/wiki/references/src-details/mcp-server.md +3 -15
- package/wiki/references/utilities/index.md +86 -10
- package/wiki/references/utilities/jsx.md +577 -0
- package/wiki/references/utilities/request.md +0 -2
- package/wiki/references/utilities/statuses.md +740 -0
- package/wiki/changelogs/planned-transaction-support.md +0 -216
- package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
- package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
- package/wiki/get-started/best-practices/data-modeling.md +0 -177
- package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
- package/wiki/get-started/best-practices/performance-optimization.md +0 -88
- package/wiki/get-started/best-practices/security-guidelines.md +0 -99
- package/wiki/get-started/core-concepts/components.md +0 -98
- package/wiki/get-started/core-concepts/persistent.md +0 -543
- package/wiki/get-started/index.md +0 -65
- package/wiki/get-started/philosophy.md +0 -296
- package/wiki/get-started/prerequisites.md +0 -113
|
@@ -1,216 +0,0 @@
|
|
|
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 |
|
|
@@ -1,266 +0,0 @@
|
|
|
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 ROUTE_CONFIGS = {
|
|
18
|
-
// ... (other routes)
|
|
19
|
-
['/4']: {
|
|
20
|
-
method: HTTP.Methods.GET,
|
|
21
|
-
path: '/4',
|
|
22
|
-
responses: jsonResponse({
|
|
23
|
-
description: 'Test decorator GET endpoint',
|
|
24
|
-
schema: z.object({ message: z.string(), method: z.string() }),
|
|
25
|
-
}),
|
|
26
|
-
},
|
|
27
|
-
['/5']: {
|
|
28
|
-
method: HTTP.Methods.POST,
|
|
29
|
-
path: '/5',
|
|
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 { ROUTE_CONFIGS } from './definitions';
|
|
58
|
-
|
|
59
|
-
@controller({ path: '/test' })
|
|
60
|
-
export class TestController extends BaseController {
|
|
61
|
-
// ...
|
|
62
|
-
|
|
63
|
-
@get({ configs: ROUTE_CONFIGS['/4'] })
|
|
64
|
-
getWithDecorator(context: TRouteContext<(typeof ROUTE_CONFIGS)['/4']>) {
|
|
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: ROUTE_CONFIGS['/5'] })
|
|
70
|
-
createWithDecorator(context: TRouteContext<(typeof ROUTE_CONFIGS)['/5']>) {
|
|
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 { ROUTE_CONFIGS } 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: ROUTE_CONFIGS['/1'],
|
|
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: ROUTE_CONFIGS['/3'],
|
|
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
|
-
```
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# Architectural Patterns
|
|
2
|
-
|
|
3
|
-
Ignis promotes separation of concerns, dependency injection, and modularity for scalable, maintainable applications.
|
|
4
|
-
|
|
5
|
-
> **Deep Dive:** See [Core Framework Reference](../../references/src-details/core.md) for implementation details.
|
|
6
|
-
|
|
7
|
-
## 1. Layered Architecture
|
|
8
|
-
|
|
9
|
-
Each layer has a single responsibility. Ignis supports **two architectural approaches**:
|
|
10
|
-
|
|
11
|
-
```mermaid
|
|
12
|
-
graph TD
|
|
13
|
-
Client[Client/API Consumer]
|
|
14
|
-
|
|
15
|
-
Client -->|HTTP Request| Controller[Controllers]
|
|
16
|
-
|
|
17
|
-
Controller -->|Simple CRUD| Repo[Repositories]
|
|
18
|
-
Controller -->|Complex Logic| Service[Services]
|
|
19
|
-
|
|
20
|
-
Service --> Repo
|
|
21
|
-
|
|
22
|
-
Repo --> DataSource[DataSources]
|
|
23
|
-
DataSource --> DB[(Database)]
|
|
24
|
-
|
|
25
|
-
style Service fill:#e1f5ff
|
|
26
|
-
style Repo fill:#fff4e1
|
|
27
|
-
style Controller fill:#ffe1f5
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
| Layer | Responsibility | Example |
|
|
31
|
-
|-------|---------------|---------|
|
|
32
|
-
| **Controllers** | Handle HTTP - parse requests, validate, format responses | `ConfigurationController` (uses `ControllerFactory`) |
|
|
33
|
-
| **Services** | Business logic - orchestrate operations | `AuthenticationService` (auth logic) |
|
|
34
|
-
| **Repositories** | Data access - CRUD operations | `ConfigurationRepository` (extends `DefaultCRUDRepository`) |
|
|
35
|
-
| **DataSources** | Database connections | `PostgresDataSource` (connects to PostgreSQL) |
|
|
36
|
-
| **Models** | Data structure - Drizzle schemas + Entity classes | `Configuration`, `User` models |
|
|
37
|
-
|
|
38
|
-
**Key Principle - Two Approaches:**
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
Simple CRUD (no business logic):
|
|
42
|
-
┌────────────┐
|
|
43
|
-
│ Controller │──────────────┐
|
|
44
|
-
└────────────┘ │
|
|
45
|
-
▼
|
|
46
|
-
┌──────────────┐
|
|
47
|
-
│ Repository │
|
|
48
|
-
└──────────────┘
|
|
49
|
-
│
|
|
50
|
-
▼
|
|
51
|
-
Database
|
|
52
|
-
|
|
53
|
-
Complex Logic (validation, orchestration):
|
|
54
|
-
┌────────────┐
|
|
55
|
-
│ Controller │────┐
|
|
56
|
-
└────────────┘ │
|
|
57
|
-
▼
|
|
58
|
-
┌─────────┐
|
|
59
|
-
│ Service │
|
|
60
|
-
└─────────┘
|
|
61
|
-
│
|
|
62
|
-
▼
|
|
63
|
-
┌──────────────┐
|
|
64
|
-
│ Repository │
|
|
65
|
-
└──────────────┘
|
|
66
|
-
│
|
|
67
|
-
▼
|
|
68
|
-
Database
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
**When to use each:**
|
|
72
|
-
- **Controller → Repository** - Simple CRUD (list, get by ID, create, update, delete)
|
|
73
|
-
- **Controller → Service → Repository** - Business logic, validation, orchestrating multiple repositories
|
|
74
|
-
|
|
75
|
-
## 2. Dependency Injection (DI)
|
|
76
|
-
|
|
77
|
-
Classes declare dependencies in their constructor - the framework automatically provides them at runtime.
|
|
78
|
-
|
|
79
|
-
**Benefits:**
|
|
80
|
-
- Loosely coupled code
|
|
81
|
-
- Easy to test (mock dependencies)
|
|
82
|
-
- Easy to swap implementations
|
|
83
|
-
|
|
84
|
-
**Example:**
|
|
85
|
-
```typescript
|
|
86
|
-
@controller({ path: BASE_PATH })
|
|
87
|
-
export class ConfigurationController extends _Controller {
|
|
88
|
-
constructor(
|
|
89
|
-
// The @inject decorator tells the container to provide
|
|
90
|
-
// an instance of ConfigurationRepository here.
|
|
91
|
-
@inject({
|
|
92
|
-
key: BindingKeys.build({
|
|
93
|
-
namespace: BindingNamespaces.REPOSITORY,
|
|
94
|
-
key: ConfigurationRepository.name,
|
|
95
|
-
}),
|
|
96
|
-
})
|
|
97
|
-
repository: ConfigurationRepository,
|
|
98
|
-
) {
|
|
99
|
-
super(repository);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## 3. Component-Based Modularity
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
**Built-in Components:**
|
|
109
|
-
- `AuthenticateComponent` - JWT authentication
|
|
110
|
-
- `SwaggerComponent` - OpenAPI documentation
|
|
111
|
-
- `HealthCheckComponent` - Health check endpoint
|
|
112
|
-
- `RequestTrackerComponent` - Request logging
|
|
113
|
-
|
|
114
|
-
**Example:**
|
|
115
|
-
```typescript
|
|
116
|
-
// src/application.ts
|
|
117
|
-
|
|
118
|
-
export class Application extends BaseApplication {
|
|
119
|
-
// ...
|
|
120
|
-
preConfigure(): ValueOrPromise<void> {
|
|
121
|
-
// ...
|
|
122
|
-
// Registering components plugs their functionality into the application.
|
|
123
|
-
this.component(HealthCheckComponent);
|
|
124
|
-
this.component(SwaggerComponent);
|
|
125
|
-
// ...
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
This architecture keeps the main `Application` class clean and focused on high-level assembly, while the details of each feature are neatly encapsulated within their respective components.
|
|
130
|
-
|
|
131
|
-
## 4. Custom Components
|
|
132
|
-
|
|
133
|
-
You can encapsulate your own logic or third-party integrations (like Socket.IO, Redis, specific Cron jobs) into reusable Components.
|
|
134
|
-
|
|
135
|
-
**Structure of a Component:**
|
|
136
|
-
1. Extend `BaseComponent`.
|
|
137
|
-
2. Define default `bindings` (optional configuration/options).
|
|
138
|
-
3. Implement `binding()` to register services, providers, or attach logic to the application.
|
|
139
|
-
|
|
140
|
-
**Example (`SocketIOComponent`):**
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
import { BaseComponent, inject, CoreBindings, Binding } from '@venizia/ignis';
|
|
144
|
-
|
|
145
|
-
export class MySocketComponent extends BaseComponent {
|
|
146
|
-
constructor(
|
|
147
|
-
@inject({ key: CoreBindings.APPLICATION_INSTANCE }) private application: BaseApplication,
|
|
148
|
-
) {
|
|
149
|
-
super({
|
|
150
|
-
scope: MySocketComponent.name,
|
|
151
|
-
// Automatically register bindings when component is loaded
|
|
152
|
-
initDefault: { enable: true, container: application },
|
|
153
|
-
bindings: {
|
|
154
|
-
// Define default configuration binding
|
|
155
|
-
'my.socket.options': Binding.bind({ key: 'my.socket.options' }).toValue({ port: 8080 }),
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// The binding method is called during application startup (preConfigure)
|
|
161
|
-
override binding(): void {
|
|
162
|
-
const options = this.application.get({ key: 'my.socket.options' });
|
|
163
|
-
|
|
164
|
-
this.logger.info('Initializing Socket.IO with options: %j', options);
|
|
165
|
-
|
|
166
|
-
// Perform setup logic, register other services, etc.
|
|
167
|
-
// this.application.bind(...).toValue(...);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
```
|