@venizia/ignis-docs 0.2.0 → 0.2.1-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +24 -13
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +8 -4
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +247 -153
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +109 -323
- package/content/extensions/components/authentication/api.md +489 -602
- package/content/extensions/components/authentication/errors.md +135 -498
- package/content/extensions/components/authentication/index.md +89 -801
- package/content/extensions/components/authentication/usage.md +231 -955
- package/content/extensions/components/authorization/api.md +991 -644
- package/content/extensions/components/authorization/errors.md +204 -208
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +88 -795
- package/content/extensions/components/authorization/usage.md +219 -528
- package/content/extensions/components/health-check.md +77 -243
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +553 -285
- package/content/extensions/components/mail/errors.md +76 -62
- package/content/extensions/components/mail/index.md +111 -463
- package/content/extensions/components/mail/usage.md +139 -173
- package/content/extensions/components/request-tracker.md +70 -173
- package/content/extensions/components/socket-io/api.md +462 -785
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +69 -372
- package/content/extensions/components/socket-io/usage.md +212 -105
- package/content/extensions/components/static-asset/api.md +461 -141
- package/content/extensions/components/static-asset/errors.md +121 -53
- package/content/extensions/components/static-asset/index.md +84 -606
- package/content/extensions/components/static-asset/usage.md +184 -299
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +311 -404
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +75 -407
- package/content/extensions/components/websocket/usage.md +120 -338
- package/content/extensions/helpers/cron/index.md +52 -160
- package/content/extensions/helpers/crypto/index.md +67 -480
- package/content/extensions/helpers/crypto/reference.md +528 -0
- package/content/extensions/helpers/env/index.md +64 -178
- package/content/extensions/helpers/error/index.md +286 -196
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +76 -550
- package/content/extensions/helpers/inversion/reference.md +530 -0
- package/content/extensions/helpers/kafka/admin.md +23 -3
- package/content/extensions/helpers/kafka/compile-binary.md +83 -52
- package/content/extensions/helpers/kafka/consumer.md +65 -28
- package/content/extensions/helpers/kafka/examples.md +46 -253
- package/content/extensions/helpers/kafka/index.md +55 -617
- package/content/extensions/helpers/kafka/producer.md +156 -22
- package/content/extensions/helpers/kafka/schema-registry.md +59 -79
- package/content/extensions/helpers/logger/hf-logger.md +220 -0
- package/content/extensions/helpers/logger/index.md +78 -552
- package/content/extensions/helpers/logger/pino.md +105 -0
- package/content/extensions/helpers/logger/reference.md +937 -0
- package/content/extensions/helpers/network/api.md +276 -195
- package/content/extensions/helpers/network/index.md +87 -524
- package/content/extensions/helpers/queue/index.md +80 -897
- package/content/extensions/helpers/queue/reference.md +494 -0
- package/content/extensions/helpers/redis/index.md +86 -640
- package/content/extensions/helpers/redis/reference.md +784 -0
- package/content/extensions/helpers/secrets/index.md +136 -0
- package/content/extensions/helpers/socket-io/api.md +325 -204
- package/content/extensions/helpers/socket-io/index.md +79 -429
- package/content/extensions/helpers/storage/api.md +584 -464
- package/content/extensions/helpers/storage/index.md +80 -573
- package/content/extensions/helpers/types/index.md +75 -495
- package/content/extensions/helpers/types/reference.md +689 -0
- package/content/extensions/helpers/uid/index.md +176 -189
- package/content/extensions/helpers/websocket/api.md +366 -214
- package/content/extensions/helpers/websocket/index.md +68 -503
- package/content/extensions/helpers/worker-thread/index.md +62 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +5 -4
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +8 -7
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +64 -22
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +600 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +25 -46
- package/content/references/base/filter-system/application-usage.md +95 -123
- package/content/references/base/filter-system/array-operators.md +33 -43
- package/content/references/base/filter-system/comparison-operators.md +55 -63
- package/content/references/base/filter-system/default-filter.md +144 -350
- package/content/references/base/filter-system/fields-order-pagination.md +116 -148
- package/content/references/base/filter-system/index.md +114 -253
- package/content/references/base/filter-system/json-filtering.md +52 -181
- package/content/references/base/filter-system/list-operators.md +28 -44
- package/content/references/base/filter-system/logical-operators.md +69 -114
- package/content/references/base/filter-system/null-operators.md +41 -98
- package/content/references/base/filter-system/pattern-matching.md +48 -51
- package/content/references/base/filter-system/quick-reference.md +93 -196
- package/content/references/base/filter-system/range-operators.md +22 -38
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +173 -232
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +43 -28
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +81 -1452
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +281 -419
- package/content/references/base/repositories/index.md +84 -644
- package/content/references/base/repositories/mixins.md +25 -21
- package/content/references/base/repositories/relations.md +194 -375
- package/content/references/base/repositories/soft-deletable.md +67 -55
- package/content/references/base/secrets.md +267 -0
- package/content/references/base/services.md +8 -6
- package/content/references/configuration/environment-variables.md +73 -21
- package/content/references/configuration/index.md +51 -31
- package/content/references/index.md +1 -1
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +6 -2
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +81 -61
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +58 -218
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +24 -23
|
@@ -108,20 +108,52 @@ this.logger.debug('[config] Server options: %j', this.serverOptions);
|
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
> [!NOTE]
|
|
111
|
-
> `%j` on an `Error` instance drops `message` and `stack`
|
|
111
|
+
> `%j` on an `Error` instance drops `message` and `stack` - they are non-enumerable, so `JSON.stringify` never sees them. Use `%s` for errors, `%j`/`%o` for plain data objects. An object passed to `%s` is inspected up to `APP_ENV_LOGGER_INSPECT_DEPTH` levels deep (default `5`) instead of Node's hard-coded `depth: 0`, so nested fields print instead of collapsing to `[Object]`. See [Logger Helper](/extensions/helpers/logger/) for details.
|
|
112
112
|
|
|
113
113
|
### Log Levels
|
|
114
114
|
|
|
115
|
+
Exactly five levels exist, each a direct method on `ILogger`:
|
|
116
|
+
|
|
115
117
|
| Level | Use For |
|
|
116
118
|
|-------|---------|
|
|
119
|
+
| `emerg` | The process cannot continue |
|
|
117
120
|
| `error` | Exceptions that need attention |
|
|
118
121
|
| `warn` | Recoverable issues, deprecations |
|
|
119
122
|
| `info` | Important business events |
|
|
120
123
|
| `debug` | Detailed debugging information |
|
|
121
124
|
|
|
125
|
+
> [!NOTE]
|
|
126
|
+
> Secret-looking keys (token, password, apiKey, ...) are redacted to `[REDACTED]` before a line
|
|
127
|
+
> reaches any transport. `APP_ENV_LOGGER_DO_REDACT=false` disables it - local debugging only.
|
|
128
|
+
|
|
129
|
+
### Never Swallow a Catch
|
|
130
|
+
|
|
131
|
+
An empty or comment-only `catch` is banned. Log, then rethrow or return a defined fallback.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// ✅ GOOD
|
|
135
|
+
try {
|
|
136
|
+
await syncRemote();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
this.logger.error('[syncRemote] Sync failed | Error: %s', error);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ❌ BAD - the failure disappears
|
|
143
|
+
try {
|
|
144
|
+
await syncRemote();
|
|
145
|
+
} catch {
|
|
146
|
+
// ignore
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
122
150
|
## Standardized Error Handling
|
|
123
151
|
|
|
124
152
|
Use the `getError` helper and `HTTP` constants to throw consistent, formatted exceptions.
|
|
153
|
+
**Never `new Error`.** Across package boundaries test identity with `isApplicationError()`, never
|
|
154
|
+
`instanceof` - the class has more than one identity in a monorepo.
|
|
155
|
+
|
|
156
|
+
The normalized message is always one shape: `{ text, code, args }`, read from `error.normalized`.
|
|
125
157
|
|
|
126
158
|
### Basic Error
|
|
127
159
|
|
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
Use JSDoc comments for public APIs to improve IDE support and generate documentation.
|
|
4
4
|
|
|
5
|
+
## Comment Style
|
|
6
|
+
|
|
7
|
+
A comment states **only the constraint the code cannot show**. Keep it to 1-3 lines.
|
|
8
|
+
|
|
9
|
+
Never write:
|
|
10
|
+
|
|
11
|
+
- History or dates ("changed in June", "was previously async")
|
|
12
|
+
- Decision narration ("we tried X, then chose Y")
|
|
13
|
+
- A restatement of what the next line already says
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// ✅ GOOD - states a constraint the code cannot show
|
|
17
|
+
// winston is an optional peer; a compiled binary must register a provider explicitly,
|
|
18
|
+
// because only a class reference carries one into the bundle.
|
|
19
|
+
LoggerFactory.use({ provider: PinoLogger });
|
|
20
|
+
|
|
21
|
+
// ❌ BAD - restates the code, narrates history
|
|
22
|
+
// 2026-07-18: after discussing with the team we decided to switch the logger
|
|
23
|
+
// provider. This line calls use() on LoggerFactory and passes PinoLogger.
|
|
24
|
+
LoggerFactory.use({ provider: PinoLogger });
|
|
25
|
+
```
|
|
26
|
+
|
|
5
27
|
## When to Use JSDoc
|
|
6
28
|
|
|
7
29
|
| Context | Required? | Reason |
|
|
@@ -56,10 +78,7 @@ async find(opts: { filter: TFilter<TUser>; options?: IExtraOptions }): Promise<T
|
|
|
56
78
|
|
|
57
79
|
```typescript
|
|
58
80
|
/**
|
|
59
|
-
* Creates a new user account
|
|
60
|
-
*
|
|
61
|
-
* Validates that the email is unique, hashes the password,
|
|
62
|
-
* and sends a welcome email upon successful creation.
|
|
81
|
+
* Creates a new user account. Email must be unique; the password is hashed here, never by the caller.
|
|
63
82
|
*
|
|
64
83
|
* @param data - User creation data
|
|
65
84
|
* @returns The created user without sensitive fields
|
|
@@ -185,14 +204,14 @@ interface IJWTStrategyOptions {
|
|
|
185
204
|
*
|
|
186
205
|
* @example
|
|
187
206
|
* @repository({ model: User, dataSource: PostgresDataSource })
|
|
188
|
-
* export class UserRepository extends
|
|
207
|
+
* export class UserRepository extends DefaultRelationalRepository<typeof User.schema> {
|
|
189
208
|
* // Custom methods here
|
|
190
209
|
* }
|
|
191
210
|
*
|
|
192
|
-
* @see {@link
|
|
193
|
-
* @see {@link
|
|
211
|
+
* @see {@link BaseRelationalEntity} for model definition
|
|
212
|
+
* @see {@link BaseRelationalDataSource} for database connection
|
|
194
213
|
*/
|
|
195
|
-
class
|
|
214
|
+
class DefaultRelationalRepository<EntitySchema extends TTableSchemaWithId = TTableSchemaWithId> {
|
|
196
215
|
// ...
|
|
197
216
|
}
|
|
198
217
|
```
|
|
@@ -213,6 +232,7 @@ class DefaultCRUDRepository<EntitySchema extends TTableSchemaWithId = TTableSche
|
|
|
213
232
|
- Don't copy TypeScript types into JSDoc (they're already visible)
|
|
214
233
|
- Don't write multi-paragraph descriptions for simple functions
|
|
215
234
|
- Don't use JSDoc for private implementation details
|
|
235
|
+
- Don't record history, dates, or the reasoning behind a past change
|
|
216
236
|
|
|
217
237
|
## See Also
|
|
218
238
|
|
|
@@ -15,11 +15,12 @@ Avoid `export default` except for configuration files (e.g., `eslint.config.mjs`
|
|
|
15
15
|
```typescript
|
|
16
16
|
// ✅ GOOD
|
|
17
17
|
export class UserController { }
|
|
18
|
-
export
|
|
18
|
+
export const createUser = () => { };
|
|
19
19
|
export const DEFAULT_OPTIONS = { };
|
|
20
20
|
|
|
21
21
|
// ❌ BAD
|
|
22
22
|
export default class UserController { }
|
|
23
|
+
export function createUser() { } // arrow functions only, never `function`
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
## The Options Object Pattern
|
|
@@ -127,7 +128,8 @@ async syncData() {
|
|
|
127
128
|
import { executeWithPerformanceMeasure } from '@venizia/ignis-helpers';
|
|
128
129
|
|
|
129
130
|
await executeWithPerformanceMeasure({
|
|
130
|
-
logger: this.logger
|
|
131
|
+
logger: this.logger,
|
|
132
|
+
level: 'info', // default: 'debug'
|
|
131
133
|
scope: 'DataSync',
|
|
132
134
|
description: 'Sync user records',
|
|
133
135
|
task: async () => {
|
|
@@ -139,12 +141,18 @@ await executeWithPerformanceMeasure({
|
|
|
139
141
|
|
|
140
142
|
**Method-scoped logging pattern:**
|
|
141
143
|
|
|
144
|
+
Any class extending `BaseHelper` (services, controllers, repositories, helpers) already has
|
|
145
|
+
`this.logger`. Acquire one standalone with `ApplicationLogger.get(...)` and always annotate
|
|
146
|
+
`ILogger` - never a concrete provider class.
|
|
147
|
+
|
|
142
148
|
```typescript
|
|
149
|
+
import { ApplicationLogger, ILogger } from '@venizia/ignis-helpers';
|
|
150
|
+
|
|
143
151
|
class UserService {
|
|
144
|
-
private logger =
|
|
152
|
+
private logger: ILogger = ApplicationLogger.get('UserService');
|
|
145
153
|
|
|
146
154
|
async createUser(data: TCreateUserRequest) {
|
|
147
|
-
//
|
|
155
|
+
// .for() returns a method-scoped child logger
|
|
148
156
|
this.logger.for('createUser').info('Creating user: %j', data);
|
|
149
157
|
// Output: [UserService-createUser] Creating user: {...}
|
|
150
158
|
|
|
@@ -153,6 +161,7 @@ class UserService {
|
|
|
153
161
|
this.logger.for('createUser').info('User created: %s', user.id);
|
|
154
162
|
return user;
|
|
155
163
|
} catch (error) {
|
|
164
|
+
// %s, not %j - `message` and `stack` are non-enumerable, so %j drops them
|
|
156
165
|
this.logger.for('createUser').error('Failed: %s', error);
|
|
157
166
|
throw error;
|
|
158
167
|
}
|
|
@@ -160,6 +169,8 @@ class UserService {
|
|
|
160
169
|
}
|
|
161
170
|
```
|
|
162
171
|
|
|
172
|
+
Levels are exactly five, each a direct method: `debug`, `info`, `warn`, `error`, `emerg`.
|
|
173
|
+
|
|
163
174
|
## See Also
|
|
164
175
|
|
|
165
176
|
- [Naming Conventions](./naming-conventions) - Class and file naming
|
|
@@ -17,13 +17,18 @@ Maintain consistent code style using **Prettier** (formatting) and **ESLint** (c
|
|
|
17
17
|
| Error format | `[ClassName][method] Message` |
|
|
18
18
|
| Logging format | `[method] Message \| Key: %s` |
|
|
19
19
|
| Default options | `DEFAULT_OPTIONS` constant |
|
|
20
|
-
| Type safety |
|
|
20
|
+
| Type safety | Avoid `any` / `unknown`; derive types, never duplicate them |
|
|
21
|
+
| Abbreviations | None - `ProductRepository`, `TDocument` |
|
|
21
22
|
| Scope naming | `ClassName.name` |
|
|
22
23
|
| Arguments | Options object (`opts`) |
|
|
24
|
+
| Functions | Arrow functions, never `function` declarations |
|
|
23
25
|
| Exports | Named exports only |
|
|
24
26
|
| Return types | Explicitly defined |
|
|
25
27
|
| Control flow | Always use braces (`{}`) |
|
|
26
28
|
| Switch statements | Braces + default case required |
|
|
29
|
+
| Catch blocks | Never silent - always log |
|
|
30
|
+
| Errors | `getError` / `ApplicationError`, never `new Error` |
|
|
31
|
+
| Logger | Type as `ILogger`; `this.logger` or `ApplicationLogger.get(...)` |
|
|
27
32
|
| Imports | Node → Third-party → Internal → Relative |
|
|
28
33
|
| Function naming | `generate*`, `build*`, `to*`, `is*`, `extract*` |
|
|
29
34
|
|
|
@@ -55,7 +60,7 @@ type TUserRequest = { };
|
|
|
55
60
|
// Classes use PascalCase with suffix
|
|
56
61
|
class UserController extends BaseRestController { }
|
|
57
62
|
class UserService extends BaseService { }
|
|
58
|
-
class UserRepository extends
|
|
63
|
+
class UserRepository extends DefaultRelationalRepository { }
|
|
59
64
|
```
|
|
60
65
|
|
|
61
66
|
### File Structure
|
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Consistent naming improves code readability and maintainability.
|
|
4
4
|
|
|
5
|
+
## Never Abbreviate an Identifier
|
|
6
|
+
|
|
7
|
+
Hard rule. Write the whole word - in class names, variables, and type parameters alike.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// ✅ GOOD
|
|
11
|
+
class ProductRepository { }
|
|
12
|
+
type TProductDocument = { };
|
|
13
|
+
const configuration = resolveConfiguration();
|
|
14
|
+
class Repository<TDocument> { }
|
|
15
|
+
|
|
16
|
+
// ❌ BAD
|
|
17
|
+
class ProductRepo { }
|
|
18
|
+
type TProductDoc = { };
|
|
19
|
+
const cfg = resolveCfg();
|
|
20
|
+
class Repository<TDoc> { }
|
|
21
|
+
```
|
|
22
|
+
|
|
5
23
|
## Directory Structure
|
|
6
24
|
|
|
7
25
|
### Component Organization
|
|
@@ -80,6 +98,8 @@ Both styles are acceptable: `[type].ts` or `[name].[type].ts`
|
|
|
80
98
|
- Use `[type].ts` when there's only one file of that type in the folder
|
|
81
99
|
- Use `[name].[type].ts` when there are multiple files of the same type
|
|
82
100
|
- Use kebab-case for multi-word names: `jwt-token.service.ts`
|
|
101
|
+
- **A file name never repeats its folder.** Name the file for its role inside the folder:
|
|
102
|
+
`logger/winston/logger.ts`, not `logger/winston/winston-logger.ts`
|
|
83
103
|
|
|
84
104
|
## Type and Interface Prefixes
|
|
85
105
|
|
|
@@ -90,12 +110,16 @@ interface IHealthCheckOptions {
|
|
|
90
110
|
}
|
|
91
111
|
|
|
92
112
|
interface IAuthService {
|
|
93
|
-
signIn(context:
|
|
113
|
+
signIn(context: TContext, opts: TSignInRequest): Promise<AnyObject>;
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
// Type aliases use 'T' prefix
|
|
97
117
|
type TSignInRequest = z.infer<typeof SignInRequestSchema>;
|
|
98
|
-
type
|
|
118
|
+
type TJWKSMode = TConstValue<typeof JWKSModes>;
|
|
119
|
+
|
|
120
|
+
// ❌ Never a bare string-literal union for an enumerable set - it has no runtime
|
|
121
|
+
// values to validate against. Use a const class + TConstValue instead.
|
|
122
|
+
type TLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'emerg';
|
|
99
123
|
|
|
100
124
|
// Generic constraints
|
|
101
125
|
type TTableSchemaWithId<TC extends TableConfig = TableConfig> = PgTable<TC> & {
|
|
@@ -123,7 +147,7 @@ export class SocketIOBindingKeys {
|
|
|
123
147
|
Use underscore prefix (`_`) for private and protected class fields to distinguish them from public fields and method parameters.
|
|
124
148
|
|
|
125
149
|
```typescript
|
|
126
|
-
class MyRepository extends
|
|
150
|
+
class MyRepository extends DefaultRelationalRepository {
|
|
127
151
|
// Private fields with underscore prefix
|
|
128
152
|
private _dataSource: AbstractDataSource;
|
|
129
153
|
private _entity: AbstractEntity;
|
|
@@ -61,6 +61,9 @@ export class UserController extends BaseRestController {
|
|
|
61
61
|
}
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
`@get`, `@post`, `@put`, `@patch`, and `@del` are the same decorator with `method` preset - the
|
|
65
|
+
config they take omits `method`.
|
|
66
|
+
|
|
64
67
|
## Method 3: Using `bindRoute` (Programmatic)
|
|
65
68
|
|
|
66
69
|
Register routes in the `binding()` lifecycle method:
|
|
@@ -115,18 +118,19 @@ Use Zod with `.openapi()` for automatic documentation:
|
|
|
115
118
|
|
|
116
119
|
```typescript
|
|
117
120
|
const CreateUserSchema = z.object({
|
|
118
|
-
email: z.
|
|
121
|
+
email: z.email(),
|
|
119
122
|
name: z.string().min(1).max(100),
|
|
120
123
|
}).openapi({
|
|
121
124
|
description: 'Create user request body',
|
|
122
125
|
example: { email: 'user@example.com', name: 'John Doe' },
|
|
123
126
|
});
|
|
124
127
|
|
|
128
|
+
// Zod v4 top-level string formats - not the deprecated z.string().email() chain
|
|
125
129
|
const UserSchema = z.object({
|
|
126
|
-
id: z.
|
|
127
|
-
email: z.
|
|
130
|
+
id: z.uuid(),
|
|
131
|
+
email: z.email(),
|
|
128
132
|
name: z.string(),
|
|
129
|
-
createdAt: z.
|
|
133
|
+
createdAt: z.iso.datetime(),
|
|
130
134
|
}).openapi({
|
|
131
135
|
description: 'User response',
|
|
132
136
|
});
|
|
@@ -165,4 +169,4 @@ export const RouteConfigs = {
|
|
|
165
169
|
|
|
166
170
|
- [API Usage Examples](../api-usage-examples) - Full API patterns
|
|
167
171
|
- [Controllers Reference](../../references/base/controllers) - Controller API
|
|
168
|
-
- [
|
|
172
|
+
- [API Reference Component](../../extensions/components/api-reference) - OpenAPI setup
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
IGNIS provides centralized development configurations via the `@venizia/dev-configs` package.
|
|
4
4
|
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> **Bun is the only package manager** - never npm, yarn, or pnpm. **Compile with `tsc` directly** -
|
|
7
|
+
> never `npx`, `bunx`, or `bun x`.
|
|
8
|
+
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
```bash
|
|
@@ -9,7 +13,7 @@ bun add -d @venizia/dev-configs
|
|
|
9
13
|
```
|
|
10
14
|
|
|
11
15
|
This package provides:
|
|
12
|
-
- **ESLint rules** -
|
|
16
|
+
- **ESLint rules** - flat config, pre-configured for Node.js/TypeScript projects
|
|
13
17
|
- **Prettier settings** - Consistent formatting across all IGNIS projects
|
|
14
18
|
- **TypeScript configs** - Shared base and common configurations
|
|
15
19
|
|
|
@@ -121,6 +125,15 @@ Use the centralized TypeScript configs:
|
|
|
121
125
|
| `useDefineForClassFields` | `false` | Decorator-compatible class fields |
|
|
122
126
|
| `strict` | `true` | Strict type checking |
|
|
123
127
|
| `skipLibCheck` | `true` | Faster compilation |
|
|
128
|
+
| `noEmitOnError` | `true` | A type error emits nothing - the build fails loudly |
|
|
129
|
+
|
|
130
|
+
Every package in the monorepo pins **TypeScript `^6.0.3`**; `@venizia/dev-configs` accepts
|
|
131
|
+
`^5.0.0 || ^6.0.0`. Do not upgrade past 6 - `typescript-eslint` breaks.
|
|
132
|
+
|
|
133
|
+
> [!WARNING]
|
|
134
|
+
> Bun does not resolve `experimentalDecorators` / `emitDecoratorMetadata` through `extends`. In an
|
|
135
|
+
> **application** `tsconfig.json` that Bun executes, declare both flags directly in
|
|
136
|
+
> `compilerOptions`, or `@inject` parameter decorators are dropped silently and DI fails at boot.
|
|
124
137
|
|
|
125
138
|
## IDE Integration
|
|
126
139
|
|
|
@@ -26,6 +26,39 @@ const data: TUserResponse = await fetchData();
|
|
|
26
26
|
const result: TProcessResult = processData();
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
### When a Cast Is Unavoidable
|
|
30
|
+
|
|
31
|
+
Some boundaries genuinely cannot be typed. Reach for the **simplest** escape hatch - `as any` or the
|
|
32
|
+
`AnyType` alias - never a chained one.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// ✅ GOOD
|
|
36
|
+
const handler = raw as any;
|
|
37
|
+
const value = raw as AnyType;
|
|
38
|
+
|
|
39
|
+
// ❌ BAD - baroque, hides what is actually being asserted
|
|
40
|
+
const handler = raw as unknown as TRouteHandler;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Derive Types, Never Duplicate Them
|
|
44
|
+
|
|
45
|
+
Prefer a compile-time type derived from the definition over a hand-maintained copy. A duplicate
|
|
46
|
+
drifts silently; a derived type breaks the build the moment the definition changes.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// ✅ GOOD - derived from the single source of truth
|
|
50
|
+
type TUser = typeof User.schema.$inferSelect;
|
|
51
|
+
type TNewUser = typeof User.schema.$inferInsert;
|
|
52
|
+
type TSignInRequest = z.infer<typeof SignInRequestSchema>;
|
|
53
|
+
|
|
54
|
+
// ❌ BAD - a parallel definition that will drift
|
|
55
|
+
type TUser = {
|
|
56
|
+
id: string;
|
|
57
|
+
email: string;
|
|
58
|
+
createdAt: Date;
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
29
62
|
## Explicit Return Types
|
|
30
63
|
|
|
31
64
|
Always define explicit return types for **public methods** and **API handlers**.
|
|
@@ -53,7 +86,7 @@ public async findUser(id: string) {
|
|
|
53
86
|
```typescript
|
|
54
87
|
// Define schema
|
|
55
88
|
export const SignInRequestSchema = z.object({
|
|
56
|
-
email: z.
|
|
89
|
+
email: z.email(),
|
|
57
90
|
password: z.string().min(8),
|
|
58
91
|
});
|
|
59
92
|
|
|
@@ -76,7 +109,7 @@ type RouteKey = keyof typeof RouteConfigs; // 'GET_USERS' | 'GET_USER_BY_ID'
|
|
|
76
109
|
### Generic Type Constraints
|
|
77
110
|
|
|
78
111
|
```typescript
|
|
79
|
-
export class
|
|
112
|
+
export class DefaultRelationalRepository<
|
|
80
113
|
EntitySchema extends TTableSchemaWithId = TTableSchemaWithId
|
|
81
114
|
> {
|
|
82
115
|
// EntitySchema is constrained to have an 'id' column
|
|
@@ -124,14 +157,14 @@ const result2 = await userRepository.create({ data: user });
|
|
|
124
157
|
|
|
125
158
|
```typescript
|
|
126
159
|
// Type guard function
|
|
127
|
-
|
|
160
|
+
const isUser = (obj: unknown): obj is TUser => {
|
|
128
161
|
return (
|
|
129
162
|
typeof obj === 'object' &&
|
|
130
163
|
obj !== null &&
|
|
131
164
|
'id' in obj &&
|
|
132
165
|
'email' in obj
|
|
133
166
|
);
|
|
134
|
-
}
|
|
167
|
+
};
|
|
135
168
|
|
|
136
169
|
// Usage
|
|
137
170
|
const data = await fetchData();
|
|
@@ -148,7 +181,7 @@ type TResult<T> =
|
|
|
148
181
|
| { success: true; data: T }
|
|
149
182
|
| { success: false; error: string };
|
|
150
183
|
|
|
151
|
-
|
|
184
|
+
const processResult = <T>(result: TResult<T>) => {
|
|
152
185
|
if (result.success) {
|
|
153
186
|
// TypeScript knows result.data exists
|
|
154
187
|
return result.data;
|
|
@@ -156,7 +189,7 @@ function processResult<T>(result: TResult<T>) {
|
|
|
156
189
|
|
|
157
190
|
// TypeScript knows result.error exists
|
|
158
191
|
throw getError({ message: result.error });
|
|
159
|
-
}
|
|
192
|
+
};
|
|
160
193
|
```
|
|
161
194
|
|
|
162
195
|
## See Also
|
|
@@ -26,7 +26,6 @@ export class Application extends BaseApplication {
|
|
|
26
26
|
|
|
27
27
|
// Services
|
|
28
28
|
this.service(MyNewService); // <-- Don't forget this line
|
|
29
|
-
this.registerAuth();
|
|
30
29
|
|
|
31
30
|
// Controllers
|
|
32
31
|
this.controller(TestController);
|
|
@@ -174,31 +173,44 @@ await userRepository.updateBy({
|
|
|
174
173
|
|
|
175
174
|
## 7. Schema Key Mismatch
|
|
176
175
|
|
|
177
|
-
**Problem:**
|
|
176
|
+
**Problem:** The repository cannot find its table in the DataSource schema.
|
|
178
177
|
|
|
179
178
|
**Error Message:**
|
|
180
179
|
```
|
|
181
|
-
[UserRepository] Schema key mismatch | Entity name 'User' not found in connector.query | Available keys: [
|
|
180
|
+
[UserRepository] Schema key mismatch | Entity name 'User' not found in connector.query | Available keys: [users, Post] | Ensure the model's TABLE_NAME matches the schema registration key
|
|
182
181
|
```
|
|
183
182
|
|
|
184
|
-
**
|
|
183
|
+
**Cause:** Two keys are resolved by *different* rules:
|
|
184
|
+
|
|
185
|
+
| Key | Rule |
|
|
186
|
+
|-----|------|
|
|
187
|
+
| Schema registration key | `@model({ tableName })` > `static TABLE_NAME` > class name |
|
|
188
|
+
| Repository lookup key (`entity.name`) | `static TABLE_NAME` > class name |
|
|
189
|
+
|
|
190
|
+
`@model({ tableName })` moves the registration key but not the lookup key. Setting it alone is what breaks:
|
|
185
191
|
|
|
186
192
|
```typescript
|
|
187
|
-
// ❌ BAD -
|
|
188
|
-
@model({ type: 'entity' })
|
|
189
|
-
export class User extends
|
|
190
|
-
static override
|
|
191
|
-
static override schema = pgTable('User', { /* ... */ });
|
|
193
|
+
// ❌ BAD - registered under 'users', looked up as 'User'
|
|
194
|
+
@model({ type: 'entity', tableName: 'users' })
|
|
195
|
+
export class User extends BaseRelationalEntity<typeof User.schema> {
|
|
196
|
+
static override schema = pgTable('users', { /* ... */ });
|
|
192
197
|
}
|
|
193
198
|
|
|
194
|
-
// ✅ GOOD -
|
|
199
|
+
// ✅ GOOD - set neither; both keys resolve to the class name
|
|
195
200
|
@model({ type: 'entity' })
|
|
196
|
-
export class User extends
|
|
197
|
-
static override schema = pgTable('
|
|
201
|
+
export class User extends BaseRelationalEntity<typeof User.schema> {
|
|
202
|
+
static override schema = pgTable('users', { /* ... */ });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ✅ GOOD - or set both to the same value
|
|
206
|
+
@model({ type: 'entity', tableName: 'users' })
|
|
207
|
+
export class User extends BaseRelationalEntity<typeof User.schema> {
|
|
208
|
+
static override TABLE_NAME = 'users';
|
|
209
|
+
static override schema = pgTable('users', { /* ... */ });
|
|
198
210
|
}
|
|
199
211
|
```
|
|
200
212
|
|
|
201
|
-
|
|
213
|
+
`static TABLE_NAME` on its own is safe - it moves both keys together. The first argument of `pgTable()` is the physical SQL table name and plays no part in either lookup.
|
|
202
214
|
|
|
203
215
|
## 8. Validation Error Response Structure
|
|
204
216
|
|
|
@@ -209,8 +221,12 @@ export class User extends BasePostgresEntity<typeof User.schema> {
|
|
|
209
221
|
```json
|
|
210
222
|
{
|
|
211
223
|
"statusCode": 422,
|
|
212
|
-
"message": "Invalid email",
|
|
213
|
-
"
|
|
224
|
+
"message": "Invalid email address",
|
|
225
|
+
"normalized": {
|
|
226
|
+
"text": "Invalid email address",
|
|
227
|
+
"code": "invalid_format",
|
|
228
|
+
"args": {}
|
|
229
|
+
},
|
|
214
230
|
"requestId": "abc123",
|
|
215
231
|
"details": {
|
|
216
232
|
"url": "http://localhost:3000/users",
|
|
@@ -218,23 +234,30 @@ export class User extends BasePostgresEntity<typeof User.schema> {
|
|
|
218
234
|
"cause": [
|
|
219
235
|
{
|
|
220
236
|
"path": "email",
|
|
221
|
-
"message": "Invalid email",
|
|
222
|
-
"code": "
|
|
223
|
-
"expected": "email",
|
|
224
|
-
"received": "string"
|
|
237
|
+
"message": "Invalid email address",
|
|
238
|
+
"code": "invalid_format"
|
|
225
239
|
},
|
|
226
240
|
{
|
|
227
241
|
"path": "age",
|
|
228
|
-
"message": "
|
|
242
|
+
"message": "Invalid input: expected number, received string",
|
|
229
243
|
"code": "invalid_type",
|
|
230
|
-
"expected": "number"
|
|
231
|
-
"received": "string"
|
|
244
|
+
"expected": "number"
|
|
232
245
|
}
|
|
233
246
|
]
|
|
234
247
|
}
|
|
235
248
|
}
|
|
236
249
|
```
|
|
237
250
|
|
|
251
|
+
- `normalized.code` comes from the first issue carrying a `params.code`, otherwise the first issue's raw Zod code. Attach a stable code clients can branch on with `.refine()`:
|
|
252
|
+
```typescript
|
|
253
|
+
z.number().refine(isTwoDecimals, {
|
|
254
|
+
message: 'At most 2 decimal places',
|
|
255
|
+
params: { code: 'app.price.too_many_places' },
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
- `args` is always `{}` here - per-field detail lives in `details.cause`.
|
|
259
|
+
- `details.stack` is omitted in production; `details.cause` is not.
|
|
260
|
+
|
|
238
261
|
**Client-side handling:**
|
|
239
262
|
```typescript
|
|
240
263
|
try {
|
|
@@ -253,32 +276,31 @@ try {
|
|
|
253
276
|
|
|
254
277
|
**Problem:** Application fails to start with `Cannot access 'X' before initialization` or similar errors.
|
|
255
278
|
|
|
256
|
-
**Cause:** Two
|
|
279
|
+
**Cause:** Two models reference each other's schema. With a cycle, one module is still mid-evaluation when the other reads its exports, so `User.schema` is `undefined`.
|
|
257
280
|
|
|
258
|
-
**Solution:** Use
|
|
281
|
+
**Solution:** `relations` accepts a resolver function. Use it - the framework calls it in `buildSchema()`, after every model has finished loading:
|
|
259
282
|
|
|
260
283
|
```typescript
|
|
261
|
-
// ❌ BAD - Direct import causes circular dependency
|
|
262
284
|
import { User } from './user.model';
|
|
263
285
|
|
|
286
|
+
// ❌ BAD - array literal is evaluated at module load, while the cycle is still open
|
|
264
287
|
@model({ type: 'entity' })
|
|
265
|
-
export class Order extends
|
|
266
|
-
static override relations
|
|
267
|
-
{
|
|
288
|
+
export class Order extends BaseRelationalEntity<typeof Order.schema> {
|
|
289
|
+
static override relations: TRelationConfig[] = [
|
|
290
|
+
{ name: 'owner', type: RelationTypes.ONE, schema: User.schema, metadata: { ... } },
|
|
268
291
|
];
|
|
269
292
|
}
|
|
270
293
|
|
|
271
|
-
// ✅ GOOD -
|
|
294
|
+
// ✅ GOOD - resolver defers the User.schema read until after both modules finish loading
|
|
272
295
|
@model({ type: 'entity' })
|
|
273
|
-
export class Order extends
|
|
274
|
-
static override relations = (): TRelationConfig[] =>
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
};
|
|
296
|
+
export class Order extends BaseRelationalEntity<typeof Order.schema> {
|
|
297
|
+
static override relations = (): TRelationConfig[] => [
|
|
298
|
+
{ name: 'owner', type: RelationTypes.ONE, schema: User.schema, metadata: { ... } },
|
|
299
|
+
];
|
|
278
300
|
}
|
|
279
301
|
```
|
|
280
302
|
|
|
281
|
-
**Alternative:** Restructure
|
|
303
|
+
**Alternative:** Restructure so both models import their shared pieces from a third module.
|
|
282
304
|
|
|
283
305
|
## 10. Transaction Not Rolling Back
|
|
284
306
|
|
|
@@ -306,6 +328,8 @@ try {
|
|
|
306
328
|
}
|
|
307
329
|
```
|
|
308
330
|
|
|
331
|
+
This exact shape is safe even when `commit()` is what failed: a rollback on an already-torn-down transaction is a no-op, so it never replaces your original error. A failed `COMMIT` always throws - never treat a resolved `commit()` as anything but success.
|
|
332
|
+
|
|
309
333
|
## 11. Fire-and-Forget Promises Losing Context
|
|
310
334
|
|
|
311
335
|
**Problem:** `getCurrentUserId()` or other context-dependent functions return `null` in background tasks.
|
|
@@ -77,6 +77,7 @@ The project uses a Makefile for common development tasks:
|
|
|
77
77
|
| `make build` | Rebuild all packages in correct order |
|
|
78
78
|
| `make clean` | Clean build artifacts from all packages |
|
|
79
79
|
| `make lint` | Lint all packages |
|
|
80
|
+
| `make lint-all` | Lint packages **and** examples - this is the bar a PR must clear |
|
|
80
81
|
| `make help` | Show all available commands |
|
|
81
82
|
|
|
82
83
|
**Individual package builds** (dependencies are automatically resolved):
|
|
@@ -153,17 +154,20 @@ git commit -m "chore: upgrade Hono to v4.0"
|
|
|
153
154
|
### Step 4: Validate
|
|
154
155
|
|
|
155
156
|
```bash
|
|
156
|
-
# Lint and format (from root)
|
|
157
|
-
make lint
|
|
157
|
+
# Lint and format (from root) - zero warnings and zero errors is the bar
|
|
158
|
+
make lint-all
|
|
158
159
|
# Or run `bun run lint:fix` inside a package directory
|
|
159
160
|
|
|
160
161
|
# Build all packages (from root)
|
|
161
162
|
make build
|
|
162
163
|
|
|
163
|
-
# Run tests (from a package directory, e.g. packages/core or packages/boot)
|
|
164
|
-
cd packages/core && bun test
|
|
164
|
+
# Run tests (from a package directory, e.g. packages/core-server or packages/boot)
|
|
165
|
+
cd packages/core-server && bun test
|
|
165
166
|
```
|
|
166
167
|
|
|
168
|
+
> [!WARNING]
|
|
169
|
+
> `make <package>` **cleans `dist/` before it builds**, and the build type-checks `__tests__` too. One broken test therefore aborts the build after `dist/` is already gone, leaving an **empty `dist/`** and a cascade of unrelated-looking import failures in `bun test`. If imports suddenly break everywhere, check `dist/` before chasing the imports.
|
|
170
|
+
|
|
167
171
|
## 3. Submit Pull Request
|
|
168
172
|
|
|
169
173
|
```bash
|