@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
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
# Data Modeling
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
IGNIS streamlines data modeling with Drizzle ORM by providing powerful helpers and "enrichers" that reduce boilerplate code for common schema patterns.
|
|
4
|
+
|
|
5
|
+
> [!NOTE] Scope: PostgreSQL connector
|
|
6
|
+
> This page covers Drizzle-backed models (`BasePostgresEntity`; the legacy `BaseEntity` export is a compatibility alias for the same class) and enrichers, which are specific to relational tables. Search documents (typesense connector) use `defineSearchCollection` instead - see [Search & Typesense](/guides/core-concepts/persistent/search-typesense). See [Connectors](/references/base/connectors) for how the engine-neutral `AbstractEntity` relates to each connector's concrete entity class.
|
|
4
7
|
|
|
5
8
|
## 1. Base Entity
|
|
6
9
|
|
|
7
|
-
All entity models should extend `
|
|
10
|
+
All PostgreSQL entity models should extend `BasePostgresEntity`. This provides integration with the framework's repository layer and automatic schema generation support.
|
|
8
11
|
|
|
9
12
|
The recommended pattern is to define the schema and relations as **static properties** on the class. This keeps the definition self-contained and enables powerful type inference.
|
|
10
13
|
|
|
11
14
|
**Example (`src/models/entities/user.model.ts`):**
|
|
12
15
|
|
|
13
16
|
```typescript
|
|
14
|
-
import {
|
|
17
|
+
import { BasePostgresEntity, extraUserColumns, generateIdColumnDefs, model } from '@venizia/ignis';
|
|
15
18
|
import { pgTable } from 'drizzle-orm/pg-core';
|
|
16
19
|
|
|
17
20
|
@model({ type: 'entity' })
|
|
18
|
-
export class User extends
|
|
21
|
+
export class User extends BasePostgresEntity<typeof User.schema> {
|
|
19
22
|
// 1. Define schema as a static property
|
|
20
23
|
static override schema = pgTable('User', {
|
|
21
24
|
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
@@ -29,7 +32,7 @@ export class User extends BaseEntity<typeof User.schema> {
|
|
|
29
32
|
|
|
30
33
|
## 2. Schema Enrichers
|
|
31
34
|
|
|
32
|
-
Instead of manually defining common columns like primary keys, timestamps, or audit fields in every table, use
|
|
35
|
+
Instead of manually defining common columns like primary keys, timestamps, or audit fields in every table, use IGNIS "enrichers".
|
|
33
36
|
|
|
34
37
|
**Available Enrichers:**
|
|
35
38
|
|
|
@@ -50,7 +53,7 @@ import {
|
|
|
50
53
|
generateTzColumnDefs,
|
|
51
54
|
generateUserAuditColumnDefs,
|
|
52
55
|
} from '@venizia/ignis';
|
|
53
|
-
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
56
|
+
import { pgTable, text, unique } from 'drizzle-orm/pg-core';
|
|
54
57
|
|
|
55
58
|
export const configurationTable = pgTable(
|
|
56
59
|
'Configuration',
|
|
@@ -151,7 +154,7 @@ export const commentTable = pgTable('Comment', {
|
|
|
151
154
|
**Querying polymorphic relations:**
|
|
152
155
|
```typescript
|
|
153
156
|
// Find all comments on a specific post
|
|
154
|
-
const comments = await
|
|
157
|
+
const comments = await commentRepository.find({
|
|
155
158
|
filter: {
|
|
156
159
|
where: {
|
|
157
160
|
commentableType: 'Post',
|
|
@@ -161,7 +164,7 @@ const comments = await commentRepo.find({
|
|
|
161
164
|
});
|
|
162
165
|
|
|
163
166
|
// Find all comments on a product
|
|
164
|
-
const productComments = await
|
|
167
|
+
const productComments = await commentRepository.find({
|
|
165
168
|
filter: {
|
|
166
169
|
where: {
|
|
167
170
|
commentableType: 'Product',
|
|
@@ -186,11 +189,11 @@ Relations are defined using the `TRelationConfig` structure within the static `r
|
|
|
186
189
|
|
|
187
190
|
**One-to-One (belongsTo):**
|
|
188
191
|
```typescript
|
|
189
|
-
import {
|
|
192
|
+
import { BasePostgresEntity, model, RelationTypes, TRelationConfig } from '@venizia/ignis';
|
|
190
193
|
import { User } from './user.model';
|
|
191
194
|
|
|
192
195
|
@model({ type: 'entity' })
|
|
193
|
-
export class Configuration extends
|
|
196
|
+
export class Configuration extends BasePostgresEntity<typeof Configuration.schema> {
|
|
194
197
|
static override schema = pgTable('Configuration', {
|
|
195
198
|
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
196
199
|
createdBy: text('created_by'),
|
|
@@ -215,7 +218,7 @@ export class Configuration extends BaseEntity<typeof Configuration.schema> {
|
|
|
215
218
|
**One-to-Many (hasMany):**
|
|
216
219
|
```typescript
|
|
217
220
|
@model({ type: 'entity' })
|
|
218
|
-
export class User extends
|
|
221
|
+
export class User extends BasePostgresEntity<typeof User.schema> {
|
|
219
222
|
static override schema = pgTable('User', {
|
|
220
223
|
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
221
224
|
name: text('name').notNull(),
|
|
@@ -248,7 +251,7 @@ export class User extends BaseEntity<typeof User.schema> {
|
|
|
248
251
|
|
|
249
252
|
```typescript
|
|
250
253
|
// Eager load single relation
|
|
251
|
-
const configs = await
|
|
254
|
+
const configs = await configurationRepository.find({
|
|
252
255
|
filter: {
|
|
253
256
|
include: [{ relation: 'creator' }],
|
|
254
257
|
},
|
|
@@ -256,7 +259,7 @@ const configs = await configRepo.find({
|
|
|
256
259
|
// Result: [{ id, code, ..., creator: { id, name, email } }]
|
|
257
260
|
|
|
258
261
|
// Eager load multiple relations
|
|
259
|
-
const users = await
|
|
262
|
+
const users = await userRepository.find({
|
|
260
263
|
filter: {
|
|
261
264
|
include: [
|
|
262
265
|
{ relation: 'posts' },
|
|
@@ -266,7 +269,7 @@ const users = await userRepo.find({
|
|
|
266
269
|
});
|
|
267
270
|
|
|
268
271
|
// Nested relations (up to 2 levels recommended)
|
|
269
|
-
const users = await
|
|
272
|
+
const users = await userRepository.find({
|
|
270
273
|
filter: {
|
|
271
274
|
include: [{
|
|
272
275
|
relation: 'posts',
|
|
@@ -283,7 +286,7 @@ const users = await userRepo.find({
|
|
|
283
286
|
|
|
284
287
|
## 4. Repositories and Auto-Discovery
|
|
285
288
|
|
|
286
|
-
|
|
289
|
+
IGNIS simplifies the connection between models, repositories, and datasources.
|
|
287
290
|
|
|
288
291
|
### DataSource Auto-Discovery
|
|
289
292
|
|
|
@@ -291,8 +294,21 @@ DataSources automatically discover their schema from the repositories that bind
|
|
|
291
294
|
|
|
292
295
|
```typescript
|
|
293
296
|
// src/datasources/postgres.datasource.ts
|
|
294
|
-
|
|
295
|
-
|
|
297
|
+
import { datasource, ValueOrPromise } from '@venizia/ignis';
|
|
298
|
+
import { BasePostgresDataSource } from '@venizia/ignis/postgres';
|
|
299
|
+
import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
|
|
300
|
+
import { Pool } from 'pg';
|
|
301
|
+
|
|
302
|
+
interface IDataSourceConfigs {
|
|
303
|
+
host: string;
|
|
304
|
+
port: number;
|
|
305
|
+
database: string;
|
|
306
|
+
user: string;
|
|
307
|
+
password: string;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@datasource({ driver: NodePostgresDriver })
|
|
311
|
+
export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
|
|
296
312
|
constructor() {
|
|
297
313
|
super({
|
|
298
314
|
name: PostgresDataSource.name,
|
|
@@ -302,9 +318,17 @@ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, I
|
|
|
302
318
|
}
|
|
303
319
|
|
|
304
320
|
override configure(): ValueOrPromise<void> {
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
321
|
+
// getSchema() automatically collects all schemas from bound repositories
|
|
322
|
+
this.logger.debug('[configure] Auto-discovered schema | Keys: %o', Object.keys(this.getSchema()));
|
|
323
|
+
|
|
324
|
+
// Keep the pool on this.client - naming NodePostgresDriver above is what wires the driver
|
|
325
|
+
// and Drizzle connector; beginTransaction() resolves its driver from this.client lazily.
|
|
326
|
+
this.client = new Pool(this.settings);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
override getConnectionString(): ValueOrPromise<string> {
|
|
330
|
+
const { host, port, user, password, database } = this.settings;
|
|
331
|
+
return `postgresql://${user}:${password}@${host}:${port}/${database}`;
|
|
308
332
|
}
|
|
309
333
|
}
|
|
310
334
|
```
|
|
@@ -356,7 +380,7 @@ Protect sensitive data by configuring properties that are excluded at the SQL le
|
|
|
356
380
|
hiddenProperties: ['password', 'secret'],
|
|
357
381
|
},
|
|
358
382
|
})
|
|
359
|
-
export class User extends
|
|
383
|
+
export class User extends BasePostgresEntity<typeof User.schema> {
|
|
360
384
|
static override schema = pgTable('User', {
|
|
361
385
|
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
362
386
|
email: text('email').notNull(),
|
|
@@ -387,7 +411,7 @@ Declare your model's authorization principal in `@model` settings to make the mo
|
|
|
387
411
|
hiddenProperties: ['password'],
|
|
388
412
|
},
|
|
389
413
|
})
|
|
390
|
-
export class User extends
|
|
414
|
+
export class User extends BasePostgresEntity<typeof User.schema> {
|
|
391
415
|
static override schema = pgTable('User', {
|
|
392
416
|
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
393
417
|
email: text('email').notNull(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Deployment Strategies
|
|
2
2
|
|
|
3
|
-
Deploy your
|
|
3
|
+
Deploy your IGNIS application reliably, securely, and efficiently.
|
|
4
4
|
|
|
5
5
|
## 1. Building for Production
|
|
6
6
|
|
|
@@ -46,7 +46,7 @@ FROM oven/bun:1-slim
|
|
|
46
46
|
WORKDIR /usr/src/app
|
|
47
47
|
|
|
48
48
|
# Copy dependency files
|
|
49
|
-
COPY package.json bun.
|
|
49
|
+
COPY package.json bun.lock ./
|
|
50
50
|
|
|
51
51
|
# Install production dependencies
|
|
52
52
|
RUN bun install --production --frozen-lockfile
|
|
@@ -109,7 +109,7 @@ services:
|
|
|
109
109
|
condition: service_started
|
|
110
110
|
restart: unless-stopped
|
|
111
111
|
healthcheck:
|
|
112
|
-
test: ["CMD", "curl", "-f", "http://localhost:3000/health
|
|
112
|
+
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
|
113
113
|
interval: 30s
|
|
114
114
|
timeout: 10s
|
|
115
115
|
retries: 3
|
|
@@ -188,7 +188,7 @@ scp dist/my-app .env user@server:/app/
|
|
|
188
188
|
```ini
|
|
189
189
|
# /etc/systemd/system/my-app.service
|
|
190
190
|
[Unit]
|
|
191
|
-
Description=My
|
|
191
|
+
Description=My IGNIS Application
|
|
192
192
|
After=network.target
|
|
193
193
|
|
|
194
194
|
[Service]
|
|
@@ -225,7 +225,7 @@ Add health check endpoint for load balancers:
|
|
|
225
225
|
this.component(HealthCheckComponent);
|
|
226
226
|
```
|
|
227
227
|
|
|
228
|
-
Access at `/health
|
|
228
|
+
Access at `/health` (default path, configurable) for liveness/readiness probes.
|
|
229
229
|
|
|
230
230
|
## 5. Build Debugging
|
|
231
231
|
|
|
@@ -273,30 +273,30 @@ Total time: 8.47s
|
|
|
273
273
|
|
|
274
274
|
### Force Update Strategy
|
|
275
275
|
|
|
276
|
-
Keep dependencies in sync with the NPM registry using the force-update script:
|
|
276
|
+
Keep dependencies in sync with the NPM registry using each package's force-update script (`packages/<name>/scripts/force-update.sh`, exposed as the `force-update` script in that package):
|
|
277
277
|
|
|
278
278
|
```bash
|
|
279
|
-
# Update to latest stable versions
|
|
280
|
-
|
|
279
|
+
# Update to latest stable versions (from a package directory)
|
|
280
|
+
bun run force-update latest
|
|
281
281
|
|
|
282
282
|
# Update to pre-release versions (for testing new features)
|
|
283
|
-
|
|
283
|
+
bun run force-update next
|
|
284
284
|
```
|
|
285
285
|
|
|
286
286
|
**What it does:**
|
|
287
|
-
1. Queries NPM registry for the specified tag (`latest` or `
|
|
288
|
-
2. Updates `package.json` with exact versions
|
|
289
|
-
3. Applies to all `@venizia/*` packages
|
|
287
|
+
1. Queries NPM registry for the specified tag (`latest`, `next`, or `highest`)
|
|
288
|
+
2. Updates the package's `@venizia/*` dependencies in `package.json` with exact versions
|
|
290
289
|
|
|
291
290
|
**When to use:**
|
|
292
291
|
| Tag | Use Case |
|
|
293
292
|
|-----|----------|
|
|
294
293
|
| `latest` | Production deployments, stable releases |
|
|
295
294
|
| `next` | Testing new features, pre-release validation |
|
|
295
|
+
| `highest` | Highest released version by semver sort |
|
|
296
296
|
|
|
297
|
-
**Makefile shortcuts:**
|
|
297
|
+
**Makefile shortcuts (repo root):**
|
|
298
298
|
```bash
|
|
299
|
-
make update #
|
|
299
|
+
make update # Reinstall deps (postinstall runs force-update)
|
|
300
300
|
make update-core # Update only @venizia/ignis
|
|
301
301
|
make update-helpers # Update only @venizia/ignis-helpers
|
|
302
302
|
```
|
|
@@ -447,7 +447,7 @@ Railway provides simple deployments with automatic builds:
|
|
|
447
447
|
},
|
|
448
448
|
"deploy": {
|
|
449
449
|
"startCommand": "bun run server:prod",
|
|
450
|
-
"healthcheckPath": "/health
|
|
450
|
+
"healthcheckPath": "/health",
|
|
451
451
|
"healthcheckTimeout": 30,
|
|
452
452
|
"restartPolicyType": "ON_FAILURE",
|
|
453
453
|
"restartPolicyMaxRetries": 3
|
|
@@ -495,7 +495,7 @@ primary_region = "sjc"
|
|
|
495
495
|
interval = 10000
|
|
496
496
|
grace_period = "5s"
|
|
497
497
|
method = "get"
|
|
498
|
-
path = "/health
|
|
498
|
+
path = "/health"
|
|
499
499
|
protocol = "http"
|
|
500
500
|
timeout = 2000
|
|
501
501
|
```
|
|
@@ -524,7 +524,7 @@ services:
|
|
|
524
524
|
generateValue: true
|
|
525
525
|
- key: APP_ENV_JWT_SECRET
|
|
526
526
|
generateValue: true
|
|
527
|
-
healthCheckPath: /health
|
|
527
|
+
healthCheckPath: /health
|
|
528
528
|
autoDeploy: true
|
|
529
529
|
|
|
530
530
|
databases:
|
|
@@ -579,13 +579,13 @@ spec:
|
|
|
579
579
|
key: db-host
|
|
580
580
|
livenessProbe:
|
|
581
581
|
httpGet:
|
|
582
|
-
path: /health
|
|
582
|
+
path: /health
|
|
583
583
|
port: 3000
|
|
584
584
|
initialDelaySeconds: 10
|
|
585
585
|
periodSeconds: 30
|
|
586
586
|
readinessProbe:
|
|
587
587
|
httpGet:
|
|
588
|
-
path: /health
|
|
588
|
+
path: /health
|
|
589
589
|
port: 3000
|
|
590
590
|
initialDelaySeconds: 5
|
|
591
591
|
periodSeconds: 10
|
|
@@ -634,7 +634,7 @@ kubectl logs -l app=ignis -f
|
|
|
634
634
|
|
|
635
635
|
### Health Check Endpoints
|
|
636
636
|
|
|
637
|
-
|
|
637
|
+
IGNIS provides built-in health checks:
|
|
638
638
|
|
|
639
639
|
```typescript
|
|
640
640
|
// In application.ts
|
|
@@ -643,9 +643,9 @@ import { HealthCheckComponent } from '@venizia/ignis';
|
|
|
643
643
|
this.component(HealthCheckComponent);
|
|
644
644
|
```
|
|
645
645
|
|
|
646
|
-
**Endpoints:**
|
|
647
|
-
- `GET /health
|
|
648
|
-
- `
|
|
646
|
+
**Endpoints (default path `/health`, configurable via component options):**
|
|
647
|
+
- `GET /health` - Basic liveness check
|
|
648
|
+
- `POST /health/ping` - Echo/ping check (send `{ "message": "..." }`, get a PONG back)
|
|
649
649
|
|
|
650
650
|
### Logging in Production
|
|
651
651
|
|
|
@@ -654,11 +654,12 @@ Configure structured logging:
|
|
|
654
654
|
```typescript
|
|
655
655
|
import { LoggerFactory } from '@venizia/ignis-helpers';
|
|
656
656
|
|
|
657
|
-
//
|
|
658
|
-
//
|
|
657
|
+
// Debug-level logs are gated by the DEBUG environment variable (DEBUG=true)
|
|
658
|
+
// and the current NODE_ENV. File output is configured via APP_ENV_LOGGER_*
|
|
659
|
+
// variables (e.g. APP_ENV_LOGGER_FOLDER_PATH, APP_ENV_LOGGER_FORMAT).
|
|
659
660
|
|
|
660
661
|
const logger = LoggerFactory.getLogger(['MyService']);
|
|
661
|
-
logger.info('Service started
|
|
662
|
+
logger.info('Service started | port: %d | env: %s', 3000, 'production');
|
|
662
663
|
```
|
|
663
664
|
|
|
664
665
|
### Metrics Collection
|
|
@@ -669,7 +670,7 @@ Add Prometheus metrics endpoint:
|
|
|
669
670
|
// src/controllers/metrics.controller.ts
|
|
670
671
|
@controller({ path: '/metrics' })
|
|
671
672
|
export class MetricsController extends BaseRestController {
|
|
672
|
-
@get({ configs: { path: '/' } })
|
|
673
|
+
@get({ configs: { path: '/', responses: { 200: { description: 'Prometheus metrics' } } } })
|
|
673
674
|
getMetrics(c: Context) {
|
|
674
675
|
return c.text(`
|
|
675
676
|
# HELP http_requests_total Total HTTP requests
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Error Handling
|
|
2
2
|
|
|
3
|
-
Comprehensive guide to handling errors gracefully in
|
|
3
|
+
Comprehensive guide to handling errors gracefully in IGNIS applications.
|
|
4
4
|
|
|
5
5
|
## Error Handling Philosophy
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ Comprehensive guide to handling errors gracefully in Ignis applications.
|
|
|
13
13
|
|
|
14
14
|
## 1. Using `getError` Helper
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
IGNIS provides `getError` for creating consistent, structured errors.
|
|
17
17
|
|
|
18
18
|
```typescript
|
|
19
19
|
import { getError, HTTP } from '@venizia/ignis-helpers';
|
|
@@ -52,7 +52,7 @@ Use the correct status code for each error type:
|
|
|
52
52
|
| 401 | `RS_4.Unauthorized` | Missing or invalid authentication |
|
|
53
53
|
| 403 | `RS_4.Forbidden` | Authenticated but insufficient permissions |
|
|
54
54
|
| 404 | `RS_4.NotFound` | Resource does not exist |
|
|
55
|
-
| 409 | `RS_4.Conflict` | Resource already exists (custom duplicate handling) |
|
|
55
|
+
| 409 | `RS_4.Conflict` | Resource already exists (custom duplicate handling); transient DB conflicts (deadlock / serialization failure, auto-handled) |
|
|
56
56
|
| 422 | `RS_4.UnprocessableEntity` | Validation failed (Zod errors) |
|
|
57
57
|
| 429 | `RS_4.TooManyRequests` | Rate limit exceeded |
|
|
58
58
|
| 500 | `RS_5.InternalServerError` | Unexpected server error |
|
|
@@ -60,7 +60,7 @@ Use the correct status code for each error type:
|
|
|
60
60
|
| 503 | `RS_5.ServiceUnavailable` | Service temporarily down |
|
|
61
61
|
|
|
62
62
|
:::tip Automatic Database Error Handling
|
|
63
|
-
Database errors in SQLSTATE
|
|
63
|
+
Database errors in SQLSTATE classes `22` (data exception), `23` (integrity constraint - unique, foreign key, not null, check, exclusion), and `44` (WITH CHECK OPTION violation) are automatically converted to HTTP 400 by the global error middleware. Transient conflicts (`40001` serialization failure, `40P01` deadlock) become HTTP 409 with a retryable message. You don't need to catch these manually. Other classes (e.g. syntax / undefined column) stay 500, and production responses are sanitized - see [Repository Layer Errors](#repository-layer-errors).
|
|
64
64
|
:::
|
|
65
65
|
|
|
66
66
|
## 3. Error Handling Patterns
|
|
@@ -73,12 +73,12 @@ import { getError, HTTP } from '@venizia/ignis-helpers';
|
|
|
73
73
|
|
|
74
74
|
export class UserService extends BaseService {
|
|
75
75
|
async createUser(data: TCreateUserRequest): Promise<TUser> {
|
|
76
|
-
// Validate business rules
|
|
77
|
-
const existingUser = await this.
|
|
76
|
+
// Validate business rules (findOne returns the record or null)
|
|
77
|
+
const existingUser = await this.userRepository.findOne({
|
|
78
78
|
filter: { where: { email: data.email } },
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
if (existingUser
|
|
81
|
+
if (existingUser) {
|
|
82
82
|
throw getError({
|
|
83
83
|
statusCode: HTTP.ResultCodes.RS_4.Conflict,
|
|
84
84
|
message: 'Email already registered',
|
|
@@ -95,13 +95,16 @@ export class UserService extends BaseService {
|
|
|
95
95
|
data.email, error.message);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
// create returns { count, data }
|
|
99
|
+
const created = await this.userRepository.create({ data });
|
|
100
|
+
return created.data;
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
async getUserOrFail(id: string): Promise<TUser> {
|
|
102
|
-
|
|
104
|
+
// findById returns the record or null (no wrapper object)
|
|
105
|
+
const user = await this.userRepository.findById({ id });
|
|
103
106
|
|
|
104
|
-
if (!user
|
|
107
|
+
if (!user) {
|
|
105
108
|
throw getError({
|
|
106
109
|
statusCode: HTTP.ResultCodes.RS_4.NotFound,
|
|
107
110
|
message: 'User not found',
|
|
@@ -109,7 +112,7 @@ export class UserService extends BaseService {
|
|
|
109
112
|
});
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
return user
|
|
115
|
+
return user;
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
```
|
|
@@ -148,33 +151,34 @@ export class UserController extends BaseRestController {
|
|
|
148
151
|
|
|
149
152
|
### Repository Layer Errors
|
|
150
153
|
|
|
151
|
-
Database errors in SQLSTATE
|
|
154
|
+
Database errors in SQLSTATE classes `22` (data exception), `23` (integrity constraint - unique, foreign key, not null, check, exclusion), and `44` (WITH CHECK OPTION violation) are **automatically handled** by the global error middleware and return HTTP 400. Transient conflicts (`40001` serialization failure, `40P01` deadlock) return HTTP 409 with a generic retryable message. Codes outside those classes (e.g. class `42` undefined column - an application/SQL bug) correctly stay 500.
|
|
152
155
|
|
|
153
156
|
**Non-production** returns the full driver context for debugging:
|
|
154
157
|
|
|
155
158
|
```json
|
|
156
159
|
{
|
|
157
160
|
"message": "Unique constraint violation\nDetail: Key (email)=(test@example.com) already exists.\nTable: User\nConstraint: UQ_User_email",
|
|
161
|
+
"messageCode": "core.system_error",
|
|
158
162
|
"statusCode": 400,
|
|
159
163
|
"requestId": "abc123"
|
|
160
164
|
}
|
|
161
165
|
```
|
|
162
166
|
|
|
163
167
|
:::warning Production sanitizes database internals
|
|
164
|
-
In production the message is the **base message only**
|
|
168
|
+
In production the message is the **base message only** - `Detail:` (which echoes row values like emails), `Table:`, and `Constraint:` are stripped, and `details.stack`/`details.cause` are omitted. Unexpected (non-client) database errors and connection failures return a generic `"Internal Server Error"`, so SQL, schema names, and connection host/port never leak. Use `requestId` + server logs to diagnose.
|
|
165
169
|
|
|
166
170
|
```json
|
|
167
|
-
{ "message": "Unique constraint violation", "statusCode": 400, "requestId": "abc123" }
|
|
171
|
+
{ "message": "Unique constraint violation", "messageCode": "core.system_error", "statusCode": 400, "requestId": "abc123" }
|
|
168
172
|
```
|
|
169
173
|
:::
|
|
170
174
|
|
|
171
175
|
You don't need to wrap repository calls in try-catch for constraint errors. If you need custom error messages, you can still handle them explicitly:
|
|
172
176
|
|
|
173
177
|
```typescript
|
|
174
|
-
import {
|
|
178
|
+
import { DefaultCRUDRepository } from '@venizia/ignis';
|
|
175
179
|
import { getError, HTTP } from '@venizia/ignis-helpers';
|
|
176
180
|
|
|
177
|
-
export class UserRepository extends
|
|
181
|
+
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
|
|
178
182
|
async createWithCustomError(data: TCreateUser): Promise<TCreateResult<TUser>> {
|
|
179
183
|
try {
|
|
180
184
|
return await this.create({ data });
|
|
@@ -194,11 +198,11 @@ export class UserRepository extends BaseRepository<typeof User.schema> {
|
|
|
194
198
|
|
|
195
199
|
## 4. Global Error Handler
|
|
196
200
|
|
|
197
|
-
|
|
201
|
+
IGNIS includes a built-in error handler. Customize behavior in your application:
|
|
198
202
|
|
|
199
203
|
```typescript
|
|
200
204
|
import { BaseApplication } from '@venizia/ignis';
|
|
201
|
-
import { ApplicationError } from '@venizia/ignis-helpers';
|
|
205
|
+
import { ApplicationError, MessageCode } from '@venizia/ignis-helpers';
|
|
202
206
|
|
|
203
207
|
export class Application extends BaseApplication {
|
|
204
208
|
override setupMiddlewares(): void {
|
|
@@ -216,6 +220,7 @@ export class Application extends BaseApplication {
|
|
|
216
220
|
return c.json({
|
|
217
221
|
statusCode: error.statusCode,
|
|
218
222
|
message: error.message,
|
|
223
|
+
messageCode: error.messageCode, // already lower-cased, never undefined
|
|
219
224
|
details: error.details,
|
|
220
225
|
requestId,
|
|
221
226
|
}, error.statusCode as StatusCode);
|
|
@@ -226,6 +231,7 @@ export class Application extends BaseApplication {
|
|
|
226
231
|
return c.json({
|
|
227
232
|
statusCode: 422,
|
|
228
233
|
message: 'Validation failed',
|
|
234
|
+
messageCode: MessageCode.DEFAULT,
|
|
229
235
|
details: { cause: error.errors },
|
|
230
236
|
requestId,
|
|
231
237
|
}, 422);
|
|
@@ -235,6 +241,7 @@ export class Application extends BaseApplication {
|
|
|
235
241
|
return c.json({
|
|
236
242
|
statusCode: 500,
|
|
237
243
|
message: 'Internal server error',
|
|
244
|
+
messageCode: MessageCode.DEFAULT,
|
|
238
245
|
requestId,
|
|
239
246
|
}, 500);
|
|
240
247
|
});
|
|
@@ -271,19 +278,23 @@ interface ErrorResponse {
|
|
|
271
278
|
{
|
|
272
279
|
"statusCode": 400,
|
|
273
280
|
"message": "Invalid request body",
|
|
281
|
+
"messageCode": "core.system_error",
|
|
274
282
|
"requestId": "abc123"
|
|
275
283
|
}
|
|
276
284
|
|
|
277
285
|
// 404 Not Found
|
|
286
|
+
// Extra keys passed to getError(...) (e.g. `details`) surface under `extra`;
|
|
287
|
+
// the top-level `details` object is reserved for middleware context (url, path, stack, cause).
|
|
278
288
|
{
|
|
279
289
|
"statusCode": 404,
|
|
280
290
|
"message": "User not found",
|
|
291
|
+
"messageCode": "core.system_error",
|
|
281
292
|
"requestId": "abc123",
|
|
282
|
-
"details": { "id": "user-uuid" }
|
|
293
|
+
"extra": { "details": { "id": "user-uuid" } }
|
|
283
294
|
}
|
|
284
295
|
|
|
285
296
|
// 422 Validation Error
|
|
286
|
-
// `message`/`messageCode` come from the first failing issue
|
|
297
|
+
// `message`/`messageCode` come from the first failing issue - its `params.code` if the schema set
|
|
287
298
|
// one, otherwise the raw Zod code (e.g. `invalid_type`, `too_small`). The full list stays in `details.cause`.
|
|
288
299
|
{
|
|
289
300
|
"statusCode": 422,
|
|
@@ -305,12 +316,25 @@ interface ErrorResponse {
|
|
|
305
316
|
{
|
|
306
317
|
"statusCode": 500,
|
|
307
318
|
"message": "Internal server error",
|
|
319
|
+
"messageCode": "core.system_error",
|
|
308
320
|
"requestId": "abc123"
|
|
309
321
|
}
|
|
310
322
|
```
|
|
311
323
|
|
|
312
324
|
## 6. Logging Errors
|
|
313
325
|
|
|
326
|
+
### `%s`, Never `%j`, for an `Error`
|
|
327
|
+
|
|
328
|
+
`message` and `stack` are non-enumerable properties on a native `Error` (and on `ApplicationError`, which extends it). `%j` serializes via `JSON.stringify`, which only visits enumerable own properties - so `logger.error('... | error: %j', error)` silently drops both `message` and `stack`, logging little more than `{}`. Always use `%s` to log an `Error` instance; reserve `%j`/`%o` for plain data objects.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// ✅ Good - %s prints message + stack
|
|
332
|
+
this.logger.error('[createOrder] Failed | error: %s', error);
|
|
333
|
+
|
|
334
|
+
// ❌ Bad - %j drops message and stack (non-enumerable)
|
|
335
|
+
this.logger.error('[createOrder] Failed | error: %j', error);
|
|
336
|
+
```
|
|
337
|
+
|
|
314
338
|
### What to Log
|
|
315
339
|
|
|
316
340
|
```typescript
|
|
@@ -358,7 +382,7 @@ this.logger.debug('[query] Executing | sql: %s | params: %j', sql, params);
|
|
|
358
382
|
```typescript
|
|
359
383
|
// ✅ Good - Errors propagate naturally with async/await
|
|
360
384
|
async function processOrder(orderId: string) {
|
|
361
|
-
const order = await
|
|
385
|
+
const order = await orderRepository.findById({ id: orderId }); // Throws if fails
|
|
362
386
|
const payment = await paymentService.charge(order); // Throws if fails
|
|
363
387
|
return payment;
|
|
364
388
|
}
|
|
@@ -402,11 +426,11 @@ this.sendNotification(userId); // If this rejects, crash!
|
|
|
402
426
|
|
|
403
427
|
```typescript
|
|
404
428
|
async function transferFunds(from: string, to: string, amount: number) {
|
|
405
|
-
const tx = await
|
|
429
|
+
const tx = await accountRepository.beginTransaction();
|
|
406
430
|
|
|
407
431
|
try {
|
|
408
|
-
await
|
|
409
|
-
await
|
|
432
|
+
await accountRepository.debit({ id: from, amount, options: { transaction: tx } });
|
|
433
|
+
await accountRepository.credit({ id: to, amount, options: { transaction: tx } });
|
|
410
434
|
|
|
411
435
|
await tx.commit();
|
|
412
436
|
return { success: true };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Best Practices
|
|
2
2
|
|
|
3
|
-
Production-ready patterns, security guidelines, and optimization strategies for building robust
|
|
3
|
+
Production-ready patterns, security guidelines, and optimization strategies for building robust IGNIS applications. These best practices are distilled from real-world experience building enterprise applications.
|
|
4
4
|
|
|
5
5
|
<div class="guide-cards">
|
|
6
6
|
|
|
@@ -112,7 +112,7 @@ request: { body: jsonContent({ schema: z.object({ email: z.string().email() }) }
|
|
|
112
112
|
// ❌ Business logic in controllers
|
|
113
113
|
@get({ configs: RouteConfigs.GET_USER })
|
|
114
114
|
async getUser(c: Context) {
|
|
115
|
-
const user = await this.
|
|
115
|
+
const user = await this.userRepository.findById({ id });
|
|
116
116
|
if (user.lastLogin < cutoff) await this.sendReminder(user); // Move to service!
|
|
117
117
|
return c.json(user);
|
|
118
118
|
}
|
|
@@ -130,7 +130,7 @@ const data: any = await fetchData(); // Use proper types!
|
|
|
130
130
|
|-------|--------|
|
|
131
131
|
| Secrets | Store in environment variables, never in code |
|
|
132
132
|
| Input | Validate with Zod schemas at API boundaries |
|
|
133
|
-
| Auth | Protect routes with `
|
|
133
|
+
| Auth | Protect routes with `authenticate: { strategies: [Authentication.STRATEGY_JWT] }` |
|
|
134
134
|
| Sensitive data | Use `hiddenProperties` in model settings |
|
|
135
135
|
| File uploads | Use `sanitizeFilename()` for all user-provided filenames |
|
|
136
136
|
| CORS | Configure allowed origins explicitly |
|
|
@@ -186,7 +186,7 @@ const data: any = await fetchData(); // Use proper types!
|
|
|
186
186
|
|-------|-------------|
|
|
187
187
|
| [Contribution Workflow](./contribution-workflow) | Git workflow, PR guidelines, code review |
|
|
188
188
|
|
|
189
|
-
> [!TIP] New to
|
|
189
|
+
> [!TIP] New to IGNIS?
|
|
190
190
|
> Start with the [Getting Started Guide](/guides/) for tutorials, then return here for production-ready patterns.
|
|
191
191
|
|
|
192
192
|
> [!WARNING] Production Deployment?
|
|
@@ -194,7 +194,7 @@ const data: any = await fetchData(); // Use proper types!
|
|
|
194
194
|
|
|
195
195
|
## See Also
|
|
196
196
|
|
|
197
|
-
- [Getting Started](/guides/) - New to
|
|
197
|
+
- [Getting Started](/guides/) - New to IGNIS? Start here
|
|
198
198
|
- [API Reference](/references/) - Detailed API documentation
|
|
199
199
|
- [Core Concepts](/guides/core-concepts/application/) - Deep dive into architecture
|
|
200
200
|
- [Changelogs](/changelogs/) - Version history and updates
|