syntropylog 0.11.1 → 0.11.3

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 (3) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +287 -472
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Refresh logo: README now points to syntropysoft.com/syntropylog-logo.png.
8
+
9
+ ## 0.11.2
10
+
11
+ ### Patch Changes
12
+
13
+ - Docs: README overhaul — full picture table (14 features), init-as-Promise pattern, and a "How" section per feature so users can see what the library does and how to use it. Serialization timeout example set to 100ms (50–100ms recommended). Aligned with doc-es/caracteristicas-y-ejemplos.md.
14
+
3
15
  ## 0.11.1
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # SyntropyLog
2
2
 
3
3
  <p align="center">
4
- <img src="https://raw.githubusercontent.com/Syntropysoft/SyntropyLog/main/assets/beaconLog-2.png" alt="SyntropyLog Logo" width="170"/>
4
+ <img src="https://syntropysoft.com/syntropylog-logo.png" alt="SyntropyLog Logo" width="170"/>
5
5
  </p>
6
6
 
7
7
  <h1 align="center">SyntropyLog</h1>
@@ -17,640 +17,455 @@
17
17
  <a href="https://github.com/Syntropysoft/SyntropyLog/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/syntropylog.svg" alt="License"></a>
18
18
  <a href="https://github.com/Syntropysoft/SyntropyLog/actions/workflows/ci.yaml"><img src="https://github.com/Syntropysoft/SyntropyLog/actions/workflows/ci.yaml/badge.svg" alt="CI Status"></a>
19
19
  <a href="#"><img src="https://img.shields.io/badge/coverage-95.13%25-brightgreen" alt="Test Coverage"></a>
20
- <a href="#"><img src="https://img.shields.io/badge/status-v0.11.0-brightgreen.svg" alt="Version 0.11.0"></a>
20
+ <a href="#"><img src="https://img.shields.io/badge/status-v0.11.3-brightgreen.svg" alt="Version 0.11.3"></a>
21
21
  <a href="https://socket.dev/npm/package/syntropylog"><img src="https://socket.dev/api/badge/npm/package/syntropylog" alt="Socket Badge"></a>
22
22
  </p>
23
23
 
24
24
  ---
25
25
 
26
- ## 🎯 What is SyntropyLog?
26
+ ## What is SyntropyLog?
27
27
 
28
- SyntropyLog is a **structured observability framework** for Node.js built from scratch, designed around flexibility and control.
28
+ SyntropyLog is a **structured observability framework** for Node.js. You declare what your logs should carry (context, level-based fields, retention, masking), and SyntropyLog makes it happen everywhere—automatically. No manual plumbing, no hidden behavior.
29
29
 
30
- The core idea is simple: **you declare what your logs should carry, and SyntropyLog makes it happen everywhere, automatically.** No manual plumbing, no scattered configuration, no hidden behavior.
30
+ It is built for **high demand** and **regulated environments** (banking, healthcare, financial services): HIPAA-style field control via the Logging Matrix, SOX-style audit trails via `withRetention`, and a pipeline that never lets logging crash your app.
31
31
 
32
- That means:
33
- - A **declarative Logging Matrix** that controls exactly which context fields appear at each log level — lean on `info`, full context on `error`.
34
- - A **fluent logger API** (`withRetention`, `withSource`, `withTransactionId`) that lets you create specialized loggers carrying arbitrary organization-defined metadata.
35
- - **Add, remove, or override transports on demand** — per log call you can send only to specific transports (`.override()`), add extra destinations (`.add()`), or drop one (`.remove()`), without creating new logger instances.
36
- - A **MaskingEngine** that redacts sensitive fields before they reach any transport — built-in strategies and fully custom rules.
37
- - An **Intelligent Serialization Pipeline** that automatically detects and neutralizes circular references, limits object depth, and enforces execution timeouts — making logs immune to application crashes.
38
- - A **UniversalAdapter** that routes logs to any backend (PostgreSQL, MongoDB, Elasticsearch, S3) via a single `executor` function — no coupling, no lock-in.
39
- - A **SanitizationEngine** that strips control characters from all log output, preventing log injection attacks.
40
- - **Lightning Speed**: Optimized core pipeline delivering **~1,000,000 ops/s** even with full masking and context.
41
-
42
- **This is not just another logger. It's about giving your team full declarative control over observability data with industry-leading performance and reliability.**
43
-
44
- ### Built for regulated industries
32
+ ---
45
33
 
46
- SyntropyLog was designed with the constraints of **banking, healthcare, and financial services** in mind:
34
+ ## Full picture what's in the box
35
+
36
+ Everything below is part of the same stack (benchmarks use this full stack). Each item has a **How** section in this README so you can see how to use it.
37
+
38
+ | # | Feature | What it does |
39
+ |---|--------|---------------|
40
+ | 1 | **Native addon (Rust)** | Single-pass serialize + mask + sanitize; ANSI strip. Falls back to JS if unavailable. |
41
+ | 2 | **Logging Matrix** | Declarative control of which context fields appear per level (lean on `info`, full on `error`). |
42
+ | 3 | **Universal Adapter** | Send logs to any backend (PostgreSQL, MongoDB, Elasticsearch, S3) via one `executor` function. |
43
+ | 4 | **MaskingEngine** | Redact sensitive fields before any transport; built-in + custom rules. |
44
+ | 5 | **Serialization pipeline** | Circular refs, depth limit, timeout; logging never blocks the event loop. |
45
+ | 6 | **SanitizationEngine** | Strip control characters; log injection resistant. |
46
+ | 7 | **Context / headers** | Correlation ID and transaction ID from config; single source of truth. |
47
+ | 8 | **Fluent API** | `withRetention`, `withSource`, `withTransactionId` — bind once, carry on every log. |
48
+ | 9 | **Per-call transport control** | `.override()`, `.add()`, `.remove()` for one log call without new logger instances. |
49
+ | 10 | **Audit & retention** | `audit` level (always logged); `withRetention(anyJson)` for compliance routing. |
50
+ | 11 | **Lifecycle** | `init()` / `shutdown()`; graceful flush on SIGTERM/SIGINT. |
51
+ | 12 | **Observability hooks** | `onLogFailure`, `onTransportError`, `onSerializationFallback`, etc.; `isNativeAddonInUse()`. |
52
+ | 13 | **Matrix in runtime** | `reconfigureLoggingMatrix()` without restart; only field visibility, not security. |
53
+ | 14 | **Tree-shaking** | `sideEffects: false` + ESM; bundle only what you import. |
54
+
55
+ **More detail and examples (ES):** [doc-es/caracteristicas-y-ejemplos.md](doc-es/caracteristicas-y-ejemplos.md).
47
56
 
