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
JavaScript
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