@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.
Files changed (145) hide show
  1. package/content/best-practices/architectural-patterns.md +3 -3
  2. package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
  3. package/content/best-practices/contribution-workflow.md +2 -2
  4. package/content/best-practices/error-handling.md +94 -89
  5. package/content/best-practices/security-guidelines.md +5 -5
  6. package/content/extensions/components/api-reference.md +22 -21
  7. package/content/extensions/components/authentication/api.md +64 -28
  8. package/content/extensions/components/authentication/errors.md +19 -5
  9. package/content/extensions/components/authentication/index.md +19 -18
  10. package/content/extensions/components/authentication/usage.md +55 -30
  11. package/content/extensions/components/authorization/api.md +351 -84
  12. package/content/extensions/components/authorization/errors.md +51 -17
  13. package/content/extensions/components/authorization/getting-started.md +227 -0
  14. package/content/extensions/components/authorization/index.md +24 -16
  15. package/content/extensions/components/authorization/usage.md +45 -21
  16. package/content/extensions/components/health-check.md +15 -9
  17. package/content/extensions/components/index.md +24 -90
  18. package/content/extensions/components/mail/api.md +105 -54
  19. package/content/extensions/components/mail/errors.md +12 -10
  20. package/content/extensions/components/mail/index.md +32 -13
  21. package/content/extensions/components/mail/usage.md +26 -18
  22. package/content/extensions/components/request-tracker.md +18 -14
  23. package/content/extensions/components/socket-io/api.md +377 -882
  24. package/content/extensions/components/socket-io/errors.md +49 -51
  25. package/content/extensions/components/socket-io/index.md +72 -88
  26. package/content/extensions/components/socket-io/usage.md +107 -117
  27. package/content/extensions/components/static-asset/api.md +83 -31
  28. package/content/extensions/components/static-asset/errors.md +18 -7
  29. package/content/extensions/components/static-asset/index.md +18 -11
  30. package/content/extensions/components/static-asset/usage.md +11 -6
  31. package/content/extensions/components/template/index.md +3 -3
  32. package/content/extensions/components/websocket/api.md +58 -27
  33. package/content/extensions/components/websocket/errors.md +3 -3
  34. package/content/extensions/components/websocket/index.md +8 -7
  35. package/content/extensions/components/websocket/usage.md +21 -8
  36. package/content/extensions/helpers/cron/index.md +8 -7
  37. package/content/extensions/helpers/crypto/index.md +16 -8
  38. package/content/extensions/helpers/crypto/reference.md +96 -24
  39. package/content/extensions/helpers/env/index.md +14 -10
  40. package/content/extensions/helpers/error/index.md +99 -23
  41. package/content/extensions/helpers/index.md +61 -47
  42. package/content/extensions/helpers/inversion/index.md +23 -6
  43. package/content/extensions/helpers/inversion/reference.md +30 -22
  44. package/content/extensions/helpers/kafka/admin.md +3 -2
  45. package/content/extensions/helpers/kafka/compile-binary.md +69 -44
  46. package/content/extensions/helpers/kafka/consumer.md +24 -21
  47. package/content/extensions/helpers/kafka/examples.md +22 -234
  48. package/content/extensions/helpers/kafka/index.md +30 -62
  49. package/content/extensions/helpers/kafka/producer.md +31 -26
  50. package/content/extensions/helpers/kafka/schema-registry.md +19 -14
  51. package/content/extensions/helpers/logger/hf-logger.md +49 -22
  52. package/content/extensions/helpers/logger/index.md +37 -12
  53. package/content/extensions/helpers/logger/pino.md +31 -11
  54. package/content/extensions/helpers/logger/reference.md +243 -52
  55. package/content/extensions/helpers/network/api.md +65 -30
  56. package/content/extensions/helpers/network/index.md +32 -11
  57. package/content/extensions/helpers/queue/index.md +17 -6
  58. package/content/extensions/helpers/queue/reference.md +52 -25
  59. package/content/extensions/helpers/redis/index.md +29 -11
  60. package/content/extensions/helpers/redis/reference.md +85 -28
  61. package/content/extensions/helpers/secrets/index.md +82 -12
  62. package/content/extensions/helpers/socket-io/api.md +40 -21
  63. package/content/extensions/helpers/socket-io/index.md +26 -10
  64. package/content/extensions/helpers/storage/api.md +42 -24
  65. package/content/extensions/helpers/storage/index.md +10 -7
  66. package/content/extensions/helpers/types/index.md +20 -7
  67. package/content/extensions/helpers/types/reference.md +53 -14
  68. package/content/extensions/helpers/uid/index.md +166 -10
  69. package/content/extensions/helpers/websocket/api.md +52 -13
  70. package/content/extensions/helpers/websocket/index.md +7 -4
  71. package/content/extensions/helpers/worker-thread/index.md +9 -5
  72. package/content/extensions/helpers/worker-thread/reference.md +20 -20
  73. package/content/extensions/index.md +38 -39
  74. package/content/extensions/src-details/mcp-server.md +96 -548
  75. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  76. package/content/guides/core-concepts/persistent/index.md +5 -1
  77. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  78. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  79. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  80. package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
  81. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  82. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  83. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  84. package/content/guides/get-started/philosophy.md +135 -670
  85. package/content/guides/get-started/setup.md +53 -74
  86. package/content/guides/migrations/redis-helpers-migration.md +4 -3
  87. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  88. package/content/guides/migrations/unified-connectors-migration.md +7 -6
  89. package/content/guides/tutorials/realtime-chat.md +1 -1
  90. package/content/references/base/application.md +63 -21
  91. package/content/references/base/components.md +3 -3
  92. package/content/references/base/connectors.md +14 -14
  93. package/content/references/base/controllers.md +12 -12
  94. package/content/references/base/datasources-reference.md +32 -31
  95. package/content/references/base/datasources.md +6 -6
  96. package/content/references/base/dependency-injection.md +12 -11
  97. package/content/references/base/filter-system/application-usage.md +54 -30
  98. package/content/references/base/filter-system/array-operators.md +24 -46
  99. package/content/references/base/filter-system/comparison-operators.md +47 -67
  100. package/content/references/base/filter-system/default-filter.md +59 -53
  101. package/content/references/base/filter-system/fields-order-pagination.md +92 -146
  102. package/content/references/base/filter-system/index.md +25 -13
  103. package/content/references/base/filter-system/json-filtering.md +45 -184
  104. package/content/references/base/filter-system/list-operators.md +23 -53
  105. package/content/references/base/filter-system/logical-operators.md +63 -121
  106. package/content/references/base/filter-system/null-operators.md +34 -104
  107. package/content/references/base/filter-system/pattern-matching.md +40 -55
  108. package/content/references/base/filter-system/quick-reference.md +86 -198
  109. package/content/references/base/filter-system/range-operators.md +18 -46
  110. package/content/references/base/filter-system/tips.md +6 -6
  111. package/content/references/base/filter-system/use-cases.md +33 -15
  112. package/content/references/base/grpc-controllers.md +53 -17
  113. package/content/references/base/index.md +5 -3
  114. package/content/references/base/middlewares.md +11 -10
  115. package/content/references/base/models-reference.md +17 -17
  116. package/content/references/base/models.md +4 -3
  117. package/content/references/base/providers.md +8 -8
  118. package/content/references/base/repositories/advanced.md +224 -326
  119. package/content/references/base/repositories/index.md +19 -6
  120. package/content/references/base/repositories/mixins.md +5 -5
  121. package/content/references/base/repositories/relations.md +160 -293
  122. package/content/references/base/repositories/soft-deletable.md +16 -6
  123. package/content/references/base/secrets.md +17 -13
  124. package/content/references/base/services.md +6 -4
  125. package/content/references/configuration/environment-variables.md +31 -23
  126. package/content/references/configuration/index.md +6 -4
  127. package/content/references/index.md +1 -1
  128. package/content/references/utilities/duration.md +85 -0
  129. package/content/references/utilities/index.md +5 -1
  130. package/content/references/utilities/jsx-reference.md +11 -11
  131. package/content/references/utilities/jsx.md +2 -2
  132. package/content/references/utilities/module.md +78 -25
  133. package/content/references/utilities/request.md +2 -1
  134. package/content/references/utilities/retry.md +139 -0
  135. package/content/references/utilities/schema.md +2 -2
  136. package/content/references/utilities/statuses-reference.md +4 -4
  137. package/content/references/utilities/statuses.md +5 -5
  138. package/dist/mcp-server/index.js +0 -0
  139. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  140. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  141. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  142. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  143. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  144. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  145. package/package.json +17 -16
