halua 2.0.2 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,364 @@
1
1
  # Halua
2
2
 
3
- Package that takes control of logging, metrics and other stuff.
3
+ **A powerful, extensible logging library for Node.js, browsers, and edge runtimes.**
4
4
 
5
- Supports Text, JSON, Console handlers as output. Custom handlers can be easily created and used.
5
+ Halua gives you full control over log output through pluggable dispatchers (text, JSON, console), hierarchical child
6
+ loggers, fine-grained level filtering (including minor levels like `INFO+3`), and zero-config defaults that just work.
6
7
 
7
- ### documentation
8
+ [![npm version](https://img.shields.io/npm/v/halua.svg)](https://www.npmjs.com/package/halua)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
10
 
9
- There is a short doc [Tour of Halua](./docs/tour_of_halua.md) - it describes basic concepts
11
+ ## Features
10
12
 
11
- ### installation
13
+ - Zero-config default logger (writes to `console` using appropriate methods)
14
+ - Four built-in dispatchers: `NewTextDispatcher`, `NewJSONDispatcher`, `NewConsoleDispatcher`, `NewConsoleColoredDispatcher`
15
+ - Compose any number of dispatchers per logger instance
16
+ - Child loggers that automatically append context (`logger.child("user", 42)`)
17
+ - Powerful level system: `TRACE` < `DEBUG` < `INFO` < `NOTICE` < `WARN` < `ERROR` < `FATAL` + minor levels (`INFO+5`)
18
+ - Per-dispatcher level overrides and exact-match mode
19
+ - Beautiful structured formatting for objects, arrays, Maps, Sets, Errors, etc.
20
+ - Safe by design — dispatcher errors never crash your application
21
+ - `.stamp(label, id?)` + `.stampEnd(id)` (or returned ender) for `performance.now`-based timing with automatic pretty
22
+ `took X.XXms` logging
23
+ - Tiny, fast, tree-shakeable ESM + CJS + TypeScript
12
24
 
13
- ```text
14
- npm i halua
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install halua
29
+ # or
30
+ pnpm add halua
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```ts
36
+ import { halua } from "halua"
37
+
38
+ halua.info("Application started")
39
+ halua.warn("Disk space low", { available: "12%" })
40
+ halua.error("timeout") // strings accepted too (unknownToError normalizes to Error)
41
+ ```
42
+
43
+ **Default output (console):**
44
+
45
+ ```
46
+ 22/05/2026 21:55:50 INFO Application started
47
+ 22/05/2026 21:55:50 WARN Disk space low { available: '12%' }
48
+ 22/05/2026 21:55:50 ERROR Error: timeout
49
+ at ...
50
+ ```
51
+
52
+ ## Dedicated Loggers & Dispatchers
53
+
54
+ Use the built-in dispatcher factories to create purpose-specific loggers:
55
+
56
+ ```ts
57
+ import { halua, NewTextDispatcher, NewJSONDispatcher, Level } from "halua"
58
+
59
+ // Text logger (human readable)
60
+ let textLogger = halua.create(NewTextDispatcher((line) => sendToLogServer(line)))
61
+
62
+ // JSON logger (for structured ingestion)
63
+ let jsonLogger = halua.create(NewJSONDispatcher((json) => writeToArchive(json)))
64
+
65
+ // Console logger (explicit)
66
+ let consoleLogger = halua.create(NewConsoleDispatcher(console))
67
+
68
+ // Colored console (ANSI in Node, %c CSS in browsers; colors: trace/debug=purple, info=blue, notice=orange, warn/error/fatal=red)
69
+ let colorLogger = halua.create(NewConsoleColoredDispatcher(console))
70
+
71
+ textLogger.info("user action", { id: 123, type: "click" })
72
+ // -> 22/05/2026 21:55:50 INFO user action { id: 123, type: "click" }
73
+
74
+ jsonLogger.info("structured", { success: true })
75
+ // -> {"timestamp":"2026-05-22T18:55:50.430Z","level":"INFO","args":["structured",{"success": true}]}
15
76
  ```
77
+
78
+ You can pass an array to use **multiple dispatchers at once**:
79
+
80
+ ```ts
81
+ let prodLogger = halua.create([NewTextDispatcher(sendToFile), NewJSONDispatcher(sendToElastic)], { level: Level.Info })
82
+ ```
83
+
84
+ ## Child Loggers (Context)
85
+
86
+ ```ts
87
+ let requestLogger = halua.child("requestId", "abc-123", "user", 42)
88
+
89
+ requestLogger.info("processing started")
90
+ // -> ... INFO processing started requestId abc-123 user 42
91
+
92
+ let stepLogger = requestLogger.child("step", "validate")
93
+ stepLogger.warn("slow validation")
94
+ // -> ... WARN slow validation requestId abc-123 user 42 step validate
95
+ ```
96
+
97
+ Call `.create({ withArgs: [] })` to clear context on a child.
98
+
99
+ ## Level Control
100
+
101
+ ```ts
102
+ import { Level } from "halua"
103
+
104
+ // Instance level (affects all dispatchers that don't override)
105
+ let logger = halua.create({ level: Level.Warn })
106
+
107
+ logger.debug("hidden")
108
+ logger.info("hidden")
109
+ logger.warn("visible")
110
+ logger.error("visible")
111
+ ```
112
+
113
+ ### Per-Dispatcher Levels
114
+
115
+ ```ts
116
+ let logger = halua.create([
117
+ NewTextDispatcher(sendToFile, { level: Level.Info }),
118
+ NewJSONDispatcher(sendToMetrics, { level: Level.Error }),
119
+ ])
120
+ ```
121
+
122
+ ### Minor / Custom Levels
123
+
124
+ Use the `LEVEL+N` syntax for fine-grained control (e.g. sampling, feature flags):
125
+
126
+ ```ts
127
+ let logger = halua.create(NewTextDispatcher(out), { level: `${Level.Info}+2` })
128
+
129
+ logger.logTo("INFO+1", "sampled out")
130
+ logger.logTo("INFO+2", "important info") // logged
131
+ logger.logTo("INFO+3", "very important") // logged
132
+ logger.logTo("WARN", "always higher major level") // logged
133
+ ```
134
+
135
+ You can also pass string levels directly: `{ level: "ERROR+7" }` or `logTo("DEBUG+10", ...)`.
136
+
137
+ ## Sensitive Data Redaction
138
+
139
+ Pass `redactDataRegExp` (a `RegExp`) to `halua.create(options)` for the logger instance (applies to all its dispatchers) or to individual `New*Dispatcher(..., { redactDataRegExp })` (overrides the logger default).
140
+
141
+ - In strings (and strings inside arrays): all matches of the regexp are replaced by `"^_^"`
142
+ - In objects and Maps: if a _key_ matches the regexp, its value (any type) is replaced entirely by `"^_^"`
143
+ - Works for both text and structured (JSON) output, and for `errorMeta`
144
+ - Use the exported `DefaultRedactRegExp` for common PII (passwords, tokens, api keys, emails, SSNs, JWTs, credit cards, etc.) or provide your own.
145
+
146
+ ```ts
147
+ import { halua, NewJSONDispatcher, DefaultRedactRegExp } from "halua"
148
+
149
+ // logger-level default (affects dispatchers without their own setting)
150
+ let prodLogger = halua.create(
151
+ [
152
+ NewJSONDispatcher(sendToStore),
153
+ NewTextDispatcher(sendToFile, { level: Level.Warn }), // this one can override if needed
154
+ ],
155
+ { redactDataRegExp: DefaultRedactRegExp },
156
+ )
157
+
158
+ prodLogger.info("login", { user: "alice", password: "hunter2", apiKey: "sk_xxx" })
159
+ // args become: ["login", { user: "alice", password: "^_^", apiKey: "^_^" }]
160
+
161
+ prodLogger.info("token eyJhbGciOi...abc.123", "email: foo@bar.com")
162
+ // the string arg will have secrets replaced by ^_^
163
+ ```
164
+
165
+ The `redact` helper is also exported for custom dispatchers or preprocessing.
166
+
167
+ ## Dispatcher Options
168
+
169
+ All `New*Dispatcher` factories accept a second `options` argument:
170
+
171
+ | Option | Type | Default | Description |
172
+ | ------------------ | ------------------------ | ----------- | ---------------------------------------------------------------------------------------- |
173
+ | `level` | `LogLevel` | `undefined` | Minimum level this dispatcher accepts |
174
+ | `exact` | `LogLevel \| LogLevel[]` | `null` | Only log these exact levels (ignores normal hierarchy) |
175
+ | `printTimestamp` | `boolean` | `true` | Include timestamp in output |
176
+ | `printLevel` | `boolean` | `true` | Include level name in output |
177
+ | `spacing` | `boolean` | `true` | Pretty-print objects/arrays with tabs & newlines (Text & JSON only) |
178
+ | `redactDataRegExp` | `RegExp` | `undefined` | Redact sensitive data in strings/arrays and by key in objects/maps (see feature section) |
179
+
180
+ `NewConsoleDispatcher` and `NewConsoleColoredDispatcher` do not support `spacing` (they pass values directly to console methods).
181
+
182
+ ## API Reference
183
+
184
+ ### Main Export
185
+
186
+ ```ts
187
+ import {
188
+ halua,
189
+ Level,
190
+ NewTextDispatcher,
191
+ NewJSONDispatcher,
192
+ NewConsoleDispatcher,
193
+ NewConsoleColoredDispatcher,
194
+ } from "halua"
195
+ ```
196
+
197
+ - `halua` — default logger instance (preconfigured with `NewConsoleDispatcher`)
198
+ - `Level` — enum: `Trace | Debug | Info | Notice | Warn | Error | Fatal`
199
+ - `NewTextDispatcher(send: (line: string, errorMeta?: Record<string, any>) => void, options?)` → factory
200
+ - `NewJSONDispatcher(send: (json: string, errorMeta?: Record<string, any>) => void, options?)` → factory
201
+ - `NewConsoleDispatcher(console: {debug,info,warn,error}, options?)` → factory
202
+ - `NewConsoleColoredDispatcher(console: {debug,info,warn,error}, options?)` → factory (colors levels: TRACE/DEBUG=purple, INFO=blue, NOTICE=orange, WARN/ERROR/FATAL=red; uses ANSI in Node, %c in browsers)
203
+
204
+ ### Advanced Exports (for custom dispatcher authors)
205
+
206
+ ```ts
207
+ import { DispatcherBase, format, getType, toJSONValue, Dispatcher, HaluaLogger, ConsoleLike } from "halua"
208
+ ```
209
+
210
+ - `DispatcherBase` — extendable base class implementing `dispatch(meta, args)` + timestamp/level prefixing; override via
211
+ `formatArg`
212
+ - `format(spec: {type, value, ...})` — the text pretty-printer (handles circulars, Errors, Maps, etc.)
213
+ - `getType(value)` — returns `ArgumentType` discriminant for any JS value
214
+ - `toJSONValue(value)` — converts any value to a JSON-legal tree (Errors → {name,message,stack[]}, etc.)
215
+ - `redact(value, regexp?)` — recursively redacts strings by content match and object/map values by key match (used internally by the redact feature)
216
+ - `DefaultRedactRegExp` — built-in regexp matching common sensitive keys and value patterns (password, token, email, ssn, jwt, cc, etc.)
217
+ - `Dispatcher` — interface for raw custom dispatchers (`dispatch(meta, args): void`)
218
+ - `HaluaLogger` — the logger instance interface
219
+ - `ConsoleLike` — minimal `{ debug, info, warn, error }` shape accepted by `NewConsoleDispatcher` / `NewConsoleColoredDispatcher`
220
+
221
+ ### Logger Instance Methods
222
+
223
+ | Method | Description |
224
+ | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
225
+ | `.create(dispatcher?, options?)` | Create a new independent logger (inherits dispatchers/options when partial) |
226
+ | `.child(...args)` | Create child logger that appends context to every message |
227
+ | `.setDispatchers(dispatcher \| dispatchers[])` | Replace all dispatchers |
228
+ | `.appendDispatchers(...)` | Add more dispatchers to existing set |
229
+ | `.logTo(level, ...args)` | Log at a custom / minor level |
230
+ | `.trace / .debug / .info / .warn / .notice / .fatal(...args)` | Standard levels (varargs) |
231
+ | `.error(error, meta?)` | Log at ERROR level; first arg (unknown) is normalized to Error; optional `meta?: ErrorMeta` (generic on the logger instance) — when supplied, the normalized Error instance is auto-attached under `error` key and the augmented object becomes the second arg to dispatchers |
232
+ | `.assert(condition, error, meta?)` | Log at ERROR only on falsy condition; same error + optional `meta?: ErrorMeta` semantics as `.error` (auto-attaches normalized Error under `error` when meta supplied) |
233
+ | `.stamp(label, id?)` | Start high-res perf timer (`performance.now`); returns ender fn; optional id for `.stampEnd` |
234
+ | `.stampEnd(id)` | End named stamp started with same id on this logger; logs pretty `label took X.XXms` |
235
+
236
+ Every method returns a new `HaluaLogger` when using `.create` / `.child`, so they are fully chainable.
237
+
238
+ `setDispatchers` and `appendDispatchers` mutate the dispatcher list on the **live instance only**. They do not update the blueprint used by later `.create(...)` or `.child(...)` calls on that same logger (those continue to inherit the dispatchers that were supplied when the logger was originally built). If you need a fresh logger with the new set, call `halua.create(newDispatchers)` (or the mutated logger's `.create(newDispatchers)`).
239
+
240
+ ## Error Handling
241
+
242
+ Halua never throws from logging calls. If a dispatcher fails, the error is reported via `console.error` (best-effort)
243
+ and logging continues for other dispatchers.
244
+
245
+ ### Using `errorMeta` with error trackers (Sentry, Rollbar, etc.)
246
+
247
+ The special `.error(unknown, meta?)` and `.assert(condition, unknown, meta?)` methods accept an optional second `meta`
248
+ object. When you use a custom `send` callback with `NewTextDispatcher` (or `NewJSONDispatcher`), this `meta` is
249
+ delivered as the **second argument** to your send function.
250
+
251
+ Halua automatically appends the _normalized Error instance_ (the same one passed to the primary log args) under the
252
+ `error` key of the meta object. This makes it trivial to forward the live `Error` (including `.cause`, custom props,
253
+ and accurate stack) to error trackers instead of a string or plain-object snapshot.
254
+
255
+ This is ideal for attaching correlation IDs, issue keys, user context, or routing hints to your error reporting service
256
+ without polluting the normal log arguments.
257
+
258
+ ```ts
259
+ import * as Sentry from "@sentry/node"
260
+ import { halua, NewTextDispatcher } from "halua"
261
+
262
+ // Human-readable logs via TextDispatcher, while still forwarding
263
+ // rich errorMeta (issueKey, etc.) to your error tracker.
264
+ let errorSink = NewTextDispatcher((line, errorMeta) => {
265
+ if (errorMeta?.issueKey) {
266
+ const err = errorMeta.error
267
+ // Destructure to omit the Error from `extra` (Sentry serializes it nicely on its own)
268
+ const { error: _err, ...context } = errorMeta
269
+ if (err instanceof Error) {
270
+ Sentry.captureException(err, {
271
+ level: "error",
272
+ tags: {
273
+ issueKey: errorMeta.issueKey,
274
+ component: errorMeta.component,
275
+ },
276
+ extra: context,
277
+ })
278
+ } else {
279
+ Sentry.captureMessage(line, { extra: context })
280
+ }
281
+ } else {
282
+ // Fallback: still surface the error even without extra context
283
+ Sentry.captureMessage(line, "error")
284
+ }
285
+ })
286
+
287
+ let logger = halua.create(errorSink, { level: "WARN" })
288
+
289
+ // Normal log — no meta attached (Note that .error will serialize passed string to Error)
290
+ logger.error("something odd happened")
291
+
292
+ // Critical path with traceable issue key — when you supply the second meta arg to .error or .assert,
293
+ // Halua auto-appends the normalized Error under `errorMeta.error` (in addition to your fields).
294
+ // The formatted line stays clean; your send fn receives the live Error + context for captureException.
295
+ logger.error(new Error("Payment declined"), {
296
+ issueKey: "PAY-48291",
297
+ userId: 8472,
298
+ component: "checkout",
299
+ requestId: "req_abc123",
300
+ })
301
+ ```
302
+
303
+ The user-supplied portion of `meta` is never mixed into the formatted `args` (exception: ConsoleDispatcher) — it (plus the auto-attached `error`) is always available as a clean second parameter to your send function.
304
+ When meta is absent, the second argument is `undefined`.
305
+
306
+ ## Advanced / Custom Dispatchers
307
+
308
+ For simple file (or any sink) logging the easiest approach is to use a built-in factory with your own send function:
309
+
310
+ ```ts
311
+ import { halua, NewTextDispatcher } from "halua"
312
+ import fs from "node:fs"
313
+
314
+ let logPath = "app.log"
315
+
316
+ let fileLogger = halua.create(
317
+ NewTextDispatcher((line) => {
318
+ fs.appendFileSync(logPath, line + "\n")
319
+ }),
320
+ )
321
+ ```
322
+
323
+ If you need full control (custom dispatch, different prefixing, rotation, binary framing, remote calls, etc.) extend `DispatcherBase` and use the exported `format` + `getType` (or `toJSONValue`) exactly as the built-ins do:
324
+
325
+ ```ts
326
+ import { halua, DispatcherBase, format, getType, toJSONValue } from "halua"
327
+ import fs from "node:fs"
328
+
329
+ const NewFileDispatcher = (filePath: string) => {
330
+ return () =>
331
+ new (class FileDispatcher extends DispatcherBase {
332
+ constructor() {
333
+ super((line) => {
334
+ fs.appendFileSync(filePath, line + "\n")
335
+ })
336
+ this.formatArg = (v) => format({ type: getType(v), value: v }, true)
337
+ }
338
+ })()
339
+ }
340
+
341
+ let fileLogger = halua.create(NewFileDispatcher("app.log"), { level: "INFO" })
342
+ fileLogger.warn("something happened", { user: 42 })
343
+ ```
344
+
345
+ The `Dispatcher` interface (`dispatch(meta, args)`) + `DispatcherBase` + `format`/`getType`/`toJSONValue` are the public
346
+ extension surface. See `src/main/dispatchers/text-dispatcher.ts`, `json-dispatcher.ts` for reference implementations.
347
+
348
+ **Semver note for custom dispatchers**: `Dispatcher`, `dispatch`, `DispatcherBase`, and the formatter trio are stable
349
+ within a major version. Changes that would break existing custom `Dispatcher` implementations are released only as
350
+ majors and recorded in `docs/dr.md`.
351
+
352
+ For most use cases the three built-in dispatchers are sufficient.
353
+
354
+ ## TypeScript
355
+
356
+ Full TypeScript support included. All types are exported.
357
+
358
+ ## License
359
+
360
+ MIT © [inshinrei](https://github.com/inshinrei)
361
+
362
+ ---
363
+
364
+ **See also:** [Tour of Halua](https://github.com/inshinrei/halua/blob/main/docs/tour-of-halua.md) for a narrative deep dive and decision records in `docs/dr.md`.
package/lib/AGENTS.md ADDED
@@ -0,0 +1,143 @@
1
+ # AGENTS.md
2
+
3
+ > Guidelines for AI coding agents working in projects that depend on the `halua` logging library (when the package is
4
+ > installed from npm).
5
+
6
+ This lightweight version of the contributor guide is automatically placed into `lib/AGENTS.md` during build and shipped
7
+ inside the published npm package. It contains only the information relevant to **consumers** of the library.
8
+
9
+ ## Logging Policy (mandatory)
10
+
11
+ All application code that uses halua **must** route its logs through a halua logger. Never call `console.log`,
12
+ `console.info`, `console.warn`, `console.error`, `console.debug` etc. directly for business or debug logging.
13
+
14
+ - Import the ready-to-use instance: `import { halua } from "halua"`
15
+ - Or create purpose-built loggers:
16
+ `import { halua, NewTextDispatcher, NewJSONDispatcher, NewConsoleDispatcher, NewConsoleColoredDispatcher, Level } from "halua"`
17
+ - Use `.error(err)` **only** for `Error` instances (or subclasses). For plain messages use a regular level or
18
+ `halua.error("message string")` — the implementation normalizes unknown values.
19
+ - Always prefer structured data as subsequent arguments: `logger.info("user action", { userId, action })`
20
+
21
+ Example (correct usage):
22
+
23
+ ```ts
24
+ import { halua } from "halua"
25
+
26
+ try {
27
+ await doSomething()
28
+ } catch (err) {
29
+ if (err instanceof Error) {
30
+ halua.error(err, { userId: 42, route: "/checkout" }) // meta goes to error trackers via custom dispatcher
31
+ } else {
32
+ halua.warn("non-error failure", err)
33
+ }
34
+ }
35
+ ```
36
+
37
+ ## Public API & Common Patterns (for AI agents)
38
+
39
+ ### Core exports
40
+
41
+ ```ts
42
+ import {
43
+ halua, // default Console-backed logger instance
44
+ Level, // Trace | Debug | Info | Notice | Warn | Error | Fatal
45
+ NewTextDispatcher,
46
+ NewJSONDispatcher,
47
+ NewConsoleDispatcher,
48
+ NewConsoleColoredDispatcher,
49
+ // advanced (for custom dispatchers)
50
+ DispatcherBase,
51
+ Dispatcher,
52
+ HaluaLogger,
53
+ ConsoleLike,
54
+ format,
55
+ getType,
56
+ toJSONValue,
57
+ redact,
58
+ DefaultRedactRegExp,
59
+ } from "halua"
60
+ ```
61
+
62
+ ### Creating dedicated loggers
63
+
64
+ ```ts
65
+ let fileLogger = halua.create(
66
+ NewTextDispatcher((line, errorMeta) => fs.appendFileSync("app.log", line + "\n")),
67
+ { level: Level.Info, redactDataRegExp: DefaultRedactRegExp },
68
+ )
69
+
70
+ let jsonLogger = halua.create(NewJSONDispatcher(sendToElastic))
71
+ ```
72
+
73
+ You can pass an array for multiple dispatchers on the same logger.
74
+
75
+ ### Child contexts (recommended for request/trace scoping)
76
+
77
+ ```ts
78
+ let reqLogger = halua.child("requestId", reqId, "user", userId)
79
+ reqLogger.info("starting work")
80
+ // output will contain the context pairs automatically
81
+ ```
82
+
83
+ ### Minor levels & sampling
84
+
85
+ ```ts
86
+ logger.logTo("INFO+3", "very important event")
87
+ halua.create(dispatcher, { level: "DEBUG+2" })
88
+ ```
89
+
90
+ ### Redaction of secrets
91
+
92
+ Use `DefaultRedactRegExp` (covers password, token, apiKey, email, ssn, jwt, credit-card patterns, etc.) or supply your
93
+ own RegExp. Works for both text and JSON paths and for `errorMeta`.
94
+
95
+ ### Timing with stamps
96
+
97
+ ```ts
98
+ let end = logger.stamp("database query")
99
+ await db.query(...)
100
+ end() // logs: ... database query took 12.34ms
101
+ ```
102
+
103
+ ### Error handling contract
104
+
105
+ Halua **never** throws from any logging call. Dispatcher failures are caught and reported via a best-effort
106
+ `console.error` (the original error is not lost). Your send functions can safely throw; they will be isolated.
107
+
108
+ ### Custom dispatchers (advanced)
109
+
110
+ For most cases the built-in factories + your own `send` callback are sufficient.
111
+
112
+ When you truly need different formatting, framing, rotation, remote transport, etc., extend `DispatcherBase`:
113
+
114
+ ```ts
115
+ import { halua, DispatcherBase, format, getType } from "halua"
116
+ import fs from "node:fs"
117
+
118
+ const NewFileDispatcher = (path: string) => () =>
119
+ new (class extends DispatcherBase {
120
+ constructor() {
121
+ super((line) => fs.appendFileSync(path, line + "\n"))
122
+ this.formatArg = (v) => format({ type: getType(v), value: v }, true)
123
+ }
124
+ })()
125
+ ```
126
+
127
+ See the implementations of `NewTextDispatcher` / `NewJSONDispatcher` (inside the package under `lib/` or
128
+ `src/main/dispatchers/`) for the exact pattern.
129
+
130
+ `Dispatcher`, `DispatcherBase`, `format`, `getType`, and `toJSONValue` are the stable public surface for extension
131
+ authors. Breaking changes to them are only done in major releases.
132
+
133
+ ## When in doubt
134
+
135
+ - Start with the default `halua` or `halua.create(New*Dispatcher(yourSendFn))`.
136
+ - Use child loggers for context instead of manually prefixing strings.
137
+ - Let halua do the formatting — feed it rich JS values (Errors, Maps, Dates, circular structures are all handled).
138
+ - Read the package README.md (shipped alongside this file) for the complete reference.
139
+
140
+ ---
141
+
142
+ Follow the logging policy strictly. When the public API or recommended usage patterns change, the source of truth for
143
+ this file (`agents-for-module.md` in the repo) must be updated together with README.md and docs/tour-of-halua.md.