diff --git a/packages/lens/src/parseAtoms.test.ts b/packages/lens/src/parseAtoms.test.ts index 4baa89e6d..b7cbd7205 100644 --- a/packages/lens/src/parseAtoms.test.ts +++ b/packages/lens/src/parseAtoms.test.ts @@ -1,7 +1,7 @@ import { createTestCtx } from '@reatom/testing' import { suite } from 'uvu' import * as assert from 'uvu/assert' -import { atom } from '@reatom/core' +import { Atom, atom } from '@reatom/core' import { parseAtoms } from './parseAtoms' const test = suite('parseAtoms') @@ -151,4 +151,24 @@ test('should parse deep structures', () => { ;`👍` //? }) +test('circular structures', () => { + const ctx = createTestCtx() + + const dummy = atom(null) + + const a = atom((ctx):{b: Atom} => { + ctx.spy(dummy) // add a dependency to prevent it from restarting on every call + return {b} + }) + + const b = atom((ctx) => { + ctx.spy(dummy) + return {a} + }) + + const snapshot = parseAtoms(ctx, a) + assert.equal(snapshot, snapshot.b.a) + assert.equal(snapshot.b, snapshot.b.a.b) +}) + test.run() diff --git a/packages/lens/src/parseAtoms.ts b/packages/lens/src/parseAtoms.ts index 2f9769a3b..796f01ad1 100644 --- a/packages/lens/src/parseAtoms.ts +++ b/packages/lens/src/parseAtoms.ts @@ -13,37 +13,35 @@ export type ParseAtoms = T extends Atom } : T -export const parseAtoms = ( +export const parseAtoms = (ctx: Ctx, value: T) => + unwrap(ctx, value, new WeakMap()) + +const unwrap = ( ctx: Ctx, - value: Value, -): ParseAtoms => { + value: T, + cache: WeakMap, +): ParseAtoms => { while (isAtom(value)) value = ctx.spy ? ctx.spy(value) : ctx.get(value) if (typeof value !== 'object' || value === null) return value as any - if (isRec(value)) { - const res = {} as Rec - for (const k in value) res[k] = parseAtoms(ctx, value[k]) - return res as any - } + if (cache.has(value)) return cache.get(value) - if (Array.isArray(value)) { - const res = [] - for (const v of value) res.push(parseAtoms(ctx, v)) - return res as any - } + let res: any = value - if (value instanceof Map) { - const res = new Map() - for (const [k, v] of value) res.set(k, parseAtoms(ctx, v)) - return res as any - } - - if (value instanceof Set) { - const res = new Set() - for (const v of value) res.add(parseAtoms(ctx, v)) - return res as any + if (isRec(value)) { + cache.set(value, (res = {})) + for (const k in value) res[k] = unwrap(ctx, value[k], cache) + } else if (Array.isArray(value)) { + cache.set(value, (res = [])) + for (const v of value) res.push(unwrap(ctx, v, cache)) + } else if (value instanceof Map) { + cache.set(value, (res = new Map())) + for (const [k, v] of value) res.set(k, unwrap(ctx, v, cache)) + } else if (value instanceof Set) { + cache.set(value, (res = new Set())) + for (const v of value) res.add(unwrap(ctx, v, cache)) } - return value as any + return res }