# node-execution-context
A straightforward library that provides a persistent process-level context wrapper using node "async_hooks" feature.
This library will try to use by default [`AsyncLocalStorage`](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage) implementation based if current node version supports it, otherwise it will fallback to raw [`async_hooks`](https://nodejs.org/api/async_hooks.html) implementation for lower versions which mimics this behaviour.

## Installation

```
npm i node-execution-context
```

## Getting started

Let's start with creating the context initialisation point of our app, we will take a simple express app for this example

```js
// main.js

const express = require('express');
const Context = require('node-execution-context');
const UserController = require('./controllers/user');
const app = express();
const port = 3000;

const ContextMiddleware = (req, res, next) => {
    Context.run(next, { reference: Math.random() });
};

app.use('/', ContextMiddleware);
app.post('/user', UserController.create);

app.listen(port);

```

This will expose any point of your code form this point that handles that request.

```js
// ./controller/user/index.js

const Context = require('node-execution-context');
const mongo = require('../service/mongo');
const logger = require('../service/logger');

export class UserController {
    async create (req) {
        const { user } = req.body;

        // This will return the reference number set by out ContextMiddleware (generated by Math.random())
        const { reference } = Context.get();

        logger.info('Created user for reference: ', reference);

        return await mongo.create('user', user);
    }
}
```

## API

### run(fn: Function, context: unknown)

Runs given callback that will be exposed to the given context.
The context will be exposed to all callbacks and promise chains triggered from the given `fn`.

### get()

Gets the current asynchronous execution context.

> The `get` result is returned by `reference` meaning if you wish any immutability applied, it will have to be manually applied.

> This API may throw CONTEXT_DOES_NOT_EXIST error if accessed without initializing the context properly.

### set(context: unknown)

Sets the current asynchronous execution context to given value.

> This API may throw CONTEXT_DOES_NOT_EXIST error if accessed without initializing the context properly.

### create(context?: unknown)

Creates a given context for the current asynchronous execution.
It is recommended to use the `run` method. This method should be used in special cases in which the `run` method cannot be used directly.

> Note that if this method will be called not within a AsyncResource context, it will effect current execution context and should be used with caution.

#### Example

```js
const Context = require('node-execution-context');

// context-events.js
const context = { id: Math.random() };

emitter.on('contextual-event', () => {
  Context.create(context);
});

// service.js
emitter.on('contextual-event', () => {
  Context.get(); // Returns { id: random }
});

// main.js
emitter.emit('contextual-event');

Context.get(); // Returns { id: random }
```

### configure(config: ExecutionContextConfig) : void

Configures execution context settings.

> Relevant only for node versions lower than `v12.17.0`.

### monitor(): ExecutionMapUsage

Returns an monitoring report over the current execution map resources

> Relevant only for node versions lower than `v12.17.0`.

> Before calling `monitor`, you should `configure` execution context to monitor it's nodes. by default the data kept is as possible.

#### Example

```js
const Context = require('node-execution-context');

// Startup
Context.configure({ monitor: true });

// Later on
const usage = Context.monitor();
console.log(usage); // Prints execution context usage report.
```

### Raw API Usage

```js
const Context = require('node-execution-context');

Context.create({
    value: true
});

Promise.resolve().then(() => {
    console.log(Context.get()); // outputs: {"value": true}

    Context.set({
        value: false
    });

    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(Context.get()); // outputs: {"value": false}

            Context.set({
                butter: 'fly'
            });

            process.nextTick(() => {
                console.log(Context.get()); // outputs: {"butter": 'fly'}
                resolve();
            });

        }, 1000);

        console.log(Context.get()); // outputs: {"value": false}
    });
});
```

The following errors can be thrown while accessing to the context API :

| Code | When |
|-|-
| CONTEXT_DOES_NOT_EXIST | When attempting to `get` / `set` a context, that has not yet been created.
| MONITOR_MISS_CONFIGURATION | When try to `monitor` without calling `configure` with monitoring option.

