# KeyValueStore

`KeyValueStore` is the small storage primitive used across the persistence
package. It speaks two value shapes — `string` and `Uint8Array` — and exposes a
uniform `Effect`-based interface (`get`, `set`, `has`, `remove`, `clear`, …) over
several interchangeable backends: an in-memory `Map`, the filesystem, a SQL
table, and the browser's Web Storage API. Every operation can fail with
`KeyValueStoreError`.

When you need typed values rather than raw strings, wrap a store with
[`toSchemaStore`](#toschemastore) to get JSON encoding/decoding driven by a
`Schema`. To share one backend across logical namespaces, use
[`prefix`](#prefix).

```ts
import { KeyValueStore } from "effect/unstable/persistence";
```
**Unstable module:** `KeyValueStore` lives under `effect/unstable/persistence`. Unstable modules are
  production-usable but may change shape between minor releases.

## Quickstart

Access the store with the `KeyValueStore` service tag inside `Effect.gen`, then
call its operations. The simplest backend is [`layerMemory`](#layermemory), a
process-local `Map`.

```ts
import { Effect } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;

  // Write a couple of string values
  yield* store.set("user:1", "Alice");
  yield* store.set("user:2", "Bob");

  // Read one back — get resolves to `string | undefined`
  const alice = yield* store.get("user:1");
  // => "Alice"

  // Membership and counting
  const exists = yield* store.has("user:2");
  // => true
  const count = yield* store.size;
  // => 2

  // Remove a key, then confirm
  yield* store.remove("user:1");
  const gone = yield* store.get("user:1");
  // => undefined

  return { alice, exists, count, gone };
});

// Provide the in-memory backend and run
Effect.runPromise(program.pipe(Effect.provide(KeyValueStore.layerMemory)));
```

## Storing typed values with SchemaStore

Raw stores only hold strings and bytes. To persist domain objects, wrap a store
with [`toSchemaStore`](#toschemastore): values are JSON-encoded through the
schema on `set` and decoded on `get`. `get` returns an `Option` (rather than
`undefined`), and `modify` maps the decoded value in place.

```ts
import { Effect, Option, Schema } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

const User = Schema.Struct({
  name: Schema.String,
  age: Schema.Number,
});

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;

  // Wrap the raw store with the User schema
  const users = KeyValueStore.toSchemaStore(store, User);

  // `set` takes the decoded Type and encodes it to JSON under the hood
  yield* users.set("user:1", { name: "Alice", age: 30 });

  // `get` decodes back into the typed value, wrapped in Option
  const alice = yield* users.get("user:1");
  // => Option.some({ name: "Alice", age: 30 })

  const missing = yield* users.get("user:404");
  // => Option.none()

  // `modify` reads, applies `f`, and writes the result back
  const updated = yield* users.modify("user:1", (u) => ({
    ...u,
    age: u.age + 1,
  }));
  // => Option.some({ name: "Alice", age: 31 })

  return { alice, missing, updated };
});

Effect.runPromise(program.pipe(Effect.provide(KeyValueStore.layerMemory)));
```

The typed operations widen both channels relative to the raw store:

- `get`/`set`/`modify` can additionally fail with `Schema.SchemaError`.
- `get` requires the schema's `DecodingServices`, `set` requires its
  `EncodingServices` (and `modify` requires both). For plain schemas like the
  one above these are `never`, so nothing extra needs to be provided.
**Schema evolution:** Values are persisted as JSON produced by the schema. If you change the schema
  in an incompatible way (rename a field, tighten a type), previously stored JSON
  may fail to decode and `get` will surface a `Schema.SchemaError`. Plan
  migrations or version your keys when evolving stored shapes.

## Namespacing with prefix

[`prefix`](#prefix) returns a view of a store that prepends a fixed string to
every key. This lets several logical stores safely share a single backend
without colliding.

```ts
import { Effect } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;

  // Two namespaced views over the same backing store
  const users = KeyValueStore.prefix(store, "users:");
  const sessions = KeyValueStore.prefix(store, "sessions:");

  yield* users.set("1", "Alice"); //    real key: "users:1"
  yield* sessions.set("1", "tok_abc"); // real key: "sessions:1"

  const user = yield* users.get("1");
  // => "Alice"
  const session = yield* sessions.get("1");
  // => "tok_abc"

  // The underlying store sees the full, prefixed keys
  const total = yield* store.size;
  // => 2

  return { user, session, total };
});

Effect.runPromise(program.pipe(Effect.provide(KeyValueStore.layerMemory)));
```

## Choosing a backend
**Backend caveats:** The backends are deliberately small but not byte-for-byte identical — pick keys
  and value formats with their constraints in mind:

  - **Memory** ([`layerMemory`](#layermemory)) is a process-local `Map`. Nothing
    survives a restart; use it for tests and ephemeral state.
  - **Filesystem** ([`layerFileSystem`](#layerfilesystem)) writes one file per
    key, naming files with `encodeURIComponent(key)`. Requires `FileSystem` and
    `Path` and fails with `PlatformError` if the directory can't be created.
  - **SQL** ([`layerSql`](#layersql)) creates a table (default
    `effect_key_value_store`) with an extra `value_type` column to remember
    whether each value was a string or binary. Requires a `SqlClient`.
  - **Web Storage** ([`layerStorage`](#layerstorage)) is **string-only**. Binary
    values go through [`makeStringOnly`](#makestringonly), which stores them as
    base64.

  There is **no native TTL** in any backend; higher-level persistence layers
  encode expiration metadata inside the stored value when they need it.

## Reference

### KeyValueStore (interface)

The store interface. All operations return an `Effect` that may fail with
`KeyValueStoreError`. `get`/`set`/`modify`/`has` are functions of a key; `clear`,
`size`, and `isEmpty` are plain `Effect` values (not functions).

| Member | Signature | Description |
| --- | --- | --- |
| `get` | `(key) => Effect<string \| undefined, …>` | Read a string value, or `undefined` if absent. |
| `getUint8Array` | `(key) => Effect<Uint8Array \| undefined, …>` | Read a value as bytes. |
| `set` | `(key, value: string \| Uint8Array) => Effect<void, …>` | Write a string or binary value. |
| `remove` | `(key) => Effect<void, …>` | Delete a key. |
| `clear` | `Effect<void, …>` | Delete every entry. |
| `size` | `Effect<number, …>` | Count entries. |
| `modify` | `(key, f: (s: string) => string) => Effect<string \| undefined, …>` | Read-map-write a string; `undefined` if the key was absent. |
| `modifyUint8Array` | `(key, f: (u: Uint8Array) => Uint8Array) => Effect<Uint8Array \| undefined, …>` | Read-map-write bytes. |
| `has` | `(key) => Effect<boolean, …>` | Whether the key exists. |
| `isEmpty` | `Effect<boolean, …>` | Whether the store has no entries. |

```ts
import { Effect } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;

  const empty = yield* store.isEmpty;
  // => true

  yield* store.set("count", "1");

  // modify reads the current string and writes the mapped result
  const next = yield* store.modify("count", (s) => String(Number(s) + 1));
  // => "2"

  const present = yield* store.has("count");
  // => true
  const n = yield* store.size;
  // => 1
  const value = yield* store.get("count");
  // => "2"

  return { empty, next, present, n, value };
});

Effect.runPromise(program.pipe(Effect.provide(KeyValueStore.layerMemory)));
```

### KeyValueStore (service tag)

A `Context.Service` whose service type is the `KeyValueStore` interface. `yield*`
the tag to access the store; provide an implementation with one of the `layer*`
constructors below.

```ts
import { Effect } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

// Access inside Effect.gen
const use = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;
  return yield* store.size;
});

// Provide a backend
Effect.runPromise(use.pipe(Effect.provide(KeyValueStore.layerMemory)));
// => 0
```

### KeyValueStoreError

A `Data.TaggedError` (`_tag: "KeyValueStoreError"`) carrying `message` and
`method`, plus optional `key` and `cause`. Use `Effect.catchTag` to handle it.

```ts
import { Effect } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;
  return yield* store.get("missing");
}).pipe(
  Effect.catchTag("KeyValueStoreError", (error) =>
    Effect.succeed(`failed in ${error.method}: ${error.message}`),
  ),
);
// error.method => e.g. "get"; error.key => the offending key (when applicable)

Effect.runPromise(program.pipe(Effect.provide(KeyValueStore.layerMemory)));
```

### make

Builds a `KeyValueStore` from primitive callbacks. `get`, `getUint8Array`, `set`,
`remove`, `clear`, and `size` are **required**; `has`, `isEmpty`, `modify`, and
`modifyUint8Array` are **derived** for you (via `get`/`size`/`set`) unless you
override them.

```ts
import { Effect, Encoding } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

// A minimal Map-backed store; has/isEmpty/modify are derived automatically.
const make = () => {
  const map = new Map<string, string | Uint8Array>();
  return KeyValueStore.make({
    get: (key) =>
      Effect.sync(() => {
        const v = map.get(key);
        return v === undefined || typeof v === "string"
          ? v
          : Encoding.encodeBase64(v);
      }),
    getUint8Array: (key) =>
      Effect.sync(() => {
        const v = map.get(key);
        return typeof v === "string" ? new TextEncoder().encode(v) : v;
      }),
    set: (key, value) => Effect.sync(() => void map.set(key, value)),
    remove: (key) => Effect.sync(() => void map.delete(key)),
    clear: Effect.sync(() => map.clear()),
    size: Effect.sync(() => map.size),
  });
};

const store = make();
Effect.runPromise(store.set("k", "v").pipe(Effect.andThen(store.has("k"))));
// => true
```

### makeStringOnly

Adapts a backend that can only store strings into a full `KeyValueStore`. Binary
values passed to `set` are encoded as base64; `getUint8Array` base64-decodes
stored values and falls back to UTF-8 encoding for strings that are not valid
base64. Used internally by [`layerStorage`](#layerstorage).

```ts
import { Effect } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

const store = KeyValueStore.makeStringOnly({
  get: (key) => Effect.sync(() => globalThis.localStorage.getItem(key) ?? undefined),
  set: (key, value) => Effect.sync(() => globalThis.localStorage.setItem(key, value)),
  remove: (key) => Effect.sync(() => globalThis.localStorage.removeItem(key)),
  clear: Effect.sync(() => globalThis.localStorage.clear()),
  size: Effect.sync(() => globalThis.localStorage.length),
});

// store.set("k", new Uint8Array([1, 2, 3])) persists base64 ("AQID");
// store.getUint8Array("k") decodes it back => Uint8Array([1, 2, 3])
```

### MakeOptions / MakeStringOptions / LayerSqlOptions

Option types for the constructors above and for [`layerSql`](#layersql).
`MakeOptions` is `Partial<KeyValueStore>` with the six primitive operations made
required; `MakeStringOptions` is the same but with a string-only `set` and no
`getUint8Array`. `LayerSqlOptions` carries a single optional `table` (default
`"effect_key_value_store"`).

```ts
import type { KeyValueStore } from "effect/unstable/persistence";

// table defaults to "effect_key_value_store" when omitted
const opts: KeyValueStore.LayerSqlOptions = { table: "kv" };
```

### prefix

Dual-signature combinator returning a view whose keys are all prefixed with the
given string. Available data-first (`prefix(store, "p:")`) and data-last
(`prefix("p:")`) for use in `pipe`.

```ts
import { Effect } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;

  // data-last form, composed with pipe
  const scoped = store.pipe(KeyValueStore.prefix("cache:"));

  yield* scoped.set("a", "1"); // underlying key => "cache:a"
  const raw = yield* store.get("cache:a");
  // => "1"

  return raw;
});

Effect.runPromise(program.pipe(Effect.provide(KeyValueStore.layerMemory)));
```

### layerMemory

A `Layer<KeyValueStore>` backed by an in-process `Map`. No dependencies, no I/O
errors, nothing persisted across restarts — ideal for tests and transient state.

```ts
import { Effect } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;
  yield* store.set("k", "v");
  return yield* store.get("k");
});

Effect.runPromise(program.pipe(Effect.provide(KeyValueStore.layerMemory)));
// => "v"
```

### layerFileSystem

A `Layer<KeyValueStore, PlatformError, FileSystem | Path>` that stores one file
per key under `directory`. The directory is created (recursively) on layer
construction, and keys are turned into file names with `encodeURIComponent`.
Provide a platform `FileSystem` and `Path` (e.g. from `@effect/platform-node`).

```ts
import { Effect, Layer } from "effect";
import { NodeContext } from "@effect/platform-node";
import { KeyValueStore } from "effect/unstable/persistence";

// Files land in ./data, e.g. ./data/user%3A1 for the key "user:1"
const StoreLive = KeyValueStore.layerFileSystem("./data").pipe(
  Layer.provide(NodeContext.layer),
);

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;
  yield* store.set("user:1", "Alice");
  return yield* store.get("user:1");
  // => "Alice"
});

Effect.runPromise(program.pipe(Effect.provide(StoreLive)));
```

### layerSql

A `Layer<KeyValueStore, never, SqlClient>` backed by a SQL table. On construction
it issues `CREATE TABLE IF NOT EXISTS` (dialect-aware: SQLite/Postgres/MySQL/MSSQL)
for the configured table, with columns `id`, `value`, and a `value_type` flag
recording whether each value was stored as a string or as bytes. Supply a
`SqlClient` from a driver layer.

```ts
import { Effect, Layer } from "effect";
import { SqliteClient } from "@effect/sql-sqlite-node";
import { KeyValueStore } from "effect/unstable/persistence";

// Driver layer supplies SqlClient; layerSql creates "kv" and reads/writes it.
const SqlLive = SqliteClient.layer({ filename: "kv.db" });

const StoreLive = KeyValueStore.layerSql({ table: "kv" }).pipe(
  Layer.provide(SqlLive),
);

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;
  yield* store.set("user:1", "Alice");
  return yield* store.get("user:1");
  // => "Alice"
});

Effect.runPromise(program.pipe(Effect.provide(StoreLive)));
```

### layerStorage

A `Layer<KeyValueStore>` backed by a Web `Storage` instance (`localStorage` or
`sessionStorage`). It is string-only and routes through
[`makeStringOnly`](#makestringonly), so binary values are persisted as base64.
Pass a thunk that returns the storage object so evaluation is deferred to layer
build time.

```ts
import { Effect } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

// In a browser environment:
const StoreLive = KeyValueStore.layerStorage(() => window.localStorage);

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;
  yield* store.set("theme", "dark");
  return yield* store.get("theme");
  // => "dark"
});

Effect.runPromise(program.pipe(Effect.provide(StoreLive)));
```

### SchemaStore (interface)

The typed view returned by [`toSchemaStore`](#toschemastore). It mirrors
`KeyValueStore` but operates on decoded values: `get` returns
`Option<S["Type"]>`, `set`/`modify` take the decoded type, and the encoding/
decoding operations widen the error channel with `Schema.SchemaError` and add the
schema's `DecodingServices`/`EncodingServices` to requirements. `remove`, `clear`,
`size`, `has`, and `isEmpty` are unchanged from the underlying store.

```ts
import { Effect, Schema } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

const Counter = Schema.Struct({ value: Schema.Number });

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;
  const counters: KeyValueStore.SchemaStore<typeof Counter> =
    KeyValueStore.toSchemaStore(store, Counter);

  yield* counters.set("hits", { value: 1 });
  const has = yield* counters.has("hits");
  // => true
  const size = yield* counters.size;
  // => 1

  return { has, size };
});

Effect.runPromise(program.pipe(Effect.provide(KeyValueStore.layerMemory)));
```

### toSchemaStore

Wraps a `KeyValueStore` into a [`SchemaStore`](#schemastore-interface) using the
schema's JSON codec. `set` encodes the value to a JSON string before writing
(encode direction); `get` reads the string and decodes it back to the typed value
wrapped in `Option` (decode direction).

```ts
import { Effect, Schema } from "effect";
import { KeyValueStore } from "effect/unstable/persistence";

const Point = Schema.Struct({ x: Schema.Number, y: Schema.Number });

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore;
  const points = KeyValueStore.toSchemaStore(store, Point);

  // Encode direction: the object is serialized to JSON and stored
  yield* points.set("origin", { x: 0, y: 0 });
  // raw string in the backend => '{"x":0,"y":0}'

  // Decode direction: the JSON is parsed back into the typed Option
  const origin = yield* points.get("origin");
  // => Option.some({ x: 0, y: 0 })

  const absent = yield* points.get("nope");
  // => Option.none()

  return { origin, absent };
});

Effect.runPromise(program.pipe(Effect.provide(KeyValueStore.layerMemory)));
```