Skip to content

Repository files navigation

lino-cache

A cache-manager compatible file-based cache using Links Notation (.lino) format instead of JSON.

Tests npm version License: Unlicense

Features

  • cache-manager compatible - Implements the full cache-manager store interface
  • Links Notation storage - Uses lino-objects-codec for serialization
  • Two storage modes:
    • Folder mode - Each cache key stored in a separate .lino file
    • Single-file mode - All cache entries in one .lino file
  • TTL support - Time-to-live for automatic expiration
  • Atomic cache primitives - Conditional writes, TTL mutation, counters, string mutation, and get-and-mutate operations
  • Multi-runtime - Works with Node.js, Bun, and Deno
  • TypeScript support - Full type definitions included

See the competitor compatibility matrix for an explicit comparison with Redis, Memcached, and Dragonfly and the staged implementation roadmap. The package currently provides semantic compatibility for common cache operations; it does not claim wire-protocol compatibility.

Installation

npm install lino-cache

Quick Start

import { LinoCache, createLinoCache, linoStore } from 'lino-cache';

// Create a cache instance (folder mode by default)
const cache = new LinoCache({
  basePath: '.cache',
  ttl: 60000, // Default TTL: 1 minute
});

// Basic operations
await cache.set('user:1', { name: 'Alice', age: 30 });
const user = await cache.get('user:1');
console.log(user); // { name: 'Alice', age: 30 }

// Delete
await cache.del('user:1');

// Check existence
const exists = await cache.has('user:1'); // false

Storage Modes

Folder Mode (Default)

Each cache key is stored in a separate .lino file. Best for:

  • Large number of cache entries
  • Independent access to cache entries
  • When you need to inspect individual cached values
const cache = new LinoCache({
  mode: 'folder', // Default
  basePath: '.cache',
});

await cache.set('key1', 'value1'); // Creates .cache/key1.lino
await cache.set('key2', 'value2'); // Creates .cache/key2.lino

Single-File Mode

All cache entries stored in one .lino file. Best for:

  • Small number of cache entries
  • When you want all cache data in one file
  • Simpler file management
const cache = new LinoCache({
  mode: 'file',
  basePath: '.cache',
  fileName: 'cache.lino',
});

await cache.set('key1', 'value1'); // Both stored in
await cache.set('key2', 'value2'); // .cache/cache.lino

API Reference

Constructor Options

interface LinoCacheOptions {
  ttl?: number; // Default TTL in milliseconds (0 = no expiration)
  mode?: 'file' | 'folder'; // Storage mode (default: 'folder')
  basePath?: string; // Cache directory (default: '.cache')
  fileName?: string; // File name for single-file mode (default: 'cache.lino')
}

Methods

set(key, value, [ttl])

Sets a value in the cache.

await cache.set('key', 'value');
await cache.set('key', 'value', 5000); // With 5 second TTL

get(key)

Gets a value from the cache. Returns undefined if not found or expired.

const value = await cache.get('key');

del(key)

Deletes a value from the cache.

const deleted = await cache.del('key'); // true if deleted

add(key, value, [ttl]) / replace(key, value, [ttl])

Conditionally writes a value. add writes only when no live entry exists; replace writes only when one does. Both return whether the write occurred.

await cache.add('job-lock', 'worker-1', 5000); // true
await cache.add('job-lock', 'worker-2', 5000); // false
await cache.replace('job-lock', 'worker-3', 5000); // true

touch(key, [ttl]) / getex(key, [ttl])

Updates expiration without replacing the value. touch returns whether the key existed; getex returns the value while updating its expiration.

await cache.touch('session', 60000);
const session = await cache.getex('session', 120000);

getdel(key)

Returns and deletes a value as one local atomic operation.

const job = await cache.getdel('next-job');

Descriptive aliases getAndDelete and getAndTouch are also available.

incr(key, [amount]) / decr(key, [amount])

Atomically mutates finite numeric values. Missing counters start at zero, results may be negative, and an existing TTL is preserved. The longer aliases increment and decrement are also available.

await cache.incr('requests'); // 1
await cache.incr('requests', 4); // 5
await cache.decr('requests', 2); // 3

append(key, suffix) / prepend(key, prefix)

Atomically mutates string values, creates a missing key from an empty string, and preserves an existing TTL.

await cache.append('message', 'world');
await cache.prepend('message', 'hello '); // 'hello world'

Atomic mutation methods are serialized across cache instances in the same JavaScript process. Concurrent-process locking and network protocol compatibility are separate roadmap items.

has(key)

Checks if a key exists and is not expired.

const exists = await cache.has('key'); // boolean

keys()

Returns all non-expired keys.

const allKeys = await cache.keys(); // ['key1', 'key2', ...]

clear() / reset()

Clears all values from the cache.

await cache.clear();

mset(entries)

Sets multiple values at once.

await cache.mset([
  { key: 'key1', value: 'value1' },
  { key: 'key2', value: 'value2', ttl: 5000 },
]);

mget(keys)

Gets multiple values at once.

const values = await cache.mget(['key1', 'key2', 'key3']);
// [value1, value2, undefined]

mdel(keys)

Deletes multiple values at once.

const deleted = await cache.mdel(['key1', 'key2']);

wrap(key, fn, [ttl])

Wraps a function with caching. Returns cached value if available, otherwise executes the function and caches the result.

const data = await cache.wrap(
  'expensive-operation',
  async () => {
    // This only runs if not cached
    return await fetchExpensiveData();
  },
  60000
);

ttl(key)

Gets the remaining TTL for a key in milliseconds.

const remaining = await cache.ttl('key');
// > 0: remaining time
// -1: no TTL set
// -2: key not found

disconnect()

Closes the cache and releases resources.

await cache.disconnect();

Factory Functions

createLinoCache(options)

Creates a new LinoCache instance.

const cache = createLinoCache({ basePath: '.cache' });

linoStore(options)

Creates a cache-manager compatible store.

import { caching } from 'cache-manager';
import { linoStore } from 'lino-cache';

const cache = await caching(
  linoStore({
    basePath: '.cache',
    ttl: 60000,
  })
);

Why Links Notation?

Links Notation (.lino) is a human-readable serialization format that:

  • Supports circular references natively
  • Preserves object identity
  • Is more compact for certain data structures
  • Provides better debugging experience

Learn more: Links Notation

Development

# Install dependencies
npm install

# Run tests
npm test

# Lint code
npm run lint

# Format code
npm run format

# Run all checks
npm run check

License

Unlicense - Public Domain

Links

About

A cache, that stores objects in Links Notation, instead of JSON

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages