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

gnomalies

v1.0.2

Published

Reversible, controlled anomaly detection and management ![The Gnomaly](https://raw.githubusercontent.com/c0d3runn3r/gnomalies/master/img/gnome.png) ## Table of Contents

Downloads

173

Readme

Gnomalies

Reversible, controlled anomaly detection and management The Gnomaly

Table of Contents

Example

const Gnomalies = require("../index.js");

// This Anomaly "fixes" lowercase letters.  Skipping normal class syntax for brevity here...
class EvilLowercase extends Gnomalies.Anomaly {}
EvilLowercase.detect = async (system, opts) => system.str.match(/[a-z]/)?true:false;
EvilLowercase.prototype.action = async (system, opts) => system.str = system.str.toUpperCase();

// This one turns sad faces into happy faces
class SadFace extends Gnomalies.Anomaly {}
SadFace.detect = async (system, opts) => system.str.match(/😔/)?true:false;
SadFace.prototype.action = async (system, opts) => system.str = system.str.replace(/😔/g, "🙂");

// Here is a system with things to fix
let system ={ str: "Hello World 😔" };

(async ()=>{

    // Fix the system
    let processor = new Gnomalies.Processor([EvilLowercase, SadFace]);
    await processor.detect(system);  // processor.anomalies now contains relevant anomalies
    await processor.process(system);

    console.log(system.str); // "HELLO WORLD 🙂"

})();

Anomalies

We define a system as "an entity represented by a collection of values". An anomaly is the condition that a subset of those values are in an inferior state that could potentially be transformed into a superior state. The Anomaly class containes a set of functions that allow us to process these anomalies in a systematic way, including fingerprinting and reversion management.

| Wrapper | Override this function | Purpose | |-------------------------------|---------------------------|------------------------------------------------------------| | Anomaly.detect(system, opts)| _detect(system, opts) | Detect an anomaly (static function!) | | .action(system, opts) | _action(system, opts) | Correct the anomaly by changing system | | .revert(system, opts) | _revert(system, opts) | Undo action() | | .evaluate(system, opts) | _evaluate(system, opts) | Evaluate the successfulness of action() or revert() |

As you can see, the workhorse anomaly management functions you should override start with _underscore. Do not call the underscore function directly however; please call the wrapper (no underscore). This allows us to handle events, fingerprinting, etc while keeping your code simple.

When an anomaly is actioned we take before-and-after fingerprints so that we can ensure any reversion is done properly and actually results in a resoration of the original state. We also check fingerprints before starting a reversion (to make sure we are reverting the same thing we actioned). By default, this is all done by taking the SHA256 hash of JSON.serialize(system). Do make sure you set fingerprint_keys to something sensible if you don't want everything about your system fingerprinted.

revert() is intelligent enough to know that a fingerprint matching your preaction fingerprint means no reversion is necessary; overloaded _revert() will never gets called. In that case, the fingerprint is further checked against the postaction fingerprint. A match means that we should be able to perform a clean reversion; _revert() is called. A mismatch means we are in some sort of dirty state; an error is thrown.

Once _revert() is done, the wrapping revert() checks fingerprints to make sure we have a pristine preaction state. A mismatch will throw an error.

Processor

A helpful Processor class is included that can automate much of the anomaly detection, processing, error handling, and reversion work. To use a Processor, pass an array of Anomaly classes (not instances!) to it's constructor. Then call .detect(), passing a system. The Processor will call each anomaly's .detect() method, constructing anomaly objects for those that return true and storing the resulting array in .anomalies. You can then call Processor.process() to .action() each anomaly on the system.

When using processor.process(), errors thrown in anomaly.action() result in an automatic call to anomaly.revert(). If revert() also throws, the anomaly.dirty will be set to true.

Once you have processed all anomalies, you should check to see which ones are paused, and if any of those are dirty.

Anomaly State Graph

A few important departures from the original concept (//delete me after everyone is on board)

  1. Anomaly.state tracks only the state of the anomaly with respect to the action().
  2. Since 'paused' (or ManualReview) doesn't tell us anything about the state, it's actually just a property - not a state. This is .paused.
  3. Activity notifications about work being done are events (e.g. Anomaly#activity -> {"activity" : "detect", "progress" : "45" })
  4. We don't retain any logger references or emit 'error_log' events. Instead we emit Anomaly#log events for all Anomaly.log() calls. If you want to log these in a logger, just consume them from the Processor.

Subclassing notes

  • Most overridden functions are async
  • When you implement _revert(), you are expected to store whatever data you need during _action() in order to perform the reversion. Make sure you override .serialize() and .from_serialized() so that this data gets stored with your class!
  • You should override fingerprint() to call the base method with just the keys that should be used in the fingerprint. Otherwise all keys in system will be fingerprinted by default.
  • You may emit Anomaly#activity with your progress, in percent. Anomaly will emit 0 and 100 for you as bookends automatically.
  • Use the built in Anomaly.log.{debug|info|warn|error}() methods for logging. It is accessable via .history. Each call will also emit Anomaly#log events, making it easy to connect with your external logging engine.

Todo

Acknowledgements

Thanks to Dr. Jonathan Van Schenck for developing the Anomaly Report concept. This project is based on his original class.

MIT License

Copyright (c) 2022 Nova Dynamics

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

API

Modules

Classes

Gnomalies

Gnomalies.Anomaly

Anomaly

To use this class, extend it and override .detect(), .action(), and any other methods you need. If you are saving the anomaly for later use, you should also make sure .serialize() and .deserialize() will meet your needs. If your processor will be using fingerprints, you should also make sure .fingerprint() will meet your needs.

Kind: static class of Gnomalies

new Anomaly(params)

constructor

Throws:

  • Error error on invalid parameter

| Param | Type | Default | Description | | --- | --- | --- | --- | | params | object | | parameters for this object | | [params.history] | array | | the log | | [params.id] | string | | the id | | [params.description] | string | | a short description of this anomaly type | | [params.state] | string | | the state | | [params.paused] | boolean | | whether the anomaly is paused | | [params.dirty] | boolean | | whether the anomaly is dirty | | [params.fingerprint_keys] | array | | the keys that will be used to generate fingerprints, or null for all keys. Expects full paths into the systems to be analyzed, e.g. ["a.name", "b.name.first"] | | [params.fingerprints] | object | | the fingerprints |

anomaly.fingerprints ⇒ object | string | string

fingerprints (getter)

Kind: instance property of Anomaly
Returns: object - fingerprints the fingerprintsstring - fingerprints.preaction the fingerprint before the actionstring - fingerprints.postaction the fingerprint after the action

anomaly.fingerprint_keys ⇒ array.<string>

fingerprint_keys (getter)

This is set in the constructor and can't be changed after instantiation (otherwise fingerprints would stop being reliable)

Kind: instance property of Anomaly
Returns: array.<string> - the keys that are used to generate fingerprints

anomaly.dirty ⇒ boolean

dirty (getter)

This flag is set by the processor to indicate we failed somewhere during a state transition

Kind: instance property of Anomaly
Returns: boolean - true if this anomaly is dirty

anomaly.dirty

dirty (setter)

This flag is set by the processor to indicate we failed somewhere during a state transition

Kind: instance property of Anomaly

| Param | Type | Description | | --- | --- | --- | | dirty | boolean | true if this anomaly is dirty |

anomaly.name ⇒ string

name (getter)

Kind: instance property of Anomaly
Returns: string - the name of this anomaly

anomaly.paused ⇒ boolean

paused (getter)

Kind: instance property of Anomaly
Returns: boolean - true if this is paused

anomaly.id ⇒ string

id (getter)

Kind: instance property of Anomaly
Returns: string - the id for this anomaly

anomaly.history ⇒ array

Log (getter)

Kind: instance property of Anomaly
Returns: array - the log for this anomaly

anomaly.state ⇒ string

state (getter)

Kind: instance property of Anomaly
Returns: string - the state of the anomaly

anomaly.action(system, opts) ⇒ Promise

Action

Performs the action for this anomaly. If the anomaly is not in a preaction state, an error is thrown. When using fingerprints, we take the fingerprint before and after calling _action(). Do not override me. Override _action() instead!

Kind: instance method of Anomaly
Returns: Promise - promise that resolves when the action is complete
Throws:

  • Error error if the anomaly is not in a preaction state or system is undefined

Emits: activity

| Param | Type | Description | | --- | --- | --- | | system | object | the system being analyzed | | opts | object | arbitrary options |

anomaly.revert(system, opts) ⇒ Promise

Revert

Undo the action for this anomaly. If we are in a preaction state and using fingerprints, we verify the fingerprint and then return. Otherwise, we check that we match the postaction fingerprint; call _revert(), and then check the preaction fingerprint. If any of this fails, we throw an error. Do not override me. Override _revert() instead!

Kind: instance method of Anomaly
Returns: Promise - promise that resolves when the reversion is complete
Throws:

  • Error error on error

Emits: activity

| Param | Type | Description | | --- | --- | --- | | system | object | the system being analyzed | | opts | object | arbitrary options |

anomaly.evaluate(system, opts) ⇒ Promise

Evaluate

Evaluate the success of our action. Throw an error if you believe the action or reversion failed. Can also be used for post-action cleanup, statistics, etc. Do not override me. Override _evaluate() instead!

Kind: instance method of Anomaly
Returns: Promise - promise that resolves when the evaluation is complete
Throws:

  • Error error on error

Emits: activity

| Param | Type | Description | | --- | --- | --- | | system | object | the system being analyzed | | opts | object | arbitrary options |

anomaly.toJSON(keys) ⇒ object

toJSON

Serialize this anomaly for storage. By default, we serialize the following keys: #id, #name, #state, #log, #paused, #dirty, #fingerprint_keys

If you override this funciton, you should call super.serialize() and add your own keys to the result.

Kind: instance method of Anomaly
Returns: object - the JSON-ized object
Throws:

  • Error error on error

| Param | Type | Description | | --- | --- | --- | | keys | array.<string> | the keys to serialize |

anomaly.snapshot(system) ⇒ object

Snapshot

Take a snapshot of the system suitable for fingerprinting.

Kind: instance method of Anomaly
Returns: object - the snapshot

| Param | Type | Description | | --- | --- | --- | | system | object | the system being analyzed |

anomaly.fingerprint(system) ⇒ string

Fingerprint

Creates a SHA256 hash of the system's keys and values, using the set of keys that were specified in our constructor. Skips keys that point to functions.

Kind: instance method of Anomaly
Returns: string - the fingerprint as a hex string
Throws:

  • Error error if we can't find a specified key

| Param | Type | Description | | --- | --- | --- | | system | object | the system being analyzed |

anomaly.iterations([state]) ⇒ number

State iterations counter

Show how many times we have transitioned to a given state

Kind: instance method of Anomaly
Returns: number - the number of times we have transitioned to this state

| Param | Type | Default | Description | | --- | --- | --- | --- | | [state] | string | "&quot;postaction&quot;" | the state to count |

anomaly.pause(reason)

pause

Pause this anomaly

Kind: instance method of Anomaly
Emit: Anomaly#pause

| Param | Type | Description | | --- | --- | --- | | reason | string | the reason for the pause |

anomaly.resume(reason)

resume

Resume this anomaly

Kind: instance method of Anomaly
Emit: Anomaly#resume

| Param | Type | Description | | --- | --- | --- | | reason | string | the reason for the resume |

Anomaly.allowed_states ⇒ array

allowed_states (getter)

Kind: static property of Anomaly
Returns: array - the allowed states

Anomaly._detect()

Placeholder functions - these don't do anything until they are overidden Function signature matches that of their non-underscored wrappers

Kind: static method of Anomaly

Anomaly.detect(system, opts) ⇒ Promiose.<boolean>

Detect an anomaly. Don't override me. Override _detect() instead!

Kind: static method of Anomaly
Returns: Promiose.<boolean> - true if an anomaly is detected
Throws:

  • Error error on error

| Param | Type | Description | | --- | --- | --- | | system | object | the system being analyzed | | opts | object | arbitrary options |

Gnomalies.Processor

AnomalyProcessor

Detect and process anomalies in a queue. Call the constructor with an array of Anomaly classes, then await .detect() and .process()

Kind: static class of Gnomalies

new Processor([classes])

Create a new Processor

Returns: Processor - the new Processor
Throws:

  • Error if classes is not an array

| Param | Type | Default | Description | | --- | --- | --- | --- | | [classes] | Array.<Anomaly> | [] | An array of Anomaly classes we may detect |

processor.anomalies ⇒ Array.<Anomaly>

Get all anomalies

Kind: instance property of Processor
Returns: Array.<Anomaly> - the queue of anomalies

processor.classes ⇒ Array.<Anomaly>

Get all classes

Kind: instance property of Processor
Returns: Array.<Anomaly> - all Anomaly classes

processor.reset() ⇒ Processor

Reset the set of anomalies

Kind: instance method of Processor
Returns: Processor - this for chaining
Emits: reset

processor.serialize() ⇒ string

Serialize all anomalies

Kind: instance method of Processor
Returns: string - the serialized anomalies

processor.deserialize(data) ⇒ Processor

Deserialize anomalies into our .anomalies property

Kind: instance method of Processor
Returns: Processor - this for chaining
Throws:

  • Error error on error

| Param | Type | Description | | --- | --- | --- | | data | string | The serialized anomalies |

processor.anomalies_with_state(state) ⇒ Array.<Anomaly>

Get anomalies with a particular state

Kind: instance method of Processor
Returns: Array.<Anomaly> - anomalies with the specified state
Throws:

  • Error if state is invalid

| Param | Type | Description | | --- | --- | --- | | state | string | The state to filter on |

processor.detect(system, opts)

Detect anomalies in a system

Adds anomalies to the queue

Kind: instance method of Processor

| Param | Type | Description | | --- | --- | --- | | system | object | The system to detect anomalies in | | opts | object | Options to pass to the detect() methods |

processor.process(system, opts) ⇒ Promise

Process all anomalies

Processes all anomalies in the queue from the preaction state to the resolved state (if possible). May call .action(), .evaluate(), .revert() Events are bubbled up from each anomaly. Anomalies that fail between states will be paused and will be set to .dirty state

Kind: instance method of Processor
Returns: Promise - resolves when all anomalies are processed
Emits: log, state, pause, resume, activity

| Param | Type | Description | | --- | --- | --- | | system | object | The system to detect anomalies in | | opts | object | Options to pass to the methods (action() etc) |

processor.process_one() ⇒ Promise.<(Anomaly|null)>

Process one anomaly

Finds the first (by order) anomaly with a state of "preaction" and processes it to the resolved state (if possible). May call .action(), .evaluate(), .revert() Events are bubbled up. Anomalies that fail between states will be paused and will be set to .dirty state

Kind: instance method of Processor
Returns: Promise.<(Anomaly|null)> - the anomaly processed, or null if there are no anomalies to process. You can check for success by checking the anomaly itself.
Emits: log, state, pause, resume, activity

Key

Key

A rich representation of a key extracted from an object

Kind: global class

new Key(name, type, [path], [value])

Constructor

| Param | Type | Default | Description | | --- | --- | --- | --- | | name | string | | the name of the key | | type | string | | the type of thing pointed to by the key | | [path] | string | null | the parent path | | [value] | any | | the value of the key |

key.fullname ⇒ string

Get the full name (path) of the key, e.g. "a.b.0.name"

Kind: instance property of Key
Returns: string - the full name of the key

key.name ⇒ string

Get the name of the key, e.g. "name"

Kind: instance property of Key
Returns: string - the name of the key

key.type ⇒ string

Get the type of the key, e.g. "string"

Kind: instance property of Key
Returns: string - the type of the key

key.path ⇒ string

Get the path of the key, but not its own name: e.g. "a.b.0"

Kind: instance property of Key
Returns: string - the path of the key

key.value ⇒ any

Get the value pointed at by the key (if any) in the original object

Kind: instance property of Key
Returns: any - the value of the key

key.compare(other) ⇒ number

Compare another key to this key

Kind: instance method of Key
Returns: number - -1 if this < other, 0 if this == other, 1 if this > other

| Param | Type | Description | | --- | --- | --- | | other | Key | the other key |

Key.compare(a, b) ⇒ number

Compare two keys (for sorting). This is just a string comparison of .fullname. Future work could be to make this sort number-like array elements numerically, e.g. "a.b.2" should be before "a.b.10"

Kind: static method of Key
Returns: number - -1 if a < b, 0 if a == b, 1 if a > b

| Param | Type | Description | | --- | --- | --- | | a | Key | the first key | | b | Key | the second key |

KeyExtractor

KeyExtractor

Extract all keys from an object

Kind: global class

KeyExtractor.extract(obj) ⇒ array.<Key>

Extract all keys from an object

This function extracts a set of deep keys (like "a.b.0.name") from a nested collection of objects, arrays, Maps and Sets.
Map and Set are converted to Object and Array, respectively, before processing. This results in map keys being stringified.
In other words, the keys "0" and 0 are not distinguishable in the result of this function.

Kind: static method of KeyExtractor
Returns: array.<Key> - an array of keys

| Param | Type | Description | | --- | --- | --- | | obj | Object | an array, map, set, or object whose paths need extraction |