@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.
- package/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +22 -11
- 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 +26 -2
- 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 +6 -2
- 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 +182 -93
- 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 +107 -322
- package/content/extensions/components/authentication/api.md +454 -603
- package/content/extensions/components/authentication/errors.md +121 -498
- package/content/extensions/components/authentication/index.md +88 -801
- package/content/extensions/components/authentication/usage.md +207 -956
- package/content/extensions/components/authorization/api.md +736 -656
- package/content/extensions/components/authorization/errors.md +168 -206
- package/content/extensions/components/authorization/index.md +82 -797
- package/content/extensions/components/authorization/usage.md +194 -527
- package/content/extensions/components/health-check.md +71 -243
- package/content/extensions/components/mail/api.md +504 -287
- package/content/extensions/components/mail/errors.md +73 -61
- package/content/extensions/components/mail/index.md +96 -467
- package/content/extensions/components/mail/usage.md +130 -172
- package/content/extensions/components/request-tracker.md +66 -173
- package/content/extensions/components/socket-io/api.md +195 -13
- package/content/extensions/components/socket-io/errors.md +3 -3
- package/content/extensions/components/socket-io/index.md +50 -337
- package/content/extensions/components/socket-io/usage.md +143 -26
- package/content/extensions/components/static-asset/api.md +410 -142
- package/content/extensions/components/static-asset/errors.md +110 -53
- package/content/extensions/components/static-asset/index.md +79 -608
- package/content/extensions/components/static-asset/usage.md +180 -300
- package/content/extensions/components/websocket/api.md +275 -399
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +74 -407
- package/content/extensions/components/websocket/usage.md +110 -341
- package/content/extensions/helpers/cron/index.md +51 -160
- package/content/extensions/helpers/crypto/index.md +62 -483
- package/content/extensions/helpers/crypto/reference.md +456 -0
- package/content/extensions/helpers/env/index.md +60 -178
- package/content/extensions/helpers/error/index.md +221 -207
- package/content/extensions/helpers/inversion/index.md +65 -556
- package/content/extensions/helpers/inversion/reference.md +522 -0
- package/content/extensions/helpers/kafka/admin.md +20 -1
- package/content/extensions/helpers/kafka/compile-binary.md +41 -35
- package/content/extensions/helpers/kafka/consumer.md +54 -20
- package/content/extensions/helpers/kafka/examples.md +21 -16
- package/content/extensions/helpers/kafka/index.md +80 -610
- package/content/extensions/helpers/kafka/producer.md +134 -5
- package/content/extensions/helpers/kafka/schema-registry.md +45 -70
- package/content/extensions/helpers/logger/hf-logger.md +193 -0
- package/content/extensions/helpers/logger/index.md +64 -563
- package/content/extensions/helpers/logger/pino.md +85 -0
- package/content/extensions/helpers/logger/reference.md +746 -0
- package/content/extensions/helpers/network/api.md +241 -195
- package/content/extensions/helpers/network/index.md +72 -530
- package/content/extensions/helpers/queue/index.md +72 -900
- package/content/extensions/helpers/queue/reference.md +467 -0
- package/content/extensions/helpers/redis/index.md +73 -645
- package/content/extensions/helpers/redis/reference.md +727 -0
- package/content/extensions/helpers/secrets/index.md +66 -0
- package/content/extensions/helpers/socket-io/api.md +305 -203
- package/content/extensions/helpers/socket-io/index.md +66 -432
- package/content/extensions/helpers/storage/api.md +564 -462
- package/content/extensions/helpers/storage/index.md +77 -573
- package/content/extensions/helpers/types/index.md +66 -499
- package/content/extensions/helpers/types/reference.md +650 -0
- package/content/extensions/helpers/uid/index.md +58 -227
- package/content/extensions/helpers/websocket/api.md +329 -216
- package/content/extensions/helpers/websocket/index.md +65 -503
- package/content/extensions/helpers/worker-thread/index.md +58 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
- package/content/guides/core-concepts/persistent/transactions.md +1 -1
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/migrations/redis-helpers-migration.md +1 -1
- package/content/guides/migrations/unified-connectors-migration.md +2 -2
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/references/base/application.md +1 -1
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/datasources-reference.md +599 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +17 -39
- package/content/references/base/filter-system/application-usage.md +69 -121
- package/content/references/base/filter-system/array-operators.md +12 -0
- package/content/references/base/filter-system/comparison-operators.md +12 -0
- package/content/references/base/filter-system/default-filter.md +136 -348
- package/content/references/base/filter-system/fields-order-pagination.md +38 -16
- package/content/references/base/filter-system/index.md +106 -257
- package/content/references/base/filter-system/json-filtering.md +12 -2
- package/content/references/base/filter-system/list-operators.md +16 -2
- package/content/references/base/filter-system/logical-operators.md +13 -0
- package/content/references/base/filter-system/null-operators.md +13 -0
- package/content/references/base/filter-system/pattern-matching.md +12 -0
- package/content/references/base/filter-system/quick-reference.md +11 -2
- package/content/references/base/filter-system/range-operators.md +12 -0
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +156 -233
- package/content/references/base/middlewares.md +35 -21
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +80 -1452
- package/content/references/base/repositories/advanced.md +156 -192
- package/content/references/base/repositories/index.md +77 -650
- package/content/references/base/repositories/mixins.md +22 -18
- package/content/references/base/repositories/relations.md +123 -171
- package/content/references/base/repositories/soft-deletable.md +58 -56
- package/content/references/base/secrets.md +263 -0
- package/content/references/base/services.md +2 -2
- package/content/references/configuration/environment-variables.md +48 -4
- package/content/references/configuration/index.md +49 -31
- 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/index.md +1 -1
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +29 -62
- 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 +57 -218
- 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/package.json +8 -8
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Worker Thread - Full Reference
|
|
3
|
+
description: Complete reference for WorkerPoolHelper, BaseWorkerHelper, BaseWorkerThreadHelper, BaseWorkerBusHelper, and every constructor option and error message
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Worker Thread - Full Reference
|
|
8
|
+
|
|
9
|
+
Exhaustive reference for the worker-thread classes, every constructor option, event-handler default, and troubleshooting case. For a readable introduction and the common tasks, start with the [Worker Thread overview](/extensions/helpers/worker-thread/).
|
|
10
|
+
|
|
11
|
+
**Files:**
|
|
12
|
+
|
|
13
|
+
- [`packages/helpers/src/modules/worker-thread/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/base.ts) - `AbstractWorkerHelper`, `BaseWorkerHelper`, `AbstractWorkerThreadHelper`, `BaseWorkerThreadHelper`
|
|
14
|
+
- [`packages/helpers/src/modules/worker-thread/worker-bus.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/worker-bus.ts) - `AbstractWorkerBusHelper`, `BaseWorkerBusHelper`, `AbstractWorkerMessageBusHandlerHelper`, `BaseWorkerMessageBusHandlerHelper`
|
|
15
|
+
- [`packages/helpers/src/modules/worker-thread/worker-pool.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/worker-pool.ts) - `WorkerPoolHelper`
|
|
16
|
+
- [`packages/helpers/src/modules/worker-thread/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/types.ts) - `IWorker`, `IWorkerThread`, `IWorkerBus`, `IWorkerMessageBusHandler`
|
|
17
|
+
- [`packages/helpers/src/modules/worker-thread/index.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/index.ts) - barrel
|
|
18
|
+
|
|
19
|
+
## Quick Reference
|
|
20
|
+
|
|
21
|
+
| Class | Extends | Use Case |
|
|
22
|
+
|-------|---------|----------|
|
|
23
|
+
| `WorkerPoolHelper` | `BaseHelper` | Singleton registry that tracks and limits active worker instances |
|
|
24
|
+
| `BaseWorkerHelper<MessageType>` | `AbstractWorkerHelper<MessageType>` | Wraps a `Worker` with event lifecycle hooks (online, exit, error, message) |
|
|
25
|
+
| `BaseWorkerThreadHelper` | `AbstractWorkerThreadHelper` | Runs inside a worker thread; manages named `WorkerBus` channels |
|
|
26
|
+
| `BaseWorkerBusHelper<IC, IP>` | `AbstractWorkerBusHelper<IC, IP>` | Bidirectional `MessagePort` communication with pre/post hooks |
|
|
27
|
+
| `BaseWorkerMessageBusHandlerHelper<IC>` | `AbstractWorkerMessageBusHandlerHelper<IC>` | Defines event handlers for a worker bus (message, close, error, exit) |
|
|
28
|
+
|
|
29
|
+
| Item | Value |
|
|
30
|
+
|------|-------|
|
|
31
|
+
| **Package** | `@venizia/ignis-helpers` |
|
|
32
|
+
| **Peer Dependency** | None (uses built-in `node:worker_threads`) |
|
|
33
|
+
| **Runtimes** | Node.js (uses `node:worker_threads` and `node:os`) |
|
|
34
|
+
|
|
35
|
+
### Import paths
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import {
|
|
39
|
+
WorkerPoolHelper,
|
|
40
|
+
BaseWorkerHelper,
|
|
41
|
+
BaseWorkerThreadHelper,
|
|
42
|
+
BaseWorkerBusHelper,
|
|
43
|
+
BaseWorkerMessageBusHandlerHelper,
|
|
44
|
+
AbstractWorkerHelper,
|
|
45
|
+
AbstractWorkerThreadHelper,
|
|
46
|
+
AbstractWorkerBusHelper,
|
|
47
|
+
AbstractWorkerMessageBusHandlerHelper,
|
|
48
|
+
} from '@venizia/ignis-helpers';
|
|
49
|
+
|
|
50
|
+
import type {
|
|
51
|
+
IWorker,
|
|
52
|
+
IWorkerThread,
|
|
53
|
+
IWorkerBus,
|
|
54
|
+
IWorkerMessageBusHandler,
|
|
55
|
+
} from '@venizia/ignis-helpers';
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## WorkerPoolHelper
|
|
59
|
+
|
|
60
|
+
`Source ->` [`worker-pool.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/worker-pool.ts)
|
|
61
|
+
|
|
62
|
+
Singleton registry for active workers. Limits the pool size to the number of CPU cores by default.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { WorkerPoolHelper } from '@venizia/ignis-helpers';
|
|
66
|
+
|
|
67
|
+
// Get the singleton instance
|
|
68
|
+
const pool = WorkerPoolHelper.getInstance();
|
|
69
|
+
|
|
70
|
+
// Or construct a custom instance directly
|
|
71
|
+
const customPool = new WorkerPoolHelper({ ignoreMaxWarning: true });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
> [!NOTE]
|
|
75
|
+
> `WorkerPoolHelper.getInstance()` always creates the singleton with `ignoreMaxWarning: false`. To override this behavior, construct a new instance manually - the singleton and a manually-constructed instance are independent registries.
|
|
76
|
+
|
|
77
|
+
### Constructor options
|
|
78
|
+
|
|
79
|
+
| Option | Type | Default | Description |
|
|
80
|
+
|--------|------|---------|-------------|
|
|
81
|
+
| `ignoreMaxWarning` | `boolean` | `false` | When `true`, allows registering workers beyond the CPU core count. When `false`, registration is skipped once the pool reaches the CPU core limit. |
|
|
82
|
+
|
|
83
|
+
### Methods
|
|
84
|
+
|
|
85
|
+
| Method | Signature | Description |
|
|
86
|
+
|--------|-----------|-------------|
|
|
87
|
+
| `getInstance` | `static getInstance(): WorkerPoolHelper` | Returns the singleton pool instance (creates one with `ignoreMaxWarning: false` if needed) |
|
|
88
|
+
| `register` | `register<MessageType>(opts: { key: string; worker: IWorker<MessageType> }): boolean` | Adds a worker to the pool. Returns `false` and logs instead of registering if the key already exists or the pool is at the CPU limit |
|
|
89
|
+
| `unregister` | `async unregister(opts: { key: string }): Promise<void>` | Terminates the worker (`worker.terminate()`) and removes it from the pool. The registry entry is deleted even if termination throws |
|
|
90
|
+
| `get` | `get<MessageType>(opts: { key: string }): IWorker<MessageType> \| undefined` | Retrieves a registered worker by key |
|
|
91
|
+
| `has` | `has(opts: { key: string }): boolean` | Checks if a worker is registered under the given key |
|
|
92
|
+
| `size` | `size(): number` | Returns the number of currently registered workers |
|
|
93
|
+
|
|
94
|
+
> [!WARNING]
|
|
95
|
+
> When the pool reaches the CPU core limit and `ignoreMaxWarning` is `false`, further `register()` calls return `false` and log a warning. No error is thrown.
|
|
96
|
+
|
|
97
|
+
## BaseWorkerHelper (main thread worker wrapper)
|
|
98
|
+
|
|
99
|
+
`Source ->` [`base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/base.ts)
|
|
100
|
+
|
|
101
|
+
Creates a `Worker` from a file path and automatically binds all lifecycle events.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { BaseWorkerHelper } from '@venizia/ignis-helpers';
|
|
105
|
+
|
|
106
|
+
const worker = new BaseWorkerHelper<MyMessageType>({
|
|
107
|
+
identifier: 'data-processor',
|
|
108
|
+
path: './workers/data-processor.js',
|
|
109
|
+
options: { workerData: { batchSize: 100 } },
|
|
110
|
+
eventHandlers: {
|
|
111
|
+
onMessage: opts => console.log('Received:', opts.message),
|
|
112
|
+
onError: opts => console.error('Worker error:', opts.error),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Constructor options
|
|
118
|
+
|
|
119
|
+
| Option | Type | Default | Description |
|
|
120
|
+
|--------|------|---------|-------------|
|
|
121
|
+
| `identifier` | `string` | - | A unique name for this worker instance, used as the `BaseHelper` identifier and in log output. Required. |
|
|
122
|
+
| `path` | `string \| URL` | - | Path to the worker script file, passed directly to `new Worker()`. Required. |
|
|
123
|
+
| `options` | `WorkerOptions` (Node.js) | - | Passed directly to `new Worker()`. Required. Supports `workerData`, `transferList`, `env`, etc. |
|
|
124
|
+
| `scope` | `string` | `'BaseWorkerHelper'` | Accepted but currently ignored - the constructor always sets the logger scope to `BaseWorkerHelper.name`, not `opts.scope`. |
|
|
125
|
+
| `eventHandlers` | `Partial<Pick<IWorker<MessageType>, 'onOnline' \| 'onExit' \| 'onError' \| 'onMessage' \| 'onMessageError'>>` | `undefined` | Optional overrides for lifecycle event callbacks. Any handler not provided falls back to default logging behavior. |
|
|
126
|
+
|
|
127
|
+
### Event handler overrides
|
|
128
|
+
|
|
129
|
+
| Handler | Signature | Default behavior |
|
|
130
|
+
|---------|-----------|-------------------|
|
|
131
|
+
| `onOnline` | `() => ValueOrPromise<void>` | Logs `"Worker ONLINE"` at info level |
|
|
132
|
+
| `onExit` | `(opts: { code: string \| number }) => ValueOrPromise<void>` | Logs `"Worker EXIT | Code: %s"` at warn level |
|
|
133
|
+
| `onError` | `(opts: { error: Error }) => ValueOrPromise<void>` | Logs `"Worker ERROR | Error: %s"` at error level |
|
|
134
|
+
| `onMessage` | `(opts: { message: MessageType }) => ValueOrPromise<void>` | Logs `"Worker MESSAGE | message: %j"` at error level |
|
|
135
|
+
| `onMessageError` | `(opts: { error: Error }) => ValueOrPromise<void>` | Logs `"Worker MESSAGE_ERROR | Error: %s"` at error level |
|
|
136
|
+
|
|
137
|
+
### Methods
|
|
138
|
+
|
|
139
|
+
| Method | Signature | Description |
|
|
140
|
+
|--------|-----------|-------------|
|
|
141
|
+
| `binding` | `binding(): void` | Binds all five event handlers (`online`, `exit`, `error`, `message`, `messageerror`) to the internal `Worker` instance. Called automatically by the constructor. Throws `[binding] Invalid worker instance to bind event handlers` if `this.worker` is falsy |
|
|
142
|
+
|
|
143
|
+
Every handler runs through `invokeHook`, which wraps the call in a `try/catch`: a synchronous throw inside a user handler is caught and logged (`"Hook execution FAILED | Error: %s"`) rather than crashing the process. Async rejections are settled the same way via `voidExecution`.
|
|
144
|
+
|
|
145
|
+
### AbstractWorkerHelper (subclassing)
|
|
146
|
+
|
|
147
|
+
For full control, extend `AbstractWorkerHelper` and implement all five lifecycle methods directly instead of passing `eventHandlers`:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { AbstractWorkerHelper } from '@venizia/ignis-helpers';
|
|
151
|
+
|
|
152
|
+
class CustomWorker extends AbstractWorkerHelper<MyMessage> {
|
|
153
|
+
onOnline() {
|
|
154
|
+
// Custom online handling
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
onExit(opts: { code: string | number }) {
|
|
158
|
+
// Custom exit handling, e.g., restart logic
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
onError(opts: { error: Error }) {
|
|
162
|
+
// Custom error handling
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
onMessage(opts: { message: MyMessage }) {
|
|
166
|
+
// Custom message processing
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
onMessageError(opts: { error: Error }) {
|
|
170
|
+
// Custom message error handling
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`AbstractWorkerHelper<MessageType>` declares `worker: Worker`, `options: WorkerOptions`, and the five abstract lifecycle methods above; it does not implement `binding()` itself.
|
|
176
|
+
|
|
177
|
+
## BaseWorkerThreadHelper (inside a worker thread)
|
|
178
|
+
|
|
179
|
+
`Source ->` [`base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/base.ts)
|
|
180
|
+
|
|
181
|
+
Used inside a worker script to manage named communication buses. Must be instantiated from within a worker thread - constructing it on the main thread throws.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// Inside worker-script.js
|
|
185
|
+
import { BaseWorkerThreadHelper } from '@venizia/ignis-helpers';
|
|
186
|
+
|
|
187
|
+
const thread = new BaseWorkerThreadHelper({ scope: 'DataProcessor' });
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Constructor options
|
|
191
|
+
|
|
192
|
+
| Option | Type | Default | Description |
|
|
193
|
+
|--------|------|---------|-------------|
|
|
194
|
+
| `scope` | `string` | - | Logger scope and `BaseHelper` identifier for this worker thread. Required. |
|
|
195
|
+
|
|
196
|
+
The constructor checks `isMainThread` (from `node:worker_threads`) and throws `[BaseWorker] Cannot start worker in MAIN_THREAD` if `true`.
|
|
197
|
+
|
|
198
|
+
### Methods
|
|
199
|
+
|
|
200
|
+
| Method | Signature | Description |
|
|
201
|
+
|--------|-----------|-------------|
|
|
202
|
+
| `bindWorkerBus` | `bindWorkerBus<IC, IP>(opts: { key: string; bus: IWorkerBus<IC, IP> }): void` | Registers a bus under the given key. Logs a warning and returns without overwriting if the key already exists |
|
|
203
|
+
| `unbindWorkerBus` | `unbindWorkerBus(opts: { key: string }): void` | Calls `port.removeAllListeners()` on the bus's port, then deletes it from the registry. Logs a warning if the key is not found |
|
|
204
|
+
| `getWorkerBus` | `getWorkerBus<IC, IP>(opts: { key: string }): IWorkerBus<IC, IP>` | Returns the bus for the given key. Throws `[getWorkerBus] Not found worker bus | key: {key}` if not found |
|
|
205
|
+
|
|
206
|
+
`unbindWorkerBus` is defined only on `BaseWorkerThreadHelper`, not on the `AbstractWorkerThreadHelper` interface - `AbstractWorkerThreadHelper` declares `buses`, `bindWorkerBus`, and `getWorkerBus` as abstract.
|
|
207
|
+
|
|
208
|
+
## BaseWorkerMessageBusHandlerHelper
|
|
209
|
+
|
|
210
|
+
`Source ->` [`worker-bus.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/worker-bus.ts)
|
|
211
|
+
|
|
212
|
+
Defines the event callbacks for a worker bus.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
const handler = new BaseWorkerMessageBusHandlerHelper<MyPayload>({
|
|
216
|
+
scope: 'ProcessorHandler',
|
|
217
|
+
onMessage: opts => console.log('Processing:', opts.message),
|
|
218
|
+
onError: opts => console.error('Bus error:', opts.error),
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Constructor options
|
|
223
|
+
|
|
224
|
+
| Option | Type | Default | Description |
|
|
225
|
+
|--------|------|---------|-------------|
|
|
226
|
+
| `scope` | `string` | - | Logger scope and identifier. Required. |
|
|
227
|
+
| `onMessage` | `(opts: { message: IConsumePayload }) => ValueOrPromise<void>` | - | Handler called when a message is received on the port. Required. |
|
|
228
|
+
| `onClose` | `() => ValueOrPromise<void>` | No-op `() => {}` | Handler called when the port emits `close`. |
|
|
229
|
+
| `onError` | `(opts: { error: Error }) => ValueOrPromise<void>` | Logs `"worker error: %s"` at error level | Handler called on port `error` and `messageerror` events. |
|
|
230
|
+
| `onExit` | `(opts: { exitCode: number \| string }) => ValueOrPromise<void>` | Logs `"worker EXITED | exitCode: %s"` at warn level | Handler called when the port emits `exit`. |
|
|
231
|
+
|
|
232
|
+
## BaseWorkerBusHelper (MessagePort communication)
|
|
233
|
+
|
|
234
|
+
`Source ->` [`worker-bus.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/worker-bus.ts)
|
|
235
|
+
|
|
236
|
+
Wraps a `MessagePort` to provide structured bidirectional messaging with lifecycle event handlers.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { BaseWorkerBusHelper, BaseWorkerMessageBusHandlerHelper } from '@venizia/ignis-helpers';
|
|
240
|
+
import { parentPort } from 'node:worker_threads';
|
|
241
|
+
|
|
242
|
+
const handler = new BaseWorkerMessageBusHandlerHelper<IncomingMessage>({
|
|
243
|
+
scope: 'MyBusHandler',
|
|
244
|
+
onMessage: opts => console.log('Received:', opts.message),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const bus = new BaseWorkerBusHelper<IncomingMessage, OutgoingMessage>({
|
|
248
|
+
scope: 'MyBus',
|
|
249
|
+
port: parentPort!,
|
|
250
|
+
busHandler: handler,
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Constructor options
|
|
255
|
+
|
|
256
|
+
| Option | Type | Default | Description |
|
|
257
|
+
|--------|------|---------|-------------|
|
|
258
|
+
| `scope` | `string` | - | Logger scope and identifier. Required. |
|
|
259
|
+
| `port` | `MessagePort` | - | The `MessagePort` to bind for communication. Required. |
|
|
260
|
+
| `busHandler` | `IWorkerMessageBusHandler<IConsumePayload>` | - | Handler that receives incoming messages and lifecycle events. Required. |
|
|
261
|
+
|
|
262
|
+
The constructor binds `message`, `error`, `messageerror`, `exit`, and `close` listeners on `port`, each routed through the handler's matching callback (`error` and `messageerror` both call `handler.onError`).
|
|
263
|
+
|
|
264
|
+
### Methods
|
|
265
|
+
|
|
266
|
+
| Method | Signature | Description |
|
|
267
|
+
|--------|-----------|-------------|
|
|
268
|
+
| `postMessage` | `postMessage(opts: { message: IPublishPayload; transferList: readonly Transferable[] \| undefined }): ValueOrPromise<void>` | Sends a message through the port. If `transferList` is provided, calls `port.postMessage(message, [...transferList])`; otherwise `port.postMessage(message)`. Logs and returns (does not throw) if `port` is falsy |
|
|
269
|
+
|
|
270
|
+
### Pre/post message hooks
|
|
271
|
+
|
|
272
|
+
`onBeforePostMessage` and `onAfterPostMessage` are optional properties declared on the class but left `undefined` by the constructor - assign them after construction:
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
bus.onBeforePostMessage = opts => {
|
|
276
|
+
console.log('About to send:', opts.message);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
bus.onAfterPostMessage = opts => {
|
|
280
|
+
console.log('Sent:', opts.message);
|
|
281
|
+
};
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`postMessage` invokes `onBeforePostMessage` (if set) before the port write and `onAfterPostMessage` (if set) after, both through the same `try/catch`-wrapped `invokeHook` used for the port event listeners.
|
|
285
|
+
|
|
286
|
+
### Sending transferable objects
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// Send a simple message
|
|
290
|
+
bus.postMessage({
|
|
291
|
+
message: { result: 'processed' },
|
|
292
|
+
transferList: undefined,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Send with transferable objects (zero-copy)
|
|
296
|
+
const buffer = new ArrayBuffer(1024);
|
|
297
|
+
bus.postMessage({
|
|
298
|
+
message: { result: 'binary-data' },
|
|
299
|
+
transferList: [buffer],
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Types
|
|
304
|
+
|
|
305
|
+
`Source ->` [`types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/types.ts)
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
interface IWorker<MessageType> {
|
|
309
|
+
worker: Worker;
|
|
310
|
+
options: WorkerOptions;
|
|
311
|
+
|
|
312
|
+
onOnline(): ValueOrPromise<void>;
|
|
313
|
+
onExit(opts: { code: string | number }): ValueOrPromise<void>;
|
|
314
|
+
onError(opts: { error: Error }): ValueOrPromise<void>;
|
|
315
|
+
onMessage(opts: { message: MessageType }): ValueOrPromise<void>;
|
|
316
|
+
onMessageError(opts: { error: Error }): ValueOrPromise<void>;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
interface IWorkerThread {
|
|
320
|
+
buses: { [workerKey: string | symbol]: IWorkerBus<AnyType, AnyType> };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
interface IWorkerMessageBusHandler<IConsumePayload> {
|
|
324
|
+
onMessage: (opts: { message: IConsumePayload }) => ValueOrPromise<void>;
|
|
325
|
+
onClose: () => ValueOrPromise<void>;
|
|
326
|
+
onError: (opts: { error: Error }) => ValueOrPromise<void>;
|
|
327
|
+
onExit: (opts: { exitCode: number | string }) => ValueOrPromise<void>;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
interface IWorkerBus<IConsumePayload, IPublishPayload> {
|
|
331
|
+
port: MessagePort;
|
|
332
|
+
handler: IWorkerMessageBusHandler<IConsumePayload>;
|
|
333
|
+
|
|
334
|
+
onBeforePostMessage?(opts: { message: IPublishPayload }): ValueOrPromise<void>;
|
|
335
|
+
onAfterPostMessage?(opts: { message: IPublishPayload }): ValueOrPromise<void>;
|
|
336
|
+
postMessage(opts: {
|
|
337
|
+
message: IPublishPayload;
|
|
338
|
+
transferList: readonly Transferable[] | undefined;
|
|
339
|
+
}): ValueOrPromise<void>;
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Troubleshooting
|
|
344
|
+
|
|
345
|
+
### "[BaseWorker] Cannot start worker in MAIN_THREAD"
|
|
346
|
+
|
|
347
|
+
**Cause:** `BaseWorkerThreadHelper` was instantiated on the main thread. This class is designed to run only inside a worker thread (where `isMainThread` from `node:worker_threads` is `false`).
|
|
348
|
+
|
|
349
|
+
**Fix:** Only create `BaseWorkerThreadHelper` instances inside worker scripts that are spawned via `new Worker(path)`:
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// worker-script.js (spawned by the main thread)
|
|
353
|
+
import { BaseWorkerThreadHelper } from '@venizia/ignis-helpers';
|
|
354
|
+
|
|
355
|
+
const thread = new BaseWorkerThreadHelper({ scope: 'MyWorker' }); // OK here
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### "[binding] Invalid worker instance to bind event handlers"
|
|
359
|
+
|
|
360
|
+
**Cause:** `BaseWorkerHelper.binding()` was called but the internal `Worker` instance is falsy. This can occur if the worker script path is invalid and the `Worker` constructor fails.
|
|
361
|
+
|
|
362
|
+
**Fix:** Ensure the `path` passed to `BaseWorkerHelper` points to a valid, existing JavaScript file:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const worker = new BaseWorkerHelper({
|
|
366
|
+
identifier: 'my-worker',
|
|
367
|
+
path: './workers/my-worker.js', // Must exist and be a valid worker script
|
|
368
|
+
options: {},
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### "[register] Invalid worker registry instance | please init registry before register new worker!"
|
|
373
|
+
|
|
374
|
+
**Cause:** `WorkerPoolHelper.register()` was called but the internal registry `Map` is falsy. This is a defensive check that should not occur under normal usage.
|
|
375
|
+
|
|
376
|
+
**Fix:** Ensure you are using either `WorkerPoolHelper.getInstance()` or `new WorkerPoolHelper()`, both of which initialize the registry in the constructor.
|
|
377
|
+
|
|
378
|
+
### "[getWorkerBus] Not found worker bus | key: {key}"
|
|
379
|
+
|
|
380
|
+
**Cause:** `BaseWorkerThreadHelper.getWorkerBus()` was called with a key that has not been registered via `bindWorkerBus()`.
|
|
381
|
+
|
|
382
|
+
**Fix:** Verify the bus was bound before retrieving it:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
thread.bindWorkerBus({ key: 'my-bus', bus: myBus });
|
|
386
|
+
|
|
387
|
+
// Now safe to retrieve
|
|
388
|
+
const bus = thread.getWorkerBus({ key: 'my-bus' });
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### "Failed to post message to main | Invalid parentPort!"
|
|
392
|
+
|
|
393
|
+
**Cause:** `BaseWorkerBusHelper.postMessage()` was called but the `port` property is falsy. This typically means the bus was constructed with an invalid `MessagePort`. This is logged at error level, not thrown - the message is silently dropped.
|
|
394
|
+
|
|
395
|
+
**Fix:** Ensure a valid `MessagePort` (e.g., `parentPort` from `node:worker_threads` or a port from `new MessageChannel()`) is passed to the constructor:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { parentPort } from 'node:worker_threads';
|
|
399
|
+
|
|
400
|
+
const bus = new BaseWorkerBusHelper({
|
|
401
|
+
scope: 'MyBus',
|
|
402
|
+
port: parentPort!, // Must be a valid MessagePort
|
|
403
|
+
busHandler: handler,
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Worker pool silently skips registration
|
|
408
|
+
|
|
409
|
+
**Cause:** The pool has reached the CPU core limit and `ignoreMaxWarning` is `false` (the default for `getInstance()`). `register()` returns `false` and logs a warning instead of throwing.
|
|
410
|
+
|
|
411
|
+
**Fix:** Either free up pool slots first, or create a pool with `ignoreMaxWarning: true`:
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// Option 1: Free up pool slots
|
|
415
|
+
await pool.unregister({ key: 'old-worker' });
|
|
416
|
+
pool.register({ key: 'new-worker', worker: newWorker });
|
|
417
|
+
|
|
418
|
+
// Option 2: Allow exceeding the limit
|
|
419
|
+
const pool = new WorkerPoolHelper({ ignoreMaxWarning: true });
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## See also
|
|
423
|
+
|
|
424
|
+
- [Worker Thread overview](/extensions/helpers/worker-thread/) - introduction and the most common tasks
|
|
425
|
+
- [Queue Helper](/extensions/helpers/queue/) - message-queue processing as an alternative to worker threads
|
|
426
|
+
- [Node.js Worker Threads](https://nodejs.org/api/worker_threads.html) - official `worker_threads` documentation
|
|
427
|
+
- [MessagePort API](https://nodejs.org/api/worker_threads.html#class-messageport) - underlying port communication
|
|
428
|
+
- [Transferable Objects](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects) - zero-copy data transfer
|
|
@@ -200,7 +200,7 @@ const user = await userRepo.findById({ id: '123' });
|
|
|
200
200
|
// { id: '123', email: 'john@example.com' }
|
|
201
201
|
|
|
202
202
|
// Connector query - includes all fields
|
|
203
|
-
const connector = userRepo.
|
|
203
|
+
const connector = userRepo.connector;
|
|
204
204
|
const [fullUser] = await connector
|
|
205
205
|
.select()
|
|
206
206
|
.from(User.schema)
|
|
@@ -254,8 +254,8 @@ export class ArticleRepository extends DefaultSearchRepository<TArticleDocument>
|
|
|
254
254
|
|
|
255
255
|
### Write error semantics (shared by both search engines)
|
|
256
256
|
|
|
257
|
-
- **`create()` with a duplicate id throws `409` (`
|
|
258
|
-
- **`updateById()` against a missing id throws `404` (`
|
|
257
|
+
- **`create()` with a duplicate id throws `409` (`normalized.code: 'core.search_engine.already_exists'`)** on both Typesense and Meilisearch - a duplicate is a conflict, never a silent overwrite. If you want last-write-wins, call `upsert()` explicitly. (On Meilisearch the pre-check is not atomic - see the [Meilisearch guide](./search-meilisearch); use `upsert()` when concurrent same-id creates are possible.)
|
|
258
|
+
- **`updateById()` against a missing id throws `404` (`normalized.code: 'core.search_engine.not_found'`)** on both engines. This is the deliberate divergence from the PostgreSQL connector, where a missing-id `updateById` is a silent `{ count: 0 }`.
|
|
259
259
|
- **`deleteById()` against a missing id is silent** (`{ count: 0, data: null }`), matching the relational connectors.
|
|
260
260
|
|
|
261
261
|
## Searching with `search({ mode })`
|
|
@@ -413,7 +413,7 @@ The typesense connector has no transaction or row-level-locking model. Passing a
|
|
|
413
413
|
await articleRepository.updateById({
|
|
414
414
|
id: '123',
|
|
415
415
|
data: { title: 'New' },
|
|
416
|
-
options: { transaction: tx }, // throws: 501,
|
|
416
|
+
options: { transaction: tx }, // throws: 501, normalized.code 'core.not_supported'
|
|
417
417
|
});
|
|
418
418
|
```
|
|
419
419
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
IGNIS supports explicit transaction objects that can be passed across multiple services and repositories, allowing for complex, multi-step business logic to be atomic.
|
|
4
4
|
|
|
5
5
|
> [!NOTE] PostgreSQL-only capability
|
|
6
|
-
> Real transactions are a **PostgreSQL connector** capability - `BasePostgresDataSource.getCapabilities()` returns `{ transactions: true }` and its `beginTransaction()` opens a real database transaction, as documented below. The typesense connector inherits the engine-neutral `AbstractDataSource` default: calling `beginTransaction()` on it throws a `501 Not Implemented` (`
|
|
6
|
+
> Real transactions are a **PostgreSQL connector** capability - `BasePostgresDataSource.getCapabilities()` returns `{ transactions: true }` and its `beginTransaction()` opens a real database transaction, as documented below. The typesense connector inherits the engine-neutral `AbstractDataSource` default: calling `beginTransaction()` on it throws a `501 Not Implemented` (`normalized.code: 'core.not_supported'`) via the shared `throwNotSupported` utility. See [Connectors](/references/base/connectors) for the capabilities model.
|
|
7
7
|
|
|
8
8
|
## Using Transactions
|
|
9
9
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Secrets & Vault
|
|
3
|
+
description: Load configuration and credentials from a vault, hydrate them at boot, and rotate database credentials without a restart
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Secrets & Vault
|
|
8
|
+
|
|
9
|
+
IGNIS can load configuration and credentials from a vault instead of reading `process.env` directly. This guide shows how to enable it, how to consume secrets in your code, and how to rotate dynamic database credentials into a live pool without restarting the server.
|
|
10
|
+
|
|
11
|
+
> [!TIP] You do not have to do anything to keep working as before
|
|
12
|
+
> Until you override `registerSecrets()`, IGNIS uses the `system-envs` provider, which reads `process.env`. Everything below is opt-in.
|
|
13
|
+
|
|
14
|
+
## The Mental Model
|
|
15
|
+
|
|
16
|
+
Secrets reach your app through two paths:
|
|
17
|
+
|
|
18
|
+
- **Hydrate** - at boot, IGNIS fetches secrets and merges them into `process.env` / `Envs`. Existing code that reads `process.env.APP_ENV_*` keeps working with no change.
|
|
19
|
+
- **Provider** - the resolved provider is bound in the container at `@app/config`. Inject it wherever you need on-demand reads, dynamic credentials, or rotation.
|
|
20
|
+
|
|
21
|
+
You configure both in one place: a `registerSecrets()` override on your application class.
|
|
22
|
+
|
|
23
|
+
## Enabling a Provider
|
|
24
|
+
|
|
25
|
+
Override `registerSecrets()` on your `Application extends BaseApplication`.
|
|
26
|
+
|
|
27
|
+
### HashiCorp Vault
|
|
28
|
+
|
|
29
|
+
Recommended for enterprise: KV v2 static secrets plus dynamic database credentials with automatic rotation.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { SecretProviders, VaultAuthMethods } from '@venizia/ignis-helpers';
|
|
33
|
+
|
|
34
|
+
export class Application extends BaseApplication {
|
|
35
|
+
override registerSecrets() {
|
|
36
|
+
return {
|
|
37
|
+
provider: SecretProviders.HASHICORP_VAULT,
|
|
38
|
+
|
|
39
|
+
config: {
|
|
40
|
+
endpoint: 'https://vault.internal:8200',
|
|
41
|
+
// Dev: a raw token. Production: AppRole or Kubernetes.
|
|
42
|
+
auth: { method: VaultAuthMethods.APP_ROLE, roleId, secretId },
|
|
43
|
+
// or: { method: VaultAuthMethods.TOKEN, token }
|
|
44
|
+
// or: { method: VaultAuthMethods.KUBERNETES, role: 'my-app' }
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Static KV v2 secrets -> merged into Envs once at boot.
|
|
48
|
+
hydrate: [
|
|
49
|
+
{ path: 'secret/data/myapp/config' }, // keys land as-is
|
|
50
|
+
{ path: 'secret/data/myapp/db', prefix: 'APP_ENV_DS_' }, // optional prefix
|
|
51
|
+
// { path: 'secret/data/myapp/db', keys: { password: 'APP_ENV_DS_PASSWORD' } },
|
|
52
|
+
],
|
|
53
|
+
|
|
54
|
+
// Dynamic engine secrets -> renewed and rotated automatically.
|
|
55
|
+
lease: [
|
|
56
|
+
{ key: 'datasources.PostgresDataSource', path: 'database/creds/app-role' },
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`node-vault` is an optional peer. Install it in the application that uses this provider:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bun add node-vault
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Applications that do not use this provider never need it - not even when compiling a binary with
|
|
70
|
+
`Bun.build`. If your application uses this provider **and** compiles a binary, ship `node-vault` in
|
|
71
|
+
`node_modules` next to the binary, or inject a ready-made `client` through the helper options.
|
|
72
|
+
|
|
73
|
+
### Dotenv Vault
|
|
74
|
+
|
|
75
|
+
An encrypted `.env.vault` file decrypted at runtime with a per-environment `DOTENV_KEY`. Static only.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { SecretProviders } from '@venizia/ignis-helpers';
|
|
79
|
+
|
|
80
|
+
export class Application extends BaseApplication {
|
|
81
|
+
override registerSecrets() {
|
|
82
|
+
return {
|
|
83
|
+
provider: SecretProviders.DOTENV_VAULT,
|
|
84
|
+
config: { path: '.env.vault', dotenvKey: process.env.DOTENV_KEY },
|
|
85
|
+
hydrate: [{ path: 'ignored' }], // decrypts the file and merges it into Envs
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
bun add @dotenvx/dotenvx
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### System Env (default)
|
|
96
|
+
|
|
97
|
+
The default; shown here only to be explicit. Reads `process.env`, no dependencies.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
override registerSecrets() {
|
|
101
|
+
return { provider: SecretProviders.SYSTEM_ENVS };
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Addressing and Merging
|
|
106
|
+
|
|
107
|
+
Each `hydrate` entry resolves a path to a flat key-value object that is merged into `Envs` and `process.env`:
|
|
108
|
+
|
|
109
|
+
- **Convention** - store the keys in the vault already named `APP_ENV_...` and they merge as-is, no mapping needed.
|
|
110
|
+
- **`prefix`** - prepend a string to every merged key.
|
|
111
|
+
- **`keys`** - an explicit `vaultKey -> envKey` map for precise control (wins over `prefix`).
|
|
112
|
+
|
|
113
|
+
When the provider is live, vault values take precedence over `process.env`.
|
|
114
|
+
|
|
115
|
+
## Reading Secrets in Your Code
|
|
116
|
+
|
|
117
|
+
Inject the provider bound at `@app/config`.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import type { ISecretsHelper } from '@venizia/ignis-helpers';
|
|
121
|
+
|
|
122
|
+
@service()
|
|
123
|
+
export class BillingService {
|
|
124
|
+
constructor(
|
|
125
|
+
@inject({ key: '@app/config' }) private secrets: ISecretsHelper,
|
|
126
|
+
) {}
|
|
127
|
+
|
|
128
|
+
async run() {
|
|
129
|
+
// A single keyed value (TTL-cached):
|
|
130
|
+
const apiKey = await this.secrets.get({ path: 'secret/data/myapp/stripe', key: 'apiKey' });
|
|
131
|
+
|
|
132
|
+
// Or the whole bundle at a path:
|
|
133
|
+
const bundle = await this.secrets.getBundle({ path: 'secret/data/myapp/stripe' });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Code that only relies on hydrated values keeps reading `process.env.APP_ENV_*` and needs no injection at all.
|
|
139
|
+
|
|
140
|
+
## Rotating Database Credentials
|
|
141
|
+
|
|
142
|
+
Dynamic secrets let Vault mint short-lived database credentials that expire and rotate. IGNIS renews them and, on rotation, rebuilds the connection pool gracefully.
|
|
143
|
+
|
|
144
|
+
You do **not** write any rotation code for PostgreSQL. Two steps enable it:
|
|
145
|
+
|
|
146
|
+
1. Configure a dynamic database engine in Vault so a read against `database/creds/<role>` returns fresh credentials with a TTL.
|
|
147
|
+
2. Add a `lease` entry whose `key` is the DI binding key of your datasource (`datasources.<ClassName>`), as in the HashiCorp example above.
|
|
148
|
+
|
|
149
|
+
From there IGNIS:
|
|
150
|
+
|
|
151
|
+
- renews the lease before it expires;
|
|
152
|
+
- when Vault issues fresh credentials, calls the datasource's `onSecretRotated()`, which swaps the new credentials in, builds a new pool, and drains the old one so in-flight transactions finish;
|
|
153
|
+
- keeps the app running - no restart.
|
|
154
|
+
|
|
155
|
+
> [!WARNING] Your datasource configure() must read from this.settings
|
|
156
|
+
> Rotation writes the new credentials onto `this.settings` and re-runs `configure()`. If your `configure()` builds the pool from a hard-coded connection string instead, rotation rebuilds with stale credentials. Build the pool from `this.settings`:
|
|
157
|
+
> ```typescript
|
|
158
|
+
> override configure(): void {
|
|
159
|
+
> this.client = new Pool(this.settings);
|
|
160
|
+
> }
|
|
161
|
+
> ```
|
|
162
|
+
|
|
163
|
+
## Behaviour When the Vault Is Down
|
|
164
|
+
|
|
165
|
+
The failure policy is keyed on `NODE_ENV`:
|
|
166
|
+
|
|
167
|
+
- **Development** (`local`, `debug`, `development`, `dev`, `sit`) - a vault failure logs a warning and falls back to `process.env`, so you can work offline.
|
|
168
|
+
- **Everything else** - a vault failure throws and crashes the boot. The app never starts with missing or empty secrets.
|
|
169
|
+
|
|
170
|
+
## Testing Against a Real Vault
|
|
171
|
+
|
|
172
|
+
A local end-to-end setup (Vault dev-mode plus a throwaway PostgreSQL) is described in `docs/superpowers/vault-integration-local-testing.md` in the repository.
|
|
173
|
+
|
|
174
|
+
## See Also
|
|
175
|
+
|
|
176
|
+
- [Secrets & Vault Reference](/references/base/secrets) - full API, machinery, and const-classes
|
|
177
|
+
- [DataSources](/guides/core-concepts/persistent/datasources) - the pool that rotation rebuilds
|
|
@@ -57,7 +57,7 @@ export class UserService extends BaseService {
|
|
|
57
57
|
|
|
58
58
|
## Registering a Service
|
|
59
59
|
|
|
60
|
-
Registration is always imperative - call `this.service(ClassName)` inside an application lifecycle method
|
|
60
|
+
Registration is always imperative - call `this.service(ClassName)` inside an application lifecycle method, which handles the binding. There is no class-level decorator for this.
|
|
61
61
|
|
|
62
62
|
```typescript
|
|
63
63
|
import { BaseApplication } from '@venizia/ignis';
|
|
@@ -155,7 +155,7 @@ If you wrapped `subscribe(...)` in a `try/catch` expecting it to throw, that bra
|
|
|
155
155
|
|
|
156
156
|
These are new and do not break anything:
|
|
157
157
|
|
|
158
|
-
- **`RedisSentinelHelper`** - Redis Sentinel topology with automatic failover. See the [Redis Helper reference](/extensions/helpers/redis/) (Sentinel section).
|
|
158
|
+
- **`RedisSentinelHelper`** - Redis Sentinel topology with automatic failover. See the [Redis Helper full reference](/extensions/helpers/redis/reference#construction-sentinel) (Sentinel section).
|
|
159
159
|
- **`createRedisHelper({ mode })`** - a factory that builds the right helper from `RedisModes.SINGLE | CLUSTER | SENTINEL`, so you can pick a topology from config.
|
|
160
160
|
- **`RedisModes`** and **`RedisSentinelRoles`** - const-classes for the enumerable mode/role strings, with matching `TRedisMode` / `TRedisSentinelRole` types.
|
|
161
161
|
- **Expanded data API** - key lifecycle and counters (`exists`, `expire`, `expireAt`, `ttl`, `persist`, `incr`, `decr`, `incrBy`, `decrBy`), hash completion (`hGet`, `hDel`, `hExists`, `hKeys`, `hVals`, `hIncrBy`, `hLen`), sets (`sAdd`, `sRem`, `sMembers`, `sIsMember`, `sCard`), and lists (`lPush`, `rPush`, `lPop`, `rPop`, `lRange`, `lLen`).
|