Skip to content

MemoryStore

Store in process memory, the reference implementation of the storage contract. The store make builds keeps what the contract leaves to storage: each commit’s entries as one batch per log, the slice each idempotency key wrote, and the position an append expected. It runs over a Binding: how to read committed state and the live transaction, how to open one, and the locks that order a log’s writers. bind makes one connection’s binding over a Database of this process, which several connections may share as hosts share one system, and layer gives a store over a database of its own. A host whose memory storage keeps more than logs, such as records committed beside them, provides Binding over that storage instead.

Since v0.2.0

One connection’s binding over database, as one host’s connection to a shared system: this connection’s transactions wait for each other, and another connection’s writer is contention. A standalone append waits only for its log’s other writers, on any connection.

Signature

declare const bind: (database: Database) => Effect.Effect<Binding["Service"]>

Source

Since v0.2.0

Logs with nothing stored.

Signature

declare const empty: () => Space

Source

Since v0.2.0

The key of a keyed append’s slice in Space.appends: its log and idempotency key, unambiguous whatever either contains.

Signature

declare const keyOf: (logId: string, key: string) => string

Source

Since v0.2.0

The store over the caller’s Binding: appends inside a transaction or alone, reads of committed batches or the transaction’s own, and waiting for appends. Store.make checks every input first.

Signature

declare const make: Effect.Effect<
{
readonly getAppend: (
logId: LogEntry.LogId,
key: Store.IdempotencyKey
) => Effect.Effect<Option.Option<Store.AppendReceipt>, StoreError.StoreError>
readonly transaction: <A, E, R>(
program: Effect.Effect<A, E, R>
) => Effect.Effect<A, E | StoreError.StoreError | Store.CommitUncertain, Exclude<R, Store.Transaction>>
readonly append: (
request: Store.AppendInput
) => Effect.Effect<ReadonlyArray<Store.Entry>, StoreError.StoreError | Store.CommitUncertain>
readonly getEntry: (
logId: LogEntry.LogId,
entryId: LogEntry.EntryId
) => Effect.Effect<Option.Option<Store.Entry>, StoreError.StoreError>
readonly get: (
logId: LogEntry.LogId,
commitId: Commit.CommitId
) => Effect.Effect<Option.Option<Store.Batch>, StoreError.StoreError>
readonly head: (logId: LogEntry.LogId) => Effect.Effect<Option.Option<LogEntry.Position>, StoreError.StoreError>
readonly read: (
logId: LogEntry.LogId,
options: Store.ReadOptions
) => Effect.Effect<ReadonlyArray<Store.Batch>, StoreError.StoreError>
readonly readEntries: (
logId: LogEntry.LogId,
options: Store.EntriesOptions
) => Effect.Effect<ReadonlyArray<Store.Entry>, StoreError.StoreError>
readonly waitForAppend: (
logId: LogEntry.LogId,
after: Option.Option<LogEntry.Position>
) => Effect.Effect<void, StoreError.StoreError>
},
never,
Binding
>

Source

Since v0.2.0

A database with nothing stored and no connection.

Signature

declare const makeDatabase: Effect.Effect<Database, never, never>

Source

Since v0.2.0

Provide Store.Store in process memory, over a database of its own with one connection. Nothing survives the layer.

Example (Typed logs in memory)

import { Entry, Log, MemoryStore } from "@amends-dev/log"
import { Effect, Layer, Schema } from "effect"
const program = Effect.gen(function* () {
const logs = yield* Log.Log
const steps = logs.open({ id: Entry.LogId.make("steps"), schema: Schema.String })
yield* steps.append("started")
return yield* steps.read({ limit: 10 })
})
program.pipe(Effect.provide(Log.layer.pipe(Layer.provide(MemoryStore.layer))))

Signature

declare const layer: Layer.Layer<Store.Store, never, never>

Source

Since v0.2.0

The slice of a batch one keyed append wrote: its commit, and where in the batch its entries begin and how many there are. The store returns it as the append’s receipt.

Signature

export interface Append {
readonly commitId: Commit.CommitId
readonly firstIndex: number
readonly entryCount: number
}

Source

Since v0.2.0

One commit’s entries in one log, at one position. Stored batches are never changed: a commit’s further appends to the log replace its batch with a longer one in the transaction’s snapshot.

Signature

export interface Batch {
readonly position: LogEntry.Position
readonly commitId: Commit.CommitId
readonly entries: ReadonlyArray<Store.Entry>
}

Source

Since v0.2.0

Logs in this process’s memory that several connections share, as hosts share one system: the committed state, which connection’s transaction is writing, the logs’ locks, and the signal waiting readers take. makeDatabase makes an empty one, and bind connects to it.

Signature

export interface Database {
/** Committed state, replaced as each transaction commits. */
committed: Space
/** The connection whose transaction is writing, if any. */
writer: object | undefined
/** Each log's writer lock, created on first use. */
readonly locks: Map<string, Semaphore.Semaphore>
/** Completed, and replaced, whenever a commit appends. */
appended: Deferred.Deferred<void>
/** Issue an identifier unique within this database, for commits and entries. */
readonly nextId: (prefix: string) => string
}

Source

Since v0.2.0

One open transaction as the store sees it: the commit it records, its snapshot of the logs, which its appends change and a commit makes committed, the logs it holds, what to run once it commits, and whether it is still open. A binding opens one for each transaction.

Signature

export interface Session {
/** The commit it records; the snapshot's record of it counts the entries appended so far. */
readonly commit: Commit.Commit
/** Its snapshot of the logs, which its appends change and a commit makes committed. */
readonly logs: Space
/** Logs it holds against their other writers; the binding releases them when it ends. */
readonly held: Set<string>
/** What `afterCommit` asked to run once it commits, in order. */
readonly afterCommit: Array<Effect.Effect<void>>
/** Cleared when it ends, so a handle kept past it fails. */
active: boolean
}

Source

Since v0.2.0

The logs’ stored state. Stored values never change: writes replace values rather than edit them, so a copy of these maps is an isolated snapshot, which is how a transaction stages its writes.

Signature

export interface Space {
/** Every commit to the logs by ID, with its entry count. */
readonly commits: Map<string, Commit.Commit>
/** Each log's batches in position order. */
readonly batches: Map<string, ReadonlyArray<Batch>>
/** Keyed append slices by log and key, as `keyOf` joins them. */
readonly appends: Map<string, Append>
}

Source

Since v0.2.0

What the store runs over: how it reads committed state and the live transaction, how it opens one, and the locks that order a log’s writers. bind makes one over a Database; a host whose memory storage keeps more than logs provides one over that storage.

Signature

declare class Binding

Source

Since v0.2.0

Committed state once a transaction’s snapshot lands: the logs it held replace the committed ones, other logs keep what their own writers committed meanwhile, and keys and commits combine.

Signature

declare const commit: (current: Space, staged: Space, held: ReadonlySet<string>) => Space

Source

Since v0.2.0

An isolated snapshot: every map is copied, and the values they hold never change.

Signature

declare const copy: (space: Space) => Space

Source

Since v0.2.0