@venizia/ignis-docs 0.2.0 → 0.2.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.
Files changed (142) 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 +22 -11
  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 +26 -2
  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 +6 -2
  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 +182 -93
  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 +107 -322
  26. package/content/extensions/components/authentication/api.md +454 -603
  27. package/content/extensions/components/authentication/errors.md +121 -498
  28. package/content/extensions/components/authentication/index.md +88 -801
  29. package/content/extensions/components/authentication/usage.md +207 -956
  30. package/content/extensions/components/authorization/api.md +736 -656
  31. package/content/extensions/components/authorization/errors.md +168 -206
  32. package/content/extensions/components/authorization/index.md +82 -797
  33. package/content/extensions/components/authorization/usage.md +194 -527
  34. package/content/extensions/components/health-check.md +71 -243
  35. package/content/extensions/components/mail/api.md +504 -287
  36. package/content/extensions/components/mail/errors.md +73 -61
  37. package/content/extensions/components/mail/index.md +96 -467
  38. package/content/extensions/components/mail/usage.md +130 -172
  39. package/content/extensions/components/request-tracker.md +66 -173
  40. package/content/extensions/components/socket-io/api.md +195 -13
  41. package/content/extensions/components/socket-io/errors.md +3 -3
  42. package/content/extensions/components/socket-io/index.md +50 -337
  43. package/content/extensions/components/socket-io/usage.md +143 -26
  44. package/content/extensions/components/static-asset/api.md +410 -142
  45. package/content/extensions/components/static-asset/errors.md +110 -53
  46. package/content/extensions/components/static-asset/index.md +79 -608
  47. package/content/extensions/components/static-asset/usage.md +180 -300
  48. package/content/extensions/components/websocket/api.md +275 -399
  49. package/content/extensions/components/websocket/errors.md +47 -56
  50. package/content/extensions/components/websocket/index.md +74 -407
  51. package/content/extensions/components/websocket/usage.md +110 -341
  52. package/content/extensions/helpers/cron/index.md +51 -160
  53. package/content/extensions/helpers/crypto/index.md +62 -483
  54. package/content/extensions/helpers/crypto/reference.md +456 -0
  55. package/content/extensions/helpers/env/index.md +60 -178
  56. package/content/extensions/helpers/error/index.md +221 -207
  57. package/content/extensions/helpers/inversion/index.md +65 -556
  58. package/content/extensions/helpers/inversion/reference.md +522 -0
  59. package/content/extensions/helpers/kafka/admin.md +20 -1
  60. package/content/extensions/helpers/kafka/compile-binary.md +41 -35
  61. package/content/extensions/helpers/kafka/consumer.md +54 -20
  62. package/content/extensions/helpers/kafka/examples.md +21 -16
  63. package/content/extensions/helpers/kafka/index.md +80 -610
  64. package/content/extensions/helpers/kafka/producer.md +134 -5
  65. package/content/extensions/helpers/kafka/schema-registry.md +45 -70
  66. package/content/extensions/helpers/logger/hf-logger.md +193 -0
  67. package/content/extensions/helpers/logger/index.md +64 -563
  68. package/content/extensions/helpers/logger/pino.md +85 -0
  69. package/content/extensions/helpers/logger/reference.md +746 -0
  70. package/content/extensions/helpers/network/api.md +241 -195
  71. package/content/extensions/helpers/network/index.md +72 -530
  72. package/content/extensions/helpers/queue/index.md +72 -900
  73. package/content/extensions/helpers/queue/reference.md +467 -0
  74. package/content/extensions/helpers/redis/index.md +73 -645
  75. package/content/extensions/helpers/redis/reference.md +727 -0
  76. package/content/extensions/helpers/secrets/index.md +66 -0
  77. package/content/extensions/helpers/socket-io/api.md +305 -203
  78. package/content/extensions/helpers/socket-io/index.md +66 -432
  79. package/content/extensions/helpers/storage/api.md +564 -462
  80. package/content/extensions/helpers/storage/index.md +77 -573
  81. package/content/extensions/helpers/types/index.md +66 -499
  82. package/content/extensions/helpers/types/reference.md +650 -0
  83. package/content/extensions/helpers/uid/index.md +58 -227
  84. package/content/extensions/helpers/websocket/api.md +329 -216
  85. package/content/extensions/helpers/websocket/index.md +65 -503
  86. package/content/extensions/helpers/worker-thread/index.md +58 -396
  87. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  88. package/content/guides/core-concepts/persistent/models.md +1 -1
  89. package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
  90. package/content/guides/core-concepts/persistent/transactions.md +1 -1
  91. package/content/guides/core-concepts/secrets-vault.md +177 -0
  92. package/content/guides/core-concepts/services.md +1 -1
  93. package/content/guides/migrations/redis-helpers-migration.md +1 -1
  94. package/content/guides/migrations/unified-connectors-migration.md +2 -2
  95. package/content/guides/tutorials/ecommerce-api.md +3 -8
  96. package/content/references/base/application.md +1 -1
  97. package/content/references/base/connectors.md +79 -136
  98. package/content/references/base/datasources-reference.md +599 -0
  99. package/content/references/base/datasources.md +84 -444
  100. package/content/references/base/dependency-injection.md +17 -39
  101. package/content/references/base/filter-system/application-usage.md +69 -121
  102. package/content/references/base/filter-system/array-operators.md +12 -0
  103. package/content/references/base/filter-system/comparison-operators.md +12 -0
  104. package/content/references/base/filter-system/default-filter.md +136 -348
  105. package/content/references/base/filter-system/fields-order-pagination.md +38 -16
  106. package/content/references/base/filter-system/index.md +106 -257
  107. package/content/references/base/filter-system/json-filtering.md +12 -2
  108. package/content/references/base/filter-system/list-operators.md +16 -2
  109. package/content/references/base/filter-system/logical-operators.md +13 -0
  110. package/content/references/base/filter-system/null-operators.md +13 -0
  111. package/content/references/base/filter-system/pattern-matching.md +12 -0
  112. package/content/references/base/filter-system/quick-reference.md +11 -2
  113. package/content/references/base/filter-system/range-operators.md +12 -0
  114. package/content/references/base/filter-system/tips.md +70 -133
  115. package/content/references/base/filter-system/use-cases.md +156 -233
  116. package/content/references/base/middlewares.md +35 -21
  117. package/content/references/base/models-reference.md +886 -0
  118. package/content/references/base/models.md +80 -1452
  119. package/content/references/base/repositories/advanced.md +156 -192
  120. package/content/references/base/repositories/index.md +77 -650
  121. package/content/references/base/repositories/mixins.md +22 -18
  122. package/content/references/base/repositories/relations.md +123 -171
  123. package/content/references/base/repositories/soft-deletable.md +58 -56
  124. package/content/references/base/secrets.md +263 -0
  125. package/content/references/base/services.md +2 -2
  126. package/content/references/configuration/environment-variables.md +48 -4
  127. package/content/references/configuration/index.md +49 -31
  128. package/content/references/quick-reference.md +3 -16
  129. package/content/references/utilities/crypto.md +35 -76
  130. package/content/references/utilities/date.md +33 -73
  131. package/content/references/utilities/index.md +1 -1
  132. package/content/references/utilities/jsx-reference.md +298 -0
  133. package/content/references/utilities/jsx.md +82 -525
  134. package/content/references/utilities/module.md +29 -62
  135. package/content/references/utilities/parse.md +34 -64
  136. package/content/references/utilities/performance.md +33 -58
  137. package/content/references/utilities/promise.md +28 -62
  138. package/content/references/utilities/request.md +57 -218
  139. package/content/references/utilities/schema.md +43 -137
  140. package/content/references/utilities/statuses-reference.md +361 -0
  141. package/content/references/utilities/statuses.md +63 -667
  142. package/package.json +8 -8