@@ -1,12 +1,21 @@
1
1
  ---
2
2
  title: UID
3
- description: Snowflake ID generator with Base62 encoding for unique, time-sortable distributed identifiers
3
+ description: Two ID generators - time-sortable Snowflake IDs, and short random IDs a human can read back
4
4
  difficulty: beginner
5
5
  ---
6
6
 
7
7
  # UID
8
8
 
9
- `SnowflakeUidHelper` generates 70-bit, time-sortable Snowflake IDs and encodes them as compact Base62 strings, suitable as database primary keys.
9
+ Two generators, and they answer different questions.
10
+
11
+ | Helper | Reads back as | Pick it when |
12
+ |---|---|---|
13
+ | `SnowflakeUidHelper` | Timestamp, worker ID, sequence | The ID is a primary key and you want insert order for free |
14
+ | `OpaqueUidHelper` | Nothing | The ID leaves your system - a human reads it, or a stranger sees it |
15
+
16
+ A Snowflake ID is transparent by design. `parseId()` gives back the exact millisecond it was minted and the worker that minted it. That is what you want in a log, and what you do not want printed on an invoice a customer keeps.
17
+
18
+ An opaque ID gives nothing back. You trade the ordering for that, and for a short ID a human can read out loud.
10
19
 
11
20
  ## In one example
12
21
 
@@ -18,15 +27,19 @@ const id = generator.nextId();
18
27
  // => e.g. "9du1sJXO88"
19
28
  ```
20
29
 
21
- Use one `SnowflakeUidHelper` instance per worker process - `nextId()` returns a URL-safe Base62 string that sorts the same order as the IDs were generated.
30
+ Use one `SnowflakeUidHelper` instance per worker process. `nextId()` returns a URL-safe Base62 string, and it sorts in the same order the IDs were generated.
22
31
 
23
32
  ## How it works
24
33
 
25
- - **The ID packs three fields into 70 bits.** A 48-bit timestamp (offset from a custom epoch), a 10-bit worker ID (0-1023), and a 12-bit sequence (0-4095) - shifted and OR'd together in `nextSnowflake()`. Sorting the raw bigints, or the Base62 strings, sorts by generation time.
26
- - **The sequence resets every millisecond, per worker.** Within the same millisecond it increments and wraps at 4096; a wrap forces the generator to busy-wait for the next millisecond, capping throughput at 4,096,000 IDs/second/worker.
27
- - **Backward clock drift is handled, up to a point.** A drift up to 100ms (`MAX_CLOCK_BACKWARD_MS`) busy-waits until the clock catches up, logging a warning. A larger drift throws instead of risking a duplicate ID.
28
- - **`encodeBase62` / `decodeBase62` are just a bigint <-> string codec.** They do not know about the Snowflake layout - `parseId()` is `decodeBase62()` followed by the three `extract*` calls, all built on the same instance's `epoch` and bit-shift constants.
29
- - **An expiry warning logs once the epoch nears its 48-bit limit.** ~8,919 years after the configured epoch the timestamp field overflows; a warning starts logging 10 years before that.
34
+ - **The ID packs three fields into 70 bits.** `nextSnowflake()` shifts and OR's together a timestamp, a worker ID, and a sequence - see the bit layout below.
35
+ - **Sorting the ID sorts by generation time.** This holds for the raw `bigint`, and for the Base62 string.
36
+ - **The sequence resets every millisecond, per worker.** It increments within the millisecond and wraps at 4096.
37
+ - **A wrap forces a busy-wait for the next millisecond.** That caps throughput at 4,096,000 IDs per second per worker.
38
+ - **A small backward clock drift busy-waits.** Up to 100ms (`MAX_CLOCK_BACKWARD_MS`), the generator waits for the clock to catch up, and logs a warning.
39
+ - **A larger drift throws instead.** IGNIS refuses to risk generating a duplicate ID.
40
+ - **`encodeBase62` / `decodeBase62` are a bigint <-> string codec.** They don't know about the Snowflake layout.
41
+ - **`parseId()` layers on top of the codec.** It calls `decodeBase62()`, then the three `extract*` calls, using the same instance's `epoch` and bit-shift constants.
42
+ - **An expiry warning logs once the epoch nears its 48-bit limit.** The timestamp field overflows about 8,919 years after the configured epoch. A warning starts logging 10 years before that.
30
43
 
31
44
  **Snowflake layout (70 bits)**
32
45
 
@@ -47,7 +60,7 @@ Use one `SnowflakeUidHelper` instance per worker process - `nextId()` returns a
47
60
 
48
61
  ### Generate an ID
49
62
 
50
- `nextId()` is the common case - a Base62 string, 10-12 characters. `nextSnowflake()` returns the raw `bigint` when you need arithmetic or bit-level access.
63
+ `nextId()` is the common case - a Base62 string, 10-12 characters. `nextSnowflake()` returns the raw `bigint`, for arithmetic or bit-level access.
51
64
 
52
65
  ```typescript
