1.1.3 • Published 6 months ago

deadbeef v1.1.3

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

deadbeef

deadbeef is a unique key generator that will generate a key for any combo of anything--without leaking memory.

deadbeef has some design constraints due to how Javascript engines work (to not to leak memory), and so the unique keys generated won't always be the same length, might be very long, and aren't very good to use as an id that can be persisted. The keys generated will also differ across application runs, and engine contexts (i.e. web-workers). However, given a single running context, the keys are guaranteed to be unique and consistent.

The reason I created deadbeef was to be able to create keys for caching engines. Any use-case that requires consistent unique keys within a single engine context is a perfect fit for deadbeef. If you require unique consistency across machines or different engine contexts, then you are out of luck! Sorry!

Install

npm i --save deadbeef

Use

To use deadbeef, simply throw any number of arguments at it to get a unique key. Keys generated by deadbeef will always be strings. Keys are guaranteed to be unique and consistent for the arguments provided.

For example:

const deadbeef = require('deadbeef');

console.log(deadbeef('hello', 'world'));
// -> '2:string:hello:string:world'

console.log(deadbeef('hello', 'world'));
// -> '2:string:hello:string:world'

console.log(deadbeef('world', 'hello'));
// -> '2:string:world:string:hello'

However, deadbeef is designed such that we shouldn't care what the generated value is. Rather, the importance is in the equality of unique values generated. These can for example be used as keys into a Map.

const deadbeef = require('deadbeef');

console.log(deadbeef('hello', 'world') === deadbeef('hello', 'world'));
// -> true

console.log(deadbeef('hello', 'world') === deadbeef('hello', 'world1'));
// -> false

And the coolest part is that deadbeef will accept absolutely any value to create a key from, even null, undefined, functions, objects, symbols, and no arguments at all!

assert.strictEqual(deadbeef(), deadbeef());
assert.strictEqual(deadbeef(undefined), deadbeef(undefined));
assert.strictEqual(deadbeef(null), deadbeef(null));
assert.strictEqual(deadbeef(1), deadbeef(1));
assert.strictEqual(deadbeef(1, 1), deadbeef(1, 1));
assert.strictEqual(deadbeef(1, 1, true), deadbeef(1, 1, true));

const myFunction = () => {};
assert.strictEqual(deadbeef(myFunction, true), deadbeef(myFunction, true));

Sorted

deadbeef has another method that can be used to generate a unique key: deadbeef.sorted. This sorts the generated ids for each argument before combining them. This means that you can check equality across many elements, even if they are not in order. For example, if you would like to know if two arrays contain the same values (even if not in the same order), you can do so:

const deadbeef = require('deadbeef');

assert.strictEqual(deadbeef.sorted(...[ 1, 2, 3, 4, 5 ]), deadbeef.sorted(...[ 5, 3, 2, 4, 1 ]));

As always, this works with all values, not just numbers or strings. Throw functions at it, objects, symbols, who cares!

Want to check if two objects are identical in their contents? i.e. to check for "prop differences"? No problamo pal!

let obj1 = { test: true, 1: 2, node: '.js' };
let obj2 = { 1: 2, node: '.js', test: true };

assert.strictEqual(
  deadbeef.sorted(
    ...Object.keys(obj1),
    ...Object.values(obj1)
  ),
  deadbeef.sorted(
    ...Object.keys(obj2),
    ...Object.values(obj2)
  )
);

Maps

Since you can generate a unique id for any combo of values, you can easily use a Map, or a standard Object to store metadata on a combination of values, to use in caching engines, or whatever!

function getCachedValue(...values) {
  let cacheKey = deadbeef(...values);
  if (!myCacheMap.has(cacheKey));
    return;

  return myCacheMap.get(cacheKey);
}

function setCachedValue(value, ...values) {
  let cacheKey = deadbeef(...values);
  myCacheMap.set(cacheKey, value);
}

Generating your own ids

