UNPKG

@andrew_l/context

Version:

Like composition api but for Node.

206 lines (198 loc) 5.15 kB
'use strict'; const toolkit = require('@andrew_l/toolkit'); let idSec = 0; let ALS; let currentScope = null; import('node:async_hooks').then((r) => { ALS = new r.AsyncLocalStorage(); }).catch(toolkit.noop); class Scope { constructor(detached = false) { this.detached = detached; this.id = ++idSec; if (!detached) { this.parent = getCurrentScope(); } } /** * @internal */ id; /** * @internal */ providers = /* @__PURE__ */ new Map(); /** * @internal */ parent = null; /** * @internal */ cleanups = []; /** * @internal */ _activeRuns = 0; run(fn) { this._activeRuns++; const onComplete = ([err, result]) => { this._activeRuns--; if (this._activeRuns === 0) { this.stop(); } if (err) { throw err; } return result; }; const r = runInScope(this, fn); if (toolkit.isPromise(r)) { return r.then(onComplete); } return onComplete(r); } stop() { for (let i = 0, l = this.cleanups.length; i < l; i++) { this.cleanups[i](); } this.cleanups.length = 0; } get active() { return this._activeRuns > 0; } } function createScope(detached) { return new Scope(detached); } function getCurrentScope() { if (ALS) { return ALS.getStore() ?? null; } return currentScope; } function setCurrentScope(scope) { if (ALS) { ALS.enterWith(scope); } else { currentScope = scope; } } function runInScope(scope, fn) { if (ALS) { return toolkit.catchError(() => ALS.run(scope, fn)); } const prevScope = getCurrentScope(); const onComplete = ([err, result2]) => { if (prevScope) { setCurrentScope(prevScope); } else { unsetCurrentScope(); } return [err, result2]; }; const result = toolkit.catchError(fn); if (toolkit.isPromise(result)) { return result.then(onComplete); } return onComplete(result); } const unsetCurrentScope = () => { if (ALS) { ALS.enterWith(null); } else { currentScope = null; } }; function provide(key, value, enterWith) { let currentScope = getCurrentScope(); if (!currentScope) { if (enterWith) { currentScope = new Scope(); setCurrentScope(currentScope); } else { console.warn( `provide() is called when there is no active scope to be associated with. ` + toolkit.captureStackTrace(provide) ); return; } } currentScope.providers.set(key, value); } function inject(key, defaultValue) { let currentScope = getCurrentScope(); if (!currentScope) { console.warn( `inject() is called when there is no active scope to be associated with. ` + toolkit.captureStackTrace(inject) ); return; } const handled = /* @__PURE__ */ new WeakSet(); let value; do { value = currentScope.providers.get(key); handled.add(currentScope); currentScope = currentScope.parent; } while (currentScope && value === void 0 && !handled.has(currentScope)); if (value === void 0 && defaultValue !== void 0) { value = toolkit.isFunction(defaultValue) ? defaultValue() : defaultValue; } return value; } function hasInjectionContext() { return !!getCurrentScope(); } function createContext(providerName, contextName) { const symbolDescription = typeof providerName === "string" && !contextName ? `${providerName}Context` : contextName; const injectionKey = Symbol(symbolDescription); const injectContext = (fallback) => { const context = inject(injectionKey, fallback); if (context) return context; if (context === null) return context; throw new Error( `Injection \`${injectionKey.toString()}\` not found. Must be used within ${Array.isArray(providerName) ? `one of the following providers: ${providerName.join(", ")}` : `\`${providerName}\``}` ); }; const provideContext = (contextValue) => { provide(injectionKey, contextValue); return contextValue; }; return [injectContext, provideContext]; } function onScopeDispose(fn) { const activeScope = getCurrentScope(); toolkit.assert.ok( activeScope, "onScopeDispose() is called when there is no active scope to be associated with." + toolkit.captureStackTrace(onScopeDispose) ); activeScope.cleanups.push(fn); } function withContext(fn, detached = false) { return function(...args) { const scope = createScope(detached); return scope.run(fn.bind(this, ...args)); }; } function runWithContext(fn, isolated = false) { return withContext(fn, isolated)(); } function bindContext(fn) { const activeScope = getCurrentScope(); toolkit.assert.ok( activeScope, "bindContext() is called when there is no active scope to be associated with." ); return () => activeScope.run(fn); } exports.bindContext = bindContext; exports.createContext = createContext; exports.getCurrentScope = getCurrentScope; exports.hasInjectionContext = hasInjectionContext; exports.inject = inject; exports.onScopeDispose = onScopeDispose; exports.provide = provide; exports.runWithContext = runWithContext; exports.withContext = withContext; //# sourceMappingURL=index.cjs.map