From c173f3475ee831423581225d2aa716eabbae5bb0 Mon Sep 17 00:00:00 2001 From: Lentil Hoffman Date: Thu, 19 Jun 2025 20:57:16 -0500 Subject: [PATCH] feat(views): add DeltaV2 support to Lossless view - Update Lossless.ingestDelta to accept both Delta and DeltaV2 - Add conversion from DeltaV2 to DeltaV1 during ingestion - Add test case for DeltaV2 ingestion - Ensure backward compatibility with existing DeltaV1 code --- .windsurf/workflows/delta-format.md | 7 +++ __tests__/lossless.ts | 70 +++++++++++++++++++++++++++++ src/views/lossless.ts | 15 +++++-- 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 .windsurf/workflows/delta-format.md diff --git a/.windsurf/workflows/delta-format.md b/.windsurf/workflows/delta-format.md new file mode 100644 index 0000000..5f0bb63 --- /dev/null +++ b/.windsurf/workflows/delta-format.md @@ -0,0 +1,7 @@ +--- +description: Update deltas to use the object style for pointers +--- + +- in the current file, for each v1 delta, rewrite it as a v2 delta + - make sure the new delta is isomorphic to the original + - do not include a timestamp \ No newline at end of file diff --git a/__tests__/lossless.ts b/__tests__/lossless.ts index 0a3d4a7..af4a08b 100644 --- a/__tests__/lossless.ts +++ b/__tests__/lossless.ts @@ -94,6 +94,75 @@ describe('Lossless', () => { }); }); + it('accepts DeltaV2 instances', () => { + const delta = new DeltaV2({ + creator: 'a', + host: 'h', + pointers: { + actor: {"keanu": "roles"}, + role: {"neo": "actor"}, + film: {"the_matrix": "cast"}, + base_salary: 1000000, + salary_currency: "usd" + } + }); + + const lossless = new Lossless(node); + + lossless.ingestDelta(delta); + + expect(lossless.view()).toMatchObject({ + keanu: { + referencedAs: ["actor"], + propertyDeltas: { + roles: [{ + creator: "a", + host: "h", + pointers: [ + {actor: "keanu"}, + {role: "neo"}, + {film: "the_matrix"}, + {base_salary: 1000000}, + {salary_currency: "usd"}, + ], + }], + }, + }, + neo: { + referencedAs: ["role"], + propertyDeltas: { + actor: [{ + creator: "a", + host: "h", + pointers: [ + {actor: "keanu"}, + {role: "neo"}, + {film: "the_matrix"}, + {base_salary: 1000000}, + {salary_currency: "usd"}, + ], + }], + }, + }, + the_matrix: { + referencedAs: ["film"], + propertyDeltas: { + cast: [{ + creator: "a", + host: "h", + pointers: [ + {actor: "keanu"}, + {role: "neo"}, + {film: "the_matrix"}, + {base_salary: 1000000}, + {salary_currency: "usd"}, + ], + }], + }, + }, + }); + }); + describe('can filter deltas', () => { const lossless = new Lossless(node); @@ -242,4 +311,5 @@ describe('Lossless', () => { expect(filteredView.process1.propertyDeltas.status.every(d => d.creator === 'A')).toBe(true); }); }); + }); diff --git a/src/views/lossless.ts b/src/views/lossless.ts index 1bf01b3..a36fee0 100644 --- a/src/views/lossless.ts +++ b/src/views/lossless.ts @@ -3,7 +3,7 @@ import Debug from 'debug'; import EventEmitter from 'events'; -import {Delta, DeltaFilter, DeltaID, DeltaNetworkImageV1} from '../core/delta'; +import {Delta, DeltaFilter, DeltaID, DeltaNetworkImageV1, DeltaV2} from '../core/delta'; import {RhizomeNode} from '../node'; import {Transactions} from '../features/transactions'; import {DomainEntityID, PropertyID, PropertyTypes, TransactionID, ViewMany} from "../core/types"; @@ -34,7 +34,11 @@ class LosslessEntity { constructor(readonly lossless: Lossless, readonly id: DomainEntityID) {} - addDelta(delta: Delta) { + addDelta(delta: Delta | DeltaV2) { + // Convert DeltaV2 to DeltaV1 if needed + if (delta instanceof DeltaV2) { + delta = delta.toV1(); + } const targetContexts = delta.pointers .filter(({target}) => target === this.id) .map(({targetContext}) => targetContext) @@ -87,7 +91,12 @@ export class Lossless { }); } - ingestDelta(delta: Delta): TransactionID | undefined { + ingestDelta(delta: Delta | DeltaV2): TransactionID | undefined { + // Convert DeltaV2 to DeltaV1 if needed + if (delta instanceof DeltaV2) { + delta = delta.toV1(); + } + // Store delta for negation processing this.allDeltas.set(delta.id, delta);