48
- - **HIPAA**: Field-level control over what appears in logs at each severity level via the Logging Matrix.
49
- - **SOX**: Immutable audit trail via `withRetention` bindings and dedicated transports.
57
+ ---
50
58
 
51
- ### Tree-shaking friendly
59
+ ## Quick Start
52
60
 
53
- We ship with `sideEffects: false` and ESM so bundlers (Vite, Rollup, webpack, esbuild) can tree-shake unused code. What ends up in your app is only what you import.
61
+ ### Install
54
62
 
55
- ---
63
+ ```bash
64
+ npm install syntropylog
65
+ ```
56
66
 
57
- ## 🛡️ Why SyntropyLog? (The Resilience Factor)
67
+ Prebuilt native addon (Rust) for Linux, Windows, macOS installs automatically on Node ≥20. If unavailable, the JS pipeline is used transparently.
58
68
 
59
- Traditional loggers (and even modern ones) share a common weakness: **serialization is a blocking operation**. If you log a massive, deeply nested, or circular object, the Node.js Event Loop stops. Your API stops responding. Your service might even crash with a `TypeError`.
69
+ ### Init and first log
60
70
 
61
- SyntropyLog v0.11.0 introduces the **Log Resilience Engine**, making your application immune to "Death by Log":
71
+ **Initialization is a Promise that must resolve before you can print logs.** Until it resolves (on the `ready` event), `getLogger()` returns a no-op logger that drops all messages. Listen for `ready` and `error` *before* calling `init()`.
62
72
 
63
- 1. **Event Loop Protection**: Every serialization step is wrapped in a mandatory timeout (default: **50ms**). If serialization takes too long, it is aborted via `Promise.race`, and a safe subset of the data is logged instead. Your app keeps running.
64
- 2. **Circular Reference Immunity**: Built-in hygiene automatically detects and neutralizes circular references. No more `TypeError: Converting circular structure to JSON`.
65
- 3. **Configurable Safety Limits**: Every object is protected by a double-guard:
66
- - `serializationMaxDepth` (default: **10**): Automatically truncates objects deeper than this to prevent Stack Overflow.
67
- - `serializationMaxBreadth` (default: **100**): Limits arrays and object keys to prevent Event Loop blockage on massive structures.
68
- 4. **Silent Observer**: Logging should never throw. Our pipeline catches and reports its own failures inside the log message itself, ensuring 100% reliability.
69
- 5. **Lightning Pipeline**: Consolidation of serialization, masking, and sanitization into a single recursive pass, reaching Pino-level efficiency with Enterprise-level security.
73
+ ```typescript
74
+ import { syntropyLog } from 'syntropylog';
70
75
 
71
- > [!WARNING]
72
- > **Pathological Object Protection**: When an object exceeds the configured Depth or Breadth, it will be automatically truncated (e.g., `[Max Depth reached]` or `[Truncated 500 items]`). This is a feature, not a bug — it ensures your application survives even when buggy code tries to log half the database!
76
+ async function initializeSyntropyLog() {
77
+ return new Promise<void>((resolve, reject) => {
78
+ syntropyLog.on('ready', () => resolve());
79
+ syntropyLog.on('error', (err) => reject(err));
80
+ syntropyLog.init({
81
+ logger: { level: 'info', serviceName: 'my-app' },
82
+ });
83
+ });
84
+ }
73
85
 
74
- ---
86
+ async function main() {
87
+ await initializeSyntropyLog(); // promise must resolve before any log
88
+ const log = syntropyLog.getLogger();
89
+ log.info('Hello, SyntropyLog.');
90
+ }
91
+ main();
92
+ ```
75
93
 
76
- ## 🚀 Quick Start (60 seconds)
94
+ ### Graceful shutdown
77
95
 
78
- ### **1. Install**
79
- ```bash
80
- npm install syntropylog
96
+ ```typescript
97
+ process.on('SIGTERM', async () => {
98
+ await syntropyLog.shutdown();
99
+ process.exit(0);
100
+ });
101
+ process.on('SIGINT', async () => {
102
+ await syntropyLog.shutdown();
103
+ process.exit(0);
104
+ });
81
105
  ```
82
106
 
83
- For the best performance, the package includes **prebuilt native addon binaries (Rust)** for Linux, Windows, and macOS; they install automatically on supported platforms. If the addon is unavailable (e.g. unsupported Node version or platform), the framework falls back to the JS pipeline transparently.
107
+ ### Framework entry points
108
+
109
+ | Framework | Where to call `await init` |
110
+ |----------|----------------------------|
111
+ | Express / Fastify | Before `app.listen()` in server entry |
112
+ | NestJS | `AppModule.onModuleInit()` or before `app.listen()` in `bootstrap()` |
113
+ | Lambda / Serverless | Module-level lazy singleton (outside the handler) |
84
114
 
85
- ### **Available Console Transports**
115
+ ---
86
116
 
87
- By default, SyntropyLog outputs **lightweight plain JSON to the console — automatically, with no configuration needed**. No imports, no setup, no extra dependencies.
117
+ ## 1. Native addon (Rust)
88
118
 
89
- If you want **colored, human-readable output** for development, use one of the pretty console transports. Colours use **built-in ANSI codes** (no chalk or extra dependency). When stdout is a TTY you get colours; when piping or in CI, or if `NO_COLOR` is set, the same format is shown in plain text.
119
+ **What:** Optional Rust addon does serialize + mask + sanitize in one pass. Used automatically when available; no config. Disable with `SYNTROPYLOG_NATIVE_DISABLE=1` (e.g. debugging).
90
120
 
91
- | Transport | Style | Recommended for |
92
- | :--- | :--- | :--- |
93
- | *(default)* | Plain JSON | Production / log aggregators |
94
- | `ClassicConsoleTransport` | Structured single-line, colored | Development |
95
- | `PrettyConsoleTransport` | Human-readable pretty, colored | Development / debugging |
96
- | `CompactConsoleTransport` | Compact one-liner, colored | Development |
97
- | `ColorfulConsoleTransport` | Full-line colored | Development |
121
+ **How:** Nothing to configure. Check at runtime:
98
122
 
99
123
  ```typescript
