2.0.1 • Published 7 years ago

data-path-store v2.0.1

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
7 years ago
╔╦╗┌─┐┌┬┐┌─┐  ╔═╗┌─┐┌┬┐┬ ┬  ╔═╗┌┬┐┌─┐┬─┐┌─┐
 ║║├─┤ │ ├─┤  ╠═╝├─┤ │ ├─┤  ╚═╗ │ │ │├┬┘├┤
═╩╝┴ ┴ ┴ ┴ ┴  ╩  ┴ ┴ ┴ ┴ ┴  ╚═╝ ┴ └─┘┴└─└─┘

(Ascii-art generated by patorjk.com)

2.0 Upgrade Notice

This version smooths out a lot and makes a lot of enhancements and consistencies. It also inadvertently makes a few breaking changes which were unavoidable but minimized best as possible.

In a nutshell, callbacks were removed in favor of Promises, errors now throw and have become standardized, and many methods and properties have become static and read-only.

For complete details on all the changes read CHANGELOG.md and for an easy upgrade checkout the file UPGRADE_2.0.md which lists easy steps for upgrading from 1.0 to 2.0.

Overview


In a nutshell, data-path-store is a local memory cache that runs with your program or package. Its simple and lightweight but is unique in that it stores data in memory by a path referred to as an endpoint url which can later be retrieved by the endpoint url directly or the 2 segments that makeup the endpoint url, namespace and path.

Furthermore, if the retrieved data is a function, it can instead optionally be executed and the result of the function returned instead.

Data path store was created with the idea of having a central place to store data that needs to be accessed outside of individual classes or code. Such data may even be available in some form or another through a local or remote service.

With this in mind, its structured so that each class or code who wants to "publish" data to the store can be given a shared or exclusive piece of the store to work with. Data from the entire store is readable but only writeable from within the piece the class or code is given.

Furthermore, other code may subscribe to store-wide changes to be notified when new updates are published or existing data changes.

Read below to see what all this class can do.

Creating your store

The store is a memory cache for storing data intended to be accessed in many places. Therefore you usually only need a single store unless you need or want more than one.

var store = require("data-path-store");
store = new store();

With this theres quite a bit we can do but firstly lets discuss some very basics.

Namespaces

