handle escaping and honor root pointer (#52)

* handle escaping and honor root pointer

* Update client/lib/json-patch-lib.ts

* Update client/lib/json-patch-lib.ts

Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>
This commit is contained in:
Matt Hill
2022-10-03 10:56:50 -06:00
committed by GitHub
parent 00564ca1ca
commit 6c3079786f
2 changed files with 33 additions and 10 deletions

View File

@@ -25,12 +25,12 @@ export type Operation<T> =
export function getValueByPointer<T extends Record<string, T>>( export function getValueByPointer<T extends Record<string, T>>(
data: T, data: T,
pointer: string, path: string,
): any { ): any {
if (pointer === '/') return data if (!path) return data
try { try {
return jsonPathToKeyArray(pointer).reduce((acc, next) => acc[next], data) return arrayFromPath(path).reduce((acc, next) => acc[next], data)
} catch (e) { } catch (e) {
return undefined return undefined
} }
@@ -40,11 +40,33 @@ export function applyOperation<T>(
doc: DBCache<Record<string, any>>, doc: DBCache<Record<string, any>>,
{ path, op, value }: Operation<T> & { value?: T }, { path, op, value }: Operation<T> & { value?: T },
) { ) {
doc.data = recursiveApply(doc.data, jsonPathToKeyArray(path), op, value) doc.data = recursiveApply(doc.data, arrayFromPath(path), op, value)
} }
export function jsonPathToKeyArray(path: string): string[] { export function arrayFromPath(path: string): string[] {
return path.split('/').slice(1) return path
.split('/')
.slice(1)
.map(p =>
// order matters, always replace "~1" first
p.replace(new RegExp('~1', 'g'), '/').replace(new RegExp('~0', 'g'), '~'),
)
}
export function pathFromArray(args: Array<string | number>): string {
if (!args.length) return ''
return (
'/' +
args
.map(a =>
String(a)
// do not change order, "~" needs to be replaced first
.replace(new RegExp('~', 'g'), '~0')
.replace(new RegExp('/', 'g'), '~1'),
)
.join('/')
)
} }
function recursiveApply<T extends Record<string, any> | any[]>( function recursiveApply<T extends Record<string, any> | any[]>(

View File

@@ -10,8 +10,9 @@ import {
} from 'rxjs' } from 'rxjs'
import { import {
applyOperation, applyOperation,
arrayFromPath,
getValueByPointer, getValueByPointer,
jsonPathToKeyArray, pathFromArray,
} from './json-patch-lib' } from './json-patch-lib'
export class PatchDB<T extends { [key: string]: any }> { export class PatchDB<T extends { [key: string]: any }> {
@@ -133,12 +134,12 @@ export class PatchDB<T extends { [key: string]: any }> {
filter(({ sequence }) => !!sequence), filter(({ sequence }) => !!sequence),
take(1), take(1),
switchMap(({ data }) => { switchMap(({ data }) => {
const path = args.length ? `/${args.join('/')}` : '' const path = pathFromArray(args)
if (!this.watchedNodes[path]) { if (!this.watchedNodes[path]) {
const value = getValueByPointer(data, path) const value = getValueByPointer(data, path)
this.watchedNodes[path] = { this.watchedNodes[path] = {
subject: new BehaviorSubject(value), subject: new BehaviorSubject(value),
pathArr: jsonPathToKeyArray(path), pathArr: arrayFromPath(path),
} }
} }
return this.watchedNodes[path].subject return this.watchedNodes[path].subject
@@ -183,7 +184,7 @@ export class PatchDB<T extends { [key: string]: any }> {
} }
private updateWatchedNodes(revisionPath: string, data: T): void { private updateWatchedNodes(revisionPath: string, data: T): void {
const r = jsonPathToKeyArray(revisionPath) const r = arrayFromPath(revisionPath)
Object.entries(this.watchedNodes).forEach(([path, { pathArr }]) => { Object.entries(this.watchedNodes).forEach(([path, { pathArr }]) => {
if (startsWith(pathArr, r) || startsWith(r, pathArr)) { if (startsWith(pathArr, r) || startsWith(r, pathArr)) {
this.updateWatchedNode(path, data) this.updateWatchedNode(path, data)