deep signals: modify primitive set patch logic

feat/orm-diffs^2
Laurin Weger 10 hours ago
parent 177e2f2739
commit 1b21fd2b7c
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 32
      sdk/js/alien-deepsignals/src/deepSignal.ts
  2. 99
      sdk/js/alien-deepsignals/src/test/watchPatches.test.ts

@ -490,8 +490,9 @@ function getFromSet(
objToProxy.set(entryVal, childProxy);
entryVal = childProxy;
}
// Set entry add: emit object vs literal variant.
// Set entry add: emit object vs primitive variant.
if (entryVal && typeof entryVal === "object") {
// Object entry: path includes synthetic id
queuePatch({
root: metaNow.root,
path: [...containerPath, synthetic],
@ -499,21 +500,36 @@ function getFromSet(
type: "object",
});
} else {
// Primitive entry: path is just the Set, value contains the primitive
queuePatch({
root: metaNow.root,
path: [...containerPath, synthetic],
path: containerPath,
op: "add",
value: entryVal,
type: "set",
value: [entryVal],
});
}
} else if (key === "delete") {
const entry = args[0];
const synthetic = getSetEntryKey(entry);
queuePatch({
root: metaNow.root,
path: [...containerPath, synthetic],
op: "remove",
});
// Check if entry is primitive or object
if (entry && typeof entry === "object") {
// Object entry: path includes synthetic id
queuePatch({
root: metaNow.root,
path: [...containerPath, synthetic],
op: "remove",
});
} else {
// Primitive entry: path is just the Set, value contains the primitive
queuePatch({
root: metaNow.root,
path: containerPath,
op: "remove",
type: "set",
value: entry,
});
}
} else if (key === "clear") {
// Structural clear: remove prior entry-level patches for this Set this tick.
if (pendingPatches) {

@ -95,7 +95,16 @@ describe("watch (patch mode)", () => {
await Promise.resolve();
expect(batches.length >= 1).toBe(true);
const allPaths = batches.flatMap((b) => b.map((p) => p.path.join(".")));
expect(allPaths.some((p) => p.startsWith("s."))).toBe(true);
// For primitives, the path should be just "s" (the Set itself)
expect(allPaths.every((p) => p === "s")).toBe(true);
// Check the values
const patches = batches.flat();
const addPatches = patches.filter((p) => p.op === "add");
const deletePatches = patches.filter((p) => p.op === "remove");
expect(addPatches.length).toBe(1);
expect(deletePatches.length).toBe(1);
expect((addPatches[0] as any).value[0]).toBe(3);
expect((deletePatches[0] as any).value).toBe(1);
stop();
});
@ -226,6 +235,89 @@ describe("watch (patch mode)", () => {
});
describe("Set", () => {
it("emits patches for primitive adds", async () => {
const st = deepSignal({ s: new Set<any>() });
const batches: DeepPatch[][] = [];
const { stopListening: stop } = watch(st, ({ patches }) =>
batches.push(patches)
);
st.s.add(true);
st.s.add(2);
st.s.add("3");
await Promise.resolve();
expect(batches.length).toBe(1);
const patches = batches[0];
expect(patches.length).toBe(3);
// All patches should have the same path (the Set itself)
patches.forEach((p) => {
expect(p.path.join(".")).toBe("s");
expect(p.op).toBe("add");
expect((p as any).type).toBe("set");
});
// Check that values are in the value field, not in path
const values = patches.map((p: any) => p.value[0]);
expect(values).toContain(true);
expect(values).toContain(2);
expect(values).toContain("3");
stop();
});
it("emits patches for primitive deletes", async () => {
const st = deepSignal({ s: new Set<any>([true, 2, "3"]) });
const batches: DeepPatch[][] = [];
const { stopListening: stop } = watch(st, ({ patches }) =>
batches.push(patches)
);
st.s.delete(true);
st.s.delete(2);
await Promise.resolve();
expect(batches.length).toBe(1);
const patches = batches[0];
expect(patches.length).toBe(2);
// All patches should have the same path (the Set itself)
patches.forEach((p) => {
expect(p.path.join(".")).toBe("s");
expect(p.op).toBe("remove");
expect((p as any).type).toBe("set");
});
// Check that values are in the value field
const values = patches.map((p: any) => p.value);
expect(values).toContain(true);
expect(values).toContain(2);
stop();
});
it("does not emit patches for non-existent primitives", async () => {
const st = deepSignal({ s: new Set<any>([1, 2]) });
const batches: DeepPatch[][] = [];
const { stopListening: stop } = watch(st, ({ patches }) =>
batches.push(patches)
);
st.s.delete("nonexistent");
st.s.delete(999);
await Promise.resolve();
expect(batches.length).toBe(0);
stop();
});
it("does not emit patches for already added primitive", async () => {
const st = deepSignal({ s: new Set<any>([1, "test", true]) });
const batches: DeepPatch[][] = [];
const { stopListening: stop } = watch(st, ({ patches }) =>
batches.push(patches)
);
st.s.add(1);
st.s.add("test");
st.s.add(true);
await Promise.resolve();
expect(batches.length).toBe(0);
stop();
});
it("emits single structural patch on Set.clear()", async () => {
const st = deepSignal({ s: new Set<any>() });
addWithId(st.s as any, { id: "a", x: 1 }, "a");
@ -288,8 +380,11 @@ describe("watch (patch mode)", () => {
const ret = addWithId(st.s as any, 5, "ignored");
expect(ret).toBe(5);
await Promise.resolve();
// For primitives, path should be just "s" and value should be in the value field
const paths = patches.flat().map((p) => p.path.join("."));
expect(paths).toContain("s.5");
expect(paths).toContain("s");
const values = patches.flat().map((p: any) => p.value?.[0]);
expect(values).toContain(5);
stop();
});
it("setSetEntrySyntheticId applies custom id without helper", async () => {

Loading…
Cancel
Save