@andrew_l/context 0.0.1 → 0.2.4

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 CHANGED
@@ -1,27 +1,50 @@
1
- # Description
1
+ # Context Toolkit <!-- omit in toc -->
2
2
 
3
- Bind async context like vue composition api.
3
+ ![license](https://img.shields.io/npm/l/%40andrew_l%2Fcontext) <!-- omit in toc -->
4
+ ![npm version](https://img.shields.io/npm/v/%40andrew_l%2Fcontext) <!-- omit in toc -->
5
+ ![npm bundle size](https://img.shields.io/bundlephobia/minzip/%40andrew_l%2Fcontext) <!-- omit in toc -->
6
+
7
+ A lightweight toolkit to bind asynchronous contexts, inspired by Vue's Composition API. Easily provide and inject values across asynchronous scopes with support for scoped disposal.
4
8
 
5
9
  [Documentation](https://men232.github.io/toolkit/reference/@andrew_l/context/)
6
10
 
7
- # Usage
11
+ <!-- install placeholder -->
8
12
 
9
- ```js
10
- import { delay } from '@andrew_l/toolkit';
11
- import { withContext } from '@andrew_l/context';
13
+ ## ✨ Features
14
+
15
+ - **Intuitive API**: Designed to resemble Vue's `provide`/`inject`, making it familiar for developers experienced with Vue.
16
+ - **Deep Injection**: Seamlessly inject values across nested contexts.
17
+ - **Scoped Disposal**: Automatically execute all registered `onScopeDispose` callbacks when a scope is disposed, ensuring proper cleanup.
18
+
19
+ ## 🚀 Example: Basic Usage
12
20
 
13
- const main = withContext(() => {
21
+ The following example demonstrates how to provide and inject values across an asynchronous context.
22
+
23
+ ```js
24
+ // Define the main function with a context
25
+ const main = withContext(async () => {
26
+ // Provide a user object to the context
14
27
  provide('user', { id: 1, name: 'Andrew' });
15
28
 
16
29
  await delay(1000);
17
30
 
18
- doCoolStaff();
31
+ // Call another function that uses the provided context
32
+ doCoolStuff();
19
33
  });
20
34
 
21
- const doCoolStaff = () => {
35
+ // A separate function that retrieves and uses the injected context
36
+ const doCoolStuff = () => {
37
+ // Inject the user object from the context
22
38
  const user = inject('user');
23
- console.log(user); // { id: 1, name: 'Andrew' }
39
+ console.log(user); // Output: { id: 1, name: 'Andrew' }
24
40
  };
25
41
 
42
+ // Execute the main function
26
43
  main();
27
44
  ```
45
+
46
+ ## 🤔 Why Use This Package?
47
+
48
+ 1. **Streamlined Context Management:** Provides an elegant solution for managing shared state across asynchronous functions without relying on global variables or complex patterns.
49
+ 2. **Scoped Cleanup:** Automatically dispose of resources or listeners when a scope is terminated.
50
+ 3. **Vue-Like Syntax:** Offers a familiar and intuitive API for developers with experience in Vue's Composition API.
package/dist/index.cjs CHANGED
@@ -178,7 +178,7 @@ function onScopeDispose(fn) {
178
178
  function withContext(fn, detached = false) {
179
179
  return function(...args) {
180
180
  const scope = createScope(detached);
181
- return scope.run(fn.bind(this, args));
181
+ return scope.run(fn.bind(this, ...args));
182
182
  };
183
183
  }
184
184
  function runWithContext(fn, isolated = false) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/scope.ts","../src/hooks/provide.ts","../src/hooks/createContext.ts","../src/hooks/onScopeDispose.ts","../src/index.ts"],"sourcesContent":["import { type Awaitable, catchError, isPromise } from '@andrew_l/toolkit';\nimport type { AsyncLocalStorage } from 'node:async_hooks';\n\nlet idSec = 0;\nlet ALS: AsyncLocalStorage<Scope | null> | undefined;\nlet currentScope: Scope | null = null;\n\ncatchError(async () => {\n ALS = new (\n await import('node:async_hooks')\n ).AsyncLocalStorage<Scope | null>();\n});\n\nexport class Scope {\n /**\n * @internal\n */\n id: number;\n\n /**\n * @internal\n */\n providers = new Map<any, any>();\n\n /**\n * @internal\n */\n parent: Scope | null = null;\n\n /**\n * @internal\n */\n cleanups: (() => void)[] = [];\n\n /**\n * @internal\n */\n private _activeRuns: number = 0;\n\n constructor(public detached = false) {\n this.id = ++idSec;\n\n if (!detached) {\n this.parent = getCurrentScope();\n }\n }\n\n run<T>(fn: () => T): T {\n this._activeRuns++;\n\n const onComplete = ([err, result]: [Error | undefined, any]): T => {\n this._activeRuns--;\n\n if (this._activeRuns === 0) {\n this.stop();\n }\n\n if (err) {\n throw err;\n }\n\n return result;\n };\n\n const r = runInScope(this, fn);\n\n if (isPromise<any>(r)) {\n return r.then(onComplete) as T;\n }\n\n return onComplete(r);\n }\n\n stop() {\n for (let i = 0, l = this.cleanups.length; i < l; i++) {\n this.cleanups[i]();\n }\n\n this.cleanups.length = 0;\n }\n\n get active(): boolean {\n return this._activeRuns > 0;\n }\n}\n\n/**\n * @param detached - Can be used to create a \"detached\" scope.\n */\nexport function createScope(detached?: boolean): Scope {\n return new Scope(detached);\n}\n\n/**\n * @group Main\n */\nexport function getCurrentScope(): Scope | null {\n if (ALS) {\n return ALS.getStore() ?? null;\n }\n\n return currentScope;\n}\n\nexport function setCurrentScope(scope: Scope) {\n if (ALS) {\n ALS.enterWith(scope);\n } else {\n currentScope = scope;\n }\n}\n\nfunction runInScope<T>(\n scope: Scope,\n fn: () => T,\n): Awaitable<[Error | undefined, any]> {\n if (ALS) {\n return catchError(() => ALS!.run(scope, fn));\n }\n\n const prevScope = getCurrentScope();\n\n const onComplete = ([err, result]: [Error | undefined, any]): [\n Error | undefined,\n any,\n ] => {\n if (prevScope) {\n setCurrentScope(prevScope);\n } else {\n unsetCurrentScope();\n }\n\n return [err, result];\n };\n\n const result = catchError(fn);\n\n if (isPromise<any>(result)) {\n return result.then(onComplete);\n }\n\n return onComplete(result);\n}\n\nconst unsetCurrentScope = (): void => {\n if (ALS) {\n ALS.enterWith(null);\n } else {\n currentScope = null;\n }\n};\n","import { captureStackTrace, isFunction } from '@andrew_l/toolkit';\nimport { Scope, getCurrentScope, setCurrentScope } from '../scope';\n\nexport type ProvideKey = symbol | string | number | object;\nexport type ProvideValue<T = unknown> = T | undefined;\nexport type InjectionKey = symbol | string | number | object;\n\n/**\n * To provide data to a descendants\n * @param enterWith Enter into injection context (Experimental)\n * @group Main\n */\nexport function provide(key: ProvideKey, value: any, enterWith?: boolean) {\n let currentScope = getCurrentScope();\n\n if (!currentScope) {\n if (enterWith) {\n currentScope = new Scope();\n setCurrentScope(currentScope);\n } else {\n console.warn(\n `provide() is called when there is no active scope to be associated with.\\n` +\n captureStackTrace(provide),\n );\n return;\n }\n }\n\n currentScope.providers.set(key, value);\n}\n\n/**\n * Inject previously provided data\n * @group Main\n */\nexport function inject<T = any>(\n key: ProvideKey,\n defaultValue?: T | (() => T),\n): ProvideValue<T> {\n let currentScope = getCurrentScope();\n\n if (!currentScope) {\n console.warn(\n `inject() is called when there is no active scope to be associated with.\\n` +\n captureStackTrace(inject),\n );\n return;\n }\n\n const handled = new WeakSet();\n\n let value;\n\n do {\n value = currentScope!.providers.get(key);\n handled.add(currentScope!);\n currentScope = currentScope!.parent;\n } while (currentScope && value === undefined && !handled.has(currentScope));\n\n if (value === undefined && defaultValue !== undefined) {\n value = isFunction(defaultValue) ? defaultValue() : defaultValue;\n }\n\n return value;\n}\n\n/**\n * Returns true if `inject()` can be used without warning about being called in the wrong place.\n * @group Main\n */\nexport function hasInjectionContext() {\n return !!getCurrentScope();\n}\n","import { type InjectionKey, inject, provide } from './provide';\n\n/**\n * Wrapper around `provide/inject` function to simple usage.\n *\n * @param providerName - The name(s) of the providing the context.\n *\n * There are situations where context can come from multiple scopes. In such cases, you might need to give an array of names to provide your context, instead of just a single string.\n *\n * @param contextName The description for injection key symbol.\n *\n * @example\n * const [injectTraceId, provideTraceId] = createContext<string>('withContext');\n *\n * // this function will returns the same trace if for execution context\n * export const useTraceId = () => {\n * let traceId = injectTraceId(null);\n *\n * if (!traceId) {\n * traceId = uuidv4();\n * provideTraceId(traceId);\n * }\n *\n * return traceId;\n * };\n *\n * @group Main\n */\nexport function createContext<ContextValue>(\n providerName: string | string[],\n contextName?: string,\n) {\n const symbolDescription =\n typeof providerName === 'string' && !contextName\n ? `${providerName}Context`\n : contextName;\n\n const injectionKey: InjectionKey = Symbol(symbolDescription);\n\n /**\n * @param fallback The context value to return if the injection fails.\n *\n * @throws When context injection failed and no fallback is specified.\n * This happens when the scope injecting the context is not a child of the root scope providing the context.\n */\n const injectContext = <\n T extends ContextValue | null | undefined = ContextValue,\n >(\n fallback?: T | (() => T),\n ): T extends null ? ContextValue | null : ContextValue => {\n const context = inject(injectionKey, fallback);\n if (context) return context;\n\n if (context === null) return context as any;\n\n throw new Error(\n `Injection \\`${injectionKey.toString()}\\` not found. Must be used within ${\n Array.isArray(providerName)\n ? `one of the following providers: ${providerName.join(', ')}`\n : `\\`${providerName}\\``\n }`,\n );\n };\n\n const provideContext = (contextValue: ContextValue) => {\n provide(injectionKey, contextValue);\n return contextValue;\n };\n\n return [injectContext, provideContext] as const;\n}\n","import { assert, captureStackTrace } from '@andrew_l/toolkit';\nimport { getCurrentScope } from '../scope';\n\n/**\n * The callback will be invoked when the associated context completes.\n *\n * @example\n * const fn = withContext(() => {\n * onScopeDispose(() => {\n * console.log(2);\n * });\n *\n * console.log(1);\n * });\n *\n * fn();\n *\n * console.log(3);\n *\n * // 1\n * // 2\n * // 3\n *\n * @group Main\n */\nexport function onScopeDispose(fn: () => void) {\n const activeScope = getCurrentScope();\n\n assert.ok(\n activeScope,\n 'onScopeDispose() is called when there is no active scope to be associated with.' +\n captureStackTrace(onScopeDispose),\n );\n\n activeScope.cleanups.push(fn);\n}\n","import { type AnyFunction, assert } from '@andrew_l/toolkit';\nimport { createScope, getCurrentScope } from './scope';\n\nexport * from './hooks/createContext';\nexport * from './hooks/onScopeDispose';\nexport * from './hooks/provide';\nexport { getCurrentScope } from './scope';\n\n/**\n * Creates a function within the injection context and returns its result. Providers/injections are only accessible within the callback function.\n * @param isolated Do not inject parent providers into this context (Default: `false`)\n *\n * @example\n * const main = withContext(() => {\n * provide('user', { id: 1, name: 'Andrew' });\n * doCoolStaff();\n * });\n *\n * const doCoolStaff = () => {\n * const user = inject('user');\n * console.log(user); // { id: 1, name: 'Andrew' }\n * };\n *\n * main();\n *\n * @group Main\n */\nexport function withContext<T extends AnyFunction>(fn: T, detached = false): T {\n return function (this: any, ...args: any[]) {\n const scope = createScope(detached);\n return scope.run(fn.bind(this, args));\n } as T;\n}\n\n/**\n * Runs a function within the injection context and returns its result. Providers/injections are only accessible inside the callback function.\n * @param isolated Do not inject parent providers into this context (Default: `false`)\n * @group Main\n */\nexport function runWithContext<T = any>(fn: () => T, isolated = false): T {\n return withContext(fn, isolated)();\n}\n\n/**\n * Binds the current context to the provided function. Useful for creating callbacks with the current context, such as `setTimeout` or `EventEmitter` handlers.\n * @example\n * const main = withContext(() => {\n * provide(\"user\", { id: 1, name: \"Andrew\" });\n *\n * setInterval(bindContext(() => {\n * const user = inject(\"user\");\n * console.log(user); // { id: 1, name: 'Andrew' }\n * }));\n * });\n *\n * main();\n *\n * @group Main\n */\nexport function bindContext<T>(fn: () => T): () => T {\n const activeScope = getCurrentScope();\n\n assert.ok(\n activeScope,\n 'bindContext() is called when there is no active scope to be associated with.',\n );\n\n return () => activeScope.run(fn);\n}\n"],"names":["catchError","isPromise","result","captureStackTrace","isFunction","assert"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/scope.ts","../src/hooks/provide.ts","../src/hooks/createContext.ts","../src/hooks/onScopeDispose.ts","../src/index.ts"],"sourcesContent":["import { type Awaitable, catchError, isPromise } from '@andrew_l/toolkit';\nimport type { AsyncLocalStorage } from 'node:async_hooks';\n\nlet idSec = 0;\nlet ALS: AsyncLocalStorage<Scope | null> | undefined;\nlet currentScope: Scope | null = null;\n\ncatchError(async () => {\n ALS = new (\n await import('node:async_hooks')\n ).AsyncLocalStorage<Scope | null>();\n});\n\nexport class Scope {\n /**\n * @internal\n */\n id: number;\n\n /**\n * @internal\n */\n providers = new Map<any, any>();\n\n /**\n * @internal\n */\n parent: Scope | null = null;\n\n /**\n * @internal\n */\n cleanups: (() => void)[] = [];\n\n /**\n * @internal\n */\n private _activeRuns: number = 0;\n\n constructor(public detached = false) {\n this.id = ++idSec;\n\n if (!detached) {\n this.parent = getCurrentScope();\n }\n }\n\n run<T>(fn: () => T): T {\n this._activeRuns++;\n\n const onComplete = ([err, result]: [Error | undefined, any]): T => {\n this._activeRuns--;\n\n if (this._activeRuns === 0) {\n this.stop();\n }\n\n if (err) {\n throw err;\n }\n\n return result;\n };\n\n const r = runInScope(this, fn);\n\n if (isPromise<any>(r)) {\n return r.then(onComplete) as T;\n }\n\n return onComplete(r);\n }\n\n stop() {\n for (let i = 0, l = this.cleanups.length; i < l; i++) {\n this.cleanups[i]();\n }\n\n this.cleanups.length = 0;\n }\n\n get active(): boolean {\n return this._activeRuns > 0;\n }\n}\n\n/**\n * @param detached - Can be used to create a \"detached\" scope.\n */\nexport function createScope(detached?: boolean): Scope {\n return new Scope(detached);\n}\n\n/**\n * @group Main\n */\nexport function getCurrentScope(): Scope | null {\n if (ALS) {\n return ALS.getStore() ?? null;\n }\n\n return currentScope;\n}\n\nexport function setCurrentScope(scope: Scope) {\n if (ALS) {\n ALS.enterWith(scope);\n } else {\n currentScope = scope;\n }\n}\n\nfunction runInScope<T>(\n scope: Scope,\n fn: () => T,\n): Awaitable<[Error | undefined, any]> {\n if (ALS) {\n return catchError(() => ALS!.run(scope, fn));\n }\n\n const prevScope = getCurrentScope();\n\n const onComplete = ([err, result]: [Error | undefined, any]): [\n Error | undefined,\n any,\n ] => {\n if (prevScope) {\n setCurrentScope(prevScope);\n } else {\n unsetCurrentScope();\n }\n\n return [err, result];\n };\n\n const result = catchError(fn);\n\n if (isPromise<any>(result)) {\n return result.then(onComplete);\n }\n\n return onComplete(result);\n}\n\nconst unsetCurrentScope = (): void => {\n if (ALS) {\n ALS.enterWith(null);\n } else {\n currentScope = null;\n }\n};\n","import { captureStackTrace, isFunction } from '@andrew_l/toolkit';\nimport { Scope, getCurrentScope, setCurrentScope } from '../scope';\n\nexport type ProvideKey = symbol | string | number | object;\nexport type ProvideValue<T = unknown> = T | undefined;\nexport type InjectionKey = symbol | string | number | object;\n\n/**\n * To provide data to a descendants\n * @param enterWith Enter into injection context (Experimental)\n * @group Main\n */\nexport function provide(key: ProvideKey, value: any, enterWith?: boolean) {\n let currentScope = getCurrentScope();\n\n if (!currentScope) {\n if (enterWith) {\n currentScope = new Scope();\n setCurrentScope(currentScope);\n } else {\n console.warn(\n `provide() is called when there is no active scope to be associated with.\\n` +\n captureStackTrace(provide),\n );\n return;\n }\n }\n\n currentScope.providers.set(key, value);\n}\n\n/**\n * Inject previously provided data\n * @group Main\n */\nexport function inject<T = any>(\n key: ProvideKey,\n defaultValue?: T | (() => T),\n): ProvideValue<T> {\n let currentScope = getCurrentScope();\n\n if (!currentScope) {\n console.warn(\n `inject() is called when there is no active scope to be associated with.\\n` +\n captureStackTrace(inject),\n );\n return;\n }\n\n const handled = new WeakSet();\n\n let value;\n\n do {\n value = currentScope!.providers.get(key);\n handled.add(currentScope!);\n currentScope = currentScope!.parent;\n } while (currentScope && value === undefined && !handled.has(currentScope));\n\n if (value === undefined && defaultValue !== undefined) {\n value = isFunction(defaultValue) ? defaultValue() : defaultValue;\n }\n\n return value;\n}\n\n/**\n * Returns true if `inject()` can be used without warning about being called in the wrong place.\n * @group Main\n */\nexport function hasInjectionContext() {\n return !!getCurrentScope();\n}\n","import { type InjectionKey, inject, provide } from './provide';\n\n/**\n * Wrapper around `provide/inject` function to simple usage.\n *\n * @param providerName - The name(s) of the providing the context.\n *\n * There are situations where context can come from multiple scopes. In such cases, you might need to give an array of names to provide your context, instead of just a single string.\n *\n * @param contextName The description for injection key symbol.\n *\n * @example\n * const [injectTraceId, provideTraceId] = createContext<string>('withContext');\n *\n * // this function will returns the same trace if for execution context\n * export const useTraceId = () => {\n * let traceId = injectTraceId(null);\n *\n * if (!traceId) {\n * traceId = uuidv4();\n * provideTraceId(traceId);\n * }\n *\n * return traceId;\n * };\n *\n * @group Main\n */\nexport function createContext<ContextValue>(\n providerName: string | string[],\n contextName?: string,\n) {\n const symbolDescription =\n typeof providerName === 'string' && !contextName\n ? `${providerName}Context`\n : contextName;\n\n const injectionKey: InjectionKey = Symbol(symbolDescription);\n\n /**\n * @param fallback The context value to return if the injection fails.\n *\n * @throws When context injection failed and no fallback is specified.\n * This happens when the scope injecting the context is not a child of the root scope providing the context.\n */\n const injectContext = <\n T extends ContextValue | null | undefined = ContextValue,\n >(\n fallback?: T | (() => T),\n ): T extends null ? ContextValue | null : ContextValue => {\n const context = inject(injectionKey, fallback);\n if (context) return context;\n\n if (context === null) return context as any;\n\n throw new Error(\n `Injection \\`${injectionKey.toString()}\\` not found. Must be used within ${\n Array.isArray(providerName)\n ? `one of the following providers: ${providerName.join(', ')}`\n : `\\`${providerName}\\``\n }`,\n );\n };\n\n const provideContext = (contextValue: ContextValue) => {\n provide(injectionKey, contextValue);\n return contextValue;\n };\n\n return [injectContext, provideContext] as const;\n}\n","import { assert, captureStackTrace } from '@andrew_l/toolkit';\nimport { getCurrentScope } from '../scope';\n\n/**\n * The callback will be invoked when the associated context completes.\n *\n * @example\n * const fn = withContext(() => {\n * onScopeDispose(() => {\n * console.log(2);\n * });\n *\n * console.log(1);\n * });\n *\n * fn();\n *\n * console.log(3);\n *\n * // 1\n * // 2\n * // 3\n *\n * @group Main\n */\nexport function onScopeDispose(fn: () => void) {\n const activeScope = getCurrentScope();\n\n assert.ok(\n activeScope,\n 'onScopeDispose() is called when there is no active scope to be associated with.' +\n captureStackTrace(onScopeDispose),\n );\n\n activeScope.cleanups.push(fn);\n}\n","import { type AnyFunction, assert } from '@andrew_l/toolkit';\nimport { createScope, getCurrentScope } from './scope';\n\nexport * from './hooks/createContext';\nexport * from './hooks/onScopeDispose';\nexport * from './hooks/provide';\nexport { getCurrentScope } from './scope';\n\n/**\n * Creates a function within the injection context and returns its result. Providers/injections are only accessible within the callback function.\n * @param isolated Do not inject parent providers into this context (Default: `false`)\n *\n * @example\n * const main = withContext(() => {\n * provide('user', { id: 1, name: 'Andrew' });\n * doCoolStaff();\n * });\n *\n * const doCoolStaff = () => {\n * const user = inject('user');\n * console.log(user); // { id: 1, name: 'Andrew' }\n * };\n *\n * main();\n *\n * @group Main\n */\nexport function withContext<T extends AnyFunction>(fn: T, detached = false): T {\n return function (this: any, ...args: any[]) {\n const scope = createScope(detached);\n return scope.run(fn.bind(this, ...args));\n } as T;\n}\n\n/**\n * Runs a function within the injection context and returns its result. Providers/injections are only accessible inside the callback function.\n * @param isolated Do not inject parent providers into this context (Default: `false`)\n * @group Main\n */\nexport function runWithContext<T = any>(fn: () => T, isolated = false): T {\n return withContext(fn, isolated)();\n}\n\n/**\n * Binds the current context to the provided function. Useful for creating callbacks with the current context, such as `setTimeout` or `EventEmitter` handlers.\n * @example\n * const main = withContext(() => {\n * provide(\"user\", { id: 1, name: \"Andrew\" });\n *\n * setInterval(bindContext(() => {\n * const user = inject(\"user\");\n * console.log(user); // { id: 1, name: 'Andrew' }\n * }));\n * });\n *\n * main();\n *\n * @group Main\n */\nexport function bindContext<T>(fn: () => T): () => T {\n const activeScope = getCurrentScope();\n\n assert.ok(\n activeScope,\n 'bindContext() is called when there is no active scope to be associated with.',\n );\n\n return () => activeScope.run(fn);\n}\n"],"names":["catchError","isPromise","result","captureStackTrace","isFunction","assert"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -176,7 +176,7 @@ function onScopeDispose(fn) {
176
176
  function withContext(fn, detached = false) {
177
177
  return function(...args) {
178
178
  const scope = createScope(detached);
179
- return scope.run(fn.bind(this, args));
179
+ return scope.run(fn.bind(this, ...args));
180
180
  };
181
181
  }
182
182
  function runWithContext(fn, isolated = false) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/scope.ts","../src/hooks/provide.ts","../src/hooks/createContext.ts","../src/hooks/onScopeDispose.ts","../src/index.ts"],"sourcesContent":["import { type Awaitable, catchError, isPromise } from '@andrew_l/toolkit';\nimport type { AsyncLocalStorage } from 'node:async_hooks';\n\nlet idSec = 0;\nlet ALS: AsyncLocalStorage<Scope | null> | undefined;\nlet currentScope: Scope | null = null;\n\ncatchError(async () => {\n ALS = new (\n await import('node:async_hooks')\n ).AsyncLocalStorage<Scope | null>();\n});\n\nexport class Scope {\n /**\n * @internal\n */\n id: number;\n\n /**\n * @internal\n */\n providers = new Map<any, any>();\n\n /**\n * @internal\n */\n parent: Scope | null = null;\n\n /**\n * @internal\n */\n cleanups: (() => void)[] = [];\n\n /**\n * @internal\n */\n private _activeRuns: number = 0;\n\n constructor(public detached = false) {\n this.id = ++idSec;\n\n if (!detached) {\n this.parent = getCurrentScope();\n }\n }\n\n run<T>(fn: () => T): T {\n this._activeRuns++;\n\n const onComplete = ([err, result]: [Error | undefined, any]): T => {\n this._activeRuns--;\n\n if (this._activeRuns === 0) {\n this.stop();\n }\n\n if (err) {\n throw err;\n }\n\n return result;\n };\n\n const r = runInScope(this, fn);\n\n if (isPromise<any>(r)) {\n return r.then(onComplete) as T;\n }\n\n return onComplete(r);\n }\n\n stop() {\n for (let i = 0, l = this.cleanups.length; i < l; i++) {\n this.cleanups[i]();\n }\n\n this.cleanups.length = 0;\n }\n\n get active(): boolean {\n return this._activeRuns > 0;\n }\n}\n\n/**\n * @param detached - Can be used to create a \"detached\" scope.\n */\nexport function createScope(detached?: boolean): Scope {\n return new Scope(detached);\n}\n\n/**\n * @group Main\n */\nexport function getCurrentScope(): Scope | null {\n if (ALS) {\n return ALS.getStore() ?? null;\n }\n\n return currentScope;\n}\n\nexport function setCurrentScope(scope: Scope) {\n if (ALS) {\n ALS.enterWith(scope);\n } else {\n currentScope = scope;\n }\n}\n\nfunction runInScope<T>(\n scope: Scope,\n fn: () => T,\n): Awaitable<[Error | undefined, any]> {\n if (ALS) {\n return catchError(() => ALS!.run(scope, fn));\n }\n\n const prevScope = getCurrentScope();\n\n const onComplete = ([err, result]: [Error | undefined, any]): [\n Error | undefined,\n any,\n ] => {\n if (prevScope) {\n setCurrentScope(prevScope);\n } else {\n unsetCurrentScope();\n }\n\n return [err, result];\n };\n\n const result = catchError(fn);\n\n if (isPromise<any>(result)) {\n return result.then(onComplete);\n }\n\n return onComplete(result);\n}\n\nconst unsetCurrentScope = (): void => {\n if (ALS) {\n ALS.enterWith(null);\n } else {\n currentScope = null;\n }\n};\n","import { captureStackTrace, isFunction } from '@andrew_l/toolkit';\nimport { Scope, getCurrentScope, setCurrentScope } from '../scope';\n\nexport type ProvideKey = symbol | string | number | object;\nexport type ProvideValue<T = unknown> = T | undefined;\nexport type InjectionKey = symbol | string | number | object;\n\n/**\n * To provide data to a descendants\n * @param enterWith Enter into injection context (Experimental)\n * @group Main\n */\nexport function provide(key: ProvideKey, value: any, enterWith?: boolean) {\n let currentScope = getCurrentScope();\n\n if (!currentScope) {\n if (enterWith) {\n currentScope = new Scope();\n setCurrentScope(currentScope);\n } else {\n console.warn(\n `provide() is called when there is no active scope to be associated with.\\n` +\n captureStackTrace(provide),\n );\n return;\n }\n }\n\n currentScope.providers.set(key, value);\n}\n\n/**\n * Inject previously provided data\n * @group Main\n */\nexport function inject<T = any>(\n key: ProvideKey,\n defaultValue?: T | (() => T),\n): ProvideValue<T> {\n let currentScope = getCurrentScope();\n\n if (!currentScope) {\n console.warn(\n `inject() is called when there is no active scope to be associated with.\\n` +\n captureStackTrace(inject),\n );\n return;\n }\n\n const handled = new WeakSet();\n\n let value;\n\n do {\n value = currentScope!.providers.get(key);\n handled.add(currentScope!);\n currentScope = currentScope!.parent;\n } while (currentScope && value === undefined && !handled.has(currentScope));\n\n if (value === undefined && defaultValue !== undefined) {\n value = isFunction(defaultValue) ? defaultValue() : defaultValue;\n }\n\n return value;\n}\n\n/**\n * Returns true if `inject()` can be used without warning about being called in the wrong place.\n * @group Main\n */\nexport function hasInjectionContext() {\n return !!getCurrentScope();\n}\n","import { type InjectionKey, inject, provide } from './provide';\n\n/**\n * Wrapper around `provide/inject` function to simple usage.\n *\n * @param providerName - The name(s) of the providing the context.\n *\n * There are situations where context can come from multiple scopes. In such cases, you might need to give an array of names to provide your context, instead of just a single string.\n *\n * @param contextName The description for injection key symbol.\n *\n * @example\n * const [injectTraceId, provideTraceId] = createContext<string>('withContext');\n *\n * // this function will returns the same trace if for execution context\n * export const useTraceId = () => {\n * let traceId = injectTraceId(null);\n *\n * if (!traceId) {\n * traceId = uuidv4();\n * provideTraceId(traceId);\n * }\n *\n * return traceId;\n * };\n *\n * @group Main\n */\nexport function createContext<ContextValue>(\n providerName: string | string[],\n contextName?: string,\n) {\n const symbolDescription =\n typeof providerName === 'string' && !contextName\n ? `${providerName}Context`\n : contextName;\n\n const injectionKey: InjectionKey = Symbol(symbolDescription);\n\n /**\n * @param fallback The context value to return if the injection fails.\n *\n * @throws When context injection failed and no fallback is specified.\n * This happens when the scope injecting the context is not a child of the root scope providing the context.\n */\n const injectContext = <\n T extends ContextValue | null | undefined = ContextValue,\n >(\n fallback?: T | (() => T),\n ): T extends null ? ContextValue | null : ContextValue => {\n const context = inject(injectionKey, fallback);\n if (context) return context;\n\n if (context === null) return context as any;\n\n throw new Error(\n `Injection \\`${injectionKey.toString()}\\` not found. Must be used within ${\n Array.isArray(providerName)\n ? `one of the following providers: ${providerName.join(', ')}`\n : `\\`${providerName}\\``\n }`,\n );\n };\n\n const provideContext = (contextValue: ContextValue) => {\n provide(injectionKey, contextValue);\n return contextValue;\n };\n\n return [injectContext, provideContext] as const;\n}\n","import { assert, captureStackTrace } from '@andrew_l/toolkit';\nimport { getCurrentScope } from '../scope';\n\n/**\n * The callback will be invoked when the associated context completes.\n *\n * @example\n * const fn = withContext(() => {\n * onScopeDispose(() => {\n * console.log(2);\n * });\n *\n * console.log(1);\n * });\n *\n * fn();\n *\n * console.log(3);\n *\n * // 1\n * // 2\n * // 3\n *\n * @group Main\n */\nexport function onScopeDispose(fn: () => void) {\n const activeScope = getCurrentScope();\n\n assert.ok(\n activeScope,\n 'onScopeDispose() is called when there is no active scope to be associated with.' +\n captureStackTrace(onScopeDispose),\n );\n\n activeScope.cleanups.push(fn);\n}\n","import { type AnyFunction, assert } from '@andrew_l/toolkit';\nimport { createScope, getCurrentScope } from './scope';\n\nexport * from './hooks/createContext';\nexport * from './hooks/onScopeDispose';\nexport * from './hooks/provide';\nexport { getCurrentScope } from './scope';\n\n/**\n * Creates a function within the injection context and returns its result. Providers/injections are only accessible within the callback function.\n * @param isolated Do not inject parent providers into this context (Default: `false`)\n *\n * @example\n * const main = withContext(() => {\n * provide('user', { id: 1, name: 'Andrew' });\n * doCoolStaff();\n * });\n *\n * const doCoolStaff = () => {\n * const user = inject('user');\n * console.log(user); // { id: 1, name: 'Andrew' }\n * };\n *\n * main();\n *\n * @group Main\n */\nexport function withContext<T extends AnyFunction>(fn: T, detached = false): T {\n return function (this: any, ...args: any[]) {\n const scope = createScope(detached);\n return scope.run(fn.bind(this, args));\n } as T;\n}\n\n/**\n * Runs a function within the injection context and returns its result. Providers/injections are only accessible inside the callback function.\n * @param isolated Do not inject parent providers into this context (Default: `false`)\n * @group Main\n */\nexport function runWithContext<T = any>(fn: () => T, isolated = false): T {\n return withContext(fn, isolated)();\n}\n\n/**\n * Binds the current context to the provided function. Useful for creating callbacks with the current context, such as `setTimeout` or `EventEmitter` handlers.\n * @example\n * const main = withContext(() => {\n * provide(\"user\", { id: 1, name: \"Andrew\" });\n *\n * setInterval(bindContext(() => {\n * const user = inject(\"user\");\n * console.log(user); // { id: 1, name: 'Andrew' }\n * }));\n * });\n *\n * main();\n *\n * @group Main\n */\nexport function bindContext<T>(fn: () => T): () => T {\n const activeScope = getCurrentScope();\n\n assert.ok(\n activeScope,\n 'bindContext() is called when there is no active scope to be associated with.',\n );\n\n return () => activeScope.run(fn);\n}\n"],"names":["result"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../src/scope.ts","../src/hooks/provide.ts","../src/hooks/createContext.ts","../src/hooks/onScopeDispose.ts","../src/index.ts"],"sourcesContent":["import { type Awaitable, catchError, isPromise } from '@andrew_l/toolkit';\nimport type { AsyncLocalStorage } from 'node:async_hooks';\n\nlet idSec = 0;\nlet ALS: AsyncLocalStorage<Scope | null> | undefined;\nlet currentScope: Scope | null = null;\n\ncatchError(async () => {\n ALS = new (\n await import('node:async_hooks')\n ).AsyncLocalStorage<Scope | null>();\n});\n\nexport class Scope {\n /**\n * @internal\n */\n id: number;\n\n /**\n * @internal\n */\n providers = new Map<any, any>();\n\n /**\n * @internal\n */\n parent: Scope | null = null;\n\n /**\n * @internal\n */\n cleanups: (() => void)[] = [];\n\n /**\n * @internal\n */\n private _activeRuns: number = 0;\n\n constructor(public detached = false) {\n this.id = ++idSec;\n\n if (!detached) {\n this.parent = getCurrentScope();\n }\n }\n\n run<T>(fn: () => T): T {\n this._activeRuns++;\n\n const onComplete = ([err, result]: [Error | undefined, any]): T => {\n this._activeRuns--;\n\n if (this._activeRuns === 0) {\n this.stop();\n }\n\n if (err) {\n throw err;\n }\n\n return result;\n };\n\n const r = runInScope(this, fn);\n\n if (isPromise<any>(r)) {\n return r.then(onComplete) as T;\n }\n\n return onComplete(r);\n }\n\n stop() {\n for (let i = 0, l = this.cleanups.length; i < l; i++) {\n this.cleanups[i]();\n }\n\n this.cleanups.length = 0;\n }\n\n get active(): boolean {\n return this._activeRuns > 0;\n }\n}\n\n/**\n * @param detached - Can be used to create a \"detached\" scope.\n */\nexport function createScope(detached?: boolean): Scope {\n return new Scope(detached);\n}\n\n/**\n * @group Main\n */\nexport function getCurrentScope(): Scope | null {\n if (ALS) {\n return ALS.getStore() ?? null;\n }\n\n return currentScope;\n}\n\nexport function setCurrentScope(scope: Scope) {\n if (ALS) {\n ALS.enterWith(scope);\n } else {\n currentScope = scope;\n }\n}\n\nfunction runInScope<T>(\n scope: Scope,\n fn: () => T,\n): Awaitable<[Error | undefined, any]> {\n if (ALS) {\n return catchError(() => ALS!.run(scope, fn));\n }\n\n const prevScope = getCurrentScope();\n\n const onComplete = ([err, result]: [Error | undefined, any]): [\n Error | undefined,\n any,\n ] => {\n if (prevScope) {\n setCurrentScope(prevScope);\n } else {\n unsetCurrentScope();\n }\n\n return [err, result];\n };\n\n const result = catchError(fn);\n\n if (isPromise<any>(result)) {\n return result.then(onComplete);\n }\n\n return onComplete(result);\n}\n\nconst unsetCurrentScope = (): void => {\n if (ALS) {\n ALS.enterWith(null);\n } else {\n currentScope = null;\n }\n};\n","import { captureStackTrace, isFunction } from '@andrew_l/toolkit';\nimport { Scope, getCurrentScope, setCurrentScope } from '../scope';\n\nexport type ProvideKey = symbol | string | number | object;\nexport type ProvideValue<T = unknown> = T | undefined;\nexport type InjectionKey = symbol | string | number | object;\n\n/**\n * To provide data to a descendants\n * @param enterWith Enter into injection context (Experimental)\n * @group Main\n */\nexport function provide(key: ProvideKey, value: any, enterWith?: boolean) {\n let currentScope = getCurrentScope();\n\n if (!currentScope) {\n if (enterWith) {\n currentScope = new Scope();\n setCurrentScope(currentScope);\n } else {\n console.warn(\n `provide() is called when there is no active scope to be associated with.\\n` +\n captureStackTrace(provide),\n );\n return;\n }\n }\n\n currentScope.providers.set(key, value);\n}\n\n/**\n * Inject previously provided data\n * @group Main\n */\nexport function inject<T = any>(\n key: ProvideKey,\n defaultValue?: T | (() => T),\n): ProvideValue<T> {\n let currentScope = getCurrentScope();\n\n if (!currentScope) {\n console.warn(\n `inject() is called when there is no active scope to be associated with.\\n` +\n captureStackTrace(inject),\n );\n return;\n }\n\n const handled = new WeakSet();\n\n let value;\n\n do {\n value = currentScope!.providers.get(key);\n handled.add(currentScope!);\n currentScope = currentScope!.parent;\n } while (currentScope && value === undefined && !handled.has(currentScope));\n\n if (value === undefined && defaultValue !== undefined) {\n value = isFunction(defaultValue) ? defaultValue() : defaultValue;\n }\n\n return value;\n}\n\n/**\n * Returns true if `inject()` can be used without warning about being called in the wrong place.\n * @group Main\n */\nexport function hasInjectionContext() {\n return !!getCurrentScope();\n}\n","import { type InjectionKey, inject, provide } from './provide';\n\n/**\n * Wrapper around `provide/inject` function to simple usage.\n *\n * @param providerName - The name(s) of the providing the context.\n *\n * There are situations where context can come from multiple scopes. In such cases, you might need to give an array of names to provide your context, instead of just a single string.\n *\n * @param contextName The description for injection key symbol.\n *\n * @example\n * const [injectTraceId, provideTraceId] = createContext<string>('withContext');\n *\n * // this function will returns the same trace if for execution context\n * export const useTraceId = () => {\n * let traceId = injectTraceId(null);\n *\n * if (!traceId) {\n * traceId = uuidv4();\n * provideTraceId(traceId);\n * }\n *\n * return traceId;\n * };\n *\n * @group Main\n */\nexport function createContext<ContextValue>(\n providerName: string | string[],\n contextName?: string,\n) {\n const symbolDescription =\n typeof providerName === 'string' && !contextName\n ? `${providerName}Context`\n : contextName;\n\n const injectionKey: InjectionKey = Symbol(symbolDescription);\n\n /**\n * @param fallback The context value to return if the injection fails.\n *\n * @throws When context injection failed and no fallback is specified.\n * This happens when the scope injecting the context is not a child of the root scope providing the context.\n */\n const injectContext = <\n T extends ContextValue | null | undefined = ContextValue,\n >(\n fallback?: T | (() => T),\n ): T extends null ? ContextValue | null : ContextValue => {\n const context = inject(injectionKey, fallback);\n if (context) return context;\n\n if (context === null) return context as any;\n\n throw new Error(\n `Injection \\`${injectionKey.toString()}\\` not found. Must be used within ${\n Array.isArray(providerName)\n ? `one of the following providers: ${providerName.join(', ')}`\n : `\\`${providerName}\\``\n }`,\n );\n };\n\n const provideContext = (contextValue: ContextValue) => {\n provide(injectionKey, contextValue);\n return contextValue;\n };\n\n return [injectContext, provideContext] as const;\n}\n","import { assert, captureStackTrace } from '@andrew_l/toolkit';\nimport { getCurrentScope } from '../scope';\n\n/**\n * The callback will be invoked when the associated context completes.\n *\n * @example\n * const fn = withContext(() => {\n * onScopeDispose(() => {\n * console.log(2);\n * });\n *\n * console.log(1);\n * });\n *\n * fn();\n *\n * console.log(3);\n *\n * // 1\n * // 2\n * // 3\n *\n * @group Main\n */\nexport function onScopeDispose(fn: () => void) {\n const activeScope = getCurrentScope();\n\n assert.ok(\n activeScope,\n 'onScopeDispose() is called when there is no active scope to be associated with.' +\n captureStackTrace(onScopeDispose),\n );\n\n activeScope.cleanups.push(fn);\n}\n","import { type AnyFunction, assert } from '@andrew_l/toolkit';\nimport { createScope, getCurrentScope } from './scope';\n\nexport * from './hooks/createContext';\nexport * from './hooks/onScopeDispose';\nexport * from './hooks/provide';\nexport { getCurrentScope } from './scope';\n\n/**\n * Creates a function within the injection context and returns its result. Providers/injections are only accessible within the callback function.\n * @param isolated Do not inject parent providers into this context (Default: `false`)\n *\n * @example\n * const main = withContext(() => {\n * provide('user', { id: 1, name: 'Andrew' });\n * doCoolStaff();\n * });\n *\n * const doCoolStaff = () => {\n * const user = inject('user');\n * console.log(user); // { id: 1, name: 'Andrew' }\n * };\n *\n * main();\n *\n * @group Main\n */\nexport function withContext<T extends AnyFunction>(fn: T, detached = false): T {\n return function (this: any, ...args: any[]) {\n const scope = createScope(detached);\n return scope.run(fn.bind(this, ...args));\n } as T;\n}\n\n/**\n * Runs a function within the injection context and returns its result. Providers/injections are only accessible inside the callback function.\n * @param isolated Do not inject parent providers into this context (Default: `false`)\n * @group Main\n */\nexport function runWithContext<T = any>(fn: () => T, isolated = false): T {\n return withContext(fn, isolated)();\n}\n\n/**\n * Binds the current context to the provided function. Useful for creating callbacks with the current context, such as `setTimeout` or `EventEmitter` handlers.\n * @example\n * const main = withContext(() => {\n * provide(\"user\", { id: 1, name: \"Andrew\" });\n *\n * setInterval(bindContext(() => {\n * const user = inject(\"user\");\n * console.log(user); // { id: 1, name: 'Andrew' }\n * }));\n * });\n *\n * main();\n *\n * @group Main\n */\nexport function bindContext<T>(fn: () => T): () => T {\n const activeScope = getCurrentScope();\n\n assert.ok(\n activeScope,\n 'bindContext() is called when there is no active scope to be associated with.',\n );\n\n return () => activeScope.run(fn);\n}\n"],"names":["result"],"mappings":";;AAGA,IAAI,KAAQ,GAAA,CAAA,CAAA;AACZ,IAAI,GAAA,CAAA;AACJ,IAAI,YAA6B,GAAA,IAAA,CAAA;AAEjC,UAAA,CAAW,YAAY;AACrB,EAAA,GAAA,GAAM,IACJ,CAAA,MAAM,OAAO,kBAAkB,GAC/B,iBAAgC,EAAA,CAAA;AACpC,CAAC,CAAA,CAAA;AAEM,MAAM,KAAM,CAAA;AAAA,EA0BjB,WAAA,CAAmB,WAAW,KAAO,EAAA;AAAlB,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA,CAAA;AACjB,IAAA,IAAA,CAAK,KAAK,EAAE,KAAA,CAAA;AAEZ,IAAA,IAAI,CAAC,QAAU,EAAA;AACb,MAAA,IAAA,CAAK,SAAS,eAAgB,EAAA,CAAA;AAAA,KAChC;AAAA,GACF;AAAA;AAAA;AAAA;AAAA,EA5BA,EAAA,CAAA;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,uBAAgB,GAAc,EAAA,CAAA;AAAA;AAAA;AAAA;AAAA,EAK9B,MAAuB,GAAA,IAAA,CAAA;AAAA;AAAA;AAAA;AAAA,EAKvB,WAA2B,EAAC,CAAA;AAAA;AAAA;AAAA;AAAA,EAKpB,WAAsB,GAAA,CAAA,CAAA;AAAA,EAU9B,IAAO,EAAgB,EAAA;AACrB,IAAK,IAAA,CAAA,WAAA,EAAA,CAAA;AAEL,IAAA,MAAM,UAAa,GAAA,CAAC,CAAC,GAAA,EAAK,MAAM,CAAmC,KAAA;AACjE,MAAK,IAAA,CAAA,WAAA,EAAA,CAAA;AAEL,MAAI,IAAA,IAAA,CAAK,gBAAgB,CAAG,EAAA;AAC1B,QAAA,IAAA,CAAK,IAAK,EAAA,CAAA;AAAA,OACZ;AAEA,MAAA,IAAI,GAAK,EAAA;AACP,QAAM,MAAA,GAAA,CAAA;AAAA,OACR;AAEA,MAAO,OAAA,MAAA,CAAA;AAAA,KACT,CAAA;AAEA,IAAM,MAAA,CAAA,GAAI,UAAW,CAAA,IAAA,EAAM,EAAE,CAAA,CAAA;AAE7B,IAAI,IAAA,SAAA,CAAe,CAAC,CAAG,EAAA;AACrB,MAAO,OAAA,CAAA,CAAE,KAAK,UAAU,CAAA,CAAA;AAAA,KAC1B;AAEA,IAAA,OAAO,WAAW,CAAC,CAAA,CAAA;AAAA,GACrB;AAAA,EAEA,IAAO,GAAA;AACL,IAAS,KAAA,IAAA,CAAA,GAAI,GAAG,CAAI,GAAA,IAAA,CAAK,SAAS,MAAQ,EAAA,CAAA,GAAI,GAAG,CAAK,EAAA,EAAA;AACpD,MAAK,IAAA,CAAA,QAAA,CAAS,CAAC,CAAE,EAAA,CAAA;AAAA,KACnB;AAEA,IAAA,IAAA,CAAK,SAAS,MAAS,GAAA,CAAA,CAAA;AAAA,GACzB;AAAA,EAEA,IAAI,MAAkB,GAAA;AACpB,IAAA,OAAO,KAAK,WAAc,GAAA,CAAA,CAAA;AAAA,GAC5B;AACF,CAAA;AAKO,SAAS,YAAY,QAA2B,EAAA;AACrD,EAAO,OAAA,IAAI,MAAM,QAAQ,CAAA,CAAA;AAC3B,CAAA;AAKO,SAAS,eAAgC,GAAA;AAC9C,EAAA,IAAI,GAAK,EAAA;AACP,IAAO,OAAA,GAAA,CAAI,UAAc,IAAA,IAAA,CAAA;AAAA,GAC3B;AAEA,EAAO,OAAA,YAAA,CAAA;AACT,CAAA;AAEO,SAAS,gBAAgB,KAAc,EAAA;AAC5C,EAAA,IAAI,GAAK,EAAA;AACP,IAAA,GAAA,CAAI,UAAU,KAAK,CAAA,CAAA;AAAA,GACd,MAAA;AACL,IAAe,YAAA,GAAA,KAAA,CAAA;AAAA,GACjB;AACF,CAAA;AAEA,SAAS,UAAA,CACP,OACA,EACqC,EAAA;AACrC,EAAA,IAAI,GAAK,EAAA;AACP,IAAA,OAAO,WAAW,MAAM,GAAA,CAAK,GAAI,CAAA,KAAA,EAAO,EAAE,CAAC,CAAA,CAAA;AAAA,GAC7C;AAEA,EAAA,MAAM,YAAY,eAAgB,EAAA,CAAA;AAElC,EAAA,MAAM,UAAa,GAAA,CAAC,CAAC,GAAA,EAAKA,OAAM,CAG3B,KAAA;AACH,IAAA,IAAI,SAAW,EAAA;AACb,MAAA,eAAA,CAAgB,SAAS,CAAA,CAAA;AAAA,KACpB,MAAA;AACL,MAAkB,iBAAA,EAAA,CAAA;AAAA,KACpB;AAEA,IAAO,OAAA,CAAC,KAAKA,OAAM,CAAA,CAAA;AAAA,GACrB,CAAA;AAEA,EAAM,MAAA,MAAA,GAAS,WAAW,EAAE,CAAA,CAAA;AAE5B,EAAI,IAAA,SAAA,CAAe,MAAM,CAAG,EAAA;AAC1B,IAAO,OAAA,MAAA,CAAO,KAAK,UAAU,CAAA,CAAA;AAAA,GAC/B;AAEA,EAAA,OAAO,WAAW,MAAM,CAAA,CAAA;AAC1B,CAAA;AAEA,MAAM,oBAAoB,MAAY;AACpC,EAAA,IAAI,GAAK,EAAA;AACP,IAAA,GAAA,CAAI,UAAU,IAAI,CAAA,CAAA;AAAA,GACb,MAAA;AACL,IAAe,YAAA,GAAA,IAAA,CAAA;AAAA,GACjB;AACF,CAAA;;AC1IgB,SAAA,OAAA,CAAQ,GAAiB,EAAA,KAAA,EAAY,SAAqB,EAAA;AACxE,EAAA,IAAI,eAAe,eAAgB,EAAA,CAAA;AAEnC,EAAA,IAAI,CAAC,YAAc,EAAA;AACjB,IAAA,IAAI,SAAW,EAAA;AACb,MAAA,YAAA,GAAe,IAAI,KAAM,EAAA,CAAA;AACzB,MAAA,eAAA,CAAgB,YAAY,CAAA,CAAA;AAAA,KACvB,MAAA;AACL,MAAQ,OAAA,CAAA,IAAA;AAAA,QACN,CAAA;AAAA,CAAA,GACE,kBAAkB,OAAO,CAAA;AAAA,OAC7B,CAAA;AACA,MAAA,OAAA;AAAA,KACF;AAAA,GACF;AAEA,EAAa,YAAA,CAAA,SAAA,CAAU,GAAI,CAAA,GAAA,EAAK,KAAK,CAAA,CAAA;AACvC,CAAA;AAMgB,SAAA,MAAA,CACd,KACA,YACiB,EAAA;AACjB,EAAA,IAAI,eAAe,eAAgB,EAAA,CAAA;AAEnC,EAAA,IAAI,CAAC,YAAc,EAAA;AACjB,IAAQ,OAAA,CAAA,IAAA;AAAA,MACN,CAAA;AAAA,CAAA,GACE,kBAAkB,MAAM,CAAA;AAAA,KAC5B,CAAA;AACA,IAAA,OAAA;AAAA,GACF;AAEA,EAAM,MAAA,OAAA,uBAAc,OAAQ,EAAA,CAAA;AAE5B,EAAI,IAAA,KAAA,CAAA;AAEJ,EAAG,GAAA;AACD,IAAQ,KAAA,GAAA,YAAA,CAAc,SAAU,CAAA,GAAA,CAAI,GAAG,CAAA,CAAA;AACvC,IAAA,OAAA,CAAQ,IAAI,YAAa,CAAA,CAAA;AACzB,IAAA,YAAA,GAAe,YAAc,CAAA,MAAA,CAAA;AAAA,WACtB,YAAgB,IAAA,KAAA,KAAU,UAAa,CAAC,OAAA,CAAQ,IAAI,YAAY,CAAA,EAAA;AAEzE,EAAI,IAAA,KAAA,KAAU,KAAa,CAAA,IAAA,YAAA,KAAiB,KAAW,CAAA,EAAA;AACrD,IAAA,KAAA,GAAQ,UAAW,CAAA,YAAY,CAAI,GAAA,YAAA,EAAiB,GAAA,YAAA,CAAA;AAAA,GACtD;AAEA,EAAO,OAAA,KAAA,CAAA;AACT,CAAA;AAMO,SAAS,mBAAsB,GAAA;AACpC,EAAO,OAAA,CAAC,CAAC,eAAgB,EAAA,CAAA;AAC3B;;AC5CgB,SAAA,aAAA,CACd,cACA,WACA,EAAA;AACA,EAAM,MAAA,iBAAA,GACJ,OAAO,YAAiB,KAAA,QAAA,IAAY,CAAC,WACjC,GAAA,CAAA,EAAG,YAAY,CACf,OAAA,CAAA,GAAA,WAAA,CAAA;AAEN,EAAM,MAAA,YAAA,GAA6B,OAAO,iBAAiB,CAAA,CAAA;AAQ3D,EAAM,MAAA,aAAA,GAAgB,CAGpB,QACwD,KAAA;AACxD,IAAM,MAAA,OAAA,GAAU,MAAO,CAAA,YAAA,EAAc,QAAQ,CAAA,CAAA;AAC7C,IAAA,IAAI,SAAgB,OAAA,OAAA,CAAA;AAEpB,IAAI,IAAA,OAAA,KAAY,MAAa,OAAA,OAAA,CAAA;AAE7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,eAAe,YAAa,CAAA,QAAA,EAAU,CAAA,kCAAA,EACpC,MAAM,OAAQ,CAAA,YAAY,CACtB,GAAA,CAAA,gCAAA,EAAmC,aAAa,IAAK,CAAA,IAAI,CAAC,CAC1D,CAAA,GAAA,CAAA,EAAA,EAAK,YAAY,CACvB,EAAA,CAAA,CAAA,CAAA;AAAA,KACF,CAAA;AAAA,GACF,CAAA;AAEA,EAAM,MAAA,cAAA,GAAiB,CAAC,YAA+B,KAAA;AACrD,IAAA,OAAA,CAAQ,cAAc,YAAY,CAAA,CAAA;AAClC,IAAO,OAAA,YAAA,CAAA;AAAA,GACT,CAAA;AAEA,EAAO,OAAA,CAAC,eAAe,cAAc,CAAA,CAAA;AACvC;;AC7CO,SAAS,eAAe,EAAgB,EAAA;AAC7C,EAAA,MAAM,cAAc,eAAgB,EAAA,CAAA;AAEpC,EAAO,MAAA,CAAA,EAAA;AAAA,IACL,WAAA;AAAA,IACA,iFAAA,GACE,kBAAkB,cAAc,CAAA;AAAA,GACpC,CAAA;AAEA,EAAY,WAAA,CAAA,QAAA,CAAS,KAAK,EAAE,CAAA,CAAA;AAC9B;;ACRgB,SAAA,WAAA,CAAmC,EAAO,EAAA,QAAA,GAAW,KAAU,EAAA;AAC7E,EAAA,OAAO,YAAwB,IAAa,EAAA;AAC1C,IAAM,MAAA,KAAA,GAAQ,YAAY,QAAQ,CAAA,CAAA;AAClC,IAAA,OAAO,MAAM,GAAI,CAAA,EAAA,CAAG,KAAK,IAAM,EAAA,GAAG,IAAI,CAAC,CAAA,CAAA;AAAA,GACzC,CAAA;AACF,CAAA;AAOgB,SAAA,cAAA,CAAwB,EAAa,EAAA,QAAA,GAAW,KAAU,EAAA;AACxE,EAAO,OAAA,WAAA,CAAY,EAAI,EAAA,QAAQ,CAAE,EAAA,CAAA;AACnC,CAAA;AAkBO,SAAS,YAAe,EAAsB,EAAA;AACnD,EAAA,MAAM,cAAc,eAAgB,EAAA,CAAA;AAEpC,EAAO,MAAA,CAAA,EAAA;AAAA,IACL,WAAA;AAAA,IACA,8EAAA;AAAA,GACF,CAAA;AAEA,EAAO,OAAA,MAAM,WAAY,CAAA,GAAA,CAAI,EAAE,CAAA,CAAA;AACjC;;;;"}
package/package.json CHANGED
@@ -1,8 +1,17 @@
1
1
  {
2
2
  "name": "@andrew_l/context",
3
- "version": "0.0.1",
3
+ "version": "0.2.4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
+ "keywords": [
7
+ "async",
8
+ "context",
9
+ "local",
10
+ "storage",
11
+ "provide",
12
+ "inject",
13
+ "hooks"
14
+ ],
6
15
  "repository": {
7
16
  "type": "git",
8
17
  "url": "git+https://github.com/men232/toolkit.git",
@@ -20,13 +29,13 @@
20
29
  "dist"
21
30
  ],
22
31
  "devDependencies": {
23
- "unbuild": "3.0.0-rc.11",
24
32
  "@types/node": "^20.16.10",
25
33
  "typescript": "~5.6.2",
34
+ "unbuild": "3.0.0-rc.11",
26
35
  "vitest": "^2.1.3"
27
36
  },
28
37
  "dependencies": {
29
- "@andrew_l/toolkit": "0.0.1"
38
+ "@andrew_l/toolkit": "0.2.4"
30
39
  },
31
40
  "scripts": {
32
41
  "build": "unbuild",