Files @ 7d26fa1ed4e7
Branch filter:

Location: light9/light9/web/patch.ts

drewp@bigasterisk.com
renames and comments (mostly)
import * as async from "async";
import debug from "debug";
import * as N3 from "n3";
import { NamedNode, Parser, Quad, Writer } from "n3";
const log = debug("patch");

export class Patch {
  // immutable
  private _allPredsCache?: Set<string>;
  private _allSubjsCache?: Set<string>;
  constructor(private dels: Quad[], private adds: Quad[]) {
    this.validate();
  }
  private validate() {
    // todo: finish porting this from coffeescript
    [this.adds, this.dels].map((qs: Quad[]) =>
      (() => {
        const result = [];
        for (let q of Array.from(qs)) {
          if (!q.equals) {
            throw new Error("doesn't look like a proper Quad");
          }
          if (!q.subject.id || q.graph.id == null || q.predicate.id == null) {
            throw new Error(`corrupt patch: ${JSON.stringify(q)}`);
          } else {
            result.push(undefined);
          }
        }
        return result;
      })()
    );
  }

  isEmpty() {
    return !this.dels.length && !this.adds.length;
  }

  applyToGraph(g: N3.Store) {
    for (let quad of Array.from(this.dels)) {
      g.removeQuad(quad);
    }
    for (let quad of Array.from(this.adds)) {
      g.addQuad(quad);
    }
  }

  update(other: Patch): Patch {
    // this is approx, since it doesnt handle matching existing quads.
    return new Patch(this.dels.concat(other.dels), this.adds.concat(other.adds));
  }

  summary(): string {
    return "-" + this.dels.length + " +" + this.adds.length;
  }

  async toJsonPatch(): Promise<string> {
    return new Promise((res, rej) => {
      const out: SyncgraphPatchMessage = { patch: { adds: "", deletes: "" } };

      const writeDels = (cb1: () => void) => {
        const writer = new Writer({ format: "N-Quads" });
        writer.addQuads(this.dels);
        writer.end(function (err: any, result: string) {
          out.patch.deletes = result;
          cb1();
        });
      };

      const writeAdds = (cb2: () => void) => {
        const writer = new Writer({ format: "N-Quads" });
        writer.addQuads(this.adds);
        writer.end(function (err: any, result: string) {
          out.patch.adds = result;
          cb2();
        });
      };

      async.parallel([writeDels, writeAdds], (err: any) => res(JSON.stringify(out)));
    });
  }

  containsAnyPreds(preds: NamedNode[]): boolean {
    if (this._allPredsCache === undefined) {
      this._allPredsCache = new Set();
      for (let qq of [this.adds, this.dels]) {
        for (let q of Array.from(qq)) {
          this._allPredsCache.add(q.predicate.value);
        }
      }
    }

    for (let p of Array.from(preds)) {
      if (this._allPredsCache.has(p.value)) {
        return true;
      }
    }
    return false;
  }

  allSubjs(): Set<string> {
    // returns subjs as Set of strings
    if (this._allSubjsCache === undefined) {
      this._allSubjsCache = new Set();
      for (let qq of [this.adds, this.dels]) {
        for (let q of Array.from(qq)) {
          this._allSubjsCache.add(q.subject.value);
        }
      }
    }

    return this._allSubjsCache;
  }
  allPreds(): Set<NamedNode> {
    const ret = new Set<NamedNode>();
    for (let qq of [this.adds, this.dels]) {
      for (let q of Array.from(qq)) {
        if (q.predicate.termType == "Variable") throw "unsupported";
        ret.add(q.predicate);
      }
    }
    return ret;
  }
}

export interface SyncgraphPatchMessage {
  patch: { adds: string; deletes: string };
}

export function patchToDeleteEntireGraph(g: N3.Store) {
  return new Patch(g.getQuads(null, null, null, null), []);
}

export function parseJsonPatch(input: SyncgraphPatchMessage, cb: (p: Patch) => void): void {
  // note response cb doesn't have an error arg.
  const dels: Quad[] = [];
  const adds: Quad[] = [];

  const parseAdds = (cb2: () => any) => {
    const parser = new Parser();
    return parser.parse(input.patch.adds, (error: any, quad: Quad, prefixes: any) => {
      if (quad) {
        return adds.push(quad);
      } else {
        return cb2();
      }
    });
  };
  const parseDels = (cb3: () => any) => {
    const parser = new Parser();
    return parser.parse(input.patch.deletes, (error: any, quad: any, prefixes: any) => {
      if (quad) {
        return dels.push(quad);
      } else {
        return cb3();
      }
    });
  };

  // todo: is it faster to run them in series? might be
  async.parallel([parseAdds, parseDels], (err: any) => cb(new Patch(dels, adds)));
}

// /** @deprecated replace with  p=p.update(p2) */
// export function patchUpdate(p1: Patch, p2: Patch): void {
//   throw "";
// }

// /** @deprecated replace with  p.summary() */
// export function patchSizeSummary(patch: Patch) {
//   throw "";
// }

// /** @deprecated moved to Patch.toJsonPatch */
// export function toJsonPatch(jsPatch: Patch, cb: (jsonString: string) => void): void {
//   throw "";
// }
// /** @deprecated moved to Patch.containsAnyPreds */
// export function patchContainsPreds(patch: Patch, preds: NamedNode[]): boolean {
//   throw "";
// }
// /** @deprecated moved to Patch.allSubjs */
// export function allPatchSubjs(patch: Patch): Set<string> {
//   throw "";
// }