
Security News
PolinRider: North Korea-Linked Supply Chain Campaign Expands Across Open Source Ecosystems
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.
jolly-donny
Advanced tools
A TypeScript library for powerful offline data management. Supports pluggable storage providers (LocalStorage, IndexedDB), LINQ-style query API, async CRUD operations, change tracking, and enhanced QueryableArray utilities.
A TypeScript library for powerful offline data management. Supports pluggable storage providers (LocalStorage, IndexedDB, SQLite via WASM), LINQ-style query API, async CRUD operations, change tracking, and enhanced QueryableArray utilities.
✨ Features
Generic Collections – Create type-safe collections for structured data management.
CRUD Operations – Perform standard Create, Read, Update, and Delete actions with ease.
Flexible Querying – Use intuitive query functions to filter and transform data.
Asynchronous Execution – All operations are async to keep your app responsive and non-blocking.
Pluggable Storage Providers – Works with various backends: LocalStorage, IndexedDB, File System API, and SQLite (WASM).
Data Synchronization – Easily sync local data with remote APIs using built-in helpers.
Transformable Fetch – Fetch method supports optional transformation logic or returns raw JSON.
Timeout & Cancellation Support – Use AbortController to handle request timeouts and cancellations.
Built with TypeScript – Enjoy full type safety and enhanced developer tooling.
Easily Extensible – Add your own custom storage providers or extend existing features effortlessly.
npm i jolly-donny
Initialize OfflineStorage
import { OfflineStorage, IndexedDBProvider,LocalStorageProvider, PersistedEntity } from 'your-package-name';
class User extends PersistedEntity<User> {
constructor(public name: string, public age: number) {
super();
}
}
const provider = new IndexedDBProvider();
// const provider = new LocalStorageProvider();
const storage = new OfflineStorage(provider, 'userStorage');
async function initStorage() {
await storage.init();
console.log('Storage initialized');
}
initStorage();
Add and Retrieve Data
async function addAndRetrieveUsers() {
const users = storage.getCollection<User>('users');
const newUser = new User('John Doe', 25);
await users.insert(newUser);
const allUsers = await users.all();
console.log('All users:', allUsers);
const filteredUsers = await users.find(user => user.age > 25);
console.log('Filtered users:', filteredUsers);
const firstUser = await users.first();
console.log('First user:', firstUser);
}
addAndRetrieveUsers();
Update and Delete Data
async function updateAndDeleteUsers() {
const users = storage.getCollection<User>('users');
const firstUser = await users.first();
if (firstUser) {
firstUser.age = 35;
await users.update(firstUser);
console.log('User updated:', firstUser);
await users.delete(firstUser);
console.log('User deleted:', firstUser);
}
}
updateAndDeleteUsers();
Using QueryableArray
async function useQueryableArray() {
const users = storage.getCollection<User>('users');
const usersArray = await users.all();
const filteredUsers = usersArray
.where(user => user.age > 20)
.orderBy(user => user.name)
.take(5);
console.log('Filtered and ordered users:', filteredUsers);
}
useQueryableArray();
Change Tracking
storage.onChange = (change) => {
console.log('Data changed:', change);
};
async function insertUserWithChangeTracking() {
const users = storage.getCollection<User>('users');
const newUser = new User('Jane Smith', 25);
await users.insert(newUser);
}
insertUserWithChangeTracking();
Using the fetch Helper for Data Synchronization
interface IMenu {
dishes: IDish[];
categories: ICategory[];
}
interface IDish {
id: number;
category: number;
title: string;
priceString: string;
sku: number;
description: string;
uuid: string;
showInLimited: boolean;
}
interface ICategory {
id: number;
name: string;
}
class ExtendedDish {
id: number;
category: number;
title: string;
priceString: string;
sku: number;
description: string;
uuid: string;
showInLimited: boolean;
categoryName: string;
get price(): number {
const parsedPrice = parseFloat(this.priceString);
return isNaN(parsedPrice) ? 0 : parsedPrice;
}
set price(value: number) {
this.priceString = value.toFixed(2);
}
}
async function syncAndStoreDishes() {
try {
const extendedDishes = await OfflineStorage.fetch<IMenu, ExtendedDish[]>('fake-api/data.json', (result) => {
const dishes = result.dishes.map((dish) => {
const category = result.categories.find((cat) => cat.id === dish.category);
const extendedDish = new ExtendedDish();
Object.assign(extendedDish, dish);
extendedDish.categoryName = category ? category.name : 'Unknown';
return extendedDish;
});
return dishes;
});
for (const dish of extendedDishes) {
const existingDish = await storage.getCollection<ExtendedDish>('dishStorage').find((d) => d.uuid === dish.uuid);
if (!existingDish || existingDish.length === 0) {
await storage.getCollection<ExtendedDish>('dishStorage').insert(dish);
}
}
const storedDishes = await storage.getCollection<ExtendedDish>('dishStorage').all();
console.log('Stored dishes:', storedDishes);
} catch (error) {
console.error('Synchronization failed:', error);
}
}
syncAndStoreDishes();
Using Formatters with PersistedEntityBuilder
import { PersistedEntity, PersistedEntityBuilder, IFormatter } from 'your-package-name';
class CustomStringFormater implements IFormatter<string> {
format(value: string): string {
return value.toLowerCase(); // always store the value in lower case
}
parse(value: string): string {
return value.toUpperCase(); // always return the value in upper case
}
}
class CustomDateFormater implements IFormatter<Date | null> {
format(value: Date | null): Date | null {
// Store the Date object as is
return value;
}
parse(value: Date | null): Date | null {
if (!value) {
return null;
}
value.setHours(0, 0, 0, 0); // set the time to 00:00:00.000
return value;
}
}
class User extends PersistedEntity<User> {
id: string;
created: number;
lastModified: number;
name: string;
age: number;
birthDate: Date | null = null;
constructor(name: string, age: number) {
super(new PersistedEntityBuilder<User>()
.addFormatter('name', new CustomStringFormater())
.addFormatter('birthDate', new CustomDateFormater())
);
this.id = crypto.randomUUID();
this.created = Date.now();
this.lastModified = Date.now();
this.name = name;
this.age = age;
this.birthDate = new Date();
}
}
// Example usage:
async function exampleFormatters() {
const users = storage.getCollection<User>('users');
const newUser = new User('Example User', 30);
await users.insert(newUser);
const allUsers = await users.all();
console.log('Users with formatters:', allUsers);
}
exampleFormatters();
The FileSystemProvider enables storing and loading collections using the File System Access API (in supported browsers).
import {
FileSystemProvider,
OfflineStorage,
IProviderConfig,
} from 'jolly-donny';
import { IMenu } from './types'; // Your data interfaces
// Optional: define a parser to extract collections from a loaded file
const dishFileParser = (content: string): Map<string, any> => {
const parsed: IMenu = JSON.parse(content);
const collections = new Map<string, any>();
if (Array.isArray(parsed.dishes)) collections.set('dishes', parsed.dishes);
if (Array.isArray(parsed.categories)) collections.set('categories', parsed.categories);
return collections;
};
async function useFileSystemProvider() {
const providerConfig: IProviderConfig = {
parser: dishFileParser,
};
const provider = new FileSystemProvider(providerConfig);
const storage = new OfflineStorage(provider, 'menu');
await storage.init();
const dishes = await storage.getCollection('dishes').all();
console.log('Loaded dishes from file:', dishes);
// Modify and save
if (dishes.length > 0) {
dishes[0].price = 99;
await storage.update('dishes', dishes[0]);
await storage.save(); // Save back to file
}
}
Note: The File System API is available in Chromium-based browsers like Chrome and Edge. When init() is called, the user will be prompted to select a file.
This example demonstrates how to use the SQLiteSchemeProvider for storing and retrieving data in an SQLite database.
import { SQLiteSchemeProvider } from 'jolly-donny';
// Create a new instance of the SQLiteProvider
const provider = new SQLiteSchemeProvider();
// Initialize the provider with a storage name
provider.init('notes-db').then(async () => {
console.log('SQLiteProvider initialized');
// Create a new note object
const newNote = { title: 'First Note', content: 'This is the content of the first note.' };
// Insert the note into the database
await provider.update('notes', newNote);
// Retrieve all notes from the database
const allNotes = await provider.all('notes');
console.log('All notes:', allNotes);
}).catch((error) => {
console.error('Error initializing SQLite provider:', error);
});
FAQs
A TypeScript library for powerful offline data management. Supports pluggable storage providers (LocalStorage, IndexedDB), LINQ-style query API, async CRUD operations, change tracking, and enhanced QueryableArray utilities.
The npm package jolly-donny receives a total of 3 weekly downloads. As such, jolly-donny popularity was classified as not popular.
We found that jolly-donny 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
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.

Security News
Open source attacks are accelerating as AI coding agents pull in dependencies faster, with less human review.

Research
/Security News
Malicious Chrome and Firefox extensions posed as free VPNs while stealing clipboard data through later extension updates.