@venizia/ignis-docs 0.0.1-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mcp-server/dist/common/config.d.ts +27 -0
- package/mcp-server/dist/common/config.d.ts.map +1 -0
- package/mcp-server/dist/common/config.js +27 -0
- package/mcp-server/dist/common/config.js.map +1 -0
- package/mcp-server/dist/common/index.d.ts +3 -0
- package/mcp-server/dist/common/index.d.ts.map +1 -0
- package/mcp-server/dist/common/index.js +19 -0
- package/mcp-server/dist/common/index.js.map +1 -0
- package/mcp-server/dist/common/paths.d.ts +13 -0
- package/mcp-server/dist/common/paths.d.ts.map +1 -0
- package/mcp-server/dist/common/paths.js +23 -0
- package/mcp-server/dist/common/paths.js.map +1 -0
- package/mcp-server/dist/helpers/docs.helper.d.ts +81 -0
- package/mcp-server/dist/helpers/docs.helper.d.ts.map +1 -0
- package/mcp-server/dist/helpers/docs.helper.js +171 -0
- package/mcp-server/dist/helpers/docs.helper.js.map +1 -0
- package/mcp-server/dist/helpers/index.d.ts +3 -0
- package/mcp-server/dist/helpers/index.d.ts.map +1 -0
- package/mcp-server/dist/helpers/index.js +19 -0
- package/mcp-server/dist/helpers/index.js.map +1 -0
- package/mcp-server/dist/helpers/logger.helper.d.ts +7 -0
- package/mcp-server/dist/helpers/logger.helper.d.ts.map +1 -0
- package/mcp-server/dist/helpers/logger.helper.js +22 -0
- package/mcp-server/dist/helpers/logger.helper.js.map +1 -0
- package/mcp-server/dist/index.d.ts +3 -0
- package/mcp-server/dist/index.d.ts.map +1 -0
- package/mcp-server/dist/index.js +62 -0
- package/mcp-server/dist/index.js.map +1 -0
- package/mcp-server/dist/tools/base.tool.d.ts +98 -0
- package/mcp-server/dist/tools/base.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/base.tool.js +47 -0
- package/mcp-server/dist/tools/base.tool.js.map +1 -0
- package/mcp-server/dist/tools/get-doc-content.tool.d.ts +30 -0
- package/mcp-server/dist/tools/get-doc-content.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/get-doc-content.tool.js +127 -0
- package/mcp-server/dist/tools/get-doc-content.tool.js.map +1 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.d.ts +40 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.js +121 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.js.map +1 -0
- package/mcp-server/dist/tools/index.d.ts +8 -0
- package/mcp-server/dist/tools/index.d.ts.map +1 -0
- package/mcp-server/dist/tools/index.js +18 -0
- package/mcp-server/dist/tools/index.js.map +1 -0
- package/mcp-server/dist/tools/list-categories.tool.d.ts +20 -0
- package/mcp-server/dist/tools/list-categories.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/list-categories.tool.js +105 -0
- package/mcp-server/dist/tools/list-categories.tool.js.map +1 -0
- package/mcp-server/dist/tools/list-docs.tool.d.ts +32 -0
- package/mcp-server/dist/tools/list-docs.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/list-docs.tool.js +121 -0
- package/mcp-server/dist/tools/list-docs.tool.js.map +1 -0
- package/mcp-server/dist/tools/search-docs.tool.d.ts +32 -0
- package/mcp-server/dist/tools/search-docs.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/search-docs.tool.js +120 -0
- package/mcp-server/dist/tools/search-docs.tool.js.map +1 -0
- package/package.json +102 -0
- package/wiki/get-started/5-minute-quickstart.md +266 -0
- package/wiki/get-started/best-practices/api-usage-examples.md +222 -0
- package/wiki/get-started/best-practices/architectural-patterns.md +129 -0
- package/wiki/get-started/best-practices/code-style-standards.md +122 -0
- package/wiki/get-started/best-practices/common-pitfalls.md +136 -0
- package/wiki/get-started/best-practices/contribution-workflow.md +145 -0
- package/wiki/get-started/best-practices/deployment-strategies.md +121 -0
- package/wiki/get-started/best-practices/performance-optimization.md +88 -0
- package/wiki/get-started/best-practices/security-guidelines.md +97 -0
- package/wiki/get-started/best-practices/troubleshooting-tips.md +100 -0
- package/wiki/get-started/building-a-crud-api.md +717 -0
- package/wiki/get-started/core-concepts/application.md +168 -0
- package/wiki/get-started/core-concepts/components.md +96 -0
- package/wiki/get-started/core-concepts/controllers.md +441 -0
- package/wiki/get-started/core-concepts/dependency-injection.md +160 -0
- package/wiki/get-started/core-concepts/persistent.md +591 -0
- package/wiki/get-started/core-concepts/services.md +88 -0
- package/wiki/get-started/index.md +65 -0
- package/wiki/get-started/mcp-docs-server.md +840 -0
- package/wiki/get-started/philosophy.md +123 -0
- package/wiki/get-started/prerequisites.md +113 -0
- package/wiki/get-started/quickstart.md +382 -0
- package/wiki/index.md +48 -0
- package/wiki/references/base/application.md +67 -0
- package/wiki/references/base/components.md +80 -0
- package/wiki/references/base/controllers.md +361 -0
- package/wiki/references/base/datasources.md +105 -0
- package/wiki/references/base/dependency-injection.md +83 -0
- package/wiki/references/base/models.md +104 -0
- package/wiki/references/base/repositories.md +118 -0
- package/wiki/references/base/services.md +97 -0
- package/wiki/references/components/authentication.md +224 -0
- package/wiki/references/components/health-check.md +190 -0
- package/wiki/references/components/index.md +61 -0
- package/wiki/references/components/request-tracker.md +35 -0
- package/wiki/references/components/socket-io.md +127 -0
- package/wiki/references/components/swagger.md +175 -0
- package/wiki/references/helpers/cron.md +94 -0
- package/wiki/references/helpers/crypto.md +117 -0
- package/wiki/references/helpers/env.md +67 -0
- package/wiki/references/helpers/error.md +80 -0
- package/wiki/references/helpers/index.md +21 -0
- package/wiki/references/helpers/inversion.md +141 -0
- package/wiki/references/helpers/logger.md +98 -0
- package/wiki/references/helpers/network.md +143 -0
- package/wiki/references/helpers/queue.md +131 -0
- package/wiki/references/helpers/redis.md +121 -0
- package/wiki/references/helpers/socket-io.md +103 -0
- package/wiki/references/helpers/storage.md +130 -0
- package/wiki/references/helpers/testing.md +115 -0
- package/wiki/references/helpers/worker-thread.md +162 -0
- package/wiki/references/src-details/core.md +249 -0
- package/wiki/references/src-details/dev-configs.md +302 -0
- package/wiki/references/src-details/docs.md +61 -0
- package/wiki/references/src-details/helpers.md +211 -0
- package/wiki/references/src-details/inversion.md +345 -0
- package/wiki/references/src-details/mcp-server.md +726 -0
- package/wiki/references/utilities/crypto.md +39 -0
- package/wiki/references/utilities/date.md +72 -0
- package/wiki/references/utilities/index.md +12 -0
- package/wiki/references/utilities/module.md +40 -0
- package/wiki/references/utilities/parse.md +68 -0
- package/wiki/references/utilities/performance.md +64 -0
- package/wiki/references/utilities/promise.md +83 -0
- package/wiki/references/utilities/request.md +66 -0
- package/wiki/references/utilities/schema.md +88 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Deep Dive: Dependency Injection
|
|
2
|
+
|
|
3
|
+
Technical reference for the DI system in Ignis - managing resource lifecycles and dependency resolution.
|
|
4
|
+
|
|
5
|
+
**Files:**
|
|
6
|
+
- `packages/core/src/helpers/inversion/container.ts`
|
|
7
|
+
- `packages/core/src/base/metadata/injectors.ts`
|
|
8
|
+
- `packages/helpers/src/helpers/inversion/registry.ts`
|
|
9
|
+
|
|
10
|
+
## Quick Reference
|
|
11
|
+
|
|
12
|
+
| Component | Purpose | Key Methods |
|
|
13
|
+
|-----------|---------|-------------|
|
|
14
|
+
| **Container** | DI registry managing resource lifecycles | `bind()`, `get()`, `instantiate()`, `findByTag()` |
|
|
15
|
+
| **Binding** | Single registered dependency configuration | `toClass()`, `toValue()`, `toProvider()`, `setScope()` |
|
|
16
|
+
| **@inject** | Decorator marking injection points | Applied to constructor parameters/properties |
|
|
17
|
+
| **MetadataRegistry** | Stores decorator metadata | Singleton accessed via `getInstance()` |
|
|
18
|
+
|
|
19
|
+
## `Container` Class
|
|
20
|
+
|
|
21
|
+
Heart of the DI system - registry managing all application resources.
|
|
22
|
+
|
|
23
|
+
**File:** `packages/core/src/helpers/inversion/container.ts`
|
|
24
|
+
|
|
25
|
+
### Key Methods
|
|
26
|
+
|
|
27
|
+
| Method | Description |
|
|
28
|
+
| :--- | :--- |
|
|
29
|
+
| **`bind<T>({ key })`** | Starts a new binding for a given key. It returns a `Binding` instance that you can use to configure the dependency. |
|
|
30
|
+
| **`get<T>({ key, isOptional })`** | Retrieves a dependency from the container. The `key` can be a string, a symbol, or an object like `{ namespace: 'services', key: 'MyService' }`. If the dependency is not found and `isOptional` is `false` (the default), it will throw an error. |
|
|
31
|
+
| **`instantiate<T>(cls)`** | Creates a new instance of a class, automatically injecting any dependencies specified in its constructor or on its properties. This is the method the container uses internally to create your controllers, services, etc. |
|
|
32
|
+
| **`findByTag({ tag })`** | Finds all bindings that have been tagged with a specific tag (e.g., `'controllers'`, `'components'`). This is used by the application to discover and initialize all registered resources of a certain type. |
|
|
33
|
+
|
|
34
|
+
## `Binding` Class
|
|
35
|
+
|
|
36
|
+
A `Binding` represents a single registered dependency in the container. It's a fluent API that allows you to specify *how* a dependency should be created and managed.
|
|
37
|
+
|
|
38
|
+
- **File:** `packages/core/src/helpers/inversion/container.ts`
|
|
39
|
+
|
|
40
|
+
### Configuration Methods
|
|
41
|
+
|
|
42
|
+
| Method | Description |
|
|
43
|
+
| :--- | :--- |
|
|
44
|
+
| **`toClass(MyClass)`** | Binds the key to a class. The container will instantiate this class (and resolve its dependencies) when the key is requested. |
|
|
45
|
+
| **`toValue(someValue)`** | Binds the key to a constant value (e.g., a configuration object, a string, a number). |
|
|
46
|
+
| **`toProvider(MyProvider)`**| Binds the key to a provider class or function. This is for dependencies that require complex creation logic. |
|
|
47
|
+
| **`setScope(scope)`** | Sets the lifecycle scope of the binding. See "Binding Scopes" below. |
|
|
48
|
+
|
|
49
|
+
### Binding Scopes
|
|
50
|
+
|
|
51
|
+
| Scope | Description |
|
|
52
|
+
| :--- | :--- |
|
|
53
|
+
| **`BindingScopes.TRANSIENT`** | (Default) A new instance of the dependency is created every time it is injected or requested from the container. |
|
|
54
|
+
| **`BindingScopes.SINGLETON`** | A single instance is created the first time it is requested, and that same instance is reused for all subsequent requests. DataSources and Components are typically singletons. |
|
|
55
|
+
|
|
56
|
+
## `@inject` Decorator
|
|
57
|
+
|
|
58
|
+
The `@inject` decorator is used to mark where dependencies should be injected.
|
|
59
|
+
|
|
60
|
+
- **File:** `packages/core/src/base/metadata/injectors.ts`
|
|
61
|
+
|
|
62
|
+
### How It Works
|
|
63
|
+
|
|
64
|
+
1. When you apply the `@inject` decorator to a constructor parameter or a class property, it uses `Reflect.metadata` to attach metadata to the class.
|
|
65
|
+
2. The metadata includes the **binding key** of the dependency to be injected.
|
|
66
|
+
3. When the `container.instantiate(MyClass)` method is called, it reads this metadata.
|
|
67
|
+
4. It then calls `container.get({ key })` for each decorated parameter/property to resolve the dependency.
|
|
68
|
+
5. Finally, it creates the instance of `MyClass`, passing the resolved dependencies to the constructor or setting them on the instance properties.
|
|
69
|
+
|
|
70
|
+
This entire process is managed by the framework when your application starts up, ensuring that all your registered classes are created with their required dependencies.
|
|
71
|
+
|
|
72
|
+
## `MetadataRegistry`
|
|
73
|
+
|
|
74
|
+
The `MetadataRegistry` is a crucial part of the DI and routing systems. It's a singleton class responsible for storing and retrieving all the metadata attached by decorators like `@inject`, `@controller`, `@get`, etc.
|
|
75
|
+
|
|
76
|
+
- **File:** `packages/helpers/src/helpers/inversion/registry.ts`
|
|
77
|
+
|
|
78
|
+
### Role in DI
|
|
79
|
+
|
|
80
|
+
- When you use a decorator (e.g., `@inject`), it calls a method on the `MetadataRegistry.getInstance()` to store information about the injection (like the binding key and target property/parameter).
|
|
81
|
+
- When the `Container` instantiates a class, it queries the `MetadataRegistry` to find out which dependencies need to be injected and where.
|
|
82
|
+
|
|
83
|
+
You typically won't interact with the `MetadataRegistry` directly, but it's the underlying mechanism that makes the decorator-based DI and routing systems work seamlessly.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Deep Dive: Models and Enrichers
|
|
2
|
+
|
|
3
|
+
Technical reference for model architecture and schema enrichers in Ignis.
|
|
4
|
+
|
|
5
|
+
**Files:**
|
|
6
|
+
- `packages/core/src/base/models/base.ts`
|
|
7
|
+
- `packages/core/src/base/models/enrichers/*.ts`
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
| Component | Purpose | Key Features |
|
|
12
|
+
|-----------|---------|--------------|
|
|
13
|
+
| **BaseEntity** | Wraps Drizzle schema | Schema encapsulation, Zod generation, `toObject()`/`toJSON()` |
|
|
14
|
+
| **Schema Enrichers** | Add common columns to tables | `generateIdColumnDefs()`, `generateTzColumnDefs()`, etc. |
|
|
15
|
+
|
|
16
|
+
## `BaseEntity` Class
|
|
17
|
+
|
|
18
|
+
Fundamental building block wrapping a Drizzle ORM schema.
|
|
19
|
+
|
|
20
|
+
**File:** `packages/core/src/base/models/base.ts`
|
|
21
|
+
|
|
22
|
+
### Purpose
|
|
23
|
+
|
|
24
|
+
| Feature | Description |
|
|
25
|
+
|---------|-------------|
|
|
26
|
+
| **Schema Encapsulation** | Holds Drizzle `pgTable` schema for consistent repository access |
|
|
27
|
+
| **Metadata** | Works with `@model` decorator to mark database entities |
|
|
28
|
+
| **Schema Generation** | Uses `drizzle-zod` to generate Zod schemas (`SELECT`, `CREATE`, `UPDATE`) |
|
|
29
|
+
| **Convenience** | Includes `toObject()` and `toJSON()` methods |
|
|
30
|
+
|
|
31
|
+
### Class Definition
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { createSchemaFactory } from 'drizzle-zod';
|
|
35
|
+
import { BaseHelper } from '../helpers';
|
|
36
|
+
import { SchemaTypes, TSchemaType, TTableSchemaWithId } from './common';
|
|
37
|
+
|
|
38
|
+
export class BaseEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId> extends BaseHelper {
|
|
39
|
+
name: string;
|
|
40
|
+
schema: Schema;
|
|
41
|
+
schemaFactory: ReturnType<typeof createSchemaFactory>;
|
|
42
|
+
|
|
43
|
+
constructor(opts: { name: string; schema: Schema }) {
|
|
44
|
+
super({ scope: opts.name });
|
|
45
|
+
this.name = opts.name;
|
|
46
|
+
this.schema = opts.schema;
|
|
47
|
+
this.schemaFactory = createSchemaFactory();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getSchema(opts: { type: TSchemaType }) {
|
|
51
|
+
switch (opts.type) {
|
|
52
|
+
case SchemaTypes.CREATE: {
|
|
53
|
+
return this.schemaFactory.createInsertSchema(this.schema);
|
|
54
|
+
}
|
|
55
|
+
case SchemaTypes.UPDATE: {
|
|
56
|
+
return this.schemaFactory.createUpdateSchema(this.schema);
|
|
57
|
+
}
|
|
58
|
+
case SchemaTypes.SELECT: {
|
|
59
|
+
return this.schemaFactory.createSelectSchema(this.schema);
|
|
60
|
+
}
|
|
61
|
+
default: {
|
|
62
|
+
throw getError({
|
|
63
|
+
message: `[getSchema] Invalid schema type | type: ${opts.type} | valid: ${[SchemaTypes.SELECT, SchemaTypes.UPDATE, SchemaTypes.CREATE]}`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
When you define a model in your application, you extend `BaseEntity`, passing your Drizzle table schema to the `super` constructor.
|
|
72
|
+
|
|
73
|
+
## Schema Enrichers
|
|
74
|
+
|
|
75
|
+
Enrichers are helper functions located in `packages/core/src/base/models/enrichers/` that return an object of Drizzle ORM column definitions. They are designed to be spread into a `pgTable` definition to quickly add common, standardized fields to your models.
|
|
76
|
+
|
|
77
|
+
### Available Enrichers
|
|
78
|
+
|
|
79
|
+
| Enricher Function | Purpose |
|
|
80
|
+
| :--- | :--- |
|
|
81
|
+
| **`generateIdColumnDefs`** | Adds a primary key `id` column (string UUID or numeric serial). |
|
|
82
|
+
| **`generateTzColumnDefs`** | Adds `createdAt` and `modifiedAt` timestamp columns with timezone support. |
|
|
83
|
+
| **`generateUserAuditColumnDefs`** | Adds `createdBy` and `modifiedBy` columns to track user audit information. |
|
|
84
|
+
| **`generateDataTypeColumnDefs`** | Adds generic data type columns (`dataType`, `nValue`, `tValue`, `bValue`, `jValue`, `boValue`) for flexible data storage. |
|
|
85
|
+
| **`generatePrincipalColumnDefs`** | Adds polymorphic fields for associating with different principal types. |
|
|
86
|
+
| **`extraUserColumns`** (from `components/auth/models/entities/user.model.ts`) | Adds common fields for a user model, such as `realm`, `status`, `type`, `activatedAt`, `lastLoginAt`, and `parentId`. |
|
|
87
|
+
|
|
88
|
+
### Example Usage
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
92
|
+
import {
|
|
93
|
+
generateIdColumnDefs,
|
|
94
|
+
generateTzColumnDefs,
|
|
95
|
+
generateUserAuditColumnDefs,
|
|
96
|
+
} from '@venizia/ignis';
|
|
97
|
+
|
|
98
|
+
export const myTable = pgTable('MyTable', {
|
|
99
|
+
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
100
|
+
...generateTzColumnDefs(),
|
|
101
|
+
...generateUserAuditColumnDefs({ created: { dataType: 'string' }, modified: { dataType: 'string' } }),
|
|
102
|
+
name: text('name').notNull(),
|
|
103
|
+
});
|
|
104
|
+
```
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Deep Dive: Repositories
|
|
2
|
+
|
|
3
|
+
Technical reference for repository classes - the data access layer in Ignis.
|
|
4
|
+
|
|
5
|
+
**Files:** `packages/core/src/base/repositories/core/*.ts`
|
|
6
|
+
|
|
7
|
+
## Quick Reference
|
|
8
|
+
|
|
9
|
+
| Class | Capabilities | Use Case |
|
|
10
|
+
|-------|--------------|----------|
|
|
11
|
+
| **AbstractRepository** | Base class with properties | Extend for custom repositories |
|
|
12
|
+
| **ReadableRepository** | Read-only operations | Views, external tables |
|
|
13
|
+
| **PersistableRepository** | Read + Write operations | Rarely used directly |
|
|
14
|
+
| **DefaultCRUDRepository** | Full CRUD operations | Standard data tables ✅ |
|
|
15
|
+
|
|
16
|
+
## `AbstractRepository`
|
|
17
|
+
|
|
18
|
+
Base class for all repositories - sets up fundamental properties and dependencies for data access.
|
|
19
|
+
|
|
20
|
+
**File:** `packages/core/src/base/repositories/core/base.ts`
|
|
21
|
+
|
|
22
|
+
### Key Properties
|
|
23
|
+
|
|
24
|
+
- `entity` (`BaseEntity`): An instance of the model class associated with this repository. It provides access to the Drizzle schema.
|
|
25
|
+
- `dataSource` (`IDataSource`): The datasource instance injected into the repository, which holds the database connection.
|
|
26
|
+
- `connector`: A getter that provides direct access to the Drizzle ORM instance from the datasource.
|
|
27
|
+
- `filterBuilder` (`DrizzleFilterBuilder`): An instance of the filter builder responsible for converting `Ignis`'s filter objects into Drizzle-compatible query options.
|
|
28
|
+
- `relations` (`{ [relationName: string]: TRelationConfig }`): A map of relation configurations defined for the entity.
|
|
29
|
+
|
|
30
|
+
### Abstract Methods
|
|
31
|
+
|
|
32
|
+
`AbstractRepository` defines the method signatures for standard CRUD operations that concrete repository classes must implement:
|
|
33
|
+
- `count()`
|
|
34
|
+
- `existsWith()`
|
|
35
|
+
- `find()`
|
|
36
|
+
- `findOne()`
|
|
37
|
+
- `findById()`
|
|
38
|
+
- `create()`
|
|
39
|
+
- `updateById()`
|
|
40
|
+
- `deleteById()`
|
|
41
|
+
- (and `...All` variants)
|
|
42
|
+
|
|
43
|
+
## `ReadableRepository`
|
|
44
|
+
|
|
45
|
+
The `ReadableRepository` provides a **read-only** implementation of the repository pattern. It is ideal for data sources that should not be modified, such as views or tables from an external system.
|
|
46
|
+
|
|
47
|
+
- **File:** `packages/core/src/base/repositories/core/readable.ts`
|
|
48
|
+
|
|
49
|
+
### Implemented Methods
|
|
50
|
+
|
|
51
|
+
`ReadableRepository` provides concrete implementations for all read operations:
|
|
52
|
+
|
|
53
|
+
- **`find(opts)`**: Returns an array of entities matching the filter.
|
|
54
|
+
- **`findOne(opts)`**: Returns the first entity matching the filter.
|
|
55
|
+
- **`findById(opts)`**: A convenience method that calls `findOne` with an ID-based `where` clause.
|
|
56
|
+
- **`count(opts)`**: Returns the number of entities matching the `where` clause.
|
|
57
|
+
- **`existsWith(opts)`**: Returns `true` if at least one entity matches the `where` clause.
|
|
58
|
+
|
|
59
|
+
### Write Operations
|
|
60
|
+
|
|
61
|
+
`ReadableRepository` throws a "NOT ALLOWED" error for all write operations (`create`, `update`, `delete`).
|
|
62
|
+
|
|
63
|
+
## `PersistableRepository`
|
|
64
|
+
|
|
65
|
+
The `PersistableRepository` extends `ReadableRepository` and adds **write operations**. It provides the core logic for creating, updating, and deleting records.
|
|
66
|
+
|
|
67
|
+
- **File:** `packages/core/src/base/repositories/core/persistable.ts`
|
|
68
|
+
|
|
69
|
+
### Implemented Methods
|
|
70
|
+
|
|
71
|
+
- `create(opts)`
|
|
72
|
+
- `createAll(opts)`
|
|
73
|
+
- `updateById(opts)`
|
|
74
|
+
- `updateAll(opts)`
|
|
75
|
+
- `deleteById(opts)`
|
|
76
|
+
- `deleteAll(opts)`
|
|
77
|
+
|
|
78
|
+
You will typically not use this class directly, but rather the `DefaultCRUDRepository`.
|
|
79
|
+
|
|
80
|
+
## `DefaultCRUDRepository`
|
|
81
|
+
|
|
82
|
+
This is the primary class you should extend for repositories that require full **Create, Read, Update, and Delete (CRUD)** capabilities. It extends `PersistableRepository` and serves as the standard, full-featured repository implementation.
|
|
83
|
+
|
|
84
|
+
- **File:** `packages/core/src/base/repositories/core/default-crud.ts`
|
|
85
|
+
|
|
86
|
+
### Example Implementation
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// src/repositories/configuration.repository.ts
|
|
90
|
+
import {
|
|
91
|
+
Configuration,
|
|
92
|
+
configurationRelations,
|
|
93
|
+
TConfigurationSchema,
|
|
94
|
+
} from '@/models/entities';
|
|
95
|
+
import { IDataSource, inject, repository, DefaultCRUDRepository } from '@venizia/ignis';
|
|
96
|
+
|
|
97
|
+
// Decorator to mark this class as a repository for DI
|
|
98
|
+
@repository({})
|
|
99
|
+
export class ConfigurationRepository extends DefaultCRUDRepository<TConfigurationSchema> {
|
|
100
|
+
constructor(
|
|
101
|
+
// Inject the configured datasource
|
|
102
|
+
@inject({ key: 'datasources.PostgresDataSource' }) dataSource: IDataSource,
|
|
103
|
+
) {
|
|
104
|
+
// Pass the datasource, the model's Entity class, and the relations definitions to the super constructor
|
|
105
|
+
super({ dataSource, entityClass: Configuration, relations: configurationRelations.definitions });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// You can add custom data access methods here
|
|
109
|
+
async findByCode(code: string): Promise<Configuration | undefined> {
|
|
110
|
+
// 'this.connector' gives you direct access to the Drizzle instance
|
|
111
|
+
const result = await this.connector.query.Configuration.findFirst({
|
|
112
|
+
where: (table, { eq }) => eq(table.code, code)
|
|
113
|
+
});
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
This architecture provides a clean and powerful abstraction for data access, separating the "how" of data fetching (Drizzle logic) from the "what" of business logic (services).
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Deep Dive: Services
|
|
2
|
+
|
|
3
|
+
Technical reference for `BaseService` - the foundation for business logic layers in Ignis.
|
|
4
|
+
|
|
5
|
+
**File:** `packages/core/src/base/services/base.ts`
|
|
6
|
+
|
|
7
|
+
## Quick Reference
|
|
8
|
+
|
|
9
|
+
| Feature | Benefit |
|
|
10
|
+
|---------|---------|
|
|
11
|
+
| **Extends `BaseHelper`** | Auto-configured scoped logger (`this.logger`) |
|
|
12
|
+
| **DI Integration** | Fits into framework's dependency injection system |
|
|
13
|
+
| **Business Logic Layer** | Bridge between Controllers and Repositories |
|
|
14
|
+
|
|
15
|
+
## `BaseService` Class
|
|
16
|
+
|
|
17
|
+
Abstract class that all application services should extend.
|
|
18
|
+
|
|
19
|
+
### Key Features
|
|
20
|
+
|
|
21
|
+
| Feature | Description |
|
|
22
|
+
| :--- | :--- |
|
|
23
|
+
| **Standardization** | Common base for all services, fits framework architecture |
|
|
24
|
+
| **Logging** | Extends `BaseHelper` - auto-configured logger at `this.logger` (scope = class name) |
|
|
25
|
+
| **Clarity** | Signals the class contains business logic |
|
|
26
|
+
|
|
27
|
+
### Class Definition
|
|
28
|
+
|
|
29
|
+
The implementation is straightforward:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { BaseHelper } from '../helpers';
|
|
33
|
+
import { IService } from './types';
|
|
34
|
+
|
|
35
|
+
export abstract class BaseService extends BaseHelper implements IService {
|
|
36
|
+
constructor(opts: { scope: string }) {
|
|
37
|
+
super({ scope: opts.scope });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## How Services Fit into the Architecture
|
|
43
|
+
|
|
44
|
+
Services are the core of your application's logic. They act as a bridge between the presentation layer (Controllers) and the data access layer (Repositories).
|
|
45
|
+
|
|
46
|
+
### Typical Service Flow
|
|
47
|
+
|
|
48
|
+
1. **Instantiated by DI Container**: When the application starts, the DI container creates instances of your services.
|
|
49
|
+
2. **Dependencies Injected**: The service's constructor receives instances of any repositories or other services it depends on.
|
|
50
|
+
3. **Called by a Controller**: An HTTP request comes into a controller, which then calls a method on a service to handle the business logic for that request.
|
|
51
|
+
4. **Orchestrates Logic**: The service method executes the business logic. This may involve:
|
|
52
|
+
- Validating input data.
|
|
53
|
+
- Calling one or more repository methods to fetch or save data.
|
|
54
|
+
- Calling other services to perform related tasks.
|
|
55
|
+
- Performing calculations or data transformations.
|
|
56
|
+
5. **Returns Data**: The service returns the result of the operation back to the controller, which then formats it into an HTTP response.
|
|
57
|
+
|
|
58
|
+
### Example
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { BaseService, inject } from '@venizia/ignis';
|
|
62
|
+
import { UserRepository } from '../repositories/user.repository';
|
|
63
|
+
import { TUser } from '../models/entities';
|
|
64
|
+
|
|
65
|
+
// 1. Service is decorated with `@injectable` (or registered via `app.service()`)
|
|
66
|
+
@injectable()
|
|
67
|
+
export class UserService extends BaseService {
|
|
68
|
+
// 2. Dependencies (like UserRepository) are injected
|
|
69
|
+
constructor(
|
|
70
|
+
@inject({ key: 'repositories.UserRepository' })
|
|
71
|
+
private userRepository: UserRepository,
|
|
72
|
+
) {
|
|
73
|
+
super({ scope: UserService.name });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 3. Method is called by a controller
|
|
77
|
+
async getUserProfile(userId: string): Promise<Partial<TUser>> {
|
|
78
|
+
this.logger.info(`Fetching profile for user ${userId}`);
|
|
79
|
+
|
|
80
|
+
// 4. Orchestrates logic: calls the repository
|
|
81
|
+
const user = await this.userRepository.findById({ id: userId });
|
|
82
|
+
|
|
83
|
+
if (!user) {
|
|
84
|
+
throw new Error('User not found');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 5. Returns transformed data
|
|
88
|
+
return {
|
|
89
|
+
id: user.id,
|
|
90
|
+
name: user.name, // Assuming a 'name' field exists
|
|
91
|
+
email: user.email,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
By adhering to this pattern, you keep your code organized, testable, and maintainable. You can easily test `UserService` by providing a mock `UserRepository` without needing a real database connection.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Authentication Component
|
|
2
|
+
|
|
3
|
+
JWT-based authentication and authorization system for Ignis applications.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Component | Purpose |
|
|
8
|
+
|-----------|---------|
|
|
9
|
+
| **AuthenticateComponent** | Main component registering auth services and controllers |
|
|
10
|
+
| **AuthenticationStrategyRegistry** | Singleton managing available auth strategies |
|
|
11
|
+
| **JWTAuthenticationStrategy** | JWT verification using `JWTTokenService` |
|
|
12
|
+
| **JWTTokenService** | Generate, verify, encrypt/decrypt JWT tokens |
|
|
13
|
+
| **IAuthService** | Interface for custom auth implementation (sign-in, sign-up) |
|
|
14
|
+
|
|
15
|
+
### Key Environment Variables
|
|
16
|
+
|
|
17
|
+
| Variable | Purpose | Required |
|
|
18
|
+
|----------|---------|----------|
|
|
19
|
+
| `APP_ENV_APPLICATION_SECRET` | Encrypt JWT payload | ✅ Yes |
|
|
20
|
+
| `APP_ENV_JWT_SECRET` | Sign and verify JWT signature | ✅ Yes |
|
|
21
|
+
| `APP_ENV_JWT_EXPIRES_IN` | Token expiration (seconds) | Optional |
|
|
22
|
+
|
|
23
|
+
## Architecture Components
|
|
24
|
+
|
|
25
|
+
- **`AuthenticateComponent`**: Registers all necessary services and controllers
|
|
26
|
+
- **`AuthenticationStrategyRegistry`**: Singleton managing authentication strategies
|
|
27
|
+
- **`JWTAuthenticationStrategy`**: JWT strategy implementation using `JWTTokenService`
|
|
28
|
+
- **`JWTTokenService`**: Generates, verifies, encrypts/decrypts JWT payloads
|
|
29
|
+
- **Protected Routes**: Use `authStrategies` in route configs to secure endpoints
|
|
30
|
+
|
|
31
|
+
## Implementation Details
|
|
32
|
+
|
|
33
|
+
### Tech Stack
|
|
34
|
+
|
|
35
|
+
- **Hono**
|
|
36
|
+
- **`jose`:** For JWT signing, verification, and encryption.
|
|
37
|
+
- **`@venizia/ignis`**: The core framework.
|
|
38
|
+
|
|
39
|
+
### Configuration
|
|
40
|
+
|
|
41
|
+
Configure the authentication feature using environment variables:
|
|
42
|
+
|
|
43
|
+
- `APP_ENV_APPLICATION_SECRET`: A secret for encrypting the JWT payload.
|
|
44
|
+
- `APP_ENV_JWT_SECRET`: The secret for signing and verifying the JWT signature.
|
|
45
|
+
- `APP_ENV_JWT_EXPIRES_IN`: The JWT expiration time in seconds.
|
|
46
|
+
|
|
47
|
+
::: danger SECURITY NOTE
|
|
48
|
+
Both `APP_ENV_APPLICATION_SECRET` and `APP_ENV_JWT_SECRET` are **mandatory**. For security purposes, you must set these to strong, unique secret values. The application will fail to start if these environment variables are missing or left empty.
|
|
49
|
+
:::
|
|
50
|
+
|
|
51
|
+
**Example `.env` file:**
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
APP_ENV_APPLICATION_SECRET=your-strong-application-secret
|
|
55
|
+
APP_ENV_JWT_SECRET=your-strong-jwt-secret
|
|
56
|
+
APP_ENV_JWT_EXPIRES_IN=86400
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Code Samples
|
|
60
|
+
|
|
61
|
+
#### 1. Registering the Authentication Component
|
|
62
|
+
|
|
63
|
+
In `src/application.ts`, register the `AuthenticateComponent` and the `JWTAuthenticationStrategy`. You also need to provide an `AuthenticationService`.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// src/application.ts
|
|
67
|
+
import {
|
|
68
|
+
AuthenticateComponent,
|
|
69
|
+
Authentication,
|
|
70
|
+
AuthenticationStrategyRegistry,
|
|
71
|
+
JWTAuthenticationStrategy,
|
|
72
|
+
BaseApplication,
|
|
73
|
+
ValueOrPromise,
|
|
74
|
+
} from '@venizia/ignis';
|
|
75
|
+
import { AuthenticationService } from './services'; // Your custom auth service
|
|
76
|
+
|
|
77
|
+
export class Application extends BaseApplication {
|
|
78
|
+
// ...
|
|
79
|
+
|
|
80
|
+
registerAuth() {
|
|
81
|
+
this.service(AuthenticationService);
|
|
82
|
+
this.component(AuthenticateComponent);
|
|
83
|
+
AuthenticationStrategyRegistry.getInstance().register({
|
|
84
|
+
container: this,
|
|
85
|
+
name: Authentication.STRATEGY_JWT,
|
|
86
|
+
strategy: JWTAuthenticationStrategy,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
preConfigure(): ValueOrPromise<void> {
|
|
91
|
+
// ...
|
|
92
|
+
this.registerAuth();
|
|
93
|
+
// ...
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### 2. Implementing an AuthenticationService
|
|
99
|
+
|
|
100
|
+
The `AuthenticateComponent` depends on a service that implements the `IAuthService` interface. You need to provide your own implementation for this service, which will contain your application's specific logic for user authentication.
|
|
101
|
+
|
|
102
|
+
Here is a minimal example of what this service might look like:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// src/services/authentication.service.ts
|
|
106
|
+
import {
|
|
107
|
+
BaseService,
|
|
108
|
+
inject,
|
|
109
|
+
IAuthService,
|
|
110
|
+
IJWTTokenPayload,
|
|
111
|
+
JWTTokenService,
|
|
112
|
+
TSignInRequest,
|
|
113
|
+
} from '@venizia/ignis';
|
|
114
|
+
import { Context } from 'hono';
|
|
115
|
+
|
|
116
|
+
export class AuthenticationService extends BaseService implements IAuthService {
|
|
117
|
+
constructor(
|
|
118
|
+
@inject({ key: 'services.JWTTokenService' }) private jwtService: JWTTokenService,
|
|
119
|
+
) {
|
|
120
|
+
super({ scope: AuthenticationService.name });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async signIn(context: Context, opts: TSignInRequest): Promise<{ token: string }> {
|
|
124
|
+
const { identifier, credential } = opts;
|
|
125
|
+
|
|
126
|
+
// --- Your custom logic here ---
|
|
127
|
+
// 1. Find the user by identifier (e.g., username or email).
|
|
128
|
+
// 2. Verify the credential (e.g., check the password).
|
|
129
|
+
// 3. If valid, create a JWT payload.
|
|
130
|
+
const user = { id: 'user-id-from-db', roles: [] }; // Dummy user
|
|
131
|
+
|
|
132
|
+
if (identifier.value !== 'test_username' || credential.value !== 'test_password') {
|
|
133
|
+
throw new Error('Invalid credentials');
|
|
134
|
+
}
|
|
135
|
+
// --- End of custom logic ---
|
|
136
|
+
|
|
137
|
+
const payload: IJWTTokenPayload = {
|
|
138
|
+
userId: user.id,
|
|
139
|
+
roles: user.roles,
|
|
140
|
+
// Add any other data you want in the token
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const token = await this.jwtService.generate({ payload });
|
|
144
|
+
return { token };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async signUp(context: Context, opts: any): Promise<any> {
|
|
148
|
+
// Implement your sign-up logic
|
|
149
|
+
throw new Error('Method not implemented.');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async changePassword(context: Context, opts: any): Promise<any> {
|
|
153
|
+
// Implement your change password logic
|
|
154
|
+
throw new Error('Method not implemented.');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This service is then registered in `application.ts` as shown in the previous step. It injects the `JWTTokenService` (provided by the `AuthenticateComponent`) to generate a token upon successful sign-in.
|
|
160
|
+
|
|
161
|
+
#### 3. Securing Routes
|
|
162
|
+
|
|
163
|
+
In your controllers, use decorator-based routing (`@get`, `@post`, etc.) with the `authStrategies` property in the `configs` object to protect endpoints. This will automatically run the necessary authentication middlewares and attach the authenticated user to the Hono `Context`, which can then be accessed type-safely using `TRouteContext`.
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// src/controllers/test.controller.ts
|
|
167
|
+
import {
|
|
168
|
+
Authentication,
|
|
169
|
+
BaseController,
|
|
170
|
+
controller,
|
|
171
|
+
get, // Or @api, @post, etc.
|
|
172
|
+
HTTP,
|
|
173
|
+
jsonResponse,
|
|
174
|
+
IJWTTokenPayload,
|
|
175
|
+
TRouteContext, // Import TRouteContext for type safety
|
|
176
|
+
} from '@venizia/ignis';
|
|
177
|
+
import { z } from '@hono/zod-openapi';
|
|
178
|
+
|
|
179
|
+
const SECURE_ROUTE_CONFIG = {
|
|
180
|
+
path: '/secure-data',
|
|
181
|
+
method: HTTP.Methods.GET,
|
|
182
|
+
authStrategies: [Authentication.STRATEGY_JWT],
|
|
183
|
+
responses: jsonResponse({
|
|
184
|
+
description: 'Test message content',
|
|
185
|
+
schema: z.object({ message: z.string() }),
|
|
186
|
+
}),
|
|
187
|
+
} as const;
|
|
188
|
+
|
|
189
|
+
@controller({ path: '/test' })
|
|
190
|
+
export class TestController extends BaseController {
|
|
191
|
+
constructor() {
|
|
192
|
+
super({
|
|
193
|
+
scope: TestController.name,
|
|
194
|
+
path: '/test',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@get({ configs: SECURE_ROUTE_CONFIG })
|
|
199
|
+
secureData(c: TRouteContext<typeof SECURE_ROUTE_CONFIG>) {
|
|
200
|
+
// 'c' is fully typed here, including c.get and c.json return type
|
|
201
|
+
const user = c.get(Authentication.CURRENT_USER) as IJWTTokenPayload | undefined;
|
|
202
|
+
return c.json({ message: `Hello, ${user?.userId || 'guest'} from protected data` });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### 4. Accessing the Current User in Context
|
|
208
|
+
|
|
209
|
+
After a route has been processed, the authenticated user's payload is available directly on the Hono `Context` object, using the `Authentication.CURRENT_USER` key.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { Context } from 'hono';
|
|
213
|
+
import { Authentication, IJWTTokenPayload } from '@venizia/ignis';
|
|
214
|
+
|
|
215
|
+
// Inside a route handler or a custom middleware
|
|
216
|
+
const user = c.get(Authentication.CURRENT_USER) as IJWTTokenPayload | undefined;
|
|
217
|
+
|
|
218
|
+
if (user) {
|
|
219
|
+
console.log('Authenticated user ID:', user.userId);
|
|
220
|
+
// You can also access roles, email, etc. from the user object
|
|
221
|
+
} else {
|
|
222
|
+
console.log('User is not authenticated or not found in context.');
|
|
223
|
+
}
|
|
224
|
+
```
|