contexify
Version:
A TypeScript library providing a powerful dependency injection container with context-based IoC capabilities, inspired by LoopBack's Context system.
212 lines • 8.46 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
import assert from "assert";
import { ClassDecoratorFactory, DecoratorFactory, MetadataAccessor, MetadataInspector, MethodDecoratorFactory } from "metarize";
import { injectable } from "../binding/binding-decorator.js";
import { createBindingFromClass, isProviderClass } from "../binding/binding-inspector.js";
import { BindingKey } from "../binding/binding-key.js";
import { sortBindingsByPhase } from "../binding/binding-sorter.js";
import createDebugger from "../utils/debug.js";
import { ContextBindings, ContextTags, GLOBAL_INTERCEPTOR_NAMESPACE, LOCAL_INTERCEPTOR_NAMESPACE } from "../utils/keys.js";
import { tryWithFinally } from "../utils/value-promise.js";
import { invokeInterceptors } from "./interceptor-chain.js";
import { InvocationContext } from "./invocation.js";
const debug = createDebugger("contexify:interceptor");
class InterceptedInvocationContext extends InvocationContext {
static {
__name(this, "InterceptedInvocationContext");
}
/**
* Discover all binding keys for global interceptors (tagged by
* ContextTags.GLOBAL_INTERCEPTOR)
*/
getGlobalInterceptorBindingKeys() {
let bindings = this.findByTag(ContextTags.GLOBAL_INTERCEPTOR);
bindings = bindings.filter((binding) => (
// Only include interceptors that match the source type of the invocation
this.applicableTo(binding)
));
this.sortGlobalInterceptorBindings(bindings);
const keys = bindings.map((b) => b.key);
if (debug.enabled) {
debug("Global interceptor binding keys:", keys);
}
return keys;
}
/**
* Check if the binding for a global interceptor matches the source type
* of the invocation
* @param binding - Binding
*/
applicableTo(binding) {
const sourceType = this.source?.type;
if (sourceType == null) return true;
const allowedSource = binding.tagMap[ContextTags.GLOBAL_INTERCEPTOR_SOURCE];
return (
// No tag, always apply
allowedSource == null || // source matched
allowedSource === sourceType || // source included in the string[]
Array.isArray(allowedSource) && allowedSource.includes(sourceType)
);
}
/**
* Sort global interceptor bindings by `globalInterceptorGroup` tags
* @param bindings - An array of global interceptor bindings
*/
sortGlobalInterceptorBindings(bindings) {
const orderedGroups = this.getSync(ContextBindings.GLOBAL_INTERCEPTOR_ORDERED_GROUPS, {
optional: true
}) ?? [];
return sortBindingsByPhase(bindings, ContextTags.GLOBAL_INTERCEPTOR_GROUP, orderedGroups);
}
/**
* Load all interceptors for the given invocation context. It adds
* interceptors from possibly three sources:
* 1. method level `@intercept`
* 2. class level `@intercept`
* 3. global interceptors discovered in the context
*/
loadInterceptors() {
let interceptors = MetadataInspector.getMethodMetadata(INTERCEPT_METHOD_KEY, this.target, this.methodName) ?? [];
const targetClass = typeof this.target === "function" ? this.target : this.target.constructor;
const classInterceptors = MetadataInspector.getClassMetadata(INTERCEPT_CLASS_KEY, targetClass) ?? [];
interceptors = mergeInterceptors(classInterceptors, interceptors);
const globalInterceptors = this.getGlobalInterceptorBindingKeys();
interceptors = mergeInterceptors(globalInterceptors, interceptors);
if (debug.enabled) {
debug("Interceptors for %s", this.targetName, interceptors);
}
return interceptors;
}
}
function asGlobalInterceptor(group) {
return (binding) => {
binding.tag(ContextTags.GLOBAL_INTERCEPTOR).tag({
[ContextTags.NAMESPACE]: GLOBAL_INTERCEPTOR_NAMESPACE
});
if (group) binding.tag({
[ContextTags.GLOBAL_INTERCEPTOR_GROUP]: group
});
};
}
__name(asGlobalInterceptor, "asGlobalInterceptor");
function globalInterceptor(group, ...specs) {
return injectable(asGlobalInterceptor(group), ...specs);
}
__name(globalInterceptor, "globalInterceptor");
const INTERCEPT_METHOD_KEY = MetadataAccessor.create("intercept:method");
function mergeInterceptors(interceptorsFromSpec, existingInterceptors) {
const interceptorsToApply = new Set(interceptorsFromSpec);
const appliedInterceptors = new Set(existingInterceptors);
for (const i of interceptorsToApply) {
if (appliedInterceptors.has(i)) {
interceptorsToApply.delete(i);
}
}
for (const i of appliedInterceptors) {
interceptorsToApply.add(i);
}
return Array.from(interceptorsToApply);
}
__name(mergeInterceptors, "mergeInterceptors");
const INTERCEPT_CLASS_KEY = MetadataAccessor.create("intercept:class");
let InterceptClassDecoratorFactory = class InterceptClassDecoratorFactory2 extends ClassDecoratorFactory {
static {
__name(this, "InterceptClassDecoratorFactory");
}
mergeWithOwn(ownMetadata, _target) {
ownMetadata = ownMetadata || [];
return mergeInterceptors(this.spec, ownMetadata);
}
};
let InterceptMethodDecoratorFactory = class InterceptMethodDecoratorFactory2 extends MethodDecoratorFactory {
static {
__name(this, "InterceptMethodDecoratorFactory");
}
mergeWithOwn(ownMetadata, _target, methodName, _methodDescriptor) {
ownMetadata = ownMetadata || {};
const interceptors = ownMetadata[methodName] || [];
ownMetadata[methodName] = mergeInterceptors(this.spec, interceptors);
return ownMetadata;
}
};
function intercept(...interceptorOrKeys) {
return /* @__PURE__ */ __name(function interceptDecoratorForClassOrMethod(target, method, methodDescriptor) {
if (method && methodDescriptor) {
return InterceptMethodDecoratorFactory.createDecorator(INTERCEPT_METHOD_KEY, interceptorOrKeys, {
decoratorName: "@intercept"
})(target, method, methodDescriptor);
}
if (typeof target === "function" && !method && !methodDescriptor) {
return InterceptClassDecoratorFactory.createDecorator(INTERCEPT_CLASS_KEY, interceptorOrKeys, {
decoratorName: "@intercept"
})(target);
}
throw new Error("@intercept cannot be used on a property: " + DecoratorFactory.getTargetName(target, method, methodDescriptor));
}, "interceptDecoratorForClassOrMethod");
}
__name(intercept, "intercept");
function invokeMethodWithInterceptors(context, target, methodName, args, options = {}) {
assert(!options.skipInterceptors, "skipInterceptors is not allowed");
const invocationCtx = new InterceptedInvocationContext(context, target, methodName, args, options.source);
invocationCtx.assertMethodExists();
return tryWithFinally(() => {
const interceptors = invocationCtx.loadInterceptors();
const targetMethodInvoker = /* @__PURE__ */ __name(() => invocationCtx.invokeTargetMethod(options), "targetMethodInvoker");
interceptors.push(targetMethodInvoker);
return invokeInterceptors(invocationCtx, interceptors);
}, () => invocationCtx.close());
}
__name(invokeMethodWithInterceptors, "invokeMethodWithInterceptors");
function registerInterceptor(ctx, interceptor, options = {}) {
let { global } = options;
const { group, source } = options;
if (group != null || source != null) {
global = global !== false;
}
const namespace = options.namespace ?? options.defaultNamespace ?? global ? GLOBAL_INTERCEPTOR_NAMESPACE : LOCAL_INTERCEPTOR_NAMESPACE;
let binding;
if (isProviderClass(interceptor)) {
binding = createBindingFromClass(interceptor, {
defaultNamespace: namespace,
...options
});
if (binding.tagMap[ContextTags.GLOBAL_INTERCEPTOR]) {
global = true;
}
ctx.add(binding);
} else {
let key = options.key;
if (!key) {
const name = options.name ?? interceptor.name;
if (!name) {
key = BindingKey.generate(namespace).key;
} else {
key = `${namespace}.${name}`;
}
}
binding = ctx.bind(key).to(interceptor);
}
if (global) {
binding.apply(asGlobalInterceptor(group));
if (source) {
binding.tag({
[ContextTags.GLOBAL_INTERCEPTOR_SOURCE]: source
});
}
}
return binding;
}
__name(registerInterceptor, "registerInterceptor");
export {
INTERCEPT_CLASS_KEY,
INTERCEPT_METHOD_KEY,
InterceptedInvocationContext,
asGlobalInterceptor,
globalInterceptor,
intercept,
invokeMethodWithInterceptors,
mergeInterceptors,
registerInterceptor
};
//# sourceMappingURL=interceptor.js.map