@tstdl/base 0.93.138 → 0.93.140

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 (138) hide show
  1. package/README.md +166 -0
  2. package/ai/genkit/multi-region.plugin.js +5 -3
  3. package/ai/genkit/tests/multi-region.test.d.ts +1 -0
  4. package/ai/genkit/tests/multi-region.test.js +5 -2
  5. package/ai/parser/parser.js +2 -2
  6. package/ai/prompts/build.js +1 -0
  7. package/ai/prompts/instructions-formatter.d.ts +15 -2
  8. package/ai/prompts/instructions-formatter.js +36 -31
  9. package/ai/prompts/prompt-builder.js +5 -5
  10. package/ai/prompts/steering.d.ts +3 -2
  11. package/ai/prompts/steering.js +3 -1
  12. package/ai/tests/instructions-formatter.test.js +1 -0
  13. package/api/README.md +403 -0
  14. package/api/client/client.js +7 -13
  15. package/api/client/tests/api-client.test.js +10 -10
  16. package/api/default-error-handlers.js +1 -1
  17. package/api/response.d.ts +2 -2
  18. package/api/response.js +22 -33
  19. package/api/server/api-controller.d.ts +1 -1
  20. package/api/server/api-controller.js +3 -3
  21. package/api/server/api-request-token.provider.d.ts +1 -0
  22. package/api/server/api-request-token.provider.js +1 -0
  23. package/api/server/middlewares/allowed-methods.middleware.js +2 -1
  24. package/api/server/middlewares/content-type.middleware.js +2 -1
  25. package/api/types.d.ts +3 -2
  26. package/application/README.md +240 -0
  27. package/application/application.js +2 -2
  28. package/audit/README.md +267 -0
  29. package/authentication/README.md +288 -0
  30. package/authentication/client/authentication.service.d.ts +12 -11
  31. package/authentication/client/authentication.service.js +21 -21
  32. package/authentication/client/http-client.middleware.js +2 -2
  33. package/authentication/tests/authentication.client-error-handling.test.js +2 -1
  34. package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
  35. package/browser/README.md +401 -0
  36. package/cancellation/README.md +156 -0
  37. package/cancellation/tests/coverage.test.d.ts +1 -0
  38. package/cancellation/tests/coverage.test.js +49 -0
  39. package/cancellation/tests/leak.test.d.ts +1 -0
  40. package/cancellation/tests/leak.test.js +35 -0
  41. package/cancellation/tests/token.test.d.ts +1 -0
  42. package/cancellation/tests/token.test.js +136 -0
  43. package/cancellation/token.d.ts +53 -177
  44. package/cancellation/token.js +132 -201
  45. package/context/README.md +174 -0
  46. package/cookie/README.md +161 -0
  47. package/css/README.md +157 -0
  48. package/data-structures/README.md +320 -0
  49. package/decorators/README.md +140 -0
  50. package/distributed-loop/README.md +231 -0
  51. package/distributed-loop/distributed-loop.js +1 -1
  52. package/document-management/README.md +403 -0
  53. package/document-management/server/services/document-management.service.js +9 -7
  54. package/document-management/tests/document-management-core.test.js +2 -7
  55. package/document-management/tests/document-management.api.test.js +6 -7
  56. package/document-management/tests/document-statistics.service.test.js +11 -12
  57. package/document-management/tests/document.service.test.js +3 -3
  58. package/document-management/tests/enum-helpers.test.js +2 -3
  59. package/dom/README.md +213 -0
  60. package/enumerable/README.md +259 -0
  61. package/enumeration/README.md +121 -0
  62. package/errors/README.md +267 -0
  63. package/file/README.md +191 -0
  64. package/formats/README.md +210 -0
  65. package/function/README.md +144 -0
  66. package/http/README.md +318 -0
  67. package/http/client/adapters/undici.adapter.js +1 -1
  68. package/http/client/http-client-request.d.ts +6 -5
  69. package/http/client/http-client-request.js +8 -9
  70. package/http/server/node/node-http-server.js +1 -2
  71. package/image-service/README.md +137 -0
  72. package/injector/README.md +491 -0
  73. package/injector/injector.d.ts +1 -0
  74. package/injector/injector.js +17 -5
  75. package/injector/tests/leak.test.d.ts +1 -0
  76. package/injector/tests/leak.test.js +45 -0
  77. package/intl/README.md +113 -0
  78. package/json-path/README.md +182 -0
  79. package/jsx/README.md +154 -0
  80. package/key-value-store/README.md +191 -0
  81. package/lock/README.md +249 -0
  82. package/lock/web/web-lock.js +119 -47
  83. package/logger/README.md +287 -0
  84. package/mail/README.md +256 -0
  85. package/memory/README.md +144 -0
  86. package/message-bus/README.md +244 -0
  87. package/message-bus/message-bus-base.js +1 -1
  88. package/module/README.md +182 -0
  89. package/module/module.d.ts +1 -1
  90. package/module/module.js +77 -17
  91. package/module/modules/web-server.module.js +1 -1
  92. package/notification/tests/notification-type.service.test.js +24 -15
  93. package/object-storage/README.md +300 -0
  94. package/openid-connect/README.md +274 -0
  95. package/orm/README.md +423 -0
  96. package/package.json +8 -6
  97. package/password/README.md +164 -0
  98. package/pdf/README.md +246 -0
  99. package/polyfills.js +1 -0
  100. package/pool/README.md +198 -0
  101. package/process/README.md +237 -0
  102. package/promise/README.md +252 -0
  103. package/promise/cancelable-promise.js +1 -1
  104. package/random/README.md +193 -0
  105. package/reflection/README.md +305 -0
  106. package/rpc/README.md +386 -0
  107. package/rxjs-utils/README.md +262 -0
  108. package/schema/README.md +342 -0
  109. package/serializer/README.md +342 -0
  110. package/signals/implementation/README.md +134 -0
  111. package/sse/README.md +278 -0
  112. package/task-queue/README.md +300 -0
  113. package/task-queue/postgres/task-queue.d.ts +2 -1
  114. package/task-queue/postgres/task-queue.js +32 -2
  115. package/task-queue/task-context.js +1 -1
  116. package/task-queue/task-queue.d.ts +17 -0
  117. package/task-queue/task-queue.js +103 -44
  118. package/task-queue/tests/complex.test.js +4 -4
  119. package/task-queue/tests/dependencies.test.js +4 -2
  120. package/task-queue/tests/queue.test.js +111 -0
  121. package/task-queue/tests/worker.test.js +21 -13
  122. package/templates/README.md +287 -0
  123. package/testing/README.md +157 -0
  124. package/text/README.md +346 -0
  125. package/threading/README.md +238 -0
  126. package/types/README.md +311 -0
  127. package/utils/README.md +322 -0
  128. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  129. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  130. package/utils/async-iterable-helpers/take-until.js +4 -4
  131. package/utils/backoff.js +89 -30
  132. package/utils/retry-with-backoff.js +1 -1
  133. package/utils/timer.d.ts +1 -1
  134. package/utils/timer.js +5 -7
  135. package/utils/timing.d.ts +1 -1
  136. package/utils/timing.js +2 -4
  137. package/utils/z-base32.d.ts +1 -0
  138. package/utils/z-base32.js +1 -0
