value-object-cache
Version:
A value object cache that can be used to make value objects behave like primitive types, i.e. if two variables `a` and `b` point to instances of the same class and have the same value, then `a === b`, otherwise `a !== b`.
115 lines (114 loc) • 5.1 kB
JavaScript
var __typeError = (msg) => {
throw TypeError(msg);
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
// src/index.ts
var VALUE_OBJECT_BRAND = Symbol();
var _a;
_a = VALUE_OBJECT_BRAND;
var ValueObject = class {
constructor(props, values) {
this.props = props;
this[_a] = true;
Object.freeze(this.props);
return valueObjectCache.getObject(this.constructor, values, () => this);
}
};
function isPrimitive(x) {
return typeof x !== "function" && (typeof x !== "object" || x === null);
}
function isValueArray(x) {
return Array.isArray(x) && x.every(isValue);
}
function isValueObject(x) {
return x instanceof ValueObject;
}
function isValue(x) {
return isPrimitive(x) || isValueArray(x) || isValueObject(x);
}
var _rootNode, _finalizationRegistry, _ValueObjectCache_instances, get_fn, _a2;
var valueObjectCache = new (_a2 = class {
constructor() {
__privateAdd(this, _ValueObjectCache_instances);
__privateAdd(this, _rootNode, { children: null, instanceRef: null });
__privateAdd(this, _finalizationRegistry, new FinalizationRegistry((path) => {
var _a3, _b;
const walkedNodes = [
{ currentNode: __privateGet(this, _rootNode), parentNode: null, parentKey: null }
];
let node = __privateGet(this, _rootNode);
for (const key of path) {
const childNode = (_a3 = node.children) == null ? void 0 : _a3.get(key);
if (!childNode) break;
walkedNodes.push({ currentNode: childNode, parentNode: node, parentKey: key });
node = childNode;
}
walkedNodes.reverse();
for (const { currentNode, parentNode, parentKey } of walkedNodes) {
if (currentNode.children && !currentNode.children.size) currentNode.children = null;
if (currentNode.instanceRef && !currentNode.instanceRef.deref()) currentNode.instanceRef = null;
if (currentNode.children || currentNode.instanceRef) break;
(_b = parentNode == null ? void 0 : parentNode.children) == null ? void 0 : _b.delete(parentKey);
}
}));
}
/** Look for an instance of the provided class constructor matching the provided values. If a matching instance is
* found then it is returned, otherwise the factory function is called to create a new instance, which is then stored
* in the cache and returned - all future calls to this method made with the same constructor and values will return
* this instance until it is garbage-collected. */
getObject(constructor, values, factory) {
return __privateMethod(this, _ValueObjectCache_instances, get_fn).call(this, constructor, values.map((v) => this.getValue(v)), () => Object.freeze(factory()));
}
/** Look for an {@link Array} containing a specific list of values in the cache. If a matching {@link Array} is found
* then it is returned, otherwise a new {@link Array} is stored in the cache and returned. All returned arrays are
* frozen (readonly). */
getArray(values) {
const array = values.map((v) => this.getValue(v));
return __privateMethod(this, _ValueObjectCache_instances, get_fn).call(this, array.constructor, array, () => Object.freeze(array));
}
getValue(value) {
if (isPrimitive(value)) {
return value;
} else if (Array.isArray(value)) {
return this.getArray(value);
} else if (isValueObject(value)) {
return value;
} else {
throw new TypeError("Invalid value type: a value must be a primitive, an array, or a ValueObject.");
}
}
}, _rootNode = new WeakMap(), _finalizationRegistry = new WeakMap(), _ValueObjectCache_instances = new WeakSet(), get_fn = function(constructor, values, factory) {
var _a3;
const path = [constructor, ...values];
let node = __privateGet(this, _rootNode);
for (const key of path) {
if (!node.children) node.children = /* @__PURE__ */ new Map();
let childNode = node.children.get(key);
if (!childNode) {
childNode = { children: null, instanceRef: null };
node.children.set(key, childNode);
}
node = childNode;
}
const cachedInstance = (_a3 = node.instanceRef) == null ? void 0 : _a3.deref();
if (cachedInstance) return cachedInstance;
const instance = factory();
if (instance.constructor !== constructor) {
throw new TypeError("factory must return an instance of the provided constructor");
}
node.instanceRef = new WeakRef(instance);
__privateGet(this, _finalizationRegistry).register(instance, path);
return instance;
}, _a2)();
export {
ValueObject,
isPrimitive,
isValue,
isValueArray,
isValueObject,
valueObjectCache
};
//# sourceMappingURL=index.mjs.map