Skip to content

Instantly share code, notes, and snippets.

@developit
Last active August 20, 2021 15:03
Show Gist options
  • Save developit/998926ad50b5743ac5a108dfde2fbeb2 to your computer and use it in GitHub Desktop.
Save developit/998926ad50b5743ac5a108dfde2fbeb2 to your computer and use it in GitHub Desktop.
Compute an object diff, then apply it as a patch to an existing object. https://npm.im/object-diff-patch

object-diff-patch

// on the main thread
import { apply } from 'object-diff-patch/apply';

import createWorker from 'workerize-loader!./worker.js';
const worker = createWorker();

const CACHE = new Map();

export async function getThing(name) {
  let obj = CACHE.get(name);
  
  const patch = await worker.getThing(name);
  
  obj = apply(obj, patch); // turn the old object into the new one
  
  CACHE.set(name, obj); // cache so we can apply patches later
  return obj;
}
// worker.js
import { diff } from 'object-diff-patch/diff';

const CACHE = new Map();

export async function getThing(name) {
  const res = await fetch(`/things/${name}`);
  const thing = await res.json();
  
  const old = CACHE.get(name);  // can be null/undefined
  
  const patch = diff(thing, old); // generate the delta object
  
  CACHE.set(name, thing); // store for next call
  
  return patch; // pass only the patch/delta to the main thread
}
/**
* Use this to apply diffs on the main thread.
* newObject = apply(oldObject, patch);
*/
export function apply(obj, diff) {
if (typeof obj !== typeof diff) {
return diff;
}
if (typeof diff === 'object') {
if (Array.isArray(diff)) {
return diff;
}
let out;
if (Array.isArray(obj)) {
out = obj;
for (let i in diff) {
out[i] = apply(obj[i], diff[i]);
}
}
else {
out = obj;
for (let i in diff) {
out[i] = apply(obj[i], diff[i]);
}
}
return out;
}
return diff;
}
/**
* Use this to create a "patch" object in the worker thread.
* patch = diff(newObject, oldObject);
*/
export function diff (obj, old) {
if (typeof obj === 'object') {
const isArray = Array.isArray(obj);
if (!old || typeof old !== 'object' || (isArray !== Array.isArray(old))) {
return obj;
}
if (isArray) {
let out;
let i = 0;
const max = Math.min(obj.length, old.length);
for ( ; i<max; i++) {
const differs = different(obj[i], old[i]);
if (differs) break;
}
// for previously-empty arrays, hint at newness by using an Array
const useArray = old.length === 0;
const offset = obj.length - old.length;
for (let j = obj.length; j-- > i; ) {
const oldJ = j - offset;
if (oldJ >= 0) {
const differs = different(obj[j], old[oldJ]);
if (differs) {
if (!out) out = useArray ? [] : {};
out[j] = diff(obj[j], old[oldJ]);
}
}
else {
if (!out) out = useArray ? [] : {};
out[j] = obj[j];
}
}
return out;
}
let out;
for (let key in obj) {
if (!(key in old) || obj[key] !== old[key]) {
if (!out) out = {};
// `undefined` means removed, missing means unchanged.
const r = diff(obj[key], old[key]);
if (r !== undefined) {
out[key] = r;
}
}
}
for (let key in old) {
if (obj == null || !(key in obj)) {
if (!out) out = {};
// `undefined` means removed, missing means unchanged.
out[key] = undefined;
}
}
return out;
}
else if (obj != old) {
return obj;
}
}
function different (obj, old) {
for (let key in obj) {
if (old == null || !(key in old) || obj[key] !== old[key]) {
return true;
}
}
for (let key in old) {
if (obj == null || !(key in obj)) {
return true;
}
}
return false;
}
export { diff } from './diff.js';
export { apply } from './apply.js';
{
"name": "object-diff-patch",
"version": "0.1.1",
"repository": "gist:998926ad50b",
"homepage": "https://gist.github.com/998926ad50b5743ac5a108dfde2fbeb2",
"main": "index.js",
"module": "index.js",
"type": "module",
"scripts": { "prepack": "mv *object-diff-patch.md README.md", "postpack": "mv README.md *object-diff-patch.md" },
"exports": {
".": "./index.js",
"./diff": "./diff.js",
"./patch": "./patch.js"
},
"author": "Jason Miller <jason@developit.ca>",
"license": "Apache-2.0"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment