Skip to content

Commit 71cd3a5

Browse files
committed
refactor: use custom isEqual
1 parent 9ab46bd commit 71cd3a5

File tree

4 files changed

+88
-2
lines changed

4 files changed

+88
-2
lines changed

packages/svelte-stores/src/lib/queryParamsStore.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { derived, get, type Readable } from 'svelte/store';
22
import type { Page } from '@sveltejs/kit';
3-
import { isEqual } from 'lodash-es';
43

54
import * as Serialize from '@layerstack/utils/serialize';
65
import rollup from '@layerstack/utils/rollup';
7-
import { entries, type ValueOf } from '@layerstack/utils';
6+
import { entries, isEqual, type ValueOf } from '@layerstack/utils';
87

98
// Matches $app/navigation's goto without dependency - https://kit.svelte.dev/docs/modules#$app-navigation-goto
109
type Goto = (url: string | URL, opts?: any) => any;

packages/utils/src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export * from './logger.js';
3535
export { round, clamp, randomInteger } from './number.js';
3636
export { get } from './get.js';
3737
export { set } from './set.js';
38+
export { isEqual } from './isEqual.js';
3839
export { isEmptyObject, isLiteralObject, omit, pick } from './object.js';
3940
export { mergeWith, merge, defaultsDeep } from './mergeWith.js';
4041
export * from './promise.js';
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, it, expect } from 'vitest';
2+
3+
import { isEqual } from './isEqual.js';
4+
5+
describe('isEqual', () => {
6+
it('compares primitives', () => {
7+
expect(isEqual(1, 1)).toBe(true);
8+
expect(isEqual(1, 2)).toBe(false);
9+
expect(isEqual('a', 'a')).toBe(true);
10+
expect(isEqual(true, false)).toBe(false);
11+
expect(isEqual(null, null)).toBe(true);
12+
expect(isEqual(undefined, undefined)).toBe(true);
13+
expect(isEqual(null, undefined)).toBe(false);
14+
});
15+
16+
it('treats NaN as equal to NaN', () => {
17+
expect(isEqual(Number.NaN, Number.NaN)).toBe(true);
18+
});
19+
20+
it('compares arrays deeply', () => {
21+
expect(isEqual([1, 2, 3], [1, 2, 3])).toBe(true);
22+
expect(isEqual([1, 2, 3], [1, 2, 4])).toBe(false);
23+
});
24+
25+
it('compares objects deeply', () => {
26+
expect(isEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } })).toBe(true);
27+
expect(isEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } })).toBe(false);
28+
});
29+
30+
it('compares dates by timestamp', () => {
31+
expect(
32+
isEqual(new Date('2020-01-01T00:00:00.000Z'), new Date('2020-01-01T00:00:00.000Z'))
33+
).toBe(true);
34+
expect(
35+
isEqual(new Date('2020-01-01T00:00:00.000Z'), new Date('2020-01-02T00:00:00.000Z'))
36+
).toBe(false);
37+
});
38+
39+
it('handles circular references', () => {
40+
const a: any = { x: 1 };
41+
a.self = a;
42+
const b: any = { x: 1 };
43+
b.self = b;
44+
expect(isEqual(a, b)).toBe(true);
45+
});
46+
});

packages/utils/src/lib/isEqual.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export function isEqual(a: any, b: any, seen = new WeakMap()): boolean {
2+
if (a === b || (a !== a && b !== b)) return true; // identical or both NaN
3+
if (a == null || b == null) return a === b;
4+
if (typeof a !== 'object' || typeof b !== 'object') return false;
5+
6+
// Circular reference handling
7+
if (seen.has(a)) return seen.get(a) === b;
8+
seen.set(a, b);
9+
10+
// Dates
11+
if (a instanceof Date && b instanceof Date) return +a === +b;
12+
if (a instanceof Date || b instanceof Date) return false;
13+
14+
// Maps
15+
if (a instanceof Map && b instanceof Map) {
16+
if (a.size !== b.size) return false;
17+
for (const [k, v] of a) if (!b.has(k) || !isEqual(v, b.get(k), seen)) return false;
18+
return true;
19+
}
20+
if (a instanceof Map || b instanceof Map) return false;
21+
22+
// Sets
23+
if (a instanceof Set && b instanceof Set) {
24+
if (a.size !== b.size) return false;
25+
for (const v of a) if (!b.has(v)) return false;
26+
return true;
27+
}
28+
if (a instanceof Set || b instanceof Set) return false;
29+
30+
// Arrays
31+
if (Array.isArray(a) && Array.isArray(b)) {
32+
return a.length === b.length && a.every((v, i) => isEqual(v, b[i], seen));
33+
}
34+
if (Array.isArray(a) || Array.isArray(b)) return false;
35+
36+
// Plain objects
37+
const keysA = Object.keys(a);
38+
const keysB = Object.keys(b);
39+
return keysA.length === keysB.length && keysA.every((k) => k in b && isEqual(a[k], b[k], seen));
40+
}

0 commit comments

Comments
 (0)