@travetto/context 2.1.5 → 2.2.2
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 +1 -1
- package/package.json +3 -3
- package/src/decorator.ts +10 -5
- package/src/extension/rest.interceptor.ts +1 -1
- package/src/service.ts +38 -14
- package/test-support/suite-context.ts +2 -1
package/README.md
CHANGED
|
@@ -64,4 +64,4 @@ export class SystemInitiatedContext {
|
|
|
64
64
|
|
|
65
65
|
## Extension - Rest
|
|
66
66
|
|
|
67
|
-
Within the [RESTful API](https://github.com/travetto/travetto/tree/main/module/rest#readme "Declarative api for RESTful APIs with support for the dependency injection module.") module, it can be
|
|
67
|
+
Within the [RESTful API](https://github.com/travetto/travetto/tree/main/module/rest#readme "Declarative api for RESTful APIs with support for the dependency injection module.") module, it can be challenging to share context across the various layers that may be touched by a request. This module provides [AsyncContextInterceptor](https://github.com/travetto/travetto/tree/main/module/context/src/extension/rest.interceptor.ts#L17) to create a data store that will be private to an individual request. This is used by [Rest Auth](https://github.com/travetto/travetto/tree/main/module/auth-rest#readme "Rest authentication integration support for the travetto framework") to store authenticated user information for built-in authorization and permission validation.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/context",
|
|
3
3
|
"displayName": "Async Context",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.2",
|
|
5
5
|
"description": "Async-aware state management, maintaining context across asynchronous calls.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"async-hooks",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"directory": "module/context"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/di": "^2.
|
|
30
|
+
"@travetto/di": "^2.2.2"
|
|
31
31
|
},
|
|
32
32
|
"optionalPeerDependencies": {
|
|
33
|
-
"@travetto/rest": "^2.
|
|
33
|
+
"@travetto/rest": "^2.2.2"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
package/src/decorator.ts
CHANGED
|
@@ -4,11 +4,16 @@ import { AsyncContext } from './service';
|
|
|
4
4
|
* Allows running a function while providing an async context
|
|
5
5
|
*/
|
|
6
6
|
export function WithAsyncContext<T extends { context: AsyncContext }>(data?: Record<string, unknown>) {
|
|
7
|
-
return function <U extends unknown[], V = unknown>(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
return function <U extends unknown[], V = unknown>(
|
|
8
|
+
target: T,
|
|
9
|
+
prop: string,
|
|
10
|
+
descriptor: TypedPropertyDescriptor<(...args: U) => Promise<V>>
|
|
11
|
+
): typeof descriptor {
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
13
|
+
const og = descriptor.value! as (this: T, ...args: unknown[]) => Promise<V>;
|
|
14
|
+
descriptor.value = function (this: T, ...args: unknown[]): Promise<V> {
|
|
15
|
+
return (this.context.run(og.bind(this, ...args),
|
|
16
|
+
data ? JSON.parse(JSON.stringify(data)) : {}));
|
|
12
17
|
};
|
|
13
18
|
|
|
14
19
|
Object.defineProperty(descriptor.value, 'name', { value: og.name });
|
|
@@ -28,7 +28,7 @@ export class AsyncContextInterceptor implements RestInterceptor {
|
|
|
28
28
|
return !this.config.disabled;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
intercept(req: Request, res: Response, next: () => Promise<void>) {
|
|
31
|
+
intercept(req: Request, res: Response, next: () => Promise<void>): Promise<void> {
|
|
32
32
|
return this.context.run(next);
|
|
33
33
|
}
|
|
34
34
|
}
|
package/src/service.ts
CHANGED
|
@@ -14,13 +14,14 @@ type Ctx = Record<string | symbol, unknown>;
|
|
|
14
14
|
export class AsyncContext {
|
|
15
15
|
|
|
16
16
|
alStorage = new AsyncLocalStorage<{ value: Ctx }>();
|
|
17
|
+
active = 0;
|
|
17
18
|
|
|
18
19
|
constructor() {
|
|
19
20
|
this.run = this.run.bind(this);
|
|
20
21
|
this.iterate = this.iterate.bind(this);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
#store(setAs?: Ctx | null) {
|
|
24
|
+
#store(setAs?: Ctx | null): Ctx {
|
|
24
25
|
const val = this.alStorage.getStore();
|
|
25
26
|
if (!val) {
|
|
26
27
|
throw new AppError('Context is not initialized', 'general');
|
|
@@ -39,13 +40,14 @@ export class AsyncContext {
|
|
|
39
40
|
* Get entire context or a portion by key
|
|
40
41
|
*/
|
|
41
42
|
get<T = unknown>(key: string | symbol): T;
|
|
42
|
-
get():
|
|
43
|
-
get<T>(key?: string | symbol) {
|
|
43
|
+
get(): Ctx;
|
|
44
|
+
get<T>(key?: string | symbol): Ctx | T {
|
|
44
45
|
const root = this.#store();
|
|
45
46
|
if (key) {
|
|
46
|
-
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
48
|
+
return root[key as string] as T;
|
|
47
49
|
} else {
|
|
48
|
-
return root
|
|
50
|
+
return root;
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -54,29 +56,51 @@ export class AsyncContext {
|
|
|
54
56
|
*/
|
|
55
57
|
set(key: string | symbol, val: unknown): void;
|
|
56
58
|
set(val: Ctx): void;
|
|
57
|
-
set(keyOrVal: string | symbol | Ctx, valWithKey?: unknown) {
|
|
59
|
+
set(keyOrVal: string | symbol | Ctx, valWithKey?: unknown): void {
|
|
58
60
|
if (typeof keyOrVal === 'string' || typeof keyOrVal === 'symbol') {
|
|
59
|
-
this.get()[keyOrVal
|
|
61
|
+
this.get()[keyOrVal] = valWithKey;
|
|
60
62
|
} else {
|
|
61
|
-
this.#store(keyOrVal
|
|
63
|
+
this.#store(keyOrVal);
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
#context(ctx: Ctx = {}): { value: Ctx } {
|
|
66
|
-
return { value: this.alStorage.getStore() ? { ...this.#store(), ...ctx } : ctx };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
67
|
/**
|
|
70
68
|
* Run an async function and ensure the context is available during execution
|
|
71
69
|
*/
|
|
72
70
|
async run<T = unknown>(fn: () => Promise<T>, init: Ctx = {}): Promise<T> {
|
|
73
|
-
|
|
71
|
+
if (this.alStorage.getStore()) {
|
|
72
|
+
init = { ...this.#store(), ...init };
|
|
73
|
+
}
|
|
74
|
+
this.active += 1;
|
|
75
|
+
this.alStorage.enterWith({ value: init });
|
|
76
|
+
try {
|
|
77
|
+
return await fn();
|
|
78
|
+
} finally {
|
|
79
|
+
// @ts-expect-error
|
|
80
|
+
delete this.alStorage.getStore().value;
|
|
81
|
+
if ((this.active -= 1) === 0) {
|
|
82
|
+
this.alStorage.disable();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
/**
|
|
77
88
|
* Run an async function and ensure the context is available during execution
|
|
78
89
|
*/
|
|
79
90
|
async * iterate<T>(fn: () => AsyncGenerator<T>, init: Ctx = {}): AsyncGenerator<T> {
|
|
80
|
-
|
|
91
|
+
if (this.alStorage.getStore()) {
|
|
92
|
+
init = { ...this.#store(), ...init };
|
|
93
|
+
}
|
|
94
|
+
this.active += 1;
|
|
95
|
+
this.alStorage.enterWith({ value: init });
|
|
96
|
+
try {
|
|
97
|
+
return yield* fn();
|
|
98
|
+
} finally {
|
|
99
|
+
// @ts-expect-error
|
|
100
|
+
delete this.alStorage.getStore().value;
|
|
101
|
+
if ((this.active -= 1) === 0) {
|
|
102
|
+
this.alStorage.disable();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
81
105
|
}
|
|
82
106
|
}
|
|
@@ -11,7 +11,7 @@ const Init = Symbol();
|
|
|
11
11
|
* @param data
|
|
12
12
|
*/
|
|
13
13
|
export function WithSuiteContext(data: Record<string, unknown> = {}) {
|
|
14
|
-
return (target: Class) => {
|
|
14
|
+
return (target: Class): void => {
|
|
15
15
|
function wrapped(ctx: AsyncContext, og: Function) {
|
|
16
16
|
return function (this: unknown) {
|
|
17
17
|
return ctx.run(og.bind(this), JSON.parse(JSON.stringify(data)));
|
|
@@ -24,6 +24,7 @@ export function WithSuiteContext(data: Record<string, unknown> = {}) {
|
|
|
24
24
|
this[Init] = true;
|
|
25
25
|
const ctx = await DependencyRegistry.getInstance(AsyncContext);
|
|
26
26
|
for (const t of SuiteRegistry.get(target).tests) {
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
27
28
|
const fn = wrapped(ctx, this[t.methodName] as Function);
|
|
28
29
|
Object.defineProperty(fn, 'name', { value: t.methodName });
|
|
29
30
|
this[t.methodName] = fn;
|