Skip to content

Commit eb8dfa8

Browse files
Copilotstreamich
andcommitted
Complete refactoring: remove encode() methods from all AbstractOp subclasses and create standalone encodeOperationToMsgpack function
Co-authored-by: streamich <9773803+streamich@users.noreply.github.com>
1 parent 6f55471 commit eb8dfa8

32 files changed

+310
-266
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import {MsgPackEncoderFast as EncoderMessagePack} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoderFast';
22
import type {Op} from '../../op';
3+
import {encodeOperationToMsgpack} from './encodeOperationToMsgpack';
34

45
export class Encoder extends EncoderMessagePack {
56
public encode(patch: Op[]): Uint8Array {
67
this.writer.reset();
78
this.encodeArrayHeader(patch.length);
89
const length = patch.length;
9-
for (let i = 0; i < length; i++) patch[i].encode(this);
10+
for (let i = 0; i < length; i++) encodeOperationToMsgpack(patch[i], this);
1011
return this.writer.flush();
1112
}
1213
}
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack';
2+
import type {AbstractOp} from '../../op/AbstractOp';
3+
import {OPCODE} from '../../constants';
4+
5+
/**
6+
* Standalone function to encode operations to MessagePack format.
7+
* This replaces the instance method approach with a centralized switch statement.
8+
*/
9+
export function encodeOperationToMsgpack(op: AbstractOp, encoder: IMessagePackEncoder, parent?: AbstractOp): void {
10+
switch (op.code()) {
11+
case OPCODE.add: {
12+
const addOp = op as any;
13+
encoder.encodeArrayHeader(3);
14+
encoder.writer.u8(OPCODE.add);
15+
encoder.encodeArray(addOp.path as unknown[]);
16+
encoder.encodeAny(addOp.value);
17+
break;
18+
}
19+
20+
case OPCODE.remove: {
21+
const removeOp = op as any;
22+
const hasOldValue = removeOp.oldValue !== undefined;
23+
encoder.encodeArrayHeader(hasOldValue ? 3 : 2);
24+
encoder.writer.u8(OPCODE.remove);
25+
encoder.encodeArray(removeOp.path as unknown[]);
26+
if (hasOldValue) encoder.encodeAny(removeOp.oldValue);
27+
break;
28+
}
29+
30+
case OPCODE.replace: {
31+
const replaceOp = op as any;
32+
const hasOldValue = replaceOp.oldValue !== undefined;
33+
encoder.encodeArrayHeader(hasOldValue ? 4 : 3);
34+
encoder.writer.u8(OPCODE.replace);
35+
encoder.encodeArray(replaceOp.path as unknown[]);
36+
encoder.encodeAny(replaceOp.value);
37+
if (hasOldValue) encoder.encodeAny(replaceOp.oldValue);
38+
break;
39+
}
40+
41+
case OPCODE.copy: {
42+
const copyOp = op as any;
43+
encoder.encodeArrayHeader(3);
44+
encoder.writer.u8(OPCODE.copy);
45+
encoder.encodeArray(copyOp.path as unknown[]);
46+
encoder.encodeArray(copyOp.from as unknown[]);
47+
break;
48+
}
49+
50+
case OPCODE.move: {
51+
const moveOp = op as any;
52+
encoder.encodeArrayHeader(3);
53+
encoder.writer.u8(OPCODE.move);
54+
encoder.encodeArray(moveOp.path as unknown[]);
55+
encoder.encodeArray(moveOp.from as unknown[]);
56+
break;
57+
}
58+
59+
case OPCODE.test: {
60+
const testOp = op as any;
61+
encoder.encodeArrayHeader(testOp.not ? 4 : 3);
62+
encoder.writer.u8(OPCODE.test);
63+
encoder.encodeArray(parent ? testOp.path.slice(parent.path.length) : (testOp.path as unknown[]));
64+
encoder.encodeAny(testOp.value);
65+
if (testOp.not) encoder.writer.u8(1);
66+
break;
67+
}
68+
69+
case OPCODE.str_ins: {
70+
const strInsOp = op as any;
71+
encoder.encodeArrayHeader(4);
72+
encoder.writer.u8(OPCODE.str_ins);
73+
encoder.encodeArray(strInsOp.path as unknown[]);
74+
encoder.encodeNumber(strInsOp.pos);
75+
encoder.encodeString(strInsOp.str);
76+
break;
77+
}
78+
79+
case OPCODE.str_del: {
80+
const strDelOp = op as any;
81+
const hasStr = typeof strDelOp.str === 'string';
82+
encoder.encodeArrayHeader(hasStr ? 4 : 5);
83+
encoder.writer.u8(OPCODE.str_del);
84+
encoder.encodeArray(strDelOp.path as unknown[]);
85+
encoder.encodeNumber(strDelOp.pos);
86+
if (hasStr) {
87+
encoder.encodeString(strDelOp.str as string);
88+
} else {
89+
encoder.writer.u8(0);
90+
encoder.encodeNumber(strDelOp.len!);
91+
}
92+
break;
93+
}
94+
95+
case OPCODE.flip: {
96+
const flipOp = op as any;
97+
encoder.encodeArrayHeader(2);
98+
encoder.writer.u8(OPCODE.flip);
99+
encoder.encodeArray(flipOp.path as unknown[]);
100+
break;
101+
}
102+
103+
case OPCODE.inc: {
104+
const incOp = op as any;
105+
encoder.encodeArrayHeader(3);
106+
encoder.writer.u8(OPCODE.inc);
107+
encoder.encodeArray(incOp.path as unknown[]);
108+
encoder.encodeNumber(incOp.inc);
109+
break;
110+
}
111+
112+
case OPCODE.split: {
113+
const splitOp = op as any;
114+
encoder.encodeArrayHeader(splitOp.props ? 4 : 3);
115+
encoder.writer.u8(OPCODE.split);
116+
encoder.encodeArray(splitOp.path as unknown[]);
117+
encoder.encodeNumber(splitOp.pos);
118+
if (splitOp.props) encoder.encodeObject(splitOp.props as Record<string, unknown>);
119+
break;
120+
}
121+
122+
case OPCODE.merge: {
123+
const mergeOp = op as any;
124+
encoder.encodeArrayHeader(mergeOp.props ? 4 : 3);
125+
encoder.writer.u8(OPCODE.merge);
126+
encoder.encodeArray(mergeOp.path as unknown[]);
127+
encoder.encodeNumber(mergeOp.pos);
128+
if (mergeOp.props) encoder.encodeAny(mergeOp.props);
129+
break;
130+
}
131+
132+
case OPCODE.extend: {
133+
const extendOp = op as any;
134+
const {deleteNull} = extendOp;
135+
encoder.encodeArrayHeader(deleteNull ? 4 : 3);
136+
encoder.writer.u8(OPCODE.extend);
137+
encoder.encodeArray(extendOp.path as unknown[]);
138+
encoder.encodeObject(extendOp.props);
139+
if (deleteNull) encoder.writer.u8(1);
140+
break;
141+
}
142+
143+
case OPCODE.contains: {
144+
const containsOp = op as any;
145+
const ignoreCase = containsOp.ignore_case;
146+
encoder.encodeArrayHeader(ignoreCase ? 4 : 3);
147+
encoder.writer.u8(OPCODE.contains);
148+
encoder.encodeArray(parent ? containsOp.path.slice(parent.path.length) : (containsOp.path as unknown[]));
149+
encoder.encodeString(containsOp.value);
150+
if (ignoreCase) encoder.writer.u8(1);
151+
break;
152+
}
153+
154+
case OPCODE.defined: {
155+
const definedOp = op as any;
156+
encoder.encodeArrayHeader(2);
157+
encoder.writer.u8(OPCODE.defined);
158+
encoder.encodeArray(parent ? definedOp.path.slice(parent.path.length) : (definedOp.path as unknown[]));
159+
break;
160+
}
161+
162+
case OPCODE.ends: {
163+
const endsOp = op as any;
164+
const ignoreCase = endsOp.ignore_case;
165+
encoder.encodeArrayHeader(ignoreCase ? 4 : 3);
166+
encoder.writer.u8(OPCODE.ends);
167+
encoder.encodeArray(parent ? endsOp.path.slice(parent.path.length) : (endsOp.path as unknown[]));
168+
encoder.encodeString(endsOp.value);
169+
if (ignoreCase) encoder.writer.u8(1);
170+
break;
171+
}
172+
173+
case OPCODE.in: {
174+
const inOp = op as any;
175+
encoder.encodeArrayHeader(3);
176+
encoder.writer.u8(OPCODE.in);
177+
encoder.encodeArray(parent ? inOp.path.slice(parent.path.length) : (inOp.path as unknown[]));
178+
encoder.encodeArray(inOp.value);
179+
break;
180+
}
181+
182+
case OPCODE.less: {
183+
const lessOp = op as any;
184+
encoder.encodeArrayHeader(3);
185+
encoder.writer.u8(OPCODE.less);
186+
encoder.encodeArray(parent ? lessOp.path.slice(parent.path.length) : (lessOp.path as unknown[]));
187+
encoder.encodeNumber(lessOp.value);
188+
break;
189+
}
190+
191+
case OPCODE.matches: {
192+
const matchesOp = op as any;
193+
const ignoreCase = matchesOp.ignore_case;
194+
encoder.encodeArrayHeader(ignoreCase ? 4 : 3);
195+
encoder.writer.u8(OPCODE.matches);
196+
encoder.encodeArray(parent ? matchesOp.path.slice(parent.path.length) : (matchesOp.path as unknown[]));
197+
encoder.encodeString(matchesOp.value);
198+
if (ignoreCase) encoder.writer.u8(1);
199+
break;
200+
}
201+
202+
case OPCODE.more: {
203+
const moreOp = op as any;
204+
encoder.encodeArrayHeader(3);
205+
encoder.writer.u8(OPCODE.more);
206+
encoder.encodeArray(parent ? moreOp.path.slice(parent.path.length) : (moreOp.path as unknown[]));
207+
encoder.encodeNumber(moreOp.value);
208+
break;
209+
}
210+
211+
case OPCODE.starts: {
212+
const startsOp = op as any;
213+
const ignoreCase = startsOp.ignore_case;
214+
encoder.encodeArrayHeader(ignoreCase ? 4 : 3);
215+
encoder.writer.u8(OPCODE.starts);
216+
encoder.encodeArray(parent ? startsOp.path.slice(parent.path.length) : (startsOp.path as unknown[]));
217+
encoder.encodeString(startsOp.value);
218+
if (ignoreCase) encoder.writer.u8(1);
219+
break;
220+
}
221+
222+
case OPCODE.test_type: {
223+
const testTypeOp = op as any;
224+
encoder.encodeArrayHeader(3);
225+
encoder.writer.u8(OPCODE.test_type);
226+
encoder.encodeArray(parent ? testTypeOp.path.slice(parent.path.length) : (testTypeOp.path as unknown[]));
227+
encoder.encodeArray(testTypeOp.type);
228+
break;
229+
}
230+
231+
case OPCODE.test_string: {
232+
const testStringOp = op as any;
233+
encoder.encodeArrayHeader(testStringOp.not ? 5 : 4);
234+
encoder.writer.u8(OPCODE.test_string);
235+
encoder.encodeArray(parent ? testStringOp.path.slice(parent.path.length) : (testStringOp.path as unknown[]));
236+
encoder.encodeNumber(testStringOp.pos);
237+
encoder.encodeString(testStringOp.str);
238+
if (testStringOp.not) encoder.writer.u8(1);
239+
break;
240+
}
241+
242+
case OPCODE.test_string_len: {
243+
const testStringLenOp = op as any;
244+
encoder.encodeArrayHeader(testStringLenOp.not ? 4 : 3);
245+
encoder.writer.u8(OPCODE.test_string_len);
246+
encoder.encodeArray(
247+
parent ? testStringLenOp.path.slice(parent.path.length) : (testStringLenOp.path as unknown[]),
248+
);
249+
encoder.encodeNumber(testStringLenOp.len);
250+
if (testStringLenOp.not) encoder.writer.u8(1);
251+
break;
252+
}
253+
254+
case OPCODE.type: {
255+
const typeOp = op as any;
256+
encoder.encodeArrayHeader(3);
257+
encoder.writer.u8(OPCODE.type);
258+
encoder.encodeArray(parent ? typeOp.path.slice(parent.path.length) : (typeOp.path as unknown[]));
259+
encoder.encodeString(typeOp.value);
260+
break;
261+
}
262+
263+
case OPCODE.undefined: {
264+
const undefinedOp = op as any;
265+
encoder.encodeArrayHeader(2);
266+
encoder.writer.u8(OPCODE.undefined);
267+
encoder.encodeArray(parent ? undefinedOp.path.slice(parent.path.length) : (undefinedOp.path as unknown[]));
268+
break;
269+
}
270+
271+
case OPCODE.and: {
272+
const andOp = op as any;
273+
const path = parent ? andOp.path.slice(parent.path.length) : andOp.path;
274+
encoder.encodeArrayHeader(3);
275+
encoder.writer.u8(OPCODE.and);
276+
encoder.encodeArray(path as unknown[]);
277+
const length = andOp.ops.length;
278+
encoder.encodeArrayHeader(length);
279+
for (let i = 0; i < length; i++) encodeOperationToMsgpack(andOp.ops[i], encoder, andOp);
280+
break;
281+
}
282+
283+
case OPCODE.not: {
284+
const notOp = op as any;
285+
encoder.encodeArrayHeader(3);
286+
encoder.writer.u8(OPCODE.not);
287+
encoder.encodeArray(parent ? notOp.path.slice(parent.path.length) : (notOp.path as unknown[]));
288+
const length = notOp.ops.length;
289+
encoder.encodeArrayHeader(length);
290+
for (let i = 0; i < length; i++) encodeOperationToMsgpack(notOp.ops[i], encoder, notOp);
291+
break;
292+
}
293+
294+
case OPCODE.or: {
295+
const orOp = op as any;
296+
encoder.encodeArrayHeader(3);
297+
encoder.writer.u8(OPCODE.or);
298+
encoder.encodeArray(parent ? orOp.path.slice(parent.path.length) : (orOp.path as unknown[]));
299+
const length = orOp.ops.length;
300+
encoder.encodeArrayHeader(length);
301+
for (let i = 0; i < length; i++) encodeOperationToMsgpack(orOp.ops[i], encoder, orOp);
302+
break;
303+
}
304+
305+
default:
306+
throw new Error(`Unknown operation code: ${op.code()}`);
307+
}
308+
}

