@warlock.js/context 5.23.2 → 5.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +343 -343
- package/llms-full.txt +321 -321
- package/package.json +1 -1
- package/skills/define-context/SKILL.md +160 -160
- package/skills/orchestrate-contexts/SKILL.md +161 -161
package/README.md
CHANGED
|
@@ -1,343 +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
|
|
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
|