@venizia/ignis-docs 0.0.8 → 0.2.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 +7 -7
- package/content/best-practices/api-usage-examples.md +15 -12
- package/content/best-practices/architectural-patterns.md +70 -78
- package/content/best-practices/architecture-decisions.md +91 -60
- package/content/best-practices/code-style-standards/advanced-patterns.md +56 -44
- package/content/best-practices/code-style-standards/constants-configuration.md +11 -11
- package/content/best-practices/code-style-standards/control-flow.md +5 -2
- package/content/best-practices/code-style-standards/documentation.md +13 -13
- package/content/best-practices/code-style-standards/function-patterns.md +9 -10
- package/content/best-practices/code-style-standards/index.md +1 -1
- package/content/best-practices/code-style-standards/naming-conventions.md +10 -8
- package/content/best-practices/code-style-standards/route-definitions.md +30 -12
- package/content/best-practices/code-style-standards/tooling.md +8 -5
- package/content/best-practices/code-style-standards/type-safety.md +13 -12
- package/content/best-practices/common-pitfalls.md +56 -37
- package/content/best-practices/contribution-workflow.md +13 -14
- package/content/best-practices/data-modeling.md +46 -22
- package/content/best-practices/deployment-strategies.md +28 -27
- package/content/best-practices/error-handling.md +48 -24
- package/content/best-practices/index.md +5 -5
- package/content/best-practices/performance-optimization.md +40 -31
- package/content/best-practices/security-guidelines.md +52 -23
- package/content/best-practices/testing-strategies.md +65 -51
- package/content/best-practices/troubleshooting-tips.md +24 -24
- package/content/extensions/components/{swagger.md → api-reference.md} +40 -31
- package/content/extensions/components/authentication/api.md +19 -19
- package/content/extensions/components/authentication/errors.md +7 -7
- package/content/extensions/components/authentication/index.md +10 -8
- package/content/extensions/components/authentication/usage.md +101 -6
- package/content/extensions/components/authorization/api.md +45 -25
- package/content/extensions/components/authorization/errors.md +6 -6
- package/content/extensions/components/authorization/index.md +11 -10
- package/content/extensions/components/authorization/usage.md +21 -21
- package/content/extensions/components/health-check.md +1 -1
- package/content/extensions/components/index.md +5 -5
- package/content/extensions/components/mail/errors.md +15 -15
- package/content/extensions/components/mail/index.md +1 -2
- package/content/extensions/components/mail/usage.md +1 -1
- package/content/extensions/components/request-tracker.md +1 -1
- package/content/extensions/components/socket-io/api.md +9 -9
- package/content/extensions/components/socket-io/errors.md +5 -5
- package/content/extensions/components/socket-io/index.md +8 -8
- package/content/extensions/components/socket-io/usage.md +1 -1
- package/content/extensions/components/static-asset/api.md +17 -4
- package/content/extensions/components/static-asset/errors.md +4 -4
- package/content/extensions/components/static-asset/index.md +26 -28
- package/content/extensions/components/static-asset/usage.md +13 -12
- package/content/extensions/components/template/index.md +2 -2
- package/content/extensions/components/template/setup-page.md +1 -1
- package/content/extensions/components/websocket/api.md +3 -3
- package/content/extensions/components/websocket/errors.md +5 -5
- package/content/extensions/components/websocket/index.md +5 -5
- package/content/extensions/components/websocket/usage.md +3 -3
- package/content/extensions/helpers/cron/index.md +2 -2
- package/content/extensions/helpers/crypto/index.md +1 -1
- package/content/extensions/helpers/env/index.md +27 -12
- package/content/extensions/helpers/error/index.md +81 -25
- package/content/extensions/helpers/index.md +2 -3
- package/content/extensions/helpers/inversion/index.md +15 -7
- package/content/extensions/helpers/kafka/compile-binary.md +92 -0
- package/content/extensions/helpers/kafka/examples.md +1 -1
- package/content/extensions/helpers/kafka/index.md +3 -0
- package/content/extensions/helpers/logger/index.md +32 -2
- package/content/extensions/helpers/network/index.md +6 -0
- package/content/extensions/helpers/queue/index.md +14 -17
- package/content/extensions/helpers/redis/index.md +548 -323
- package/content/extensions/helpers/socket-io/index.md +14 -10
- package/content/extensions/helpers/storage/api.md +44 -8
- package/content/extensions/helpers/storage/index.md +43 -7
- package/content/extensions/helpers/template/index.md +6 -3
- package/content/extensions/helpers/types/index.md +11 -8
- package/content/extensions/helpers/websocket/api.md +9 -9
- package/content/extensions/helpers/websocket/index.md +7 -7
- package/content/extensions/helpers/worker-thread/index.md +2 -2
- package/content/extensions/index.md +3 -4
- package/content/extensions/src-details/mcp-server.md +18 -24
- package/content/guides/core-concepts/application/bootstrapping.md +11 -14
- package/content/guides/core-concepts/application/index.md +3 -3
- package/content/guides/core-concepts/components.md +19 -10
- package/content/guides/core-concepts/dependency-injection.md +6 -3
- package/content/guides/core-concepts/grpc-controllers.md +6 -5
- package/content/guides/core-concepts/persistent/datasources.md +42 -43
- package/content/guides/core-concepts/persistent/index.md +16 -7
- package/content/guides/core-concepts/persistent/models.md +24 -20
- package/content/guides/core-concepts/persistent/postgres-drivers.md +201 -0
- package/content/guides/core-concepts/persistent/repositories.md +40 -23
- package/content/guides/core-concepts/persistent/search-meilisearch.md +185 -0
- package/content/guides/core-concepts/persistent/search-typesense.md +431 -0
- package/content/guides/core-concepts/persistent/transactions.md +61 -25
- package/content/guides/core-concepts/rest-controllers.md +12 -9
- package/content/guides/core-concepts/services.md +330 -60
- package/content/guides/get-started/5-minute-quickstart.md +15 -15
- package/content/guides/get-started/philosophy.md +36 -36
- package/content/guides/get-started/setup.md +3 -3
- package/content/guides/index.md +3 -3
- package/content/guides/migrations/redis-helpers-migration.md +177 -0
- package/content/guides/migrations/scoped-rbac-migration.md +17 -17
- package/content/guides/migrations/unified-connectors-migration.md +113 -0
- package/content/guides/reference/glossary.md +19 -12
- package/content/guides/reference/mcp-docs-server.md +22 -18
- package/content/guides/tutorials/building-a-crud-api.md +37 -44
- package/content/guides/tutorials/complete-installation.md +17 -17
- package/content/guides/tutorials/ecommerce-api.md +163 -124
- package/content/guides/tutorials/realtime-chat.md +181 -135
- package/content/guides/tutorials/testing.md +65 -523
- package/content/index.md +2 -180
- package/content/public/apple-touch-icon.png +0 -0
- package/content/public/og-image.png +0 -0
- package/content/public/site.webmanifest +11 -0
- package/content/references/base/application.md +4 -5
- package/content/references/base/bootstrapping.md +18 -5
- package/content/references/base/components.md +149 -120
- package/content/references/base/connectors.md +178 -0
- package/content/references/base/controllers.md +41 -30
- package/content/references/base/datasources.md +163 -92
- package/content/references/base/dependency-injection.md +34 -22
- package/content/references/base/filter-system/application-usage.md +17 -14
- package/content/references/base/filter-system/array-operators.md +7 -2
- package/content/references/base/filter-system/comparison-operators.md +3 -0
- package/content/references/base/filter-system/default-filter.md +89 -71
- package/content/references/base/filter-system/fields-order-pagination.md +22 -22
- package/content/references/base/filter-system/index.md +6 -3
- package/content/references/base/filter-system/json-filtering.md +20 -1
- package/content/references/base/filter-system/list-operators.md +1 -1
- package/content/references/base/filter-system/logical-operators.md +33 -1
- package/content/references/base/filter-system/null-operators.md +30 -1
- package/content/references/base/filter-system/quick-reference.md +23 -4
- package/content/references/base/filter-system/tips.md +5 -5
- package/content/references/base/filter-system/use-cases.md +12 -12
- package/content/references/base/grpc-controllers.md +13 -13
- package/content/references/base/index.md +24 -12
- package/content/references/base/middlewares.md +265 -327
- package/content/references/base/models.md +63 -49
- package/content/references/base/providers.md +136 -130
- package/content/references/base/repositories/advanced.md +59 -58
- package/content/references/base/repositories/index.md +115 -91
- package/content/references/base/repositories/mixins.md +55 -291
- package/content/references/base/repositories/relations.md +54 -64
- package/content/references/base/repositories/soft-deletable.md +31 -30
- package/content/references/base/services.md +296 -93
- package/content/references/configuration/environment-variables.md +49 -31
- package/content/references/configuration/index.md +6 -6
- package/content/references/index.md +17 -12
- package/content/references/quick-reference.md +65 -106
- package/content/references/utilities/crypto.md +65 -23
- package/content/references/utilities/index.md +3 -3
- package/content/references/utilities/jsx.md +6 -4
- package/content/references/utilities/module.md +68 -20
- package/content/references/utilities/parse.md +4 -14
- package/content/references/utilities/promise.md +9 -7
- package/content/references/utilities/schema.md +5 -3
- package/dist/mcp-server/common/guards.d.ts +8 -0
- package/dist/mcp-server/common/guards.d.ts.map +1 -0
- package/dist/mcp-server/common/guards.js +14 -0
- package/dist/mcp-server/common/guards.js.map +1 -0
- package/dist/mcp-server/common/index.d.ts +1 -0
- package/dist/mcp-server/common/index.d.ts.map +1 -1
- package/dist/mcp-server/common/index.js +1 -0
- package/dist/mcp-server/common/index.js.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.js +4 -2
- package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
- package/dist/mcp-server/helpers/github.helper.js +1 -1
- package/dist/mcp-server/index.js +7 -2
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/mcp-server/tools/base.tool.d.ts +6 -2
- package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/base.tool.js.map +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
- package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
- package/package.json +9 -9
- package/content/extensions/helpers/testing/index.md +0 -510
- package/content/references/base/middleware.md +0 -347
|
@@ -2,101 +2,371 @@
|
|
|
2
2
|
|
|
3
3
|
Services contain your application's business logic, orchestrating data flow and executing use cases.
|
|
4
4
|
|
|
5
|
-
> **Deep Dive:** See [Services Reference](../../references/base/services.md) for
|
|
5
|
+
> **Deep Dive:** See [Services Reference](../../references/base/services.md) for the full API reference.
|
|
6
6
|
|
|
7
7
|
## The Business Logic Layer
|
|
8
8
|
|
|
9
|
-
Services
|
|
9
|
+
Services sit between controllers and repositories. A controller receives an HTTP request and immediately delegates to a service; the service applies business rules, calls one or more repositories, and returns the result.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- **Coordinating Operations**: Using repositories to fetch and persist data, or orchestrating other services to execute complex business workflows.
|
|
13
|
-
- **Reusing Business Logic**: Services can inject other services to reuse established logic, ensuring "Don't Repeat Yourself" (DRY) principles are maintained across the application.
|
|
14
|
-
- **Isolating Controllers**: Keeping controllers thin by handling all the complex logic, so controllers are only responsible for handling the HTTP request and response.
|
|
11
|
+
Primary responsibilities:
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
- **Encapsulate business rules** - validation, state-machine transitions, authorization guards, calculations
|
|
14
|
+
- **Coordinate repositories** - fetch related data, combine writes, enforce consistency
|
|
15
|
+
- **Manage transactions** - begin, commit, or roll back across multiple repository calls
|
|
16
|
+
- **Compose services** - inject and call other services to avoid duplicating logic
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Controllers stay thin. Repositories stay dumb. Everything in between is a service.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Creating a Service
|
|
23
|
+
|
|
24
|
+
Extend `BaseService` and call `super({ scope: ClassName.name })` in the constructor:
|
|
19
25
|
|
|
20
26
|
```typescript
|
|
21
27
|
import { BaseService, inject } from '@venizia/ignis';
|
|
22
|
-
import {
|
|
28
|
+
import { getError, HTTP } from '@venizia/ignis-helpers';
|
|
23
29
|
import { UserRepository } from '../repositories';
|
|
24
|
-
import { LoggingService } from './logging.service'; // Example of another service
|
|
25
|
-
import { TConfiguration } from '../models/entities';
|
|
26
30
|
|
|
27
|
-
export class
|
|
31
|
+
export class UserService extends BaseService {
|
|
32
|
+
constructor(
|
|
33
|
+
@inject({ key: 'repositories.UserRepository' })
|
|
34
|
+
private userRepository: UserRepository,
|
|
35
|
+
) {
|
|
36
|
+
super({ scope: UserService.name });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async getUser(opts: { id: string }) {
|
|
40
|
+
const user = await this.userRepository.findById({ id: opts.id });
|
|
41
|
+
|
|
42
|
+
if (!user) {
|
|
43
|
+
throw getError({
|
|
44
|
+
statusCode: HTTP.ResultCodes.RS_4.NotFound,
|
|
45
|
+
message: 'User not found',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return user;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**`scope`** is the tag written into every log line produced by this service. Use `ClassName.name` - it's the project convention and avoids string drift when renaming the class.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Registering a Service
|
|
59
|
+
|
|
60
|
+
Registration is always imperative - call `this.service(ClassName)` inside an application lifecycle method. Do not add `@injectable` to services; `this.service()` handles the binding.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { BaseApplication } from '@venizia/ignis';
|
|
64
|
+
import { PostgresDataSource } from './datasources';
|
|
65
|
+
import { UserRepository } from './repositories';
|
|
66
|
+
import { UserService } from './services';
|
|
67
|
+
|
|
68
|
+
export class Application extends BaseApplication {
|
|
69
|
+
preConfigure(): void {
|
|
70
|
+
// Register dependencies first, then the service
|
|
71
|
+
this.dataSource(PostgresDataSource);
|
|
72
|
+
this.repository(UserRepository);
|
|
73
|
+
this.service(UserService); // binds as 'services.UserService'
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`this.service(UserService)` binds the class at the key `services.UserService`. Any other binding that injects `@inject({ key: 'services.UserService' })` resolves an instance from this binding. Service bindings are **transient** by default - each resolution creates a new instance. If your service must be shared (e.g., it holds state), set the scope explicitly: `this.service(UserService).setScope(BindingScopes.SINGLETON)`.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Injecting Dependencies
|
|
83
|
+
|
|
84
|
+
Use `@inject({ key })` on each constructor parameter. Two equivalent forms:
|
|
85
|
+
|
|
86
|
+
### Plain string keys (concise)
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
constructor(
|
|
90
|
+
@inject({ key: 'repositories.UserRepository' })
|
|
91
|
+
private userRepository: UserRepository,
|
|
92
|
+
|
|
93
|
+
@inject({ key: 'services.NotificationService' })
|
|
94
|
+
private notificationService: NotificationService,
|
|
95
|
+
) {
|
|
96
|
+
super({ scope: UserService.name });
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `BindingKeys.build` (refactor-safe)
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { BindingKeys, BindingNamespaces } from '@venizia/ignis';
|
|
104
|
+
|
|
105
|
+
constructor(
|
|
106
|
+
@inject({
|
|
107
|
+
key: BindingKeys.build({
|
|
108
|
+
namespace: BindingNamespaces.REPOSITORY, // 'repositories'
|
|
109
|
+
key: UserRepository.name, // 'UserRepository'
|
|
110
|
+
}),
|
|
111
|
+
})
|
|
112
|
+
private userRepository: UserRepository,
|
|
113
|
+
) {
|
|
114
|
+
super({ scope: UserService.name });
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Both produce the same runtime key (`repositories.UserRepository`). `BindingKeys.build` is preferred in larger codebases because a class rename caught by the TypeScript compiler automatically updates the key.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Logging
|
|
123
|
+
|
|
124
|
+
`BaseService` inherits `this.logger` from `BaseHelper`. Scope log lines to the current method:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
async signIn(opts: { username: string }): Promise<string> {
|
|
128
|
+
this.logger.for('signIn').info('SignIn called | username: %s', opts.username);
|
|
129
|
+
|
|
130
|
+
const user = await this.userRepository.findByUsername(opts.username);
|
|
131
|
+
|
|
132
|
+
if (!user) {
|
|
133
|
+
this.logger.for('signIn').warn('User not found | username: %s', opts.username);
|
|
134
|
+
throw getError({ statusCode: HTTP.ResultCodes.RS_4.Unauthorized, message: 'Invalid credentials' });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.logger.for('signIn').info('SignIn successful | userId: %s', user.id);
|
|
138
|
+
return await this.generateToken({ userId: user.id });
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`this.logger.for('signIn')` adds a `[signIn]` tag to each line without creating a new logger object.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Service-to-Service Composition
|
|
147
|
+
|
|
148
|
+
Services can inject other services the same way they inject repositories. This is the primary mechanism for logic reuse.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
export class RepositoryTestService extends BaseService {
|
|
152
|
+
constructor(
|
|
153
|
+
@inject({
|
|
154
|
+
key: BindingKeys.build({
|
|
155
|
+
namespace: BindingNamespaces.SERVICE,
|
|
156
|
+
key: CrudTestService.name,
|
|
157
|
+
}),
|
|
158
|
+
})
|
|
159
|
+
private readonly crudTestService: CrudTestService,
|
|
160
|
+
|
|
161
|
+
@inject({
|
|
162
|
+
key: BindingKeys.build({
|
|
163
|
+
namespace: BindingNamespaces.SERVICE,
|
|
164
|
+
key: TransactionTestService.name,
|
|
165
|
+
}),
|
|
166
|
+
})
|
|
167
|
+
private readonly transactionTestService: TransactionTestService,
|
|
168
|
+
) {
|
|
169
|
+
super({ scope: RepositoryTestService.name });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async runAll(): Promise<void> {
|
|
173
|
+
await this.crudTestService.run();
|
|
174
|
+
await this.transactionTestService.run();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Register all participating services in `preConfigure()`:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
this.service(CrudTestService);
|
|
183
|
+
this.service(TransactionTestService);
|
|
184
|
+
this.service(RepositoryTestService);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The DI container resolves the dependency graph automatically - registration order within the same lifecycle phase does not matter.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Worked Example: Authentication Service
|
|
192
|
+
|
|
193
|
+
The following is representative of `examples/vert/src/services/authentication.service.ts`. It shows the complete pattern: multiple injected dependencies, method-scoped logging, error handling, and calling other services.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { BaseService, IAuthService, inject, JWKSIssuerTokenService, TContext } from '@venizia/ignis';
|
|
197
|
+
import { getError, HTTP } from '@venizia/ignis-helpers';
|
|
198
|
+
import { compare, genSalt, hash } from 'bcrypt';
|
|
199
|
+
import { Env } from 'hono';
|
|
200
|
+
|
|
201
|
+
export class AuthenticationService
|
|
202
|
+
extends BaseService
|
|
203
|
+
implements IAuthService<Env, TSignInRequest, TSignInResponse, ...>
|
|
204
|
+
{
|
|
28
205
|
constructor(
|
|
29
|
-
|
|
30
|
-
private configurationRepository: ConfigurationRepository,
|
|
206
|
+
// Inject a repository by string key
|
|
31
207
|
@inject({ key: 'repositories.UserRepository' })
|
|
32
208
|
private userRepository: UserRepository,
|
|
33
|
-
|
|
34
|
-
|
|
209
|
+
|
|
210
|
+
// Inject a built-in framework service by string key
|
|
211
|
+
@inject({ key: 'services.JWKSIssuerTokenService' })
|
|
212
|
+
private jwksTokenService: JWKSIssuerTokenService,
|
|
35
213
|
) {
|
|
36
|
-
super({ scope:
|
|
214
|
+
super({ scope: AuthenticationService.name });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async signIn(_context: TContext<Env>, opts: TSignInRequest): Promise<TSignInResponse> {
|
|
218
|
+
this.logger.for('signIn').info('SignIn called | identifier: %j', opts.identifier);
|
|
219
|
+
|
|
220
|
+
const user = await this.userRepository.findByUsername(opts.identifier.value);
|
|
221
|
+
|
|
222
|
+
if (!user) {
|
|
223
|
+
throw getError({
|
|
224
|
+
statusCode: HTTP.ResultCodes.RS_4.Unauthorized,
|
|
225
|
+
message: 'Invalid credentials',
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const isValid = await compare(opts.credential.value, user.password);
|
|
230
|
+
if (!isValid) {
|
|
231
|
+
throw getError({
|
|
232
|
+
statusCode: HTTP.ResultCodes.RS_4.Unauthorized,
|
|
233
|
+
message: 'Invalid credentials',
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const token = await this.jwksTokenService.generate({
|
|
238
|
+
payload: { userId: user.id, email: user.email },
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
this.logger.for('signIn').info('SignIn successful | userId: %s', user.id);
|
|
242
|
+
return { token: { value: token, type: 'Bearer' } };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async signUp(_context: TContext<Env>, opts: TSignUpRequest): Promise<TSignUpResponse> {
|
|
246
|
+
this.logger.for('signUp').info('SignUp called | username: %s', opts.username);
|
|
247
|
+
|
|
248
|
+
const existing = await this.userRepository.findByUsername(opts.username);
|
|
249
|
+
if (existing) {
|
|
250
|
+
throw getError({
|
|
251
|
+
statusCode: HTTP.ResultCodes.RS_4.Conflict,
|
|
252
|
+
message: 'Username already exists',
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const salt = await genSalt();
|
|
257
|
+
const hashedPassword = await hash(opts.credential, salt);
|
|
258
|
+
|
|
259
|
+
await this.userRepository.create({ data: { username: opts.username, password: hashedPassword } });
|
|
260
|
+
|
|
261
|
+
return { message: 'User registered successfully' };
|
|
37
262
|
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
38
265
|
|
|
39
|
-
|
|
40
|
-
userId: string;
|
|
41
|
-
data: Partial<TConfiguration>;
|
|
42
|
-
}): Promise<TConfiguration> {
|
|
43
|
-
// Call another service logic
|
|
44
|
-
await this.loggingService.audit({ message: `Creating config for user: ${opts.userId}` });
|
|
266
|
+
Registration:
|
|
45
267
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
268
|
+
```typescript
|
|
269
|
+
// application.ts
|
|
270
|
+
preConfigure(): void {
|
|
271
|
+
this.dataSource(PostgresDataSource);
|
|
272
|
+
this.repository(UserRepository);
|
|
273
|
+
this.service(AuthenticationService); // 'services.AuthenticationService'
|
|
274
|
+
}
|
|
49
275
|
```
|
|
50
276
|
|
|
51
|
-
|
|
277
|
+
---
|
|
52
278
|
|
|
53
|
-
|
|
279
|
+
## Transaction Orchestration
|
|
280
|
+
|
|
281
|
+
Use a datasource reference to begin a transaction, then pass the transaction handle through repository `options`:
|
|
54
282
|
|
|
55
283
|
```typescript
|
|
56
|
-
export
|
|
57
|
-
constructor(
|
|
58
|
-
|
|
284
|
+
export class CheckoutService extends BaseService {
|
|
285
|
+
constructor(
|
|
286
|
+
@inject({ key: 'datasources.PostgresDataSource' })
|
|
287
|
+
private dataSource: PostgresDataSource,
|
|
288
|
+
|
|
289
|
+
@inject({ key: 'repositories.OrderRepository' })
|
|
290
|
+
private orderRepository: OrderRepository,
|
|
291
|
+
|
|
292
|
+
@inject({ key: 'repositories.InventoryRepository' })
|
|
293
|
+
private inventoryRepository: InventoryRepository,
|
|
294
|
+
) {
|
|
295
|
+
super({ scope: CheckoutService.name });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async placeOrder(opts: { userId: string; items: OrderItem[] }): Promise<Order> {
|
|
299
|
+
const log = this.logger.for('placeOrder');
|
|
300
|
+
const transaction = await this.dataSource.beginTransaction();
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
const { data: order } = await this.orderRepository.create({
|
|
304
|
+
data: { userId: opts.userId },
|
|
305
|
+
options: { transaction },
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
for (const item of opts.items) {
|
|
309
|
+
await this.inventoryRepository.updateById({
|
|
310
|
+
id: item.productId,
|
|
311
|
+
data: { stock: item.quantity },
|
|
312
|
+
options: { transaction },
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
await transaction.commit();
|
|
317
|
+
log.info('Order placed | orderId: %s', order.id);
|
|
318
|
+
return order;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
await transaction.rollback();
|
|
321
|
+
log.error('Order failed, rolled back | error: %s', error);
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
59
324
|
}
|
|
60
325
|
}
|
|
61
326
|
```
|
|
62
327
|
|
|
63
|
-
|
|
328
|
+
Always call `rollback()` in `catch` - an uncommitted transaction holds a database connection until it is released.
|
|
64
329
|
|
|
65
|
-
|
|
330
|
+
---
|
|
66
331
|
|
|
67
|
-
|
|
332
|
+
## Architecture Diagram
|
|
68
333
|
|
|
69
334
|
```mermaid
|
|
70
335
|
graph LR
|
|
71
|
-
A[
|
|
72
|
-
B -->
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
subgraph "Presentation
|
|
336
|
+
A[HTTP Request] --> B(Controller)
|
|
337
|
+
B --> C{Service A}
|
|
338
|
+
C --> D{Service B}
|
|
339
|
+
C --> E[Repository A]
|
|
340
|
+
D --> F[Repository B]
|
|
341
|
+
E --> G((Database))
|
|
342
|
+
F --> G
|
|
343
|
+
|
|
344
|
+
subgraph "Presentation"
|
|
80
345
|
B
|
|
81
346
|
end
|
|
82
|
-
subgraph "Business Logic
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
end
|
|
86
|
-
subgraph "Data Access Layer"
|
|
87
|
-
D1
|
|
88
|
-
D2
|
|
347
|
+
subgraph "Business Logic"
|
|
348
|
+
C
|
|
349
|
+
D
|
|
89
350
|
end
|
|
90
|
-
subgraph "Data
|
|
351
|
+
subgraph "Data Access"
|
|
91
352
|
E
|
|
353
|
+
F
|
|
92
354
|
end
|
|
93
355
|
```
|
|
94
356
|
|
|
95
|
-
|
|
357
|
+
Controllers call services. Services call other services and repositories. Repositories call the database. No layer reaches past its immediate neighbor.
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Provider vs Service
|
|
362
|
+
|
|
363
|
+
**Services** contain business logic and are resolved from the DI container (transient scope by default).
|
|
364
|
+
|
|
365
|
+
**Providers** implement the Factory pattern - their `value(container)` method produces a configured value or instance on demand (mail transport, cache driver, middleware). Use a Provider when you need to select between multiple implementations at runtime.
|
|
366
|
+
|
|
367
|
+
See [Providers Reference](/references/base/providers) for the full comparison and examples.
|
|
96
368
|
|
|
97
|
-
|
|
98
|
-
- **Easier to Test:** You can test your business logic in isolation by providing mock repositories to your services, without needing a live database.
|
|
99
|
-
- **More Flexible:** You can change your database or data access implementation (e.g., switch from PostgreSQL to MySQL) by only changing the repository and datasource layer, with no changes to your business logic.
|
|
369
|
+
---
|
|
100
370
|
|
|
101
371
|
## See Also
|
|
102
372
|
|
|
@@ -107,13 +377,13 @@ This layered architecture makes your application:
|
|
|
107
377
|
|
|
108
378
|
- **References:**
|
|
109
379
|
- [BaseService API](/references/base/services) - Complete API reference
|
|
110
|
-
- [Providers](/references/base/providers) - Factory pattern
|
|
380
|
+
- [Providers](/references/base/providers) - Factory pattern
|
|
111
381
|
- [Logger Helper](/extensions/helpers/logger/) - Logging in services
|
|
112
382
|
|
|
113
383
|
- **Best Practices:**
|
|
114
|
-
- [Architectural Patterns](/best-practices/architectural-patterns)
|
|
115
|
-
- [Testing](/guides/tutorials/testing)
|
|
384
|
+
- [Architectural Patterns](/best-practices/architectural-patterns)
|
|
385
|
+
- [Testing](/guides/tutorials/testing)
|
|
116
386
|
|
|
117
387
|
- **Tutorials:**
|
|
118
|
-
- [Building a CRUD API](/guides/tutorials/building-a-crud-api)
|
|
119
|
-
- [E-commerce API](/guides/tutorials/ecommerce-api)
|
|
388
|
+
- [Building a CRUD API](/guides/tutorials/building-a-crud-api)
|
|
389
|
+
- [E-commerce API](/guides/tutorials/ecommerce-api)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 5-Minute Quickstart
|
|
2
2
|
|
|
3
|
-
Build your first
|
|
3
|
+
Build your first IGNIS API endpoint in 5 minutes. No database, no complex setup - just a working "Hello World" API.
|
|
4
4
|
|
|
5
5
|
**Time to Complete:** ~5 minutes
|
|
6
6
|
|
|
@@ -64,7 +64,7 @@ node_modules
|
|
|
64
64
|
## Step 3: Write Your API (2 minutes)
|
|
65
65
|
|
|
66
66
|
:::info What is a Decorator?
|
|
67
|
-
A decorator is a TypeScript feature that adds behavior to classes, methods, or properties. It's the `@something` syntax you see before definitions (like `@controller`, `@get`, `@inject`). Decorators in
|
|
67
|
+
A decorator is a TypeScript feature that adds behavior to classes, methods, or properties. It's the `@something` syntax you see before definitions (like `@controller`, `@get`, `@inject`). Decorators in IGNIS handle routing, dependency injection, and API documentation automatically.
|
|
68
68
|
|
|
69
69
|
[Learn more →](/guides/reference/glossary#decorators)
|
|
70
70
|
:::
|
|
@@ -86,7 +86,7 @@ import {
|
|
|
86
86
|
get,
|
|
87
87
|
IApplicationInfo,
|
|
88
88
|
jsonContent,
|
|
89
|
-
|
|
89
|
+
ApiReferenceComponent,
|
|
90
90
|
} from "@venizia/ignis";
|
|
91
91
|
import { HTTP } from "@venizia/ignis-helpers";
|
|
92
92
|
import { Context } from "hono";
|
|
@@ -115,7 +115,7 @@ class HelloController extends BaseRestController {
|
|
|
115
115
|
},
|
|
116
116
|
})
|
|
117
117
|
sayHello(c: Context) {
|
|
118
|
-
return c.json({ message: "Hello from
|
|
118
|
+
return c.json({ message: "Hello from IGNIS!" }, HTTP.ResultCodes.RS_2.Ok);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
|
@@ -130,7 +130,7 @@ class App extends BaseApplication {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
preConfigure() {
|
|
133
|
-
this.component(
|
|
133
|
+
this.component(ApiReferenceComponent);
|
|
134
134
|
this.controller(HelloController);
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -222,7 +222,7 @@ Visit `http://localhost:3000/api/hello` in your browser!
|
|
|
222
222
|
**Response:**
|
|
223
223
|
|
|
224
224
|
```json
|
|
225
|
-
{ "message": "Hello from
|
|
225
|
+
{ "message": "Hello from IGNIS!" }
|
|
226
226
|
```
|
|
227
227
|
|
|
228
228
|
## View API Docs
|
|
@@ -240,27 +240,27 @@ Open `http://localhost:3000/doc/explorer` to see interactive Swagger UI document
|
|
|
240
240
|
| `Zod schema` | Validates request/response and auto-generates OpenAPI docs |
|
|
241
241
|
| `BaseRestController` | Provides lifecycle hooks, route binding, and OpenAPI integration for REST controllers |
|
|
242
242
|
| `BaseApplication` | Manages dependency injection, middleware, and server startup |
|
|
243
|
-
| `
|
|
243
|
+
| `ApiReferenceComponent` | Generates interactive API docs at `/doc/explorer` |
|
|
244
244
|
| `app.start()` | Runs the full lifecycle (preConfigure → register resources → middlewares) then starts HTTP server on port 3000 |
|
|
245
245
|
|
|
246
246
|
### Why Development Configs?
|
|
247
247
|
|
|
248
248
|
You might wonder why we set up TypeScript, ESLint, and Prettier configs in a "quickstart". Here's why:
|
|
249
249
|
|
|
250
|
-
**
|
|
250
|
+
**IGNIS is opinionated about code quality.** We believe clean, consistent code from day one prevents technical debt later. The `@venizia/dev-configs` package provides pre-configured settings that:
|
|
251
251
|
|
|
252
252
|
| Config | Purpose |
|
|
253
253
|
|--------|---------|
|
|
254
|
-
| `tsconfig.json` | Strict TypeScript settings optimized for
|
|
254
|
+
| `tsconfig.json` | Strict TypeScript settings optimized for IGNIS decorators and path aliases |
|
|
255
255
|
| `eslint.config.mjs` | Catches common errors, enforces best practices, works with TypeScript |
|
|
256
|
-
| `.prettierrc.mjs` | Consistent formatting across your team
|
|
256
|
+
| `.prettierrc.mjs` | Consistent formatting across your team - no more style debates |
|
|
257
257
|
|
|
258
|
-
**Benefits of starting with
|
|
258
|
+
**Benefits of starting with IGNIS code style:**
|
|
259
259
|
|
|
260
|
-
- **Consistency**
|
|
261
|
-
- **IDE Support**
|
|
262
|
-
- **Team Ready**
|
|
263
|
-
- **CI/CD Friendly**
|
|
260
|
+
- **Consistency** - Same patterns across all IGNIS projects
|
|
261
|
+
- **IDE Support** - Better autocomplete, error detection, and refactoring
|
|
262
|
+
- **Team Ready** - New developers can onboard faster with familiar structure
|
|
263
|
+
- **CI/CD Friendly** - Lint and format checks work out of the box
|
|
264
264
|
|
|
265
265
|
> [!TIP]
|
|
266
266
|
> All configs extend from `@venizia/dev-configs`, so you get updates automatically. Customize by overriding specific rules in your local config files.
|