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

specmap

v0.5.12

Published

A pluggable object mutator

Downloads

42

Readme

SpecMap

A pluggable, recursive, async, JavaScript object resolver.

Built to resolve swagger specs. But in a generic way.

ES5 compliant, you might need a polyfill for older browsers.

This lib uses commonjs modules, suitable for nodejs, webpack, browserify, or other web bundler.

It was designed to be light, and so has few dependencies. There is room for optimizations. Particularly to make things immutable. (I've avoided including the entire immutable js lib, despite how fun it is. Looking forward to a immutable-lite version :smile:)

The idea

So we give SpecMap some JSON, a bunch of plugins and we get some JSON out.

Plugins

A plugin is a function/object or the specmap property of any given object. A plugin will receive (Array, SpecMapInstance) as arguments. The array will be an array of patches, to work with. And the SpecMap instance is the current instance of SpecMap, which includes some important methods. Each plugin only handles stuff it hasn't seen before to make it faster.

Plugin order

Plugins run in the order you define. But they work a little differently, than you are perhaps used to.

  • The first plugin will run and (possibly) return some patches.
  • The first plugin will run again over the patches it just returned.
  • Once the first plugin is stable and doesn't return any patches it moves to the second plugin
  • The second plugin will return some patches.
  • The first plugin will run again, on the patches coming from the second plugin.
  • It'll continue like this (first plugin gets to see all the new patches, then the second, etc) until no plugins return patches. Then we're done.

This is the recursive part of SpecMap.

In summation each plugin will get a chance to work with each new patch. Each patch will first be seen by the first plugin, then the second, etc.

Patches

To accomplish all this, we work in patches. Each plugin will receive a bunch of patches and it can return more patches. A patch is an object like this... {op: 'add', path: ['over', 'here'], value: 'something'} It says, add this value to this path. These JSON-Patches are supported...

  • add
  • remove
  • replace
  • merge (not official JSON-Patch)

To make life easier, you can use helper methods instead of writing out {op: 'merge', path...}. They can be found over in require('specmap/lib'). And used like this... lib.add(['some', 'path'], {something: true})

More Patches

Those are the normal patches, there are a few more interesting ones...

  • Error, not a patch. Just any instance of Error, which will be collected and spat out at the end for the user to deal with.
  • Context: {type: 'context', path: Path, value: Any}, add some context for other plugins (or your own) to read about later on.
    • This is quite useful in the JSON-Refs plugin, where we record which document the ref came from. So that we can resolve relative refs.
    • You'll see an example on how to use later on.
  • Promised Patch: {...any patch, whose value is a promise}, these patches are the async part. Which get put into a special queue, and as soon as they resolve they get put back into the normal patch queue for plugins to handle
    • TODO: allow .get to fetch promised values.

Instance methods

Plugins need a way to access the current state of the spec, and that cannot be done with patches, (unless you know a way?). We expose a .get method and a .getContext method for fetching state.

Example:

// Some plugin.
function(patches, specmap) {
  console.log(specmap.get(['some', 'path'])) // Whatever is at /some/path inside the current state.
  specmap.getContext(['some','path']) // Some context for this path.. merged with its parents context. So you have *entire* the context for this path. Pretty cool.0
}

Caution...

  • plugins will iterate over every new patch, including its own. This means you can run into an infinite loop.
    • I hope to fix this by creating an invariant. A plugin can only run if the state hasn't changed.

And because its late, and I'm starting to see funny shapes. Here is a working test to see how it comes together.

Ok, how to use it... (Copy/paste whats below.. it'll work :))

var expect = require('expect')
var mapSpec = require('specmap')

it('should download some data, and mutate the new stuff', function(){
  this.timeout(10 * 1000)

  return mapSpec({
    spec: {
      hello: 'world',
      'deps': {
        $ref: 'https://rawgit.com/ponelat/specmap/master/package.json#/dependencies' // a JSON-Ref... https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03
      }
    },
    plugins: [
      mapSpec.plugins.refs, // JSON-Ref plugin

      // Pluins can be functions. We pass in the new patches, and the specmap instance
      function (patches, specmap) {
        // patches, all the /mutative/ patches (add, merge, remove, replace)
        return specmap.forEachNewPrimitive(patches, function (val, key, fullPath) { // Helper to iterate over the patches
          if (key === 'js-yaml' && specmap.get(fullPath) !== 'latest') { // We're working on make this easier, because every plugin runs over every change, and we don't want an endless loop
            var patch = specmap.replace(fullPath, 'latest') // Use latest js-yaml :)
            // patch is the atom of the system. We have helpers to build them for you
            // patch = {op: replace', path: [.., 'js-yaml'], value: 'latest'}
            return patch
          }
        })
      }
    ]
  }).then(function (res) {
    expect(res).toEqual({
      errors: [], // Hopefully :)
      spec: {
        hello: 'world',
        'deps': {
          // "js-yaml": "^3.5.4", when the Refs plugin added the 'deps', it introduced new patches. Which our function handled
          "js-yaml": "latest", // Whoop! We mutated this!
          "object-assign": "^4.0.1",
          "path-loader": "^1.0.1",
          "promise-polyfill": "^3.1.0"
        }
      }
    })
  })
})