@rsdk/actx 5.8.1 → 5.9.0-next.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/CHANGELOG.md +4 -0
- package/dist/async-context.exception.d.ts +9 -0
- package/dist/async-context.exception.js +19 -0
- package/dist/async-context.exception.js.map +1 -0
- package/dist/async-context.js +3 -2
- package/dist/async-context.js.map +1 -1
- package/dist/async-context.provider.d.ts +11 -0
- package/dist/async-context.provider.js +37 -1
- package/dist/async-context.provider.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/async-context.exception.ts +15 -0
- package/src/async-context.provider.ts +60 -1
- package/src/async-context.ts +12 -2
- package/src/index.ts +4 -0
- package/test/async-context.provider.spec.ts +255 -0
- package/test/async-context.spec.ts +77 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [5.8.2](https://github.com/R-Vision/rsdk/compare/v5.8.1...v5.8.2) (2025-06-17)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @rsdk/actx
|
|
9
|
+
|
|
6
10
|
## [5.8.1](https://github.com/R-Vision/rsdk/compare/v5.8.0...v5.8.1) (2025-05-22)
|
|
7
11
|
|
|
8
12
|
**Note:** Version bump only for package @rsdk/actx
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare enum AsyncContextExceptionCode {
|
|
2
|
+
ASYNC_CONTEXT_IS_NOT_PROVIDED = "ASYNC_CONTEXT_IS_NOT_PROVIDED",
|
|
3
|
+
UNEXPECTED_REWRITE = "UNEXPECTED_REWRITE",
|
|
4
|
+
VALUE_NOT_PROVIDED = "VALUE_NOT_PROVIDED"
|
|
5
|
+
}
|
|
6
|
+
export declare class AsyncContextException extends Error {
|
|
7
|
+
readonly code: AsyncContextExceptionCode;
|
|
8
|
+
constructor(message: string, code: AsyncContextExceptionCode);
|
|
9
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AsyncContextException = exports.AsyncContextExceptionCode = void 0;
|
|
4
|
+
var AsyncContextExceptionCode;
|
|
5
|
+
(function (AsyncContextExceptionCode) {
|
|
6
|
+
AsyncContextExceptionCode["ASYNC_CONTEXT_IS_NOT_PROVIDED"] = "ASYNC_CONTEXT_IS_NOT_PROVIDED";
|
|
7
|
+
AsyncContextExceptionCode["UNEXPECTED_REWRITE"] = "UNEXPECTED_REWRITE";
|
|
8
|
+
AsyncContextExceptionCode["VALUE_NOT_PROVIDED"] = "VALUE_NOT_PROVIDED";
|
|
9
|
+
})(AsyncContextExceptionCode || (exports.AsyncContextExceptionCode = AsyncContextExceptionCode = {}));
|
|
10
|
+
// TODO: после того как вынесем ошибки в @rsdk/exception, надо сделать InternalException
|
|
11
|
+
class AsyncContextException extends Error {
|
|
12
|
+
code;
|
|
13
|
+
constructor(message, code) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.AsyncContextException = AsyncContextException;
|
|
19
|
+
//# sourceMappingURL=async-context.exception.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-context.exception.js","sourceRoot":"","sources":["../src/async-context.exception.ts"],"names":[],"mappings":";;;AAAA,IAAY,yBAIX;AAJD,WAAY,yBAAyB;IACnC,4FAA+D,CAAA;IAC/D,sEAAyC,CAAA;IACzC,sEAAyC,CAAA;AAC3C,CAAC,EAJW,yBAAyB,yCAAzB,yBAAyB,QAIpC;AAED,wFAAwF;AACxF,MAAa,qBAAsB,SAAQ,KAAK;IACrC,IAAI,CAA4B;IAEzC,YAAY,OAAe,EAAE,IAA+B;QAC1D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAPD,sDAOC"}
|
package/dist/async-context.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AsyncContext = void 0;
|
|
4
|
+
const async_context_exception_1 = require("./async-context.exception");
|
|
4
5
|
const async_context_storage_1 = require("./async-context.storage");
|
|
5
6
|
class AsyncContext {
|
|
6
7
|
static set(key, v, rewritable = false) {
|
|
@@ -19,7 +20,7 @@ class AsyncContext {
|
|
|
19
20
|
static getStoreOrThrow() {
|
|
20
21
|
const store = async_context_storage_1.AsyncContextStorage.getStore();
|
|
21
22
|
if (!store) {
|
|
22
|
-
throw new
|
|
23
|
+
throw new async_context_exception_1.AsyncContextException('AsyncContext is not provided', async_context_exception_1.AsyncContextExceptionCode.ASYNC_CONTEXT_IS_NOT_PROVIDED);
|
|
23
24
|
}
|
|
24
25
|
return store;
|
|
25
26
|
}
|
|
@@ -28,7 +29,7 @@ class AsyncContext {
|
|
|
28
29
|
return;
|
|
29
30
|
}
|
|
30
31
|
if (AsyncContext.has(key)) {
|
|
31
|
-
throw new
|
|
32
|
+
throw new async_context_exception_1.AsyncContextException('Unexpected rewrite', async_context_exception_1.AsyncContextExceptionCode.UNEXPECTED_REWRITE);
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async-context.js","sourceRoot":"","sources":["../src/async-context.ts"],"names":[],"mappings":";;;AAAA,mEAA8D;AAE9D,MAAa,YAAY;IACvB,MAAM,CAAC,GAAG,CAAC,GAAW,EAAE,CAAU,EAAE,UAAU,GAAG,KAAK;QACpD,MAAM,KAAK,GAAG,YAAY,CAAC,eAAe,EAAE,CAAC;QAE7C,YAAY,CAAC,gBAAgB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QAC/C,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,CAAC,GAAG,CAAI,GAAW;QACvB,MAAM,KAAK,GAAG,2CAAmB,CAAC,QAAQ,EAAE,CAAC;QAE7C,OAAO,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,GAAW;QACpB,MAAM,KAAK,GAAG,2CAAmB,CAAC,QAAQ,EAAE,CAAC;QAE7C,OAAO,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC;IAClC,CAAC;IAEO,MAAM,CAAC,eAAe;QAC5B,MAAM,KAAK,GAAG,2CAAmB,CAAC,QAAQ,EAAE,CAAC;QAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"async-context.js","sourceRoot":"","sources":["../src/async-context.ts"],"names":[],"mappings":";;;AAAA,uEAGmC;AACnC,mEAA8D;AAE9D,MAAa,YAAY;IACvB,MAAM,CAAC,GAAG,CAAC,GAAW,EAAE,CAAU,EAAE,UAAU,GAAG,KAAK;QACpD,MAAM,KAAK,GAAG,YAAY,CAAC,eAAe,EAAE,CAAC;QAE7C,YAAY,CAAC,gBAAgB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QAC/C,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,CAAC,GAAG,CAAI,GAAW;QACvB,MAAM,KAAK,GAAG,2CAAmB,CAAC,QAAQ,EAAE,CAAC;QAE7C,OAAO,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,GAAW;QACpB,MAAM,KAAK,GAAG,2CAAmB,CAAC,QAAQ,EAAE,CAAC;QAE7C,OAAO,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC;IAClC,CAAC;IAEO,MAAM,CAAC,eAAe;QAC5B,MAAM,KAAK,GAAG,2CAAmB,CAAC,QAAQ,EAAE,CAAC;QAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,+CAAqB,CAC7B,8BAA8B,EAC9B,mDAAyB,CAAC,6BAA6B,CACxD,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,MAAM,CAAC,gBAAgB,CAAC,GAAW,EAAE,UAAmB;QAC9D,IAAI,UAAU,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QACD,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,+CAAqB,CAC7B,oBAAoB,EACpB,mDAAyB,CAAC,kBAAkB,CAC7C,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AA1CD,oCA0CC"}
|
|
@@ -3,6 +3,17 @@ export interface AsyncContextProvider<V> {
|
|
|
3
3
|
get(): V | undefined;
|
|
4
4
|
getOrThrow(): V;
|
|
5
5
|
set(v: V): void;
|
|
6
|
+
/**
|
|
7
|
+
* Данный метод позволяет создать дочерний контекст для провайдера с указанным новым значением.
|
|
8
|
+
* Это может быть полезно, например, для системных действий по крону или для нужд тестирования.
|
|
9
|
+
* При использовании `set` данного провайдера внутри callback'а родительский контекст не изменится,
|
|
10
|
+
* а при использовании `set` других провайдеров изменения всплывут в родительский контекст.
|
|
11
|
+
*
|
|
12
|
+
* @param value Новое значение в провайдере, которое будет доступно в callback
|
|
13
|
+
* @param callback Callback, внутри которого замкнуто новое значение провайдера
|
|
14
|
+
* @returns Результат выполнения callback
|
|
15
|
+
*/
|
|
16
|
+
runWith<T>(value: V, callback: () => T): T;
|
|
6
17
|
}
|
|
7
18
|
export declare const createAsyncContextProvider: <V = never>(config: {
|
|
8
19
|
/**
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createAsyncContextProvider = void 0;
|
|
4
4
|
const async_context_1 = require("./async-context");
|
|
5
|
+
const async_context_exception_1 = require("./async-context.exception");
|
|
6
|
+
const async_context_storage_1 = require("./async-context.storage");
|
|
5
7
|
const createAsyncContextProvider = (config) => {
|
|
6
8
|
const token = config.name;
|
|
7
9
|
let isSettled = false;
|
|
@@ -14,7 +16,7 @@ const createAsyncContextProvider = (config) => {
|
|
|
14
16
|
},
|
|
15
17
|
getOrThrow() {
|
|
16
18
|
if (!async_context_1.AsyncContext.has(token)) {
|
|
17
|
-
throw new
|
|
19
|
+
throw new async_context_exception_1.AsyncContextException('Value not provided: ' + token, async_context_exception_1.AsyncContextExceptionCode.VALUE_NOT_PROVIDED);
|
|
18
20
|
}
|
|
19
21
|
return async_context_1.AsyncContext.get(token);
|
|
20
22
|
},
|
|
@@ -22,6 +24,40 @@ const createAsyncContextProvider = (config) => {
|
|
|
22
24
|
isSettled = true;
|
|
23
25
|
async_context_1.AsyncContext.set(token, v, config.rewritable);
|
|
24
26
|
},
|
|
27
|
+
runWith(value, callback) {
|
|
28
|
+
const parentStore = async_context_storage_1.AsyncContextStorage.getStore() ?? new Map();
|
|
29
|
+
const localOverrides = new Map([[token, value]]);
|
|
30
|
+
const proxyStore = new Proxy(parentStore, {
|
|
31
|
+
get(target, prop, receiver) {
|
|
32
|
+
switch (prop) {
|
|
33
|
+
case 'get':
|
|
34
|
+
return (key) => {
|
|
35
|
+
if (key === token) {
|
|
36
|
+
return localOverrides.get(key);
|
|
37
|
+
}
|
|
38
|
+
return target.get(key);
|
|
39
|
+
};
|
|
40
|
+
case 'set':
|
|
41
|
+
return (key, val) => {
|
|
42
|
+
if (key === token) {
|
|
43
|
+
localOverrides.set(key, val);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
target.set(key, val);
|
|
47
|
+
}
|
|
48
|
+
return proxyStore;
|
|
49
|
+
};
|
|
50
|
+
case 'has':
|
|
51
|
+
return (key) => {
|
|
52
|
+
return key === token || target.has(key);
|
|
53
|
+
};
|
|
54
|
+
default:
|
|
55
|
+
return Reflect.get(target, prop, receiver);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
return async_context_storage_1.AsyncContextStorage.run(proxyStore, callback);
|
|
60
|
+
},
|
|
25
61
|
};
|
|
26
62
|
};
|
|
27
63
|
exports.createAsyncContextProvider = createAsyncContextProvider;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async-context.provider.js","sourceRoot":"","sources":["../src/async-context.provider.ts"],"names":[],"mappings":";;;AAAA,mDAA+C;
|
|
1
|
+
{"version":3,"file":"async-context.provider.js","sourceRoot":"","sources":["../src/async-context.provider.ts"],"names":[],"mappings":";;;AAAA,mDAA+C;AAC/C,uEAGmC;AACnC,mEAA8D;AAuBvD,MAAM,0BAA0B,GAAG,CAAY,MAUrD,EAA2B,EAAE;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC;IAE1B,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,OAAO;QACL,IAAI,SAAS;YACX,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,GAAG;YACD,OAAO,4BAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;QAED,UAAU;YACR,IAAI,CAAC,4BAAY,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC7B,MAAM,IAAI,+CAAqB,CAC7B,sBAAsB,GAAG,KAAK,EAC9B,mDAAyB,CAAC,kBAAkB,CAC7C,CAAC;YACJ,CAAC;YACD,OAAO,4BAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;QAED,GAAG,CAAC,CAAI;YACN,SAAS,GAAG,IAAI,CAAC;YACjB,4BAAY,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;QAChD,CAAC;QAED,OAAO,CAAI,KAAQ,EAAE,QAAiB;YACpC,MAAM,WAAW,GAAG,2CAAmB,CAAC,QAAQ,EAAE,IAAI,IAAI,GAAG,EAAE,CAAC;YAEhE,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;YAEjD,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,WAAW,EAAE;gBACxC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ;oBACxB,QAAQ,IAAI,EAAE,CAAC;wBACb,KAAK,KAAK;4BACR,OAAO,CAAC,GAAQ,EAAO,EAAE;gCACvB,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oCAClB,OAAO,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gCACjC,CAAC;gCAED,OAAO,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;4BACzB,CAAC,CAAC;wBACJ,KAAK,KAAK;4BACR,OAAO,CAAC,GAAQ,EAAE,GAAQ,EAAiB,EAAE;gCAC3C,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oCAClB,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gCAC/B,CAAC;qCAAM,CAAC;oCACN,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gCACvB,CAAC;gCAED,OAAO,UAAU,CAAC;4BACpB,CAAC,CAAC;wBACJ,KAAK,KAAK;4BACR,OAAO,CAAC,GAAQ,EAAW,EAAE;gCAC3B,OAAO,GAAG,KAAK,KAAK,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;4BAC1C,CAAC,CAAC;wBACJ;4BACE,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;oBAC/C,CAAC;gBACH,CAAC;aACF,CAAC,CAAC;YAEH,OAAO,2CAAmB,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACvD,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AA7EW,QAAA,0BAA0B,8BA6ErC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createAsyncContextProvider = exports.AsyncContextModule = void 0;
|
|
3
|
+
exports.AsyncContextExceptionCode = exports.AsyncContextException = exports.createAsyncContextProvider = exports.AsyncContextModule = void 0;
|
|
4
4
|
var async_context_module_1 = require("./async-context.module");
|
|
5
5
|
Object.defineProperty(exports, "AsyncContextModule", { enumerable: true, get: function () { return async_context_module_1.AsyncContextModule; } });
|
|
6
6
|
var async_context_provider_1 = require("./async-context.provider");
|
|
7
7
|
Object.defineProperty(exports, "createAsyncContextProvider", { enumerable: true, get: function () { return async_context_provider_1.createAsyncContextProvider; } });
|
|
8
|
+
var async_context_exception_1 = require("./async-context.exception");
|
|
9
|
+
Object.defineProperty(exports, "AsyncContextException", { enumerable: true, get: function () { return async_context_exception_1.AsyncContextException; } });
|
|
10
|
+
Object.defineProperty(exports, "AsyncContextExceptionCode", { enumerable: true, get: function () { return async_context_exception_1.AsyncContextExceptionCode; } });
|
|
8
11
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+DAA4D;AAAnD,0HAAA,kBAAkB,OAAA;AAE3B,mEAGkC;AAFhC,oIAAA,0BAA0B,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+DAA4D;AAAnD,0HAAA,kBAAkB,OAAA;AAE3B,mEAGkC;AAFhC,oIAAA,0BAA0B,OAAA;AAG5B,qEAGmC;AAFjC,gIAAA,qBAAqB,OAAA;AACrB,oIAAA,yBAAyB,OAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rsdk/actx",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0-next.0",
|
|
4
4
|
"description": "Library for AsyncLocalStorage",
|
|
5
5
|
"license": "Apache License 2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -15,5 +15,5 @@
|
|
|
15
15
|
"@nestjs/core": "^10.0.0",
|
|
16
16
|
"rxjs": "^7.1.0"
|
|
17
17
|
},
|
|
18
|
-
"gitHead": "
|
|
18
|
+
"gitHead": "8dce12d7a98f6d31fc4bc49fda990d219a0b88a7"
|
|
19
19
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export enum AsyncContextExceptionCode {
|
|
2
|
+
ASYNC_CONTEXT_IS_NOT_PROVIDED = 'ASYNC_CONTEXT_IS_NOT_PROVIDED',
|
|
3
|
+
UNEXPECTED_REWRITE = 'UNEXPECTED_REWRITE',
|
|
4
|
+
VALUE_NOT_PROVIDED = 'VALUE_NOT_PROVIDED',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// TODO: после того как вынесем ошибки в @rsdk/exception, надо сделать InternalException
|
|
8
|
+
export class AsyncContextException extends Error {
|
|
9
|
+
readonly code: AsyncContextExceptionCode;
|
|
10
|
+
|
|
11
|
+
constructor(message: string, code: AsyncContextExceptionCode) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { AsyncContext } from './async-context';
|
|
2
|
+
import {
|
|
3
|
+
AsyncContextException,
|
|
4
|
+
AsyncContextExceptionCode,
|
|
5
|
+
} from './async-context.exception';
|
|
6
|
+
import { AsyncContextStorage } from './async-context.storage';
|
|
2
7
|
|
|
3
8
|
export interface AsyncContextProvider<V> {
|
|
4
9
|
get isSettled(): boolean;
|
|
@@ -7,6 +12,18 @@ export interface AsyncContextProvider<V> {
|
|
|
7
12
|
getOrThrow(): V;
|
|
8
13
|
|
|
9
14
|
set(v: V): void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Данный метод позволяет создать дочерний контекст для провайдера с указанным новым значением.
|
|
18
|
+
* Это может быть полезно, например, для системных действий по крону или для нужд тестирования.
|
|
19
|
+
* При использовании `set` данного провайдера внутри callback'а родительский контекст не изменится,
|
|
20
|
+
* а при использовании `set` других провайдеров изменения всплывут в родительский контекст.
|
|
21
|
+
*
|
|
22
|
+
* @param value Новое значение в провайдере, которое будет доступно в callback
|
|
23
|
+
* @param callback Callback, внутри которого замкнуто новое значение провайдера
|
|
24
|
+
* @returns Результат выполнения callback
|
|
25
|
+
*/
|
|
26
|
+
runWith<T>(value: V, callback: () => T): T;
|
|
10
27
|
}
|
|
11
28
|
|
|
12
29
|
export const createAsyncContextProvider = <V = never>(config: {
|
|
@@ -34,7 +51,10 @@ export const createAsyncContextProvider = <V = never>(config: {
|
|
|
34
51
|
|
|
35
52
|
getOrThrow(): V {
|
|
36
53
|
if (!AsyncContext.has(token)) {
|
|
37
|
-
throw new
|
|
54
|
+
throw new AsyncContextException(
|
|
55
|
+
'Value not provided: ' + token,
|
|
56
|
+
AsyncContextExceptionCode.VALUE_NOT_PROVIDED,
|
|
57
|
+
);
|
|
38
58
|
}
|
|
39
59
|
return AsyncContext.get(token);
|
|
40
60
|
},
|
|
@@ -43,5 +63,44 @@ export const createAsyncContextProvider = <V = never>(config: {
|
|
|
43
63
|
isSettled = true;
|
|
44
64
|
AsyncContext.set(token, v, config.rewritable);
|
|
45
65
|
},
|
|
66
|
+
|
|
67
|
+
runWith<T>(value: V, callback: () => T): T {
|
|
68
|
+
const parentStore = AsyncContextStorage.getStore() ?? new Map();
|
|
69
|
+
|
|
70
|
+
const localOverrides = new Map([[token, value]]);
|
|
71
|
+
|
|
72
|
+
const proxyStore = new Proxy(parentStore, {
|
|
73
|
+
get(target, prop, receiver): any {
|
|
74
|
+
switch (prop) {
|
|
75
|
+
case 'get':
|
|
76
|
+
return (key: any): any => {
|
|
77
|
+
if (key === token) {
|
|
78
|
+
return localOverrides.get(key);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return target.get(key);
|
|
82
|
+
};
|
|
83
|
+
case 'set':
|
|
84
|
+
return (key: any, val: any): Map<any, any> => {
|
|
85
|
+
if (key === token) {
|
|
86
|
+
localOverrides.set(key, val);
|
|
87
|
+
} else {
|
|
88
|
+
target.set(key, val);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return proxyStore;
|
|
92
|
+
};
|
|
93
|
+
case 'has':
|
|
94
|
+
return (key: any): boolean => {
|
|
95
|
+
return key === token || target.has(key);
|
|
96
|
+
};
|
|
97
|
+
default:
|
|
98
|
+
return Reflect.get(target, prop, receiver);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return AsyncContextStorage.run(proxyStore, callback);
|
|
104
|
+
},
|
|
46
105
|
};
|
|
47
106
|
};
|
package/src/async-context.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AsyncContextException,
|
|
3
|
+
AsyncContextExceptionCode,
|
|
4
|
+
} from './async-context.exception';
|
|
1
5
|
import { AsyncContextStorage } from './async-context.storage';
|
|
2
6
|
|
|
3
7
|
export class AsyncContext {
|
|
@@ -23,7 +27,10 @@ export class AsyncContext {
|
|
|
23
27
|
private static getStoreOrThrow(): Map<any, any> {
|
|
24
28
|
const store = AsyncContextStorage.getStore();
|
|
25
29
|
if (!store) {
|
|
26
|
-
throw new
|
|
30
|
+
throw new AsyncContextException(
|
|
31
|
+
'AsyncContext is not provided',
|
|
32
|
+
AsyncContextExceptionCode.ASYNC_CONTEXT_IS_NOT_PROVIDED,
|
|
33
|
+
);
|
|
27
34
|
}
|
|
28
35
|
return store;
|
|
29
36
|
}
|
|
@@ -33,7 +40,10 @@ export class AsyncContext {
|
|
|
33
40
|
return;
|
|
34
41
|
}
|
|
35
42
|
if (AsyncContext.has(key)) {
|
|
36
|
-
throw new
|
|
43
|
+
throw new AsyncContextException(
|
|
44
|
+
'Unexpected rewrite',
|
|
45
|
+
AsyncContextExceptionCode.UNEXPECTED_REWRITE,
|
|
46
|
+
);
|
|
37
47
|
}
|
|
38
48
|
}
|
|
39
49
|
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import type { AsyncContextProvider } from '../src';
|
|
2
|
+
import {
|
|
3
|
+
AsyncContextException,
|
|
4
|
+
AsyncContextExceptionCode,
|
|
5
|
+
createAsyncContextProvider,
|
|
6
|
+
} from '../src';
|
|
7
|
+
import { AsyncContext } from '../src/async-context';
|
|
8
|
+
import { AsyncContextStorage } from '../src/async-context.storage';
|
|
9
|
+
|
|
10
|
+
describe('AsyncContextProvider', () => {
|
|
11
|
+
const token = 'some token';
|
|
12
|
+
const oldValue = { value: 'old value' };
|
|
13
|
+
const newValue = { value: 'new value' };
|
|
14
|
+
let provider: AsyncContextProvider<{ value: string }>;
|
|
15
|
+
|
|
16
|
+
describe('rewritable', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
provider = createAsyncContextProvider({
|
|
19
|
+
name: token,
|
|
20
|
+
rewritable: true,
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('get', () => {
|
|
25
|
+
it('should return undefined if async context is not provided', () => {
|
|
26
|
+
expect(provider.get()).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should return undefined if value is not set', () => {
|
|
30
|
+
AsyncContextStorage.run(new Map(), () => {
|
|
31
|
+
expect(provider.get()).toBeUndefined();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return value', () => {
|
|
36
|
+
AsyncContextStorage.run(new Map([[token, oldValue]]), () => {
|
|
37
|
+
expect(provider.get()).toStrictEqual(oldValue);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('getOrThrow', () => {
|
|
43
|
+
it('should throw if async context is not provided', () => {
|
|
44
|
+
expect(async () => provider.getOrThrow()).rejects.toMatchObject({
|
|
45
|
+
constructor: AsyncContextException,
|
|
46
|
+
code: AsyncContextExceptionCode.VALUE_NOT_PROVIDED,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should throw if value is not set', () => {
|
|
51
|
+
AsyncContextStorage.run(new Map(), () => {
|
|
52
|
+
expect(async () => provider.getOrThrow()).rejects.toMatchObject({
|
|
53
|
+
constructor: AsyncContextException,
|
|
54
|
+
code: AsyncContextExceptionCode.VALUE_NOT_PROVIDED,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return value', () => {
|
|
60
|
+
AsyncContextStorage.run(new Map([[token, oldValue]]), () => {
|
|
61
|
+
expect(provider.getOrThrow()).toStrictEqual(oldValue);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('set', () => {
|
|
67
|
+
it('should change isSettled', () => {
|
|
68
|
+
expect(provider.isSettled).toStrictEqual(false);
|
|
69
|
+
AsyncContextStorage.run(new Map(), () => {
|
|
70
|
+
provider.set(newValue);
|
|
71
|
+
});
|
|
72
|
+
expect(provider.isSettled).toStrictEqual(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should call AsyncContext.set', () => {
|
|
76
|
+
const asyncContextMock = jest.spyOn(AsyncContext, 'set');
|
|
77
|
+
|
|
78
|
+
AsyncContextStorage.run(new Map([[token, oldValue]]), () => {
|
|
79
|
+
provider.set(newValue);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(asyncContextMock).toHaveBeenCalledWith(token, newValue, true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('runWith', () => {
|
|
87
|
+
const anotherToken = 'another token';
|
|
88
|
+
let anotherProvider: AsyncContextProvider<{ value: string }>;
|
|
89
|
+
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
anotherProvider = createAsyncContextProvider({
|
|
92
|
+
name: anotherToken,
|
|
93
|
+
rewritable: true,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should not throw if async context is not provided', () => {
|
|
98
|
+
provider.runWith(oldValue, () => {
|
|
99
|
+
provider.set(newValue);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should not rewrite parent store for this token', () => {
|
|
104
|
+
const valueInRunWith = { value: 'value in run with' };
|
|
105
|
+
const overrideValueInRunWith = { value: 'override value in run with' };
|
|
106
|
+
|
|
107
|
+
AsyncContextStorage.run(new Map([[token, oldValue]]), () => {
|
|
108
|
+
expect(provider.get()).toStrictEqual(oldValue);
|
|
109
|
+
|
|
110
|
+
provider.runWith(valueInRunWith, () => {
|
|
111
|
+
expect(provider.get()).toStrictEqual(valueInRunWith);
|
|
112
|
+
provider.set(overrideValueInRunWith);
|
|
113
|
+
expect(provider.get()).toStrictEqual(overrideValueInRunWith);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(provider.get()).toStrictEqual(oldValue);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should propagate value to parent store if set for another provider', () => {
|
|
121
|
+
const oldValueForAnotherProvider = {
|
|
122
|
+
value: 'oldValueForAnotherProvider',
|
|
123
|
+
};
|
|
124
|
+
const newValueForAnotherProvider = {
|
|
125
|
+
value: 'newValueForAnotherProvider',
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
AsyncContextStorage.run(
|
|
129
|
+
new Map([
|
|
130
|
+
[token, oldValue],
|
|
131
|
+
[anotherToken, oldValueForAnotherProvider],
|
|
132
|
+
]),
|
|
133
|
+
() => {
|
|
134
|
+
expect(anotherProvider.get()).toStrictEqual(
|
|
135
|
+
oldValueForAnotherProvider,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
provider.runWith(newValue, () => {
|
|
139
|
+
expect(anotherProvider.get()).toStrictEqual(
|
|
140
|
+
oldValueForAnotherProvider,
|
|
141
|
+
);
|
|
142
|
+
anotherProvider.set(newValueForAnotherProvider);
|
|
143
|
+
expect(anotherProvider.get()).toStrictEqual(
|
|
144
|
+
newValueForAnotherProvider,
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(anotherProvider.get()).toStrictEqual(
|
|
149
|
+
newValueForAnotherProvider,
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should correctly works in complex example', () => {
|
|
156
|
+
const firstProviderValue1 = { value: '1' };
|
|
157
|
+
const firstProviderValue2 = { value: '2' };
|
|
158
|
+
const firstProviderValue3 = { value: '3' };
|
|
159
|
+
const firstProviderValue4 = { value: '4' };
|
|
160
|
+
const firstProviderValue5 = { value: '5' };
|
|
161
|
+
const firstProviderValue6 = { value: '6' };
|
|
162
|
+
|
|
163
|
+
const secondProviderValue1 = { value: '1' };
|
|
164
|
+
const secondProviderValue2 = { value: '2' };
|
|
165
|
+
const secondProviderValue3 = { value: '3' };
|
|
166
|
+
const secondProviderValue4 = { value: '4' };
|
|
167
|
+
const secondProviderValue5 = { value: '5' };
|
|
168
|
+
|
|
169
|
+
AsyncContextStorage.run(
|
|
170
|
+
new Map([
|
|
171
|
+
[token, firstProviderValue1],
|
|
172
|
+
[anotherToken, secondProviderValue1],
|
|
173
|
+
]),
|
|
174
|
+
() => {
|
|
175
|
+
expect(provider.get()).toStrictEqual(firstProviderValue1);
|
|
176
|
+
expect(anotherProvider.get()).toStrictEqual(secondProviderValue1);
|
|
177
|
+
|
|
178
|
+
provider.runWith(firstProviderValue2, () => {
|
|
179
|
+
expect(provider.get()).toStrictEqual(firstProviderValue2);
|
|
180
|
+
expect(anotherProvider.get()).toStrictEqual(secondProviderValue1);
|
|
181
|
+
|
|
182
|
+
provider.set(firstProviderValue3);
|
|
183
|
+
anotherProvider.set(secondProviderValue2);
|
|
184
|
+
|
|
185
|
+
expect(provider.get()).toStrictEqual(firstProviderValue3);
|
|
186
|
+
expect(anotherProvider.get()).toStrictEqual(secondProviderValue2);
|
|
187
|
+
|
|
188
|
+
anotherProvider.runWith(secondProviderValue3, () => {
|
|
189
|
+
expect(provider.get()).toStrictEqual(firstProviderValue3);
|
|
190
|
+
expect(anotherProvider.get()).toStrictEqual(
|
|
191
|
+
secondProviderValue3,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
provider.set(firstProviderValue4);
|
|
195
|
+
anotherProvider.set(secondProviderValue4);
|
|
196
|
+
|
|
197
|
+
expect(provider.get()).toStrictEqual(firstProviderValue4);
|
|
198
|
+
expect(anotherProvider.get()).toStrictEqual(
|
|
199
|
+
secondProviderValue4,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
provider.runWith(firstProviderValue5, () => {
|
|
203
|
+
expect(provider.get()).toStrictEqual(firstProviderValue5);
|
|
204
|
+
expect(anotherProvider.get()).toStrictEqual(
|
|
205
|
+
secondProviderValue4,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
provider.set(firstProviderValue6);
|
|
209
|
+
anotherProvider.set(secondProviderValue5);
|
|
210
|
+
|
|
211
|
+
expect(provider.get()).toStrictEqual(firstProviderValue6);
|
|
212
|
+
expect(anotherProvider.get()).toStrictEqual(
|
|
213
|
+
secondProviderValue5,
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
expect(provider.get()).toStrictEqual(firstProviderValue4);
|
|
218
|
+
expect(anotherProvider.get()).toStrictEqual(
|
|
219
|
+
secondProviderValue5,
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
expect(provider.get()).toStrictEqual(firstProviderValue4);
|
|
224
|
+
expect(anotherProvider.get()).toStrictEqual(secondProviderValue2);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(provider.get()).toStrictEqual(firstProviderValue1);
|
|
228
|
+
expect(anotherProvider.get()).toStrictEqual(secondProviderValue2);
|
|
229
|
+
},
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('is not rewritable', () => {
|
|
236
|
+
beforeEach(() => {
|
|
237
|
+
provider = createAsyncContextProvider({
|
|
238
|
+
name: token,
|
|
239
|
+
rewritable: false,
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('set', () => {
|
|
244
|
+
it('should call AsyncContext.set', () => {
|
|
245
|
+
const asyncContextMock = jest.spyOn(AsyncContext, 'set');
|
|
246
|
+
|
|
247
|
+
AsyncContextStorage.run(new Map(), () => {
|
|
248
|
+
provider.set(newValue);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
expect(asyncContextMock).toHaveBeenCalledWith(token, newValue, false);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { AsyncContextException, AsyncContextExceptionCode } from '../src';
|
|
2
|
+
import { AsyncContext } from '../src/async-context';
|
|
3
|
+
import { AsyncContextStorage } from '../src/async-context.storage';
|
|
4
|
+
|
|
5
|
+
describe('AsyncContext', () => {
|
|
6
|
+
const token = 'key';
|
|
7
|
+
const oldValue = { value: 'old value' };
|
|
8
|
+
const newValue = { value: 'new value' };
|
|
9
|
+
|
|
10
|
+
describe('set', () => {
|
|
11
|
+
it('should throw if async context is not provided', () => {
|
|
12
|
+
expect(async () =>
|
|
13
|
+
AsyncContext.set(token, newValue, true),
|
|
14
|
+
).rejects.toMatchObject({
|
|
15
|
+
constructor: AsyncContextException,
|
|
16
|
+
code: AsyncContextExceptionCode.ASYNC_CONTEXT_IS_NOT_PROVIDED,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should throw if token is not rewritable and value was set', () => {
|
|
21
|
+
AsyncContextStorage.run(new Map([[token, oldValue]]), () => {
|
|
22
|
+
expect(async () =>
|
|
23
|
+
AsyncContext.set(token, newValue, false),
|
|
24
|
+
).rejects.toMatchObject({
|
|
25
|
+
constructor: AsyncContextException,
|
|
26
|
+
code: AsyncContextExceptionCode.UNEXPECTED_REWRITE,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should set value', () => {
|
|
32
|
+
AsyncContextStorage.run(new Map([[token, oldValue]]), () => {
|
|
33
|
+
const store = AsyncContextStorage.getStore()!;
|
|
34
|
+
|
|
35
|
+
expect(store.get(token)).toStrictEqual(oldValue);
|
|
36
|
+
AsyncContext.set(token, newValue, true);
|
|
37
|
+
expect(store.get(token)).toStrictEqual(newValue);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('get', () => {
|
|
43
|
+
it('should return undefined if async context is not provided', () => {
|
|
44
|
+
expect(AsyncContext.get(token)).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should return undefined if value is not set', () => {
|
|
48
|
+
AsyncContextStorage.run(new Map(), () => {
|
|
49
|
+
expect(AsyncContext.get(token)).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should return value', () => {
|
|
54
|
+
AsyncContextStorage.run(new Map([[token, newValue]]), () => {
|
|
55
|
+
expect(AsyncContext.get(token)).toStrictEqual(newValue);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('has', () => {
|
|
61
|
+
it('should return false if async context is not provided', () => {
|
|
62
|
+
expect(AsyncContext.has(token)).toStrictEqual(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return false if value is not set', () => {
|
|
66
|
+
AsyncContextStorage.run(new Map(), () => {
|
|
67
|
+
expect(AsyncContext.has(token)).toStrictEqual(false);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should return value', () => {
|
|
72
|
+
AsyncContextStorage.run(new Map([[token, newValue]]), () => {
|
|
73
|
+
expect(AsyncContext.has(token)).toStrictEqual(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|