Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ function compare(

```ts
type CompareOptions = {
filterer?: (a: unknown, b: unknown) => boolean,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What inputs are expected for a and b? If they're values in the object graph, then how does a filterer differentiate

  • extra vs. missing vs. mismatch
  • property descriptor
  • prototype

or just in general, where in the graph its invocation relates to?

@JakobJingleheimer JakobJingleheimer Jun 13, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, I suppose the type of A and B are not unknown: they're a primitive (or "special" key). And a couple things are missing.

So I guess the type would be more like:

Suggested change
filterer?: (a: unknown, b: unknown) => boolean,
filterer?: (
a: Primitive,
b: Primitive,
path: (string | Symbol)[],
reason: 'extra' | 'missing' | 'mismatch',
) => boolean,

For descriptor, I think we already covered this: A and/or B would have that special key in path (which was missing before, so that probably created the confusion).

RE the "special" path segment, what about a well-known symbol, like Symbol(Deviation:Descriptor) & Symbol(Deviation:Prototype) (or Symbol(DescriptorDeviation) & Symbol(PrototypeDeviation))? I think I'd prefer the : delimited version.


Thoughts on mismatch → unequal, equality, or maybe even the equality algo used like same-zero? mismatch seems a little "shit happened" to me 😅

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RE the "special" path segment, what about a well-known symbol, like Symbol(Deviation:Descriptor) & Symbol(Deviation:Prototype) (or Symbol(DescriptorDeviation) & Symbol(PrototypeDeviation))? I think I'd prefer the : delimited version.

The special path segment can't be a symbol, because any such symbol could itself be an actual property key—e.g., { foo: { [Symbol(DescriptorDeviation)]: { value: 42 } } } would have an value-mode path like ["foo", Symbol(DescriptorDeviation), "value"] which could not be distinguished from a similar descriptor-mode path for { foo: 42 }.

Thoughts on mismatch → unequal, equality, or maybe even the equality algo used like same-zero? mismatch seems a little "shit happened" to me 😅

Yeah, I don't love "mismatch" either. "unequal" works for me.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Symbols are unique though, so Symbol(foo) ≠ Symbol(foo). If the symbol we use is well-known, it can be checked and there is no collision.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't stop someone from extracting that symbol and using it as a property key.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh. That's true… but then they clearly effed up, no? They broke themselves.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, there does not exist any value in JS that can be a "sentinel" value, because every such value could somehow appear in a user's object.

This claim is true but irrelevant here; we're talking about an array of property keys so objects are in fact available as sentinels.

Ahh. That's true… but then they clearly effed up, no? They broke themselves.

No, that's not clear at all. I am not willing to write off such use as "broken".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there is no legitimate use-case for a user to pass an object containing our own symbol to compare.

@gibson042 gibson042 Jun 18, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's one: verification that an actual Symbol built-in matches expectations. There are multiple ways to do accomplish this, but at least one of them involves using the object itself (e.g., compare(globalThis.Symbol, expectedSymbol)).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Legitimate" isn't really relevant (although I can come up with some) - if it is possible, we have to define behavior for it, and having a special sentinel value produce confusing results just isn't viable.

A library can arbitrarily declare a subset of value types are valid, but the language can not.

mode?:
| 'fast' // (default) return => boolean
| 'full' // return => Iterator<Deviation>
Expand All @@ -181,6 +182,9 @@ type CompareOptions = {
```

<dl>
<dt><em>filterer</em></dt>
<dd>When a deviation has been detected, a predicate function to determine whether to include in the <code>Deviations</code> output (return <code>true</code> to include or <code>false</code> to exclude). If all deviations are excluded due to this, the final output will be <code>false</code> instead of an Iterator.</dd>

<dt><em>mode</em></dt>
<dd>How the comparison reports the result</dd>

Expand Down
Loading