@venizia/ignis-docs 0.2.1-0 → 0.2.1-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/best-practices/architectural-patterns.md +3 -3
- package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/content/best-practices/contribution-workflow.md +2 -2
- package/content/best-practices/error-handling.md +94 -89
- package/content/best-practices/security-guidelines.md +5 -5
- package/content/extensions/components/api-reference.md +22 -21
- package/content/extensions/components/authentication/api.md +64 -28
- package/content/extensions/components/authentication/errors.md +19 -5
- package/content/extensions/components/authentication/index.md +19 -18
- package/content/extensions/components/authentication/usage.md +55 -30
- package/content/extensions/components/authorization/api.md +351 -84
- package/content/extensions/components/authorization/errors.md +51 -17
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +24 -16
- package/content/extensions/components/authorization/usage.md +45 -21
- package/content/extensions/components/health-check.md +15 -9
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +105 -54
- package/content/extensions/components/mail/errors.md +12 -10
- package/content/extensions/components/mail/index.md +32 -13
- package/content/extensions/components/mail/usage.md +26 -18
- package/content/extensions/components/request-tracker.md +18 -14
- package/content/extensions/components/socket-io/api.md +377 -882
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +72 -88
- package/content/extensions/components/socket-io/usage.md +107 -117
- package/content/extensions/components/static-asset/api.md +83 -31
- package/content/extensions/components/static-asset/errors.md +18 -7
- package/content/extensions/components/static-asset/index.md +18 -11
- package/content/extensions/components/static-asset/usage.md +11 -6
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +58 -27
- package/content/extensions/components/websocket/errors.md +3 -3
- package/content/extensions/components/websocket/index.md +8 -7
- package/content/extensions/components/websocket/usage.md +21 -8
- package/content/extensions/helpers/cron/index.md +8 -7
- package/content/extensions/helpers/crypto/index.md +16 -8
- package/content/extensions/helpers/crypto/reference.md +96 -24
- package/content/extensions/helpers/env/index.md +14 -10
- package/content/extensions/helpers/error/index.md +99 -23
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +23 -6
- package/content/extensions/helpers/inversion/reference.md +30 -22
- package/content/extensions/helpers/kafka/admin.md +3 -2
- package/content/extensions/helpers/kafka/compile-binary.md +69 -44
- package/content/extensions/helpers/kafka/consumer.md +24 -21
- package/content/extensions/helpers/kafka/examples.md +22 -234
- package/content/extensions/helpers/kafka/index.md +30 -62
- package/content/extensions/helpers/kafka/producer.md +31 -26
- package/content/extensions/helpers/kafka/schema-registry.md +19 -14
- package/content/extensions/helpers/logger/hf-logger.md +49 -22
- package/content/extensions/helpers/logger/index.md +37 -12
- package/content/extensions/helpers/logger/pino.md +31 -11
- package/content/extensions/helpers/logger/reference.md +243 -52
- package/content/extensions/helpers/network/api.md +65 -30
- package/content/extensions/helpers/network/index.md +32 -11
- package/content/extensions/helpers/queue/index.md +17 -6
- package/content/extensions/helpers/queue/reference.md +52 -25
- package/content/extensions/helpers/redis/index.md +29 -11
- package/content/extensions/helpers/redis/reference.md +85 -28
- package/content/extensions/helpers/secrets/index.md +82 -12
- package/content/extensions/helpers/socket-io/api.md +40 -21
- package/content/extensions/helpers/socket-io/index.md +26 -10
- package/content/extensions/helpers/storage/api.md +42 -24
- package/content/extensions/helpers/storage/index.md +10 -7
- package/content/extensions/helpers/types/index.md +20 -7
- package/content/extensions/helpers/types/reference.md +53 -14
- package/content/extensions/helpers/uid/index.md +166 -10
- package/content/extensions/helpers/websocket/api.md +52 -13
- package/content/extensions/helpers/websocket/index.md +7 -4
- package/content/extensions/helpers/worker-thread/index.md +9 -5
- package/content/extensions/helpers/worker-thread/reference.md +20 -20
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +4 -3
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +7 -6
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +63 -21
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +14 -14
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +32 -31
- package/content/references/base/datasources.md +6 -6
- package/content/references/base/dependency-injection.md +12 -11
- package/content/references/base/filter-system/application-usage.md +54 -30
- package/content/references/base/filter-system/array-operators.md +24 -46
- package/content/references/base/filter-system/comparison-operators.md +47 -67
- package/content/references/base/filter-system/default-filter.md +59 -53
- package/content/references/base/filter-system/fields-order-pagination.md +92 -146
- package/content/references/base/filter-system/index.md +25 -13
- package/content/references/base/filter-system/json-filtering.md +45 -184
- package/content/references/base/filter-system/list-operators.md +23 -53
- package/content/references/base/filter-system/logical-operators.md +63 -121
- package/content/references/base/filter-system/null-operators.md +34 -104
- package/content/references/base/filter-system/pattern-matching.md +40 -55
- package/content/references/base/filter-system/quick-reference.md +86 -198
- package/content/references/base/filter-system/range-operators.md +18 -46
- package/content/references/base/filter-system/tips.md +6 -6
- package/content/references/base/filter-system/use-cases.md +33 -15
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +11 -10
- package/content/references/base/models-reference.md +17 -17
- package/content/references/base/models.md +4 -3
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +224 -326
- package/content/references/base/repositories/index.md +19 -6
- package/content/references/base/repositories/mixins.md +5 -5
- package/content/references/base/repositories/relations.md +160 -293
- package/content/references/base/repositories/soft-deletable.md +16 -6
- package/content/references/base/secrets.md +17 -13
- package/content/references/base/services.md +6 -4
- package/content/references/configuration/environment-variables.md +31 -23
- package/content/references/configuration/index.md +6 -4
- package/content/references/index.md +1 -1
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +5 -1
- package/content/references/utilities/jsx-reference.md +11 -11
- package/content/references/utilities/jsx.md +2 -2
- package/content/references/utilities/module.md +78 -25
- package/content/references/utilities/request.md +2 -1
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +2 -2
- package/content/references/utilities/statuses-reference.md +4 -4
- package/content/references/utilities/statuses.md +5 -5
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +17 -16
|
@@ -20,13 +20,33 @@ throw getError({
|
|
|
20
20
|
});
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
The framework's `AppErrorMiddleware` catches `ApplicationError` instances and formats them into a consistent JSON response
|
|
23
|
+
The framework's `AppErrorMiddleware` catches `ApplicationError` instances and formats them into a consistent JSON response.
|
|
24
|
+
|
|
25
|
+
## Find what you need
|
|
26
|
+
|
|
27
|
+
| You want to | Go to |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Throw a one-off error with a status code | [Throw a free-form error](#throw-a-free-form-error) |
|
|
30
|
+
| Re-throw an error you already caught and shaped | [Recognize an already-shaped error in a catch block](#recognize-an-already-shaped-error-in-a-catch-block) |
|
|
31
|
+
| Declare a reusable, i18n-ready error once | [Catalog a reusable error](#catalog-a-reusable-error) |
|
|
32
|
+
| Get autocomplete on `messageCode` | [Register catalog keys for messageCode autocomplete](#register-catalog-keys-for-messagecode-autocomplete) |
|
|
33
|
+
| Build a message code outside a catalog | [Build a code outside a catalog](#build-a-code-outside-a-catalog) |
|
|
34
|
+
| See the exact JSON shape a client receives | [Read the error response shape](#read-the-error-response-shape) |
|
|
35
|
+
| Read or rehydrate a server error in a browser app | [Consume the error response from a browser client](#consume-the-error-response-from-a-browser-client) |
|
|
36
|
+
| Look up what a specific input produces | [Every shape and its output](#every-shape-and-its-output) |
|
|
37
|
+
| Confirm you're checking errors the safe way | [How it works](#how-it-works) - `isApplicationError()`, never `instanceof` |
|
|
24
38
|
|
|
25
39
|
## How it works
|
|
26
40
|
|
|
27
|
-
- **Three equivalent entry points.** `getError(opts)`, `new ApplicationError(opts)`, and the static `ApplicationError.getError(opts)` all
|
|
28
|
-
- **Two input shapes.** Free-form
|
|
29
|
-
|
|
41
|
+
- **Three equivalent entry points.** `getError(opts)`, `new ApplicationError(opts)`, and the static `ApplicationError.getError(opts)` all build the same object from the same input. Use the class form only when a direct reference reads better.
|
|
42
|
+
- **Two input shapes.** Free-form covers one-off failures - most throw sites. Catalogued raises a failure declared once at module scope, so its code, status, and default text can't drift across the call sites that raise it.
|
|
43
|
+
|
|
44
|
+
| Shape | Input |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Free-form | `{ message, statusCode?, messageCode? }` |
|
|
47
|
+
| Catalogued | `{ error: TErrorDefinition }` |
|
|
48
|
+
|
|
49
|
+
- **One message shape everywhere.** `message` is either the historical string (with sibling `messageCode?`/`messageArgs?`), or an object mirroring `normalized`: `{ text, code?, args? }`. Both resolve to the same `normalized`:
|
|
30
50
|
|
|
31
51
|
```typescript
|
|
32
52
|
getError({ message: 'Only %{n} left', messageCode: 'stock.low', messageArgs: { n: 2 } }); // flat
|
|
@@ -34,13 +54,20 @@ The framework's `AppErrorMiddleware` catches `ApplicationError` instances and fo
|
|
|
34
54
|
getError({ error: StockErrors.LOW, messageArgs: { n: 2 } }); // catalogued
|
|
35
55
|
```
|
|
36
56
|
|
|
37
|
-
On the catalogued form, `message` is a **partial** override
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
- **`
|
|
41
|
-
-
|
|
42
|
-
- **`
|
|
43
|
-
- **`
|
|
57
|
+
On the catalogued form, `message` is a **partial** override. `{ message: { args } }` amends just the args and keeps the definition's `text`/`code`.
|
|
58
|
+
|
|
59
|
+
- **Precedence resolves most-specific-first.** See the [full precedence table](#every-shape-and-its-output) below - a definition's own `message.code` beats a flat `messageCode`.
|
|
60
|
+
- **`messageCode` always resolves to something.** `MessageCode.resolve()` lower-cases it and falls back to `MessageCode.DEFAULT` (`'core.system_error'`) when none is given or empty. `error.normalized.code` is never `undefined`.
|
|
61
|
+
- **`normalized` is the single source - there's no flat duplicate.** It's `{ text, code, args }`. `text` defaults to `message`; `code` and `args` resolve per the precedence table.
|
|
62
|
+
- **`args` is always populated, so no consumer needs a null check.** One lookup renders any error: `translate(error.normalized.code, error.normalized.args)`.
|
|
63
|
+
- **`messageCode` and `messageArgs` are INPUTS only.** There's no `error.messageCode` field, and `extra` never mirrors `messageArgs`. Pass `transform` to build `normalized` yourself in place of the default.
|
|
64
|
+
- **Any key the input doesn't declare rides into `extra`.** Attach whatever context your clients need: `getError({ message, transaction: {...} })` lands at `error.extra.transaction`. Passing `extra` explicitly works too - the two merge, and the explicit one wins on a clash.
|
|
65
|
+
- **`extra` carries caller context only.** It's `undefined` when there is nothing to carry.
|
|
66
|
+
- **`cause` reaches the native `Error.cause`, not `extra`.** Wrap a lower-level failure with `getError({ message, cause: originalError })`, and every tool that reads `.cause` sees it.
|
|
67
|
+
- **`error` is refused on the free-form branch (`error?: never`).** This makes `getError({ message, error: caughtError })` fail to compile, so the mistake is caught before runtime - use `cause` instead.
|
|
68
|
+
|
|
69
|
+
> [!IMPORTANT]
|
|
70
|
+
> **Use `isApplicationError()`, never `instanceof ApplicationError`.** There is one `ApplicationError` class, defined in `@venizia/ignis-inversion`. `helpers` re-exports it, so a browser app can raise and read the same errors the server does. But `instanceof` still fails across a package boundary: inversion ships dual CJS+ESM builds, so one source class has two runtime constructors. `isApplicationError()` tests shape, not identity, so it works everywhere.
|
|
44
71
|
|
|
45
72
|
**Options shared by both forms**
|
|
46
73
|
|
|
@@ -53,10 +80,11 @@ The framework's `AppErrorMiddleware` catches `ApplicationError` instances and fo
|
|
|
53
80
|
| `cause` | `unknown` | The wrapped failure - reaches `Error.cause`. Use this to wrap a caught error; `error` is refused on the free-form branch for exactly this case |
|
|
54
81
|
| `extra` | `Record<string, unknown>` | Explicit context, merged with any swept keys - and it wins on a clash |
|
|
55
82
|
| `transform` | `TErrorNormalizeTransformFn` | Builds `normalized` in place of the default. Receives `{ message: TErrorNormalized, statusCode, extra? }` - `message` is the default normalized being replaced, so `s => ({ ...s.message, text: 'x' })` amends one field |
|
|
83
|
+
| `logLevel` | `error \| emerg \| warn \| info \| debug` | The level the error handler logs this at. Defaults to `error`; lower it for an expected failure (`getError({ message, statusCode: 404, logLevel: 'warn' })`), or raise it to `emerg`. Steers the server log only - never the response |
|
|
56
84
|
| _anything else_ | `unknown` | Rides into `extra` under its own name. `getError({ message, transaction })` lands at `error.extra.transaction` |
|
|
57
85
|
|
|
58
86
|
> [!TIP]
|
|
59
|
-
> Spreading a definition now resolves identically to passing it as `error`: `getError({ ...CategoryErrors.CREATE_DUPLICATE_NAME })` and `getError({ error: CategoryErrors.CREATE_DUPLICATE_NAME })` build the same `ApplicationError`. A definition's `message` is `{ text, code, args? }` - the same shape the free-form input accepts - so the spread degrades to nothing. Prefer `{ error: DEF }` anyway
|
|
87
|
+
> Spreading a definition now resolves identically to passing it as `error`: `getError({ ...CategoryErrors.CREATE_DUPLICATE_NAME })` and `getError({ error: CategoryErrors.CREATE_DUPLICATE_NAME })` build the same `ApplicationError`. A definition's `message` is `{ text, code, args? }` - the same shape the free-form input accepts - so the spread degrades to nothing. Prefer `{ error: DEF }` anyway. It reads as "raise this catalogued error", not "raise these loose fields."
|
|
60
88
|
|
|
61
89
|
## Every shape and its output
|
|
62
90
|
|
|
@@ -190,14 +218,14 @@ throw getError({ error: CategoryErrors.CREATE_DUPLICATE_NAME, messageArgs: { nam
|
|
|
190
218
|
throw getError({ error: CategoryErrors.CREATE_DUPLICATE_NAME, message: { args: { name: 'Vé' } } });
|
|
191
219
|
```
|
|
192
220
|
|
|
193
|
-
`ErrorScopes` groups a failure by intent - `AUTH`, `VALIDATION`, `BUSINESS`, `SYSTEM`, `INTEGRATION
|
|
221
|
+
`ErrorScopes` groups a failure by intent - `AUTH`, `VALIDATION`, `BUSINESS`, `SYSTEM`, `INTEGRATION`. `statusCode` can't do this job: a `409` is a business conflict in one place and a validation clash in another.
|
|
194
222
|
|
|
195
223
|
> [!WARNING]
|
|
196
|
-
> `category` is catalog **metadata only** - it does not reach the error response. `getError` reads `message.text`, `message.code` and `statusCode` off a definition and ignores the rest. Use it to group and filter catalogs (ops dashboards, translator exports)
|
|
224
|
+
> `category` is catalog **metadata only** - it does not reach the error response. `getError` reads `message.text`, `message.code` and `statusCode` off a definition and ignores the rest. Use it to group and filter catalogs (ops dashboards, translator exports). Don't expect a client to receive it.
|
|
197
225
|
|
|
198
226
|
### Register catalog keys for `messageCode` autocomplete
|
|
199
227
|
|
|
200
|
-
Augment the module the file already imports from. `IErrorKeyRegistry` is declared in `@venizia/ignis-inversion` and re-exported by `helpers
|
|
228
|
+
Augment the module the file already imports from. `IErrorKeyRegistry` is declared in `@venizia/ignis-inversion` and re-exported by `helpers`. Merging follows the re-export, so either name populates the same registry.
|
|
201
229
|
|
|
202
230
|
```typescript
|
|
203
231
|
import type { TRegisterErrors } from '@venizia/ignis-helpers';
|
|
@@ -225,7 +253,7 @@ const NOT_FOUND = MessageCode.build({ parts: ['core', 'user', 'not_found'] });
|
|
|
225
253
|
|
|
226
254
|
### Read the error response shape
|
|
227
255
|
|
|
228
|
-
`AppErrorMiddleware` (from `@venizia/ignis`) routes every thrown value into one of five shapes. All five carry `message`, `statusCode`, `normalized` and `details
|
|
256
|
+
`AppErrorMiddleware` (from `@venizia/ignis`) routes every thrown value into one of five shapes. All five carry `message`, `statusCode`, `normalized` and `details`. Only an intentional error can carry `extra`. The code lives at `normalized.code` - there is no flat `messageCode` on the response.
|
|
229
257
|
|
|
230
258
|
| What was thrown | Status | `normalized.code` | `message` | `extra` |
|
|
231
259
|
| ----------------------------------------- | ------- | ------------------------------------------------------ | --------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
@@ -235,11 +263,11 @@ const NOT_FOUND = MessageCode.build({ parts: ['core', 'user', 'not_found'] });
|
|
|
235
263
|
| `getError(...)` - intentional | its own | its own | its own | when the throw site attached `extra` or unknown keys |
|
|
236
264
|
| Anything else | 500 | `core.system_error` | `Internal Server Error` in production; the raw message in development | never |
|
|
237
265
|
|
|
238
|
-
Only the intentional branch reports what the throw site wrote. The other four **replace** the message
|
|
266
|
+
Only the intentional branch reports what the throw site wrote. The other four **replace** the message - a driver error can carry SQL, schema, and constraint names. `normalized` is built from that replacement, so it can never leak what `message` just scrubbed.
|
|
239
267
|
|
|
240
268
|
`rootKey` (e.g. `new AppErrorMiddleware({ logger, rootKey: 'error' }).value()`) nests any of these under that key.
|
|
241
269
|
|
|
242
|
-
The handler is fail-closed on environment
|
|
270
|
+
The handler is fail-closed on environment. It exposes `stack` and `cause` in `details` only for `Environment.DEVELOPMENT_ENVS`: `local`, `debug`, `development`, `dev`, `sit`. Everything else - including `alpha`, `staging`, a typo, or an unset `NODE_ENV` - gets the sanitized shape:
|
|
243
271
|
|
|
244
272
|
```json
|
|
245
273
|
{
|
|
@@ -263,10 +291,58 @@ The handler is fail-closed on environment: it exposes `stack` and `cause` in `de
|
|
|
263
291
|
> [!IMPORTANT]
|
|
264
292
|
> **`messageCode` and `extra.messageArgs` are GONE from the response.** They duplicated `normalized.code` and `normalized.args`. Read `normalized` - it is the only source. A client still reading either must migrate: `translate(error.messageCode, error.extra?.messageArgs)` becomes `translate(error.normalized.code, error.normalized.args)`.
|
|
265
293
|
>
|
|
266
|
-
> Note the two `details
|
|
294
|
+
> Note the two `details`. The inner one is context the throw site attached - it went through `extra`. The outer one is the middleware's own request info. They're unrelated despite sharing a name.
|
|
267
295
|
|
|
268
296
|
> [!NOTE]
|
|
269
|
-
> `message` and `normalized.text` are the same string unless a `transform` deliberately makes them differ
|
|
297
|
+
> `message` and `normalized.text` are the same string, unless a `transform` deliberately makes them differ. `message` stays the raw text the throw site wrote. `normalized.text` is what a client shows. Most errors never set `transform`, so most of the time the two match.
|
|
298
|
+
|
|
299
|
+
### Consume the error response from a browser client
|
|
300
|
+
|
|
301
|
+
The error layer lives in `@venizia/ignis-inversion`, not in helpers, precisely so a browser app can
|
|
302
|
+
share it. It depends only on `lodash`, ships dual CJS+ESM, and pulls in no server module. A frontend
|
|
303
|
+
throws its own failures with the same `getError` the server uses, and reads the server's with the
|
|
304
|
+
same field names.
|
|
305
|
+
|
|
306
|
+
**Reading is all most clients need.** `normalized` is on every error response:
|
|
307
|
+
|
|
308
|
+
- `getError` always builds one.
|
|
309
|
+
- A foreign error gets one synthesized.
|
|
310
|
+
- A `ZodError` builds its own.
|
|
311
|
+
|
|
312
|
+
So there's no null-check and no parsing step: one lookup handles every case.
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
const { error } = await response.json(); // `error` is the rootKey, if one is configured
|
|
316
|
+
|
|
317
|
+
translate(error.normalized.code, error.normalized.args);
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Rehydrating** is for the client that wants one `catch` block for both a server failure and a
|
|
321
|
+
locally thrown one. `fromError` inverts the response the middleware emitted:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
import { fromError, isApplicationError } from '@venizia/ignis-inversion';
|
|
325
|
+
|
|
326
|
+
const { error } = await response.json();
|
|
327
|
+
throw fromError({ error }); // now an ApplicationError - isApplicationError() is true
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
| Wire field | Where it lands |
|
|
331
|
+
| ------------ | --------------------------------------------------------------------------------- |
|
|
332
|
+
| `normalized` | `normalized`, verbatim - `text`, `code` and `args` round-trip unchanged |
|
|
333
|
+
| `message` | `normalized.text`, but only when `normalized` is missing entirely |
|
|
334
|
+
| `statusCode` | `statusCode`; `400` when the payload carries none |
|
|
335
|
+
| `extra` | `extra`, verbatim |
|
|
336
|
+
| `requestId` | `extra.requestId` - it is the identifier a support ticket quotes, so it survives |
|
|
337
|
+
| `details` | dropped - `url` and `path` the client already knows, and `stack` is the server's |
|
|
338
|
+
|
|
339
|
+
Every field of `TResponsedError` is optional by design: a client parses what a gateway, a proxy or an
|
|
340
|
+
older server actually sent. A body that is not an IGNIS error at all still yields an
|
|
341
|
+
`ApplicationError`, degraded to `MessageCode.DEFAULT` and status `400`. No call site branches on
|
|
342
|
+
a parse failure.
|
|
343
|
+
|
|
344
|
+
> [!TIP]
|
|
345
|
+
> Same rule here: prefer `isApplicationError()` over `instanceof ApplicationError`. See the [callout above](#how-it-works) for why - a browser app bundles its own copy of the class, so `instanceof` fails across the boundary.
|
|
270
346
|
|
|
271
347
|
**Common status codes**
|
|
272
348
|
|
|
@@ -290,8 +366,8 @@ The handler is fail-closed on environment: it exposes `stack` and `cause` in `de
|
|
|
290
366
|
**Files:**
|
|
291
367
|
|
|
292
368
|
- [`packages/helpers/src/modules/error/index.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/error/index.ts) - module barrel
|
|
293
|
-
- [`packages/helpers/src/modules/error/
|
|
294
|
-
- [`packages/inversion/src/modules/error/app-error.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/inversion/src/modules/error/app-error.ts) - `ApplicationError`, `getError`, `isApplicationError`
|
|
295
|
-
- [`packages/inversion/src/modules/error/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/inversion/src/modules/error/types.ts) - `TError`, `TErrorDefinition`, `TErrorNormalized`, `IErrorKeyRegistry`, `TRegisterErrors`
|
|
369
|
+
- [`packages/helpers/src/modules/error/schemas.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/error/schemas.ts) - `ErrorSchema`, `TErrorResponse` (the RESPONSE schema, for OpenAPI)
|
|
370
|
+
- [`packages/inversion/src/modules/error/app-error.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/inversion/src/modules/error/app-error.ts) - `ApplicationError`, `getError`, `fromError`, `isApplicationError`
|
|
371
|
+
- [`packages/inversion/src/modules/error/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/inversion/src/modules/error/types.ts) - `TError`, `TErrorDefinition`, `TErrorNormalized`, `TResponsedError`, `IErrorKeyRegistry`, `TRegisterErrors`
|
|
296
372
|
- [`packages/inversion/src/modules/error/definition.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/inversion/src/modules/error/definition.ts) - `ErrorScopes`, `TErrorScope`
|
|
297
373
|
- [`packages/inversion/src/modules/error/message-code.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/inversion/src/modules/error/message-code.ts) - `MessageCode`
|
|
@@ -1,49 +1,63 @@
|
|
|
1
1
|
# Helpers
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
| Helper |
|
|
8
|
-
|
|
9
|
-
| [
|
|
10
|
-
| [Cron](./cron/) |
|
|
11
|
-
| [Crypto](./crypto/) |
|
|
12
|
-
| [Environment](./env/) |
|
|
13
|
-
| [Error](./error/) | Error
|
|
14
|
-
| [
|
|
15
|
-
| [
|
|
16
|
-
| [
|
|
17
|
-
| [
|
|
18
|
-
| [
|
|
19
|
-
| [
|
|
20
|
-
| [
|
|
21
|
-
| [
|
|
22
|
-
| [
|
|
23
|
-
| [
|
|
24
|
-
| [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
| `@venizia/ignis-helpers/
|
|
34
|
-
| `@venizia/ignis-helpers/
|
|
35
|
-
| `@venizia/ignis-helpers/
|
|
36
|
-
| `@venizia/ignis-helpers/
|
|
37
|
-
| `@venizia/ignis-helpers/
|
|
38
|
-
| `@venizia/ignis-helpers/
|
|
39
|
-
| `@venizia/ignis-helpers/
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
3
|
+
Standalone utility classes for infrastructure concerns - extend `BaseHelper` for scoped logging, and inject them wherever you need them.
|
|
4
|
+
|
|
5
|
+
Every peer dependency below is optional. You install one only when you use the helper that needs it.
|
|
6
|
+
|
|
7
|
+
| Helper | What it does | When you reach for it | Peer dependency |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| [Types](./types/) | Shared utility types | You need IGNIS's shared TypeScript utility types | None |
|
|
10
|
+
| [Cron](./cron/) | Scheduled tasks | You run code on a cron schedule | `cron` |
|
|
11
|
+
| [Crypto](./crypto/) | Encryption and signing | You hash, encrypt, or sign data | None |
|
|
12
|
+
| [Environment](./env/) | Env var management | You need typed, validated env var access | None |
|
|
13
|
+
| [Error](./error/) | Error utilities | You throw or handle an error | None |
|
|
14
|
+
| [Secrets & Vault](./secrets/) | Secrets and credentials | You read config or credentials from Vault or a vaulted `.env` | `node-vault` or `@dotenvx/dotenvx` |
|
|
15
|
+
| [Inversion](./inversion/) | DI container | You build custom bindings or providers | None |
|
|
16
|
+
| [Logger](./logger/) | Logging | You need scoped, leveled logging | `winston` or `pino` |
|
|
17
|
+
| [Network](./network/) | HTTP/TCP/UDP clients | You call another service over HTTP, TCP, or UDP | `axios`, for the Axios client only |
|
|
18
|
+
| [Kafka](./kafka/) | Kafka messaging | You publish or consume Kafka topics | `@platformatic/kafka` |
|
|
19
|
+
| [Queue](./queue/) | Job queues | You need background or delayed work | `bullmq` or `mqtt` |
|
|
20
|
+
| [Redis](./redis/) | Redis client | You need a Redis connection - cache, pub/sub, locks | None - `ioredis` ships with the package |
|
|
21
|
+
| [Socket.IO](./socket-io/) | Socket.IO server | You build a custom real-time feature | `socket.io` |
|
|
22
|
+
| [WebSocket](./websocket/) | WebSocket server | You build a custom real-time feature | None |
|
|
23
|
+
| [Storage](./storage/) | File storage | You read/write files to MinIO or disk directly | `minio`, for the MinIO backend only |
|
|
24
|
+
| [UID](./uid/) | Snowflake IDs | You need unique, sortable IDs | None |
|
|
25
|
+
| [Worker Thread](./worker-thread/) | Worker pools | You move CPU-heavy work off the main thread | None |
|
|
26
|
+
|
|
27
|
+
## Subpath imports
|
|
28
|
+
|
|
29
|
+
A helper with an optional peer dependency ships from its own subpath, so a bundler never pulls in a peer you don't use.
|
|
30
|
+
|
|
31
|
+
| Import from | Requires |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `@venizia/ignis-helpers/cron` | `cron` |
|
|
34
|
+
| `@venizia/ignis-helpers/axios` | `axios` |
|
|
35
|
+
| `@venizia/ignis-helpers/kafka` | `@platformatic/kafka` |
|
|
36
|
+
| `@venizia/ignis-helpers/bullmq` | `bullmq` |
|
|
37
|
+
| `@venizia/ignis-helpers/mqtt` | `mqtt` |
|
|
38
|
+
| `@venizia/ignis-helpers/socket-io` | `socket.io`, `socket.io-client` |
|
|
39
|
+
| `@venizia/ignis-helpers/minio` | `minio` |
|
|
40
|
+
| `@venizia/ignis-helpers/bun-s3` | none - Bun native |
|
|
41
|
+
| `@venizia/ignis-helpers/hashicorp-vault` | `node-vault` |
|
|
42
|
+
| `@venizia/ignis-helpers/dotenv-vault` | `@dotenvx/dotenvx` |
|
|
43
|
+
| `@venizia/ignis-helpers/winston` | `winston` |
|
|
44
|
+
| `@venizia/ignis-helpers/pino` | `pino` |
|
|
45
|
+
|
|
46
|
+
`@venizia/ignis-helpers/common` is a subpath of a different kind. It isolates nothing - it exposes the
|
|
47
|
+
part of this package that is already browser-safe: `HTTP`, `TConstValue`, and the constant and
|
|
48
|
+
redaction tables. Import it when your code has to bundle for a browser.
|
|
49
|
+
|
|
50
|
+
The root barrel cannot: it re-exports every module, so reaching one constant through it pulls in 14
|
|
51
|
+
node builtins and 27 packages, `winston` and `ioredis` among them. A guard test bundles both entry
|
|
52
|
+
points for a browser target and fails if that ever changes.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { HTTP } from '@venizia/ignis-helpers/common';
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## See also
|
|
59
|
+
|
|
60
|
+
- [Services](/guides/core-concepts/services) - using helpers in the service layer
|
|
61
|
+
- [Controllers](/guides/core-concepts/rest-controllers) - using helpers in controllers
|
|
62
|
+
- [Utilities](/references/utilities/) - pure utility functions
|
|
63
|
+
- [Components](/extensions/components/) - framework components
|
|
@@ -29,14 +29,29 @@ container.bind<UserService>({ key: 'services.UserService' })
|
|
|
29
29
|
const userService = container.get<UserService>({ key: 'services.UserService' });
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
A class needs no decorator to be bindable
|
|
32
|
+
A class needs no decorator to be bindable. The binding is always created explicitly with `container.bind()`, and its scope is set via `setScope()`.
|
|
33
|
+
|
|
34
|
+
The framework layer (`@venizia/ignis`) creates these bindings for you - for controllers, services, and repositories - via `app.controller()` / `app.service()` / `@repository`.
|
|
33
35
|
|
|
34
36
|
## How it works
|
|
35
37
|
|
|
36
|
-
- **
|
|
37
|
-
|
|
38
|
+
- **A binding resolves one of three ways.** All `Binding` setters return `this`, so calls chain.
|
|
39
|
+
|
|
40
|
+
| Resolver | Behavior |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `toClass` | Container instantiates the class with full DI |
|
|
43
|
+
| `toValue` | Returns the value as-is |
|
|
44
|
+
| `toProvider` | Calls a factory function, or an `IProvider` class |
|
|
45
|
+
|
|
46
|
+
- **Instantiation is two-phase.** `container.resolve(cls)` and `container.instantiate(cls)` are the same method - `resolve` is an alias.
|
|
47
|
+
|
|
48
|
+
| Phase | What happens |
|
|
49
|
+
|---|---|
|
|
50
|
+
| 1. Constructor injection | Reads `@inject` metadata by parameter index, passes resolved values as constructor args |
|
|
51
|
+
| 2. Property injection | Assigns each `@inject`-decorated property on the built instance |
|
|
52
|
+
|
|
38
53
|
- **Every constructor parameter must carry `@inject`.** The metadata array is index-keyed - an undecorated parameter leaves a hole the container has no way to fill. `instantiate()` refuses the class by name and parameter index rather than passing `undefined`.
|
|
39
|
-
- **Namespaces auto-tag bindings.** A key like `services.UserService` tags the binding `services` automatically
|
|
54
|
+
- **Namespaces auto-tag bindings.** A key like `services.UserService` tags the binding `services` automatically. `setTags()` adds more. `findByTag()` queries by tag, with an `exclude` list.
|
|
40
55
|
- **Keys** can be a `string`, a `symbol`, or `{ namespace, key }` (built into a dotted string via `BindingKeys.build`).
|
|
41
56
|
|
|
42
57
|
**Scopes**
|
|
@@ -47,9 +62,11 @@ A class needs no decorator to be bindable - the binding is always created explic
|
|
|
47
62
|
| Singleton | `BindingScopes.SINGLETON` | Cached on the `Binding` after first resolution |
|
|
48
63
|
|
|
49
64
|
> [!IMPORTANT]
|
|
50
|
-
> Singleton caching lives on the `Binding` object, not the container. Rebinding a key creates a fresh `Binding` with its own cache
|
|
65
|
+
> Singleton caching lives on the `Binding` object, not the container. Rebinding a key creates a fresh `Binding` with its own cache. If you hold onto an old `Binding` reference, its cache stays independent of `container.clear()`/`reset()` calls made against the new `Binding` registered under the same key.
|
|
66
|
+
|
|
67
|
+
Property-injected classes only get their `@inject` properties populated when built through the container - `container.resolve()` or `instantiate()`. A plain `new MyClass()` leaves them `undefined`.
|
|
51
68
|
|
|
52
|
-
|
|
69
|
+
The [Full reference](/extensions/helpers/inversion/reference) covers `MetadataRegistry`, `gets()`, key formats, `IProvider`, and every error message in detail.
|
|
53
70
|
|
|
54
71
|
## Common tasks
|
|
55
72
|
|
|
@@ -80,7 +80,7 @@ import type {
|
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
> [!NOTE]
|
|
83
|
-
> The framework package `@venizia/ignis` re-exports DI
|
|
83
|
+
> The framework package `@venizia/ignis` re-exports these DI symbols from `@venizia/ignis-inversion`, types included: `Binding`, `BindingKeys`, `BindingScopes`, `BindingValueTypes`, `IProvider`, `isClass`, `isClassProvider`, `TBindingScope`, `TBindingValueType`, `IBindingTag`. It also adds higher-level helpers of its own (`app.controller()`, `app.service()`, etc.).
|
|
84
84
|
|
|
85
85
|
## Class Hierarchy
|
|
86
86
|
|
|
@@ -92,7 +92,7 @@ AbstractContainer extends BaseHelper implements IContainer # contract only - e
|
|
|
92
92
|
└── Container # instantiate() = two-phase decorator injection
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
`AbstractContainer` exists so a container implementation that shares nothing with the shipped storage can start there
|
|
95
|
+
`AbstractContainer` exists so a container implementation that shares nothing with the shipped storage can start there. One that only wants to vary resolution extends `BaseContainer` instead. `binding/` and `container/` only talk to each other through `IContainer`/`IBinding` - there is no import cycle between the two folders.
|
|
96
96
|
|
|
97
97
|
## Creating a Container
|
|
98
98
|
|
|
@@ -124,7 +124,7 @@ const container = new Container({ scope: 'MyApp' });
|
|
|
124
124
|
| `reset` | `reset(): void` | Remove all bindings entirely |
|
|
125
125
|
| `getMetadataRegistry` | `getMetadataRegistry(): MetadataRegistry` | Return the shared `metadataRegistry` singleton |
|
|
126
126
|
|
|
127
|
-
All keys passed to `bind`, `isBound`, and `unbind` are normalized with `String(key)` before being used as the `Map` key
|
|
127
|
+
All keys passed to `bind`, `isBound`, and `unbind` are normalized with `String(key)` before being used as the `Map` key. A `Symbol` key resolves to its `.toString()` form (`'Symbol(...)'`), so a `symbol` and the equivalent string are distinct entries.
|
|
128
128
|
|
|
129
129
|
### `instantiate()` - two-phase algorithm
|
|
130
130
|
|
|
@@ -139,10 +139,11 @@ override instantiate<T>(cls: TClass<T>): T
|
|
|
139
139
|
|
|
140
140
|
The array is already index-keyed (`setInjectMetadata` writes to `injects[index]`) - there is no sort step. Once all arguments are resolved, `new cls(...args)` builds the instance.
|
|
141
141
|
|
|
142
|
-
**Phase 2 - property injection.** Reads `registry.getPropertiesMetadata({ target: instance })`. If there is none, returns the instance as-is.
|
|
142
|
+
**Phase 2 - property injection.** Reads `registry.getPropertiesMetadata({ target: instance })`. If there is none, returns the instance as-is.
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
Otherwise, for each `[propertyKey, metadata]` entry, resolves `this.get({ key: metadata.bindingKey, isOptional: metadata.isOptional ?? false })` and assigns it to `instance[propertyKey]`.
|
|
145
|
+
|
|
146
|
+
`@inject({ key, isOptional: true })` on a **property** behaves exactly like on a constructor parameter: an unbound key resolves to `undefined` instead of throwing. A required property (`isOptional` omitted or `false`) still throws when its key is unbound.
|
|
146
147
|
|
|
147
148
|
### Key formats
|
|
148
149
|
|
|
@@ -167,7 +168,7 @@ const [svcA, svcB] = container.gets<[ServiceA, ServiceB]>({
|
|
|
167
168
|
});
|
|
168
169
|
```
|
|
169
170
|
|
|
170
|
-
Internally maps each entry through `this.get({ ...opt, isOptional: true })
|
|
171
|
+
Internally maps each entry through `this.get({ ...opt, isOptional: true })`. Regardless of what `isOptional` was set on the entry, `gets()` always resolves with `isOptional: true` - anything unbound returns `undefined` instead of throwing.
|
|
171
172
|
|
|
172
173
|
## Binding
|
|
173
174
|
|
|
@@ -194,17 +195,20 @@ Internally maps each entry through `this.get({ ...opt, isOptional: true })` - re
|
|
|
194
195
|
constructor(opts: { key: string })
|
|
195
196
|
```
|
|
196
197
|
|
|
197
|
-
Splits `key` on
|
|
198
|
+
Splits `key` on `.`. If there is more than one segment, the first segment is auto-added as a tag via `setTags()`. `'services.UserService'` auto-tags `'services'`. A key with no `.` gets no automatic tag.
|
|
198
199
|
|
|
199
200
|
### `getValue()` resolution by type
|
|
200
201
|
|
|
201
202
|
| Resolver type | Behavior | Throws when |
|
|
202
203
|
|----------------|----------|--------------|
|
|
203
204
|
| `VALUE` | Returns the stored value directly | Never |
|
|
204
|
-
| `PROVIDER`
|
|
205
|
+
| `PROVIDER` (plain function) | Calls `provider(container)` | No `container` argument was passed - `[getValue] Invalid context/container to get provider value` |
|
|
206
|
+
| `PROVIDER` (class, matched via `isClassProvider`) | `container.instantiate()`s the class, then calls `.value(container)` on the instance | Same as above |
|
|
205
207
|
| `CLASS` | `container.instantiate(this.resolver.value)` | No `container` argument was passed - `[getValue] Invalid context/container to instantiate class` |
|
|
206
208
|
|
|
207
|
-
|
|
209
|
+
`isClassProvider` matches a class whose prototype has a `value()` method - see [Class-based provider](#class-based-provider) below.
|
|
210
|
+
|
|
211
|
+
If `bindScope` is `SINGLETON`, the resolved instance is cached on `this.cached`. Every subsequent call returns it directly without re-invoking the resolver - caching is per-`Binding` instance, not per-container.
|
|
208
212
|
|
|
209
213
|
### Class-based provider
|
|
210
214
|
|
|
@@ -279,7 +283,6 @@ interface IInjectMetadata {
|
|
|
279
283
|
interface IPropertyMetadata {
|
|
280
284
|
bindingKey: TBindingKey;
|
|
281
285
|
isOptional?: boolean;
|
|
282
|
-
[key: string]: any;
|
|
283
286
|
}
|
|
284
287
|
```
|
|
285
288
|
|
|
@@ -330,7 +333,7 @@ BindingKeys.build({ namespace: 'services', key: '' });
|
|
|
330
333
|
// throws: [BindingKeys][build] Invalid key to build | key:
|
|
331
334
|
```
|
|
332
335
|
|
|
333
|
-
`key` is required and must be non-empty
|
|
336
|
+
`key` is required and must be non-empty. `namespace` is optional - it's silently omitted from the joined string when empty.
|
|
334
337
|
|
|
335
338
|
```typescript
|
|
336
339
|
container.bind({ key: 'workers.EmailWorker' }).toClass(EmailWorker).setTags('background', 'email');
|
|
@@ -356,11 +359,13 @@ class ApplicationError extends Error {
|
|
|
356
359
|
getError(opts: TError): ApplicationError; // factory function
|
|
357
360
|
```
|
|
358
361
|
|
|
359
|
-
`opts.message` accepts two shapes: the historical string (paired with sibling `messageCode?`/`messageArgs?`), or an object mirroring `normalized` - `{ text, code?, args? }`. Both resolve to the same `normalized
|
|
362
|
+
`opts.message` accepts two shapes: the historical string (paired with sibling `messageCode?`/`messageArgs?`), or an object mirroring `normalized` - `{ text, code?, args? }`. Both resolve to the same `normalized`.
|
|
360
363
|
|
|
361
|
-
|
|
364
|
+
`messageCode`/`messageArgs` are lowest precedence - `message.code`/`message.args`, or a catalogued definition's own, win when both are present. There is no flat `error.messageCode`. `extra` never mirrors `messageArgs`. `normalized.args` is always populated (`{}` when empty).
|
|
362
365
|
|
|
363
|
-
`
|
|
366
|
+
The catalogued form (`{ error: TErrorDefinition }`) takes `message` as a **partial** override. `{ message: { args } }` amends just the args and keeps the definition's `text`/`code`. `error` is refused on the free-form branch (`error?: never`) - wrap a caught failure with `cause` instead.
|
|
367
|
+
|
|
368
|
+
`ApplicationError`'s constructor defaults `statusCode` to `400` when omitted, and moves any property it does not model into `this.extra`. The error RESPONSE schema (`ErrorSchema`, for OpenAPI) lives in `@venizia/ignis-helpers`, not here. It needs `@hono/zod-openapi`, which inversion must not depend on - inversion ships to browsers.
|
|
364
369
|
|
|
365
370
|
```typescript
|
|
366
371
|
throw getError({ message: 'Something failed', statusCode: 500, messageCode: 'ERR_INTERNAL' });
|
|
@@ -407,7 +412,7 @@ interface IBindingTag {
|
|
|
407
412
|
function isClass<T>(target: any): target is TClass<T>;
|
|
408
413
|
```
|
|
409
414
|
|
|
410
|
-
`isClass` tests `typeof target === 'function' && target.prototype !== undefined` plus a regex match on the function's stringified source (`/^class[\s{]/`)
|
|
415
|
+
`isClass` tests `typeof target === 'function' && target.prototype !== undefined` plus a regex match on the function's stringified source (`/^class[\s{]/`). It relies on the class being emitted as an ES2024 `class`, not transpiled down to an ES5 constructor function.
|
|
411
416
|
|
|
412
417
|
```typescript
|
|
413
418
|
interface IProvider<T> {
|
|
@@ -438,7 +443,7 @@ function isClassProvider<T>(target: any): target is TClass<IProvider<T>>;
|
|
|
438
443
|
**Fix:**
|
|
439
444
|
1. Verify the binding exists: `container.isBound({ key: 'services.UserService' })`.
|
|
440
445
|
2. Check for typos between `@inject({ key: '...' })` and the key used in `container.bind({ key: '...' })`.
|
|
441
|
-
3. If the dependency is genuinely optional,
|
|
446
|
+
3. If the dependency is genuinely optional, add `isOptional: true` to the `@inject` call - constructor parameter or property, both work - or use `container.get({ key: '...', isOptional: true })`.
|
|
442
447
|
|
|
443
448
|
### "[getValue] Invalid context/container to instantiate class"
|
|
444
449
|
|
|
@@ -472,9 +477,12 @@ function isClassProvider<T>(target: any): target is TClass<IProvider<T>>;
|
|
|
472
477
|
|
|
473
478
|
### "[ClassName] Constructor parameter N has no @inject"
|
|
474
479
|
|
|
475
|
-
**Cause:** A container-instantiated class has a constructor mixing decorated and undecorated parameters. `@inject` stores metadata at the parameter's index, so an undecorated parameter leaves a hole in that array
|
|
480
|
+
**Cause:** A container-instantiated class has a constructor mixing decorated and undecorated parameters. `@inject` stores metadata at the parameter's index, so an undecorated parameter leaves a hole in that array. There is no channel through which the container could supply it anyway.
|
|
481
|
+
|
|
482
|
+
**Fix:** Decorate every constructor parameter with `@inject`. There is no partial-injection escape hatch. If a value doesn't come from the container (e.g. a plain `scope: string`), choose one:
|
|
476
483
|
|
|
477
|
-
|
|
484
|
+
- Pass it through a factory/provider instead of a bare constructor parameter.
|
|
485
|
+
- Have the subclass forward it via its own `@inject`-decorated parameter.
|
|
478
486
|
|
|
479
487
|
### "@inject decorator can only be used on class properties or constructor parameters"
|
|
480
488
|
|
|
@@ -482,11 +490,11 @@ function isClassProvider<T>(target: any): target is TClass<IProvider<T>>;
|
|
|
482
490
|
|
|
483
491
|
**Fix:** Only use `@inject` on constructor parameters or class properties.
|
|
484
492
|
|
|
485
|
-
### Property injection
|
|
493
|
+
### Property injection never runs
|
|
486
494
|
|
|
487
|
-
**Cause:**
|
|
495
|
+
**Cause:** The class was instantiated with `new MyClass()` directly instead of through the container. Only `container.resolve()`/`instantiate()` reads `@inject` metadata and populates properties. A plain `new` leaves them at whatever their field initializer set - `undefined` if none.
|
|
488
496
|
|
|
489
|
-
**Fix:** Always use `container.resolve(MyClass)` or `container.instantiate(MyClass)` to create instances
|
|
497
|
+
**Fix:** Always use `container.resolve(MyClass)` or `container.instantiate(MyClass)` to create instances that use property injection.
|
|
490
498
|
|
|
491
499
|
### "getInjectMetadata returns undefined"
|
|
492
500
|
|
|
@@ -13,7 +13,7 @@ class KafkaAdminHelper extends BaseKafkaHelper<Admin>
|
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
> [!NOTE]
|
|
16
|
-
> `KafkaAdminHelper` has **no generic type parameters**
|
|
16
|
+
> `KafkaAdminHelper` has **no generic type parameters** - the Admin client does not deal with serialized messages.
|
|
17
17
|
|
|
18
18
|
## Helper API
|
|
19
19
|
|
|
@@ -24,6 +24,7 @@ class KafkaAdminHelper extends BaseKafkaHelper<Admin>
|
|
|
24
24
|
| `isHealthy()` | `(): boolean` | `true` when broker connected |
|
|
25
25
|
| `isReady()` | `(): boolean` | Same as `isHealthy()` |
|
|
26
26
|
| `getHealthStatus()` | `(): TKafkaHealthStatus` | `'connected'` \| `'disconnected'` \| `'unknown'` |
|
|
27
|
+
| `getConnectedBrokerCount()` | `(): number` | Number of currently connected brokers |
|
|
27
28
|
| `close(opts?)` | `(opts?: { isForce?: boolean }): Promise<void>` | Close the admin connection (default: graceful) |
|
|
28
29
|
|
|
29
30
|
## IKafkaAdminOptions
|
|
@@ -71,7 +72,7 @@ await helper.close({ isForce: true });
|
|
|
71
72
|
|
|
72
73
|
## Graceful Shutdown
|
|
73
74
|
|
|
74
|
-
`close()` uses the base `closeClient()`
|
|
75
|
+
`close()` uses the base `closeClient()`, wrapped in a graceful timeout. `closeClient()` calls `this.client.close()` directly. If the graceful close exceeds `shutdownTimeout` (default 30s), it automatically force-closes. After `close()`, `healthStatus` becomes `'disconnected'`.
|
|
75
76
|
|
|
76
77
|
```typescript
|
|
77
78
|
// Graceful (recommended)
|