All data in a store is stored in endpoint urls, the first part of an endpoint URL is a namespace which can be anything, ultimately the only two restrictions is:

  • it must be a string and
  • it cannot have double slashes in it (//).

However usually a namespace will limit itself to url characters and optionally contain single slashes for separation, if any.

Namespaces are like categories of information that are separate from other information, they can also be enforced so that a piece of code cannot write outside of its own namespace.

An empty namespace is considered the root namespace.

Paths

Paths are the second segment, they point to data within the namespace. An empty path is a root path.

Like namespaces, paths usually consist of url characters and nested paths usually are separated by a single slash. However, like namespaces, the only two requirements are:

  • it must be a string and
  • it cannot have double slashes in it (//).

Parameters

Parameters are a bit unique and are only used in certain cases. They are the last but optional segment and make up the third and final piece. They are also usually stripped off the endpoint url when given to one of the class methods however they do serve a purpose.

  1. When requesting data normally
  2. and the data is a function
  3. and the function takes parameters
  • or when wanting to pass-through information as parameters stick through the request

The parameter segment is there for those reasons, it consists of single slash separated parameters in a single string. The parameters are split by the single slash passed to the function as an array of strings. Therefore parameters have one additional requirement from the others above:

  • it must be a string and
  • it cannot have double slashes in it (//) and
  • a single parameter cannot have a single slash (/) since they are the separator between parameters.

Setting and Getting data

Setting and getting data is easy, to set data use setData which takes a namespace, a path, and the data. Do note that the data store considers a value of undefined to not exist and therefore its the only value which isn't directly settable or gettable.

var store = require("data-path-store");
store = new store();
store.setData("clicheGreetings", "hello", "world");

Despite the single set method, you have a myriad of get methods:

store.getData("clicheGreetings", "hello"); // {Promise}
store.matchData("//clicheGreetings//hello"); // {Promise}
store.getRawData("clicheGreetings", "hello"); // "world"
store.matchRawData("//clicheGreetings//hello"); // "world"

get methods obtain data by providing the namespace and path directly. match methods obtain directly via the endpoint. The raw data obtains the data as-is no matter what it may be while the regular non-raw methods use a Promise system and return much more information back including the ns, path, endpoint, data, parameters, and any error message or data.

Additionally, for non-raw get methods, if the data is undefined the Promise will be rejected with a non-exist error and if the data is a function it will instead be invoked for the actual data passing in any parameters along with a way to resolve or reject the promise making it compatible with non-promise based code.

Getting ES7 Style

If your a fan of ES7 then this meshes well with that

store.setData("clicheGreetings", "hello", "world");
var value = await store.getData("clicheGreetings", "hello");

console.log(value);
// {
//     ns: "clicheGreetings",
//     path: "hello",
//     endpoint: "//clicheGreetings//hello",
//     data: "world",
//     params: undefined
// }

Setting with a function

As mentioned above, theres a lot of flexibility with functions, heres setting and getting functions in a nutshell.

Please note though, this section only applies to non-raw methods, raw methods just return the function as-is.

Heres an example where the function just mirrors what you pass to it:

store.setData("clicheGreetings", "hello", function(params, resolve, reject)
{
    resolve(params);
});

// {...data: "world", ...}
store.getData("clicheGreetings", "hello", "world").then(/*...*/);

 // {...data: "Thor", ...}
store.getData("clicheGreetings", "hello", "Thor").then(/*...*/);

Since we're using Promises the function doesn't have to immediately return.

store.setData("clicheGreetings", "hello", function(params, resolve, reject)
{
    setTimeout(function()
    {
        resolve(params);
    }, 1000);
});

store.getData("clicheGreetings", "hello", "world")
    .then(function(value)
    {
        console.log(value.data); // -> "world"
    });

If the function throws an error or rejects the data for some reason or another, you can grab the error via the normal ways in the Promise system.

store.setData("clicheGreetings", "hello", function(params, resolve, reject)
{
    reject(params)
});

store.getData("clicheGreetings", "hello", "world")
.catch(function(value)
{
    console.log(value.data); // -> "world"
});

Live Updates

The store makes use of the NodeJS event system, to use, simply set a listener to listen for the change event. This event fires anytime data within the store is added or updated given the data is not a function.

Functions are excluded because they contain code that must be run to get the actual value, sometimes that code requires parameters and sometimes it must be run asynchronously. To prevent complications functions are excluded.

You can listen like so:

function coolFunc(val)
{
    console.log(val);

    // {
    //     ns: "clicheGreetings",
    //     path: "hello",
    //     endpoint: "//clicheGreetings//hello",
    //     data: "world"
    // }
}

var store = require("data-path-store");
store = new store();
store.on("change", coolFunc);
store.setData("clicheGreetings", "hello", "world");

// Logs ["clicheGreetings", "hello", "//clicheGreetings//hello", "world"];

You can also see the endpoint created from the given namespace and path which was mentioned earlier.

Data changes

If you create or change the data directly, an update will be issued for any interested callbacks. But if the data is the same and deeply nested data within changes the store has no way of knowing.

For example if you add an object to the store it will trigger an update but not if you add or change a property within that object. Same goes for amending or editing arrays.

In those cases use the dataChanged method which will notify the store that, despite the data being the same, internally it has changed. This can also be used for event timers and such as a nice side-effect.

Interfaces

The class provides 2 interfaces which you can optionally take advantage of.

  • A public interface
  • A protected or exclusive interface

The interfaces are ideal for giving access to the store for a piece of code. The public interface gives read-only access, in other words the get methods and all utility methods. The protected interface gives the public interface in addition to the write methods but locked to a specific namespace.

For example, using the public interface to allow a function to access and read from the store.

var store = require("data-path-store");
store = new store();
store.setData("clicheGreetings", "hello", "world");

function someFunc(store)
{
    store.getData("clicheGreetings", "hello").then(function(val)
    {
        val.data === "world";
    });
}

someFunc(store.publicInterface);

Here we use similar code but grant write access to its own namespace

var store = require("data-path-store");
store = new store();
store.setData("clicheGreetings", "hello", "world");

function someFunc(store)
{
    var helloWorld = store.getData("clicheGreetings", "hello").then(function(val)
    {
        val.data === "world";
        store.setData("tmp", helloWorld); // At //someFunc//tmp === "world"
    });
}

someFunc(store.protectedInterface("someFunc"));

What the latter does is generate a protected (read & write) interface, the write methods are locked to the "someFunc" namespace and theres no getting around this. Because of this you may notice that the setData method no longer requires the namespace parameter.

The public interface offers these methods

  • on
  • generateEndpoint, parseEndpoint, and parseCapturedEndpoint
  • getData & getRawData
  • matchData & matchRawData
  • parseParamString & generateParamString

Since the protected interface is locked into a namespace for writing, you'll find it has many additional convenient methods in addition to all the public interface methods.

  • setData & setOwnData - Both are the same
  • getOwnData & getOwnRawData - These allow skipping of the namespace param
  • dataChanged

Please note it's not fool-proof

The above is just for separation of concerns and code cleanliness. Its nowhere near the level of fool-proof and doesn't try to be as it would overly complicate and bloat the code.

There are currently several loopholes, and while I try to keep the public interface mostly read-only and protected interface full access to their assigned namespace the following loopholes are ones that I'm aware of that have inevitably happened.

  • The public interface can read-only non-primitive data from another namespace and then turn around and modify that data.
  • The this property in on("change") code has full unrestricted access to the entire class. (Looking to possibly fix this one...)

But many other loopholes may be possible, just keep this in mind as the interfaces are more for code cleanliness and separation.

Working with Endpoints

The class comes with several utility methods, among them is generating and parsing endpoints in different ways.

Generating Endpoints

var Store = require("data-path-store");
Store.generateEndpoint("clicheGreetings", "hello");
// -> "//clicheGreetings//hello"

Parsing Endpoints

var Store = require("data-path-store");
Store.parseEndpoint("//clicheGreetings//hello");
// -> {
//     ns: "clicheGreetings",
//     path: "hello"
// }

If you have captured an endpoint url through the provided regex params then, given an array or numbered object properties from the capture groups, you can finish parsing that as well.

var Store = require("data-path-store");
Store.parseCapturedEndpoint({
    0: "clicheGreetings",
    1: "hello"
});

// -> {
//     ns: "clicheGreetings",
//     path: "hello"
// }

Working with Parameters

There are utility methods for parsing and generating parameter strings

var Store = require("data-path-store");
Store.parseParamString("world/world2");
// -> [
//     "world",
//     "world2"
// ]
var Store = require("data-path-store");
Store.generateParamString(["world", "world2"]);
// -> "world/world2"

Errors

This class has strict error checking to ensure things run smoothly. Quite a bit of thought went into the error system to ensure the most consistent and best experience came out from it.

All error types are defined in a static variable called errors. In this is an object where the key names are the error names and the values are the error descriptions.

When an error is thrown somewhere in your program, an instance of Error is created. The instance is given the name and message of the particular error from one of the items in the object and is finally amended with a data property containing arbitrary data most likely an object containing as many properties as possible right before the error occurred.

This way you have access to the stack trace, usable name, helpful description, and known data before or cause of error. To react on different error messages you just simply compare the error name to what your looking for.

var Store = require("data-path-store");
var store = new Store();

try
{
    store.setData(1234, 5678);
}
catch(err)
{
    if(err.name === "INVALID_PARAMS")
    {
        console.log(err.data.ns, err.data.path);
        // -> 1234 5678
    }
}

This package is solid

This package is actively tested with Jasmine and thoroughly documented throughout. It also contains complete JSDoc comments and full online JSDoc generated documentation.

Do you like this package or have an issue?

If this package has helped you I encourage you to follow it, give it a star, or fork it on github. If you have any ideas, suggestions, issues, or ways to improve upon it then let us know over at github by filing an issue.

Contributions are also encouraged and the CONTRIBUTING.md file shows all the details on development and how to contribute back but its mostly just commit changes and send a pull request.

This project is licensed Apache 2.0

http://www.apache.org/licenses/LICENSE-2.0.txt

Run it in your browser

Thanks to TonicDev, you now have a way to run this package right in your browser to test it out. If your on the NPM site you can see the TonicDev link on the right-hand side, otherwise you can always go to the link below.

TonicDev will load up a preset example we provide that you can play around with to get a feel for this package.

https://tonicdev.com/npm/data-path-store

How to develop


Look at the CONTRIBUTING.md file which, apart from contributions, also covers how to develop in case your looking to fork for your own project or for contributing back.

Documentation


Detailed documentation exists for this package, it should already by viewable on clone. Feel free to look through it for any questions you may have. The source code is also heavily documented if you want to look through it as well for answers.

Online docs exist here http://junestoolbox.github.io/data-path-store

Whats New


Check out the CHANGELOG.md file for latest changes and updates

2.0.1

7 years ago

2.0.0

8 years ago

1.0.0

8 years ago