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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@leverege/ignition-skeletons

v1.4.0

Published

An ignition plugin library to create skeleton directories

Readme

API Indexer

This library will analyze the blueprints and construct a a node path matching system that allows the attributes in a data update to be formatted for the ElasticSearch index update.

Install

npm install --save @leverege/api-indexer

Usage

Before the indexer can be used, the Indexer.groovy script must be uploaded to Elastic Search.

const scriptId = 'indexer'
await ScriptFile.uploadIndexerScripts( client, scriptId )

Next, create a Blueprints store to manage the Blueprint objects. The source is used to retrieve the blueprint data and normally invokes Api library to get them.

const source = {
  get( projectId, id ) {
    return api.project( projectId ).blueprint( id ).get()
  }
}

const blueprints = new Blueprints( { source } )

Once you have this, retrieve the Blueprint from it when you need to process a deviceDataEventMsg. Invoke the BlueprintIndexer to get back an IndexRequest. This request can then be given to IndexUpdate to run.

// This is an efficient way to get the blueprint if it is ready for use without having to await.
const bp = blueprints.getIfReady( blueprintId ) || await blueprints.get( projectid, blueprintId )

// Invoke the indexer and create the Bulk commands
const opts = { indexUpdated : true, writeDataMetadata : false }
const idxReq = BlueprintIndexer.createIndex( pathValueArray, time, bp, opts ) // time could also be { time, source : { ... } }
const bulk = IndexUpdate.createBulkCommands( { id : deviceId, index : deviceIndex, idxReq, scriptId } )

// Invoke ElasticSearch to index it
const br = await ES.bulk( { refresh : true, body : bulk } )

Simple Indexing

Attributes in the blueprint will are indexed will be placed into the idx field on the document. For non-collection attributes (attributes whose paths contain no * or @ symbols), there is a simple mapping between these fields.

| Attribute Type | Extension | |----------------|-----------| | boolean | -b | | int | -n | | number | -n | | percent | -n | | <unit> | -n | | string | -s | | enum | -k | | resourceEnum | -k | | date | -d | | timestamp | -d | | geoPoint | -gp | | geoShape | -gs | | geoJson | -gs | | point | -cp | | shape | -cs | | version | -v | | ip | -ip |

A few other fields are indexed as well, with these extension

| Field | Extension | |-------|-----------| | UUID reference such as parentIds, blueprintIds, etc | -k | | keyword | -k | | Time a field was updated | -u |

When a request to set blueprint attributes occurs, the result indexed fields will look something like this:

// Path / Value request
const msg = [
  { path : 'name', value : 'Stephen' },
  { path : 'favoriteNumber', value : 12 },
  { path : 'geo/position', value : { lat : 34.0, lon : -78.0 } }
]

const idx = blueprintIndexer.createIndex( msg, time, blueprint )
expect( idx.getIndex() ).to.deep.equal( {
  'name-s' : 'Stephen',
  'name-u' : time,
  'favoriteNumber-n' : 12,
  'favoriteNumber-u' : time,
  geo : {
    'position-gp' : { lat : 34.0, lon : -78.0 },
    'position-u' : time
  }
} )

The -u extensions represent the time when the field was set.

Attribute Primitive Arrays

An attribute with the path name values* indicate that multiple values can be stored in an array at the field name values. Because ElasticSearch supports arrays natively, setting of the values* attribute in its entirety results in a simple set, as descripted above. The extension ( -n, -s, etc ) is based on the attribute's type according the table above. For example:

const msg = [ { path : 'values', value : [ 14, 17, 20 ] } ]

const idx = blueprintIndexer.createIndex( msg, time, blueprint )

expect( idx.getIndex() ).to.deep.equal( {
  'values-n' : [ 14, 17, 20 ],
  'values-u' : time
} )

Although not recommended, You can update a single element in this array. If you need to update only one field in the array and you do not know thw values of the other elements in the array, you an use path with an index in it:

const msg = [ { path : 'values/1', value : 18 } ]

This will result in a scripting action instead of a simple ElasticSearch index option. See Scripting definitions below for a description. When this is called, the array must be able to hold the index specified. If an array index of 10 is given, elements zero through nine will be set to null if they do not already exist.

Attribute Primitive Maps

An attribute with the path name ending in @ (such as values@) indicate that multiple values can be stored in a map at the field name values. In order to support this, the extension type for the root is appended with -attrmap, and a Nested object array is used. Each element in the array contain a key, value-<ext> and value-u. If the attribute is set in its entirety, this can be inserted using the simple, non-scripted mechanism. For example:

const msg = [ { path : 'values', value : { battery1 : 100, battery2 : 97.7 } } ]

const idx = blueprintIndexer.createIndex( msg, time, blueprint )

expect( idx.getIndex() ).to.deep.equal( {
  'values-attrmap' : [ 
    { key : 'battery1', 'value-n' : 100, 'value-u' : time }
    { key : 'battery2', 'value-n' : 97.7, 'value-u' : time }
  ],
  'values-u' : time
} )

