npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

merge-helper

v1.0.0

Published

A safe, deterministic object merge algorithm

Downloads

3

Readme

Merge Helper

Tiny conflict resolution algorithm

About

Merge helper is an implementation of gunDB's deterministic conflict resolution system, implemented as a microservice.

Merge helper does not merge for you. It tells you

  • which fields should be updated
  • which fields are outdated
  • which fields should be deferred

leaving the rest up to you. That way, you can alert the user if their changes are being overwritten, you can add middleware that emits changefeeds, or you can add past states to a journal. The point is it gives you more control and flexibility.

The algorithm is deterministically eventually consistent. No matter what order the updates arrive in, so long as each has an author-relative timestamp, each computer will reach the exact same conclusion every time without syncing to a remote authority.

Usage

The library itself is lightweight, and can be downloaded via npm.

npm:

$ npm install merge-helper --save

Once downloaded, you can require it from your project. It exports a function.

const merge = require('merge-helper')

const current = {
	hello: {
		state: Date.now() - 100,
		value: 'potato',
	},
}

const update = {
	hello: {
		state: Date.now(),
		value: 'world!',
	},
}

const result = merge(current, update)

console.log('Result:', result)
/*
	Result: {
		updates: {
			hello: {
				state: 1467567468878,
				value: 'world!',
			},
		},
		historical: {},
		deferred: {},
	}
*/

There was a lot in that example. Let's break it down...

Current State vs Update

When a value is changed, a timestamp must be attached relative to it's author, otherwise a merge will be only as effective as Object.assign. This is the format merge helper is expecting:

{
	"hello": {
		"value": "world",
		"state": 1467567468878
	},
	"temperature": {
		"value": 78,
		"state": 1467567468123
	}
}

You pass two of those formatted objects:

  • the first is the object you're merging into
  • the second object is the update.

Merge helper returns an object with merge instructions. This includes the fields that need to be updated, and the fields that should be updated at a later point in time.

Let's take a look at an example:

// Our current state
const current = {
	hello: {
		value: 'world',
		state: new Date('2012').getTime(),
	},
	answer: {
		value: 42,
		state: Date.now(),
	}
}

// An incoming update
const update = {
	hello: {
		value: 'cool person',
		state: Date.now(),
	},
	answer: {
		value: 'message from the future',
		state: new Date('2050').getTime(),
	},
}

const result = merge(current, update)

console.log('Result:', result)
/*
	// Here's what merge helper gives us:
	Result: {
		historical: {},

		// Safe to merge this one.
		updates: {
			hello: {
				value: 'cool person',
				state: 1467567468878,
			}
		},

		// Don't merge this one yet...
		deferred: {
			// The year 2050!
			answer: 2524608000000
		}
	}
*/

If one of your values is an object, you'll need to pass it a unique ID. Since every feature of an object can change, there needs to be something constant to measure against. It can be any primitive, so long as it's consistent.

Warning: if two separate objects share a unique ID and state, no winner can be decided, so one is picked arbitrarily. If this matters in your program, make sure no two objects share a UID (a case where this wouldn't matter is Date objects, whose ID might be their timestamp. It doesn't matter which wins since they both convey the same information.)

{
	"value": { "data": true },
	"state": 1467567468878,
	"UID": "The object's unique id"
}

If an object is passed without a UID, a TypeError will be thrown.

Deferred Updates

This is a feature of HAM, gunDB's conflict resolution engine.

Your users' computer clocks are not always accurate. Some are slow, some are fast, and others are changed maliciously. Algorithms that say "the most recent update wins" are susceptible to time traveler attacks. Try setting your clock 10 years in the future, now your updates will win for the next 10 years. That's not a good thing.

Our solution is to embrace updates from the future, not by merging them, but normalizing them. If an update says it's 10 years ahead of your time, wait 10 years to merge it. This has some interesting and useful side effects, but they're beyond the scope of this section.

Important note: Never persist deferred updates to disk. Instead, keep them volatile, keep them in memory. The further into the future an update is, the more likely it is to be malicious, and the less you want it anywhere near your database. If an update is 5 minutes into the future, it'll probably make it to your database. But as that number grows, the more volatile the update, and the less likely it is to ever merge. If real-time is a concern, you can use client-server time sync to normalize your users' clocks.

Bonus feature! If your attackers are persistent, and want to schedule millions of updates a year in the future that hit you all at once, you're using up memory, and your server is more likely to crash, losing all their malicious updates. Nobody wants their server going down, but you've just eliminated an obscure database vulnerability and moved it into DDoS territory, which has far more tooling, support, and broader application benefits.

When using client-server time sync, attackers and users are brought to a middle ground, eliminating the notion of "future". This is generally a good idea.

Support

If you have questions about merge helper, feel free to ask on our gitter channel (ask for me as @PsychoLlama).