UNPKG

@inversifyjs/core

Version:

InversifyJs core package

362 lines 16.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PlanResultCacheService = void 0; const BindingType_1 = require("../../binding/models/BindingType"); const WeakList_1 = require("../../common/models/WeakList"); const addRootServiceNodeBindingIfContextFree_1 = require("../actions/addRootServiceNodeBindingIfContextFree"); const addServiceNodeBindingIfContextFree_1 = require("../actions/addServiceNodeBindingIfContextFree"); const removeRootServiceNodeBindingIfContextFree_1 = require("../actions/removeRootServiceNodeBindingIfContextFree"); const removeServiceNodeBindingIfContextFree_1 = require("../actions/removeServiceNodeBindingIfContextFree"); const CacheBindingInvalidationKind_1 = require("../models/CacheBindingInvalidationKind"); const LazyPlanServiceNode_1 = require("../models/LazyPlanServiceNode"); const CHAINED_MASK = 0x4; const IS_MULTIPLE_MASK = 0x2; const OPTIONAL_MASK = 0x1; const MAP_ARRAY_LENGTH = 0x8; /** * Service to cache plans. * * This class is used to cache plans and to notify PlanService subscribers when the cache is cleared. * The cache should be cleared when a new binding is registered or when a binding is unregistered. * * Subscribers are supposed to be plan services from child containers. * * Ancestor binding constraints are the reason to avoid reusing plans from plan children nodes. */ class PlanResultCacheService { #serviceIdToNonCachedServiceNodeMapMap; #serviceIdToValuePlanMap; #namedServiceIdToValuePlanMap; #namedTaggedServiceIdToValuePlanMap; #taggedServiceIdToValuePlanMap; #subscribers; constructor() { this.#serviceIdToNonCachedServiceNodeMapMap = new Map(); this.#serviceIdToValuePlanMap = this.#buildInitializedMapArray(); this.#namedServiceIdToValuePlanMap = this.#buildInitializedMapArray(); this.#namedTaggedServiceIdToValuePlanMap = this.#buildInitializedMapArray(); this.#taggedServiceIdToValuePlanMap = this.#buildInitializedMapArray(); this.#subscribers = new WeakList_1.WeakList(); } clearCache() { for (const map of this.#getMaps()) { map.clear(); } for (const subscriber of this.#subscribers) { subscriber.clearCache(); } } get(options) { if (options.name === undefined) { if (options.tag === undefined) { return this.#getMapFromMapArray(this.#serviceIdToValuePlanMap, options).get(options.serviceIdentifier); } else { return this.#getMapFromMapArray(this.#taggedServiceIdToValuePlanMap, options) .get(options.serviceIdentifier) ?.get(options.tag.key) ?.get(options.tag.value); } } else { if (options.tag === undefined) { return this.#getMapFromMapArray(this.#namedServiceIdToValuePlanMap, options) .get(options.serviceIdentifier) ?.get(options.name); } else { return this.#getMapFromMapArray(this.#namedTaggedServiceIdToValuePlanMap, options) .get(options.serviceIdentifier) ?.get(options.name) ?.get(options.tag.key) ?.get(options.tag.value); } } } invalidateServiceBinding(invalidation) { this.#invalidateServiceMap(invalidation); this.#invalidateNamedServiceMap(invalidation); this.#invalidateNamedTaggedServiceMap(invalidation); this.#invalidateTaggedServiceMap(invalidation); this.#invalidateNonCachedServiceNodeSetMap(invalidation); for (const subscriber of this.#subscribers) { subscriber.invalidateServiceBinding(invalidation); } } set(options, planResult) { if (options.name === undefined) { if (options.tag === undefined) { this.#getMapFromMapArray(this.#serviceIdToValuePlanMap, options).set(options.serviceIdentifier, planResult); } else { this.#getOrBuildMapValueFromMapMap(this.#getOrBuildMapValueFromMapMap(this.#getMapFromMapArray(this.#taggedServiceIdToValuePlanMap, options), options.serviceIdentifier), options.tag.key).set(options.tag.value, planResult); } } else { if (options.tag === undefined) { this.#getOrBuildMapValueFromMapMap(this.#getMapFromMapArray(this.#namedServiceIdToValuePlanMap, options), options.serviceIdentifier).set(options.name, planResult); } else { this.#getOrBuildMapValueFromMapMap(this.#getOrBuildMapValueFromMapMap(this.#getOrBuildMapValueFromMapMap(this.#getMapFromMapArray(this.#namedTaggedServiceIdToValuePlanMap, options), options.serviceIdentifier), options.name), options.tag.key).set(options.tag.value, planResult); } } } setNonCachedServiceNode(node, context) { let nonCachedMap = this.#serviceIdToNonCachedServiceNodeMapMap.get(node.serviceIdentifier); if (nonCachedMap === undefined) { nonCachedMap = new Map(); this.#serviceIdToNonCachedServiceNodeMapMap.set(node.serviceIdentifier, nonCachedMap); } nonCachedMap.set(node, context); } subscribe(subscriber) { this.#subscribers.push(subscriber); } #buildInitializedMapArray() { const mapArray = new Array(MAP_ARRAY_LENGTH); for (let i = 0; i < mapArray.length; ++i) { mapArray[i] = new Map(); } return mapArray; } #buildUpdatePlanParams(invalidation, index, name, tag) { const isMultiple = (index & IS_MULTIPLE_MASK) !== 0; let planParamsConstraint; if (isMultiple) { const isChained = (index & IS_MULTIPLE_MASK & CHAINED_MASK) !== 0; planParamsConstraint = { chained: isChained, isMultiple, serviceIdentifier: invalidation.binding.serviceIdentifier, }; } else { planParamsConstraint = { isMultiple, serviceIdentifier: invalidation.binding.serviceIdentifier, }; } const isOptional = (index & OPTIONAL_MASK) !== 0; if (isOptional) { planParamsConstraint.isOptional = true; } if (name !== undefined) { planParamsConstraint.name = name; } if (tag !== undefined) { planParamsConstraint.tag = tag; } return { autobindOptions: undefined, operations: invalidation.operations, rootConstraints: planParamsConstraint, servicesBranch: [], }; } #getOrBuildMapValueFromMapMap(map, key) { let valueMap = map.get(key); if (valueMap === undefined) { valueMap = new Map(); map.set(key, valueMap); } return valueMap; } #getMapFromMapArray(mapArray, options) { return mapArray[this.#getMapArrayIndex(options)]; } #getMaps() { return [ this.#serviceIdToNonCachedServiceNodeMapMap, ...this.#serviceIdToValuePlanMap, ...this.#namedServiceIdToValuePlanMap, ...this.#namedTaggedServiceIdToValuePlanMap, ...this.#taggedServiceIdToValuePlanMap, ]; } #getMapArrayIndex(options) { if (options.isMultiple) { return ((options.chained ? CHAINED_MASK : 0) | (options.optional ? OPTIONAL_MASK : 0) | IS_MULTIPLE_MASK); } else { return options.optional ? OPTIONAL_MASK : 0; } } #invalidateNamedServiceMap(invalidation) { for (const [index, map] of this.#namedServiceIdToValuePlanMap.entries()) { const servicePlans = map.get(invalidation.binding.serviceIdentifier); if (servicePlans !== undefined) { for (const [name, servicePlan] of servicePlans.entries()) { this.#updatePlan(invalidation, servicePlan, index, name, undefined); } } } } #invalidateNamedTaggedServiceMap(invalidation) { for (const [index, map,] of this.#namedTaggedServiceIdToValuePlanMap.entries()) { const servicePlanMapMapMap = map.get(invalidation.binding.serviceIdentifier); if (servicePlanMapMapMap !== undefined) { for (const [name, servicePlanMapMap,] of servicePlanMapMapMap.entries()) { for (const [tag, servicePlanMap] of servicePlanMapMap.entries()) { for (const [tagValue, servicePlan] of servicePlanMap.entries()) { this.#updatePlan(invalidation, servicePlan, index, name, { key: tag, value: tagValue, }); } } } } } } #invalidateNonCachePlanBindingNodeDescendents(planBindingNode) { switch (planBindingNode.binding.type) { case BindingType_1.bindingTypeValues.ServiceRedirection: for (const redirection of planBindingNode.redirections) { this.#invalidateNonCachePlanBindingNodeDescendents(redirection); } break; case BindingType_1.bindingTypeValues.Instance: for (const constructorParam of planBindingNode .constructorParams) { if (constructorParam !== undefined) { this.#invalidateNonCachePlanServiceNode(constructorParam); } } for (const propertyParam of planBindingNode.propertyParams.values()) { this.#invalidateNonCachePlanServiceNode(propertyParam); } break; case BindingType_1.bindingTypeValues.ResolvedValue: for (const resolvedValue of planBindingNode.params) { this.#invalidateNonCachePlanServiceNode(resolvedValue); } break; default: } } #invalidateNonCachePlanServiceNode(planServiceNode) { const serviceNonCachedMap = this.#serviceIdToNonCachedServiceNodeMapMap.get(planServiceNode.serviceIdentifier); if (serviceNonCachedMap === undefined || !serviceNonCachedMap.has(planServiceNode)) { return; } serviceNonCachedMap.delete(planServiceNode); this.#invalidateNonCachePlanServiceNodeDescendents(planServiceNode); } #invalidateNonCachePlanServiceNodeDescendents(planServiceNode) { if (LazyPlanServiceNode_1.LazyPlanServiceNode.is(planServiceNode) && !planServiceNode.isExpanded()) { return; } if (planServiceNode.bindings === undefined) { return; } if (Array.isArray(planServiceNode.bindings)) { for (const binding of planServiceNode.bindings) { this.#invalidateNonCachePlanBindingNodeDescendents(binding); } } else { this.#invalidateNonCachePlanBindingNodeDescendents(planServiceNode.bindings); } } #invalidateNonCachedServiceNodeSetMap(invalidation) { const serviceNonCachedServiceNodeMap = this.#serviceIdToNonCachedServiceNodeMapMap.get(invalidation.binding.serviceIdentifier); if (serviceNonCachedServiceNodeMap !== undefined) { switch (invalidation.kind) { case CacheBindingInvalidationKind_1.CacheBindingInvalidationKind.bindingAdded: for (const [serviceNode, context] of serviceNonCachedServiceNodeMap) { const result = (0, addServiceNodeBindingIfContextFree_1.addServiceNodeBindingIfContextFree)({ autobindOptions: undefined, operations: invalidation.operations, servicesBranch: [], }, serviceNode, invalidation.binding, context.bindingConstraintsList, context.chainedBindings); if (result.isContextFreeBinding) { if (result.shouldInvalidateServiceNode && LazyPlanServiceNode_1.LazyPlanServiceNode.is(serviceNode)) { this.#invalidateNonCachePlanServiceNodeDescendents(serviceNode); serviceNode.invalidate(); } } else { this.clearCache(); } } break; case CacheBindingInvalidationKind_1.CacheBindingInvalidationKind.bindingRemoved: for (const [serviceNode, context] of serviceNonCachedServiceNodeMap) { const result = (0, removeServiceNodeBindingIfContextFree_1.removeServiceNodeBindingIfContextFree)(serviceNode, invalidation.binding, context.bindingConstraintsList, context.optionalBindings); if (result.isContextFreeBinding) { if (result.bindingNodeRemoved !== undefined) { this.#invalidateNonCachePlanBindingNodeDescendents(result.bindingNodeRemoved); } } else { this.clearCache(); } } break; } } } #invalidateServiceMap(invalidation) { for (const [index, map] of this.#serviceIdToValuePlanMap.entries()) { const servicePlan = map.get(invalidation.binding.serviceIdentifier); this.#updatePlan(invalidation, servicePlan, index, undefined, undefined); } } #invalidateTaggedServiceMap(invalidation) { for (const [index, map] of this.#taggedServiceIdToValuePlanMap.entries()) { const servicePlanMapMap = map.get(invalidation.binding.serviceIdentifier); if (servicePlanMapMap !== undefined) { for (const [tag, servicePlanMap] of servicePlanMapMap.entries()) { for (const [tagValue, servicePlan] of servicePlanMap.entries()) { this.#updatePlan(invalidation, servicePlan, index, undefined, { key: tag, value: tagValue, }); } } } } } #updatePlan(invalidation, servicePlan, index, name, tag) { if (servicePlan !== undefined && LazyPlanServiceNode_1.LazyPlanServiceNode.is(servicePlan.tree.root)) { const planParams = this.#buildUpdatePlanParams(invalidation, index, name, tag); switch (invalidation.kind) { case CacheBindingInvalidationKind_1.CacheBindingInvalidationKind.bindingAdded: { const result = (0, addRootServiceNodeBindingIfContextFree_1.addRootServiceNodeBindingIfContextFree)(planParams, servicePlan.tree.root, invalidation.binding); if (result.isContextFreeBinding) { if (result.shouldInvalidateServiceNode) { this.#invalidateNonCachePlanServiceNodeDescendents(servicePlan.tree.root); servicePlan.tree.root.invalidate(); } } else { this.clearCache(); } } break; case CacheBindingInvalidationKind_1.CacheBindingInvalidationKind.bindingRemoved: { const result = (0, removeRootServiceNodeBindingIfContextFree_1.removeRootServiceNodeBindingIfContextFree)(planParams, servicePlan.tree.root, invalidation.binding); if (result.isContextFreeBinding) { if (result.bindingNodeRemoved !== undefined) { this.#invalidateNonCachePlanBindingNodeDescendents(result.bindingNodeRemoved); } } else { this.clearCache(); } } break; } } } } exports.PlanResultCacheService = PlanResultCacheService; //# sourceMappingURL=PlanResultCacheService.js.map