@venizia/ignis-docs 0.0.7 → 0.0.8-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/dist/mcp-server/common/paths.d.ts +4 -2
- package/dist/mcp-server/common/paths.d.ts.map +1 -1
- package/dist/mcp-server/common/paths.js +8 -6
- package/dist/mcp-server/common/paths.js.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.js +1 -1
- package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
- package/dist/mcp-server/tools/base.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.js +7 -7
- package/dist/mcp-server/tools/docs/get-document-metadata.tool.js +3 -3
- package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-package-overview.tool.js +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.js +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
- package/package.json +9 -9
- package/wiki/best-practices/api-usage-examples.md +9 -9
- package/wiki/best-practices/architectural-patterns.md +19 -3
- package/wiki/best-practices/architecture-decisions.md +6 -6
- package/wiki/best-practices/code-style-standards/advanced-patterns.md +1 -1
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/code-style-standards/function-patterns.md +2 -2
- package/wiki/best-practices/code-style-standards/index.md +2 -2
- package/wiki/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/wiki/best-practices/code-style-standards/route-definitions.md +4 -4
- package/wiki/best-practices/data-modeling.md +1 -1
- package/wiki/best-practices/deployment-strategies.md +1 -1
- package/wiki/best-practices/error-handling.md +2 -2
- package/wiki/best-practices/performance-optimization.md +3 -3
- package/wiki/best-practices/security-guidelines.md +2 -2
- package/wiki/best-practices/troubleshooting-tips.md +1 -1
- package/wiki/{references → extensions}/components/authentication/api.md +12 -20
- package/wiki/{references → extensions}/components/authentication/errors.md +1 -1
- package/wiki/{references → extensions}/components/authentication/index.md +5 -8
- package/wiki/{references → extensions}/components/authentication/usage.md +20 -36
- package/wiki/{references → extensions}/components/authorization/api.md +62 -13
- package/wiki/{references → extensions}/components/authorization/errors.md +12 -7
- package/wiki/{references → extensions}/components/authorization/index.md +93 -6
- package/wiki/{references → extensions}/components/authorization/usage.md +42 -4
- package/wiki/{references → extensions}/components/health-check.md +5 -4
- package/wiki/{references → extensions}/components/index.md +2 -0
- package/wiki/{references → extensions}/components/mail/index.md +1 -1
- package/wiki/{references → extensions}/components/request-tracker.md +1 -1
- package/wiki/{references → extensions}/components/socket-io/api.md +2 -2
- package/wiki/{references → extensions}/components/socket-io/errors.md +2 -0
- package/wiki/{references → extensions}/components/socket-io/index.md +24 -20
- package/wiki/{references → extensions}/components/socket-io/usage.md +2 -2
- package/wiki/{references → extensions}/components/static-asset/api.md +14 -15
- package/wiki/{references → extensions}/components/static-asset/errors.md +3 -1
- package/wiki/{references → extensions}/components/static-asset/index.md +158 -89
- package/wiki/{references → extensions}/components/static-asset/usage.md +8 -5
- package/wiki/{references → extensions}/components/swagger.md +3 -3
- package/wiki/{references → extensions}/components/template/index.md +4 -4
- package/wiki/{references → extensions}/components/template/setup-page.md +1 -1
- package/wiki/{references → extensions}/components/template/single-page.md +1 -1
- package/wiki/{references → extensions}/components/websocket/api.md +7 -6
- package/wiki/{references → extensions}/components/websocket/errors.md +17 -3
- package/wiki/{references → extensions}/components/websocket/index.md +17 -11
- package/wiki/{references → extensions}/components/websocket/usage.md +2 -2
- package/wiki/{references → extensions}/helpers/crypto/index.md +1 -1
- package/wiki/{references → extensions}/helpers/env/index.md +9 -5
- package/wiki/{references → extensions}/helpers/error/index.md +2 -7
- package/wiki/{references → extensions}/helpers/index.md +18 -6
- package/wiki/{references → extensions}/helpers/kafka/admin.md +13 -1
- package/wiki/{references → extensions}/helpers/kafka/consumer.md +32 -31
- package/wiki/{references → extensions}/helpers/kafka/examples.md +20 -20
- package/wiki/{references → extensions}/helpers/kafka/index.md +61 -54
- package/wiki/{references → extensions}/helpers/kafka/producer.md +21 -20
- package/wiki/{references → extensions}/helpers/kafka/schema-registry.md +25 -25
- package/wiki/{references → extensions}/helpers/logger/index.md +2 -2
- package/wiki/{references → extensions}/helpers/queue/index.md +400 -4
- package/wiki/{references → extensions}/helpers/storage/api.md +170 -10
- package/wiki/{references → extensions}/helpers/storage/index.md +44 -8
- package/wiki/{references → extensions}/helpers/template/index.md +1 -1
- package/wiki/{references → extensions}/helpers/testing/index.md +4 -4
- package/wiki/{references → extensions}/helpers/types/index.md +63 -16
- package/wiki/{references → extensions}/helpers/websocket/index.md +1 -1
- package/wiki/extensions/index.md +48 -0
- package/wiki/guides/core-concepts/application/bootstrapping.md +55 -37
- package/wiki/guides/core-concepts/application/index.md +95 -35
- package/wiki/guides/core-concepts/components-guide.md +23 -19
- package/wiki/guides/core-concepts/components.md +34 -10
- package/wiki/guides/core-concepts/dependency-injection.md +99 -34
- package/wiki/guides/core-concepts/grpc-controllers.md +295 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +37 -19
- package/wiki/guides/core-concepts/persistent/index.md +6 -6
- package/wiki/guides/core-concepts/persistent/models.md +50 -6
- package/wiki/guides/core-concepts/persistent/repositories.md +83 -8
- package/wiki/guides/core-concepts/persistent/transactions.md +39 -8
- package/wiki/guides/core-concepts/{controllers.md → rest-controllers.md} +32 -35
- package/wiki/guides/core-concepts/services.md +19 -6
- package/wiki/guides/get-started/5-minute-quickstart.md +17 -17
- package/wiki/guides/get-started/philosophy.md +1 -1
- package/wiki/guides/index.md +2 -2
- package/wiki/guides/reference/glossary.md +7 -7
- package/wiki/guides/reference/mcp-docs-server.md +1 -1
- package/wiki/guides/tutorials/building-a-crud-api.md +45 -39
- package/wiki/guides/tutorials/complete-installation.md +74 -51
- package/wiki/guides/tutorials/ecommerce-api.md +39 -30
- package/wiki/guides/tutorials/realtime-chat.md +12 -13
- package/wiki/guides/tutorials/testing.md +2 -2
- package/wiki/index.md +4 -3
- package/wiki/references/base/application.md +341 -21
- package/wiki/references/base/bootstrapping.md +43 -13
- package/wiki/references/base/components.md +259 -8
- package/wiki/references/base/controllers.md +556 -253
- package/wiki/references/base/datasources.md +159 -79
- package/wiki/references/base/dependency-injection.md +299 -48
- package/wiki/references/base/filter-system/application-usage.md +18 -2
- package/wiki/references/base/filter-system/array-operators.md +14 -6
- package/wiki/references/base/filter-system/comparison-operators.md +9 -3
- package/wiki/references/base/filter-system/default-filter.md +28 -3
- package/wiki/references/base/filter-system/fields-order-pagination.md +17 -13
- package/wiki/references/base/filter-system/index.md +169 -11
- package/wiki/references/base/filter-system/json-filtering.md +51 -18
- package/wiki/references/base/filter-system/list-operators.md +4 -3
- package/wiki/references/base/filter-system/logical-operators.md +7 -2
- package/wiki/references/base/filter-system/null-operators.md +50 -0
- package/wiki/references/base/filter-system/quick-reference.md +82 -243
- package/wiki/references/base/filter-system/range-operators.md +7 -1
- package/wiki/references/base/filter-system/tips.md +34 -7
- package/wiki/references/base/filter-system/use-cases.md +6 -5
- package/wiki/references/base/grpc-controllers.md +984 -0
- package/wiki/references/base/index.md +32 -24
- package/wiki/references/base/middleware.md +347 -0
- package/wiki/references/base/models.md +390 -46
- package/wiki/references/base/providers.md +14 -14
- package/wiki/references/base/repositories/advanced.md +195 -69
- package/wiki/references/base/repositories/index.md +447 -12
- package/wiki/references/base/repositories/mixins.md +103 -98
- package/wiki/references/base/repositories/relations.md +129 -45
- package/wiki/references/base/repositories/soft-deletable.md +104 -23
- package/wiki/references/base/services.md +94 -14
- package/wiki/references/index.md +12 -10
- package/wiki/references/quick-reference.md +98 -65
- package/wiki/references/utilities/crypto.md +21 -4
- package/wiki/references/utilities/date.md +25 -7
- package/wiki/references/utilities/index.md +26 -24
- package/wiki/references/utilities/jsx.md +54 -54
- package/wiki/references/utilities/module.md +8 -6
- package/wiki/references/utilities/parse.md +16 -9
- package/wiki/references/utilities/performance.md +22 -7
- package/wiki/references/utilities/promise.md +19 -16
- package/wiki/references/utilities/request.md +48 -26
- package/wiki/references/utilities/schema.md +69 -6
- package/wiki/references/utilities/statuses.md +131 -140
- /package/wiki/{references → extensions}/components/mail/api.md +0 -0
- /package/wiki/{references → extensions}/components/mail/errors.md +0 -0
- /package/wiki/{references → extensions}/components/mail/usage.md +0 -0
- /package/wiki/{references → extensions}/components/template/api-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/errors-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/usage-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/cron/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/inversion/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/redis/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/template/single-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/uid/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/websocket/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/worker-thread/index.md +0 -0
- /package/wiki/{references → extensions}/src-details/mcp-server.md +0 -0
|
@@ -6,15 +6,17 @@ Models define your data structure using Drizzle ORM schemas. A model is a single
|
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
8
|
// src/models/entities/user.model.ts
|
|
9
|
-
import { BaseEntity,
|
|
10
|
-
import { pgTable } from 'drizzle-orm/pg-core';
|
|
9
|
+
import { BaseEntity, generateIdColumnDefs, generateTzColumnDefs, model } from '@venizia/ignis';
|
|
10
|
+
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
11
11
|
|
|
12
12
|
@model({ type: 'entity' })
|
|
13
13
|
export class User extends BaseEntity<typeof User.schema> {
|
|
14
14
|
// Define schema as static property
|
|
15
15
|
static override schema = pgTable('User', {
|
|
16
16
|
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
17
|
-
...
|
|
17
|
+
...generateTzColumnDefs(),
|
|
18
|
+
name: text('name').notNull(),
|
|
19
|
+
email: text('email').notNull(),
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
// Relations (empty array if none)
|
|
@@ -126,7 +128,8 @@ static override schema = pgTable('User', {
|
|
|
126
128
|
```typescript
|
|
127
129
|
static override schema = pgTable('User', {
|
|
128
130
|
...generateIdColumnDefs({ id: { dataType: 'string' } }), // id (text with UUID default)
|
|
129
|
-
...
|
|
131
|
+
...generateTzColumnDefs(), // createdAt, modifiedAt
|
|
132
|
+
...generateUserAuditColumnDefs(), // createdBy, modifiedBy
|
|
130
133
|
// ... your fields
|
|
131
134
|
});
|
|
132
135
|
```
|
|
@@ -139,7 +142,6 @@ static override schema = pgTable('User', {
|
|
|
139
142
|
| `generateTzColumnDefs()` | `createdAt`, `modifiedAt` | Track timestamps |
|
|
140
143
|
| `generateUserAuditColumnDefs()` | `createdBy`, `modifiedBy` | Track who created/updated |
|
|
141
144
|
| `generateDataTypeColumnDefs()` | `dataType`, `tValue`, `nValue`, etc. | Configuration tables |
|
|
142
|
-
| `extraUserColumns()` | Combines audit + status + type | Full-featured entities |
|
|
143
145
|
|
|
144
146
|
:::note User Audit Options
|
|
145
147
|
The `generateUserAuditColumnDefs` enricher supports an `allowAnonymous` option (default: `true`). Set to `false` to require authenticated user context and throw errors for anonymous operations:
|
|
@@ -208,6 +210,35 @@ const [fullUser] = await connector
|
|
|
208
210
|
For complete hidden properties documentation, see the [Models Reference](../../../references/base/models.md#hidden-properties).
|
|
209
211
|
:::
|
|
210
212
|
|
|
213
|
+
## Default Filter
|
|
214
|
+
|
|
215
|
+
Apply automatic filters to all repository queries. This is commonly used for soft-delete patterns:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
@model({
|
|
219
|
+
type: 'entity',
|
|
220
|
+
settings: {
|
|
221
|
+
defaultFilter: { where: { isDeleted: false } },
|
|
222
|
+
hiddenProperties: ['deletedAt'],
|
|
223
|
+
},
|
|
224
|
+
})
|
|
225
|
+
export class Article extends BaseEntity<typeof Article.schema> {
|
|
226
|
+
// ...
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The default filter is applied automatically to all read operations. Bypass it with `shouldSkipDefaultFilter: true` in the options:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// Normal query - auto-filters out soft-deleted records
|
|
234
|
+
const articles = await articleRepo.find({});
|
|
235
|
+
|
|
236
|
+
// Include deleted records
|
|
237
|
+
const allArticles = await articleRepo.find({
|
|
238
|
+
options: { shouldSkipDefaultFilter: true },
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
211
242
|
## Authorization Settings
|
|
212
243
|
|
|
213
244
|
Declare your model's authorization principal directly in `@model` settings. The decorator auto-populates `AUTHORIZATION_SUBJECT` for type-safe references in route configs:
|
|
@@ -237,9 +268,22 @@ authorize: {
|
|
|
237
268
|
```
|
|
238
269
|
|
|
239
270
|
:::tip
|
|
240
|
-
For full authorization integration details, see the [Authorization Usage Reference](../../../
|
|
271
|
+
For full authorization integration details, see the [Authorization Usage Reference](../../../extensions/components/authorization/usage#model-based-resource-references).
|
|
241
272
|
:::
|
|
242
273
|
|
|
274
|
+
## Model Metadata Types
|
|
275
|
+
|
|
276
|
+
The `@model` decorator accepts the following metadata:
|
|
277
|
+
|
|
278
|
+
| Field | Type | Description |
|
|
279
|
+
| :--- | :--- | :--- |
|
|
280
|
+
| `type` | `'entity' \| 'view'` | Whether this is a table or a database view |
|
|
281
|
+
| `tableName` | `string` | Optional explicit table name |
|
|
282
|
+
| `skipMigrate` | `boolean` | Skip this model during migrations |
|
|
283
|
+
| `settings.hiddenProperties` | `string[]` | Properties excluded from all query results |
|
|
284
|
+
| `settings.defaultFilter` | `TFilter` | Default filter auto-applied to all queries |
|
|
285
|
+
| `settings.authorize.principal` | `string` | Authorization subject name for this model |
|
|
286
|
+
|
|
243
287
|
## Model Template
|
|
244
288
|
|
|
245
289
|
```typescript
|
|
@@ -71,13 +71,26 @@ export class UserRepository extends ReadableRepository<typeof User.schema> {
|
|
|
71
71
|
> - After the first argument, you can inject any additional dependencies you need
|
|
72
72
|
> - When `@inject` is at param index 0, auto-injection is skipped
|
|
73
73
|
|
|
74
|
-
## Repository
|
|
74
|
+
## Repository Hierarchy
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
AbstractRepository (base + mixins: FieldsVisibilityMixin + DefaultFilterMixin)
|
|
78
|
+
↓
|
|
79
|
+
ReadableRepository (read-only: find, findOne, findById, count, existsWith)
|
|
80
|
+
↓
|
|
81
|
+
PersistableRepository (+ create, updateById, updateAll)
|
|
82
|
+
↓
|
|
83
|
+
DefaultCRUDRepository (+ deleteById, deleteAll) — recommended default
|
|
84
|
+
↓
|
|
85
|
+
SoftDeletableRepository (overrides delete to set deletedAt timestamp)
|
|
86
|
+
```
|
|
75
87
|
|
|
76
88
|
| Type | Description |
|
|
77
89
|
|------|-------------|
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
90
|
+
| `ReadableRepository` | Read-only operations. Write operations throw errors. |
|
|
91
|
+
| `PersistableRepository` | Read + write operations (create, update). Extends ReadableRepository. |
|
|
92
|
+
| `DefaultCRUDRepository` | Full CRUD including delete. Extends PersistableRepository. **Recommended for most use cases.** |
|
|
93
|
+
| `SoftDeletableRepository` | Extends DefaultCRUDRepository. Overrides delete to set `deletedAt` timestamp instead of physically removing records. |
|
|
81
94
|
|
|
82
95
|
## Querying Data
|
|
83
96
|
|
|
@@ -134,6 +147,40 @@ await repo.updateById({
|
|
|
134
147
|
await repo.deleteById({ id: 'uuid-here' });
|
|
135
148
|
```
|
|
136
149
|
|
|
150
|
+
## Extra Options
|
|
151
|
+
|
|
152
|
+
All repository operations accept an `options` parameter with these fields:
|
|
153
|
+
|
|
154
|
+
| Option | Type | Description |
|
|
155
|
+
| :--- | :--- | :--- |
|
|
156
|
+
| `transaction` | `ITransaction` | Transaction context for atomic operations |
|
|
157
|
+
| `shouldReturn` | `boolean` | Whether to return created/updated data (default: `true`) |
|
|
158
|
+
| `shouldQueryRange` | `boolean` | Return `{ data, range: { total, skip, limit } }` for pagination |
|
|
159
|
+
| `shouldSkipDefaultFilter` | `boolean` | Bypass the model's default filter (e.g., soft delete) |
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// Create without returning data (faster)
|
|
163
|
+
await repo.create({
|
|
164
|
+
data: { code: 'SETTING', group: 'SYSTEM' },
|
|
165
|
+
options: { shouldReturn: false },
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Bulk create multiple records
|
|
169
|
+
await repo.createAll({
|
|
170
|
+
data: [
|
|
171
|
+
{ code: 'SETTING_A', group: 'SYSTEM' },
|
|
172
|
+
{ code: 'SETTING_B', group: 'SYSTEM' },
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Query with pagination range
|
|
177
|
+
const result = await repo.find({
|
|
178
|
+
filter: { limit: 20, skip: 0 },
|
|
179
|
+
options: { shouldQueryRange: true }
|
|
180
|
+
});
|
|
181
|
+
// result = { data: [...], range: { total: 150, skip: 0, limit: 20 } }
|
|
182
|
+
```
|
|
183
|
+
|
|
137
184
|
## Querying with Relations
|
|
138
185
|
|
|
139
186
|
Use `include` to fetch related data. The relation name must match what you defined in `static relations`:
|
|
@@ -162,6 +209,34 @@ export class Application extends BaseApplication {
|
|
|
162
209
|
}
|
|
163
210
|
```
|
|
164
211
|
|
|
212
|
+
## SoftDeletableRepository
|
|
213
|
+
|
|
214
|
+
For soft-delete patterns, use `SoftDeletableRepository` which overrides delete operations to set a `deletedAt` timestamp instead of physically removing records:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { SoftDeletableRepository, repository, model, BaseEntity } from '@venizia/ignis';
|
|
218
|
+
import { pgTable, timestamp } from 'drizzle-orm/pg-core';
|
|
219
|
+
|
|
220
|
+
@model({
|
|
221
|
+
type: 'entity',
|
|
222
|
+
settings: {
|
|
223
|
+
hiddenProperties: ['deletedAt'],
|
|
224
|
+
defaultFilter: { where: { deletedAt: null } },
|
|
225
|
+
},
|
|
226
|
+
})
|
|
227
|
+
export class Category extends BaseEntity<typeof Category.schema> {
|
|
228
|
+
static override schema = pgTable('Category', {
|
|
229
|
+
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
230
|
+
...generateTzColumnDefs(),
|
|
231
|
+
deletedAt: timestamp('deleted_at', { withTimezone: true }),
|
|
232
|
+
name: text('name').notNull(),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@repository({ dataSource: PostgresDataSource, model: Category })
|
|
237
|
+
export class CategoryRepository extends SoftDeletableRepository<typeof Category.schema> {}
|
|
238
|
+
```
|
|
239
|
+
|
|
165
240
|
## Repository Template
|
|
166
241
|
|
|
167
242
|
```typescript
|
|
@@ -177,7 +252,7 @@ export class MyModelRepository extends DefaultCRUDRepository<typeof MyModel.sche
|
|
|
177
252
|
|
|
178
253
|
### Performance: Core API Optimization
|
|
179
254
|
|
|
180
|
-
Ignis automatically optimizes "flat" queries (no relations, no field selection) by using Drizzle's Core API. This provides **~15-20% faster** queries for simple reads.
|
|
255
|
+
Ignis automatically optimizes "flat" queries (no relations, no field selection) by using Drizzle's Core API. This provides **~15-20% faster** queries for simple reads. The `canUseCoreAPI()` method on `ReadableRepository` determines when this optimization applies.
|
|
181
256
|
|
|
182
257
|
### Modular Persistence with Components
|
|
183
258
|
|
|
@@ -186,9 +261,9 @@ Bundle related persistence resources into Components for better organization:
|
|
|
186
261
|
```typescript
|
|
187
262
|
export class UserManagementComponent extends BaseComponent {
|
|
188
263
|
override binding() {
|
|
189
|
-
this.
|
|
190
|
-
this.
|
|
191
|
-
this.
|
|
264
|
+
this._application.dataSource(PostgresDataSource);
|
|
265
|
+
this._application.repository(UserRepository);
|
|
266
|
+
this._application.repository(ProfileRepository);
|
|
192
267
|
}
|
|
193
268
|
}
|
|
194
269
|
```
|
|
@@ -4,10 +4,10 @@ Ignis supports explicit transaction objects that can be passed across multiple s
|
|
|
4
4
|
|
|
5
5
|
## Using Transactions
|
|
6
6
|
|
|
7
|
-
To use transactions, start one from a repository
|
|
7
|
+
To use transactions, start one from a datasource (via the repository's `beginTransaction` method), and then pass it to subsequent operations via the `options` parameter.
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
|
-
// 1. Start a transaction
|
|
10
|
+
// 1. Start a transaction from the datasource (accessed through a repository)
|
|
11
11
|
const tx = await userRepo.beginTransaction({
|
|
12
12
|
isolationLevel: 'SERIALIZABLE' // Optional, defaults to 'READ COMMITTED'
|
|
13
13
|
});
|
|
@@ -38,6 +38,20 @@ try {
|
|
|
38
38
|
}
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## Transaction Object
|
|
42
|
+
|
|
43
|
+
The transaction object returned by `beginTransaction()` has the following properties:
|
|
44
|
+
|
|
45
|
+
| Property/Method | Type | Description |
|
|
46
|
+
| :--- | :--- | :--- |
|
|
47
|
+
| `connector` | `TNodePostgresConnector` | A Drizzle connector bound to the transaction's database client |
|
|
48
|
+
| `isolationLevel` | `TIsolationLevel` | The isolation level of this transaction |
|
|
49
|
+
| `isActive` | `boolean` | Whether the transaction is still active (not yet committed/rolled back) |
|
|
50
|
+
| `commit()` | `Promise<void>` | Commit the transaction and release the connection |
|
|
51
|
+
| `rollback()` | `Promise<void>` | Rollback the transaction and release the connection |
|
|
52
|
+
|
|
53
|
+
Calling `commit()` or `rollback()` on an already-ended transaction throws an error.
|
|
54
|
+
|
|
41
55
|
## Isolation Levels
|
|
42
56
|
|
|
43
57
|
Ignis supports standard PostgreSQL isolation levels:
|
|
@@ -48,10 +62,13 @@ Ignis supports standard PostgreSQL isolation levels:
|
|
|
48
62
|
| `REPEATABLE READ` | Queries see a snapshot as of the start of the transaction. | Reports, consistent reads across multiple queries. |
|
|
49
63
|
| `SERIALIZABLE` | Strictest level. Emulates serial execution. | Financial transactions, critical data integrity. |
|
|
50
64
|
|
|
65
|
+
> [!NOTE]
|
|
66
|
+
> Ignis only supports these three levels. `READ UNCOMMITTED` is **not** accepted — PostgreSQL treats it as `READ COMMITTED` anyway, so Ignis omits it to avoid confusion.
|
|
67
|
+
|
|
51
68
|
## Best Practices
|
|
52
69
|
|
|
53
|
-
1. **Always use `try...catch
|
|
54
|
-
2. **Keep it short**: Long-running transactions hold database
|
|
70
|
+
1. **Always use `try...catch`**: Ensure `rollback()` is called on error to release the connection back to the pool.
|
|
71
|
+
2. **Keep it short**: Long-running transactions hold database connections from the pool and can cause connection exhaustion.
|
|
55
72
|
3. **Pass explicit options**: When calling other services inside a transaction, ensure they accept and use the `transaction` option.
|
|
56
73
|
|
|
57
74
|
```typescript
|
|
@@ -109,19 +126,19 @@ export class OrderService extends BaseService {
|
|
|
109
126
|
|
|
110
127
|
```typescript
|
|
111
128
|
@controller({ path: '/orders' })
|
|
112
|
-
export class OrderController extends
|
|
129
|
+
export class OrderController extends BaseRestController {
|
|
113
130
|
constructor(
|
|
114
131
|
@inject({ key: 'repositories.OrderRepository' })
|
|
115
132
|
private _orderRepository: OrderRepository,
|
|
116
133
|
@inject({ key: 'services.OrderService' })
|
|
117
134
|
private _orderService: OrderService,
|
|
118
135
|
) {
|
|
119
|
-
super({ scope: OrderController.name
|
|
136
|
+
super({ scope: OrderController.name });
|
|
120
137
|
}
|
|
121
138
|
|
|
122
139
|
@post({ configs: OrderRoutes.CREATE })
|
|
123
140
|
async createOrder(c: TRouteContext) {
|
|
124
|
-
const body = c.req.valid<{ order: any; items: any[] }>('json');
|
|
141
|
+
const body = c.req.valid<{ order: any; items: any[] }>('json');
|
|
125
142
|
|
|
126
143
|
const tx = await this._orderRepository.beginTransaction({
|
|
127
144
|
isolationLevel: 'SERIALIZABLE',
|
|
@@ -144,6 +161,20 @@ export class OrderController extends BaseController {
|
|
|
144
161
|
}
|
|
145
162
|
```
|
|
146
163
|
|
|
164
|
+
## How Transactions Work Internally
|
|
165
|
+
|
|
166
|
+
When you pass a `transaction` option to a repository method, the repository uses the transaction's `connector` (a Drizzle instance bound to the transaction's `PoolClient`) instead of the default datasource connector. This ensures all operations within the transaction use the same database connection and see a consistent view of the data.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Inside AbstractRepository (simplified)
|
|
170
|
+
protected resolveConnector(opts?: { transaction?: ITransaction }) {
|
|
171
|
+
if (opts?.transaction) {
|
|
172
|
+
return opts.transaction.connector;
|
|
173
|
+
}
|
|
174
|
+
return this.dataSource.connector;
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
147
178
|
> **Deep Dive:** See [Repository Reference](../../../references/base/repositories/) for more transaction options and patterns.
|
|
148
179
|
|
|
149
180
|
## See Also
|
|
@@ -151,7 +182,7 @@ export class OrderController extends BaseController {
|
|
|
151
182
|
- **Related Concepts:**
|
|
152
183
|
- [Repositories](/guides/core-concepts/persistent/repositories) - Provide transaction API
|
|
153
184
|
- [Services](/guides/core-concepts/services) - Orchestrate transactional operations
|
|
154
|
-
- [Controllers](/guides/core-concepts/controllers) - Initiate transactions from HTTP handlers
|
|
185
|
+
- [Controllers](/guides/core-concepts/rest-controllers) - Initiate transactions from HTTP handlers
|
|
155
186
|
- [DataSources](/guides/core-concepts/persistent/datasources) - Database connections
|
|
156
187
|
|
|
157
188
|
- **References:**
|
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
# Controllers
|
|
1
|
+
# REST Controllers
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
REST controllers handle incoming HTTP requests and return JSON responses -- they are your API endpoints. This is the default transport in Ignis and covers the majority of use cases.
|
|
4
4
|
|
|
5
|
-
> **Deep Dive:** See [Controllers Reference](../../references/base/controllers.md) for
|
|
5
|
+
> **Deep Dive:** See [REST Controllers Reference](../../references/base/controllers.md) for the complete API.
|
|
6
6
|
|
|
7
|
-
## Creating a Controller
|
|
7
|
+
## Creating a REST Controller
|
|
8
8
|
|
|
9
|
-
Extend `
|
|
9
|
+
Extend `BaseRestController` and use decorators to define routes:
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
|
-
import {
|
|
12
|
+
import { BaseRestController, controller, get, jsonResponse, z, TRouteContext } from '@venizia/ignis';
|
|
13
13
|
import { HTTP } from '@venizia/ignis-helpers';
|
|
14
|
-
import { Context } from 'hono';
|
|
15
14
|
|
|
16
15
|
@controller({ path: '/users' })
|
|
17
|
-
export class UserController extends
|
|
16
|
+
export class UserController extends BaseRestController {
|
|
18
17
|
constructor() {
|
|
19
|
-
//
|
|
20
|
-
super({ scope: UserController.name
|
|
18
|
+
// scope is used for logging; path can be omitted if @controller provides it
|
|
19
|
+
super({ scope: UserController.name });
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
@get({
|
|
@@ -29,12 +28,13 @@ export class UserController extends BaseController {
|
|
|
29
28
|
}),
|
|
30
29
|
},
|
|
31
30
|
})
|
|
32
|
-
getAllUsers(c:
|
|
31
|
+
getAllUsers(c: TRouteContext) {
|
|
33
32
|
return c.json([{ id: '1', name: 'John Doe' }], HTTP.ResultCodes.RS_2.Ok);
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
```
|
|
37
|
-
|
|
36
|
+
|
|
37
|
+
Notice that the `binding()` method is no longer needed when using decorators. The path is resolved from the `@controller` decorator metadata first, then falls back to the constructor `path` option.
|
|
38
38
|
|
|
39
39
|
## Controller Lifecycle
|
|
40
40
|
|
|
@@ -42,8 +42,8 @@ Controllers have a simple and predictable lifecycle managed by the application.
|
|
|
42
42
|
|
|
43
43
|
| Stage | Method | Description |
|
|
44
44
|
| :--- | :--- | :--- |
|
|
45
|
-
| **1. Instantiation** | `constructor(opts)` | The controller is created by the DI container. Dependencies are injected, and
|
|
46
|
-
| **2. Configuration**| `
|
|
45
|
+
| **1. Instantiation** | `constructor(opts)` | The controller is created by the DI container. Dependencies are injected, and `super()` initializes the internal `OpenAPIHono` router. The `path` is resolved from `@controller` decorator metadata, falling back to the constructor option. |
|
|
46
|
+
| **2. Configuration**| `configure()` | The application calls `configure()` which invokes `binding()` (for manual routes) and `registerRoutesFromRegistry()` (for decorator routes). This method is **idempotent** -- calling it multiple times has no effect after the first call. |
|
|
47
47
|
|
|
48
48
|
## Defining Routes with Decorators (Recommended)
|
|
49
49
|
|
|
@@ -71,14 +71,13 @@ The `opts` object contains a `configs` property that defines the route's path, r
|
|
|
71
71
|
For optimal organization and type safety, define your route configurations in a constant with `as const`. This allows TypeScript to precisely infer the types for your request data and expected responses within your handler methods.
|
|
72
72
|
|
|
73
73
|
```typescript
|
|
74
|
-
import {
|
|
74
|
+
import { BaseRestController, controller, get, post, jsonContent, jsonResponse, TRouteContext } from '@venizia/ignis';
|
|
75
75
|
import { HTTP } from '@venizia/ignis-helpers';
|
|
76
76
|
import { z } from '@hono/zod-openapi';
|
|
77
77
|
|
|
78
78
|
// Define route configs as const for type inference
|
|
79
79
|
const TestRoutes = {
|
|
80
80
|
GET_DATA: {
|
|
81
|
-
method: HTTP.Methods.GET,
|
|
82
81
|
path: '/',
|
|
83
82
|
responses: jsonResponse({
|
|
84
83
|
description: 'A simple message',
|
|
@@ -86,7 +85,6 @@ const TestRoutes = {
|
|
|
86
85
|
}),
|
|
87
86
|
},
|
|
88
87
|
CREATE_ITEM: {
|
|
89
|
-
method: HTTP.Methods.POST,
|
|
90
88
|
path: '/',
|
|
91
89
|
request: {
|
|
92
90
|
body: jsonContent({
|
|
@@ -102,9 +100,9 @@ const TestRoutes = {
|
|
|
102
100
|
} as const; // Crucial for strict type inference!
|
|
103
101
|
|
|
104
102
|
@controller({ path: '/my-items' })
|
|
105
|
-
export class MyItemsController extends
|
|
103
|
+
export class MyItemsController extends BaseRestController {
|
|
106
104
|
constructor() {
|
|
107
|
-
super({ scope: MyItemsController.name
|
|
105
|
+
super({ scope: MyItemsController.name });
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
@get({ configs: TestRoutes.GET_DATA })
|
|
@@ -171,7 +169,7 @@ import { HTTP } from '@venizia/ignis-helpers';
|
|
|
171
169
|
const GetUsersRoute = {
|
|
172
170
|
path: '/',
|
|
173
171
|
method: 'get',
|
|
174
|
-
|
|
172
|
+
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
175
173
|
responses: jsonResponse({
|
|
176
174
|
description: 'List of all users',
|
|
177
175
|
schema: z.array(z.object({ id: z.number(), name: z.string() })),
|
|
@@ -197,7 +195,7 @@ import { HTTP } from '@venizia/ignis-helpers';
|
|
|
197
195
|
// ... inside the binding() method
|
|
198
196
|
|
|
199
197
|
const GetUserByIdRoute = {
|
|
200
|
-
path: '
|
|
198
|
+
path: '/{id}',
|
|
201
199
|
method: 'get',
|
|
202
200
|
responses: jsonResponse({
|
|
203
201
|
description: 'A single user',
|
|
@@ -220,7 +218,7 @@ this.bindRoute({
|
|
|
220
218
|
For standard CRUD (Create, Read, Update, Delete) operations, `Ignis` provides a `ControllerFactory` that can generate a full-featured controller for any given entity. This significantly reduces boilerplate code.
|
|
221
219
|
|
|
222
220
|
```typescript
|
|
223
|
-
// src/controllers/configuration.controller.ts (Example from @examples/vert)
|
|
221
|
+
// src/controllers/configuration/configuration.controller.ts (Example from @examples/vert)
|
|
224
222
|
import { Configuration } from '@/models';
|
|
225
223
|
import { ConfigurationRepository } from '@/repositories';
|
|
226
224
|
import {
|
|
@@ -238,7 +236,7 @@ const _Controller = ControllerFactory.defineCrudController({
|
|
|
238
236
|
controller: {
|
|
239
237
|
name: 'ConfigurationController',
|
|
240
238
|
basePath: BASE_PATH,
|
|
241
|
-
isStrict: true,
|
|
239
|
+
isStrict: { path: true, requestSchema: true },
|
|
242
240
|
},
|
|
243
241
|
entity: () => Configuration, // Provide a resolver for your entity class
|
|
244
242
|
});
|
|
@@ -264,12 +262,12 @@ The `ControllerFactory.defineCrudController` method automatically sets up the fo
|
|
|
264
262
|
| :--- | :--- | :--- | :--- |
|
|
265
263
|
| `count` | `GET` | `/count` | Get the number of records matching a filter. |
|
|
266
264
|
| `find` | `GET` | `/` | Retrieve all records matching a filter. |
|
|
267
|
-
| `findById` | `GET` |
|
|
265
|
+
| `findById` | `GET` | `/{id}` | Retrieve a single record by its ID. |
|
|
268
266
|
| `findOne` | `GET` | `/find-one` | Retrieve a single record matching a filter. |
|
|
269
267
|
| `create` | `POST` | `/` | Create a new record. |
|
|
270
|
-
| `updateById` | `PATCH` |
|
|
268
|
+
| `updateById` | `PATCH` | `/{id}` | Update a single record by its ID. |
|
|
271
269
|
| `updateBy` | `PATCH` | `/` | Update multiple records matching a `where` filter. |
|
|
272
|
-
| `deleteById` | `DELETE` |
|
|
270
|
+
| `deleteById` | `DELETE` | `/{id}` | Delete a single record by its ID. |
|
|
273
271
|
| `deleteBy` | `DELETE` | `/` | Delete multiple records matching a `where` filter. |
|
|
274
272
|
|
|
275
273
|
:::info Customization
|
|
@@ -357,7 +355,7 @@ export const MainLayout: FC<PropsWithChildren<MainLayoutProps>> = ({ title, chil
|
|
|
357
355
|
</header>
|
|
358
356
|
<main>{children}</main>
|
|
359
357
|
<footer>
|
|
360
|
-
<p
|
|
358
|
+
<p>© 2025 My App</p>
|
|
361
359
|
</footer>
|
|
362
360
|
</body>
|
|
363
361
|
</html>
|
|
@@ -373,7 +371,7 @@ When you define Zod schemas in your route's `request` configuration (whether wit
|
|
|
373
371
|
|
|
374
372
|
```typescript
|
|
375
373
|
import { z } from '@hono/zod-openapi';
|
|
376
|
-
import { jsonContent, put } from '@venizia/ignis';
|
|
374
|
+
import { jsonContent, put, TRouteContext } from '@venizia/ignis';
|
|
377
375
|
import { HTTP } from '@venizia/ignis-helpers';
|
|
378
376
|
|
|
379
377
|
// ... inside a controller class
|
|
@@ -382,8 +380,7 @@ const UserSchema = z.object({ name: z.string(), email: z.string().email() });
|
|
|
382
380
|
|
|
383
381
|
@put({
|
|
384
382
|
configs: {
|
|
385
|
-
path: '
|
|
386
|
-
method: 'put',
|
|
383
|
+
path: '/{id}',
|
|
387
384
|
request: {
|
|
388
385
|
params: z.object({ id: z.string() }),
|
|
389
386
|
query: z.object({ notify: z.string().optional() }),
|
|
@@ -392,7 +389,7 @@ const UserSchema = z.object({ name: z.string(), email: z.string().email() });
|
|
|
392
389
|
// ... responses
|
|
393
390
|
},
|
|
394
391
|
})
|
|
395
|
-
updateUser(c:
|
|
392
|
+
updateUser(c: TRouteContext) {
|
|
396
393
|
// Access validated data from the request
|
|
397
394
|
const { id } = c.req.valid('param');
|
|
398
395
|
const { notify } = c.req.valid('query');
|
|
@@ -417,8 +414,7 @@ import { HTTP } from '@venizia/ignis-helpers';
|
|
|
417
414
|
// ... inside a controller class
|
|
418
415
|
|
|
419
416
|
const UpdateUserConfig = {
|
|
420
|
-
path: '
|
|
421
|
-
method: 'put',
|
|
417
|
+
path: '/{id}',
|
|
422
418
|
request: {
|
|
423
419
|
params: z.object({ id: z.string() }),
|
|
424
420
|
query: z.object({ notify: z.string().optional() }),
|
|
@@ -453,11 +449,12 @@ Using `TRouteContext` provides a typed context object. By using `c.req.valid<T>(
|
|
|
453
449
|
- [Application](/guides/core-concepts/application/) - Registering controllers
|
|
454
450
|
- [Services](/guides/core-concepts/services) - Business logic layer called by controllers
|
|
455
451
|
- [Dependency Injection](/guides/core-concepts/dependency-injection) - Injecting services into controllers
|
|
452
|
+
- [gRPC Controllers](/guides/core-concepts/grpc-controllers) - RPC-based controllers
|
|
456
453
|
|
|
457
454
|
- **References:**
|
|
458
|
-
- [
|
|
455
|
+
- [BaseRestController API](/references/base/controllers) - Complete REST controller API reference
|
|
459
456
|
- [Middlewares](/references/base/middlewares) - Request interceptors
|
|
460
|
-
- [Swagger Component](/
|
|
457
|
+
- [Swagger Component](/extensions/components/swagger) - Auto-generate API docs
|
|
461
458
|
- [Schema Utilities](/references/utilities/schema) - Request/response helpers
|
|
462
459
|
|
|
463
460
|
- **Tutorials:**
|
|
@@ -15,11 +15,10 @@ Services contain the core business logic of your application. They orchestrate t
|
|
|
15
15
|
|
|
16
16
|
### Creating a Service
|
|
17
17
|
|
|
18
|
-
To create a service,
|
|
18
|
+
To create a service, extend the `BaseService` class and inject the repositories or other services it depends on.
|
|
19
19
|
|
|
20
20
|
```typescript
|
|
21
21
|
import { BaseService, inject } from '@venizia/ignis';
|
|
22
|
-
import { getError } from '@venizia/ignis-helpers';
|
|
23
22
|
import { ConfigurationRepository } from '../repositories';
|
|
24
23
|
import { UserRepository } from '../repositories';
|
|
25
24
|
import { LoggingService } from './logging.service'; // Example of another service
|
|
@@ -27,9 +26,9 @@ import { TConfiguration } from '../models/entities';
|
|
|
27
26
|
|
|
28
27
|
export class ConfigurationService extends BaseService {
|
|
29
28
|
constructor(
|
|
30
|
-
@inject({ key: 'repositories.ConfigurationRepository' })
|
|
29
|
+
@inject({ key: 'repositories.ConfigurationRepository' })
|
|
31
30
|
private configurationRepository: ConfigurationRepository,
|
|
32
|
-
@inject({ key: 'repositories.UserRepository' })
|
|
31
|
+
@inject({ key: 'repositories.UserRepository' })
|
|
33
32
|
private userRepository: UserRepository,
|
|
34
33
|
@inject({ key: 'services.LoggingService' })
|
|
35
34
|
private loggingService: LoggingService, // Injecting another service for reuse
|
|
@@ -49,6 +48,20 @@ export class ConfigurationService extends BaseService {
|
|
|
49
48
|
// ...
|
|
50
49
|
```
|
|
51
50
|
|
|
51
|
+
### BaseService API
|
|
52
|
+
|
|
53
|
+
`BaseService` is intentionally minimal. It extends `BaseHelper` to provide scoped logging:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
export abstract class BaseService extends BaseHelper implements IService {
|
|
57
|
+
constructor(opts: { scope: string }) {
|
|
58
|
+
super({ scope: opts.scope });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
There is no built-in CRUD service -- implement business logic directly in your service methods. This keeps the service layer focused on your domain-specific operations rather than generic data access patterns (which belong in repositories).
|
|
64
|
+
|
|
52
65
|
## How Services Fit into the Architecture
|
|
53
66
|
|
|
54
67
|
Services act as the primary layer for business logic, sitting between controllers and repositories. While controllers are the typical entry point, **services can also inject and call other services**. This enables powerful logic reuse and allows you to build complex use cases by composing smaller, specialized services.
|
|
@@ -88,14 +101,14 @@ This layered architecture makes your application:
|
|
|
88
101
|
## See Also
|
|
89
102
|
|
|
90
103
|
- **Related Concepts:**
|
|
91
|
-
- [Controllers](/guides/core-concepts/controllers) - Call services to handle requests
|
|
104
|
+
- [Controllers](/guides/core-concepts/rest-controllers) - Call services to handle requests
|
|
92
105
|
- [Repositories](/guides/core-concepts/persistent/repositories) - Data access layer used by services
|
|
93
106
|
- [Dependency Injection](/guides/core-concepts/dependency-injection) - Injecting dependencies into services
|
|
94
107
|
|
|
95
108
|
- **References:**
|
|
96
109
|
- [BaseService API](/references/base/services) - Complete API reference
|
|
97
110
|
- [Providers](/references/base/providers) - Factory pattern for runtime instantiation
|
|
98
|
-
- [Logger Helper](/
|
|
111
|
+
- [Logger Helper](/extensions/helpers/logger/) - Logging in services
|
|
99
112
|
|
|
100
113
|
- **Best Practices:**
|
|
101
114
|
- [Architectural Patterns](/best-practices/architectural-patterns) - Service layer design
|