core-services-sdk 1.3.29 → 1.3.31
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/package.json +1 -1
- package/src/util/context.js +81 -0
- package/src/util/index.js +1 -0
- package/tests/util/context.test.js +67 -0
package/package.json
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
+
|
|
3
|
+
const als = new AsyncLocalStorage()
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Context utility built on top of Node.js AsyncLocalStorage.
|
|
7
|
+
* Provides a per-request (or per-async-chain) storage mechanism that
|
|
8
|
+
* allows passing metadata (like correlation IDs, user info, tenant ID, etc.)
|
|
9
|
+
* without explicitly threading it through every function call.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const Context = {
|
|
13
|
+
/**
|
|
14
|
+
* Run a callback within a given context store.
|
|
15
|
+
* Everything `await`ed or invoked inside this callback will have access
|
|
16
|
+
* to the provided store via {@link Context.get}, {@link Context.set}, or {@link Context.all}.
|
|
17
|
+
*
|
|
18
|
+
* @template T
|
|
19
|
+
* @param {Record<string, any>} store
|
|
20
|
+
* @param {() => T} callback - Function to execute inside the context.
|
|
21
|
+
* @returns {T} The return value of the callback (sync or async).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* Context.run(
|
|
25
|
+
* { correlationId: 'abc123' },
|
|
26
|
+
* async () => {
|
|
27
|
+
* console.log(Context.get('correlationId')) // "abc123"
|
|
28
|
+
* }
|
|
29
|
+
* )
|
|
30
|
+
*/
|
|
31
|
+
run(store, callback) {
|
|
32
|
+
return als.run(store, callback)
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Retrieve a single value from the current async context store.
|
|
37
|
+
*
|
|
38
|
+
* @template T
|
|
39
|
+
* @param {string} key - The key of the value to retrieve.
|
|
40
|
+
* @returns {T|undefined} The stored value, or `undefined` if no store exists or key not found.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const userId = Context.get('userId')
|
|
44
|
+
*/
|
|
45
|
+
get(key) {
|
|
46
|
+
const store = als.getStore()
|
|
47
|
+
return store?.[key]
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Set a single key-value pair in the current async context store.
|
|
52
|
+
* If there is no active store (i.e. outside of a {@link Context.run}),
|
|
53
|
+
* this function does nothing.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} key - The key under which to store the value.
|
|
56
|
+
* @param {any} value - The value to store.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* Context.set('tenantId', 'tnt_1234')
|
|
60
|
+
*/
|
|
61
|
+
set(key, value) {
|
|
62
|
+
const store = als.getStore()
|
|
63
|
+
if (store) {
|
|
64
|
+
store[key] = value
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the entire store object for the current async context.
|
|
70
|
+
*
|
|
71
|
+
* @returns {Record<string, any>} The current store object,
|
|
72
|
+
* or an empty object if no store exists.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* const all = Context.all()
|
|
76
|
+
* console.log(all) // { correlationId: 'abc123', userId: 'usr_789' }
|
|
77
|
+
*/
|
|
78
|
+
all() {
|
|
79
|
+
return als.getStore() || {}
|
|
80
|
+
},
|
|
81
|
+
}
|
package/src/util/index.js
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { Context } from '../../src/util/context.js'
|
|
3
|
+
|
|
4
|
+
describe('Context utility', () => {
|
|
5
|
+
it('should return empty object and undefined outside run()', () => {
|
|
6
|
+
expect(Context.all()).toEqual({})
|
|
7
|
+
expect(Context.get('foo')).toBeUndefined()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('should provide store values inside run()', () => {
|
|
11
|
+
Context.run({ correlationId: 'abc123', userId: 'usr_1' }, () => {
|
|
12
|
+
expect(Context.get('correlationId')).toBe('abc123')
|
|
13
|
+
expect(Context.get('userId')).toBe('usr_1')
|
|
14
|
+
expect(Context.all()).toEqual({
|
|
15
|
+
correlationId: 'abc123',
|
|
16
|
+
userId: 'usr_1',
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should allow setting new values inside run()', () => {
|
|
22
|
+
Context.run({ initial: true }, () => {
|
|
23
|
+
expect(Context.get('initial')).toBe(true)
|
|
24
|
+
Context.set('added', 42)
|
|
25
|
+
expect(Context.get('added')).toBe(42)
|
|
26
|
+
expect(Context.all()).toEqual({ initial: true, added: 42 })
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should isolate different runs()', () => {
|
|
31
|
+
Context.run({ value: 'first' }, () => {
|
|
32
|
+
expect(Context.get('value')).toBe('first')
|
|
33
|
+
})
|
|
34
|
+
Context.run({ value: 'second' }, () => {
|
|
35
|
+
expect(Context.get('value')).toBe('second')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should preserve context across async/await', async () => {
|
|
40
|
+
await Context.run({ requestId: 'req-1' }, async () => {
|
|
41
|
+
expect(Context.get('requestId')).toBe('req-1')
|
|
42
|
+
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
44
|
+
expect(Context.get('requestId')).toBe('req-1')
|
|
45
|
+
|
|
46
|
+
await Promise.resolve().then(() => {
|
|
47
|
+
expect(Context.get('requestId')).toBe('req-1')
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should not leak context between async runs', async () => {
|
|
53
|
+
const results = await Promise.all([
|
|
54
|
+
Context.run({ user: 'alice' }, async () => {
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, 5))
|
|
56
|
+
return Context.get('user')
|
|
57
|
+
}),
|
|
58
|
+
Context.run({ user: 'bob' }, async () => {
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, 1))
|
|
60
|
+
return Context.get('user')
|
|
61
|
+
}),
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
expect(results).toContain('alice')
|
|
65
|
+
expect(results).toContain('bob')
|
|
66
|
+
})
|
|
67
|
+
})
|