From c6cbda5fd3d8bfbba130e21941a74521b6dcc514 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 5 May 2025 20:17:39 +0200 Subject: [PATCH] start bin_ins operation implementation --- src/json-patch/codec/compact/types.ts | 6 +++ src/json-patch/codec/json/decode.ts | 6 +-- src/json-patch/codec/json/types.ts | 23 ++++++++++ src/json-patch/constants.ts | 4 ++ src/json-patch/op/OpBinIns.ts | 63 +++++++++++++++++++++++++++ src/json-patch/opcodes.ts | 3 ++ 6 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 src/json-patch/op/OpBinIns.ts diff --git a/src/json-patch/codec/compact/types.ts b/src/json-patch/codec/compact/types.ts index ad4dde2f1e..453233b868 100644 --- a/src/json-patch/codec/compact/types.ts +++ b/src/json-patch/codec/compact/types.ts @@ -50,6 +50,7 @@ export type OPCODE_SPLIT = OPCODE.split | 'split'; export type OPCODE_STARTS = OPCODE.starts | 'starts'; export type OPCODE_STR_DEL = OPCODE.str_del | 'str_del'; export type OPCODE_STR_INS = OPCODE.str_ins | 'str_ins'; +export type OPCODE_BIN_INS = OPCODE.bin_ins | 'bin_ins'; export type OPCODE_TEST = OPCODE.test | 'test'; export type OPCODE_TEST_STRING = OPCODE.test_string | 'test_string'; export type OPCODE_TEST_STRING_LEN = OPCODE.test_string_len | 'test_string_len'; @@ -246,6 +247,11 @@ export type CompactStrDelOp = */ export type CompactStrInsOp = [opcode: OPCODE_STR_INS, path: string | Path, pos: number, str: string]; +/** + * @category JSON Patch Extended + */ +export type CompactBinInsOp = [opcode: OPCODE_BIN_INS, path: string | Path, pos: number, bin: Uint8Array]; + /** * @category JSON Patch Extended */ diff --git a/src/json-patch/codec/json/decode.ts b/src/json-patch/codec/json/decode.ts index 2771794b46..cbd240ecb1 100644 --- a/src/json-patch/codec/json/decode.ts +++ b/src/json-patch/codec/json/decode.ts @@ -1,5 +1,3 @@ -import type {Op, PredicateOp} from '../../op'; -import type {Operation} from './types'; import {OpAdd} from '../../op/OpAdd'; import {OpRemove} from '../../op/OpRemove'; import {OpReplace} from '../../op/OpReplace'; @@ -30,8 +28,10 @@ import {OpNot} from '../../op/OpNot'; import {OpMatches} from '../../op/OpMatches'; import {OpType} from '../../op/OpType'; import {toPath} from '@jsonjoy.com/json-pointer'; -import type {JsonPatchOptions} from '../../types'; import {createMatcherDefault} from '../../util'; +import type {Op, PredicateOp} from '../../op'; +import type {Operation} from './types'; +import type {JsonPatchOptions} from '../../types'; export const operationToOp = (op: Operation, options: JsonPatchOptions): Op => { switch (op.op) { diff --git a/src/json-patch/codec/json/types.ts b/src/json-patch/codec/json/types.ts index f18da763e4..71796e5bf6 100644 --- a/src/json-patch/codec/json/types.ts +++ b/src/json-patch/codec/json/types.ts @@ -255,6 +255,8 @@ export interface OperationOr extends OperationBase { export type JsonPatchExtendedOperation = | OperationStrIns | OperationStrDel + | OperationBinIns + | OperationBinDel | OperationFlip | OperationInc | OperationSplit @@ -302,6 +304,27 @@ export interface OperationStrDel extends OperationBase { readonly len?: number; } +/** + * Inserts a `value` blob into a blob at position `pos`. + * + * @category JSON Patch Extended + */ +export interface OperationBinIns extends OperationBase { + readonly op: 'bin_ins'; + readonly pos: number; + readonly bin: Uint8Array; +} + +/** + * @category JSON Patch Extended + */ +export interface OperationBinDel extends OperationBase { + readonly op: 'bin_del'; + readonly pos: number; + readonly bin?: Uint8Array; + readonly len?: number; +} + /** * Flips boolean value to the opposite one. * diff --git a/src/json-patch/constants.ts b/src/json-patch/constants.ts index f3be76b76a..c9d18c1492 100644 --- a/src/json-patch/constants.ts +++ b/src/json-patch/constants.ts @@ -37,4 +37,8 @@ export enum OPCODE { and = 43, not = 44, or = 45, + + // Binary editing. + bin_ins = 60, + bin_del = 61, } diff --git a/src/json-patch/op/OpBinIns.ts b/src/json-patch/op/OpBinIns.ts new file mode 100644 index 0000000000..c8f98421aa --- /dev/null +++ b/src/json-patch/op/OpBinIns.ts @@ -0,0 +1,63 @@ +import {AbstractOp} from './AbstractOp'; +import {OPCODE} from '../constants'; +import {find, type Path, formatJsonPointer} from '@jsonjoy.com/json-pointer'; +import type {CompactBinInsOp, OPCODE_BIN_INS} from '../codec/compact/types'; +import type {OperationBinIns} from '../types'; +import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack'; + +/** + * @category JSON Patch Extended + */ +export class OpBinIns extends AbstractOp<'bin_ins'> { + constructor( + path: Path, + public readonly pos: number, + public readonly bin: Uint8Array, + ) { + super(path); + } + + public op() { + return 'bin_ins' as const; + } + + public code() { + return OPCODE.bin_ins; + } + + public apply(doc: unknown) { + const {val, key, obj} = find(doc, this.path); + if (!(val instanceof Uint8Array)) { + if (val !== undefined) throw new Error('NOT_BIN'); + if (this.pos !== 0) throw new Error('POS'); + } + const bin: Uint8Array = val instanceof Uint8Array ? val : new Uint8Array(0); + const pos = Math.min(this.pos, bin.length); + const result = new Uint8Array(bin.length + this.bin.length); + result.set(bin.slice(0, pos), 0); + result.set(this.bin, pos); + result.set(bin.slice(pos), pos + this.bin.length); + if (obj) (obj as any)[key as any] = result; + else doc = result; + return {doc, old: val}; + } + + public toJson(parent?: AbstractOp): OperationBinIns { + const op: OperationBinIns = { + op: 'bin_ins', + path: formatJsonPointer(this.path), + pos: this.pos, + bin: this.bin, + }; + return op; + } + + public toCompact(parent: undefined | AbstractOp, verbose: boolean): CompactBinInsOp { + const opcode: OPCODE_BIN_INS = verbose ? 'bin_ins' : OPCODE.bin_ins; + return [opcode, this.path, this.pos, this.bin]; + } + + public encode(encoder: IMessagePackEncoder, parent?: AbstractOp) { + throw new Error('Not implemented'); + } +} diff --git a/src/json-patch/opcodes.ts b/src/json-patch/opcodes.ts index 8fc74e164c..78aa13ecbf 100644 --- a/src/json-patch/opcodes.ts +++ b/src/json-patch/opcodes.ts @@ -38,6 +38,9 @@ export type JsonPatchExtendedOpType = // Operations needed for text collaboration. | 'str_ins' | 'str_del' + // Operations needed for binary blob collaboration. + | 'bin_ins' + | 'bin_del' // Operations needed for Slate.js. | 'split' | 'merge'