Like the array, the Map can also be updated individually.

const msg = [ { path : 'values/battery2', value : 81.2 } ]

Setting a key to null will remove the entry completely from the attribute map list.

Map Collections

Map collections are paths whose non-leaf path contain the @ at the end of a path name. For example, a set of attributes such as:

  { name : 'Module Battery', path : 'module@/battery', type : 'percent' },
  { name : 'Module Temp', path : 'module@/temperature', type : 'temperature' }

indicates that ther can be many modules, each with a name (the key) and battery and temperature. The data for this might look like:

{
  module : {
    engine : { battery : 92, temperature : 70 },
    fan : { battery : 45, temperature : 50 },
  }
}

In the idx field in elastic search, this is store in a nested array and would look like:

{
  idx : {
    module-map : [
      { key : 'engine', 'battery-n' : 92, 'battery-u' : <time>, 'temperature-n' : 70, 'temperature-u' : <time> },
      { key : 'fan', 'battery-n' : 45, 'battery-u' : <time>, 'temperature-n' : 50, 'temperature-u' : <time> }
    ]
  }
}

The map can be updated in its entirety, or at one key in its entirety, or at individual fields in the map. The path values would look like:

// The entire map
{ 
  path : 'module', 
  value : {
    engine : { battery : 92, temperature : 70 },
    fan : { battery : 45, temperature : 50 },
  }
}

// One key in map
{ 
  path : 'module/fan', 
  value : { battery : 40, temperature : 49 },
}

// Individual field
{ path : 'module/engine/battery', value : 91 }

To remove the key from the map, a path of moduel/<key> with a value of null must be sent. This will completely free up the indexing space for that key. Setting all attributes in the key to null will still retain the field updated timestamps for each attribute.

Array Collections

Array collections are paths whose non-leaf path contain the * at the end of a path name. For example, a set of attributes such as:

  { name : 'Array Battery', path : 'fields*/battery', type : 'percent' },
  { name : 'Array Temp', path : 'fields*/temperature', type : 'temperature' }

indicates that ther can be many modules, each with a battery and temperature. The data for this might look like:

{
  fields : [
    { battery : 92, temperature : 70 },
    { battery : 45, temperature : 50 },
  ]
}

In the idx field in elastic search, this is store in a nested array and would look like:

{
  idx : {
    fields-arr : [
      { index : 0, 'battery-n' : 92, 'battery-u' : <time>, 'temperature-n' : 70, 'temperature-u' : <time> },
      { index : 1, 'battery-n' : 45, 'battery-u' : <time>, 'temperature-n' : 50, 'temperature-u' : <time> }
    ]
  }
}

Like the map, the array can be updated in its entirety, or at one key in its entirety, or at individual fields in the map. The path values would look like:

// The entire map
{ 
  path : 'fields', 
  value : [
    { battery : 92, temperature : 70 },
    { battery : 45, temperature : 50 },
  ]
}

// One key in map
{ 
  path : 'fields/0', 
  value : { battery : 40, temperature : 49 },
}

// Individual field
{ path : 'fields/0/battery', value : 91 }

Unlike the map however, setting a value to null will insert a null into the array instead of removing it and shifting indices. If you wish to accomplish the 'shift' behavior, you will need to set the entire array yourself.

{ path : 'fields/0/battery', value : null }

will result in

{ fields : [ null, { battery : 45, temperature : 50 }, ] }

Scripts

EnsureGroup

  • create the HashMap if not already there
  • Holds commands against the group

EnsureArray

  • create the ArrayList if not already there
  • Holds commands against the group

EnsureMap

  • create the Array if not already there
  • Holds commands against the group

EnsureKey

  • Makes sure the Map's key exists
  • Holds commands against the key

EnsureIndex

  • Makes sre the Array's key exists
  • Holds commands against the entry

SetValue

  • Sets the key to the given value

Data representations and undefined attributes

The data object stored in the Elastic Search document is meant to closely match the form of the data. In particular, Maps are represented as JSON object, and the type extensions are removed. Because the path/value object methods allow setting of fields at various levels in the object hierarchy, the data object might need to be scripted as well. Further, the data might contain data that is not defined in the blueprint itself. There are a few special situations that arise from this that need to be addressed.

Map vs Array

Consider the follow path/value objects. (Please note, these in the )

{ path : 'value', value : [ 4, 5, 6 ] }

{ path : 'value/0', value : 1 },

{ path : 'value/bar', value : 2 }

The way this would be handled is that value is initially an array, the array is then updated, and then on the last set, the value with key bar to 2. For data that does not have attributes defined, this behavior will follow Firebase's behavior, which will be to promote the array to a map. The indices will be used as keys in the new map.

For attribute paths that are defined as arrays, the array will not be promoted and Elastic Search will reject the change.