53
66
  const id = generator.nextId(); // "9du1sJXO88"
@@ -71,7 +84,7 @@ const sequence = generator.extractSequence(raw);
71
84
 
72
85
  ### Run one generator per worker in a distributed deployment
73
86
 
74
- Give each process a unique `workerId`; keep `epoch` identical across all of them so ID ordering stays meaningful.
87
+ Give each process a unique `workerId`. Keep `epoch` identical across every process, so ID ordering stays meaningful.
75
88
 
76
89
  ```typescript
77
90
  const generator = new SnowflakeUidHelper({ workerId: Number(process.env.WORKER_ID) });
@@ -89,6 +102,147 @@ try {
89
102
  }
90
103
  ```
91
104
 
105
+ ## OpaqueUidHelper
106
+
107
+ ```typescript
108
+ import { OpaqueUidHelper } from '@venizia/ignis-helpers';
109
+
110
+ const generator = new OpaqueUidHelper({
111
+ prefix: { enable: true, value: 'INV' },
112
+ delimiter: { enable: true, value: '-' },
113
+ length: 6,
114
+ });
115
+
116
+ generator.nextId();
117
+ // => e.g. "INV-7K2MQ9"
118
+ ```
119
+
120
+ `length` counts the body only. The prefix and the delimiter sit in front of it, so that ID is 10 characters.
121
+
122
+ ### Options
123
+
124
+ | Option | Type | Default | Meaning |
125
+ |---|---|---|---|
126
+ | `prefix` | `{ enable, value? }` | disabled | Leads the ID. `value` survives being disabled, so you can toggle it back on |
127
+ | `delimiter` | `{ enable, value? }` | disabled, `_` | Separates prefix from body. Requires a prefix |
128
+ | `length` | `number` | `6` | Characters in the body |
129
+ | `caseForm` | `'upper' \| 'lower' \| 'mixed'` | `'upper'` | Folds the alphabet before sampling |
130
+ | `alphabet` | `string` | `UidAlphabets.CROCKFORD` | The characters to draw from |
131
+ | `exclude` | `string` | `''` | Removed from `alphabet` |
132
+
133
+ Four alphabets ship with it:
134
+
135
+ | Alphabet | Size | Drops | Use for |
136
+ |---|---|---|---|
137
+ | `CROCKFORD` | 32 | Lowercase, `I` `L` `O` `U` | IDs a human reads, types, or says aloud |
138
+ | `BASE58` | 58 | `0` `O` `I` `l` | IDs in a URL or a log |
139
+ | `NO_VOWEL` | 50 | `BASE58` plus every vowel | Customer-facing IDs - no word can form |
140
+ | `BASE62` | 62 | Nothing | Machines only |
141
+
142
+ `getAlphabet()` returns the set actually in use, after the case fold and every exclusion. That is what `length` is measured against, so read it before calculating how many IDs a length affords you.
143
+
144
+ ### Six characters needs a unique index
145
+
146
+ Six Crockford characters is 2^30 combinations, the size of an airline record locator. That works for airlines because of three things, not because 2^30 is large:
147
+
148
+ 1. **Scoped.** A record locator is unique per carrier, not worldwide.
149
+ 2. **Recycled.** The airline reuses it once the trip is over.
150
+ 3. **Retried.** The reservation system regenerates on conflict.
151
+
152
+ > [!WARNING]
153
+ > At the default length, a 1% chance of at least one collision arrives at roughly 4,600 IDs in one space, and 50% at roughly 38,000.
154
+
155
+ A `prefix` PARTITIONS the space. `INV-7K2MQ9` can never collide with `CUS-7K2MQ9`, so ten entity types get ten separate spaces instead of sharing one. It does not make an ID unique inside its own space - within `INV`, the numbers above still hold.
156
+
157
+ So do both. Give each entity type its own `prefix`, and put the column behind a unique index.
158
+
159
+ ### Reject an ID you already hold
160
+
161
+ `nextId()` takes a callback. Return `true` to accept the ID, `false` to draw another.
162
+
163
+ ```typescript
164
+ const issued = new Set<string>();
165
+ const generator = new OpaqueUidHelper({ length: 8 });
166
+
167
+ const id = generator.nextId({
168
+ isAvailable: candidate => !issued.has(candidate),
169
+ });
170
+ issued.add(id);
171
+ ```
172
+
173
+ Every argument is optional, including the options object itself - `nextId()` alone is the common case.
174
+
175
+ **`nextId()` options**
176
+
177
+ | Option | Type | Default | Meaning |
178
+ |---|---|---|---|
179
+ | `prefix` | `string` | The configured prefix | Replaces it for this call |
180
+ | `isAvailable` | `(id: string) => ValueOrPromise<boolean>` | none | `true` accepts, `false` draws again |
181
+ | `maxAttempts` | `number` | `10` | Draws before it throws. Requires `isAvailable` |
182
+
183
+ The callback sees the finished ID, prefix and delimiter included - the string the column stores.
184
+
185
+ **The callback's return type decides `nextId`'s.** A synchronous check keeps the call synchronous, so no promise is allocated. An asynchronous one makes it a promise:
186
+
187
+ ```typescript
188
+ const id = generator.nextId({ isAvailable: candidate => !issued.has(candidate) });
189
+ // string
190
+
191
+ const id = await generator.nextId({
192
+ isAvailable: async candidate => !(await invoiceRepository.exists(candidate)),
193
+ });
194
+ // Promise<string>
195
+ ```
196
+
197
+ > [!WARNING]
198
+ > An asynchronous check does not make an ID safe. Against a database it is a read and then a write with a gap between them, and another request can take the ID inside that gap. `isAvailable` lowers the collision rate; the unique index is what removes it.
199
+
200
+ `maxAttempts` bounds the redraws. Repeated rejection means the space is full, and the helper throws with the length and alphabet size rather than looping.
201
+
202
+ ### Regenerate on conflict, never check first
203
+
204
+ Insert and catch the violation. Checking whether an ID exists and then inserting it leaves a window where another request takes the same ID between the two statements.
205
+
206
+ ```typescript
207
+ import { executeWithRetry, OpaqueUidHelper } from '@venizia/ignis-helpers';
208
+
209
+ const generator = new OpaqueUidHelper({
210
+ prefix: { enable: true, value: 'INV' },
211
+ delimiter: { enable: true, value: '-' },
212
+ });
213
+
214
+ const invoice = await executeWithRetry({
215
+ operation: 'createInvoice',
216
+ maxAttempts: 5,
217
+ // A fresh ID per attempt - retrying with the same one would collide forever.
218
+ execution: () => invoiceRepository.create({ data: { id: generator.nextId(), total } }),
219
+ shouldRetry: context => isUniqueViolation(context.error),
220
+ });
221
+ ```
222
+
223
+ Five attempts is generous. At the default length a second collision on the same insert has a probability in the millionths.
224
+
225
+ Or raise `length` - every extra character multiplies the space by 32.
226
+
227
+ ### What the constructor refuses
228
+
229
+ Each of these fails loudly at construction, because each one fails silently at runtime:
230
+
231
+ | Configuration | Why it is refused |
232
+ |---|---|
233
+ | `delimiter` enabled, `prefix` disabled | The delimiter would lead the ID and separate nothing |
234
+ | A delimiter character that is in the alphabet | The ID could not be split back into prefix and body |
235
+ | A `prefix` that disagrees with `caseForm` | A lowercase prefix on an uppercase ID defeats the reason for choosing one case |
236
+ | `caseForm` folding a two-case alphabet | Uppercasing `BASE58` brings `I` and `O` back - the pair it drops so `1` and `0` stay readable |
237
+ | An alphabet under 16 characters | `exclude` has eaten it |
238
+ | `maxAttempts` passed without `isAvailable` | Nothing rejects a draw, so the retry it configures cannot happen |
239
+
240
+ ### Randomness
241
+
242
+ `OpaqueUidHelper` draws from `crypto.getRandomValues`, and rejects samples that fall outside the alphabet rather than wrapping them. A `byte % 58` would favour the first 24 characters by about 1.5%, forever, and no caller could see it.
243
+
244
+ Unlike `crypto.randomUUID`, `getRandomValues` works outside a secure context. The generator runs on a plain-http origin and inside a browser Worker.
245
+
92
246
  ## See also
