
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
easy-persist
Advanced tools
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
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.
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});
}
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
}
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
}
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']
}
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!'));
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']
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.
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
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
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"
FAQs
Zero dependencies extensible storage library for Typescript.
We found that easy-persist demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.