diff --git a/src/DeepCompositeSymbol.ts b/src/DeepCompositeSymbol.ts index 45df621a..b4872846 100644 --- a/src/DeepCompositeSymbol.ts +++ b/src/DeepCompositeSymbol.ts @@ -13,11 +13,21 @@ const DeepCompositeSymbol = (object: any, filter?: (entry: [string, any]) => boo return Tuple.unsafeSymbol(...flatten(entries)); }; -export const shallow = Symbol('shallow'); +export const shallowKey = Symbol('shallow'); +export const shallowCache = new WeakSet(); +export const shallow = (a: A): A => { + shallowCache.add(a); + return a; +}; const update = (entry: [string, any], filter?: any) => { const value = entry[1]; - if (isObject(value) && !(value as any)[shallow] && !(value instanceof Tuple)) { + if ( + isObject(value) && + !(value as any)[shallowKey] && + !(value instanceof Tuple) && + !shallowCache.has(value) + ) { entry[1] = DeepCompositeSymbol(value, filter); } }; diff --git a/src/tuplerone.ts b/src/tuplerone.ts index d13273e5..c855dbcd 100644 --- a/src/tuplerone.ts +++ b/src/tuplerone.ts @@ -4,7 +4,7 @@ import ValueObject from './ValueObject'; export { symbol as CompositeSymbol } from './Tuple'; export { memoize } from './memoize'; -export { shallow } from './DeepCompositeSymbol'; +export { shallowKey, shallow } from './DeepCompositeSymbol'; /** * A tuple whose members are allowed to all be primitive, diff --git a/test/ValueObject.test.ts b/test/ValueObject.test.ts index 67cf7cdf..bd9430f3 100644 --- a/test/ValueObject.test.ts +++ b/test/ValueObject.test.ts @@ -1,4 +1,4 @@ -import { ValueObject, shallow } from '../src/tuplerone'; +import { ValueObject, shallowKey, shallow } from '../src/tuplerone'; describe(ValueObject.name, () => { it('constructs', () => { @@ -45,7 +45,15 @@ describe(ValueObject.name, () => { const b = { a }; a.b = b; expect(() => ValueObject(a)).toThrow(); - a[shallow] = true; + a[shallowKey] = true; expect(() => ValueObject(a)).not.toThrow(); }); + + it('can be short-circuited using a WeakSet registry', () => { + const a: any = {}; + const b = { a }; + a.b = b; + expect(() => ValueObject(a)).toThrow(); + expect(() => ValueObject(shallow(a))).not.toThrow(); + }); });