An Elixir implementation of AT Protocol-flavoured Merkle Search Trees (MST)
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: add README and other docs

+135 -11
+16
CHANGELOG.md
··· 1 + # Changelog 2 + 3 + All notable changes to elixir-mst will be documented in this file. 4 + 5 + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 + and this project adheres to 7 + [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 8 + 9 + <!--## [Unreleased] --> 10 + 11 + ## [0.1.0] - 2026-04-09 12 + 13 + Initial release. 14 + 15 + [unreleased]: https://github.com/cometsh/elixir-mst/compare/v0.1.0...HEAD 16 + [0.1.0]: https://github.com/cometsh/elixir-mst/releases/tag/v0.1.0
+18
LICENSE
··· 1 + Copyright 2026 comet.sh 2 + 3 + Permission is hereby granted, free of charge, to any person obtaining a copy of 4 + this software and associated documentation files (the “Software”), to deal in 5 + the Software without restriction, including without limitation the rights to 6 + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 + the Software, and to permit persons to whom the Software is furnished to do so, 8 + subject to the following conditions: 9 + 10 + The above copyright notice and this permission notice shall be included in all 11 + copies or substantial portions of the Software. 12 + 13 + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+71 -9
README.md
··· 1 - # MST 1 + # elixir-mst 2 2 3 - **TODO: Add description** 3 + An Elixir implementation of [AT Protocol-flavoured Merkle Search Trees (MST)][spec]. 4 + 5 + ## Overview 6 + 7 + A Merkle Search Tree is a content-addressed, ordered key/value structure. 8 + Every unique set of key/value pairs produces a unique root CID, making it 9 + suitable for Merkle proofs, efficient diffs, and repository synchronisation. 10 + This library implements the AT Protocol flavour: nodes are encoded as 11 + DRISL, keys are arbitrary byte strings, values are `DASL.CID` links, and 12 + tree depth is derived from the SHA-256 hash of each key. 13 + 14 + [spec]: https://atproto.com/specs/repository#mst-structure 4 15 5 16 ## Installation 6 17 7 - If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 - by adding `mst` to your list of dependencies in `mix.exs`: 18 + Get elixir-mst from [hex.pm](https://hex.pm) by adding it to your `mix.exs`: 9 19 10 20 ```elixir 21 + # mix.exs 11 22 def deps do 12 23 [ 13 - {:mst, "~> 0.1.0"} 24 + {:mst, "~> 0.1"} 14 25 ] 15 26 end 16 27 ``` 17 28 18 - Documentation can be generated with 19 - [ExDoc](https://github.com/elixir-lang/ex_doc) and published on 20 - [HexDocs](https://hexdocs.pm). Once published, the docs can be found at 21 - <https://hexdocs.pm/mst>. 29 + Documentation can be found on HexDocs at https://hexdocs.pm/mst. 30 + 31 + ## Quick start 32 + 33 + ```elixir 34 + # Build a tree 35 + tree = MST.new() 36 + 37 + val = DASL.CID.compute("my record data") 38 + {:ok, tree} = MST.put(tree, "app.bsky.feed.post/3jqfcqzm3ft2j", val) 39 + {:ok, ^val} = MST.get(tree, "app.bsky.feed.post/3jqfcqzm3ft2j") 40 + 41 + # Mutate (persistent — the original tree is unchanged) 42 + {:ok, tree2} = MST.put(tree, "app.bsky.feed.post/3jqfcqzm3fz2j", val) 43 + {:ok, tree2} = MST.delete(tree2, "app.bsky.feed.post/3jqfcqzm3ft2j") 44 + 45 + # Enumerate in sorted order 46 + {:ok, pairs} = MST.to_list(tree2) 47 + # => [{"app.bsky.feed.post/3jqfcqzm3fz2j", val}] 48 + 49 + # Or stream lazily 50 + tree2 |> MST.stream() |> Enum.each(fn {key, cid} -> ... end) 51 + ``` 52 + 53 + ## CAR import / export 54 + 55 + ```elixir 56 + # Export to a CARv1 binary 57 + {:ok, car_bytes} = MST.to_car(tree) 58 + File.write!("repo.car", car_bytes) 59 + 60 + # Import from a CARv1 binary 61 + {:ok, tree} = MST.from_car(File.read!("repo.car")) 62 + ``` 63 + 64 + ## Diffing two trees 65 + 66 + ```elixir 67 + {:ok, diff} = MST.diff(tree_a, tree_b) 68 + 69 + diff.created_nodes # MapSet of node CIDs added in tree_b 70 + diff.deleted_nodes # MapSet of node CIDs removed from tree_a 71 + 72 + for %MST.Diff.Op{key: key, old_value: old, new_value: new} <- diff.record_ops do 73 + case {old, new} do 74 + {nil, cid} -> IO.puts("create #{key} → #{cid}") 75 + {cid, nil} -> IO.puts("delete #{key} (was #{cid})") 76 + {old, new} -> IO.puts("update #{key}: #{old} → #{new}") 77 + end 78 + end 79 + ``` 80 + 81 + --- 82 + 83 + This project is licensed under the [MIT License](./LICENSE)
+30 -2
mix.exs
··· 1 1 defmodule MST.MixProject do 2 2 use Mix.Project 3 3 4 + @version "0.1.0" 5 + @github "https://github.com/cometsh/elixir-mst" 6 + @tangled "https://tangled.org/@comet.sh/elixir-mst" 7 + 4 8 def project do 5 9 [ 6 10 app: :mst, 7 - version: "0.1.0", 11 + version: @version, 8 12 elixir: "~> 1.18", 9 13 start_permanent: Mix.env() == :prod, 10 - deps: deps() 14 + deps: deps(), 15 + name: "mst", 16 + description: "An Elixir implementation of AT Protocol-flavoured Merkle Search Trees (MST)", 17 + package: package(), 18 + docs: docs() 11 19 ] 12 20 end 13 21 ··· 25 33 {:typedstruct, "~> 0.5"}, 26 34 {:ex_doc, "~> 0.34", only: :dev, runtime: false}, 27 35 {:credo, "~> 1.7", only: [:dev, :test], runtime: false} 36 + ] 37 + end 38 + 39 + defp package do 40 + [ 41 + licenses: ["MIT"], 42 + links: %{"GitHub" => @github, "Tangled" => @tangled} 43 + ] 44 + end 45 + 46 + defp docs do 47 + [ 48 + extras: [ 49 + "README.md": [title: "Overview"], 50 + "CHANGELOG.md": [title: "Changelog"] 51 + ], 52 + main: "readme", 53 + source_url: @github, 54 + source_ref: "v#{@version}", 55 + formatters: ["html"] 28 56 ] 29 57 end 30 58 end