contexify
Version:
A TypeScript library providing a powerful dependency injection container with context-based IoC capabilities, inspired by LoopBack's Context system.
352 lines • 14 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
import { DecoratorFactory, MetadataAccessor, MetadataInspector, ParameterDecoratorFactory, PropertyDecoratorFactory, Reflector } from "metarize";
import { Binding } from "../binding/binding.js";
import { filterByTag, isBindingAddress, isBindingTagFilter } from "../binding/binding-filter.js";
import { ContextView, createViewGetter } from "../context/context-view.js";
import { ResolutionSession } from "../resolution/resolution-session.js";
import createDebugger from "../utils/debug.js";
const INJECT_PARAMETERS_KEY = MetadataAccessor.create("inject:parameters");
const INJECT_PROPERTIES_KEY = MetadataAccessor.create("inject:properties");
const INJECT_METHODS_KEY = MetadataAccessor.create("inject:methods");
const debug = createDebugger("contexify:inject");
function inject(bindingSelector, metadata, resolve) {
if (typeof bindingSelector === "function" && !resolve) {
resolve = resolveValuesByFilter;
}
const injectionMetadata = Object.assign({
decorator: "@inject"
}, metadata);
if (injectionMetadata.bindingComparator && !resolve) {
throw new Error("Binding comparator is only allowed with a binding filter");
}
if (!bindingSelector && typeof resolve !== "function") {
throw new Error("A non-empty binding selector or resolve function is required for @inject");
}
return /* @__PURE__ */ __name(function markParameterOrPropertyAsInjected(target, member, methodDescriptorOrParameterIndex) {
if (typeof methodDescriptorOrParameterIndex === "number") {
const paramDecorator = ParameterDecoratorFactory.createDecorator(
INJECT_PARAMETERS_KEY,
{
target,
member,
methodDescriptorOrParameterIndex,
bindingSelector,
metadata: injectionMetadata,
resolve
},
// Do not deep clone the spec as only metadata is mutable and it's
// shallowly cloned
{
cloneInputSpec: false,
decoratorName: injectionMetadata.decorator
}
);
paramDecorator(target, member, methodDescriptorOrParameterIndex);
} else if (member) {
if (target instanceof Function) {
throw new Error("@inject is not supported for a static property: " + DecoratorFactory.getTargetName(target, member));
}
if (methodDescriptorOrParameterIndex) {
throw new Error("@inject cannot be used on a method: " + DecoratorFactory.getTargetName(target, member, methodDescriptorOrParameterIndex));
}
const propDecorator = PropertyDecoratorFactory.createDecorator(
INJECT_PROPERTIES_KEY,
{
target,
member,
methodDescriptorOrParameterIndex,
bindingSelector,
metadata: injectionMetadata,
resolve
},
// Do not deep clone the spec as only metadata is mutable and it's
// shallowly cloned
{
cloneInputSpec: false,
decoratorName: injectionMetadata.decorator
}
);
propDecorator(target, member);
} else {
throw new Error("@inject can only be used on a property or a method parameter");
}
}, "markParameterOrPropertyAsInjected");
}
__name(inject, "inject");
(function(Getter2) {
function fromValue(value) {
return () => Promise.resolve(value);
}
__name(fromValue, "fromValue");
Getter2.fromValue = fromValue;
})(Getter || (Getter = {}));
(function(inject2) {
inject2.getter = /* @__PURE__ */ __name(function injectGetter(bindingSelector, metadata) {
metadata = Object.assign({
decorator: "@inject.getter"
}, metadata);
return inject2(bindingSelector, metadata, isBindingAddress(bindingSelector) ? resolveAsGetter : resolveAsGetterByFilter);
}, "injectGetter");
inject2.setter = /* @__PURE__ */ __name(function injectSetter(bindingKey, metadata) {
metadata = Object.assign({
decorator: "@inject.setter"
}, metadata);
return inject2(bindingKey, metadata, resolveAsSetter);
}, "injectSetter");
inject2.binding = /* @__PURE__ */ __name(function injectBinding(bindingKey, metadata) {
metadata = Object.assign({
decorator: "@inject.binding"
}, metadata);
return inject2(bindingKey ?? "", metadata, resolveAsBinding);
}, "injectBinding");
inject2.tag = /* @__PURE__ */ __name(function injectByTag(bindingTag, metadata) {
metadata = Object.assign({
decorator: "@inject.tag",
tag: bindingTag
}, metadata);
return inject2(filterByTag(bindingTag), metadata);
}, "injectByTag");
inject2.view = /* @__PURE__ */ __name(function injectContextView(bindingFilter, metadata) {
metadata = Object.assign({
decorator: "@inject.view"
}, metadata);
return inject2(bindingFilter, metadata, resolveAsContextView);
}, "injectContextView");
inject2.context = /* @__PURE__ */ __name(function injectContext() {
return inject2("", {
decorator: "@inject.context"
}, (ctx) => ctx);
}, "injectContext");
})(inject || (inject = {}));
function assertTargetType(injection, expectedType, expectedTypeName) {
const targetName = ResolutionSession.describeInjection(injection).targetName;
const targetType = inspectTargetType(injection);
if (targetType && targetType !== expectedType) {
expectedTypeName = expectedTypeName ?? expectedType.name;
throw new Error(`The type of ${targetName} (${targetType.name}) is not ${expectedTypeName}`);
}
return targetName;
}
__name(assertTargetType, "assertTargetType");
function resolveAsGetter(ctx, injection, _session) {
assertTargetType(injection, Function, "Getter function");
const bindingSelector = injection.bindingSelector;
const options = {
// We should start with a new session for `getter` resolution to avoid
// possible circular dependencies
session: void 0,
...injection.metadata
};
return /* @__PURE__ */ __name(function getter() {
return ctx.get(bindingSelector, options);
}, "getter");
}
__name(resolveAsGetter, "resolveAsGetter");
function resolveAsSetter(ctx, injection) {
const targetName = assertTargetType(injection, Function, "Setter function");
const bindingSelector = injection.bindingSelector;
if (!isBindingAddress(bindingSelector)) {
throw new Error(`@inject.setter (${targetName}) does not allow BindingFilter.`);
}
if (bindingSelector === "") {
throw new Error("Binding key is not set for @inject.setter");
}
return /* @__PURE__ */ __name(function setter(value) {
const binding = findOrCreateBindingForInjection(ctx, injection);
binding.to(value);
}, "setter");
}
__name(resolveAsSetter, "resolveAsSetter");
function resolveAsBinding(ctx, injection, session) {
const targetName = assertTargetType(injection, Binding);
const bindingSelector = injection.bindingSelector;
if (!isBindingAddress(bindingSelector)) {
throw new Error(`@inject.binding (${targetName}) does not allow BindingFilter.`);
}
return findOrCreateBindingForInjection(ctx, injection, session);
}
__name(resolveAsBinding, "resolveAsBinding");
function findOrCreateBindingForInjection(ctx, injection, session) {
if (injection.bindingSelector === "") return session?.currentBinding;
const bindingCreation = injection.metadata && injection.metadata.bindingCreation;
const binding = ctx.findOrCreateBinding(injection.bindingSelector, bindingCreation);
return binding;
}
__name(findOrCreateBindingForInjection, "findOrCreateBindingForInjection");
function shouldSkipBaseConstructorInjection(targetClass) {
const classDef = targetClass.toString();
const MAX_CLASS_DEF_LENGTH = 5e4;
if (classDef.length > MAX_CLASS_DEF_LENGTH) {
if (debug.enabled) {
debug("Class definition too long, skipping regex check for %s", targetClass.constructor?.name || "unknown");
}
return false;
}
return (
/*
* Handle class decorators that return a new constructor
* A class decorator can return a new constructor that mixes in
* additional properties/methods.
*
* @example
* ```ts
* class extends baseConstructor {
* // The constructor calls `super(...arguments)`
* constructor() {
* super(...arguments);
* }
* classProperty = 'a classProperty';
* classFunction() {
* return 'a classFunction';
* }
* };
* ```
*
* We check the following pattern:
* ```ts
* constructor() {
* super(...arguments);
* }
* ```
*/
// Use non-greedy quantifiers (*? and +?) to reduce backtracking
!classDef.match(/\s+constructor\s*?\(\s*?\)\s*?\{\s*?super\(\.\.\.arguments\)/) && /*
* Handle subclasses without explicit constructors
*
* @example
* ```ts
* class BaseClass {
* constructor(@inject('foo') protected foo: string) {}
* // ...
* }
*
* class SubClass extends BaseClass {
* // No explicit constructor is present
*
* @inject('bar')
* private bar: number;
* // ...
* };
*
*/
// Use non-greedy quantifiers and keep multiline flag
// eslint-disable-next-line no-useless-escape
classDef.match(/\s+constructor\s*?\([^)]*?\)\s*?\{/m)
);
}
__name(shouldSkipBaseConstructorInjection, "shouldSkipBaseConstructorInjection");
function describeInjectedArguments(target, method) {
method = method ?? "";
const cache = MetadataInspector.getAllMethodMetadata(INJECT_METHODS_KEY, target, {
ownMetadataOnly: true
}) ?? {};
let meta = cache[method];
if (meta) return meta;
const options = {};
if (method === "") {
if (shouldSkipBaseConstructorInjection(target)) {
options.ownMetadataOnly = true;
}
} else if (Object.hasOwn(target, method)) {
options.ownMetadataOnly = true;
}
meta = MetadataInspector.getAllParameterMetadata(INJECT_PARAMETERS_KEY, target, method, options) ?? [];
cache[method] = meta;
MetadataInspector.defineMetadata(INJECT_METHODS_KEY, cache, target);
return meta;
}
__name(describeInjectedArguments, "describeInjectedArguments");
function inspectTargetType(injection) {
if (typeof injection.methodDescriptorOrParameterIndex === "number") {
const designType = MetadataInspector.getDesignTypeForMethod(injection.target, injection.member);
return designType?.parameterTypes?.[injection.methodDescriptorOrParameterIndex];
}
return MetadataInspector.getDesignTypeForProperty(injection.target, injection.member);
}
__name(inspectTargetType, "inspectTargetType");
function resolveValuesByFilter(ctx, injection, session) {
assertTargetType(injection, Array);
const bindingFilter = injection.bindingSelector;
const view = new ContextView(ctx, bindingFilter, injection.metadata.bindingComparator);
return view.resolve(session);
}
__name(resolveValuesByFilter, "resolveValuesByFilter");
function resolveAsGetterByFilter(ctx, injection, session) {
assertTargetType(injection, Function, "Getter function");
const bindingFilter = injection.bindingSelector;
return createViewGetter(ctx, bindingFilter, injection.metadata.bindingComparator, session);
}
__name(resolveAsGetterByFilter, "resolveAsGetterByFilter");
function resolveAsContextView(ctx, injection) {
assertTargetType(injection, ContextView);
const bindingFilter = injection.bindingSelector;
const view = new ContextView(ctx, bindingFilter, injection.metadata.bindingComparator);
view.open();
return view;
}
__name(resolveAsContextView, "resolveAsContextView");
function describeInjectedProperties(target) {
const metadata = MetadataInspector.getAllPropertyMetadata(INJECT_PROPERTIES_KEY, target) ?? {};
return metadata;
}
__name(describeInjectedProperties, "describeInjectedProperties");
function inspectInjections(binding) {
const json = {};
const ctor = binding.valueConstructor ?? binding.providerConstructor;
if (ctor == null) return json;
const constructorInjections = describeInjectedArguments(ctor, "").map(inspectInjection);
if (constructorInjections.length) {
json.constructorArguments = constructorInjections;
}
const propertyInjections = describeInjectedProperties(ctor.prototype);
const properties = {};
for (const p in propertyInjections) {
properties[p] = inspectInjection(propertyInjections[p]);
}
if (Object.keys(properties).length) {
json.properties = properties;
}
return json;
}
__name(inspectInjections, "inspectInjections");
function inspectInjection(injection) {
const injectionInfo = ResolutionSession.describeInjection(injection);
const descriptor = {};
if (injectionInfo.targetName) {
descriptor.targetName = injectionInfo.targetName;
}
if (isBindingAddress(injectionInfo.bindingSelector)) {
descriptor.bindingKey = injectionInfo.bindingSelector.toString();
} else if (isBindingTagFilter(injectionInfo.bindingSelector)) {
descriptor.bindingTagPattern = JSON.parse(JSON.stringify(injectionInfo.bindingSelector.bindingTagPattern));
} else {
descriptor.bindingFilter = injectionInfo.bindingSelector?.name ?? "<function>";
}
if (injectionInfo.metadata) {
if (injectionInfo.metadata.decorator && injectionInfo.metadata.decorator !== "@inject") {
descriptor.decorator = injectionInfo.metadata.decorator;
}
if (injectionInfo.metadata.optional) {
descriptor.optional = injectionInfo.metadata.optional;
}
}
return descriptor;
}
__name(inspectInjection, "inspectInjection");
function hasInjections(cls) {
return MetadataInspector.getClassMetadata(INJECT_PARAMETERS_KEY, cls) != null || Reflector.getMetadata(INJECT_PARAMETERS_KEY.toString(), cls.prototype) != null || MetadataInspector.getAllPropertyMetadata(INJECT_PROPERTIES_KEY, cls.prototype) != null;
}
__name(hasInjections, "hasInjections");
var Getter;
export {
Getter,
assertTargetType,
describeInjectedArguments,
describeInjectedProperties,
hasInjections,
inject,
inspectInjections,
inspectTargetType
};
//# sourceMappingURL=inject.js.map