UNPKG

mosx

Version:

Multiview observable state management engine

211 lines (171 loc) 5.33 kB
import { PatchPack } from "patchpack" import { ITreeNode, IReversibleJsonPatch, mx } from "../internal" import { Serializer } from "." import { values } from "mobx" export interface ILightSchema { [name: string]: ISchemaItem } export interface ISchemaItem { index: number parent: string props: string[] schema: IPropsSchema } export interface IPropsSchema { [prop: string]: string | IPropSchema } export interface IPropSchema { type: "map" | "set" | "array" items: string } export class LightSerializer extends Serializer { private _schema: ILightSchema = {} public onCreate() { const context = mx.$context const propType = (prop: any) => { if (typeof prop.type === "string") { return prop.type } else if (Array.isArray(prop.type)) { return { type: "array", items: prop.type.map.name, } } else if (typeof prop.type === "function") { return prop.type.name } else if (typeof prop.type === "object") { return { type: "map", items: prop.type.map.name, } } } context.types.forEach(( { name, $mx } ) => { const propSchema: IPropsSchema = {} $mx.props.forEach((prop: any) => { propSchema[prop.key] = propType(prop) }) const meta = context.meta.get(name) if (!meta) { return } this._schema[name] = { index: meta.index, parent: meta.parent, props: $mx.props.map((prop: any) => prop.key), schema: propSchema, } }) } private entryTypePath (entry?: ITreeNode): string[] { const pathArr = [] while (entry) { pathArr.push(entry.meta && entry.meta.type || "") entry = entry.parent } return pathArr.reverse() } /** * light compression * | | 0 | 1 | 2...n+2 | n+3... | * |------|----|----|---------|----------------------------| * | byte | op | n | path | [params, value, oldValue]* | * * op: * 0 -> "add" * 1 -> "replace" * 2 -> "remove" * * n: length of path * * path: * first byte >= 0 -> typeIndex, second byte -> propIndex * first byte < 0 -> param * * "*" - encoded with notepack */ public encodePatch(patch: IReversibleJsonPatch, entry: ITreeNode): Buffer { const pathTypes = this.entryTypePath(entry) const op = ["add", "replace", "remove"].indexOf(patch.op) const pathArr = patch.path.replace(/^\/+|\/+$/g, "").split("/") const pathBuffer: number[] = [] const pathParams: any[] = [] pathArr.forEach((key, i) => { const typeName = pathTypes[i] if (!typeName) { pathBuffer.push(-1) pathParams.push(key) } else { pathBuffer.push(...this.typePropIndex(typeName, key)) } }) // tslint:disable-next-line: no-bitwise const buffer = Buffer.from([op, pathBuffer.length, ...pathBuffer]) if (patch.op !== "remove") { pathParams.push(patch.value) } if (patch.op !== "add" && "oldValue" in patch) { pathParams.push(patch.oldValue) } const paramsBuffer = Buffer.from(PatchPack.encode(pathParams)) return Buffer.concat([buffer, paramsBuffer]) } private typePropIndex (type: string, prop: string) { const meta = mx.$context.meta.get(type) if (!meta) { throw new Error(`Cannot get prop ${prop} index - object type ${type} not found!`) } const props: any[] = meta.ref.$mx.props const index = props.findIndex(({ key }) => key === prop) return [meta.index, index] } public decodePatch (buffer: Buffer): IReversibleJsonPatch { const opIndex = buffer.readInt8(0) const pathLength = buffer.readInt8(1) const pathBuffer = buffer.slice(2, pathLength + 2) const paramsBuffer = buffer.slice(pathLength + 2) const params = PatchPack.decode(paramsBuffer).reverse() let path = "" for (let i = 0; i < pathBuffer.length; i++) { const typeIndex = pathBuffer.readInt8(i) if (typeIndex < 0) { path += "/" + params.pop() } else { const propIndex = pathBuffer.readInt8(++i) path += "/" + this.propName(typeIndex, propIndex) } } const patch: any = { op: ["add", "replace", "remove"][opIndex], path, } if (params.length && patch.op !== "remove") { patch.value = params.pop() } if (params.length && patch.op !== "add") { patch.oldValue = params.pop() } if (params.length) { throw new Error(`Unhandled params: ${params.toString()}`) } return patch } private propName(typeIndex: number, propIndex: number) { const context = mx.$context const typeName = context.index[typeIndex] const meta = context.meta.get(typeName) if (!meta) { throw new Error(`Cannot get prop ${propIndex} name - object type ${typeName} not found!`) } const prop: any = meta.ref.$mx.props[propIndex] if (!prop) { throw new Error(`Cannot get prop ${propIndex} name - property not found!`) } return prop.key } public encodeSnapshot(value: any): Buffer { return PatchPack.encode([this._schema, values]) } public decodeSnapshot(buffer: Buffer): any { const [schema, snapshot] = PatchPack.decode(buffer) this._schema = schema return snapshot } }