UNPKG

@andrew_l/context

Version:

Like composition api but for Node.

1 lines 15.7 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 } 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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"}