100
- // Default — no import needed, works out of the box
101
- syntropyLog.init({ logger: { level: 'info', serviceName: 'my-app' } });
124
+ if (syntropyLog.isNativeAddonInUse()) {
125
+ // Rust pipeline active
126
+ }
127
+ ```
102
128
 
103
- // Pretty + colors (built-in ANSI; no extra deps)
104
- import { ClassicConsoleTransport } from 'syntropylog';
129
+ See [doc-es/building-native-addon.es.md](doc-es/building-native-addon.es.md) for building from source.
105
130
 
106
- syntropyLog.init({
107
- logger: {
108
- level: 'info',
109
- serviceName: 'my-app',
110
- transports: [new ClassicConsoleTransport()],
111
- },
112
- });
113
- ```
131
+ ---
114
132
 
115
- ### **Transport pool and per-environment routing**
133
+ ## 2. Logging Matrix
116
134
 
117
- You can define a **named pool of transports** (`transportList`) and choose **per environment** which ones are active (`env`). For a single log call you can override, add, or remove destinations with `.override()`, `.add()`, and `.remove()`.
135
+ **What:** A JSON contract that defines exactly which context fields appear at each log level. If a field isn’t in the matrix for that level, it never appears in the output.
118
136
 
119
- ```typescript
120
- import { syntropyLog, ColorfulConsoleTransport, AdapterTransport, UniversalAdapter } from 'syntropylog';
121
-
122
- const mockToConsole = (label: string) =>
123
- new AdapterTransport({
124
- name: label,
125
- adapter: new UniversalAdapter({
126
- executor: (data) => console.log(`[${label}]`, JSON.stringify(data)),
127
- }),
128
- });
137
+ **How:** Set `loggingMatrix` in `init()`:
129
138
 
139
+ ```typescript
130
140
  await syntropyLog.init({
131
- logger: {
132
- envKey: 'NODE_ENV',
133
- transportList: {
134
- consola: new ColorfulConsoleTransport({ name: 'consola' }),
135
- db: mockToConsole('db'),
136
- azure: mockToConsole('azure'),
137
- archivo: mockToConsole('archivo'),
138
- },
139
- env: {
140
- development: ['consola'],
141
- staging: ['consola', 'archivo', 'azure'],
142
- production: ['consola', 'db', 'azure'],
143
- },
141
+ logger: { level: 'info', serviceName: 'my-app' },
142
+ loggingMatrix: {
143
+ default: ['correlationId'],
144
+ info: ['correlationId', 'userId', 'operation'],
145
+ warn: ['correlationId', 'userId', 'operation', 'errorCode'],
146
+ error: ['correlationId', 'userId', 'operation', 'errorCode', 'tenantId', 'orderId'],
147
+ fatal: ['*'], // all context fields
144
148
  },
145
- redis: { instances: [] },
146
149
  });
147
-
148
- const log = syntropyLog.getLogger('app');
149
- log.info('default according to env');
150
- log.override('consola').info('only to console');
151
- log.remove('db').add('archivo').info('default minus db, plus file');
152
150
  ```
153
151
 
154
- **Full guide and runnable example:** see [examples/TRANSPORT_POOL_AND_ENV.md](examples/TRANSPORT_POOL_AND_ENV.md) and run `examples/TransportPoolExample.ts` to see it in action.
152
+ | Matrix key | Resolves to context |
153
+ |------------|---------------------|
154
+ | `correlationId` | `x-correlation-id`, `correlationId` |
155
+ | `transactionId` | `x-trace-id`, `transactionId` |
156
+ | `userId`, `tenantId`, `operation`, `errorCode`, `orderId`, `paymentId`, `eventType` | same key |
157
+ | `*` | All context fields |
155
158
 
156
159
  ---
157
160
 
158
- ```typescript
159
- import { syntropyLog } from 'syntropylog';
160
- import { ClassicConsoleTransport } from 'syntropylog';
161
+ ## 3. Universal Adapter — log to any backend
161
162
 
162
- const config = {
163
- logger: {
164
- level: 'info',
165
- serviceName: 'ecommerce-app',
166
- transports: [new ClassicConsoleTransport()],
167
- },
168
- context: {
169
- correlationIdHeader: 'X-Correlation-ID', // defines the header name used everywhere
170
- },
171
- };
172
-
173
- // Always wait for the 'ready' event — see the Critical section below
174
- async function initializeSyntropyLog() {
175
- return new Promise<void>((resolve, reject) => {
176
- syntropyLog.on('ready', () => resolve());
177
- syntropyLog.on('error', (err) => reject(err));
178
- syntropyLog.init(config);
179
- });
180
- }
163
+ **What:** Send each log to PostgreSQL, MongoDB, Elasticsearch, S3, etc. by implementing a single `executor`. No vendor lock-in; you receive one structured object per log (already masked).
181
164
 
182
- await initializeSyntropyLog();
165
+ **How:** Use `AdapterTransport` + `UniversalAdapter`:
183
166
 
184
- // Safe to use from here on
185
- const logger = syntropyLog.getLogger();
186
- logger.info('System is clean and ready.');
167
+ ```typescript
168
+ import { AdapterTransport, UniversalAdapter } from 'syntropylog';
169
+
170
+ const dbTransport = new AdapterTransport({
171
+ name: 'db',
172
+ adapter: new UniversalAdapter({
173
+ executor: async (logEntry) => {
174
+ await prisma.systemLog.create({
175
+ data: {
176
+ level: logEntry.level,
177
+ message: logEntry.message,
178
+ serviceName: logEntry.serviceName,
179
+ correlationId: logEntry.correlationId,
180
+ payload: logEntry.meta,
181
+ timestamp: new Date(logEntry.timestamp),
182
+ },
183
+ });
184
+ },
185
+ }),
186
+ });
187
187
  ```
188
188
 
189
- ### **Optional error and fallback hooks**
189
+ `logEntry` has: `level`, `message`, `serviceName`, `correlationId`, `timestamp`, `meta`. If `executor` throws, SyntropyLog logs the error and continues (Silent Observer).
190
190
 
191
- You can pass optional callbacks in the config for observability (logging never throws; these hooks let you observe failures):
191
+ ---
192
192
 
193
- | Hook | When it is called |
194
- |------|--------------------|
195
- | `onLogFailure?(error, entry)` | When a log call fails (serialization or transport error). |
196
- | `onTransportError?(error, context)` | When a transport fails (flush, shutdown, or log write). `context` is `'flush'`, `'shutdown'`, or `'log'`. |
197
- | `onSerializationFallback?(reason)` | When the native addon is used but fails for a call and the framework falls back to the JS pipeline. |
198
- | `onStepError?(step, error)` | When a pipeline step fails (e.g. hygiene). |
199
- | `masking.onMaskingError?(error)` | When masking fails (e.g. timeout); never receives raw payload. |
193
+ ## 4. MaskingEngine
200
194
 
201
- Use `onSerializationFallback` to detect when the native addon failed for a given call and the JS pipeline was used instead (e.g. for metrics or alerting). You can also call `syntropyLog.isNativeAddonInUse()` at runtime to check if the Rust addon is loaded.
195
+ **What:** Redacts sensitive fields before logs reach any transport. Built-in rules (password, email, token, card, SSN, phone) plus custom rules by name/regex.
202
196
 
203
- Example: `syntropyLog.init({ onLogFailure: (err) => metrics.increment('log_failures'), ... });`
197
+ **How:** Configure `masking` in `init()`:
204
198
 
205
- ### **3. Graceful Shutdown (Essential)**
206
199
  ```typescript
207
- async function gracefulShutdown() {
208
- await syntropyLog.shutdown();
209
- }
200
+ import { MaskingStrategy } from 'syntropylog';
210
201
 
211
- process.on('SIGTERM', async () => { await gracefulShutdown(); process.exit(0); });
212
- process.on('SIGINT', async () => { await gracefulShutdown(); process.exit(0); });
202
+ await syntropyLog.init({
203
+ logger: { ... },
204
+ masking: {
205
+ enableDefaultRules: true,
206
+ maskChar: '*',
207
+ preserveLength: true,
208
+ rules: [
209
+ {
210
+ pattern: /cuit|cuil/i,
211
+ strategy: MaskingStrategy.CUSTOM,
212
+ customMask: (value) => value.replace(/\d(?=\d{4})/g, '*'),
213
+ },
214
+ ],
215
+ },
216
+ });
213
217
  ```
214
218
 
215
- ---
219
+ | Strategy | Example output |
220
+ |----------|----------------|
221
+ | PASSWORD | `********` |
222
+ | EMAIL | `j***@example.com` |
223
+ | TOKEN | `eyJh...a1B9c` |
224
+ | CREDIT_CARD | `****-****-****-1234` |
216
225
 
217
- ## ⚠️ Critical: Await Initialization Before Use
226
+ If masking fails, the pipeline does not throw (Silent Observer). Custom rules: use ReDoS-safe regex; keys longer than 256 chars are skipped.
218
227
 
219
- > **SyntropyLog MUST be fully initialized before any call to `getLogger()`, `getRedis()`, or any other resource.** Calling these methods before initialization completes will silently produce a no-op logger that **drops all messages**.
228
+ ---
220
229
 
221
- ### Why this matters
230
+ ## 5. Serialization pipeline (resilience)
222
231
 
223
- `syntropyLog.init()` bootstraps the internal pipeline, connects managed resources (Redis, brokers), and wires up serialization and masking layers asynchronously. Until the `ready` event fires, there is no active logger.
232
+ **What:** Prevents "death by log": circular refs are neutralized, depth is limited (default 10 → `[MAX_DEPTH_REACHED]`), and a configurable timeout via `serializerTimeoutMs` aborts long serialization so the event loop keeps running. For most apps 50–100ms is enough; the library default is higher.
224
233
 
225
- ### Anti-pattern fire-and-forget init
234
+ **How:** Set timeout in logger config (e.g. 50–100ms):
226
235
 
227
236
  ```typescript
228
- syntropyLog.init(config); // ← not awaited
229
-
230
- const logger = syntropyLog.getLogger();
231
- logger.info('This message is silently dropped.'); // ← init not complete yet
232
- ```
233
-
234
- ### ✅ Correct pattern — event-based initialization
235
-
236
- ```typescript
237
- async function initializeSyntropyLog() {
238
- return new Promise<void>((resolve, reject) => {
239
- syntropyLog.on('ready', () => resolve()); // ← wait for this
240
- syntropyLog.on('error', (err) => reject(err));
241
- syntropyLog.init(config);
242
- });
237
+ logger: {
238
+ serviceName: 'my-app',
239
+ serializerTimeoutMs: 100, // optional; e.g. 50–100ms for most apps
243
240
  }
244
-
245
- await initializeSyntropyLog(); // ← nothing runs before this resolves
246
- const logger = syntropyLog.getLogger();
247
- logger.info('System initialized and ready.');
248
241
  ```
249
242
 
250
- ### Framework entry points
251
-
252
- | Framework | Bootstrap location |
253
- | :--- | :--- |
254
- | **Express / Fastify** | Call `await initializeSyntropyLog()` before `app.listen()` in `server.ts` / `main.ts` |
255
- | **NestJS** | Inside `AppModule.onModuleInit()` or in `bootstrap()` before `app.listen()` |
256
- | **Lambda / Serverless** | Module-level block (outside the handler), using a lazy singleton pattern |
243
+ Logging never throws; failures are reported inside the log payload.
257
244
 
258
245
  ---
259
246
 
260
- ## 🔗 Context: Automatic Correlation ID Propagation
247
+ ## 6. SanitizationEngine
248
+
249
+ **What:** Strips control characters and ANSI from string values before any transport writes. Reduces log injection risk.
261
250
 
262
- > Context management is powered by Node.js `AsyncLocalStorage`. Context is **not global** — it only exists inside `contextManager.run()` blocks. In production, SyntropyLog's adapters create it automatically for every request.
251
+ **How:** No configuration; it runs inside the pipeline. Together with the Logging Matrix (whitelist), it forms the safety boundary for log content.
263
252
 
264
- ### How it works
253
+ ---
265
254
 
266
- The `correlationIdHeader` you set in `context` config is the **single source of truth** it's the header name read from incoming requests and propagated to all outgoing calls and logs.
255
+ ## 7. Contextcorrelation ID and transaction ID
267
256
 
268
- ```typescript
269
- // Defined once in init()
270
- context: {
271
- correlationIdHeader: 'X-Correlation-ID',
272
- }
273
- ```
257
+ **What:** One config defines header names; correlation and transaction IDs propagate to all logs and operations inside the same context (e.g. one request).
274
258
 
275
- ### The Context Wrapper
259
+ **How:** Set `context` in `init()` and use a small middleware:
276
260
 
277
261
  ```typescript
278
- // ❌ WITHOUT context — no correlationId in logs
279
- logger.info('User logged in');
280
-
281
- // ✅ WITH context — correlationId flows automatically to every log
282
- await contextManager.run(async () => {
283
- logger.info('User logged in'); // correlationId attached automatically
262
+ await syntropyLog.init({
263
+ context: {
264
+ correlationIdHeader: 'X-Correlation-ID',
265
+ transactionIdHeader: 'X-Transaction-ID',
266
+ },
284
267
  });
285
- ```
286
-
287
- ### 🔮 The Magic Middleware (2 Lines of Code)
288
-
289
- Add this **once** to your Express/Fastify app and never think about Correlation IDs again:
290
268
 
291
- ```typescript
269
+ // Express/Fastify middleware (once per app)
270
+ const { contextManager } = syntropyLog;
292
271
  app.use(async (req, res, next) => {
293
272
  await contextManager.run(async () => {
294
- // 🎯 MAGIC: Just 2 lines!
295
- const correlationId = contextManager.getCorrelationId(); // Detects incoming or generates new
296
- contextManager.set(contextManager.getCorrelationIdHeaderName(), correlationId); // Sets in context
297
-
273
+ const correlationId = contextManager.getCorrelationId();
274
+ contextManager.set(contextManager.getCorrelationIdHeaderName(), correlationId);
298
275
  next();
299
276
  });
300
277
  });
301
278
  ```
302
279
 
303
- Why this is marvelous:
304
-
305
- - **Intelligent Detection**: `getCorrelationId()` uses the existing ID from the incoming request or generates a new one
306
- - **Automatic Configuration**: `getCorrelationIdHeaderName()` reads your `context.correlationIdHeader` config — change it once, updated everywhere
307
- - **Automatic Propagation**: Once set, it propagates to all logs and operations
280
+ After that, every `logger.info(...)` inside the request carries the same `correlationId` without passing it manually.
308
281
 
309
- ### In Real Applications
310
-
311
- In production, context is created automatically by SyntropyLog's built-in adapters (no manual `run()` needed):
312
-
313
- - HTTP middleware (Express, Fastify, Koa)
314
- - Message queue handlers (Kafka, RabbitMQ)
315
- - Background job processors / API gateways
282
+ ---
316
283
 
317
- | Scenario | Pattern |
318
- | :--- | :--- |
319
- | **Examples & quick tests** | Wrap logging in `contextManager.run()` manually |
320
- | **Production apps** | Use SyntropyLog's HTTP/broker adapters — context is automatic |
284
+ ## 8. Fluent API — withRetention, withSource, withTransactionId
321
285
 
322
- ---
286
+ **What:** Builders that return new loggers with bound metadata: `withSource('ModuleName')`, `withTransactionId('txn-123')`, `withRetention({ policy: 'SOX', years: 5 })`. Every log from that logger carries that data.
323
287
 
324
- ## 🎯 What Your Logs Look Like
288
+ **How:**
325
289
 
326
- The **Logging Matrix** lets you control exactly how much data appears per log level — lean on success, rich on failure:
290
+ ```typescript
291
+ const log = syntropyLog.getLogger();
327
292
 
328
- **INFO (success path)** — minimal cost:
329
- ```
330
- 12:56:00 [INFO] (ecommerce-app): User request processed { status: 'completed', duration: '150ms' }
331
- { "userId": 123, "operation": "user-login" }
332
- ```
293
+ const auditLogger = log
294
+ .withSource('PaymentService')
295
+ .withRetention({ policy: 'SOX_AUDIT_TRAIL', years: 5 });
333
296
 
334
- **ERROR (failure path)** full context for debugging:
335
- ```
336
- 12:56:00 [ERROR] (ecommerce-app): User request processed { status: 'completed', duration: '150ms' }
337
- {
338
- "userId": 123, "email": "user@example.com", "password": "***MASKED***",
339
- "firstName": "John", "ipAddress": "127.0.0.1",
340
- "sessionId": "sess-789", "requestId": "req-456"
341
- }
297
+ auditLogger.info({ userId: 123, action: 'payment' }, 'Payment processed');
298
+ // Entry includes source and retention; your executor can route by retention.policy
342
299
  ```
343
300
 
344
- > Same log call, different information per level. Success logs are lean and cheap; error logs have all the context you need to debug — with sensitive fields always masked.
301
+ | Builder | Binds |
302
+ |---------|--------|
303
+ | `withSource('X')` | `source: 'X'` |
304
+ | `withTransactionId('id')` | `transactionId: 'id'` |
305
+ | `withRetention({ ... })` | `retention: { ... }` (any JSON) |
306
+ | `child({ k: v })` | arbitrary key-value |
345
307
 
346
308
  ---
347
309
 
348
- ## 📋 Logging Matrix — Declarative Field Control
349
-
350
- The **Logging Matrix** is a JSON contract that defines *exactly* which context fields appear in each log level. It's lightweight, powerful, and provides a strong security guarantee: **if a field isn't in the matrix, it can't appear in the log output — no matter what's in the context.**
310
+ ## 9. Per-call transport control
351
311
 
352
- ### Why this matters
312
+ **What:** For a single log call you can send only to specific transports (`.override()`), add destinations (`.add()`), or remove one (`.remove()`), without creating new logger instances.
353
313
 
354
- Without it, any field stored in the `AsyncLocalStorage` context would flow into every log. With it, you have an explicit whitelist per level:
314
+ **How:** Define a transport pool with `transportList` and `env`, then use the fluent methods on the next call only:
355
315
 
356
316
  ```typescript
357
317
  await syntropyLog.init({
358
- logger: { ... },
359
- context: { ... },
360
- loggingMatrix: {
361
- // Always include these in every level (unless overridden below)
362
- default: ['correlationId'],
363
-
364
- // info: lean — just what you need to understand the happy path
365
- info: ['correlationId', 'userId', 'operation'],
366
-
367
- // warn: a bit more context to understand what triggered the warning
368
- warn: ['correlationId', 'userId', 'operation', 'errorCode'],
369
-
370
- // error/fatal: everything — full context to debug the failure
371
- error: ['correlationId', 'userId', 'operation', 'errorCode', 'tenantId', 'orderId'],
372
- fatal: ['*'], // wildcard: include ALL context fields
318
+ logger: {
319
+ envKey: 'NODE_ENV',
320
+ transportList: {
321
+ consola: new ColorfulConsoleTransport({ name: 'consola' }),
322
+ db: dbTransport,
323
+ azure: azureTransport,
324
+ },
325
+ env: {
326
+ development: ['consola'],
327
+ production: ['consola', 'db', 'azure'],
328
+ },
373
329
  },
374
330
  });
375
- ```
376
331
 
377
- Available named fields mapped by the engine:
378
-
379
- | Matrix key | Context key(s) it resolves |
380
- | :--- | :--- |
381
- | `correlationId` | `x-correlation-id`, `correlationId` |
382
- | `transactionId` | `x-trace-id`, `transactionId` |
383
- | `userId` | `userId` |
384
- | `tenantId` | `tenantId` |
385
- | `operation` | `operation` |
386
- | `errorCode` | `errorCode` |
387
- | `orderId` | `orderId` |
388
- | `paymentId` | `paymentId` |
389
- | `eventType` | `eventType` |
390
- | `*` | All context fields (wildcard for `debug`/`fatal`) |
332
+ const log = syntropyLog.getLogger('app');
333
+ log.info('uses env default');
334
+ log.override('consola').info('only to console');
335
+ log.remove('db').add('azure').info('default minus db, plus azure');
336
+ ```
391
337
 
392
- ### 🔒 Injection Safety
338
+ See [examples/TRANSPORT_POOL_AND_ENV.md](examples/TRANSPORT_POOL_AND_ENV.md) and `examples/TransportPoolExample.ts`.
393
339
 
394
- The `SanitizationEngine` runs **before** any transport writes a log entry. It strips ANSI escape codes and control characters from every string value — making log injection attacks against terminal or SIEM viewers impossible.
340
+ ---
395
341
 
396
- Two-layer protection:
397
- 1. **Logging Matrix** — whitelist filter: only declared fields pass through
398
- 2. **SanitizationEngine** — strips control characters from string values before output
342
+ ## 10. Audit and retention
399
343
 
400
- ### Dynamic reconfiguration
344
+ **What:** The `audit` level is always logged regardless of the configured level. `withRetention(anyJson)` attaches policy metadata (e.g. GDPR, SOX, PCI-DSS) so your `executor` can route to different tables or buckets.
401
345
 
402
- The matrix can be updated at runtime **without restarting** your application. Useful for temporarily increasing verbosity in production:
346
+ **How:**
403
347
 
404
348
  ```typescript
405
- const { contextManager } = syntropyLog;
406
-
407
- // Temporarily enable full context for debug investigation
408
- contextManager.reconfigureLoggingMatrix({
409
- default: ['correlationId'],
410
- info: ['correlationId', 'userId', 'operation'],
411
- error: ['*'], // full context on errors
412
- });
413
-
414
- // Later, restore original config
415
- contextManager.reconfigureLoggingMatrix(originalMatrix);
349
+ const auditLogger = log.withRetention({ policy: 'GDPR_ARTICLE_17', years: 7 });
350
+ auditLogger.audit({ userId: 123, action: 'data-export' }, 'GDPR export');
351
+ // Always written; retention travels in the entry for routing
416
352
  ```
417
353
 
418
- > **Security boundary**: `reconfigureLoggingMatrix()` only changes *which* context fields are visible it cannot alter masking rules, transports, or any security configuration set at `init()` time.
354
+ Your `executor` can read `logEntry.retention?.policy` and persist to the right store.
419
355
 
420
356
  ---
421
357
 
422
- ## 🛡️ Data Masking
423
-
424
- SyntropyLog ships with a **MaskingEngine** that automatically redacts sensitive fields in every log — before they ever reach a transport or database.
425
-
426
- ### Built-in strategies (enabled by default)
358
+ ## 11. Lifecycle — init / shutdown
427
359
 
428
- | Strategy | Matched field names (regex) | Example output |
429
- | :--- | :--- | :--- |
430
- | `PASSWORD` | `password`, `pass`, `pwd`, `secret` | `********` |
431
- | `EMAIL` | `email` | `j***@example.com` |
432
- | `TOKEN` | `token`, `api_key`, `auth_token`, `jwt`, `bearer` | `eyJh...a1B9c` |
433
- | `CREDIT_CARD` | `credit_card`, `card_number`, `payment_number` | `****-****-****-1234` |
434
- | `SSN` | `ssn`, `social_security`, `security_number` | `***-**-6789` |
435
- | `PHONE` | `phone`, `phone_number`, `mobile_number` | `***-***-4567` |
360
+ **What:** `init()` starts the pipeline and emits `ready` when safe to use. `shutdown()` flushes in-flight logs and closes resources; hook it to SIGTERM/SIGINT so you don’t lose logs on exit.
436
361
 
437
- ### Configuration in `init()`
362
+ **How:** Init is shown in Quick Start. Shutdown:
438
363
 
439
364
  ```typescript
440
- import { MaskingStrategy } from 'syntropylog';
441
-
442
- await syntropyLog.init({
443
- logger: { ... },
444
- context: { ... },
445
- masking: {
446
- // Default rules are ON — set to false to start from scratch
447
- enableDefaultRules: true,
448
-
449
- // Global mask character
450
- maskChar: '*',
451
-
452
- // Preserve original field length by default
453
- preserveLength: true,
454
-
455
- // Add your own rules on top of the defaults
456
- rules: [
457
- {
458
- // Mask any field whose name contains 'cuit' or 'cuil'
459
- pattern: /cuit|cuil/i,
460
- strategy: MaskingStrategy.CUSTOM,
461
- customMask: (value) => value.replace(/\d(?=\d{4})/g, '*'), // keep last 4 digits
462
- },
463
- {
464
- // Mask internal API keys
465
- pattern: /internal_key|service_secret/i,
466
- strategy: MaskingStrategy.TOKEN,
467
- },
468
- ],
469
- },
365
+ process.on('SIGTERM', async () => {
366
+ await syntropyLog.shutdown();
367
+ process.exit(0);
470
368
  });
471
369
  ```
472
370
 
473
- > **Silent Observer guarantee**: if the masking engine fails for any reason, it returns the original object and the application keeps running — it never throws.
474
-
475
- **Performance**: Built-in rules use synchronous regex matching (safe, known patterns). Custom rules use the same synchronous engine for maximum speed.
476
-
477
- > ⚠️ **Security Warning (Custom Rules)**
478
- > Node.js regular expressions execute synchronously on the main thread. To prevent Event Loop blocking, SyntropyLog automatically skips evaluating any JSON keys longer than 256 characters. However, when writing **Custom Masking Rules**, it is your responsibility to write optimized, ReDoS-safe regular expressions. A catastrophic backtracking pattern in a custom rule could temporarily freeze your application if triggered by a specifically crafted payload.
479
-
480
371
  ---
481
372
 
482
- ## 💾 Universal Persistence — Log to Any Database
373
+ ## 12. Observability hooks
483
374
 
484
- The `UniversalAdapter` lets you send structured logs to **any storage backend** (PostgreSQL, MongoDB, Elasticsearch, S3, etc.) by providing a single `executor` function. No coupling to any ORM or driver.
375
+ **What:** Optional callbacks to observe failures without logging throwing: `onLogFailure`, `onTransportError`, `onSerializationFallback`, `onStepError`, `masking.onMaskingError`. Plus `isNativeAddonInUse()` at runtime.
485
376
 
486
- ```typescript
487
- import { UniversalAdapter } from 'syntropylog';
488
- import { prisma } from './db'; // your Prisma client, Mongoose model, pg pool, etc.
489
-
490
- const dbTransport = new UniversalAdapter({
491
- executor: async (logEntry) => {
492
- // logEntry is the fully-formed, masked log object
493
- await prisma.systemLog.create({
494
- data: {
495
- level: logEntry.level,
496
- message: logEntry.message,
497
- service: logEntry.serviceName,
498
- correlationId: logEntry.correlationId,
499
- payload: logEntry.meta, // JSON column
500
- timestamp: new Date(logEntry.timestamp),
501
- },
502
- });
503
- },
504
- });
377
+ **How:** Pass them in `init()`:
505
378
 
379
+ ```typescript
506
380
  await syntropyLog.init({
507
- logger: {
508
- serviceName: 'ecommerce-app',
509
- transports: [new ClassicConsoleTransport(), dbTransport], // add alongside console
510
- },
511
- ...
381
+ logger: { ... },
382
+ onLogFailure: (err, entry) => metrics.increment('log_failures'),
383
+ onTransportError: (err, context) => alerting.notify('transport', context, err),
384
+ onSerializationFallback: () => metrics.increment('serialization_fallback'),
385
+ masking: { onMaskingError: (err) => metrics.increment('masking_errors') },
512
386
  });
513
387
  ```
514
388
 
515
- ### How the executor receives data
516
-
517
- The `executor` receives a single structured object already masked and serialized — with these fields:
389
+ | Hook | When it runs |
390
+ |------|----------------|
391
+ | `onLogFailure?(error, entry)` | Log call fails (serialization or transport) |
392
+ | `onTransportError?(error, context)` | Transport fails; `context` is `'flush'`, `'shutdown'`, or `'log'` |
393
+ | `onSerializationFallback?(reason)` | Native addon failed for this call; JS pipeline used |
394
+ | `onStepError?(step, error)` | Pipeline step failed (e.g. hygiene) |
395
+ | `masking.onMaskingError?(error)` | Masking failed (e.g. timeout); never receives raw payload |
518
396
 
519
- | Field | Type | Description |
520
- | :--- | :--- | :--- |
521
- | `level` | `string` | Log level: `info`, `error`, etc. |
522
- | `message` | `string` | The log message |
523
- | `serviceName` | `string` | From your config |
524
- | `correlationId` | `string` | From the active context |
525
- | `timestamp` | `number` | Unix timestamp |
526
- | `meta` | `object` | Any extra fields passed to the log call — already masked |
527
-
528
- > **Silent Observer**: if your `executor` throws, SyntropyLog logs the error to console and continues — your app never crashes because of a log transport failure.
529
-
530
- ### 🔗 Fluent Logger API — Specialized Loggers
531
-
532
- The `ILogger` interface provides **fluent builders** that create new, immutable child loggers with specific bindings attached. Every log call from that logger will carry those bindings automatically — no passing parameters around.
533
-
534
- | Builder | What it binds | Typical use |
535
- | :--- | :--- | :--- |
536
- | `withSource('ModuleName')` | `source: 'ModuleName'` | Tag logs by module or component |
537
- | `withTransactionId('txn-123')` | `transactionId: 'txn-123'` | Track a business transaction ID |
538
- | `withRetention({ ...anyJson })` | `retention: { ...anyJson }` | Attach org-defined metadata to route logs |
539
- | `child({ key: value })` | Any key-value pairs | General-purpose bindings |
397
+ ---
540
398
 
541
- All builders return a **new logger** — the original is never mutated.
399
+ ## 13. Matrix in runtime
542
400
 
543
- #### `withRetention()` Your JSON, Your Rules
401
+ **What:** Change which context fields are visible per level without restart. Security boundary: only field visibility changes; masking and transports stay as set at `init()`.
544
402
 
545
- `withRetention()` accepts **any JSON** object. SyntropyLog deep-clones it and attaches it as a `retention` field in every log entry — without reading or interpreting its contents. The meaning belongs entirely to your organization.
403
+ **How:**
546
404
 
547
405
  ```typescript
548
- const logger = syntropyLog.getLogger();
549
-
550
- // Each logger carries the metadata your team defined
551
- const complianceLogger = logger.withRetention({
552
- policy: 'GDPR_ARTICLE_17',
553
- years: 7,
554
- immutable: true,
555
- region: 'eu-west-1',
556
- });
557
-
558
- const auditLogger = logger.withRetention({
559
- policy: 'SOX_AUDIT_TRAIL',
560
- years: 5,
406
+ syntropyLog.reconfigureLoggingMatrix({
407
+ default: ['correlationId'],
408
+ info: ['correlationId', 'userId', 'operation'],
409
+ error: ['*'],
561
410
  });
411
+ // Restore later with original matrix
412
+ ```
562
413
 
563
- const debugLogger = logger.withRetention({
564
- policy: 'EPHEMERAL',
565
- days: 7,
566
- tier: 'hot',
567
- });
414
+ ---
568
415
 
569
- // Combine with other fluent builders
570
- const paymentAuditLogger = logger
571
- .withSource('PaymentService')
572
- .withRetention({ policy: 'PCI_DSS', years: 5 });
573
- ```
416
+ ## 14. Tree-shaking
574
417
 
575
- Use them anywhere in your application the binding is already in every log:
418
+ **What:** Package is published with `sideEffects: false` and ESM so bundlers include only what you import.
576
419
 
577
- ```typescript
578
- // In PaymentService.ts
579
- complianceLogger.info({ userId: 123, action: 'data-export' }, 'GDPR event');
420
+ **How:** Import only what you use; unused transports and adapters are dropped from the bundle.
580
421
 
581
- // In AuthModule.ts — completely independent, same pattern
582
- auditLogger.warn({ userId: 456, action: 'login-failed' }, 'Security event');
583
- ```
422
+ ---
584
423
 
585
- Each entry arrives at the transport with `retention` ready to be acted upon:
586
-
587
- ```json
588
- {
589
- "level": "info",
590
- "message": "GDPR event",
591
- "correlationId": "uuid-...",
592
- "source": "PaymentService",
593
- "retention": { "policy": "GDPR_ARTICLE_17", "years": 7, "immutable": true },
594
- "userId": 123,
595
- "action": "data-export"
596
- }
597
- ```
424
+ ## Console transports (default and pretty)
598
425
 
599
- Your executor routes it SyntropyLog has no opinion on the content:
426
+ By default the library outputs **plain JSON** to the console. For colored, human-readable output in development, use a pretty transport:
600
427
 
601
- ```typescript
602
- const dbTransport = new UniversalAdapter({
603
- executor: async (logEntry) => {
604
- const policy = logEntry.retention?.policy;
428
+ | Transport | Style |
429
+ |-----------|--------|
430
+ | *(default)* | Plain JSON |
431
+ | `ClassicConsoleTransport` | Single-line, colored |
432
+ | `PrettyConsoleTransport` | Pretty-printed, colored |
433
+ | `CompactConsoleTransport` | Compact one-liner, colored |
434
+ | `ColorfulConsoleTransport` | Full-line colored |
605
435
 
606
- const destination =
607
- policy === 'GDPR_ARTICLE_17' || policy === 'SOX_AUDIT_TRAIL' ? 'audit_logs'
608
- : policy === 'PCI_DSS' ? 'payment_audit_logs'
609
- : policy === 'EPHEMERAL' ? 'debug_logs'
610
- : 'system_logs';
436
+ Colors use built-in ANSI; no chalk. Disabled when stdout is not a TTY or when `NO_COLOR` is set.
611
437
 
612
- await db[destination].insert(logEntry);
438
+ ```typescript
439
+ import { ClassicConsoleTransport } from 'syntropylog';
440
+ syntropyLog.init({
441
+ logger: {
442
+ level: 'info',
443
+ serviceName: 'my-app',
444
+ transports: [new ClassicConsoleTransport()],
613
445
  },
614
446
  });
615
447
  ```
616
448
 
617
- > **The JSON is yours.** Field names, values, and interpretation belong entirely to your organization. `withRetention()` is just the mechanism to bind it once and have it travel with every log — automatically, safely, immutably.
618
-
619
- ---
620
-
621
- ## ✨ Key Capabilities
622
-
623
- | Feature | Description |
624
- | :--- | :--- |
625
- | **Instance Management** | Register shared resources once, use them everywhere with confidence. |
626
- | **Correlation Tracking** | Trace requests across multiple services and DBs automatically. |
627
- | **Silent Observer** | If logging fails, your application keeps running perfectly. |
628
- | **Universal Persistence** | Map logs to ANY database (SQL/NoSQL) with pure JSON mapping. |
629
-
630
- ---
631
-
632
- ---
633
-
634
449
  ---
635
450
 
636
- ## 🔒 Security & Compliance
451
+ ## Security & Compliance
637
452
 
638
- | | Dynamically configurable | Immutable |
639
- | :--- | :--- | :--- |
640
- | ✅ **Safe to change** | Logging Matrix, Log Level, additive Masking Fields | |
641
- | 🔒 **Fixed at init** | — | Transports, core masking config (`maskChar`, `maxDepth`), Redis/HTTP/broker infrastructure |
453
+ | Dynamically configurable | Fixed at init |
454
+ |--------------------------|---------------|
455
+ | Logging Matrix, log level, additive masking rules | Transports, core masking config (`maskChar`, `maxDepth`), Redis/HTTP/broker |
642
456
 
643
457
  ---
644
458
 
645
- ## 📚 Documentation
459
+ ## Documentation
646
460
 
647
- - **[Improvement plan & roadmap](docs/code-improvement-analysis-and-plan.md)** — Code analysis, prioritized backlog, and phased work plan.
648
- - **[Rust addon implementation plan](doc-es/rust-implementation-plan.md)** (ES) Phased checklist to maximize use of the native addon (“Formula 1” path); links to [rust-pipeline-optimization.md](doc-es/rust-pipeline-optimization.md) for details.
649
- - **Benchmarks** — Run `pnpm run bench` or `pnpm run bench:memory` from the repo root. [Benchmark run report (throughput + memory + high-demand stack)](docs/benchmark-memory-run.md) (EN) · [Informe de ejecución (ES)](doc-es/benchmark-memory-run.md). With the optional Rust addon built (`cd syntropylog-native && pnpm run build`), the benchmark reports native addon usage. To compare native vs JS-only: `pnpm run bench` vs `SYNTROPYLOG_NATIVE_DISABLE=1 pnpm run bench`.
461
+ - **[Features and examples (ES)](doc-es/caracteristicas-y-ejemplos.md)** — Canonical stack list with explanations and code examples; aligned with the benchmark report.
462
+ - **[Benchmark report (throughput + memory)](doc-es/benchmark-memory-run.md)** — Run `pnpm run bench` or `pnpm run bench:memory` from repo root. Compare native vs JS: `pnpm run bench` vs `SYNTROPYLOG_NATIVE_DISABLE=1 pnpm run bench`.
463
+ - **[Rust addon (ES)](doc-es/building-native-addon.es.md)** Build from source.
464
+ - **[Improvement plan & roadmap](doc-es/code-improvement-analysis-and-plan.md)** — Backlog and phased plan.
465
+ - **[Rust implementation plan (ES)](doc-es/rust-implementation-plan.md)** — Native addon checklist; links to [rust-pipeline-optimization.md](doc-es/rust-pipeline-optimization.md).
650
466
 
651
467
  ---
652
468
 
653
- ## 🤝 Contributing & License
469
+ ## Contributing & License
654
470
 
655
- We love contributors! Check our [Contributing Guide](./CONTRIBUTING.md).
656
- Project licensed under **Apache-2.0**.
471
+ See [CONTRIBUTING.md](./CONTRIBUTING.md). License: **Apache-2.0**.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "syntropylog",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "engines": {
5
5
  "node": ">=20.0.0"
6
6
  },