@@ -1,247 +1,178 @@
1
- import { BehaviorSubject, defer, filter, first, firstValueFrom, from, fromEvent, isObservable, map, skip, take } from 'rxjs';
2
- import { match, P } from 'ts-pattern';
1
+ import { isDevMode } from '../core.js';
3
2
  import { registerFinalization } from '../memory/finalization.js';
3
+ import { toArray } from '../utils/array/array.js';
4
4
  import { noop } from '../utils/noop.js';
5
- import { isBoolean } from '../utils/type-guards.js';
5
+ import { assertDefinedPass, isInstanceOf, isUndefined } from '../utils/type-guards.js';
6
6
  /**
7
- * A read-only view of a CancellationToken's state.
8
- *
9
- * It allows consumers to subscribe to state changes, await cancellation,
10
- * or convert to other signal types (like AbortSignal) without being able
11
- * to change the token's state.
12
- *
13
- * It is both `PromiseLike<void>` (resolves when set) and `Subscribable<void>`
14
- * (emits when set).
7
+ * A CancellationSignal represents a signal that can be used to indicate cancellation of an operation.
8
+ * It is immutable and can be shared across multiple operations.
9
+ * It can be created from one or more cancellation sources, such as other signals or AbortControllers.
15
10
  */
16
11
  export class CancellationSignal {
17
- _stateSubject;
12
+ #disposeAbortController = new AbortController();
18
13
  #abortSignal;
19
- /**
20
- * Observable which emits the current state (true for set, false for unset)
21
- * and any subsequent state changes.
22
- */
23
- state$ = defer(() => this._stateSubject);
24
- /**
25
- * Observable which emits `void` only when the token becomes set.
26
- */
27
- set$ = this.state$.pipe(filter((state) => state), map(() => undefined));
28
- /**
29
- * Observable which emits `void` only when the token becomes unset.
30
- */
31
- unset$ = this.state$.pipe(filter((state) => !state), map(() => undefined));
32
- /**
33
- * A promise that resolves with the next state of the token.
34
- */
35
- get $state() {
36
- // skip(1) to wait for the *next* change, not the current state.
37
- return firstValueFrom(this.state$.pipe(skip(1)));
14
+ get abortSignal() {
15
+ return this.#abortSignal;
38
16
  }
39
- /**
40
- * A promise that resolves when the token is next set.
41
- * If the token is already set, it resolves immediately.
42
- */
43
- get $set() {
44
- return firstValueFrom(this.set$.pipe(first()));
17
+ get isSet() {
18
+ return this.abortSignal.aborted;
45
19
  }
46
- /**
47
- * A promise that resolves when the token is next unset.
48
- * If the token is already unset, it resolves immediately.
49
- */
50
- get $unset() {
51
- return firstValueFrom(this.unset$.pipe(first()));
20
+ get isUnset() {
21
+ return !this.isSet;
52
22
  }
53
- /**
54
- * The current state of the token.
55
- * @returns `true` if set, `false` if unset.
56
- */
57
- get state() {
58
- return this._stateSubject.value;
23
+ get reason() {
24
+ return this.abortSignal.reason;
59
25
  }
60
- /**
61
- * Whether the token is currently in a 'set' state.
62
- */
63
- get isSet() {
64
- return this._stateSubject.value;
26
+ constructor(sources) {
27
+ const signal = sourcesToAbortSignal(sources, this.#disposeAbortController);
28
+ this.#abortSignal = signal;
29
+ if (isDevMode()) {
30
+ // this a development only check to catch cases where the signal is used are created but not used, which can lead to memory leaks if they are not properly disposed.
31
+ registerFinalization(this, (controller) => controller.abort(new Error('CancellationSignal was garbage collected while still in use. This can lead to memory leaks.')), this.#disposeAbortController);
32
+ }
65
33
  }
66
- /**
67
- * Whether the token is currently in an 'unset' state.
68
- */
69
- get isUnset() {
70
- return !this._stateSubject.value;
34
+ static from(sources) {
35
+ return new CancellationSignal(sources);
71
36
  }
72
- /**
73
- * @internal
74
- */
75
- constructor(stateSubject) {
76
- this._stateSubject = stateSubject;
37
+ static timeout(milliseconds) {
38
+ const signal = AbortSignal.timeout(milliseconds);
39
+ return new CancellationSignal(signal);
77
40
  }
78
- /**
79
- * Returns a standard `AbortSignal` that is aborted when this token is set.
80
- * Useful for interoperability with APIs like `fetch`.
81
- */
82
- get abortSignal() {
83
- return (this.#abortSignal ??= this.asAbortSignal());
41
+ [Symbol.dispose]() {
42
+ this.#disposeAbortController.abort();
84
43
  }
85
- /**
86
- * Returns a standard `AbortSignal` that is aborted when this token is set.
87
- * Useful for interoperability with APIs like `fetch`.
88
- */
89
- asAbortSignal() {
90
- const abortController = new AbortController();
91
- const subscription = this.set$.pipe(first()).subscribe(() => abortController.abort());
92
- const signal = abortController.signal;
93
- registerFinalization(signal, (subscription) => subscription.unsubscribe(), subscription);
94
- return signal;
44
+ [Symbol.observable]() {
45
+ return this;
95
46
  }
96
- /**
97
- * Creates a new `CancellationToken` that is connected to this one.
98
- * State changes from this token will propagate to the new child token.
99
- * @see {@link connect}
100
- */
101
- createChild(config) {
102
- const child = new CancellationToken();
103
- this.connect(child, config);
104
- return child;
47
+ dispose() {
48
+ this[Symbol.dispose]();
105
49
  }
106
50
  /**
107
- * Propagates state changes from this token to a target `CancellationToken`.
108
- * @param target The token to receive state updates.
109
- * @param config Configuration for the connection.
51
+ * Creates a new CancellationToken that is also set when this signal is set.
52
+ * If this signal is already set, the new token will also be set.
110
53
  */
111
- connect(target, config) {
112
- CancellationToken.connect(this.state$, target, config);
54
+ fork() {
55
+ return new CancellationToken(this);
113
56
  }
114
57
  /**
115
- * Allows this object to be awaited. The promise resolves when the token is set.
116
- * Implements the `PromiseLike<void>` interface.
58
+ * Creates a new CancellationSignal that is set when either this signal or any of the specified parent signals are set.
59
+ * If any of the parent signals are already set, the new signal will also be set.
60
+ * @param parents The parent token or signal(s) to inherit from.
117
61
  */
118
- async then(onfulfilled) {
119
- await this.$set;
120
- return onfulfilled?.();
62
+ inherit(parents) {
63
+ return CancellationSignal.from([this, ...toArray(parents)]);
121
64
  }
122
65
  /**
123
- * Subscribes to notifications for when the token becomes set.
124
- * This is an alias for `this.set$.subscribe(observer)`.
125
- * Implements the `Subscribable<void>` interface.
66
+ * Creates a new CancellationSignal that is set when either this signal or any of the specified parent signals are set.
67
+ * If any of the parent signals are already set, the new signal will also be set.
68
+ * If no parents are provided, it simply returns this signal.
69
+ * @param parents The parent token or signal(s) to optionally inherit from.
126
70
  */
127
- subscribe(observer) {
128
- return this.set$.subscribe(observer);
71
+ optionallyInherit(parents) {
72
+ if (isUndefined(parents)) {
73
+ return this;
74
+ }
75
+ return this.inherit(parents);
129
76
  }
130
- }
131
- /**
132
- * A CancellationToken represents a unit of work that can be cancelled.
133
- * It can be explicitly set/unset, completed, or errored.
134
- *
135
- * You can create child tokens, connect them to other async primitives
136
- * (Promises, AbortSignals), and pass around its read-only `signal`
137
- * to consumers that should only react to cancellation, not trigger it.
138
- */
139
- export class CancellationToken extends CancellationSignal {
140
- #signal;
141
77
  /**
142
- * A read-only view of this token.
143
- * Useful for passing to functions that should only be able to check for
144
- * cancellation but not trigger it.
78
+ * Creates a new CancellationSignal that is set when either this signal is set or the specified timeout elapses.
79
+ * @param milliseconds The timeout duration in milliseconds.
145
80
  */
146
- get signal() {
147
- return (this.#signal ??= new CancellationSignal(this._stateSubject));
81
+ withTimeout(milliseconds) {
82
+ return this.inherit(AbortSignal.timeout(milliseconds));
148
83
  }
149
84
  /**
150
- * @param initialState The initial state of the token. `false` (unset) by default.
85
+ * Creates a new CancellationSignal that is set when either this signal is set or the specified timeout elapses.
86
+ * If no timeout is provided, it simply returns this signal.
87
+ * @param milliseconds The timeout duration in milliseconds.
151
88
  */
152
- constructor(initialState = false) {
153
- const stateSubject = new BehaviorSubject(initialState);
154
- super(stateSubject);
155
- }
156
- static from(source, config) {
157
- const source$ = match(source)
158
- .with(P.instanceOf(AbortSignal), (signal) => fromEvent(signal, 'abort').pipe(map(() => true)))
159
- .with(P.when(isObservable), (obs) => obs.pipe(map((state) => (isBoolean(state) ? state : true))))
160
- .otherwise((source) => from(source).pipe(map(() => true)));
161
- const token = new CancellationToken();
162
- CancellationToken.connect(source$, token, config);
163
- return token;
89
+ withOptionalTimeout(milliseconds) {
90
+ if (isUndefined(milliseconds)) {
91
+ return this;
92
+ }
93
+ return this.withTimeout(milliseconds);
94
+ }
95
+ subscribe(observer) {
96
+ const signal = this.abortSignal;
97
+ const abortHandler = () => {
98
+ observer.next?.();
99
+ observer.complete?.();
100
+ };
101
+ if (signal.aborted) {
102
+ abortHandler();
103
+ return { unsubscribe: noop };
104
+ }
105
+ signal.addEventListener('abort', abortHandler, { once: true });
106
+ return {
107
+ unsubscribe: () => signal.removeEventListener('abort', abortHandler),
108
+ };
164
109
  }
165
110
  /**
166
- * Connects an observable state source to a target token.
167
- * @param source$ The observable providing boolean state.
168
- * @param target The CancellationToken to update.
169
- * @param config The connection configuration.
111
+ * Waits for the token to be set. Resolves or rejects when the token is set.
112
+ * @param options Configuration for the wait behavior.
170
113
  */
171
- static connect(source$, target, config = {}) {
172
- const { set = true, unset = true, complete = true, error = true, immediate = true, once = false } = config;
173
- let stateObservable = source$;
174
- if (!immediate) {
175
- stateObservable = stateObservable.pipe(skip(1));
176
- }
177
- if (!set || !unset) {
178
- if (set) {
179
- stateObservable = stateObservable.pipe(filter((state) => state));
180
- }
181
- else if (unset) {
182
- stateObservable = stateObservable.pipe(filter((state) => !state));
183
- }
184
- else {
185
- stateObservable = stateObservable.pipe(filter(() => false));
114
+ async wait(options) {
115
+ const abortSignal = this.abortSignal;
116
+ await new Promise((resolve) => {
117
+ if (this.isSet) {
118
+ resolve();
119
+ return;
186
120
  }
187
- }
188
- if (once) {
189
- stateObservable = stateObservable.pipe(take(1));
190
- }
191
- const subscription = stateObservable.subscribe({
192
- next: (state) => target.setState(state),
193
- error: error ? (errorValue) => target.error(errorValue) : noop,
194
- complete: complete ? () => target.complete() : noop,
195
- });
196
- // Ensure the connection is torn down if the target is completed or errors.
197
- target._stateSubject.subscribe({
198
- error: () => subscription.unsubscribe(),
199
- complete: () => subscription.unsubscribe(),
121
+ abortSignal.addEventListener('abort', () => resolve(), { once: true });
200
122
  });
123
+ if (options?.throw == true) {
124
+ throwIfSet(abortSignal);
125
+ }
201
126
  }
202
- /**
203
- * Makes this token a child of a parent token.
204
- * State changes from the parent will propagate to this token.
205
- * @param parent The token to inherit state from.
206
- * @param config Configuration for the connection.
207
- */
208
- inherit(parent, config) {
209
- CancellationToken.connect(parent.state$, this, config);
127
+ async waitThrow() {
128
+ await this.wait({ throw: true });
129
+ }
130
+ throwIfSet() {
131
+ throwIfSet(this.abortSignal);
132
+ }
133
+ }
134
+ /**
135
+ * A CancellationToken is a mutable cancellation source that can be set to signal cancellation.
136
+ * It extends CancellationSignal, so it can be used wherever a CancellationSignal is expected.
137
+ * It provides a `set()` method to signal cancellation, which will trigger any subscribers or waiters on the token or any tokens that inherit from it.
138
+ */
139
+ export class CancellationToken extends CancellationSignal {
140
+ #controller;
141
+ get signal() {
210
142
  return this;
211
143
  }
212
144
  /**
213
- * Sets the token's state to `true`.
214
- * This signals cancellation or completion of the associated operation.
145
+ * Creates a new CancellationToken that is set when the parent signals.
146
+ * If the parent is already set, the new token will also be set.
147
+ * @param parents The parent token or signal(s) to inherit from.
215
148
  */
216
- set() {
217
- this.setState(true);
149
+ constructor(parents) {
150
+ const controller = new AbortController();
151
+ super([...toArray(parents ?? []), controller]);
152
+ this.#controller = controller;
218
153
  }
219
- /**
220
- * Sets the token's state to `false`.
221
- * This can be used to reset the token.
222
- */
223
- unset() {
224
- this.setState(false);
154
+ set(reason) {
155
+ const error = new Error('Operation cancelled', { cause: reason });
156
+ this.#controller.abort(error);
225
157
  }
226
- /**
227
- * Explicitly sets the state of the token.
228
- * @param state `true` for set, `false` for unset.
229
- */
230
- setState(state) {
231
- this._stateSubject.next(state);
158
+ }
159
+ export function sourcesToAbortSignal(...sources) {
160
+ const signals = sources.flat().map(getAbortSignal);
161
+ return (signals.length > 1)
162
+ ? AbortSignal.any(signals)
163
+ : assertDefinedPass(signals[0], 'At least one cancellation source must be provided');
164
+ }
165
+ function getAbortSignal(source) {
166
+ if (isInstanceOf(source, CancellationSignal)) {
167
+ return source.abortSignal;
232
168
  }
233
- /**
234
- * Puts the token into an errored state.
235
- * All current and future subscribers will receive this error.
236
- */
237
- error(error) {
238
- this._stateSubject.error(error);
169
+ if (isInstanceOf(source, AbortController)) {
170
+ return source.signal;
239
171
  }
240
- /**
241
- * Completes the token's lifecycle.
242
- * No further state changes can occur. Active awaits may throw an error.
243
- */
244
- complete() {
245
- this._stateSubject.complete();
172
+ return source;
173
+ }
174
+ function throwIfSet(signal) {
175
+ if (signal.aborted) {
176
+ throw signal.reason;
246
177
  }
247
178
  }
@@ -0,0 +1,174 @@
1
+ # Context
2
+
3
+ The **context** module provides a factory for creating type-safe, named context managers. It simplifies the management of global or scoped state by generating a suite of strongly-typed functions to set, retrieve, and assert execution contexts without manually passing arguments through the call stack.
4
+
5
+ ## Table of Contents
6
+
7
+ - [✨ Features](#-features)
8
+ - [Core Concepts](#core-concepts)
9
+ - [🚀 Basic Usage](#-basic-usage)
10
+ - [🔧 Advanced Topics](#-advanced-topics)
11
+ - [Manual Context Management](#manual-context-management)
12
+ - [Concurrency Note](#concurrency-note)
13
+ - [📚 API](#-api)
14
+
15
+ ## ✨ Features
16
+
17
+ - **Type-Safe Generation**: Automatically generates functions with correct input/output types based on your context definition.
18
+ - **Dynamic Naming**: Generates readable function names (e.g., `getCurrentUserContext`, `runInUserContext`) based on a provided string literal.
19
+ - **Automatic Cleanup**: The `runIn...` pattern ensures the context is restored to its previous state after execution, even if errors occur.
20
+ - **Assertion Helpers**: Built-in functions to enforce that code is running within a specific context.
21
+ - **Zero Dependencies**: Lightweight implementation using closures.
22
+
23
+ ## Core Concepts
24
+
25
+ This module solves the problem of "prop drilling" or passing state objects (like user sessions, transaction scopes, or configuration) through every function in a call chain.
26
+
27
+ Instead of a singleton or a global variable that is hard to manage, `createContextProvider` creates a closed scope with specific accessor functions.
28
+
29
+ When you create a context provider named `'Session'`, the module generates an object containing:
30
+
31
+ 1. `getCurrentSessionContext`: To read the data.
32
+ 2. `runInSessionContext`: To execute a function with that data active.
33
+ 3. `setCurrentSessionContext`: To manually swap the context.
34
+ 4. `isInSessionContext`: To check availability.
35
+ 5. `assertInSessionContext`: To throw if unavailable.
36
+
37
+ ## 🚀 Basic Usage
38
+
39
+ The most common use case is defining a context for a specific domain entity (e.g., a User or Request) and using the `runIn...` helper.
40
+
41
+ ```typescript
42
+ import { createContextProvider } from '@tstdl/base/context';
43
+
44
+ // 1. Define your Context type
45
+ type UserContext = {
46
+ id: string;
47
+ username: string;
48
+ isAdmin: boolean;
49
+ };
50
+
51
+ // 2. Create the provider.
52
+ // The second generic argument and the function argument must match to generate correct names.
53
+ export const { getCurrentUserContext, runInUserContext, assertInUserContext, isInUserContext } = createContextProvider<UserContext, 'User'>('User');
54
+
55
+ // 3. Define functions that rely on the context
56
+ function performAdminTask() {
57
+ // Pass the function reference (performAdminTask) for better error messages if context is missing.
58
+ // When 'required' is true, the return type is automatically narrowed to non-nullable (UserContext).
59
+ const user = getCurrentUserContext(true, performAdminTask);
60
+
61
+ if (!user.isAdmin) {
62
+ throw new Error(`User ${user.username} is not authorized.`);
63
+ }
64
+
65
+ console.log(`Admin task performed by ${user.username}`);
66
+ }
67
+
68
+ function main() {
69
+ const adminUser: UserContext = { id: '1', username: 'alice', isAdmin: true };
70
+ const guestUser: UserContext = { id: '2', username: 'bob', isAdmin: false };
71
+
72
+ // 4. Run code within the context
73
+ runInUserContext(adminUser, () => {
74
+ console.log('Context is active:', isInUserContext()); // true
75
+ performAdminTask(); // Works
76
+ });
77
+
78
+ // Context is automatically cleared here
79
+ console.log('Context is active:', isInUserContext()); // false
80
+
81
+ // Running with a different user
82
+ runInUserContext(guestUser, () => {
83
+ try {
84
+ performAdminTask(); // Throws Error: User bob is not authorized
85
+ } catch (e) {
86
+ console.error(e);
87
+ }
88
+ });
89
+ }
90
+
91
+ main();
92
+ ```
93
+
94
+ ## 🏗️ Framework Examples
95
+
96
+ The library uses this module internally to manage cross-cutting concerns without passing them explicitly through every constructor.
97
+
98
+ ### Dependency Injection (Resolution Context)
99
+
100
+ The `injector` module uses a context to track the current resolution stack to detect circular dependencies and provide access to the resolution state.
101
+
102
+ ```typescript
103
+ const { assertInResolutionContext, getCurrentResolutionContext, runInResolutionContext } = createContextProvider<ResolutionContext<any>, 'Resolution'>('Resolution');
104
+ ```
105
+
106
+ ### Transaction Management
107
+
108
+ The ORM uses a transactional context to ensure that multiple repository calls within a `Transactional` block share the same database connection and transaction state.
109
+
110
+ ```typescript
111
+ const { getCurrentTransactionalContext, runInTransactionalContext, isInTransactionalContext } = createContextProvider<TransactionalContext, 'Transactional'>('Transactional');
112
+ ```
113
+
114
+ ## 🔧 Advanced Topics
115
+
116
+ ### Manual Context Management
117
+
118
+ While `runIn...` is preferred because it handles `try/finally` logic automatically, you can manage the context stack manually using `setCurrent...`. This is useful for complex setups or when the scope cannot be easily wrapped in a callback.
119
+
120
+ ```typescript
121
+ import { createContextProvider } from '@tstdl/base/context';
122
+
123
+ const { setCurrentConfigContext, getCurrentConfigContext } = createContextProvider<Record<string, any>, 'Config'>('Config');
124
+
125
+ function initialize() {
126
+ // Set context and store the previous one (usually null)
127
+ const previous = setCurrentConfigContext({ env: 'production' });
128
+
129
+ try {
130
+ // Application logic...
131
+ console.log(getCurrentConfigContext());
132
+ } finally {
133
+ // Ideally, restore the previous context when done
134
+ setCurrentConfigContext(previous);
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### Concurrency Note
140
+
141
+ The context provider uses a **module-level variable** (closure) to store the current state. It is **not** based on `AsyncLocalStorage`.
142
+
143
+ - **Synchronous Operations**: It works perfectly for synchronous call stacks.
144
+ - **Asynchronous Operations**: Because the state is stored in a simple variable, concurrent asynchronous operations (like handling multiple HTTP requests in parallel on the same Node.js event loop) will overwrite each other's context if they share the same provider instance.
145
+
146
+ If you need context isolation across asynchronous boundaries (e.g., for a web server request context), you should use this module in conjunction with a mechanism that ensures isolation (like creating a new provider instance per request) or use a dedicated `AsyncLocalStorage` wrapper if available in your environment.
147
+
148
+ ## 📚 API
149
+
150
+ ### `createContextProvider`
151
+
152
+ Creates a new context provider with named utility functions.
153
+
154
+ ```typescript
155
+ function createContextProvider<Context, Name extends string>(name: Name): GeneratedContextAPI<Context, Name>;
156
+ ```
157
+
158
+ **Parameters:**
159
+
160
+ | Parameter | Type | Description |
161
+ | :--- | :--- | :--- |
162
+ | `name` | `Name` (string literal) | The name used to suffix the generated functions (e.g., 'User' -> `getCurrentUserContext`). |
163
+
164
+ **Returns:**
165
+
166
+ An object containing the following functions, where `[Name]` is replaced by the provided `name` argument:
167
+
168
+ | Function Name | Signature | Description |
169
+ | :--- | :--- | :--- |
170
+ | `getCurrent[Name]Context` | `(required: true, debugFn: Function) => Context`<br>`(required?: false, debugFn?: Function) => Context \| null` | Retrieves the current context. If `required` is true, it returns `Context` (throws if null). `debugFn.name` is used for the error message. |
171
+ | `setCurrent[Name]Context` | `(context: Context \| null) => Context \| null` | Sets the context to the provided value. Returns the *previous* context value. |
172
+ | `runIn[Name]Context` | `<R>(context: Context, fn: () => R) => R` | Sets the context, executes `fn`, and then restores the previous context (even if `fn` throws). Returns the result of `fn`. |
173
+ | `isIn[Name]Context` | `() => boolean` | Returns `true` if the current context is not null. |
174
+ | `assertIn[Name]Context` | `(debugFnOrMessage: Function \| string) => void` | Throws an error if the current context is null. Accepts a function reference (for its name) or a custom message string. |