New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

easy-persist

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

easy-persist

Zero dependencies extensible storage library for Typescript.

latest
Source
npmnpm
Version
2.1.0
Version published
Maintainers
1
Created
Source

Easy-persist

Zero dependencies extensible storage library for Typescript.

Easy-persist is compatible with popular validation and caching libraries, but not restricted to any specific option. Anything can be plugged in thanks to duck typing.

npm i easy-persist

Contents

  • Using default storage
  • Using multiple storages
  • Configuration
  • Repositories

Using default storage

Default storage serializes data using JSON.stringify and stores it in data/default.json file.

import { persist, obtain } from 'easy-persist';

async function example() {
    await persist({text: 'Hello, World!'});
    const value = await obtain<{text: string}>();
    console.log(value?.text); // 'Hello, World!'
}

If file doesn't exist, obtain will return undefined.

Using default record

Managing your data is easier using record (to pass it around and let other parts of code update it).

If you need multiple records, see repositories.

import { record } from 'easy-persist';

interface AppConfig {
    foo: string;
    bar: number;
}

async function example() {
    // you can optionally specify default value
    const appConfig = await record<AppConfig>({
        foo: 'hello',
        bar: 42,
    });

    // value can be accessed synchronously once the record was loaded
    console.log(appConfig.value.foo);

    // object values can be partially updated
    await appConfig.update({bar: 123});
}

Using multiple storages

Any number of storages can be created.

In this example, data will be saved to file data/animal.json.

import Persist from 'easy-persist';

interface Animal {
    type: 'cat' | 'dog';
    name: string;
}
const p = new Persist<Animal>('animal');

async function example() {
    await p.set({type: 'cat', name: 'Fluffy'});
    const animal = await p.get();
    console.log(animal?.name); // Fluffy
}

Data validation

There is no data validation by default, but it can be configured (e.g. using zod).

By default data validation only applies to get method, but it can be configured for set, see Configuration.

import Persist from 'easy-persist';
import z from "zod";

const animalSchema = z.object({
    type: z.union([z.literal('cat'), z.literal('dog')]),
    name: z.string(),
});
type Animal = z.infer<typeof animalSchema>;

// Generic type is inferred from the validator
const p = new Persist('animal', { validator: animalSchema.parse });

async function example() {
    await p.set({type: 'dog', name: 'Cookie'});
    const animal: Animal|undefined = await p.get();
    console.log(animal?.name); // Cookie
}

Listing names

import Persist from 'easy-persist';

const foo = new Persist<string>('foo');
const bar = new Persist<string>('bar');

async function example() {
    await foo.set('Hello');
    await bar.set('World');

    const names: string[] = await Persist.getDefaultFactory().listNames(); // or foo.getFactory().listNames()
    console.log(names); // ['foo', 'bar']
}

Configuration

Global defaults can be configured to use with both default storage and other storages.

Storage factory can be changed to affect how and where data is stored.

import Persist, { persist } from 'easy-persist';
import { FileStorageFactory } from 'easy-persist/storage';
import * as yaml from 'yaml';

// Must be called before using default storage
Persist.setDefaults({
    // Set name for default storage
    defaultName: 'greetings',
    // Set file storage in current directory and change format to yaml
    storageFactory: new FileStorageFactory('.', {
        serializer: v => Buffer.from(yaml.stringify(v), 'utf-8'),
        deserializer: b => yaml.parse(b.toString('utf-8')),
        fileExtension: 'yml',
    }),
});

// Will be saved to './greetings.yml'
persist({text: 'Hello, World!'})
    .then(() => console.log('Done!'));

Separate storage configuration

Each storage can be configured separately as well.

import Persist from 'easy-persist';
import { FileStorageFactory } from 'easy-persist/storage';

const animalSchema = z.object({
    type: z.union([z.literal('cat'), z.literal('dog')]),
    name: z.string(),
});

const customFactory = new FileStorageFactory('/opt/my-app-data');
const p = new Persist('animal', {
    validator: animalSchema.parse,
    storageFactory: customFactory,
    validateSet: true, // To validate value on "set" as well
});

// Will be saved to '/opt/my-app-data/animal.json'
p.set({type: 'cat', name: 'Fluffy'})
    .then(() => console.log('Done!'));

// Will fail when validateSet is true
p.set({something: 'else'} as any)
    .catch(() => console.error('Oh, no!'));

// Configured factory can be used directly to list instances with existing data
customFactory.listNames()
    .then(console.log) // ['animal']

Repositories

Repository is a class built around the storage factory to easily manage multiple data items of the same type.

It accepts same config structure as Persist instances and respects default config to create underlying instances.

It operates with Records, which is implemented in an ActiveRecord-flavoured style.

Repositories can cache records in memory (in key,value pairs) if cacheHandler is provided in the config.

Record, which value is not persisted is in ephemeral state. Such records are evicted from cache automatically.

Basic examples

import Persist from 'easy-persist';

type MyType = {
    text: string;
    planet: string;
};
const repo = Persist.getRepo<MyType>(/* optionally, add config here */);

// This will automatically persist the value, unless repo.createEphemeral() is used instead
const record = await repo.create('repo-example', {text: 'Hello, World!', planet: 'Earth'});
console.log(record.value.text); // 'Hello, World!'

// New value can be set and persisted
await record.set({text: 'Hello, Universe!', planet: 'Mars'});
console.log(record.value.text); // 'Hello, Universe!'

// Object values can be partially updated
await record.update({planet: 'Jupiter'});
console.log(record.value); // {text: 'Hello, Universe!', planet: 'Jupiter'}

// Value is no longer persisted and exists only in the record instance
await record.delete();
console.log(record.ephemeral); // true

// Ephemeral record can be persisted again
await record.save();
console.log(record.ephemeral); // false

Bring your own record class

It can be helpful to define custom logic in a record class.

import { AbstractRecord } from "easy-persist/repository";

type ExampleValue = {
    exampleNumber: number,
    exampleString: string,
};

class ExampleRecord extends AbstractRecord<ExampleValue> {
    public async randomiseNumber(): Promise<void> {
        await this.update({
            exampleNumber: Math.floor(Math.random() * 10000),
        });
    }
}

const repo = Persist.getRepo({ recordClass: ExampleRecord });
const record = await repo.create('something', {
    exampleNumber: 42,
    exampleString: 'test',
});
await record.randomiseNumber();
console.log(record.value.exampleNumber); // will output a random number

Cached Repository

All records can be preloaded in memory at once, which allows getting records and iterating over them synchronously.

import Persist from 'easy-persist';

type TextMessage = {
    from: string;
    to: string;
    text: string;
};
const repo = await Persist.preloadRepo<TextMessage>();

// Get by name
const record = repo.get('message-123');

// Filter records by value using callback
const messagesFromBob = repo.filter(v => v.from == 'Bob');

// Find one record by value
const messageFromAlice = repo.find(v => v.to == 'Alice');

// Record creation is still asynchronous, as it will persist the new value
const newRecord = await repo.create('message-456', {
    from: 'Bob',
    to: 'Alice',
    text: 'Hello!',
});

// Or ephemeral record can be created
const newRecord2 = repo.createEphemeral('message-789', {
    from: 'Alice',
    to: 'Bob',
    text: 'Hey',
});
// It won't be discoverable, until it's saved
console.log(repo.get('message-789')); // undefined
await newRecord2.save();
console.log(repo.get('message-789')?.value?.text); // "Hey"

Keywords

backend

FAQs

Package last updated on 10 Jun 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts