{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type Value = Primitive | ValueArray | ValueObject;\n\nexport type Primitive = string | number | boolean | symbol | bigint | null | undefined;\n\nexport interface ValueArray extends ReadonlyArray<Value> {}\n\nconst VALUE_OBJECT_BRAND = Symbol();\n\nexport abstract class ValueObject<T extends object = object> {\n  readonly [VALUE_OBJECT_BRAND] = true;\n\n  protected constructor(protected readonly props: Readonly<T>, values: ValueArray) {\n    Object.freeze(this.props);\n    return valueObjectCache.getObject(this.constructor, values, () => this);\n  }\n}\n\nexport type ReadonlyValue<T extends Value> =\n  T extends readonly [infer U extends Value, ...(infer R extends readonly Value[])]\n    ? R extends [...never[]]\n      ? readonly [ReadonlyValue<U>]\n      : readonly [ReadonlyValue<U>, ...ReadonlyValue<R>]\n    : T extends readonly (infer U extends Value)[]\n      ? readonly ReadonlyValue<U>[]\n      : T;\n\nexport function isPrimitive(x: unknown): x is Primitive {\n  return typeof x !== 'function' && (typeof x !== 'object' || x === null);\n}\n\nexport function isValueArray(x: unknown): x is ValueArray {\n  return Array.isArray(x) && x.every(isValue);\n}\n\nexport function isValueObject(x: unknown): x is ValueObject {\n  return x instanceof ValueObject;\n}\n\nexport function isValue(x: unknown): x is Value {\n  return isPrimitive(x) || isValueArray(x) || isValueObject(x);\n}\n\n/** A cache tree node used to store an object {@link WeakRef} and a {@link Map} of child nodes, both optional. */\ninterface CacheTreeNode {\n  children: Map<unknown, CacheTreeNode> | null;\n  instanceRef: WeakRef<object> | null;\n}\n\n/**\n * A value object cache that can be used to make value objects behave like primitive types, i.e. if two variables `a`\n * and `b` point to an instance of the same class and have the same value, then `a === b`, otherwise `a !== b`.\n *\n * To achieve this, the cache can be queried with three arguments: a class constructor, an array of values, and a\n * factory function. Values represent the \"identity\" of an instance: all calls to the cache with the same constructor\n * and instance parameters (according to the [same-value-zero equality](\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#same-value-zero_equality))\n * will return the same instance. If the cache already contains an instance of this class with the same values, then it\n * is returned. Otherwise, the provided factory is called to create a new instance, which is then stored in the cache\n * and returned - all the following calls to the cache with the same constructor and values will now return this\n * instance until it is garbage-collected. Because, as the cache only stores weak references to the instances and their\n * constructors, they can still be garbage-collected once they become unreachable.\n *\n * While value objects aren't usually expected to have the same identity when they're equal, making sure they do can\n * make life easier in situations where specifying a custom equality function isn't practical or even doable, such as\n * when using React hooks like {@link useCallback}, {@link useMemo}, {@link useEffect}, etc.\n *\n * // TODO update doc\n *\n * @see https://en.wikipedia.org/wiki/Value_object\n *\n * @example\n * ```ts\n * abstract class Dimension<Unit extends string> {\n *   constructor(\n *     readonly scalar: number,\n *     readonly unit: Unit,\n *   ) {\n *     Object.freeze(this);\n *     return valueObjectCache.getInstance(this.constructor, [scalar, unit], () => this);\n *   }\n * }\n *\n * type LengthUnit = 'mm' | 'm' | 'km';\n * class Length extends Dimension<LengthUnit> {}\n * class OtherLength extends Dimension<LengthUnit> {}\n *\n * console.log(new Length(1, 'm') === new Length(1, 'm')); // outputs 'true'\n * console.log(new Length(1, 'm') === new Length(2, 'm')); // outputs 'false'\n * console.log(new Length(1, 'm') === new OtherLength(1, 'm')); // outputs 'false'\n * ```\n */\nexport const valueObjectCache = new (class ValueObjectCache {\n  readonly #rootNode: CacheTreeNode = { children: null, instanceRef: null };\n\n  readonly #finalizationRegistry = new FinalizationRegistry<readonly unknown[]>((path) => {\n    const walkedNodes: { currentNode: CacheTreeNode; parentNode: CacheTreeNode | null; parentKey: unknown }[] = [\n      { currentNode: this.#rootNode, parentNode: null, parentKey: null },\n    ];\n\n    let node = this.#rootNode;\n    for (const key of path) {\n      const childNode = node.children?.get(key);\n      if (!childNode) break;\n      walkedNodes.push({ currentNode: childNode, parentNode: node, parentKey: key });\n      node = childNode;\n    }\n\n    walkedNodes.reverse();\n\n    for (const { currentNode, parentNode, parentKey } of walkedNodes) {\n      if (currentNode.children && !currentNode.children.size) currentNode.children = null;\n      if (currentNode.instanceRef && !currentNode.instanceRef.deref()) currentNode.instanceRef = null;\n      if (currentNode.children || currentNode.instanceRef) break;\n      parentNode?.children?.delete(parentKey);\n    }\n  });\n\n  #get<T extends object>(constructor: Function, values: ValueArray, factory: () => T): T {\n    const path = [constructor, ...values];\n    let node = this.#rootNode;\n    // console.log('#get()', path);\n\n    for (const key of path) {\n      if (!node.children) node.children = new Map();\n      let childNode = node.children.get(key);\n      if (!childNode) {\n        childNode = { children: null, instanceRef: null };\n        node.children.set(key, childNode);\n      }\n      node = childNode;\n    }\n\n    const cachedInstance = node.instanceRef?.deref();\n    if (cachedInstance) return cachedInstance as T;\n\n    const instance = factory();\n    if (instance.constructor !== constructor) {\n      throw new TypeError('factory must return an instance of the provided constructor');\n    }\n\n    // console.log('storing', instance, 'at path', path);\n    node.instanceRef = new WeakRef(instance);\n    this.#finalizationRegistry.register(instance, path);\n\n    return instance;\n  }\n\n  /** Look for an instance of the provided class constructor matching the provided values. If a matching instance is\n   * found then it is returned, otherwise the factory function is called to create a new instance, which is then stored\n   * in the cache and returned - all future calls to this method made with the same constructor and values will return\n   * this instance until it is garbage-collected. */\n  getObject<const T extends object>(constructor: Function, values: ValueArray, factory: () => T): T {\n    return this.#get(constructor, values.map((v) => this.getValue(v)), () => Object.freeze(factory()));\n  }\n\n  /** Look for an {@link Array} containing a specific list of values in the cache. If a matching {@link Array} is found\n   * then it is returned, otherwise a new {@link Array} is stored in the cache and returned. All returned arrays are\n   * frozen (readonly). */\n  getArray<const T extends ValueArray>(values: T): ReadonlyValue<T> {\n    const array = values.map((v) => this.getValue(v));\n    return this.#get(array.constructor, array, () => Object.freeze(array)) as ReadonlyValue<T>;\n  }\n\n  getValue<const T extends Value>(value: T): ReadonlyValue<T> {\n    // getByValueStack.push(value);\n    // console.log(`[${++getByValueNum}-${getByValueStack.length}] getByValue()`, value);\n    // console.log('stack', getByValueStack);\n    // try {\n    if (isPrimitive(value)) {\n      return value as ReadonlyValue<T>;\n    } else if (Array.isArray(value)) {\n      return this.getArray(value);\n    } else if (isValueObject(value)) {\n      return value as ReadonlyValue<T>;\n    } else {\n      throw new TypeError('Invalid value type: a value must be a primitive, an array, or a ValueObject.');\n    }\n    // } finally {\n    //   getByValueStack.pop();\n    // }\n  }\n})();\n\n// let getByValueNum = 0;\n// let getByValueStack: unknown[] = [];\n\n// type LengthUnit = 'mm' | 'm' | 'km';\n// export class Length extends ValueObject<{scalar: number; unit: LengthUnit}> {\n//   constructor(scalar: number, unit: LengthUnit) {\n//     super({ scalar, unit }, [scalar, unit]);\n//   }\n// }\n\n// const getObj = () =>\n//   valueObjectCache.getValue([\n//     ['a', [1, 2, ['foo', 'bar']]],\n//     ['b', [3, 4, ['baz', 'qux']]],\n//     ['c', new Length(100, 'km')],\n//   ]);\n\n// const obj1 = getObj();\n// const obj2 = getObj();\n\n// if (obj1 !== obj2) {\n//   throw new Error('getObj1() returned different objects');\n// }\n"],"mappings":"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","names":["_a"]}