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

fs-json-store

v8.0.1

Published

Node.js module for storing JSON data on the file system

Downloads

638

Readme

fs-json-store

is a module for Node.js for storing JSON data on the file system.

GitHub Actions CI

Module simply serializes/deserializes a file using JSON.stringify / JSON.parse functions, so it would not be a great idea to use it with a huge data sets, but it's ok for handling simple needs like storing app settings, etc.

Features

  • Atomic writing. Means data is not going to be corrupted (like getting half-written data file on abnormal program exit or power loss).
  • File system abstraction.
  • Custom adapters support. See fs-json-store-encryption-adapter as an example.
  • Custom validation functions support.
  • Optimistic locking support (versioning).

Technical Notes

Motivation

I needed a simple to use module for storing JSON data on the file system with atomic writing, custom adapters, custom validators, optimistic locking features supported and TypeScript declarations provided. Besides store is supposed to cope with the EPERM errors pseudo-randomly happening on Windows. I didn't find an existing module that would meet the criteria, so a new one has been built.

Getting started

Using JavaScript and Promises:

const {Store} = require("fs-json-store");

const store = new Store({file: "data.json"});

store.write(["hello"])
    .then((data) => store.write([...data, "world"]))
    .then(console.log) // prints "[ 'hello', 'world' ]"
    .then(() => store.read())
    .then(console.log); // prints "[ 'hello', 'world' ]"

Using TypeScript and async/await:

import {Store} from "fs-json-store";

(async () => {
    const store = new Store({file: "data.json"});

    console.log( // prints "[ 'hello', 'world' ]"
        await store.write([...await store.write(["hello"]), "world",]),
    );
    console.log( // prints "[ 'hello', 'world' ]"
        await store.read(),
    );
})();

Store Signatures

constructor(options)

  • options (object, required): an object with the flowing properties:
    • file (string, required): store file path.

    • fs (object, optional, defaults to the built-in node's "fs" wrapper): file system abstraction implementation. There is ony one built-in implementations which is a wrapped node's fs module. Custom abstractions can be added implementing the StoreFs interface.

    • adapter (object, optional): object or class instance with the write(data: Buffer): Promise<Buffer> and read(data: Buffer): Promise<Buffer functions implemented. The custom adapter can be used for example for data encryption/archiving.

    • optimisticLocking (boolean, optional, defaults to false): flag property that toggles optimistic locking feature. With optimistic locking feature enabled stored data must be of the JSON object type, since the system _rev counter property needs be injected into the stored object.

    • validators (array, optional): array of the custom validation functions with the (data) => Promise<string | null> signature, where data is the stored data. Store executes exposed validate method during both read / write operations.

clone([options]): Store<E>

Synchronous method that returns a cloned store instance. See options parameter description in the constructor section, with the only difference is in that all the properties are optional, including the file property.

readable(): Promise<boolean>

Asynchronous method that returns true if file associated with store is readable. It's basically a replacement for the exists method.

readExisting([options]): Promise<E>

Asynchronous method that returns the stored data. Method throws an error if store is not readable(). Optional options argument is an object that can have the optional adapter property. Store uses the explicitly specified adapter overriding the instance's adapter just for the single read method execution (it might be useful for example in case if the data file initially was written using another adapter, so initial reading can be done using explicitly specified adapter).

read([options]): Promise<E | null>

Asynchronous method that returns the stored data. Optional options argument is the same argument as in the readExisting method case.

write(data, [options]): Promise<E>

Asynchronous method that writes data to file and returns the actual data. Optional options.readAdapter argument will be passed to the read method as the options.adapter argument (read method needs to be called during writing in case of the optimistic locking feature enabled).

validate(data, messagePrefix?: string): Promise<void>

Asynchronous method that runs validation functions and throws an error in case of failed validation. Optional messagePrefix parameter will be added as a prefix to the error message.

remove(): Promise<void>

Asynchronous method that removes the store associated file.

Usage Examples

import * as path from "path";
import * as pako from "pako";
import {Store, Model} from "fs-json-store";

const dataDirectory = path.join(process.cwd(), "output", String(Number(new Date())));

const examples = [
    // basic
    async () => {
        const store = new Store({file: path.join(dataDirectory, "basic.json")});

        await store.write([
            ...await store.write(["hello"]),
            "world",
        ]);

        console.log((await store.read()).join(" ")); // prints `hello world`
    },

    // archiving adapter
    async () => {
        const store = new Store({
            file: path.join(dataDirectory, "archiving-adapter.bin"),
            adapter: {
                async read(data) {
                    return Buffer.from(pako.ungzip(data.toString(), {to: "string"}));
                },
                async write(data) {
                    return Buffer.from(pako.gzip(data.toString(), {to: "string"}));
                },
            },
        });

        await store.write({data: "archive data"});

        console.log(JSON.stringify(await store.read())); // prints `{"data":"archive data"}`
    },

    // validation
    async () => {
        interface DataModel extends Partial<Model.StoreEntity> {
            numbers: number[];
        }

        const store = new Store<DataModel>({
            file: path.join(dataDirectory, "validation.json"),
            validators: [
                async ({numbers}) => {
                    if (!numbers || !numbers.length) {
                        return `"numbers" array is not supposed to be empty`;
                    }

                    return null;
                },
            ],
        });

        try {
            await store.write({numbers: []});
        } catch (error) {
            console.log(error); // prints error due to the failed validation
        }

        const storedData = await  store.write({numbers: [1]});
        console.log(JSON.stringify(storedData)); // prints `{"numbers":[1]}`
    },

    // optimistic locking (versioning)
    async () => {
        const store = new Store({
            file: path.join(dataDirectory, "versioning.json"),
            optimisticLocking: true,
        });
        let storedData = await store.write({property: "initial data"});

        console.log(storedData._rev); // prints `0`

        try {
            await store.write({newProperty: "new data"});
        } catch (error) {
            console.log(error); // prints error since `_rev` has not been specified
        }

        try {
            await store.write({newProperty: "new data", _rev: 3});
        } catch (error) {
            console.log(error); // prints error since valid `_rev` has not been specified
        }

        storedData = await store.write({newProperty: "new data", _rev: storedData._rev});
        console.log(storedData._rev); // prints `1`
    },
];

(async () => {
    for (const example of examples) {
        await example();
    }
})();