pocket-state
Version:
tiny global store
75 lines (70 loc) • 1.98 kB
text/typescript
const isIterable = (obj: object): obj is Iterable<unknown> =>
Symbol.iterator in obj;
const hasIterableEntries = (
value: Iterable<unknown>,
): value is Iterable<unknown> & {
entries(): Iterable<[unknown, unknown]>;
} =>
// HACK: avoid checking entries type
'entries' in value;
const compareEntries = (
valueA: {entries(): Iterable<[unknown, unknown]>},
valueB: {entries(): Iterable<[unknown, unknown]>},
) => {
const mapA = valueA instanceof Map ? valueA : new Map(valueA.entries());
const mapB = valueB instanceof Map ? valueB : new Map(valueB.entries());
if (mapA.size !== mapB.size) {
return false;
}
for (const [key, value] of mapA) {
if (!Object.is(value, mapB.get(key))) {
return false;
}
}
return true;
};
// Ordered iterables
const compareIterables = (
valueA: Iterable<unknown>,
valueB: Iterable<unknown>,
) => {
const iteratorA = valueA[Symbol.iterator]();
const iteratorB = valueB[Symbol.iterator]();
let nextA = iteratorA.next();
let nextB = iteratorB.next();
while (!nextA.done && !nextB.done) {
if (!Object.is(nextA.value, nextB.value)) {
return false;
}
nextA = iteratorA.next();
nextB = iteratorB.next();
}
return !!nextA.done && !!nextB.done;
};
export function shallow<T>(valueA: T, valueB: T): boolean {
if (Object.is(valueA, valueB)) {
return true;
}
if (
typeof valueA !== 'object' ||
valueA === null ||
typeof valueB !== 'object' ||
valueB === null
) {
return false;
}
if (Object.getPrototypeOf(valueA) !== Object.getPrototypeOf(valueB)) {
return false;
}
if (isIterable(valueA) && isIterable(valueB)) {
if (hasIterableEntries(valueA) && hasIterableEntries(valueB)) {
return compareEntries(valueA, valueB);
}
return compareIterables(valueA, valueB);
}
// assume plain objects
return compareEntries(
{entries: () => Object.entries(valueA)},
{entries: () => Object.entries(valueB)},
);
}