93
247
 
94
248
  - [Models](/guides/core-concepts/persistent/models) - using UIDs as primary keys
@@ -100,4 +254,6 @@ try {
100
254
  **Files:**
101
255
 
102
256
  - [`packages/helpers/src/modules/uid/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/uid/helper.ts) - `SnowflakeUidHelper`, `SnowflakeConfig`
257
+ - [`packages/helpers/src/modules/uid/opaque.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/uid/opaque.ts) - `OpaqueUidHelper`
258
+ - [`packages/helpers/src/modules/uid/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/uid/common/constants.ts) - `UidAlphabets`, `UidCaseForms`
103
259
  - [`packages/helpers/src/modules/uid/index.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/uid/index.ts) - module barrel
@@ -15,6 +15,19 @@ Exhaustive reference for `WebSocketServerHelper` and `WebSocketEmitter`. For a r
15
15
  - [`packages/helpers/src/modules/socket/websocket/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/common/types.ts) - option, callback, and wire types
16
16
  - [`packages/helpers/src/modules/socket/websocket/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/common/constants.ts) - `WebSocketEvents`, `WebSocketChannels`, `WebSocketDefaults`, `WebSocketMessageTypes`, `WebSocketClientStates`
17
17
 
18
+ ## Find what you need
19
+
20
+ | You want to | Go to |
21
+ |---|---|
22
+ | See every class and its role at a glance | [Quick Reference](#quick-reference) |
23
+ | Follow a connection from upgrade to disconnect | [Client connection lifecycle](#client-connection-lifecycle) |
24
+ | Look up every server constructor option | [`IWebSocketServerOptions`](#iwebsocketserveroptions) |
25
+ | Send to one client, a user, a room, or everyone (same process) | [Server API](#server-api) |
26
+ | Send to clients on any server instance, or publish from a process with no server of its own | [Server API](#server-api) / [Emitter API](#emitter-api) |
27
+ | Look up a wire type, option type, or callback signature | [Types Reference](#types-reference) |
28
+ | Look up an event name, channel, default, or close code | [Constants](#constants) |
29
+ | Debug a stuck, rejected, or cross-instance delivery problem | [Troubleshooting](#troubleshooting) |
30
+
18
31
  ## Quick Reference
19
32
 
20
33
  | Class | Extends | Role |
@@ -157,7 +170,7 @@ Server A Redis Server B
157
170
 
158
171
  - **Duplication.** `WebSocketServerHelper` duplicates its `redisConnection` twice (`redisPub`, `redisSub`); `WebSocketEmitter` duplicates it once (`redisPub` only).
159
172
  - **Connection types.** Both single-instance `Redis` and `Cluster` connections from ioredis are supported - the parent helper connection stays independent and unconsumed.
160
- - **Dedup on receipt.** Every server instance generates a unique `serverId` (UUID) at construction; messages carrying the same `serverId` on receipt are skipped to prevent double delivery to the originating instance.
173
+ - **Dedup on receipt.** Every server instance generates a unique `serverId` (UUID) at construction. A message carrying that same `serverId` on receipt is skipped, so the originating instance never delivers to itself twice.
161
174
 
162
175
  ## Server API
163
176
 
@@ -169,11 +182,20 @@ Server A Redis Server B
169
182
  constructor(opts: IWebSocketServerOptions<AuthDataType, MetadataType>)
170
183
  ```
171
184
 
172
- Generates a unique `serverId` (UUID), stores all options with defaults applied, and initializes two duplicated Redis clients (`redisPub`, `redisSub`). Throws `getError({ statusCode: 500, message: '[WebSocketServerHelper] Invalid redis connection!' })` if `redisConnection` is falsy.
185
+ On construction:
186
+
187
+ - Generates a unique `serverId` (UUID).
188
+ - Stores all options, with defaults applied.
189
+ - Initializes two duplicated Redis clients (`redisPub`, `redisSub`).
190
+
191
+ Throws `getError({ statusCode: 500, message: '[WebSocketServerHelper] Invalid redis connection!' })` if `redisConnection` is falsy.
173
192
 
174
193
  #### Generic type parameters
175
194
 
176
- `AuthDataType` types the payload passed to `authenticateFn`/`handshakeFn`; `MetadataType` types the value returned as `metadata` and stored on `IWebSocketClient`. Both default to `Record<string, unknown>`.
195
+ | Type parameter | Types | Default |
196
+ |---|---|---|
197
+ | `AuthDataType` | The payload passed to `authenticateFn`/`handshakeFn` | `Record<string, unknown>` |
198
+ | `MetadataType` | The value returned as `metadata` and stored on `IWebSocketClient` | `Record<string, unknown>` |
177
199
 
178
200
  ```typescript
179
201
  interface AuthPayload { type: string; token: string; publicKey?: string }
@@ -219,7 +241,7 @@ const helper = new WebSocketServerHelper<AuthPayload, UserMetadata>({
219
241
  | `handshakeFn` | `TWebSocketHandshakeFn` | No | - | Required when `requireEncryption` is `true`. Returns `{ serverPublicKey, salt }` to accept, `null`/`false` to reject |
220
242
 
221
243
  - **All callbacks run through `invokeHook()`.** Applies to `authenticateFn`, `validateRoomFn`, `clientConnectedFn`, `clientDisconnectedFn`, `messageHandler`, and `handshakeFn`.
222
- - **Failures do not crash the process.** A synchronous throw inside a Bun socket handler is caught and logged; rejected promises are logged the same way via `voidExecution`.
244
+ - **Failures do not crash the process.** A synchronous throw inside a Bun socket handler is caught and logged. A rejected promise is logged the same way, via `voidExecution`.
223
245
 
224
246
  ### `configure()`
225
247
 
@@ -253,7 +275,7 @@ Returns the Bun WebSocket handler object. Pass it to `server.reload({ websocket
253
275
  #### Bun native configuration
254
276
 
255
277
  - **`serverOptions` is spread into the returned handler.** `WebSocketServerHelper` only applies its own default for `sendPings`, `idleTimeout`, and `maxPayloadLength`.
256
- - **Everything else falls through to Bun.** The remaining fields are `undefined` unless you set them, and Bun applies its own runtime default in that case.
278
+ - **Everything else falls through to Bun.** The remaining fields are `undefined` unless you set them. Bun applies its own runtime default in that case.
257
279
 
258
280
  | Option | Type | Helper default | Description |
259
281
  |--------|------|-----------------|--------------|
@@ -306,7 +328,13 @@ Returns every client in `room`; `[]` if the room does not exist or is empty.
306
328
  onClientConnect(opts: { clientId: string; socket: IWebSocket }): void
307
329
  ```
308
330
 
309
- Handles a new WebSocket connection: creates an `IWebSocketClient` entry with state `UNAUTHORIZED`, subscribes the socket to its own `clientId` topic (Bun native pub/sub), and starts the authentication timeout. Returns early - and logs `'Client already existed'` - if the client ID already exists.
331
+ Handles a new WebSocket connection:
332
+
333
+ - Creates an `IWebSocketClient` entry with state `UNAUTHORIZED`.
334
+ - Subscribes the socket to its own `clientId` topic (Bun native pub/sub).
335
+ - Starts the authentication timeout.
336
+
337
+ If the client ID already exists, it returns early and logs `'Client already existed'`.
310
338
 
311
339
  ### `onClientMessage()`
312
340
 
@@ -316,7 +344,8 @@ onClientMessage(opts: { clientId: string; raw: string }): void
316
344
 
317
345
  Routes an incoming message:
318
346
 
319
- - Parses `raw` as JSON. On failure, sends `{ event: 'error', data: { message: 'Invalid message format' } }` and returns. A message with no `event` field is logged and dropped.
347
+ - Parses `raw` as JSON. On failure, sends `{ event: 'error', data: { message: 'Invalid message format' } }` and returns.
348
+ - A parsed message with no `event` field is logged and dropped.
320
349
  - `heartbeat`: consumed silently (`lastActivity` is already updated by the `message` callback before this runs).
321
350
  - `authenticate`: delegates to `handleAuthenticate()`.
322
351
  - Any other event, when the client is not `AUTHENTICATED`: sends `{ event: 'error', data: { message: 'Not authenticated' } }`.
@@ -329,7 +358,14 @@ Routes an incoming message:
329
358
  onClientDisconnect(opts: { clientId: string }): void
330
359
  ```
331
360
 
332
- Cleans up a disconnected client: clears the pending auth timer, removes the client from its user's index, removes it from every joined room, removes it from the clients map, then invokes `clientDisconnectedFn`.
361
+ Cleans up a disconnected client:
362
+
363
+ - Clears the pending auth timer.
364
+ - Removes the client from its user's index.
365
+ - Removes it from every joined room.
366
+ - Removes it from the clients map.
367
+
368
+ It then invokes `clientDisconnectedFn`.
333
369
 
334
370
  ### `joinRoom()`
335
371
 
@@ -337,7 +373,7 @@ Cleans up a disconnected client: clears the pending auth timer, removes the clie
337
373
  joinRoom(opts: { clientId: string; room: string }): void
338
374
  ```
339
375
 
340
- Adds `clientId` to the room index and to `client.rooms`, and subscribes the socket to the room's Bun native pub/sub topic - unless the client is encrypted, in which case the subscribe step is skipped (delivery goes through the transformer instead). No-op if the client does not exist.
376
+ Adds `clientId` to the room index and to `client.rooms`, and subscribes the socket to the room's Bun native pub/sub topic. An encrypted client skips the subscribe step - delivery goes through the transformer instead. No-op if the client does not exist.
341
377
 
342
378
  ### `leaveRoom()`
343
379
 
@@ -355,7 +391,10 @@ Removes `clientId` from the room index and from `client.rooms`, and unsubscribes
355
391
  - Maximum 256 characters.
356
392
  - Cannot start with `ws:` (reserved for the internal Redis channel prefix).
357
393
 
358
- - **Rejection is silent.** If every requested room is filtered out, `validateRoomFn` is not configured, or it resolves to an empty array, the join is rejected and logged - no error is sent back to the client.
394
+ - **Rejection is silent.** No error goes back to the client - the join is only rejected and logged. That happens when:
395
+ - every requested room is filtered out,
396
+ - `validateRoomFn` is not configured, or
397
+ - it resolves to an empty array.
359
398
  - **`leave` does not re-filter.** `{ event: 'leave' }` only processes rooms the client is actually a member of (`client.rooms.has(room)`).
360
399
  - **Programmatic calls bypass validation.** `joinRoom()`/`leaveRoom()` called directly skip all of the above.
361
400
 
@@ -365,7 +404,7 @@ Removes `clientId` from the room index and from `client.rooms`, and unsubscribes
365
404
  enableClientEncryption(opts: { clientId: string }): void
366
405
  ```
367
406
 
368
- Unsubscribes the client from every Bun native pub/sub topic (the broadcast topic plus all joined rooms) so `server.publish()` no longer reaches it; messages are instead delivered individually through `outboundTransformer`. No-op if the client is already encrypted or does not exist.
407
+ Unsubscribes the client from every Bun native pub/sub topic (the broadcast topic plus all joined rooms), so `server.publish()` no longer reaches it. Messages are instead delivered individually through `outboundTransformer`. No-op if the client is already encrypted or does not exist.
369
408
 
370
409
  > [!WARNING]
371
410
  > Irreversible for the lifetime of the connection. Once a client is encrypted it cannot be switched back to Bun native pub/sub delivery.
@@ -377,7 +416,7 @@ sendToClient(opts: { clientId: string; event: string; data: unknown; doLog?: boo
377
416
  ```
378
417
 
379
418
  - **Local delivery only.**
380
- - **Encrypted client, `outboundTransformer` configured.** The transformer runs (async) before `socket.send()`; a transformer error is logged and the message is dropped.
419
+ - **Encrypted client, `outboundTransformer` configured.** The transformer runs (async) before `socket.send()`. A transformer error is logged, and the message is dropped.
381
420
  - **Otherwise.** Sends `JSON.stringify({ event, data })` directly.
382
421
  - **`doLog: true`** emits an info log after delivery.
383
422
 
@@ -450,7 +489,7 @@ shutdown(): Promise<void>
450
489
  `Source ->` [`emitter/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/emitter/helper.ts)
451
490
 
452
491
  - **Role.** A Redis-only publisher for processes with no WebSocket server of their own - background workers, other microservices, cron jobs.
453
- - **No self-skip.** It always publishes with `serverId: 'emitter'`, so every server instance processes the message; there is no dedup skip on the sending side.
492
+ - **No self-skip.** It always publishes with `serverId: 'emitter'`, so every server instance processes the message. There is no dedup skip on the sending side.
454
493
 
455
494
  ### Constructor
456
495
 
@@ -6,7 +6,9 @@ difficulty: intermediate
6
6
 
7
7
  # WebSocket
8
8
 
9
- A Bun-native WebSocket server with post-connection authentication, rooms, heartbeat, and Redis Pub/Sub for horizontal scaling, plus a Redis-only emitter for publishing to it from other processes.
9
+ A Bun-native WebSocket server with post-connection authentication, rooms, heartbeat, and Redis Pub/Sub for horizontal scaling. A Redis-only emitter sends to it from other processes that run no server of their own.
10
+
11
+ This helper is the raw class: construct it and wire it into your own server yourself. Need it inside an IGNIS app instead? [`WebSocketComponent`](/extensions/components/websocket/) does that wiring for you. It creates and binds the helper through DI once your app's Bun server is listening.
10
12
 
11
13
  > [!IMPORTANT]
12
14
  > **Bun only.** `WebSocketServerHelper` uses Bun's native WebSocket API and will not run on Node.js. For Node.js, use the [Socket.IO Helper](../socket-io/) instead.
@@ -46,7 +48,7 @@ ws.onopen = () => ws.send(JSON.stringify({ event: 'authenticate', data: { token:
46
48
 
47
49
  ## How it works
48
50
 
49
- - **Every connection starts unauthenticated.** The WebSocket upgrade always succeeds; the client starts as `UNAUTHORIZED` and must send `{ event: 'authenticate' }` within `authTimeout` (5s default) or get closed with code `4001`.
51
+ - **Every connection starts unauthenticated.** The WebSocket upgrade always succeeds, and the client starts as `UNAUTHORIZED`. It must send `{ event: 'authenticate' }` within `authTimeout` (5s default) or get closed with code `4001`.
50
52
  - **`authenticateFn` decides accept or reject.** On success the client is indexed by `userId`, joins `defaultRooms`, and moves to `AUTHENTICATED`.
51
53
  - **Delivery has two tiers** - local-only fan-out on this process, or a Redis Pub/Sub layer that also reaches other server instances:
52
54
 
@@ -56,7 +58,8 @@ ws.onopen = () => ws.send(JSON.stringify({ event: 'authenticate', data: { token:
56
58
  | Cross-instance | `send()`, `WebSocketEmitter` | Clients on any server instance | Redis Pub/Sub, via the server's duplicated `redisPub`/`redisSub` connections |
57
59
 
58
60
  - **Liveness is passive.** The server never pings clients - they must periodically send `{ event: 'heartbeat' }`. A sweep every `heartbeatInterval` (30s) closes anyone silent for longer than `heartbeatTimeout` (90s) with code `4002`.
59
- - **Encryption is opt-in per client.** An `outboundTransformer` callback intercepts every outbound message before `socket.send()`. Once a client is encrypted, Bun native pub/sub is bypassed for it - `sendToRoom`/`broadcast` fall back to iterating encrypted clients individually.
61
+ - **Encryption is opt-in per client.** An `outboundTransformer` callback intercepts every outbound message before `socket.send()`.
62
+ - **Once encrypted, delivery changes.** Bun native pub/sub is bypassed for that client. `sendToRoom`/`broadcast` then fall back to iterating encrypted clients individually.
60
63
 
61
64
  ## Common tasks
62
65
 
@@ -96,7 +99,7 @@ helper.broadcast({ event: 'system:announcement', data: { text: 'Maintenance in 5
96
99
 
97
100
  ### Let clients join rooms
98
101
 
99
- Clients can only join custom rooms when you supply `validateRoomFn` - without it, every join request is rejected by default:
102
+ Clients can only join custom rooms when you supply `validateRoomFn`. Without it, every join request is rejected by default:
100
103
 
101
104
  ```typescript
102
105
  const helper = new WebSocketServerHelper({
@@ -6,7 +6,7 @@ difficulty: intermediate
6
6
 
7
7
  # Worker Thread
8
8
 
9
- The worker-thread helper wraps Node's `worker_threads` with a pooled registry, lifecycle-event helpers, and a `MessagePort` bus for two-way communication between the main thread and a worker.
9
+ The worker-thread helper wraps Node's `worker_threads`. It adds a pooled registry, lifecycle-event helpers, and a `MessagePort` bus for two-way communication between the main thread and a worker.
10
10
 
11
11
  ## In one example
12
12
 
@@ -30,16 +30,20 @@ pool.register({ key: 'image-resizer', worker });
30
30
 
31
31
  ## How it works
32
32
 
33
- - **Main thread vs worker thread.** Two class families split by side: `BaseWorkerHelper` runs on the main thread and wraps a `Worker` instance; `BaseWorkerThreadHelper` runs inside the spawned worker script and throws `[BaseWorker] Cannot start worker in MAIN_THREAD` if constructed there instead.
33
+ - **Main thread vs worker thread.** Two class families split by side.
34
+ - `BaseWorkerHelper` runs on the main thread and wraps a `Worker` instance.
35
+ - `BaseWorkerThreadHelper` runs inside the spawned worker script. It throws `[BaseWorker] Cannot start worker in MAIN_THREAD` if you construct it on the main thread instead.
34
36
  - **Pool caps concurrency.** `WorkerPoolHelper` is a lazy singleton (`getInstance()`) that limits registrations to `os.cpus().length`. Past the limit, `register()` returns `false` and logs a warning - it never throws.
35
- - **Lifecycle hooks, not raw events.** `BaseWorkerHelper` binds `online`, `exit`, `error`, `message`, and `messageerror` once in its constructor. Each has a default logging behavior, overridable per instance via `eventHandlers`. A synchronous throw inside a handler is caught and logged, not left to crash the process.
36
- - **Two-way messaging via buses.** Inside a worker script, `BaseWorkerThreadHelper` manages named `BaseWorkerBusHelper` instances - each wraps one `MessagePort` - so a single worker can multiplex several independent channels by key.
37
+ - **Lifecycle hooks, not raw events.**
38
+ - `BaseWorkerHelper` binds `online`, `exit`, `error`, `message`, and `messageerror` once in its constructor. Each has a default logging behavior, overridable per instance via `eventHandlers`.
39
+ - A synchronous throw inside a handler is caught and logged, not left to crash the process.
40
+ - **Two-way messaging via buses.** Inside a worker script, `BaseWorkerThreadHelper` manages named `BaseWorkerBusHelper` instances, each wrapping one `MessagePort`. A single worker can multiplex several independent channels this way, one per key.
37
41
 
38
42
  ## Common tasks
39
43
 
40
44
  ### Look up and message a registered worker
41
45
 
42
- `get()` and `has()` read the pool by key; `size()` reports how many workers are registered.
46
+ `get()` and `has()` read the pool by key. `size()` reports how many workers are registered.
43
47
 
44
48
  ```typescript
45
49
  const worker = pool.get<string>({ key: 'image-resizer' });
@@ -18,13 +18,13 @@ Exhaustive reference for the worker-thread classes, every constructor option, ev
18
18
 
19
19
  ## Quick Reference
20
20
 
21
- | Class | Extends | Use Case |
21
+ | Class | Extends | Use case |
22
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) |
23
+ | [`WorkerPoolHelper`](#workerpoolhelper) | `BaseHelper` | Singleton registry that tracks and limits active worker instances |
24
+ | [`BaseWorkerHelper<MessageType>`](#baseworkerhelper-main-thread-worker-wrapper) | `AbstractWorkerHelper<MessageType>` | Wraps a `Worker` with event lifecycle hooks - online, exit, error, message |
25
+ | [`BaseWorkerThreadHelper`](#baseworkerthreadhelper-inside-a-worker-thread) | `AbstractWorkerThreadHelper` | Runs inside a worker thread. Manages named `WorkerBus` channels |
26
+ | [`BaseWorkerBusHelper<IConsumePayload, IPublishPayload>`](#baseworkerbushelper-messageport-communication) | `AbstractWorkerBusHelper<IConsumePayload, IPublishPayload>` | Bidirectional `MessagePort` communication with pre/post hooks |
27
+ | [`BaseWorkerMessageBusHandlerHelper<IConsumePayload>`](#baseworkermessagebushandlerhelper) | `AbstractWorkerMessageBusHandlerHelper<IConsumePayload>` | Defines event handlers for a worker bus - message, close, error, exit |
28
28
 
29
29
  | Item | Value |
30
30
  |------|-------|
@@ -72,7 +72,7 @@ const customPool = new WorkerPoolHelper({ ignoreMaxWarning: true });
72
72
  ```
73
73
 
74
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.
75
+ > `WorkerPoolHelper.getInstance()` always creates the singleton with `ignoreMaxWarning: false`. To override this, construct a new instance manually. The singleton and a manually-constructed instance are independent registries.
76
76
 
77
77
  ### Constructor options
78
78
 
@@ -85,7 +85,7 @@ const customPool = new WorkerPoolHelper({ ignoreMaxWarning: true });
85
85
  | Method | Signature | Description |
86
86
  |--------|-----------|-------------|
87
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 |
88
+ | `register` | `register<MessageType>(opts: { key: string; worker: IWorker<MessageType> }): boolean` | Adds a worker to the pool. Returns `false` and logs, without registering, if the key exists or the pool is full |
89
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
90
  | `get` | `get<MessageType>(opts: { key: string }): IWorker<MessageType> \| undefined` | Retrieves a registered worker by key |
91
91
  | `has` | `has(opts: { key: string }): boolean` | Checks if a worker is registered under the given key |
@@ -138,9 +138,9 @@ const worker = new BaseWorkerHelper<MyMessageType>({
138
138
 
139
139
  | Method | Signature | Description |
140
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 |
141
+ | `binding` | `binding(): void` | Binds all five event handlers 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
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`.
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 as `"Hook execution FAILED | Error: %s"`, rather than crashing the process. Async rejections are settled the same way, via `voidExecution`.
144
144
 
145
145
  ### AbstractWorkerHelper (subclassing)
146
146
 
@@ -155,7 +155,7 @@ class CustomWorker extends AbstractWorkerHelper<MyMessage> {
155
155
  }
156
156
 
157
157
  onExit(opts: { code: string | number }) {
158
- // Custom exit handling, e.g., restart logic
158
+ // Custom exit handling - for example, restart logic
159
159
  }
160
160
 
161
161
  onError(opts: { error: Error }) {
@@ -172,13 +172,13 @@ class CustomWorker extends AbstractWorkerHelper<MyMessage> {
172
172
  }
173
173
  ```
174
174
 
175
- `AbstractWorkerHelper<MessageType>` declares `worker: Worker`, `options: WorkerOptions`, and the five abstract lifecycle methods above; it does not implement `binding()` itself.
175
+ `AbstractWorkerHelper<MessageType>` declares `worker: Worker`, `options: WorkerOptions`, and the five abstract lifecycle methods above. It does not implement `binding()` itself.
176
176
 
177
177
  ## BaseWorkerThreadHelper (inside a worker thread)
178
178
 
179
179
  `Source ->` [`base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/worker-thread/base.ts)
180
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.
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
182
 
183
183
  ```typescript
184
184
  // Inside worker-script.js
@@ -203,7 +203,7 @@ The constructor checks `isMainThread` (from `node:worker_threads`) and throws `[
203
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
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
205
 
206
- `unbindWorkerBus` is defined only on `BaseWorkerThreadHelper`, not on the `AbstractWorkerThreadHelper` interface - `AbstractWorkerThreadHelper` declares `buses`, `bindWorkerBus`, and `getWorkerBus` as abstract.
206
+ `unbindWorkerBus` is defined only on `BaseWorkerThreadHelper`, not on the `AbstractWorkerThreadHelper` interface. `AbstractWorkerThreadHelper` declares `buses`, `bindWorkerBus`, and `getWorkerBus` as abstract members only.
207
207
 
208
208
  ## BaseWorkerMessageBusHandlerHelper
209
209
 
@@ -259,17 +259,17 @@ const bus = new BaseWorkerBusHelper<IncomingMessage, OutgoingMessage>({
259
259
  | `port` | `MessagePort` | - | The `MessagePort` to bind for communication. Required. |
260
260
  | `busHandler` | `IWorkerMessageBusHandler<IConsumePayload>` | - | Handler that receives incoming messages and lifecycle events. Required. |
261
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`).
262
+ The constructor binds `message`, `error`, `messageerror`, `exit`, and `close` listeners on `port`. Each routes to the handler's matching callback. `error` and `messageerror` both route to `handler.onError`.
263
263
 
264
264
  ### Methods
265
265
 
266
266
  | Method | Signature | Description |
267
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 |
268
+ | `postMessage` | `postMessage(opts: { message: IPublishPayload; transferList: readonly Transferable[] \| undefined }): ValueOrPromise<void>` | Sends a message through the port. Calls `port.postMessage(message, [...transferList])` if `transferList` is set, otherwise `port.postMessage(message)`. Logs and returns, without throwing, if `port` is falsy |
269
269
 
270
270
  ### Pre/post message hooks
271
271
 
272
- `onBeforePostMessage` and `onAfterPostMessage` are optional properties declared on the class but left `undefined` by the constructor - assign them after construction:
272
+ `onBeforePostMessage` and `onAfterPostMessage` are optional properties declared on the class. The constructor leaves them `undefined`. Assign them yourself, after construction:
273
273
 
274
274
  ```typescript
275
275
  bus.onBeforePostMessage = opts => {
@@ -281,7 +281,7 @@ bus.onAfterPostMessage = opts => {
281
281
  };
282
282
  ```
283
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.
284
+ `postMessage` invokes `onBeforePostMessage` before the port write, if set. It invokes `onAfterPostMessage` after the write, if set. Both hooks run through the same `try/catch`-wrapped `invokeHook` used for the port event listeners.
285
285
 
286
286
  ### Sending transferable objects
287
287
 
@@ -390,9 +390,9 @@ const bus = thread.getWorkerBus({ key: 'my-bus' });
390
390
 
391
391
  ### "Failed to post message to main | Invalid parentPort!"
392
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.
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
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:
395
+ **Fix:** Ensure a valid `MessagePort` is passed to the constructor - for example `parentPort` from `node:worker_threads`, or a port from `new MessageChannel()`:
396
396
 
397
397
  ```typescript
398
398
  import { parentPort } from 'node:worker_threads';