@warlock.js/context 4.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,343 @@
1
+ # @warlock.js/context
2
+
3
+ A lightweight, type-safe context management library built on Node.js's `AsyncLocalStorage`. Provides a simple and unified way to share context across async operations in your applications.
4
+
5
+ Part of the [Warlock.js](https://github.com/warlockjs) ecosystem.
6
+
7
+ ## Features
8
+
9
+ - 🚀 **Simple API** - Intuitive methods for context management
10
+ - 🔒 **Type-safe** - Full TypeScript support with generics
11
+ - 🔗 **Chainable** - Fluent API for registering multiple contexts
12
+ - 🎯 **Extensible** - Abstract base class for custom context implementations
13
+ - 📦 **Zero dependencies** - Only uses Node.js built-in `AsyncLocalStorage`
14
+ - 🔄 **Multi-context support** - Orchestrate multiple contexts together
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @warlock.js/context
20
+ ```
21
+
22
+ ```bash
23
+ yarn add @warlock.js/context
24
+ ```
25
+
26
+ ```bash
27
+ pnpm add @warlock.js/context
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### Creating a Custom Context
33
+
34
+ Extend the `Context` abstract class to create your own typed context:
35
+
36
+ ```typescript
37
+ import { Context, contextManager } from "@warlock.js/context";
38
+
39
+ // Define your store type
40
+ interface UserContextStore {
41
+ userId: string;
42
+ role: "admin" | "user";
43
+ tenantId: string;
44
+ }
45
+
46
+ // Create your context class
47
+ class UserContext extends Context<UserContextStore> {
48
+ /**
49
+ * Called when contextManager executese buildStores()
50
+ */
51
+ public buildStore(payload?: Record<string, any>): UserContextStore {
52
+ // Initialize from payload or defaults
53
+ return {
54
+ userId: payload?.userId ?? "",
55
+ role: payload?.role ?? "user",
56
+ tenantId: payload?.tenantId ?? "",
57
+ };
58
+ }
59
+ }
60
+
61
+ // Create a singleton instance
62
+ export const userContext = new UserContext();
63
+ // register it in the context manager
64
+ contextManager.register("user", userContext);
65
+ ```
66
+
67
+ ### Using the Context
68
+
69
+ #### With `run()` - Scoped execution
70
+
71
+ ```typescript
72
+ await userContext.run(
73
+ { userId: "123", role: "admin", tenantId: "acme" },
74
+ async () => {
75
+ // Context is available throughout this async scope
76
+ const userId = userContext.get("userId"); // '123'
77
+ const role = userContext.get("role"); // 'admin'
78
+
79
+ await someAsyncOperation(); // Context propagates through async calls
80
+ }
81
+ );
82
+ ```
83
+
84
+ #### With `enter()` - Middleware-style
85
+
86
+ ```typescript
87
+ // In your middleware
88
+ function authMiddleware(req, res, next) {
89
+ userContext.enter({
90
+ userId: req.user.id,
91
+ role: req.user.role,
92
+ tenantId: req.headers["x-tenant-id"],
93
+ });
94
+
95
+ next(); // Context is available for the rest of the request
96
+ }
97
+ ```
98
+
99
+ ### Managing Multiple Contexts
100
+
101
+ Use the global `contextManager` to orchestrate multiple contexts together:
102
+
103
+ ```typescript
104
+ import { Context, contextManager } from "@warlock.js/context";
105
+
106
+ // Define your contexts
107
+ class RequestContext extends Context<{ requestId: string; path: string }> {
108
+ /**
109
+ * Called when contextManager executese buildStores()
110
+ */
111
+ public buildStore(payload?: Record<string, any>) {
112
+ return { requestId: payload?.requestId ?? "", path: payload?.path ?? "" };
113
+ }
114
+ }
115
+
116
+ class DatabaseContext extends Context<{ dataSource: string }> {
117
+ /**
118
+ * Called when contextManager executese buildStores()
119
+ */
120
+ public buildStore(payload?: Record<string, any>) {
121
+ return { dataSource: payload?.dataSource ?? "primary" };
122
+ }
123
+ }
124
+
125
+ // Create instances and register them immediately
126
+ export const requestContext = new RequestContext();
127
+ contextManager.register("request", requestContext);
128
+
129
+ export const databaseContext = new DatabaseContext();
130
+ contextManager.register("database", databaseContext);
131
+
132
+ // Build stores first - each context's buildStore() is called with the payload
133
+ const stores = contextManager.buildStores({
134
+ requestId: "req-123",
135
+ path: "/api/users",
136
+ dataSource: "replica",
137
+ });
138
+
139
+ // Run all contexts together
140
+ await contextManager.runAll(stores, async () => {
141
+ // All contexts are active!
142
+ const reqId = requestContext.get("requestId"); // 'req-123'
143
+ const ds = databaseContext.get("dataSource"); // 'replica'
144
+ });
145
+ ```
146
+
147
+ ## API Reference
148
+
149
+ ### `Context<TStore>` (Abstract Class)
150
+
151
+ The base class for all context implementations.
152
+
153
+ #### Methods
154
+
155
+ | Method | Description |
156
+ | --------------------------------------------------------------- | -------------------------------------------------------- |
157
+ | `run<T>(store: TStore, callback: () => Promise<T>): Promise<T>` | Execute a callback within a new context scope |
158
+ | `enter(store: TStore): void` | Enter a context without a callback (middleware-style) |
159
+ | `update(updates: Partial<TStore>): void` | Merge new data into the current context |
160
+ | `getStore(): TStore \| undefined` | Get the entire current context store |
161
+ | `get<K extends keyof TStore>(key: K): TStore[K] \| undefined` | Get a specific value from context |
162
+ | `set<K extends keyof TStore>(key: K, value: TStore[K]): void` | Set a specific value in context |
163
+ | `clear(): void` | Clear the current context |
164
+ | `hasContext(): boolean` | Check if currently within a context |
165
+ | `buildStore(payload?: Record<string, any>): TStore` | **Abstract** - Override to provide custom initialization |
166
+
167
+ ### `ContextManager`
168
+
169
+ Orchestrates multiple contexts together.
170
+
171
+ #### Methods
172
+
173
+ | Method | Description |
174
+ | -------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
175
+ | `register(name: string, context: Context<any>): this` | Register a context with a unique name |
176
+ | `unregister(name: string): boolean` | Remove a registered context |
177
+ | `runAll<T>(stores: Record<string, any>, callback: () => Promise<T>): Promise<T>` | Run all contexts together |
178
+ | `enterAll(stores: Record<string, any>): void` | Enter all contexts at once |
179
+ | `clearAll(): void` | Clear all contexts |
180
+ | `buildStores(payload?: Record<string, any>): Record<string, any>` | Build stores for all contexts using their `buildStore()` methods |
181
+ | `getContext<T>(name: string): T \| undefined` | Get a registered context by name |
182
+ | `hasContext(name: string): boolean` | Check if a context is registered |
183
+
184
+ ### Global Context Manager
185
+
186
+ A pre-configured singleton is exported for convenience:
187
+
188
+ ```typescript
189
+ import { contextManager } from "@warlock.js/context";
190
+
191
+ contextManager.register("myContext", myContextInstance);
192
+ ```
193
+
194
+ ## Real-World Examples
195
+
196
+ ### Multi-Tenant Application
197
+
198
+ ```typescript
199
+ import { Context, contextManager } from "@warlock.js/context";
200
+
201
+ interface TenantStore {
202
+ tenantId: string;
203
+ tenantName: string;
204
+ config: Record<string, any>;
205
+ }
206
+
207
+ class TenantContext extends Context<TenantStore> {
208
+ /**
209
+ * Called when contextManager executese buildStores()
210
+ */
211
+ public buildStore(payload?: Record<string, any>): TenantStore {
212
+ return {
213
+ tenantId: payload?.tenantId ?? "",
214
+ tenantName: payload?.tenantName ?? "",
215
+ config: payload?.config ?? {},
216
+ };
217
+ }
218
+
219
+ // Convenience getters
220
+ public get tenantId() {
221
+ return this.get("tenantId");
222
+ }
223
+
224
+ public get config() {
225
+ return this.get("config");
226
+ }
227
+ }
228
+
229
+ export const tenantContext = new TenantContext();
230
+
231
+ // In your middleware
232
+ app.use(async (req, res, next) => {
233
+ const tenant = await getTenantFromRequest(req);
234
+
235
+ tenantContext.enter({
236
+ tenantId: tenant.id,
237
+ tenantName: tenant.name,
238
+ config: tenant.config,
239
+ });
240
+
241
+ next();
242
+ });
243
+
244
+ // Anywhere in your application
245
+ function getDatabaseConnection() {
246
+ const tenantId = tenantContext.tenantId;
247
+ return getConnectionForTenant(tenantId);
248
+ }
249
+ ```
250
+
251
+ ### Request Tracing
252
+
253
+ ```typescript
254
+ import { Context } from "@warlock.js/context";
255
+ import { randomUUID } from "crypto";
256
+
257
+ interface TraceStore {
258
+ traceId: string;
259
+ spanId: string;
260
+ startTime: number;
261
+ }
262
+
263
+ class TraceContext extends Context<TraceStore> {
264
+ /**
265
+ * Called when contextManager executese buildStores()
266
+ */
267
+ public buildStore(): TraceStore {
268
+ return {
269
+ traceId: randomUUID(),
270
+ spanId: randomUUID(),
271
+ startTime: Date.now(),
272
+ };
273
+ }
274
+
275
+ public get traceId() {
276
+ return this.get("traceId");
277
+ }
278
+
279
+ public log(message: string) {
280
+ const store = this.getStore();
281
+ console.log(`[${store?.traceId}] ${message}`);
282
+ }
283
+ }
284
+
285
+ export const traceContext = new TraceContext();
286
+
287
+ // Usage
288
+ app.use((req, res, next) => {
289
+ const stores = { trace: traceContext.buildStore() };
290
+
291
+ traceContext.run(stores.trace, async () => {
292
+ traceContext.log(`Request started: ${req.path}`);
293
+ await next();
294
+ traceContext.log(
295
+ `Request completed in ${Date.now() - stores.trace.startTime}ms`
296
+ );
297
+ });
298
+ });
299
+ ```
300
+
301
+ ### Combining with ContextManager
302
+
303
+ ```typescript
304
+ import { contextManager } from "@warlock.js/context";
305
+ import { requestContext } from "./request-context";
306
+ import { traceContext } from "./trace-context";
307
+ import { tenantContext } from "./tenant-context";
308
+
309
+ // Register all contexts at app startup
310
+ contextManager
311
+ .register("request", requestContext)
312
+ .register("trace", traceContext)
313
+ .register("tenant", tenantContext);
314
+
315
+ // In your request handler
316
+ app.use(async (req, res, next) => {
317
+ // Build all stores from the request payload
318
+ const stores = contextManager.buildStores({
319
+ request: req,
320
+ response: res,
321
+ tenantId: req.headers["x-tenant-id"],
322
+ });
323
+
324
+ // Run all contexts together
325
+ await contextManager.runAll(stores, async () => {
326
+ await next();
327
+ });
328
+ });
329
+ ```
330
+
331
+ ## Requirements
332
+
333
+ - Node.js >= 18.0.0
334
+ - TypeScript >= 5.0 (for development)
335
+
336
+ ## License
337
+
338
+ MIT © [hassanzohdy](https://github.com/hassanzohdy)
339
+
340
+ ## Related Packages
341
+
342
+ - [@warlock.js/core](https://github.com/warlockjs/core) - Core Warlock.js framework
343
+ - [Warlock.js](https://github.com/warlockjs) - Full-featured Node.js framework
package/cjs/index.js ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var async_hooks=require('async_hooks');var s=class{storage=new async_hooks.AsyncLocalStorage;run(t,e){return this.storage.run(t,e)}enter(t){this.storage.enterWith(t);}update(t){let e=this.storage.getStore();e?Object.assign(e,t):this.enter(t);}getStore(){return this.storage.getStore()}get(t){return this.storage.getStore()?.[t]}set(t,e){this.update({[t]:e});}clear(){this.storage.enterWith({});}hasContext(){return this.storage.getStore()!==void 0}};var n=class{contexts=new Map;register(t,e){return this.contexts.set(t,e),this}async runAll(t,e){return Array.from(this.contexts.entries()).reduceRight((c,[a,u])=>()=>u.run(t[a]||{},c),e)()}enterAll(t){for(let[e,r]of this.contexts.entries())t[e]&&r.enter(t[e]);}clearAll(){for(let t of this.contexts.values())t.clear();}getContext(t){return this.contexts.get(t)}hasContext(t){return this.contexts.has(t)}buildStores(t){let e={};for(let[r,o]of this.contexts.entries())e[r]=o.buildStore(t)??{};return e}unregister(t){return this.contexts.delete(t)}},p=new n;exports.Context=s;exports.ContextManager=n;exports.contextManager=p;//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../warlock.js/context/src/base-context.ts","../../../../../../../warlock.js/context/src/context-manager.ts"],"names":["Context","AsyncLocalStorage","store","callback","updates","current","key","value","ContextManager","name","context","stores","next","payload","contextManager"],"mappings":"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","file":"index.js","sourcesContent":["import { AsyncLocalStorage } from \"async_hooks\";\n\n/**\n * Base class for all AsyncLocalStorage-based contexts\n *\n * Provides a consistent API for managing context across async operations.\n * All framework contexts (request, storage, database) extend this class.\n *\n * @template TStore - The type of data stored in context\n *\n * @example\n * ```typescript\n * interface MyContextStore {\n * userId: string;\n * tenant: string;\n * }\n *\n * class MyContext extends Context<MyContextStore> {}\n * const myContext = new MyContext();\n *\n * // Use it\n * await myContext.run({ userId: '123', tenant: 'acme' }, async () => {\n * const userId = myContext.get('userId'); // '123'\n * });\n * ```\n */\nexport abstract class Context<TStore extends Record<string, any>> {\n protected readonly storage: AsyncLocalStorage<TStore> = new AsyncLocalStorage<TStore>();\n\n /**\n * Run a callback within a new context\n *\n * Creates a new async context with the provided store data.\n * All operations within the callback will have access to this context.\n *\n * @param store - Initial context data\n * @param callback - Async function to execute\n * @returns Result of the callback\n */\n public run<T>(store: TStore, callback: () => Promise<T>): Promise<T> {\n return this.storage.run(store, callback);\n }\n\n /**\n * Enter a new context without a callback\n *\n * Useful for middleware where you want to set context for the rest of the request.\n * Unlike `run()`, this doesn't require a callback.\n *\n * @param store - Context data to set\n */\n public enter(store: TStore): void {\n this.storage.enterWith(store);\n }\n\n /**\n * Update the current context\n *\n * Merges new data into existing context, or enters new context if none exists.\n *\n * @param updates - Partial context data to merge\n */\n public update(updates: Partial<TStore>): void {\n const current = this.storage.getStore();\n\n if (current) {\n Object.assign(current, updates);\n } else {\n this.enter(updates as TStore);\n }\n }\n\n /**\n * Get the current context store\n *\n * @returns Current context or undefined if not in context\n */\n public getStore(): TStore | undefined {\n return this.storage.getStore();\n }\n\n /**\n * Get a specific value from context\n *\n * @param key - Key to retrieve\n * @returns Value or undefined\n */\n public get<K extends keyof TStore>(key: K): TStore[K] | undefined {\n return this.storage.getStore()?.[key];\n }\n\n /**\n * Set a specific value in context\n *\n * @param key - Key to set\n * @param value - Value to store\n */\n public set<K extends keyof TStore>(key: K, value: TStore[K]): void {\n this.update({ [key]: value } as any);\n }\n\n /**\n * Clear the context\n */\n public clear(): void {\n this.storage.enterWith({} as TStore);\n }\n\n /**\n * Check if currently in a context\n */\n public hasContext(): boolean {\n return this.storage.getStore() !== undefined;\n }\n\n /**\n * Build the initial store for this context\n *\n * Override this method to provide custom initialization logic.\n * Called by ContextManager.buildStores() for each registered context.\n *\n * @param payload - Generic payload (e.g., { request, response } for HTTP contexts)\n * @returns Initial store data\n */\n public abstract buildStore(payload?: Record<string, any>): TStore;\n}\n","import type { Context } from \"./base-context\";\n\n/**\n * Context Manager - Orchestrates multiple contexts together\n *\n * Allows running multiple AsyncLocalStorage contexts in a single operation,\n * making it easy to link request, storage, database, and other contexts.\n *\n * @example\n * ```typescript\n * // Register contexts\n * contextManager\n * .register('request', requestContext)\n * .register('storage', storageDriverContext)\n * .register('database', databaseDataSourceContext);\n *\n * // Run all contexts together\n * await contextManager.runAll({\n * request: { request, response, user },\n * storage: { driver, metadata: { tenantId: '123' } },\n * database: { dataSource: 'primary' },\n * }, async () => {\n * // All contexts active!\n * await handleRequest();\n * });\n * ```\n */\nexport class ContextManager {\n private contexts = new Map<string, Context<any>>();\n\n /**\n * Register a context\n *\n * @param name - Unique context name\n * @param context - Context instance\n * @returns This instance for chaining\n */\n public register(name: string, context: Context<any>): this {\n this.contexts.set(name, context);\n return this;\n }\n\n /**\n * Run all registered contexts together\n *\n * Nests all context.run() calls, ensuring all contexts are active\n * for the duration of the callback.\n *\n * @param stores - Context stores keyed by context name\n * @param callback - Async function to execute\n * @returns Result of the callback\n */\n public async runAll<T>(stores: Record<string, any>, callback: () => Promise<T>): Promise<T> {\n const entries = Array.from(this.contexts.entries());\n\n // Build nested context runners\n const runner = entries.reduceRight((next, [name, context]) => {\n return () => context.run(stores[name] || {}, next);\n }, callback);\n\n return runner();\n }\n\n /**\n * Enter all contexts at once (for middleware)\n *\n * @param stores - Context stores keyed by context name\n */\n public enterAll(stores: Record<string, any>): void {\n for (const [name, context] of this.contexts.entries()) {\n if (stores[name]) {\n context.enter(stores[name]);\n }\n }\n }\n\n /**\n * Clear all contexts\n */\n public clearAll(): void {\n for (const context of this.contexts.values()) {\n context.clear();\n }\n }\n\n /**\n * Get a specific registered context\n *\n * @param name - Context name\n * @returns Context instance or undefined\n */\n public getContext<T extends Context<any>>(name: string): T | undefined {\n return this.contexts.get(name) as T | undefined;\n }\n\n /**\n * Check if a context is registered\n *\n * @param name - Context name\n * @returns True if context is registered\n */\n public hasContext(name: string): boolean {\n return this.contexts.has(name);\n }\n\n /**\n * Build all context stores by calling each context's buildStore() method\n *\n * This is the immutable pattern - returns a new record of stores.\n * Each context defines its own initialization logic.\n *\n * @param payload - Payload passed to each buildStore() (e.g., { request, response })\n * @returns Record of context name -> store data\n *\n * @example\n * ```typescript\n * const httpContextStore = contextManager.buildStores({ request, response });\n * await contextManager.runAll(httpContextStore, async () => { ... });\n * ```\n */\n public buildStores(payload?: Record<string, any>): Record<string, any> {\n const stores: Record<string, any> = {};\n\n for (const [name, context] of this.contexts.entries()) {\n stores[name] = context.buildStore(payload) ?? {};\n }\n\n return stores;\n }\n\n /**\n * Unregister a context\n *\n * @param name - Context name to remove\n * @returns True if context was removed\n */\n public unregister(name: string): boolean {\n return this.contexts.delete(name);\n }\n}\n\n/**\n * Global context manager instance\n *\n * Use this singleton to register and manage all framework contexts.\n */\nexport const contextManager = new ContextManager();\n"]}
package/esm/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import {AsyncLocalStorage}from'async_hooks';var s=class{storage=new AsyncLocalStorage;run(t,e){return this.storage.run(t,e)}enter(t){this.storage.enterWith(t);}update(t){let e=this.storage.getStore();e?Object.assign(e,t):this.enter(t);}getStore(){return this.storage.getStore()}get(t){return this.storage.getStore()?.[t]}set(t,e){this.update({[t]:e});}clear(){this.storage.enterWith({});}hasContext(){return this.storage.getStore()!==void 0}};var n=class{contexts=new Map;register(t,e){return this.contexts.set(t,e),this}async runAll(t,e){return Array.from(this.contexts.entries()).reduceRight((c,[a,u])=>()=>u.run(t[a]||{},c),e)()}enterAll(t){for(let[e,r]of this.contexts.entries())t[e]&&r.enter(t[e]);}clearAll(){for(let t of this.contexts.values())t.clear();}getContext(t){return this.contexts.get(t)}hasContext(t){return this.contexts.has(t)}buildStores(t){let e={};for(let[r,o]of this.contexts.entries())e[r]=o.buildStore(t)??{};return e}unregister(t){return this.contexts.delete(t)}},p=new n;export{s as Context,n as ContextManager,p as contextManager};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../warlock.js/context/src/base-context.ts","../../../../../../../warlock.js/context/src/context-manager.ts"],"names":["Context","AsyncLocalStorage","store","callback","updates","current","key","value","ContextManager","name","context","stores","next","payload","contextManager"],"mappings":"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","file":"index.js","sourcesContent":["import { AsyncLocalStorage } from \"async_hooks\";\n\n/**\n * Base class for all AsyncLocalStorage-based contexts\n *\n * Provides a consistent API for managing context across async operations.\n * All framework contexts (request, storage, database) extend this class.\n *\n * @template TStore - The type of data stored in context\n *\n * @example\n * ```typescript\n * interface MyContextStore {\n * userId: string;\n * tenant: string;\n * }\n *\n * class MyContext extends Context<MyContextStore> {}\n * const myContext = new MyContext();\n *\n * // Use it\n * await myContext.run({ userId: '123', tenant: 'acme' }, async () => {\n * const userId = myContext.get('userId'); // '123'\n * });\n * ```\n */\nexport abstract class Context<TStore extends Record<string, any>> {\n protected readonly storage: AsyncLocalStorage<TStore> = new AsyncLocalStorage<TStore>();\n\n /**\n * Run a callback within a new context\n *\n * Creates a new async context with the provided store data.\n * All operations within the callback will have access to this context.\n *\n * @param store - Initial context data\n * @param callback - Async function to execute\n * @returns Result of the callback\n */\n public run<T>(store: TStore, callback: () => Promise<T>): Promise<T> {\n return this.storage.run(store, callback);\n }\n\n /**\n * Enter a new context without a callback\n *\n * Useful for middleware where you want to set context for the rest of the request.\n * Unlike `run()`, this doesn't require a callback.\n *\n * @param store - Context data to set\n */\n public enter(store: TStore): void {\n this.storage.enterWith(store);\n }\n\n /**\n * Update the current context\n *\n * Merges new data into existing context, or enters new context if none exists.\n *\n * @param updates - Partial context data to merge\n */\n public update(updates: Partial<TStore>): void {\n const current = this.storage.getStore();\n\n if (current) {\n Object.assign(current, updates);\n } else {\n this.enter(updates as TStore);\n }\n }\n\n /**\n * Get the current context store\n *\n * @returns Current context or undefined if not in context\n */\n public getStore(): TStore | undefined {\n return this.storage.getStore();\n }\n\n /**\n * Get a specific value from context\n *\n * @param key - Key to retrieve\n * @returns Value or undefined\n */\n public get<K extends keyof TStore>(key: K): TStore[K] | undefined {\n return this.storage.getStore()?.[key];\n }\n\n /**\n * Set a specific value in context\n *\n * @param key - Key to set\n * @param value - Value to store\n */\n public set<K extends keyof TStore>(key: K, value: TStore[K]): void {\n this.update({ [key]: value } as any);\n }\n\n /**\n * Clear the context\n */\n public clear(): void {\n this.storage.enterWith({} as TStore);\n }\n\n /**\n * Check if currently in a context\n */\n public hasContext(): boolean {\n return this.storage.getStore() !== undefined;\n }\n\n /**\n * Build the initial store for this context\n *\n * Override this method to provide custom initialization logic.\n * Called by ContextManager.buildStores() for each registered context.\n *\n * @param payload - Generic payload (e.g., { request, response } for HTTP contexts)\n * @returns Initial store data\n */\n public abstract buildStore(payload?: Record<string, any>): TStore;\n}\n","import type { Context } from \"./base-context\";\n\n/**\n * Context Manager - Orchestrates multiple contexts together\n *\n * Allows running multiple AsyncLocalStorage contexts in a single operation,\n * making it easy to link request, storage, database, and other contexts.\n *\n * @example\n * ```typescript\n * // Register contexts\n * contextManager\n * .register('request', requestContext)\n * .register('storage', storageDriverContext)\n * .register('database', databaseDataSourceContext);\n *\n * // Run all contexts together\n * await contextManager.runAll({\n * request: { request, response, user },\n * storage: { driver, metadata: { tenantId: '123' } },\n * database: { dataSource: 'primary' },\n * }, async () => {\n * // All contexts active!\n * await handleRequest();\n * });\n * ```\n */\nexport class ContextManager {\n private contexts = new Map<string, Context<any>>();\n\n /**\n * Register a context\n *\n * @param name - Unique context name\n * @param context - Context instance\n * @returns This instance for chaining\n */\n public register(name: string, context: Context<any>): this {\n this.contexts.set(name, context);\n return this;\n }\n\n /**\n * Run all registered contexts together\n *\n * Nests all context.run() calls, ensuring all contexts are active\n * for the duration of the callback.\n *\n * @param stores - Context stores keyed by context name\n * @param callback - Async function to execute\n * @returns Result of the callback\n */\n public async runAll<T>(stores: Record<string, any>, callback: () => Promise<T>): Promise<T> {\n const entries = Array.from(this.contexts.entries());\n\n // Build nested context runners\n const runner = entries.reduceRight((next, [name, context]) => {\n return () => context.run(stores[name] || {}, next);\n }, callback);\n\n return runner();\n }\n\n /**\n * Enter all contexts at once (for middleware)\n *\n * @param stores - Context stores keyed by context name\n */\n public enterAll(stores: Record<string, any>): void {\n for (const [name, context] of this.contexts.entries()) {\n if (stores[name]) {\n context.enter(stores[name]);\n }\n }\n }\n\n /**\n * Clear all contexts\n */\n public clearAll(): void {\n for (const context of this.contexts.values()) {\n context.clear();\n }\n }\n\n /**\n * Get a specific registered context\n *\n * @param name - Context name\n * @returns Context instance or undefined\n */\n public getContext<T extends Context<any>>(name: string): T | undefined {\n return this.contexts.get(name) as T | undefined;\n }\n\n /**\n * Check if a context is registered\n *\n * @param name - Context name\n * @returns True if context is registered\n */\n public hasContext(name: string): boolean {\n return this.contexts.has(name);\n }\n\n /**\n * Build all context stores by calling each context's buildStore() method\n *\n * This is the immutable pattern - returns a new record of stores.\n * Each context defines its own initialization logic.\n *\n * @param payload - Payload passed to each buildStore() (e.g., { request, response })\n * @returns Record of context name -> store data\n *\n * @example\n * ```typescript\n * const httpContextStore = contextManager.buildStores({ request, response });\n * await contextManager.runAll(httpContextStore, async () => { ... });\n * ```\n */\n public buildStores(payload?: Record<string, any>): Record<string, any> {\n const stores: Record<string, any> = {};\n\n for (const [name, context] of this.contexts.entries()) {\n stores[name] = context.buildStore(payload) ?? {};\n }\n\n return stores;\n }\n\n /**\n * Unregister a context\n *\n * @param name - Context name to remove\n * @returns True if context was removed\n */\n public unregister(name: string): boolean {\n return this.contexts.delete(name);\n }\n}\n\n/**\n * Global context manager instance\n *\n * Use this singleton to register and manage all framework contexts.\n */\nexport const contextManager = new ContextManager();\n"]}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@warlock.js/context",
3
+ "version": "4.0.39",
4
+ "description": "A simple and unified way to share context using AsyncLocalStorage for the Warlock.js framework",
5
+ "keywords": [
6
+ "warlock",
7
+ "warlockjs",
8
+ "context",
9
+ "async-local-storage",
10
+ "asynclocalstorage",
11
+ "async-context",
12
+ "request-context",
13
+ "nodejs"
14
+ ],
15
+ "author": "hassanzohdy",
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/warlockjs/context.git"
20
+ },
21
+ "homepage": "https://github.com/warlockjs/context#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/warlockjs/context/issues"
24
+ },
25
+ "main": "./cjs/index.js",
26
+ "module": "./esm/index.js",
27
+ "types": "./esm/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "import": {
31
+ "types": "./esm/index.d.ts",
32
+ "default": "./esm/index.js"
33
+ },
34
+ "require": {
35
+ "types": "./cjs/index.d.ts",
36
+ "default": "./cjs/index.js"
37
+ }
38
+ }
39
+ },
40
+ "type": "module",
41
+ "sideEffects": false,
42
+ "engines": {
43
+ "node": ">=18.0.0"
44
+ }
45
+ }