View Source Fuzler

Fuzler is a lightweight, reusable cache built on top of an ETS table and wrapped in a GenServer, with built‑in fuzzy text search powered by a Rust NIF for high-performance similarity scoring.


features

Features

  • Named ETS table: Give any atom as :table.
  • Public reads / protected writes: ETS is :public with concurrency options; only the GenServer process can write.
  • O(1) lookup: Table name → ETS table mapping stored in :persistent_term, avoiding extra GenServer calls.
  • Hot reload: reload/1 clears and repopulates from your loader function.
  • Insert / Get / Stream: Standard cache operations.
  • Fuzzy full-text search: text_search/3 returns top‑N {key, value, score} suggestions using a SIMD‑accelerated Rust NIF.

installation

Installation

Add fuzler to your dependencies in mix.exs:

def deps do
  [
    {:fuzler, git: "https://github.com/elchemista/fuzler.git"}
  ]
end

Then fetch and compile:

mix deps.get
mix compile

Make sure you have Rust installed; the Rustler NIF will compile automatically.


quickstart

Quickstart

1-start-the-cache

1. Start the cache

loader = fn ->
  [
    {"apple",  %{id: 1}},
    {"banana", %{id: 2}},
    {"cantaloupe", %{id: 3}}
  ]
end

{:ok, _pid} =
  Fuzler.start_link(
    table: :fruit_cache,
    loader: loader,
    name: :my_cache
  )

2-basic-operations

2. Basic operations

# Get a value
Fuzler.get("banana")
#⇒ %{id: 2}

# Insert a new item
Fuzler.insert({"durian", %{id: 4}})
Fuzler.get("durian")
#⇒ %{id: 4}

# Reload all data
Fuzler.reload(:my_cache)

# Stream entries matching predicate
Fuzler.stream(fn %{id: id} -> id <= 2 end)
|> Enum.map(&elem(&1, 0))
#⇒ ["apple", "banana"]
# Suggest keys similar to "ap"
Fuzler.text_search("ap", limit: 5)
#⇒ [
#   {"apple", %{id: 1}, 1.0},
#   {"grape", %{id: 7}, 0.75},
#   ...
#]
  • Options:
    • :limit – maximum results (default: 15)
    • :threshold – minimum score (default: 0.10)
    • :keys – pre-collected list of keys to search (avoids re-scanning ETS)

module-api

Module API

@spec start_link(opts :: keyword()) :: GenServer.on_start()
@spec reload(server \ server())        :: :ok
@spec insert({key, value}, server)     :: :ok
@spec get(key, server)                 :: value | nil
@spec stream((value -> boolean), server) :: Enumerable.t()
@spec text_search(String.t(), keyword(), server) :: [{key, value, float()}]

running-tests

Running tests

mix test    # runs Elixir tests

license

License

MIT License