I'd be a bit worried about putting private fields in R/T. As mentioned on github, all this can be done explicitly in user land by wrapping the target in a record, and including an opaque identity carrying primitive (Tag
) to identify the brand.
A Tag
is basically like a symbol which can be used to convey identity and be used in Weak collections:
function Tag(description) {
const tagString = `Tag(${description})`;
const tagObject = Object.assign(Object.create(null), {
description: description.valueOf(),
toString: Object.freeze(() => tagString),
valueOf: Object.freeze(() => tagBox),
});
Object.defineProperty(tagObject, "toString", { enumerable: false });
Object.defineProperty(tagObject, "valueOf", { enumerable: false });
Object.freeze(tagObject);
const tagBox = Box(tagObject);
return tagBox;
}
Regarding tagging, there are 2 use cases I see:
- The tagged data should be visible and participate in the structure checks of R/T that may contain it. In that case the tagging primitive would be a Boxed unique object, and the "public marker" would keep track of these tagged records:
class PublicMarker() {
#marked = new WeakSet();
#tag;
constructor(description) {
this.#tag = new Tag(description);
}
get mark() {
return this.#tag;
}
wrap(record) {
const marked = #{mark: this.#tag, wrapped: record};
this.#marked.add(marked);
return marked;
}
unwrap(marked) {
if (!this.isWrapped(marked)) throw new TypeError();
return marked.wrapped;
}
isWrapped(record) {
return record.mark === this.#tag && this.#marked.has(record);
}
}
- The tagged data should be opaque and privately wrapped so that only the wrapper can unwrap it. The wrapped data would be contained in a Box to exclude it from participating in
sameStructure
checks.
class PrivateMarker {
#Wrapper = new WrapperRegistry();
#tag;
constructor(description) {
this.#tag = new Tag(description);
}
get mark() {
return this.#tag;
}
wrap(record) {
const wrapped = Box(this.#Wrapper.wrap(value));
return #{mark: this.#tag, wrapped};
}
unwrap(marked) {
if (!this.isWrapped(marked)) throw new TypeError();
return this.#Wrapper.unwrap(Box.unbox(marked.wrapped));
}
isWrapped(record) {
try {
const correctShape =
Record.isRecord(marked) &&
#[...Object.keys(marked)] === #['mark', 'wrapped'] &&
marked.mark === this.#tag;
return correctShape && Box.unbox(marked.wrapped) instanceof this.#Wrapper;
} catch {
return false;
}
}
}
The WrapperRegistry
would be this one: Wrap any value into a registered object ยท GitHub