Vault

Memory Store

In-memory store for development and testing.

The memory store is a fully in-memory implementation of store.Store. It keeps all data in Go maps protected by a sync.RWMutex, making it safe for concurrent access. No external dependencies are required.

Installation

The memory store is included in the Vault module. No additional packages are needed.

import "github.com/xraph/vault/store/memory"

Creating a store

store := memory.New()

memory.New() returns a *memory.Store with all internal maps initialized and ready to use.

Implemented interfaces

The memory store satisfies all six subsystem store interfaces plus the three lifecycle methods that form the composite store.Store interface:

InterfacePackageMethods
secret.Storegithub.com/xraph/vault/secretGetSecret, SetSecret, DeleteSecret, ListSecrets, GetSecretVersion, ListSecretVersions
flag.Storegithub.com/xraph/vault/flagDefineFlag, GetFlagDefinition, ListFlagDefinitions, DeleteFlagDefinition, SetFlagRules, GetFlagRules, SetFlagTenantOverride, GetFlagTenantOverride, DeleteFlagTenantOverride, ListFlagTenantOverrides
config.Storegithub.com/xraph/vault/configGetConfig, SetConfig, DeleteConfig, ListConfig, GetConfigVersion, ListConfigVersions
override.Storegithub.com/xraph/vault/overrideGetOverride, SetOverride, DeleteOverride, ListOverridesByTenant, ListOverridesByKey
rotation.Storegithub.com/xraph/vault/rotationSaveRotationPolicy, GetRotationPolicy, ListRotationPolicies, DeleteRotationPolicy, RecordRotation, ListRotationRecords
audit.Storegithub.com/xraph/vault/auditRecordAudit, ListAudit, ListAuditByKey

Compile-time checks guarantee that the store satisfies every interface:

var (
    _ secret.Store   = (*Store)(nil)
    _ flag.Store     = (*Store)(nil)
    _ config.Store   = (*Store)(nil)
    _ override.Store = (*Store)(nil)
    _ rotation.Store = (*Store)(nil)
    _ audit.Store    = (*Store)(nil)
)

Concurrency safety

All methods acquire either a read lock (RLock) or a write lock (Lock) on the internal sync.RWMutex. Data returned from reads is deep-copied so that callers cannot mutate internal state. Byte slices (secret values) and any values (config, flag defaults) are copied via copyBytes and JSON round-trip respectively.

Lifecycle methods

MethodBehaviour
Migrate(ctx)No-op -- returns nil
Ping(ctx)Always returns nil
Close()No-op -- returns nil

The memory store has no schema to create and no connection to check, so all lifecycle methods succeed immediately.

Auto-versioning

Both SetSecret and SetConfig perform automatic versioning. When an entry already exists, the version number is incremented and a version record is appended to the version history. When creating a new entry, the version starts at 1.

Internal key scheme

Entities are keyed using composite strings:

EntityKey format
Secrets, Flags, Config, Rotationkey:appID
Overrides, Flag Tenant Overrideskey:appID:tenantID

This ensures that entries with the same key but different app IDs or tenant IDs remain isolated.

When to use

  • Unit tests -- No setup, no teardown, zero dependencies.
  • Development -- Quick iteration without running a database.
  • CI pipelines -- Fast, deterministic tests with no external services.
  • Prototyping -- Explore Vault's API surface without configuring infrastructure.

When not to use

  • Production -- Data is lost on process restart.
  • Multi-instance deployments -- Each process has its own isolated store.
  • Performance benchmarking -- Behaviour characteristics differ from a real database.

Complete example

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/xraph/vault/config"
    "github.com/xraph/vault/crypto"
    "github.com/xraph/vault/secret"
    "github.com/xraph/vault/store/memory"
)

func main() {
    ctx := context.Background()

    // Create the in-memory store.
    store := memory.New()

    // Migrate is a no-op but good practice to call.
    if err := store.Migrate(ctx); err != nil {
        log.Fatal(err)
    }

    // Create an encryptor with a 32-byte key.
    key := make([]byte, 32)
    copy(key, "my-secret-encryption-key-32byte")
    enc, err := crypto.NewEncryptor(key)
    if err != nil {
        log.Fatal(err)
    }

    // Use the store with a secret service.
    secretSvc := secret.NewService(store, enc, secret.WithAppID("myapp"))
    meta, err := secretSvc.Set(ctx, "database_url", []byte("postgres://localhost/mydb"), "")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Stored secret: key=%s version=%d\n", meta.Key, meta.Version)

    // Retrieve and decrypt.
    sec, err := secretSvc.Get(ctx, "database_url", "")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Retrieved: %s\n", string(sec.Value))

    // Use the store with a config service.
    cfgSvc := config.NewService(store, config.WithAppID("myapp"))
    if err := cfgSvc.Set(ctx, "rate_limit", 100, ""); err != nil {
        log.Fatal(err)
    }

    limit := cfgSvc.Int(ctx, "rate_limit", 50)
    fmt.Printf("Rate limit: %d\n", limit)
}

Swapping to PostgreSQL

The memory store implements the same store.Store interface as the PostgreSQL and Bun stores. Switching to a production backend is a single-line change:

// Development
store := memory.New()

// Production
store, err := postgres.New(ctx, connString)

On this page