@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.
nameisTtl.isOpenis alwaystrue.- Set the expiry per operation with the
@unikvs/ttl:ttl/@unikvs/ttl:expiresAtvars (barettl/expiresAtare also accepted for backward compatibility), or withdefaultTtl. - Expired
get/has/stream/copy/movebehave asKeyNotFoundError. copyandmovecarry the source expiry to the destination only when every TTL-applicable target in the middleware’s branch iscommitted.
Install with:
npm install @unikvs/ttlpnpm add @unikvs/ttlyarn add @unikvs/ttlbun add @unikvs/ttlnub add @unikvs/ttlaube add @unikvs/ttlUsage
Ttl implements IMiddleware. Pass it to UniKvs with use().
import { Ttl } from "@unikvs/ttl";
Constructor arguments:
defaultTtl?TtlInput
Default TTL used when an operation specifies none. Accepts relative seconds, absolute time, or the object form.
TtlInputnow?() => number
Returns the current time (epoch ms). Use it to freeze time in tests.
() => numberDate.nowThe 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) |
encodewraps the value as{ "@unikvs/ttl": 1, expiresAt: number, data: unknown }when a TTL applies. Overwrites without a TTL pass data through.decodeunwraps the envelope while it is valid, and throwsKeyNotFoundErroronce 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.
encodeStreamanddecodeStreampass data through. - TTL resolution prefers the
ttlvar, then theexpiresAtvar, thendefaultTtl. 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.
setapplies the staged expiry after every target commits. An overwrite without a TTL removes the record. On failure, the previous record is kept.copycarries the source expiry to the destination when every TTL-applicable target in the middleware’s branch iscommitted. If the source has no record, the destination record is removed. If any target isnotFoundorfailed, the destination record is left unchanged.moveupdates the destination record and removes the source record when every TTL-applicable target in the middleware’s branch iscommitted. If any target isnotFoundorfailed, 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
usetime. It is read again from the record inafterUse(while the key locks are held), so a concurrentsetthat changes the source TTL afteruseis reflected in the destination. deleteandclearremove 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
KeyNotFoundErrorbut keeps its record. Streams carry no in-band expiry, so this preserves expiry detection after a failed copy or move. A successfulsetreplaces the record with the new expiry (or removes it when the write has no TTL); a successfuldeleteorclearremoves it. decodeStreamkeeps the record when it detects expiry. Later reads of the same key still fail withKeyNotFoundError.- Repair writes performed by
get/streamare treated as part of the read and do not triggerafterUse. 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
actualunknown
The specified TTL value.
unknownMeaning
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.