@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.
Files changed (174) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +24 -13
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +8 -4
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +247 -153
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +109 -323
  26. package/content/extensions/components/authentication/api.md +489 -602
  27. package/content/extensions/components/authentication/errors.md +135 -498
  28. package/content/extensions/components/authentication/index.md +89 -801
  29. package/content/extensions/components/authentication/usage.md +231 -955
  30. package/content/extensions/components/authorization/api.md +991 -644
  31. package/content/extensions/components/authorization/errors.md +204 -208
  32. package/content/extensions/components/authorization/getting-started.md +227 -0
  33. package/content/extensions/components/authorization/index.md +88 -795
  34. package/content/extensions/components/authorization/usage.md +219 -528
  35. package/content/extensions/components/health-check.md +77 -243
  36. package/content/extensions/components/index.md +24 -90
  37. package/content/extensions/components/mail/api.md +553 -285
  38. package/content/extensions/components/mail/errors.md +76 -62
  39. package/content/extensions/components/mail/index.md +111 -463
  40. package/content/extensions/components/mail/usage.md +139 -173
  41. package/content/extensions/components/request-tracker.md +70 -173
  42. package/content/extensions/components/socket-io/api.md +462 -785
  43. package/content/extensions/components/socket-io/errors.md +49 -51
  44. package/content/extensions/components/socket-io/index.md +69 -372
  45. package/content/extensions/components/socket-io/usage.md +212 -105
  46. package/content/extensions/components/static-asset/api.md +461 -141
  47. package/content/extensions/components/static-asset/errors.md +121 -53
  48. package/content/extensions/components/static-asset/index.md +84 -606
  49. package/content/extensions/components/static-asset/usage.md +184 -299
  50. package/content/extensions/components/template/index.md +3 -3
  51. package/content/extensions/components/websocket/api.md +311 -404
  52. package/content/extensions/components/websocket/errors.md +47 -56
  53. package/content/extensions/components/websocket/index.md +75 -407
  54. package/content/extensions/components/websocket/usage.md +120 -338
  55. package/content/extensions/helpers/cron/index.md +52 -160
  56. package/content/extensions/helpers/crypto/index.md +67 -480
  57. package/content/extensions/helpers/crypto/reference.md +528 -0
  58. package/content/extensions/helpers/env/index.md +64 -178
  59. package/content/extensions/helpers/error/index.md +286 -196
  60. package/content/extensions/helpers/index.md +61 -47
  61. package/content/extensions/helpers/inversion/index.md +76 -550
  62. package/content/extensions/helpers/inversion/reference.md +530 -0
  63. package/content/extensions/helpers/kafka/admin.md +23 -3
  64. package/content/extensions/helpers/kafka/compile-binary.md +83 -52
  65. package/content/extensions/helpers/kafka/consumer.md +65 -28
  66. package/content/extensions/helpers/kafka/examples.md +46 -253
  67. package/content/extensions/helpers/kafka/index.md +55 -617
  68. package/content/extensions/helpers/kafka/producer.md +156 -22
  69. package/content/extensions/helpers/kafka/schema-registry.md +59 -79
  70. package/content/extensions/helpers/logger/hf-logger.md +220 -0
  71. package/content/extensions/helpers/logger/index.md +78 -552
  72. package/content/extensions/helpers/logger/pino.md +105 -0
  73. package/content/extensions/helpers/logger/reference.md +937 -0
  74. package/content/extensions/helpers/network/api.md +276 -195
  75. package/content/extensions/helpers/network/index.md +87 -524
  76. package/content/extensions/helpers/queue/index.md +80 -897
  77. package/content/extensions/helpers/queue/reference.md +494 -0
  78. package/content/extensions/helpers/redis/index.md +86 -640
  79. package/content/extensions/helpers/redis/reference.md +784 -0
  80. package/content/extensions/helpers/secrets/index.md +136 -0
  81. package/content/extensions/helpers/socket-io/api.md +325 -204
  82. package/content/extensions/helpers/socket-io/index.md +79 -429
  83. package/content/extensions/helpers/storage/api.md +584 -464
  84. package/content/extensions/helpers/storage/index.md +80 -573
  85. package/content/extensions/helpers/types/index.md +75 -495
  86. package/content/extensions/helpers/types/reference.md +689 -0
  87. package/content/extensions/helpers/uid/index.md +176 -189
  88. package/content/extensions/helpers/websocket/api.md +366 -214
  89. package/content/extensions/helpers/websocket/index.md +68 -503
  90. package/content/extensions/helpers/worker-thread/index.md +62 -396
  91. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  92. package/content/extensions/index.md +38 -39
  93. package/content/extensions/src-details/mcp-server.md +96 -548
  94. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  95. package/content/guides/core-concepts/persistent/index.md +5 -1
  96. package/content/guides/core-concepts/persistent/models.md +1 -1
  97. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  98. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  99. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  100. package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
  101. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  102. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  103. package/content/guides/core-concepts/secrets-vault.md +177 -0
  104. package/content/guides/core-concepts/services.md +1 -1
  105. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  106. package/content/guides/get-started/philosophy.md +135 -670
  107. package/content/guides/get-started/setup.md +53 -74
  108. package/content/guides/migrations/redis-helpers-migration.md +5 -4
  109. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  110. package/content/guides/migrations/unified-connectors-migration.md +8 -7
  111. package/content/guides/tutorials/ecommerce-api.md +3 -8
  112. package/content/guides/tutorials/realtime-chat.md +1 -1
  113. package/content/references/base/application.md +64 -22
  114. package/content/references/base/components.md +3 -3
  115. package/content/references/base/connectors.md +79 -136
  116. package/content/references/base/controllers.md +12 -12
  117. package/content/references/base/datasources-reference.md +600 -0
  118. package/content/references/base/datasources.md +84 -444
  119. package/content/references/base/dependency-injection.md +25 -46
  120. package/content/references/base/filter-system/application-usage.md +95 -123
  121. package/content/references/base/filter-system/array-operators.md +33 -43
  122. package/content/references/base/filter-system/comparison-operators.md +55 -63
  123. package/content/references/base/filter-system/default-filter.md +144 -350
  124. package/content/references/base/filter-system/fields-order-pagination.md +116 -148
  125. package/content/references/base/filter-system/index.md +114 -253
  126. package/content/references/base/filter-system/json-filtering.md +52 -181
  127. package/content/references/base/filter-system/list-operators.md +28 -44
  128. package/content/references/base/filter-system/logical-operators.md +69 -114
  129. package/content/references/base/filter-system/null-operators.md +41 -98
  130. package/content/references/base/filter-system/pattern-matching.md +48 -51
  131. package/content/references/base/filter-system/quick-reference.md +93 -196
  132. package/content/references/base/filter-system/range-operators.md +22 -38
  133. package/content/references/base/filter-system/tips.md +70 -133
  134. package/content/references/base/filter-system/use-cases.md +173 -232
  135. package/content/references/base/grpc-controllers.md +53 -17
  136. package/content/references/base/index.md +5 -3
  137. package/content/references/base/middlewares.md +43 -28
  138. package/content/references/base/models-reference.md +886 -0
  139. package/content/references/base/models.md +81 -1452
  140. package/content/references/base/providers.md +8 -8
  141. package/content/references/base/repositories/advanced.md +281 -419
  142. package/content/references/base/repositories/index.md +84 -644
  143. package/content/references/base/repositories/mixins.md +25 -21
  144. package/content/references/base/repositories/relations.md +194 -375
  145. package/content/references/base/repositories/soft-deletable.md +67 -55
  146. package/content/references/base/secrets.md +267 -0
  147. package/content/references/base/services.md +8 -6
  148. package/content/references/configuration/environment-variables.md +73 -21
  149. package/content/references/configuration/index.md +51 -31
  150. package/content/references/index.md +1 -1
  151. package/content/references/quick-reference.md +3 -16
  152. package/content/references/utilities/crypto.md +35 -76
  153. package/content/references/utilities/date.md +33 -73
  154. package/content/references/utilities/duration.md +85 -0
  155. package/content/references/utilities/index.md +6 -2
  156. package/content/references/utilities/jsx-reference.md +298 -0
  157. package/content/references/utilities/jsx.md +82 -525
  158. package/content/references/utilities/module.md +81 -61
  159. package/content/references/utilities/parse.md +34 -64
  160. package/content/references/utilities/performance.md +33 -58
  161. package/content/references/utilities/promise.md +28 -62
  162. package/content/references/utilities/request.md +58 -218
  163. package/content/references/utilities/retry.md +139 -0
  164. package/content/references/utilities/schema.md +43 -137
  165. package/content/references/utilities/statuses-reference.md +361 -0
  166. package/content/references/utilities/statuses.md +63 -667
  167. package/dist/mcp-server/index.js +0 -0
  168. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  169. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  171. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  172. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  173. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  174. 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` -- 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.
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 with the given data.
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 DefaultCRUDRepository<typeof User.schema> {
207
+ * export class UserRepository extends DefaultRelationalRepository<typeof User.schema> {
189
208
  * // Custom methods here
190
209
  * }
191
210
  *
192
- * @see {@link BasePostgresEntity} for model definition
193
- * @see {@link BasePostgresDataSource} for database connection
211
+ * @see {@link BaseRelationalEntity} for model definition
212
+ * @see {@link BaseRelationalDataSource} for database connection
194
213
  */
195
- class DefaultCRUDRepository<EntitySchema extends TTableSchemaWithId = TTableSchemaWithId> {
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 function createUser() { }
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.for('syncData'),
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 = Logger.get('UserService');
152
+ private logger: ILogger = ApplicationLogger.get('UserService');
145
153
 
146
154
  async createUser(data: TCreateUserRequest) {
147
- // Use .for() to add method context to all logs
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 | No `any` or `unknown` allowed |
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 DefaultCRUDRepository { }
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: Context, opts: TSignInRequest): Promise<AnyObject>;
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 TAuthStrategy = TConstValue<typeof AuthenticateStrategy>;
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 DefaultCRUDRepository {
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.string().email(),
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.string().uuid(),
127
- email: z.string().email(),
130
+ id: z.uuid(),
131
+ email: z.email(),
128
132
  name: z.string(),
129
- createdAt: z.string().datetime(),
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
- - [Swagger Component](../../extensions/components/api-reference) - OpenAPI setup
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** - Pre-configured for Node.js/TypeScript projects
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.string().email(),
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 DefaultCRUDRepository<
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
- function isUser(obj: unknown): obj is TUser {
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
- function processResult<T>(result: TResult<T>) {
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:** Entity name doesn't match the table name registered in the DataSource's schema.
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: [Configuration, Post] | Ensure the model's TABLE_NAME matches the schema registration key
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
- **Solution:** The schema registration key follows the precedence `@model tableName metadata > static TABLE_NAME > class name`, while the repository looks the query interface up by the entity **class name**. Do not set `TABLE_NAME` (or `tableName` metadata) to a value that differs from the class name:
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 - Schema is registered under 'users', but looked up as 'User'
188
- @model({ type: 'entity' })
189
- export class User extends BasePostgresEntity<typeof User.schema> {
190
- static override TABLE_NAME = 'users'; // Differs from class name!
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 - No TABLE_NAME override; registration key defaults to the class name
199
+ // ✅ GOOD - set neither; both keys resolve to the class name
195
200
  @model({ type: 'entity' })
196
- export class User extends BasePostgresEntity<typeof User.schema> {
197
- static override schema = pgTable('User', { /* ... */ });
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
- **Why this matters:** The framework uses `entity.name` (class name) to look up the query interface in `connector.query`, but registers the schema under the resolved table name. If they don't match, the repository can't find its table. The first argument of `pgTable()` is the physical SQL table name and is independent of this lookup - by convention it matches the class name.
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
- "messageCode": "invalid_string",
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": "invalid_string",
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": "Expected number, received string",
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 or more modules import each other directly, creating a circular reference that JavaScript cannot resolve.
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 lazy imports or restructure your modules:
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 BasePostgresEntity<typeof Order.schema> {
266
- static override relations = (): TRelationConfig[] => [
267
- { schema: User.schema, ... }, // User imports Order, Order imports User
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 - Lazy import breaks the cycle
294
+ // ✅ GOOD - resolver defers the User.schema read until after both modules finish loading
272
295
  @model({ type: 'entity' })
273
- export class Order extends BasePostgresEntity<typeof Order.schema> {
274
- static override relations = (): TRelationConfig[] => {
275
- const { User } = require('./user.model'); // Lazy require
276
- return [{ schema: User.schema, ... }];
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 to have a shared module that both import from.
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