Skip to content
// EFFECT DAYS 2026

Persistence unstable

Stores encoded results for Persistable requests.

The Persistence service creates scoped stores keyed by each request's PrimaryKey. Stores read and write schema-encoded Exit values with optional TTLs, letting request workflows reuse expensive or idempotent results across fibers, process restarts, or workers that share a backing store.

17 exports Since v4.0.0 Source

Converting

Since v4.0.0

Converts a TTL to an absolute expiration timestamp in milliseconds.

Details

Returns null for no TTL and uses clock.currentTimeMillisUnsafe, so it is intended for backing-store internals.

Signature

declare function unsafeTtlToExpires(clock: Clock, ttl: Duration | undefined): number | null

Errors

Since v4.0.0

Error raised by persistence and backing-store operations.

Signature

declare class PersistenceError extends {
readonly _tag: "PersistenceError";
readonly cause?: unknown;
readonly message: string;
} & YieldableError<this> {
constructor(...args: [props: {
readonly _tag?: "PersistenceError";
readonly cause?: unknown;
readonly message: string;
}, options?: MakeOptions]);
readonly "~effect/persistence/Persistence/PersistenceError": "~effect/persistence/Persistence/PersistenceError";
}

Layers

layer

unstable
Since v4.0.0

Provides Persistence from BackingPersistence.

Details

The layer serializes and deserializes Persistable exits, applies per-entry TTLs, and skips writes whose TTL is zero or negative.

Signature

declare const layer: Layer<Persistence, never, BackingPersistence>
Since v4.0.0

Provides BackingPersistence using a KeyValueStore.

Details

Entries are stored under <storeId>:<key>, with % and : in the store id percent-encoded so store namespaces cannot overlap. Values are stored as JSON with optional expiration timestamps.

clear uses KeyValueStore.clearPrefix to remove only the store's entries, so the backing store must support it.

Signature

declare const layerBackingKvs: Layer.Layer<BackingPersistence, never, KeyValueStore.KeyValueStore>
Since v4.0.0

Provides an in-memory BackingPersistence grouped by store id.

Details

Entries are process-local and expire according to their stored TTL.

Signature

declare const layerBackingMemory: Layer.Layer<BackingPersistence>
Since v4.0.0

Provides Redis-backed persistence.

Details

Entries are stored under <storeId>:<key>, with % and : in the store id percent-encoded so store namespaces cannot overlap. Values are JSON-encoded, finite TTLs are stored with Redis expiration, and clear only removes keys in the store's namespace.

Signature

declare const layerBackingRedis: Layer.Layer<BackingPersistence, never, Redis.Redis>
Since v4.0.0

Provides SQL-backed persistence using a shared effect_persistence table.

Details

Rows are partitioned by store_id and store JSON-encoded values with optional expiration timestamps.

Signature

declare const layerBackingSql: Layer.Layer<BackingPersistence, never, SqlClient.SqlClient>

Provides SQL-backed persistence using one table per store id.

Details

Each table is created if needed and stores JSON-encoded values with optional expiration timestamps.

Gotchas

Store ids are used directly in effect_persistence_${storeId} table names, without encoding or collision detection. Choose ids whose full table names are distinct under the database's identifier rules:

  • Do not use dots: they are interpreted as schema separators.
  • Do not rely on case alone to distinguish stores: SQLite treats ASCII-case variants such as Users and users as the same table.
  • Stay within the database's identifier length limit, including the prefix. PostgreSQL normally truncates identifiers to 63 bytes, leaving 44 bytes for the store id. Ids sharing those first 44 bytes can alias the same table.

Aliased stores share entries: writes can overwrite another store's values, and clearing one clears the other. For arbitrary store ids, prefer layerBackingSql, which stores ids as row values rather than table names.

See

  • layerBackingSql

Signature

declare const layerBackingSqlMultiTable: Layer.Layer<BackingPersistence, never, SqlClient.SqlClient>

layerKvs

unstable
Since v4.0.0

Provides Persistence backed by the current KeyValueStore.

Signature

declare const layerKvs: Layer.Layer<Persistence, never, KeyValueStore.KeyValueStore>

