@venizia/ignis-docs 0.0.8-3 → 0.1.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/{wiki → content}/best-practices/api-usage-examples.md +15 -12
- package/{wiki → content}/best-practices/architectural-patterns.md +70 -78
- package/{wiki → content}/best-practices/architecture-decisions.md +91 -60
- package/{wiki → content}/best-practices/code-style-standards/advanced-patterns.md +56 -44
- package/{wiki → content}/best-practices/code-style-standards/constants-configuration.md +11 -11
- package/{wiki → content}/best-practices/code-style-standards/control-flow.md +5 -2
- package/{wiki → content}/best-practices/code-style-standards/documentation.md +13 -13
- package/{wiki → content}/best-practices/code-style-standards/function-patterns.md +9 -10
- package/{wiki → content}/best-practices/code-style-standards/index.md +1 -1
- package/{wiki → content}/best-practices/code-style-standards/naming-conventions.md +10 -8
- package/{wiki → content}/best-practices/code-style-standards/route-definitions.md +30 -12
- package/{wiki → content}/best-practices/code-style-standards/tooling.md +8 -5
- package/{wiki → content}/best-practices/code-style-standards/type-safety.md +13 -12
- package/{wiki → content}/best-practices/common-pitfalls.md +56 -37
- package/{wiki → content}/best-practices/contribution-workflow.md +13 -14
- package/{wiki → content}/best-practices/data-modeling.md +44 -20
- package/{wiki → content}/best-practices/deployment-strategies.md +28 -27
- package/{wiki → content}/best-practices/error-handling.md +48 -24
- package/{wiki → content}/best-practices/index.md +5 -5
- package/{wiki → content}/best-practices/performance-optimization.md +36 -28
- package/{wiki → content}/best-practices/security-guidelines.md +52 -23
- package/{wiki → content}/best-practices/testing-strategies.md +65 -51
- package/{wiki → content}/best-practices/troubleshooting-tips.md +24 -24
- package/{wiki/extensions/components/swagger.md → content/extensions/components/api-reference.md} +40 -31
- package/{wiki → content}/extensions/components/authentication/api.md +19 -19
- package/{wiki → content}/extensions/components/authentication/errors.md +7 -7
- package/{wiki → content}/extensions/components/authentication/index.md +10 -8
- package/{wiki → content}/extensions/components/authentication/usage.md +101 -6
- package/{wiki → content}/extensions/components/authorization/api.md +45 -25
- package/{wiki → content}/extensions/components/authorization/errors.md +6 -6
- package/{wiki → content}/extensions/components/authorization/index.md +11 -10
- package/{wiki → content}/extensions/components/authorization/usage.md +21 -21
- package/{wiki → content}/extensions/components/health-check.md +1 -1
- package/{wiki → content}/extensions/components/index.md +5 -5
- package/{wiki → content}/extensions/components/mail/errors.md +15 -15
- package/{wiki → content}/extensions/components/mail/index.md +1 -2
- package/{wiki → content}/extensions/components/mail/usage.md +1 -1
- package/{wiki → content}/extensions/components/request-tracker.md +1 -1
- package/{wiki → content}/extensions/components/socket-io/api.md +9 -9
- package/{wiki → content}/extensions/components/socket-io/errors.md +5 -5
- package/{wiki → content}/extensions/components/socket-io/index.md +8 -8
- package/{wiki → content}/extensions/components/socket-io/usage.md +1 -1
- package/{wiki → content}/extensions/components/static-asset/api.md +17 -4
- package/{wiki → content}/extensions/components/static-asset/errors.md +4 -4
- package/{wiki → content}/extensions/components/static-asset/index.md +26 -28
- package/{wiki → content}/extensions/components/static-asset/usage.md +13 -12
- package/{wiki → content}/extensions/components/template/index.md +2 -2
- package/{wiki → content}/extensions/components/template/setup-page.md +1 -1
- package/{wiki → content}/extensions/components/websocket/api.md +3 -3
- package/{wiki → content}/extensions/components/websocket/errors.md +5 -5
- package/{wiki → content}/extensions/components/websocket/index.md +5 -5
- package/{wiki → content}/extensions/components/websocket/usage.md +3 -3
- package/{wiki → content}/extensions/helpers/cron/index.md +2 -2
- package/{wiki → content}/extensions/helpers/crypto/index.md +1 -1
- package/{wiki → content}/extensions/helpers/env/index.md +27 -12
- package/content/extensions/helpers/error/index.md +283 -0
- package/{wiki → content}/extensions/helpers/index.md +2 -3
- package/{wiki → content}/extensions/helpers/inversion/index.md +15 -7
- package/{wiki → content}/extensions/helpers/kafka/examples.md +1 -1
- package/{wiki → content}/extensions/helpers/logger/index.md +32 -2
- package/{wiki → content}/extensions/helpers/network/index.md +6 -0
- package/{wiki → content}/extensions/helpers/queue/index.md +14 -17
- package/content/extensions/helpers/redis/index.md +713 -0
- package/{wiki → content}/extensions/helpers/socket-io/index.md +14 -10
- package/{wiki → content}/extensions/helpers/storage/api.md +44 -8
- package/{wiki → content}/extensions/helpers/storage/index.md +43 -7
- package/{wiki → content}/extensions/helpers/template/index.md +6 -3
- package/{wiki → content}/extensions/helpers/types/index.md +11 -8
- package/{wiki → content}/extensions/helpers/websocket/api.md +9 -9
- package/{wiki → content}/extensions/helpers/websocket/index.md +7 -7
- package/{wiki → content}/extensions/helpers/worker-thread/index.md +2 -2
- package/{wiki → content}/extensions/index.md +3 -4
- package/{wiki → content}/extensions/src-details/mcp-server.md +18 -24
- package/{wiki → content}/guides/core-concepts/application/bootstrapping.md +11 -14
- package/{wiki → content}/guides/core-concepts/application/index.md +3 -3
- package/{wiki → content}/guides/core-concepts/components.md +19 -10
- package/{wiki → content}/guides/core-concepts/dependency-injection.md +6 -3
- package/{wiki → content}/guides/core-concepts/grpc-controllers.md +6 -5
- package/{wiki → content}/guides/core-concepts/persistent/datasources.md +33 -27
- package/{wiki → content}/guides/core-concepts/persistent/index.md +16 -5
- package/{wiki → content}/guides/core-concepts/persistent/models.md +24 -20
- package/content/guides/core-concepts/persistent/postgres-drivers.md +167 -0
- package/{wiki → content}/guides/core-concepts/persistent/repositories.md +40 -23
- package/content/guides/core-concepts/persistent/search-meilisearch.md +183 -0
- package/content/guides/core-concepts/persistent/search-typesense.md +429 -0
- package/{wiki → content}/guides/core-concepts/persistent/transactions.md +61 -25
- package/{wiki → content}/guides/core-concepts/rest-controllers.md +12 -9
- package/content/guides/core-concepts/services.md +389 -0
- package/{wiki → content}/guides/get-started/5-minute-quickstart.md +19 -19
- package/{wiki → content}/guides/get-started/philosophy.md +36 -36
- package/{wiki → content}/guides/get-started/setup.md +3 -3
- package/{wiki → content}/guides/index.md +3 -3
- package/content/guides/migrations/redis-helpers-migration.md +177 -0
- package/{wiki → content}/guides/migrations/scoped-rbac-migration.md +17 -17
- package/content/guides/migrations/unified-connectors-migration.md +113 -0
- package/{wiki → content}/guides/reference/glossary.md +19 -12
- package/{wiki → content}/guides/reference/mcp-docs-server.md +22 -18
- package/{wiki → content}/guides/tutorials/building-a-crud-api.md +30 -33
- package/{wiki → content}/guides/tutorials/complete-installation.md +17 -17
- package/{wiki → content}/guides/tutorials/ecommerce-api.md +158 -119
- package/{wiki → content}/guides/tutorials/realtime-chat.md +176 -130
- package/content/guides/tutorials/testing.md +264 -0
- package/content/index.md +5 -0
- 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/{wiki → content}/references/base/application.md +4 -5
- package/{wiki → content}/references/base/bootstrapping.md +18 -5
- package/{wiki → content}/references/base/components.md +149 -120
- package/content/references/base/connectors.md +178 -0
- package/{wiki → content}/references/base/controllers.md +41 -30
- package/content/references/base/datasources.md +527 -0
- package/{wiki → content}/references/base/dependency-injection.md +34 -22
- package/{wiki → content}/references/base/filter-system/application-usage.md +17 -14
- package/{wiki → content}/references/base/filter-system/array-operators.md +7 -2
- package/{wiki → content}/references/base/filter-system/comparison-operators.md +3 -0
- package/{wiki → content}/references/base/filter-system/default-filter.md +89 -71
- package/{wiki → content}/references/base/filter-system/fields-order-pagination.md +22 -22
- package/{wiki → content}/references/base/filter-system/index.md +6 -3
- package/{wiki → content}/references/base/filter-system/json-filtering.md +20 -1
- package/{wiki → content}/references/base/filter-system/list-operators.md +1 -1
- package/{wiki → content}/references/base/filter-system/logical-operators.md +33 -1
- package/{wiki → content}/references/base/filter-system/null-operators.md +30 -1
- package/{wiki → content}/references/base/filter-system/quick-reference.md +23 -4
- package/{wiki → content}/references/base/filter-system/tips.md +5 -5
- package/{wiki → content}/references/base/filter-system/use-cases.md +12 -12
- package/{wiki → content}/references/base/grpc-controllers.md +13 -13
- package/{wiki → content}/references/base/index.md +24 -12
- package/{wiki/references/base/middleware.md → content/references/base/middlewares.md} +205 -24
- package/{wiki → content}/references/base/models.md +63 -49
- package/{wiki → content}/references/base/providers.md +136 -130
- package/{wiki → content}/references/base/repositories/advanced.md +59 -58
- package/{wiki → content}/references/base/repositories/index.md +115 -91
- package/content/references/base/repositories/mixins.md +99 -0
- package/{wiki → content}/references/base/repositories/relations.md +54 -64
- package/{wiki → content}/references/base/repositories/soft-deletable.md +31 -30
- package/content/references/base/services.md +404 -0
- package/{wiki → content}/references/configuration/environment-variables.md +46 -30
- package/{wiki → content}/references/configuration/index.md +6 -6
- package/{wiki → content}/references/index.md +17 -12
- package/{wiki → content}/references/quick-reference.md +65 -106
- package/content/references/utilities/crypto.md +98 -0
- package/{wiki → content}/references/utilities/index.md +3 -3
- package/{wiki → content}/references/utilities/jsx.md +6 -4
- package/content/references/utilities/module.md +90 -0
- package/{wiki → content}/references/utilities/parse.md +4 -14
- package/{wiki → content}/references/utilities/promise.md +9 -7
- package/{wiki → 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/common/paths.d.ts.map +1 -1
- package/dist/mcp-server/common/paths.js +2 -2
- 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 +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 +12 -12
- package/wiki/extensions/helpers/error/index.md +0 -227
- package/wiki/extensions/helpers/redis/index.md +0 -488
- package/wiki/extensions/helpers/testing/index.md +0 -510
- package/wiki/guides/core-concepts/services.md +0 -119
- package/wiki/guides/tutorials/testing.md +0 -722
- package/wiki/index.md +0 -183
- package/wiki/references/base/datasources.md +0 -454
- package/wiki/references/base/middlewares.md +0 -590
- package/wiki/references/base/repositories/mixins.md +0 -335
- package/wiki/references/base/services.md +0 -201
- package/wiki/references/utilities/crypto.md +0 -56
- package/wiki/references/utilities/module.md +0 -42
- /package/{wiki → content}/extensions/components/mail/api.md +0 -0
- /package/{wiki → content}/extensions/components/template/api-page.md +0 -0
- /package/{wiki → content}/extensions/components/template/errors-page.md +0 -0
- /package/{wiki → content}/extensions/components/template/single-page.md +0 -0
- /package/{wiki → content}/extensions/components/template/usage-page.md +0 -0
- /package/{wiki → content}/extensions/helpers/kafka/admin.md +0 -0
- /package/{wiki → content}/extensions/helpers/kafka/consumer.md +0 -0
- /package/{wiki → content}/extensions/helpers/kafka/index.md +0 -0
- /package/{wiki → content}/extensions/helpers/kafka/producer.md +0 -0
- /package/{wiki → content}/extensions/helpers/kafka/schema-registry.md +0 -0
- /package/{wiki → content}/extensions/helpers/network/api.md +0 -0
- /package/{wiki → content}/extensions/helpers/socket-io/api.md +0 -0
- /package/{wiki → content}/extensions/helpers/template/single-page.md +0 -0
- /package/{wiki → content}/extensions/helpers/uid/index.md +0 -0
- /package/{wiki → content}/guides/core-concepts/components-guide.md +0 -0
- /package/{wiki → content}/public/logo.svg +0 -0
- /package/{wiki → content}/references/base/filter-system/pattern-matching.md +0 -0
- /package/{wiki → content}/references/base/filter-system/range-operators.md +0 -0
- /package/{wiki → content}/references/utilities/date.md +0 -0
- /package/{wiki → content}/references/utilities/performance.md +0 -0
- /package/{wiki → content}/references/utilities/request.md +0 -0
- /package/{wiki → content}/references/utilities/statuses.md +0 -0
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
2
|
+
title: Middlewares Reference
|
|
3
3
|
description: Technical reference for built-in middlewares in IGNIS
|
|
4
4
|
difficulty: intermediate
|
|
5
|
-
lastUpdated: 2026-
|
|
5
|
+
lastUpdated: 2026-06-14
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# Middlewares Reference
|
|
9
9
|
|
|
10
|
-
IGNIS provides built-in middleware functions and a provider-based middleware class for handling common HTTP concerns: error handling, request logging, 404 responses, and favicon serving. These are registered automatically by `BaseApplication` during startup.
|
|
10
|
+
IGNIS provides built-in middleware functions and a provider-based middleware class for handling common HTTP concerns: error handling, request logging, 404 responses, and favicon serving. These are registered automatically by `BaseApplication` during startup - you do not import or wire them manually.
|
|
11
11
|
|
|
12
12
|
**Files:**
|
|
13
|
-
- `packages/core/src/base/middlewares/app-error.middleware.ts`
|
|
14
|
-
- `packages/core/src/base/middlewares/not-found.middleware.ts`
|
|
15
|
-
- `packages/core/src/base/middlewares/request-spy.middleware.ts`
|
|
16
|
-
- `packages/core/src/base/middlewares/emoji-favicon.middleware.ts`
|
|
13
|
+
- `packages/core/src/base/middlewares/app-error/app-error.middleware.ts`
|
|
14
|
+
- `packages/core/src/base/middlewares/not-found/not-found.middleware.ts`
|
|
15
|
+
- `packages/core/src/base/middlewares/request-spy/request-spy.middleware.ts`
|
|
16
|
+
- `packages/core/src/base/middlewares/emoji-favicon/emoji-favicon.middleware.ts`
|
|
17
17
|
|
|
18
18
|
## Prerequisites
|
|
19
19
|
|
|
@@ -64,6 +64,8 @@ After `registerDefaultMiddlewares()`, the application calls user-defined `static
|
|
|
64
64
|
|
|
65
65
|
Global error handler registered via `server.onError()`. Handles ZodError validation errors, PostgreSQL constraint violations, and generic errors.
|
|
66
66
|
|
|
67
|
+
**Not exported from `@venizia/ignis`** - registered automatically by `BaseApplication`.
|
|
68
|
+
|
|
67
69
|
### Signature
|
|
68
70
|
|
|
69
71
|
```typescript
|
|
@@ -82,11 +84,14 @@ function appErrorHandler(opts: {
|
|
|
82
84
|
|
|
83
85
|
#### 1. ZodError (Validation Errors)
|
|
84
86
|
|
|
85
|
-
When `error.name === 'ZodError'`, returns HTTP `422 Unprocessable Entity
|
|
87
|
+
When `error.name === 'ZodError'`, returns HTTP `422 Unprocessable Entity`.
|
|
88
|
+
|
|
89
|
+
Top-level `message`/`messageCode` come from the first failing issue - its `params.code` if the schema set one, otherwise its raw Zod code. The full per-field list stays under `details.cause`.
|
|
86
90
|
|
|
87
91
|
```json
|
|
88
92
|
{
|
|
89
|
-
"message": "
|
|
93
|
+
"message": "Invalid email address",
|
|
94
|
+
"messageCode": "user.email.invalid",
|
|
90
95
|
"statusCode": 422,
|
|
91
96
|
"requestId": "abc-123",
|
|
92
97
|
"details": {
|
|
@@ -96,7 +101,7 @@ When `error.name === 'ZodError'`, returns HTTP `422 Unprocessable Entity`:
|
|
|
96
101
|
"cause": [
|
|
97
102
|
{
|
|
98
103
|
"path": "email",
|
|
99
|
-
"message": "Invalid email",
|
|
104
|
+
"message": "Invalid email address",
|
|
100
105
|
"code": "invalid_string",
|
|
101
106
|
"expected": "string",
|
|
102
107
|
"received": "undefined"
|
|
@@ -106,22 +111,36 @@ When `error.name === 'ZodError'`, returns HTTP `422 Unprocessable Entity`:
|
|
|
106
111
|
}
|
|
107
112
|
```
|
|
108
113
|
|
|
114
|
+
To emit a stable, domain-specific `messageCode`, attach `params.code` to a custom check:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
z.string().refine(isEmail, {
|
|
118
|
+
message: 'Invalid email address',
|
|
119
|
+
params: { code: 'user.email.invalid' }
|
|
120
|
+
});
|
|
121
|
+
// produces "messageCode": "user.email.invalid"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
> [!NOTE]
|
|
125
|
+
> When `error.message` cannot be parsed as the expected Zod issue array (a malformed or unrecognized `ZodError`), no issue-derived `messageCode` exists - the response still carries one, resolved to `MessageCode.DEFAULT` (`"core.system_error"`) via `MessageCode.resolve(undefined)`. No error response from this middleware is ever missing `messageCode`.
|
|
126
|
+
|
|
109
127
|
#### 2. PostgreSQL Constraint Violations
|
|
110
128
|
|
|
111
|
-
Database errors
|
|
129
|
+
Database errors in SQLSTATE class `22` (data exception), `23` (integrity constraint), and `44` (WITH CHECK OPTION violation) are detected by class and returned as HTTP `400 Bad Request`. A known code uses its specific message; any other in-class code uses `"Invalid database request"` as a fallback.
|
|
130
|
+
|
|
131
|
+
| Class | Codes with a specific message |
|
|
132
|
+
|-------|-------------------------------|
|
|
133
|
+
| `23` Integrity | `23505` unique, `23503` foreign key, `23502` not null, `23514` check, `23P01` exclusion, `23000` integrity, `23001` restrict |
|
|
134
|
+
| `22` Data exception | `22001` string too long, `22003` numeric range, `22004` null not allowed, `22007` datetime format, `22008` datetime overflow, `22009` tz displacement, `22011` substring, `22012` division by zero, `22023` invalid parameter, `22025` invalid escape, `22026` length mismatch, `22030` duplicate JSON key, `22032` invalid JSON, `22P01` floating-point, `22P02` invalid text, `22P03` invalid binary, `22P05` untranslatable char |
|
|
135
|
+
| `44` View check | `44000` WITH CHECK OPTION violation |
|
|
112
136
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
| `23503` | Foreign key constraint violation |
|
|
117
|
-
| `23502` | Not null constraint violation |
|
|
118
|
-
| `23514` | Check constraint violation |
|
|
119
|
-
| `23P01` | Exclusion constraint violation |
|
|
120
|
-
| `22P02` | Invalid text representation |
|
|
121
|
-
| `22003` | Numeric value out of range |
|
|
122
|
-
| `22001` | String data too long |
|
|
137
|
+
:::tip Transient conflicts return 409, not 400/500
|
|
138
|
+
Class `40` (`40001` serialization failure, `40P01` deadlock) is transient/retryable and returns **409 Conflict** with `messageCode: "database.conflict"` and a safe "please retry" message - the client can safely retry the same request. Programming/infra classes (`42` syntax, `53` resources, `0A`, `25`, `28`) remain 500.
|
|
139
|
+
:::
|
|
123
140
|
|
|
124
|
-
|
|
141
|
+
:::warning Production sanitizes database internals
|
|
142
|
+
In **production** the message is the base message only - `Detail:`/`Table:`/`Constraint:` are stripped (they echo row values and schema names), and `details.stack`/`details.cause` are omitted. Codes outside class 22/23/44 (e.g. `42703` undefined column) and connection failures return a generic `"Internal Server Error"`, so SQL, schema names, and connection host/port never leak.
|
|
143
|
+
:::
|
|
125
144
|
|
|
126
145
|
#### 3. Generic Errors
|
|
127
146
|
|
|
@@ -132,6 +151,7 @@ All other errors use the `statusCode` property from the error if present, otherw
|
|
|
132
151
|
```json
|
|
133
152
|
{
|
|
134
153
|
"message": "Error message",
|
|
154
|
+
"messageCode": "core.system_error",
|
|
135
155
|
"statusCode": 500,
|
|
136
156
|
"requestId": "abc-123",
|
|
137
157
|
"details": {
|
|
@@ -149,6 +169,7 @@ When `rootKey` is provided (e.g., `rootKey: 'error'`), the response is wrapped:
|
|
|
149
169
|
{
|
|
150
170
|
"error": {
|
|
151
171
|
"message": "Error message",
|
|
172
|
+
"messageCode": "core.system_error",
|
|
152
173
|
"statusCode": 500,
|
|
153
174
|
"requestId": "abc-123",
|
|
154
175
|
"details": { ... }
|
|
@@ -158,11 +179,38 @@ When `rootKey` is provided (e.g., `rootKey: 'error'`), the response is wrapped:
|
|
|
158
179
|
|
|
159
180
|
**Production behavior:** `stack` and `cause` fields are omitted when `NODE_ENV` is `'production'`.
|
|
160
181
|
|
|
182
|
+
### Custom Errors
|
|
183
|
+
|
|
184
|
+
Throw any error with a `statusCode` property and the handler picks it up:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
class NotFoundError extends Error {
|
|
188
|
+
statusCode = 404;
|
|
189
|
+
|
|
190
|
+
constructor(message: string) {
|
|
191
|
+
super(message);
|
|
192
|
+
this.name = 'NotFoundError';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// In a controller
|
|
197
|
+
@get({ configs: GetUserConfig })
|
|
198
|
+
async getUser(c: TRouteContext) {
|
|
199
|
+
const user = await this.userRepository.findById(id);
|
|
200
|
+
if (!user) {
|
|
201
|
+
throw new NotFoundError(`User ${id} not found`);
|
|
202
|
+
}
|
|
203
|
+
return c.json(user, HTTP.ResultCodes.RS_2.Ok);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
161
207
|
|
|
162
208
|
## notFoundHandler
|
|
163
209
|
|
|
164
210
|
Returns a JSON 404 response when no route matches. Registered via `server.notFound()`.
|
|
165
211
|
|
|
212
|
+
**Not exported from `@venizia/ignis`** - registered automatically by `BaseApplication`.
|
|
213
|
+
|
|
166
214
|
### Signature
|
|
167
215
|
|
|
168
216
|
```typescript
|
|
@@ -194,6 +242,8 @@ The handler logs the 404 at error level with the request ID, path, and full URL.
|
|
|
194
242
|
|
|
195
243
|
A provider-based middleware class that logs incoming request details and outgoing response timing. It extends `BaseHelper` and implements `IProvider<MiddlewareHandler>`.
|
|
196
244
|
|
|
245
|
+
**Not exported from `@venizia/ignis`** - registered automatically via `RequestTrackerComponent` by `BaseApplication`.
|
|
246
|
+
|
|
197
247
|
### Class Definition
|
|
198
248
|
|
|
199
249
|
```typescript
|
|
@@ -256,11 +306,37 @@ Returns `null` if no `Content-Type` header or `Content-Length` is `0`/missing. T
|
|
|
256
306
|
|
|
257
307
|
The middleware resolves the client IP from the connection info or falls back to `x-real-ip` / `x-forwarded-for` headers. If neither is available, it throws HTTP 400 `'Malformed Connection Info'`.
|
|
258
308
|
|
|
309
|
+
### Accessing the Request ID
|
|
310
|
+
|
|
311
|
+
`RequestSpyMiddleware.REQUEST_ID_KEY` is `'requestId'` - since the middleware class itself is not exported from `@venizia/ignis`, read the context variable by that key:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import { get, jsonResponse, TRouteContext } from '@venizia/ignis';
|
|
315
|
+
import { z } from '@hono/zod-openapi';
|
|
316
|
+
import { HTTP } from '@venizia/ignis-helpers';
|
|
317
|
+
|
|
318
|
+
const ExampleConfig = {
|
|
319
|
+
method: HTTP.Methods.GET,
|
|
320
|
+
path: '/example',
|
|
321
|
+
responses: jsonResponse({
|
|
322
|
+
schema: z.object({ requestId: z.string() }),
|
|
323
|
+
}),
|
|
324
|
+
} as const;
|
|
325
|
+
|
|
326
|
+
@get({ configs: ExampleConfig })
|
|
327
|
+
async example(c: TRouteContext) {
|
|
328
|
+
const requestId = c.get('requestId');
|
|
329
|
+
return c.json({ requestId }, HTTP.ResultCodes.RS_2.Ok);
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
259
333
|
|
|
260
334
|
## emojiFavicon
|
|
261
335
|
|
|
262
336
|
A simple middleware that serves an emoji as an SVG favicon on `/favicon.ico`.
|
|
263
337
|
|
|
338
|
+
**Not exported from `@venizia/ignis`** - registered automatically by `BaseApplication`.
|
|
339
|
+
|
|
264
340
|
### Signature
|
|
265
341
|
|
|
266
342
|
```typescript
|
|
@@ -295,7 +371,7 @@ interface IApplicationConfigs {
|
|
|
295
371
|
|
|
296
372
|
## User-Defined Middlewares
|
|
297
373
|
|
|
298
|
-
The `setupMiddlewares()` abstract method on `AbstractApplication` is called after `initialize()` and before the server starts. Use this hook to register additional Hono middlewares
|
|
374
|
+
The `setupMiddlewares()` abstract method on `AbstractApplication` is called after `initialize()` and before the server starts. Use this hook to register additional Hono middlewares via `this.getServer()`:
|
|
299
375
|
|
|
300
376
|
```typescript
|
|
301
377
|
export class MyApplication extends BaseApplication {
|
|
@@ -307,6 +383,9 @@ export class MyApplication extends BaseApplication {
|
|
|
307
383
|
|
|
308
384
|
// Body limit
|
|
309
385
|
server.use(bodyLimit({ maxSize: 1024 * 1024 })); // 1MB
|
|
386
|
+
|
|
387
|
+
// Route-specific
|
|
388
|
+
server.use('/api/admin/*', adminAuthMiddleware());
|
|
310
389
|
}
|
|
311
390
|
}
|
|
312
391
|
```
|
|
@@ -336,6 +415,105 @@ interface IBaseMiddlewareOptions {
|
|
|
336
415
|
```
|
|
337
416
|
|
|
338
417
|
|
|
418
|
+
## Creating Custom Middleware
|
|
419
|
+
|
|
420
|
+
IGNIS uses Hono's middleware system. Create custom middleware using the `createMiddleware` factory from `hono/factory`.
|
|
421
|
+
|
|
422
|
+
### Basic Middleware
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { createMiddleware } from 'hono/factory';
|
|
426
|
+
import type { MiddlewareHandler } from 'hono';
|
|
427
|
+
|
|
428
|
+
export const myMiddleware = (): MiddlewareHandler => {
|
|
429
|
+
return createMiddleware(async (context, next) => {
|
|
430
|
+
// Before request handling
|
|
431
|
+
console.log('Before:', context.req.path);
|
|
432
|
+
|
|
433
|
+
await next();
|
|
434
|
+
|
|
435
|
+
// After request handling
|
|
436
|
+
console.log('After:', context.req.path);
|
|
437
|
+
});
|
|
438
|
+
};
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Middleware with Options
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
interface MyMiddlewareOptions {
|
|
445
|
+
enabled: boolean;
|
|
446
|
+
prefix?: string;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export const myMiddleware = (opts: MyMiddlewareOptions): MiddlewareHandler => {
|
|
450
|
+
const { enabled, prefix = 'LOG' } = opts;
|
|
451
|
+
|
|
452
|
+
return createMiddleware(async (context, next) => {
|
|
453
|
+
if (enabled) {
|
|
454
|
+
console.log(`[${prefix}]`, context.req.path);
|
|
455
|
+
}
|
|
456
|
+
await next();
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Provider-Based Middleware
|
|
462
|
+
|
|
463
|
+
For middleware requiring dependency injection, implement `IProvider<MiddlewareHandler>`:
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
import { BaseHelper } from '@venizia/ignis-helpers';
|
|
467
|
+
import { IProvider } from '@venizia/ignis-inversion';
|
|
468
|
+
import { createMiddleware } from 'hono/factory';
|
|
469
|
+
import type { MiddlewareHandler } from 'hono';
|
|
470
|
+
|
|
471
|
+
export class MyMiddleware extends BaseHelper implements IProvider<MiddlewareHandler> {
|
|
472
|
+
constructor() {
|
|
473
|
+
super({ scope: MyMiddleware.name });
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
value(): MiddlewareHandler {
|
|
477
|
+
return createMiddleware(async (context, next) => {
|
|
478
|
+
this.logger.info('Processing request:', context.req.path);
|
|
479
|
+
await next();
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Register it with `.toProvider()` (the same pattern `RequestTrackerComponent` uses for `RequestSpyMiddleware`), then use the resolved handler inside `setupMiddlewares()` - `get()` returns the produced `MiddlewareHandler` because the container calls `value()` for provider bindings:
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
export class MyApplication extends BaseApplication {
|
|
489
|
+
preConfigure() {
|
|
490
|
+
this.bind({ key: 'middlewares.MyMiddleware' })
|
|
491
|
+
.toProvider(MyMiddleware)
|
|
492
|
+
.setScope(BindingScopes.SINGLETON);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async setupMiddlewares() {
|
|
496
|
+
const myMiddleware = this.get<MiddlewareHandler>({ key: 'middlewares.MyMiddleware' });
|
|
497
|
+
this.getServer().use(myMiddleware);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
## Performance Considerations
|
|
504
|
+
|
|
505
|
+
### Request Spy in Production
|
|
506
|
+
|
|
507
|
+
`RequestSpyMiddleware` logs every request. IGNIS automatically skips body logging in production (`NODE_ENV === 'production'`), but the middleware still runs. For ultra-high-traffic workloads consider sampling strategies or externalizing log aggregation.
|
|
508
|
+
|
|
509
|
+
### Error Logging Volume
|
|
510
|
+
|
|
511
|
+
Error handlers log every error. For high error rates, consider:
|
|
512
|
+
- Sampling (log 1 in N errors)
|
|
513
|
+
- Error aggregation services (Sentry, Rollbar)
|
|
514
|
+
- Rate-limited logging
|
|
515
|
+
|
|
516
|
+
|
|
339
517
|
## See Also
|
|
340
518
|
|
|
341
519
|
- **Related References:**
|
|
@@ -345,3 +523,6 @@ interface IBaseMiddlewareOptions {
|
|
|
345
523
|
|
|
346
524
|
- **Guides:**
|
|
347
525
|
- [Application Guide](/guides/core-concepts/application/)
|
|
526
|
+
|
|
527
|
+
- **External Resources:**
|
|
528
|
+
- [Hono Middleware Documentation](https://hono.dev/docs/guides/middleware)
|
|
@@ -6,24 +6,31 @@ difficulty: intermediate
|
|
|
6
6
|
|
|
7
7
|
# Deep Dive: Models and Enrichers
|
|
8
8
|
|
|
9
|
-
Technical reference for model architecture and schema enrichers in
|
|
9
|
+
Technical reference for model architecture and schema enrichers in IGNIS.
|
|
10
|
+
|
|
11
|
+
> [!IMPORTANT] Base vs. Connectors
|
|
12
|
+
> The engine-neutral root `AbstractEntity` (`packages/core/src/base/models/base.ts`) has no Drizzle, no `pgTable`, and no `drizzle-zod` - just a `name`, an abstract `getSchema()`, a `getIdType(): TIdSchemaType` method (default `'string'`), and `toObject()`/`toJSON()`. Everything described below - the Drizzle-backed `BaseEntity`, `drizzle-zod` schema generation, and all schema enrichers - belongs to the **PostgreSQL connector**'s `BasePostgresEntity`, not the neutral base. See [Connectors](./connectors) for the full base-vs-connectors architecture.
|
|
10
13
|
|
|
11
14
|
**Files:**
|
|
12
|
-
- `packages/core/src/base/models/base.ts`
|
|
13
|
-
- `packages/core/src/
|
|
15
|
+
- `packages/core/src/base/models/base.ts` (neutral `AbstractEntity`)
|
|
16
|
+
- `packages/core/src/connectors/postgres/models/base.ts` (PostgreSQL `BasePostgresEntity`)
|
|
17
|
+
- `packages/core/src/connectors/postgres/models/enrichers/*.ts`
|
|
14
18
|
|
|
15
19
|
## Quick Reference
|
|
16
20
|
|
|
17
21
|
| Component | Purpose | Key Features |
|
|
18
22
|
|-----------|---------|--------------|
|
|
19
|
-
| **
|
|
23
|
+
| **BasePostgresEntity** (alias: `BaseEntity`) | Wraps Drizzle schema | Schema encapsulation, Zod generation, `toObject()`/`toJSON()` |
|
|
20
24
|
| **Schema Enrichers** | Add common columns to tables | `generateIdColumnDefs()`, `generateTzColumnDefs()`, etc. |
|
|
21
25
|
|
|
22
|
-
## `
|
|
26
|
+
## `BasePostgresEntity` Class (alias: `BaseEntity`)
|
|
27
|
+
|
|
28
|
+
PostgreSQL connector's entity class, wrapping a Drizzle ORM schema. Extends the neutral `AbstractEntity`.
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
**File:** `packages/core/src/connectors/postgres/models/base.ts`
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
> [!TIP] Naming
|
|
33
|
+
> `BasePostgresEntity` is the canonical, engine-carrying name. `BaseEntity` is a compatibility alias re-exporting the same class from `connectors/postgres/models/index.ts` (`export { BasePostgresEntity as BaseEntity } from './base'`) - both resolve to identical runtime behavior. Code samples on this page use `BaseEntity` since it remains the most common import today.
|
|
27
34
|
|
|
28
35
|
### Purpose
|
|
29
36
|
|
|
@@ -68,13 +75,13 @@ The `@model` decorator marks a class as a database entity and configures its beh
|
|
|
68
75
|
| `settings.hiddenProperties` | `string[]` | Array of property names to exclude from all repository query results |
|
|
69
76
|
| `settings.defaultFilter` | `TFilter` | Filter automatically applied to all repository queries (see [Default Filter](/references/base/filter-system/default-filter)) |
|
|
70
77
|
| `settings.defaultLimit` | `number` | Default row limit applied when a query omits `limit`. Must be a positive integer (validated at decoration time). Falls back to the global `DEFAULT_LIMIT` (10). See [Pagination](/references/base/filter-system/fields-order-pagination#default-limit) |
|
|
71
|
-
| `settings.authorize` | `IModelAuthorizeSettings` | Authorization settings
|
|
78
|
+
| `settings.authorize` | `IModelAuthorizeSettings` | Authorization settings - declares the model's authorization principal (see [Authorization](/extensions/components/authorization/usage#model-based-resource-references)) |
|
|
72
79
|
| `settings.authorize.principal` | `string` | The authorization subject name for this model. Auto-populates `AUTHORIZATION_SUBJECT` static property |
|
|
73
80
|
|
|
74
81
|
#### `@model` Behavior
|
|
75
82
|
|
|
76
83
|
When the `@model` decorator is applied:
|
|
77
|
-
1. If `settings.defaultLimit` is provided, it is validated to be a positive integer
|
|
84
|
+
1. If `settings.defaultLimit` is provided, it is validated to be a positive integer - otherwise the decorator throws at decoration (boot) time
|
|
78
85
|
2. If `settings.authorize.principal` is provided and `AUTHORIZATION_SUBJECT` is not already defined on the class, it auto-populates `AUTHORIZATION_SUBJECT` with the principal value
|
|
79
86
|
3. The model is registered in the `MetadataRegistry` model registry, keyed by table name (resolved as: `metadata.tableName` > `static TABLE_NAME` > class name)
|
|
80
87
|
4. The static `relations` property is stored as a resolver (not immediately resolved) to avoid circular dependency issues between models
|
|
@@ -126,11 +133,11 @@ export class User extends BaseEntity<typeof User.schema> {
|
|
|
126
133
|
|
|
127
134
|
```typescript
|
|
128
135
|
// Repository query - password/secret NOT included
|
|
129
|
-
const user = await
|
|
136
|
+
const user = await userRepository.findById({ id: '123' });
|
|
130
137
|
// user = { id: '123', email: 'john@example.com' }
|
|
131
138
|
|
|
132
139
|
// Direct connector query - ALL fields included
|
|
133
|
-
const connector =
|
|
140
|
+
const connector = userRepository.getConnector();
|
|
134
141
|
const [fullUser] = await connector
|
|
135
142
|
.select()
|
|
136
143
|
.from(User.schema)
|
|
@@ -178,11 +185,11 @@ Use `shouldSkipDefaultFilter: true` to bypass:
|
|
|
178
185
|
|
|
179
186
|
```typescript
|
|
180
187
|
// Normal query - includes default filter
|
|
181
|
-
await
|
|
188
|
+
await postRepository.find({ filter: {} });
|
|
182
189
|
// WHERE isDeleted = false LIMIT 100
|
|
183
190
|
|
|
184
191
|
// Admin query - bypass default filter
|
|
185
|
-
await
|
|
192
|
+
await postRepository.find({
|
|
186
193
|
filter: {},
|
|
187
194
|
options: { shouldSkipDefaultFilter: true }
|
|
188
195
|
});
|
|
@@ -271,7 +278,7 @@ interface IEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId> {
|
|
|
271
278
|
|--------|-------------|
|
|
272
279
|
| `getSchema({ type })` | Get Zod schema for validation (`'select'`, `'create'`, `'update'`) |
|
|
273
280
|
| `toObject()` | Convert to plain object (shallow spread of `this`) |
|
|
274
|
-
| `toJSON()` | Delegates to `toObject()`
|
|
281
|
+
| `toJSON()` | Delegates to `toObject()` - returns a plain object (used by `JSON.stringify`) |
|
|
275
282
|
|
|
276
283
|
### `getSchema` Method
|
|
277
284
|
|
|
@@ -307,12 +314,11 @@ The `schemaFactory` is a static lazy singleton created via `drizzle-zod`'s `crea
|
|
|
307
314
|
### Class Definition
|
|
308
315
|
|
|
309
316
|
```typescript
|
|
310
|
-
export class
|
|
311
|
-
extends
|
|
317
|
+
export class BasePostgresEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId>
|
|
318
|
+
extends AbstractEntity
|
|
312
319
|
implements IEntity<Schema>
|
|
313
320
|
{
|
|
314
|
-
// Instance
|
|
315
|
-
name: string;
|
|
321
|
+
// Instance property (name, toObject(), toJSON() are inherited from AbstractEntity)
|
|
316
322
|
schema: Schema;
|
|
317
323
|
|
|
318
324
|
// Static properties - override in subclass
|
|
@@ -325,23 +331,27 @@ export class BaseEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId>
|
|
|
325
331
|
// Performance optimization: avoids creating new factory per entity
|
|
326
332
|
private static _schemaFactory?: ReturnType<typeof createSchemaFactory>;
|
|
327
333
|
protected static get schemaFactory(): ReturnType<typeof createSchemaFactory> {
|
|
328
|
-
return (
|
|
334
|
+
return (BasePostgresEntity._schemaFactory ??= createSchemaFactory());
|
|
329
335
|
}
|
|
330
336
|
|
|
331
337
|
// Constructor supports both patterns
|
|
332
338
|
constructor(opts?: { name?: string; schema?: Schema }) {
|
|
333
|
-
const ctor = new.target as typeof
|
|
339
|
+
const ctor = new.target as typeof BasePostgresEntity;
|
|
334
340
|
// Resolution order: opts.name > static TABLE_NAME > class name
|
|
335
341
|
const name = opts?.name ?? ctor.TABLE_NAME ?? ctor.name;
|
|
336
342
|
|
|
337
|
-
super({
|
|
343
|
+
super({ name });
|
|
338
344
|
|
|
339
|
-
this.name = name;
|
|
340
345
|
this.schema = opts?.schema || (ctor.schema as Schema);
|
|
341
346
|
}
|
|
342
347
|
|
|
348
|
+
// Maps the pgTable id column's Drizzle dataType to 'number' or 'string'
|
|
349
|
+
override getIdType(): TIdSchemaType {
|
|
350
|
+
return getIdType({ entity: this.schema }) === 'number' ? 'number' : 'string';
|
|
351
|
+
}
|
|
352
|
+
|
|
343
353
|
getSchema(opts: { type: TSchemaType }) {
|
|
344
|
-
const factory =
|
|
354
|
+
const factory = BasePostgresEntity.schemaFactory; // Uses static singleton
|
|
345
355
|
switch (opts.type) {
|
|
346
356
|
case SchemaTypes.CREATE:
|
|
347
357
|
return factory.createInsertSchema(this.schema);
|
|
@@ -355,14 +365,6 @@ export class BaseEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId>
|
|
|
355
365
|
});
|
|
356
366
|
}
|
|
357
367
|
}
|
|
358
|
-
|
|
359
|
-
toObject() {
|
|
360
|
-
return { ...this };
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
toJSON() {
|
|
364
|
-
return this.toObject();
|
|
365
|
-
}
|
|
366
368
|
}
|
|
367
369
|
```
|
|
368
370
|
|
|
@@ -441,11 +443,11 @@ From `@venizia/ignis-helpers`, enables lazy resolution to avoid circular depende
|
|
|
441
443
|
type TValueOrResolver<T> = T | TResolver<T>; // T or () => T
|
|
442
444
|
```
|
|
443
445
|
|
|
444
|
-
Used for `relations` on `BaseEntity`
|
|
446
|
+
Used for `relations` on `BaseEntity` - store a function that returns the relations array, resolved lazily when `DataSource.buildSchema()` is called.
|
|
445
447
|
|
|
446
448
|
## Schema Enrichers
|
|
447
449
|
|
|
448
|
-
Enrichers are helper functions located in `packages/core/src/
|
|
450
|
+
Enrichers are helper functions located in `packages/core/src/connectors/postgres/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.
|
|
449
451
|
|
|
450
452
|
### Available Enrichers
|
|
451
453
|
|
|
@@ -456,7 +458,7 @@ Enrichers are helper functions located in `packages/core/src/base/models/enriche
|
|
|
456
458
|
| **`generateUserAuditColumnDefs`** | `enrichUserAudit` | Adds `createdBy` and `modifiedBy` columns to track user audit information. |
|
|
457
459
|
| **`generatePrincipalColumnDefs`** | `enrichPrincipal` | Adds polymorphic principal columns (`{discriminator}Id` and `{discriminator}Type`). |
|
|
458
460
|
| **`generateDataTypeColumnDefs`** | `enrichDataTypes` | Adds generic data type columns (`dataType`, `nValue`, `tValue`, `bValue`, `jValue`, `boValue`) for flexible data storage. |
|
|
459
|
-
| **`extraUserColumns`** |
|
|
461
|
+
| **`extraUserColumns`** | - | Adds common user fields (`realm`, `status`, `type`, `activatedAt`, `lastLoginAt`, `parentId`). Imported from `@venizia/ignis` (part of auth component). |
|
|
460
462
|
|
|
461
463
|
Each `generate*` function returns column definition objects for spreading into `pgTable`. The `enrich*` convenience wrappers accept an existing `TColumnDefinitions` object as the first argument and merge the generated columns into it.
|
|
462
464
|
|
|
@@ -488,7 +490,7 @@ export const myTable = pgTable('MyTable', {
|
|
|
488
490
|
|
|
489
491
|
Adds a primary key `id` column with support for string UUID, integer, or big integer types with full TypeScript type inference.
|
|
490
492
|
|
|
491
|
-
**File:** `packages/core/src/
|
|
493
|
+
**File:** `packages/core/src/connectors/postgres/models/enrichers/id.enricher.ts`
|
|
492
494
|
|
|
493
495
|
#### Signature
|
|
494
496
|
|
|
@@ -688,7 +690,7 @@ const columns = enrichId(
|
|
|
688
690
|
|
|
689
691
|
Adds timestamp columns for tracking entity creation, modification, and soft deletion.
|
|
690
692
|
|
|
691
|
-
**File:** `packages/core/src/
|
|
693
|
+
**File:** `packages/core/src/connectors/postgres/models/enrichers/tz.enricher.ts`
|
|
692
694
|
|
|
693
695
|
#### Signature
|
|
694
696
|
|
|
@@ -722,7 +724,7 @@ The `modified` and `deleted` options use a discriminated union pattern:
|
|
|
722
724
|
| Column | Type | Constraints | Default | Description |
|
|
723
725
|
|--------|------|-------------|---------|-------------|
|
|
724
726
|
| `createdAt` | `timestamp` | `NOT NULL` | `now()` | When the record was created (always included) |
|
|
725
|
-
| `modifiedAt` | `timestamp` | `NOT NULL` | `now()`, auto-updates via `$onUpdate(() => new Date())` | When the record was last modified (optional, enabled by default) |
|
|
727
|
+
| `modifiedAt` | `timestamp` | `NOT NULL` | `now()`, auto-updates via `$onUpdate(() => new Date().toISOString())` | When the record was last modified (optional, enabled by default) |
|
|
726
728
|
| `deletedAt` | `timestamp` | nullable | `null` | When the record was soft-deleted (optional, **disabled by default**) |
|
|
727
729
|
|
|
728
730
|
#### Usage Examples
|
|
@@ -831,8 +833,10 @@ await db.update(myTable)
|
|
|
831
833
|
The enricher provides **conditional TypeScript type inference** based on the options:
|
|
832
834
|
|
|
833
835
|
```typescript
|
|
836
|
+
type TIsoTimestampColumn = ReturnType<typeof isoTimestamp>; // custom ISO 8601 timestamp column
|
|
837
|
+
|
|
834
838
|
type TTzEnricherResult<Opts extends TTzEnricherOptions | undefined = undefined> = {
|
|
835
|
-
createdAt: NotNull<HasDefault<
|
|
839
|
+
createdAt: NotNull<HasDefault<TIsoTimestampColumn>>;
|
|
836
840
|
} & (/* modifiedAt included unless opts.modified.enable === false */)
|
|
837
841
|
& (/* deletedAt included only when opts.deleted.enable === true */);
|
|
838
842
|
```
|
|
@@ -854,7 +858,7 @@ Merges timestamp columns into an existing column definitions object.
|
|
|
854
858
|
|
|
855
859
|
Adds `createdBy` and `modifiedBy` columns to track which user created or modified a record.
|
|
856
860
|
|
|
857
|
-
**File:** `packages/core/src/
|
|
861
|
+
**File:** `packages/core/src/connectors/postgres/models/enrichers/user-audit.enricher.ts`
|
|
858
862
|
|
|
859
863
|
#### Signature
|
|
860
864
|
|
|
@@ -888,8 +892,8 @@ type TUserAuditEnricherOptions = {
|
|
|
888
892
|
|
|
889
893
|
The enricher uses Hono's `contextStorage` (via `tryGetContext()`) to automatically retrieve the current user ID from the request context at insert/update time:
|
|
890
894
|
|
|
891
|
-
- **`createdBy`**: Set via `$default()`
|
|
892
|
-
- **`modifiedBy`**: Set via both `$default()` and `$onUpdate()`
|
|
895
|
+
- **`createdBy`**: Set via `$default()` - only populated on record creation
|
|
896
|
+
- **`modifiedBy`**: Set via both `$default()` and `$onUpdate()` - populated on creation and updated on every modification
|
|
893
897
|
|
|
894
898
|
The user ID is read from the `Authentication.AUDIT_USER_ID` key in the Hono context.
|
|
895
899
|
|
|
@@ -1023,7 +1027,7 @@ Merges user audit columns into an existing column definitions object with proper
|
|
|
1023
1027
|
|
|
1024
1028
|
Adds polymorphic principal columns for associating a record with different entity types. This is the polymorphic association pattern where a row can belong to different parent types (e.g., a comment can belong to a Post, User, or Product).
|
|
1025
1029
|
|
|
1026
|
-
**File:** `packages/core/src/
|
|
1030
|
+
**File:** `packages/core/src/connectors/postgres/models/enrichers/principal.enricher.ts`
|
|
1027
1031
|
|
|
1028
1032
|
#### Signature
|
|
1029
1033
|
|
|
@@ -1142,7 +1146,7 @@ Merges principal columns into an existing column definitions object.
|
|
|
1142
1146
|
|
|
1143
1147
|
Adds polymorphic data storage columns for entities that need to store values of different types in a single table. This is useful for key-value stores, settings tables, or any schema where a row's value type is determined at runtime.
|
|
1144
1148
|
|
|
1145
|
-
**File:** `packages/core/src/
|
|
1149
|
+
**File:** `packages/core/src/connectors/postgres/models/enrichers/data-type.enricher.ts`
|
|
1146
1150
|
|
|
1147
1151
|
#### Signature
|
|
1148
1152
|
|
|
@@ -1215,7 +1219,7 @@ export const settingTable = pgTable('Setting', {
|
|
|
1215
1219
|
// Generates columns with SQL defaults:
|
|
1216
1220
|
// data_type text DEFAULT 'text'
|
|
1217
1221
|
// t_value text DEFAULT ''
|
|
1218
|
-
// nValue, bValue, jValue, boValue
|
|
1222
|
+
// nValue, bValue, jValue, boValue - no defaults
|
|
1219
1223
|
```
|
|
1220
1224
|
|
|
1221
1225
|
**Key-value store pattern:**
|
|
@@ -1276,7 +1280,7 @@ Generates a Zod schema for path parameters containing an `id` field, suitable fo
|
|
|
1276
1280
|
#### Signature
|
|
1277
1281
|
|
|
1278
1282
|
```typescript
|
|
1279
|
-
idParamsSchema(opts?: { idType:
|
|
1283
|
+
idParamsSchema(opts?: { idType: TIdSchemaType }): z.ZodObject<{ id: z.ZodNumber | z.ZodString }>
|
|
1280
1284
|
```
|
|
1281
1285
|
|
|
1282
1286
|
| `idType` | Default | Zod Type | Examples |
|
|
@@ -1479,13 +1483,23 @@ try {
|
|
|
1479
1483
|
|
|
1480
1484
|
### `getIdType`
|
|
1481
1485
|
|
|
1482
|
-
|
|
1486
|
+
There are two distinct `getIdType`s in the framework - don't confuse them:
|
|
1487
|
+
|
|
1488
|
+
| | Neutral instance method | PostgreSQL utility function |
|
|
1489
|
+
|---|---|---|
|
|
1490
|
+
| **Location** | `AbstractEntity.getIdType()` (`packages/core/src/base/models/base.ts`) | `getIdType()` (`packages/core/src/connectors/postgres/models/common/types.ts`) |
|
|
1491
|
+
| **Signature** | `getIdType(): TIdSchemaType` | `getIdType<T extends TTableSchemaWithId>(opts: { entity: T }): string` |
|
|
1492
|
+
| **Purpose** | Neutral capability every engine's entity implements - returns `'string'` \| `'number'` at the entity level. Used by `idParamsSchema` to build the right Zod schema for path parameters. | PostgreSQL-specific: inspects a Drizzle table schema's `id` column and returns its `dataType` (e.g., `'number'`, `'string'`), or `'unknown'` if not determinable |
|
|
1483
1493
|
|
|
1484
1494
|
```typescript
|
|
1485
|
-
|
|
1486
|
-
|
|
1495
|
+
// Neutral - instance method every AbstractEntity subclass exposes (default 'string', BasePostgresEntity overrides based on the column)
|
|
1496
|
+
const entity = new User();
|
|
1497
|
+
entity.getIdType(); // 'string' | 'number'
|
|
1487
1498
|
|
|
1488
|
-
|
|
1499
|
+
// PostgreSQL connector - standalone utility inspecting a raw Drizzle schema
|
|
1500
|
+
import { getIdType } from '@venizia/ignis/postgres';
|
|
1501
|
+
getIdType({ entity: User.schema }); // 'string' | 'number' | 'unknown'
|
|
1502
|
+
```
|
|
1489
1503
|
|
|
1490
1504
|
## See Also
|
|
1491
1505
|
|