another way to solve this, when cyclic special instances are expected, could be this one:
const bars = new WeakMap;
class Foo {
#bar;
get bar() {
return this.#bar;
}
initBar(bar) {
if (this.#bar !== undefined) throw Error();
this.#bar = bar;
}
static isFoo(foo) {
return #bar in foo;
}
static asClonePayload(foo) {
return foo.#bar.foo === foo ? null : foo.#bar;
}
static fromClonePayload(bar) {
const foo = new Foo;
if (!bar) {
bar = new Bar(foo);
bars.set(foo, bar);
}
foo.initBar(bar);
return foo;
}
}
class Bar {
#foo;
constructor(foo) {
this.#foo = foo;
}
get foo() {
return this.#foo;
}
static isBar(bar) {
return #foo in bar;
}
static asClonePayload(bar) {
return bar.#foo;
}
static fromClonePayload(foo) {
return bars.get(foo) ?? new Bar(foo);
}
}
I guess what I am trying to say is that if a user wants to play "seppuku" there are solutions to avoid that but that's a user excercise, it's nothing strictly related to the proposal which can work for most common scenarios (and cycles are not a common scenario, these are rather edge cases 'cause these are throwing in JSON and non-existent in structuredClone due its well known compatible instances that never carry cycles, just cross-reference, eventually).