layerMemory

unstable
Since v4.0.0

Provides Persistence backed by process-local in-memory storage.

Signature

declare const layerMemory: Layer.Layer<Persistence>

layerRedis

unstable
Since v4.0.0

Provides Persistence backed by the current Redis service.

Signature

declare const layerRedis: Layer.Layer<Persistence, never, Redis.Redis>

layerSql

unstable
Since v4.0.0

Provides Persistence backed by SQL using a shared persistence table.

Signature

declare const layerSql: Layer.Layer<Persistence, never, SqlClient.SqlClient>
Since v4.0.0

Provides Persistence backed by SQL with one table per store id.

Gotchas

Store ids become effect_persistence_${storeId} table names and must obey the database's identifier rules. Avoid dots, case-only distinctions, and names exceeding the database's identifier length limit (including the prefix). Colliding names share data, including writes and clears. Prefer layerSql for arbitrary store ids.

See

  • layerBackingSqlMultiTable for database-specific store-id restrictions
  • layerSql

Signature

declare const layerSqlMultiTable: Layer.Layer<Persistence, never, SqlClient.SqlClient>

Models

PersistenceStore

interface unstable
Since v4.0.0

Typed store for persisted Exit values keyed by Persistable requests.

Signature

interface PersistenceStore {
readonly clear: Effect<void, PersistenceError>;
readonly get: <A extends Constraint, E extends Constraint>(key: Persistable<A, E>) => Effect<Exit<A["Type"], E["Type"]> | undefined, SchemaError | PersistenceError, A["DecodingServices"] | E["DecodingServices"]>;
readonly getMany: <A extends Constraint, E extends Constraint>(keys: Iterable<Persistable<A, E>>) => Effect<Array<Exit<A["Type"], E["Type"]> | undefined>, SchemaError | PersistenceError, A["DecodingServices"] | E["DecodingServices"]>;
readonly remove: <A extends Constraint, E extends Constraint>(key: Persistable<A, E>) => Effect<void, PersistenceError>;
readonly set: <A extends Constraint, E extends Constraint>(key: Persistable<A, E>, value: Exit<A["Type"], E["Type"]>) => Effect<void, SchemaError | PersistenceError, A["EncodingServices"] | E["EncodingServices"]>;
readonly setMany: <A extends Constraint, E extends Constraint>(entries: Iterable<readonly [Persistable<A, E>, Exit<A["Type"], E["Type"]>]>) => Effect<void, SchemaError | PersistenceError, A["EncodingServices"] | E["EncodingServices"]>;
}

Services

Since v4.0.0

Service for creating raw backing stores for persistence store ids.

Signature

declare class BackingPersistence extends Shape<"effect/persistence/BackingPersistence", {
readonly make: (storeId: string) => Effect<BackingPersistenceStore, never, Scope>;
}, this> {
constructor(_: never);
}

BackingPersistenceStore

interface unstable
Since v4.0.0

Raw persistence backing store for JSON-compatible objects with optional TTLs.

Signature

interface BackingPersistenceStore {
readonly clear: Effect<void, PersistenceError>;
readonly get: (key: string) => Effect<object | undefined, PersistenceError>;
readonly getMany: (keys: [string, ...Array<string>]) => Effect<[object | undefined, ...Array<object | undefined>], PersistenceError>;
readonly remove: (key: string) => Effect<void, PersistenceError>;
readonly set: (key: string, value: object, ttl: Duration | undefined) => Effect<void, PersistenceError>;
readonly setMany: (entries: [readonly [string, object, Duration | undefined], ...Array<readonly [string, object, Duration | undefined]>]) => Effect<void, PersistenceError>;
}

Persistence

unstable
Since v4.0.0

Service for creating scoped stores of persisted Persistable request results.

Signature

declare class Persistence extends Shape<"effect/persistence/Persistence", {
readonly make: (options: {
readonly storeId: string;
readonly timeToLive?: (exit: Exit<unknown, unknown>, key: Any) => Input;
}) => Effect<PersistenceStore, never, Scope>;
}, this> {
constructor(_: never);
}