---
title: Drift concepts
description: Understand Drift's synchronous store, TTL expiry, LRU eviction, persistence, events, and batched writes.
url: https://pr-14-c0fdf0ef9f59.thally.app/drift/concepts
---

# Drift concepts

Understand Drift's synchronous store, TTL expiry, LRU eviction, persistence, events, and batched writes.

Drift is a zero-runtime-dependency ESM package. A store and every namespace
view use one synchronous, in-process API.

## TTL expiry is lazy but never observable

Set a default TTL on the store or pass `{ ttlMs }` to one `set()`. An expired
entry may remain allocated until a read touches it, `sweep()` runs, or capacity
pressure reclaims it, but public reads treat it as absent.

- `get()` and `peek()` return `undefined` for expired entries.
- `has()` returns `false`.
- `keys()`, `values()`, `entries()`, `size()`, and `isEmpty()` exclude them.
- `ttl()` returns the positive remaining milliseconds or `undefined`.
- `touch()` refreshes recency and restarts a configured TTL without changing
  the value.

Call `sweep()` on an interval in a long-lived store when you need prompt memory
reclamation.

## LRU capacity is store-wide

`maxEntries` caps live entries. `get()`, `set()`, and `touch()` refresh recency;
`peek()` and `has()` do not. Before evicting a live entry, Drift reclaims
expired entries. All namespace views share the same capacity and LRU order.

## Namespaces are prefix views

`store.namespace("users")` prefixes keys with `users:` and returns the full
store API. Views share storage, options, listeners, and persistence. They are
not separate databases. Read [Using namespaces](/guides/using-namespaces) for
nested and persistence examples.

## Events observe lifecycle changes

`on()` and `off()` handle `set`, `delete`, `expire`, and `evict`. Listeners run
synchronously in registration order and receive `{ key }` with the full,
namespace-prefixed key. A throwing listener is ignored so it cannot break the
store operation. `clear()` intentionally emits no lifecycle events.

## Persistence is explicit

With `persistPath`, `createStore()` loads an existing versioned JSON snapshot.
`flush()` sweeps expired entries and writes the whole store using a temporary
file followed by a rename. A namespace flush still includes every namespace.

Snapshots preserve LRU order and absolute expiry deadlines. Corrupt,
unrecognized, or unsupported snapshots throw instead of silently starting
empty. Values must survive `JSON.stringify`; types such as `Date`, `Map`, and
object properties containing `undefined` do not round-trip unchanged.

## Batches and transactions

`createBatch(store)` queues `set` and `delete` operations until `commit()`.
`transaction(store, body)` adds read-your-writes and discards all pending
writes if `body` throws. Both APIs are synchronous and process-local; they do
not coordinate multiple processes.

```typescript
import { createStore, transaction } from "driftkv";

const counters = createStore<number>();

const next = transaction(counters, (tx) => {
  const current = tx.get("checkout") ?? 0;
  tx.set("checkout", current + 1);
  return current + 1;
});
```

See the [store method reference](/api/store-methods) for every exported
operation and return value.