@marceloraineri/async-context 1.0.0 → 1.1.0
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 +118 -1
- package/dist/core/context.d.ts +46 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +75 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/integrations/adonis.d.ts +14 -0
- package/dist/core/integrations/adonis.d.ts.map +1 -0
- package/dist/core/integrations/adonis.js +24 -0
- package/dist/core/integrations/adonis.js.map +1 -0
- package/dist/core/integrations/express.d.ts +44 -0
- package/dist/core/integrations/express.d.ts.map +1 -0
- package/dist/core/integrations/express.js +55 -0
- package/dist/core/integrations/express.js.map +1 -0
- package/dist/core/integrations/nest.d.ts +12 -0
- package/dist/core/integrations/nest.d.ts.map +1 -0
- package/dist/core/integrations/nest.js +17 -0
- package/dist/core/integrations/nest.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/package.json +21 -3
- package/index.js +0 -31
- package/index.ts +0 -38
package/README.md
CHANGED
|
@@ -1 +1,118 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AsyncContext
|
|
2
|
+
|
|
3
|
+
> Request-scoped context storage backed by Node.js `AsyncLocalStorage`, with first-class Express integration.
|
|
4
|
+
|
|
5
|
+
AsyncContext is a tiny utility library that standardizes how you propagate contextual data across asynchronous flows. It offers a singleton `Context` wrapper around `AsyncLocalStorage` plus helpers to enrich the active store and an Express middleware that bootstraps a fresh context for every incoming request.
|
|
6
|
+
|
|
7
|
+
## Why AsyncContext?
|
|
8
|
+
|
|
9
|
+
- **Consistent async context** – Create one logical context per request, job, or background task without passing parameters through every function call.
|
|
10
|
+
- **Drop-in API** – Call `Context.addValue` or `Context.addObjectValue` anywhere inside the active flow to append metadata.
|
|
11
|
+
- **Observability ready** – Ship correlation IDs, tenant information, user data, or tracing metadata through your stack.
|
|
12
|
+
- **Framework friendly** – Includes an `AsyncContextExpresssMiddleware` that assigns a unique `instance_id` to each Express request and runs all downstream handlers inside that context.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm i @marceloraineri/async-context
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
When developing locally inside this repo, import from the relative `core` entry point instead.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Context } from "@marceloraineri/async-context";
|
|
26
|
+
Context.addValue("user", { id: 42, name: "Ada" });
|
|
27
|
+
|
|
28
|
+
await Promise.resolve();
|
|
29
|
+
|
|
30
|
+
const store = Context.getInstance().getStore();
|
|
31
|
+
console.log(store.requestId); // 184fa9a3-f967-4a98-9d8f-57152e7cbe64
|
|
32
|
+
console.log(store.user); // { id: 42, name: "Ada" }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Express middleware
|
|
36
|
+
|
|
37
|
+
`AsyncContextExpresssMiddleware` (note the triple “s”) creates a new context for every Express request, seeds it with a UUID `instance_id`, and ensures the context is available throughout the request lifecycle.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import express from "express";
|
|
41
|
+
import {
|
|
42
|
+
AsyncContextExpresssMiddleware,
|
|
43
|
+
Context,
|
|
44
|
+
} from "@marceloraineri/async-context";
|
|
45
|
+
|
|
46
|
+
const app = express();
|
|
47
|
+
|
|
48
|
+
app.use(AsyncContextExpresssMiddleware);
|
|
49
|
+
|
|
50
|
+
app.get("/ping", (_req, res) => {
|
|
51
|
+
const store = Context.getInstance().getStore();
|
|
52
|
+
res.json({ instanceId: store?.instance_id ?? null });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
app.listen(3000, () => console.log("API listening on :3000"));
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Nest middleware
|
|
59
|
+
|
|
60
|
+
`AsyncContextNestMiddleware` reuses the Express middleware so you can enable async context in Nest (default Express adapter).
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
|
|
64
|
+
import { AsyncContextNestMiddleware } from "@marceloraineri/async-context";
|
|
65
|
+
|
|
66
|
+
@Module({})
|
|
67
|
+
export class AppModule implements NestModule {
|
|
68
|
+
configure(consumer: MiddlewareConsumer) {
|
|
69
|
+
consumer.apply(AsyncContextNestMiddleware).forRoutes("*");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
> Note: This middleware targets Nest's Express adapter. If you use the Fastify adapter, consider a custom interceptor that calls `Context.getInstance().run(...)` per request.
|
|
75
|
+
|
|
76
|
+
## AdonisJS middleware
|
|
77
|
+
|
|
78
|
+
`AsyncContextAdonisMiddleware` plugs into AdonisJS' middleware pipeline and initializes a new async context for each request.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// app/Http/Middleware/AsyncContext.ts
|
|
82
|
+
import { AsyncContextAdonisMiddleware } from "@marceloraineri/async-context";
|
|
83
|
+
|
|
84
|
+
export default AsyncContextAdonisMiddleware;
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Register it as a global middleware in your AdonisJS kernel (per your Adonis version docs).
|
|
88
|
+
|
|
89
|
+
## API reference
|
|
90
|
+
|
|
91
|
+
### `Context.getInstance(): AsyncLocalStorage`
|
|
92
|
+
Returns (and lazily instantiates) the singleton `AsyncLocalStorage` used by the library. You typically call `run(store, callback)` on this instance to spawn a new context.
|
|
93
|
+
|
|
94
|
+
### `Context.addObjectValue(values: Record<string, unknown>): Record<string, unknown>`
|
|
95
|
+
Merges the provided object into the active context. Also throws if no context is active.
|
|
96
|
+
|
|
97
|
+
### `AsyncContextExpresssMiddleware(req, res, next)`
|
|
98
|
+
Express middleware that:
|
|
99
|
+
|
|
100
|
+
1. Generates a UUID via `crypto.randomUUID()`.
|
|
101
|
+
2. Calls `Context.getInstance().run({ instance_id: uuid }, () => next())`.
|
|
102
|
+
3. Makes the context (and `instance_id`) available to any downstream code.
|
|
103
|
+
|
|
104
|
+
### `AsyncContextNestMiddleware`
|
|
105
|
+
Nest middleware (Express adapter) that initializes a new async context per request by delegating to `AsyncContextExpresssMiddleware`.
|
|
106
|
+
|
|
107
|
+
### `AsyncContextAdonisMiddleware`
|
|
108
|
+
AdonisJS middleware that initializes a new async context per request using `Context.getInstance().run(...)`.
|
|
109
|
+
|
|
110
|
+
## Best practices & caveats
|
|
111
|
+
|
|
112
|
+
- Avoid replacing the entire store object manually; instead mutate it through `addValue`/`addObjectValue` to keep shared references intact.
|
|
113
|
+
- `AsyncLocalStorage` state is scoped to a single Node.js process. If you spawn workers or separate processes, each will maintain its own context.
|
|
114
|
+
- Be mindful of long-lived contexts: if you never exit a `run` callback (e.g., forgetting to call `next()` in Express), the store will never be released.
|
|
115
|
+
|
|
116
|
+
## Contributing
|
|
117
|
+
|
|
118
|
+
Issues and pull requests are welcome. Please include reproduction steps or tests whenever you propose a change to the async context behavior.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
/**
|
|
3
|
+
* Provides an application-wide asynchronous context using Node.js AsyncLocalStorage.
|
|
4
|
+
* Allows storing and retrieving key/value data within the active async execution flow.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Context {
|
|
7
|
+
/**
|
|
8
|
+
* Singleton instance of AsyncLocalStorage.
|
|
9
|
+
* @type {AsyncLocalStorage<unknown>}
|
|
10
|
+
*/
|
|
11
|
+
static asyncLocalStorageInstance: AsyncLocalStorage<unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* Private constructor initializes the AsyncLocalStorage instance.
|
|
14
|
+
* Called automatically when the instance does not yet exist.
|
|
15
|
+
* @private
|
|
16
|
+
*/
|
|
17
|
+
private constructor();
|
|
18
|
+
/**
|
|
19
|
+
* Returns the global AsyncLocalStorage instance, creating it if necessary.
|
|
20
|
+
*
|
|
21
|
+
* @returns {AsyncLocalStorage<unknown>} The AsyncLocalStorage singleton.
|
|
22
|
+
*/
|
|
23
|
+
static getInstance(): AsyncLocalStorage<unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* Adds a single key/value pair to the active asynchronous context.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} key - Key to store inside the context.
|
|
28
|
+
* @param {*} value - Value to associate with the given key.
|
|
29
|
+
* @returns {Record<string, any>} The updated context object.
|
|
30
|
+
*
|
|
31
|
+
* @throws {Error} If called outside of an active `Context.getInstance().run()`.
|
|
32
|
+
*/
|
|
33
|
+
static addValue(key: string, value: any): Record<string, any>;
|
|
34
|
+
/**
|
|
35
|
+
* Merges an object of values into the active asynchronous context.
|
|
36
|
+
*
|
|
37
|
+
* @param {Record<string, any>} object - Object containing key/value pairs to merge.
|
|
38
|
+
* @returns {Record<string, any>} The merged context object.
|
|
39
|
+
*
|
|
40
|
+
* @throws {Error} If called outside of an active `Context.getInstance().run()`.
|
|
41
|
+
*/
|
|
42
|
+
static addObjectValue(object: Record<string, any>): Record<string, any>;
|
|
43
|
+
static remove(key: string): Record<string, any>;
|
|
44
|
+
static safeRemove(key: string): Record<string, any>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../core/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;GAGG;AACH,qBAAa,OAAO;IAClB;;;OAGG;IACH,OAAc,yBAAyB,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAEpE;;;;OAIG;IACH,OAAO;IAIP;;;;OAIG;IACH,MAAM,CAAC,WAAW,IAAI,iBAAiB,CAAC,OAAO,CAAC;IAOhD;;;;;;;;OAQG;IACH,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAc7D;;;;;;;OAOG;IACH,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAUvE,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM;IAezB,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM;CAY9B"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Context = void 0;
|
|
4
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
5
|
+
/**
|
|
6
|
+
* Provides an application-wide asynchronous context using Node.js AsyncLocalStorage.
|
|
7
|
+
* Allows storing and retrieving key/value data within the active async execution flow.
|
|
8
|
+
*/
|
|
9
|
+
class Context {
|
|
10
|
+
/**
|
|
11
|
+
* Private constructor initializes the AsyncLocalStorage instance.
|
|
12
|
+
* Called automatically when the instance does not yet exist.
|
|
13
|
+
* @private
|
|
14
|
+
*/
|
|
15
|
+
constructor() {
|
|
16
|
+
Context.asyncLocalStorageInstance = new node_async_hooks_1.AsyncLocalStorage();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns the global AsyncLocalStorage instance, creating it if necessary.
|
|
20
|
+
*
|
|
21
|
+
* @returns {AsyncLocalStorage<unknown>} The AsyncLocalStorage singleton.
|
|
22
|
+
*/
|
|
23
|
+
static getInstance() {
|
|
24
|
+
if (!Context.asyncLocalStorageInstance) {
|
|
25
|
+
new Context();
|
|
26
|
+
}
|
|
27
|
+
return Context.asyncLocalStorageInstance;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Adds a single key/value pair to the active asynchronous context.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} key - Key to store inside the context.
|
|
33
|
+
* @param {*} value - Value to associate with the given key.
|
|
34
|
+
* @returns {Record<string, any>} The updated context object.
|
|
35
|
+
*
|
|
36
|
+
* @throws {Error} If called outside of an active `Context.getInstance().run()`.
|
|
37
|
+
*/
|
|
38
|
+
static addValue(key, value) {
|
|
39
|
+
const contextObject = Context.getInstance().getStore();
|
|
40
|
+
if (!contextObject)
|
|
41
|
+
throw new Error("No active context found. Use Context.getInstance().run() or use the context middleware.");
|
|
42
|
+
contextObject[key] = value;
|
|
43
|
+
return contextObject;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Merges an object of values into the active asynchronous context.
|
|
47
|
+
*
|
|
48
|
+
* @param {Record<string, any>} object - Object containing key/value pairs to merge.
|
|
49
|
+
* @returns {Record<string, any>} The merged context object.
|
|
50
|
+
*
|
|
51
|
+
* @throws {Error} If called outside of an active `Context.getInstance().run()`.
|
|
52
|
+
*/
|
|
53
|
+
static addObjectValue(object) {
|
|
54
|
+
const contextObject = Context.getInstance().getStore();
|
|
55
|
+
if (!contextObject)
|
|
56
|
+
throw new Error("No active context found. Use Context.getInstance().run() or use the context middleware.");
|
|
57
|
+
return Object.assign(contextObject, object);
|
|
58
|
+
}
|
|
59
|
+
static remove(key) {
|
|
60
|
+
const contextObject = Context.getInstance().getStore();
|
|
61
|
+
if (contextObject[key])
|
|
62
|
+
delete contextObject[key];
|
|
63
|
+
if (!contextObject)
|
|
64
|
+
throw new Error("No active context found. Use Context.getInstance().run() or use the context middleware.");
|
|
65
|
+
return contextObject;
|
|
66
|
+
}
|
|
67
|
+
static safeRemove(key) {
|
|
68
|
+
const contextObject = Context.getInstance().getStore();
|
|
69
|
+
if (!contextObject[key])
|
|
70
|
+
throw new Error("You are trying to remove something that does not exist.");
|
|
71
|
+
return Context.remove(key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.Context = Context;
|
|
75
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../core/context.ts"],"names":[],"mappings":";;;AAAA,uDAAqD;AAErD;;;GAGG;AACH,MAAa,OAAO;IAOlB;;;;OAIG;IACH;QACE,OAAO,CAAC,yBAAyB,GAAG,IAAI,oCAAiB,EAAE,CAAC;IAC9D,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,WAAW;QAChB,IAAI,CAAC,OAAO,CAAC,yBAAyB,EAAE,CAAC;YACvC,IAAI,OAAO,EAAE,CAAC;QAChB,CAAC;QACD,OAAO,OAAO,CAAC,yBAAyB,CAAC;IAC3C,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,QAAQ,CAAC,GAAW,EAAE,KAAU;QACrC,MAAM,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,EAGnD,CAAC;QACF,IAAI,CAAC,aAAa;YAChB,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;QAEJ,aAAa,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC3B,OAAO,aAAa,CAAC;IACvB,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,cAAc,CAAC,MAA2B;QAC/C,MAAM,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC;QACvD,IAAI,CAAC,aAAa;YAChB,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;QAEJ,OAAO,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,GAAW;QACvB,MAAM,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,EAGnD,CAAC;QAEF,IAAI,aAAa,CAAC,GAAG,CAAC;YAAE,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAClD,IAAI,CAAC,aAAa;YAChB,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;QAEJ,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,UAAU,CAAC,GAAW;QAC3B,MAAM,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,EAGnD,CAAC;QAEF,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,yDAAyD,CAC1D,CAAC;QACJ,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;CACF;AAhGD,0BAgGC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Context } from './context';
|
|
2
|
+
export { AsyncContextExpresssMiddleware } from './integrations/express';
|
|
3
|
+
export { AsyncContextNestMiddleware } from './integrations/nest';
|
|
4
|
+
export { AsyncContextAdonisMiddleware } from './integrations/adonis';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AsyncContextAdonisMiddleware = exports.AsyncContextNestMiddleware = exports.AsyncContextExpresssMiddleware = exports.Context = void 0;
|
|
4
|
+
var context_1 = require("./context");
|
|
5
|
+
Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return context_1.Context; } });
|
|
6
|
+
var express_1 = require("./integrations/express");
|
|
7
|
+
Object.defineProperty(exports, "AsyncContextExpresssMiddleware", { enumerable: true, get: function () { return express_1.AsyncContextExpresssMiddleware; } });
|
|
8
|
+
var nest_1 = require("./integrations/nest");
|
|
9
|
+
Object.defineProperty(exports, "AsyncContextNestMiddleware", { enumerable: true, get: function () { return nest_1.AsyncContextNestMiddleware; } });
|
|
10
|
+
var adonis_1 = require("./integrations/adonis");
|
|
11
|
+
Object.defineProperty(exports, "AsyncContextAdonisMiddleware", { enumerable: true, get: function () { return adonis_1.AsyncContextAdonisMiddleware; } });
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../core/index.ts"],"names":[],"mappings":";;;AAAA,qCAAoC;AAA3B,kGAAA,OAAO,OAAA;AAChB,kDAAwE;AAA/D,yHAAA,8BAA8B,OAAA;AACvC,4CAAiE;AAAxD,kHAAA,0BAA0B,OAAA;AACnC,gDAAqE;AAA5D,sHAAA,4BAA4B,OAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type NextFunction = () => Promise<unknown>;
|
|
2
|
+
type AdonisContext = Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* AdonisJS middleware that initializes a new asynchronous context
|
|
5
|
+
* for each incoming request.
|
|
6
|
+
*
|
|
7
|
+
* Compatible with AdonisJS' middleware signature:
|
|
8
|
+
* `async handle(ctx, next)`.
|
|
9
|
+
*/
|
|
10
|
+
export declare class AsyncContextAdonisMiddleware {
|
|
11
|
+
handle(_ctx: AdonisContext, next: NextFunction): Promise<unknown>;
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=adonis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adonis.d.ts","sourceRoot":"","sources":["../../../core/integrations/adonis.ts"],"names":[],"mappings":"AAGA,KAAK,YAAY,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;AAC3C,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE7C;;;;;;GAMG;AACH,qBAAa,4BAA4B;IACjC,MAAM,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY;CAMrD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AsyncContextAdonisMiddleware = void 0;
|
|
7
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
|
+
const context_1 = require("../context");
|
|
9
|
+
/**
|
|
10
|
+
* AdonisJS middleware that initializes a new asynchronous context
|
|
11
|
+
* for each incoming request.
|
|
12
|
+
*
|
|
13
|
+
* Compatible with AdonisJS' middleware signature:
|
|
14
|
+
* `async handle(ctx, next)`.
|
|
15
|
+
*/
|
|
16
|
+
class AsyncContextAdonisMiddleware {
|
|
17
|
+
async handle(_ctx, next) {
|
|
18
|
+
const uuid = node_crypto_1.default.randomUUID();
|
|
19
|
+
const localStorageInstance = context_1.Context.getInstance();
|
|
20
|
+
return localStorageInstance.run({ instance_id: uuid }, () => next());
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.AsyncContextAdonisMiddleware = AsyncContextAdonisMiddleware;
|
|
24
|
+
//# sourceMappingURL=adonis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adonis.js","sourceRoot":"","sources":["../../../core/integrations/adonis.ts"],"names":[],"mappings":";;;;;;AAAA,8DAAiC;AACjC,wCAAqC;AAKrC;;;;;;GAMG;AACH,MAAa,4BAA4B;IACvC,KAAK,CAAC,MAAM,CAAC,IAAmB,EAAE,IAAkB;QAClD,MAAM,IAAI,GAAG,qBAAM,CAAC,UAAU,EAAE,CAAC;QACjC,MAAM,oBAAoB,GAAG,iBAAO,CAAC,WAAW,EAAE,CAAC;QAEnD,OAAO,oBAAoB,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC;CACF;AAPD,oEAOC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type * as http from "node:http";
|
|
2
|
+
/**
|
|
3
|
+
* Express middleware that initializes a new asynchronous context
|
|
4
|
+
* for each incoming request.
|
|
5
|
+
*
|
|
6
|
+
* This middleware assigns a unique `instance_id` (UUID) to the
|
|
7
|
+
* request lifecycle and stores it inside AsyncLocalStorage,
|
|
8
|
+
* allowing the application to later retrieve per-request data
|
|
9
|
+
* without passing parameters through function calls.
|
|
10
|
+
*
|
|
11
|
+
* It is designed to simplify building request-scoped state,
|
|
12
|
+
* such as logging correlation IDs or storing metadata across
|
|
13
|
+
* asynchronous operations.
|
|
14
|
+
*
|
|
15
|
+
* @function AsyncContextExpressMiddleware
|
|
16
|
+
*
|
|
17
|
+
* @param {http.IncomingMessage} req - The current HTTP request.
|
|
18
|
+
* @param {http.ServerResponse} res - The current HTTP response.
|
|
19
|
+
* @param {Function} next - Express continuation callback.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Usage in an Express application
|
|
23
|
+
* import express from "express";
|
|
24
|
+
* import { AsyncContextExpressMiddleware } from "@marceloraineri/async-context";
|
|
25
|
+
*
|
|
26
|
+
* const app = express();
|
|
27
|
+
*
|
|
28
|
+
* app.use(AsyncContextExpressMiddleware);
|
|
29
|
+
*
|
|
30
|
+
* app.get("/test", (req, res) => {
|
|
31
|
+
* const context = Context.getInstance().getStore();
|
|
32
|
+
* console.log(context.instance_id); // Unique per request
|
|
33
|
+
* res.send("OK");
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* @description
|
|
37
|
+
* A new asynchronous context is created via:
|
|
38
|
+
* `Context.getInstance().run({ instance_id: <uuid> }, ...)`
|
|
39
|
+
*
|
|
40
|
+
* This ensures that all async operations inside the request
|
|
41
|
+
* share the same context object until the request completes.
|
|
42
|
+
*/
|
|
43
|
+
export declare function AsyncContextExpresssMiddleware(req: http.IncomingMessage, res: http.ServerResponse, next: () => void): void;
|
|
44
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../../core/integrations/express.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AAIvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,IAAI,EAAE,MAAM,IAAI,QAMjB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AsyncContextExpresssMiddleware = AsyncContextExpresssMiddleware;
|
|
7
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
|
+
const context_1 = require("../context");
|
|
9
|
+
/**
|
|
10
|
+
* Express middleware that initializes a new asynchronous context
|
|
11
|
+
* for each incoming request.
|
|
12
|
+
*
|
|
13
|
+
* This middleware assigns a unique `instance_id` (UUID) to the
|
|
14
|
+
* request lifecycle and stores it inside AsyncLocalStorage,
|
|
15
|
+
* allowing the application to later retrieve per-request data
|
|
16
|
+
* without passing parameters through function calls.
|
|
17
|
+
*
|
|
18
|
+
* It is designed to simplify building request-scoped state,
|
|
19
|
+
* such as logging correlation IDs or storing metadata across
|
|
20
|
+
* asynchronous operations.
|
|
21
|
+
*
|
|
22
|
+
* @function AsyncContextExpressMiddleware
|
|
23
|
+
*
|
|
24
|
+
* @param {http.IncomingMessage} req - The current HTTP request.
|
|
25
|
+
* @param {http.ServerResponse} res - The current HTTP response.
|
|
26
|
+
* @param {Function} next - Express continuation callback.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Usage in an Express application
|
|
30
|
+
* import express from "express";
|
|
31
|
+
* import { AsyncContextExpressMiddleware } from "@marceloraineri/async-context";
|
|
32
|
+
*
|
|
33
|
+
* const app = express();
|
|
34
|
+
*
|
|
35
|
+
* app.use(AsyncContextExpressMiddleware);
|
|
36
|
+
*
|
|
37
|
+
* app.get("/test", (req, res) => {
|
|
38
|
+
* const context = Context.getInstance().getStore();
|
|
39
|
+
* console.log(context.instance_id); // Unique per request
|
|
40
|
+
* res.send("OK");
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* @description
|
|
44
|
+
* A new asynchronous context is created via:
|
|
45
|
+
* `Context.getInstance().run({ instance_id: <uuid> }, ...)`
|
|
46
|
+
*
|
|
47
|
+
* This ensures that all async operations inside the request
|
|
48
|
+
* share the same context object until the request completes.
|
|
49
|
+
*/
|
|
50
|
+
function AsyncContextExpresssMiddleware(req, res, next) {
|
|
51
|
+
const uuid = node_crypto_1.default.randomUUID();
|
|
52
|
+
const LocalStorageInstance = context_1.Context.getInstance();
|
|
53
|
+
LocalStorageInstance.run({ instance_id: uuid }, () => next());
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../../core/integrations/express.ts"],"names":[],"mappings":";;;;;AA8CA,wEASC;AAtDD,8DAAiC;AACjC,wCAAqC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,SAAgB,8BAA8B,CAC5C,GAAyB,EACzB,GAAwB,EACxB,IAAgB;IAEhB,MAAM,IAAI,GAAG,qBAAM,CAAC,UAAU,EAAE,CAAC;IACjC,MAAM,oBAAoB,GAAG,iBAAO,CAAC,WAAW,EAAE,CAAC;IAEnD,oBAAoB,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type NextFunction = () => void;
|
|
2
|
+
/**
|
|
3
|
+
* Nest middleware that reuses the Express integration to initialize
|
|
4
|
+
* a new asynchronous context for each incoming request.
|
|
5
|
+
*
|
|
6
|
+
* Works with the default Nest Express adapter.
|
|
7
|
+
*/
|
|
8
|
+
export declare class AsyncContextNestMiddleware {
|
|
9
|
+
use(req: unknown, res: unknown, next: NextFunction): void;
|
|
10
|
+
}
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=nest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nest.d.ts","sourceRoot":"","sources":["../../../core/integrations/nest.ts"],"names":[],"mappings":"AAEA,KAAK,YAAY,GAAG,MAAM,IAAI,CAAC;AAE/B;;;;;GAKG;AACH,qBAAa,0BAA0B;IACrC,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;CAOnD"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AsyncContextNestMiddleware = void 0;
|
|
4
|
+
const express_1 = require("./express");
|
|
5
|
+
/**
|
|
6
|
+
* Nest middleware that reuses the Express integration to initialize
|
|
7
|
+
* a new asynchronous context for each incoming request.
|
|
8
|
+
*
|
|
9
|
+
* Works with the default Nest Express adapter.
|
|
10
|
+
*/
|
|
11
|
+
class AsyncContextNestMiddleware {
|
|
12
|
+
use(req, res, next) {
|
|
13
|
+
(0, express_1.AsyncContextExpresssMiddleware)(req, res, next);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.AsyncContextNestMiddleware = AsyncContextNestMiddleware;
|
|
17
|
+
//# sourceMappingURL=nest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nest.js","sourceRoot":"","sources":["../../../core/integrations/nest.ts"],"names":[],"mappings":";;;AAAA,uCAA2D;AAI3D;;;;;GAKG;AACH,MAAa,0BAA0B;IACrC,GAAG,CAAC,GAAY,EAAE,GAAY,EAAE,IAAkB;QAChD,IAAA,wCAA8B,EAC5B,GAA2D,EAC3D,GAA2D,EAC3D,IAAI,CACL,CAAC;IACJ,CAAC;CACF;AARD,gEAQC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Context } from "./core/context";
|
|
2
|
+
export { AsyncContextExpresssMiddleware } from "./core/integrations/express";
|
|
3
|
+
export { AsyncContextNestMiddleware } from "./core/integrations/nest";
|
|
4
|
+
export { AsyncContextAdonisMiddleware } from "./core/integrations/adonis";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AsyncContextAdonisMiddleware = exports.AsyncContextNestMiddleware = exports.AsyncContextExpresssMiddleware = exports.Context = void 0;
|
|
4
|
+
var context_1 = require("./core/context");
|
|
5
|
+
Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return context_1.Context; } });
|
|
6
|
+
var express_1 = require("./core/integrations/express");
|
|
7
|
+
Object.defineProperty(exports, "AsyncContextExpresssMiddleware", { enumerable: true, get: function () { return express_1.AsyncContextExpresssMiddleware; } });
|
|
8
|
+
var nest_1 = require("./core/integrations/nest");
|
|
9
|
+
Object.defineProperty(exports, "AsyncContextNestMiddleware", { enumerable: true, get: function () { return nest_1.AsyncContextNestMiddleware; } });
|
|
10
|
+
var adonis_1 = require("./core/integrations/adonis");
|
|
11
|
+
Object.defineProperty(exports, "AsyncContextAdonisMiddleware", { enumerable: true, get: function () { return adonis_1.AsyncContextAdonisMiddleware; } });
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;AAAA,0CAAyC;AAAhC,kGAAA,OAAO,OAAA;AAChB,uDAA6E;AAApE,yHAAA,8BAA8B,OAAA;AACvC,iDAAsE;AAA7D,kHAAA,0BAA0B,OAAA;AACnC,qDAA0E;AAAjE,sHAAA,4BAA4B,OAAA"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marceloraineri/async-context",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
6
19
|
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
20
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
21
|
+
"dev": "tsx watch --inspect core/index.ts",
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
8
24
|
},
|
|
9
25
|
"repository": {
|
|
10
26
|
"type": "git",
|
|
@@ -18,6 +34,8 @@
|
|
|
18
34
|
"homepage": "https://github.com/ohraineri/AsyncContext#readme",
|
|
19
35
|
"devDependencies": {
|
|
20
36
|
"@types/node": "^24.10.1",
|
|
37
|
+
"eslint": "^9.39.1",
|
|
38
|
+
"tsx": "^4.20.6",
|
|
21
39
|
"typescript": "^5.9.3"
|
|
22
40
|
}
|
|
23
41
|
}
|
package/index.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Context = void 0;
|
|
4
|
-
var node_async_hooks_1 = require("node:async_hooks");
|
|
5
|
-
var Context = /** @class */ (function () {
|
|
6
|
-
function Context() {
|
|
7
|
-
Context.asyncLocalStorageInstance = new node_async_hooks_1.AsyncLocalStorage();
|
|
8
|
-
}
|
|
9
|
-
Context.getInstance = function () {
|
|
10
|
-
if (!Context.asyncLocalStorageInstance) {
|
|
11
|
-
new Context();
|
|
12
|
-
}
|
|
13
|
-
return Context.asyncLocalStorageInstance;
|
|
14
|
-
};
|
|
15
|
-
Context.addValue = function (key, value) {
|
|
16
|
-
var contextObject = Context.getInstance().getStore();
|
|
17
|
-
if (!contextObject)
|
|
18
|
-
throw new Error('Nenhum contexto ativo encontrado. Use Context.getInstance().run().');
|
|
19
|
-
contextObject[key] = value;
|
|
20
|
-
return contextObject;
|
|
21
|
-
};
|
|
22
|
-
Context.addObjectValue = function (object) {
|
|
23
|
-
var contextObject = Context.getInstance().getStore();
|
|
24
|
-
if (!contextObject)
|
|
25
|
-
throw new Error('Nenhum contexto ativo encontrado. Use Context.getInstance().run().');
|
|
26
|
-
var merged = Object.assign(contextObject, object);
|
|
27
|
-
return merged;
|
|
28
|
-
};
|
|
29
|
-
return Context;
|
|
30
|
-
}());
|
|
31
|
-
exports.Context = Context;
|
package/index.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
|
|
3
|
-
type objectStorageType = {
|
|
4
|
-
[key: string] : unknown
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export class Context {
|
|
8
|
-
static asyncLocalStorageInstance: AsyncLocalStorage<unknown>;
|
|
9
|
-
|
|
10
|
-
private constructor() {
|
|
11
|
-
Context.asyncLocalStorageInstance = new AsyncLocalStorage();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
static getInstance() {
|
|
15
|
-
if (!Context.asyncLocalStorageInstance) {
|
|
16
|
-
new Context();
|
|
17
|
-
}
|
|
18
|
-
return Context.asyncLocalStorageInstance;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
static addValue(key: string, value: any) {
|
|
22
|
-
const contextObject = Context.getInstance().getStore() as Record<string, any>;
|
|
23
|
-
if (!contextObject)
|
|
24
|
-
throw new Error('Nenhum contexto ativo encontrado. Use Context.getInstance().run().');
|
|
25
|
-
|
|
26
|
-
contextObject[key] = value;
|
|
27
|
-
return contextObject;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
static addObjectValue(object: Record<string, any>) {
|
|
31
|
-
const contextObject = Context.getInstance().getStore();
|
|
32
|
-
if (!contextObject)
|
|
33
|
-
throw new Error('Nenhum contexto ativo encontrado. Use Context.getInstance().run().');
|
|
34
|
-
|
|
35
|
-
const merged = Object.assign(contextObject, object);
|
|
36
|
-
return merged;
|
|
37
|
-
}
|
|
38
|
-
}
|