diff --git a/package.json b/package.json index fe35ce55..39b4509b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "data-monorepo", - "version": "0.9.75", + "version": "0.9.76", "private": true, "scripts": { "build": "pnpm -r run build", diff --git a/packages/data-ai/package.json b/packages/data-ai/package.json index fe15661c..c8f94a31 100644 --- a/packages/data-ai/package.json +++ b/packages/data-ai/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-ai", - "version": "0.9.75", + "version": "0.9.76", "description": "Cross-agent architecture skills for @adobe/data — installable as a Claude Code plugin or copied into any Agent-Skills-compatible agent (Cursor, Codex).", "type": "module", "private": false, diff --git a/packages/data-gpu-samples/package.json b/packages/data-gpu-samples/package.json index 6dc4ebd6..bf1775e8 100644 --- a/packages/data-gpu-samples/package.json +++ b/packages/data-gpu-samples/package.json @@ -1,6 +1,6 @@ { "name": "data-gpu-samples", - "version": "0.9.75", + "version": "0.9.76", "description": "WebGPU samples built on @adobe/data-gpu", "type": "module", "private": true, diff --git a/packages/data-gpu/package.json b/packages/data-gpu/package.json index 9163ffe5..d7d11764 100644 --- a/packages/data-gpu/package.json +++ b/packages/data-gpu/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-gpu", - "version": "0.9.75", + "version": "0.9.76", "description": "Adobe data WebGPU plugins and types for graphics and compute", "type": "module", "private": false, diff --git a/packages/data-lit-tictactoe/package.json b/packages/data-lit-tictactoe/package.json index be1e4ca8..7e2908ce 100644 --- a/packages/data-lit-tictactoe/package.json +++ b/packages/data-lit-tictactoe/package.json @@ -1,6 +1,6 @@ { "name": "data-lit-tictactoe", - "version": "0.9.75", + "version": "0.9.76", "description": "Tic-Tac-Toe sample - Lit web components with @adobe/data-lit and AgenticService", "type": "module", "private": true, diff --git a/packages/data-lit-todo/package.json b/packages/data-lit-todo/package.json index 4854bcb1..a2c57b4e 100644 --- a/packages/data-lit-todo/package.json +++ b/packages/data-lit-todo/package.json @@ -1,6 +1,6 @@ { "name": "data-lit-todo", - "version": "0.9.75", + "version": "0.9.76", "description": "Todo sample app demonstrating @adobe/data with Lit", "type": "module", "private": true, diff --git a/packages/data-lit/package.json b/packages/data-lit/package.json index 26448ff4..0fb17e98 100644 --- a/packages/data-lit/package.json +++ b/packages/data-lit/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-lit", - "version": "0.9.75", + "version": "0.9.76", "description": "Adobe data Lit bindings - hooks, elements, decorators", "type": "module", "private": false, diff --git a/packages/data-p2p-tictactoe/package.json b/packages/data-p2p-tictactoe/package.json index 66afda91..4c647e11 100644 --- a/packages/data-p2p-tictactoe/package.json +++ b/packages/data-p2p-tictactoe/package.json @@ -1,6 +1,6 @@ { "name": "data-p2p-tictactoe", - "version": "0.9.75", + "version": "0.9.76", "description": "Serverless P2P tic-tac-toe — WebRTC DataChannel + @adobe/data-sync", "type": "module", "private": true, diff --git a/packages/data-persistence/package.json b/packages/data-persistence/package.json index 50e46338..2601befc 100644 --- a/packages/data-persistence/package.json +++ b/packages/data-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-persistence", - "version": "0.9.75", + "version": "0.9.76", "description": "Worker-based incremental persistence layer for @adobe/data ECS over OPFS (browser) and node:fs (server).", "type": "module", "sideEffects": false, diff --git a/packages/data-react-hello/package.json b/packages/data-react-hello/package.json index ed04efeb..7fc201bb 100644 --- a/packages/data-react-hello/package.json +++ b/packages/data-react-hello/package.json @@ -1,6 +1,6 @@ { "name": "data-react-hello", - "version": "0.9.75", + "version": "0.9.76", "description": "Hello World sample - click counter using @adobe/data-react", "type": "module", "private": true, diff --git a/packages/data-react-pixie/package.json b/packages/data-react-pixie/package.json index 0ecaf906..5753c6e4 100644 --- a/packages/data-react-pixie/package.json +++ b/packages/data-react-pixie/package.json @@ -1,6 +1,6 @@ { "name": "data-react-pixie", - "version": "0.9.75", + "version": "0.9.76", "description": "PixiJS React sample - ECS sprites (bunny, fox) with @adobe/data-react", "type": "module", "private": true, diff --git a/packages/data-react/package.json b/packages/data-react/package.json index e8b20ef1..bd868e59 100644 --- a/packages/data-react/package.json +++ b/packages/data-react/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-react", - "version": "0.9.75", + "version": "0.9.76", "description": "Adobe data React bindings — hooks and context for ECS database", "type": "module", "private": false, diff --git a/packages/data-solid-dashboard/package.json b/packages/data-solid-dashboard/package.json index 215ddf3f..a4ebd8ee 100644 --- a/packages/data-solid-dashboard/package.json +++ b/packages/data-solid-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "data-solid-dashboard", - "version": "0.9.75", + "version": "0.9.76", "description": "Mini dashboard sample — multiple components sharing one @adobe/data ECS database with SolidJS", "type": "module", "private": true, diff --git a/packages/data-solid/package.json b/packages/data-solid/package.json index f69305d1..6ad88bc1 100644 --- a/packages/data-solid/package.json +++ b/packages/data-solid/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-solid", - "version": "0.9.75", + "version": "0.9.76", "description": "Adobe data SolidJS bindings — context and provider for ECS database", "type": "module", "private": false, diff --git a/packages/data-sync/package.json b/packages/data-sync/package.json index cbdb6a49..d8a450b7 100644 --- a/packages/data-sync/package.json +++ b/packages/data-sync/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-sync", - "version": "0.9.75", + "version": "0.9.76", "description": "Multi-user real-time synchronisation for @adobe/data ECS — server, client, and in-process loopback.", "type": "module", "sideEffects": false, diff --git a/packages/data/package.json b/packages/data/package.json index 95d3fc9c..09b9ad99 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data", - "version": "0.9.75", + "version": "0.9.76", "description": "Adobe data oriented programming library", "type": "module", "sideEffects": false, diff --git a/packages/data/src/ecs/database/transactional-store/create-transactional-store.test.ts b/packages/data/src/ecs/database/transactional-store/create-transactional-store.test.ts index 60f5c6af..7bfa1f4e 100644 --- a/packages/data/src/ecs/database/transactional-store/create-transactional-store.test.ts +++ b/packages/data/src/ecs/database/transactional-store/create-transactional-store.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect } from "vitest"; import { createTransactionalStore } from "./create-transactional-store.js"; import { coalesceTransactions } from "./coalesce-actions.js"; +import { applyOperations } from "./apply-operations.js"; import { Store } from "../../store/index.js"; import { Entity } from "../../entity/entity.js"; import { F32 } from "../../../math/f32/index.js"; @@ -766,4 +767,51 @@ describe("createTransactionalStore", () => { expect(coalesced.persistent).toBe(true); }); }); -}); \ No newline at end of file + + describe("column removal via update-with-undefined", () => { + // Removing a component with `update(entity, { comp: undefined })` migrates the + // entity to an archetype lacking that column. This is the shape used by the + // orphan-delete model (sever parent link). The recorded redo op must still + // carry the column removal so delete -> undo -> redo re-removes the column. + it("records the removal in the redo op and round-trips through undo/redo", () => { + const baseStore = Store.create({ + components: { position: positionSchema, health: healthSchema }, + resources: {}, + archetypes: {} + }); + const store = createTransactionalStore(baseStore); + + // Insert an entity that has the health column. + let entity: Entity = 0 as Entity; + store.execute(t => { + const archetype = t.ensureArchetype(["id", "position", "health"]); + entity = archetype.insert({ + position: { x: 1, y: 2, z: 3 }, + health: { current: 100, max: 100 }, + }); + }); + expect(baseStore.read(entity)?.health).toEqual({ current: 100, max: 100 }); + + // Remove the health column. + const result = store.execute(t => { + t.update(entity, { health: undefined }); + }); + expect(baseStore.read(entity)?.health).toBeUndefined(); + + // The recorded redo op must still carry the column removal, not an empty {}. + const redoUpdate = result.redo.find(op => op.type === "update"); + expect(redoUpdate?.type).toBe("update"); + if (redoUpdate?.type === "update") { + expect("health" in redoUpdate.values).toBe(true); + expect(redoUpdate.values.health).toBeUndefined(); + } + + // Full round-trip: undo re-adds the column, redo must re-remove it. + applyOperations(baseStore, result.undo); + expect(baseStore.read(entity)?.health).toEqual({ current: 100, max: 100 }); + + applyOperations(baseStore, result.redo); + expect(baseStore.read(entity)?.health).toBeUndefined(); + }); + }); +}); \ No newline at end of file diff --git a/packages/data/src/ecs/database/transactional-store/create-transactional-store.ts b/packages/data/src/ecs/database/transactional-store/create-transactional-store.ts index 617832b1..dbb2b010 100644 --- a/packages/data/src/ecs/database/transactional-store/create-transactional-store.ts +++ b/packages/data/src/ecs/database/transactional-store/create-transactional-store.ts @@ -108,6 +108,13 @@ export function createTransactionalStore< changed.archetypes.add(location.archetype.id); } + // The core store mutates `values` in place: when a value is `undefined` it + // deletes that key so it can reuse the object as the new (smaller) archetype's + // row data. Snapshot the redo values first — otherwise a column-removal update + // (`{ comp: undefined }`) records an empty redo op and redo becomes a no-op, + // so delete -> undo -> redo would leave the column in place. + const redoValues = { ...values }; + // Perform the actual update store.update(entity, values as any); @@ -118,7 +125,7 @@ export function createTransactionalStore< } // Add operations with potential combining - addUpdateOperationsMaybeCombineLast(undoOperationsInReverseOrder, redoOperations, entity, values, replacedValues); + addUpdateOperationsMaybeCombineLast(undoOperationsInReverseOrder, redoOperations, entity, redoValues, replacedValues); }; const deleteEntity = (entity: Entity) => {