UNPKG

@andrew_l/context

Version:

Like composition api but for Node.

1 lines 15.3 kB
{"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, noop } 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\nimport('node:async_hooks')\n .then(r => {\n ALS = new r.AsyncLocalStorage<Scope | null>();\n })\n .catch(noop);\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":["noop","isPromise","catchError","result","captureStackTrace","isFunction","assert"],"mappings":"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"}