Skip to content
UniKVS
Esc
↑↓navigate↵open⌘Jpreview
On this page

@unikvs/ttl

Middleware that attaches a TTL in relative seconds or absolute time, treating expired reads as missing.

Overview

Ttl attaches a relative-seconds or absolute-time expiry to written values, and treats expired reads as missing.Middleware

Use it for short-lived caches or session-like data. Plain values and streams expire differently.

  • name is Ttl.
  • isOpen is always true.
  • Set the expiry per operation with the @unikvs/ttl:ttl / @unikvs/ttl:expiresAt vars (bare ttl / expiresAt are also accepted for backward compatibility), or with defaultTtl.
  • Expired get / has / stream / copy / move behave as KeyNotFoundError.
  • copy and move carry the source expiry to the destination only when every TTL-applicable target in the middleware’s branch is committed.

Install with:

npm install @unikvs/ttl
pnpm add @unikvs/ttl
yarn add @unikvs/ttl
bun add @unikvs/ttl
nub add @unikvs/ttl
aube add @unikvs/ttl

Usage

Ttl implements IMiddleware. Pass it to UniKvs with use().

import { Ttl } from "@unikvs/ttl";

Constructor arguments:

PropType
defaultTtl?TtlInput

Default TTL used when an operation specifies none. Accepts relative seconds, absolute time, or the object form.

TypeTtlInput
now?() => number

Returns the current time (epoch ms). Use it to freeze time in tests.

Type() => number
DefaultDate.now

The following sets a 60-second default TTL and overrides it per write:

import { Memory } from "@unikvs/memory";
import { Ttl } from "@unikvs/ttl";
import * as unikvs from "unikvs";

const kvs = new unikvs.Builder<{ report: unikvs.PlainValue<string> }>()
  .use(new Ttl({ defaultTtl: 60 }))
  .persist(new Memory())
  .build();

await kvs.open();
await kvs.set("report", "hello");
await kvs.set("short", "bye", { vars: { "@unikvs/ttl:ttl": 10 } });
console.log(await kvs.get("report")); // "hello"
await kvs.close();

Ttl

Middleware that manages expiry with an expiry envelope plus per-operation records. It checks expiry before reads and treats existence checks as missing.

TtlOptions

Options accepted by the Ttl constructor. Specifies the default TTL and the clock.

TtlInput

Per-write TTL value. A number is relative seconds (fractions allowed; zero or below expires immediately), a Date is an absolute expiry time, and the object form takes ttl in relative seconds or expiresAt in epoch ms or a Date.

Data types

Method Input Output
encode unknown envelope or passthrough
decode envelope or any value unknown
encodeStream unknown (chunk) unknown (chunk, passthrough)
decodeStream unknown (chunk) unknown (chunk, passthrough)
  • encode wraps the value as { "@unikvs/ttl": 1, expiresAt: number, data: unknown } when a TTL applies. Overwrites without a TTL pass data through.
  • decode unwraps the envelope while it is valid, and throws KeyNotFoundError once expired. When the record is missing (e.g. after a restart), it restores the record from the envelope.
  • Streams never embed the expiry in-band (to fit chunk type constraints) and are managed by records only. encodeStream and decodeStream pass data through.
  • TTL resolution prefers the ttl var, then the expiresAt var, then defaultTtl. Namespaced vars take precedence over bare names.

When records are committed

This middleware receives storage outcomes through afterUse and commits records then. use, encode, and encodeStream do not mutate records; they stage pending values in the operation’s variable store. A failed write therefore cannot leave an expiry on a value that was never stored. The targets passed to afterUse are only those in the branch where this middleware is applied.

  • set applies the staged expiry after every target commits. An overwrite without a TTL removes the record. On failure, the previous record is kept.
  • copy carries the source expiry to the destination when every TTL-applicable target in the middleware’s branch is committed. If the source has no record, the destination record is removed. If any target is notFound or failed, the destination record is left unchanged.
  • move updates the destination record and removes the source record when every TTL-applicable target in the middleware’s branch is committed. If any target is notFound or failed, it keeps the source record and does not change the destination record.
  • If an operation is aborted after storage settles, its result is still recorded when every TTL-applicable target in the middleware’s branch is committed. If any target is uncommitted, records are left unchanged as on failure.
  • The source expiry is not frozen at use time. It is read again from the record in afterUse (while the key locks are held), so a concurrent set that changes the source TTL after use is reflected in the destination.
  • delete and clear remove records only when they succeed. Partial success keeps records so surviving values do not lose their expiry.
  • An expired copy or move source fails with KeyNotFoundError but keeps its record. Streams carry no in-band expiry, so this preserves expiry detection after a failed copy or move. A successful set replaces the record with the new expiry (or removes it when the write has no TTL); a successful delete or clear removes it.
  • decodeStream keeps the record when it detects expiry. Later reads of the same key still fail with KeyNotFoundError.
  • Repair writes performed by get / stream are treated as part of the read and do not trigger afterUse. Therefore the reading instance does not commit a stream record for a repaired destination that has no in-band expiry envelope.

Errors

Errors thrown by this package:

TtlInvalidValueError

PropType
actualunknown

The specified TTL value.

Typeunknown

Meaning

Thrown when a TTL value matches none of relative seconds, absolute time, or the object form. Both the constructor’s defaultTtl and per-write vars are validated.

How to fix

Pass a finite number (relative seconds), a Date, or an object such as { ttl } or { expiresAt }. Strings and non-finite values are rejected.

Example

Catching TtlInvalidValueError:

import { TtlInvalidValueError } from "@unikvs/ttl";

try {
  await kvs.set("report", "hello", { vars: { ttl: "60" } });
} catch (ex) {
  if (ex instanceof TtlInvalidValueError) {
    console.log(ex.meta); // { actual: "60" }
  } else {
    throw ex;
  }
}

Expired reads fail as @unikvs/core KeyNotFoundError. See Errors.

Was this page helpful?