Skip to content

Commit 9b6112d

Browse files
committed
feat(json-crdt): 🎸 add JSON Patch+ str_del operation support
1 parent 858ae48 commit 9b6112d

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

src/json-crdt/json-patch/JsonPatchDraft.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {ObjectType} from '../types/lww-object/ObjectType';
66
import {UNDEFINED_ID} from '../../json-crdt-patch/constants';
77
import {toPath} from '../../json-pointer/util';
88
import type {Model} from '../model';
9-
import type {Operation, OperationAdd, OperationRemove, OperationReplace, OperationMove, OperationCopy, OperationTest, OperationStrIns} from '../../json-patch';
9+
import type {Operation, OperationAdd, OperationRemove, OperationReplace, OperationMove, OperationCopy, OperationTest, OperationStrIns, OperationStrDel} from '../../json-patch';
1010

1111
export class JsonPatchDraft {
1212
public readonly draft = new Draft();
@@ -26,6 +26,7 @@ export class JsonPatchDraft {
2626
case 'copy': this.applyCopy(op); break;
2727
case 'test': this.applyTest(op); break;
2828
case 'str_ins': this.applyStrIns(op); break;
29+
case 'str_del': this.applyStrDel(op); break;
2930
}
3031
}
3132

@@ -117,6 +118,17 @@ export class JsonPatchDraft {
117118
builder.insStr(node.id, after, op.str);
118119
}
119120

121+
public applyStrDel(op: OperationStrDel): void {
122+
const path = toPath(op.path);
123+
const {node} = this.model.api.str(path);
124+
const {builder} = this.draft;
125+
const length = node.length();
126+
if (length <= op.pos) return;
127+
const after = node.findId(op.pos);
128+
const deletionLength = Math.min(op.len ?? op.str!.length, length - op.pos);
129+
builder.del(node.id, after, deletionLength);
130+
}
131+
120132
private get(steps: Path): unknown {
121133
if (!steps.length) return this.model.toView();
122134
else {

src/json-crdt/json-patch/__tests__/JsonPatch.str.spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,71 @@ const testCases: TestCase[] = [
6666
],
6767
doc2: {foo: [{bar: 'baz!'}]},
6868
},
69+
{
70+
name: 'can delete a single char',
71+
doc1: 'a',
72+
patches: [
73+
[{op: 'str_del', path: [], pos: 0, len: 1}]
74+
],
75+
doc2: '',
76+
},
77+
{
78+
name: 'can delete from already empty string',
79+
doc1: '',
80+
patches: [
81+
[{op: 'str_del', path: [], pos: 0, len: 1}]
82+
],
83+
doc2: '',
84+
},
85+
{
86+
name: 'can delete at the end of string',
87+
doc1: 'ab',
88+
patches: [
89+
[{op: 'str_del', path: [], pos: 1, len: 1}]
90+
],
91+
doc2: 'a',
92+
},
93+
{
94+
name: 'can delete at the beginning of string',
95+
doc1: 'ab',
96+
patches: [
97+
[{op: 'str_del', path: [], pos: 0, len: 1}]
98+
],
99+
doc2: 'b',
100+
},
101+
{
102+
name: 'can delete in the middle of string',
103+
doc1: 'abc',
104+
patches: [
105+
[{op: 'str_del', path: [], pos: 1, len: 1}]
106+
],
107+
doc2: 'ac',
108+
},
109+
{
110+
name: 'can delete multiple chars',
111+
doc1: '1234',
112+
patches: [
113+
[{op: 'str_del', path: [], pos: 1, len: 2}],
114+
[{op: 'str_del', path: [], pos: 1, len: 5}],
115+
],
116+
doc2: '1',
117+
},
118+
{
119+
name: 'handles deletion beyond end of string',
120+
doc1: '1234',
121+
patches: [
122+
[{op: 'str_del', path: [], pos: 1111, len: 2}],
123+
],
124+
doc2: '1234',
125+
},
126+
{
127+
name: 'can delete a string in object',
128+
doc1: {foo: '123'},
129+
patches: [
130+
[{op: 'str_del', path: '/foo', pos: 1, len: 2}],
131+
],
132+
doc2: {foo: '1'},
133+
},
69134
];
70135

71136
for (const {only, name, doc1, doc2, patches, throws} of testCases) {

0 commit comments

Comments
 (0)