@marceloraineri/async-context 1.0.1 → 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 +39 -6
- 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/{core/integrations/express.ts → dist/core/integrations/express.d.ts} +3 -15
- 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 +17 -5
- package/core/context.ts +0 -76
- package/core/index.ts +0 -2
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ AsyncContext is a tiny utility library that standardizes how you propagate conte
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npm i @marceloraineri/
|
|
17
|
+
npm i @marceloraineri/async-context
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
When developing locally inside this repo, import from the relative `core` entry point instead.
|
|
@@ -55,6 +55,37 @@ app.get("/ping", (_req, res) => {
|
|
|
55
55
|
app.listen(3000, () => console.log("API listening on :3000"));
|
|
56
56
|
```
|
|
57
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
|
+
|
|
58
89
|
## API reference
|
|
59
90
|
|
|
60
91
|
### `Context.getInstance(): AsyncLocalStorage`
|
|
@@ -63,13 +94,19 @@ Returns (and lazily instantiates) the singleton `AsyncLocalStorage` used by the
|
|
|
63
94
|
### `Context.addObjectValue(values: Record<string, unknown>): Record<string, unknown>`
|
|
64
95
|
Merges the provided object into the active context. Also throws if no context is active.
|
|
65
96
|
|
|
66
|
-
### `
|
|
97
|
+
### `AsyncContextExpresssMiddleware(req, res, next)`
|
|
67
98
|
Express middleware that:
|
|
68
99
|
|
|
69
100
|
1. Generates a UUID via `crypto.randomUUID()`.
|
|
70
101
|
2. Calls `Context.getInstance().run({ instance_id: uuid }, () => next())`.
|
|
71
102
|
3. Makes the context (and `instance_id`) available to any downstream code.
|
|
72
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
|
+
|
|
73
110
|
## Best practices & caveats
|
|
74
111
|
|
|
75
112
|
- Avoid replacing the entire store object manually; instead mutate it through `addValue`/`addObjectValue` to keep shared references intact.
|
|
@@ -79,7 +116,3 @@ Express middleware that:
|
|
|
79
116
|
## Contributing
|
|
80
117
|
|
|
81
118
|
Issues and pull requests are welcome. Please include reproduction steps or tests whenever you propose a change to the async context behavior.
|
|
82
|
-
|
|
83
|
-
## License
|
|
84
|
-
|
|
85
|
-
Specify the desired license (e.g., MIT) here.
|
|
@@ -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"}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type * as http from "node:http";
|
|
2
|
-
import crypto from "node:crypto";
|
|
3
|
-
import { Context } from "../..";
|
|
4
|
-
|
|
5
2
|
/**
|
|
6
3
|
* Express middleware that initializes a new asynchronous context
|
|
7
|
-
* for each incoming request.
|
|
4
|
+
* for each incoming request.
|
|
8
5
|
*
|
|
9
6
|
* This middleware assigns a unique `instance_id` (UUID) to the
|
|
10
7
|
* request lifecycle and stores it inside AsyncLocalStorage,
|
|
@@ -43,14 +40,5 @@ import { Context } from "../..";
|
|
|
43
40
|
* This ensures that all async operations inside the request
|
|
44
41
|
* share the same context object until the request completes.
|
|
45
42
|
*/
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
req: http.IncomingMessage,
|
|
49
|
-
res: http.ServerResponse,
|
|
50
|
-
next: () => void
|
|
51
|
-
) {
|
|
52
|
-
const uuid = crypto.randomUUID();
|
|
53
|
-
const LocalStorageInstance = Context.getInstance();
|
|
54
|
-
|
|
55
|
-
LocalStorageInstance.run({ instance_id: uuid }, () => next());
|
|
56
|
-
}
|
|
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,12 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marceloraineri/async-context",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "",
|
|
5
|
-
"main": "
|
|
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
20
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
21
|
"dev": "tsx watch --inspect core/index.ts",
|
|
9
|
-
"build": "tsc"
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
10
24
|
},
|
|
11
25
|
"repository": {
|
|
12
26
|
"type": "git",
|
|
@@ -21,8 +35,6 @@
|
|
|
21
35
|
"devDependencies": {
|
|
22
36
|
"@types/node": "^24.10.1",
|
|
23
37
|
"eslint": "^9.39.1",
|
|
24
|
-
"ts-node": "^10.9.2",
|
|
25
|
-
"ts-node-dev": "^2.0.0",
|
|
26
38
|
"tsx": "^4.20.6",
|
|
27
39
|
"typescript": "^5.9.3"
|
|
28
40
|
}
|
package/core/context.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Provides an application-wide asynchronous context using Node.js AsyncLocalStorage.
|
|
5
|
-
* Allows storing and retrieving key/value data within the active async execution flow.
|
|
6
|
-
*/
|
|
7
|
-
export class Context {
|
|
8
|
-
/**
|
|
9
|
-
* Singleton instance of AsyncLocalStorage.
|
|
10
|
-
* @type {AsyncLocalStorage<unknown>}
|
|
11
|
-
*/
|
|
12
|
-
public static asyncLocalStorageInstance: AsyncLocalStorage<unknown>;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Private constructor initializes the AsyncLocalStorage instance.
|
|
16
|
-
* Called automatically when the instance does not yet exist.
|
|
17
|
-
* @private
|
|
18
|
-
*/
|
|
19
|
-
private constructor() {
|
|
20
|
-
Context.asyncLocalStorageInstance = new AsyncLocalStorage();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Returns the global AsyncLocalStorage instance, creating it if necessary.
|
|
25
|
-
*
|
|
26
|
-
* @returns {AsyncLocalStorage<unknown>} The AsyncLocalStorage singleton.
|
|
27
|
-
*/
|
|
28
|
-
static getInstance(): AsyncLocalStorage<unknown> {
|
|
29
|
-
if (!Context.asyncLocalStorageInstance) {
|
|
30
|
-
new Context();
|
|
31
|
-
}
|
|
32
|
-
return Context.asyncLocalStorageInstance;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Adds a single key/value pair to the active asynchronous context.
|
|
37
|
-
*
|
|
38
|
-
* @param {string} key - Key to store inside the context.
|
|
39
|
-
* @param {*} value - Value to associate with the given key.
|
|
40
|
-
* @returns {Record<string, any>} The updated context object.
|
|
41
|
-
*
|
|
42
|
-
* @throws {Error} If called outside of an active `Context.getInstance().run()`.
|
|
43
|
-
*/
|
|
44
|
-
static addValue(key: string, value: any) {
|
|
45
|
-
const contextObject = Context.getInstance().getStore() as Record<
|
|
46
|
-
string,
|
|
47
|
-
any
|
|
48
|
-
>;
|
|
49
|
-
if (!contextObject)
|
|
50
|
-
throw new Error(
|
|
51
|
-
"No active context found. Use Context.getInstance().run()."
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
contextObject[key] = value;
|
|
55
|
-
return contextObject;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Merges an object of values into the active asynchronous context.
|
|
60
|
-
*
|
|
61
|
-
* @param {Record<string, any>} object - Object containing key/value pairs to merge.
|
|
62
|
-
* @returns {Record<string, any>} The merged context object.
|
|
63
|
-
*
|
|
64
|
-
* @throws {Error} If called outside of an active `Context.getInstance().run()`.
|
|
65
|
-
*/
|
|
66
|
-
static addObjectValue(object: Record<string, any>) {
|
|
67
|
-
const contextObject = Context.getInstance().getStore();
|
|
68
|
-
if (!contextObject)
|
|
69
|
-
throw new Error(
|
|
70
|
-
"No active context found. Use Context.getInstance().run()."
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const merged = Object.assign(contextObject, object);
|
|
74
|
-
return merged;
|
|
75
|
-
}
|
|
76
|
-
}
|
package/core/index.ts
DELETED