# @push.rocks/smartcontext A module to enrich logs with context, featuring async log contexts and scope management. ## Install Make sure you have Node.js and npm installed, then run: ```bash npm install @push.rocks/smartcontext ``` This will install the library and its dependencies into your local `node_modules` folder. ## Usage The `@push.rocks/smartcontext` module provides an efficient way to enrich your logging (or any functionality) with contextual information. It uses asynchronous context management to support hierarchical scopes—particularly handy when dealing with complex or nested asynchronous operations in Node.js. ### Setting Up and Basic Usage First, import the necessary classes: ```typescript import { AsyncContext } from '@push.rocks/smartcontext'; ``` #### Creating a Context An `AsyncContext` manages a top-level `AsyncStore`. You can attach any data you want to that store. For example: ```typescript const asyncContext = new AsyncContext(); // Add data to the top-level store asyncContext.store.add('username', 'john_doe'); console.log(asyncContext.store.get('username')); // 'john_doe' ``` ### Using `runScoped` The `runScoped` method now creates a child store *automatically* and makes that child store accessible via `asyncContext.store` **only** within the passed-in function. Once that scoped function completes, `asyncContext.store` reverts to the original (parent) store. #### Example ```typescript await asyncContext.runScoped(async () => { // While in this scope, asyncContext.store refers to the child store. asyncContext.store.add('transactionId', 'txn_123456'); console.log(asyncContext.store.get('transactionId')); // 'txn_123456' // Data from the parent store is also accessible in the child console.log(asyncContext.store.get('username')); // 'john_doe' }); // Outside runScoped, asyncContext.store is the parent store again console.log(asyncContext.store.get('transactionId')); // undefined ``` 1. **Child Store Access**: Within the scope, `asyncContext.store` points to a *child* store. 2. **Parent Data**: Any keys from the parent are still visible in the child (unless overridden). 3. **No Cross-Contamination**: Keys added in the child store don’t persist in the parent after the scope ends. ### Isolating and Deleting Data If you add a key within a `runScoped` block, it stays there unless you intentionally move it to the parent. Also, if you delete a key in the child, it won’t remove it from the parent store: ```typescript // Parent store has some initial data asyncContext.store.add('deletableKey', 'initialValue'); await asyncContext.runScoped(async () => { console.log(asyncContext.store.get('deletableKey')); // 'initialValue' asyncContext.store.delete('deletableKey'); // Delete in child console.log(asyncContext.store.get('deletableKey')); // undefined (child only) }); // Outside scope, parent's data is intact console.log(asyncContext.store.get('deletableKey')); // 'initialValue' ``` ### Multiple Scopes You can create multiple scopes, each maintaining its own data isolation. That way, concurrent or sequential operations don’t overwrite or “contaminate” each other’s data in the shared context. ```typescript // Scope 1 await asyncContext.runScoped(async () => { asyncContext.store.add('key1', 'value1'); console.log(asyncContext.store.get('key1')); // 'value1' }); // Scope 2 await asyncContext.runScoped(async () => { asyncContext.store.add('key2', 'value2'); console.log(asyncContext.store.get('key2')); // 'value2' }); // After both scopes finish, neither key1 nor key2 is visible to the parent console.log(asyncContext.store.get('key1')); // undefined console.log(asyncContext.store.get('key2')); // undefined ``` ### Deeper Nesting If you nest `runScoped` calls, each subsequent call creates another child store, which inherits from the store that’s currently active. This allows you to build deeper context layers when needed. ```typescript await asyncContext.runScoped(async () => { asyncContext.store.add('layer1', true); await asyncContext.runScoped(async () => { asyncContext.store.add('layer2', true); // This store sees data from both layer1 and layer2 console.log(asyncContext.store.get('layer1')); // true console.log(asyncContext.store.get('layer2')); // true }); // Only layer1 data remains in scope here; layer2 is gone console.log(asyncContext.store.get('layer2')); // undefined }); ``` ### Testing Below is a simplified test example using [tapbundle](https://www.npmjs.com/package/@push.rocks/tapbundle). It demonstrates `runScoped()` creating isolated child stores and verifying behavior such as data inheritance and cleanup: ```typescript import { tap, expect } from '@push.rocks/tapbundle'; import { AsyncContext } from './logcontext.classes.asynccontext.js'; const asyncContext = new AsyncContext(); tap.test('should run scoped and see parent data', async () => { asyncContext.store.add('parentKey', 'parentValue'); expect(asyncContext.store.get('parentKey')).toEqual('parentValue'); await asyncContext.runScoped(async () => { asyncContext.store.add('childKey', 'childValue'); expect(asyncContext.store.get('childKey')).toEqual('childValue'); expect(asyncContext.store.get('parentKey')).toEqual('parentValue'); }); }); tap.test('should isolate new keys within a child scope', async () => { await asyncContext.runScoped(async () => { asyncContext.store.add('temporaryKey', 'temporaryValue'); expect(asyncContext.store.get('temporaryKey')).toEqual('temporaryValue'); }); // Key won't leak to the parent expect(asyncContext.store.get('temporaryKey')).toBeUndefined(); }); // More tests ... tap.start(); ``` ### Conclusion The updated `runScoped` usage makes child store management more intuitive by dynamically switching `asyncContext.store` to a child store inside the scope, then reverting back to the parent store afterwards. This design ensures: - **Scoped Modifications**: Data added or removed in a child scope stays in that scope. - **Parent Inheritance**: Child scopes still read the parent’s data. - **Easy Syntax**: No need to pass the child store around; simply call `asyncContext.store` within `runScoped`. With `@push.rocks/smartcontext`, you can maintain clear boundaries between different parts of your asynchronous code. This not only enriches your logging with context but also simplifies tracking data across nested or concurrent operations. ## License and Legal Information This repository is under the [MIT License](./license). Please note that the MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as necessary for reasonable use in describing the origin of the work. ### Trademarks This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Usage must be approved in writing by Task Venture Capital GmbH. ### Company Information Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany For any legal inquiries, please contact us at hello@task.vc. By using this repository, you acknowledge that you have read this section and agree to comply with its terms.