@@ -6,16 +6,19 @@ difficulty: intermediate
6
6
 
7
7
  # SoftDeletableRepository
8
8
 
9
- A repository that overrides delete operations to set a `deletedAt` timestamp instead of physically removing records. Extends `DefaultCRUDRepository` with restore capabilities.
9
+ Reference for `SoftDeletableRepository` - delete methods set a `deletedAt` timestamp instead of removing the row, with matching restore methods. For the common tasks, start with the [Repositories overview](/references/base/repositories/).
10
10
 
11
- **File:** `packages/core/src/connectors/postgres/repositories/core/soft-deletable.ts` (PostgreSQL connector - soft delete via `deletedAt` is a Drizzle/SQL-specific pattern, not part of the engine-neutral `AbstractRepository`)
11
+ **Files:**
12
12
 
13
+ - [`packages/core/src/connectors/postgres/repositories/core/soft-deletable.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/core/soft-deletable.ts) - `SoftDeletableRelationalRepository` - delete/restore overrides, `isStrict` findById
14
+ - [`packages/core/src/connectors/postgres/repositories/core/index.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/core/index.ts) - compatibility alias `SoftDeletableRepository`
15
+ - [`packages/core/src/connectors/postgres/models/enrichers/tz.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers/tz.enricher.ts) - `generateTzColumnDefs` - adds the `deletedAt` column
13
16
 
14
17
  ## Setup
15
18
 
16
- ### 1. Define Model with Soft Delete
19
+ ### 1. Define the model with a `deletedAt` column
17
20
 
18
- Use the `generateTzColumnDefs` enricher with the `deleted` option enabled to add a `deletedAt` column:
21
+ Use the `generateTzColumnDefs` enricher with the `deleted` option enabled:
19
22
 
20
23
  ```typescript
21
24
  import { pgTable, text } from 'drizzle-orm/pg-core';
@@ -45,12 +48,12 @@ export class Category extends BaseEntity<typeof Category.schema> {
45
48
  ```
46
49
 
47
50
  > [!IMPORTANT]
48
- > - The model **must** have a `deletedAt` column (`Date | null`). The `SoftDeletableRepository` requires `TSoftDeletableTableSchema` which enforces `{ deletedAt: AnyPgColumn<{ data: Date | null }> }`.
51
+ > - The model **must** have a `deletedAt` column. `SoftDeletableRepository` requires `TSoftDeletableTableSchema`, which enforces `{ deletedAt: AnyPgColumn<{ data: Date | string | null }> }`.
49
52
  > - Set `defaultFilter: { where: { deletedAt: null } }` in `@model` settings so soft-deleted records are excluded by default.
50
53
  > - Optionally add `deletedAt` to `hiddenProperties` to hide it from API responses.
51
54
  > - Use `generateTzColumnDefs` with `deleted: { enable: true, ... }` to add the column, or define it manually with `timestamp('deleted_at', { mode: 'date', withTimezone: true })`.
52
55
 
53
- ### 2. Create Repository
56
+ ### 2. Extend `SoftDeletableRepository`
54
57
 
55
58
  ```typescript
56
59
  import { repository, SoftDeletableRepository } from '@venizia/ignis';
@@ -61,10 +64,10 @@ import { PostgresDataSource } from '@/datasources/postgres.datasource';
61
64
  export class CategoryRepository extends SoftDeletableRepository<typeof Category.schema> {}
62
65
  ```
63
66
 
64
-
65
67
  ## Delete Operations
66
68
 
67
- All delete methods set `deletedAt = new Date()` instead of removing the row. They internally call the corresponding `update` method (`updateById` or `updateAll`).
69
+ - **Delete sets a timestamp, not a `DELETE`.** All delete methods set `deletedAt = new Date()` instead of removing the row - `deleteById` calls `updateById` internally, `deleteAll`/`deleteBy` call `updateAll`.
70
+ - **Bypass with `shouldHardDelete`.** Pass `options.shouldHardDelete: true` to skip soft delete and delegate to the parent's real `DELETE`.
68
71
 
69
72
  ### deleteById
70
73
 
@@ -88,24 +91,33 @@ const result = await repository.deleteById({
88
91
 
89
92
  ### deleteAll
90
93
 
94
+ `where` only needs `options.force: true` when it is empty - a non-empty condition never needs it.
95
+
91
96
  ```typescript
92
- // Soft delete all matching records
97
+ // Soft delete matching records - non-empty where, no force needed
93
98
  const result = await repository.deleteAll({
94
99
  where: { status: 'archived' },
100
+ });
101
+
102
+ // Soft delete every row - empty where requires force
103
+ const result = await repository.deleteAll({
104
+ where: {},
95
105
  options: { force: true },
96
106
  });
97
107
 
98
- // Hard delete all matching records
108
+ // Hard delete matching records
99
109
  const result = await repository.deleteAll({
100
110
  where: { status: 'archived' },
101
- options: { shouldHardDelete: true, force: true },
111
+ options: { shouldHardDelete: true },
102
112
  });
103
113
  ```
104
114
 
105
115
  ### deleteBy
106
116
 
117
+ Same behavior as `deleteAll`, but `where` is a required parameter instead of optional.
118
+
107
119
  ```typescript
108
- // Soft delete by where condition (alias for deleteAll)
120
+ // Soft delete by where condition
109
121
  const result = await repository.deleteBy({
110
122
  where: { name: 'Obsolete' },
111
123
  });
@@ -117,10 +129,10 @@ const result = await repository.deleteBy({
117
129
  });
118
130
  ```
119
131
 
120
-
121
132
  ## Restore Operations
122
133
 
123
- Restore methods set `deletedAt = null` and automatically use `shouldSkipDefaultFilter: true` to find soft-deleted records.
134
+ - **Restore clears the timestamp.** Restore methods set `deletedAt = null`.
135
+ - **Default filter is bypassed automatically.** They internally pass `shouldSkipDefaultFilter: true` so they can find the soft-deleted records the default filter would otherwise hide - you do not need to set this yourself.
124
136
 
125
137
  ### restoreById
126
138
 
@@ -138,7 +150,7 @@ const result = await repository.restoreById({
138
150
  ### restoreAll
139
151
 
140
152
  ```typescript
141
- // Restore all soft-deleted records (requires force for empty where)
153
+ // Restore all soft-deleted records (empty where requires force)
142
154
  const result = await repository.restoreAll({
143
155
  where: {},
144
156
  options: { force: true },
@@ -153,13 +165,12 @@ const result = await repository.restoreAll({
153
165
  ### restoreBy
154
166
 
155
167
  ```typescript
156
- // Alias for restoreAll
168
+ // Calls restoreAll internally
157
169
  const result = await repository.restoreBy({
158
170
  where: { status: 'archived' },
159
171
  });
160
172
  ```
161
173
 
162
-
163
174
  ## Read Operations
164
175
 
165
176
  ### findById with isStrict
@@ -178,8 +189,8 @@ const category = await repository.findById({
178
189
  // Throws: [CategoryRepository][findById] Entity with id 123 not found (HTTP 404)
179
190
  ```
180
191
 
181
- All other read operations (`find`, `findOne`, `count`, `existsWith`) work as normal. The default filter (`{ deletedAt: null }`) automatically excludes soft-deleted records. Use `shouldSkipDefaultFilter: true` to include them.
182
-
192
+ - **Everything else is inherited unchanged.** `find`, `findOne`, `count`, `existsWith` behave exactly as on `DefaultCRUDRepository`.
193
+ - **The default filter excludes soft-deleted rows.** `{ deletedAt: null }` is applied automatically - pass `options: { shouldSkipDefaultFilter: true }` to include them.
183
194
 
184
195
  ## Options Reference
185
196
 
@@ -189,7 +200,7 @@ All other read operations (`find`, `findOne`, `count`, `existsWith`) work as nor
189
200
  |--------|------|---------|-------------|
190
201
  | `shouldHardDelete` | `boolean` | `false` | Bypass soft delete and physically remove the row |
191
202
  | `shouldReturn` | `boolean` | `true` | Return the updated/deleted record |
192
- | `force` | `boolean` | `false` | Allow empty `where` condition (`deleteAll`/`deleteBy`) |
203
+ | `force` | `boolean` | `false` | Allow an empty `where` condition (`deleteAll`/`deleteBy`) |
193
204
  | `transaction` | `ITransaction` | - | Transaction context |
194
205
  | `log` | `{ use: boolean; level?: TLogLevel }` | - | Enable operation logging |
195
206
  | `shouldSkipDefaultFilter` | `boolean` | `false` | Bypass the default filter |
@@ -199,27 +210,22 @@ All other read operations (`find`, `findOne`, `count`, `existsWith`) work as nor
199
210
  | Option | Type | Default | Description |
200
211
  |--------|------|---------|-------------|
201
212
  | `shouldReturn` | `boolean` | `true` | Return the restored record |
202
- | `force` | `boolean` | `false` | Allow empty `where` condition (`restoreAll`) |
213
+ | `force` | `boolean` | `false` | Allow an empty `where` condition (`restoreAll` only - `restoreById` has no `force` option) |
203
214
  | `transaction` | `ITransaction` | - | Transaction context |
204
215
 
205
- > [!NOTE]
206
- > Restore operations automatically set `shouldSkipDefaultFilter: true` internally so they can find soft-deleted records that the default filter would normally hide. You do not need to set this yourself.
207
-
208
-
209
216
  ## How It Works
210
217
 
211
218
  | Operation | SQL Behavior |
212
219
  |-----------|-------------|
213
220
  | `deleteById` | `UPDATE SET deletedAt = NOW() WHERE id = ?` (via `updateById`) |
214
221
  | `deleteAll` / `deleteBy` | `UPDATE SET deletedAt = NOW() WHERE ...` (via `updateAll`) |
215
- | `restoreById` | `UPDATE SET deletedAt = NULL WHERE id = ?` (via `updateById` with `shouldSkipDefaultFilter: true`) |
216
- | `restoreAll` / `restoreBy` | `UPDATE SET deletedAt = NULL WHERE ...` (via `updateAll` with `shouldSkipDefaultFilter: true`) |
222
+ | `restoreById` | `UPDATE SET deletedAt = NULL WHERE id = ?` (with `shouldSkipDefaultFilter: true`) |
223
+ | `restoreAll` / `restoreBy` | `UPDATE SET deletedAt = NULL WHERE ...` (with `shouldSkipDefaultFilter: true`) |
217
224
  | `find` / `findOne` / `count` | Default filter automatically adds `WHERE deletedAt IS NULL` |
218
- | `deleteById({ shouldHardDelete: true })` | `DELETE FROM ... WHERE id = ?` (delegates to parent `PersistableRepository`) |
225
+ | `deleteById({ shouldHardDelete: true })` | `DELETE FROM ... WHERE id = ?` (delegates to the parent repository) |
219
226
 
220
227
  > [!TIP]
221
- > The `shouldHardDelete` option bypasses soft delete entirely and delegates to the parent `DefaultCRUDRepository`'s delete implementation, which performs a real SQL `DELETE`.
222
-
228
+ > `shouldHardDelete` bypasses soft delete entirely and delegates to the parent `DefaultCRUDRepository`'s delete implementation, which performs a real SQL `DELETE`.
223
229
 
224
230
  ## With Transactions
225
231
 
@@ -232,64 +238,60 @@ try {
232
238
  options: { transaction: tx },
233
239
  });
234
240
  await tx.commit();
235
- } catch {
241
+ } catch (error) {
236
242
  await tx.rollback();
243
+ throw error;
237
244
  }
238
245
  ```
239
246
 
240
-
241
247
  ## Type Constraint
242
248
 
243
249
  `SoftDeletableRepository` enforces that the schema includes a `deletedAt` column at the type level:
244
250
 
245
251
  ```typescript
252
+ export type TDeletedAtColumn = AnyPgColumn<{ data: Date | string | null }>;
253
+
246
254
  export type TSoftDeletableTableSchema = TTableSchemaWithId & {
247
- deletedAt: AnyPgColumn<{ data: Date | null }>;
255
+ deletedAt: TDeletedAtColumn;
248
256
  };
249
257
 
250
- export class SoftDeletableRepository<
258
+ export class SoftDeletableRelationalRepository<
251
259
  EntitySchema extends TSoftDeletableTableSchema = TSoftDeletableTableSchema,
252
260
  // ...
253
- > extends DefaultCRUDRepository<EntitySchema, ...> { }
261
+ > extends DefaultRelationalRepository<EntitySchema, ...> { }
254
262
  ```
255
263
 
256
- If your schema does not have a `deletedAt` column, you will get a TypeScript compilation error when extending `SoftDeletableRepository`.
257
-
264
+ If your schema does not have a `deletedAt` column, you get a TypeScript compilation error when extending `SoftDeletableRepository`.
258
265
 
259
266
  ## Class Hierarchy
260
267
 
268
+ `SoftDeletableRepository` is the friendly alias for `SoftDeletableRelationalRepository`, the last tier of the PostgreSQL repository chain:
269
+
261
270
  ```
262
271
  AbstractRepository (engine-neutral, src/base)
263
- -> PostgresBaseRepository (connectors/postgres)
264
- -> ReadableRepository
265
- -> PersistableRepository
266
- -> DefaultCRUDRepository
267
- -> SoftDeletableRepository <-- you are here
272
+ -> RelationalBaseRepository (PostgresBaseRepository)
273
+ -> ReadableRelationalRepository (ReadableRepository)
274
+ -> PersistableRelationalRepository (PersistableRepository)
275
+ -> DefaultRelationalRepository (DefaultCRUDRepository)
276
+ -> SoftDeletableRelationalRepository (SoftDeletableRepository) <-- you are here
268
277
  ```
269
278
 
270
-
271
279
  ## Quick Reference
272
280
 
273
281
  | Want to... | Code |
274
282
  |------------|------|
275
283
  | Soft delete by ID | `repository.deleteById({ id })` |
276
284
  | Hard delete by ID | `repository.deleteById({ id, options: { shouldHardDelete: true } })` |
277
- | Soft delete by condition | `repository.deleteAll({ where, options: { force: true } })` |
285
+ | Soft delete by condition | `repository.deleteAll({ where })` |
278
286
  | Restore by ID | `repository.restoreById({ id })` |
279
287
  | Restore by condition | `repository.restoreAll({ where })` |
280
288
  | Find including deleted | `repository.find({ filter, options: { shouldSkipDefaultFilter: true } })` |
281
289
  | Strict findById (404) | `repository.findById({ id, options: { isStrict: true } })` |
282
290
 
291
+ ## See also
283
292
 
284
- ## Next Steps
285
-
286
- - [Advanced Features](./advanced.md) - Transactions, hidden properties
287
- - [Repository Mixins (Removed)](./mixins.md) - Where default-filter and fields-visibility behavior lives now
288
- - [Repository Overview](./index.md) - Repository basics
289
-
290
- ## See Also
291
-
292
- - **Related Concepts:**
293
- - [Repositories Overview](./index) - Core repository operations
294
- - [Default Filter](../filter-system/default-filter) - Automatic filtering
295
- - [Models](/guides/core-concepts/persistent/models) - Entity definitions with enrichers
293
+ - [Repositories overview](/references/base/repositories/) - CRUD basics, common tasks
294
+ - [Advanced Features](./advanced) - transactions, hidden properties, performance
295
+ - [Repository Mixins (Removed)](./mixins) - where default-filter and fields-visibility behavior lives now
296
+ - [Default Filter](/references/base/filter-system/default-filter) - configuring `@model` default filters
297
+ - [Models - Full Reference](/references/base/models-reference) - `@model`, entity hierarchy, schema enrichers
@@ -0,0 +1,263 @@
1
+ ---
2
+ title: Secrets & Vault Reference
3
+ description: Technical reference for the Secrets provider family, boot-time hydration, dynamic leases, and credential rotation
4
+ difficulty: advanced
5
+ ---
6
+
7
+ # Deep Dive: Secrets & Vault
8
+
9
+ Technical reference for the `Secrets` provider family - how IGNIS loads configuration and credentials from a vault instead of reading `process.env` directly, hydrates static secrets at boot, and rotates dynamic database credentials into a live connection pool without a restart.
10
+
11
+ > [!IMPORTANT] Dormant by default
12
+ > An application that does not override `registerSecrets()` gets the `system-envs` provider, which reads `process.env` exactly as before. The whole subsystem is additive - existing apps behave identically until they opt in.
13
+
14
+ **Files:** `packages/helpers/src/modules/secrets/*.ts` (provider family) and `packages/core/src/base/applications/base.ts` (`hydrateSecrets()` lifecycle, DI binding, rotation wiring)
15
+
16
+ ## Quick Reference
17
+
18
+ | Class / Interface | Purpose | Key Members |
19
+ |-------------------|---------|-------------|
20
+ | **ISecretsHelper** | Contract every provider implements | `get()`, `getBundle()`, `lease()`, `onRotate()`, `registerRotatable()`, `shutdown()`, `configure()` |
21
+ | **AbstractSecretsHelper** | Provider-agnostic machinery | TTL cache, lease registry, renewal scheduler, `onRotate` dispatch; abstract `fetchRaw`/`renewRaw`/`revokeRaw` |
22
+ | **SystemEnvsHelper** | Default, static, zero-dependency | Reads `process.env`; `lease()` throws NotSupported |
23
+ | **HashiCorpVaultHelper** | KV v2 + dynamic leases + rotation | Token / AppRole / Kubernetes auth; token self-renewal; `node-vault` optional peer |
24
+ | **DotenvVaultHelper** | Static, encrypted `.env.vault` | Decrypts via `@dotenvx/dotenvx`; `lease()` throws NotSupported |
25
+ | **SecretProviders** | Provider const-class | `SYSTEM_ENVS`, `HASHICORP_VAULT`, `DOTENV_VAULT`, `SCHEME_SET`, `isValid()` |
26
+ | **VaultAuthMethods** | Auth const-class | `TOKEN`, `APP_ROLE`, `KUBERNETES`, `SCHEME_SET`, `isValid()` |
27
+ | **ISecretsRegistration** | What `registerSecrets()` returns | `provider`, `config`, `hydrate[]`, `lease[]`, `renewBeforeRatio`, `cacheTtlSeconds` |
28
+ | **ISecretRotatable** | Opt-in consumer hook | `onSecretRotated({ key, secret })` |
29
+
30
+ ## Two Paths: Hydrate and Provider
31
+
32
+ Secrets reach the application through two independent channels.
33
+
34
+ - **Hydrate (static)** - during boot, the framework fetches secrets from the vault and merges them into `Envs` and `process.env`. Any code that already reads `process.env.APP_ENV_*` keeps working unchanged. Set once, never rotated.
35
+ - **Provider (dynamic)** - the resolved provider is bound in the container at the key `@app/config`. Code that needs on-demand reads, dynamic credentials, or rotation injects that provider and calls `await secrets.get(...)`.
36
+
37
+ A single registration drives both. The hydrate path is a convenience over the provider path - the same provider produces both.
38
+
39
+ ## Provider Tiers
40
+
41
+ ```
42
+ AbstractSecretsHelper (BaseHelper; TTL cache, lease registry, renewal scheduler, onRotate dispatch)
43
+ ├── SystemEnvsHelper // 'system-envs' - process.env / Envs. Static, zero deps, default.
44
+ ├── HashiCorpVaultHelper // 'hashicorp-vault' - full KV + dynamic + lease + rotation.
45
+ └── DotenvVaultHelper // 'dotenv-vault' - encrypted .env.vault, static only.
46
+ ```
47
+
48
+ The renewal scheduler, TTL cache, lease registry, and `onRotate` dispatch all live in `AbstractSecretsHelper`. A concrete provider implements only the raw calls against its backend (`fetchRaw`, `renewRaw`, `revokeRaw`), which is why the two static providers are thin and why only HashiCorp exercises the lease machinery.
49
+
50
+ ## `ISecretsHelper` Interface
51
+
52
+ **File:** `packages/helpers/src/modules/secrets/common/types.ts`
53
+
54
+ ```typescript
55
+ interface ISecretsHelper extends IConfigurable {
56
+ // One-shot read of a static / KV value (TTL-cached).
57
+ get<TValue = string>(opts: { path: string; key?: string; defaultValue?: TValue }): Promise<TValue>;
58
+
59
+ // Whole KV bundle at a path - hydration merges this into Envs.
60
+ getBundle(opts: { path: string }): Promise<Record<string, string>>;
61
+
62
+ // Lease-bearing dynamic secret. The provider tracks the lease, schedules renewal,
63
+ // and emits onRotate when the backend issues fresh credentials.
64
+ lease(opts: { path: string; key: string }): Promise<ISecretLease>;
65
+
66
+ // Rotation event - mirrors the onInitialized / onConnected / onError vocabulary.
67
+ onRotate(handler: (event: ISecretRotationEvent) => ValueOrPromise<void>): void;
68
+
69
+ // Connect a live consumer (a pool holder) to a lease key.
70
+ registerRotatable(opts: { key: string; target: ISecretRotatable }): void;
71
+
72
+ // Stop renewal timers and revoke outstanding leases.
73
+ shutdown(): Promise<void>;
74
+ }
75
+
76
+ interface ISecretLease {
77
+ value: Record<string, string>; // e.g. { username, password }
78
+ leaseId: string;
79
+ ttlSeconds: number;
80
+ renewable: boolean;
81
+ }
82
+
83
+ interface ISecretRotationEvent {
84
+ key: string; // logical key, e.g. 'datasources.PostgresDataSource'
85
+ lease: ISecretLease;
86
+ }
87
+
88
+ // Opt-in consumer hook. Soft-evict, NOT a hard teardown.
89
+ interface ISecretRotatable {
90
+ onSecretRotated(opts: { key: string; secret: Record<string, string> }): Promise<void>;
91
+ }
92
+ ```
93
+
94
+ > [!NOTE] Static vs dynamic secrets
95
+ > `get()` / `getBundle()` return static values (KV, env) and are TTL-cached. `lease()` returns a dynamic, lease-bearing secret and is supported only by `HashiCorpVaultHelper`; the static providers throw an explicit NotSupported error rather than returning a fake lease.
96
+
97
+ ## Registration
98
+
99
+ An application opts in by overriding `registerSecrets()` on its `BaseApplication` subclass. The method returns an `ISecretsRegistration` and runs before datasources are built.
100
+
101
+ **File:** `packages/helpers/src/modules/secrets/common/types.ts`
102
+
103
+ ```typescript
104
+ interface ISecretsRegistration {
105
+ provider: TSecretProvider; // SecretProviders.*
106
+ config?: AnyObject; // provider-specific (endpoint, auth, ...)
107
+ hydrate?: Array<{ // static KV -> merged into Envs at boot
108
+ path: string;
109
+ prefix?: string; // prepend to every merged key
110
+ keys?: Record<string, string>; // explicit vaultKey -> envKey (wins over prefix)
111
+ }>;
112
+ lease?: Array<{ // dynamic leases -> renewed + rotated
113
+ key: string; // binding key of the consuming datasource
114
+ path: string;
115
+ }>;
116
+ renewBeforeRatio?: number; // renew a lease at ttl * ratio (default 0.66)
117
+ cacheTtlSeconds?: number; // TTL for the get/getBundle cache (default 300)
118
+ }
119
+ ```
120
+
121
+ ### `registerSecrets()` default
122
+
123
+ ```typescript
124
+ // packages/core/src/base/applications/base.ts
125
+ registerSecrets(): ValueOrPromise<ISecretsRegistration> {
126
+ return { provider: SecretProviders.SYSTEM_ENVS };
127
+ }
128
+ ```
129
+
130
+ Because the default names `system-envs`, an app that never overrides it hydrates nothing, leases nothing, and reads `process.env` as always.
131
+
132
+ ## Const-classes
133
+
134
+ **File:** `packages/helpers/src/modules/secrets/common/constants.ts`
135
+
136
+ ```typescript
137
+ export class SecretProviders {
138
+ static readonly SYSTEM_ENVS = 'system-envs';
139
+ static readonly HASHICORP_VAULT = 'hashicorp-vault';
140
+ static readonly DOTENV_VAULT = 'dotenv-vault';
141
+
142
+ static readonly SCHEME_SET = new Set([this.SYSTEM_ENVS, this.HASHICORP_VAULT, this.DOTENV_VAULT]);
143
+ static isValid(value: string): value is TSecretProvider {
144
+ return this.SCHEME_SET.has(value);
145
+ }
146
+ }
147
+
148
+ export class VaultAuthMethods {
149
+ static readonly TOKEN = 'token';
150
+ static readonly APP_ROLE = 'app-role';
151
+ static readonly KUBERNETES = 'kubernetes';
152
+
153
+ static readonly SCHEME_SET = new Set([this.TOKEN, this.APP_ROLE, this.KUBERNETES]);
154
+ static isValid(value: string): value is TVaultAuthMethod {
155
+ return this.SCHEME_SET.has(value);
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## Lifecycle Integration
161
+
162
+ The registration is consumed by a new async boot phase, `hydrateSecrets()`, inserted between `preConfigure()` and `registerDataSources()`.
163
+
164
+ ```
165
+ validateEnvs → staticConfigure → preConfigure
166
+ → hydrateSecrets() ← resolve provider, hydrate Envs, bind @app/config, set up leases
167
+ → registerDataSources
168
+ → wireSecretRotatables() ← connect each lease key to its datasource
169
+ → registerComponents → registerControllers → postConfigure
170
+ ```
171
+
172
+ `hydrateSecrets()` does four things:
173
+
174
+ 1. Calls `registerSecrets()` and builds the provider via `createSecretsHelper({ provider })`.
175
+ 2. Runs `provider.configure()` (authentication), then merges every `hydrate` entry into `Envs` + `process.env`, and opens every `lease`.
176
+ 3. Binds the live provider at `CoreBindings.APPLICATION_CONFIG` (`@app/config`) as a singleton.
177
+ 4. Registers a post-stop hook (`secrets.shutdown`) so `provider.shutdown()` runs on teardown, revoking outstanding leases.
178
+
179
+ `wireSecretRotatables()` runs after datasources are registered: for each `lease` entry it resolves the datasource at `entry.key` and, if the instance implements `onSecretRotated`, calls `provider.registerRotatable({ key, target })`. A datasource that does not implement the hook is skipped.
180
+
181
+ > [!TIP] Why a lifecycle phase and not a Component
182
+ > Components register *after* datasources, but secrets must be resolved *before* datasources build their pools. Hydration is therefore a dedicated phase, not a component.
183
+
184
+ ## Failure Mode
185
+
186
+ `hydrateSecrets()` is fail-closed in production and forgiving in development, keyed on `Environment.DEVELOPMENT_ENVS` (`local`, `debug`, `development`, `dev`, `sit`).
187
+
188
+ | Environment | Vault unreachable / auth fails / secret missing |
189
+ |-------------|-------------------------------------------------|
190
+ | Development set | Log a warning, fall back to a `system-envs` provider, continue booting |
191
+ | Everything else (prod, staging, and any unrecognized name) | Throw `ApplicationError`, crash the boot |
192
+
193
+ The fallback in development builds a fresh `SystemEnvsHelper` and shuts down the partially-built provider first, so a half-authenticated Vault client leaves no renewal timer or lease behind.
194
+
195
+ ## `AbstractSecretsHelper` Machinery
196
+
197
+ **File:** `packages/helpers/src/modules/secrets/base/abstract.helper.ts`
198
+
199
+ - **TTL cache** - `get()` / `getBundle()` cache each path for `cacheTtlSeconds` (default 300) and re-fetch on expiry.
200
+ - **Renewal scheduler** - each lease schedules a renewal at `ttlSeconds × renewBeforeRatio` (default 0.66) through an injectable timer seam. On fire, `renewRaw()` extends the same lease; on renew failure or max-TTL, `fetchRaw()` mints a fresh lease and a single rotation is dispatched.
201
+ - **Rotation dispatch** - `onRotate` handlers run first, then the registered rotatables for that key run **in series**; a throwing consumer is logged and does not abort the others.
202
+ - **Shutdown** - clears every timer and revokes every lease via `revokeRaw()`.
203
+
204
+ The scheduler, cache, and clock are injectable, so the machinery is tested deterministically without real timers or a live vault.
205
+
206
+ ## HashiCorp Vault Provider
207
+
208
+ **File:** `packages/helpers/src/modules/secrets/hashicorp/hashicorp.helper.ts`
209
+
210
+ - **Auth** - a Zod discriminated union on `method`: `token`, `app-role` (`roleId` + `secretId`), `kubernetes` (`role`, optional `jwtPath`). `configure()` logs in and stores the Vault token.
211
+ - **KV v2** - `getBundle()` unwraps the KV-v2 `.data.data` envelope automatically.
212
+ - **Dynamic secrets** - a read against a dynamic engine (for example `database/creds/...`) returns a lease (`lease_id`, `lease_duration`, `renewable`), which drives the renewal scheduler.
213
+ - **Token self-renewal** - the Vault auth token has its own TTL. The provider schedules the token for renewal in the same cadence and re-runs the login flow (re-auth) if the token can no longer be renewed, so AppRole / Kubernetes deployments survive past the token TTL without a restart.
214
+
215
+ `node-vault` is an optional peer. It is reached only through the `@venizia/ignis-helpers/hashicorp-vault` sub-path and a bundler-invisible dynamic import (`importOptionalModule`), so importing the root package never requires it - and `Bun.build`-compiled applications need no `external: ['node-vault']` workaround. An application that does use this provider and compiles a binary must ship `node-vault` in `node_modules` next to the binary, or inject a ready-made `client` through the helper options.
216
+
217
+ ## Dotenv Vault Provider
218
+
219
+ **File:** `packages/helpers/src/modules/secrets/dotenv/dotenv.helper.ts`
220
+
221
+ Decrypts a committed `.env.vault` file with a per-environment `DOTENV_KEY` (via `@dotenvx/dotenvx`) into a flat key-value map, which the hydrate path merges into `Envs`. Static only - `lease()` throws NotSupported. `@dotenvx/dotenvx` is an optional peer reached through the `@venizia/ignis-helpers/dotenv-vault` sub-path.
222
+
223
+ ## Rotation and the Soft-Evict Contract
224
+
225
+ When a dynamic credential rotates, a live connection pool holding the old credentials must rebuild. IGNIS uses an event plus an opt-in rebuild callback, mirroring the Spring Cloud Vault + HikariCP pattern.
226
+
227
+ The PostgreSQL datasource implements `onSecretRotated()` on `AbstractRelationalDataSource`:
228
+
229
+ 1. Capture the current pool.
230
+ 2. Apply the new credentials onto `this.settings` (`{ username, password }` maps to pg's `{ user, password }`).
231
+ 3. Clear the driver / connector / client, re-run `configure()` to build a fresh pool, and re-wire the driver.
232
+ 4. Drain the old pool with `end()` - which resolves once checked-out clients are released, so in-flight transactions finish on the old pool while new work uses the new one.
233
+
234
+ > [!WARNING] configure() must read from this.settings
235
+ > Rotation applies new credentials by writing them onto `this.settings` and re-running `configure()`. A `configure()` that builds its pool from a hard-coded connection string, or reads `Envs` directly, will rebuild with **stale** credentials.
236
+
237
+ ## Consuming the Provider
238
+
239
+ Any provider-fed code injects the singleton at `@app/config`.
240
+
241
+ ```typescript
242
+ import type { ISecretsHelper } from '@venizia/ignis-helpers';
243
+
244
+ @service()
245
+ export class PaymentService {
246
+ constructor(
247
+ @inject({ key: '@app/config' }) private secrets: ISecretsHelper,
248
+ ) {}
249
+
250
+ async charge() {
251
+ const apiKey = await this.secrets.get({ path: 'secret/data/myapp/stripe', key: 'apiKey' });
252
+ // ...
253
+ }
254
+ }
255
+ ```
256
+
257
+ For a step-by-step walkthrough of enabling each provider, see the [Secrets & Vault guide](/guides/core-concepts/secrets-vault).
258
+
259
+ ## See Also
260
+
261
+ - [Secrets & Vault guide](/guides/core-concepts/secrets-vault) - practical setup for each provider
262
+ - [DataSources](./datasources) - the pool that rotation rebuilds
263
+ - [Environment Variables](/references/configuration/environment-variables) - where hydrated secrets land
@@ -19,7 +19,7 @@ Technical reference for `BaseService` - the foundation for the business logic la
19
19
  | **Logging** | `this.logger` (scoped to constructor `scope`) |
20
20
  | **Registration** | `this.service(MyService)` in application lifecycle |
21
21
  | **Binding key** | `services.{ClassName}` (e.g., `services.AuthenticationService`) |
22
- | **DI decorator** | `@injectable` is NOT used on services - registration is imperative only |
22
+ | **DI decorator** | None on the class itself - only `@inject` on constructor parameters or properties |
23
23
  | **CRUD service** | Removed - use `DefaultCRUDRepository` for data access |
24
24
 
25
25
  ---
@@ -54,7 +54,7 @@ export interface IService {}
54
54
 
55
55
  ## Registering a Service
56
56
 
57
- Services are registered imperatively in an application lifecycle method. There is no `@injectable` or `@service` decorator used on service classes in application code - `this.service()` handles everything.
57
+ Services are registered imperatively in an application lifecycle method. No class decorator is involved - `this.service()` creates the binding and handles everything.
58
58
 
59
59
  ```typescript
60
60
  // In your Application class (e.g., in preConfigure())
@@ -1,7 +1,15 @@
1
1
  # Environment Variables Reference
2
2
 
3
- Complete reference of all environment variables used by IGNIS framework.
3
+ Complete reference of all environment variables used by the IGNIS framework, grouped by category with defaults and required/optional status.
4
4
 
5
+ **Files:**
6
+
7
+ - [`packages/core/src/common/environments.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/common/environments.ts) - `EnvironmentKeys`
8
+ - [`packages/helpers/src/modules/env/app-env.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/env/app-env.ts) - `applicationEnvironment`, `Environment`
9
+ - [`packages/core/src/base/applications/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/applications/abstract.ts) - `validateEnvs()`, host/port resolution priority
10
+ - [`packages/core/src/base/applications/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/applications/base.ts) - `registerSecrets()`, `hydrateSecrets()`
11
+ - [`packages/helpers/src/modules/secrets/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/secrets/common/constants.ts) - `SecretProviders`, `VaultAuthMethods`
12
+ - [`packages/helpers/src/modules/secrets/hashicorp/hashicorp.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/secrets/hashicorp/hashicorp.helper.ts) - HashiCorp Vault helper
5
13
 
6
14
  ## Overview
7
15
 
@@ -189,7 +197,9 @@ export class PostgresDataSource extends BaseDataSource {
189
197
 
190
198
  ### Security Requirements
191
199
 
192
- The `AuthenticateComponent` receives its secrets programmatically via the `jwtOptions` binding (`jwtSecret`, `getTokenExpiresFn`), not by reading these environment variables directly. These `EnvironmentKeys` constants are the conventional way for your application to supply those values. The component throws at startup if `jwtSecret` is missing or left at the placeholder value - so wiring it from an unset environment variable will fail the boot.
200
+ - **Not read directly.** The `AuthenticateComponent` receives its secrets programmatically via the `jwtOptions` binding (`jwtSecret`, `getTokenExpiresFn`) - it never reads these environment variables itself.
201
+ - **`EnvironmentKeys` is the convention, not a requirement.** These constants are the conventional way for your application to supply those values into the binding.
202
+ - **A missing or placeholder secret fails the boot.** The component throws at startup if `jwtSecret` is missing or left at the placeholder value - wiring it from an unset environment variable fails the same way.
193
203
 
194
204
  ### Generate Strong Secrets
195
205
 
@@ -221,9 +231,11 @@ APP_ENV_JWT_EXPIRES_IN=86400
221
231
 
222
232
  | Variable | Required | Default | Description |
223
233
  |----------|----------|---------|-------------|
224
- | `APP_ENV_LOGGER_FOLDER_PATH` | No | `./` | Directory for log files |
234
+ | `APP_ENV_LOGGER_FOLDER_PATH` | No | _(unset)_ | Directory for log files; file logging is OFF when unset |
225
235
  | `APP_ENV_LOGGER_FORMAT` | No | `text` | Log output format |
236
+ | `APP_ENV_LOGGER_LEVEL` | No | `debug` | Logger-level floor; transports without their own level inherit it |
226
237
  | `APP_ENV_LOGGER_INSPECT_DEPTH` | No | `5` | How deep a `%s` argument is inspected. Node hard-codes depth `0` for `%s`, which prints `[Object]` one level in; IGNIS widens it. Non-negative integers only - an absent, invalid or negative value falls back to `5` |
238
+ | `APP_ENV_LOGGER_DO_REDACT` | No | `true` | Secret redaction in logged values. ONLY the literal `false` disables it (reveals raw credentials in log lines); anything else keeps redaction ON. Never disable in production |
227
239
  | `APP_ENV_LOGGER_FILE_FREQUENCY` | No | `1h` | Log file rotation frequency |
228
240
  | `APP_ENV_LOGGER_FILE_MAX_SIZE` | No | `100m` | Max size per log file |
229
241
  | `APP_ENV_LOGGER_FILE_MAX_FILES` | No | `5d` | Log file retention |
@@ -329,7 +341,9 @@ APP_ENV_MAIL_REFRESH_TOKEN=your-oauth2-refresh-token
329
341
  | `NODE_ENV` | No | `development` | Environment mode. One of `local`, `debug`, `development`, `dev`, `sit`, `uat`, `alpha`, `beta`, `staging`, `production` |
330
342
  | `ALLOW_EMPTY_ENV_VALUE` | No | `false` | Allow empty env values |
331
343
 
332
- The gate is fail-closed: an environment IGNIS does not recognise is treated as production, so error responses are sanitized. `local`, `debug`, `development`, `dev` and `sit` are the development environments - only they expose internal error detail. `alpha`, `beta`, `staging` and `production` stay sanitized.
344
+ - **Fail-closed by default.** An environment IGNIS does not recognize is treated as production, so error responses are sanitized.
345
+ - **Development environments expose error detail.** `local`, `debug`, `development`, `dev`, and `sit` are the development set - only these show internal error detail.
346
+ - **Everything else stays sanitized.** `alpha`, `beta`, `staging`, and `production` never expose internal detail, matching production behavior.
333
347
 
334
348
  ### Example
335
349
 
@@ -343,6 +357,28 @@ NODE_ENV=production
343
357
  ```
344
358
 
345
359
 
360
+ ## Secrets & Vault
361
+
362
+ - **A `.env` file is one option, not a requirement.** IGNIS can load these variables from a vault (HashiCorp Vault, an encrypted `.env.vault`, or plain `process.env`) and **hydrate** them into the same `APP_ENV_*` keys at boot. Code that reads `process.env.APP_ENV_*` keeps working unchanged - the values simply arrive from the vault instead of a file.
363
+ - **Hydration runs before datasources are configured** (after `preConfigure()`, before `registerDataSources()`), so a hydrated `APP_ENV_DS_PASSWORD` is available exactly where a file-based one would be.
364
+ - **Vault values take precedence over `process.env`** when the provider is live - a hydrated key overwrites whatever was already in `process.env`.
365
+
366
+ ```typescript
367
+ // Store the key in the vault already named APP_ENV_... and it merges as-is.
368
+ override registerSecrets() {
369
+ return {
370
+ provider: SecretProviders.HASHICORP_VAULT,
371
+ config: { endpoint, auth: { method: VaultAuthMethods.APP_ROLE, roleId, secretId } },
372
+ hydrate: [{ path: 'secret/data/myapp/config' }],
373
+ };
374
+ }
375
+ ```
376
+
377
+ > [!NOTE] Failure policy
378
+ > If the vault is unreachable, development environments (`local`, `debug`, `development`, `dev`, `sit`) fall back to `process.env`; every other environment fails the boot rather than starting with missing secrets.
379
+
380
+ See the [Secrets & Vault guide](/guides/core-concepts/secrets-vault) for setup and the [Secrets & Vault reference](/references/base/secrets) for the full API.
381
+
346
382
  ## Environment-Specific Files
347
383
 
348
384
  Create environment-specific `.env` files:
@@ -444,3 +480,11 @@ APP_ENV_LOGGER_FOLDER_PATH=./logs
444
480
  # APP_ENV_MAIL_CLIENT_SECRET=
445
481
  # APP_ENV_MAIL_REFRESH_TOKEN=
446
482
  ```
483
+
484
+ ## See also
485
+
486
+ - [Configuration Reference](./index.md) - `EnvironmentKeys` constants and the `applicationEnvironment` helper
487
+ - [Secrets & Vault Guide](/guides/core-concepts/secrets-vault) - setup walkthrough for vault-backed secrets
488
+ - [Secrets & Vault Reference](/references/base/secrets) - full provider API (`registerSecrets`, `SecretProviders`, `VaultAuthMethods`)
489
+ - [DataSources Guide](/guides/core-concepts/persistent/datasources) - wiring `APP_ENV_POSTGRES_*` into a DataSource
490
+ - [Logger Helper](/extensions/helpers/logger/) - `APP_ENV_LOGGER_*` variables in depth