deadbeef will look for a Symbol.for('@@deadbeefUniqueID') symbol key on any value it is generating an id for. If this key exists on any given value, and is a function, then deadbeef will call it, and generate a unique id on the return value (which could be another generated id).

deadbeef has a helper property called deadbeef.idSym that is this symbol.

For example, let's say you have User models loaded from the database, which might be different references, but might have the same id. You could add a Symbol.for('@@deadbeefUniqueID') method to your user models, and return the user id, which means deadbeef would always generate the same unique key for your User model instances, even though they might be different instances:

class User {
  constructor(userID) {
    this.id = userID;
  }

  [deadbeef.idSym]() {
    return this.id;
  }
}

// Two of the same user...
// but two different instances
let user1 = new User(123);
let user2 = new User(123);

console.log(deadbeef(user1) === deadbeef(user2));
// -> true

Or, maybe in your case users are unique when they have the same id and organizationID. No problamo gentleladies!

class User {
  constructor(userID, organizationID) {
    this.id = userID;
    this.organizationID = organizationID;
  }

  [deadbeef.idSym]() {
    // Generate a unique key that
    // will generate a unique key!
    return deadbeef(this.id, this.organizationID);
  }
}

// Two of the same user...
// but two different instances
let user1 = new User(42, 0xDEADBEEF);
let user2 = new User(42, 0xDEADBEEF);

console.log(deadbeef(user1) === deadbeef(user2));
// -> true

Generating ids for foreign types

But what if I want to generate an id for a type I don't control? Let's say for example you want to generate ids in the browser for Element types, where the id will be generated based on the elements tag name, and its id attribute. No problem!

deadbeef has the method generateIDFor which will allow you to generate an id for any type of value. The way this works is that you provide generateIDFor a helper method that will check if the type matches, and a generator method that will generate the id for a given type.

In our example, we want to generate an id for Element types in the browser. Here is how you would do that:

const deadbeef = require('deadbeef');

const isElementHelper = (value) => value instanceof Element;

// Set us up to generate ids for Element types
deadbeef.generateIdFor(
  isElementHelper,
  (element) => deadbeef(element.tagName, element.getAttribute('id')),
);

// Are elements the same?
console.log(deadbeef(document.getElementByID('myDiv1')) === deadbeef(document.getElementByID('myDiv1')));

// Don't forget to cleanup!
// To do so, we pass the `helper`
// method to `removeIDGenerator`
deadbeef.removeIDGenerator(isElementHelper);

The sky is the limit

Though this library is super simple, it allows for some really awesome things to be created. Just think of the possibilities!

  1. Check if arrays contain all the same values
  2. Check if arrays contain all of the same values in the same order
  3. Check object equality
  4. Generate cache keys for anything, or any combo of anything
  5. Check if a UI component is the same, and has the same children
  6. Check if two instances of anything are the same
  7. Check if multiple instances are the same
  8. Generate combo-keys for Maps
  9. Check if Elements have the same classes, styles, attributes, or whatever!
  10. ... and much more!

Enjoy! Just for kix and giggles, drop us a line to let us know the creative application you have discovered for deadbeef.

What's with the name bro?

Generating unique ids for anything in javascript is no easy task (if you care about memory leaks... which you should). I couldn't think up any better name, and 0xDEADBEEF has a long history in software development in debugging and memory-related issues. Because of its history, and relation to memory, I figured it was a perfect name for the library.

If you don't like the name, feel free to import it under another name!

const ID = require('deadbeef');

if (ID(0xDEADBEEF, 'hello', 'moo moo') === ID(0xDEADBEEF, 'hello', 'moo moo'))
  console.log('We ❤️️ cows! Moo!');

Requirements

The only requirement for deadbeef to work properly on your javascript engine is the presence and proper implementation of WeakMap. If the engine (or version of the engine) you are using supports WeakMap, then deadbeef will work for you. For NodeJS, this requires version 0.12.0 or later.