# Persistence

The `effect/unstable/persistence` package provides a small stack of durable
storage primitives: a low-level key/value store, a schema-aware store for
request results, and three higher-level building blocks (a durable cache, a
durable job queue, and a shared rate limiter). Each layer builds on the one
below it, and a single `Redis` service can power all of the Redis-backed
variants at once.

```ts
import {
  KeyValueStore,
  Persistable,
  PersistedCache,
  PersistedQueue,
  Persistence,
  RateLimiter,
  Redis
} from "effect/unstable/persistence"
```
**Unstable API:** Every module in this section lives under `effect/unstable/persistence`. The
  `unstable` namespace is not yet covered by the stability guarantees of the
  core `effect` package, so const program = Effect.gen(function* () {
  const redis = yield* Redis
  yield* redis.send("SET", "greeting", "hello")
  const value = yield* redis.send<string>("GET", "greeting")
  // => "hello"
  return value
})
```

### `make`

Wraps a raw command sender into a `Redis` service. It builds an internal cache
that loads each Lua script once via `SCRIPT LOAD`, remembers its SHA, and runs
it with `EVALSHA`. This is how you turn a real client into the service — wrap
the result in `Layer.effect(Redis)(...)`.

```ts
// `client` here is your own connected Redis client (node-redis, ioredis, ...).
declare const client: {
  sendCommand: (args: ReadonlyArray<string>) => Promise<unknown>
}

const RedisLive = Layer.effect(Redis)(
  Redis.make({
    // Map every command + arg list onto your client, and turn any client or
    // network failure into a RedisError.
    send: <A = unknown>(command: string, ...args: ReadonlyArray<string>) =>
      Effect.tryPromise({
        try: () => client.sendCommand([command, ...args]) as Promise<A>,
        catch: (cause) => new Redis.RedisError({ cause })
      })
  })
)
// => Layer<Redis>
```

### `RedisError`

A `Schema.ErrorClass` (tag `"RedisError"`) carrying the underlying `cause` as a
`Schema.Defect`. Raised by command and script execution; your `send`
implementation should map client failures into it.

```ts
const error = new Redis.RedisError({ cause: new Error("ECONNREFUSED") })
error._tag // => "RedisError"
```

### `script`

Constructs a typed `Script` descriptor. `f` maps your typed parameters to the
ordered argument list passed to Lua, and `numberOfKeys` (a number or a function
of the params) tells Redis how many leading arguments are `KEYS` versus `ARGV`.
The result type defaults to `void`.

```ts
// INCRBY a counter key by an amount. The key is a Redis KEY; the amount is ARGV.
const incrBy = Redis.script(
  (key: string, amount: number) => [key, amount],
  {
    lua: `return redis.call("INCRBY", KEYS[1], ARGV[1])`,
    numberOfKeys: 1
  }
)
// => Script<{ params: [string, number]; result: void }>
```

### `Script` / `withReturnType`

The `Script<Config>` interface holds the Lua source, the param-to-arg mapping,
and the key count. Because Lua return types are opaque to TypeScript, refine the
result type with `.withReturnType<R>()`, then run it via `redis.eval(script)`.

```ts
const incrBy = Redis.script(
  (key: string, amount: number) => [key, amount],
  {
    lua: `return redis.call("INCRBY", KEYS[1], ARGV[1])`,
    numberOfKeys: 1
  }
).withReturnType<number>()
// => Script<{ params: [string, number]; result: number }>

const program = Effect.gen(function* () {
  const redis = yield* Redis
  const total = yield* redis.eval(incrBy)("visits", 5)
  // => number, e.g. 5
  return total
})
```

<Aside type="tip" title="One Redis layer powers everything">
  The `Persistence.layerRedis`, `PersistedQueue.layerStoreRedis`, and
  `RateLimiter.layerStoreRedis` layers all depend on this same `Redis` service.
  Provide one `Redis` layer (built with `make`) and every Redis-backed
  persistence feature can share the same connection.