Skip to content

Commit a578917

Browse files
committed
feat: add stripNullValues utility function and integrate it into handlePushRequest for improved object comparison
1 parent 551f5a7 commit a578917

File tree

3 files changed

+98
-3
lines changed

3 files changed

+98
-3
lines changed

src/planner-replication.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
type RxReplicationWriteToMasterRow,
99
} from "rxdb/plugins/core";
1010
import { PrismaClient, Prisma } from "./generated/client";
11-
import { deepCompare } from "./utils/deepCompare";
11+
import { deepCompare, stripNullValues } from "./utils/deepCompare";
1212
import { HTTPException } from "hono/http-exception";
1313
import type { Bindings } from "./index";
1414
import prismaClients from "./prisma/client";
@@ -191,8 +191,12 @@ async function handlePushRequest<M extends Model>(
191191
if (itemInDb && assumedMasterState) {
192192
const itemAsDocument = handlers.transformItemToDocument(itemInDb);
193193

194+
// Strip null values from both objects before comparison
195+
const strippedItemAsDocument = stripNullValues(itemAsDocument);
196+
const strippedAssumedState = stripNullValues(assumedMasterState);
197+
194198
// If we have an assumed state but it doesn't match what's in the DB, it's a conflict
195-
if (!deepCompare(itemAsDocument, assumedMasterState)) {
199+
if (!deepCompare(strippedItemAsDocument, strippedAssumedState)) {
196200
conflicts.push(itemAsDocument);
197201
continue;
198202
}

src/utils/deepCompare.test.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { deepCompare } from "./deepCompare";
1+
import { deepCompare, stripNullValues } from "./deepCompare";
22
import { describe, it, expect } from "bun:test";
33

44
describe("deepCompare", () => {
@@ -130,3 +130,72 @@ describe("deepCompare", () => {
130130
expect(deepCompare(obj1, obj2)).toBe(true);
131131
});
132132
});
133+
134+
describe("stripNullValues", () => {
135+
it("should return the same object if no null values exist", () => {
136+
const obj = { a: 1, b: 'test', c: true };
137+
expect(stripNullValues(obj)).toEqual(obj);
138+
});
139+
140+
it("should remove null values from an object", () => {
141+
const obj = { a: 1, b: null, c: 'test' };
142+
expect(stripNullValues(obj)).toEqual({ a: 1, c: 'test' });
143+
});
144+
145+
it("should handle nested objects", () => {
146+
const obj = {
147+
a: 1,
148+
b: {
149+
c: null,
150+
d: 'test',
151+
e: {
152+
f: null,
153+
g: 42
154+
}
155+
}
156+
};
157+
158+
// Type assertion is needed to avoid TypeScript errors about missing properties
159+
const expected = {
160+
a: 1,
161+
b: {
162+
d: 'test',
163+
e: {
164+
g: 42
165+
}
166+
}
167+
};
168+
169+
expect(stripNullValues(obj)).toEqual(expected as any);
170+
});
171+
172+
it("should handle arrays as values", () => {
173+
const obj = { a: [1, 2, 3], b: null };
174+
expect(stripNullValues(obj)).toEqual({ a: [1, 2, 3] });
175+
});
176+
177+
it("should return empty object when all values are null", () => {
178+
const obj = { a: null, b: null };
179+
expect(stripNullValues(obj)).toEqual({});
180+
});
181+
182+
it("should handle undefined values", () => {
183+
const obj = { a: undefined, b: null };
184+
expect(stripNullValues(obj)).toEqual({ a: undefined });
185+
});
186+
187+
it("should not modify the original object", () => {
188+
const obj = { a: 1, b: null };
189+
const result = stripNullValues(obj);
190+
expect(obj).toEqual({ a: 1, b: null });
191+
expect(result).toEqual({ a: 1 });
192+
});
193+
194+
it("should work with deepCompare to treat null properties as non-existent", () => {
195+
const obj1 = { a: 1, b: null };
196+
const obj2 = { a: 1 };
197+
198+
// Using stripNullValues should make these objects equal for comparison
199+
expect(deepCompare(stripNullValues(obj1), stripNullValues(obj2))).toBe(true);
200+
});
201+
});

src/utils/deepCompare.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,25 @@ export function deepCompare(
4848
deepCompare(a[key], b[key], maxDepth, currentDepth + 1),
4949
);
5050
}
51+
52+
/**
53+
* Recursively removes null values from an object
54+
* Treats { prop: null } and {} as equivalent
55+
*/
56+
export function stripNullValues<T extends object>(obj: T): Partial<T> {
57+
if (!obj) return obj;
58+
59+
const result: Partial<T> = {};
60+
61+
for (const key in obj) {
62+
if (obj[key] !== null) {
63+
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
64+
result[key] = stripNullValues(obj[key] as any) as T[Extract<keyof T, string>];
65+
} else {
66+
result[key] = obj[key];
67+
}
68+
}
69+
}
70+
71+
return result;
72+
}

0 commit comments

Comments
 (0)