src/json-patch/op/AbstractOp.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type {Path} from '@jsonjoy.com/json-pointer';
33
import type {OpType} from '../opcodes';
44
import type {Operation} from '../types';
55
import type {OPCODE} from '../constants';
6-
import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack';
76

87
export abstract class AbstractOp<O extends OpType = OpType> {
98
public readonly from?: Path;
@@ -15,5 +14,4 @@ export abstract class AbstractOp<O extends OpType = OpType> {
1514
abstract apply(doc: unknown): {doc: unknown; old?: unknown};
1615
abstract toJson(parent?: AbstractOp): Operation;
1716
abstract toCompact(parent: undefined | AbstractOp, verbose: boolean): CompactOpBase;
18-
abstract encode(encoder: IMessagePackEncoder, parent?: AbstractOp): void;
1917
}

src/json-patch/op/OpAdd.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type {OperationAdd} from '../types';
44
import {find, type Path, formatJsonPointer} from '@jsonjoy.com/json-pointer';
55
import {OPCODE} from '../constants';
66
import {clone as deepClone} from '@jsonjoy.com/util/lib/json-clone/clone';
7-
import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack';
87

98
/**
109
* @category JSON Patch
@@ -51,11 +50,4 @@ export class OpAdd extends AbstractOp<'add'> {
5150
const opcode: OPCODE_ADD = verbose ? 'add' : OPCODE.add;
5251
return [opcode, this.path, this.value];
5352
}
54-
55-
public encode(encoder: IMessagePackEncoder) {
56-
encoder.encodeArrayHeader(3);
57-
encoder.writer.u8(OPCODE.add);
58-
encoder.encodeArray(this.path as unknown[]);
59-
encoder.encodeAny(this.value);
60-
}
6153
}

src/json-patch/op/OpAnd.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type {OperationAnd, PredicateOperation} from '../types';
55
import {OPCODE} from '../constants';
66
import {type Path, formatJsonPointer} from '@jsonjoy.com/json-pointer';
77
import type {AbstractOp} from './AbstractOp';
8-
import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack';
98

109
/**
1110
* @category JSON Predicate
@@ -48,14 +47,4 @@ export class OpAnd extends AbstractSecondOrderPredicateOp<'and'> {
4847
this.ops.map((op) => op.toCompact(this, verbose)),
4948
];
5049
}
51-
52-
public encode(encoder: IMessagePackEncoder, parent?: AbstractOp) {
53-
const path = parent ? this.path.slice(parent.path.length) : this.path;
54-
encoder.encodeArrayHeader(3);
55-
encoder.writer.u8(OPCODE.and);
56-
encoder.encodeArray(path as unknown[]);
57-
const length = this.ops.length;
58-
encoder.encodeArrayHeader(length);
59-
for (let i = 0; i < length; i++) this.ops[i].encode(encoder, this);
60-
}
6150
}

0 commit comments

Comments
 (0)