UNPKG

contexify

Version:

A TypeScript library providing a powerful dependency injection container with context-based IoC capabilities, inspired by LoopBack's Context system.

231 lines 6.59 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); import { EventEmitter } from "events"; import { asResolutionOptions, ResolutionSession } from "../resolution/resolution-session.js"; import createDebugger from "../utils/debug.js"; import { isPromiseLike, resolveList } from "../utils/value-promise.js"; const debug = createDebugger("contexify:view"); class ContextView extends EventEmitter { static { __name(this, "ContextView"); } context; filter; comparator; resolutionOptions; /** * An array of cached bindings that matches the binding filter */ _cachedBindings; /** * A map of cached values by binding */ _cachedValues; _subscription; /** * Create a context view * @param context - Context object to watch * @param filter - Binding filter to match bindings of interest * @param comparator - Comparator to sort the matched bindings */ constructor(context, filter, comparator, resolutionOptions) { super(), this.context = context, this.filter = filter, this.comparator = comparator, this.resolutionOptions = resolutionOptions; } /** * Update the cached values keyed by binding * @param values - An array of resolved values */ updateCachedValues(values) { if (this._cachedBindings == null) return void 0; this._cachedValues = /* @__PURE__ */ new Map(); for (let i = 0; i < this._cachedBindings?.length; i++) { this._cachedValues.set(this._cachedBindings[i], values[i]); } return this._cachedValues; } /** * Get an array of cached values */ getCachedValues() { return Array.from(this._cachedValues?.values() ?? []); } /** * Start listening events from the context */ open() { if (debug.enabled) { debug("Start listening on changes of context %s", this.context.name); } if (this.context.isSubscribed(this)) { return this._subscription; } this._subscription = this.context.subscribe(this); return this._subscription; } /** * Stop listening events from the context */ close() { if (debug.enabled) { debug("Stop listening on changes of context %s", this.context.name); } if (!this._subscription || this._subscription.closed) return; this._subscription.unsubscribe(); this._subscription = void 0; this.emit("close"); } /** * Get the list of matched bindings. If they are not cached, it tries to find * them from the context. */ get bindings() { if (debug.enabled) { debug("Reading bindings"); } if (this._cachedBindings == null) { this._cachedBindings = this.findBindings(); } return this._cachedBindings; } /** * Find matching bindings and refresh the cache */ findBindings() { if (debug.enabled) { debug("Finding matching bindings"); } const found = this.context.find(this.filter); if (typeof this.comparator === "function") { found.sort(this.comparator); } if (debug.enabled) { debug("Bindings found", found.map((b) => b.key)); } return found; } /** * Listen on `bind` or `unbind` and invalidate the cache */ observe(event, binding, context) { const ctxEvent = { context, binding, type: event }; if (debug.enabled) { debug("Observed event %s %s %s", event, binding.key, context.name); } if (event === "unbind") { const cachedValue = this._cachedValues?.get(binding); this.emit(event, { ...ctxEvent, cachedValue }); } else { this.emit(event, ctxEvent); } this.refresh(); } /** * Refresh the view by invalidating its cache */ refresh() { if (debug.enabled) { debug("Refreshing the view by invalidating cache"); } this._cachedBindings = void 0; this._cachedValues = void 0; this.emit("refresh"); } /** * Resolve values for the matching bindings * @param session - Resolution session */ resolve(session) { if (debug.enabled) { debug("Resolving values"); } if (this._cachedValues != null) { return this.getCachedValues(); } const bindings = this.bindings; let result = resolveList(bindings, (b) => { const options = { ...this.resolutionOptions, ...asResolutionOptions(session) }; options.session = void 0; return b.getValue(this.context, options); }); if (isPromiseLike(result)) { result = result.then((values) => { const list = values.filter((v) => v != null); this.updateCachedValues(list); this.emit("resolve", list); return list; }); } else { const list = result = result.filter((v) => v != null); this.updateCachedValues(list); this.emit("resolve", list); } return result; } /** * Get the list of resolved values. If they are not cached, it tries to find * and resolve them. */ async values(session) { if (debug.enabled) { debug("Reading values"); } await new Promise((resolve) => { process.nextTick(() => resolve()); }); if (this._cachedValues == null) { return this.resolve(session); } return this.getCachedValues(); } /** * As a `Getter` function */ asGetter(session) { return () => this.values(session); } /** * Get the single value */ async singleValue(session) { const values = await this.values(session); if (values.length === 0) return void 0; if (values.length === 1) return values[0]; throw new Error("The ContextView has more than one value. Use values() to access them."); } // eslint-disable-next-line @typescript-eslint/no-explicit-any on(event, listener) { return super.on(event, listener); } // eslint-disable-next-line @typescript-eslint/no-explicit-any once(event, listener) { return super.once(event, listener); } } function createViewGetter(ctx, bindingFilter, bindingComparatorOrSession, session) { let bindingComparator; if (typeof bindingComparatorOrSession === "function") { bindingComparator = bindingComparatorOrSession; } else if (bindingComparatorOrSession instanceof ResolutionSession) { session = bindingComparatorOrSession; } const options = asResolutionOptions(session); const view = new ContextView(ctx, bindingFilter, bindingComparator, options); view.open(); return view.asGetter(options); } __name(createViewGetter, "createViewGetter"); export { ContextView, createViewGetter }; //# sourceMappingURL=context-view.js.map