@rsdk/actx 5.8.2 → 5.9.0-next.1

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.
@@ -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"}
@@ -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 Error('AsyncContext is not provided');
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 Error('Unexpected rewrite');
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,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAClD,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,KAAK,CAAC,oBAAoB,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;CACF;AApCD,oCAoCC"}
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 Error('Value not provided: ' + token);
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;AAWxC,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,KAAK,CAAC,sBAAsB,GAAG,KAAK,CAAC,CAAC;YAClD,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;KACF,CAAC;AACJ,CAAC,CAAC;AAnCW,QAAA,0BAA0B,8BAmCrC"}
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
@@ -1,2 +1,3 @@
1
1
  export { AsyncContextModule } from './async-context.module';
2
2
  export { createAsyncContextProvider, AsyncContextProvider, } from './async-context.provider';
3
+ export { AsyncContextException, AsyncContextExceptionCode, } from './async-context.exception';
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.8.2",
3
+ "version": "5.9.0-next.1",
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": "524bd5c5e00b467f2fa46d6d5f55b330c687abc5"
18
+ "gitHead": "0ac1c409f8e5f7c79d5625a72950169d46e04b00"
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 Error('Value not provided: ' + token);
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
  };
@@ -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 Error('AsyncContext is not provided');
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 Error('Unexpected rewrite');
43
+ throw new AsyncContextException(
44
+ 'Unexpected rewrite',
45
+ AsyncContextExceptionCode.UNEXPECTED_REWRITE,
46
+ );
37
47
  }
38
48
  }
39
49
  }
package/src/index.ts CHANGED
@@ -4,3 +4,7 @@ export {
4
4
  createAsyncContextProvider,
5
5
  AsyncContextProvider,
6
6
  } from './async-context.provider';
7
+ export {
8
+ AsyncContextException,
9
+ AsyncContextExceptionCode,
10
+ } from './async-context.exception';
@@ -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
+ });