Skip to content

JSON Patch operations cannot be applied to JSON CRDT vec nodes #971

@nickgros

Description

@nickgros

Hello,

After computing an RFC 6902 JSON Patch, it appears that operations on the contents of vec nodes cannot be applied to a JSON CRDT model, resulting in a thrown error.

Here is a minimum reproducible example script:

import { Model } from "json-joy/lib/json-crdt";
import { JsonPatch } from "json-joy/lib/json-crdt/json-patch";
import { JsonPatchDiff } from "json-joy/lib/json-patch-diff/JsonPatchDiff";
import { s } from "json-joy/lib/json-crdt-patch";

const schema = s.obj({ vector: s.vec() });
const model1 = Model.create(schema).fork(1);
model1.api.vec(["vector"]).push(s.con("a"));
model1.api.vec(["vector"]).push(s.con("b"));
model1.api.vec(["vector"]).push(s.con("c"));

const model2 = Model.create(schema).fork(2);
model2.api.vec(["vector"]).push(s.con("x"));
model2.api.vec(["vector"]).push(s.con("y"));
model2.api.vec(["vector"]).push(s.con("z"));

model1.api.flush();

const differ = new JsonPatchDiff();

const patch = differ.diff(
  "",
  model1.api.getSnapshot(),
  model2.api.getSnapshot(),
);

const patcher = new JsonPatch(model1);
console.log(patch);
patcher.apply(patch);

Output:

[
  { op: 'str_ins', path: '/vector/2', pos: 1, str: 'z' },
  { op: 'str_del', path: '/vector/2', pos: 0, len: 1, str: 'c' },
  { op: 'str_ins', path: '/vector/1', pos: 1, str: 'y' },
  { op: 'str_del', path: '/vector/1', pos: 0, len: 1, str: 'b' },
  { op: 'str_ins', path: '/vector/0', pos: 1, str: 'x' },
  { op: 'str_del', path: '/vector/0', pos: 0, len: 1, str: 'a' }
]
<redacted>/node_modules/.pnpm/[email protected][email protected][email protected][email protected][email protected][email protected]/node_modules/json-joy/lib/json-crdt/json-patch/JsonPatch.js:157
            throw new Error('NOT_FOUND');
                  ^

Error: NOT_FOUND
    at JsonPatch.strIns (<redacted>/node_modules/.pnpm/[email protected][email protected][email protected][email protected][email protected][email protected]/node_modules/json-joy/src/json-crdt/json-patch/JsonPatch.ts:150:43)
    at JsonPatch.applyOp (<redacted>/node_modules/.pnpm/[email protected][email protected][email protected][email protected][email protected][email protected]/node_modules/json-joy/src/json-crdt/json-patch/JsonPatch.ts:46:14)
    at <anonymous> (<redacted>/node_modules/.pnpm/[email protected][email protected][email protected][email protected][email protected][email protected]/node_modules/json-joy/src/json-crdt/json-patch/JsonPatch.ts:20:45)
    at ModelApi.transaction (<redacted>/node_modules/.pnpm/[email protected][email protected][email protected][email protected][email protected][email protected]/node_modules/json-joy/src/json-crdt/model/api/nodes.ts:990:9)
    at JsonPatch.apply (<redacted>/node_modules/.pnpm/[email protected][email protected][email protected][email protected][email protected][email protected]/node_modules/json-joy/src/json-crdt/json-patch/JsonPatch.ts:19:20)
    at <anonymous> (<redacted>/src/snapshot-test/vectorPatchMinRepro.ts:30:9)
    at ModuleJob.run (node:internal/modules/esm/module_job:274:25)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:644:26)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5)

Node.js v22.16.0

Is this a bug, or a technical limitation due to the limited set of operations supported by vec nodes?

For context, we are trying to implement 'snapshot' or 'time-travel' functionality. The idea is that a database would persist an object representing a previous state of the CRDT model. A replica could load this object and compute a JSON Patch comparing the current model.view with the previous state, then apply the JSON Patch to the model to achieve the desired state (producing a patch that can be consumed by other replicas). Is there a more straightforward way we could achieve this?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions