@inversifyjs/core
Version:
InversifyJs core package
362 lines • 16.9 kB
JavaScript
"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