@hg-ts/async-context 0.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.
Files changed (50) hide show
  1. package/.eslintcache +1 -0
  2. package/.eslintrc.json +1 -0
  3. package/.mocharc.cjs +1 -0
  4. package/.nyc_output/1fdb802a-0f74-4beb-92f1-7d2d13cf70c1.json +1 -0
  5. package/.nyc_output/48e1f41f-91f1-4439-8349-fc1f9c978622.json +1 -0
  6. package/.nyc_output/b72c51ff-5eff-456d-8aad-03eabc7d5197.json +1 -0
  7. package/.nyc_output/processinfo/1fdb802a-0f74-4beb-92f1-7d2d13cf70c1.json +1 -0
  8. package/.nyc_output/processinfo/48e1f41f-91f1-4439-8349-fc1f9c978622.json +1 -0
  9. package/.nyc_output/processinfo/b72c51ff-5eff-456d-8aad-03eabc7d5197.json +1 -0
  10. package/.nyc_output/processinfo/index.json +1 -0
  11. package/.nycrc +3 -0
  12. package/dist/async-context.not-found.exception.d.ts +5 -0
  13. package/dist/async-context.not-found.exception.d.ts.map +1 -0
  14. package/dist/async-context.not-found.exception.js +11 -0
  15. package/dist/async-context.not-found.exception.js.map +1 -0
  16. package/dist/async-context.provider.d.ts +18 -0
  17. package/dist/async-context.provider.d.ts.map +1 -0
  18. package/dist/async-context.provider.js +61 -0
  19. package/dist/async-context.provider.js.map +1 -0
  20. package/dist/async-context.test-suite.d.ts +16 -0
  21. package/dist/async-context.test-suite.d.ts.map +1 -0
  22. package/dist/async-context.test-suite.js +159 -0
  23. package/dist/async-context.test-suite.js.map +1 -0
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +6 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist-esm/async-context.not-found.exception.d.ts +5 -0
  29. package/dist-esm/async-context.not-found.exception.d.ts.map +1 -0
  30. package/dist-esm/async-context.not-found.exception.js +7 -0
  31. package/dist-esm/async-context.not-found.exception.js.map +1 -0
  32. package/dist-esm/async-context.provider.d.ts +18 -0
  33. package/dist-esm/async-context.provider.d.ts.map +1 -0
  34. package/dist-esm/async-context.provider.js +57 -0
  35. package/dist-esm/async-context.provider.js.map +1 -0
  36. package/dist-esm/async-context.test-suite.d.ts +16 -0
  37. package/dist-esm/async-context.test-suite.d.ts.map +1 -0
  38. package/dist-esm/async-context.test-suite.js +156 -0
  39. package/dist-esm/async-context.test-suite.js.map +1 -0
  40. package/dist-esm/index.d.ts +3 -0
  41. package/dist-esm/index.d.ts.map +1 -0
  42. package/dist-esm/index.js +3 -0
  43. package/dist-esm/index.js.map +1 -0
  44. package/package.json +44 -0
  45. package/src/async-context.not-found.exception.ts +7 -0
  46. package/src/async-context.provider.ts +82 -0
  47. package/src/async-context.test-suite.ts +161 -0
  48. package/src/index.ts +2 -0
  49. package/tsconfig.esm.json +9 -0
  50. package/tsconfig.json +9 -0
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./async-context.provider"), exports);
5
+ tslib_1.__exportStar(require("./async-context.not-found.exception"), exports);
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mEAAyC;AACzC,8EAAoD"}
@@ -0,0 +1,5 @@
1
+ import { BaseException } from '@hg-ts/exception';
2
+ export declare class AsyncContextNotFoundException extends BaseException {
3
+ constructor();
4
+ }
5
+ //# sourceMappingURL=async-context.not-found.exception.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context.not-found.exception.d.ts","sourceRoot":"","sources":["../src/async-context.not-found.exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,qBAAa,6BAA8B,SAAQ,aAAa;;CAI/D"}
@@ -0,0 +1,7 @@
1
+ import { BaseException } from '@hg-ts/exception';
2
+ export class AsyncContextNotFoundException extends BaseException {
3
+ constructor() {
4
+ super('Async context not provided');
5
+ }
6
+ }
7
+ //# sourceMappingURL=async-context.not-found.exception.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context.not-found.exception.js","sourceRoot":"","sources":["../src/async-context.not-found.exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,OAAO,6BAA8B,SAAQ,aAAa;IAC/D;QACC,KAAK,CAAC,4BAA4B,CAAC,CAAC;IACrC,CAAC;CACD"}
@@ -0,0 +1,18 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="@hg-ts/types" />
3
+ import { AsyncLocalStorage } from 'node:async_hooks';
4
+ import { MonoTypeOperatorFunction } from 'rxjs';
5
+ type AsyncContextProviderCallback<T> = () => T;
6
+ type AsyncContextFactory<InputType, Context> = (input: InputType) => Context;
7
+ export declare class AsyncContextProvider<Context> {
8
+ protected readonly storage: AsyncLocalStorage<Context>;
9
+ get(): Nullable<Context>;
10
+ getOrFail(): NonNullable<Context>;
11
+ run<ReturnType>(context: Context, callback: AsyncContextProviderCallback<ReturnType>): ReturnType;
12
+ exit<ReturnType>(callback: AsyncContextProviderCallback<ReturnType>): ReturnType;
13
+ runOperator<T>(factory?: AsyncContextFactory<T, Context>): MonoTypeOperatorFunction<T>;
14
+ exitOperator<T>(): MonoTypeOperatorFunction<T>;
15
+ private createOperator;
16
+ }
17
+ export {};
18
+ //# sourceMappingURL=async-context.provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context.provider.d.ts","sourceRoot":"","sources":["../src/async-context.provider.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EACN,wBAAwB,EAExB,MAAM,MAAM,CAAC;AAGd,KAAK,4BAA4B,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAC/C,KAAK,mBAAmB,CAAC,SAAS,EAAE,OAAO,IAAI,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC;AAI7E,qBAAa,oBAAoB,CAAC,OAAO;IACxC,SAAS,CAAC,QAAQ,CAAC,OAAO,6BAAoC;IAEvD,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC;IAIxB,SAAS,IAAI,WAAW,CAAC,OAAO,CAAC;IAUjC,GAAG,CAAC,UAAU,EACpB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,4BAA4B,CAAC,UAAU,CAAC,GAChD,UAAU;IAIN,IAAI,CAAC,UAAU,EACrB,QAAQ,EAAE,4BAA4B,CAAC,UAAU,CAAC,GAChD,UAAU;IAIN,WAAW,CAAC,CAAC,EACnB,OAAO,CAAC,EAAE,mBAAmB,CAAC,CAAC,EAAE,OAAO,CAAC,GACvC,wBAAwB,CAAC,CAAC,CAAC;IAOvB,YAAY,CAAC,CAAC,KAAK,wBAAwB,CAAC,CAAC,CAAC;IAIrD,OAAO,CAAC,cAAc;CA0BtB"}
@@ -0,0 +1,57 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { Observable, } from 'rxjs';
3
+ import { AsyncContextNotFoundException } from './async-context.not-found.exception';
4
+ export class AsyncContextProvider {
5
+ storage = new AsyncLocalStorage();
6
+ get() {
7
+ return this.storage.getStore() ?? null;
8
+ }
9
+ getOrFail() {
10
+ const context = this.get();
11
+ if (typeof context === 'undefined' || context === null) {
12
+ throw new AsyncContextNotFoundException();
13
+ }
14
+ return context;
15
+ }
16
+ run(context, callback) {
17
+ return this.storage.run(context, callback);
18
+ }
19
+ exit(callback) {
20
+ return this.storage.exit(callback);
21
+ }
22
+ runOperator(factory) {
23
+ return this.createOperator((next, value) => {
24
+ const context = factory ? factory(value) : value;
25
+ this.storage.run(context, next);
26
+ });
27
+ }
28
+ exitOperator() {
29
+ return this.createOperator(next => this.storage.exit(next));
30
+ }
31
+ createOperator(onNext) {
32
+ return (source) => new Observable(subscriber => {
33
+ const callNext = input => {
34
+ const next = () => {
35
+ if (!subscriber.closed) {
36
+ subscriber.next(input);
37
+ }
38
+ };
39
+ onNext(next, input);
40
+ };
41
+ const callError = input => {
42
+ const next = () => {
43
+ if (!subscriber.closed) {
44
+ subscriber.error(input);
45
+ }
46
+ };
47
+ onNext(next, input);
48
+ };
49
+ source.subscribe({
50
+ next: callNext,
51
+ error: callError,
52
+ complete: () => subscriber.complete(),
53
+ });
54
+ });
55
+ }
56
+ }
57
+ //# sourceMappingURL=async-context.provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context.provider.js","sourceRoot":"","sources":["../src/async-context.provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAEN,UAAU,GACV,MAAM,MAAM,CAAC;AACd,OAAO,EAAE,6BAA6B,EAAE,MAAM,qCAAqC,CAAC;AAOpF,MAAM,OAAO,oBAAoB;IACb,OAAO,GAAG,IAAI,iBAAiB,EAAW,CAAC;IAEvD,GAAG;QACT,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC;IACxC,CAAC;IAEM,SAAS;QACf,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE3B,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,KAAK,IAAI,EAAE;YACvD,MAAM,IAAI,6BAA6B,EAAE,CAAC;SAC1C;QAED,OAAO,OAA+B,CAAC;IACxC,CAAC;IAEM,GAAG,CACT,OAAgB,EAChB,QAAkD;QAElD,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAEM,IAAI,CACV,QAAkD;QAElD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAEM,WAAW,CACjB,OAAyC;QAEzC,OAAO,IAAI,CAAC,cAAc,CAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAY,CAAC;YACxD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;IACJ,CAAC;IAEM,YAAY;QAClB,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,CAAC;IAEO,cAAc,CAAI,MAA4C;QACrE,OAAO,CAAC,MAAM,EAAiB,EAAE,CAAC,IAAI,UAAU,CAAI,UAAU,CAAC,EAAE;YAChE,MAAM,QAAQ,GAAoB,KAAK,CAAC,EAAE;gBACzC,MAAM,IAAI,GAAG,GAAS,EAAE;oBACvB,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;wBACvB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;qBACvB;gBACF,CAAC,CAAC;gBACF,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACrB,CAAC,CAAC;YACF,MAAM,SAAS,GAAoB,KAAK,CAAC,EAAE;gBAC1C,MAAM,IAAI,GAAG,GAAS,EAAE;oBACvB,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;wBACvB,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;qBACxB;gBACF,CAAC,CAAC;gBACF,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACrB,CAAC,CAAC;YAEF,MAAM,CAAC,SAAS,CAAC;gBAChB,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,SAAS;gBAChB,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE;aACrC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACJ,CAAC;CACD"}
@@ -0,0 +1,16 @@
1
+ import { TestSuite } from '@hg-ts/tests';
2
+ export declare class AsyncContextTestSuite extends TestSuite {
3
+ private context;
4
+ emptyContext(): Promise<void>;
5
+ noPromiseContext(): Promise<void>;
6
+ promiseContext(): Promise<void>;
7
+ operatorContext(): Promise<void>;
8
+ operatorWithFactory(): Promise<void>;
9
+ operatorWithErrorContext(): Promise<void>;
10
+ operatorAfterUnsubscribe(): Promise<void>;
11
+ operatorWithErrorAfterUnsubscribe(): Promise<void>;
12
+ contextNotFound(): Promise<void>;
13
+ beforeEach(): Promise<void>;
14
+ private createOperatorTestSubscription;
15
+ }
16
+ //# sourceMappingURL=async-context.test-suite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context.test-suite.d.ts","sourceRoot":"","sources":["../src/async-context.test-suite.ts"],"names":[],"mappings":"AAAA,OAAO,EAKN,SAAS,EACT,MAAM,cAAc,CAAC;AAiBtB,qBACa,qBAAsB,SAAQ,SAAS;IACnD,OAAO,CAAC,OAAO,CAA+B;IAGjC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAK7B,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBjC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB/B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAUhC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAUpC,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAUzC,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAWzC,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IAYlD,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjD,OAAO,CAAC,8BAA8B;CAgCtC"}
@@ -0,0 +1,156 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { Describe, expect, ExpectException, Test, TestSuite, } from '@hg-ts/tests';
3
+ import { Subject, tap, } from 'rxjs';
4
+ import { AsyncContextNotFoundException } from './async-context.not-found.exception';
5
+ import { AsyncContextProvider } from './async-context.provider';
6
+ let AsyncContextTestSuite = class AsyncContextTestSuite extends TestSuite {
7
+ context;
8
+ async emptyContext() {
9
+ expect(this.context.get()).toBe(null);
10
+ }
11
+ async noPromiseContext() {
12
+ const contextValue = Math.random();
13
+ const resultValue = Math.random();
14
+ const result = this.context.run(contextValue, () => {
15
+ expect(this.context.getOrFail()).toBe(contextValue);
16
+ this.context.exit(() => {
17
+ expect(this.context.get()).toBe(null);
18
+ });
19
+ return resultValue;
20
+ });
21
+ expect(result).toBe(resultValue);
22
+ }
23
+ async promiseContext() {
24
+ const contextValue = Math.random();
25
+ const resultValue = Math.random();
26
+ const result = await this.context.run(contextValue, async () => {
27
+ expect(this.context.getOrFail()).toBe(contextValue);
28
+ await this.context.exit(async () => {
29
+ expect(this.context.get()).toBe(null);
30
+ });
31
+ return resultValue;
32
+ });
33
+ expect(result).toBe(resultValue);
34
+ }
35
+ async operatorContext() {
36
+ const { subject, input } = this.createOperatorTestSubscription();
37
+ subject.next(input);
38
+ subject.complete();
39
+ expect.assertions(4);
40
+ }
41
+ async operatorWithFactory() {
42
+ const { subject, input } = this.createOperatorTestSubscription(value => -value);
43
+ subject.next(input);
44
+ subject.complete();
45
+ expect.assertions(4);
46
+ }
47
+ async operatorWithErrorContext() {
48
+ const { subject, input } = this.createOperatorTestSubscription();
49
+ subject.error(input);
50
+ subject.complete();
51
+ expect.assertions(4);
52
+ }
53
+ async operatorAfterUnsubscribe() {
54
+ const { subject, input, subscription } = this.createOperatorTestSubscription();
55
+ subscription.unsubscribe();
56
+ subject.next(input);
57
+ subject.complete();
58
+ expect.assertions(2);
59
+ }
60
+ async operatorWithErrorAfterUnsubscribe() {
61
+ const { subject, input, subscription } = this.createOperatorTestSubscription();
62
+ subscription.unsubscribe();
63
+ subject.error(input);
64
+ subject.complete();
65
+ expect.assertions(2);
66
+ }
67
+ async contextNotFound() {
68
+ this.context.getOrFail();
69
+ }
70
+ async beforeEach() {
71
+ this.context = new AsyncContextProvider();
72
+ }
73
+ createOperatorTestSubscription(factory) {
74
+ const subject = new Subject();
75
+ const input = Math.random();
76
+ const context = factory ? factory(input) : input;
77
+ const withContext = (value) => {
78
+ expect(value).toBe(input);
79
+ expect(this.context.getOrFail()).toBe(context);
80
+ };
81
+ const withoutContext = (value) => {
82
+ expect(value).toBe(input);
83
+ expect(this.context.get()).toBe(null);
84
+ };
85
+ const subscription = subject.asObservable()
86
+ .pipe(this.context.runOperator(factory), tap({
87
+ next: withContext,
88
+ error: withContext,
89
+ }), this.context.exitOperator(), tap({
90
+ next: withoutContext,
91
+ error: withoutContext,
92
+ }))
93
+ .subscribe({ next() { }, error() { } });
94
+ return { subject, subscription, input, context };
95
+ }
96
+ };
97
+ __decorate([
98
+ Test(),
99
+ __metadata("design:type", Function),
100
+ __metadata("design:paramtypes", []),
101
+ __metadata("design:returntype", Promise)
102
+ ], AsyncContextTestSuite.prototype, "emptyContext", null);
103
+ __decorate([
104
+ Test(),
105
+ __metadata("design:type", Function),
106
+ __metadata("design:paramtypes", []),
107
+ __metadata("design:returntype", Promise)
108
+ ], AsyncContextTestSuite.prototype, "noPromiseContext", null);
109
+ __decorate([
110
+ Test(),
111
+ __metadata("design:type", Function),
112
+ __metadata("design:paramtypes", []),
113
+ __metadata("design:returntype", Promise)
114
+ ], AsyncContextTestSuite.prototype, "promiseContext", null);
115
+ __decorate([
116
+ Test(),
117
+ __metadata("design:type", Function),
118
+ __metadata("design:paramtypes", []),
119
+ __metadata("design:returntype", Promise)
120
+ ], AsyncContextTestSuite.prototype, "operatorContext", null);
121
+ __decorate([
122
+ Test(),
123
+ __metadata("design:type", Function),
124
+ __metadata("design:paramtypes", []),
125
+ __metadata("design:returntype", Promise)
126
+ ], AsyncContextTestSuite.prototype, "operatorWithFactory", null);
127
+ __decorate([
128
+ Test(),
129
+ __metadata("design:type", Function),
130
+ __metadata("design:paramtypes", []),
131
+ __metadata("design:returntype", Promise)
132
+ ], AsyncContextTestSuite.prototype, "operatorWithErrorContext", null);
133
+ __decorate([
134
+ Test(),
135
+ __metadata("design:type", Function),
136
+ __metadata("design:paramtypes", []),
137
+ __metadata("design:returntype", Promise)
138
+ ], AsyncContextTestSuite.prototype, "operatorAfterUnsubscribe", null);
139
+ __decorate([
140
+ Test(),
141
+ __metadata("design:type", Function),
142
+ __metadata("design:paramtypes", []),
143
+ __metadata("design:returntype", Promise)
144
+ ], AsyncContextTestSuite.prototype, "operatorWithErrorAfterUnsubscribe", null);
145
+ __decorate([
146
+ Test(),
147
+ ExpectException(AsyncContextNotFoundException),
148
+ __metadata("design:type", Function),
149
+ __metadata("design:paramtypes", []),
150
+ __metadata("design:returntype", Promise)
151
+ ], AsyncContextTestSuite.prototype, "contextNotFound", null);
152
+ AsyncContextTestSuite = __decorate([
153
+ Describe()
154
+ ], AsyncContextTestSuite);
155
+ export { AsyncContextTestSuite };
156
+ //# sourceMappingURL=async-context.test-suite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context.test-suite.js","sourceRoot":"","sources":["../src/async-context.test-suite.ts"],"names":[],"mappings":";AAAA,OAAO,EACN,QAAQ,EACR,MAAM,EACN,eAAe,EACf,IAAI,EACJ,SAAS,GACT,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,OAAO,EAEP,GAAG,GACH,MAAM,MAAM,CAAC;AAEd,OAAO,EAAE,6BAA6B,EAAE,MAAM,qCAAqC,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAUzD,IAAM,qBAAqB,GAA3B,MAAM,qBAAsB,SAAQ,SAAS;IAC3C,OAAO,CAA+B;IAGjC,AAAN,KAAK,CAAC,YAAY;QACxB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IAGY,AAAN,KAAK,CAAC,gBAAgB;QAC5B,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACnC,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAElC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,EAAE;YAClD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAEpD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;gBACtB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvC,CAAC,CAAC,CAAC;YACH,OAAO,WAAW,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;IAGY,AAAN,KAAK,CAAC,cAAc;QAC1B,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACnC,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAElC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,IAAG,EAAE;YAC7D,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAEpD,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,IAAG,EAAE;gBACjC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvC,CAAC,CAAC,CAAC;YACH,OAAO,WAAW,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;IAGY,AAAN,KAAK,CAAC,eAAe;QAC3B,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,8BAA8B,EAAE,CAAC;QAEjE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,OAAO,CAAC,QAAQ,EAAE,CAAC;QAEnB,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IAGY,AAAN,KAAK,CAAC,mBAAmB;QAC/B,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,8BAA8B,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;QAEhF,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,OAAO,CAAC,QAAQ,EAAE,CAAC;QAEnB,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IAGY,AAAN,KAAK,CAAC,wBAAwB;QACpC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,8BAA8B,EAAE,CAAC;QAEjE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,QAAQ,EAAE,CAAC;QAEnB,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IAGY,AAAN,KAAK,CAAC,wBAAwB;QACpC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC,8BAA8B,EAAE,CAAC;QAE/E,YAAY,CAAC,WAAW,EAAE,CAAC;QAC3B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,OAAO,CAAC,QAAQ,EAAE,CAAC;QAEnB,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IAGY,AAAN,KAAK,CAAC,iCAAiC;QAC7C,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC,8BAA8B,EAAE,CAAC;QAE/E,YAAY,CAAC,WAAW,EAAE,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,QAAQ,EAAE,CAAC;QAEnB,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IAIY,AAAN,KAAK,CAAC,eAAe;QAC3B,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;IAC1B,CAAC;IAEe,KAAK,CAAC,UAAU;QAC/B,IAAI,CAAC,OAAO,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC3C,CAAC;IAEO,8BAA8B,CAAC,OAAmC;QACzE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAU,CAAC;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAEjD,MAAM,WAAW,GAAG,CAAC,KAAa,EAAQ,EAAE;YAC3C,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChD,CAAC,CAAC;QAEF,MAAM,cAAc,GAAG,CAAC,KAAa,EAAQ,EAAE;YAC9C,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,CAAC,CAAC;QAEF,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE;aACzC,IAAI,CACJ,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,EACjC,GAAG,CAAC;YACH,IAAI,EAAE,WAAW;YACjB,KAAK,EAAE,WAAW;SAClB,CAAC,EACF,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAC3B,GAAG,CAAC;YACH,IAAI,EAAE,cAAc;YACpB,KAAK,EAAE,cAAc;SACrB,CAAC,CACF;aACA,SAAS,CAAC,EAAE,IAAI,KAAI,CAAC,EAAE,KAAK,KAAI,CAAC,EAAE,CAAC,CAAC;QAEvC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAClD,CAAC;CACD,CAAA;AApIa;IADZ,IAAI,EAAE;;;;yDAGN;AAGY;IADZ,IAAI,EAAE;;;;6DAeN;AAGY;IADZ,IAAI,EAAE;;;;2DAeN;AAGY;IADZ,IAAI,EAAE;;;;4DAQN;AAGY;IADZ,IAAI,EAAE;;;;gEAQN;AAGY;IADZ,IAAI,EAAE;;;;qEAQN;AAGY;IADZ,IAAI,EAAE;;;;qEASN;AAGY;IADZ,IAAI,EAAE;;;;8EASN;AAIY;IAFZ,IAAI,EAAE;IACN,eAAe,CAAC,6BAA6B,CAAC;;;;4DAG9C;AAlGW,qBAAqB;IADjC,QAAQ,EAAE;GACE,qBAAqB,CAwIjC"}
@@ -0,0 +1,3 @@
1
+ export * from './async-context.provider';
2
+ export * from './async-context.not-found.exception';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,qCAAqC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './async-context.provider';
2
+ export * from './async-context.not-found.exception';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,qCAAqC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@hg-ts/async-context",
3
+ "version": "0.1.0",
4
+ "main": "dist/index.js",
5
+ "module": "dist-esm/index.js",
6
+ "repository": "git@gitlab.com:hyper-graph/framework.git",
7
+ "scripts": {
8
+ "prepack": "yarn build && yarn lint && yarn test",
9
+ "build": "hg build",
10
+ "build:dev": "hg build:dev",
11
+ "lint": "hg lint",
12
+ "lint:fix": "hg lint:fix",
13
+ "test": "hg test",
14
+ "test:dev": "hg test:dev",
15
+ "test:coverage": "hg test:coverage"
16
+ },
17
+ "devDependencies": {
18
+ "@hg-ts-config/eslint-config": "0.1.0",
19
+ "@hg-ts-config/nyc": "0.1.0",
20
+ "@hg-ts-config/typescript": "0.1.0",
21
+ "@hg-ts/cli": "0.1.0",
22
+ "@hg-ts/exception": "0.1.0",
23
+ "@hg-ts/tests": "0.1.0",
24
+ "@hg-ts/types": "0.1.0",
25
+ "@types/node": "20.8.7",
26
+ "@typescript-eslint/eslint-plugin": "6.8.0",
27
+ "@typescript-eslint/parser": "6.8.0",
28
+ "eslint": "8.52.0",
29
+ "mocha": "10.2.0",
30
+ "mocha-junit-reporter": "2.2.1",
31
+ "nyc": "15.1.0",
32
+ "reflect-metadata": "0.1.13",
33
+ "rxjs": "7.8.1",
34
+ "tsc-watch": "6.0.4",
35
+ "tslib": "2.6.2",
36
+ "typescript": "5.2.2"
37
+ },
38
+ "peerDependencies": {
39
+ "@hg-ts/exception": "0.1.0",
40
+ "reflect-metadata": "*",
41
+ "rxjs": "*",
42
+ "tslib": "*"
43
+ }
44
+ }
@@ -0,0 +1,7 @@
1
+ import { BaseException } from '@hg-ts/exception';
2
+
3
+ export class AsyncContextNotFoundException extends BaseException {
4
+ public constructor() {
5
+ super('Async context not provided');
6
+ }
7
+ }
@@ -0,0 +1,82 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import {
3
+ MonoTypeOperatorFunction,
4
+ Observable,
5
+ } from 'rxjs';
6
+ import { AsyncContextNotFoundException } from './async-context.not-found.exception';
7
+
8
+ type AsyncContextProviderCallback<T> = () => T;
9
+ type AsyncContextFactory<InputType, Context> = (input: InputType) => Context;
10
+
11
+ type NextCallback<T> = (value: T) => void;
12
+
13
+ export class AsyncContextProvider<Context> {
14
+ protected readonly storage = new AsyncLocalStorage<Context>();
15
+
16
+ public get(): Nullable<Context> {
17
+ return this.storage.getStore() ?? null;
18
+ }
19
+
20
+ public getOrFail(): NonNullable<Context> {
21
+ const context = this.get();
22
+
23
+ if (typeof context === 'undefined' || context === null) {
24
+ throw new AsyncContextNotFoundException();
25
+ }
26
+
27
+ return context as NonNullable<Context>;
28
+ }
29
+
30
+ public run<ReturnType>(
31
+ context: Context,
32
+ callback: AsyncContextProviderCallback<ReturnType>,
33
+ ): ReturnType {
34
+ return this.storage.run(context, callback);
35
+ }
36
+
37
+ public exit<ReturnType>(
38
+ callback: AsyncContextProviderCallback<ReturnType>,
39
+ ): ReturnType {
40
+ return this.storage.exit(callback);
41
+ }
42
+
43
+ public runOperator<T>(
44
+ factory?: AsyncContextFactory<T, Context>,
45
+ ): MonoTypeOperatorFunction<T> {
46
+ return this.createOperator<T>((next, value) => {
47
+ const context = factory ? factory(value) : value as any;
48
+ this.storage.run(context, next);
49
+ });
50
+ }
51
+
52
+ public exitOperator<T>(): MonoTypeOperatorFunction<T> {
53
+ return this.createOperator(next => this.storage.exit(next));
54
+ }
55
+
56
+ private createOperator<T>(onNext: (next: () => void, value: T) => void): MonoTypeOperatorFunction<any> {
57
+ return (source): Observable<T> => new Observable<T>(subscriber => {
58
+ const callNext: NextCallback<T> = input => {
59
+ const next = (): void => {
60
+ if (!subscriber.closed) {
61
+ subscriber.next(input);
62
+ }
63
+ };
64
+ onNext(next, input);
65
+ };
66
+ const callError: NextCallback<T> = input => {
67
+ const next = (): void => {
68
+ if (!subscriber.closed) {
69
+ subscriber.error(input);
70
+ }
71
+ };
72
+ onNext(next, input);
73
+ };
74
+
75
+ source.subscribe({
76
+ next: callNext,
77
+ error: callError,
78
+ complete: () => subscriber.complete(),
79
+ });
80
+ });
81
+ }
82
+ }
@@ -0,0 +1,161 @@
1
+ import {
2
+ Describe,
3
+ expect,
4
+ ExpectException,
5
+ Test,
6
+ TestSuite,
7
+ } from '@hg-ts/tests';
8
+ import {
9
+ Subject,
10
+ Subscription,
11
+ tap,
12
+ } from 'rxjs';
13
+
14
+ import { AsyncContextNotFoundException } from './async-context.not-found.exception';
15
+ import { AsyncContextProvider } from './async-context.provider';
16
+
17
+ type OperatorTestSubscription = {
18
+ subscription: Subscription;
19
+ subject: Subject<number>;
20
+ input: number;
21
+ context: number;
22
+ };
23
+
24
+ @Describe()
25
+ export class AsyncContextTestSuite extends TestSuite {
26
+ private context: AsyncContextProvider<number>;
27
+
28
+ @Test()
29
+ public async emptyContext(): Promise<void> {
30
+ expect(this.context.get()).toBe(null);
31
+ }
32
+
33
+ @Test()
34
+ public async noPromiseContext(): Promise<void> {
35
+ const contextValue = Math.random();
36
+ const resultValue = Math.random();
37
+
38
+ const result = this.context.run(contextValue, () => {
39
+ expect(this.context.getOrFail()).toBe(contextValue);
40
+
41
+ this.context.exit(() => {
42
+ expect(this.context.get()).toBe(null);
43
+ });
44
+ return resultValue;
45
+ });
46
+
47
+ expect(result).toBe(resultValue);
48
+ }
49
+
50
+ @Test()
51
+ public async promiseContext(): Promise<void> {
52
+ const contextValue = Math.random();
53
+ const resultValue = Math.random();
54
+
55
+ const result = await this.context.run(contextValue, async() => {
56
+ expect(this.context.getOrFail()).toBe(contextValue);
57
+
58
+ await this.context.exit(async() => {
59
+ expect(this.context.get()).toBe(null);
60
+ });
61
+ return resultValue;
62
+ });
63
+
64
+ expect(result).toBe(resultValue);
65
+ }
66
+
67
+ @Test()
68
+ public async operatorContext(): Promise<void> {
69
+ const { subject, input } = this.createOperatorTestSubscription();
70
+
71
+ subject.next(input);
72
+ subject.complete();
73
+
74
+ expect.assertions(4);
75
+ }
76
+
77
+ @Test()
78
+ public async operatorWithFactory(): Promise<void> {
79
+ const { subject, input } = this.createOperatorTestSubscription(value => -value);
80
+
81
+ subject.next(input);
82
+ subject.complete();
83
+
84
+ expect.assertions(4);
85
+ }
86
+
87
+ @Test()
88
+ public async operatorWithErrorContext(): Promise<void> {
89
+ const { subject, input } = this.createOperatorTestSubscription();
90
+
91
+ subject.error(input);
92
+ subject.complete();
93
+
94
+ expect.assertions(4);
95
+ }
96
+
97
+ @Test()
98
+ public async operatorAfterUnsubscribe(): Promise<void> {
99
+ const { subject, input, subscription } = this.createOperatorTestSubscription();
100
+
101
+ subscription.unsubscribe();
102
+ subject.next(input);
103
+ subject.complete();
104
+
105
+ expect.assertions(2);
106
+ }
107
+
108
+ @Test()
109
+ public async operatorWithErrorAfterUnsubscribe(): Promise<void> {
110
+ const { subject, input, subscription } = this.createOperatorTestSubscription();
111
+
112
+ subscription.unsubscribe();
113
+ subject.error(input);
114
+ subject.complete();
115
+
116
+ expect.assertions(2);
117
+ }
118
+
119
+ @Test()
120
+ @ExpectException(AsyncContextNotFoundException)
121
+ public async contextNotFound(): Promise<void> {
122
+ this.context.getOrFail();
123
+ }
124
+
125
+ public override async beforeEach(): Promise<void> {
126
+ this.context = new AsyncContextProvider();
127
+ }
128
+
129
+ private createOperatorTestSubscription(factory?: (input: number) => number): OperatorTestSubscription {
130
+ const subject = new Subject<number>();
131
+ const input = Math.random();
132
+ const context = factory ? factory(input) : input;
133
+
134
+ const withContext = (value: number): void => {
135
+ expect(value).toBe(input);
136
+ expect(this.context.getOrFail()).toBe(context);
137
+ };
138
+
139
+ const withoutContext = (value: number): void => {
140
+ expect(value).toBe(input);
141
+ expect(this.context.get()).toBe(null);
142
+ };
143
+
144
+ const subscription = subject.asObservable()
145
+ .pipe(
146
+ this.context.runOperator(factory),
147
+ tap({
148
+ next: withContext,
149
+ error: withContext,
150
+ }),
151
+ this.context.exitOperator(),
152
+ tap({
153
+ next: withoutContext,
154
+ error: withoutContext,
155
+ }),
156
+ )
157
+ .subscribe({ next() {}, error() {} });
158
+
159
+ return { subject, subscription, input, context };
160
+ }
161
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './async-context.provider';
2
+ export * from './async-context.not-found.exception';
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@hg-ts-config/typescript/esm",
3
+ "compilerOptions": {
4
+ "baseUrl": "src",
5
+ "rootDir": "src",
6
+ "outDir": "dist-esm"
7
+ },
8
+ "exclude": ["dist-esm", "dist"]
9
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@hg-ts-config/typescript/commonjs",
3
+ "compilerOptions": {
4
+ "baseUrl": "src",
5
+ "rootDir": "src",
6
+ "outDir": "dist"
7
+ },
8
+ "exclude": ["dist-esm", "dist"]
9
+ }